Restoring a Host from Backup#

Quick Reference#

NixOS Darwin
Backup tool borgmatic (service) raw borg (launchd script)
Config/credentials /etc/borgmatic.d/voile.yaml, /etc/borgmatic/key, /etc/borgmatic/pass /etc/borgmatic/run_backup.sh, /etc/borgmatic/key, /etc/borgmatic/pass
List archives sudo borgmatic list borg list
Extract files sudo borgmatic extract --archive <name> --path <rel-path> borg extract ::<name> <rel-path>
Archive naming <hostname>-<timestamp> (borgmatic-generated) <hostname>-<ISO8601>
Switch command nh os switch .# -H <hostname> nh darwin switch .# -H <hostname>

Restoring Files — NixOS (borgmatic)#

After a full wipe and rebuild (see sops bootstrap below), borgmatic is fully configured by activation. The borgmatic config is at /etc/borgmatic.d/voile.yaml with the repo URL, SSH key, and passphrase already wired in.

List borgmatic archives#

sudo borgmatic list

Output looks like:

voile: Listing archives
mokou-2025-01-15T03:00:01.123456  Wed, 2025-01-15 03:00:01 [...]
mokou-2025-01-14T03:00:01.654321  Tue, 2025-01-14 03:00:01 [...]

Extract specific files (borgmatic)#

borgmatic extract always runs in the current working directory and reconstructs the path hierarchy from root. Run from /tmp to avoid collisions:

cd /tmp
sudo borgmatic extract --archive mokou-2025-01-15T03:00:01.123456 \
  --path home/tsunami/.config/sops/age/keys.txt

The file lands at /tmp/home/tsunami/.config/sops/age/keys.txt.

Extract the user sops age key — NixOS#

cd /tmp
sudo borgmatic extract --archive <latest-archive> \
  --path home/tsunami/.config/sops/age/keys.txt

mkdir -p ~/.config/sops/age
sudo mv /tmp/home/tsunami/.config/sops/age/keys.txt ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt

Then re-run activation to decrypt user-level secrets:

nh os switch .# -H <hostname>

Full home restore#

To restore all of /home:

cd /
sudo borgmatic extract --archive <archive-name> --path home

This overwrites in place. Use --dry-run first to preview.

Borgmatic sources#

The NixOS borgmatic config backs up:

  • /etc
  • /home
  • /var/log
  • /var/backup

Restoring Files — Darwin (borg direct)#

Darwin uses raw borg via a launchd-managed shell script. There is no borgmatic. After rebuild and nh darwin switch, credentials are at the same paths as NixOS.

Set up environment#

The repo URL is in the backup script. Pull it from there:

grep 'BORG_REPO=' /etc/borgmatic/run_backup.sh
# → export BORG_REPO="ssh://borgwarehouse@voile.armadillo-banfish.ts.net:22222/./<repo-id>"

export BORG_REPO="ssh://borgwarehouse@voile.armadillo-banfish.ts.net:22222/./<repo-id>"
export BORG_RSH="ssh -i /etc/borgmatic/key"
export BORG_PASSPHRASE="$(cat /etc/borgmatic/pass)"

List borg archives#

borg list

Archive names look like: work-laptop-2025-01-15T03:00:00Z

Extract specific files (borg)#

borg extract also runs relative to the current directory. From /tmp:

cd /tmp
borg extract ::work-laptop-2025-01-15T03:00:00Z \
  Users/bcraton/.config/sops/age/keys.txt

File lands at /tmp/Users/bcraton/.config/sops/age/keys.txt.

Extract the user sops age key — Darwin#

cd /tmp
borg extract ::<hostname>-<timestamp> Users/<user>/.config/sops/age/keys.txt

mkdir -p /Users/<user>/.config/sops/age
mv /tmp/Users/<user>/.config/sops/age/keys.txt /Users/<user>/.config/sops/age/keys.txt
chmod 600 /Users/<user>/.config/sops/age/keys.txt

nh darwin switch .# -H <hostname>

Darwin backup sources#

The Darwin backup script backs up / with extensive excludes for Nix store, caches, Library folders, derived data, etc. See modules/darwin/borg/default.nix for the full exclude list.


Sops Bootstrap After a Wipe#

Before borg is accessible, the system must activate successfully — which requires secrets.yaml to be encrypted for the new SSH host key. This is the bootstrap procedure that unblocks everything above.

Key architecture#

Layer Key source Protects
System /etc/ssh/ssh_host_ed25519_key Borg SSH key, borg passphrase, nix cache key, builder key
User (home-manager) ~/.config/sops/age/keys.txt GitHub SSH key, taskchampion, Obsidian API key, etc.

After a wipe, the SSH host key changes, breaking system-level decryption. You must re-encrypt secrets.yaml for the new key before switching.

Phase 1 — From an authorized host (e.g. mokou)#

1. Get the new host age key#

On the wiped machine, once nix is available:

nix shell nixpkgs#ssh-to-age --command \
  sh -c 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'

2. Update .sops.yaml#

keys:
  - &hosts:
    # ...
    - &myhostname age1<new-key-from-step-1>

3. Re-encrypt secrets.yaml#

sops updatekeys secrets.yaml   # review the key diff, confirm with y
git add .sops.yaml secrets.yaml
git commit -m "Update <hostname> sops key after machine wipe"
git push

Phase 2 — On the wiped machine#

git pull
nh os switch .# -H <hostname>       # NixOS
# nh darwin switch .# -H <hostname>  # Darwin

sops-nix activates using the SSH host key and places all system secrets, including the borg credentials at /etc/borgmatic/key and /etc/borgmatic/pass.

Borg is now accessible — proceed with file extraction above.

Generating a new user sops key (if old key unrecoverable)#

On the wiped machine:

mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
# Note the public key: age1...

On an authorized host, update .sops.yaml:

keys:
  - &users:
    - &username-hostname age1<new-public-key>
sops updatekeys secrets.yaml
git add .sops.yaml secrets.yaml
git commit -m "Rotate <user>-<hostname> user sops key"
git push

Back on the wiped machine:

git pull
nh os switch .# -H <hostname>

Troubleshooting#

sops activation fails after switch#

git log --oneline -3                    # confirm updatekeys commit is present
sops --decrypt secrets.yaml | head -5   # test decryption directly

Borg credentials missing after activation#

ls -la /etc/borgmatic/key /etc/borgmatic/pass

If missing, sops activation silently failed. Check that tsunaminoai.borg.enable is set and that borgmatic/<hostname>/key and borgmatic/<hostname>/passphrase exist in secrets.yaml.

borgmatic: no archives found#

The host may not have backed up yet (new repo) or the repo wasn’t initialized. Check /var/log/borgmatic.log and look for the init-borg-repo systemd unit:

systemctl status init-borg-repo
journalctl -u borgmatic

borg: Permission denied on key#

The SSH key at /etc/borgmatic/key is 0400 root:root. Run borg as root, or copy the key to a temp path with user-readable permissions for one-off access:

sudo cp /etc/borgmatic/key /tmp/borg-key && chmod 400 /tmp/borg-key
export BORG_RSH="ssh -i /tmp/borg-key"