installer v1.28.0: the removal genuinely reverses the installation (R-316)
gates / gates (push) Successful in 13s
gates / gates (push) Successful in 13s
v1.27.0's fix worked exactly once per machine. Measured on drill-r50 from virgin, on the PUBLISHED v1.27.0, before anything was changed: cycle 1 recorded 'no' and freed :53; cycle 2 recorded 'yes' and left dnsmasq running on 0.0.0.0:53; cycle 3 refused, exit 1. Every box already in the field is at cycle 2, and a reinstall onto a machine that has had Felhom is cycle 2 by definition. Why cycle 2 says yes: the preflight's ownership question is dpkg-query package presence and nothing else - not the absence of a record. Stopping the unit and leaving the package made our own package read as the household's one cycle later. Now the uninstall removes the package when the record says we installed it. Order unchanged and load-bearing: read the record, act, then delete the state file that holds it. TWO packages are recorded, because dnsmasq ships the unit and dnsmasq-base ships /usr/sbin/dnsmasq, and each is taken back only if we added it. The dependency check is a SIMULATION, not a guess: apt-get -s purge is asked what it would remove and the purge proceeds only if that set is a subset of ours; otherwise stop+disable, naming the package that blocked it. Never interactive, never fatal, and the success is re-queried rather than read off an exit code. Watched: three fixed cycles -> install 3 PASSES; a household resolver untouched; a dependent package not purged and named; no record -> untouched with the command named. Red-proofs with the mutation asserted applied: remove the purge -> cycle 3 refuses in those exact words; remove the ownership check -> a household resolver is purged; infer ownership -> the guess is taken. Also: R-317 (the agent stats a path dnsmasq-base owns to decide whether to install dnsmasq - pre-existing, now reachable), R-318 (no honest ownership marker exists for existing boxes; the preflight message is the mechanism), and the status page's decisions section rewritten to say what each decision costs and what doing nothing selects.
This commit is contained in:
@@ -1,3 +1,55 @@
|
||||
## felhom-host-install.sh v1.28.0 — the removal genuinely reverses the installation (2026-08-13, R-316)
|
||||
|
||||
**v1.27.0's fix worked exactly once per machine, and this is the measurement.** Three full cycles on
|
||||
`drill-r50` from `virgin`, on the PUBLISHED v1.27.0, before anything was changed:
|
||||
|
||||
| cycle | recorded | uninstall did | result |
|
||||
|---|---|---|---|
|
||||
| 1 | `no` | stop + disable | `:53` FREE |
|
||||
| 2 | **`yes`** | **left it running** | `0.0.0.0:53` taken |
|
||||
| 3 | — | — | **PRE-FLIGHT FAIL (exit 1)** — *"a resolver is already bound to :53"* |
|
||||
|
||||
**Why cycle 2 says `yes`:** the preflight asks `dpkg-query` whether `dnsmasq` is installed and nothing
|
||||
else — **package presence alone** (not the absence of a record). v1.27.0 stopped the unit and left the
|
||||
package, so the answer stayed yes and our own package became "the household's" one cycle later. **Every
|
||||
box already in the field is at cycle 2**, and a reinstall onto a machine that has had Felhom is cycle 2
|
||||
by definition.
|
||||
|
||||
**Now:** when the install-time record says we installed it, the uninstall **removes the package** as
|
||||
well as stopping the unit — which is what makes the next install a first install again. Order is
|
||||
unchanged and load-bearing: read the record, act, and only then delete the state file that holds it.
|
||||
|
||||
**TWO packages are recorded, not one.** `dnsmasq` ships the systemd unit; **`dnsmasq-base` ships
|
||||
`/usr/sbin/dnsmasq`**. They are separately installable, so each is recorded at preflight and taken back
|
||||
only if we added it.
|
||||
|
||||
**Guard rails, all measured on the box rather than reasoned:**
|
||||
|
||||
- **Ownership is read, never inferred** — no mtimes, no package metadata, no file dates.
|
||||
- **The dependency check is a SIMULATION.** `apt-get -s purge` is asked what it would remove, and the
|
||||
purge proceeds only if the answer is a subset of what we are entitled to remove. Otherwise: stop +
|
||||
disable, and the log names the package that stopped us.
|
||||
- **Never interactive, never fatal.** A wedged apt cannot strand a teardown — the failure is recorded
|
||||
and restated in the closing NOTE, because a half-completed teardown is how cycle 3 refuses.
|
||||
- **The success is asserted, not assumed:** after `apt-get` exits 0 the package is re-queried, because
|
||||
an exit code is not an observation.
|
||||
|
||||
**Scenarios, each watched:** three fixed cycles → **install 3 PASSES**; a household resolver →
|
||||
untouched; a dependent package (`household-dns-thing`) → **not purged, named in the log**, `:53` still
|
||||
freed; no ownership record → untouched, reason logged, exact command named.
|
||||
|
||||
**Red-proofs, mutation asserted applied each time:** remove the purge → **cycle 3 refuses again in
|
||||
those exact words**; remove the ownership check → **a household resolver is purged**; infer ownership
|
||||
when there is no record → **the guess is taken and a field box loses its own DNS**.
|
||||
|
||||
**Known residue, filed as R-317:** where `dnsmasq-base` pre-dated Felhom we correctly keep it, but it
|
||||
owns `/usr/sbin/dnsmasq`, which the agent stats to decide whether to install
|
||||
(`felhom-agent internal/lanresolver/lanresolver.go:105`). On that host shape the next install can skip
|
||||
the apt step and then fail to enable a unit that is gone. The uninstall says so explicitly rather than
|
||||
leaving it to be rediscovered from a silent resolver.
|
||||
|
||||
---
|
||||
|
||||
## PUBLISHED — `installer-v1.27.0` (2026-08-12)
|
||||
|
||||
**v1.26.0 (R-297) and v1.27.0 (R-300) were both written, pushed and deliberately left unpublished
|
||||
|
||||
Reference in New Issue
Block a user