Aner Zakobar 0b73d493d8 Working NixOS port: all core services operational
- Fix Caddy cfProxy helper for cloudflared http:// vhosts (X-Forwarded-Proto)
- Fix Authelia LDAP bind (readonly user ACL + password sync)
- Add gitea-admin-setup oneshot service to survive rebuilds
- Update Authelia forward_auth with header_up X-Forwarded-Proto https
- Update TODO.org with completed tasks and LDAP config details
- Remove old Helm/k8s artifacts (Chart.yaml, templates/, values/, scripts)
- Add result to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 14:46:21 +03:00

Homey

A home environment for everyone!

NixOS Deployment (active branch: nixos-port)

Prerequisites

Before building, make sure the following are set in the repo:

  • hosts/pi-main/default.nix — SSH public key, static IP, WiFi SSID
  • secrets/secrets.yaml — all secrets populated and sops-encrypted
  • WiFi password secret formatted as wifi_psk=YourPassword (see below)

Adding / updating secrets

sops secrets/secrets.yaml

Opens your editor with the decrypted file. Save and quit to re-encrypt.

The WiFi password entry must use the wifi_psk= prefix so wpa_supplicant can look up the value by name:

wifi/psk: "wifi_psk=YourActualWifiPassword"

Phase 1 — Bootstrap image (flash this first)

The full pi-main config requires sops secrets, which require an age key on the Pi — but the age key doesn't exist until after first boot. To break the chicken-and-egg problem, flash a minimal bootstrap image first.

Before building, fill in the WiFi password in flake.nix in the pi-main-bootstrap config (search for WIFI_PASSWORD_HERE):

networks."Zakobar".psk = "your-actual-wifi-password";

Build the bootstrap SD image (requires aarch64-linux build capability — either boot.binfmt.emulatedSystems = ["aarch64-linux"] on your workstation, or an aarch64 remote builder):

nix build .#nixosConfigurations.pi-main-bootstrap.config.system.build.sdImage \
  --system aarch64-linux

Find your SD card device, then flash (double-check /dev/sdX!):

lsblk

zstdcat result/sd-image/nixos-sd-image-*.img.zst | \
  sudo dd of=/dev/sdX bs=4M status=progress conv=fsync

The Pi will boot at 192.168.1.100, connect to Zakobar WiFi, and accept SSH connections with your key. No services run yet.

Phase 2 — Generate age key and re-encrypt secrets

# SSH into the Pi
ssh admin@192.168.1.100

# Generate the age key
sudo age-keygen -o /var/lib/sops-nix/key.txt

# Print the public key — copy it
sudo age-keygen -y /var/lib/sops-nix/key.txt

Back on your workstation, add the public key to secrets/.sops.yaml alongside the existing PGP key:

keys:
  - &pi_main age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
creation_rules:
  - path_regex: secrets/secrets.yaml$
    key_groups:
      - pgp:
          - 076AA297579A0064
        age:
          - *pi_main

Then re-encrypt so the Pi can decrypt its own secrets:

sops updatekeys secrets/secrets.yaml

Phase 3 — Deploy the full config

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

The Pi builds its own config natively (no cross-compilation). sops-nix will now decrypt all secrets and start all services.

Caddy plugin hash

The first deploy will fail at the Caddy build step because lib.fakeHash is a placeholder. Copy the correct hash from the error output and replace it in modules/caddy.nix:

caddyWithCloudflare = pkgs.caddy.withPlugins {
  plugins = [ "github.com/caddy-dns/cloudflare@..." ];
  hash = "sha256-REPLACE_WITH_REAL_HASH=";  # ← paste here
};

Then re-run the deploy command from Phase 3.

Ongoing deploys from workstation

All future config changes follow the same pattern:

  1. Edit files on workstation
  2. Run:
nixos-rebuild switch \
  --flake .#pi-main \
  --target-host admin@192.168.1.100 \
  --build-host admin@192.168.1.100 \
  --use-remote-sudo

NixOS activates the new config on the Pi immediately, with an automatic rollback if activation fails.

Installation (legacy Helm)

Install using

helm upgrade --install homey . -n homey

Backing up

Backups use restic and run automatically via systemd on a daily schedule.

Strategy — two tiers

  1. Primary (automatic): Daily backup to an S3-compatible bucket (Backblaze B2, Wasabi, AWS S3, etc.). Restic deduplicates and encrypts before upload. Retention: 7 daily, 4 weekly, 6 monthly snapshots.
  2. Offload (manual): Run scripts/offload-backup.sh --target /path/to/disk to clone snapshots from the S3 repo onto a local disk (USB plugged into the Pi, or a disk on your workstation). Uses restic copy so deduplication is preserved on the target.

What is backed up

All service data under /mnt/data/:

  • openldap/ — LDAP database and config
  • authelia/ — Authelia config and state
  • gitea/ — Gitea repositories and data
  • nextcloud/ — Nextcloud files + a pg_dump of the database
  • jellyfin/ — Jellyfin metadata (media files are excluded — re-downloadable)
  • transmission/ — Torrent client config

Nextcloud is placed into maintenance mode and postgres is pg_dump'd before each backup to ensure a consistent snapshot.

Configuration

Repository URL and credentials are set per-host:

# hosts/pi-main/default.nix
homey.backup.repository = "s3:https://s3.us-west-002.backblazeb2.com/your-bucket";

S3 credentials live in secrets/secrets.yaml as restic/s3_access_key_id and restic/s3_secret_access_key.

Restore

# List snapshots
restic -r s3:https://... snapshots

# Restore latest snapshot to /mnt/data
restic -r s3:https://... restore latest --target /mnt/data

# Restore a single service
restic -r s3:https://... restore latest --target /mnt/data --include /mnt/data/gitea

LDAP Configuration

Logins are done to PHPLDAPADMIN

DN is like:

cn=admin,dc=,dc=io get-secret-val.sh homey openldap-admin password

First thing we do is create an organization unit called users

To add a new user, we create a child entry to ou=users

It has to be of type inetOrgPerson

cn = Common Name, sn = Sur Name. Select RDN = User Name (uid) (FROM DROP DOWN MENU) UID = USERNAME, that is what is important. (In PHPLdapAdmin it is under User Name)

Now we may continue!

GITEA

Site Title: whatever

SSH Server Domain: git.<YOUR URL> SSH Server Port: 2222 Gitea Base URL: http://git.<YOUR URL>

Then add Administrator Account Settings:

Administrator Username: gitea-admin Password: from gitea-admin-pass Email address must be populated

That will work after a few minutes.

Now we go into Authentication Sources

Add a new LDAP Authentication source

Authentication name: Home LDAP Host: openldap Port: 389 Bind DN = cn=readonly,dc=,dc=io Bind Password: openldap-ro password User Search Base: ou=users,dc=,dc=io user search filter = (uid=%s) Admin filter (title=admin) Username Attribute: uid First Name Attribute: cn Surname Attribute: sn Email Attribute: mail

NEXTCLOUD

I ran THIS command inside su www-data -s /bin/bash -c php occ ldap:promote-group "admins"

When maintenence mode

#+begin_example kubectl exec tty stdin -n homey deploy/nextcloud su -l www-data -s /bin/bash php /var/www/html/occ maintenance:mode off

#+end_src

I UNDERSTAND

I need to backup Chen's stuff And… I need to Jellyfin

S
Description
No description provided
Readme 514 KiB
Languages
Nix 97.7%
Shell 2.3%