SPIKE 4: a .deb in the ISO DOES deliver on an interactive install
Findings only — no script, profile or build file changed; no release ISO built, nothing published. documentation/audits/SPIKE-universal-iso-4-2026-07-31.md MEASURED, with a control, and the negative control is in the SAME box. One ISO (15 GRUB entries), a trivial probe .deb injected into /proxmox/packages/, two qm-created VMs on demo-hp (400 interactive / 401 automated control) on a scratch dir storage at the /mnt/nvme-1tb mount ROOT. Interactive (Terminal UI) install: - package installed (ii felhom-spike4-probe 0.0.1) - postinst RAN (marker + content intact) - it enabled a systemd unit, and that unit FIRED ON FIRST BOOT (uptime 7.98s, pid1=systemd) - while on the same machine proxmox-first-boot is NOT installed and /var/lib/proxmox-first-boot does not exist — Spike 3's negative reproduced, not assumed. Postinst environment (identical both paths): pid1=unconfigured.sh, NO running systemd, but 'systemctl enable' SUCCEEDS; /proc+/sys mounted; network+DNS happened to be up (inherited from the installer's DHCP — must NOT be relied on). Constraints: never systemctl start/daemon-reload, never require network, never fail, do the real work in the unit at first boot. Repack preserves it, but a naive 'xorriso -boot_image any replay' fails with 'Overlapping MBR partition entries' — iso-repack.sh:270-292 already documents that exact failure and its fix. R-153 RETRACTED into R-94 leg (b): OPEN-ITEMS.md:15 carries it verbatim at READY (XS), and R-29 says explicitly 'do not mint a new ID for a new instance'. Spike 3's further claim that the drift leaves the generator 'three minor versions stale' was FALSE and is corrected — R-94 retracts that exact reading; the served script is always main, so 1.22.0 is what every install already gets. No new R-rows opened.
This commit is contained in:
@@ -1,88 +1,97 @@
|
||||
# REPORT — SPIKE 3: on an interactive install, does the ISO deliver anything at all? (2026-07-31)
|
||||
# REPORT — SPIKE 4: can a `.deb` in the ISO deliver the stub on an interactive install? (2026-07-31)
|
||||
|
||||
> Written as `REPORT-universal-iso-spike.md`, not `REPORT.md`: the shared file belongs to today's hub
|
||||
> v0.85.0 session and the second session in a shared clone never touches it. Supersedes this file's
|
||||
> Spike 1 and Spike 2 contents.
|
||||
> Spike 1–3 contents.
|
||||
|
||||
**Class: Spike.** Findings only — no production file changed, no release ISO built, nothing published.
|
||||
Evidence: `documentation/audits/SPIKE-universal-iso-3-2026-07-31.md`.
|
||||
Evidence: `documentation/audits/SPIKE-universal-iso-4-2026-07-31.md`.
|
||||
|
||||
## The answer: no, and more completely than expected
|
||||
## The answer is yes, and it was measured with the negative control in the same box
|
||||
|
||||
**`[first-boot]` does not fire on an interactive install.** Measured, with a control from the *same*
|
||||
image (one ISO, 15 GRUB entries):
|
||||
One ISO, 15 GRUB entries, a trivial probe `.deb` injected into `/proxmox/packages/`. Two VMs on
|
||||
demo-hp built with `qm` so the run was visible in the web console: **400 interactive**, **401
|
||||
automated control**.
|
||||
|
||||
- **Automated entry** → hook fires: `ttyS0` marker, marker file,
|
||||
`/var/lib/proxmox-first-boot/proxmox-first-boot` (0700), activation symlink, unit `active`.
|
||||
- **Terminal UI entry, normal manual install** → every one of those **absent**, and the
|
||||
`proxmox-first-boot` **package is not installed at all**. A whole-filesystem grep for the marker
|
||||
returns nothing.
|
||||
On the **interactive** install (`spikefour.felhom.eu`):
|
||||
|
||||
It is not that the hook fails to run — nothing that could run it is ever placed on the system. The
|
||||
mechanism, cited: `Config.pm:118` defaults `first_boot.enabled => 0` and `set_first_boot_opt` is never
|
||||
called anywhere in the Perl tree; `Install.pm:746` returns early without the flag; `Install.pm:1360`
|
||||
skips the package. And `proxinstall`, the graphical installer, contains **zero** occurrences of
|
||||
`first-boot`. **This is a property of the PVE installer, not a Felhom defect** — but nothing in our
|
||||
tree records it, which is R-154.
|
||||
- the package is installed — `ii felhom-spike4-probe 0.0.1`
|
||||
- its **postinst ran** — marker file present, content intact
|
||||
- it **enabled a systemd unit**, and **that unit fired on first boot** (uptime 7.98 s, `pid1: systemd`)
|
||||
- and on **that same machine**, `proxmox-first-boot` is not installed and `/var/lib/proxmox-first-boot`
|
||||
does not exist — Spike 3's negative reproduced, not assumed
|
||||
|
||||
## The commercially relevant part
|
||||
So the two delivery mechanisms are independent, and the one that survives the path we are actually
|
||||
shipping is the `.deb`. **The product — insert the stick, install Proxmox normally choosing your own
|
||||
disk and password, box sets itself up and waits for a claim code — is now measured rather than hoped
|
||||
for.** With one honest caveat: what was measured is a trivial probe package, not Felhom's real stub.
|
||||
Packaging the real stub and confirming pairing end-to-end is the last step before a spec (~60 min).
|
||||
|
||||
**A delivery mechanism does exist.** `Install.pm:1343-1372` unpacks **every** `.deb` in the ISO's
|
||||
`/proxmox/packages/` into the target on *every* install path — a fixed skip-list of known package
|
||||
names, everything else installed — then `dpkg --configure -a` runs postinsts. That is exactly how PVE
|
||||
ships `proxmox-first-boot` itself.
|
||||
## What a postinst may and may not do
|
||||
|
||||
**It is read from source, not measured**, and I have not proven either that our repack preserves an
|
||||
added `.deb` or that a postinst running in the installer chroot can do the job (it runs *inside the
|
||||
installer*, not on a booted system). Proving it is the top follow-up, ~45 min. Until then the honest
|
||||
position is: the *insert-stick-install-type-claim-code* experience is **plausible but unvalidated**,
|
||||
and the documented one-command manual path is what works today.
|
||||
Identical on both paths: `pid1 = unconfigured.sh`, **no running systemd**, `/proc` and `/sys` mounted,
|
||||
and **`systemctl enable` succeeds** (it wrote the symlink). Network and DNS *happened* to be up —
|
||||
inherited from the installer's own DHCP.
|
||||
|
||||
Incidentally confirmed: the interactive installer shows the target disk and a summary listing
|
||||
`Bootdisk(s) /dev/sda` before erasing. Your ruling needs no Felhom work for that warning.
|
||||
Four constraints for the real postinst, so they get written against rather than discovered:
|
||||
|
||||
## The manual path is already most of the way there
|
||||
1. Never `systemctl start` or `daemon-reload` — there is no systemd running. `enable` is the only verb.
|
||||
2. **Never require the network**, despite it being present here. A box installed with the cable out
|
||||
gives a postinst no route, and a failing postinst breaks the customer's install.
|
||||
3. Never fail — guard everything, `exit 0`.
|
||||
4. Do the real work in the unit at first boot, where systemd, network and a booted kernel exist.
|
||||
|
||||
Two of the three things I was asked to look for already exist:
|
||||
## Two smaller results
|
||||
|
||||
- **The hub already renders the command with the customer-id filled in**
|
||||
(`customer_unified.html:563-564`, plus a JS generator that adjusts the flags per role).
|
||||
- **The URL is already short, public and always current** —
|
||||
`felhom.eu/scripts/felhom-host-install.sh`, git-synced from `main` on a 30-second period.
|
||||
- **Console display does not exist.** Nothing writes `/etc/issue`; PVE's own banner is what appears
|
||||
after install. That file is the obvious carrier. Reported, not built.
|
||||
**The repack preserves the `.deb`, but not naively.** `xorriso … -boot_image any replay` fails with
|
||||
*"Overlapping MBR partition entries"* — and `iso-repack.sh:270-292` already documents that exact
|
||||
failure and its fix. Mirroring it produced a working image (19 El Torito entries; the `.deb` extracted
|
||||
back out is byte-identical). So this is an insertion into an extract→modify→re-master cycle our repack
|
||||
already performs, not a new build stage.
|
||||
|
||||
## Two things I'd flag
|
||||
**Q3:** `iso-repack.sh:100-106` refuses an ISO without `auto-installer-mode.toml`. It is a guard, not
|
||||
a structural requirement, and its reasoning is sound for the shape it was written for — already R-155,
|
||||
cited exactly here. With no mode file the stock grub.cfg does not emit the Automated entry at all; with
|
||||
a mode file but no answer, that entry aborts safely and loudly.
|
||||
|
||||
**A working gate is red and nobody runs it.** `configs.go:28` has `hostInstallVersion = "1.19.0"`
|
||||
against `SCRIPT_VERSION = "1.22.0"`, and `scripts/hostinstall_gates.py` detects it and exits 1. The
|
||||
comment saying drift "is now gated" is true of the gate's existence and false of its enforcement, so
|
||||
the customer page's install-command generator targets a flag surface three minor versions stale.
|
||||
**R-153.**
|
||||
## A correction I owe you from last session
|
||||
|
||||
**The cleanest public-image posture is currently blocked by our own tooling.** §6 of the findings
|
||||
argues the public image should carry **no `answer.toml` at all** — which removes the baked root hash,
|
||||
the disk profile, and every question Spikes 1–2 wrestled with, and makes "no `answer.toml`" a
|
||||
one-line release gate. But `iso-repack.sh:100-106` refuses any ISO lacking `auto-installer-mode.toml`.
|
||||
That guard is sound for its original case and wrong for this one. **R-155.**
|
||||
**R-153 is retracted.** The register grep this task mandated shows R-94 already carries it verbatim at
|
||||
`OPEN-ITEMS.md:15`, status `READY (XS)`, with leg (b) being precisely "the gate fails today and is
|
||||
invoked by nothing" — and R-29, the class, says in terms *"do not mint a new ID for a new instance."*
|
||||
Spike 3 filed a duplicate.
|
||||
|
||||
## Not reached
|
||||
**And the substantive half of that Spike 3 claim was wrong.** I wrote that the drift left the customer
|
||||
page's install-command generator "targeting a flag surface three minor versions stale." R-94 explicitly
|
||||
retracts exactly that reading: the constant selects no script — it renders as a text label, and the
|
||||
command beneath it fetches the script the website git-syncs from `main`, so **1.22.0 is what every
|
||||
install already gets**. It is a wrong number on your screen and nothing more. I overstated it.
|
||||
|
||||
**Q3 — the real stub at `before-network`** was not run; driving the interactive install consumed the
|
||||
session. It is recorded as not reached, not inferred. Spike 2's structural case stands unrefuted, and
|
||||
§2 narrows its scope: with no first-boot hook on the interactive path, that question now governs
|
||||
**operator-built images only**. ~40 min when wanted.
|
||||
## Still unknown
|
||||
|
||||
**The real stub has not been packaged** — that is the one thing between here and a build spec.
|
||||
Also unproven: `dpkg --configure -a` ordering for a package with dependencies; an ISO that never went
|
||||
through `prepare-iso` (blocked by R-155, which this spike was fenced from changing); and the Graphical
|
||||
installer, where the result should hold *a fortiori* since the `.deb` path is in `Install.pm`, shared
|
||||
by all front-ends — but that is inference, not measurement.
|
||||
|
||||
**Spike 3's Q3** — the real stub at `before-network` — **this session did not touch it.** Note it is
|
||||
now partly superseded: on the `.deb` route the unit's ordering comes from the unit file, not from
|
||||
`[first-boot].ordering`.
|
||||
|
||||
## R-rows
|
||||
|
||||
**R-153** (a working gate is red and unrun) · **R-154** (`[first-boot]` is automated-only and nothing
|
||||
in our tree says so) · **R-155** (`iso-repack.sh` blocks the no-`answer.toml` posture).
|
||||
**None opened.** Each candidate was grepped against the register first: the delivery result is a
|
||||
positive finding, the postinst constraints belong in the build spec, and the repack guard is already
|
||||
R-155. **R-153 retracted** into R-94 leg (b) / R-29.
|
||||
|
||||
## Teardown
|
||||
|
||||
All three layers verified positively. demo-hp back to its exact pre-spike state — `spike3/` removed,
|
||||
0 QEMU processes, 0 loop devices, **disk usage 6.6 G, identical to pre-spike**, 4 storages unchanged,
|
||||
nothing on `local-lvm`, `drill-r50` stopped and 9201 running. demo-felhom: nothing placed, external
|
||||
drive observed only. DooPlex: scratchpad 4.4 GB → 80 K, both throwaway passwords destroyed,
|
||||
`felhom-iso/out/` untouched at 17 ISOs, no production file modified. **Hub-side: nothing created** —
|
||||
verified by fetching and searching the customer list, not asserted. Nothing published.
|
||||
All three layers plus the scratch storage, verified positively. demo-hp: VMs 400/401 purged, **storage
|
||||
`spike4` removed** (`storage.cfg` back to 4, `grep -c spike4` = 0), `/mnt/nvme-1tb/images/` empty,
|
||||
**disk usage 6.6 G — identical to pre-spike**, probe ISO and driver removed, 0 loop devices,
|
||||
`drill-r50` stopped and untouched, 9201 running, `felhom-backup` unmodified, nothing on `local-lvm`.
|
||||
DooPlex: workspace scratch **4.8 GB removed**, scratchpad **3.3 GB → 88 K**, both throwaway passwords
|
||||
destroyed, 17 ISOs in `out/` untouched, no production file modified. **Hub-side: nothing created** —
|
||||
the VMs took LAN DHCP leases but never ran `felhom-host-install.sh` or contacted the hub; verified by
|
||||
fetching and searching the customer list. Nothing published.
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
# SPIKE 4 — can a `.deb` in the ISO deliver the stub on an interactive install? (2026-07-31)
|
||||
|
||||
> **Class: Spike.** Findings only. No production file changed, no release ISO built, nothing published.
|
||||
>
|
||||
> Continues `SPIKE-universal-iso-{1,2,3}-2026-07-31.md`. Spike 3's Q1 result and its `Install.pm`
|
||||
> reading are premises and are not re-derived.
|
||||
|
||||
**Q1 is YES, measured, with a control — and the negative control sits in the same box.** On a normal
|
||||
interactive install the Felhom `.deb` is installed, its postinst runs, it enables a systemd unit, and
|
||||
**that unit fires on first boot** — while on that same machine the PVE first-boot hook is absent
|
||||
exactly as Spike 3 measured. The two mechanisms are independent, and the one that survives the
|
||||
interactive path is the `.deb`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Venue — stated first, while it exists
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Host** | `demo-hp` (t740), Tier 0, the designated drill + build VM host |
|
||||
| **VMs** | **400 `spike4-interactive`** (the test) · **401 `spike4-auto-control`** (the control) |
|
||||
| **Storage** | **`spike4`**, a `dir` storage created for this run at **`/mnt/nvme-1tb` — the mount ROOT, not a subdirectory**, `content=images` |
|
||||
| **Why the root** | the agent's `exactMount` check reads a storage at a *subdirectory* of a mountpoint as `disconnected` forever. Placing it at the root avoids that. It coexists with the pre-existing `felhom-backup` dir storage on the same path (`content=backup`), which was **not modified** — the demo box's configuration is fenced |
|
||||
| **Disks** | `/mnt/nvme-1tb/images/{400,401}/vm-*-disk-*.raw`. **Nothing on `local-lvm`** |
|
||||
| **Console** | web console → VM → Console, or `qm terminal <vmid>` / `qm monitor <vmid>` on demo-hp. Both VMs were created with `qm`, so they appear in `qm list` and the web UI — unlike Spikes 1–3, which used raw QEMU processes PVE could not see |
|
||||
| **Teardown** | VMs destroyed and the `spike4` storage removed — §7 |
|
||||
|
||||
### Baselines
|
||||
|
||||
| Baseline | Value |
|
||||
|---|---|
|
||||
| `ISO_VERSION` | `1.25.0` — `scripts/iso/build-felhom-iso.sh:44` |
|
||||
| `SCRIPT_VERSION` | `1.22.0` — `scripts/felhom-host-install.sh:187` |
|
||||
| `felhom-bootstrap.sh` @ HEAD | `21bf6a6bde0cb13e3809e2f5c136a49929dcc82eb8d40bbdf6f290a886ee8ab7` |
|
||||
| PVE base | `proxmox-ve_9.2-1.iso`, `4e88fe416df9b527624a175f24c9aa07c714d3332afb1ee3dbf3879573ef2c6c` |
|
||||
| `felhom.eu` HEAD | **`bb29186`**, clean, `== origin/main` |
|
||||
|
||||
### Fixture provenance
|
||||
|
||||
One ISO for both paths. Built on DooPlex: baseline PVE ISO → `prepare-iso --fetch-from partition
|
||||
--partition-label proxmox-ais --on-first-boot hook.sh` → **the probe `.deb` injected into
|
||||
`/proxmox/packages/`** → re-mastered. Final sha256
|
||||
`6f723910239bb7f333c952c1d1933ae3951f03c5b6cc536f15e33a8da37eba5f`, **verified identical after
|
||||
transfer** to demo-hp. **15 GRUB menu entries** (Automated + Graphical + Terminal UI + the rest), so
|
||||
control and test come from the same build.
|
||||
|
||||
The probe `.deb` (`felhom-spike4-probe_0.0.1_all.deb`, 2016 bytes) carries **no Felhom logic** — a
|
||||
postinst that writes a marker and reports its environment, plus a trivial systemd unit and script. It
|
||||
tests delivery, not payload.
|
||||
|
||||
**Completion signals:** the automated control used `reboot-mode = "power-off"` (guest powers itself
|
||||
off); the interactive install's completion was observed on the console and then by an SSH banner from
|
||||
the installed system. **No install was judged complete from a changed artifact** (Spike 1's `LastRun`
|
||||
lesson) — and that mattered here: an ARP reply from `192.168.0.100` initially looked like the installed
|
||||
system and was in fact the **installer environment**, caught by screendumping the console, which showed
|
||||
the install still at 67 %.
|
||||
|
||||
---
|
||||
|
||||
## 2. Q1 — does the `.deb` reach the installed system? **YES, both observables, on both paths.**
|
||||
|
||||
The two observables are kept separate because "package present, postinst did not run" is a distinct
|
||||
and important outcome.
|
||||
|
||||
| Observable | **Control — Automated entry** (VM 401, `spike4auto.local`) | **Test — Terminal UI entry** (VM 400, `spikefour.felhom.eu`) |
|
||||
|---|---|---|
|
||||
| **A. package installed** (`dpkg -l`) | `ii felhom-spike4-probe 0.0.1` | **`ii felhom-spike4-probe 0.0.1`** |
|
||||
| **B. postinst ran** (marker + content) | `/var/log/SPIKE4-POSTINST`, 543 B, intact | **`/var/log/SPIKE4-POSTINST`, 542 B, intact** |
|
||||
| unit enabled | `enabled` | **`enabled`** |
|
||||
| **unit fired on FIRST BOOT** | `@@SPIKE4_FIRSTBOOT_RAN@@`, uptime 7.92 s | **`@@SPIKE4_FIRSTBOOT_RAN@@`, uptime 7.98 s** |
|
||||
| PVE first-boot hook (`SPIKE4-HOOK`) | present — `@@SPIKE4_HOOK_FIRED@@` | **absent** |
|
||||
| `proxmox-first-boot` package | installed | **`no packages found`** |
|
||||
| `/var/lib/proxmox-first-boot/` | present | **absent** |
|
||||
|
||||
**The last three rows are the point.** On one machine, from one image, the `.deb` arrived and the PVE
|
||||
first-boot hook did not. Spike 3's negative is reproduced here rather than assumed, and the `.deb`
|
||||
mechanism is proven independent of it.
|
||||
|
||||
**Did the repack preserve the `.deb`?** Yes, and the answer is more useful than a simple yes.
|
||||
|
||||
- A naive in-place `xorriso … -boot_image any replay -map …` **fails**:
|
||||
`libisofs: FAILURE : Overlapping MBR partition entries requested`.
|
||||
- `iso-repack.sh:270-292` **documents this exact failure and its fix**: re-master from the ISO's own
|
||||
`-report_el_torito as_mkisofs` report with `-V`, `--modification-date=`, `-apm-block-size`,
|
||||
`-hfsplus`, `-part_like_isohybrid` and `-isohybrid-gpt-basdat` filtered out, pinning the volume id
|
||||
and modification-date so the embedded fs-uuid stays valid.
|
||||
- Mirroring that in a throwaway script produced a working image: **19 El Torito boot entries**
|
||||
(`iso-repack.sh:298` requires ≥2), 543 `.deb`s, and the probe `.deb` extracted back out of the
|
||||
finished ISO is **byte-identical** to the input (`sha256` first 32: `c624acec93aa9450ac168d39e877807f`
|
||||
both sides).
|
||||
|
||||
So the delivery path is: **add the `.deb` to `/proxmox/packages/` in the extract tree, and re-master
|
||||
with the existing recipe.** Felhom's own repack already performs that extract→modify→re-master cycle,
|
||||
so this is an insertion into a step that runs anyway — not a new build stage.
|
||||
|
||||
**Mechanism, from Spike 3 and confirmed visually here:** `Install.pm:1343-1372` unpacks every `.deb`
|
||||
in `/proxmox/packages/` with a fixed skip-list of known package-name patterns, then
|
||||
`dpkg --force-confold --configure -a` at `:1378` runs the postinsts. The console screendump caught the
|
||||
loop mid-run (`67 %`, `extracting mesa-libgallium_25.0.7-2_amd64.deb`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Q2 — what a postinst can do from the installer chroot
|
||||
|
||||
The postinst reported its own environment. **Identical on both paths**, which is itself worth noting —
|
||||
the chroot does not differ between automated and interactive installs:
|
||||
|
||||
| Property | Value | Consequence for a real postinst |
|
||||
|---|---|---|
|
||||
| `pid1_comm` | **`unconfigured.sh`** | it is the installer chroot, not a booted system |
|
||||
| `systemd_running` (`/run/systemd/system`) | **`no`** | **`systemctl start` / `daemon-reload` are meaningless — never call them** |
|
||||
| `systemctl_path` | `/bin/systemctl` | present, and usable in *offline* mode |
|
||||
| **`systemctl enable`** | **SUCCEEDED** — `Created symlink '/etc/systemd/system/multi-user.target.wants/spike4-firstboot.service' → …` | **enabling a unit works; no need to hand-write the symlink** |
|
||||
| `/proc`, `/sys` | both mounted | ordinary file/proc operations are fine |
|
||||
| `default_route` | `default via 192.168.0.1 dev ens18` | network *happened* to be up — see the caveat below |
|
||||
| `dns_resolvable` | `yes` | ditto |
|
||||
| postinst arg | `configure` | standard `dpkg` first-install invocation |
|
||||
|
||||
**Answering Q2.2 directly: yes.** The realistic job — drop a unit, enable it, exit — works, and the
|
||||
unit **fired on first boot** on both installs (uptime ≈ 8 s, `pid1: systemd`, network up).
|
||||
|
||||
**The constraints a real postinst must respect**, so they are written against rather than discovered:
|
||||
|
||||
1. **Never `systemctl start`, `daemon-reload`, or anything needing a running systemd.** There isn't
|
||||
one. `enable` is the only systemd verb available.
|
||||
2. **Never require the network, even though it was present.** It was present only because the
|
||||
*installer* had DHCP and the chroot inherits its network namespace. A box installed with the cable
|
||||
out, or on a LAN with no DHCP, gives a postinst with no route — and a postinst that fails takes the
|
||||
whole install down. Fetch nothing; ship what is needed inside the package.
|
||||
3. **Never fail.** `dpkg --configure -a` runs it; a non-zero exit surfaces as an install error to a
|
||||
customer mid-install. Guard everything and `exit 0`.
|
||||
4. **Do the real work in the unit at first boot, not in the postinst.** The postinst's only job is to
|
||||
place files and enable the unit. That is also where network, systemd and a booted kernel actually
|
||||
exist.
|
||||
5. **Ordering within `dpkg --configure -a` was not tested.** The probe depended on nothing. A package
|
||||
that needs another configured first is unproven here — see §6.
|
||||
|
||||
---
|
||||
|
||||
## 4. Q3 — can the image ship with no `answer.toml`?
|
||||
|
||||
**Where it refuses — cited.** `scripts/iso/iso-repack.sh:100-106`:
|
||||
|
||||
```bash
|
||||
AIM="$(find "$EX" -maxdepth 2 -iname 'auto-installer-mode.toml' | head -1)"
|
||||
[[ -n "$AIM" ]] || {
|
||||
echo "iso-repack: auto-installer-mode.toml not found in the ISO — this is not a prepared" >&2
|
||||
echo " auto-install ISO, so the single Felhom entry would boot the MANUAL installer. Refusing." >&2
|
||||
exit 10
|
||||
}
|
||||
```
|
||||
|
||||
**It is a guard, not a structural requirement**, and its reasoning is sound *for the shape it was
|
||||
written for*: the Felhom menu emits exactly one entry which boots the automated installer, so an ISO
|
||||
without the mode file would put a "Felhom telepítés" label on a manual disk-picker. That is precisely
|
||||
the outcome the single-entry menu exists to prevent.
|
||||
|
||||
Under the operator's ruling the premise is inverted — the menu should offer the interactive installer
|
||||
deliberately — so the guard now blocks the desired shape. It needs to distinguish *"prepared,
|
||||
single-entry"* from *"unprepared, interactive-by-design"* rather than treating the second as the
|
||||
first's failure. Already filed as **R-155**; this session cites the exact refusal.
|
||||
|
||||
**Does the automated entry degrade gracefully with no answer file?** Two measurements bear on it:
|
||||
|
||||
- **With no `auto-installer-mode.toml` at all**, the stock `grub.cfg` does not emit the Automated entry
|
||||
— it sits inside `if [ -f auto-installer-mode.toml ]` (Spike 1 §5). So the entry is *absent*, not
|
||||
broken. Nothing to hang.
|
||||
- **With the mode file but no reachable answer**, observed incidentally this session (Spike 3's VM
|
||||
rebooting into the CD): the automated entry **aborts safely and loudly** —
|
||||
`INFO: Testing partition search path "/dev/disk/by-label/proxmox-ais"` … `ERROR: Aborting: Could not
|
||||
find any answer file!` … `Installation aborted - unable to continue (type exit or CTRL-D to reboot)`,
|
||||
leaving a shell and touching no disk.
|
||||
|
||||
**Not measured: whether an ISO that never went through `prepare-iso` boots and installs correctly after
|
||||
Felhom branding.** That needs the guard relaxed, which is a production-code change this spike is fenced
|
||||
from making. See §6.
|
||||
|
||||
**Release gate, if the posture is adopted:** `osirrox -indev <iso> -find / -maxdepth 1` contains
|
||||
**neither `/answer.toml` nor `/auto-installer-mode.toml`**. One line, checkable against the artifact.
|
||||
|
||||
---
|
||||
|
||||
## 5. Q4 — the resulting product shape, as constraints
|
||||
|
||||
Given the ruling (no fully automatic install) and Q1/Q2 positive. **Constraints on a build spec — not
|
||||
an implementation, and nothing was built.**
|
||||
|
||||
| # | Constraint | Release gate (checkable against the artifact) |
|
||||
|---|---|---|
|
||||
| 1 | **No `answer.toml`, and no `auto-installer-mode.toml`.** Q1 makes the first-boot hook pointless on the shipped path; the ruling removes the automated path. Dropping both deletes the baked root hash, the disk profile and the whole Spike 1–2 problem space at once. | `osirrox -find / -maxdepth 1` contains neither file. |
|
||||
| 2 | **A Felhom `.deb` in `/proxmox/packages/` is the delivery mechanism.** Measured (§2): it arrives and its postinst runs on the interactive path. | the ISO contains exactly one `felhom-*.deb`; extracting it back yields a sha256 matching the built package. |
|
||||
| 3 | **The postinst places files and enables a unit. Nothing else.** §3's five constraints — no running systemd, no network dependency, never fail, real work in the unit. | the packaged postinst contains no `systemctl start`, no `daemon-reload`, and no network fetch (`curl`/`wget`/`apt`); it ends `exit 0`. |
|
||||
| 4 | **No baked credential of any kind** — falls out of 1. The human sets their own password in the installer. | `grep -c root-password-hashed` and `grep -c root-ssh-keys` over the ISO both `0`; no `.rootpw.txt` emitted beside the output. |
|
||||
| 5 | **The boot menu is restored, with an interactive entry as default and a human-length timeout.** Spike 2 lost a probe to the installed system's **1-second** menu; a person choosing between install paths needs far longer. The stock ISO uses 10 s. | `grub.cfg`: `set default=` points at an interactive entry; `set timeout` ≥ 10; `timeout_style` uses the **underscore** spelling (the stock hyphenated `timeout-style` is not a GRUB variable at all — Spike 2 §5.2). |
|
||||
| 6 | **Relaxing the menu gates means relaxing the count gates only.** Four gates enforce the single entry (`iso-repack.sh:153-155`, `:156`, `:160-164`, `:314-319`). **Keep the banned-token gate exactly as it is** — it is what prevents a route to a debug/rescue/manual-disk-picker path, and a stock *Graphical* entry passes it unchanged. | `grub.cfg` still has no live `proxtui`/`proxdebug`/`nomodeset`/`Rescue Boot`/`memtest`/`fwsetup`; the entry-count assertion becomes an allowlist rather than `== 1`. |
|
||||
| 7 | **`iso-repack.sh:100-106` must learn the new shape** (R-155) before constraint 1 is buildable. | the repack accepts an unprepared ISO when branding an interactive-by-design image, and still refuses one when emitting a single automated entry. |
|
||||
| 8 | **The profile the public image uses must be COMMITTED.** Spike 1 found demo-felhom's baked SSH key comes from an uncommitted profile, so a Tier 0 reference box is not reproducible from `main`. | `--profile` resolves inside the repo tree **and** `git status --porcelain` is empty for it. |
|
||||
| 9 | **The artifact must be reproducible** (R-144: `nested-probe`'s profile no longer exists). | the manifest records the committed profile path + repo commit; both resolve. |
|
||||
|
||||
**The product this describes:** insert the stick, install Proxmox normally choosing your own disk and
|
||||
password, and the box sets itself up and waits for a claim code. **That is now measured, not hoped
|
||||
for** — with the caveat that what was measured is a trivial probe package, not Felhom's real stub
|
||||
(§6).
|
||||
|
||||
---
|
||||
|
||||
## 6. R-153 / R-94 — reconciled. **R-153 is retracted.**
|
||||
|
||||
The register grep the fence requires was run, and it shows Spike 3 should not have minted a row.
|
||||
|
||||
- **`OPEN-ITEMS.md:15` — R-94**, status **`READY (XS)`**, states the finding verbatim:
|
||||
*"A hand-synced version constant drifts, and the gate that would catch it is never run —
|
||||
`hub/internal/web/configs.go:28` pins `hostInstallVersion = "1.19.0"` while
|
||||
`scripts/felhom-host-install.sh:187` is `SCRIPT_VERSION="1.22.0"`"*, with **leg (b)** being exactly
|
||||
*"`scripts/hostinstall_gates.py` fails today and is invoked by no Makefile, hook or `CLAUDE.md` —
|
||||
wire it or delete it."*
|
||||
- **`OPEN-ITEMS.md:27` — R-29** is the **class**, and says in terms:
|
||||
*"do not mint a new ID for a new instance"* — it already absorbed a third independent re-raise on
|
||||
2026-07-29 without minting one, and **already names `scripts/hostinstall_gates.py` as R-94 leg (b)**.
|
||||
|
||||
**So R-153 duplicates R-94 leg (b) and violates R-29's explicit instruction. It is retracted here and
|
||||
should be struck from Spike 3's row list.** Nothing is lost: R-94 covers it in more detail.
|
||||
|
||||
**A second correction, and this one matters more.** Spike 3's report claimed the drift meant *"the
|
||||
customer page's install-command generator targets a flag surface three minor versions stale."*
|
||||
**That is false, and R-94 explicitly retracts exactly that reading.** The constant selects no script:
|
||||
its only consumers are `configs.go:487` and `render_test.go:219`, and it renders as a **text label** at
|
||||
`customer_unified.html:494`. The command beneath it fetches
|
||||
`https://felhom.eu/scripts/felhom-host-install.sh`, which the website git-syncs from `main` on a 30 s
|
||||
period — **so 1.22.0 is what every install already gets**, and every flag the generator emits is parsed
|
||||
by it. The defect is a wrong number on the operator's screen, nothing more. Spike 3's sentence
|
||||
overstated it and is corrected here.
|
||||
|
||||
**The lesson for this arc:** the fence that caught this — *grep the register before minting* — worked,
|
||||
and it worked one session too late. R-123 names this exact class.
|
||||
|
||||
---
|
||||
|
||||
## 7. What is still unknown
|
||||
|
||||
| # | Unknown | What it takes |
|
||||
|---|---|---|
|
||||
| 1 | **The real Felhom stub has not been packaged.** §2 proves a *trivial* `.deb` is delivered and its postinst can enable a unit that fires. It does not prove `felhom-bootstrap.sh` + its unit + the pairing env survive that route, nor that pairing completes end-to-end from an interactively-installed box. | Package the real stub as a `.deb` per §3's constraints, repeat the §2 run, and confirm pairing reaches the hub. ~60 min. **This is the last thing between here and a spec.** |
|
||||
| 2 | **`dpkg --configure -a` ordering** (§3, constraint 5) — the probe depended on nothing. | A package with a dependency, or one that must run after `pve-manager`. Only matters if the real stub needs it. |
|
||||
| 3 | **An ISO that never went through `prepare-iso`** (§4) — unmeasured, and blocked by R-155 which this spike is fenced from changing. | Relax the guard in a branch, build, boot. ~30 min. |
|
||||
| 4 | **Spike 3's Q3 — the real stub at `before-network`** — **this session did not touch it.** It now governs operator-built images only, and note item 1 partly supersedes it: on the `.deb` route the unit's ordering is set by the unit file, not by `[first-boot].ordering`. | ~40 min if still wanted. |
|
||||
| 5 | **The graphical installer was not tested** — §2 used the Terminal UI. `proxinstall` has strictly less first-boot capability, and the `.deb` path does not involve the front-end at all (it is `Install.pm`, shared), so the result should hold a fortiori — but that is inference. | Repeat §2 via the Graphical entry. ~30 min. |
|
||||
| 6 | Spike 1's two open items — multi-match determinism, boot-medium exclusion. | Unchanged. |
|
||||
|
||||
---
|
||||
|
||||
## 8. R-rows opened
|
||||
|
||||
**None.** Each candidate was grepped against the register first, per the fence:
|
||||
|
||||
- The `.deb` delivery result is a **positive** finding, not a defect.
|
||||
- The postinst constraints (§3) belong in the eventual build spec, not the defect register.
|
||||
- The `iso-repack.sh` guard is **already R-155** (opened by Spike 3) — extended here with the exact
|
||||
citation rather than duplicated.
|
||||
- **R-153 is retracted** into R-94 leg (b) / R-29 (§6).
|
||||
|
||||
---
|
||||
|
||||
## 9. Teardown
|
||||
|
||||
**Layer 1 — demo-hp**, including the scratch storage.
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| VMs 400, 401 | `qm destroy --purge` — *"purging VM 400/401 from related configurations"*; `qm list` now shows only `300 drill-r50 stopped` |
|
||||
| **scratch storage `spike4`** | `pvesm remove spike4` — **removed**; `/etc/pve/storage.cfg` back to **4 storages**, `grep -c spike4` = **0** |
|
||||
| `/mnt/nvme-1tb/images/` | **empty** — both VM disk directories gone with the purge |
|
||||
| `/mnt/nvme-1tb/` contents | `dump e2d-images felhom-data images lost+found` — the pre-spike set |
|
||||
| disk usage | **6.6 G — identical to pre-spike** |
|
||||
| `probe4.iso` in the ISO store | removed |
|
||||
| driver, answer file, password files, screendumps (`/root/…`) | all removed |
|
||||
| loop devices / `/mnt/s4ans` | `0` / removed |
|
||||
| `drill-r50` (VM 300) | `stopped`, untouched — never destroyed or unblocked (R-93 fixture intact) |
|
||||
| guest 9201 | `running`, untouched |
|
||||
| `felhom-backup` storage | **unmodified** — the demo box's own configuration was not changed |
|
||||
| `local-lvm` | never written |
|
||||
|
||||
**Layer 2 — demo-felhom.** Nothing was placed there this session; it was not contacted.
|
||||
|
||||
**Layer 3 — DooPlex.**
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| workspace scratch `/mnt/5_hdd/felhom.eu/spike4-scratch` (ISO extract tree + two 1.7 GB ISOs) | **4.8 GB → removed**; `/mnt/5_hdd` at 24 % |
|
||||
| session scratchpad | **3.3 GB → 88 K** |
|
||||
| throwaway passwords | the answer-file password `shred -u`'d and verified absent; the interactive install's console password existed only as a `0600` file on demo-hp, deleted in the same command |
|
||||
| probe `.deb` build tree | removed |
|
||||
| `felhom-iso/out/` | untouched, **17 ISOs** |
|
||||
| production files | none modified — no `build-felhom-iso.sh`, no `iso-repack.sh`, no profile, no `answer.toml.tmpl`, no `felhom-bootstrap.sh` |
|
||||
|
||||
Note: the ISO extract used **disk-backed workspace scratch, not `/tmp`** — `/tmp` on DooPlex is a
|
||||
32 GB **tmpfs**, and a 3.4 GB extract there would have taken RAM from a live k3s node.
|
||||
|
||||
**Hub-side disposition.** No customer, host or appliance was created. The VMs were bridged to the
|
||||
demo-site LAN and took DHCP leases (`192.168.0.47`, `192.168.0.100`), but neither ran
|
||||
`felhom-host-install.sh` nor contacted the hub — the probe package carries no Felhom logic. Verified
|
||||
by fetching the customer list and searching it, not asserted.
|
||||
|
||||
**Published artifacts: none.**
|
||||
@@ -1,3 +1,40 @@
|
||||
## SPIKE 4 (no code change) — a `.deb` in the ISO DOES deliver on an interactive install (2026-07-31)
|
||||
|
||||
**No script, profile or build file was modified.** Findings:
|
||||
`documentation/audits/SPIKE-universal-iso-4-2026-07-31.md`. **No new R-rows; R-153 RETRACTED.**
|
||||
|
||||
**MEASURED, with a control, and the negative control is in the same box.** One ISO (15 GRUB entries),
|
||||
a trivial probe `.deb` injected into `/proxmox/packages/`, two VMs on demo-hp created with `qm` (400
|
||||
interactive / 401 automated control) on a scratch dir storage at the `/mnt/nvme-1tb` mount ROOT.
|
||||
|
||||
On the **Terminal-UI (interactive)** install: the package is installed (`ii felhom-spike4-probe`), its
|
||||
postinst ran, it enabled a systemd unit, and **that unit FIRED on first boot** (uptime 7.98s,
|
||||
pid1=systemd) — while on the *same machine* `proxmox-first-boot` is not installed and
|
||||
`/var/lib/proxmox-first-boot` does not exist, reproducing Spike 3's negative rather than assuming it.
|
||||
The automated control delivered both mechanisms.
|
||||
|
||||
**The postinst environment (identical on both paths):** `pid1=unconfigured.sh`, **no running systemd**,
|
||||
but `systemctl enable` SUCCEEDS; `/proc` + `/sys` mounted; network and DNS happened to be up (inherited
|
||||
from the installer's DHCP — **must not be relied on**). Constraints for a real postinst: never
|
||||
`systemctl start`/`daemon-reload`, never require network, never fail (`dpkg --configure -a` surfaces it
|
||||
as an install error), do the real work in the unit at first boot.
|
||||
|
||||
**Repack preserves it** — but a naive `xorriso -boot_image any replay` fails with "Overlapping MBR
|
||||
partition entries"; `iso-repack.sh:270-292` already documents that exact failure and its fix, and
|
||||
mirroring it produced a working image (19 El Torito entries, probe `.deb` byte-identical out of the
|
||||
finished ISO).
|
||||
|
||||
**Q3:** `iso-repack.sh:100-106` refuses an ISO without `auto-installer-mode.toml` — a guard, not a
|
||||
structural requirement, already filed as R-155 and cited exactly here. With no mode file the stock
|
||||
grub.cfg does not emit the Automated entry at all; with a mode file but no answer, the automated entry
|
||||
aborts safely and loudly.
|
||||
|
||||
**R-153 is RETRACTED** into R-94 leg (b): `OPEN-ITEMS.md:15` already carries it verbatim at
|
||||
`READY (XS)`, and R-29 (the class) says explicitly "do not mint a new ID for a new instance".
|
||||
**Spike 3's claim that the drift leaves the install-command generator "three minor versions stale" was
|
||||
also FALSE and is corrected** — R-94 retracts exactly that reading: the constant selects no script, the
|
||||
served script is always `main`, so it is a wrong number on a screen and nothing more.
|
||||
|
||||
## SPIKE 3 (no code change) — `[first-boot]` does NOT fire on an interactive install (2026-07-31)
|
||||
|
||||
**No script, profile or build file was modified.** Findings:
|
||||
|
||||
Reference in New Issue
Block a user