···11+# sops-nix setup on NixOS (Raspberry Pi 4 from macOS)
22+33+## Goal
44+55+Migrate a plain `configuration.nix` to Nix Flakes and encrypt secrets using
66+`sops-nix`, deploying from macOS to a Raspberry Pi 4.
77+88+## Stack
99+1010+- **sops** — encrypts secrets in git
1111+- **age** — encryption backend (derived from existing ED25519 SSH keys via `ssh-to-age`)
1212+- **sops-nix** — NixOS module that decrypts secrets at boot
1313+- **deploy-rs** — deploys from macOS to the Pi over SSH with automatic rollback
1414+1515+---
1616+1717+## Setup steps
1818+1919+### 1. Desktop — derive age identity from your SSH key
2020+2121+```bash
2222+mkdir -p $HOME/.config/sops/age/
2323+read -s SSH_TO_AGE_PASSPHRASE; export SSH_TO_AGE_PASSPHRASE
2424+nix run nixpkgs#ssh-to-age -- -private-key -i $HOME/.ssh/id_ed25519 -o $HOME/.config/sops/age/keys.txt
2525+```
2626+2727+Get your age public key:
2828+2929+```bash
3030+nix shell nixpkgs#age --command age-keygen -y $HOME/.config/sops/age/keys.txt
3131+```
3232+3333+### 2. Raspberry Pi — get the host age public key
3434+3535+```bash
3636+cat /etc/ssh/ssh_host_ed25519_key.pub | nix run nixpkgs#ssh-to-age
3737+```
3838+3939+### 3. Desktop — create `.sops.yaml`
4040+4141+```yaml
4242+keys:
4343+ - &admin_you age1... # from step 1
4444+ - &raspberrypi age1... # from step 2
4545+creation_rules:
4646+ - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
4747+ key_groups:
4848+ - age:
4949+ - *admin_you
5050+ - *raspberrypi
5151+```
5252+5353+### 4. Desktop — create and edit secrets
5454+5555+```bash
5656+mkdir -p secrets
5757+nix run nixpkgs#sops secrets/raspberry.yaml
5858+```
5959+6060+---
6161+6262+## Secrets in `configuration.nix`
6363+6464+Sops secrets are **files on disk at runtime**, not Nix strings. They cannot be
6565+interpolated with `${}` at build time. Each type of secret needs a different
6666+approach:
6767+6868+| Secret | How to consume it |
6969+|---|---|
7070+| User password | `hashedPasswordFile = config.sops.secrets.X.path` |
7171+| WiFi credentials | `networking.wireless.secretsFile` + `@VARIABLE@` placeholders |
7272+| SSH public key | Plain text in config — it's public, no need to encrypt |
7373+7474+### WiFi
7575+7676+WiFi credentials need a single file containing both values:
7777+7878+```yaml
7979+# secrets/raspberry.yaml
8080+WIFI_CREDENTIALS: |
8181+ WIFI_SSID=YourNetwork
8282+ WIFI_PASSWORD=YourPassword
8383+```
8484+8585+```nix
8686+networking.wireless = {
8787+ enable = true;
8888+ secretsFile = config.sops.secrets.WIFI_CREDENTIALS.path;
8989+ networks."@WIFI_SSID@".psk = "@WIFI_PASSWORD@";
9090+};
9191+```
9292+9393+### User password
9494+9595+The secret must have `neededForUsers = true` so it is available before user
9696+activation runs:
9797+9898+```nix
9999+sops.secrets.USER_PASSWORD = { neededForUsers = true; };
100100+101101+users.users.alice = {
102102+ hashedPasswordFile = config.sops.secrets.USER_PASSWORD.path;
103103+};
104104+```
105105+106106+---
107107+108108+## Errors encountered and fixes
109109+110110+### sops can't decrypt the file on second open
111111+112112+```
113113+Did not find keys in locations 'SOPS_AGE_KEY_FILE', '/Users/.../.ssh/id_rsa'
114114+```
115115+116116+**Cause:** sops looks for `id_rsa` by default and doesn't find `keys.txt`.
117117+118118+**Fix:** export the variable pointing to the age keys file and add it to your shell config:
119119+120120+```bash
121121+export SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt"
122122+```
123123+124124+---
125125+126126+### `/run` is forbidden in pure evaluation mode
127127+128128+```
129129+error: access to absolute path '/run' is forbidden in pure evaluation mode
130130+```
131131+132132+**Cause:** `openssh.authorizedKeys.keyFiles` was set to
133133+`config.sops.secrets.SSH_PUBLIC_KEY.path`, which resolves to `/run/secrets/...`.
134134+Nix evaluates this path at build time, but `/run` only exists at runtime.
135135+136136+**Fix:** SSH public keys are not sensitive. Remove `SSH_PUBLIC_KEY` from sops
137137+and put the key directly in the config:
138138+139139+```nix
140140+openssh.authorizedKeys.keys = [
141141+ "ssh-ed25519 AAAA... you@desktop"
142142+];
143143+```