docs: condense comments across the repo

Comments had drifted into multi-paragraph narrative (git commit
lineage, debugging stories, restated code) in several hot spots
(scripts/deploy, hermes-agent.nix, flake.nix, gitea.nix, headscale.nix).
Trim every comment to its load-bearing "why" — gotchas, safety
warnings, and non-obvious rationale survive verbatim in substance,
just tightened to 1-2 sentences; historical narrative and anything
already covered in CLAUDE.md is cut. No code/logic changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UJqEmY1y3AYX3JoX4Y6b21
This commit is contained in:
2026-09-18 21:36:30 +02:00
co-authored by Claude Sonnet 5
parent 3899290c5b
commit 6f24ab69ad
47 changed files with 1051 additions and 1965 deletions
+88 -152
View File
@@ -31,39 +31,32 @@
url = "github:strangeglyph/mediamanager-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# livesync-bridge — headless CouchDB <-> filesystem sync for Obsidian
# LiveSync, used on mars to give luna a real directory of markdown
# (hosts/mars/livesync-bridge.nix). Not a flake and not in nixpkgs, so it
# comes in as plain source pinned by flake.lock; the service copies it out
# and runs it under deno. Pinning matters more than usual here — this is a
# small third-party project with open bugs on the storage->couchdb path,
# so an unreviewed bump could quietly change how the agent's notes are
# written back.
# Headless CouchDB<->filesystem sync for Obsidian LiveSync
# (hosts/mars/livesync-bridge.nix); not a flake/not in nixpkgs, so plain
# source pinned by flake.lock. Pin carefully — it's a small third-party
# project with open storage->couchdb bugs, so an unreviewed bump could
# silently change how notes get written back.
livesync-bridge = {
url = "github:vrtmrz/livesync-bridge";
flake = false;
};
authentik-nix.url = "github:nix-community/authentik-nix";
nix-flatpak.url = "github:gmodena/nix-flatpak";
# Own Hyprland plugin (border + title bar), public repo, fetched over
# https (no credentials needed, unlike tome below). `nixpkgs.follows` is
# what makes its packaged build ABI-correct — Hyprland plugins are
# ABI-locked to the exact Hyprland build they load into, so it has to be
# built against THIS flake's own nixpkgs, not whatever hypr-chrome's own
# flake.lock happens to pin standalone.
# Own Hyprland plugin (border + title bar), public repo over https.
# `nixpkgs.follows` is required since Hyprland plugins are ABI-locked to
# the exact Hyprland build — it must share this flake's nixpkgs, not
# whatever hypr-chrome's own lock pins standalone.
hypr-chrome = {
url = "git+https://git.mgaction.town/darman/hypr-chrome.git";
inputs.nixpkgs.follows = "nixpkgs";
};
# Tome (formerly AudibleLibrary) — darman's own .NET/Photino desktop app.
# Private repo on our own gitea; fetched over ssh with darman's ambient key,
# same as any other git flake input. `flake = false`: it's a plain source
# tree, not itself a flake. See pkgs/tome.nix.
# Private repo on our own gitea, fetched over ssh with darman's ambient
# key; plain source tree (`flake = false`), see pkgs/tome.nix.
#
# NOTE: the credential-less installer-iso can't fetch this (git+ssh needs
# darman's key), so `./scripts/deploy install terra localhost` will fail
# at nixos-install (post-disko) while this input is present. Known
# tradeoff — re-removed this once before (4f79ec7) for the same reason.
# NOTE: the credential-less installer-iso can't fetch this, so
# `./scripts/deploy install terra localhost` fails at nixos-install
# (post-disko) while this input is present — a known tradeoff.
tome = {
url = "git+ssh://gitea@git.mgaction.town:2222/darman/TOME.git";
flake = false;
@@ -136,10 +129,9 @@
];
};
# mercury — Raspberry Pi 3B+ (aarch64), DNS/DHCP. Boots from an SD image:
# mercury — Raspberry Pi 3B+ (aarch64), DNS/DHCP; SD image via:
# nix build .#nixosConfigurations.mercury.config.system.build.sdImage
# (aarch64 build — needs binfmt/qemu on this x86 host, or a remote/aarch64
# builder; substitutes most paths from cache.nixos.org.)
# Needs binfmt/qemu for the aarch64 build on this x86 host (or a remote aarch64 builder).
mercury = nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
specialArgs = { inherit inputs; };
@@ -209,16 +201,12 @@
];
};
# Bootable USB recovery installer with our SSH key + sshd + DHCP. Clones
# the (now public) homelab repo fresh at every boot to /root/homelab
# always current master, so the same USB stick stays useful across
# install/rescue occasions without ever needing a rebuild. No
# rsync/copy-the-repo-over step: boot it, ssh in,
# `cd /root/homelab && ./scripts/deploy install ...`.
# Reusable for any host's manual-USB install path (jupiter, terra, ...).
# Build the ISO:
# nix build .#nixosConfigurations.installer-iso.config.system.build.isoImage
# dd it to a USB stick, boot the target from it, SSH in, ./deploy install.
# Bootable USB recovery installer with our SSH key + sshd + DHCP; clones
# the public homelab repo fresh at every boot to /root/homelab, so the
# same stick stays current without a rebuild. Reusable for any host's
# manual-USB install path.
# Build: nix build .#nixosConfigurations.installer-iso.config.system.build.isoImage,
# dd to USB, boot the target, ssh in, ./scripts/deploy install ...
installer-iso = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
@@ -233,34 +221,18 @@
console.keyMap = "de"; # matches common.nix's real hosts
environment.systemPackages = [ pkgs.git ];
# findiso= is a SCRIPT-stage-1 feature (stage-1-init.sh) only. The
# systemd initrd — the default since 26.05 — has no findiso path
# at all: it mounts /iso straight from
# /dev/disk/by-label/<volumeID> (iso-image.nix), which only exists
# when the ISO is the physical boot medium. Booted as a kernel +
# initrd off the ESP with the iso as a plain file elsewhere, that
# label never appears and stage 1 times out into an emergency
# shell (mounts /sysroot fine, then fails /sysroot/nix/.ro-store).
# Script stage 1 instead loop-mounts the file findiso= points at
# and symlinks it to /dev/root — which is the whole mechanism this
# install path relies on. So force it off here.
# The systemd initrd (default since 26.05) has no findiso= path — only
# the legacy script stage-1 does — so this install method needs it off.
boot.initrd.systemd.enable = false;
# installation-cd-minimal leaves experimental-features unset, so
# the ISO's nix.conf has no `nix-command`/`flakes` at all (unlike
# the nixos-images kexec installer, which sets
# extra-experimental-features itself — which is why the same
# `install <config> localhost` branch works after kexec-local but
# not here). Without this, both `nix run .#disko` and
# `nixos-install --flake` die with "experimental Nix feature
# 'nix-command' is disabled".
# installation-cd-minimal ships with experimental-features unset;
# without this, both `nix run .#disko` and `nixos-install --flake`
# die with "experimental Nix feature 'nix-command' is disabled".
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Fresh clone of a PUBLIC repo no credentials baked into the
# ISO. require_tracked() in scripts/deploy still works fine here
# (this IS a real git checkout, unlike the old baked-`self`
# approach), but retry manually with `systemctl restart
# homelab-checkout` if DHCP was still coming up at boot.
# Fresh clone of the public repo (no credentials baked in) so
# scripts/deploy's require_tracked() sees a real checkout; retry
# with `systemctl restart homelab-checkout` if DHCP wasn't up yet.
systemd.services.homelab-checkout = {
description = "Clone the homelab repo to /root/homelab";
after = [ "network-online.target" ];
@@ -277,34 +249,24 @@
'';
};
# Finishes a local_install_prepare_and_reboot() run (scripts/deploy)
# unattended: that function stages this ISO, points a systemd-boot
# one-shot entry at it with `homelab.install=<config>` on the kernel
# cmdline, and reboots. Once booted here, this re-runs the exact same
# `./scripts/deploy install <config> localhost` command — now genuinely
# inside the installer (hostname homelab-installer), so is_live_installer
# takes the disko+nixos-install branch instead of preparing again.
# A manual boot of this ISO with no such cmdline param is a no-op.
# Completes an unattended local_install_prepare_and_reboot() run:
# re-runs `./scripts/deploy install <config> localhost`, now genuinely
# inside the installer so it takes the disko+nixos-install branch.
# No-op if homelab.install= isn't on the kernel cmdline.
systemd.services.homelab-auto-install = {
description = "Auto-run the homelab install if homelab.install= was passed on the kernel cmdline";
after = [ "homelab-checkout.service" ];
requires = [ "homelab-checkout.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig.Type = "oneshot";
# Full system PATH, not the restricted default a `path = [...]`
# produces: this unit execs `./scripts/deploy`, whose
# `#!/usr/bin/env bash` needs bash, and which then reaches for
# nix / nixos-install / git / sudo / efibootmgr. The default
# service PATH gave "env: 'bash': No such file or directory"
# (status 127) before the script even started.
# /run/current-system/sw/bin carries all of it on the installer;
# /run/wrappers/bin for sudo. mkForce because NixOS otherwise
# derives environment.PATH from `path` and that line would win.
# Needs the full system PATH: scripts/deploy execs bash then shells
# out to nix/nixos-install/git/sudo/efibootmgr, none of which a
# restricted `path = [...]` PATH provides. mkForce overrides NixOS's
# default PATH derivation from `path`.
#
# HOME too: systemd sets no $HOME for a service without User=
# (systemd.exec(5): SetLoginEnvironment= defaults false), and
# scripts/deploy runs under `set -u`, so a bare $HOME aborted the
# whole run with an "unbound variable" that read like a bug.
# HOME too: systemd sets no $HOME without User= (SetLoginEnvironment=
# defaults false), and scripts/deploy runs under `set -u`, so a
# missing $HOME aborted with a confusing "unbound variable".
environment = {
HOME = "/root";
PATH = lib.mkForce "/run/current-system/sw/bin:/run/wrappers/bin";
@@ -316,17 +278,11 @@
exit 0
fi
# Persist this whole run to a file that OUTLIVES the install.
# The systemd journal is on the installer's tmpfs and dies with
# the reboot, and by the time anything interesting fails disko
# has already wiped the OS disk so a failed attempt used to
# leave nothing to debug. local_install_prepare_and_reboot()
# (scripts/deploy) passes the STAGING partition's PARTUUID as
# homelab.logpart=; that partition holds the iso and is on a
# different disk from the one disko wipes, so it survives. The
# actual install runs inside do_install() below so one tee at
# the end captures all of it. Every step here is best-effort:
# logging must never be the thing that breaks an install.
# Persist this run to a file that outlives the install: the journal
# dies with the reboot and disko wipes the OS disk before a failure
# can be read back. homelab.logpart= points at the staging partition
# (survives the wipe); every step here is best-effort so logging
# itself can't break an install.
logfile=""
logpart=$(grep -o 'homelab\.logpart=[^ ]*' /proc/cmdline | cut -d= -f2 || true)
if [ -n "$logpart" ]; then
@@ -336,9 +292,8 @@
if mount -o rw "$dev" /run/homelab-log 2>/dev/null; then
logdir=/run/homelab-log
elif where=$(findmnt -fno TARGET "$dev" 2>/dev/null) && [ -n "$where" ]; then
# stage-1's findiso already holds this partition mounted
# (that is how it reached the iso) write into the existing
# mount rather than trying to stack a second one on it.
# stage-1's findiso already has this partition mounted (how it
# reached the iso) reuse that mount instead of a second one.
mount -o remount,rw "$where" 2>/dev/null || true
logdir="$where"
fi
@@ -358,12 +313,10 @@
fi
do_install() {
# The host key scripts/deploy seeds /etc/ssh with (so sops can
# decrypt on boot #1) cannot live in this ISO: it is built from
# a PUBLIC repo and the private keys are deliberately off-repo.
# local_install_prepare_and_reboot() therefore drops it on the
# boot partition and passes that partition's PARTUUID here.
# That copy dies with the disko wipe a few minutes later.
# The host key (so sops can decrypt on first boot) can't live in
# this public-repo ISO; local_install_prepare_and_reboot() drops it
# on the boot partition instead and passes that PARTUUID here the
# copy dies with disko's wipe minutes later.
keypart=$(grep -o 'homelab\.keypart=[^ ]*' /proc/cmdline | cut -d= -f2 || true)
if [ -n "$keypart" ]; then
mkdir -p /run/homelab-key
@@ -384,12 +337,10 @@
fi
fi
# On a box whose old bootloader had no one-shot (Limine on
# terra), scripts/deploy got us here via a temporary UEFI
# entry + BootNext (arm_efi_bootnext). BootNext is already
# spent, but the entry itself would linger in NVRAM pointing
# at a partition disko is about to reformat. Drop it now, so
# even an install that fails later leaves NVRAM clean.
# On bootloaders with no one-shot (Limine on terra), scripts/deploy
# got here via a temporary UEFI entry + BootNext (arm_efi_bootnext);
# BootNext is spent but the entry would linger pointing at a
# partition disko is about to wipe, so remove it now.
for n in $(efibootmgr 2>/dev/null \
| sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\?[[:space:]]Homelab Installer[[:space:]].*/\1/p'); do
echo "removing temporary UEFI entry Boot$n"
@@ -419,19 +370,11 @@
};
};
# VM test for `./scripts/deploy kexec-local`. Run:
# nix build .#checks.x86_64-linux.kexec-local -L
#
# Worth having because kexec-local is the one command that cannot be
# rehearsed on real hardware: it jumps the machine you are typing at, and
# a failure looks exactly like a slow boot. It regression-tests the
# subtle one — kexec-run.sh backgrounds `sleep 6 && kexec -e` and returns,
# so anything that cleans up the staging dir on exit deletes the binary
# that performs the jump and the box silently never leaves the old kernel.
#
# After the jump the test driver's backdoor is gone with the old kernel,
# so the installer is driven over a forwarded ssh port instead (the same
# approach nixos-images uses in its own kexec test).
# VM test for `./scripts/deploy kexec-local` (nix build .#checks.x86_64-linux.kexec-local -L)
# — the one command that can't be rehearsed on real hardware since it jumps
# the machine you're on. Regression-tests kexec-run.sh's backgrounded
# `sleep 6 && kexec -e`: cleaning up the staging dir on exit would delete
# the jump binary and the box would silently stay on the old kernel.
checks.${system} = {
kexec-local =
let
@@ -489,9 +432,9 @@
machine.succeed("install -Dm755 /etc/deploy /root/deploy")
# systemd-run starts units with a bare PATH that lacks
# /run/current-system/sw/bin, so `#!/usr/bin/env bash` cannot even
# resolve bash, let alone tar/findmnt/nohup. Set it explicitly.
# systemd-run starts units with a bare PATH lacking
# /run/current-system/sw/bin, so bash (and tar/findmnt/nohup)
# can't resolve set it explicitly.
env = (
" --setenv=PATH=/run/wrappers/bin:/run/current-system/sw/bin"
" --setenv=HOMELAB_KEXEC_TARBALL=${tarball}/nixos-kexec-installer-${system}.tar.gz"
@@ -513,9 +456,9 @@
while ssh(["true"], check=False).returncode != 0:
time.sleep(1)
# Refuses without --yes when stdin is not a tty (read gets EOF).
# Must reach the confirmation prompt, so it needs the same env
# otherwise it just dies early on the nix build and proves nothing.
# Refuses without --yes when stdin isn't a tty; needs the same env to
# reach the confirmation prompt, else it dies early on the nix build
# and proves nothing.
out = machine.fail(f"{envsh} /root/deploy kexec-local </dev/null 2>&1")
assert "using prebuilt kexec installer" in out, \
f"never reached the prompt, so the refusal proves nothing:\n{out}"
@@ -575,27 +518,23 @@
# `nix develop` — hot-reload loop for dotfiles/quickshell.
#
# hosts/terra/home.nix ships the shell via `xdg.configFile."quickshell"`,
# which COPIES the tree into the store, so ~/.config/quickshell is a
# read-only symlink into /nix/store and every QML tweak costs a
# nixos-rebuild. quickshell DOES hot-reload on file save — but only for
# the files it is watching, which are those frozen store copies. Pointing
# it at the working tree with `qs -p` restores edit-save-see, no rebuild.
# hosts/terra/home.nix ships the shell as a store copy (`xdg.configFile`),
# which only hot-reloads its own frozen files; pointing at the working
# tree with `qs -p` restores edit-save-see without a rebuild.
#
# quickshell keys instance identity on the CONFIG PATH, so a working-tree
# instance and the store-backed one are two different instances that would
# both map layer-shell bars onto every output. Hence a swap, not a second
# instance — and the swap starts dev FIRST, killing the packaged shell
# only once dev is confirmed up, so a QML error in the working tree leaves
# you on your normal bar instead of no bar at all.
# quickshell keys instance identity on the config path, so the
# working-tree and store-backed shells are different instances that
# would both claim every output — hence a swap, not a second instance.
# The swap starts dev first and only kills the packaged shell once dev
# is confirmed up, so a QML error leaves you on your normal bar.
#
# Every kill is scoped to one config (`qs kill` = default only, `qs kill
# -p` = that path only). A blanket kill would also take out unrelated
# quickshell instances pkgs/rishot.nix is one.
# Every kill is scoped to one config (`qs kill` = default, `qs kill -p
# <path>` = that path) since a blanket kill would also take out
# unrelated instances like pkgs/rishot.nix.
#
# Deliberately NOT wired to direnv (no .envrc in this repo): programs.direnv
# is enabled for this user, so a `use flake` would swap the running desktop
# shell on every `cd` into the checkout, including over ssh.
# Deliberately not wired to direnv: programs.direnv is enabled for this
# user, so a `use flake` would swap the desktop shell on every `cd`
# into the checkout, including over ssh.
devShells.${system}.default =
let
pkgs = nixpkgs.legacyPackages.${system};
@@ -652,10 +591,9 @@
echo "qs-dev: live on $cfg edits there now hot-reload"
'';
# qs log -f prints everything the instance logs; WARN and ERROR are the
# two that mean something is wrong with the QML in front of you. A
# binding loop or a failed binding is a WARN and easy to miss when it
# scrolls past inside a reload's worth of chatter.
# qs log -f prints everything the instance logs; WARN/ERROR are what
# mean something is actually wrong with the QML (a binding loop or
# failed binding is a WARN, easy to miss in the reload chatter).
qs-log = pkgs.writeShellScriptBin "qs-log" ''
set -uo pipefail
${preamble}
@@ -665,12 +603,10 @@
-a|--all) filter='.' ;;
esac
# -t 1: `qs log -f` replays the whole backlog first, which would dump
# every historical warning into the terminal on shell entry.
#
# `qs log -f` ends when the instance it attached to exits, and the dev
# shell outlives individual instances a QML error kills one, `qs-dev`
# starts another. Re-attach instead of going quiet for the session.
# -t 1: `qs log -f` otherwise replays the whole backlog on shell entry.
# It also ends when the attached instance exits, and the dev shell
# outlives individual instances (a QML error kills one, qs-dev starts
# another) so re-attach in a loop instead of going quiet for the session.
while :; do
if running "$cfg"; then
${qs} log -p "$cfg" -t 1 -f 2>/dev/null | ${grep} --line-buffered -E "$filter" >&2