feat(deploy flash): auto-install sops age key onto the SD boot partition

- after dd, if ~/.config/homelab/<config>/age.txt exists, mount the FAT boot
  partition and drop it as sops-age.txt (mercury). Key stays off-repo + out of
  the store + out of the image; no manual mount step.
This commit is contained in:
erik
2026-07-13 21:02:34 +02:00
parent 1937d59b2c
commit 2f89a5c5e7
3 changed files with 27 additions and 7 deletions
+3 -3
View File
@@ -3,9 +3,9 @@
# sops-nix wiring for mercury. Encrypted values in ../../secrets/mercury.yaml. # sops-nix wiring for mercury. Encrypted values in ../../secrets/mercury.yaml.
# #
# SD images have no `--extra-files` step, so mercury uses a DEDICATED age key # SD images have no `--extra-files` step, so mercury uses a DEDICATED age key
# (not ssh-host-key-derived) placed on the FAT boot partition after flashing: # placed on the FAT boot partition. `./deploy flash mercury <dev>` does this
# mount the boot partition, copy ~/.config/homelab/mercury/age.txt to it as # automatically (copies ~/.config/homelab/mercury/age.txt -> sops-age.txt).
# sops-age.txt, unmount. The key never enters the repo or the nix store. # The key never enters the repo, the nix store, or the image itself.
{ {
sops.defaultSopsFile = ../../secrets/mercury.yaml; sops.defaultSopsFile = ../../secrets/mercury.yaml;
sops.age.keyFile = "/boot/firmware/sops-age.txt"; sops.age.keyFile = "/boot/firmware/sops-age.txt";
+21 -1
View File
@@ -11,7 +11,9 @@
# ./deploy boot <config> <host> stage for next boot, don't activate now. # ./deploy boot <config> <host> stage for next boot, don't activate now.
# ./deploy test <config> <host> activate without adding a boot entry. # ./deploy test <config> <host> activate without adding a boot entry.
# ./deploy image <config> build an SD-card image (e.g. rpi mercury). # ./deploy image <config> build an SD-card image (e.g. rpi mercury).
# ./deploy flash <config> <dev> build the SD image + write it to <dev>. # ./deploy flash <config> <dev> build SD image, write to <dev>, and (if
# ~/.config/homelab/<config>/age.txt exists)
# drop the sops key on its boot partition.
# #
# <config> = a nixosConfigurations name (e.g. jupiter, vps). Its pre-generated # <config> = a nixosConfigurations name (e.g. jupiter, vps). Its pre-generated
# SSH host key must be at ~/.config/homelab/<config>/ssh_host_ed25519_key. # SSH host key must be at ~/.config/homelab/<config>/ssh_host_ed25519_key.
@@ -115,6 +117,24 @@ case "$cmd" in
[ "$ok" = yes ] || die "aborted" [ "$ok" = yes ] || die "aborted"
zstdcat "$img" | sudo dd of="$dev" bs=4M status=progress oflag=sync zstdcat "$img" | sudo dd of="$dev" bs=4M status=progress oflag=sync
sync sync
# If this config has a dedicated sops age key, drop it on the FAT boot
# partition (mounts at /boot/firmware) so sops decrypts on first boot.
# Key stays off-repo and out of the nix store; the image itself has no key.
keyfile="$HOME/.config/homelab/$config/age.txt"
if [ -f "$keyfile" ]; then
echo ">> installing sops age key onto the boot partition"
sudo partprobe "$dev" 2>/dev/null || sudo blockdev --rereadpt "$dev" 2>/dev/null || true
sudo udevadm settle 2>/dev/null || true
fatpart="$(lsblk -lno PATH,FSTYPE "$dev" | awk '$2=="vfat"{print $1; exit}')"
[ -n "$fatpart" ] || die "no FAT boot partition found on $dev — copy $keyfile to it manually as sops-age.txt"
mnt="$(mktemp -d)"
sudo mount "$fatpart" "$mnt"
sudo cp "$keyfile" "$mnt/sops-age.txt"
sudo sync
sudo umount "$mnt"; rmdir "$mnt"
echo ">> age key installed (sops-age.txt)"
fi
echo ">> done — insert the card into the Pi and boot." echo ">> done — insert the card into the Pi and boot."
;; ;;
+3 -3
View File
@@ -1,4 +1,4 @@
darman_password: ENC[AES256_GCM,data:HzFkwGIyQL1sbPegJq970n3VASkaO/iY1t3fjodqk6FSoGX+4zlVoZfNAuTeiyAwv+0bZHhJoK+cAPKp5HMqsnhXJrhJUV7KVA3beyBXPPg8IpXi4X4maKv9KLIiWi2BJQuE421LUjb/2A==,iv:dcCD3aaEfVfPBh0jwFZkmNz0L/ZfRzMROJIHXd6pkG8=,tag:5vRhPo+fE28HrZPp6ZJ1MQ==,type:str] darman_password: ENC[AES256_GCM,data:vlcmADPCvt3H+etrXeowGl8w5+RQj6vwPzrx0+3qXb2U7s22H7VN8oXSwnMHQh+lAacrnl1c8u4D3SOh22QQBIix1wgAMQTFN15fo1RE3bTAUtiaV4HKfPNEsM+brTQs4oaX2Pnl3kZAbQ==,iv:DSTreGrypTYm0fAf8k6YZ277XMKYrocwPUUG0IBs4Jg=,tag:GuujksyB01YuX3rp82Jz3Q==,type:str]
sops: sops:
age: age:
- enc: | - enc: |
@@ -19,7 +19,7 @@ sops:
ZsBrZS0j0B3MhTI+6gUWB82xozgmnh95w+SXSSZZvoss8RuicE5ysA== ZsBrZS0j0B3MhTI+6gUWB82xozgmnh95w+SXSSZZvoss8RuicE5ysA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
recipient: age1cpty7zrgnn6l97upq00w5wa8zcvnkxkdt2jvhlj97jh83exure4slha43t recipient: age1cpty7zrgnn6l97upq00w5wa8zcvnkxkdt2jvhlj97jh83exure4slha43t
lastmodified: "2026-07-13T18:50:12Z" lastmodified: "2026-07-13T18:55:42Z"
mac: ENC[AES256_GCM,data:8e1VHvwPzI97toTrkbT53ky8OV8Jepyn5yhEHq87wcDP4cywZTm6WbJsqJNgvYdwwxIgaLK7WyausaFfnqIBRFSxauhmTD7mjXKwGFAvSyeyFL3RYbdTc3+GInTE8gF8oBQ7OGIREW6pcDFswzhmcTKbJ01V+vJ2sgxnvifNe7E=,iv:pmLsvkPro4aw//UdldKErbYfVmlK+xb2/4CbN0I6gQQ=,tag:sy9wBkTYIdYrlJ8U4TdJIw==,type:str] mac: ENC[AES256_GCM,data:dVDVsrqz10/EhNCqVRRTKo8tKNCBTU8ZLA7ASE7exWzzUVB1LPIw4ht4QV88cd7iMKy10UMnLUjyg7m0RTbTEJXzlMHjDUbJThNCZ+/oQyQbGlwvCV61KIwFWK3G1bO3Bz6ux+Lo7IG3a8pJIjntTFfb4+l1612QRd73XHp8wT4=,iv:8kyta/3+wXRnC05GmNbDz6IA/AEFSZl1VM4QF6C0Nxc=,tag:rQf5GUelRFZVmdXK4DjcBw==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.13.1 version: 3.13.1