16 Commits

Author SHA1 Message Date
admin e0b56c976f REPORT + CONTEXT: the third name, the second door, and a number that answered a different question
gates / gates (push) Successful in 15s
Three rules carried forward. A name must separate on the STEM, not the noun — naming
this secret after the act it is used in would have recreated the trap, because the
other factor on the same page is the „Párosító kód". A guard is worth what its positive
control is worth: this one's selftest convicted its own step-3 case and found a defect
in the guard itself. And a suppression must rest on the machine's own declaration, then
be checked for the SECOND door — recording the disabled state rather than deleting it
is what let the deadline check skip it too.

Yesterday's report is preserved to audits/ because it carries the only record of the
self-heal verdict (Part C was dropped, so that reasoning is in no register row) — the
rule written last night, applied to itself the first time it mattered.
2026-08-13 16:01:45 +02:00
admin bbd59f4a44 Deploy hub 0.105.0 — the third name, the quiet-machine alarm, and the copy guard
gates / gates (push) Successful in 13s
2026-08-13 15:51:49 +02:00
admin b03a105375 hub v0.105.0: the third name, a machine told to be quiet, and a guard for the hub's own words
gates / gates (push) Successful in 17s
Hub only. No controller change, no agent change, no wire change — nothing to bake.
demo-hp untouched: the operator is re-deploying it this evening.

R-323 — the five-word phrase is „Tulajdonosi jelmondat". It was „Visszaállító
jelszó": one word from the name retired last week, and false besides — it restores
nothing, it proves the account owns the box being bound. Five sites, all in the hub;
felhom-controller and felhom-agent carry the name nowhere, so no halt and no bake.
Both suggested names were rejected with reasons: „Fiókjelszó" would collide with the
dashboard login (a DIFFERENT real secret), and „Összekötési jelszó" would leave the
two factors on this page separated only by kód-versus-jelszó — the exact shape being
removed, since the other factor is the „Párosító kód". The chosen name differs on
both axes, stem and noun. Naming only; the acceptance pin drives the real handler.

R-324 — the hub's customer copy is under a guard for the first time. Retired names
banned across all 95 hub files; retrieval stems registered in four declared customer
surfaces. The selftest found a defect in its own instrument on the first run. One
shared vocabulary in scripts/, drift-checked into the controller gate rather than
copied (R-325 removes the scaffold).

R-321 — a machine we told to be quiet is no longer reported as dead, and it was two
doors, not one: because the state is RECORDED rather than deleted, the morning
deadline check can skip it too. A deleted state returns "", which is not "down" —
R-195's shape returning through a second door. The clock runs from the report the hub
can see, so re-enabling starts it there and emits no recovery for an outage that never
happened. Three red-proofs; the one that matters showed a genuinely dead machine
sitting at "disabled" when the suppression was made unconditional.

R-326 — "which claims are unproven" is answerable by a command now. The nine I have
been repeating was the count of claims the 9 August pass DOWNGRADED, not the count of
unproven ones. The real figures: 55 claims, 23 walked, 32 not — and only 6 of those 32
cite evidence. Its first run found a stale claim (R-327).
2026-08-13 15:50:32 +02:00
admin 955a4f07b7 REPORT + CONTEXT: A4 reverses its own premise, the naming half, and the first R-264 reader
gates / gates (push) Successful in 41s
A4's evidence contradicts the task's framing and the register now says so: peti-felhom
is a real machine with 482 reports and a real person behind it; david -> tester-1 is a
record that has never had a host, an escrow or a report. The risk is real; only the
word that named it was wrong.

CONTEXT gains three standing rulings: read the fact that carries the RISK (heals_last_hour,
not state) and decode the one that decides the question (heal_succeeded — R-260's lesson);
one secret in two situations keeps its name and changes its sentence, and the mail must
name the page the machine actually shows; and evidence dies in the INTERMEDIATE revert —
with the corollary that a durable citation may never point at a file whose contract is to
be overwritten, which is why the R-316 report was moved to audits/ before this one was
written.
2026-08-13 10:59:55 +02:00
admin 7c97c949f6 Deploy hub 0.104.0 — the guest-network reader and the naming half
gates / gates (push) Successful in 25s
2026-08-13 10:51:23 +02:00
admin 4d6ec7c7bb hub v0.104.0: the guest network gets a reader (R-319), and the hub half of the naming (R-295)
gates / gates (push) Successful in 14s
Four paper debts and one fact given a reader. Hub-only — nothing to bake.

A4 — the entry about "the tester's machine" named a risk correctly and labelled it
in a way that invited deleting it. Established from the hub's own store: `peti-felhom`
is a REAL machine (482 reports, 2026-02-27 → 2026-07-15, a named person's own box) and
the 3.6 GB with no key and no backup is real. `david` → `tester-1` is a DIFFERENT record
with no host, no escrow and no report, ever — deleted 07:55:49 and re-created 07:56:47
this morning. The prompt's premise conflated the two; the register now says which is which.

A1 — R-312/R-313/R-303 recorded as DECIDED with their re-open triggers, and moved out
of STATUS's "Waiting on you", which is now empty.

A3 — day0-install §C.1 said pushing the installer publishes it. It has not since
R-110. Corrected, with the two manifest pins named and an outside-verification command;
the one copy that repeated it (a dated audit, true when written) carries a superseded note.

A5 — standing rule 5: evidence comes off the machine at the end of the phase that
produced it, before any revert. Earned twice in three days on the same box at the same
point (R-320). Four homes, plus what to do when it is already gone.

R-295 hub half — „Beállító kód" everywhere; „Visszaállító kód" retired. New `reenroll`
mail kind so the mail names the page a REBUILT box actually shows („A szerver
beállítása"), not the „Elfelejtett jelszó" page it has no login screen to reach.
Naming only; the acceptance pin proves the secret is untouched.

R-319 — the hub models `guest_net` after 23 days of receiving and discarding it. The
signal is `heals_last_hour`, not `state`: a guest the watchdog keeps repairing reads
healthy between repairs. `heal_succeeded` decoded too (R-260's lesson). Unknown is never
drawn as healthy — three absences, three sentences. No alarm, deliberately.
Three red-proofs, mutations asserted applied. Wire-gate checked tags 182 → 190.

B1 — the operator's 2026-08-12 dispositions were NOT in the register; they are now.
Third allowlist kind for the five ruled "no reader wanted"; `reporting_disabled`
reclassified redundant. 8 read · 5 deliberately unread · 1 redundant · 6 still owed.

Also filed: R-321 (a deliberately-silent box still alarms stale/down — the checker is
age-only, and decoding the flag would not have fixed it), R-322 (the claim guard has
never scanned the hub; a hand scan returns zero, so it is a scope gap, not a defect).
2026-08-13 10:50:12 +02:00
admin 2d05b29b82 REPORT: installer v1.28.0 published and verified live; evidence, and the Part 1 logs I lost again
gates / gates (push) Successful in 13s
2026-08-13 08:19:41 +02:00
admin 823cd2949b Publish installer-v1.28.0: move both git-sync refs to the new tag
gates / gates (push) Successful in 13s
Pushing publishes nothing here - /scripts/ follows the installer TAG, and both the
sidecar and the init container carry the ref. Verified against the live URL after
the sync, because the runbook still says otherwise (R-309, still open).
2026-08-13 08:16:28 +02:00
admin fc737b0fc0 installer v1.28.0: the removal genuinely reverses the installation (R-316)
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.
2026-08-13 08:16:04 +02:00
admin d102ca5767 Vouch delivered fleet-wide; the MinAgent hold observed firing and releasing for the first time
gates / gates (push) Successful in 20s
Operator vouched golden 0.214.0 / agent 0.129.0 / min agent 0.129.0 and raised the
floor to 0.214.0. Both artifact shas in hub_settings match the bake and the release
byte-for-byte.

With the floor at 0.214.0 and demo-hp still on agent 0.128.0, the hub HELD the
controller floor - 'agent 0.128.0 < MinAgent 0.129.0 (controller floor withheld)' -
and released it 6 seconds after the agent was brought up. That is the R-216 guard
doing exactly what it exists for, seen firing for the first time, and it is the
argument for declaring MinAgent in the CHANGELOG header.

Both demo boxes verified on the boxes: controller 0.214.0, agent 0.129.0, healthy.
2026-08-12 21:14:38 +02:00
admin 684cd2eb11 R-265 third sighting: the jobs API names the failing step when the log 404s, and a re-run disambiguates
gates / gates (push) Successful in 15s
2026-08-12 19:18:44 +02:00
admin 4906aeb3f9 R-311 proven live (HTTP 422 on hardware); R-308 WITHDRAWN — my quoting bug, not a stale credential
gates / gates (push) Successful in 19s
The live test read as a FAILURE for twenty minutes because I stripped only double
quotes from a credentials value wrapped in SINGLE ones, sending a literal ' as part
of the recovery code. Correctly unquoted: the old code returns 422 with
opens_retained=true and the supersession date; a wrong code still returns 400.

The same bug produced the R-308 finding in the previous report. The dashboard
password is fine - HTTP 302 with a session cookie on the first try. Third time this
project has produced a wrong 'the credential is stale' verdict from that one trap.
2026-08-12 19:05:58 +02:00
admin 8b188bea68 hub v0.103.0 — a host can read the packages we kept for it (R-311)
gates / gates (push) Successful in 37s
ListSupersededEscrow had zero production callers for nineteen days. It is the only
reader of a retained identity_blob, so the retention shipped in v0.93.0 was
material the product could not reach - proven on the fixture 2026-08-12, where a
code that opens a retained package was answered as a code that opened nothing.

New GET /api/v1/hosts/<id>/escrow/retained: self-scoped exactly as the current-row
GET, same recovery-mode gate, same audit event written BEFORE the bytes leave,
capped at 16. Rows with a NULL identity_blob are WITHHELD and returned as
unopenable_count - they retain the PBS key, not the repository password, so they
can never open what the caller is asking about, and serving them would let the
screen claim an earlier package is openable on exactly the boxes the original
defect hurt. The count is returned because their existence is load-bearing and
underivable by the caller.

The trade, stated rather than waved through: the hub still cannot read any of it -
sealed bytes in, sealed bytes out, no decrypt path, no recovery code ever held.
What widens is volume, bounded by self-scope, the recovery-mode gate and the cap.

The response is a NAMED TYPE, not a map, so the wire-contract gate can resolve it;
the wire is declared as a fourth ROOT and the gate now checks 182 tags rather than
174. A positive control shows that check is name-presence, not decodability -
filed as R-315 rather than reported as coverage.

Also: golden 0.214.0 baked, published and round-trip verified; the countdown on
demo-felhom cancelled on the operator's ruling (R-307); the spike that halted
Part 3 recorded as R-312; the set-aside store found unrecoverable as R-313.
Six hub tests through the real endpoint; four red-proofs asserted applied.
2026-08-12 18:56:44 +02:00
admin 6362bb6cb6 hub v0.103.0 — a host can read the packages we kept for it (R-311)
ListSupersededEscrow had zero production callers for nineteen days. It is the only
reader of a retained identity_blob, so the retention shipped in v0.93.0 was
material the product could not reach - proven on the fixture 2026-08-12, where a
code that opens a retained package was answered as a code that opened nothing.

New GET /api/v1/hosts/<id>/escrow/retained: self-scoped exactly as the current-row
GET, same recovery-mode gate, same audit event written BEFORE the bytes leave,
capped at 16.

Rows with a NULL identity_blob are WITHHELD and returned as unopenable_count.
They retain the PBS key, not the repository password, so they can never open what
the caller is asking about; serving them would have the agent try packages that
cannot succeed and would let the screen claim an earlier package is openable on
exactly the boxes the original defect hurt. The count is returned because their
existence is load-bearing and underivable.

The trade, stated rather than waved through: the hub still cannot read any of it -
sealed bytes in, sealed bytes out, no decrypt path, no recovery code ever held.
What widens is volume, bounded by self-scope, the recovery-mode gate and the cap.

The response is a NAMED TYPE, not a map, so the wire-contract gate can resolve it;
the wire is declared as a fourth ROOT and the gate now checks 182 tags rather than
174. A positive control shows that check is name-presence, not decodability -
filed as R-315 rather than reported as coverage.

Six tests through the real endpoint; four red-proofs asserted applied.
2026-08-12 18:43:07 +02:00
admin 1d4985d87c REPORT: the same wall-clock correction
gates / gates (push) Successful in 16s
2026-08-12 17:45:39 +02:00
admin c1319a91a8 Correct the drill's wall clock to the measured end time (17:45, not the estimated 17:55)
gates / gates (push) Successful in 19s
2026-08-12 17:45:23 +02:00
66 changed files with 16287 additions and 176 deletions
+7
View File
@@ -125,6 +125,13 @@ something, not only sessions that touch `documentation/` — which is why it is
the operator in plain language, and deliberately **not** `CONTEXT.md`.
- **The capability map** (`documentation/architecture/00-capability-map.md`), if a capability's
status changed — with its new evidence citation.
- **`python3 scripts/unproven.py --summary`** — one line per status, and the not-walked total. Run it
at the end of any session that shipped, broke or proved something, and **say in the report if a
number moved**. It exists because "which claims are unproven?" was answerable only by a person
reading a page: a session asked for "the nine grey claims" could not determine which nine and
rightly refused to guess (R-326). *Nine was real and answered a different question — it is the
count of claims the 2026-08-09 pass DOWNGRADED. Not-walked is 32 of 55.* A status that moves
without anyone noticing is how the picture stops being true.
- **Confirm your own last push's CI run went green, by run ID.** CI emails on failure, which is a
PUSH signal; this is the PULL check that catches a lost, filtered or unread mail. Quote the run id
and its conclusion, e.g.
+222
View File
@@ -15,6 +15,228 @@
> would make one of the two audiences stop reading. `STATUS.md` is also a **view of `OPEN-ITEMS.md`**
> and holds nothing of its own; this file does hold its own content, namely the standing rulings below.
## A name must differ from its neighbours on a stem, not on a noun (2026-08-13, R-323)
The third near-homograph is renamed: the five-word phrase that proves an account owns the box being
bound is „**Tulajdonosi jelmondat**". It was „Visszaállító jelszó" — one word from the „Visszaállító
kód" retired days earlier, and false besides: **the phrase restores nothing.**
**The rule this instance yields, and it generalises past this secret.** Both obvious replacements were
rejected, and the reasons are the transferable part:
- **Do not name a secret after the ACT it is used in, when another secret on the same screen is named
after the same act.** The other factor on that page is the „**Párosító kód**". „Összekötési jelszó"
would have left the two things a customer types in one sitting separated only by *kód*-versus-
*jelszó***structurally the very trap being removed**. A name that matches the page's verb feels
right and is the wrong axis.
- **Do not name a secret after a THING that already has a password.** „Fiókjelszó" collides with the
dashboard login, i.e. trades one homograph for a worse one.
- **Separate on the STEM first, the noun second.** Beállító / Helyreállítási / Párosító / **Tulajdonosi**
— four distinct stems; *kód* / *jelmondat* is the second axis, not the first. A pair distinguished
only by its noun is what failed twice.
**Naming is not function, and the pin says so** (`TestSelfBindPassphrase_StillAcceptedAfterTheRename`
drives the real handler with human-messy spacing). **Nothing printed was stranded** — no customer
document names the phrase; it is delivered out-of-band, so the description in the hint sentence is
what identifies it, which is why that sentence was kept and extended rather than trimmed.
## A guard is worth what its positive control is worth (2026-08-13, R-324)
The hub's customer copy came under a guard for the first time — every customer e-mail and both binding
pages, the sentences a customer reads *before* they have seen any box screen. Nothing had ever looked
at them, in either repo.
**The part worth carrying: the selftest found a defect in the guard itself on its first run.** The
plant→convict→remove→pass control convicted its own step-3 case (a phrase inside a comment) because
the synthetic source was named `<selftest>` and comment-stripping keys off the `.go` extension. **A
guard that had shipped without a control would have been quietly over-broad**, and the first person to
write an honest explanatory comment would have been convicted by it.
**And on sharing a word list across two repos:** the vocabulary lives once, in `scripts/`, the same
home both repos already consume gates from. The consumer that could not be edited that evening is
**drift-checked rather than duplicated** — the gate reads the other gate's literal and fails on
divergence. That is a scaffold with a register row (R-325) to remove it, not a design; **two copies of
a word list is the R-299 defect waiting to happen**, and a drift check is the cheapest way to refuse
the copy without making a cross-repo edit at the wrong moment.
## Suppress an alarm on the machine's own declaration, never on an inference — and check for the second door (2026-08-13, R-321)
A box whose reporting is deliberately switched off announces it (`health.status = "disabled"`) and
then goes quiet by design. The hub stored that, rendered it, and **alarmed on it anyway**, because the
staleness verdict was computed from report AGE alone.
**Three things worth carrying:**
1. **The discriminator must be the box's own last word.** That is what made this a suppression rather
than a guess — and the standing rule is right that suppressing on a guess is worse than the false
alarm it removes.
2. **RECORD the state; do not delete it.** The `blocked` precedent deletes, and `GetState` then
returns `""` — which is not `"down"`, so **`CheckBackupDeadlines` would have gone on alarming from
a second function.** One fix, two doors; R-195's shape returning through the one nobody looked at.
*Ask of any suppression: which OTHER checker reads this state, and what does it see now?*
3. **A clock must restart at the event that ends the suppression**, not at the last observation before
it began. Timing from the pre-disable report would fire an instant stale/down for a quiet period we
requested. Re-entering the new-customer branch gives a first observation instead of a recovery
event.
**The limit is stated in the code, not hidden:** a box re-enabled that then fails to report keeps
being suppressed, because the hub's view changes only when a report arrives. **That is why the state
is made visible** — an operator who re-enabled a box and still sees `disabled` is being told it has
not come back.
**And a test lesson:** red-proof 2 revealed that scenario A passed on its state assertion alone even
with the suppression deleted, because it seeded the customer *already* disabled and the new-customer
branch swallows the first observation. **A test that cannot see the alarm it exists to prevent is not
a test** — it now observes the machine healthy first.
## A number everyone repeats is a claim, and claims get checked (2026-08-13, R-326)
"The nine grey claims" was repeated across sessions and **exists nowhere**. A session asked to report
on them could not determine which nine and declined to guess; that refusal was correct and is the
finding.
**Nine is real and answers a different question: it is the count of claims carrying
`verdict: downgraded`** — the ones the 2026-08-09 pass *lowered*. The count of unproven claims is
**32 of 55** (`walked` 23, `partial` 14, `built` 14, `missing` 4), and of those 32 only **6** cite an
evidence document.
`scripts/unproven.py` makes it a command, wired into the end-of-session checklist. **The generalisable
part: a picture built for a person to read is not a picture anyone can check.** The dataset carried the
statuses all along; what was missing was one command that answers the question the operator actually
asked. Its first run found a stale claim (R-327) — which is the argument for having built it.
## A fact the boxes send and the hub cannot read is a future false green — and one now has a reader (2026-08-13, R-319)
**The first of R-264's twenty-one unconsumed facts is read.** The agent emitted `guest_net` on every
heartbeat for **twenty-three days** (agent v0.92.0, R-54, 2026-07-21) while the string occurred
**nowhere** in `felhom.eu/hub/` — stored as raw text in `report_json`, reaching no check, no alarm and
no screen. Hub v0.104.0 models it: `parseGuestNet`/`guestNet` in `web/hosts.go`, rendered as the
host-detail **Guest network** card.
**The rule this instance teaches, and it generalises past guest networking:**
- **Read the fact that carries the RISK, not the fact that is easiest to render.** Here that is
`heals_last_hour`, not `state`. A guest the watchdog keeps repairing is healthy at every instant
anyone looks and is nevertheless failing — rendering the state alone gives it a green tick, which is
the failed-disk-drawn-as-a-healthy-empty-disk shape wearing different clothes.
- **Decode the field that decides the question, not five of the eight.** `heal_succeeded` was added
mid-build for exactly this: six FAILED repairs is a guest that is down, six successful ones is a
nuisance, and the count alone cannot tell them apart. **R-260 is the standing warning** — a hub
decoder three fields short of the agent dropped the one its checker existed to answer.
- **Three absences, three sentences.** Agent too old / capable agent silent / guest state not asserted.
All render unknown; conflating them is how an operator starts ignoring a card. The state switch is an
**allow-list**, so adding a state to the agent can never silently paint it green here.
- **REMOVING an allowlist entry is what proves a reader is wired.** An allowlisted tag is *skipped* by
`wire_contract_gate.py`. Re-labelling the eight `guest_net` entries would have left the new reader's
own fields unchecked while the gate reported coverage. Deleting them moved checked tags **182 → 190**
and skipped **88 → 80** — the positive control that the wiring is real, independent of any test.
- **A card is not an alarm, and building the card first is deliberate.** No email was added: the
incident behind this (a killed `dhclient`, tunnel down 1 h 15 m, nobody told) was a **visibility**
failure, and with zero repairs ever recorded on either demo box any alarm threshold would be invented.
**The visible count is what will supply the threshold.**
**Where the twenty stand** (R-264, counts measured from the allowlist rather than estimated):
**8 read · 5 deliberately not consumed · 1 redundant · 6 still owed a reader.** The gate grew a **third**
entry kind — `not consumed, deliberately` — because an undecided fact and a decided one must not read
alike, and *"arguably owed"* had been carried for five days as though it were a decision.
**Two findings came out of deciding the last one rather than from building anything:**
- **R-321** — `reporting_disabled` is *redundant* (`health.status = "disabled"` travels beside it, is
decoded, and IS rendered), **but `StalenessChecker.Check` is age-only** (`monitor/staleness.go:88+`,
sole skip `IsCustomerBlocked`), so a deliberately-silent box still alarms `node_stale` then
`node_down`. **Decoding the flag would have felt like progress and left the alarm firing** — the fix
belongs in the checker, which already holds the status it needs.
- **R-322** — `retrieval_promise_gate.py` has **never scanned the hub**, which composes every customer
e-mail, i.e. the copy read *before* any box screen. A hand scan with its own four stems returns zero,
so it is a scope gap rather than a live defect — recorded before someone writes the first hub-side
retrieval promise assuming the guard has them covered.
## One secret in two situations keeps its NAME and changes its SENTENCE — and the mail must name the page the machine shows (2026-08-13, R-295 hub half)
The box side shipped 2026-08-10; the hub half was dropped twice and was the last place „Visszaállító
kód" survived — in the **mails**, the one surface a customer reads *before* they see any screen. It is
now „**Beállító kód**" everywhere; the ten-word escrow code stays „**Helyreállítási kód**"; no
dashboard-code mail names both.
**The mechanism worth carrying: the mail names a PAGE, and which page depends on state only the hub
knows.** `ReissueForReenroll` sent the *reset* mail, directing the customer to „Elfelejtett jelszó" —
but a rebuilt box has no password, so the controller computes `reset := s.authEnabled()` → false
(`felhom-controller/controller/internal/web/claim.go:279`), renders „**A szerver beállítása**", and
serves **no login page at all**. The named route was not on their screen. **The two situations are
distinguishable only at the hub, because the hub chose which call site fired** — so the fix is a new
`EmailKind` (`reenroll`), not a reworded shared template. A `default` branch falls back to the
first-setup mail, which is the safe direction and is pinned so a future reader knows it was chosen.
**The re-enrol mail deliberately says nothing about apps or backups.** A clean-slate reinstall is
precisely where that reassurance could be false; `TestFormatClaimEmail_ReenrollPromisesNothingAboutTheData`
guards the CLAIM rather than one phrasing of it.
**Naming is not function, and the pin says so:** `TestReenrollSplit_ChangesTheMailNotTheSecret` — the
generation still advances, the stored bcrypt hash still verifies the emailed code, no plaintext is
persisted. Box-side companion: the controller's `TestResetCode_StillAcceptedOnTheSetupPage`.
## Evidence dies in the INTERMEDIATE revert, not the final one (2026-08-13, R-320)
Twice in three days, on the same box at the same point, a phase's logs were destroyed by a revert to
`virgin` *between* phases (drill 2026-08-12 Phase A→B; R-316 2026-08-13 Part 1→2). Both times the
golden-bake runbook's existing *"scp the log OUT first"* was applied to the **final** teardown and not
the middle one. Both times the conclusions survived on luck.
**Standing rule 5** now: evidence comes off at the end of the phase that produced it, before any revert
**the pull is the LAST ACT OF THE PHASE**, not a step to remember later. Four homes:
`workspace-CLAUDE.md`, `runbooks/target-selection.md`, `RUNBOOK-rehearsal-v3.md`, `PROMPT-TEMPLATE.md`
report item 8. **And the already-gone case is documented rather than improvised:** say so plainly and
reproduce the finding independently.
**A corollary this session hit immediately:** the second incident was recorded in `REPORT.md`, which
every session overwrites — writing tonight's report would have **destroyed the record of a destroyed
record**. It was preserved to `audits/REPORT-r316-installer-v1.28.0-2026-08-13.md` first. *A durable
citation may never point at a file whose contract is to be overwritten.*
## Material retained is not history recoverable — ask the three questions separately (2026-08-12)
**"We keep the old key" and "the customer can get their old backups back" are three questions, and
collapsing them is how a false belief survived a week.** Ask them one at a time, and answer each with
its own evidence:
**(a) Is the material retained?** **(b) Does it, once unsealed, still open the old store?**
**(c) Does the customer have a ROUTE to (b) through the product?**
Measured 2026-08-12, and the answers were **yes, yes, no** — a shape nobody had predicted. Our records
predicted (a) yes and (b) no ("a box we fill and cannot open"); the drill found (b) works and **(c) was
the missing one.** `ListSupersededEscrow` had held the right bytes since v0.93.0 with **zero production
callers**, so the capability existed and was unreachable. The same afternoon's follow-up shipped a
partial (c) — the product now RECOGNISES a correct code for an earlier package — and established that
the rest of (c) is new surface, not wiring, because every restore entry point resolves its repository
from settings and its password from one file (R-312).
**The generalisation, which is the part worth keeping:** a stored artifact proves storage, never
usability. Between "the bytes are kept" and "the customer is helped" sit an unseal, a consumer and a
route, and each has been separately absent in this project. **Before claiming a capability, name which
of the three you have evidence for** — and if a comment or a card asserts the third, find the code path
that performs it or move the claim.
## A removal that leaves the package leaves the next install a second-cycle machine (2026-08-13)
**Stopping is not removing, and the difference does not show up until the cycle after next.** R-300
stopped and disabled the resolver we install and left the package in place — deliberately, as the
conservative act. Cycle 1 looked fixed and *was* fixed. But the next install asks dpkg "is this
installed?", gets yes, records the package as the household's, and the uninstall after that leaves it
running: cycle 3 refuses with the message that blames the customer's network. **Measured, three cycles,
before anything was changed.**
**The general shape:** a teardown that leaves an artifact behind does not merely fail to clean up — it
*changes what the next run concludes about who owns that artifact*. Ask of any "we left it in place,
to be safe": **what will the next install think this is?** If the answer is "ours, misread as theirs",
the conservative act has quietly become the destructive one, one cycle downstream.
**And the corollary that nearly shipped a second defect:** the thing you remove and the thing something
else probes for may be in **different packages**. `dnsmasq` ships the unit; `dnsmasq-base` ships the
binary the agent stats to decide whether to install. Removing one and not the other satisfies the
uninstall and breaks the next install silently (R-317). **Before removing a package, ask what else
tests for the files it shares with its siblings.**
## Two rules earned twice each (2026-08-12)
**A fact must be recorded at the moment it is a fact, not inferred later from something adjacent.**
+334 -68
View File
@@ -1,87 +1,353 @@
# REPORT — DRILL: the retained key, and the two fixes nobody had watched work (2026-08-12)
# REPORT — the third near-homograph, a machine told to be quiet, and a picture you can query (2026-08-13, late)
**Class:** drill (unattended, destructive on Tier 0) + spike for Phase C's first step
**Venues:** `drill-r50` (nested PVE on DooPlex), `demo-felhom` (guest 9201) — both Tier 0.
**`demo-hp` was never touched. `peti-felhom` was never contacted. No abandon countdown was started,
shortened or triggered.**
**Full record:** `documentation/audits/DRILL-retained-key-2026-08-12.md`
**Shipped: hub v0.105.0, deployed and verified.** All three parts complete; **nothing dropped**.
**`demo-hp` was not touched** — see §8. No controller change, no agent change, no wire change, so no
bake and no approval.
---
## The answer to the question this drill existed to answer
## 1. Part 1 — the enumeration, the name, and the reasoning
**(a) Is the old key kept? YES** — proven for the first time in the fleet's history.
**(b) Does the kept key open the old backups? YES** — three planted files, including a Hungarian
accented filename verified as raw bytes, restored **byte-identical** from a store the machine itself
could no longer open.
**(c) Can the customer get there through the product? NO — and they are told their correct code is
wrong.**
**Enumerated before editing. Five customer-facing sites, all in the hub:**
The brief said to be ready for the answer to be no, and our own records predicted retention would be
*"a box we fill and cannot open"*. **That was half right, and the wrong half was the one nobody had
checked.** The box opens. What does not exist is the door: `ListSupersededEscrow`
(`hub/internal/store/store.go:2841`) is the only reader of a retained key and has **zero production
callers**; the recovery path selects `FROM host_escrow` the current row only. Asked with the very
code that had just opened the retained row by hand, the product answered *"the recovery code did not
open the sealed bundle — nothing was written"*. → **R-304, rank 1**
| `file:line` | what it is |
|---|---|
| `hub/internal/web/selfbind.go:255` | the binding page's lead sentence |
| `hub/internal/web/selfbind.go:256` | the failure banner (said „a jelszót" — generic, but naming the secret) |
| `hub/internal/web/selfbind.go:261` | the field label — „Visszaállító jelszó" |
| `hub/internal/web/selfbind.go:263` | the hint under the field |
| `hub/internal/notify/templates.go:331` | the self-bind e-mail, item 2 |
Consequences: the census answer **stands**; the countdown banner's promise is **true in substance,
false in practice**; the capability map's recovery claim **has been moved** with today's evidence.
**No halt. The name appears nowhere in `felhom-controller` or `felhom-agent`** — the only hits there
are comments and a test asserting the *already-retired* „Visszaállító **kód**" is absent, which is a
different secret. Operator surfaces call it *"Retrieval Password"* in English and the installer uses
`FELHOM_RETRIEVAL_PASSPHRASE` as a shell identifier; neither is customer copy and neither was touched.
## What shipped
### The name: **„Tulajdonosi jelmondat"**
**`installer-v1.27.0` published** — tag cut and **both** `--ref`s in `manifests/webpage.yaml` bumped
(sidecar line 327, init container line 372). Publication was earned: both faults were watched
happening first, from a machine reset to factory state.
Checked against the table rather than against a habit. The phrase **proves the account owns the box
being bound — it restores nothing, so the old name was simply false.**
- **R-300 CLOSED** — pre-fix uninstall left dnsmasq `enabled`/`active` on `0.0.0.0:53`; the next byo
install refused, exit 1. Fixed path: recorded `not present before Felhom``stopping + disabling
it``:53 FREE` → preflight PASS. The owner's side proven too (record `yes` → left running).
- **R-297 CLOSED** — a stale `golden-0.98.3.tar.zst` planted as newest-by-filename; v1.25.0 took it
with no comparison and **the box came up on controller 0.98.3** against a vouched 0.213.0 — below
the floor and below v0.206.0 where the off-site recovery screen exists. Fixed path re-fetched and
sha-verified the vouched golden (landed 0.213.0); an operator-named stale archive was **refused**.
**Both of your suggestions are rejected, and the reasons are the argument:**
## Findings opened — ceiling R-303 → R-310
- **„Fiókjelszó" is worse than the trap it fixes.** There *is* an account password — the dashboard
login. This name would collide with a **different real secret**, trading one homograph for a worse
one.
- **„Összekötési jelszó" recreates the trap structurally.** The other factor on this very page and in
the same mail is the **„Párosító kód"** (`selfbind.go:258`, `templates.go:330`). Naming this one
after the same act would leave the two factors a customer types **in one sitting** separated only by
*kód*-versus-*jelszó* — which is precisely the „Visszaállító kód"/„Visszaállító jelszó" shape being
removed. It matches the page's verb, and that is exactly the problem.
| # | Rank | What |
**„Tulajdonosi jelmondat" is distinct on BOTH axes:**
| | stem | noun |
|---|---|---|
| **R-304** | **1** | Retained key has no product route; the correct old code is reported as wrong |
| **R-305** | 2 | The R-300 cleanup fires **once per machine** — the leftover returns on the second reinstall (proven, cycles 2/3) |
| **R-308** | 2 | Stored controller `PASSWORD` no longer opens demo-felhom (`Hibás jelszó`) — not the quoting trap |
| **R-306** | 3 | `--preflight-only` says *"no state written"* and writes `state.json` — with an ownership answer that can be wrong |
| **R-309** | 3 | The day-0 runbook says pushing publishes the installer; false since R-110 (measured: public URL served 1.25.0 while `main` had 1.27.0) |
| **R-310** | 4 | Duplicated sentence in the golden refusal; `--uninstall` needs a pty and `--force` does not bypass it |
| **R-307** | — | **Operator decision, deadline 2026-08-24** — see below |
| „**Beállító** kód" | Beállító | kód |
| „**Helyreállítási** kód" | Helyreállítási | kód |
| **Párosító** kód" (the other factor) | Párosító | kód |
| „**Tulajdonosi** jelmondat" | **Tulajdonosi** | **jelmondat** |
## What needs you
*If a plainer noun is ever wanted, „Tulajdonosi jelszó" is a one-word change — the **stem** is what
carries the separation.*
**`demo-felhom` carries a live abandon countdown** — started 2026-08-10, **firing 2026-08-24**, for
`/home/felhom-repo.orphaned-20260810`. This drill did **not** start it and deliberately did **not**
cancel it. The brief's end state asked for no countdown anywhere; satisfying that means choosing:
**cancel it** (copy kept indefinitely, storage cost, no data risk) or **let it run** (copy deleted,
irreversibly). **Doing nothing selects deletion.****R-307**
### As shipped, bytes confirmed
## End state
| string | hex |
|---|---|
| `Tulajdonosi jelmondat` | `54756c616a646f6e6f7369206a656c6d6f6e646174` |
| `tulajdonosi jelmondatodat` | `74756c616a646f6e6f7369206a656c6d6f6e6461746f646174` |
| `tulajdonosi jelmondatot` | `74756c616a646f6e6f7369206a656c6d6f6e6461746f74` |
| `Ez igazolja, hogy a fiók a tiéd` | `457a206967617a6f6c6a612c20686f67792061206669c3b36b2061207469c3a964` |
- **`demo-felhom`** — up, reporting, healthy, on the vouched pair; `repo_password` restored to the
original (`sha c60c8bc737a6b7c6…`), escrow re-sealed and uploaded, off-site repo reachable
(`restic snapshots` exit 0). Its recovery code was rotated by the final ceremony and
`R_DEMO-FELHOM` updated in place (prior file backed up alongside). Planted data removed; eight
secret-bearing files **shredded**.
- **`demo-hp`** — untouched, reporting.
- **`drill-r50`** — **reverted to snapshot `virgin`, powered off.**
- **Hub** — two new retained rows (the P1 and P2 blobs), deliberately kept as the fixture proving the
retention works. `drill-r50-0a4f9a` re-used, not duplicated: no new scratch customer.
- **Off-site** — only demo-felhom's own repository path touched, `backup` the only mutating verb used.
**No prune, no forget, no delete, no rename anywhere.** One snapshot added and deliberately left:
`6ea85413`, 66 KiB, tagged `drill-retained-key-20260812` — removable by ID if you want it gone.
Mojibake check clean in both files. **Naming only:** the form field is still `name="passphrase"`, and
`TestSelfBindPassphrase_StillAcceptedAfterTheRename` drives the real handler with the same messy human
spacing (`" Alpha Beta gamma-delta epsilon "`) and asserts the appliance still binds. The whole
pre-existing self-bind suite (A, B, C1, C4, D, E, F, mint × 2) stayed green.
## Honest gaps
---
- **The Phase A logs did not survive** the intermediate revert to `virgin`. Every quotation in the
audit is verbatim from the live run, but the raw files are gone. Procedural lesson, recorded.
- The planted data reached the store via `restic` directly, not the dashboard button, because of
R-308 — so the app-backup→unit→offsite chain went unexercised. Not what this drill measured.
- Wall clock **≈ 1 h 13 min** against a 45 h envelope. Nothing was dropped; Phase C ran concurrently
with Phase B on a different machine.
## 2. Does any customer-facing document name the old phrase? **No.**
Checked: `documentation/pilot/PETI-tester-agreement.md` does not mention the phrase **at all**;
`RUNBOOK-onboarding-draft-v4.md` and `RUNBOOK-byo-deployment.md` name it by its **English operator**
name ("retrieval passphrase"). The ISO/installer references are shell identifiers.
**So nothing printed is stranded.** The one honest caveat: the phrase reaches a customer
**out-of-band**`RUNBOOK-byo-deployment.md:11`, *"delivered to the box owner over a secure channel"*
— so the only stale copy of the old name is whatever was said in a message or on the telephone. A
person holding such a message would see „Tulajdonosi jelmondat" on the page, be told *"az öt szóból
álló kifejezés, amelyet a beállításkor kaptál"*, and be holding exactly that: **the description
identifies the thing even where the name has moved.** That was the reason for keeping the hint
sentence and extending it with *"Ez igazolja, hogy a fiók a tiéd."*
---
## 3. Part 1b — the hub guard, and the control that found a bug in itself
`scripts/hub_copy_gate.py`, registered as gate 9 in `repo_gates.py`. **Two checks, deliberately
different:**
1. **Retired names — banned outright**, across **all 95** hub `.go`/`.html` files, no allowlist. A
name a different secret now owns is never correct anywhere. Comments are stripped, because the
register rows and the code comments that record these decisions must quote the retired names.
2. **Retrieval stems — registered, not banned**, in four declared customer surfaces
(`notify/templates.go`, `web/selfbind.go`, `api/handler.go`, `notify/dispatcher.go`). A missing
declared surface is a **FAILURE, never a skip**. The allowlist is **empty, and that is a
measurement**: the hub makes no retrieval promise today.
### plant → convict → remove → pass
```
hub-copy gate SELFTEST
1. clean tree : 0 conviction(s) OK
2. planted „visszaállító jelszavadat”: CONVICTED (isszaállító jelsz) OK
3. same phrase inside a comment : not convicted OK
4. planting removed : 0 conviction(s) OK
hub-copy gate selftest OK — the guard has been watched catching, ignoring and releasing
```
**The control found a defect in its own instrument on the first run.** Step 3 convicted a comment,
because the synthetic source was named `<selftest>` and comment-stripping keys off the `.go`
extension. **The bug was in the guard, and the control is what found it** — which is the entire
argument for insisting a guard be watched working.
### One list, not two — and the gap is instrumented rather than hidden
Both lists live in **`scripts/customer_copy_vocab.py`**, the same shared-gate home
(`reuse_refs_check.py`, `instructions_gate.py`) that both repos already consume without copying.
**`retrieval_promise_gate.py` has NOT adopted it**, because the end state forbade touching
`felhom-controller` tonight. So rather than ship two copies that drift, **this gate reads the
controller gate's `STEMS` and fails if they disagree** — watched failing:
```
DRIFT: the controller gate's STEMS have diverged from customer_copy_vocab.py
controller : ['visszaállíthat', 'visszaszerezhet', 'visszahozhat', 'visszanyit']
shared : ['visszaállíthat', 'visszaszerezhet', 'visszahozhat']
HUB-COPY GATE FAILED: the shared vocabulary is no longer shared.
```
…and green again when restored. An **absent** sibling clone is **INCONCLUSIVE (exit 2), never a pass**
— the G-1 lesson. **This is a scaffold, not the destination: R-325** is the few-line felhom-controller
change that makes it import the shared list and delete both its literal and this drift check.
---
## 4. Part 2 — the four scenarios, the clock, and the red-proofs
**Re-established at `file:line`, not taken from the prompt.** The chain end to end: controller
`cmd/controller/main.go:1253` sets `Health.Status = "disabled"` in the final minimal report → hub
`store.go:953-955,968-975` parses `health.status` into `reports.health_status`
`CustomerSummary.HealthStatus` (`store.go:40`) carries it into `GetCustomers()`
`web/rollup.go:25` renders `disabled`**`monitor/staleness.go` ignored it and measured age alone.**
**The halt condition did not fire:** the discriminator is the box's **own last word**, present in the
data this checker already reads. That is not a guess.
### It was TWO doors, not one
Because the state is **recorded** (`StateDisabled`) rather than **deleted** (as the `blocked`
precedent does), `CheckBackupDeadlines` can skip it too. A deleted state returns `""` from `GetState`,
and `""` is not `"down"` — so that check would have gone on e-mailing `expected_backup_missed` every
morning about the same machine. **R-195's shape returning through a second door**, and it is closed
with the first. Recording the state also satisfies the visibility requirement: quiet-on-purpose and
quiet-by-accident no longer look identical.
### The re-enablement judgement
**The clock runs from the report the hub can actually see.** For a box that reports on re-enabling,
that report *is* the re-enablement, so the clock starts there — your recommendation, and the mechanism
already had this shape. Timing from the last report *before* the switch-off would fire an instant
stale/down for a quiet period we asked for: a false alarm produced by fixing false alarms. Leaving
`disabled` re-enters the **same branch as a new customer**, so no `node_recovered` fires for an outage
that never happened. `downtimeStart` is cleared **on entry**, so a later genuine outage cannot compute
its duration from a clock that started before the silence was requested.
**LIMIT, stated rather than hidden:** a box re-enabled that then **fails to report at all** keeps being
suppressed — the hub sees only that final `disabled` report, and its view changes only when a report
arrives. It cannot distinguish that from *still switched off*. **This is exactly why the state is made
visible:** an operator who re-enabled a box and still sees `disabled` is being told it has not come back.
### Scenarios
| | outcome | verified |
|---|---|---|
| **A** deliberately silent for days | no stale, no down, no e-mail; state visibly `disabled` | `TestStaleness_A` — observed healthy **first**, then switched off, then three passes |
| **B** simply stopped reporting | unchanged: stale then down, exactly as today | `TestStaleness_B` — state `down`, events emitted |
| **C** re-enabled, reports promptly | clean transition, **no** recovery event | `TestStaleness_C` — zero events, state `ok` |
| **D** re-enabled then genuinely quiet | stale and down fire normally, timed from re-enablement | `TestStaleness_D` — state `down`, events emitted |
Plus `TestStaleness_DisabledIsAlsoSkippedByTheDeadlineCheck` for the second door.
### Red-proofs — every mutation asserted applied by grep, and reverted after
| # | Mutation | Asserted applied | Outcome |
|---|---|---|---|
| **1** *(the one that matters)* | suppression made **unconditional** (`if true \|\| …`) | `staleness.go:132` grep | **RED — B**: *"a genuinely silent machine is `"disabled"`, want `"down"` — a real alarm was swallowed"*. **A genuinely dead machine was seen NOT alarming.** C and D also red |
| **2** | suppression **removed** (`if false && …`) | `staleness.go:132` grep | **RED — A**: *"a deliberately-disabled machine emitted `[node_down]` — three days quiet BY REQUEST"*. Today's false alarm, reproduced verbatim |
| **3** | state made **sticky** — remembered instead of re-read from the box, so the clock never leaves the pre-disable report | `staleness.go:132` grep | **RED — D**: *"a once-disabled machine was silenced for ever"*. C also red |
**A weakness in my own tests was found by red-proof 2 and fixed.** Scenario A originally seeded the
customer *already* disabled — and the checker's new-customer branch sets the first state without an
event, so **the test passed on its state assertion alone even with the suppression deleted.** It now
observes the machine healthy first, and the same mutation then fails on the **event**. A test that
cannot see the alarm it exists to prevent is not a test.
---
## 5. Part 3 — the real counts, and the number that was wrong
**Yes, a number I have been repeating is wrong.**
```
where felhom stands — 55 claims, verified_on 2026-08-09
walked 23
partial 14 (6 cite evidence, 8 prose only)
built 14 (0 cite evidence, 14 prose only)
missing 4 (0 cite evidence, 4 prose only)
NOT WALKED: 32 of 55
```
**"Nine" is real, and it answers a different question: it is the count of claims carrying
`verdict: downgraded`** — the ones the 2026-08-09 verification pass **lowered**. That is "re-judged",
not "unproven". **Yesterday's session was right to refuse to guess**, and this is what it could not
have found without counting.
The sharper cut is the evidence one: **all 23 walked claims cite an evidence document** (`check_stands.py`
convicts a `walked` claim without one), while of the **32** that are not walked, **only 6 cite evidence
and 26 are prose only**.
---
## 6. The command, and its output
```
$ python3 scripts/unproven.py --summary
where felhom stands — 55 claims, verified_on 2026-08-09
walked 23
partial 14 (6 cite evidence, 8 prose only)
built 14 (0 cite evidence, 14 prose only)
missing 4 (0 cite evidence, 4 prose only)
NOT WALKED: 32 of 55
```
Without `--summary` it prints every not-walked claim with its id, band, verdict and whether it cites
evidence, grouped `partial → built → missing`. It reads the dataset **only** — opens no evidence,
judges nothing, contacts no machine. An unrecognised status prints with a `⚠ status not known to this
script` marker rather than being silently dropped.
**Wired into the end-of-session checklist** in `felhom.eu/CLAUDE.md`, with the instruction to say in
the report if a number moved.
**Its first run found a stale claim**`claim.code-naming` is still `partial` and its title still
describes the defect R-295 and R-323 have now closed twice over. **I did not move it**, because the
dataset's own header forbids it: *"A status may not be RAISED here — the MAP changes first and this
file follows it."* Filed as **R-327**, with the honest difficulty noted: no customer has typed
„Tulajdonosi jelmondat" yet, so `walked` would be an over-claim.
**The capability map is deliberately NOT restructured** — recorded in R-326 so it does not read as
forgotten.
---
## 7. Versions, manifest, CI
| | |
|---|---|
| **Shipped** | **hub v0.105.0** (`gitea.dooplex.hu/admin/felhom-hub:0.105.0`) |
| **Unchanged** | controller **0.214.0**, agent **0.129.0**, installer **1.28.0**, golden 0.214.0, floor 0.214.0, MinAgent 0.129.0 |
| **Commits** | `b03a105` the work · `bbd59f4` the manifest bump |
| **Manifest** | `manifests/hub.yaml:128``0.105.0`, GitOps only — **no `kubectl set image`** |
| **Deploy** | ArgoCD hard-refresh + sync → **Synced / Healthy**; pod `hub-5ff87b556b-bvtvt` **1/1 Running**; deployment image confirmed `0.105.0` |
| **CI, by run ID** | run **336** (`b03a105375`) **success** · run **337** (`bbd59f4a44`) **success** |
| **Gates** | `repo_gates.py --fast` — all **9** OK (the new `hub-copy` included), and again in the pre-push hook on both pushes. **No `--no-verify`** |
| **Green gate** | `hub/`: build rc=0, vet rc=0, **test rc=0 across 18 packages**, run separately from every commit |
**Live verification of the running artifact**, ASCII-only patterns against
`/usr/local/bin/felhom-hub` in the running pod:
```
Tulajdonosi jelmondat 1 ← the field label
tulajdonosi jelmondatodat 2 ← the page lead + the mail
jelszavadat 0 ← the retired possessive is GONE
```
The negative control is the point: the old copy is **absent**, not merely accompanied.
**Method, stated: this proves the deployed artifact carries the new copy — it is not a rendering.**
Rendering the binding page requires minting a self-bind token, which mints a capability URL and sends
an e-mail against a real customer record; with `demo-hp` being re-deployed tonight I would not do that
to any customer row. The *rendering* is covered by `TestSelfBind_ThirdSecretNaming` and
`TestSelfBind_FailureBannerUsesTheSameName`, which drive the real handler through `ServeHTTP`.
**Part 2 has no live observable tonight, and I will not manufacture one.** No machine is in the
disabled state, and producing one would mean switching a real box's reporting off — precisely the kind
of thing that must not happen to a fleet of two on the evening one of them is being re-deployed. The
fix is proven by four scenarios and three red-proofs and is **latent until a box is actually disabled**.
---
## 8. `demo-hp` was not touched
**No `ssh`, no `pct`, no controller call, no agent call, no hub write of any kind** was issued against
`demo-hp` or its records this session. Everything done here was in the `felhom.eu` repo, the hub image
and the hub deployment.
Confirmed read-only, via the hub's own hosts page (`GET /hosts`, HTTP 200):
```
demo-hp-bb76ea ONLINE
demo-felhom-8363b5 ONLINE
drill-r50-0a4f9a DOWN (reverted to `virgin`, powered off — expected)
```
`demo-hp` is **online and reporting normally**, exactly as it was found. **`felhom-agent` and
`felhom-controller` are byte-unchanged** — neither repo was written to.
---
## 9. Register
**Ceiling moved R-322 → R-327** (grepped before minting).
| Row | State |
|---|---|
| **R-323** third near-homograph → „Tulajdonosi jelmondat" | **CLOSED — shipped** |
| **R-324** the hub's customer copy under a guard | **CLOSED — shipped, selftest green** |
| **R-325** controller gate should import the shared vocabulary | **READY (S)** — the drift check is the scaffold |
| **R-326** "what is unproven" made queryable | **CLOSED — shipped** |
| **R-327** the picture still describes a fixed defect | **READY (S)** — map moves first |
| **R-321** a disabled machine alarmed as dead | **CLOSED — both doors** |
| **R-322** the guard had never scanned the hub | **CLOSED by R-324** |
---
## 10. What was dropped, and observations
**Nothing was dropped.** Part 1, Part 1b, Part 2 and Part 3 are all complete. Part 3 was the
designated first drop and was not needed.
### Observations — noticed, not acted on
- **I nearly published a false negative about my own deploy.** The first grep of the running binary
used accented patterns through `kubectl exec → sh -c` and returned **0 for every string, including
ones that are certainly present** — which reads exactly like "the rename did not deploy". The
standing rule (*never let an accented pattern gate a conclusion*) is what caught it; the ASCII-only
re-run gave the real answer. **The rule earns its place again, in a chain it was not written for**
it was written for `ssh → pct exec`, and `kubectl exec` mangles identically.
- **The `blocked` branch does not clear `downtimeStart`.** The new `disabled` branch does. So a
customer that is blocked, later unblocked and later still goes down will compute its downtime from a
clock that started before the block. Small, pre-existing, and not changed under a row about
something else.
- **`gofmt -l internal/` lists 22 pre-existing unformatted files** in the hub, none of them touched by
this session. Worth one cleanup commit by someone, sometime — it makes `gofmt -l` useless as a
check, which is how a real formatting problem would hide.
- **The empty allowlist in the new gate is load-bearing and fragile in one direction.** It is empty
because the hub genuinely makes no retrieval promise. The moment someone adds one legitimately, they
must register it — and the gate's failure message says so, but nobody reads a failure message until
they hit it.
- **`unproven.py` shows 26 of 32 not-walked claims are prose only.** That is not a defect per claim —
a `missing` claim has nothing to cite — but **14 of 14 `built` claims cite no evidence at all**, and
"built" is the status that most invites being read as "done". Worth a look when the capability-map
session happens.
+77 -45
View File
@@ -1,6 +1,6 @@
# STATUS — what works, what's broken, what's next
**Updated 2026-08-12 (late evening — retained-key drill).**
**Updated 2026-08-13 (late — the third name, a machine told to be quiet, and a picture you can query).**
> **A view, not a source.** `documentation/backlog/OPEN-ITEMS.md` is the authority; this page restates
> part of it in plain words, and **nothing may exist only here**. **Items, not paragraphs. One screen.**
@@ -8,61 +8,93 @@
## Waiting on you
*(The golden-vouch and floor-raise asks that stood here are **done** — the hub reads golden **0.213.0**,
floor **0.213.0**, agent **0.128.0**. Checked live, not assumed.)*
*Nothing. All three questions that stood here were answered on 1213 August and have moved to
**Decided** below. A decided question left in the deciding list is how a person loses track of what is
actually waiting.*
- **R-307 — a countdown is already running on `demo-felhom`**, started 10 Aug, **firing 24 August**,
after which that set-aside off-site copy is deleted for good. Today's drill did not start it and
deliberately did not cancel it. Cancel it (copy kept, costs storage) or let it run (copy deleted).
**Doing nothing chooses deletion.**
- **R-304 — decide what we promise about old backups.** The retention works, but only an operator can
use it; the customer is told their correct code is wrong. Either build the door, or stop promising.
- **R-303 — one coherence decision**, ranked low: a box can still raise the "we cannot open your new
backups" card while counting down to deleting the old ones. The two no longer contradict each other,
but the state is odd and the wrong fix would hide a real second fault.
## Decided — and what would reopen each
*A decision with no trigger becomes a permanent silence, so each one names what would make us look
again.*
- **Getting old backups back yourself: NOT BUILT, deliberately.** A customer in that position is a
support conversation, and we can do it by hand. **Reopens if:** a real customer actually asks —
one request from a person who is not us. *(register: R-312)*
- **The unopenable old copy on `demo-felhom`: KEPT, as a test fixture.** Not for sentiment: it is the
only state in existence where a set-aside store is present and cannot be opened, which is the case
any future handling of lost backups has to face honestly. **Delete it when:** that work ships, or is
abandoned. Until then it is a fixture, not an accumulation. *(register: R-313)*
- **A machine in two kinds of trouble says both things: LEFT AS IT IS.** Its real-world likelihood is
unknown, and hiding one card risks hiding a real second failure. **Reopens if:** it is observed
happening outside a constructed test. *(register: R-303)*
## What works
Both demo machines are home, healthy and reporting on the approved pair. Off-site is credentialed on
`demo-hp` and its repository still opens with the machine's own key. `drill-r50` is blocked, as intended.
Both demo machines are home, healthy and reporting on the approved pair — **controller 0.214.0, agent
0.129.0**, delivered by the floor rather than by hand. Off-site is credentialed on `demo-hp` and its
repository still opens with the machine's own key. `drill-r50` is reverted to `virgin`, powered off.
**What the fleet actually is, because two summaries have now been misread:** the hub holds **five
customer records and three machines**. The machines are `demo-felhom` and `demo-hp` (both ours, both
disposable) and `drill-r50` (a nested drill VM on DooPlex, reverted and powered off). **`peti-felhom`
is a real machine we have not heard from since 15 July** and has no host record. **`tester-1` is a
record with no machine** — created 13 August, no host, no backups, nothing to lose.
## Shipped
- **The drive can be re-attached after a reinstall** (R-280). The restore page said *"this is two
clicks"* over an empty list; it was zero clicks and needed an internal path no customer could produce.
- **The orphan card stops promising** that set-aside off-site copies can be reopened — twice over
(R-294, then **R-299**, which was the same claim in the plural, in the *always-visible* half, missed
because the guard matched one inflection of a Hungarian verb).
- **The countdown banner stops promising retrieval it cannot see is still true** (R-302). The promise
is now conditional on the hub still holding the package it held when the customer decided — pinned
then, compared now. A sweep found the same claim in five places; a fourth was fixed with it and a
fifth deliberately left, because it is true where it renders.
- **One name per secret, box side** (R-295): the dashboard code is „Beállító kód" everywhere;
„Visszaállító kód" is retired. It collided with the escrow „Helyreállítási kód" and cost a real code.
- **Both installer fixes are now PUBLISHED** as `installer-v1.27.0` (R-297 + R-300). Each fault was
watched happening first, on a machine reset to factory state: the old installer really did build a
machine on a base image from July, and our own uninstall really did block our own next install.
- **The removal now genuinely reverses the installation** (R-316, `installer-v1.28.0` published). The
second reinstall used to hit our own leftover; it was watched failing on the cycle that actually
fails, then watched passing.
- **A correct recovery code is no longer called wrong** (R-311, three components). If a customer types
the code for an older set of backups, the machine now checks the packages we kept, recognises it, and
says so: *your code is correct, it belongs to an earlier package, we kept it, your current backups are
fine, write to us*. It deliberately promises no restore, because there is no button yet.
- **The drive can be re-attached after a reinstall** (R-280), and **the orphan card and the countdown
banner stop promising retrieval they cannot see is still true** (R-294, R-299, R-302).
- **One name per secret — now both halves** (R-295). The dashboard code is „Beállító kód" everywhere,
on the machine *and* in the hub's emails; „Visszaállító kód" is retired. It collided with the escrow
„Helyreállítási kód" and cost a real code.
- **The hub can see whether a machine's guest still has working networking** (R-319, first reader built
against R-264). A machine quietly repairing its own network over and over is now visible instead of
being a green tick; a machine that does not report it is drawn as unknown, never as healthy.
- **The third secret has its own name** (R-323, on your ruling). The five-word phrase that proves an
account owns the box being linked is „Tulajdonosi jelmondat". It was „Visszaállító jelszó" — one word
from the name we retired last week, and false besides: it restores nothing. Five places, all in the
hub; no machine touched.
- **A machine we tell to be quiet is no longer reported as dead** (R-321). It went stale, then down,
then e-mailed you twice about a silence you asked for. It turned out to be two alarms, not one — the
morning backup reminder had the same blind spot and is fixed with it.
- **The hub's own words are under a guard** (R-324). Every customer e-mail and the linking pages are
now checked for a retired name, and the guard has been watched catching one, ignoring an
explanation of one, and going quiet again.
- **The countdown on `demo-felhom` is cancelled** on your ruling (R-307). Nothing was deleted.
## Broken, or knowingly incomplete
- **The tester's machine has no recovery route at all** — see the `PETI` row. Its host record was
deleted on 15 July; there is no key, no off-site copy and no local backup. **If that drive fails,
everything on it is lost.** First act of the visit: copy the ~3.6 GB off before anything is
reinstalled — it is currently the only copy in existence. Whether it stays parked is your call and is
deliberately left open.
- **Kept backups can be opened — but not by the customer** (R-304). We now know for certain the old key
is kept and **does** open the old backups: three planted files came back byte-for-byte from a store the
machine itself could no longer read. But nothing in the product reads a kept key, so it takes an
operator with a shell. Worse, a customer who types their **correct** old code is told it *"did not open
the sealed bundle"*. The keeping is real; the promise that they can use it is not.
- **The dnsmasq fix helps a machine once** (R-305). On a machine that never had Felhom it works. On the
second reinstall the leftover comes back, because the package is never removed — so the machine looks,
to our own installer, as if the household had installed it. Watched happening the same afternoon.
- **The hub half of the naming is undone** (R-295 PARTIAL): the emails still use the retired name and
send people to a page a rebuilt machine does not show.
- **Peti's machine has no recovery route at all** — see the `PETI` row. **This is a real machine
belonging to a real person**, not one of ours and not a record: it reported to the hub for four and a
half months and has been silent since 15 July, when its host record was deleted. There is no key, no
off-site copy and no local backup. **If that drive fails, everything on it is lost.** First act of the
visit: copy the ~3.6 GB off before anything is reinstalled — it is currently the only copy in
existence. Whether it stays parked is your call and is deliberately left open.
- **Kept backups can be opened — but still only by us** (R-304 partly closed, R-312 decided-not-built).
Today the honest answer is "your code is right, write to us" — and we can.
- **The agent picks dnsmasq by looking at a file another package owns** (R-317). The box installs fine;
only LAN name resolution goes missing, and quietly. One line, deliberately not taken tonight.
- **The storage page has its own separate reason for showing an empty list** (R-298), untouched.
- **Three more facts the machines send still have no reader** (R-264): a staged-but-unapplied agent
update, how deep a restore test actually went, and the two backup-integrity timestamps. Five others
are now recorded as deliberately unread, which is honest rather than fixed.
- **Two thirds of the standing picture is still unproven, and now you can ask** (R-326).
`python3 scripts/unproven.py` lists it: of 55 claims, **23 are walked and 32 are not** — and of
those 32, only 6 point at an evidence document. **The "nine" I have been repeating was wrong**: nine
is how many claims the 9 August review *lowered*, which is a different question.
- **The picture still describes one defect we have since fixed twice** (R-327) — the naming claim. Its
status may only be raised after the capability map moves first, which is a separate judgement.
## Working on next
Your two decisions above (R-307's deadline is the hard one); then R-304's shape; then the hub naming;
then the 2026-08-09 batch (R-279 … R-292), still untriaged against everything since.
`demo-hp` is yours this evening — **this session did not touch it**. After that: the three remaining
R-264 readers, now that one has been built and we know what one costs; R-317 (one line in the agent);
R-327 (decide what the naming claim's status should be); then the 2026-08-09 batch (R-279 … R-292),
still untriaged against everything since.
+8 -1
View File
@@ -476,7 +476,14 @@ Report MUST include:
6. **Deployed versions** + `docker ps` / agent-service / hub-pod verification output.
7. **NOT yet live-validated — awaiting supervised [Bx]:** explicit list (the real
put-data → operate → integrity flow).
8. **Teardown evidence — all three §13 layers**, if the run provisioned anything: the machine deleted,
8. **Evidence copied off BEFORE each revert** — for every phase that ran on a machine, the logs were
pulled to the evidence directory **at the end of that phase**, before any revert, snapshot restore
or teardown, **including the intermediate ones**. *The intermediate revert is the one that gets
forgotten: two sessions lost a phase's logs to a mid-run revert to `virgin` on 2026-08-12 and
2026-08-13 — same machine, same point, three days apart (R-320).* **If a phase's evidence is
already gone, the report says so plainly and the finding is REPRODUCED independently** — that is the
expectation, not an improvisation. A quotation read live and no longer re-readable is named as such.
9. **Teardown evidence — all three §13 layers**, if the run provisioned anything: the machine deleted,
`pvesm status` before/after with the space returned, and the **hub-side record's disposition named**
(deleted / retained-with-reason / gate-blocked-with-the-command). A run that provisioned nothing says
so. "Teardown clean" without layer 3 is not a report — it is the `sess-c` failure.
File diff suppressed because one or more lines are too long
@@ -450,8 +450,8 @@ whose credentials reach nothing else — and the only mutating verb used was `ba
| Phase A — removal leftover | 16:50 → 17:12 | **~22 min** (4 installs, 4 uninstalls, 3 preflights) |
| Phase B — stale golden | 17:13 → 17:34 | **~21 min** (2 full provisions) |
| Phase C — retained key | 17:14 → 17:23 | **~25 min** (overlapped Phase B; different machine) |
| Record, register, publication, teardown | 17:35 → 17:55 | **~20 min** |
| **Total** | **16:42 → 17:55** | **≈ 1 h 13 min** |
| Record, register, publication, teardown | 17:34 → 17:45 | **~11 min** |
| **Total** | **16:42 → 17:45** | **≈ 1 h 03 min** |
Phase C ran concurrently with Phase B deliberately — different machines, no shared state. The
45 hour envelope was not needed; nothing was hurried and nothing was dropped for time.
@@ -0,0 +1,170 @@
# REPORT — The removal that works once, and a status page that says what it is asking for (2026-08-13)
**Shipped:** `installer-v1.28.0`, published and verified against the live URL.
**Register:** R-305 CLOSED (by R-316), **R-316 / R-317 / R-318 opened**; ceiling R-315 → **R-318**.
**Venue:** `drill-r50` only. Neither demo box reinstalled. `peti-felhom` not contacted. Nothing deleted.
---
## 1. Cycle 3, before and after
**Before — on the PUBLISHED v1.27.0, from `virgin`, exit 1:**
```
[ERROR] a resolver is already bound to :53 on this host:
udp UNCONN 0 0 0.0.0.0:53 … users:(("dnsmasq",pid=7076,fd=4)) …
[ERROR] a resolver is already bound to :53 on this host — Felhom needs the guest reachable by name on your LAN.
Stop or reconfigure that resolver, OR point your LAN DNS at the guest's address, then re-run.
(Felhom does NOT touch DNS services on a host it does not own — this is a refusal, not a change.)
THIS LOOKS LIKE OURS. A previous Felhom install leaves the dnsmasq PACKAGE installed and its unit
enabled (only our config snippet is removed), and unconstrained it binds 0.0.0.0:53 — which is what
this gate is seeing. If this host had no dnsmasq before Felhom, clear it with:
systemctl disable --now dnsmasq
Then re-run this installer. If dnsmasq is YOURS, leave it and use one of the two routes above.
[ERROR] PRE-FLIGHT FAIL (exit 1) — fix the finding above and re-run
```
**After — v1.28.0, three fresh cycles from `virgin`, exit 0:**
```
:53 now: 0
CYCLE 3 rc=0
[INFO] host DNS (:53): free
[OK] pre-flight passed
[OK] PRE-FLIGHT PASS (mode=byo) — no state written, no install step executed
```
The three cycles were run on bytes **sha256-identical** to what the live URL now serves
(`cca1dedd9b7c152c…`, compared three ways: live URL, tested file, repo main).
## 2. The mechanism, at `file:line`
| | |
|---|---|
| ownership **recorded** | `felhom-host-install.sh:1718` — preflight, `dpkg-query -W -f='${Status}' dnsmasq \| grep -q "install ok installed"` |
| ownership **read** | `:1097` (`_state_get dnsmasq_preexisting`) at uninstall |
| state file **deleted** | `:1268`*after* the read. **The order was already correct.** |
| Felhom's dnsmasq snippets removed | `:1162`, in the loop just above the ownership decision |
**Why cycle 2 concludes "pre-existing": package presence alone.** The preflight asks dpkg one question
and nothing else — it does **not** consult the absence of a record, and it cannot, because the record
was deleted with the state file. 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.
**What the first uninstall did and did not remove:** snippets — removed. Unit — stopped + disabled.
State file (and with it the record) — removed. **Package — left.** That last one is the whole defect.
## 3. What v1.28.0 changes
When the record says we installed it, the uninstall **removes the package** as well as stopping the
unit. Order unchanged: read the record → act → delete the state.
**Two packages are recorded, not one.** `dnsmasq` ships the systemd unit; **`dnsmasq-base` ships
`/usr/sbin/dnsmasq`** (`dpkg -S`, measured on the box). Separately installable, so each is recorded at
preflight and taken back only if we added it.
**Guard rails:** ownership **read, never inferred**; the dependency check is an **`apt-get -s purge`
simulation** that proceeds only if the removal set is a subset of ours, else stop+disable **naming the
blocking package**; never interactive; **never fatal** — a wedged apt is recorded and restated in the
closing NOTE; and the success is **re-queried** rather than read off apt's exit code.
## 4. Scenarios and red-proofs
| Scenario | Result |
|---|---|
| **A** three cycles, fixed | install 3 **PASSES**, `:53` free at every uninstall |
| **B** resolver pre-dates Felhom | **untouched**`install ok installed`, unit active, both packages recorded `yes` |
| **C** something depends on it | **not purged**; log named `household-dns-thing`; stop+disable; `:53` free; dependent survived |
| **D** no ownership record | **untouched**, reason logged, exact command named |
**Red-proofs — mutation asserted applied before each run:**
| Mutation | Outcome |
|---|---|
| remove the purge call | cycle 2 records `yes`; **cycle 3 refuses, exit 1, in those exact words** |
| remove the ownership check | **the household's resolver is PURGED** (`unknown ok not-installed`) |
| infer ownership when no record | **the guess is taken** — a field box loses its own DNS |
**One honest note on red-proof B.** Its first run appeared to pass *for the wrong reason*: apt failed
with `dpkg was interrupted` (a broken state my own earlier manual package juggling left), so the
resolver survived by accident rather than by the guard. I repaired dpkg and re-ran, and it then failed
as required. Worth stating twice over: the mutation looked like a passing guard, and **my own fix's
apt-failure path was incidentally observed doing exactly what it should** — logging, continuing, naming
the command.
## 5. The machines already in the field
**Measured, not reasoned.** A field box is one with the package installed and **no record** — scenario
D. Its uninstall leaves the resolver running with the reason logged, and its next install refuses with
the message quoted in §1.
**Is there an honest durable marker? No, and none can be invented.** The Felhom `/etc/dnsmasq.d/
felhom-*.conf` snippets are deleted by the uninstall's own loop *before* the ownership decision; the
state file carrying the record is deleted at `:1268`; nothing under `/etc/felhom*` survives.
`/var/log/dpkg.log` does record the install and is a **timestamp** — refused by the standing rule as a
heuristic dressed as a fact. → **R-318**
**The preflight message, judged as a customer would:** it states the finding, keeps its two routes and
its written promise not to touch DNS on a host we do not own, and adds the one thing that gets a person
moving — *"THIS LOOKS LIKE OURS"* plus one exact command. It hedges correctly (*looks like*). Its
weakness is that it asks them *"did this host have dnsmasq before Felhom?"* — precisely the question we
can no longer answer for them. **That is a mechanism, not a rule: the command is on the screen at the
moment it is needed.**
## 6. The defect I nearly shipped
The agent decides whether to install dnsmasq with `os.Stat("/usr/sbin/dnsmasq")`
(`felhom-agent/internal/lanresolver/lanresolver.go:105`) — but that path belongs to **`dnsmasq-base`**,
while the unit comes from **`dnsmasq`**. Purging only `dnsmasq` would leave the binary, so the next
install would skip the apt step and then fail to enable a unit that is gone — **a silent resolver where
today there is at least a visible refusal.** That is why ownership is recorded per package.
It remains reachable where `dnsmasq-base` pre-dated Felhom (we correctly keep it). **Pre-existing, not
introduced here**, filed as **R-317**, and the uninstall now says so out loud rather than leaving it to
be found from a resolver that never came up. Fixing it is a one-line agent change, deliberately **not**
made here to keep this session to one repo.
## 7. Publication
`installer-v1.28.0` tagged; **both** `--ref`s in `manifests/webpage.yaml` moved (sidecar 327, init 372);
ArgoCD synced to `823cd29`; live deployment carries the new ref.
```
live URL serves: SCRIPT_VERSION="1.28.0" (was 1.27.0)
_dnsmasq_purge_owned present in the served file: 3
```
**Pushing publishes nothing here** — `/scripts/` follows the tag. The runbook still says otherwise;
**R-309 remains open and is not forgotten**, deliberately out of scope.
## 8. Teardown — four layers
| Layer | State |
|---|---|
| **machine** | `drill-r50`: 0 guests, no `/var/lib/felhom-install`, dnsmasq `not-installed` at the end |
| **host** | `drill-r50` **reverted to snapshot `virgin`, powered off**; `qemu.pid` removed. DooPlex was drill host only |
| **hub** | `drill-r50-0a4f9a` **RETAINED** — pre-existing since 2026-07-25, re-used not duplicated. **No new host or customer row was created this session.** Nothing deleted |
| **off-site** | **Not contacted at all** — no restic, sftp or endpoint call was made. `demo-felhom`: `last_status=ok`, `escrow=escrowed`, **no abandon countdown** |
Both demo boxes untouched and healthy: controller **0.214.0**, agent **0.129.0**, `Up … (healthy)`.
## 9. Evidence, and a repeated miss
Logs at `documentation/audits/evidence-r316-2026-08-13/`: the fixed cycles (`F1`, `F2`, `F3`), all four
scenarios (`B`, `C`, `D`), and every red-proof (`RPA*`, `RPB*`, `RPD*`).
**The Part 1 logs did not survive.** They were on the drill VM's disk and were destroyed by the revert
to `virgin` between Part 1 and Part 2 — **the same mistake as Tuesday, in the same place.** The §1
quotation is verbatim from the live run as read at the time, and `RPA1/RPA2/RPA3.log` are an
independent reproduction of the identical three-cycle failure, retained. Recorded rather than glossed;
the fix is procedural and I have now got it wrong twice.
## 10. Observations — noticed, not acted on
- **`dnsmasq-base` is flagged "automatically installed and no longer required"** after `dnsmasq` goes.
We deliberately do not `autoremove` — that would be a blast radius nobody asked for.
- **`apt-get -s purge` exits 0 even when it prints `E: dpkg was interrupted`.** The simulation output is
the signal, never the exit code — which is why the guard parses `Remv`/`Purg` lines rather than
trusting `$?`. Another entry for the exit-codes-that-lie class.
- The drill VM's `pveam` index is stale on `virgin` and needs `pveam update` before listing templates —
already recorded yesterday, hit again today in passing.
@@ -0,0 +1,393 @@
# REPORT — the small debts paid, and one unread fact given a reader (2026-08-13, evening)
**Shipped: hub v0.104.0, live and verified.** Parts A and B complete; **Part C dropped**, named in §12.
No controller change, no agent change, no wire change — **so there was no bake and no approval**, as the
task expected.
---
## 1. A4 FIRST — what the fleet actually is, and the entry corrected
**The task's premise for A4 was wrong, and the evidence is not ambiguous.** It asked me to establish
the facts and not accept the framing; doing that reverses the conclusion. Two different records have
been called "the tester", and only one of them carries the risk.
**Read from the hub's own store** (`hub.db` + `-wal` + `-shm`, copied together — the WAL rule):
| | `peti-felhom` | `david``tester-1` |
|---|---|---|
| customer row | created 2026-02-27 | `david` created 2026-08-01, **deleted today 07:55:49**; `tester-1` created **07:56:47** |
| host row | `peti-felhom-86d37d`, deleted 2026-07-15 08:56:22 (`host_deletions` id 1) | **never, either name** |
| controller reports | **482**, 2026-02-27 → 2026-07-15 08:39:00 | **0** |
| host reports | present until deletion | **0** |
| escrow rows | none | none |
| events | staleness alarms after it went silent | **4 total** — three hub-side `expected_dbdump_missed` false alarms (R-195's subject) and its own deletion |
**So:**
- **"The tester" in this project's own vocabulary is Peti** — `documentation/pilot/PETI-tester-agreement.md`
says *"Operator: Viktor. Tester: Peti"* — and the machine is **a real 80-core Proxmox server
belonging to a real person**, running Felhom as a BYO guest. It reported for four and a half months.
`target-selection.md` puts it at **Tier 2 — protected**, *"because there is a real person behind it"*.
**The 3.6 GB with no key, no off-site copy and no local backup is REAL, and the entry keeps its rank.**
- **`tester-1` is a record with no machine** — no host, no escrow, no report, ever; and `david` before
it was the same. **A record with no machine behind it can lose nothing.**
**Reconciling this with what the operator said** (*"there is no actual tester yet — only a pre-created
customer, now renamed"*): both statements are true of different things. The **pilot programme** never
began — the agreement was drafted 2026-07-09, the onboarding runbook stopped at P1 — and the
pre-created record is `tester-1`. **Meanwhile the hardware and the data have existed the whole time.**
Nothing about the risk changed; only the word that names it.
**The corrected entry, as shipped** (`STATUS.md`, "Broken, or knowingly incomplete"):
> - **Peti's machine has no recovery route at all** — see the `PETI` row. **This is a real machine
> belonging to a real person**, not one of ours and not a record: it reported to the hub for four and a
> half months and has been silent since 15 July, when its host record was deleted. There is no key, no
> off-site copy and no local backup. **If that drive fails, everything on it is lost.** First act of the
> visit: copy the ~3.6 GB off before anything is reinstalled — it is currently the only copy in
> existence. Whether it stays parked is your call and is deliberately left open.
The `PETI` register row carries the full disambiguation with every count measured, and ends
*"Wherever a document says 'the tester's machine', read `peti-felhom`."*
**What the fleet actually is, in one sentence, now on `STATUS.md`:** the hub holds **five customer
records and three machines** — `demo-felhom` and `demo-hp` (ours, disposable), `drill-r50` (a nested
drill VM, reverted and powered off); `peti-felhom` is a real machine we have not heard from since 15
July and has no host record; `tester-1` is a record with no machine.
---
## 2. The hub strings as shipped, with bytes confirmed
Enumerated at `file:line` before editing. All written from explicit bytes and verified as hex — no
non-ASCII crossed a shell chain; the mojibake check (`Ã`/`Å`/`â€`) is clean in every touched file.
| Where | Was | Is | Hex of the shipped name |
|---|---|---|---|
| `notify/templates.go:216` subject | „Jelszó-visszaállítási kód" | **„Beállító kód a jelszavad visszaállításához"** | `4265c3a16c6cc3ad74c3b3206bc3b364…` |
| `notify/templates.go:221` body | „Visszaállító kód: %s" | **„Beállító kód: %s"** | `4265c3a16c6cc3ad74c3b3206bc3b3643a202573` |
| `notify/templates.go` **new** `reenroll` | *(did not exist)* | subject **„Új beállító kód — újratelepült a szervered"**, body names **„A szerver beállítása"** | `224120737a6572766572206265c3a16c6cc3ad74c3a1736122` |
| `notify/templates.go:72` lockout | „beállító/visszaállító kód próbálkozás" | **„beállító kód próbálkozás"** | `…6265c3a16c6cc3ad74c3b3206bc3b364…` |
| `web/templates/customer_unified.html:476` | „Visszaállító kód küldése" | **„Beállító kód küldése"** | `4265c3a16c6cc3ad74c3b3206bc3b364206bc3bc6c64c3a97365` |
**The page-naming fix, established at `file:line` rather than assumed.** `ReissueForReenroll` sent the
**reset** mail (`claim/engine.go:181`), which directs the customer to an „Elfelejtett jelszó" page. A
**rebuilt** box has no password, so the controller computes `reset := s.authEnabled()` → false
(`felhom-controller/controller/internal/web/claim.go:279`), renders **„A szerver beállítása"**, and
serves **no login page at all** — the named route is not on their screen. Only the hub can tell the two
situations apart, because the hub chose which call site fired, so the fix is a **new `EmailKind`**, not
a reworded shared template. Same secret, same name, different sentence.
The re-enrol mail deliberately says **nothing** about apps or backups —
`TestFormatClaimEmail_ReenrollPromisesNothingAboutTheData` pins it. A clean-slate reinstall is
precisely where such a reassurance could be false, and this project has spent four register rows
removing promises it could not see were still true.
**Live-verified** (endpoint-level, the exact URL the operator UI serves; ClusterIP + Basic auth):
`GET /customers/demo-felhom`**„Beállító kód küldése" present, „Visszaállító kód" absent**.
### Had the claim guard ever scanned the hub? **No — never.**
`retrieval_promise_gate.py` lives in `felhom-controller/controller/scripts/`; its declared surfaces are
that repo's `internal/web/templates` plus **one** Go handler file, added on 2026-08-12 by R-311 on the
express ground that the highest-stakes customer copy *"had never been scanned"*. **The same sentence is
true one repo over** — the hub composes every customer e-mail, i.e. the copy a customer reads *before*
they see any box screen.
**I scanned it by hand with the gate's own four stems** (`visszaállíthat`, `visszaszerezhet`,
`visszahozhat`, `visszanyit`) across every non-test `.go`/`.html` under `hub/internal/`: **zero
occurrences.** So nothing was hiding, and my new strings contain no stem and need no registration.
**It is a scope gap, not a live defect — filed as R-322 rather than fixed**, because pointing a
controller gate at a sibling repo makes a controller gate fail on a felhom.eu edit, and G-1 already
taught this project what a cross-repo gate costs. *(A recommendation not followed gets its line: I
recommend a hub-side sibling in `repo_gates.py` sharing ONE stem list — two copies of a word list is
how the plural got past the singular in R-299.)*
---
## 3. The runbook correction, and where else the claim lived
`runbooks/day0-install.md` §C.1 said *"there is no release tag… pushing `scripts/felhom-host-install.sh`
publishes it."* **It has published nothing since R-110 shipped on 2026-08-03.** §C.1 now opens by naming
its own former error and states the three acts that actually publish:
1. bump `SCRIPT_VERSION` and push to `main`**publishes nothing**;
2. cut and push the tag `installer-v<new SCRIPT_VERSION>`;
3. move **BOTH** `--ref=installer-v…` pins in `manifests/webpage.yaml` — the git-sync **sidecar** and the
**init container** (lines 327 and 372) — commit, sync. **The one-pin trap is named**: the running pod
keeps serving until it restarts, and a fresh pod seeded by a stale init container then serves the OLD
script with no error anywhere.
**How to verify from outside**, since a push, a green sync and a correct-looking manifest are each
consistent with nothing having been published:
```
curl -fsS https://felhom.eu/scripts/felhom-host-install.sh | grep -m1 '^SCRIPT_VERSION'
```
**Measured while writing it: served `1.28.0`, `main` `1.28.0`, both pins `installer-v1.28.0`** — the
three agreeing is the observation; any one alone is not.
**Copied elsewhere? Yes, once, and it was hunted.**
`audits/SPIKE-universal-iso-3-2026-07-31.md:184` says the same thing **and cites `day0-install.md` as
its source** — which is how it spread. It was **true on the day it was written**, so the dated finding
is kept verbatim and carries a SUPERSEDED note; falsifying a dated record to tidy it is its own defect.
Two other hits are correct in context: `hostinstall_gates.py:198` states the consequence of the
manifest *losing* its tag, and the 2026-08-12 drill record already names the sentence as false.
---
## 4. The evidence rule, as written and where it lives
> **Evidence is copied off the machine at the end of the phase that produced it — before any revert,
> snapshot restore or teardown. Not at the end of the session.**
>
> **The mechanism, because a rule without one is a wish:** the last act of a phase that ran on a machine
> is `scp`/`pct pull` of its logs into the evidence directory on DooPlex — the same act that ends the
> phase, not a separate step to remember later.
>
> **When a session notices the evidence is already gone: say so plainly in the report and REPRODUCE it
> independently.** That is the documented expectation, not an improvisation invented under pressure.
**Four homes**, chosen so a session meets it before the revert rather than after:
1. `runbooks/workspace-CLAUDE.md`**standing rule 5**, so it loads in every session, with the
two-incident record in the R-96 rationale comment beneath it.
2. `runbooks/target-selection.md` — its own section, in the doc you read *before* picking a machine to
break. Tier 0 machines are disposable, which is exactly why nothing you need may be left on one.
3. `RUNBOOK-rehearsal-v3.md` — in the standing-rules block, flagged as applying to every phase boundary.
4. `PROMPT-TEMPLATE.md` — a new **report item 8**, so a session must state that it did this.
**Provenance, both occurrences verified, not recalled:** 2026-08-12 the retained-key drill lost its
Phase A logs to the revert to `virgin` between Phase A and Phase B
(`audits/DRILL-retained-key-2026-08-12.md` §11.5); 2026-08-13 R-316 lost its Part 1 logs, **same box,
same revert, same point** (§9 — *"the same mistake as Tuesday, in the same place"*). Both times the
existing "scp the log OUT first" was applied to the **final** teardown and not the **intermediate** one.
Filed as **R-320**.
**A side effect worth naming:** that second record lived in `REPORT.md`, which this report overwrites —
**writing tonight's report would have destroyed the record of a destroyed record.** It was preserved to
`audits/REPORT-r316-installer-v1.28.0-2026-08-13.md` first, and both citations now point there.
---
## 5. The three rulings, recorded as decided
**`STATUS.md`'s "Waiting on you" section is now empty**, and says so: *"Nothing. All three questions
that stood here were answered on 1213 August and have moved to Decided below."* A new **Decided**
section carries them, and each register row's status field changed from `READY` to `DECIDED`.
| Ruling | Trigger that reopens it |
|---|---|
| **R-312 — recovering an old backup stays a phone call.** Not built, deliberately; the operator path genuinely works (the 2026-08-12 drill restored planted files byte-identical by hand) | **A real need appearing — one request from a customer who is not us.** Until then R-304's honest position stands: retention is operator-only, and nothing may promise the customer can do it |
| **R-313 — the unopenable set-aside store on `demo-felhom` is KEPT, as a test fixture.** The specific advantage the operator accepted: it is the only state in existence where a set-aside store is present and **cannot be opened** — a case that cannot be manufactured without destroying another key on purpose | **Delete it when the work it is a fixture FOR ships, or is abandoned** — i.e. when R-312 is built, or when R-312's ruling is made permanent. On either event, delete deliberately and record why |
| **R-303 — a machine in two kinds of trouble says both things: left as it is.** Real-world likelihood unknown; the tidier fix risks hiding a genuine second failure | **An observation of the combined state occurring OUTSIDE a constructed test.** One sighting on a real machine reopens it |
---
## 6. B0 — do the facts arrive, and is this hub-only?
**Yes, and yes.** Both halves established before any code was written.
- **On the wire and in the database.** `demo-felhom-8363b5`'s newest `host_reports` row carries
`guest_net.checked_at` plus per-guest `vmid/state/mode/ip/has_route/dhclient_alive/checked_at/message`.
The agent's wire type (`felhom-agent/internal/hub/report.go:139-160`) additionally declares `healed`,
`heal_succeeded`, `last_heal_at`, `heals_last_hour`, `damped` — absent from the live rows only because
they are `omitempty` on a box that has never needed a repair.
- **Read by nothing.** The string `guest_net` occurred **nowhere** in `felhom.eu/hub/` — no struct, no
template, no checker. Stored as raw text inside `report_json`.
**So no wire change and no agent change were needed: hub-only.** The halt condition did not fire.
---
## 7. Reclassification counts — measured, not estimated
The register row said *twenty-one*; **the gate's allowlist held twenty.** Twenty is what the
dispositions account for, exactly:
| State | Count | Which |
|---|---|---|
| **Read** (removed from the allowlist) | **8** | `guest_net` + its seven children |
| **Deliberately not consumed** (new third kind, with the ruling and its date) | **5** | `mgmt_plane.healed_recently`, `pbs_dr.applied_at`, `config_hash`, `stacks`, `storage.migrated_to` |
| **Redundant** (decided on its own merits) | **1** | `reporting_disabled` |
| **Still owed a reader** (R-264, open) | **6** | `selfupdate_pending`, `selfupdate_pending_version`, `restore_tests.mount_parity`, `restore_tests.mount_inventory`, `backup.last_db_dump`, `backup.last_integrity_check` |
**The eight are REMOVED rather than re-labelled, and that is the point:** an allowlisted tag is
*skipped* by the gate, so leaving them would have meant the new reader's own fields were never checked
for reachability at all.
**A thing the task told me to re-read from the register, which was not there.** The operator's
dispositions were made on 2026-08-12; **R-264 still read `READY — owner Viktor` and all twenty
allowlist entries still said "arguably owed"**. A session told to re-read them from the register would
have found none. They are written down now, which is the point of writing them down.
**`reporting_disabled`, decided on its own merits — and the decision found a real defect the flag would
not have fixed.** The product does support the state: the controller sends one minimal report carrying
`reporting_disabled: true` **and** `health.status: "disabled"` (`cmd/controller/main.go:1246-1260`),
then goes quiet by design. The hub **already decodes** `health_status` and **already renders** that
customer as `disabled` (`web/rollup.go:25`) — so the flag is a second spelling of a fact already read:
**redundant**. But `StalenessChecker.Check` (`monitor/staleness.go:88+`) is **age-only**; its sole skip
is `IsCustomerBlocked`. **A deliberately-silent box still goes `node_stale` at 30 minutes and
`node_down` at 60** — exactly the false alarm B0 predicted. **Filed as R-321 and deliberately not fixed
here:** decoding the flag would have felt like progress and left the alarm firing. The fix belongs in
the checker, which already holds the status it needs.
---
## 8. The reader as built, and the alarm judgement
**`hub/internal/web/hosts.go`** — `parseGuestNet` / `guestNet` / `guestNetView`; rendered as a **Guest
network** card on the host-detail page, which is where a person looks at a machine.
**The signal is the repair count, not the state.** A guest the watchdog keeps repairing is healthy at
every instant anyone looks and is nevertheless failing; rendering `state` alone would give it a green
tick — the exact shape of the failed-disk-drawn-as-a-healthy-empty-disk defect. `heals_last_hour` sits
**beside** the state and drives the summary badge.
**`heal_succeeded` is decoded too, and that was a deliberate addition mid-build.** My first cut carried
the count and not the outcome — which is **R-260 exactly**: a hub decoder three fields short of the
agent, missing the one that decides the question. Six *failed* repairs is a guest that is down; six
successful ones is a nuisance.
**The four scenarios, and what the card shows for each — all four verified by test, and three of them
observed on the live fleet:**
| | Rendered | Live sighting |
|---|---|---|
| **A** healthy, no repairs | `healthy` badge, address, route, dhclient, `0` repairs | **`demo-felhom-8363b5`** — 9201 healthy, `192.168.0.149` (dhcp), swept `08:45:33Z`; **`demo-hp-bb76ea`** — 9201 healthy, `192.168.0.118`, swept `08:52:35Z` |
| **B** repaired repeatedly | `needs attention`; the **count** rendered as a warn badge with the last repair time; a sentence naming the 1 h 15 m incident | not yet on hardware — neither demo box has needed a repair since the watchdog shipped |
| **C** does not report it | **`unknown`, never healthy** — three absences, three different sentences | **`drill-r50-0a4f9a`** — *"A capable agent sent no guest-network stanza"*`unknown`. **The unknown branch fired on real data, not only in a test** |
| **D** malformed | `unknown`, and the page returns **200** | covered by test; the decode error is swallowed on purpose so one box's bad field cannot break the page for the fleet |
Two extra branches beyond the four: an unrecognised guest `state` renders unknown (the switch is an
allow-list, so adding a state to the agent can never silently paint it green), and an **empty guest
list with a fresh sweep is NOT silence** — the agent's contract says that means "the watchdog ran and
found nothing", which must stay distinguishable from "the watchdog is not wired", the shape the
v0.91.0 inert seam hid behind.
### The alarm judgement: **no email, deliberately**
I agree with the task's recommendation and did not add one. The reasoning, since it is a judgement:
- **The incident was a visibility failure, not a paging failure.** Nobody could *see* the condition for
1 h 15 m. A card fixes what actually broke.
- **A new alarm on a fleet of two demo machines is untested noise**, on a dispatcher whose severity
contract is exact-match lowercase and whose customer-message allow-list must move with it — a
two-place change that fires against no real population.
- **There is no calibrated threshold to alarm on.** Neither demo box has produced a single repair since
the watchdog shipped, so any number I picked would be invented. **The visible count is what will
supply the threshold** — which is the argument for building the card first and the alarm second.
**Revisit when a third machine exists, or when a repair count is seen climbing on real hardware.**
---
## 9. Red-proofs — every mutation asserted applied before its run
Five in total: three mandated for B2, two for A2. Each mutation was **confirmed present by grep before
the test ran**, and confirmed **absent by grep after restoring**.
| # | Mutation | Asserted applied | Outcome |
|---|---|---|---|
| **1** *(the one that matters)* | The unknown branches in the card's badge chain replaced by the healthy badge | `grep -c 'title="MUTATED">healthy'`**2** | **RED — both C sub-cases.** A silent machine seen rendering as healthy: *"a silent box must SAY it is unknown"* and *"an old agent's silence must be named as an old agent's silence"* |
| **2** | `RepairCount: 0` in the decoder — the climbing-repairs signal dropped | `hosts.go:385 RepairCount: 0, … // MUTATED` | **RED — B**: *"a guest repaired 6 times in an hour is reported as fine — the whole point of the card"* |
| **3** | `Degraded()` forced `true` — the healthy branch broken | `hosts.go:348 func … { return true /* MUTATED */ }` | **RED — A**: *"a machine that is fine is being alarmed on"* — the guard is reachable in **both** directions |
| **4** | `ReissueForReenroll` routed back to `EmailReset` | `grep` → line 195 reads `EmailReset` | **RED — two tests**: the new `TestReenrollSplit_ChangesTheMailNotTheSecret` **and** the pre-existing `TestReissueForReenroll` |
| **5** | „Visszaállító kód: %s" restored in the reset mail body | `grep -n``templates.go:241` | **RED — `TestFormatClaimEmail_OneNamePerSecret`** |
**After every restore the suite is green again**, and `grep -c MUTATED` returns **0** in both touched
files.
**A positive control that the reader is WIRED and not merely written**, independent of the tests: the
wire-contract gate's checked-tag count rose **182 → 190** and its skipped count fell **88 → 80** as the
eight allowlist entries came out. The gate still passes — meaning those eight tags are now genuinely
reachable in the hub.
---
## 10. Part C — **DROPPED**, and one part of it answered anyway
**Dropped, as the task's own drop order specifies.** The honest reason is not only time: **the task
names "nine claims" that are grey, and I could not identify which nine.** The capability map holds
**22** rows marked `IMPLEMENTED`, five `PARTIAL` and four `MISSING`, and nothing in the tree enumerates
a set of nine. Picking nine myself and reporting on them would have produced a confident answer to a
question nobody asked — the A4 failure mode, one section later.
**The one I can answer, because the task identified it precisely.** The self-heal claim is the
capability-map row *"Box survives an unattended app or guest-network failure… it is noticed, and where
safe it is repaired"*, and its best document does **not** argue against it. The 1 h 15 m outage
(`audits/INCIDENT-guest-dhclient-killed-2026-07-20.md`) is the row's **origin**, not its evidence: the
evidence is a **deliberate replay of that incident the next day**`kill -9` at 12:43:18, detected on
process liveness **57 s** later while the lease was still live, healed at 12:45:18 with the incident's
verbatim invocation, and **the tunnel never dropped** (`cloudflared Up 29 hours`). That is a walk, and
the outage was prevented rather than merely observed. **The claim does not need to move down.**
**What I would flag instead:** the row's *guest-network* leg was proven on the box and, until today,
**the hub could not see the condition at all** — which is the gap this session closed and which no
capability row expressed. My new row states its own status honestly as **IMPLEMENTED, not
PROVEN-LIVE**, because **no machine has ever been observed with a climbing repair count on this card.**
---
## 11. Versions, commits, deploy, CI
| | |
|---|---|
| **Shipped** | **hub v0.104.0** (`gitea.dooplex.hu/admin/felhom-hub:0.104.0`, 25 MB) |
| **Unchanged** | controller **0.214.0**, agent **0.129.0**, installer **1.28.0**, golden 0.214.0, floor 0.214.0, MinAgent 0.129.0 |
| **Commits** | `4d6ec7c` the work · `7c97c94` the manifest bump |
| **Manifest** | `manifests/hub.yaml:128``felhom-hub:0.104.0`, GitOps only — **no `kubectl set image`** |
| **Deploy** | ArgoCD hard-refresh + sync → `Synced / Healthy`; pod `hub-5c4d8d4b49-864dc` **1/1 Running**; deployment image confirmed `0.104.0` |
| **CI, confirmed by run ID** | run **334** (`7c97c949f6`) **success** · run **333** (`4d6ec7c7bb`) **success** |
| **Gates** | `repo_gates.py --fast` — all **8** OK, and again in the pre-push hook on both pushes. **No `--no-verify`.** |
| **Green gate** | `hub/`: build rc=0, vet rc=0, **test rc=0 across 18 packages**. Run separately from every commit |
**Live validation method — stated, as the fence requires:** endpoint-level. I invoked the exact URLs
the operator UI serves (`GET /hosts/<id>`, `GET /customers/<id>`) against the hub's ClusterIP with the
operator Basic-auth credential, so no server logic was skipped — only browser rendering.
`claude-in-chrome` is not available on DooPlex; a click-through remains the operator's.
**Files:** `hub/internal/web/hosts.go`, `hub/internal/web/templates/host_detail_body.html`,
`hub/internal/claim/engine.go`, `hub/internal/notify/templates.go`, `hub/internal/web/configs.go`,
`hub/internal/web/templates/customer_unified.html`, `scripts/wire_contract_gate.py`, plus
`hub/internal/web/hosts_guestnet_test.go` (**new**, 7 tests) and `hub/internal/claim/naming_test.go`
(**new**, 6 tests). Tests **+13**.
**Register:** R-319, R-320, R-321, R-322 minted (ceiling was R-318, grepped first). R-264, R-309, R-312,
R-313, R-303 and the `PETI` row updated. `STATUS.md` and the capability map updated.
---
## 12. What was dropped, and observations
**Dropped — named plainly, nothing silently shortened:**
- **Part C in full**, except the self-heal verdict in §10. Reason in §10: the nine were not identifiable,
and inventing them would have been worse than the gap.
**Nothing else was dropped.** Part A is complete (A1A5), Part B is complete (B0, B1, and the one reader
B2 asked for — not four).
### Observations — noticed, not acted on
- **A "0 bytes" answer was nearly a wrong verdict, twice in one hour.** `demo-hp`'s host page came back
empty from a `kubectl run` curl pod, which reads exactly like "the card does not render". Retrying with
a clean pod name returned **HTTP 200** and a correct card. *An empty listing is not evidence of
emptiness* — the instrument was the fault, and the rule caught it.
- **R-264's count was wrong**: the row says twenty-one facts, the allowlist held twenty. The row is now
written against the measured number.
- **The wire gate does not detect a stale allowlist entry.** Its sibling `retrieval_promise_gate.py`
fails on one; this one silently skips. Had I re-labelled the eight `guest_net` entries instead of
deleting them, the gate would have reported a coverage it did not have and said nothing. Worth the
same treatment.
- **A third secret sits one homograph away from the two that collided.** The hub's self-bind flow calls
the **five-word** retrieval passphrase „**visszaállító jelszó**" (`web/selfbind.go:255,261`;
`notify/templates.go:285`) — *jelszó*, not *kód*, so it is outside R-295's ruling and I left it alone.
But „Visszaállító kód" was just retired for being a near-homograph of „Helyreállítási kód", and this
is a near-homograph of the name that was retired. **Three secrets, three names, and two of them still
begin with the same word.** A ruling for it is the operator's, not mine.
- **`agents.md`-style version drift in project memory**: `instructions_gate` emits 32 version-literal
warnings against `MEMORY.md`. Not a failure and not mine to fix tonight, but it is aimed at the next
model to edit that file — which is worth someone acting on before it is noise nobody reads.
@@ -183,6 +183,14 @@ no first-boot hook at all, so this question now governs **operator-built images
`main` on a 30-second period (`documentation/.../day0-install.md:150-152`) — no release tag, no
staging copy, no version selector. Pushing the script publishes it.
> **⚠ SUPERSEDED 2026-08-03, annotated 2026-08-13 (R-110, R-309). True on the day it was written;
> false ever since.** `/scripts/` is now served from the tag `installer-v<SCRIPT_VERSION>` and
> **pushing publishes nothing** — publication is a tag plus **two** `--ref` pins in
> `manifests/webpage.yaml`. The dated finding is kept as written rather than rewritten, because it
> is a record of what was true then; **the note is here because this paragraph cited
> `day0-install.md` as its source, which is how the wrong sentence spread.** Current procedure and
> the outside-verification command: `runbooks/day0-install.md` §C.1.
3. **Console display does not exist.** `felhom-host-install.sh` does not write `/etc/issue`,
`/etc/issue.net` or any MOTD (grep: no match). PVE's own `/etc/issue` banner is what the console
shows after install — observed in this session's screendumps as
@@ -0,0 +1,72 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6310 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: already installed BEFORE Felhom — recorded; uninstall will not touch it
[INFO] dnsmasq-base: already installed BEFORE Felhom — recorded; uninstall will leave it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
[OK] token minted (secret captured, not logged)
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,731 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] dnsmasq pre-dates Felhom (recorded at install) — leaving it running, restarting only
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,71 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6243 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: not present before Felhom — recorded; uninstall will remove it again if we install it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
[OK] token minted (secret captured, not logged)
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,776 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] dnsmasq was installed by Felhom (recorded at install) — stopping + disabling it
Synchronizing state of dnsmasq.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install disable dnsmasq
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
Removed '/etc/systemd/system/multi-user.target.wants/dnsmasq.service'.
[WARN] dnsmasq: NOT purging — something else now depends on it. Removing it would also take out:
[WARN] household-dns-thing
[WARN] The unit is stopped + disabled, so :53 is free. The package stays, deliberately.
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[WARN] NOTE: dnsmasq was NOT removed — purge would also remove household-dns-thing — dnsmasq left installed (stopped + disabled). Its unit is stopped + disabled, so :53 is free, but the NEXT install will read the package as pre-existing.
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,71 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6253 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: not present before Felhom — recorded; uninstall will remove it again if we install it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
[OK] token minted (secret captured, not logged)
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,734 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[WARN] dnsmasq: no ownership record for this box (installed before that was recorded).
[WARN] Leaving it RUNNING — we do not stop a resolver we cannot prove we installed.
[WARN] If this host had no dnsmasq before Felhom, a reinstall will refuse on the :53
[WARN] gate; clear it yourself with: systemctl disable --now dnsmasq
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,81 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6356 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: not present before Felhom — recorded; uninstall will remove it again if we install it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden: none local — will fetch + verify from Gitea in step 7/8
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
[OK] token minted (secret captured, not logged)
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[INFO] disabled pve-enterprise.sources (Enabled: no)
[INFO] disabled ceph.sources (Enabled: no)
[INFO] added pve-no-subscription.sources (suite=trixie)
[OK] apt repos aligned to no-subscription (changed; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[INFO] installing the 'sudo' package (required for the non-root agent model) …
[OK] sudo installed (Sudo version 1.9.16p2)
[INFO] installing the 'age' package (escrow ceremony identity-wrap dependency) …
[OK] age installed (1.2.1)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] creating island bridge vmbr9 (portless, 169.254.253.1/30)
[OK] vmbr9 up: 169.254.253.1/30
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] fetching golden v0.214.0 from Gitea → /var/lib/vz/dump/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[OK] verified sha256 3a40379cb00d98c6… matches the hub manifest
[OK] golden imported + verified: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,829 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] dnsmasq was installed by Felhom (recorded at install) — stopping + disabling it
Synchronizing state of dnsmasq.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install disable dnsmasq
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
Removed '/etc/systemd/system/multi-user.target.wants/dnsmasq.service'.
[INFO] dnsmasq: removing the package(s) Felhom installed: dnsmasq dnsmasq-base
Reading package lists...
Building dependency tree...
Reading state information...
The following packages will be REMOVED:
dnsmasq* dnsmasq-base*
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
0 upgraded, 0 newly installed, 2 to remove and 115 not upgraded.
After this operation, 1303 kB disk space will be freed.
(Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 52716 files and directories currently installed.)
Removing dnsmasq (2.91-1+deb13u1) ...
Removing dnsmasq-base (2.91-1+deb13u1) ...
Processing triggers for dbus (1.16.2-2) ...
Processing triggers for man-db (2.13.1-1) ...
(Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 52664 files and directories currently installed.)
Purging configuration files for dnsmasq-base (2.91-1+deb13u1) ...
Purging configuration files for dnsmasq (2.91-1+deb13u1) ...
[OK] dnsmasq removed — the next install starts from a clean slate, as a first install
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,71 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6220 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: not present before Felhom — recorded; uninstall will remove it again if we install it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
[OK] token minted (secret captured, not logged)
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,829 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] dnsmasq was installed by Felhom (recorded at install) — stopping + disabling it
Synchronizing state of dnsmasq.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install disable dnsmasq
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
Removed '/etc/systemd/system/multi-user.target.wants/dnsmasq.service'.
[INFO] dnsmasq: removing the package(s) Felhom installed: dnsmasq dnsmasq-base
Reading package lists...
Building dependency tree...
Reading state information...
The following packages will be REMOVED:
dnsmasq* dnsmasq-base*
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
0 upgraded, 0 newly installed, 2 to remove and 115 not upgraded.
After this operation, 1303 kB disk space will be freed.
(Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 52716 files and directories currently installed.)
Removing dnsmasq (2.91-1+deb13u1) ...
Removing dnsmasq-base (2.91-1+deb13u1) ...
Processing triggers for dbus (1.16.2-2) ...
Processing triggers for man-db (2.13.1-1) ...
(Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 52664 files and directories currently installed.)
Purging configuration files for dnsmasq-base (2.91-1+deb13u1) ...
Purging configuration files for dnsmasq (2.91-1+deb13u1) ...
[OK] dnsmasq removed — the next install starts from a clean slate, as a first install
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,64 @@
[INFO] felhom-host-install v1.28.0 — mode=byo customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6245 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] archive storage 'local' present
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] acl storages all present or pre-positioned: local local-lvm felhom-pbs
[INFO] dnsmasq: not present before Felhom — recorded; uninstall will remove it again if we install it
[INFO] host DNS (:53): free
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[STEP] BYO disclosure — installing Felhom on a host the operator does NOT own will create:
pveum: roles FelhomAgentBase/FelhomAgentGuest/FelhomAgentStore + user felhom-agent@pve + token felhom-agent@pve!agent
+ scoped ACL grants (Base@/, Guest@/pool/felhom, Store@[local local-lvm felhom-pbs]) + the 'felhom' pool
agent: service user felhom-agent + /usr/local/bin/felhom-agent (+ .bak-* backups; A/B slots via the guarded self-update)
+ /etc/systemd/system/felhom-agent.service (+ felhom-agent-rollback.service + start-limit drop-in)
+ /etc/sudoers.d/felhom-agent + state dir /var/lib/felhom-agent + config /etc/felhom-agent/agent.json (0600)
host: /usr/local/sbin/felhom-mkfs-guarded + /usr/local/sbin/felhom-selfupdate-guarded
+ /usr/local/sbin/felhom-pbs-apply (PBS-DR apply wrapper — DR capability is baked
on every install; ACTIVATION stays a hub flag, off = zero effect on this host)
+ felhom-mgmt-watchdog service+timer+script + /etc/tmpfiles.d/felhom-privsep.conf
+ guest-hook snippet under /var/lib/vz/snippets/ (agent-installed at runtime)
+ the 'sudo' and 'age' packages if absent + install state dir /var/lib/felhom-install
wg: an OUTBOUND WireGuard tunnel to the Felhom hub (wg_tunnel.enabled=true — base
infrastructure like the cloudflared tunnel; hands-free peer registration; the
operator can disable it per-customer hub-side on request)
guest: the provisioned Felhom LXC (vmid 120, capped 2 cores / 4096 MiB) + its volumes
+ the golden vzdump imported onto storage 'local'
update: operator-signed self-update authority: felhom-op-1
NOT touched in byo mode: root@pam (no break-glass), host DNS (:53), OOB sshd.
[INFO] (--preflight-only: acknowledgement prompt skipped — nothing will be mutated)
[OK] pre-flight passed
[OK] PRE-FLIGHT PASS (mode=byo) — no state written, no install step executed
@@ -0,0 +1,774 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6245 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: not present before Felhom — recorded; uninstall will remove it again if we install it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] token minted (secret captured, not logged)
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,772 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] dnsmasq was installed by Felhom (recorded at install) — stopping + disabling it
Synchronizing state of dnsmasq.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install disable dnsmasq
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
Removed '/etc/systemd/system/multi-user.target.wants/dnsmasq.service'.
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,775 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6246 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: already installed BEFORE Felhom — recorded; uninstall will not touch it
[INFO] dnsmasq-base: already installed BEFORE Felhom — recorded; uninstall will leave it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] token minted (secret captured, not logged)
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,731 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] dnsmasq pre-dates Felhom (recorded at install) — leaving it running, restarting only
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,47 @@
[INFO] felhom-host-install v1.28.0 — mode=byo customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6153 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] archive storage 'local' present
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] acl storages all present or pre-positioned: local local-lvm felhom-pbs
[INFO] dnsmasq: already installed BEFORE Felhom — recorded; uninstall will not touch it
[INFO] dnsmasq-base: already installed BEFORE Felhom — recorded; uninstall will leave it
[ERROR] a resolver is already bound to :53 on this host:
udp UNCONN 0 0 0.0.0.0:53 0.0.0.0:* users:(("dnsmasq",pid=23649,fd=4)) udp UNCONN 0 0 [::]:53 [::]:* users:(("dnsmasq",pid=23649,fd=6)) tcp LISTEN 0 32 0.0.0.0:53 0.0.0.0:* users:(("dnsmasq",pid=23649
[ERROR] a resolver is already bound to :53 on this host — Felhom needs the guest reachable by name on your LAN.
Stop or reconfigure that resolver, OR point your LAN DNS at the guest's address, then re-run.
(Felhom does NOT touch DNS services on a host it does not own — this is a refusal, not a change.)
THIS LOOKS LIKE OURS. A previous Felhom install leaves the dnsmasq PACKAGE installed and its unit
enabled (only our config snippet is removed), and unconstrained it binds 0.0.0.0:53 — which is what
this gate is seeing. If this host had no dnsmasq before Felhom, clear it with:
systemctl disable --now dnsmasq
Then re-run this installer. If dnsmasq is YOURS, leave it and use one of the two routes above.
[ERROR] PRE-FLIGHT FAIL (exit 1) — fix the finding above and re-run
@@ -0,0 +1,775 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6306 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: already installed BEFORE Felhom — recorded; uninstall will not touch it
[INFO] dnsmasq-base: already installed BEFORE Felhom — recorded; uninstall will leave it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] token minted (secret captured, not logged)
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,740 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] MUTATED: ownership check removed
[INFO] dnsmasq: removing the package(s) Felhom installed: dnsmasq
E: dpkg was interrupted, you must manually run 'dpkg --configure -a' to correct the problem.
[WARN] dnsmasq: the package removal FAILED. The unit is stopped + disabled, so :53 is free,
[WARN] but a reinstall will read the package as pre-existing. Clear it with:
[WARN] apt-get -y purge dnsmasq
[WARN] NOTE: dnsmasq-base pre-dates Felhom and is deliberately kept. It owns /usr/sbin/dnsmasq,
[WARN] which the agent stats to decide whether to install — so on this host the next install
[WARN] may skip the install step and fail to enable the unit (R-317).
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[WARN] NOTE: dnsmasq was NOT removed — apt-get purge failed — dnsmasq left installed (stopped + disabled). Its unit is stopped + disabled, so :53 is free, but the NEXT install will read the package as pre-existing.
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,775 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6214 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: already installed BEFORE Felhom — recorded; uninstall will not touch it
[INFO] dnsmasq-base: already installed BEFORE Felhom — recorded; uninstall will leave it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] token minted (secret captured, not logged)
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,790 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] MUTATED: ownership check removed
[INFO] dnsmasq: removing the package(s) Felhom installed: dnsmasq
Reading package lists...
Building dependency tree...
Reading state information...
The following package was automatically installed and is no longer required:
dnsmasq-base
Use 'apt autoremove' to remove it.
The following packages will be REMOVED:
dnsmasq*
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
0 upgraded, 0 newly installed, 1 to remove and 115 not upgraded.
After this operation, 159 kB disk space will be freed.
(Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 52716 files and directories currently installed.)
Removing dnsmasq (2.91-1+deb13u1) ...
(Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 52697 files and directories currently installed.)
Purging configuration files for dnsmasq (2.91-1+deb13u1) ...
[OK] dnsmasq removed — the next install starts from a clean slate, as a first install
[WARN] NOTE: dnsmasq-base pre-dates Felhom and is deliberately kept. It owns /usr/sbin/dnsmasq,
[WARN] which the agent stats to decide whether to install — so on this host the next install
[WARN] may skip the install step and fail to enable the unit (R-317).
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
@@ -0,0 +1,775 @@
[INFO] felhom-host-install v1.28.0 — mode=appliance customer=drill-r50 vmid=120
[STEP] 1/8 pre-flight
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[INFO] pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
[INFO] node: drill-pve (auto)
[INFO] agent config: /etc/felhom-agent/agent.json
[INFO] agent: not installed yet — will be fetched + installed in step 5/8
[INFO] local-lvm free: ~75 GiB
[WARN] local-lvm free ~75 GiB < hard min 120 GiB
[INFO] free RAM: ~6236 MiB
[INFO] existing guests on this host: 0 (pct+qm)
[INFO] acl storage 'felhom-pbs' not present yet — expected: the PBS-DR tier creates it; the grant is pre-positioned deliberately
[INFO] dnsmasq: already installed BEFORE Felhom — recorded; uninstall will not touch it
[INFO] dnsmasq-base: already installed BEFORE Felhom — recorded; uninstall will leave it
[INFO] hub reachable (https://hub.felhom.eu)
[OK] customer 'drill-r50' exists + passphrase valid
[INFO] golden (local): local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[INFO] --skip-provision: agent install/config only, no guest will be provisioned
[OK] pre-flight passed
[STEP] 2/8 Proxmox API token
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] token minted (secret captured, not logged)
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[OK] scoped ACL applied (Base@/, Guest@/pool/felhom + /vms/990000..990009, Store@[local local-lvm felhom-pbs])
[SKIP] old broad role FelhomAgent already absent
[STEP] 3/8 compute volume grows
[INFO] auto-computed from ~75 GiB free (ONE volume since R-165)
[INFO] grows: rootfs +0G (->32G), data +46G (->70G, ONE volume)
[STEP] 4/8 host enrollment (POST /host-enroll)
[OK] host REUSED (idempotent — existing credential)
[INFO] host_id: drill-r50-0a4f9a (api_key captured, not logged)
[STEP] 4b/8 break-glass credential (root@pam console password → hub vault)
[OK] root@pam password set + vaulted to the hub (retrieve via the operator /admin path; never logged here)
[WARN] NOTE: the root@pam password just CHANGED — the old one now fails at the PVE web GUI (:8006).
[WARN] Retrieve the new one at hub → host page (vaulted recovery credential).
[STEP] 5/8 agent install (fetch + verify + install)
[OK] apt repos aligned to no-subscription (already aligned; apt-get update OK)
[WARN] no git credential in controller.yaml — fetching artifacts ANONYMOUSLY (they are world-readable; sha256 verification unchanged)
[INFO] manifest: agent v0.129.0 (sha 53a54f0620afbd6d…), golden v0.214.0
[INFO] fetching agent binary v0.129.0 from Gitea …
[OK] verified sha256 53a54f0620afbd6d… matches the hub manifest
[OK] installed /usr/local/bin/felhom-agent (felhom-agent 0.129.0)
[OK] created service user felhom-agent
[OK] added felhom-agent to systemd-journal (unprivileged journal read for NAS verify)
[OK] installed /usr/local/sbin/felhom-mkfs-guarded (0755, the guarded mkfs path)
[OK] installed /usr/local/sbin/felhom-selfupdate-guarded (0755, the guarded A/B binary-swap path)
[OK] installed /usr/local/sbin/felhom-pbs-apply (0755, the guarded PBS-DR apply path)
[OK] installed /usr/local/sbin/felhom-backup-target-apply (0755, the guarded backup-target path)
[OK] installed /etc/sudoers.d/felhom-agent (0440, visudo-validated)
[OK] installed /etc/systemd/system/felhom-agent.service + enabled (started in step 6 after config)
[OK] installed self-update rollback unit + start-limit drop-in (auto-rollback armed)
[OK] installed break-glass layers 1+2 (tmpfiles /run/sshd + agent-independent watchdog timer)
[OK] installed OOB felhom-sshd instance + static belt (agent renders config + fills sets once oob.enabled)
[STEP] 6/8 agent config + service
[WARN] backup target: DEGRADED — no eligible second drive, so the whole-system backup stays on the SYSTEM drive.
[WARN] It protects against file corruption but NOT against a disk failure. Attach a second drive and assign it in the dashboard.
[INFO] island bridge vmbr9 already present — leaving it
[INFO] R-50 island ON: local_api=169.254.253.1:8443 (vmbr9); guest net1=169.254.253.2/30; lan_resolver.host_ip=10.0.2.15
[INFO] node=drill-pve local_api=169.254.253.1:8443 tls_fp=C9:1F:AC:10:AE:62…
[OK] wrote /etc/felhom-agent/agent.json (0600 felhom-agent)
[OK] agent --selftest (read-only) passed
[OK] felhom-agent service active (non-root felhom-agent reads the config OK)
[STEP] 7/8 golden archive
[INFO] local golden digest matches the manifest (3a40379cb00d98c6…) — this IS the vouched artifact
[SKIP] using local golden: local:backup/vzdump-lxc-9100-2026_08_13-07_43_44.tar.zst
[SKIP] provision (--skip-provision) — agent install/config verified only
[STEP] verify (agent only)
[INFO] binary: felhom-agent 0.129.0
[INFO] runs as: felhom-agent (want felhom-agent)
[OK] service active
[OK] --selftest=hub OK (a host-report reached the hub)
[OK] Agent install SUCCESS — felhom-agent 0.129.0 as felhom-agent, host_id=drill-r50-0a4f9a customer=drill-r50
@@ -0,0 +1,828 @@
[INFO] felhom-host-install v1.28.0 — mode=uninstall
[STEP] UNINSTALL — local host teardown
[SKIP] guest 120 already absent — skipping guest teardown
[STEP] host-level removal
Removed '/etc/systemd/system/multi-user.target.wants/felhom-agent.service'.
Removed '/etc/systemd/system/timers.target.wants/felhom-mgmt-watchdog.timer'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-sshd.service'.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-oob-nft.service'.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
.wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
instance name specified.
Removed '/etc/systemd/system/multi-user.target.wants/felhom-shared-parent.service'.
Removed '/etc/systemd/system/pve-guests.service.wants/felhom-shared-parent.service'.
[INFO] dnsmasq was installed by Felhom (recorded at install) — stopping + disabling it
Synchronizing state of dnsmasq.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install disable dnsmasq
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
Removed '/etc/systemd/system/multi-user.target.wants/dnsmasq.service'.
[INFO] dnsmasq: removing the package(s) Felhom installed: dnsmasq
Reading package lists...
Building dependency tree...
Reading state information...
The following package was automatically installed and is no longer required:
dnsmasq-base
Use 'apt autoremove' to remove it.
The following packages will be REMOVED:
dnsmasq*
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
0 upgraded, 0 newly installed, 1 to remove and 115 not upgraded.
After this operation, 159 kB disk space will be freed.
(Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 52716 files and directories currently installed.)
Removing dnsmasq (2.91-1+deb13u1) ...
(Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 52697 files and directories currently installed.)
Purging configuration files for dnsmasq (2.91-1+deb13u1) ...
[OK] dnsmasq removed — the next install starts from a clean slate, as a first install
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] old broad role FelhomAgent already absent
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = "UTF-8",
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").
[SKIP] golden vzdump left in place (pass --remove-golden to remove)
[STEP] kept vs wiped — read before pulling drives or closing the customer out
WIPED (this run):
- guest 120 (container + its OS/Docker/user-data volumes)
- the felhom-agent: binary, unit, sudoers, config (+ its .bak backups), state dir, service user
- self-update artifacts: guarded wrapper, A/B slots (.prev/.new.*), rollback unit, start-limit drop-in
- break-glass watchdog + OOB artifacts (where present); guest-hook snippet; dnsmasq snippets; the mkfs + pbs-apply wrappers
- pveum: the Felhom roles/user/token/scoped ACL; the emptied felhom pool
- the install state file
KEPT (lives on deliberately — remove/rotate these out-of-band if the customer is leaving):
- the enrolled drives + ALL data under /mnt/felhom-drives — unmounted only, NEVER wiped;
the drives are physically removable now.
- the hub host/customer record + report history (operator UI / DB)
- the escrow blob in the hub, if one exists (operator UI)
- the hub-vaulted root@pam recovery credential — the box KEEPS the password step 4b set; rotate it if the box leaves Felhom management
[OK] UNINSTALL complete — removed: guest 120, the felhom-agent (unit/sudoers/binary/state/config+baks/user + selfupdate-artifacts/shared-parent/mkfs-wrapper/pbs-apply-wrapper/hook-snippet/dnsmasq-snippets), the pveum role/user/token/ACL, the felhom pool, and /var/lib/felhom-install/state.json.
[INFO] golden vzdump: left in place (--remove-golden to remove).
[INFO] NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired.
File diff suppressed because one or more lines are too long
@@ -26,6 +26,13 @@
> **Point of no return:** booting the install-armed stick (the auto-installer needs no confirm).
> Recovery unchanged: stock ISO + `felhom-host-install.sh` — and since RESET is the plan, there
> is nothing to restore. Demo-felhom.eu is down for the window (~2 h).
>
> **EVIDENCE RULE — standing rule 5 (R-320), and it applies to every phase boundary below.** The logs
> of a phase are copied OFF the machine at the end of **that** phase, before any revert, snapshot
> restore or teardown — **including the intermediate ones, which are the ones that get forgotten.**
> Two sessions lost a phase's logs exactly this way on 2026-08-12 and 2026-08-13, same machine, same
> point. Make the pull the last act of the phase. **If evidence is already gone: say so plainly and
> reproduce the finding independently** — do not let a lost log quietly become a softer claim.
---
+38 -3
View File
@@ -167,9 +167,44 @@ chmod +x felhom-host-install.sh
./felhom-host-install.sh -h | head -3 # sanity: must print v1.15.0 (or newer) — DR-tier-by-default ships the full plumbing
```
This URL is the website's git-sync working tree tracking `main` on a 30-second period
(`manifests/webpage.yaml`) — it is always the current `main` script. There is no release tag, no
staging copy and no version selector; pushing `scripts/felhom-host-install.sh` publishes it.
**⚠ THIS PARAGRAPH USED TO SAY THE OPPOSITE OF THE TRUTH, and two sessions were misled by it before
it was corrected on 2026-08-13 (R-309, R-110).** The old text read *"it is always the current `main`
script … pushing `scripts/felhom-host-install.sh` publishes it."* **Pushing publishes NOTHING, and has
not since R-110 shipped on 2026-08-03.** Believing the old sentence is dangerous in **both**
directions: it makes an operator think a pushed fix is live when it is not, and think a pushed mistake
is live when it is not. (Measured 2026-08-12: this URL served `1.25.0` while `main` held `1.27.0`,
three and a half hours after the push.)
`manifests/webpage.yaml` runs **two** git-syncs against **different refs**: the website tracks `main`,
and `/scripts/` is checked out from the tag **`installer-v<SCRIPT_VERSION>`**. So this URL serves the
**tagged** installer, not `main`.
**What actually publishes it — three acts, and the middle one is two lines, not one:**
1. Bump `SCRIPT_VERSION` in `scripts/felhom-host-install.sh` (the single version source) and push to
`main`. *Nothing is published yet.*
2. Cut and push the tag `installer-v<new SCRIPT_VERSION>`.
3. Move **BOTH** `--ref=installer-v…` pins in `manifests/webpage.yaml` — the git-sync **sidecar** and
the **init container** (today lines 327 and 372) — commit, and sync ArgoCD. **Moving one pin is the
trap**: the running pod keeps serving until it restarts, and then a fresh pod seeded by the stale
init container serves the OLD script with no error anywhere.
**To roll back:** move the tag back and wait ~30 s. No ArgoCD sync, no deploy — that is the emergency
lever; fix forward afterwards. **Do not pin the website to the tag**, or every copy edit becomes a
release. `hostinstall_gates.py` gate 6 fails if the manifest stops naming an `installer-v…` tag or if
the website stops tracking `main`.
**How to verify from OUTSIDE that the published version actually changed** — a push, a green sync and a
correct-looking manifest are each consistent with nothing having been published, so ask the public URL
rather than the repository:
```bash
curl -fsS https://felhom.eu/scripts/felhom-host-install.sh | grep -m1 '^SCRIPT_VERSION'
```
It must print the **new** version. Confirmed by this method on 2026-08-13: served `1.28.0`, `main`
`1.28.0`, both manifest pins `installer-v1.28.0` — the three agreeing is the observation, and any one
of them alone is not.
### C.2 Preview (recommended)
@@ -25,6 +25,25 @@ Fences name **acts**, not machines. "Do not destroy demo-hp's `drill-r50` fixtur
demo-hp to host a throwaway VM" are unrelated; only the first has ever been meant. Read a per-machine
prohibition as covering the act it names and nothing more.
## Before you revert it — take the evidence off first
> **A phase's evidence is copied off the machine at the end of THAT phase, before any revert, snapshot
> restore or teardown. Not at the end of the session.** (Standing rule 5, R-320.)
>
> **The intermediate revert is the one that gets forgotten.** Both losses this project has recorded
> were the *middle* teardown, never the final one — the Phase A logs of the 2026-08-12 retained-key
> drill and the Part 1 logs of the 2026-08-13 R-316 run, both on `drill-r50`, both destroyed by a
> revert to `virgin` between phases, three days apart. Both times the conclusions survived only
> because the quotations had been read live and an independent reproduction happened to exist. **That
> is luck.**
>
> **The mechanism:** make the pull the last act of the phase, not a step to remember later —
> `pct pull` / `scp` into `documentation/audits/evidence-<topic>-<date>/` on DooPlex, then revert.
> Tier 0 machines are *disposable*, which is precisely why nothing you need may be left on one.
>
> **If you notice it is already gone:** say so plainly in the report and **reproduce the finding
> independently**. That is the documented expectation, not an improvisation.
| Tier | Meaning | Machines |
|---|---|---|
| **0 — disposable. Reach here first.** | Exists to be broken; reinstalling is a routine afternoon, not an incident. **A drill that needs a victim uses one of these.** | `demo-hp` (t740), `demo-felhom` (N100) |
@@ -56,6 +56,15 @@ roles. **A file being open in the editor is NOT an instruction. If no task is st
"working" and "stopped entirely".
4. **A recommendation that is not followed gets one line saying why.** Silence reads as agreement and
the disagreement is lost.
5. **Evidence is copied off the machine at the end of the phase that produced it — before any revert,
snapshot restore or teardown. Not at the end of the session.** The intermediate revert is the one
that gets forgotten; both losses were the *middle* teardown, never the final one. **The mechanism,
because a rule without one is a wish:** the last act of a phase that ran on a machine is `scp`/`pct
pull` its logs to the evidence directory on DooPlex — the same act that ends the phase, not a
separate step to remember later. **And when a session notices the evidence is already gone: say so
plainly in the report and REPRODUCE it independently.** That is the documented expectation, not an
improvisation to be invented under pressure — it is what both sessions did, and it is the only
reason two sets of conclusions survived.
<!--
R-96 incident record (committed 2026-07-27) — rationale, not directives.
@@ -72,6 +81,16 @@ R-96 incident record (committed 2026-07-27) — rationale, not directives.
4. Twice in the R-88/R-97 arc a review point was absorbed rather than argued: R-84 was folded into
R-82 without a word, and R-97a's operator-only guard was dropped while the claim it was meant to
enforce got committed as a comment — which is how a false guarantee shipped and survived a release.
5. Twice in three days, on the SAME machine and at the SAME point. 2026-08-12, the retained-key drill:
the Phase A logs lived on `drill-r50`'s disk and were destroyed by the revert to `virgin` between
Phase A and Phase B (`audits/DRILL-retained-key-2026-08-12.md` §11.5). 2026-08-13, R-316: the Part 1
logs, same disk, same revert, between Part 1 and Part 2 (`audits/REPORT-r316-installer-v1.28.0-2026-08-13.md`
§9 — "the same mistake as Tuesday, in the same place"; preserved out of `REPORT.md`, which is
overwritten every session). Both times the golden-bake runbook's existing "scp the log OUT first"
was applied to the FINAL teardown and not the intermediate one. Both times the conclusions survived
only because the quotations had been read live and an independent reproduction happened to exist —
that is luck, and the second occurrence is what makes it procedural rather than another apology.
Filed as R-320.
-->
## Shared conventions
@@ -0,0 +1,45 @@
# Golden bake 0.214.0 — 2026-08-12
Per `RUNBOOK-manual-build.md` §4.0/§4.1. Drill VM reverted to `virgin` before and after; guest 9100
destroyed `--purge`; `/root` residue shredded.
| | |
|---|---|
| `GOLDEN_VERSION` | **0.214.0** |
| `GOLDEN_SHA256` | **3a40379cb00d98c6f9550815b2b024783f153e9eef9be2b3252253d24d8d2c4d** |
| controller baked | `gitea.dooplex.hu/admin/felhom-controller:0.214.0` |
| `MinAgent` (CHANGELOG header) | **0.129.0** |
| published | 656 392 524 B, `upload OK (HTTP 201)` |
## Acceptance markers (`grep -F`, quoted loop variable)
`docker OK (overlay2` 1 · `including mount point rootfs` 1 · `including mount point mp0` 1 ·
`upload OK (HTTP 201)` 1 · `FATAL` 0 · `excluding` 0
## Fetchability — the SERVED bytes
```
downloaded: 656392524 bytes
sha256: 3a40379cb00d98c6f9550815b2b024783f153e9eef9be2b3252253d24d8d2c4d
```
Identical to `GOLDEN_SHA256`.
## The template index was STALE, and it would have baked the wrong base
`pveam available` on the freshly reverted `virgin` listed only `debian-13-standard_13.1-2` — the point
release our own memory records as 404-ing since 2026-07-15. **`pveam update` first**, and the real
current one is `debian-13-standard_13.6-1_amd64.tar.zst`. The runbook says the point release rots; what
it does not say is that the VM's cached index rots too, and reading it without refreshing produces a
confident answer that is a fortnight out of date.
## Secret handling
Token file→file, read inside the VM by a runner script, never on a command line.
`systemctl show golden-bake -p Environment -p ExecStart | grep -c -F <token>` = **0**. Leak grep on the
committed log = **0**, believable because a planted-token control on a copy grepped **1**. All in-VM
artefacts `shred -u`'d.
## NOT DONE
The Day-0 vouch — the operator's, and deliberately so. Three fields, each already verified downloadable
and selectable: **golden 0.214.0**, **agent 0.129.0**, **min agent 0.129.0**.
@@ -0,0 +1,326 @@
[golden] build-golden.sh v3.0.0 — baking controller gitea.dooplex.hu/admin/felhom-controller:0.214.0
[golden] creating build LXC 9100 (nesting=1,keyctl=1, unprivileged; rootfs 32G + ONE data volume 24G @ /var/lib/felhom, backup=1) …
Logical volume "vm-9100-disk-0" created.
Logical volume pve/vm-9100-disk-0 changed.
Creating filesystem with 8388608 4k blocks and 2097152 inodes
Filesystem UUID: a630e58b-5ddd-4312-8535-b83ed18030fb
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624
Logical volume "vm-9100-disk-1" created.
Logical volume pve/vm-9100-disk-1 changed.
Creating filesystem with 6291456 4k blocks and 1572864 inodes
Filesystem UUID: 247da550-80fb-4921-bb4c-0a3517bdbd21
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
extracting archive '/var/lib/vz/template/cache/debian-13-standard_13.6-1_amd64.tar.zst'
Total bytes read: 553512960 (528MiB, 177MiB/s)
Detected container architecture: amd64
Creating SSH host key 'ssh_host_ed25519_key' - this may take some time ...
done: SHA256:N/+3Phg1PMspcZ0VWshdC1mXbkAYQ2jAsYhavLNrb0Q root@felhom-golden
Creating SSH host key 'ssh_host_ecdsa_key' - this may take some time ...
done: SHA256:fl1uCYoByd5OGUy7NjwyEHlkIcOvAxk5887imnsM7tc root@felhom-golden
Creating SSH host key 'ssh_host_rsa_key' - this may take some time ...
done: SHA256:6wNca1Ee5AXkXkQjmROa6sdynmpHikRhw7MuHpxXrk0 root@felhom-golden
[golden] starting + installing Docker (official repo, trixie channel) …
apt-listchanges: Can't set locale; make sure $LC_* and $LANG are correct!
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = (unset),
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
apt-listchanges: Can't set locale; make sure $LC_* and $LANG are correct!
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_CTYPE = (unset),
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MESSAGES = (unset),
LC_MONETARY = (unset),
LC_ADDRESS = (unset),
LC_IDENTIFICATION = (unset),
LC_MEASUREMENT = (unset),
LC_PAPER = (unset),
LC_TELEPHONE = (unset),
LC_NAME = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
[golden] baking daemon.json: classic overlay2 driver (containerd-snapshotter OFF) + log rotation …
[golden] wiring the single data volume (R-165 variant V-c): /var/lib/felhom/{docker,sys_drive} -> binds …
[golden] verifying Docker works in the build guest (storage driver should be overlay2 on the ext4 data volume) …
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4f55086f7dd0: Pulling fs layer
4f55086f7dd0: Download complete
4f55086f7dd0: Pull complete
Digest: sha256:7f4da0fc94bcece205a8c0b6f4d11c8196924654ffe5c4d1aa439b7f632048b2
Status: Downloaded newer image for hello-world:latest
docker OK (overlay2; data-root /var/lib/docker)
/var/lib/docker is a real mount: /dev/mapper/pve-vm--9100--disk--1[/docker] ext4
/mnt/sys_drive is a real mount: /dev/mapper/pve-vm--9100--disk--1[/sys_drive] ext4
both paths are ONE filesystem: /dev/mapper/pve-vm--9100--disk--1 23317576
[golden] baking the in-guest controller image gitea.dooplex.hu/admin/felhom-controller:0.214.0 (no registry cred at deploy) …
WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/
0.214.0: Pulling from admin/felhom-controller
039e6f9f9752: Pulling fs layer
0094c3ac0914: Pulling fs layer
deca1dac7403: Pulling fs layer
11c19a33d1b8: Pulling fs layer
742f054a0a3b: Pulling fs layer
63bdd1abbb22: Pulling fs layer
11c19a33d1b8: Waiting
742f054a0a3b: Waiting
63bdd1abbb22: Waiting
039e6f9f9752: Verifying Checksum
039e6f9f9752: Download complete
deca1dac7403: Verifying Checksum
deca1dac7403: Download complete
11c19a33d1b8: Verifying Checksum
11c19a33d1b8: Download complete
742f054a0a3b: Verifying Checksum
742f054a0a3b: Download complete
63bdd1abbb22: Verifying Checksum
63bdd1abbb22: Download complete
0094c3ac0914: Verifying Checksum
0094c3ac0914: Download complete
039e6f9f9752: Pull complete
0094c3ac0914: Pull complete
deca1dac7403: Pull complete
11c19a33d1b8: Pull complete
742f054a0a3b: Pull complete
63bdd1abbb22: Pull complete
Digest: sha256:d2c0bc7fba3d142e7f9ee9117231fd7ea28630aa71ce2f4a7e2d0ee1fdb85d97
Status: Downloaded newer image for gitea.dooplex.hu/admin/felhom-controller:0.214.0
gitea.dooplex.hu/admin/felhom-controller:0.214.0
[golden] asking the controller which infra images it manages …
[golden] baking infra images (4): traefik:v3.6.7 cloudflare/cloudflared:2026.6.0 gtstef/filebrowser:1.3.3-stable gitea.dooplex.hu/admin/felhom-samba:1.1.0 …
v3.6.7: Pulling from library/traefik
589002ba0eae: Pulling fs layer
ef63511ea6cc: Pulling fs layer
0738e5cb835e: Pulling fs layer
3e6813f70c64: Pulling fs layer
3e6813f70c64: Waiting
ef63511ea6cc: Verifying Checksum
ef63511ea6cc: Download complete
589002ba0eae: Verifying Checksum
589002ba0eae: Download complete
3e6813f70c64: Verifying Checksum
3e6813f70c64: Download complete
0738e5cb835e: Verifying Checksum
0738e5cb835e: Download complete
589002ba0eae: Pull complete
ef63511ea6cc: Pull complete
0738e5cb835e: Pull complete
3e6813f70c64: Pull complete
Digest: sha256:a9890c898f379c1905ee5b28342f6b408dc863f08db2dab20e46c267d1ff463a
Status: Downloaded newer image for traefik:v3.6.7
docker.io/library/traefik:v3.6.7
2026.6.0: Pulling from cloudflare/cloudflared
47de5dd0b812: Pulling fs layer
c172f21841df: Pulling fs layer
99515e7b4d35: Pulling fs layer
99ba982a9142: Pulling fs layer
d6b1b89eccac: Pulling fs layer
2780920e5dbf: Pulling fs layer
7c12895b777b: Pulling fs layer
3214acf345c0: Pulling fs layer
52630fc75a18: Pulling fs layer
dd64bf2dd177: Pulling fs layer
b839dfae01f6: Pulling fs layer
ebddc55facdc: Pulling fs layer
bdfd7f7e5bf6: Pulling fs layer
2d4d7adf6272: Pulling fs layer
40008157d8d2: Pulling fs layer
bd8962e29291: Pulling fs layer
cac2ae0193cb: Pulling fs layer
74d1dac84ecc: Pulling fs layer
99ba982a9142: Waiting
d6b1b89eccac: Waiting
2780920e5dbf: Waiting
7c12895b777b: Waiting
3214acf345c0: Waiting
52630fc75a18: Waiting
dd64bf2dd177: Waiting
b839dfae01f6: Waiting
ebddc55facdc: Waiting
bdfd7f7e5bf6: Waiting
2d4d7adf6272: Waiting
40008157d8d2: Waiting
bd8962e29291: Waiting
cac2ae0193cb: Waiting
74d1dac84ecc: Waiting
99515e7b4d35: Verifying Checksum
99515e7b4d35: Download complete
c172f21841df: Verifying Checksum
c172f21841df: Download complete
47de5dd0b812: Verifying Checksum
47de5dd0b812: Download complete
99ba982a9142: Download complete
d6b1b89eccac: Verifying Checksum
d6b1b89eccac: Download complete
2780920e5dbf: Verifying Checksum
2780920e5dbf: Download complete
7c12895b777b: Verifying Checksum
7c12895b777b: Download complete
52630fc75a18: Verifying Checksum
52630fc75a18: Download complete
3214acf345c0: Verifying Checksum
3214acf345c0: Download complete
47de5dd0b812: Pull complete
dd64bf2dd177: Verifying Checksum
dd64bf2dd177: Download complete
b839dfae01f6: Verifying Checksum
b839dfae01f6: Download complete
ebddc55facdc: Verifying Checksum
ebddc55facdc: Download complete
bdfd7f7e5bf6: Download complete
40008157d8d2: Verifying Checksum
40008157d8d2: Download complete
bd8962e29291: Verifying Checksum
bd8962e29291: Download complete
2d4d7adf6272: Verifying Checksum
2d4d7adf6272: Download complete
c172f21841df: Pull complete
cac2ae0193cb: Verifying Checksum
cac2ae0193cb: Download complete
74d1dac84ecc: Verifying Checksum
74d1dac84ecc: Download complete
99515e7b4d35: Pull complete
99ba982a9142: Pull complete
d6b1b89eccac: Pull complete
2780920e5dbf: Pull complete
7c12895b777b: Pull complete
3214acf345c0: Pull complete
52630fc75a18: Pull complete
dd64bf2dd177: Pull complete
b839dfae01f6: Pull complete
ebddc55facdc: Pull complete
bdfd7f7e5bf6: Pull complete
2d4d7adf6272: Pull complete
40008157d8d2: Pull complete
bd8962e29291: Pull complete
cac2ae0193cb: Pull complete
74d1dac84ecc: Pull complete
Digest: sha256:ba461b8aa9c042156dbd39c38657fe7431bafa063220eab8d5330a523863da9f
Status: Downloaded newer image for cloudflare/cloudflared:2026.6.0
docker.io/cloudflare/cloudflared:2026.6.0
1.3.3-stable: Pulling from gtstef/filebrowser
6a0ac1617861: Pulling fs layer
ef8806083e82: Pulling fs layer
b74107c861c7: Pulling fs layer
adc935def003: Pulling fs layer
4f4fb700ef54: Pulling fs layer
18695ccc900a: Pulling fs layer
45d119d5c397: Pulling fs layer
dac52db4fc51: Pulling fs layer
6d598f86b2f2: Pulling fs layer
8aa349c8396c: Pulling fs layer
dac52db4fc51: Waiting
6d598f86b2f2: Waiting
8aa349c8396c: Waiting
4f4fb700ef54: Waiting
adc935def003: Waiting
18695ccc900a: Waiting
45d119d5c397: Waiting
6a0ac1617861: Verifying Checksum
6a0ac1617861: Download complete
b74107c861c7: Verifying Checksum
b74107c861c7: Download complete
6a0ac1617861: Pull complete
adc935def003: Verifying Checksum
adc935def003: Download complete
4f4fb700ef54: Verifying Checksum
4f4fb700ef54: Download complete
45d119d5c397: Verifying Checksum
45d119d5c397: Download complete
dac52db4fc51: Verifying Checksum
dac52db4fc51: Download complete
18695ccc900a: Verifying Checksum
18695ccc900a: Download complete
6d598f86b2f2: Verifying Checksum
6d598f86b2f2: Download complete
8aa349c8396c: Verifying Checksum
8aa349c8396c: Download complete
ef8806083e82: Verifying Checksum
ef8806083e82: Download complete
ef8806083e82: Pull complete
b74107c861c7: Pull complete
adc935def003: Pull complete
4f4fb700ef54: Pull complete
18695ccc900a: Pull complete
45d119d5c397: Pull complete
dac52db4fc51: Pull complete
6d598f86b2f2: Pull complete
8aa349c8396c: Pull complete
Digest: sha256:eb3733681db8757412632c61a99ad656f0d94ed6781bb2ea114b4d70babab78c
Status: Downloaded newer image for gtstef/filebrowser:1.3.3-stable
docker.io/gtstef/filebrowser:1.3.3-stable
1.1.0: Pulling from admin/felhom-samba
897d797d2723: Pulling fs layer
3051591aa250: Pulling fs layer
ce57a3f93416: Pulling fs layer
fb94eeec2fe1: Pulling fs layer
fb94eeec2fe1: Waiting
ce57a3f93416: Verifying Checksum
ce57a3f93416: Download complete
fb94eeec2fe1: Verifying Checksum
fb94eeec2fe1: Download complete
897d797d2723: Verifying Checksum
897d797d2723: Download complete
3051591aa250: Verifying Checksum
3051591aa250: Download complete
897d797d2723: Pull complete
3051591aa250: Pull complete
ce57a3f93416: Pull complete
fb94eeec2fe1: Pull complete
Digest: sha256:1c17c09422bec0366d7cf0e0fcfc1486ba6c90334a0a5d5c851073a9342f8f10
Status: Downloaded newer image for gitea.dooplex.hu/admin/felhom-samba:1.1.0
gitea.dooplex.hu/admin/felhom-samba:1.1.0
[golden] baking the controller-bootstrap unit (deploys the BAKED controller from the config mount) …
Created symlink '/etc/systemd/system/multi-user.target.wants/felhom-controller-bootstrap.service' → '/etc/systemd/system/felhom-controller-bootstrap.service'.
[golden] baking the controller-bootstrap PATH unit (starts the service on bootstrap-mount hot-plug — B1) …
Created symlink '/etc/systemd/system/multi-user.target.wants/felhom-controller-bootstrap.path' → '/etc/systemd/system/felhom-controller-bootstrap.path'.
[golden] baking the first-boot SSH host-key regeneration unit (F3) …
Created symlink '/etc/systemd/system/multi-user.target.wants/felhom-regen-hostkeys.service' → '/etc/systemd/system/felhom-regen-hostkeys.service'.
[golden] identity-clean + minimize …
[golden] stop + archive …
INFO: including mount point rootfs ('/') in backup
INFO: including mount point mp0 ('/var/lib/felhom') in backup
INFO: archive file size: 625MB
INFO: Finished Backup of VM 9100 (00:00:39)
[golden] DONE. golden archive volid: local:backup/vzdump-lxc-9100-2026_08_12-18_52_07.tar.zst (rootfs 32G + ONE data volume 24G @ /var/lib/felhom, all in the archive)
[golden] publishing golden (656392524 bytes, sha256 3a40379cb00d98c6…) → https://gitea.dooplex.hu/api/packages/admin/generic/felhom-golden/0.214.0/golden.tar.zst
[golden] pre-delete existing: HTTP 404 (404/204 expected)
[golden] upload OK (HTTP 201)
GOLDEN_VERSION=0.214.0
GOLDEN_SHA256=3a40379cb00d98c6f9550815b2b024783f153e9eef9be2b3252253d24d8d2c4d
[golden] Record in the hub operator UI (Configs → Day-0 artifacts): golden 0.214.0 / 3a40379cb00d98c6f9550815b2b024783f153e9eef9be2b3252253d24d8d2c4d
[golden] (the build guest 9100 is stopped; destroy it with: pct destroy 9100 --purge)
+229
View File
@@ -1,3 +1,232 @@
## v0.105.0 — the third name, a machine told to be quiet, and a guard for the hub's own words (2026-08-13)
Hub only. **No controller change, no agent change, no wire change — nothing to bake.** R-323, R-324,
R-321, R-326.
### R-323 — the third near-homograph: „Tulajdonosi jelmondat"
Three secrets a customer can hold, and they must be tellable apart by a hurried reader:
| | what it is | name |
|---|---|---|
| three words | takes control of the dashboard | „Beállító kód" |
| ten words | opens the sealed off-site backups | „Helyreállítási kód" |
| **five words** | **proves the account owns the box being bound** | **„Tulajdonosi jelmondat"** |
The five-word phrase was „Visszaállító jelszó" — **one word from the name retired last week for
colliding with „Helyreállítási kód"**, and false besides: the phrase restores nothing.
**Five customer-facing sites, all here**: `web/selfbind.go:255,256,261,263` and
`notify/templates.go:331`. Nothing in `felhom-controller` or `felhom-agent` — the only hits there are
comments and a test asserting the already-retired „Visszaállító **kód**" is absent.
**Both obvious names were rejected, with reasons.** „Fiókjelszó" is worse than the trap it fixes:
there IS an account password (the dashboard login), so it would collide with a *different* real
secret. „Összekötési jelszó" recreates the trap structurally — the other factor on the same page and
in the same mail is the **„Párosító kód"**, so naming this one after the same act would leave the two
factors a customer types in one sitting separated only by kód-versus-jelszó, which is exactly the
shape being removed. „Tulajdonosi jelmondat" is distinct on **both** axes — stem and noun.
**Naming only.** The form field is still `name="passphrase"`;
`TestSelfBindPassphrase_StillAcceptedAfterTheRename` drives the real handler with the same messy human
spacing and asserts the appliance still binds.
### R-324 — the hub's customer copy comes under a guard, for the first time
`retrieval_promise_gate.py` lives in `felhom-controller` and scans that repo only. It was extended to
Go strings on 2026-08-12 because the recovery screen's copy *"had never been scanned"* — and the same
sentence was true one repo over, for the surface a customer reads **first**.
`scripts/hub_copy_gate.py`, registered in `repo_gates.py`:
- **retired names — banned outright, across all 95 hub files**, no allowlist. Comments stripped.
- **retrieval stems — registered, not banned**, in four declared customer surfaces. A missing declared
surface is a FAILURE, never a skip. The allowlist is empty, and that is a measurement.
**The selftest found a defect in its own instrument on the first run**: the synthetic source was named
`<selftest>`, comment-stripping keys off the `.go` extension, and the step-3 control convicted a
comment. The bug was in the guard; the control is what found it.
Vocabulary is **not duplicated**`scripts/customer_copy_vocab.py` is the one list, in the same
shared-gate home both repos already consume without copying. The controller gate has not yet adopted
it (that would be a felhom-controller change, out of scope tonight), so this gate **reads its `STEMS`
and fails on divergence** — watched failing, and green again when restored. R-325 removes the scaffold.
### R-321 — a machine we told to be quiet is not a machine that died
Switching a box's reporting off is supported: the controller sends one final report carrying
`health.status = "disabled"` and goes quiet by design. The hub stored that, rendered it in the
roll-up, and **alarmed on it anyway** — stale at 30 minutes, down at 60, two e-mails about an outage
we caused on purpose.
**It was two doors, not one.** `StalenessChecker.Check` now skips the age transition; and because the
state is **recorded** (`StateDisabled`) rather than deleted, `CheckBackupDeadlines` can skip it too —
a deleted state returns `""`, which is not `"down"`, so that check would have gone on sending
`expected_backup_missed` every morning. R-195's shape returning through a second door.
The discriminator is the box's own last word, not an inference. `downtimeStart` is cleared on entry.
**The re-enablement clock runs from the report the hub can see**, so a box that reports on re-enabling
starts its clock there; leaving `disabled` re-enters the new-customer branch, so no `node_recovered`
fires for an outage that never happened. **Limit, stated:** a box re-enabled that then fails to report
keeps being suppressed — the hub's view changes only when a report arrives, which is exactly why the
state is made visible.
**Three red-proofs, mutations asserted applied.** The one that matters: an unconditional suppression
was seen leaving a genuinely dead machine at `"disabled"` instead of `"down"`. A test weakness was
found by a red-proof and fixed — scenario A seeded the customer already-disabled, so the new-customer
branch swallowed the first observation and it passed on the state assertion alone even with the
suppression deleted.
### R-326 — "what is unproven" becomes a question a machine can answer
A session asked for *"the nine grey claims"* could not determine which nine and declined to guess.
**Nine is real and answers a different question: it is the count of claims the 2026-08-09 pass
DOWNGRADED.** The real figures, from `where-felhom-stands.yaml`: **55 claims — walked 23, partial 14,
built 14, missing 4; NOT WALKED 32 of 55**, of which only 6 cite evidence and 26 are prose only.
`scripts/unproven.py` prints them; `--summary` prints the counts. Wired into the end-of-session
checklist. Its first run found a stale claim — `claim.code-naming` still describes a defect now fixed
twice over (R-327). The capability map is deliberately **not** restructured.
## v0.104.0 — the hub can see whether a guest's networking works, and one name per secret (2026-08-13, R-319 + R-295 hub half)
Two things, both hub-only. **No wire change, no agent change, no controller change, nothing to bake.**
### R-319 — the guest-network watchdog finally has a reader
The agent has reported `guest_net` on every heartbeat since **v0.92.0** (R-54, 2026-07-21). The string
`guest_net` occurred **nowhere** in this repository: the stanza was stored as raw text inside
`report_json` and read by nothing — no check, no alarm, no screen. This is the first of R-264's
twenty-one unconsumed facts to be given a reader, and it was chosen because it has a live incident
behind it: a killed `dhclient` in a guest took a tunnel down for 1 h 15 m with nobody told
(`audits/INCIDENT-guest-dhclient-killed-2026-07-20.md`).
**Established before anything was written.** The facts arrive and are persisted:
`demo-felhom-8363b5`'s newest row carries `guest_net.checked_at` plus per-guest
`vmid/state/mode/ip/has_route/dhclient_alive/checked_at/message`, and the agent's wire type
(`felhom-agent/internal/hub/report.go:139-160`) additionally carries `healed`, `heal_succeeded`,
`last_heal_at`, `heals_last_hour`, `damped` — absent from the live rows only because they are
`omitempty` on a box that has never needed a repair.
**THE SIGNAL IS THE REPAIR COUNT, NOT THE STATE.** A guest whose network the watchdog keeps repairing
is healthy at every instant anyone looks and is nevertheless failing. Rendering `state` alone would
give that machine a green tick — the exact shape of the defect that drew a failed disk as a healthy
empty disk. `heals_last_hour` is therefore surfaced *beside* the state, not behind it, and drives the
card's summary badge.
`heal_succeeded` is decoded too, deliberately. **R-260 is this project's standing warning that a
decoder three fields short of the agent dropped the one that decided the question the checker
existed to answer.** Six failed repairs is a guest that is down; six successful ones is a nuisance.
**An unknown is never drawn as healthy.** Three absences are kept apart, each with its own sentence,
following the companion-flag convention (`hostNetworkView` is the sibling):
- the agent predates v0.92.0 — absence is expected, and still tells us nothing;
- a capable agent sent no stanza — the watchdog is default-on, so this means it was switched off;
- a guest whose own `state` is empty or unrecognised — the state switch is an ALLOW-LIST, so adding a
state to the agent can never silently paint it green here.
A malformed stanza degrades to unknown and **must not 500 the page** — one box's bad field cannot
break the host page for the rest of the fleet.
**No alarm was added, and that is a judgement rather than an omission.** The incident behind this was
about nobody being able to *see* the condition, not about nobody being paged; and a new alarm on a
fleet of two demo machines is untested noise on a dispatcher whose severity contract is exact-match
lowercase. Revisit when a third machine exists, or when a repair count is observed climbing on real
hardware — the visible count is what will supply that evidence.
**Red-proofs, each mutation asserted applied by grep before its run and reverted after:**
1. the unknown branches replaced by the healthy badge → **both** scenario-C sub-cases go red, a silent
machine seen rendering as healthy. *(the one that matters)*
2. `RepairCount: 0` in the decoder → B goes red on *"a guest repaired 6 times in an hour is reported
as fine"*.
3. `Degraded()` forced true → A goes red on *"a machine that is fine is being alarmed on"* — the guard
is reachable in both directions, not just the alarming one.
**Positive control that the wiring is real and not merely written:** `wire_contract_gate.py`'s
checked-tag count rose **182 → 190** and its skipped count fell **88 → 80**, because the eight
`guest_net` allowlist entries were REMOVED. An allowlisted tag is *skipped*, so leaving them would
have meant the new reader's own fields were never checked for reachability at all.
### R-295 hub half — one name per secret, and the mail names the page the machine shows
The box side shipped 2026-08-10; the hub side was dropped twice and was the last place the retired
name survived — in the **mails**, the one surface a customer reads *before* they see any screen.
- the three-word dashboard code is **„Beállító kód"** everywhere: the mail bodies, the mail subjects,
the operator button, and the lockout notice;
- the ten-word escrow code stays **„Helyreállítási kód"**, and no dashboard-code mail names it —
naming both secrets in one message is how a customer comes to believe they are the same thing;
- **„Visszaállító kód" is retired.**
**New email kind `reenroll`, because the mail must name the page the machine is actually showing.**
`ReissueForReenroll` sent the *reset* mail, which directs the customer to an „Elfelejtett jelszó"
page — but a rebuilt box has no password, so the controller renders „A szerver beállítása"
(`web/claim.go:279`, `reset := s.authEnabled()`) and serves **no login page at all**. The route named
was not on their screen. Only the hub can tell the two situations apart, because it is the hub that
chose which call site fired. Same secret, same name, different sentence — which is exactly what the
ruling asks for. The re-enrol mail deliberately says **nothing** about apps or backups: a clean-slate
reinstall is precisely where such a reassurance could be false.
**This is naming, not function.** No acceptance logic moved.
`TestReenrollSplit_ChangesTheMailNotTheSecret` pins it: the generation still advances, the stored
bcrypt hash still verifies the emailed code, no plaintext is persisted. On the box side the companion
pin is the controller's `TestResetCode_StillAcceptedOnTheSetupPage`.
**Two red-proofs:** routing re-enrolment back to `EmailReset` reddens both the new pin and the
pre-existing `TestReissueForReenroll`; putting „Visszaállító kód" back in the reset body reddens
`TestFormatClaimEmail_OneNamePerSecret`.
**The claim guard has never scanned the hub.** `retrieval_promise_gate.py` lives in
`felhom-controller/controller/scripts/` and its declared surfaces are that repo's templates plus one
Go handler file. A scan of `felhom.eu/hub/` with the gate's own four stems returns **zero** — so
nothing was hiding, and the new strings carry no stem and need no registration. That the gate's scope
excludes a customer-facing surface is recorded as R-322 rather than fixed here.
### Also
- `wire_contract_gate.py` grows a **third** entry kind — `not consumed, deliberately` — carrying the
operator's 2026-08-12 rulings with their date. Five facts move to it; `reporting_disabled` moves to
`redundant`. **Those rulings were not in the register before today**, which is why R-264 now records
them. Where the twenty stand: **8 read · 5 deliberately unread · 1 redundant · 6 still owed**.
- **R-321 filed, not fixed:** the staleness checker is age-only, so a box on which reporting is
deliberately switched off still alarms `node_stale` then `node_down`. Decoding `reporting_disabled`
would have felt like progress and left the alarm firing; the fix belongs in the checker, which
already holds the `health.status = "disabled"` it needs.
## v0.103.0 — a host can read the packages we kept for it (2026-08-12, R-311)
**`ListSupersededEscrow` had zero production callers for nineteen days.** It is the only reader of a
retained `identity_blob`, and nothing called it — so the retention shipped in v0.93.0 was material the
product could not reach. Proven on the fixture 2026-08-12: a recovery code that opens a retained
package (unsealed by hand, and it restored planted files byte-identical from a store the box itself
could no longer open) was answered by the product as a code that opened nothing.
**New: `GET /api/v1/hosts/<id>/escrow/retained`** — the retained siblings of the existing
`/escrow` row, self-scoped to the asking host exactly as that one is, behind the same §8.3
recovery-mode gate, and audited with the same `escrow_blob_served` event BEFORE the bytes leave.
**What it will not serve.** Rows whose `identity_blob` is NULL — every pre-v0.93.0 row — are withheld
and returned as `unopenable_count` instead. They retain the PBS datastore key and not the repository
password, so they can never open what the caller is asking about; serving them would have the agent
try packages that cannot succeed and would let the recovery screen claim an earlier package is
openable on exactly the boxes the original defect hurt. The count is returned because their EXISTENCE
is load-bearing and underivable: on such a box a correct old code opens nothing, and the honest reason
is a defect of ours rather than the customer's typing.
**The trade, stated rather than waved through.** The hub still cannot read any of it — sealed bytes in,
sealed bytes out, no decrypt path, no recovery code ever held. What widens is VOLUME: a host key that
could fetch one opaque package can now fetch N. Bounded by the same self-scope, the same recovery-mode
gate, and an explicit `retainedEscrowServeCap` (16) so a long supersession history cannot become an
unbounded read.
Pinned by six tests driven through the real endpoint, with four red-proofs each asserted to have
applied: serving the CURRENT row instead (count 2→1), dropping the unopenable guard (count 1→2,
unopenable 1→0), dropping self-scope (403→200), and collapsing the route suffix (count 1→0).
---
## v0.102.0 — the hub refuses to vouch a version that cannot be installed (2026-08-09, R-273)
**The guard owed since 2026-08-09 morning.** Agent v0.128.0 had been published as a package and never
@@ -0,0 +1,218 @@
package api
import (
"encoding/base64"
"encoding/json"
"net/http"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
// R-311 (v0.103.0) — GET /hosts/<id>/escrow/retained.
//
// WHY THESE TESTS EXIST. `ListSupersededEscrow` had zero production callers for nineteen days; the
// retention it reads was proven on 2026-08-12 to hold the right material and to open a store the box
// itself could not, while the customer was told their (correct) code opened nothing. These tests pin
// the endpoint that ends that, and — more importantly — they pin the two things it must NOT do:
// serve a package that cannot be opened, and let one host read another's.
type retainedResp struct {
HostID string `json:"host_id"`
Count int `json:"count"`
UnopenableCount int `json:"unopenable_count"`
TruncatedCount int `json:"truncated_count"`
Packages []struct {
Index int `json:"index"`
SupersededAt string `json:"superseded_at"`
KeyFingerprint string `json:"key_fingerprint"`
IdentityEscrowB64 string `json:"identity_escrow_b64"`
} `json:"packages"`
}
func getRetained(t *testing.T, h *Handler, hostID, key string) (int, retainedResp) {
t.Helper()
rr := do(h, http.MethodGet, "/hosts/"+hostID+"/escrow/retained", key, "")
var out retainedResp
if rr.Code == http.StatusOK {
if err := json.Unmarshal(rr.Body.Bytes(), &out); err != nil {
t.Fatalf("decode retained response: %v (body %s)", err, rr.Body.String())
}
}
return rr.Code, out
}
// seedSuperseded drives the REAL supersession path (a PUT sealing a different password demotes the
// current row) rather than inserting into host_escrow_superseded directly — so what the test proves
// includes the demote wiring, not just this handler's SELECT.
func seedSuperseded(t *testing.T, h *Handler, hostID, key string, gens []struct{ sha, identity string }) {
t.Helper()
for i, g := range gens {
if rr := do(h, http.MethodPut, "/hosts/"+hostID+"/escrow", key, escrowBodyWithHash([]byte("k"), g.sha, g.identity)); rr.Code != http.StatusOK {
t.Fatalf("seed PUT %d = %d (%s)", i, rr.Code, rr.Body.String())
}
}
}
// THE ONE THAT MATTERS: a retained package that carries key material is served, and the caller can
// tell it apart from the current row.
//
// RED-PROOF: change the handler's SELECT source from ListSupersededEscrow to GetHostDRBundle (i.e.
// serve the CURRENT row) → the served blob becomes "age-gen3" → this FAILS on the body comparison,
// which is exactly the confusion the endpoint exists to end.
func TestRetainedEscrowGet_ServesRetainedPackages(t *testing.T) {
h, st, _ := newTestHandler(t)
st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY"})
seedSuperseded(t, h, "h1", "HKEY", []struct{ sha, identity string }{
{"SHA_GEN1", "age-gen1"},
{"SHA_GEN2", "age-gen2"},
{"SHA_GEN3", "age-gen3"}, // current after this
})
code, out := getRetained(t, h, "h1", "HKEY")
if code != http.StatusOK {
t.Fatalf("GET retained = %d, want 200", code)
}
if out.Count != 2 || len(out.Packages) != 2 {
t.Fatalf("want 2 retained packages, got count=%d len=%d", out.Count, len(out.Packages))
}
if out.UnopenableCount != 0 {
t.Errorf("unopenable_count = %d, want 0 (every seeded row carried an identity blob)", out.UnopenableCount)
}
// The retained packages must be the SUPERSEDED generations, never the current one. If this ever
// serves gen3 the endpoint is answering the wrong question and the customer is misled again.
got := map[string]bool{}
for _, p := range out.Packages {
b, err := base64.StdEncoding.DecodeString(p.IdentityEscrowB64)
if err != nil {
t.Fatalf("package %d: identity blob is not base64: %v", p.Index, err)
}
got[string(b)] = true
if p.SupersededAt == "" {
t.Errorf("package %d has no superseded_at — the screen needs a date to name the package", p.Index)
}
}
if !got["age-gen1"] || !got["age-gen2"] {
t.Errorf("served packages = %v, want the two SUPERSEDED generations (age-gen1, age-gen2)", got)
}
if got["age-gen3"] {
t.Error("the CURRENT package was served as retained — that is the wrong-package confusion this endpoint exists to end")
}
}
// A retained row with NO identity blob (every pre-v0.93.0 row) must be WITHHELD and COUNTED. Serving
// it would have the agent try a package that cannot open anything, and would let the screen claim an
// earlier package is recoverable on exactly the boxes hurt by the original defect.
//
// RED-PROOF: delete the `if len(e.IdentityBlob) == 0 { unopenable++; continue }` guard → the empty
// row is served as a package → Count becomes 2 and UnopenableCount 0 → this FAILS twice.
func TestRetainedEscrowGet_WithholdsAndCountsUnopenableRows(t *testing.T) {
h, st, _ := newTestHandler(t)
st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY"})
// gen1 carries NO identity blob — the pre-v0.93.0 shape.
seedSuperseded(t, h, "h1", "HKEY", []struct{ sha, identity string }{
{"SHA_GEN1", ""},
{"SHA_GEN2", "age-gen2"},
{"SHA_GEN3", "age-gen3"},
})
code, out := getRetained(t, h, "h1", "HKEY")
if code != http.StatusOK {
t.Fatalf("GET retained = %d, want 200", code)
}
if out.Count != 1 {
t.Errorf("count = %d, want 1 (only gen2 carries material)", out.Count)
}
if out.UnopenableCount != 1 {
t.Errorf("unopenable_count = %d, want 1 — the caller cannot derive this and needs it to explain "+
"why a correct old code opens nothing on such a box", out.UnopenableCount)
}
for _, p := range out.Packages {
if p.IdentityEscrowB64 == "" {
t.Error("a package with an empty identity blob was served — it can never open anything")
}
}
}
// SELF-SCOPE. The current-row GET has this and a retained read is strictly more material, so the same
// asymmetry must hold: a host key reads only its own.
//
// RED-PROOF: remove the `!isGlobal && authHostID != pathHostID` branch → h2's key reads h1's packages
// → this FAILS with 200.
func TestRetainedEscrowGet_SelfScoped(t *testing.T) {
h, st, _ := newTestHandler(t)
st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY1"})
st.UpsertHost(&store.Host{HostID: "h2", CustomerID: "c2", APIKey: "HKEY2"})
seedSuperseded(t, h, "h1", "HKEY1", []struct{ sha, identity string }{
{"SHA_GEN1", "age-gen1"},
{"SHA_GEN2", "age-gen2"},
})
rr := do(h, http.MethodGet, "/hosts/h1/escrow/retained", "HKEY2", "")
if rr.Code != http.StatusForbidden {
t.Fatalf("h2 reading h1's retained packages = %d, want 403 — a host key must never be a fleet-wide reader", rr.Code)
}
}
// A host that has never re-sealed gets a clean, empty answer — not a 404 and not a fault. "No retained
// package" is an ordinary situation and must be distinguishable from "something is broken", or the
// agent's try-loop cannot tell them apart either.
//
// RED-PROOF: make the handler 404 on an empty list → this FAILS with 404.
func TestRetainedEscrowGet_NoneIsACleanEmptyAnswer(t *testing.T) {
h, st, _ := newTestHandler(t)
st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY"})
if rr := do(h, http.MethodPut, "/hosts/h1/escrow", "HKEY", escrowBodyWithHash([]byte("k"), "SHA1", "age-1")); rr.Code != http.StatusOK {
t.Fatalf("seed PUT = %d", rr.Code)
}
code, out := getRetained(t, h, "h1", "HKEY")
if code != http.StatusOK {
t.Fatalf("GET retained on a never-superseded host = %d, want 200", code)
}
if out.Count != 0 || len(out.Packages) != 0 || out.UnopenableCount != 0 {
t.Errorf("want an empty answer, got count=%d packages=%d unopenable=%d", out.Count, len(out.Packages), out.UnopenableCount)
}
}
// The retained route must not be reachable without a key at all.
//
// RED-PROOF: drop the checkAuthHost block → this FAILS with 200.
func TestRetainedEscrowGet_RequiresAuth(t *testing.T) {
h, st, _ := newTestHandler(t)
st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY"})
if rr := do(h, http.MethodGet, "/hosts/h1/escrow/retained", "", ""); rr.Code != http.StatusUnauthorized {
t.Fatalf("unauthenticated retained GET = %d, want 401", rr.Code)
}
}
// ROUTING: /escrow and /escrow/retained must stay two different answers. A prefix "tidy-up" that
// collapsed them would route retained reads to the current row — silently the wrong package.
//
// RED-PROOF: change the retained case's suffix to "/escrow" (so the earlier case wins) → the retained
// route returns the CURRENT-row shape, which has no "packages" key → this FAILS.
func TestRetainedEscrowGet_IsADistinctRouteFromTheCurrentRow(t *testing.T) {
h, st, _ := newTestHandler(t)
st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY"})
seedSuperseded(t, h, "h1", "HKEY", []struct{ sha, identity string }{
{"SHA_GEN1", "age-gen1"},
{"SHA_GEN2", "age-gen2"},
})
cur := do(h, http.MethodGet, "/hosts/h1/escrow", "HKEY", "")
var curBody map[string]any
if err := json.Unmarshal(cur.Body.Bytes(), &curBody); err != nil {
t.Fatalf("decode current-row body: %v", err)
}
if _, hasPackages := curBody["packages"]; hasPackages {
t.Error("the CURRENT-row route grew a packages key — the two routes have been merged")
}
if _, hasPresent := curBody["present"]; !hasPresent {
t.Error("the current-row route lost its present flag")
}
_, ret := getRetained(t, h, "h1", "HKEY")
if ret.Count != 1 {
t.Fatalf("retained route count = %d, want 1 — it is answering the current row's question", ret.Count)
}
}
+159
View File
@@ -245,6 +245,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case r.Method == http.MethodGet && strings.HasPrefix(path, "/hosts/") && strings.HasSuffix(path, "/escrow"):
hostID := strings.TrimSuffix(strings.TrimPrefix(path, "/hosts/"), "/escrow")
h.handleHostEscrowGet(w, r, hostID)
// R-311 (v0.103.0): the RETAINED siblings of the row above. The two cases cannot collide — one
// ends `/escrow`, the other `/escrow/retained` — but do NOT "tidy" them into a single prefix
// match: a prefix match would route retained reads to the CURRENT row, which is silently the
// wrong package and is exactly the confusion this endpoint exists to end.
case r.Method == http.MethodGet && strings.HasPrefix(path, "/hosts/") && strings.HasSuffix(path, "/escrow/retained"):
hostID := strings.TrimSuffix(strings.TrimPrefix(path, "/hosts/"), "/escrow/retained")
h.handleHostEscrowRetainedGet(w, r, hostID)
// G1 break-glass: day-0 vaults the root@pam console credential (self-scoped host key); the
// operator retrieves it via the /admin/ path (global key only).
case r.Method == http.MethodPut && strings.HasPrefix(path, "/hosts/") && strings.HasSuffix(path, "/recovery-credential"):
@@ -1371,6 +1378,158 @@ func (h *Handler) handleHostEscrowGet(w http.ResponseWriter, r *http.Request, pa
})
}
// handleHostEscrowRetainedGet serves a host its own RETAINED (superseded) sealed identity packages.
//
// ── WHY THIS EXISTS (R-311) ─────────────────────────────────────────────────────────────────────
//
// `ListSupersededEscrow` has been the only reader of a retained identity blob since v0.93.0 and had
// ZERO production callers — every call site was a test. The consequence, measured on the fixture
// 2026-08-12: a recovery code that demonstrably opens a retained package (proven by hand: unsealed,
// and it restored planted files byte-identical from a store the box could no longer open) was
// reported to the customer as a code that opened nothing. The screen already hedged that with two
// possible causes (R-222/R-226) and said it could not tell them apart. **It could not tell them
// apart because nothing ever looked.** This endpoint is what makes looking possible.
//
// ── WHAT THIS DOES NOT CHANGE ───────────────────────────────────────────────────────────────────
//
// The hub still cannot read any of it. Sealed bytes in, sealed bytes out; there is no decrypt path
// here and the hub has never held a recovery code. What DOES widen is volume: a host key that could
// previously fetch one opaque package can now fetch N. The trade is stated rather than waved through
// — see the audit — and it is bounded three ways: the same self-scope as the current row, the same
// recovery-mode gate, and an explicit cap so a host with a long supersession history cannot turn one
// request into an unbounded read.
//
// Rows whose `identity_blob` is NULL are NOT served and are counted separately. They are the
// pre-v0.93.0 rows; they retain the PBS key and not the repository password, so they can never open
// anything the caller is asking about. Serving them would make the agent try packages that cannot
// succeed and would let the screen claim an earlier package is openable when it is not — the same
// false-explanation trap `SupersededPresent` already avoids at store.go. They are counted because
// their EXISTENCE is a true and load-bearing fact: on those boxes a correct old code opens nothing,
// and the honest reason is a defect of ours, not the customer's typing.
const retainedEscrowServeCap = 16
// RetainedEscrowPackage / RetainedEscrowResponse are the WIRE for GET /hosts/<id>/escrow/retained.
//
// They are named types rather than a `map[string]any` on purpose: the wire-contract gate resolves a
// declared ROOT by TYPE, so an untyped map is a cross-repo contract the gate cannot see. This wire is
// declared in `scripts/wire_contract_gate.py` ROOTS — hub → agent — and the agent's mirror is
// `felhom-agent/internal/hub.RetainedEscrowResponse`. Change a tag here and the gate fails there,
// which is the entire point.
type RetainedEscrowPackage struct {
// Index is a label WITHIN ONE RESPONSE. Not durable, never persisted, never a lookup key.
Index int `json:"index"`
// SupersededAt is when this package stopped being the current one. It is the only field here a
// customer ever sees — it is how they recognise which recovery code they are holding.
SupersededAt string `json:"superseded_at"`
KeyFingerprint string `json:"key_fingerprint"`
// IdentityEscrowB64 is OPAQUE ciphertext. The hub cannot open it and never could.
IdentityEscrowB64 string `json:"identity_escrow_b64"`
}
type RetainedEscrowResponse struct {
HostID string `json:"host_id"`
Count int `json:"count"`
// UnopenableCount is the number of retained rows withheld because they carry no key material
// (every pre-v0.93.0 row). Underivable by the caller and load-bearing: on a box with only those,
// a perfectly correct old code opens nothing and the reason is a defect of ours.
UnopenableCount int `json:"unopenable_count"`
TruncatedCount int `json:"truncated_count"`
Packages []RetainedEscrowPackage `json:"packages"`
}
func (h *Handler) handleHostEscrowRetainedGet(w http.ResponseWriter, r *http.Request, pathHostID string) {
authHostID, _, isGlobal, ok := h.checkAuthHost(r)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if pathHostID == "" {
http.Error(w, "Missing host_id", http.StatusBadRequest)
return
}
// SELF-SCOPED, identically to the current-row GET. Without this line any host key is a fleet-wide
// reader of every retained package, which is strictly worse than the same hole on one row.
if !isGlobal && authHostID != pathHostID {
h.logger.Printf("[WARN] retained escrow GET REFUSED: host %s asked for %s's packages (self-scope)", authHostID, pathHostID)
http.Error(w, "Forbidden: host_id mismatch", http.StatusForbidden)
return
}
host, err := h.store.GetHost(pathHostID)
if err != nil {
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
if host == nil {
http.Error(w, "Unknown host_id", http.StatusNotFound)
return
}
// The SAME §8.3 gate as the current row. A retained package is not less sensitive than the
// current one; if self-service retrieval is ever switched off, it must go dark with it.
if !escrowSelfServiceRetrieval && !host.InRecoveryMode(time.Now().UTC()) {
h.logger.Printf("[WARN] retained escrow GET REFUSED for %s — self-service retrieval is disabled and recovery mode is not armed", pathHostID)
http.Error(w, "Forbidden: host not in recovery mode (operator must arm it)", http.StatusForbidden)
return
}
retained, rerr := h.store.ListSupersededEscrow(pathHostID)
if rerr != nil {
h.logger.Printf("[ERROR] retained escrow GET for %s: %v", pathHostID, rerr)
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
packages := make([]RetainedEscrowPackage, 0, len(retained))
unopenable := 0
for _, e := range retained {
if len(e.IdentityBlob) == 0 {
unopenable++
continue
}
if len(packages) >= retainedEscrowServeCap {
continue
}
packages = append(packages, RetainedEscrowPackage{
Index: len(packages),
SupersededAt: e.UpdatedAt,
KeyFingerprint: e.KeyFingerprint,
IdentityEscrowB64: base64.StdEncoding.EncodeToString(e.IdentityBlob),
})
}
truncated := 0
if n := len(retained) - unopenable; n > len(packages) {
truncated = n - len(packages)
}
// The audit row is written BEFORE the bytes leave, exactly as the current-row GET does, so a
// retrieval cannot be served without its record. Severity matches that path deliberately: this is
// a recovery in progress and nothing else, and it must reach the operator by e-mail.
if host.CustomerID != "" && len(packages) > 0 {
msg := fmt.Sprintf("Retained recovery packages served: host %s retrieved %d retained sealed package(s) (%d unopenable pre-v0.93.0 row(s) withheld). "+
"This is the recovery path in use — the packages cannot be opened without the customer's recovery code, which the hub never holds. "+
"If no recovery is in progress on that box, investigate.", pathHostID, len(packages), unopenable)
details, _ := json.Marshal(map[string]any{
"host_id": pathHostID,
"served": len(packages),
"unopenable": unopenable,
"truncated": truncated,
"self_scope": !isGlobal,
})
if _, eerr := h.store.SaveEvent(host.CustomerID, eventEscrowBlobServed, "warning", msg, string(details), "hub"); eerr != nil {
h.logger.Printf("[WARN] %s event save FAILED for %s (serving anyway): %v", eventEscrowBlobServed, pathHostID, eerr)
} else if h.dispatcher != nil {
go h.dispatcher.ProcessEvent(host.CustomerID, eventEscrowBlobServed, "warning", msg, string(details), "hub")
}
}
h.logger.Printf("[WARN] retained escrow SERVED to host %s (%d package(s), %d unopenable withheld, %d over cap, self_scope=%v)",
pathHostID, len(packages), unopenable, truncated, !isGlobal)
writeJSON(w, http.StatusOK, RetainedEscrowResponse{
HostID: pathHostID,
Count: len(packages),
UnopenableCount: unopenable,
TruncatedCount: truncated,
Packages: packages,
})
}
// maybeEmitRepoKeyChanged raises ONE operator signal per supersession when the sealed offsite repo
// password demonstrably changed. Both hashes have been stored since SLICE 3 (host_escrow and, since
// v0.60.0, host_escrow_superseded) and NOTHING compared them: demo-felhom's repository password
+16 -2
View File
@@ -29,8 +29,21 @@ type EmailKind string
const (
EmailClaim EmailKind = "claim" // first setup: "Elindult a Felhom szervered"
EmailReset EmailKind = "reset" // forgotten password
EmailReset EmailKind = "reset" // forgotten password — the box HAS a password
EmailClaimed EmailKind = "claimed" // confirmation after a successful claim (carries no code)
// EmailReenroll — the box was wiped and re-enrolled, so the fresh controller has NO password
// while the hub-side claim is still set (ReissueForReenroll).
//
// R-295 (hub half, 2026-08-13): THIS EXISTS BECAUSE THE MAIL MUST NAME THE PAGE THE MACHINE IS
// ACTUALLY SHOWING. Both situations deliver the same secret and it keeps the same name — the
// three-word „Beállító kód" — but they do NOT show the same screen, and only the hub can tell
// them apart, because it is the hub that chose which call site fired. A rebuilt box renders
// „A szerver beállítása" (controller `web/claim.go:279`: `reset := s.authEnabled()`, and a fresh
// controller has no password), and it serves no login page — so it has no „Elfelejtett jelszó"
// link at all. Sending a re-enrolled customer to that page names a route that is not on their
// screen. Splitting the KIND rather than the NAME is what the ruling asks for: one secret in two
// situations keeps its name, and the sentence around it changes.
EmailReenroll EmailKind = "reenroll"
)
// Mailer delivers a claim-arc email. The code is passed through and MUST NOT be persisted or
@@ -178,7 +191,8 @@ func (e *Engine) ReissueForReenroll(cc *store.CustomerConfig) (gen int, reissued
if cs == nil || !cs.Claimed() {
return 0, false, nil // unclaimed → first-provision path; nothing to re-issue
}
gen, err = e.rotateAndSend(cc, EmailReset)
// EmailReenroll, not EmailReset: same secret, same name, different screen — see the constant.
gen, err = e.rotateAndSend(cc, EmailReenroll)
if err != nil {
return gen, true, err // reissued=true so the caller records the attempt even on email failure
}
+8 -2
View File
@@ -166,8 +166,14 @@ func TestReissueForReenroll(t *testing.T) {
if !cs.Claimed() {
t.Fatal("re-issue must NEVER un-claim (reset rides rotation)")
}
if len(m.sends) != sendsBefore+1 || !strings.HasPrefix(m.sends[len(m.sends)-1], "reset:") {
t.Fatalf("claimed re-enroll must send exactly one RESET email, got %v", m.sends)
// R-295 hub half (2026-08-13): this used to assert "reset:". The KIND was split — same
// secret, same name („Beállító kód"), different SENTENCE — because a rebuilt box has no
// password, so it shows „A szerver beállítása" and serves no login page, and the reset mail
// sent the customer to an „Elfelejtett jelszó" page that is not on their screen. The
// assertion is deliberately kept STRICT rather than loosened to "either kind": routing a
// re-enrolment back down the reset copy is exactly the regression worth failing on.
if len(m.sends) != sendsBefore+1 || !strings.HasPrefix(m.sends[len(m.sends)-1], "reenroll:") {
t.Fatalf("claimed re-enroll must send exactly one REENROLL email, got %v", m.sends)
}
})
t.Run("unclaimed is a no-op (first-provision path)", func(t *testing.T) {
+167
View File
@@ -0,0 +1,167 @@
package claim
import (
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/notify"
"golang.org/x/crypto/bcrypt"
)
// ── R-295, HUB HALF — ONE NAME PER SECRET, AND THE MAIL NAMES THE PAGE THE MACHINE SHOWS ────────
//
// Two different secrets were both called „Visszaállító kód": the THREE-word code that gives a person
// control of the dashboard, and the TEN-word code that opens the sealed off-site backups. They are
// near-homographs of each other and of „Helyreállítási kód", and the collision cost a real code. The
// box side shipped on 2026-08-10; the hub side was dropped twice and was the last place the retired
// name survived — in the mails, which is the one surface a customer reads BEFORE they see any screen.
//
// The ruling these tests pin:
// - the three-word dashboard code is „Beállító kód" everywhere;
// - the ten-word escrow code is „Helyreállítási kód";
// - „Visszaállító kód" is RETIRED;
// - one secret in two situations keeps its NAME and changes its SENTENCE;
// - the mail names the page the machine is ACTUALLY SHOWING.
//
// THIS IS NAMING, NOT FUNCTION — TestReenrollSplit_ChangesTheMailNotTheSecret is the pin that says
// so. On the box side the companion pin is the controller's
// TestResetCode_StillAcceptedOnTheSetupPage (claim_code_naming_test.go).
const retiredName = "Visszaállító kód"
// The three code-bearing mails all name the secret „Beállító kód", and none of them carries the
// retired name. The escrow code's name must not appear in a dashboard-code mail either: naming both
// secrets in one message is how a customer comes to believe they are the same thing.
func TestFormatClaimEmail_OneNamePerSecret(t *testing.T) {
for _, kind := range []EmailKind{EmailClaim, EmailReset, EmailReenroll} {
subject, body := notify.FormatClaimEmail(string(kind), "c1", "example.hu", "alma-korte-szilva")
whole := subject + "\n" + body
if !strings.Contains(body, "Beállító kód: alma-korte-szilva") {
t.Errorf("%s: the secret must be labelled „Beállító kód”; body was:\n%s", kind, body)
}
if strings.Contains(whole, retiredName) {
t.Errorf("%s: the retired name „%s” is back (subject or body)", kind, retiredName)
}
if strings.Contains(whole, "Helyreállítási kód") {
t.Errorf("%s: names the ESCROW code in a dashboard-code mail — that is the collision", kind)
}
}
}
// The two situations that deliver the SAME secret must not name the SAME page, because the machine
// does not show the same page. A box that still has a password shows a login screen carrying an
// „Elfelejtett jelszó" link; a REBUILT box has no password, renders „A szerver beállítása" and serves
// no login page at all (controller web/claim.go:279 — `reset := s.authEnabled()`).
//
// Sending a re-enrolled customer to „Elfelejtett jelszó" names a route that is not on their screen.
// That was the live defect, and this is the test that would have caught it.
func TestFormatClaimEmail_NamesThePageTheMachineShows(t *testing.T) {
_, resetBody := notify.FormatClaimEmail(string(EmailReset), "c1", "example.hu", "a-b-c")
if !strings.Contains(resetBody, `"Elfelejtett jelszó"`) {
t.Errorf("the forgot-password mail should name the page that IS on that customer's screen:\n%s", resetBody)
}
_, reBody := notify.FormatClaimEmail(string(EmailReenroll), "c1", "example.hu", "a-b-c")
if strings.Contains(reBody, "Elfelejtett jelszó") {
t.Errorf("a REBUILT box serves no login page, so it has no „Elfelejtett jelszó” link:\n%s", reBody)
}
if !strings.Contains(reBody, `"A szerver beállítása"`) {
t.Errorf("the re-enrol mail must name the page a rebuilt box actually shows:\n%s", reBody)
}
}
// The re-enrol mail must promise nothing about the customer's apps or backups. A clean-slate
// reinstall is exactly the situation in which such a reassurance could be false, and this project has
// spent four register rows (R-294, R-299, R-302, R-311) removing promises it could not see were still
// true. Guarding the CLAIM rather than one phrasing of it: any sentence that says the backups are
// unaffected would have to say so with one of these stems.
func TestFormatClaimEmail_ReenrollPromisesNothingAboutTheData(t *testing.T) {
_, body := notify.FormatClaimEmail(string(EmailReenroll), "c1", "example.hu", "a-b-c")
for _, claimWord := range []string{"mentés", "biztonsági", "alkalmazás", "adataid", "visszaállíthat", "visszaszerezhet"} {
if strings.Contains(body, claimWord) {
t.Errorf("the re-enrol mail must not talk about data or backups (found %q):\n%s", claimWord, body)
}
}
}
// An unrecognised kind falls through to the first-setup mail. That default is SAFE (a rebuilt box
// really is in a setup state) and it is deliberately asserted, because it is the failure mode of
// adding a kind on the engine side and forgetting the template: nothing panics, nothing errors, and
// the wrong-but-harmless mail goes out. Pinning it means a future reader knows it was chosen.
func TestFormatClaimEmail_UnknownKindFallsBackToSetup(t *testing.T) {
subject, body := notify.FormatClaimEmail("no-such-kind", "c1", "example.hu", "a-b-c")
if !strings.Contains(subject, "Elindult a Felhom szervered") {
t.Errorf("unknown kind should fall back to the first-setup mail, got subject %q", subject)
}
if strings.Contains(subject+body, retiredName) {
t.Errorf("the fallback carries the retired name")
}
}
// THE ACCEPTANCE PIN. Splitting the mail KIND must not touch the secret: ReissueForReenroll still
// rotates the generation, still stores a bcrypt hash that verifies the emailed code, and still stores
// no plaintext — byte-for-byte the same custody as the reset path it was split out of. A rename that
// quietly broke acceptance would be far worse than the collision it fixes.
func TestReenrollSplit_ChangesTheMailNotTheSecret(t *testing.T) {
e, st, m := newTestEngine(t)
cc := cust()
if _, err := e.EnsureIssued(cc); err != nil {
t.Fatalf("EnsureIssued: %v", err)
}
if err := e.MarkClaimed(cc); err != nil {
t.Fatalf("MarkClaimed: %v", err)
}
before, err := st.GetClaim(cc.CustomerID)
if err != nil {
t.Fatalf("GetClaim: %v", err)
}
gen, reissued, err := e.ReissueForReenroll(cc)
if err != nil || !reissued {
t.Fatalf("ReissueForReenroll: gen=%d reissued=%v err=%v", gen, reissued, err)
}
// It is the re-enrol mail, not the reset mail — the whole point of the split.
last := m.sends[len(m.sends)-1]
if !strings.HasPrefix(last, string(EmailReenroll)+":") {
t.Errorf("a re-enrolled box must get the reenroll mail, got %q", last)
}
// …and the secret behaves exactly as before: generation advanced, the stored hash verifies the
// newly emailed code, the previous code no longer verifies, and no plaintext is persisted.
after, err := st.GetClaim(cc.CustomerID)
if err != nil {
t.Fatalf("GetClaim after: %v", err)
}
if after.Generation != before.Generation+1 {
t.Errorf("generation %d → %d, want +1", before.Generation, after.Generation)
}
if bcrypt.CompareHashAndPassword([]byte(after.CodeHash), []byte(m.lastCode)) != nil {
t.Error("the stored hash does not verify the emailed code — acceptance moved")
}
if strings.Contains(after.CodeHash, m.lastCode) {
t.Error("the stored hash contains the plaintext code")
}
}
// RequestReset — a customer who still has a password — must keep sending the RESET mail. The split
// must not have swept the genuine forgot-password path along with it: that customer IS looking at a
// login page, and „Elfelejtett jelszó" is the right thing to name for them.
func TestRequestReset_StillSendsTheResetMail(t *testing.T) {
e, _, m := newTestEngine(t)
cc := cust()
if _, err := e.EnsureIssued(cc); err != nil {
t.Fatalf("EnsureIssued: %v", err)
}
if err := e.MarkClaimed(cc); err != nil {
t.Fatalf("MarkClaimed: %v", err)
}
if err := e.RequestReset(cc); err != nil {
t.Fatalf("RequestReset: %v", err)
}
last := m.sends[len(m.sends)-1]
if !strings.HasPrefix(last, string(EmailReset)+":") {
t.Errorf("a forgot-password request must still send the reset mail, got %q", last)
}
}
+19 -2
View File
@@ -324,6 +324,16 @@ func init() {
//
// Customers whose nodes are "down" (no report in >1h) are skipped — they
// already have staleness events.
// stalenessState reads a customer's staleness state, tolerating a nil checker. Named rather than
// inlined so the two states this function skips on are visible in one place — a second caller adding
// a third state must not have to rediscover that the nil check exists.
func stalenessState(staleness *StalenessChecker, customerID string) string {
if staleness == nil {
return ""
}
return staleness.GetState(customerID)
}
func CheckBackupDeadlines(s *store.Store, staleness *StalenessChecker, onEvent EventNotifyFunc, logger *log.Logger) {
customerIDs, err := s.GetActiveCustomerIDs()
if err != nil {
@@ -339,8 +349,15 @@ func CheckBackupDeadlines(s *store.Store, staleness *StalenessChecker, onEvent E
var backupMissed, dbdumpMissed, skipped, deferred, unbound int
for _, id := range customerIDs {
// Skip nodes that are down — they already have staleness events
if staleness != nil && staleness.GetState(id) == "down" {
// Skip nodes that are down — they already have staleness events.
//
// R-321 adds StateDisabled to the same skip, and it is NOT cosmetic. This check is a second
// door onto the same false alarm: a box whose reporting we switched off deliberately is not
// "down" (the staleness checker suppresses that), so without this it would keep e-mailing
// `expected_backup_missed` / `expected_dbdump_missed` every morning about a machine we asked
// to be quiet. That is R-195's shape exactly — a skip keyed off the wrong fact missing the
// customer it would most obviously cover — which is why both doors are closed together.
if st := stalenessState(staleness, id); st == "down" || st == StateDisabled {
skipped++
continue
}
+57 -2
View File
@@ -3,6 +3,7 @@ package monitor
import (
"fmt"
"log"
"strings"
"sync"
"time"
@@ -13,6 +14,27 @@ import (
// to trigger notification dispatch. Keeps monitor decoupled from notify.
type EventNotifyFunc func(customerID, eventType, severity, message, detailsJSON, source string)
// StateDisabled is the state of a customer whose box we DELIBERATELY told to stop reporting.
//
// R-321. Switching a box's hub reporting off is a supported product state: the controller sends one
// final report carrying health.status = "disabled" (felhom-controller
// `cmd/controller/main.go:1253`) and then goes quiet BY DESIGN. That status is parsed and persisted
// (`store.go:953-955` → `reports.health_status`), reaches this checker on every pass inside
// `CustomerSummary.HealthStatus` (`store.go:40`), and the operator roll-up already renders such a
// customer as `disabled` (`web/rollup.go:25`).
//
// This checker ignored it and measured only the AGE of the last report — so a machine we asked to be
// quiet went stale at 30 minutes, down at 60, and e-mailed the operator twice about an outage we
// caused on purpose. That is the false alarm that teaches people to ignore the channel, and it is a
// sibling of R-195, where the guard that should have covered a customer was keyed off the wrong fact.
//
// It is a STATE here rather than a deletion (the `blocked` branch below deletes) because other
// checkers consult GetState: CheckBackupDeadlines skips a customer that is "down", and a DELETED
// state returns "" — which is not "down", so a disabled box would have gone on alarming
// `expected_backup_missed` from a different function. Exactly the R-195 shape returning through a
// second door.
const StateDisabled = "disabled"
// StalenessChecker monitors customer report freshness and generates
// node_stale / node_down / node_recovered events on state transitions.
type StalenessChecker struct {
@@ -94,6 +116,27 @@ func (sc *StalenessChecker) Check() {
continue
}
// R-321 — a machine we told to be quiet is not a machine that died.
//
// The age-based transition is skipped entirely; no stale, no down, no e-mail. The state is
// RECORDED rather than deleted so the deliberate silence is visible to anything that asks
// (see StateDisabled). `downtimeStart` is cleared on ENTRY so that a later, genuine outage
// cannot compute its duration from a clock that started before we asked for the silence.
//
// The discriminator is the box's OWN last word, not an inference: it said `disabled` on the
// way out. LIMIT, stated rather than hidden: if reporting is re-enabled and the box then
// fails to report at all, the hub still sees only that final `disabled` report and keeps
// suppressing. The hub cannot distinguish that from "still switched off" — its view changes
// only when a report arrives. This is why the state is made VISIBLE: an operator who
// re-enabled a box and still sees `disabled` is being told it has not come back.
if strings.EqualFold(c.HealthStatus, StateDisabled) {
if sc.states[c.CustomerID] != StateDisabled {
sc.states[c.CustomerID] = StateDisabled
delete(sc.downtimeStart, c.CustomerID)
}
continue
}
age := time.Since(c.ReceivedAt)
var newState string
switch {
@@ -106,8 +149,20 @@ func (sc *StalenessChecker) Check() {
}
oldState := sc.states[c.CustomerID]
if oldState == "" {
// New customer — set state without event
if oldState == "" || oldState == StateDisabled {
// New customer — set state without event.
//
// R-321 adds the re-enabled case to the SAME branch, deliberately. A box coming back
// from a deliberate silence is a first observation, not a recovery: emitting here would
// send `node_recovered` for an outage that never happened — and would do it every time
// an operator switched reporting back on.
//
// THE RE-ENABLEMENT CLOCK, and this is the judgement: it is the age of the report the
// hub can actually see. For a box that reports on re-enabling, that report IS the
// re-enablement, so the clock starts there and scenario D (re-enabled, then genuinely
// quiet) alarms on a normal schedule timed from the moment it came back. Timing from the
// last report BEFORE the switch-off would fire an instant stale/down for a quiet period
// we asked for — a false alarm produced by fixing false alarms.
sc.states[c.CustomerID] = newState
continue
}
@@ -0,0 +1,226 @@
package monitor
// R-321 — A MACHINE WE TOLD TO BE QUIET IS NOT A MACHINE THAT DIED.
//
// Switching a box's hub reporting off is a supported product state. The controller sends one final
// report carrying health.status = "disabled" and then goes quiet BY DESIGN. The hub parses and stores
// that status, and the operator roll-up already renders such a customer as `disabled` — but this
// checker measured only the AGE of the last report, so the machine went stale at 30 minutes, down at
// 60, and e-mailed the operator twice about an outage we caused on purpose.
//
// The discriminator is the box's OWN last word, not an inference, and it is in the data this checker
// already reads (`CustomerSummary.HealthStatus`). Suppressing on a guess would be worse than the
// false alarm it removes; suppressing on the machine's own declaration is not a guess.
import (
"database/sql"
"io"
"log"
"path/filepath"
"testing"
"time"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
_ "modernc.org/sqlite"
)
// seedStalenessCustomer creates a store with one customer and one report of the given health status,
// backdated by `age`. Reports are inserted through the REAL SaveReport path so the health_status
// denormalization is exercised rather than hand-set — a test that writes the column directly cannot
// see a decode that never happens, which is the R-260 class.
func seedStalenessCustomer(t *testing.T, health string, age time.Duration) (*store.Store, string) {
t.Helper()
path := filepath.Join(t.TempDir(), "t.db")
st, err := store.New(path, log.New(io.Discard, "", 0))
if err != nil {
t.Fatalf("store.New: %v", err)
}
t.Cleanup(func() { st.Close() })
if err := st.SaveCustomerConfig(&store.CustomerConfig{
CustomerID: "c1", APIKey: "ck", RetrievalPassword: "p", Status: "active",
}); err != nil {
t.Fatalf("SaveCustomerConfig: %v", err)
}
saveReportAged(t, st, path, health, age)
return st, path
}
// saveReportAged saves a controller report with the given health status and then backdates its
// received_at. The backdate is a raw UPDATE because there is no production path that writes an old
// timestamp — which is exactly why it is confined to this one helper.
func saveReportAged(t *testing.T, st *store.Store, path, health string, age time.Duration) {
t.Helper()
body := `{"customer_id":"c1","health":{"status":"` + health + `"}}`
if err := st.SaveReport("c1", []byte(body)); err != nil {
t.Fatalf("SaveReport: %v", err)
}
if age <= 0 {
return
}
// Backdating goes through a SECOND connection to the same file rather than a test-only method on
// Store. No production path writes an old received_at, so exposing one would widen the store's
// API for a fixture — and this keeps the ageing confined to the one helper that needs it.
db, err := sql.Open("sqlite", path)
if err != nil {
t.Fatalf("open for backdate: %v", err)
}
defer db.Close()
// GetCustomers selects the report with MAX(received_at), not MAX(id) — so backdating only the
// row just written would leave an EARLIER, fresher-looking row as the one the checker reads, and
// the fixture would quietly test the wrong report. (Found by two scenarios failing on a change
// that was correct: the instrument was wrong, not the code.) Every older row is therefore pushed
// further back, so the row just written is unambiguously the latest.
when := time.Now().UTC().Add(-age)
newest := when.Format("2006-01-02 15:04:05")
older := when.Add(-time.Hour).Format("2006-01-02 15:04:05")
if _, err := db.Exec(
`UPDATE reports SET received_at = ? WHERE customer_id = 'c1' AND id < (SELECT MAX(id) FROM reports WHERE customer_id = 'c1')`,
older); err != nil {
t.Fatalf("age the older rows: %v", err)
}
if _, err := db.Exec(
`UPDATE reports SET received_at = ? WHERE id = (SELECT MAX(id) FROM reports WHERE customer_id = 'c1')`,
newest); err != nil {
t.Fatalf("backdate: %v", err)
}
}
// newChecker builds a checker with a 30-minute threshold and records every event it emits.
func newChecker(t *testing.T, st *store.Store) (*StalenessChecker, *[]string) {
t.Helper()
var events []string
sc := NewStalenessChecker(st, 30*time.Minute, func(cid, et, sev, msg, det, src string) {
events = append(events, et)
}, log.New(io.Discard, "", 0))
return sc, &events
}
// ── A — a machine deliberately silent for days ──────────────────────────────────────────────────
//
// WRONG OUTCOME GUARDED: stale, then down, then two e-mails — which is what happens today.
func TestStaleness_A_DeliberatelySilentDoesNotAlarm(t *testing.T) {
// The box is HEALTHY and OBSERVED first, then switched off. Seeding it already-disabled would
// make this test pass vacuously: the checker's new-customer branch sets the first state without
// an event, so a customer that was never seen healthy can never emit a transition — and the
// assertion below would hold even with the suppression deleted. (Confirmed: it did. The
// red-proof for this scenario passed on the state assertion alone until this line was added.)
st, path := seedStalenessCustomer(t, "ok", 0)
sc, events := newChecker(t, st)
sc.Check()
if sc.GetState("c1") != "ok" {
t.Fatalf("setup: the machine should start observed-healthy, got %q", sc.GetState("c1"))
}
// Reporting is switched off. The box says so on the way out, then goes quiet for three days.
saveReportAged(t, st, path, "disabled", 72*time.Hour)
for i := 0; i < 3; i++ { // several passes: a suppression that only holds once is not a suppression
sc.Check()
}
if len(*events) != 0 {
t.Fatalf("a deliberately-disabled machine emitted %v — three days quiet BY REQUEST", *events)
}
// The silence must be VISIBLE, not merely un-alarmed: quiet-on-purpose and quiet-by-accident
// must not look identical, and an unknown is never drawn as healthy.
if got := sc.GetState("c1"); got != StateDisabled {
t.Errorf("state = %q, want %q — the deliberate silence is invisible", got, StateDisabled)
}
}
// ── B — a machine that simply stopped reporting ─────────────────────────────────────────────────
//
// WRONG OUTCOME GUARDED: silence mistaken for a deliberate switch-off — THE ALARM THAT MATTERS,
// suppressed. This is the one that must never regress.
func TestStaleness_B_GenuineSilenceStillAlarms(t *testing.T) {
st, path := seedStalenessCustomer(t, "ok", 0)
sc, events := newChecker(t, st)
sc.Check() // first observation: healthy, no event
// The same customer now goes quiet for real.
saveReportAged(t, st, path, "ok", 3*time.Hour)
sc.Check()
if got := sc.GetState("c1"); got != "down" {
t.Fatalf("a genuinely silent machine is %q, want \"down\" — a real alarm was swallowed", got)
}
if len(*events) == 0 {
t.Fatal("a genuinely silent machine emitted NO event — the suppression is over-broad")
}
}
// ── C — a disabled machine that is re-enabled and reports promptly ──────────────────────────────
//
// WRONG OUTCOME GUARDED: a recovery e-mail for an outage that never happened.
func TestStaleness_C_ReEnabledReportsCleanlyWithNoRecoveryEvent(t *testing.T) {
st, path := seedStalenessCustomer(t, "disabled", 72*time.Hour)
sc, events := newChecker(t, st)
sc.Check()
if sc.GetState("c1") != StateDisabled {
t.Fatalf("setup: expected the disabled state, got %q", sc.GetState("c1"))
}
// Reporting is switched back on and the box reports immediately.
saveReportAged(t, st, path, "ok", 0)
sc.Check()
if len(*events) != 0 {
t.Fatalf("re-enabling emitted %v — there was no outage to recover from", *events)
}
if got := sc.GetState("c1"); got != "ok" {
t.Errorf("state after re-enable = %q, want \"ok\"", got)
}
}
// ── D — a disabled machine that is re-enabled and then goes genuinely quiet ─────────────────────
//
// WRONG OUTCOME GUARDED: permanently silenced because it was once disabled. Also pins THE CLOCK
// JUDGEMENT: timing runs from the report the box sent on re-enabling, not from the last report
// before the switch-off — otherwise coming back would fire an instant false alarm about a quiet
// period we asked for.
func TestStaleness_D_ReEnabledThenQuietAlarmsFromReEnablement(t *testing.T) {
st, path := seedStalenessCustomer(t, "disabled", 72*time.Hour)
sc, events := newChecker(t, st)
sc.Check()
// Re-enabled, reports promptly — the clean first observation.
saveReportAged(t, st, path, "ok", 0)
sc.Check()
if len(*events) != 0 {
t.Fatalf("the re-enable itself emitted %v", *events)
}
// …and then goes quiet for real. Timed from THIS report, it is down.
saveReportAged(t, st, path, "ok", 3*time.Hour)
sc.Check()
if got := sc.GetState("c1"); got != "down" {
t.Fatalf("state = %q, want \"down\" — a once-disabled machine was silenced for ever", got)
}
if len(*events) == 0 {
t.Fatal("no event for a machine that genuinely died after being re-enabled")
}
}
// The deadline check is a SECOND DOOR onto the same false alarm: a disabled box is not "down", so
// without its own skip it would keep e-mailing expected_backup_missed every morning. R-195's shape,
// which is why both doors are closed together.
func TestStaleness_DisabledIsAlsoSkippedByTheDeadlineCheck(t *testing.T) {
st, _ := seedStalenessCustomer(t, "disabled", 72*time.Hour)
if err := st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "k1"}); err != nil {
t.Fatalf("UpsertHost: %v", err)
}
sc, _ := newChecker(t, st)
sc.Check()
var events []string
CheckBackupDeadlines(st, sc, func(cid, et, sev, msg, det, src string) {
events = append(events, et)
}, log.New(io.Discard, "", 0))
for _, e := range events {
if e == "expected_backup_missed" || e == "expected_dbdump_missed" {
t.Fatalf("the deadline check alarmed on a deliberately-disabled machine: %v", events)
}
}
}
+70 -6
View File
@@ -69,7 +69,7 @@ Message: %s`, customerID, eventType, severity, now, message)
// customerMessages maps event_type → Hungarian customer message.
var customerMessages = map[string]string{
// Customer-claim arc (v0.50.0)
"claim_lockout": "Túl sok hibás beállító/visszaállító kód próbálkozás történt — a beállító oldal 15 percre zárolva lett. Ha nem te próbálkoztál, jelezd az üzemeltetőnek.",
"claim_lockout": "Túl sok hibás beállító kód próbálkozás történt — a beállító oldal 15 percre zárolva lett. Ha nem te próbálkoztál, jelezd az üzemeltetőnek.",
// Backup events
"backup_completed": "A biztonsági mentés sikeresen elkészült.",
"backup_failed": "A biztonsági mentés sikertelen! Kérjük, ellenőrizd a rendszert.",
@@ -207,18 +207,38 @@ Felhom.eu monitoring`
// ──────────────────────────────────────────────────────────────────────
// FormatClaimEmail returns (subject, textBody) for a claim-arc email. kind is one of
// "claim" | "reset" | "claimed" (claim.EmailKind values). The code appears ONLY in the
// "claim" | "reset" | "reenroll" | "claimed" (claim.EmailKind values). The code appears ONLY in the
// returned body — callers must never log it.
//
// R-295, HUB HALF (2026-08-13). ONE NAME PER SECRET, and it is „Beállító kód".
//
// The three-word code that gives a person control of the DASHBOARD is „Beállító kód" everywhere —
// in this mail, on the operator button, and on the box's own page, which has said so since the
// controller half shipped on 2026-08-10. The TEN-word code that opens the sealed backups is
// „Helyreállítási kód" and is a different secret entirely. **„Visszaállító kód" is retired**: it was
// a near-homograph of „Helyreállítási kód", the collision cost a real code, and the hub was the last
// place it survived — this half was dropped twice before it was finished.
//
// The rule the three branches below follow: ONE SECRET IN TWO SITUATIONS KEEPS ITS NAME, AND THE
// SENTENCE AROUND IT CHANGES. „reset" and „reenroll" carry the identical secret under the identical
// name; they differ only in which page the customer will actually be looking at.
//
// THIS IS NAMING, NOT FUNCTION. No acceptance logic moved: the code is minted, hashed, rotated,
// capped and consumed exactly as before, and a reset code is still accepted on the setup page.
// Pinned by TestFormatClaimEmail_OneNamePerSecret and, on the box side, by the controller's
// claim_code_naming_test.go.
func FormatClaimEmail(kind, customerID, domain, code string) (string, string) {
dashboardURL := "https://felhom." + domain
switch kind {
case "reset":
subject := "[Felhom] Jelszó-visszaállítási kód"
// The box HAS a password: the customer asked for a reset from the login screen, so the
// „Elfelejtett jelszó" link IS on their screen and naming it is correct here.
subject := "[Felhom] Beállító kód a jelszavad visszaállításához"
body := fmt.Sprintf(`Kedves Ügyfél!
Jelszó-visszaállítást kértél a Felhom vezérlőpultodhoz.
Visszaállító kód: %s
Beállító kód: %s
A kód 72 óráig érvényes, és egyszer használható fel. Add meg a vezérlőpult
"Elfelejtett jelszó" oldalán, majd válassz új jelszót:
@@ -227,6 +247,32 @@ A kód 72 óráig érvényes, és egyszer használható fel. Add meg a vezérlő
Ha nem te kérted, hagyd figyelmen kívül a jelenlegi jelszavad változatlan.
Üdvözlettel,
Felhom.eu`, code, dashboardURL)
return subject, body
case "reenroll":
// The box was rebuilt and has NO password, so it shows „A szerver beállítása" and serves no
// login page — there is no „Elfelejtett jelszó" link to send anyone to. Same secret, same
// name, the page the machine is actually showing.
//
// It deliberately says NOTHING about the apps or the backups. A clean-slate reinstall is
// exactly the situation in which such a reassurance could be false, and this project has
// spent four register rows removing promises it could not see were still true.
subject := "[Felhom] Új beállító kód — újratelepült a szervered"
body := fmt.Sprintf(`Kedves Ügyfél!
A Felhom szervered újratelepült, ezért a vezérlőpultod belépését újra be kell
állítani. A korábbi jelszavad már nem érvényes.
Beállító kód: %s
A kód 72 óráig érvényes, és egyszer használható fel. Nyisd meg a vezérlőpultot
"A szerver beállítása" oldal fogad , add meg a kódot, majd válassz új jelszót:
%s
Ha nem te telepítetted újra a szervered, vedd fel a kapcsolatot az üzemeltetővel.
Üdvözlettel,
Felhom.eu`, code, dashboardURL)
return subject, body
@@ -270,6 +316,24 @@ Felhom.eu`, code, dashboardURL)
// link (v0.66.0, R-27 slice 1). The link is the ONLY secret here — the passphrase is never in the
// mail (the customer already holds it), and the console pairing code is read off the box screen. The
// copy tells the customer they will need both factors on the page. Adult tone, no emoji.
//
// R-323, THE THIRD NAME (2026-08-13). This mail used to call the five-word phrase „visszaállító
// jelszó" — one word away from „Visszaállító kód", which had just been retired for colliding with
// „Helyreállítási kód". It is „Tulajdonosi jelmondat" now. Two reasons, and the second is the one
// that rules out the obvious alternatives:
//
// 1. THE OLD NAME WAS FALSE. The phrase restores nothing. It proves the account owns the box being
// linked — so the name says that.
// 2. IT MUST NOT COLLIDE WITH THE OTHER FACTOR ON THE SAME PAGE. Item 1 below is the „Párosító
// kód". Naming this one after the same act („Összekötési jelszó") would leave the two factors a
// customer types in one sitting distinguished only by kód-versus-jelszó — which is EXACTLY the
// „Visszaállító kód" / „Visszaállító jelszó" shape being removed. „Fiókjelszó" is worse still:
// there IS an account password (the dashboard login), so it would collide with a different real
// secret. „Tulajdonosi jelmondat" is distinct from all three on BOTH axes — the stem
// (Tulajdonosi vs Beállító / Helyreállítási / Párosító) and the noun (jelmondat vs kód / jelszó).
//
// Naming only: no acceptance logic moved, and the same phrase is still accepted. Pinned by
// TestSelfBind_ThirdSecretNaming and TestSelfBindPassphrase_StillAcceptedAfterTheRename.
func FormatSelfBindEmail(customerID, link string) (string, string) {
subject := "[Felhom] Kösd össze a Felhom dobozodat"
body := fmt.Sprintf(`Kedves Ügyfél!
@@ -282,8 +346,8 @@ tudod te magad összekötni a fiókoddal — nincs szükség bejelentkezésre:
A hivatkozás megnyitása után két adatot kell megadnod:
1. A párosító kódot, amely a doboz képernyőjén (a monitoron) látható.
2. A visszaállító jelszavadat (az 5 szóból álló kifejezést), amelyet a
beállításkor kaptál.
2. A tulajdonosi jelmondatodat (az 5 szóból álló kifejezést), amelyet a
beállításkor kaptál. Ez igazolja, hogy a fiók a tiéd.
A hivatkozás 7 napig érvényes. Biztonsági okból 5 sikertelen próbálkozás után
zárolódik ilyenkor vedd fel a kapcsolatot az ügyfélszolgálattal.
+7 -2
View File
@@ -75,8 +75,13 @@ func TestCustomerPage_ClaimCardStates(t *testing.T) {
if !strings.Contains(html, ">Claimed ") {
t.Error("missing the Claimed chip")
}
if !strings.Contains(html, "Visszaállító kód küldése") {
t.Error("claimed state should offer the reset-code button")
// R-295: the button says „Beállító kód küldése" — ONE name per secret. It used to read
// „Visszaállító kód küldése", a near-homograph of the escrow „Helyreállítási kód".
if !strings.Contains(html, "Beállító kód küldése") {
t.Error("claimed state should offer the reset-code button, named „Beállító kód küldése”")
}
if strings.Contains(html, "Visszaállító kód") {
t.Error("the retired name „Visszaállító kód” is back on the customer page")
}
}
+3 -2
View File
@@ -776,8 +776,9 @@ func (s *Server) handleConfigUpdate(w http.ResponseWriter, r *http.Request, cust
}
// handleClaimResend (v0.50.0, customer-claim arc) rotates the claim/reset code and re-sends it to
// the REGISTERED customer address — the operator "Kód újraküldése" / "Visszaállító kód küldése"
// button. The old code stops verifying immediately (single active code); the fresh hash reaches
// the REGISTERED customer address — the operator "Kód újraküldése" / "Beállító kód küldése"
// button (R-295: the button used to read „Visszaállító kód küldése"; that name is retired).
// The old code stops verifying immediately (single active code); the fresh hash reaches
// the box on its next report ACK (no config bump needed). No plaintext is ever rendered or logged.
func (s *Server) handleClaimResend(w http.ResponseWriter, r *http.Request, customerID string) {
if s.claimEngine == nil {
+173
View File
@@ -263,6 +263,175 @@ func (s *Server) hostNetwork(host *store.Host, reportJSON string) hostNetworkVie
return v
}
// ── R-319 — the guest-network watchdog gets a reader ────────────────────────────────────────────
//
// FIRST OF THE R-264 READERS. The agent has reported `guest_net` on every heartbeat since v0.92.0
// (R-54, 2026-07-21) and the hub — the component that emails the operator — modelled NONE of it: the
// string `guest_net` occurred nowhere in this repository. It was stored as raw text inside
// `report_json` and read by nothing.
//
// WHY THIS ONE FIRST. It has a live incident behind it: a killed `dhclient` in a guest took a tunnel
// down for 1 h 15 m with nobody told (`audits/INCIDENT-guest-dhclient-killed-2026-07-20.md`), and the
// watchdog built afterwards has been reporting exactly that condition ever since — to a hub that
// modelled none of it. R-264 named it "the strongest candidate of the twenty-one".
//
// WHAT IS ACTUALLY BEING READ, and it is NOT just the current state. The signal is
// `heals_last_hour`: a guest whose network the watchdog keeps REPAIRING is healthy at every instant
// anyone looks and is nevertheless failing. Rendering only `state` would give that machine a green
// tick — which is the exact shape of the defect that drew a failed disk as a healthy empty disk.
// RepairCount is therefore surfaced beside the state, not behind it.
//
// AN UNKNOWN IS NEVER DRAWN AS HEALTHY. Three distinct absences are kept apart, following the
// companion-flag convention this project settled in August (`hostNetworkView` above is the sibling):
// - AgentTooOld — the agent predates v0.92.0, so absence is EXPECTED but still tells us nothing;
// - Reported=false — a new-enough agent sent no stanza (watchdog disabled, or a report that
// predates the feature on this box);
// - a guest whose `state` is empty or unrecognised → rendered "unknown", never healthy.
// A malformed stanza degrades to Reported=false and must never 500 the page.
// minAgentForGuestNet is the agent release that first reported `guest_net` (v0.92.0, R-54). Below it
// the stanza is absent BY CONSTRUCTION, so its absence is unknown-and-expected rather than a finding.
const minAgentForGuestNet = "0.92.0"
// guestNetGuestView is one owned guest's network health as the watchdog last saw it.
type guestNetGuestView struct {
VMID int
State string // healthy | unhealthy | static_fault | unknown ("" → unknown)
Mode string // dhcp | static | unknown
IP string
HasRoute bool
DHClientAlive bool
// RepairCount is `heals_last_hour` — THE SIGNAL. A machine repairing itself over and over is
// telling you something that its current state cannot.
RepairCount int
LastHealAt string
Damped bool
Message string
// Healed / HealSucceeded are carried DELIBERATELY, and together. R-260 is this project's
// warning: the OOB decoder mirrored five of the agent's eight fields, and the three it dropped
// included the one that decided the question the checker existed to answer. A repair that FAILED
// is a different fact from a repair that worked, and the count alone cannot express it — six
// successful repairs is a nuisance, six FAILED ones is a guest that is down right now.
Healed bool
HealSucceeded bool
}
// HealFailed reports that the watchdog TRIED to repair this guest and did not succeed. Kept separate
// from State because the two can disagree: the sweep that failed to heal is not necessarily the sweep
// that set the state.
func (g guestNetGuestView) HealFailed() bool { return g.Healed && !g.HealSucceeded }
// Unknown reports whether this guest's state is one the watchdog did not positively assert. An empty
// or unrecognised state is drawn as unknown; it is never allowed to fall through to the healthy
// branch. Value receiver — a pointer receiver compiles, vets, passes the suite and 500s at render.
func (g guestNetGuestView) Unknown() bool {
switch g.State {
case "healthy", "unhealthy", "static_fault":
return false
default:
return true
}
}
// Repairing reports whether the watchdog has had to repair this guest inside the last hour. This is
// deliberately independent of State: the whole point is that a REPAIRED guest reads healthy.
func (g guestNetGuestView) Repairing() bool { return g.RepairCount > 0 }
// guestNetView is the Guest network card's whole view-model.
type guestNetView struct {
// Reported is the ONLY thing that licenses drawing any health at all. False → unknown.
Reported bool
// AgentTooOld distinguishes "this agent cannot report it" from "a capable agent said nothing".
// Both render as unknown; they need different words, and conflating them is how an operator
// starts ignoring the card.
AgentTooOld bool
CheckedAt string
Guests []guestNetGuestView
// RepairingCount / UnhealthyCount drive the card's summary badge. Counted rather than derived in
// the template: template logic that computes a verdict is logic nobody tests.
RepairingCount int
UnhealthyCount int
UnknownCount int
// HealFailedCount — repairs ATTEMPTED and not succeeded. Counted separately from
// RepairingCount: a failing repair is a harder fact than a frequent one.
HealFailedCount int
}
// Degraded is true when any owned guest is unhealthy OR is being repeatedly repaired. The repair leg
// is the one that matters: without it a guest the watchdog fixes every ten minutes reports "healthy"
// for ever.
func (v guestNetView) Degraded() bool {
return v.UnhealthyCount > 0 || v.RepairingCount > 0 || v.HealFailedCount > 0
}
// parseGuestNet extracts the `guest_net` stanza. A missing or malformed body yields Reported=false
// (unknown), never an error and never a partial claim of health.
func parseGuestNet(reportJSON string) guestNetView {
v := guestNetView{Guests: []guestNetGuestView{}}
if reportJSON == "" {
return v
}
var body struct {
GuestNet *struct {
CheckedAt string `json:"checked_at"`
Guests []struct {
VMID int `json:"vmid"`
State string `json:"state"`
Mode string `json:"mode"`
IP string `json:"ip"`
HasRoute bool `json:"has_route"`
DHClientAlive bool `json:"dhclient_alive"`
HealsLastHour int `json:"heals_last_hour"`
Healed bool `json:"healed"`
HealSucceeded bool `json:"heal_succeeded"`
LastHealAt string `json:"last_heal_at"`
Damped bool `json:"damped"`
Message string `json:"message"`
} `json:"guests"`
} `json:"guest_net"`
}
// A decode error is NOT propagated: one box's bad field must not break the page for the rest of
// the fleet. It degrades to Reported=false, which renders as unknown — the safe direction.
if err := json.Unmarshal([]byte(reportJSON), &body); err != nil || body.GuestNet == nil {
return v
}
v.Reported = true
v.CheckedAt = body.GuestNet.CheckedAt
for _, g := range body.GuestNet.Guests {
gv := guestNetGuestView{
VMID: g.VMID, State: g.State, Mode: g.Mode, IP: g.IP,
HasRoute: g.HasRoute, DHClientAlive: g.DHClientAlive,
RepairCount: g.HealsLastHour, LastHealAt: g.LastHealAt,
Damped: g.Damped, Message: g.Message,
Healed: g.Healed, HealSucceeded: g.HealSucceeded,
}
switch {
case gv.Unknown():
v.UnknownCount++
case gv.State == "unhealthy" || gv.State == "static_fault":
v.UnhealthyCount++
}
if gv.Repairing() {
v.RepairingCount++
}
if gv.HealFailed() {
v.HealFailedCount++
}
v.Guests = append(v.Guests, gv)
}
return v
}
// guestNet builds the card's view-model, applying the version gate before the report is consulted so
// that an old agent's silence is named as such rather than rendered as a finding.
func (s *Server) guestNet(host *store.Host, reportJSON string) guestNetView {
if host.AgentVersion != "" && semver.Valid(host.AgentVersion) &&
semver.Compare(host.AgentVersion, minAgentForGuestNet) < 0 {
return guestNetView{Guests: []guestNetGuestView{}, AgentTooOld: true}
}
return parseGuestNet(reportJSON)
}
// storageTargetView is the rich per-drive row the host-detail Storage Targets table renders:
// fill %, role/state, thin-pool, and SMART health/temp/wear. Parsed from the latest report's
// storage_targets[] (the full hostStorageTarget wire shape lives in the api package; this view
@@ -533,6 +702,9 @@ func (s *Server) hostDetailData(host *store.Host, r *http.Request) map[string]in
// v0.85.0 Network — the host's addresses + its WireGuard allocation.
network := s.hostNetwork(host, reportJSON)
// R-319 Guest network — the R-54 watchdog's per-guest verdict AND its repair count.
guestNet := s.guestNet(host, reportJSON)
// v0.84.0 Console access — presence + username + set_at ONLY. GetHostRecoveryMeta cannot carry
// the secret (its query does not select the column); the plaintext reaches the operator solely
// through POST /hosts/{id}/reveal-recovery-credential.
@@ -573,6 +745,7 @@ func (s *Server) hostDetailData(host *store.Host, r *http.Request) map[string]in
// v0.84.0 break-glass Console access card. NEVER add a key holding the secret.
// v0.85.0 Network card (addresses + WireGuard allocation/confirmation).
"Network": network,
"GuestNet": guestNet,
"RecoveryVaulted": recoveryMeta != nil,
"RecoveryUsername": func() string {
if recoveryMeta != nil {
+259
View File
@@ -0,0 +1,259 @@
package web
// Guest-network card (R-319) — the first R-264 reader.
//
// THE FIXTURES BELOW ARE THE REAL WIRE. `liveGuestNetJSON` is the `guest_net` stanza copied verbatim
// out of `demo-felhom-8363b5`'s newest row in the live hub's `host_reports` table on 2026-08-13
// (agent 0.129.0). Testing against a hand-written shape would have proved only that the parser
// matches my own idea of the format — and the defect this whole class comes from (R-260) was exactly
// a hub-side struct that did not match what the agent actually sends.
//
// Every test drives ServeHTTP, so a template gate that never renders is visible here. That is the
// seam-wiring rule: handler tests prove nothing about reachability, and this project has shipped four
// features whose entry point was never wired.
import (
"strings"
"testing"
)
// The real stanza: one owned guest, healthy, DHCP, no repairs. `heals_last_hour` and friends are
// ABSENT because the agent marks them `omitempty` and this box has never needed a repair — which is
// itself the shape scenario A has to survive.
const liveGuestNetJSON = `{
"host": {"cpu_percent": 4.0, "memory_percent": 30.0, "disk_percent": 20.0},
"guest_net": {
"checked_at": "2026-08-13T08:15:33Z",
"guests": [
{"vmid": 9201, "state": "healthy", "mode": "dhcp", "ip": "192.168.0.149",
"has_route": true, "dhclient_alive": true, "checked_at": "2026-08-13T08:14:30Z",
"message": "address, default route and dhclient all present"}
]
}
}`
// The same box after the watchdog has had to keep fixing it — the R-54 heal fields populated. This is
// the shape the incident of 2026-07-20 would have produced had the watchdog existed then.
const repairingGuestNetJSON = `{
"host": {"cpu_percent": 4.0, "memory_percent": 30.0, "disk_percent": 20.0},
"guest_net": {
"checked_at": "2026-08-13T08:15:33Z",
"guests": [
{"vmid": 9201, "state": "healthy", "mode": "dhcp", "ip": "192.168.0.149",
"has_route": true, "dhclient_alive": true, "checked_at": "2026-08-13T08:14:30Z",
"healed": true, "heal_succeeded": true, "last_heal_at": "2026-08-13T08:09:12Z",
"heals_last_hour": 6, "message": "dhclient was absent; restarted"}
]
}
}`
// A report from a capable agent with NO guest_net stanza at all — the watchdog switched off, or a
// report predating the feature on this box.
const silentGuestNetJSON = `{"host": {"cpu_percent": 4.0, "memory_percent": 30.0, "disk_percent": 20.0}}`
// The stanza arrives, but its contents are not what the hub expects: `guests` is an object where an
// array belongs, and `heals_last_hour` is a string. This is what a wire drift or a truncated write
// looks like from the hub's side.
const malformedGuestNetJSON = `{
"host": {"cpu_percent": 4.0},
"guest_net": {"checked_at": "2026-08-13T08:15:33Z", "guests": {"vmid": "nine-two-oh-one"}}
}`
// ── A — a machine reporting healthy guest networking, no repairs → shown as healthy ─────────────
//
// WRONG OUTCOME GUARDED: an empty or alarming state on a machine that is fine. A card that cried
// unknown on every healthy box would be switched off within a week, and then the B case below would
// never be seen either.
func TestGuestNet_A_HealthyRendersHealthy(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
seedNetHost(t, st, "demo-felhom-8363b5", "0.129.0", liveGuestNetJSON, "")
body := getHostPage(t, s, cookie, "demo-felhom-8363b5")
if !strings.Contains(body, "Guest network") {
t.Fatal("no Guest network card — the reader shows nothing")
}
if !strings.Contains(body, "192.168.0.149") {
t.Error("the guest's address did not reach the page")
}
if !strings.Contains(body, "badge-ok") {
t.Error("a healthy guest must render as healthy, not as an empty or alarming state")
}
if strings.Contains(body, "needs attention") {
t.Error("a machine that is fine is being alarmed on")
}
// The unknown branches must NOT fire for a box that reported properly.
if strings.Contains(body, "does not run the guest-network watchdog") ||
strings.Contains(body, "reported no guest-network state") {
t.Error("a reporting box rendered one of the unknown sentences")
}
}
// ── B — a machine whose watchdog has repaired the guest repeatedly ──────────────────────────────
//
// WRONG OUTCOME GUARDED: a green tick because the CURRENT state is fine. This is the exact shape of
// the failed-disk-drawn-as-a-healthy-empty-disk defect, and it is the reason this card exists at all:
// `state` says "healthy" in this fixture, because between repairs it IS.
func TestGuestNet_B_RepeatedRepairsAreVisible(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
seedNetHost(t, st, "demo-felhom-8363b5", "0.129.0", repairingGuestNetJSON, "")
body := getHostPage(t, s, cookie, "demo-felhom-8363b5")
if !strings.Contains(body, "needs attention") {
t.Fatal("a guest repaired 6 times in an hour is reported as fine — the whole point of the card")
}
if !strings.Contains(body, "had their networking repaired in the last hour") {
t.Error("the climbing-repairs sentence is missing")
}
if !strings.Contains(body, ">6<") {
t.Error("the repair COUNT is not rendered; 'some repairs' is not the signal, the number is")
}
if !strings.Contains(body, "2026-08-13T08:09:12Z") {
t.Error("the last repair time is not offered (it is in the title attribute)")
}
}
// ── C — a machine that does not report the fact at all → drawn as unknown, never healthy ────────
//
// WRONG OUTCOME GUARDED: absence read as good news. THE ONE THAT MATTERS. Two distinct absences are
// checked because they need different words: an agent that cannot report it, and a capable agent that
// said nothing.
func TestGuestNet_C_SilentIsUnknownNotHealthy(t *testing.T) {
t.Run("capable agent, no stanza", func(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
seedNetHost(t, st, "demo-hp-bb76ea", "0.129.0", silentGuestNetJSON, "")
body := getHostPage(t, s, cookie, "demo-hp-bb76ea")
if !strings.Contains(body, "reported no guest-network state") {
t.Fatal("a silent box must SAY it is unknown")
}
if strings.Contains(body, "badge-ok") && strings.Contains(body, "Guest network") {
assertNoHealthyBadgeInGuestNetCard(t, body)
}
})
t.Run("agent too old to report it", func(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
// 0.91.0 predates the R-54 watchdog (v0.92.0): the absence is expected AND still unknown.
seedNetHost(t, st, "old-agent-box", "0.91.0", silentGuestNetJSON, "")
body := getHostPage(t, s, cookie, "old-agent-box")
if !strings.Contains(body, "does not run the guest-network watchdog") {
t.Fatal("an old agent's silence must be named as an old agent's silence")
}
if !strings.Contains(body, "<strong>unknown</strong>, not healthy") {
t.Error("the unknown-is-not-healthy sentence is missing")
}
assertNoHealthyBadgeInGuestNetCard(t, body)
})
}
// ── D — the fact arrives malformed → unknown, and the hub does not 500 ──────────────────────────
//
// WRONG OUTCOME GUARDED: a page that breaks on one machine's bad field. getHostPage fails the test on
// any non-200, so surviving the call IS half the assertion.
func TestGuestNet_D_MalformedIsUnknownAndDoesNotBreakThePage(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
seedNetHost(t, st, "demo-felhom-8363b5", "0.129.0", malformedGuestNetJSON, "")
body := getHostPage(t, s, cookie, "demo-felhom-8363b5") // 200 or the test dies here
if !strings.Contains(body, "reported no guest-network state") {
t.Fatal("a malformed stanza must degrade to unknown")
}
assertNoHealthyBadgeInGuestNetCard(t, body)
}
// A guest whose state string is one the watchdog never asserts (a future value, or a truncated field)
// is drawn as unknown rather than falling through to the healthy branch. The switch in
// guestNetGuestView.Unknown is an ALLOW-LIST for exactly this reason: an unrecognised value defaults
// to unknown, and adding a state to the agent cannot silently paint it green here.
func TestGuestNet_UnrecognisedStateIsUnknown(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
seedNetHost(t, st, "demo-felhom-8363b5", "0.129.0", `{
"host": {"cpu_percent": 1.0},
"guest_net": {"checked_at": "2026-08-13T08:15:33Z",
"guests": [{"vmid": 9201, "state": "quantum", "mode": "dhcp", "ip": "10.0.0.5"}]}}`, "")
body := getHostPage(t, s, cookie, "demo-felhom-8363b5")
if !strings.Contains(body, "partly unknown") {
t.Fatal("an unrecognised guest state must summarise as unknown, not healthy")
}
}
// The empty-list case is deliberately NOT the same as silence: the agent's own contract says a stanza
// with a fresh checked_at and no guests means "the watchdog ran and found nothing to report", and that
// must stay distinguishable from "the watchdog is not wired" — the shape the v0.91.0 inert seam hid
// behind.
func TestGuestNet_EmptyListIsNotSilence(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
seedNetHost(t, st, "demo-felhom-8363b5", "0.129.0",
`{"host": {"cpu_percent": 1.0}, "guest_net": {"checked_at": "2026-08-13T08:15:33Z"}}`, "")
body := getHostPage(t, s, cookie, "demo-felhom-8363b5")
if !strings.Contains(body, "The watchdog ran and has no guest to report") {
t.Fatal("an empty guest list must say the watchdog RAN")
}
if strings.Contains(body, "reported no guest-network state") {
t.Error("an empty list was collapsed into silence — the two are different facts")
}
}
// assertNoHealthyBadgeInGuestNetCard checks that the Guest network card contains no healthy badge.
// It slices the card out of the page rather than searching the whole body, because `badge-ok` occurs
// all over a host page (capabilities, storage, WireGuard) and a whole-page search would pass
// vacuously — which is the "instrument that can drop results silently" trap.
func assertNoHealthyBadgeInGuestNetCard(t *testing.T, body string) {
t.Helper()
i := strings.Index(body, "Guest network")
if i < 0 {
t.Fatal("no Guest network card to slice — the assertion would have passed vacuously")
}
card := body[i:]
if j := strings.Index(card, "</section>"); j >= 0 {
card = card[:j]
} else {
t.Fatal("could not find the end of the Guest network card")
}
if strings.Contains(card, "badge-ok") {
t.Errorf("an UNKNOWN guest-network state rendered a healthy badge:\n%s", card)
}
}
// A repair that was ATTEMPTED and FAILED is a harder fact than a repair that worked, and the count
// alone cannot express it: six successful repairs is a nuisance, six failed ones is a guest that is
// down right now. Dropping `heal_succeeded` from the decoder would be R-260 exactly — the OOB decoder
// mirrored five of eight fields and the three it dropped included the deciding one.
func TestGuestNet_FailedRepairIsDistinctFromFrequentRepair(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
seedNetHost(t, st, "demo-felhom-8363b5", "0.129.0", `{
"host": {"cpu_percent": 1.0},
"guest_net": {"checked_at": "2026-08-13T08:15:33Z",
"guests": [{"vmid": 9201, "state": "unhealthy", "mode": "dhcp",
"has_route": false, "dhclient_alive": false,
"healed": true, "heal_succeeded": false, "heals_last_hour": 3,
"message": "dhclient restart did not restore the lease"}]}}`, "")
body := getHostPage(t, s, cookie, "demo-felhom-8363b5")
if !strings.Contains(body, "repair failed") {
t.Fatal("a repair that was attempted and failed is not distinguished from one that worked")
}
if !strings.Contains(body, "needs attention") {
t.Error("a guest whose repair failed must summarise as needing attention")
}
assertNoHealthyBadgeInGuestNetCard(t, body)
}
+4 -4
View File
@@ -252,15 +252,15 @@ const bindPageHTML = `<!DOCTYPE html>
<div class="card">
<h1>Felhom <span>doboz</span> összekötése</h1>
{{if eq .State "form"}}
<p class="lead">Kösd össze a most telepített Felhom dobozodat a fiókoddal. Add meg a doboz képernyőjén látható párosító kódot és a visszaállító jelszavadat.</p>
{{if .Failed}}<div class="banner">A megadott adatok nem megfelelőek. Ellenőrizd a párosító kódot és a jelszót, majd próbáld újra.</div>{{end}}
<p class="lead">Kösd össze a most telepített Felhom dobozodat a fiókoddal. Add meg a doboz képernyőjén látható párosító kódot és a tulajdonosi jelmondatodat.</p>
{{if .Failed}}<div class="banner">A megadott adatok nem megfelelőek. Ellenőrizd a párosító kódot és a tulajdonosi jelmondatot, majd próbáld újra.</div>{{end}}
<form method="POST" action="/bind/{{.Token}}">
<label for="pairing_code">Párosító kód</label>
<input class="code" type="text" id="pairing_code" name="pairing_code" autocomplete="off" autocapitalize="characters" spellcheck="false" required autofocus placeholder="ABC-234">
<p class="hint">A doboz monitorán jelenik meg, a telepítés után.</p>
<label for="passphrase">Visszaállító jelszó</label>
<label for="passphrase">Tulajdonosi jelmondat</label>
<input type="text" id="passphrase" name="passphrase" autocomplete="off" spellcheck="false" required placeholder="öt szó, kötőjellel vagy szóközzel">
<p class="hint">Az öt szóból álló kifejezés, amelyet a beállításkor kaptál.</p>
<p class="hint">Az öt szóból álló kifejezés, amelyet a beállításkor kaptál. Ez igazolja, hogy a fiók a tiéd.</p>
<button type="submit">Összekötés</button>
</form>
<p class="note">Biztonsági okból 5 sikertelen próbálkozás után a hivatkozás zárolódik. Ilyenkor vedd fel a kapcsolatot az ügyfélszolgálattal.</p>
+132
View File
@@ -0,0 +1,132 @@
package web
// R-323 — THE THIRD NAME. One secret, one name, and none of them a near-homograph of another.
//
// Three secrets a customer can hold, and they must be tellable apart by a hurried reader:
//
// three words takes control of the dashboard „Beállító kód"
// ten words opens the sealed off-site backups „Helyreállítási kód"
// five words proves the account owns the box being bound „Tulajdonosi jelmondat" ← this one
//
// The five-word phrase used to be „Visszaállító jelszó" — one word away from „Visszaállító kód",
// which R-295 had just retired for colliding with „Helyreállítási kód". Found while shipping that
// rename and deliberately NOT swept in with it; ruled on separately by the operator 2026-08-13.
//
// WHY NOT THE OBVIOUS CANDIDATES, since a test is where the reasoning survives:
//
// „Összekötési jelszó" — the OTHER factor on this very page is the „Párosító kód". Naming this one
// after the same act would leave the two factors a customer types in one sitting separated
// only by kód-versus-jelszó, which is structurally the „Visszaállító kód"/„Visszaállító
// jelszó" trap being removed.
// „Fiókjelszó" — there IS an account password (the dashboard login). This would collide
// with a DIFFERENT real secret, i.e. trade one homograph for a worse one.
//
// „Tulajdonosi jelmondat" is distinct on BOTH axes — stem (Tulajdonosi vs Beállító / Helyreállítási
// / Párosító) and noun (jelmondat vs kód / jelszó) — and it says what the phrase actually does.
import (
"net/http"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/notify"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
// retiredThirdName is the name this change removes. It is a SUBSTRING check on the stem so that a
// possessive or accusative form („visszaállító jelszavadat") cannot slip past a check written
// against the nominative — the R-299 lesson, where a guard matched one inflection of a Hungarian
// verb and the plural walked straight by.
const retiredThirdName = "isszaállító jelsz"
// The binding page names the secret „Tulajdonosi jelmondat" and nowhere carries the retired name.
// Driven through the real handler, so a template that never renders is visible here.
func TestSelfBind_ThirdSecretNaming(t *testing.T) {
s, st := newTestServer(t)
selfBindSetup(t, st, "acme", testCode)
token := mintLink(t, st, "acme", selfBindTTL)
body := bindGET(t, s, token).Body.String()
if !strings.Contains(body, "Tulajdonosi jelmondat") {
t.Errorf("the field label must name the secret „Tulajdonosi jelmondat”:\n%s", body)
}
if strings.Contains(body, retiredThirdName) {
t.Error("the retired name „Visszaállító jelszó” is back on the binding page")
}
// The two factors on this page must not be near-homographs of each other. The pairing code keeps
// its name; what matters is that the SECOND factor no longer differs from it by one noun.
if !strings.Contains(body, "Párosító kód") {
t.Error("the pairing code lost its name — the two factors must both be named")
}
// Neither of the OTHER two secrets may be named on this page: a customer who reads „kód" here
// twice is the failure this whole arc exists to prevent.
for _, other := range []string{"Beállító kód", "Helyreállítási kód"} {
if strings.Contains(body, other) {
t.Errorf("the binding page names a different secret (%q) — that is the collision", other)
}
}
}
// The failure banner must name the secret the same way the label does. A page that asks for a
// „Tulajdonosi jelmondat" and then complains about „a jelszó" has two names for one secret again,
// which is the defect in miniature.
func TestSelfBind_FailureBannerUsesTheSameName(t *testing.T) {
s, st := newTestServer(t)
selfBindSetup(t, st, "acme", testCode)
token := mintLink(t, st, "acme", selfBindTTL)
rr := bindPOST(t, s, token, testCodeFmt, "wrong wrong wrong wrong wrong")
body := rr.Body.String()
if !strings.Contains(body, "tulajdonosi jelmondat") {
t.Errorf("the failure banner does not name the secret the way the label does:\n%s", body)
}
if strings.Contains(body, retiredThirdName) {
t.Error("the retired name is on the failure banner")
}
}
// The e-mail that carries the link names it identically. The mail is read BEFORE the page, so a
// mismatch here is the customer's first impression of two different secrets.
func TestSelfBindEmail_UsesTheSameName(t *testing.T) {
subject, body := notify.FormatSelfBindEmail("acme", "https://hub.example/bind/tok")
if !strings.Contains(body, "tulajdonosi jelmondatodat") {
t.Errorf("the self-bind mail does not name the secret „Tulajdonosi jelmondat”:\n%s", body)
}
if strings.Contains(subject+body, retiredThirdName) {
t.Error("the retired name is still in the self-bind mail")
}
// It must still say what the thing IS — a rename that leaves the customer unable to recognise
// what they are holding has fixed a collision and broken the delivery.
if !strings.Contains(body, "5 szóból álló kifejezést") {
t.Error("the mail no longer says the phrase is five words")
}
}
// THE ACCEPTANCE PIN — the whole point of calling this naming rather than function.
//
// The SAME phrase, typed the same messy way a human types it (odd spacing, mixed case, mixed
// separators), still binds the appliance after the rename. If this ever goes red, the rename stopped
// being a rename.
func TestSelfBindPassphrase_StillAcceptedAfterTheRename(t *testing.T) {
s, st := newTestServer(t)
id := selfBindSetup(t, st, "acme", testCode)
token := mintLink(t, st, "acme", selfBindTTL)
rr := bindPOST(t, s, token, testCodeFmt, " Alpha Beta gamma-delta epsilon ")
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "egy percen belül") {
t.Fatalf("the same passphrase stopped being accepted: code=%d body=%q", rr.Code, rr.Body.String())
}
if a, _ := st.GetAppliance(id); a == nil || a.Status != store.ApplianceBound {
t.Fatalf("the appliance did not bind: %+v", a)
}
// The form field NAME is deliberately still `passphrase`: renaming customer copy must not touch
// the wire. A changed field name would be an acceptance change wearing a rename's clothes.
body := bindGET(t, s, mintLink(t, st, "acme", selfBindTTL)).Body.String()
if !strings.Contains(body, `name="passphrase"`) {
t.Error("the form field name changed — that is not a rename, that is a wire change")
}
}
@@ -473,7 +473,7 @@
{{if .Claim}}
<form method="POST" action="/configs/{{.CustomerID}}/claim-resend" style="margin-top: 0.5rem;">
{{.CSRFField}}
<button type="submit" class="btn btn-outline btn-sm" data-confirm="Send a fresh code to the registered address? The previous code stops working immediately (the box activates it on its next report, ~15 min).">{{if .Claim.ClaimedAt}}Visszaállító kód küldése{{else}}Kód újraküldése{{end}}</button>
<button type="submit" class="btn btn-outline btn-sm" data-confirm="Send a fresh code to the registered address? The previous code stops working immediately (the box activates it on its next report, ~15 min).">{{if .Claim.ClaimedAt}}Beállító kód küldése{{else}}Kód újraküldése{{end}}</button>
</form>
{{end}}
@@ -317,6 +317,82 @@
{{end}}
</section>
<!-- Guest network (R-319): the R-54 watchdog's verdict per owned guest, and — the reason this
card exists — HOW OFTEN IT HAD TO REPAIR EACH ONE. A guest the watchdog keeps fixing is
healthy every time anyone looks and is nevertheless failing; showing only the state would
give it a green tick, which is the failed-disk-drawn-as-a-healthy-empty-disk defect.
An unknown is NEVER drawn as healthy. Three absences, three sentences: an agent too old to
report it, a capable agent that reported nothing, and a guest whose own state the watchdog
did not assert. Each branch of this gate has its own render test (the template-gate rule).
The agent has sent this on every heartbeat since v0.92.0 and nothing read it until now. -->
<section class="card">
<h2>Guest network
{{if .GuestNet.AgentTooOld}}<span class="badge badge-neutral" title="This agent predates the guest-network watchdog">unknown</span>
{{else if not .GuestNet.Reported}}<span class="badge badge-neutral" title="A capable agent sent no guest-network stanza">unknown</span>
{{else if .GuestNet.Degraded}}<span class="badge badge-warn" title="A guest is unhealthy, or is being repaired repeatedly">needs attention</span>
{{else if .GuestNet.UnknownCount}}<span class="badge badge-neutral" title="The watchdog did not assert a state for every guest">partly unknown</span>
{{else if .GuestNet.Guests}}<span class="badge badge-ok" title="Every owned guest has an address, a default route and a live dhclient">healthy</span>
{{end}}
</h2>
{{if .GuestNet.AgentTooOld}}
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem;">
This host's agent (<code>{{.AgentVersion}}</code>) does not run the guest-network watchdog &mdash;
its guests' networking is <strong>unknown</strong>, not healthy. Needs agent <code>0.92.0</code> or newer.
</p>
{{else if not .GuestNet.Reported}}
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem;">
This host reported no guest-network state, so it is <strong>unknown</strong> &mdash; not healthy.
The watchdog is default-on; a silent capable agent means it was switched off
(<code>guest_net.disable</code>), or this report predates it on this box.
</p>
{{else if .GuestNet.Guests}}
<table class="data-table">
<thead>
<tr><th>Guest</th><th>State</th><th>Address</th><th>Route</th><th>dhclient</th><th>Repairs (1h)</th></tr>
</thead>
<tbody>
{{range .GuestNet.Guests}}
<tr>
<td><code>{{.VMID}}</code></td>
<td>
{{if .Unknown}}<span class="badge badge-neutral" title="{{.Message}}">unknown</span>
{{else if eq .State "healthy"}}<span class="badge badge-ok" title="{{.Message}}">healthy</span>
{{else if eq .State "static_fault"}}<span class="badge badge-warn" title="{{.Message}}">static fault</span>
{{else}}<span class="badge badge-error" title="{{.Message}}">unhealthy</span>{{end}}
{{if .HealFailed}}<span class="badge badge-error" title="The watchdog TRIED to repair this guest and did not succeed">repair failed</span>{{end}}
{{if .Damped}}<span class="badge badge-neutral" title="Repairs are rate-limited on this guest">damped</span>{{end}}
</td>
<td>{{if .IP}}<code>{{.IP}}</code> <span class="text-muted">({{.Mode}})</span>{{else}}<span class="text-muted">&mdash;</span>{{end}}</td>
<td>{{if .HasRoute}}yes{{else}}<strong>no</strong>{{end}}</td>
<td>{{if .DHClientAlive}}yes{{else if eq .Mode "static"}}<span class="text-muted">n/a</span>{{else}}<strong>no</strong>{{end}}</td>
<td>
{{if .Repairing}}<span class="badge badge-warn" title="The watchdog repaired this guest's network {{.RepairCount}} time(s) in the last hour — last at {{.LastHealAt}}">{{.RepairCount}}</span>
{{else}}<span class="text-muted">0</span>{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
{{if .GuestNet.RepairingCount}}
<p class="hint" style="font-size: 0.85rem; margin-top: 0.5rem;">
<strong>{{.GuestNet.RepairingCount}} guest(s) had their networking repaired in the last hour.</strong>
A guest that keeps being repaired reads healthy between repairs and is not. A killed
<code>dhclient</code> once took a tunnel down for 1 h 15 m with nobody told; this row is that signal.
</p>
{{end}}
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;">
Last swept {{.GuestNet.CheckedAt}}. One row per owned <em>running</em> guest the watchdog has probed.
</p>
{{else}}
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem;">
The watchdog ran and has no guest to report (swept {{.GuestNet.CheckedAt}}). This is
distinguishable from silence: the stanza arrived, its guest list is empty.
</p>
{{end}}
</section>
<!-- DR / Backup -->
<section class="card">
<h2>DR / Backup</h2>
+1 -1
View File
@@ -125,7 +125,7 @@ spec:
spec:
containers:
- name: hub
image: gitea.dooplex.hu/admin/felhom-hub:0.102.0
image: gitea.dooplex.hu/admin/felhom-hub:0.105.0
ports:
- containerPort: 8080
name: http
+2 -2
View File
@@ -324,7 +324,7 @@ spec:
image: registry.k8s.io/git-sync/git-sync:v4.4.0
args:
- --repo=https://gitea.dooplex.hu/admin/felhom.eu.git
- --ref=installer-v1.27.0
- --ref=installer-v1.28.0
- --root=/git-scripts
- --link=current
- --period=30s
@@ -369,7 +369,7 @@ spec:
image: registry.k8s.io/git-sync/git-sync:v4.4.0
args:
- --repo=https://gitea.dooplex.hu/admin/felhom.eu.git
- --ref=installer-v1.27.0
- --ref=installer-v1.28.0
- --root=/git-scripts
- --link=current
- --one-time
+52
View File
@@ -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
+64
View File
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""customer_copy_vocab — THE ONE list of customer-copy vocabulary, shared by both repos.
WHY THIS FILE EXISTS AT ALL. Two guards need the same words: the controller's
`retrieval_promise_gate.py` (which scans the box's own screens) and this repo's `hub_copy_gate.py`
(which scans the hub's e-mails and binding pages). Two copies of a word list is not a theoretical
risk here it is the R-299 defect exactly: a guard asserted one inflection of a Hungarian verb and
the plural, one paragraph above, walked straight past it. A second list drifts the same way.
So the list lives HERE, in `felhom.eu/scripts/`, which is already the established home for gates both
repos consume without copying (`reuse_refs_check.py`, `instructions_gate.py` the controller's own
`controller_gates.py` runs them from this directory and FAILS when the sibling clone is absent, which
is the fail-CLOSED shape a shared gate needs).
ADOPTION IS HALF DONE, AND SAYING SO IS THE POINT. `hub_copy_gate.py` imports this module.
`felhom-controller/controller/scripts/retrieval_promise_gate.py` still carries its own `STEMS`
literal, because the session that wrote this module was under a hard end-state requirement to leave
felhom-controller untouched (its target box was being re-deployed the same evening). Until that gate
imports this file, `hub_copy_gate.py` DRIFT-CHECKS it: it reads the controller gate's `STEMS` and
fails if the two lists disagree. That gives single-source semantics tonight without a cross-repo
edit but it is a scaffold, not the destination. Register row: R-325.
"""
# ── The retired names ────────────────────────────────────────────────────────────────────────────
#
# A name here is BANNED from customer-facing copy outright. Unlike the stems below, these are not
# claims that might be true in context — they are names for a secret that a different secret now
# owns, and a customer meeting one is being told two things have the same name.
#
# Each entry is a SUBSTRING, deliberately shorn of its leading capital and its ending, so a
# possessive or accusative form cannot slip past a check written against the nominative:
# „visszaállító jelszavadat" must convict as surely as „Visszaállító jelszó". That is the R-299
# lesson applied to nouns instead of verbs.
RETIRED_NAMES = {
"isszaállító kód": (
"R-295 (2026-08-10 box side, 2026-08-13 hub side). The THREE-word code that takes control of "
"the dashboard is „Beállító kód”. „Visszaállító kód” was a near-homograph of the escrow "
"„Helyreállítási kód” and the collision cost a real code."),
"isszaállító jelsz": (
"R-323 (2026-08-13). The FIVE-word phrase that proves the account owns the box being bound is "
"„Tulajdonosi jelmondat”. „Visszaállító jelszó” was one word from the name retired above, and "
"it was false besides — the phrase restores nothing."),
}
# ── The retrieval-promise stems ──────────────────────────────────────────────────────────────────
#
# These are NOT banned. Each carries the claim "your old backups can be got back", which is sometimes
# TRUE and must then be sayable. An occurrence must be REGISTERED with a reason in the consuming
# gate's allowlist; an unregistered one fails.
#
# The docstring of the controller gate is the long version and is worth reading before adding a stem:
# five instances of this claim surfaced ONE AT A TIME, and the obvious guard (ban the sentence) was
# tried twice and failed twice.
RETRIEVAL_STEMS = ["visszaállíthat", "visszaszerezhet", "visszahozhat", "visszanyit"]
# ── The three secrets, so a reviewer can check a name against the THING ───────────────────────────
#
# Not consumed by any check — it is here because every argument about these names has had to be
# reconstructed from register rows, and the table is the argument.
THE_THREE_SECRETS = [
("three words", "takes control of the dashboard", "Beállító kód"),
("ten words", "opens the sealed off-site backups", "Helyreállítási kód"),
("five words", "proves the account owns the box being bound", "Tulajdonosi jelmondat"),
]
+122 -3
View File
@@ -184,7 +184,7 @@
set -euo pipefail
SCRIPT_VERSION="1.27.0" # the SINGLE version source (F-1): -h and the run banners follow it.
SCRIPT_VERSION="1.28.0" # the SINGLE version source (F-1): -h and the run banners follow it.
# The hub used to carry a copy for its Setup tab; R-94 DELETED it
# (2026-08-02) because the hub cannot know which version a box runs —
# the Setup command fetches this script at run time. scripts/
@@ -842,6 +842,92 @@ _uninstall_statement() {
# run_uninstall — the full guarded teardown. Every mutation goes through run() so --dry-run prints it
# and executes nothing. Ordering is the reverse of install: guest -> agent -> pveum(ACL,token,user,
# role) -> golden(opt-in) -> state file. See the TASK spec §7/§8.
# _dnsmasq_purge_owned — remove the dnsmasq packages THIS install added, and nothing else (R-316).
#
# Called only from the `no` branch, i.e. only when the preflight RECORDED that we installed it. There
# is no inference here and there must never be: no mtimes, no package metadata, no file dates. That
# rule was earned and it is why the record exists at all.
#
# THE DEPENDENCY GUARD IS A SIMULATION, NOT A GUESS. `apt-get -s purge` is asked what it would do, and
# the purge proceeds only if the answer names a subset of the packages we are entitled to remove. If
# anything else would be dragged out, we STOP AND DISABLE instead and say which package stopped us —
# a silent downgrade to the weaker action would be a warning standing beside a success.
#
# It can fail and the uninstall still succeeds: a wedged apt must not strand a teardown. Failure is
# recorded in _DNSMASQ_PURGE_NOTE and reported in the closing statement, never swallowed.
_DNSMASQ_PURGE_NOTE=""
_dnsmasq_purge_owned() {
local want=("dnsmasq")
# dnsmasq-base ships /usr/sbin/dnsmasq and is separately installable — take it back only if we
# added it too. Leaving a pre-existing base package is correct; see the WARN below for what that
# costs on the next install.
if [[ "$(_state_get dnsmasq_base_preexisting)" == "no" ]]; then
want+=("dnsmasq-base")
fi
if ! command -v apt-get >/dev/null 2>&1; then
_DNSMASQ_PURGE_NOTE="apt-get absent — dnsmasq left installed (stopped + disabled)"
log_warn " dnsmasq: apt-get not available — the package stays installed; the unit is stopped + disabled."
return 0
fi
if $DRY_RUN; then
log_dry "DEBIAN_FRONTEND=noninteractive apt-get -y purge ${want[*]} (after simulating the blast radius)"
return 0
fi
# What would come out? Every Remv/Purg line is a package this would remove.
local sim removed extra=()
sim=$(DEBIAN_FRONTEND=noninteractive apt-get -s purge "${want[@]}" 2>/dev/null) || sim=""
if [[ -z "$sim" ]]; then
_DNSMASQ_PURGE_NOTE="purge simulation failed — dnsmasq left installed (stopped + disabled)"
log_warn " dnsmasq: could not simulate the removal — leaving the package installed (unit stopped + disabled)."
log_warn " A reinstall will read it as pre-existing; clear it with: apt-get -y purge ${want[*]}"
return 0
fi
removed=$(printf '%s\n' "$sim" | awk '/^(Remv|Purg) /{print $2}')
local p
for p in $removed; do
case " ${want[*]} " in
*" $p "*) ;;
*) extra+=("$p") ;;
esac
done
if (( ${#extra[@]} > 0 )); then
_DNSMASQ_PURGE_NOTE="purge would also remove ${extra[*]} — dnsmasq left installed (stopped + disabled)"
log_warn " dnsmasq: NOT purging — something else now depends on it. Removing it would also take out:"
log_warn " ${extra[*]}"
log_warn " The unit is stopped + disabled, so :53 is free. The package stays, deliberately."
return 0
fi
log_info " dnsmasq: removing the package(s) Felhom installed: ${want[*]}"
if run env DEBIAN_FRONTEND=noninteractive apt-get -y purge "${want[@]}"; then
# Positive observable: assert it is actually gone rather than trusting apt's exit code.
if dpkg-query -W -f='${Status}' dnsmasq 2>/dev/null | grep -q "install ok installed"; then
_DNSMASQ_PURGE_NOTE="apt-get reported success but dnsmasq is still installed"
log_warn " dnsmasq: apt-get exited 0 but the package is STILL installed — treat as not removed."
else
log_success " dnsmasq removed — the next install starts from a clean slate, as a first install"
fi
else
_DNSMASQ_PURGE_NOTE="apt-get purge failed — dnsmasq left installed (stopped + disabled)"
log_warn " dnsmasq: the package removal FAILED. The unit is stopped + disabled, so :53 is free,"
log_warn " but a reinstall will read the package as pre-existing. Clear it with:"
log_warn " apt-get -y purge ${want[*]}"
fi
# R-316: the residual case, stated because it is invisible otherwise. If dnsmasq-base pre-dated us
# we correctly leave it — but it owns /usr/sbin/dnsmasq, and the agent decides whether to install
# by stat-ing exactly that path (felhom-agent internal/lanresolver/lanresolver.go:105). So on such
# a host the next install skips the apt step and then fails to enable a unit that is no longer
# there. Filed as R-317; named here so nobody has to rediscover it from a silent resolver.
if [[ "$(_state_get dnsmasq_base_preexisting)" == "yes" ]]; then
log_warn " NOTE: dnsmasq-base pre-dates Felhom and is deliberately kept. It owns /usr/sbin/dnsmasq,"
log_warn " which the agent stats to decide whether to install — so on this host the next install"
log_warn " may skip the install step and fail to enable the unit (R-317)."
fi
return 0
}
run_uninstall() {
log_step "UNINSTALL — local host teardown"
@@ -1098,8 +1184,22 @@ run_uninstall() {
if $_dnsmasq_touched || systemctl is-enabled --quiet dnsmasq 2>/dev/null; then
case "$_dnsmasq_owner" in
no)
# R-316 — STOPPING IS NOT REMOVING, AND THE DIFFERENCE COSTS THE *NEXT* CYCLE.
#
# R-300 stopped + disabled the unit and left the PACKAGE installed. That frees :53, so
# cycle 1 looks fixed — and it is. But the next install's preflight asks dpkg "is
# dnsmasq installed?", the answer is still yes, so it records `dnsmasq_preexisting=yes`,
# the second uninstall then treats OUR OWN package as the household's and leaves it
# running, and the third install refuses again with the message that blames the
# customer's network. **Measured on drill-r50 2026-08-13, all three cycles, before this
# was written.** Every box already in the field is at cycle 2.
#
# So: stop first (the port must free even if the package operation fails), then remove
# what we added. Order matters — the state file that carries this record is deleted
# later in this same function, so the record is read before it can be lost.
log_info " dnsmasq was installed by Felhom (recorded at install) — stopping + disabling it"
run systemctl disable --now dnsmasq || true
_dnsmasq_purge_owned
;;
yes)
log_info " dnsmasq pre-dates Felhom (recorded at install) — leaving it running, restarting only"
@@ -1175,7 +1275,14 @@ run_uninstall() {
# R-300: the packages are still not PURGED (they are system packages and purging them on a host we
# may not own is the wrong blast radius) — but dnsmasq's UNIT is now stopped+disabled when the
# install-time record says Felhom installed it, so our own leftover no longer blocks our own reinstall.
log_info " NOTE: the 'sudo' and 'dnsmasq' packages were left INSTALLED (system packages, not purged); dnsmasq's unit was stopped+disabled only if Felhom installed it. The host record still exists in the hub — remove it there if desired."
# R-316: this line used to say dnsmasq was left installed, and that was the defect in one sentence.
# `sudo` genuinely is a system package and stays. dnsmasq is now taken back when the install-time
# record says we added it — and when that could not be done, the reason is stated HERE rather than
# scrolling past, because a teardown that quietly half-completed is how cycle 3 refuses.
if [[ -n "$_DNSMASQ_PURGE_NOTE" ]]; then
log_warn " NOTE: dnsmasq was NOT removed — $_DNSMASQ_PURGE_NOTE. Its unit is stopped + disabled, so :53 is free, but the NEXT install will read the package as pre-existing."
fi
log_info " NOTE: the 'sudo' package was left INSTALLED (a system package, never ours to remove). dnsmasq is removed only when the install-time record says Felhom installed it; a resolver that pre-dated Felhom is never touched. The host record still exists in the hub — remove it there if desired."
$DRY_RUN && log_warn " DRY-RUN: nothing above was actually executed."
return 0
}
@@ -1715,12 +1822,24 @@ step_preflight() {
# heuristic dressed as a fact, and this project has been bitten by exactly that shape.
#
# Recorded on EVERY mode, because appliance is where the agent actually installs it.
# R-316: TWO packages are recorded, not one, and the second is not pedantry.
#
# `dnsmasq` ships the systemd unit; **`dnsmasq-base` ships /usr/sbin/dnsmasq**. They are separately
# installable, and a host can carry the base package for some unrelated reason. The removal must
# take back only what WE added, so what we found has to be recorded package by package — the same
# rule as before, applied one level finer.
if command -v dpkg-query >/dev/null 2>&1 && dpkg-query -W -f='${Status}' dnsmasq 2>/dev/null | grep -q "install ok installed"; then
_state_put dnsmasq_preexisting yes
log_info " dnsmasq: already installed BEFORE Felhom — recorded; uninstall will not touch it"
else
_state_put dnsmasq_preexisting no
log_info " dnsmasq: not present before Felhom — recorded; uninstall may stop+disable it if we install it"
log_info " dnsmasq: not present before Felhom — recorded; uninstall will remove it again if we install it"
fi
if command -v dpkg-query >/dev/null 2>&1 && dpkg-query -W -f='${Status}' dnsmasq-base 2>/dev/null | grep -q "install ok installed"; then
_state_put dnsmasq_base_preexisting yes
log_info " dnsmasq-base: already installed BEFORE Felhom — recorded; uninstall will leave it"
else
_state_put dnsmasq_base_preexisting no
fi
# Host DNS :53 gate (byo only) — GL-8/F6. In byo the agent's own lan-resolver stays OFF (a config
+244
View File
@@ -0,0 +1,244 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""hub_copy_gate — the hub's customer-facing copy comes under a guard (R-324).
THE SCOPE GAP THIS CLOSES. `retrieval_promise_gate.py` lives in `felhom-controller` and scans that
repo only. It was extended to Go string literals on 2026-08-12 (R-311) on the express ground that the
recovery screen's messages are Go strings in a handler and "none of it had ever been scanned" — and
the identical sentence was true one repo over the whole time. **The hub composes every customer
e-mail and renders the binding pages: the first sentences a customer ever reads, before they have
seen any box screen.** Nothing looked at them.
A hand scan on 2026-08-13 returned zero retrieval claims, so this was a SCOPE gap rather than a live
defect which is the moment to close it, before someone writes the first hub-side promise assuming
the guard has them covered.
TWO CHECKS, and the difference is deliberate:
1. RETIRED NAMES banned outright, scanned across the WHOLE hub, not just declared surfaces. A
name for a secret that a different secret now owns is never correct anywhere, so there is no
allowlist and no "unless". Comments are stripped: prose explaining a rename is not the rename
coming back, and the register rows that record these decisions quote the retired names by
necessity.
2. RETRIEVAL STEMS registered, not banned, in the DECLARED customer-facing surfaces. The claim
they carry is sometimes true and must stay sayable; what must not happen is a new one appearing
where nobody was looking. Same contract as the controller gate: an occurrence is allowlisted
WITH A REASON, and a stale allowlist entry is also a failure.
BOTH lists come from `customer_copy_vocab.py` see that file for why they are not literals here.
DRIFT CHECK, and it is load-bearing. The controller gate still owns its own `STEMS` literal (the
session that wrote this could not touch felhom-controller). This gate reads that literal and FAILS if
it disagrees with the shared list, so the two cannot silently diverge in the meantime. An ABSENT
sibling clone is INCONCLUSIVE (exit 2), never a pass the G-1 lesson: a gate that skips when its
sibling is missing runs in neither home.
Run: python3 scripts/hub_copy_gate.py (from the felhom.eu repo root)
python3 scripts/hub_copy_gate.py --selftest (plant convict remove pass)
"""
import os
import re
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from customer_copy_vocab import RETIRED_NAMES, RETRIEVAL_STEMS # noqa: E402
_HERE = os.path.dirname(os.path.abspath(__file__))
_REPO = os.path.dirname(_HERE)
_HUB = os.path.join(_REPO, "hub")
# The hub files that carry HUNGARIAN CUSTOMER-FACING text — the surfaces where a retrieval claim
# could be made to a customer. Enumerated by scanning every non-test .go/.html under hub/internal for
# accented Hungarian outside comments, then keeping the ones a CUSTOMER (not the operator) reads.
#
# A declared file that is missing is a FAILURE, never a skip: the controller gate learned that when a
# renamed handler would have silently emptied its own scope.
CUSTOMER_SURFACES = [
os.path.join("hub", "internal", "notify", "templates.go"), # every customer e-mail + event copy
os.path.join("hub", "internal", "web", "selfbind.go"), # the binding pages
os.path.join("hub", "internal", "api", "handler.go"), # customer-visible event messages
os.path.join("hub", "internal", "notify", "dispatcher.go"), # the customer channel's own wording
]
# (file basename, substring that identifies the occurrence) -> why it is allowed.
# Empty today, and that is a measurement rather than an oversight: the hub makes no retrieval promise.
ALLOWLIST = {}
GO_COMMENT = re.compile(r"//[^\n]*|/\*.*?\*/", re.S)
TPL_COMMENT = re.compile(r"\{\{/\*.*?\*/\}\}", re.S)
HTML_COMMENT = re.compile(r"<!--.*?-->", re.S)
def strip_comments(text, path):
"""Remove what never reaches a customer.
Go `//` and `/* */`, Go-template `{{/* */}}`, and unlike the controller gate HTML `<!-- -->`
too. The controller gate deliberately SCANS HTML comments because its templates ship to a browser
where View Source is one keystroke. The hub's Hungarian lives in Go string literals that happen
to contain HTML, and its operator templates are not customer copy, so an HTML comment here is a
note to the next maintainer. Stated because it is a real difference between the two gates.
"""
text = TPL_COMMENT.sub("", text)
text = HTML_COMMENT.sub("", text)
if path.endswith(".go"):
text = GO_COMMENT.sub("", text)
return text
def _iter_hub_sources():
for root, _dirs, files in os.walk(os.path.join(_REPO, "hub", "internal")):
for f in sorted(files):
if f.endswith("_test.go") or not (f.endswith(".go") or f.endswith(".html")):
continue
yield os.path.join(root, f)
def scan_retired(extra_text=None):
"""Every retired name, everywhere in the hub. Returns a list of convictions."""
convictions = []
sources = [(p, open(p, encoding="utf-8").read()) for p in _iter_hub_sources()]
if extra_text is not None:
# The synthetic path MUST end in .go: strip_comments keys comment-stripping off the
# extension, so a selftest source named anything else would be scanned WITH its comments —
# which is how the first run of this selftest convicted its own step-3 control. The bug was
# in the instrument, and the control is what found it.
sources.append((os.path.join(_REPO, "hub", "internal", "selftest_planted.go"), extra_text))
for path, raw in sources:
text = strip_comments(raw, path)
for name in RETIRED_NAMES:
for m in re.finditer(re.escape(name), text):
line = text[: m.start()].count("\n") + 1
ctx = " ".join(text[max(0, m.start() - 70): m.end() + 70].split())[:150]
convictions.append((os.path.relpath(path, _REPO), line, name, ctx))
return convictions
def scan_stems():
"""Retrieval stems in the declared customer surfaces. Returns (convictions, seen_keys)."""
convictions, seen = [], set()
for rel in CUSTOMER_SURFACES:
path = os.path.join(_REPO, rel)
if not os.path.exists(path):
raise SystemExit("hub-copy gate: declared customer surface is MISSING: %s" % rel)
name = os.path.basename(path)
text = strip_comments(open(path, encoding="utf-8").read(), path)
for stem in RETRIEVAL_STEMS:
for m in re.finditer(re.escape(stem) + r"[a-záéíóöőúüű]*", text):
hit = None
for k in (k for k in ALLOWLIST if k[0] == name):
for om in re.finditer(re.escape(k[1]), text):
if om.start() <= m.start() and m.end() <= om.end():
hit = k
break
if hit:
break
if hit:
seen.add(hit)
else:
line = text[: m.start()].count("\n") + 1
ctx = " ".join(text[max(0, m.start() - 90): m.end() + 90].split())[:170]
convictions.append((name, line, m.group(0), ctx))
return convictions, seen
def check_drift():
"""The controller gate's STEMS must equal the shared list. Returns (status, message).
status: "ok" | "drift" | "inconclusive"
"""
sibling = os.path.join(os.path.dirname(_REPO), "felhom-controller",
"controller", "scripts", "retrieval_promise_gate.py")
if not os.path.exists(sibling):
return "inconclusive", ("the felhom-controller clone is absent, so the shared stem list "
"could not be compared against the gate that also uses it (%s)" % sibling)
text = open(sibling, encoding="utf-8").read()
m = re.search(r"^STEMS\s*=\s*\[(.*?)\]", text, re.M | re.S)
if not m:
return "drift", "could not find a STEMS list in the controller gate — its shape changed"
theirs = re.findall(r"[\"']([^\"']+)[\"']", m.group(1))
if theirs != RETRIEVAL_STEMS:
return "drift", ("the controller gate's STEMS have diverged from customer_copy_vocab.py\n"
" controller : %r\n shared : %r" % (theirs, RETRIEVAL_STEMS))
return "ok", "controller gate's STEMS match the shared list (%d stem(s))" % len(theirs)
def selftest():
"""Plant → convict → remove → pass. A guard never seen catching anything proves nothing."""
print("hub-copy gate SELFTEST")
baseline = scan_retired()
if baseline:
print(" FAIL: the tree is not clean before planting — %d conviction(s)" % len(baseline))
for c in baseline:
print(" %s:%d [%s]" % (c[0], c[1], c[2]))
return 1
print(" 1. clean tree : 0 conviction(s) OK")
planted = 'body := "Add meg a visszaállító jelszavadat a folytatáshoz."\n'
convicted = scan_retired(extra_text=planted)
if len(convicted) != 1 or convicted[0][2] != "isszaállító jelsz":
print(" 2. planted retired name : NOT CONVICTED — the guard is inert")
print(" got: %r" % (convicted,))
return 1
print(" 2. planted „visszaállító jelszavadat”: CONVICTED (%s) OK" % convicted[0][2])
commented = '// the old name was „visszaállító jelszó" and is retired\n'
if scan_retired(extra_text=commented):
print(" 3. same phrase inside a COMMENT : convicted — comments must not be scanned")
return 1
print(" 3. same phrase inside a comment : not convicted OK")
if scan_retired():
print(" 4. planting removed : still convicting — the scan is not deterministic")
return 1
print(" 4. planting removed : 0 conviction(s) OK")
print("hub-copy gate selftest OK — the guard has been watched catching, ignoring and releasing")
return 0
def main():
if "--selftest" in sys.argv:
return selftest()
retired = scan_retired()
stems, seen = scan_stems()
stale = [k for k in ALLOWLIST if k not in seen]
drift_status, drift_msg = check_drift()
for path, line, name, ctx in retired:
print(" %s:%d RETIRED NAME in customer copy (%s):\n%s" % (path, line, name, ctx))
print(" reason it is retired: %s" % RETIRED_NAMES[name])
for name, line, word, ctx in stems:
print(" %s:%d unregistered retrieval claim (%s):\n%s" % (name, line, word, ctx))
for k in stale:
print(" STALE ALLOWLIST ENTRY (no longer present): %s :: %r" % (k[0], k[1]))
n_files = len(list(_iter_hub_sources()))
if retired or stems or stale:
print("\nHUB-COPY GATE FAILED: %d retired name(s), %d unregistered claim(s), %d stale."
% (len(retired), len(stems), len(stale)))
print("A retired name is never correct in customer copy — use the current name. If a new")
print("retrieval claim is genuinely TRUE where it renders, make it conditional on what the")
print("hub can actually see, then register it in ALLOWLIST with the reason.")
return 1
if drift_status == "drift":
print(" DRIFT: %s" % drift_msg)
print("\nHUB-COPY GATE FAILED: the shared vocabulary is no longer shared.")
return 1
print("hub-copy gate OK — %d hub file(s) scanned for %d retired name(s); %d customer surface(s) "
"scanned for %d retrieval stem(s), %d registered claim(s), none unregistered"
% (n_files, len(RETIRED_NAMES), len(CUSTOMER_SURFACES), len(RETRIEVAL_STEMS), len(ALLOWLIST)))
if drift_status == "inconclusive":
print(" ⚠ INCONCLUSIVE (exit 2): %s" % drift_msg)
return 2
print(" drift: %s" % drift_msg)
print(" (BLIND SPOT: this checks the WORDS in the four declared customer surfaces. It cannot")
print(" tell whether a true-looking sentence is wired to a predicate that is actually true —")
print(" that is what render tests are for. And it does not read the operator's screens.)")
return 0
if __name__ == "__main__":
sys.exit(main())
+3
View File
@@ -69,6 +69,9 @@ GATES = [
("instructions", os.path.join(SCRIPTS, "instructions_gate.py"), [ROOT], True),
("golden-currency", os.path.join(SCRIPTS, "golden_currency_gate.py"), [], True),
("wire-contract", os.path.join(SCRIPTS, "wire_contract_gate.py"), [], True),
# R-324 — the hub composes every customer e-mail and renders the binding pages, and until
# 2026-08-13 no guard in either repo had ever looked at them. Fast: pure file reads.
("hub-copy", os.path.join(SCRIPTS, "hub_copy_gate.py"), [], True),
]
VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""unproven.py — "which claims are not proven?", as a question a machine can answer (R-326).
WHY THIS EXISTS. The operator asked on 2026-08-04 to see what is built and what is still unproven.
The picture was built (`where-felhom-stands.yaml` `.html`) and it is good but the QUESTION could
only be answered by a person reading it. On 2026-08-13 a session was asked for "the nine grey claims",
could not determine which nine, and declined to guess. It was right to decline, and the refusal is
the finding this closes.
WHERE "NINE" CAME FROM, since a wrong number that matches nothing is worse than no number. It is
real, and it answers a DIFFERENT question: nine claims carry `verdict: downgraded` the count the
2026-08-09 verification pass LOWERED. The count of claims that are not walked is 32. Both are true;
only one of them is "what is unproven".
This reads the dataset only. It makes no judgement, opens no evidence and contacts no machine a
claim's status is the capability map's business (the map changes first; the dataset follows it), and
`check_stands.py` is the gate that keeps the citations honest. This just answers the question.
Run: python3 scripts/unproven.py # every claim that is not walked
python3 scripts/unproven.py --summary # the counts only
"""
import io
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA = os.path.join(ROOT, "documentation", "architecture", "where-felhom-stands.yaml")
# The status that means "done end to end on real hardware, with evidence on file". Everything else is
# a degree of not-that, which is the whole point of the question.
PROVEN = "walked"
# Widest first, so the output reads as a ladder down from nearly-there to not-started.
ORDER = ["partial", "built", "missing"]
def field(entry, name):
m = re.search(r"^\s*%s:\s*\"?(.*?)\"?\s*$" % name, entry, re.M)
return m.group(1) if m else ""
def load():
if not os.path.exists(DATA):
sys.exit("unproven: the dataset is missing: %s" % DATA)
text = io.open(DATA, encoding="utf-8").read()
parts = re.split(r"\n - id: ", text)
header, entries = parts[0], parts[1:]
if not entries:
sys.exit("unproven: no claims parsed from %s — the file's shape changed" % DATA)
claims = []
for e in entries:
claims.append({
"id": e.split("\n", 1)[0].strip(),
"status": field(e, "status") or "(none)",
"title": field(e, "title"),
"band": field(e, "band"),
"verdict": field(e, "verdict"),
"evidence": "evidence:" in e,
})
return field(header, "verified_on"), claims
def main():
verified_on, claims = load()
not_walked = [c for c in claims if c["status"] != PROVEN]
counts = {}
for c in claims:
counts[c["status"]] = counts.get(c["status"], 0) + 1
print("where felhom stands — %d claims, verified_on %s" % (len(claims), verified_on))
print(" %-8s %d" % (PROVEN, counts.get(PROVEN, 0)))
for s in ORDER:
n = counts.get(s, 0)
with_ev = sum(1 for c in claims if c["status"] == s and c["evidence"])
print(" %-8s %d (%d cite evidence, %d prose only)" % (s, n, with_ev, n - with_ev))
for s in sorted(k for k in counts if k not in ORDER + [PROVEN]):
print(" %-8s %d ⚠ status not known to this script" % (s, counts[s]))
print(" NOT WALKED: %d of %d" % (len(not_walked), len(claims)))
if "--summary" in sys.argv:
return 0
print()
for s in ORDER + sorted(k for k in counts if k not in ORDER + [PROVEN]):
rows = [c for c in not_walked if c["status"] == s]
if not rows:
continue
print("%s (%d)" % (s.upper(), len(rows)))
for c in sorted(rows, key=lambda c: c["id"]):
ev = "evidence" if c["evidence"] else "PROSE ONLY"
vd = (" [%s]" % c["verdict"]) if c["verdict"] else ""
print(" %-40s %-10s %s%s" % (c["id"], c["band"], ev, vd))
print(" %s" % c["title"][:110])
print()
return 0
if __name__ == "__main__":
sys.exit(main())
+61 -19
View File
@@ -81,6 +81,12 @@ ROOTS = [
"controller", "internal/report", "Report", "hub"),
("hub -> controller (report ACK, `escrow` object)",
"hub", "internal/store", "EscrowStatus", "controller"),
# R-311. Declared the moment the wire was created, because the gate covers only what is DECLARED
# and a silent pass is indistinguishable from coverage. The hub response is a named type rather
# than a map[string]any precisely so this root can resolve — an untyped map is a cross-repo
# contract nothing can check.
("hub -> agent (GET /hosts/<id>/escrow/retained)",
"hub", "internal/api", "RetainedEscrowResponse", "agent"),
]
# Tag names whose literal string carries no information in a repo-wide search. NOT CHECKED.
@@ -97,13 +103,20 @@ GENERIC = {
# someone must be able to re-check later, so none of them is bare. A quiet exclusion would be a
# dropped field with paperwork, which is worse than the defect.
#
# TWO KINDS OF ENTRY, and the difference is deliberate:
# THREE KINDS OF ENTRY, and the differences are deliberate:
# * "redundant" — the hub already decodes something that answers the same question. No consumer
# is wanted; the entry is the end of the matter.
# * "R-264" — a fact with no consumer that ARGUABLY should have one. The entry does NOT
# close the question; it records it against an OPEN register row so that
# allowlisting cannot be mistaken for deciding. R-260 is closed by the gate plus
# the operator-access fix; the leftover appetite is R-264.
# * "not consumed, deliberately" — RULED by the operator, with the date. The question IS closed:
# a reader was considered and declined on stated grounds. This kind was added on
# 2026-08-13 because "arguably owed" had been carried for five days as though it
# were a decision, and an undecided fact and a decided one must not read alike.
# THE EMITTER IS DELIBERATELY LEFT ALONE: removing it is a coordinated two-repo
# change and it also breaks the byte-identical cross-repo host-report golden. The
# honest end here is a recorded decision, not a deletion.
#
# Keyed by (wire label, dotted emit path).
_AH = "agent -> hub (POST /host-report)"
@@ -117,6 +130,14 @@ _R264 = (
"no consumer today, and one is arguably owed — recorded against R-264 (OPEN) rather than "
"decided here. Allowlisting is not deciding.")
# RULED 2026-08-13 by the operator against R-264: these five were considered for a reader and
# declined. The reason each is declined is per-entry below — a bare "not wanted" would be the quiet
# exclusion this gate exists to prevent.
_NOREADER = (
"not consumed, DELIBERATELY — ruled 2026-08-13 (operator, against R-264). A reader was "
"considered and declined; the question is closed, not open. The emitter stays (removing it is a "
"two-repo change and breaks the host-report golden). ")
ALLOWLIST = {
# ---- redundant: the hub already decodes an equivalent ----
(_AH, "host.cpu_temp_c"): _REDUNDANT_HOST_METRICS,
@@ -143,33 +164,54 @@ ALLOWLIST = {
"redundant: hub-side wgsync reconciles peers from its own state, and the OOB path's own "
"wg_handshake_age_s IS now decoded (into HostOOBRow, for the alert text)."),
# ---- no consumer, and one is arguably owed: R-264, OPEN ----
(_AH, "guest_net"): _R264 + " The R-54 guest-network watchdog stanza (whole object).",
(_AH, "guest_net.checked_at"): _R264,
(_AH, "guest_net.guests.has_route"): _R264,
(_AH, "guest_net.guests.dhclient_alive"): _R264,
(_AH, "guest_net.guests.heal_succeeded"): _R264,
(_AH, "guest_net.guests.heals_last_hour"): _R264,
(_AH, "guest_net.guests.last_heal_at"): _R264,
(_AH, "guest_net.guests.damped"): _R264,
# ---- R-319: guest_net AND ALL SEVEN CHILDREN ARE GONE FROM THIS LIST ----
# They are no longer allowlisted because they are no longer unconsumed: the hub models the R-54
# watchdog stanza (web/hosts.go parseGuestNet → the host-detail Guest network card), including
# heals_last_hour and heal_succeeded. Removing the entries is the POINT — an allowlisted tag is
# SKIPPED by this gate, so leaving them here would mean the new reader's fields were never
# actually checked for reachability, and the gate would report a coverage it did not have.
# The first of R-264's readers; three groups remain owed one.
# ---- no consumer, and one is arguably owed: R-264, STILL OPEN ----
(_AH, "selfupdate_pending"): _R264 + (
" NOTE: the agent's own comment beside this field claimed 'the hub reads an absent field as "
"pending=false, the correct default'. The hub had no field at all, so it read nothing "
"either way. The comment was corrected in the same change as this entry."),
(_AH, "selfupdate_pending_version"): _R264,
(_AH, "mgmt_plane.healed_recently"): _R264 + (
" The hub DOES alarm on mgmt_plane.privsep_healed_at, which is the timestamp beside this "
"boolean, so the recurring-clobber signal is not lost — only this flag is."),
(_AH, "pbs_dr.applied_at"): _R264,
(_AH, "mgmt_plane.healed_recently"): _NOREADER + (
"The recurring-clobber signal is NOT lost: the hub already alarms on "
"mgmt_plane.privsep_healed_at, the timestamp sitting beside this boolean. A second reader "
"for the flag would add a second way to say the same thing and no new fact."),
(_AH, "pbs_dr.applied_at"): _NOREADER + (
"A tier-applied TIMESTAMP, and the hub already decodes pbs_dr.state — which is the verdict. "
"Presence is not success: consuming the timestamp without the state is precisely the "
"attempt-read-as-result trap, and with the state it answers nothing further."),
(_AH, "restore_tests.mount_parity"): _R264 + (
" R-262: the hub's own comment claims this contract is mirrored field-for-field and that a "
"key-set test guards drift; it is two fields short and the fixture omits the same two."),
(_AH, "restore_tests.mount_inventory"): _R264 + " R-262, as mount_parity.",
(_CH, "config_hash"): _R264,
(_CH, "reporting_disabled"): _R264,
(_CH, "stacks"): _R264 + (
" The whole per-stack report object; the hub's app view is built from app_telemetry."),
(_CH, "storage.migrated_to"): _R264,
(_CH, "config_hash"): _NOREADER + (
"A config FINGERPRINT. The hub authors the config and knows its own generation "
"(desired_generation, config_version), which is the convergence question it actually asks. "
"A hash it did not compute answers a question nobody has posed."),
(_CH, "reporting_disabled"): (
"redundant: the box sends health.status = \"disabled\" in the SAME minimal report "
"(controller cmd/controller/main.go:1254), the hub decodes it into reports.health_status, and "
"web/rollup.go:25 renders that customer as 'disabled'. The state IS visible; this flag is a "
"second spelling of a fact already read. ⚠ DECIDED ON ITS OWN MERITS 2026-08-13, and the "
"decision came with a REAL finding that a second decoded flag would NOT have fixed: "
"StalenessChecker.Check (monitor/staleness.go:88+) is age-only — it skips BLOCKED customers "
"and nothing else — so a deliberately-silent box still goes stale at 30 min and down at 60. "
"Filed as R-321. The fix belongs in the checker, which already has the health status it "
"needs; adding a field here would have felt like progress and left the alarm firing."),
(_CH, "stacks"): _NOREADER + (
"The whole per-stack report object. The hub's app view is built from app_telemetry, which is "
"a purpose-built wire with its own table; a second, differently-shaped source for the same "
"screen is how two answers to one question get shipped."),
(_CH, "storage.migrated_to"): _NOREADER + (
"A drive-migration marker: box-local bookkeeping about where data was moved ON that box. The "
"hub holds no drive-layout intent to reconcile it against, so it could only be displayed, "
"and a fact displayed with nothing to compare it to is decoration."),
(_CH, "backup.last_db_dump"): _R264,
(_CH, "backup.last_integrity_check"): _R264,
}