Propagate this build's lessons into the scripts and every runbook

addCommunityMediaServer.sh asks for the diag hostname, DNS-checks it and passes it to --hosts, so a rebuild
sets it up like every other host instead of leaving it to be discovered missing.

HOSTNAME-CHANGE.md gains §D, adding a new host, which is where the hour went: --apply is stateless and reads
hostnames back out of the Caddyfile, so it silently omits a role that is not already there and the name ends
up with no certificate. README and the Phase 13 checklist now point at --full or --hosts instead of --apply,
and the plan's risks section carries the same warning plus the path-prefix cascade.

LOGGING.md collects the four traps this produced so none of them is rediscovered. FRESH-INSTALL gains an
ordered §7b and appendix M for the diag host, including that running without a webhook is correct rather than
half-configured; the old Afterwards section is relettered N.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-03 16:32:09 -06:00
parent 18d443579d
commit 6f77764fec
7 changed files with 95 additions and 16 deletions

View File

@@ -39,11 +39,13 @@ SYNC_CUR=$(python3 -c 'import configure as c; print(c.read_hosts().get("sync",""
SYNC=$(ask "Syncthing UI hostname" "${SYNC_CUR:-sync.$BASE}")
CLIPS_CUR=$(python3 -c 'import configure as c; print(c.read_hosts().get("clips",""))')
CLIPS=$(ask "Clips self-service hostname" "${CLIPS_CUR:-clips.$BASE}")
DIAG_CUR=$(python3 -c 'import configure as c; print(c.read_hosts().get("diag",""))')
DIAG=$(ask "Diagnostics hostname: logs, metrics, alerts" "${DIAG_CUR:-diag.$BASE}")
# ---- DNS sanity ----------------------------------------------------------------------------------
say "DNS check"
MAIN_IP=$(ip_of "$MAIN"); ok=1
for h in "$AUTH" "$WATCH" "$GIT" "$BOT" "$LIVE" "$FILES" "$SYNC" "$CLIPS"; do
for h in "$AUTH" "$WATCH" "$GIT" "$BOT" "$LIVE" "$FILES" "$SYNC" "$CLIPS" "$DIAG"; do
ip=$(ip_of "$h")
if [[ -z $ip ]]; then echo " $h: does not resolve yet"; ok=0
elif [[ $ip != "$MAIN_IP" ]]; then echo " $h -> $ip (main is $MAIN_IP) — points somewhere else"; ok=0
@@ -56,7 +58,7 @@ fi
# ---- write configs, .env, start containers -----------------------------------------------------
say "Applying (configure.py --hosts)"
VG_DRY=$DRY python3 configure.py --hosts "$AUTH" "$WATCH" "$GIT" "$BOT" "$LIVE" "$FILES" "$SYNC" "$CLIPS" | sed -n '/== writing/,$p' | grep -vE '^ (techy|diceofire|[a-z0-9_-]+) password' || true
VG_DRY=$DRY python3 configure.py --hosts "$AUTH" "$WATCH" "$GIT" "$BOT" "$LIVE" "$FILES" "$SYNC" "$CLIPS" "$DIAG" | sed -n '/== writing/,$p' | grep -vE '^ (techy|diceofire|[a-z0-9_-]+) password' || true
[[ $DRY == 1 ]] && { echo "(dry run: skipping container wait)"; exit 0; }
# ---- wait ---------------------------------------------------------------------------------------
@@ -156,14 +158,15 @@ PHASE 3 — Jellyfin (direct play only on this CPU)
Custom CSS: paste jellyfin/branding.css
-> Save; reload https://$WATCH/
AUTHENTIK FORWARD-AUTH (gates every Vanguard host: $MAIN, $FILES, $CLIPS, $SYNC, $LIVE, $BOT) — once:
AUTHENTIK FORWARD-AUTH (gates every Vanguard host: $MAIN, $FILES, $CLIPS, $SYNC, $LIVE, $DIAG, $BOT) — once:
1. Applications -> Outposts -> edit "authentik Embedded Outpost" -> config: authentik_host: https://$AUTH
2. Applications -> Create with provider -> Application "Vanguard sites"; Proxy Provider, authorization
flow implicit-consent, mode "Forward auth (domain level)", authentication URL https://$AUTH,
cookie domain ${BASE}, Invalidation flow default-provider-invalidation-flow (logout = full re-login)
-> then edit the Embedded Outpost -> Applications: add it -> Update
3. Per-host access is enforced by Caddy from X-Authentik-Groups: .env BOT_REQUIRE_GROUP=bot-users
(default), LIVE_REQUIRE_GROUP= (empty = any logged-in member). ./configure.py apply regenerates.
(default), LIVE_REQUIRE_GROUP= (empty = any logged-in member), DIAG_REQUIRE_GROUP=media-admins.
./configure.py apply regenerates.
4. Discord roles -> Authentik groups: Directory -> Groups: create bot-users (keep streamers/editors/admins);
Discord source -> Scopes: "guilds guilds.members.read"; Customization -> Policies -> Expression Policy
"discord-roles-to-groups" from authentik/discord-roles-to-groups.py (server id + role-id->group table);