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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user