fix(os): #1896 the dashboard quadlet carries the three dashboard-onion values - #1942
fix(os): #1896 the dashboard quadlet carries the three dashboard-onion values#1942VijitSingh97 wants to merge 1 commit into
Conversation
…n values The appliance's dashboard unit never received DASHBOARD_ONION_ENABLED / _ADDRESS / _CLIENT_AUTH, so the header's .onion block (#1853, compose side in #1880) could not render on the machine it was written for. The quadlet writer now puts the three on the dashboard unit's Environment= line, next to the other DASHBOARD_* flags, read from the rendered .env like every other value; the three fixtures are re-rendered (one line each). Display-only: the client keys are still never passed in. test-render-quadlet gains the rule that would have caught this: every DASHBOARD_* key the compose dashboard service carries must be on the rendered quadlet unit, with a control row that the compose parse sees the service's own keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
Non-author review — PASS at
|
| Leg | renderer | onion keys inside compose's dashboard: block |
Result |
|---|---|---|---|
| Baseline, this head | this PR's | absent | 37 pass / 0 fail |
| Mutation | origin/develop's (unfixed) |
present, exactly as pull 1880 adds them | 36 pass / 1 fail |
| Merged shape | this PR's | present | 37 pass / 0 fail |
The mutation reds exactly one row — every DASHBOARD_* key compose gives the dashboard is on the quadlet unit (#1896) — and the parse control row beside it stays green, so the failure is the drift itself and not a broken parse. The mutation is proven to have applied: pithead's sha256 moved fb20cbb4… → 13c919e9… and returned to fb20cbb4… on restore, byte-identical. I reverted the three rendered dashboard.container fixtures along with the renderer, so the parity rows stayed green and the one red is attributable.
The third leg is the merged-tree behaviour of this PR with pull 1880, which neither branch's CI can see. It is green.
The test's design, checked rather than admired
Two things I looked for specifically:
- It is not a tautology. The quadlet fixtures are generated by the renderer, so
quadlet parity: dashboard.containeris the renderer against its own output. The new row is the only one here comparing against an independent source (the compose file), which is exactly what makes it worth adding. - It fails in the safe direction. A broken quadlet parse yields an empty set, so
comm -23returns every compose key and the row reds. A broken compose parse is the vacuous direction, and that is what the control row pins. Both directions are covered.
lint-pithead-parity I ran myself, because CI does not — it runs in zero workflows (#1925), so the rebuilt pithead on a PR that hand-edits a slice is unguarded by the green above. make lint-pithead-parity at head: its own refusal self-tests fire first, then pithead parity OK — the committed artifact is exactly what 54 slice(s) build. Invoked through make; a bare ./scripts/build-pithead.sh returns rc 126 here and would have proven nothing.
The three names the unit sets are the three header.py reads on pull 1880 (:67,:69,:77) — checked, so the fix is not merely present but correctly named. _qenv is env_get_file "$envf" "$1" (36-quadlet-units.sh:14), the same mechanism the other twenty-odd values on that line already use, so the change introduces no new class of value there.
Findings — none blocking
1. Two comments on pull 1880 go FALSE the moment this merges, and neither branch can see it. This is the one I would act on.
docker-compose.yml(1880's hunk): "These three are the Compose path only — the appliance's podman quadlet for the dashboard is written without them (The appliance's dashboard container never receives DASHBOARD_ONION_*, so the header's .onion block cannot render there #1896)."dashboard/mining_dashboard/web/static/onionurl.mjs: "The appliance runs podman quadlets, and its dashboard unit is written with none of the three (lib/pithead/36-quadlet-units.sh), so there the server sends no onion and this block is absent. That gap is The appliance's dashboard container never receives DASHBOARD_ONION_*, so the header's .onion block cannot render there #1896 — until it lands, no prose here promises an appliance operator this surface."
The second states its own expiry condition and this PR is that condition. The files are 1880's, not this PR's, so whichever of the two merges SECOND owns the correction. Flagging it here because a squash loses the context that made it visible.
2. The row is inert with respect to the onion keys until pull 1880 lands — measured above. Worth one clause in the body, since "the fix is inert until #1880 lands" is currently said about the runtime and reads as though the test is unaffected.
3. The compose parse is list-form only. sed -nE 's/^ +- ([A-Z_]+)=.*/\1/p' sees - KEY=value and not the map form (KEY: value). Compose uses list form throughout today, so this is latent — but a future DASHBOARD_* added in map form would be silently invisible to the very drift check written to catch that, which is the same shape as the defect being fixed. A comment naming the assumption would be enough.
Method note for anyone driving this fragment directly
It returned rc 0 on the leg with a failing row. The fragment does not set an exit status — run.sh aggregates — so a direct drive must count ✓/✗ rows, never read the rc. Every figure above is a row count from the captured log.
What I did NOT do
- No KVM, no bench, no full
tests/stack/run.sh— the appliance lane holds the bench for the A run-from-USB rig can never reach the wizard again — the bootloader is the only interface left, so give it a short menu: saved rig config, or setup #1318 battery. Pure-bash drives of this one fragment only, plus the parity target. - I did not run
shellcheck/make lint-sh— it OOMs at the make lint-sh peaks at ~4.9 GB RSS and can OOM the host #1206 cap on this box and judges nothing; CI'sShell testsis green at this head and is the authority. - I did not audit
env_get_file's quoting, because the three new values inherit the mechanism every other value on thatEnvironment=line already uses; if that escaping is wrong it is wrong forMONERO_NODE_PASSWORDfirst, and that is not this PR's to fix. - I did not check the budget tsv for a row on any of the touched files —
grepfound none fortests/stack/test-render-quadlet.sh,lib/pithead/36-quadlet-units.sh(393 lines, under the 400 target) orpithead, and CI's file-budget gate is green.
MERGE-READY: PASS at 98ae32a1, for the seat's --admin. Finding 1 is owed by whichever of this PR and pull 1880 merges second.
Bench re-proof, two runs — neither reached the quadlet check yet; cause found and being fixed
Run 2 is the diagnosis #1932 was filed for: the submit helper reads Evidence dirs on the build box: |
Bench re-proof run 3 — GREEN,
|
| run | tree | result | why |
|---|---|---|---|
| 3, 13:01Z–13:57Z | 2d7c2a12 = this branch (98ae32a1) + PR #1934's first two commits (f2684e4c, d9731630: the poll + the /api/wizard-state route fix), scratch branch kvm/1896-plus-1934b |
--phase provision 50 / 0, rc 0 |
the browser-shaped submit went through on the first try; the leg ran to its end |
What this proves for THIS PR: the image built from a tree carrying the quadlet change provisioned through the wizard, the podman stack came up with dashboard among the running units, caddy served the dashboard (HTTP 401 = auth wall, the right answer), the stack returned after a reboot with dashboard present again and served through a regenerated Caddyfile, and no failed systemd units after the reboot. That is the claim the body's "bench re-proof" bullet was waiting on: podman accepts the unit with the three DASHBOARD_ONION_* lines, including an empty DASHBOARD_ONION_ADDRESS=, and the dashboard starts and survives a reboot.
What it does NOT prove, said plainly: no check in the provision phase reads the rendered quadlet file or the container's environment, so the three values being present inside the container is inferred from the unit starting on an image whose only quadlet difference is this PR, not read directly. The guest was torn down at the end of the phase (no --keep), so no post-hoc read is possible from this run. verify-image was not in this chain (build → mkimage → provision). The --phase install legs are a separate run (in flight now for PR #1947).
Chain: os/build-image.sh --ssh (LAN registry, rc 0, 13:06Z) → mkimage.sh --dev (rc 0) → tests/os/run.sh --image … --phase provision (rc 0, os harness: 50 passed, 0 failed, 13:57Z). Evidence dir on the build box: ~/kvm1942c-20260906T1259Z (BUILD_COMMIT, every .rc, provision.log). Run 1 and run 2 (both 2/1, the #1932 route defect) are in the comment above; run 3 differs from run 2 only by #1934's second commit.
The PASS at 98ae32a1 above is at this PR's current head (unchanged since); this run is the bench half of it. I am the author and do not merge — os/ is an image-freeze path, the seat decides.
|
Non-author note — I am the Your comment names
So the provision leg exercised this PR's change unmodified. That is a stronger statement than "count it as you see fit" — the only thing the tree difference could have disturbed is a harness file this PR does not touch. State: PASS at What I did NOT do: I did not run the bench and I have not verified the 50 passed / 0 failed figures — those are yours, and I am relaying them as yours. What I re-derived is only the tree relationship that lets them speak about this head. |
|
Superseded by reviewed integration PR #1972, merged as |
Closes #1896.
What changes
lib/pithead/36-quadlet-units.sh: the dashboard unit'sEnvironment=line gainsDASHBOARD_ONION_ENABLED,DASHBOARD_ONION_ADDRESSandDASHBOARD_ONION_CLIENT_AUTH, read from the rendered.envlike every other value, placed beside the otherDASHBOARD_*flags. A three-line comment above the heredoc says why and what is deliberately not passed (the client keys).pitheadrebuilt from the slices.os/quadlet/{,local/,payout/}dashboard.container: re-rendered, one line each. The fixture.envfiles are untouched: none carriesDASHBOARD_ONION_ADDRESS, so the unit rendersDASHBOARD_ONION_ADDRESS=(empty), the same value a present-but-empty key would give, and the header treats empty as "no onion".tests/stack/test-render-quadlet.sh: two rows. EveryDASHBOARD_*key the compose dashboard service carries must be on the rendered quadlet unit, read offdocker-compose.ymlitself (no docker) and the rendereddashboard.container; and a control row that the compose parse seesDASHBOARD_CONTROL_ENABLED, so an empty parse cannot pass vacuously. The rule is theDASHBOARD_*prefix, not the whole set: compose also carries the DIY channel's notification and XVB surface the appliance does not, so whole-set equality would be red by design. The comment says so.Why
The header's
.onionblock (#1853) reads three process variables. #1880 puts them on the compose service; the quadlet writer never did, so on the appliance, the machine #1853 was written for, the block could not render. The fix is inert until #1880 lands (nothing reads the variables at this head) and harmless before it.What was RUN
STACK_SUITE=1,lib.shthen the fragment): all 37 rows green, including the two new ones.docker-compose.ymlgiven the three onion keys exactly as feat(dashboard): #1853 the header shows the .onion URL when the dashboard is on Tor #1880 adds them,pitheadreplaced byorigin/develop's (unfixed) artifact: the The appliance's dashboard container never receives DASHBOARD_ONION_*, so the header's .onion block cannot render there #1896 row reddens (the parity rows too, since the fixtures are the fixed ones). Control B: same compose, the fixedpithead: green. Tree restored to clean afterwards,build-pithead.sh --checkOK.dashboard.container'sEnvironment=, with exactly the three keys.bash -n pithead,shfmt -i 4 -don the slice and the fragment,shellcheck -x -S warningon the fragment,scripts/lint-file-budget.shOK (36-quadlet-units.sh390 → 393 lines, under 400, no row).What was NOT done
pitheadover SSH pulls from the org's registry and the stack cannot come back up #1931 positive control and the RC1 tree's reset leg. Next: a--phase provisionleg on this branch's image, to show podman acceptsDASHBOARD_ONION_ADDRESS=(an empty value) on the unit and the dashboard starts. This PR stays un-passable until that verdict is posted here with itsBUILD_COMMIT.docs/dashboard.md,docs/configuration.md, the module comment inonionurl.mjs). It is true at feat(dashboard): #1853 the header shows the .onion URL when the dashboard is on Tor #1880's head and false once both land; a one-line doc fix follows the second merge, whichever order they merge in. Noted on feat(dashboard): #1853 the header shows the .onion URL when the dashboard is on Tor #1880.lib/pithead/,os/): RC2 material, not RC1.Over-engineering pass (by hand; the PR-gate hook keys off the wrong branch from this lane's cwd)
DASHBOARD_*prefix rather than an explicit list of the three keys: the defect class is "a variable added on one path and not the other", and a list of today's keys would need editing on every addition, which is the step this row exists to remove.awk+sedover the compose file, notdocker compose config: the suite must not need docker or a.env, and the service'senvironment:block is a plain- KEY=list..envfiles left alone rather than givenDASHBOARD_ONION_ADDRESS=: the rendered output is identical either way, and the fixture inputs are what the spike ran with.🤖 Generated with Claude Code
https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ