{ description = "Basic MediaWiki primary + replica deployment"; nixConfig = { max-jobs = "auto"; cores = 0; }; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; agenix = { url = "github:ryantm/agenix"; inputs.nixpkgs.follows = "nixpkgs"; }; deploy-rs = { url = "github:serokell/deploy-rs"; inputs.nixpkgs.follows = "nixpkgs"; }; disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; nixos-anywhere = { url = "github:nix-community/nixos-anywhere"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, agenix, deploy-rs, disko, nixos-anywhere, ... }: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; lib = nixpkgs.lib; siteConfig = rec { wikiName = "Noisebridge"; baseDomain = "noisebridge.net"; replicaSubdomain = "replica"; sshUser = "root"; primaryHostName = "main-wiki"; replicaHostName = "replica-wiki"; database = { name = "noisebridge_mediawiki"; mediawikiUser = "wiki"; replicationUser = "repl"; }; hosts = { primary = { nixosName = primaryHostName; tailscaleName = primaryHostName; }; replica = { nixosName = replicaHostName; tailscaleName = replicaHostName; }; }; }; mkPublicDomain = role: if role == "primary" then siteConfig.baseDomain else "${siteConfig.replicaSubdomain}.${siteConfig.baseDomain}"; mkHostMeta = role: siteConfig.hosts.${role} // { inherit role; publicDomain = mkPublicDomain role; }; mkHost = { hostMeta, hostModule, roleModules, }: lib.nixosSystem { inherit system; specialArgs = { inherit agenix siteConfig hostMeta; }; modules = [ agenix.nixosModules.default disko.nixosModules.disko hostModule ./disk-config.nix ./modules/common.nix ./modules/security.nix ./modules/tailscale.nix ./modules/caddy.nix ./modules/mediawiki-base.nix ] ++ roleModules; }; primaryMeta = mkHostMeta "primary"; replicaMeta = mkHostMeta "replica"; in { nixosConfigurations = { main-wiki = mkHost { hostMeta = primaryMeta; hostModule = ./hosts/main-wiki; roleModules = [ ./modules/wiki-primary/mysql.nix ./modules/wiki-primary/mediawiki.nix ]; }; replica-wiki = mkHost { hostMeta = replicaMeta; hostModule = ./hosts/replica-wiki; roleModules = [ ./modules/wiki-replica/mysql.nix ./modules/wiki-replica/mediawiki.nix ]; }; }; deploy.nodes = { main-wiki = { hostname = primaryMeta.tailscaleName; profiles.system = { user = siteConfig.sshUser; sshUser = siteConfig.sshUser; path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.main-wiki; }; }; replica-wiki = { hostname = replicaMeta.tailscaleName; profiles.system = { user = siteConfig.sshUser; sshUser = siteConfig.sshUser; path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.replica-wiki; }; }; }; checks = builtins.mapAttrs (_: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib; apps.${system} = { deploy = { type = "app"; program = "${pkgs.writeShellScript "deploy-noisebridge" '' if [ "$#" -eq 0 ] || [ "''${1#-}" != "$1" ]; then exec ${deploy-rs.packages.${system}.default}/bin/deploy \ --auto-rollback true \ --magic-rollback true \ path:.# \ "$@" fi exec ${deploy-rs.packages.${system}.default}/bin/deploy \ --auto-rollback true \ --magic-rollback true \ "$@" ''}"; meta.description = "Deploy all Noisebridge wiki hosts by default"; }; bootstrap-host = { type = "app"; program = "${pkgs.writeShellScript "bootstrap-host" '' set -euo pipefail usage() { cat <<'EOF' Usage: nix run .#bootstrap-host -- [ssh-identity-file] nix run .#bootstrap-host -- [ssh-identity-file] Examples: nix run .#bootstrap-host -- main-wiki root@203.0.113.10 ~/.ssh/do-bootstrap nix run .#bootstrap-host -- root@203.0.113.10 root@203.0.113.11 ~/.ssh/do-bootstrap EOF } if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then usage exit 1 fi repo_root="$(pwd)" if [ ! -f "$repo_root/flake.nix" ]; then printf 'Run bootstrap-host from the repo root\n' >&2 exit 1 fi ssh_identity_file="" main_target="" replica_target="" selected_host="" case "$1" in main-wiki|replica-wiki) selected_host="$1" if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then usage exit 1 fi if [ "$selected_host" = "main-wiki" ]; then main_target="$2" else replica_target="$2" fi ssh_identity_file="''${3:-}" ;; *) main_target="$1" replica_target="$2" ssh_identity_file="''${3:-}" ;; esac install_root="$(mktemp -d)" cleanup() { rm -rf "$install_root" } trap cleanup EXIT ensure_host_key() { local host_name="$1" local private_key_file="$repo_root/.bootstrap/$host_name/host.age" local public_key_file="$repo_root/secrets/hosts/$host_name.age.pub" local public_key mkdir -p "$(dirname "$private_key_file")" "$(dirname "$public_key_file")" if [ ! -s "$private_key_file" ]; then public_key="$(${pkgs.age}/bin/age-keygen -o "$private_key_file" | sed 's/^Public key: //')" chmod 600 "$private_key_file" printf 'Generated host age key for %s\n' "$host_name" else public_key="$(${pkgs.age}/bin/age-keygen -y "$private_key_file")" printf 'Reused existing host age key for %s\n' "$host_name" fi printf '%s\n' "$public_key" > "$public_key_file" } run_bootstrap() { local host_name="$1" local target_host="$2" local private_key_file="$repo_root/.bootstrap/$host_name/host.age" local -a nixos_anywhere_args mkdir -p "$install_root/var/lib/agenix" ${pkgs.coreutils}/bin/install -m 0400 "$private_key_file" "$install_root/var/lib/agenix/host.age" nixos_anywhere_args=( --extra-files "$install_root" --flake "path:$repo_root#$host_name" ) if [ -n "$ssh_identity_file" ]; then nixos_anywhere_args+=( -i "$ssh_identity_file" ) fi printf 'Bootstrapping %s onto %s\n' "$host_name" "$target_host" ${ nixos-anywhere.packages.${system}.default }/bin/nixos-anywhere "''${nixos_anywhere_args[@]}" "$target_host" rm -f "$install_root/var/lib/agenix/host.age" } if [ -n "$main_target" ]; then ensure_host_key main-wiki fi if [ -n "$replica_target" ]; then ensure_host_key replica-wiki fi printf 'Rekeying agenix secrets\n' ${agenix.packages.${system}.default}/bin/agenix -r if [ -n "$main_target" ]; then run_bootstrap main-wiki "$main_target" fi if [ -n "$replica_target" ]; then run_bootstrap replica-wiki "$replica_target" fi printf '\nBootstrap complete. The hosts should now join Tailscale using their configured hostnames.\n' ''}"; meta.description = "Install NixOS on one or both raw hosts and seed agenix identities"; }; }; devShells.${system}.default = pkgs.mkShell { packages = with pkgs; [ agenix.packages.${system}.default deploy-rs.packages.${system}.default nixos-anywhere.packages.${system}.default mariadb.client rsync curl jq age openssl ]; }; }; }