feat(wifi): persist the setup hotspot password per device; let the clone warning print - #54
Merged
Conversation
…one warning print Ports litclock-dev#620/#639 and litclock-dev#649 together — both touch scripts/prepare-for-cloning.sh, so splitting them would mean porting a conflict. litclock-dev#620 — create_hotspot() minted a fresh 8-char password every provisioning cycle and persisted it nowhere while the SSID stayed LitClock-Setup, so a phone that had joined once held a saved entry whose credential no longer worked. Fires on ordinary paths: a WiFi reset after moving house, or a power cut part-way through setup. The password is now read-or-created once from /var/lib/litclock/hotspot-password (0600, atomic, fsynced) and reused. Persistence is best-effort — an unreadable, malformed or unwritable file regenerates and logs, because provisioning must never be blocked by its own bookkeeping. O_NOFOLLOW|O_NONBLOCK on read. It survives a plain factory reset and a WiFi reset deliberately; gift mode and prepare-for-cloning.sh rotate it, both failing closed. Also closes the related leak: nmcli takes credentials as space-separated argv tokens, which the redaction filter never matched (it required PASSWORD=), so sudo's command-audit line carried the PSK into persistent journald and out through the diagnostics bundle. litclock-dev#649 — under the script's `set -e`, a genuinely failing `rm` terminated prepare-for-cloning.sh ON the rm line, so the check below it never ran and the three red "Do NOT clone this card" lines never printed. The warning written to stop a key-reuse mistake was unreachable in exactly the situation it was written for. Port notes: - scripts/reset-setup.sh was hand-merged, not copied. 84 lines of the litclock-dev#528 SSH-off gate exist only on this repo and a wholesale copy would destroy them. - The CHANGELOG entry carries litclock-dev#648's corrected Android measurement directly. Porting the original text and then the correction would have this repo publish a claim it never made and then retract it. - 13 bare dev#NNN references requalified to litclock-dev#NNN. One ordering decision the two upstream changes did not settle between them, because they landed on separate repos and meet for the first time here: in the gift block the hotspot-password rotation now runs BEFORE disable_ssh_for_handoff. The rotation fails closed and can exit 1, which leaves the device with its CURRENT owner; disabling SSH first would strip that owner's remote access on the exact path where they still need it to diagnose a read-only card. Guarded by a new test, verified to fail when the order is inverted — all 63 pre-existing tests passed with it inverted.
kapoorankush
added a commit
that referenced
this pull request
Aug 16, 2026
…fixes (#56) Ports the two pre-RC blockers fixed upstream in litclock-dev#668 and #669. Both were found by review of the port PRs that brought #620 and #645 here, so this closes the loop on that train. litclock-dev#660 — the PWA "Factory reset" (litclock-reset.service runs `--wipe-wifi --strict-env-wipe --poweroff --yes`) did not rotate the setup-WiFi key that #620 had just made permanent. Because that path wipes WiFi, the next power-on raises the setup hotspot broadcasting LitClock-Setup with the PREVIOUS owner's key, permanently, surviving every reset the new owner later performs — while the card promises "Erases everything". v0.223.0 had no such leak, so this was a regression #620 introduced. The discriminator is `--wipe-wifi` AND the power-off, not either alone: a hand-run `--poweroff` never raises a hotspot, and `--wipe-wifi --reboot` is #620's same-owner WiFi reset, which must preserve. litclock-dev#667 — #645's link-local blanking removed the address from the same-IP short-circuit's coverage, so every dhcp4-change on a clock that cannot get a lease fell through to a full e-ink render (3 renders measured for 3 identical events). Review found the same hole for a genuinely empty address, which NM's defaults make at least as likely. Coalescing now keys on the observed address in its own tmpfs marker, while the blanked value stays the only thing diagnostics sees. Port notes: - `scripts/reset-setup.sh` HAND-MERGED. This repo has the litclock-dev#528/#636 SSH-off gate, which does not exist upstream at all — it was authored here in #52/#53 — so upstream's `--poweroff` arm has nothing to order against. The rotation call is placed BEFORE `disable_ssh_for_handoff`, matching the gift arm and for the same reason: the rotation fails closed and can exit 1, leaving the device with its current owner, who then still needs SSH to diagnose a read-only card. Verified after merging: the gate body is byte-identical to master and all four call sites survive. - `CLAUDE.md` deliberately NOT ported. The upstream half rewrites a #620 QA block this repo does not have (`grep -c 620 CLAUDE.md` → 0), so applying it would invent that block here. Same phantom-conflict trap as the #641/#648 half of the previous train. - `tests/test_reset_setup_sh.py` hand-merged per hunk rather than wholesale: the conflict replaced the old `_extract_block` helper with the new harness, but a whole-file resolution would have discarded this repo's SSH-gate tests. All twelve survive. - 4 bare dev#NNN references requalified. - Verified nothing was dropped: of every line the two upstream commits added, none is missing here. `test_gift_mode_rotates_hotspot_password_before_disabling_ssh` — added when the two features first met in #54 — is now parametrised over BOTH handoff arms, since #660 gave the `--poweroff` arm the same pairing and nothing covered it. Its anchor is also fixed: it matched the function name in the COMMENT above the call, so inverting the order in the new arm kept it green. Mutation-verified in both arms after the fix. Testing: - ruff + shellcheck clean - pytest: 3392 passed, 62 skipped - npm run test:js: 191 passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports litclock-dev#620/#639 and litclock-dev#649 from the private dev repo. First of two port PRs for v0.224.0. They ship together because both touch
scripts/prepare-for-cloning.sh— splitting them would mean porting a conflict.What changes
litclock-dev#620 — the setup network's password is now fixed per device.
create_hotspot()minted a fresh 8-character password every provisioning cycle and persisted it nowhere, while the SSID stayedLitClock-Setup. A phone that had joined once held a saved entry whose credential no longer worked. It fires on ordinary paths: a WiFi reset after moving house, or a power cut part-way through setup.The password is now read-or-created once from
/var/lib/litclock/hotspot-password(0600, atomic, fsynced) and reused. Persistence is best-effort by design — an unreadable, malformed or unwritable file regenerates and logs, because provisioning must never be blocked by its own bookkeeping.O_NOFOLLOW|O_NONBLOCKon read. It survives a plain factory reset and a WiFi reset deliberately; gift mode andprepare-for-cloning.shrotate it, both failing closed.A related leak closes with it: nmcli takes credentials as space-separated argv tokens, which the support-bundle redaction filter never matched (it required
PASSWORD=), so sudo's command-audit line carried the PSK into persistent journald and out through the diagnostics bundle. Harmless while the value expired each cycle; not harmless once it is permanent.litclock-dev#649 — the "do NOT clone this card" warning can now actually print. Under the script's
set -e, a genuinely failingrmterminatedprepare-for-cloning.shon the rm line, so the check below it never ran and none of its three red lines printed. The warning written to stop a key-reuse mistake was unreachable in exactly the situation it was written for.Port notes
scripts/reset-setup.shwas hand-merged, not copied. 84 lines of the litclock-dev#528 SSH-off gate exist only on this repo; a wholesale copy would destroy them. Verified after merging: the gate body is byte-identical to master and all four call sites survive.dev#NNNreferences requalified tolitclock-dev#NNN.One ordering decision
litclock-dev#620 and litclock-dev#528 never constrained each other — they landed on separate repos and meet for the first time here. In the gift block the hotspot-password rotation now runs before
disable_ssh_for_handoff. The rotation fails closed and canexit 1, which leaves the device with its current owner; disabling SSH first would strip that owner's remote access on the exact path where they still need it to diagnose a read-only card. SSH-off is the last thing before poweroff.This is guarded by a new test, mutation-verified: with the order inverted, all 63 pre-existing tests still passed.
Testing
ruff check src/ image-gen/ tests/ scripts/— cleanshellcheck --severity=warningon both changed scripts — cleanpytest— 3252 passed, 62 skipped (all skips pre-existing, in files this PR does not touch; zero skips in the four test files it does)npm run test:js— 191 passed (no JS in this PR)Follow-ups filed, not fixed here
Review surfaced several issues that are present identically on dev, so they are being fixed upstream and ported rather than patched here — patching only this repo would add to the forward divergence that already exists. The handoff-path key-rotation gap among them is scheduled to land before the v0.224.0 RC is built.