From 3701b8631a5c3e9c7992415f9e3fe1a3af77bbce Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Sat, 24 Jan 2026 17:54:51 -0800 Subject: rebuild framebox with impermanence --- README.org | 2 +- flake.nix | 4 +- machines/framebox.nix | 94 +++++++++++++++++++++++++++++++++++++++++ machines/rivendell.nix | 94 ----------------------------------------- profiles/authelia.nix | 2 +- profiles/monitoring.nix | 2 +- profiles/wireguard.nix | 7 +-- secrets/framebox/wireguard.age | 11 +++++ secrets/rivendell/wireguard.age | 11 ----- tools/deploy-nixos.py | 87 ++++++++++++++++++++++++++++++++++++++ tools/provision-nixos.py | 87 -------------------------------------- 11 files changed, 201 insertions(+), 200 deletions(-) create mode 100644 machines/framebox.nix delete mode 100644 machines/rivendell.nix create mode 100644 secrets/framebox/wireguard.age delete mode 100644 secrets/rivendell/wireguard.age create mode 100755 tools/deploy-nixos.py delete mode 100644 tools/provision-nixos.py diff --git a/README.org b/README.org index df66363..1b088a4 100644 --- a/README.org +++ b/README.org @@ -99,7 +99,7 @@ agenix -i ~/.ssh/agenix -e /wireguard.age Then add the following to the host's configuration: #+begin_src nix -age.secrets.wireguard.file = ../../../../secrets/rivendell/wireguard.age; +age.secrets.wireguard.file = ../../../../secrets/framebox/wireguard.age; networking.wireguard = { enable = true; diff --git a/flake.nix b/flake.nix index 7e95916..2e99dfe 100644 --- a/flake.nix +++ b/flake.nix @@ -144,9 +144,9 @@ machines = { nixos = { - rivendell = { + framebox = { system = "x86_64-linux"; - config = ./machines/rivendell.nix; + config = ./machines/framebox.nix; }; bree = { system = "x86_64-linux"; diff --git a/machines/framebox.nix b/machines/framebox.nix new file mode 100644 index 0000000..15a82bd --- /dev/null +++ b/machines/framebox.nix @@ -0,0 +1,94 @@ +{ + adminUser, + lib, + config, + pkgs, + ... +}: +{ + wgPublicKey = "jf7T7TMKQWSgSXhUplldZDV9G2y2BjMmHIAhg5d26ng="; + publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID76U5kt8DfBbuP16rMzfBTVTpjjPFKWnnheMALaCQEd"; + ephemeralRoot = true; + + age.secrets = { + wireguard.file = ../secrets/framebox/wireguard.age; + restic-local-pw.file = ../secrets/restic-pw.age; + restic-nas-smb-config.file = ../secrets/restic-nas-smb-config.age; + grafana-oidc.file = ../secrets/grafana-oidc.age; + miniflux-oidc.file = ../secrets/miniflux-oidc.age; + rsync-ssh-key.file = ../secrets/rsync-ssh-nas.age; + authelia-storage-key = { + file = ../secrets/authelia-storage-key.age; + owner = "authelia-main"; + }; + authelia-jwt-key = { + file = ../secrets/authelia-jwt-key.age; + owner = "authelia-main"; + }; + authelia-users = { + file = ../secrets/authelia-users.yaml.age; + owner = "authelia-main"; + }; + authelia-jwks = { + file = ../secrets/authelia-jwks.age; + owner = "authelia-main"; + }; + }; + + imports = [ + ../profiles/authelia.nix + ../profiles/core-metrics.nix + ../profiles/defaults.nix + ../profiles/disk/btrfs-on-luks.nix + ../profiles/git-server.nix + ../profiles/hardware/framework-desktop.nix + ../profiles/home-manager.nix + ../profiles/miniflux.nix + ../profiles/monitoring.nix + ../profiles/postgresql.nix + ../profiles/remote-unlock.nix + ../profiles/restic-backup.nix + ../profiles/server.nix + ../profiles/state.nix + ../profiles/users/admin-user.nix + ../profiles/users/builder.nix + ../profiles/users/home-manager.nix + ../profiles/wireguard.nix + ]; + + boot.kernelModules = [ "sg" ]; + + networking.hostName = "framebox"; + networking.useDHCP = lib.mkDefault true; + systemd.network.wait-online.anyInterface = lib.mkDefault config.networking.useDHCP; + + services = { + website = { + enable = true; + openFirewall = true; + }; + restic.backups.local.paths = [ "/persist/save" ]; + restic.backups.synology.paths = [ + "/data/archives" + "/data/media/music" + "/persist/save" + ]; + }; + + users.users.${adminUser.name}.extraGroups = [ "cdrom" ]; + + system.stateVersion = "23.11"; + + home-manager.users.${adminUser.name} = { + home.homeDirectory = "/home/${adminUser.name}"; + imports = [ + ../home/profiles/minimal.nix + ]; + home.packages = with pkgs; [ + ffmpeg + imagemagick + makemkv + mkvtoolnix-cli + ]; + }; +} diff --git a/machines/rivendell.nix b/machines/rivendell.nix deleted file mode 100644 index 90d501d..0000000 --- a/machines/rivendell.nix +++ /dev/null @@ -1,94 +0,0 @@ -{ - adminUser, - lib, - config, - pkgs, - ... -}: -{ - wgPublicKey = "jf7T7TMKQWSgSXhUplldZDV9G2y2BjMmHIAhg5d26ng="; - publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID76U5kt8DfBbuP16rMzfBTVTpjjPFKWnnheMALaCQEd"; - ephemeralRoot = true; - - age.secrets = { - wireguard.file = ../secrets/rivendell/wireguard.age; - restic-local-pw.file = ../secrets/restic-pw.age; - restic-nas-smb-config.file = ../secrets/restic-nas-smb-config.age; - grafana-oidc.file = ../secrets/grafana-oidc.age; - miniflux-oidc.file = ../secrets/miniflux-oidc.age; - rsync-ssh-key.file = ../secrets/rsync-ssh-nas.age; - authelia-storage-key = { - file = ../secrets/authelia-storage-key.age; - owner = "authelia-main"; - }; - authelia-jwt-key = { - file = ../secrets/authelia-jwt-key.age; - owner = "authelia-main"; - }; - authelia-users = { - file = ../secrets/authelia-users.yaml.age; - owner = "authelia-main"; - }; - authelia-jwks = { - file = ../secrets/authelia-jwks.age; - owner = "authelia-main"; - }; - }; - - imports = [ - ../profiles/authelia.nix - ../profiles/core-metrics.nix - ../profiles/defaults.nix - ../profiles/disk/btrfs-on-luks.nix - ../profiles/git-server.nix - ../profiles/hardware/framework-desktop.nix - ../profiles/home-manager.nix - ../profiles/miniflux.nix - ../profiles/monitoring.nix - ../profiles/postgresql.nix - ../profiles/remote-unlock.nix - ../profiles/restic-backup.nix - ../profiles/server.nix - ../profiles/state.nix - ../profiles/users/admin-user.nix - ../profiles/users/builder.nix - ../profiles/users/home-manager.nix - ../profiles/wireguard.nix - ]; - - boot.kernelModules = [ "sg" ]; - - networking.hostName = "rivendell"; - networking.useDHCP = lib.mkDefault true; - systemd.network.wait-online.anyInterface = lib.mkDefault config.networking.useDHCP; - - services = { - website = { - enable = true; - openFirewall = true; - }; - restic.backups.local.paths = [ "/persist/save" ]; - restic.backups.synology.paths = [ - "/data/archives" - "/data/media/music" - "/persist/save" - ]; - }; - - users.users.${adminUser.name}.extraGroups = [ "cdrom" ]; - - system.stateVersion = "23.11"; - - home-manager.users.${adminUser.name} = { - home.homeDirectory = "/home/${adminUser.name}"; - imports = [ - ../home/profiles/minimal.nix - ]; - home.packages = with pkgs; [ - ffmpeg - imagemagick - makemkv - mkvtoolnix-cli - ]; - }; -} diff --git a/profiles/authelia.nix b/profiles/authelia.nix index d07651d..c83af67 100644 --- a/profiles/authelia.nix +++ b/profiles/authelia.nix @@ -23,7 +23,7 @@ }; environment.persistence."/persist/save".directories = [ - config.services.authelia.instances.main.settings.storage.local.path + "/var/lib/authelia-main" ]; networking.firewall.allowedTCPPorts = [ 9092 ]; diff --git a/profiles/monitoring.nix b/profiles/monitoring.nix index a203078..ad0629e 100644 --- a/profiles/monitoring.nix +++ b/profiles/monitoring.nix @@ -75,7 +75,7 @@ ]; environment.persistence."/persist".directories = [ - config.services.victoriametrics.stateDir + "/var/lib/private/victoriametrics" config.services.grafana.dataDir ]; diff --git a/profiles/wireguard.nix b/profiles/wireguard.nix index 5620699..9abb7ea 100644 --- a/profiles/wireguard.nix +++ b/profiles/wireguard.nix @@ -17,14 +17,15 @@ let publicKey = hostConfigurations.argonath.wgPublicKey; endpoint = "157.230.146.234"; }; - rivendell = { + framebox = { ip = 60; - publicKey = hostConfigurations.rivendell.wgPublicKey; + publicKey = hostConfigurations.framebox.wgPublicKey; endpoint = "192.168.1.114"; }; test = { ip = 41; - publicKey = hostConfigurations.rivendell.wgPublicKey; + publicKey = hostConfigurations.framebox.wgPublicKey; + endpoint = "192.168.1.33"; }; }; diff --git a/secrets/framebox/wireguard.age b/secrets/framebox/wireguard.age new file mode 100644 index 0000000..9c967a5 --- /dev/null +++ b/secrets/framebox/wireguard.age @@ -0,0 +1,11 @@ +age-encryption.org/v1 +-> piv-p256 p72k4g AkjAdyuaUbt/YlcOZmguIXlc2Rsh7JdkQUt6UI9hS+S8 +5Gg1XNO9vuelTW5FV/YnBttvWadD3VPHFv2LHKuY7xM +-> piv-p256 3VHSDQ AhEFbuqTOmEU+JOk+zOu7cbAjPO7AouMpZW6aa6E/ZNc +HM6g7zA/ayZKodXRL1g8qV8AlGD+E4mvaEJ3bXnxWKw +-> piv-p256 EwG8WQ A3rSmT6cUMN/IAil9DMm8oVJp+Ibvy+avGzhap4y8fFX +2nfr7mZXTakgHbKTTSlWCdzBQ/4ztG+I5Za37c4qXm0 +-> ssh-ed25519 Y5h84Q 0NQxJc1wCM2oTAqAKerFX6z9sX95dPeDEedAhnJsTUI +55XP3UyeiQsWJOpreMh5uNmr8fxoLAc1hlZnZyUQ2zw +--- g+cGlQYSZ3ZTO4HQaSVyt6BN1HlIt6oqcTTEYgpuxlw +&{?8@yԷ-b9)~І Jpث*􊲝rssWeOR[r0) \ No newline at end of file diff --git a/secrets/rivendell/wireguard.age b/secrets/rivendell/wireguard.age deleted file mode 100644 index 9c967a5..0000000 --- a/secrets/rivendell/wireguard.age +++ /dev/null @@ -1,11 +0,0 @@ -age-encryption.org/v1 --> piv-p256 p72k4g AkjAdyuaUbt/YlcOZmguIXlc2Rsh7JdkQUt6UI9hS+S8 -5Gg1XNO9vuelTW5FV/YnBttvWadD3VPHFv2LHKuY7xM --> piv-p256 3VHSDQ AhEFbuqTOmEU+JOk+zOu7cbAjPO7AouMpZW6aa6E/ZNc -HM6g7zA/ayZKodXRL1g8qV8AlGD+E4mvaEJ3bXnxWKw --> piv-p256 EwG8WQ A3rSmT6cUMN/IAil9DMm8oVJp+Ibvy+avGzhap4y8fFX -2nfr7mZXTakgHbKTTSlWCdzBQ/4ztG+I5Za37c4qXm0 --> ssh-ed25519 Y5h84Q 0NQxJc1wCM2oTAqAKerFX6z9sX95dPeDEedAhnJsTUI -55XP3UyeiQsWJOpreMh5uNmr8fxoLAc1hlZnZyUQ2zw ---- g+cGlQYSZ3ZTO4HQaSVyt6BN1HlIt6oqcTTEYgpuxlw -&{?8@yԷ-b9)~І Jpث*􊲝rssWeOR[r0) \ No newline at end of file diff --git a/tools/deploy-nixos.py b/tools/deploy-nixos.py new file mode 100755 index 0000000..9946f03 --- /dev/null +++ b/tools/deploy-nixos.py @@ -0,0 +1,87 @@ +#!/usr/bin/env -S uv run +# /// script +# dependencies = [] +# /// + +import argparse +import subprocess +import tempfile +import shutil +from pathlib import Path + +def run_cmd(cmd, capture=False): + """Run a command and optionally capture output.""" + if capture: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True) + return result.stdout.strip() + else: + subprocess.run(cmd, shell=True, check=True) + + +def derive_public_key(private_key_path): + """Derive public key from private key.""" + return run_cmd(f"ssh-keygen -y -f {private_key_path}", capture=True) + + +def main(): + parser = argparse.ArgumentParser(description='Deploy NixOS using nixos-anywhere') + parser.add_argument('--flake', required=True, help='Name of the flake configuration') + parser.add_argument('--target-ip', required=True, help='IP address of target VM') + args = parser.parse_args() + + flake_name = args.flake + target_ip = args.target_ip + + temp_dir = Path(tempfile.mkdtemp()) + + try: + initrd_ssh_dir = temp_dir / "persist" / "secrets" + initrd_ssh_dir.mkdir(parents=True) + + host_ssh_dir = temp_dir / "persist" / "etc" / "ssh" + host_ssh_dir.mkdir(parents=True) + + ssh_key = run_cmd(f"passage show hosts/{flake_name}/ssh/ssh_host_ed25519_key", capture=True) + + initrd_ssh_key_path = initrd_ssh_dir / "ssh_host_ed25519_key" + initrd_ssh_key_path.write_text(ssh_key + '\n') + initrd_ssh_key_path.chmod(0o600) + + host_ssh_key_path = host_ssh_dir / "ssh_host_ed25519_key" + host_ssh_key_path.write_text(ssh_key + '\n') + host_ssh_key_path.chmod(0o600) + + public_key = derive_public_key(host_ssh_key_path) + + initrd_ssh_pub_path = initrd_ssh_dir / "ssh_host_ed25519_key.pub" + initrd_ssh_pub_path.write_text(public_key + '\n') + initrd_ssh_pub_path.chmod(0o644) + + host_ssh_pub_path = host_ssh_dir / "ssh_host_ed25519_key.pub" + host_ssh_pub_path.write_text(public_key + '\n') + host_ssh_pub_path.chmod(0o644) + + disk_key = run_cmd(f"passage show hosts/{flake_name}/disk-encryption/passphrase", capture=True) + disk_key_file = Path("/tmp/disk.key") + disk_key_file.write_text(disk_key) + disk_key_file.chmod(0o600) + + cmd = [ + "nix", "run", "github:nix-community/nixos-anywhere", "--", + "--flake", f".#{flake_name}", + "--build-on", "remote", + "--disk-encryption-keys", "/tmp/disk.key", str(disk_key_file), + "--target-host", f"root@{target_ip}", + "--extra-files", str(temp_dir) + ] + + subprocess.run(cmd, check=True) + + finally: + # Clean up + shutil.rmtree(temp_dir) + if Path("/tmp/disk.key").exists(): + Path("/tmp/disk.key").unlink() + +if __name__ == "__main__": + main() diff --git a/tools/provision-nixos.py b/tools/provision-nixos.py deleted file mode 100644 index 9946f03..0000000 --- a/tools/provision-nixos.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env -S uv run -# /// script -# dependencies = [] -# /// - -import argparse -import subprocess -import tempfile -import shutil -from pathlib import Path - -def run_cmd(cmd, capture=False): - """Run a command and optionally capture output.""" - if capture: - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True) - return result.stdout.strip() - else: - subprocess.run(cmd, shell=True, check=True) - - -def derive_public_key(private_key_path): - """Derive public key from private key.""" - return run_cmd(f"ssh-keygen -y -f {private_key_path}", capture=True) - - -def main(): - parser = argparse.ArgumentParser(description='Deploy NixOS using nixos-anywhere') - parser.add_argument('--flake', required=True, help='Name of the flake configuration') - parser.add_argument('--target-ip', required=True, help='IP address of target VM') - args = parser.parse_args() - - flake_name = args.flake - target_ip = args.target_ip - - temp_dir = Path(tempfile.mkdtemp()) - - try: - initrd_ssh_dir = temp_dir / "persist" / "secrets" - initrd_ssh_dir.mkdir(parents=True) - - host_ssh_dir = temp_dir / "persist" / "etc" / "ssh" - host_ssh_dir.mkdir(parents=True) - - ssh_key = run_cmd(f"passage show hosts/{flake_name}/ssh/ssh_host_ed25519_key", capture=True) - - initrd_ssh_key_path = initrd_ssh_dir / "ssh_host_ed25519_key" - initrd_ssh_key_path.write_text(ssh_key + '\n') - initrd_ssh_key_path.chmod(0o600) - - host_ssh_key_path = host_ssh_dir / "ssh_host_ed25519_key" - host_ssh_key_path.write_text(ssh_key + '\n') - host_ssh_key_path.chmod(0o600) - - public_key = derive_public_key(host_ssh_key_path) - - initrd_ssh_pub_path = initrd_ssh_dir / "ssh_host_ed25519_key.pub" - initrd_ssh_pub_path.write_text(public_key + '\n') - initrd_ssh_pub_path.chmod(0o644) - - host_ssh_pub_path = host_ssh_dir / "ssh_host_ed25519_key.pub" - host_ssh_pub_path.write_text(public_key + '\n') - host_ssh_pub_path.chmod(0o644) - - disk_key = run_cmd(f"passage show hosts/{flake_name}/disk-encryption/passphrase", capture=True) - disk_key_file = Path("/tmp/disk.key") - disk_key_file.write_text(disk_key) - disk_key_file.chmod(0o600) - - cmd = [ - "nix", "run", "github:nix-community/nixos-anywhere", "--", - "--flake", f".#{flake_name}", - "--build-on", "remote", - "--disk-encryption-keys", "/tmp/disk.key", str(disk_key_file), - "--target-host", f"root@{target_ip}", - "--extra-files", str(temp_dir) - ] - - subprocess.run(cmd, check=True) - - finally: - # Clean up - shutil.rmtree(temp_dir) - if Path("/tmp/disk.key").exists(): - Path("/tmp/disk.key").unlink() - -if __name__ == "__main__": - main() -- cgit v1.2.3