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
+38 -54
View File
@@ -13,31 +13,22 @@
# /var/lib/luna-sites/live/<name>.caddy root-owned, imported by caddy
# /opt/data/sites-status.txt what was accepted, and why not
#
# Why a registry of {name, port} instead of letting her drop Caddyfile
# snippets: a snippet can proxy to anything on this box (the dashboard on
# 9119, the webhook listener on 8644, node-exporter) or file_server anything
# caddy can read, and one syntax error keeps caddy from coming up on the next
# boot. The generator only ever emits one fixed shape from a validated name
# and a port inside portMin..portMax, so none of that is expressible.
# A registry of {name, port}, not raw Caddyfile snippets from her: a snippet
# could proxy to anything on the box or break caddy on the next boot, while
# the generator only ever emits one validated shape.
#
# Why paths, not <name>.mars.sol: mars has no fixed DHCP lease, and a wildcard
# needs one. `address=/…/` takes an IP, and pihole-FTL's dnsmasq skips
# wildcard --cname entries outside authoritative zones (cache_reload():
# `if (a->alias[1] != '*' …)`). Moving to subdomains later only changes the
# fragment the generator writes; the registry format stays.
# Paths, not <name>.mars.sol: mars has no fixed DHCP lease, and pihole-FTL's
# dnsmasq can't wildcard-CNAME without one.
#
# Why a podman socket instead of ssh: what she needs is long-running processes
# OUTSIDE her own container (anything started inside it dies with the
# container, and sits next to her Telegram/gitea tokens). The socket gives
# exactly that and no host shell. It is not a strong boundary on its own —
# rootless podman socket access is code execution as luna-apps, which can read
# whatever that user can — but luna-apps owns nothing and cannot enter
# /var/lib/hermes (0750 root:hermes), so the apps cannot reach her tokens.
# A podman socket, not ssh: gives her long-running processes outside her own
# container (which dies on restart and holds her tokens) with no host shell.
# It's not a strong boundary by itself — socket access is code execution as
# luna-apps — but luna-apps can't enter /var/lib/hermes (0750 root:hermes), so
# her apps can't reach her tokens.
#
# She learns all this from a read-only README mounted at
# /opt/data/sites-README.md (luna-sites-README.md). She self-manages her
# memories, so nothing in this file reaches her otherwise — see the dropped
# repo clone in hermes-agent.nix's header for what happens when it doesn't.
# /opt/data/sites-README.md (luna-sites-README.md) — she self-manages her own
# memory, so nothing else in this file reaches her.
#
# VM test: nix build .#checks.x86_64-linux.luna-sites -L (luna-sites-test.nix)
let
@@ -77,29 +68,26 @@ in
isNormalUser = true;
inherit uid;
description = "luna's hosted web apps (rootless podman)";
# Nothing ever logs in as this user. Only its systemd user manager runs,
# kept up without a session by linger, which is what brings the podman
# socket and podman-restart back after a reboot.
# No interactive login; linger keeps its systemd user manager (and thus
# the podman socket) running across reboots without a session.
linger = true;
autoSubUidGidRange = true; # rootless podman's user namespace
hashedPassword = "!";
shell = "${pkgs.shadow}/bin/nologin";
};
# `--restart=always` containers only come back after a reboot through this
# unit — rootless podman has no daemon to remember them. The podman module
# already enables podman.socket for every user's manager; this one is
# scoped to luna-apps.
# Rootless podman has no daemon to bring `--restart=always` containers back
# after a reboot; the podman module enables this for every user, scoped
# here to luna-apps.
systemd.user.services.podman-restart = {
wantedBy = [ "default.target" ];
unitConfig.ConditionUser = user;
};
# ---- the socket luna's container talks to ----
# luna-apps's own socket lives under /run/user/1001 (0700), which the
# container's uid cannot enter. This re-exposes it to group hermes, and the
# proxy behind it runs as luna-apps, so it holds no access beyond the socket
# it forwards to.
# luna-apps's own socket lives under /run/user/1001 (0700), unreachable to
# the container's uid; this re-exposes it to group hermes via a proxy that
# itself runs as luna-apps, so it holds no more access than the socket.
systemd.sockets.luna-apps-podman = {
wantedBy = [ "sockets.target" ];
listenStreams = [ "${socketDir}/podman.sock" ];
@@ -124,9 +112,9 @@ in
# Merges into hermes-agent.nix's container definition.
virtualisation.oci-containers.containers.hermes-agent = {
volumes = [
# The directory, not the socket file: the socket is created by systemd
# at boot, and a file bind mount would pin whatever inode was there when
# the container started. Read-only still permits connect().
# Mounts the directory, not the socket file — a file bind mount would
# pin the inode present at container start, before systemd creates the
# socket. Read-only still permits connect().
"${socketDir}:${socketDir}:ro"
"${config.virtualisation.podman.package}/bin/podman:/usr/local/bin/podman:ro"
"${readme}:/opt/data/sites-README.md:ro"
@@ -163,16 +151,13 @@ in
description = "Turn luna's site registry into caddy routes";
# Also runs once at boot, for edits made while nothing was watching.
wantedBy = [ "multi-user.target" ];
# After caddy, so the reload below never races caddy's own start. Nothing
# orders caddy after THIS unit, which is what keeps the blocking
# `systemctl reload caddy` from waiting on its own start job.
# After caddy, so the reload below can't race caddy's own start; nothing
# orders caddy after this unit, so that reload never waits on its own.
after = [ "caddy.service" ];
# No start rate limit. The default (5 starts in 10s) is hit by nothing
# more than a handful of quick writes — the VM test does exactly that —
# and when it is, systemd also fails luna-sites.path for good
# (unit-start-limit-hit): every later registration is silently ignored
# until someone runs reset-failed. Bursts are absorbed by the debounce at
# the top of the script instead.
# No start rate limit: the default (5/10s) trips from just a handful of
# quick writes and permanently disables luna-sites.path (unit-start-
# limit-hit) until someone runs reset-failed. Bursts are absorbed by the
# script's own debounce instead.
startLimitIntervalSec = 0;
path = [ pkgs.jq pkgs.util-linux pkgs.diffutils config.services.caddy.package ];
# caddy validate wants somewhere to write its data/config dirs.
@@ -195,9 +180,9 @@ in
script = ''
set -euo pipefail
# Everything that touches luna's tree runs as the container's uid, never
# as root: she controls every path under it, including swapping one for
# a symlink into /etc between a check here and its use.
# Runs as the container's uid, never root she controls every path
# under it, including swapping one for a symlink between a check here
# and its use.
as_luna() { setpriv --reuid=${hermesUid} --regid=${hermesGid} --clear-groups -- "$@"; }
if [ ! -d ${hermesHome} ]; then
@@ -313,15 +298,14 @@ in
publish_report
}
# Debounce: writes usually come in bursts (several files, or an editor's
# write-then-rename), and every trigger that lands while this oneshot
# is still activating merges into this same start job instead of
# queuing another. One second collapses a burst into one run.
# Debounce: any trigger landing while this oneshot is still activating
# merges into the same start job, so one second collapses a burst of
# writes (several files, an editor's write-then-rename) into one run.
sleep 1
# That merging also means an entry written mid-run would otherwise wait
# for the next unrelated change. Compare the registry before and after,
# and go again. Bounded, so a writer in a loop cannot pin the unit.
# That same merging means an entry written mid-run would otherwise wait
# for the next unrelated trigger, so compare the registry before/after
# and rerun if it changed bounded, so a writer in a loop can't pin it.
for attempt in 1 2 3 4 5; do
before=$(entries)
generate