bcdb04222a
Waiting to be bound is the NORMAL state of a freshly installed box, and it must
not be reported as failure. The PAIRING poll loop used to BE systemd's
Restart=on-failure/RestartSec=30 — one poll per invocation, exiting non-zero
until the bind landed — so every 30s systemd printed "Failed to start Felhom
host bootstrap" on the physical console the CUSTOMER is watching. The
2026-07-18 N100 rehearsal measured 52 FAILED lines in ~11 minutes while nothing
was wrong (VALIDATION-n100-rehearsal-2026-07-18.md F6).
felhom-bootstrap.sh: run_pairing() is now a while-loop that sleeps
POLL_INTERVAL (30s — the hub-side rate is unchanged) between polls, so the unit
sits in `activating`. Registration split into register_appliance(), which
returns non-zero for a transient problem (no network yet, no identity, no
token) and is retried by the loop instead of taking the unit down. Cadence
constants: POLL_INTERVAL=30, BANNER_EVERY=10 (5 min), HEARTBEAT_EVERY=20
(10 min).
Quiet without going dark: a 204 is logged once on entry (worded so nobody reads
it as an error) and then only on the 10-minute heartbeat with elapsed minutes;
404 and unexpected codes degrade the same way. 410 STILL exits non-zero on
purpose — delivery consumed but no local env is a real crash window, and a
clean systemd restart is the right response.
Console banner: every 5 min instead of every cycle, single accented spelling
instead of the parositasra/párosításra double, and the reassurance the
rehearsal showed was missing ("Ez a képernyő magától frissül — nincs teendő a
doboznál").
felhom-bootstrap.service: TimeoutStartSec=infinity. This is load-bearing, not
cosmetic — a Type=oneshot ExecStart is killed at DefaultTimeoutStartSec (90s),
so without it systemd would kill the new in-script wait after 90 seconds and
Restart=on-failure would silently reinstate the exact spam this removes, after
appearing to work for the first three polls. Restart=/RestartSec= are kept
deliberately: they still cover the DIRECT path, a failed host-install, and 410.
Verified behaviourally, not assumed: driven in a throwaway Debian container
against a stub hub answering 204 five times then delivering — logged the wait
once plus one heartbeat, never exited between polls, then consumed the
delivery, wrote the 0600 env, fell through to the direct install in the same
invocation and exited 0. The old design produced five unit invocations and five
"Failed to start" console lines for that same sequence.
Hub endpoints, payloads, polling rate and one-shot delivery semantics are all
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
33 lines
1.6 KiB
Desktop File
33 lines
1.6 KiB
Desktop File
[Unit]
|
|
Description=Felhom host bootstrap (fetch + run felhom-host-install.sh unattended, retry until success)
|
|
Documentation=https://felhom.eu/documentation/backlog/ROADMAP.md
|
|
# Network-dependent work lives HERE (not in the first-boot stub) per spike S8a: the stub is
|
|
# exactly-once and network-independent; this unit retries forever until host-install exits 0.
|
|
After=network-online.target pve-cluster.service pveproxy.service
|
|
Wants=network-online.target
|
|
# Second exactly-once lock (the script also writes/checks this flag): once host-install succeeds,
|
|
# the flag exists and this unit becomes a no-op even if re-enabled.
|
|
ConditionPathExists=!/etc/felhom/.bootstrap-done
|
|
# Retry forever: no start-rate limit.
|
|
StartLimitIntervalSec=0
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/sbin/felhom-bootstrap.sh
|
|
# v1.21.0 (R-33): the PAIRING wait now loops INSIDE the script, so this unit sits in `activating`
|
|
# while a box waits to be bound instead of failing every 30s. That wait is unbounded by nature —
|
|
# it ends when a human binds the box — so the oneshot start timeout MUST be lifted. Without this,
|
|
# systemd kills ExecStart at DefaultTimeoutStartSec (90s) and Restart= silently reinstates exactly
|
|
# the `Failed to start` console spam this change exists to remove.
|
|
TimeoutStartSec=infinity
|
|
# Still retry on a genuine non-zero exit (fetch failed, host-install failed mid-run, crash-window
|
|
# 410). The pairing wait no longer exits non-zero just because nobody has bound the box yet.
|
|
Restart=on-failure
|
|
RestartSec=30
|
|
# Journal-only logging (no secret ever printed; keys-not-values).
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|