Commit e8e953dad408

Vincent Demeester <vincent@sbr.pm>
2026-06-29 11:00:00
docs: add Alpine diskless appliance README
Documented architecture, usage, build/deploy/flash workflow, secrets handling, and update strategy for the RPi 4 Alpine diskless appliance setup.
1 parent c6f5219
Changed files (1)
alpine/README.md
@@ -0,0 +1,84 @@
+# Alpine Diskless Appliances
+
+Alpine Linux diskless appliances for Raspberry Pi 4 homelab servers. The entire
+OS runs from an initramfs in RAM — the SD card is read-only after boot.
+
+## Hosts
+
+| Host | Role | Packages |
+|------|------|----------|
+| **athena** | DNS (BIND), WireGuard | `bind`, `wireguard-tools` |
+| **demeter** | DNS, MQTT | `bind`, `mosquitto`, `wireguard-tools` |
+| **aix** | File/backup server | `wireguard-tools` |
+
+## Architecture
+
+```
+alpine/
+├── common/              # Shared across all hosts
+│   ├── packages         # Base package list
+│   ├── setup.sh         # Common OpenRC setup (runlevels, users)
+│   └── root/            # Root filesystem skeleton (sshd, chrony, etc.)
+├── hosts/<hostname>/    # Per-host overrides
+│   ├── packages         # Additional packages
+│   ├── setup.sh         # Host-specific services
+│   └── root/            # Host-specific config files
+├── generated/           # Nix-generated configs (gitignored)
+│   ├── zones/           # DNS zone files from globals.nix
+│   └── wireguard/       # WireGuard configs per host
+├── images/              # Build output (gitignored)
+├── build.sh             # Build initramfs in container (aarch64)
+├── build-inner.sh       # Runs inside the Alpine container
+├── deploy.sh            # rsync update to running host
+├── flash.sh             # Flash SD card (partition + copy)
+└── test.sh              # QEMU testing
+```
+
+## Usage
+
+All commands via the top-level `Makefile`:
+
+```bash
+# Generate DNS zones + WireGuard configs from Nix expressions
+make alpine/generate
+
+# Build a host image (runs generate first)
+make alpine/build/athena
+
+# Build all hosts
+make alpine/all
+
+# Test in QEMU
+make alpine/test/athena
+make alpine/validate/athena
+make alpine/stop/athena
+
+# Deploy update to running host (rsync, needs reboot)
+make alpine/deploy/athena
+
+# Flash a new SD card
+make alpine/flash/athena DEVICE=/dev/sda
+```
+
+## How It Works
+
+1. **Generate** — Nix evaluates `globals.nix` to produce DNS zone files and
+   WireGuard configs (symlinked into `generated/`)
+2. **Build** — A container (podman/docker) runs Alpine aarch64 via QEMU
+   user-mode, installs packages into a rootfs, runs setup scripts, then packs
+   everything into a cpio+gzip initramfs
+3. **Boot** — RPi firmware loads U-Boot → kernel + initramfs → entire OS in RAM
+4. **Persist** — A small ext4 partition (`label=persist`) holds SSH host keys
+   and WireGuard private keys, mounted at boot by the `persist-keys` init script
+
+## Secrets
+
+Secrets (`.age` files) are decrypted at boot by the `secrets` OpenRC init
+script using the host's SSH ed25519 key. Same encrypted files as the NixOS
+agenix setup, same keys.
+
+## Updating
+
+- Rebuild image: `make alpine/build/<host>`
+- Deploy: `make alpine/deploy/<host>` (rsync + reboot)
+- Rollback: previous initramfs saved as `initramfs.prev` on the boot partition