Files
homey/TODO.org
T
2026-04-26 00:09:52 +03:00

12 KiB

Homey NixOS Port — Outstanding Tasks

Secrets Setup

DONE Configure sops recipients in secrets/.sops.yaml

GPG encryption subkey 076AA297579A0064 is already configured in .sops.yaml. The Pi's age key will be added post-boot (see Deployment section).

DONE Populate secrets/secrets.yaml with real values

Every key in secrets/secrets.yaml needs a real value. Fill in each row below.

* Recovered from k8s backup

Key Source k8s secret Value
openldap/admin_password openldap-admin lfQWQgBZporyJT4xFSJTnu4vQMC7UevW
openldap/config_password openldap-config ZxlWbDAeHLdHi5lgdxmyZOWzsG3qDgrT
openldap/ro_password openldap-ro CZ7JLn23vSzhVjNW7UHGZ2YLFJPDLGsF
gitea/admin_password gitea-admin-pass y5kCPeCP1e1sCzahd7QmLyJqdQvd37ek
nextcloud/postgres_password nextcloud-postgres-pass hEq4zt1B1VKYtVAoiKYDmswcUmTbknSP
authelia/jwt_secret jwt-secret YJZBnQCD4OmhJkgdr6kksmMCatrKLCl3
authelia/storage_encryption_key fek-secret KYWRYApCWWIN60gpSi7jhLuj1Wcm5z9Q

* Needs generation or manual creation

Key Action
authelia/session_secret Generate fresh (64 random chars)
gitea/lfs_jwt_secret Generate fresh (43-char base64url)
gitea/oauth2_jwt_secret Generate fresh (43-char base64url)
gitea/internal_token Generate fresh (100-char alphanumeric)
restic/password Generate fresh (passphrase)
nextcloud/admin_password NOT in k8s backup; try old value or reset later
cloudflare/api_token Create DNS Edit token in Cloudflare dashboard
cloudflare/tunnel_token Create tunnel in Cloudflare Zero Trust dashboard
restic/s3_access_key_id Needs S3 provider credentials
restic/s3_secret_access_key Needs S3 provider credentials

Generate random secrets with:

# 64-char hex string
openssl rand -hex 32

# base64url (for gitea tokens)
openssl rand -base64 32 | tr '+/' '-_' | tr -d '='

# 100-char alphanumeric (gitea internal token)
openssl rand -base64 75 | tr -dc 'A-Za-z0-9' | head -c 100

DONE Encrypt secrets/secrets.yaml with sops

Encrypted with PGP key 076AA297579A0064. Safe to commit.

Pi Hardware Setup

DONE Fill in real values in hosts/pi-main/default.nix

  • users.users.admin.openssh.authorizedKeys.keys — SSH public key added
  • homey.storage.device/dev/disk/by-id/usb-WD_Ext_HDD_1021_5743415A4146313531393031-0:0-part1
  • homey.backup.repositorys3:https://s3.us-east-005.backblazeb2.com/zakobar-home-backup

DONE Integrate nixos-raspberrypi flake

Replaced nixos-hardware with nixos-raspberrypi for vendor kernel, firmware, u-boot bootloader, and binary cache. Both pi-main and pi-main-bootstrap now use nixos-raspberrypi.lib.nixosSystem and raspberry-pi-4.base. nix flake check passes.

TODO Verify SD card partition labels in hosts/pi-main/hardware.nix

The config assumes labels NIXOS_SD (root) and FIRMWARE (boot). After flashing, check with:

lsblk -o NAME,LABEL

Update fileSystems entries in hosts/pi-main/hardware.nix if they differ.

Caddy Build

TODO Fix vendorHash in modules/caddy.nix

The Caddy build with the Cloudflare DNS plugin currently uses lib.fakeHash as a placeholder. After the first nix build attempt it will fail with the correct hash in the error message. Replace lib.fakeHash with that value.

Cloudflare Setup

DONE Create Cloudflare Tunnel

  1. Go to Cloudflare Zero Trust dashboard → Networks → Tunnels → Create tunnel
  2. Name it (e.g. homey)
  3. Copy the tunnel token into secrets/secrets.yaml under cloudflare/tunnel_token
  4. Configure public hostnames for each service (see service/URL table in AGENTS.md)

DONE Create Cloudflare DNS API token

  1. Cloudflare dashboard → My Profile → API Tokens → Create Token
  2. Use the "Edit zone DNS" template, scope to zakobar.com
  3. Copy into secrets/secrets.yaml under cloudflare/api_token

Deployment

TODO Phase 1 — Build and flash bootstrap SD card image

The bootstrap image is a minimal NixOS with SSH + WiFi only (no sops, no services). Its sole purpose is to boot the Pi so you can generate the age key and then deploy the full config remotely.

Build on workstation (cross-compiles for aarch64):

# Accept the nixos-raspberrypi cache config so pre-built kernel/firmware
# are fetched instead of compiled. First build still takes ~10-20 min.
nix build .#nixosConfigurations.pi-main-bootstrap.config.system.build.sdImage \
  --accept-flake-config

Flash to SD card (replace /dev/sdX with your card's device):

# Decompress and write in one step — avoids storing the raw image on disk
zstdcat result/sd-image/*.img.zst | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync
sudo sync

Insert the SD card into the Pi and power it on. It will connect to WiFi (Zakobar) with static IP 192.168.1.100.

Verify SSH access (wait ~60 s for first boot):

ssh admin@192.168.1.100

TODO Phase 2 — Generate age key and add it to sops

On the Pi (over SSH):

sudo mkdir -p /var/lib/sops-nix
sudo age-keygen -o /var/lib/sops-nix/key.txt
# Print the public key — copy this output to your workstation clipboard
sudo age-keygen -y /var/lib/sops-nix/key.txt

On the workstation — edit secrets/.sops.yaml, uncomment the age section and replace the placeholder with the public key you just copied:

key_groups:
  - pgp:
      - 076AA297579A0064
    age:
      - age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  # paste here

Re-encrypt secrets/secrets.yaml so the Pi's age key can decrypt it:

sops updatekeys secrets/secrets.yaml

Commit and push:

git add secrets/.sops.yaml secrets/secrets.yaml
git commit -m "add Pi age key to sops recipients"

TODO Phase 3 — Fix Caddy vendorHash, then deploy full config

The full pi-main config includes Caddy built with the Cloudflare DNS plugin. The first build will fail with the correct hash in the error output.

Attempt the build to get the hash:

nix build .#nixosConfigurations.pi-main.config.system.build.toplevel \
  --accept-flake-config 2>&1 | grep 'got:'

Copy the hash from the error message and replace lib.fakeHash in modules/caddy.nix, then commit:

git add modules/caddy.nix
git commit -m "fix caddy vendorHash"

Deploy to the Pi:

# Dry-run first — shows what will change without applying
nixos-rebuild dry-activate \
  --flake .#pi-main \
  --target-host admin@192.168.1.100 \
  --use-remote-sudo \
  --accept-flake-config

# Apply when happy with the diff
nixos-rebuild switch \
  --flake .#pi-main \
  --target-host admin@192.168.1.100 \
  --use-remote-sudo \
  --accept-flake-config

After a successful switch, subsequent deploys can use the hostname:

nixos-rebuild switch --flake .#pi-main --target-host admin@pi-main --use-remote-sudo

Post-Deployment Manual Steps

DONE Configure Gitea LDAP authentication

Admin → Site Administration → Authentication Sources → Add LDAP (via BindDN):

  • Host: openldap, Port: 389, Security: Unencrypted (containers talk via the homey podman network — use container name, not 127.0.0.1)
  • Bind DN: cn=readonly,dc=zakobar,dc=com
  • Bind Password: see openldap/ro_password in sops
  • User Search Base: ou=users,dc=zakobar,dc=com
  • User Filter: (&(objectClass=inetOrgPerson)(uid=%s))
  • Username attribute: uid
  • First name attribute: cn
  • Surname attribute: sn
  • Email attribute: mail

TODO Verify Nextcloud LDAP app configuration

After restoring the Nextcloud volume, check: Admin → LDAP/AD Integration — confirm the LDAP Users and Contacts app is configured. If reconfiguring from scratch, use the same settings as Gitea above but with Nextcloud's LDAP wizard:

  • Server: openldap, Port: 389 (container name on the homey network — not 127.0.0.1)
  • Bind DN: cn=readonly,dc=zakobar,dc=com
  • Bind Password: see openldap/ro_password in sops
  • Base DN: dc=zakobar,dc=com
  • Users: filter objectClass=inetOrgPerson, search base ou=users
  • Login attribute: uid
  • Email attribute: mail

TODO (Optional) Enable Jellyfin and Transmission

When ready, in hosts/pi-main/default.nix:

homey.jellyfin.enable    = true;
homey.transmission.enable = true;

Backup Strategy

TODO Configure S3-compatible automatic backup target

Update homey.backup.repository in hosts/pi-main/default.nix to point at your S3-compatible bucket (Backblaze B2, Wasabi, AWS S3, etc.):

homey.backup.repository = "s3:https://s3.us-west-002.backblazeb2.com/your-bucket-name";
# or for AWS:
# homey.backup.repository = "s3:s3.amazonaws.com/your-bucket-name";

Add the S3 credentials to secrets/secrets.yaml:

restic/s3_access_key_id: "YOUR_KEY_ID"
restic/s3_secret_access_key: "YOUR_SECRET_KEY"

Then wire them into modules/backup.nix via environment variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (restic reads these natively).

The existing daily schedule + prune retention in modules/backup.nix will handle the rest automatically.

TODO Write manual offload script (scripts/offload-backup.sh)

A standalone script for copying backup data to an external disk — either plugged directly into the Pi or mounted on your workstation.

Design:

  • Accepts a --target argument: a local path to the mounted disk (e.g. /media/aner/backup-disk or /mnt/usb)
  • Uses restic copy to clone snapshots from the S3 repo into a local restic repo on the target disk (deduplication is preserved, no double storage)
  • Alternatively can use rsync for a plain directory copy if restic is not available on the target machine
  • Should be runnable from either the Pi or a workstation (with the Pi's data disk mounted or accessible over SSH)

Example invocation:

# On the Pi, with USB disk mounted at /mnt/usb:
./scripts/offload-backup.sh --target /mnt/usb/homey-backup

# On workstation, with Pi data disk mounted locally:
./scripts/offload-backup.sh --target /media/aner/backup-disk/homey-backup

This script does not exist yet — needs to be written.

Future

TODO Add second machine (pi-secondary)

When ready:

  1. Create hosts/pi-secondary/ directory with default.nix and hardware.nix
  2. Uncomment the pi-secondary entry in flake.nix
  3. Services communicating cross-machine should reference the primary Pi's LAN IP instead of 127.0.0.1