immich's first start: cause measured, fixed in the catalog (R-732 closed); ISO clean-tree gate (R-730 closed)
gates / gates (push) Successful in 27s

- R-732: the first-start geodata import runs up to 9 concurrent 5000-row INSERTs; the database needs
  ~400 MB anon + ~170 MB touched shared_buffers (the image's FIXED 512MB, not host-RAM sizing). 512M fits
  only with swap (bench swap 0: 61-104 kills; 9202 swap 512 MiB: survived by swapping). Controls: swap
  alone, limit alone flip it; shared_buffers 128MB alone does not. Catalog 56c4888: v3.2.4 + 768M,
  proven with swap off on both venues. audits/immich-first-start-2026-09-30/A-cause.md.
- R-730: scripts/iso/build-felhom-iso.sh refuses an uncommitted/untracked/unpushed tree (no bypass),
  records repo-commit from the gate and iso-v<version>; test iso/test/clean-tree.sh, red-proof run
  (status check removed -> 2 of 4 cases fail -> restored).
- R-731 narrowed (gitea 28.0.0 GA; mariadb 13.0 a short-term Rolling line). R-676 note.
- New rows R-733 (bench has no swap, boxes 512 MiB), R-734 (immich .immich markers -> files_may_change).
- STATUS: the golden line corrected (no bake is due; 0.283.1 is the newest release). Register 364 -> 366.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-30 16:18:17 +02:00
parent 25cb3eb9c1
commit 2866f6a318
65 changed files with 49778 additions and 12 deletions
+11
View File
@@ -1,3 +1,14 @@
## ISO build refuses a dirty or unpushed tree (2026-09-30, R-730)
- `iso/build-felhom-iso.sh`: a clean-tree gate right after argument parsing — any uncommitted or untracked change, or
`HEAD` ≠ `origin/main`, refuses the build before anything is made (no bypass flag). Why: ISO 1.29.0's manifest names
`8d539f9`, but the published image carries a fix committed 35 minutes after the build, so no commit is that image.
- The manifest's `repo-commit` comes from the gate; `iso-version-tag` now reads `iso-v<version>` (the ISO's own line —
`installer-v*` is the host-install script's).
- New test `iso/test/clean-tree.sh` (4 cases, a throwaway repo through the `FELHOM_ISO_REPO` seam); red-proof run (the
status check deleted → 2 cases fail). `iso/test/rootpw-emission.sh` builds from a throwaway clean repo too.
- No image built or published; `SCRIPT_VERSION` and `ISO_VERSION` unchanged; 1.29.0 stays untagged.
## ISO 1.29.0 source — the box's own screen speaks English too (2026-09-18, R-559)
**Source only. The image is NOT built or published by this commit** — see the release note at the end.
+27 -2
View File
@@ -145,6 +145,31 @@ while [[ $# -gt 0 ]]; do
*) die "unknown argument: $1 (see --help)" ;;
esac
done
# ── Clean-tree gate (R-730, 2026-09-30) ────────────────────────────────────────────────────────────────
# The manifest names the commit an image was built from. ISO 1.29.0's names 8d539f9, but the published image
# carries a menu fix committed 35 minutes AFTER the build (31eeb36): it was built from an uncommitted tree, so
# no commit is that image and it cannot be tagged. The workspace rule ("an unpushed change does not exist —
# never build a dirty or unpushed tree") is therefore enforced HERE, before anything is built: the tree must
# have no uncommitted or untracked change, and HEAD must equal origin/main. No bypass flag, deliberately.
# FELHOM_ISO_REPO is a TEST SEAM only (test/clean-tree.sh points it at a throwaway repo).
# COMPANION RED-PROOF (test/clean-tree.sh header): drop the status check → case 2 fails.
REPO_DIR="${FELHOM_ISO_REPO:-$(cd "$HERE/../.." && pwd)}"
REPO_COMMIT=""
clean_tree_gate() {
local st head up
st="$(git -C "$REPO_DIR" status --porcelain 2>/dev/null)" \
|| die "clean-tree gate: $REPO_DIR is not a git work tree — the build could not name its source (R-730)"
[[ -z "$st" ]] || die "clean-tree gate: the tree has uncommitted or untracked changes — an image built from it has no commit to name (R-730). Commit and push first. First entries: $(echo "$st" | head -5 | tr '\n' ';')"
head="$(git -C "$REPO_DIR" rev-parse HEAD)"
up="$(git -C "$REPO_DIR" rev-parse --verify -q origin/main)" \
|| die "clean-tree gate: no origin/main in $REPO_DIR — cannot prove the commit is pushed (R-730)"
[[ "$head" == "$up" ]] \
|| die "clean-tree gate: HEAD ${head:0:12} is not origin/main ${up:0:12} — an unpushed change does not exist (R-730)"
REPO_COMMIT="$head"
log_info "clean-tree gate: tree clean, HEAD = origin/main = ${head:0:12}"
}
clean_tree_gate
[[ -z "$LOADER_CLI" || "$LOADER_CLI" == "shim" || "$LOADER_CLI" == "mkimage" ]] \
|| die "--loader must be 'shim' or 'mkimage' (got '$LOADER_CLI')"
@@ -471,7 +496,7 @@ $RELEASE && MODE_NOTE="release (PUBLIC image — NO answer.toml, NO baked creden
cat > "$OUT_ISO.manifest.txt" <<EOF
Felhom bare-metal ISO build manifest (R-21 slice A+B+C)
built : $(date -Is)
iso-version-tag : v${ISO_VERSION}
iso-version-tag : iso-v${ISO_VERSION} (the git tag this image's source carries, when tagged — NOT installer-v*, the host-install script's line)
pve-version : ${PVE_VERSION}
source-iso : ${ISO_BASE}
source-iso-sha256 : ${ISO_SHA256}
@@ -495,7 +520,7 @@ secret-bearing : ${SECRET_BEARING}$( $RELEASE && echo ' (PUBLIC image —
root-password : $( $RELEASE && echo 'NONE — not baked. The installer prompts the person installing (release gate G2).' || echo "see $(basename "$OUT_ISO").rootpw.txt (operator-only; NEVER commit or paste into REPORTs)" )
answer-file : $( $RELEASE && echo 'NONE — no answer.toml, no auto-installer-mode.toml (release gate G1)' || echo 'baked (/answer.toml)' )
felhom-package : $( [[ -n "$RELEASE_DEB" ]] && echo "$(basename "$RELEASE_DEB") sha256=$(sha256sum "$RELEASE_DEB" | cut -d\ -f1)" || echo 'n/a (day-0 rides the answer file first-boot hook)' )
repo-commit : $(cd "$HERE" && git rev-parse HEAD 2>/dev/null || echo unknown)
repo-commit : ${REPO_COMMIT} (clean tree, = origin/main at build time — the clean-tree gate, R-730)
output : $(basename "$OUT_ISO")
output-sha256 : ${OUT_SHA}
output-size-bytes : ${OUT_SIZE}
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
# clean-tree.sh — R-730 regression test for build-felhom-iso.sh's clean-tree gate. No docker, no ISO:
# the gate runs right after argument parsing, so the build dies at the gate (refused) or at the next
# check ("--pve-iso is required", which proves it got PAST the gate). The repo is a throwaway one
# (FELHOM_ISO_REPO, the gate's test seam) with a local bare "origin".
# 1. a clean tree whose HEAD = origin/main → passes the gate
# 2. the same tree plus an UNTRACKED file → refused ("uncommitted or untracked")
# 3. a commit that is NOT on origin/main → refused ("not origin/main")
# 4. a modified TRACKED file → refused
# Red-proof (run 2026-09-30, recorded in the R-730 row): delete the `[[ -z "$st" ]] || die …` line in
# the gate → cases 2 and 4 FAIL (the build goes past the gate) → restore.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD="$HERE/../build-felhom-iso.sh"
fail=0
check() { if eval "$2"; then echo " ok: $1"; else echo " FAIL: $1"; fail=1; fi; }
S="$(mktemp -d "${TMPDIR:-/tmp}/felhom-cleantree-test.XXXXXX")"
trap 'rm -rf "$S"' EXIT
git init -q --bare "$S/origin.git"
git init -q -b main "$S/repo"
git -C "$S/repo" -c user.email=t@t -c user.name=t commit -q --allow-empty -m first
git -C "$S/repo" remote add origin "$S/origin.git"
git -C "$S/repo" push -q origin main
run() { FELHOM_ISO_REPO="$S/repo" bash "$BUILD" 2>&1; }
out="$(run)"
check "1 clean + pushed passes the gate" '[[ "$out" == *"--pve-iso is required"* && "$out" != *"clean-tree gate:"*"R-730"* ]]'
touch "$S/repo/stray.txt"
out="$(run)"
check "2 an untracked file is refused" '[[ "$out" == *"uncommitted or untracked"* && "$out" != *"--pve-iso is required"* ]]'
rm -f "$S/repo/stray.txt"
git -C "$S/repo" -c user.email=t@t -c user.name=t commit -q --allow-empty -m local-only
out="$(run)"
check "3 an unpushed commit is refused" '[[ "$out" == *"is not origin/main"* && "$out" != *"--pve-iso is required"* ]]'
git -C "$S/repo" push -q origin main
echo a > "$S/repo/f.txt"; git -C "$S/repo" add f.txt
git -C "$S/repo" -c user.email=t@t -c user.name=t commit -q -m f; git -C "$S/repo" push -q origin main
echo b > "$S/repo/f.txt"
out="$(run)"
check "4 a modified tracked file is refused" '[[ "$out" == *"uncommitted or untracked"* && "$out" != *"--pve-iso is required"* ]]'
[[ $fail -eq 0 ]] && echo "clean-tree gate: all cases pass" || echo "clean-tree gate: FAILED"
exit $fail
+6 -1
View File
@@ -30,7 +30,12 @@ FAKE_SHA="$(sha256sum "$FAKE_ISO" | awk '{print $1}')"
say "dry-run build (pairing, nested-canary) with FELHOM_ISO_KEEP_WORK=1"
BUILD_LOG="$SCRATCH/build.log"
FELHOM_ISO_KEEP_WORK=1 TMPDIR="$SCRATCH/tmp" bash "$BUILD" \
# R-730: the build refuses a dirty/unpushed tree; this test is about the root password, so it builds from a
# throwaway clean repo (the gate's own test seam) and stays runnable while the real tree has work in progress.
git init -q --bare "$SCRATCH/origin.git"; git init -q -b main "$SCRATCH/repo"
git -C "$SCRATCH/repo" -c user.email=t@t -c user.name=t commit -q --allow-empty -m first
git -C "$SCRATCH/repo" remote add origin "$SCRATCH/origin.git"; git -C "$SCRATCH/repo" push -q origin main
FELHOM_ISO_REPO="$SCRATCH/repo" FELHOM_ISO_KEEP_WORK=1 TMPDIR="$SCRATCH/tmp" bash "$BUILD" \
--pve-iso "$FAKE_ISO" --iso-sha256 "$FAKE_SHA" \
--profile "$HERE/../profiles/nested-canary.profile" \
--pairing --out "$SCRATCH/out" --dry-run > "$BUILD_LOG" 2>&1