aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranck Cuny <franck@fcuny.net>2026-01-24 10:55:16 -0800
committerFranck Cuny <franck@fcuny.net>2026-01-24 10:55:16 -0800
commit737b74c58de0712973f81c91aa07748c02deef70 (patch)
tree671639fb8ae43ff9482d36331cf6f706bfb01d1a
parentupdate documentation for creating an ISO (diff)
downloadinfra-737b74c58de0712973f81c91aa07748c02deef70.tar.gz
adding a new VM for testing
Re-key all the secrets.
-rw-r--r--README.org33
-rw-r--r--flake.lock85
-rw-r--r--flake.nix10
-rw-r--r--justfile16
-rw-r--r--machines/test.nix47
-rw-r--r--modules/host-config.nix4
-rw-r--r--profiles/defaults.nix15
-rw-r--r--profiles/disk/btrfs-on-luks.nix102
-rw-r--r--profiles/hardware/synology-vm.nix4
-rw-r--r--profiles/remote-unlock.nix2
-rw-r--r--profiles/state.nix59
-rw-r--r--profiles/users/admin-user.nix2
-rw-r--r--profiles/wireguard.nix4
-rw-r--r--secrets/acme-cloudflare-env.agebin704 -> 594 bytes
-rw-r--r--secrets/anthropic-api-key.agebin865 -> 755 bytes
-rw-r--r--secrets/argonath/wireguard.agebin691 -> 581 bytes
-rw-r--r--secrets/authelia-jwks.agebin2350 -> 2240 bytes
-rw-r--r--secrets/authelia-jwt-key.age22
-rw-r--r--secrets/authelia-storage-key.agebin733 -> 623 bytes
-rw-r--r--secrets/authelia-users.yaml.agebin905 -> 795 bytes
-rw-r--r--secrets/bree/disk-passphrase.agebin667 -> 557 bytes
-rw-r--r--secrets/bree/disk-unlock-key.agebin1045 -> 935 bytes
-rw-r--r--secrets/bree/wireguard.agebin691 -> 581 bytes
-rw-r--r--secrets/grafana-oidc.age22
-rw-r--r--secrets/miniflux-oidc.agebin719 -> 609 bytes
-rw-r--r--secrets/restic-nas-smb-config.agebin755 -> 645 bytes
-rw-r--r--secrets/restic-pw.age24
-rw-r--r--secrets/rivendell/wireguard.age22
-rw-r--r--secrets/rsync-ssh-nas.agebin1045 -> 935 bytes
-rw-r--r--secrets/ssh-remote-builder.agebin1045 -> 935 bytes
-rw-r--r--secrets/test/ssh_host_ed25519_key.agebin0 -> 813 bytes
-rw-r--r--secrets/test/wireguard.agebin0 -> 581 bytes
-rw-r--r--tools/provision-nixos.py87
33 files changed, 418 insertions, 142 deletions
diff --git a/README.org b/README.org
index dac91fb..df66363 100644
--- a/README.org
+++ b/README.org
@@ -17,14 +17,13 @@ nix run nix-darwin -- switch --flake .
Finally, switch the default shell via =chsh=, and set it to =/run/current-system/sw/bin/fish=.
Best to reboot to complete the installation.
-
** Steps for a new droplet on DigitalOcean
Start by creating a droplet using Debian. Create a new host configuration.
Once the droplet is provisioned, we can use =nixos-anywhere= to convert the droplet to a NixOS installation.
#+begin_src sh
-nix run github:nix-community/nixos-anywhere -- --flake .#<host> --target-host root@<IP>
+just deploy-nixos flake ip
#+end_src
Once the host reboots, check that it's converted to NixOS by running =uname -a=.
@@ -33,31 +32,31 @@ Once the host reboots, check that it's converted to NixOS by running =uname -a=.
- use UEFI for boot
- use the ISO generated with =nix build .#nixosConfigurations.iso.config.system.build.isoImage=
- boot to the installer
-- run =nix run github:nix-community/nixos-anywhere -- --flake .#<name> --target-host <ip>
+- run =just deploy-nixos name ip=
** Create the nixos installer
Run
#+begin_src sh
nix build .#nixosConfigurations.iso.config.system.build.isoImage
#+end_src
+If you need to install this on a USB drive, use the Samsung disk for this. Follow these steps:
+- =diskutil list= to identify the disk (e.g. =/dev/disk5=)
+- =diskutil unmountDisk /dev/disk5= to un-mount the drive
+
Then copy to a USB stick with:
#+begin_src sh
sudo dd if=result/iso/nixos-minimal-25.05git.25e53aa156d-x86_64-linux.iso of=/dev/rdisk5 bs=1M conv=sync status=progress
#+end_src
-
** Bare metal machine
We can install remotely a machine with =nixos-anywhere=, including full disk encryption.
-First, create a password in 1password for the machine (using the convention "nix/<hostname>/encryption"). Next run the following snippet to create the SSH host key for init boot (this is needed so we can ssh to the host to unlock it).
+First, create a password in =passage= for the machine (using the convention =hosts/<hostname>/disk-encryption/passphrase=), and the ssh private key (=ed25519=). Next run the following snippet to create the SSH host key for init boot (this is needed so we can ssh to the host to unlock it).
#+begin_src sh
-set temp (mktemp -d)
-ssh-keygen -t ed25519 -N "" -C "initrd-root-ssh" -f "$temp/etc/initrd/ssh_host_ed25519_key"
-nix run github:nix-community/nixos-anywhere -- --flake .#rivendell --build-on remote --disk-encryption-keys /tmp/pass (op read "op://Private/vmifhwbjtvaqp3422gfbjxdq2y/password"|psub) --target-host root@192.168.1.112 --extra-files "$temp"
+just deploy-nixos name ip
#+end_src
* DNS
Update records through the [[https://dash.cloudflare.com/2c659eeaf2ae9a0206c589c706b3748e/fcuny.net][console]].
-
* Secrets
Get the identity under =secrets/identity.txt= with:
#+begin_src sh
@@ -68,7 +67,7 @@ age-plugin-yubikey --list --slot 1 > identity.txt
To create or edit a secret:
#+begin_src sh
cd (git rev-parse --show-toplevel)/secrets
-agenix -i identity.txt -e users/fcuny/llm.age
+age -R $PASSAGE_RECIPIENTS_FILE -o users/fcuny/llm.age
#+end_src
And to rekey the secrets:
@@ -80,8 +79,10 @@ agenix -i identity.txt -r
You can validate that the file is correct with:
#+begin_src sh
cd (git rev-parse --show-toplevel)/secrets
-nix eval --file secrets.nix
+nix eval --json --pretty --file secrets.nix
+age-inspect --json users/fcuny/llm.age
#+end_src
+The output of =age-inspect= should list in the =stanza_types= key at least one =ssh-ed25519= (it indicates one of the recipient is using a SSH key).
* Network
** Wireguard
*** New host
@@ -138,13 +139,3 @@ Then:
#+begin_src shell
psql -U postgres -h localhost -p 35432
#+end_src
-** Build the ISO
-To install nixos, we can build our own iso. For this, run:
-#+begin_src shell
-just build-iso
-#+end_src
-
-If you need to install this on a USB drive, use the Samsung disk for this. Follow these steps:
-- =diskutil list= to identify the disk (e.g. =/dev/disk5=)
-- =diskutil unmountDisk /dev/disk5= to un-mount the drive
-- =sudo dd if=result/iso/nixos-minimal-25.11git.30a3c519afc-x86_64-linux.iso of=/dev/rdisk5 status=progress=
diff --git a/flake.lock b/flake.lock
index 569d905..1935384 100644
--- a/flake.lock
+++ b/flake.lock
@@ -26,7 +26,7 @@
"cl-nix-lite": {
"inputs": {
"flake-parts": "flake-parts",
- "nixpkgs": "nixpkgs",
+ "nixpkgs": "nixpkgs_2",
"systems": "systems_2",
"treefmt-nix": "treefmt-nix"
},
@@ -339,12 +339,52 @@
"type": "github"
}
},
+ "home-manager_3": {
+ "inputs": {
+ "nixpkgs": [
+ "impermanence",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1768598210,
+ "narHash": "sha256-kkgA32s/f4jaa4UG+2f8C225Qvclxnqs76mf8zvTVPg=",
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "rev": "c47b2cc64a629f8e075de52e4742de688f930dc6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "type": "github"
+ }
+ },
+ "impermanence": {
+ "inputs": {
+ "home-manager": "home-manager_3",
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1768941735,
+ "narHash": "sha256-OyxsfXNcOkt06/kM+4bnuC8moDx+t7Qr+RB0BBa83Ig=",
+ "owner": "nix-community",
+ "repo": "impermanence",
+ "rev": "69ecf31e8fddc9354a4b418f3a517445d486bb54",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "impermanence",
+ "type": "github"
+ }
+ },
"mac-app-util": {
"inputs": {
"cl-nix-lite": "cl-nix-lite",
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
- "nixpkgs": "nixpkgs_3",
+ "nixpkgs": "nixpkgs_4",
"systems": "systems_3",
"treefmt-nix": "treefmt-nix_2"
},
@@ -365,7 +405,7 @@
"my-go-tools": {
"inputs": {
"flake-utils": "flake-utils_2",
- "nixpkgs": "nixpkgs_5",
+ "nixpkgs": "nixpkgs_6",
"pre-commit-hooks": "pre-commit-hooks",
"treefmt-nix": "treefmt-nix_3"
},
@@ -401,16 +441,16 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1766736597,
- "narHash": "sha256-BASnpCLodmgiVn0M1MU2Pqyoz0aHwar/0qLkp7CjvSQ=",
+ "lastModified": 1768564909,
+ "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "f560ccec6b1116b22e6ed15f4c510997d99d5852",
+ "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
"type": "github"
},
"original": {
"owner": "nixos",
- "ref": "nixos-25.11",
+ "ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
@@ -448,6 +488,22 @@
},
"nixpkgs_2": {
"locked": {
+ "lastModified": 1766736597,
+ "narHash": "sha256-BASnpCLodmgiVn0M1MU2Pqyoz0aHwar/0qLkp7CjvSQ=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "f560ccec6b1116b22e6ed15f4c510997d99d5852",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-25.11",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_3": {
+ "locked": {
"lastModified": 1761236834,
"narHash": "sha256-+pthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE+fV2Q=",
"owner": "nixos",
@@ -462,7 +518,7 @@
"type": "github"
}
},
- "nixpkgs_3": {
+ "nixpkgs_4": {
"locked": {
"lastModified": 1732617236,
"narHash": "sha256-PYkz6U0bSEaEB1al7O1XsqVNeSNS+s3NVclJw7YC43w=",
@@ -478,7 +534,7 @@
"type": "github"
}
},
- "nixpkgs_4": {
+ "nixpkgs_5": {
"locked": {
"lastModified": 1761236834,
"narHash": "sha256-+pthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE+fV2Q=",
@@ -494,7 +550,7 @@
"type": "github"
}
},
- "nixpkgs_5": {
+ "nixpkgs_6": {
"locked": {
"lastModified": 1767325753,
"narHash": "sha256-o4rBYiMcleYJx1dTiknARe3mfKfrc4m8WT/cWG+OG1M=",
@@ -507,7 +563,7 @@
"url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"
}
},
- "nixpkgs_6": {
+ "nixpkgs_7": {
"locked": {
"lastModified": 1767480499,
"narHash": "sha256-EFkzO1bnguFRUEeNvdkkGEU/P35obLVp+36NuaIQSr8=",
@@ -593,10 +649,11 @@
"disko": "disko",
"emacs-overlay": "emacs-overlay",
"home-manager": "home-manager_2",
+ "impermanence": "impermanence",
"mac-app-util": "mac-app-util",
"my-go-tools": "my-go-tools",
"nixos-hardware": "nixos-hardware",
- "nixpkgs": "nixpkgs_6",
+ "nixpkgs": "nixpkgs_7",
"nur": "nur",
"pre-commit-hooks": "pre-commit-hooks_2",
"treefmt-nix": "treefmt-nix_4"
@@ -664,7 +721,7 @@
},
"treefmt-nix": {
"inputs": {
- "nixpkgs": "nixpkgs_2"
+ "nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1766000401,
@@ -682,7 +739,7 @@
},
"treefmt-nix_2": {
"inputs": {
- "nixpkgs": "nixpkgs_4"
+ "nixpkgs": "nixpkgs_5"
},
"locked": {
"lastModified": 1766000401,
diff --git a/flake.nix b/flake.nix
index cf2b69f..7e95916 100644
--- a/flake.nix
+++ b/flake.nix
@@ -48,6 +48,8 @@
inputs.nixpkgs.follows = "nixpkgs";
};
+ impermanence.url = "github:nix-community/impermanence";
+
mac-app-util.url = "github:hraban/mac-app-util";
my-go-tools.url = "git+https://code.fcuny.net/x";
@@ -66,6 +68,7 @@
emacs-overlay,
nur,
my-go-tools,
+ impermanence,
...
}:
let
@@ -114,6 +117,7 @@
nixosAdminUser = baseAdminUser // {
uid = 1000;
+ gid = 100;
};
darwinAdminUser = baseAdminUser // {
@@ -125,6 +129,7 @@
agenix.nixosModules.age
disko.nixosModules.disko
home-manager.nixosModules.home-manager
+ impermanence.nixosModules.impermanence
./modules/default.nix
];
@@ -155,6 +160,10 @@
system = "x86_64-linux";
config = ./machines/iso.nix;
};
+ test = {
+ system = "x86_64-linux";
+ config = ./machines/test.nix;
+ };
};
darwin = {
mba-m2 = {
@@ -292,6 +301,7 @@
git
just
nixos-rebuild
+ uv
];
};
}
diff --git a/justfile b/justfile
index 3d49102..edebd3e 100644
--- a/justfile
+++ b/justfile
@@ -29,29 +29,29 @@ switch:
@echo "switching to new config..."
sudo darwin-rebuild switch --flake .
+# build the iso to bootstrap a new machine
[group('linux')]
build-iso:
@echo "building an ISO for nixos..."
nix build .#nixosConfigurations.iso.config.system.build.isoImage
+# provision a new machine with nixos-anywhere
+[group('linux')]
+deploy-nixos flake ip:
+ ./tools/deploy-nixos.py --flake {{flake}} --target-ip {{ip}}
+
+# rebuild the nixos configuration for a host
[group('linux')]
rbuild hostname:
@echo "building {{hostname}} nixos config..."
nixos-rebuild build --keep-going --flake ".#{{hostname}}" --target-host {{hostname}} --fast --use-remote-sudo --use-substitutes
+# apply the nixos configuration for a host
[group('linux')]
rswitch hostname:
@echo "switching {{hostname}} to new config..."
nixos-rebuild switch --keep-going --flake ".#{{hostname}}" --target-host {{hostname}} --fast --use-remote-sudo --use-substitutes
-# sync agenix key from 1password
-[group('secrets')]
-sync-agenix-key:
- @echo "copying agenix SSH key from 1password..."
- mkdir -p ~/.ssh
- op --account my.1password.com read "op://Private/agenix/private key?ssh-format=openssh" > ~/.ssh/agenix
- op --account my.1password.com read "op://Private/agenix/public key" > ~/.ssh/agenix.pub
-
# generate a new OIDC secret
[group('secrets')]
oidc-secret:
diff --git a/machines/test.nix b/machines/test.nix
new file mode 100644
index 0000000..3c6e138
--- /dev/null
+++ b/machines/test.nix
@@ -0,0 +1,47 @@
+{
+ adminUser,
+ lib,
+ config,
+ ...
+}:
+{
+ wgPublicKey = "c3z4rypRBn+kFj31I6Z90pjVjRYB8w5GCoq1tZP+4mc=";
+ publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDbwKdEY93hEVOx2DS4EMliiVTmsyxjqUG2stgCRMGwj";
+ ephemeralRoot = true;
+
+ age = {
+ identityPaths = [ "/persist/etc/ssh/ssh_host_ed25519_key" ];
+ secrets = {
+ wireguard.file = ../secrets/test/wireguard.age;
+ };
+ };
+
+ imports = [
+ ../profiles/core-metrics.nix
+ ../profiles/defaults.nix
+ ../profiles/disk/btrfs-on-luks.nix
+ ../profiles/hardware/synology-vm.nix
+ ../profiles/home-manager.nix
+ ../profiles/remote-unlock.nix
+ ../profiles/server.nix
+ ../profiles/state.nix
+ ../profiles/users/admin-user.nix
+ ../profiles/users/home-manager.nix
+ ../profiles/wireguard.nix
+ ];
+
+ disko.devices.disk.disk1.device = "/dev/sda";
+
+ networking.hostName = "test";
+ networking.useDHCP = lib.mkDefault true;
+ systemd.network.wait-online.anyInterface = lib.mkDefault config.networking.useDHCP;
+
+ system.stateVersion = "25.11"; # Did you read the comment?
+
+ home-manager.users.${adminUser.name} = {
+ home.homeDirectory = "/home/${adminUser.name}";
+ imports = [
+ ../home/profiles/minimal.nix
+ ];
+ };
+}
diff --git a/modules/host-config.nix b/modules/host-config.nix
index 92d8eea..ff1eaa5 100644
--- a/modules/host-config.nix
+++ b/modules/host-config.nix
@@ -13,5 +13,9 @@
type = lib.types.str;
default = "xxxx";
};
+ ephemeralRoot = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
};
}
diff --git a/profiles/defaults.nix b/profiles/defaults.nix
index 2683c5a..834c28d 100644
--- a/profiles/defaults.nix
+++ b/profiles/defaults.nix
@@ -5,6 +5,17 @@
adminUser,
...
}:
+let
+ inherit (lib // builtins)
+ attrNames
+ hasAttr
+ mkIf
+ length
+ ;
+ hasState =
+ hasAttr "persistence" config.environment && (length (attrNames config.environment.persistence)) > 0;
+ hasSecrets = config.age.secrets != { };
+in
{
imports = [
./cgroups.nix
@@ -145,4 +156,8 @@
vim
wireguard-tools
];
+
+ system.activationScripts.agenixNewGeneration = mkIf (
+ hasSecrets && hasState && config.ephemeralRoot
+ ) { deps = [ "persist-files" ]; };
}
diff --git a/profiles/disk/btrfs-on-luks.nix b/profiles/disk/btrfs-on-luks.nix
index 3fe57f7..aea2c0c 100644
--- a/profiles/disk/btrfs-on-luks.nix
+++ b/profiles/disk/btrfs-on-luks.nix
@@ -1,4 +1,4 @@
-{ ... }:
+{ lib, config, ... }:
let
btrfsopt = [
"compress=zstd"
@@ -13,55 +13,52 @@ in
];
disko.devices = {
- disk = {
- main = {
- type = "disk";
- device = "/dev/nvme0n1";
- content = {
- type = "gpt";
- partitions = {
- ESP = {
- size = "2G";
- type = "EF00";
- content = {
- type = "filesystem";
- format = "vfat";
- mountpoint = "/boot";
- mountOptions = [
- "fmask=0022"
- "dmask=0022"
- ];
- };
+ disk.disk1 = {
+ type = "disk";
+ device = lib.mkDefault "/dev/nvme0n1";
+ content = {
+ type = "gpt";
+ partitions = {
+ boot = {
+ name = "boot";
+ size = "1M";
+ type = "EF02";
+ };
+ esp = {
+ size = "2G";
+ type = "EF00";
+ content = {
+ type = "filesystem";
+ format = "vfat";
+ mountpoint = "/boot";
};
- luks = {
- size = "100%";
+ };
+ luks = {
+ size = "100%";
+ content = {
+ type = "luks";
+ name = "nixos";
+ passwordFile = "/tmp/disk.key";
+ settings.allowDiscards = true;
content = {
- type = "luks";
- name = "nixos";
- passwordFile = "/tmp/pass";
- settings = {
- allowDiscards = true;
- };
- content = {
- type = "btrfs";
- extraArgs = [ "-f" ];
- subvolumes = {
- "@root" = {
- mountpoint = "/";
- mountOptions = btrfsopt;
- };
- "@home" = {
- mountpoint = "/home";
- mountOptions = btrfsopt;
- };
- "@nix" = {
- mountpoint = "/nix";
- mountOptions = btrfsopt;
- };
- "@data" = {
- mountpoint = "/data";
- mountOptions = btrfsopt;
- };
+ type = "btrfs";
+ extraArgs = [ "-f" ];
+ subvolumes = {
+ "/root" = lib.mkIf (!config.ephemeralRoot) {
+ mountpoint = "/";
+ mountOptions = btrfsopt;
+ };
+ "/nix" = {
+ mountpoint = "/nix";
+ mountOptions = btrfsopt;
+ };
+ "/data" = {
+ mountpoint = "/data";
+ mountOptions = btrfsopt;
+ };
+ "/persist" = {
+ mountpoint = "/persist";
+ mountOptions = btrfsopt;
};
};
};
@@ -70,5 +67,14 @@ in
};
};
};
+ nodev."/" = lib.mkIf config.ephemeralRoot {
+ fsType = "tmpfs";
+ mountOptions = [
+ "size=16G"
+ "defaults"
+ "mode=755"
+ ];
+ };
};
+ fileSystems."/persist".neededForBoot = true;
}
diff --git a/profiles/hardware/synology-vm.nix b/profiles/hardware/synology-vm.nix
index 053cf41..970a23c 100644
--- a/profiles/hardware/synology-vm.nix
+++ b/profiles/hardware/synology-vm.nix
@@ -15,7 +15,9 @@
"sd_mod"
"sr_mod"
];
- boot.initrd.kernelModules = [ ];
+ boot.initrd.kernelModules = [
+ "virtio_net"
+ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
diff --git a/profiles/remote-unlock.nix b/profiles/remote-unlock.nix
index 9812ce8..0e2cb1b 100644
--- a/profiles/remote-unlock.nix
+++ b/profiles/remote-unlock.nix
@@ -12,7 +12,7 @@
enable = true;
port = 911;
hostKeys = [
- "/etc/initrd/ssh_host_ed25519_key"
+ "/persist/secrets/ssh_host_ed25519_key"
];
authorizedKeys = with adminUser.userinfo.sshPublicKeys; [
onepassword
diff --git a/profiles/state.nix b/profiles/state.nix
new file mode 100644
index 0000000..0869d11
--- /dev/null
+++ b/profiles/state.nix
@@ -0,0 +1,59 @@
+{
+ adminUser,
+ config,
+ lib,
+ ...
+}:
+{
+ system.activationScripts = lib.mkIf config.ephemeralRoot {
+ "createPersistentStorageDirs".deps = [
+ "var-lib-private-permissions"
+ "home-user-permissions"
+ "users"
+ "groups"
+ ];
+ "var-lib-private-permissions" = {
+ deps = [ "specialfs" ];
+ text = ''
+ mkdir -p /persist/var/lib/private
+ chmod 0700 /persist/var/lib/private
+ '';
+ };
+ "home-user-permissions" = {
+ deps = [ "specialfs" ];
+ text = ''
+ mkdir -p /persist/save/home/${adminUser.name}
+ chown -R ${toString adminUser.uid}:${toString adminUser.gid} /persist/save/home/${adminUser.name}
+ chmod 0700 /persist/save/home/${adminUser.name}
+ '';
+ };
+ };
+
+ environment.persistence."/persist" = {
+ enable = config.ephemeralRoot;
+ hideMounts = true;
+ directories = [
+ "/root"
+ "/var/lib/containers"
+ "/var/lib/nixos"
+ "/var/lib/systemd"
+ "/var/log"
+ ];
+ files = [
+ "/etc/machine-id"
+ "/etc/ssh/ssh_host_ed25519_key"
+ "/etc/ssh/ssh_host_ed25519_key.pub"
+ ];
+ };
+
+ environment.persistence."/persist/save" = {
+ enable = config.ephemeralRoot;
+ hideMounts = true;
+ users.${adminUser.name} = {
+ directories = [ ];
+ files = [
+ ".ssh/known_hosts"
+ ];
+ };
+ };
+}
diff --git a/profiles/users/admin-user.nix b/profiles/users/admin-user.nix
index 2e33603..8624fb4 100644
--- a/profiles/users/admin-user.nix
+++ b/profiles/users/admin-user.nix
@@ -10,7 +10,7 @@
inherit (adminUser) uid;
shell = pkgs.fish;
isNormalUser = true;
- hashedPassword = "$y$j9T$U3mXpCzXC1VUp8wV5snJz/$32vTk0KwVXvP/jLO13nMlGPHy0nCe4ZtebdvqU4hwmD";
+ hashedPassword = "$y$j9T$TbnCB1V7n6W9C32LEroix1$P.OvRgLlhSbwFvR6ADE43Gc5Hc0ezxXMETS/DjIeXC8";
openssh.authorizedKeys.keys = pkgs.lib.attrValues adminUser.userinfo.sshPublicKeys;
extraGroups = [
"wheel"
diff --git a/profiles/wireguard.nix b/profiles/wireguard.nix
index 76586ba..5620699 100644
--- a/profiles/wireguard.nix
+++ b/profiles/wireguard.nix
@@ -22,6 +22,10 @@ let
publicKey = hostConfigurations.rivendell.wgPublicKey;
endpoint = "192.168.1.114";
};
+ test = {
+ ip = 41;
+ publicKey = hostConfigurations.rivendell.wgPublicKey;
+ };
};
wgPort = 51820;
diff --git a/secrets/acme-cloudflare-env.age b/secrets/acme-cloudflare-env.age
index f204f55..0db746f 100644
--- a/secrets/acme-cloudflare-env.age
+++ b/secrets/acme-cloudflare-env.age
Binary files differ
diff --git a/secrets/anthropic-api-key.age b/secrets/anthropic-api-key.age
index 3cf3e31..524df4e 100644
--- a/secrets/anthropic-api-key.age
+++ b/secrets/anthropic-api-key.age
Binary files differ
diff --git a/secrets/argonath/wireguard.age b/secrets/argonath/wireguard.age
index 7f25906..5edd73d 100644
--- a/secrets/argonath/wireguard.age
+++ b/secrets/argonath/wireguard.age
Binary files differ
diff --git a/secrets/authelia-jwks.age b/secrets/authelia-jwks.age
index 57e62ca..f036674 100644
--- a/secrets/authelia-jwks.age
+++ b/secrets/authelia-jwks.age
Binary files differ
diff --git a/secrets/authelia-jwt-key.age b/secrets/authelia-jwt-key.age
index 44c511b..e1d24b2 100644
--- a/secrets/authelia-jwt-key.age
+++ b/secrets/authelia-jwt-key.age
@@ -1,13 +1,11 @@
age-encryption.org/v1
--> ssh-ed25519 pFjJaA ab95Xy3qdnkT+1QNbE7nM0KTxEsvZbJSaGlQHkMeuVY
-Aos+Y6URghnBymVQXMW4KMB4MupcMFVubUILUgfObcM
--> ssh-ed25519 Y5h84Q EXMHz89OpNU9k6ehLg8UiR9QVS5IRAiZ/QM5U5gb3BU
-h5nndbtxIXsMw5VKQTXPWIzzI1wZxa1LpoA2sXnUnco
--> piv-p256 p72k4g AyneNJUchntzS/JK92mNx7sExe+Qw319/8wys4rXcCJ7
-LaAxI45aCP3qlK+dxNiBYWMTdWk8IgxDaBtwiEAOzSY
--> piv-p256 3VHSDQ AoL8W+RHWh3VCPyIqSnRKdqL2mOtsSNadlApadjmvLYD
-aDy2pnH4fqfornVC5VWCVnVXOppA2fU4ZYBXQduq2wU
--> piv-p256 EwG8WQ ApU0xcZy2cLt+ZlTMJCpB2/qZ2+FsTBAtiMXGMUHW3Dr
-fcY3frM+Rh8y6QzGZViTYfsSAg7mNtu3o5rpick0yb0
---- 0Hkdlz6iYjC4NXrIut1N4XnSFHKAnrtQVPvE3Ua4rA8
-aq\FWɮa D~+lu~d0q$?RIy2=Xr"0Q"Pv['Ie,>|*Fk~?N \6ą0 ? \ No newline at end of file
+-> piv-p256 p72k4g AoOkiRon1Euqtk0bGkg7jj3/moc86443Qiv3o/eD8Mvu
+2iXCi4fFHVyS6EhR8s0RqymZujwd+CkQq53N2fhbQbg
+-> piv-p256 3VHSDQ AgFXfoGRwV6ojVkhpdInCEgVE59KhoUBpEQCnyQMFTMa
+OLx/OYmfYwVyciUnrsKHMMuNbVj7MN0eHfvBK3rV8l8
+-> piv-p256 EwG8WQ A3tES/tGDJ7eHADR3ThB8HwLevNSRKfV/qxO8+sfhKnG
+rG4sBBi8Q7Y9jkLftD2sHr1aI5muE64bNfOjEGdUvzU
+-> ssh-ed25519 Y5h84Q x1Wqj46Ro+2N6wBGMOQ5Y/dHHURmtWk3eG+6/20XLXs
+GNYg1JTQ/UHwPtFJ2TcpIsi7g0D0E8Q2sVcm0Xij2Ig
+--- TbVqe8KDEi/RSGkjNIN2m7enPpb6tTaemJTipak53cI
+.cHT0퓼Vm|ؐJ*M[<NJGaNY @~ƙXٙrI_f ʈ, ӜS.J\Ȑ{<&sQ*ȉ=ɇ"_8rtG0 \ No newline at end of file
diff --git a/secrets/authelia-storage-key.age b/secrets/authelia-storage-key.age
index 5aa6d4e..1878c7c 100644
--- a/secrets/authelia-storage-key.age
+++ b/secrets/authelia-storage-key.age
Binary files differ
diff --git a/secrets/authelia-users.yaml.age b/secrets/authelia-users.yaml.age
index 15082f3..191a68d 100644
--- a/secrets/authelia-users.yaml.age
+++ b/secrets/authelia-users.yaml.age
Binary files differ
diff --git a/secrets/bree/disk-passphrase.age b/secrets/bree/disk-passphrase.age
index e755597..d42cfa1 100644
--- a/secrets/bree/disk-passphrase.age
+++ b/secrets/bree/disk-passphrase.age
Binary files differ
diff --git a/secrets/bree/disk-unlock-key.age b/secrets/bree/disk-unlock-key.age
index 5845c22..d79c0e9 100644
--- a/secrets/bree/disk-unlock-key.age
+++ b/secrets/bree/disk-unlock-key.age
Binary files differ
diff --git a/secrets/bree/wireguard.age b/secrets/bree/wireguard.age
index cb548f9..b618190 100644
--- a/secrets/bree/wireguard.age
+++ b/secrets/bree/wireguard.age
Binary files differ
diff --git a/secrets/grafana-oidc.age b/secrets/grafana-oidc.age
index 9c1ac05..fabee96 100644
--- a/secrets/grafana-oidc.age
+++ b/secrets/grafana-oidc.age
@@ -1,13 +1,11 @@
age-encryption.org/v1
--> ssh-ed25519 pFjJaA kJ/tIhFivUGhT9Gx8cNeAcxdghf0taPWzqUhR8qo4Qc
-AIvNKY3xm6z7SUJ5On8RE6Ucjsv6TlaeYaOYTgyvQq4
--> ssh-ed25519 Y5h84Q XNWAeUxr9DjIzWM4qhIOhEJ6Ecp5ypCX0Yvcz9Xesj4
-oZ2FkjFw0qTwgiYcVMWtzPoqGWZY1l+U73J+TgZ3X1w
--> piv-p256 p72k4g A0Mdy32eOyBtKGo5dGoEC7FMpm9ZN5+Raa60lQBn1TmE
-6li+w4ekWyfjvOs9HuhEOLsashLP+juLk2hx2pLgjwA
--> piv-p256 3VHSDQ AjByue1R3VFjCow5Z6Mqe52Vz3o3o6IfbqRHlzcT4l7y
-KRhQPUqHlQopY2oMLJKzgznBWMH9BkxLs93VK84fDFs
--> piv-p256 EwG8WQ A87+jQw1s4BFTg6mUG62NCrgpCr/7EQoOEsqcSYQkDRd
-HSX9YmHQHzzVxfoEB7x+W0S2nqO44CVHdLQ/FbTGO60
---- XKDOSYe7vUkzn6sSLb3KHLBm9v/H9paHMKmkUnTB/Jg
-ǣ3o^G4dyJ =T@J)+A-Z UXVe BQ}V,OdL<oY*1Ae頠`; \ No newline at end of file
+-> piv-p256 p72k4g A4oi87WF3sdfjikAhFnGojH4wFhN8PS/LYcTJeeSBPap
+pAaJUbWOECxcvCQKxQ2lSUBHCQO7bAZWuDl5YM2fRu8
+-> piv-p256 3VHSDQ Alk3vY0FgDeN/gCdOSxgnUo/hfVZ7URyD0cJuQv0yww3
+gAQg5IVFyPa3f1bS+n5C6MU3s/WWMhMlW7yD7TiT8rA
+-> piv-p256 EwG8WQ Aqk2hmEcO5LyuU4HVFaCfMs0VLlHD4o5pZq2hG69ZJ6z
+dNOTD0LgWcbCDJr2dO8DdTa5tZouNWNKWaRG/UOIJWI
+-> ssh-ed25519 Y5h84Q LNqSVvXMBSpHNh+gKbqW/jESC1MKXzdSD9exBNw7TXc
+6MsJL/xRuWiXIwwl4jzpI5BKM42m21jIjAiBYK7ur/w
+--- GEENxbXM7M/YFFieNUZTsWbZ5iNTvs2IUMHLlPni+OQ
+ agqI&J>g/k&V R+ZF*}#>%ϳ8_nMH=m<U՘ \ No newline at end of file
diff --git a/secrets/miniflux-oidc.age b/secrets/miniflux-oidc.age
index 0276a3e..281b22e 100644
--- a/secrets/miniflux-oidc.age
+++ b/secrets/miniflux-oidc.age
Binary files differ
diff --git a/secrets/restic-nas-smb-config.age b/secrets/restic-nas-smb-config.age
index 85bf7a3..364c5b8 100644
--- a/secrets/restic-nas-smb-config.age
+++ b/secrets/restic-nas-smb-config.age
Binary files differ
diff --git a/secrets/restic-pw.age b/secrets/restic-pw.age
index 7d16a16..2c95b51 100644
--- a/secrets/restic-pw.age
+++ b/secrets/restic-pw.age
@@ -1,13 +1,13 @@
age-encryption.org/v1
--> ssh-ed25519 pFjJaA syGYV6BJA1h3LhjxbemVAmYitI5aegvlNrrjAdCsH3k
-Lxo6Y5Dv/G8D4GXBNcN+OPscp3QfFN+N4vs4DKxE0lU
--> ssh-ed25519 Y5h84Q SGVhQY9w4IqevD+IJy4BvmcFm9YIhvbWXUVLwwmbnwM
-75wLxUwTGAmYBmTtN1CxV/oZZukFLk7xovtGBmvz6uo
--> piv-p256 p72k4g A/f4dbKBG2cI37D3D4J3WPE6Yx6vzNdP+5ntufwM/Gha
-lEPCCOwtjpq8HJg9SmjIcQMh9gJFWbhYQFjxu4dSXGc
--> piv-p256 3VHSDQ Apy0ALbHVi49Bj8w2ek/i/eV3/05/A9dDPBMQPn9FkPx
-GBtBwEsIQLOyyAM3FYikVVz7okArqQ9jkXwSF29F3Xk
--> piv-p256 EwG8WQ AwWFId0nB8e6gxB+NnwRFMCPqIIEccDR6nl//AouYjDN
-3sggJZLjKxwOhy1s9knJRAWBX8XZxQxJyu00ATHNksU
---- WxnsY5qOy2Mp2lmdGCp2nwsiTj7YAg2hYGbDiFx3Q3I
-B~?>hMK([UC[oP҅()x_.T~y"ҝķvB \ No newline at end of file
+-> piv-p256 p72k4g AzrfxleD97vj+b0i/4MirdjWWywswZyt0cqe7NYo9YQ4
+fOWYwjASDxhqUcNp6w1uQZmwI51/4blsHlbFGlZM91I
+-> piv-p256 3VHSDQ Ascoksz5SOuRdWgH2TfVm2wuGOAdwfY5+Zsc9MGAouXA
+Z7T8RemQIs/Mkdjrv+iYMx6N9kG+jjYIW3L2KEHE4G0
+-> piv-p256 EwG8WQ AgEaKl9tYMlfoSmBiZavstcJNOyxaMRdJSUFDspr6nP2
+e0voo2M30ZrNJWCfPoAnEXoRwd7UW3mTqdiSv2OzHMI
+-> ssh-ed25519 Y5h84Q FIKfbc0p7IyAF0ay+kLcA0/df673JsOUnkxpUr220nE
+CP9UvOFluuRATTjbaT9SfZAJQCrkrqS9vKVX9YMoR8w
+-> ssh-ed25519 E2Yu8Q 5IivN0WwNdllxfMFbEmuaA6txuboiH3eecOztNHClVY
+c/tLSOQ68p5xRrGiEq3fBdDPVw6fbFqB2fIK9tqBsDE
+--- HqeM47p0TjMol/fRmjLHOOR0+yXp4pNuqw2Th+4jm1k
+62UE:?g8JMx'?{Gog W}Vp]2qh \ No newline at end of file
diff --git a/secrets/rivendell/wireguard.age b/secrets/rivendell/wireguard.age
index 2be928e..9c967a5 100644
--- a/secrets/rivendell/wireguard.age
+++ b/secrets/rivendell/wireguard.age
@@ -1,13 +1,11 @@
age-encryption.org/v1
--> ssh-ed25519 pFjJaA 2qv6LHBYtAiaDhZ8sAyaw+bSqQiyFkQG9ZB1y1aolxE
-9uOB2Nrm0QZvo3wIB8/mNqmTOorYZdK8OK3wotbtUyA
--> ssh-ed25519 Y5h84Q b9FraCr9H3n/fFfmVhWWiJre56xr7QVj2rG1A93FkGU
-iYoBQE9x+pMVZFVTtTaU2br/BbU/+B8rZWQLrOzQgpw
--> piv-p256 p72k4g A7ErQNxB/ZHqci4wbRIlLcGzVKJPL66YSvOGperLqoa2
-q8ok0ZH+qhWg4wodnhrGdO+o6YgstfkP0jiqJCNfjh0
--> piv-p256 3VHSDQ A3CZRYI7mdEdYfjoiBupjgt4IuxspjEAiF/U0L7BUPgM
-dfefVCTufhqSpmC7VRPlHU1uesBxswQtT2sG0ZhN5kU
--> piv-p256 EwG8WQ AwOVx3tXdx2hKs5mXbalBIcNgdmDFx1B5ZSptImOmc6G
-/fAXlZ6TFT3uPi/8nsTygmMWQ9skuFHBACEgoPBv4qw
---- 8O55twCZmB3scabd9rETAM2O1a+qHw2q5rwLHuU8K1E
-VsX6BE[鞇T4hn]˟wq 3MShD`exl))˫z  揙Eg \ No newline at end of file
+-> 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/rsync-ssh-nas.age b/secrets/rsync-ssh-nas.age
index 4c84a2e..c768b22 100644
--- a/secrets/rsync-ssh-nas.age
+++ b/secrets/rsync-ssh-nas.age
Binary files differ
diff --git a/secrets/ssh-remote-builder.age b/secrets/ssh-remote-builder.age
index b4c94a4..4878d95 100644
--- a/secrets/ssh-remote-builder.age
+++ b/secrets/ssh-remote-builder.age
Binary files differ
diff --git a/secrets/test/ssh_host_ed25519_key.age b/secrets/test/ssh_host_ed25519_key.age
new file mode 100644
index 0000000..f36df9c
--- /dev/null
+++ b/secrets/test/ssh_host_ed25519_key.age
Binary files differ
diff --git a/secrets/test/wireguard.age b/secrets/test/wireguard.age
new file mode 100644
index 0000000..f5d3937
--- /dev/null
+++ b/secrets/test/wireguard.age
Binary files differ
diff --git a/tools/provision-nixos.py b/tools/provision-nixos.py
new file mode 100644
index 0000000..9946f03
--- /dev/null
+++ b/tools/provision-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()