You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
authored
fix(restore): preserve archived dashboard auth and prove every secret category (#2585)
* fix(restore): preserve archived dashboard auth
* fix(restore): trust dashboard auth by fingerprint, not a Caddy probe
Restore now keeps the archived dashboard hash under the render path's own
rule: only while its fingerprint matches dashboard.auth.password. A stale
pair is dropped and rehashed instead of refusing the archive. The Caddy
container probe added no protection (the archive also carries the
password) and failed wherever Compose or Docker was unavailable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(gitleaks): ignore superseded #2579 restore fixtures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(restore): validate the archive against its own environment
The read-only validation read the live .env, so an archived dashboard
password that differed from the live render forced a Caddy rehash (and
Docker) before the stage was even canonicalized.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(restore): say when the validation step would rehash
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(integration): prove every secret category across the lifecycle restore
The lifecycle backup -> restore round-trip compared only the proxy token
and onion addresses, so a restore that rehashed the dashboard login
passed it and surfaced only when a failing run's rollback made the
per-category check (#2579). Compare the same wallet, proxy, dashboard,
RPC and onion fingerprints the rollback does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(restore): shape-check the kept dashboard hash; assert every kept secret
Reviewers found openssl accepts a valid bcrypt base64 followed by junk,
and the hermetic test sampled one key per category. Require exactly 80
base64 characters, compare every kept key byte-for-byte, cover an auto
stratum password, and document the refusal and the archive trust model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(restore): keep a matching archived dashboard hash through the wizard too
Operator ruling on #2579 over #2231: restore keeps the archived dashboard
hash and fingerprint exactly while the fingerprint matches the restored
password, and regenerates a stale pair. A hash that is not well-formed
bcrypt (an older release's, or a damaged one) is now dropped and
regenerated instead of refusing the archive. The appliance restore rows
and the KVM restore leg assert the archived hash survives, which is what
an archive pithead produced authenticates with.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(restore): the restore leg keeps the archived dashboard hash
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(restore): name the malformed-hash rows literally
The suite refuses PASS labels that interpolate an unreviewed value; the
loop's $cr_bad label tripped it in Shell CI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: pithead-developer[bot] <4933481+pithead-developer[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: pithead-developer[bot] <pithead-developer[bot]@users.noreply.github.com>
Copy file name to clipboardExpand all lines: docs/dev/appliance-release.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -306,7 +306,7 @@ is the only thing standing between the hand-written boot path and a fleet.
306
306
|`boot`| EFI boot to userspace; first-boot wizard announces itself with a console token; wizard serves the token gate on `:80`; machine-id is STABLE across a plain reboot (#895 — an empty-baked id with no restore mechanism regenerates on every boot, worse than the bug it fixes) | 4 |
307
307
| `update` | `/data` grew to the disk and slots did not (#784); bundle installs into the spare; spare boots; **an uncommitted update reverts on reboot**; a committed update persists; **after the commit, the page served comes from the NEW dashboard image** (marker baked into the image and read back over HTTP — the tag never changes, so "containers run" proves nothing about staleness, #798); an operator can roll back off a committed version; **host identity (SSH host-key fingerprint, machine-id) survives the A/B swap** (#894/#895 — both live on `/data`, untouched by a slot swap). Then leg 4, **the dashboard OS-update action end-to-end** (#976): the machine is provisioned through the wizard's real HTTP flow, the release lookup is pointed at a bench-local server through the root-owned test seam, and the flow runs check → download (a pre-staged partial must RESUME, not restart) → verify — with the `/data`-floor and **bad-signature refusals proven against RAUC's real keyring**, refused bundles deleted — → install (in-flight flag armed, state says reboot-pending — **and the header badge carries reboot-pending plus the target version, so a reboot stays owed even with the modal closed** — nothing auto-reboots) → the explicit reboot intent → the new slot boots, the health gate commits, and the persisted **"updated" verdict reaches `/api/state`** | 15 + leg 4 |
308
308
| `provision` | A config submitted through the wizard's real HTTP flow provisions the stack: validation, cosign-verified image pulls, containers running under podman, dashboard served through caddy. Before the successful submission, an unreachable remote node must be refused by preflight with its safe form values retained; a separate injected post-validation setup fault must return to the setup page with a useful error and the retained values for an ordinary corrected submit (#1955). The submitted config also enables the on-box miner, so the **built-in RigForge worker** must come up on its own, wired to the machine's own stratum, with mining held behind the sync gate cleanly. After provisioning, dashboard control applies a benign setting, refuses a disruptive setting without `APPLY`, accepts it with `APPLY`, returns doctor and log-tail reports, and creates a downloadable encrypted backup; the stack and dashboard must recover after that backup (#1931/#1965). Then a **reboot with no hands on it** must return the stack unaided through `pithead-boot`. M10 cuts that live stack three times and after EVERY cut requires every pre-cut container, image digest, non-regressing monerod height, miner, Caddy, and committed slot to survive. Finally the **commit gate's honesty** (#852) requires the real `pithead doctor --json` gate to PASS on the healthy still-syncing stack yet REFUSE once a revenue service is crashed. The provisioned boot also pins hugepages sizing, migration and floor-fallback behavior, and a Tari node that dies after the migrating slot commits: status, doctor and the dashboard must report it, and `./pithead up` must recover it. | Printed by the run |
309
-
| `install` | The image boots as **removable** media (usb bus — the gate keys on it); the inventory offers the internal disk and never the boot medium; the real installer runs; the machine then boots from the target alone with a **complete** copy (`/var/lib/dpkg` — the overlay made an incomplete copy easy and invisible), a fresh machine-id, `/data` sized to the target, and the wizard serving. Then the **reinstall leg** plants a sentinel in `/data`, installs over the same disk, and requires the sentinel afterwards. The keep leg reinstalls from a **newer stick** over `/data` that holds the old dashboard image and digest record; the image ID and served page must change (#798). A 1.x `xmrig_proxy` pre-fill must migrate to `xvb` without carrying the removed key (#1954). The restore leg uploads the checked-in encrypted v1.20.0 fixture to an existing appliance disk, then proves the running stack carries its wallet, Tor identity, opaque RPC/onion secrets, and both fixture and target chain-data sentinels without a resync; its dashboard password survives while the bcrypt and fingerprint are regenerated and authenticate with it (#2001/#2230). | 34 |
309
+
| `install` | The image boots as **removable** media (usb bus — the gate keys on it); the inventory offers the internal disk and never the boot medium; the real installer runs; the machine then boots from the target alone with a **complete** copy (`/var/lib/dpkg` — the overlay made an incomplete copy easy and invisible), a fresh machine-id, `/data` sized to the target, and the wizard serving. Then the **reinstall leg** plants a sentinel in `/data`, installs over the same disk, and requires the sentinel afterwards. The keep leg reinstalls from a **newer stick** over `/data` that holds the old dashboard image and digest record; the image ID and served page must change (#798). A 1.x `xmrig_proxy` pre-fill must migrate to `xvb` without carrying the removed key (#1954). The restore leg uploads the checked-in encrypted v1.20.0 fixture to an existing appliance disk, then proves the running stack carries its wallet, Tor identity, opaque RPC/onion secrets, and both fixture and target chain-data sentinels without a resync; its dashboard password survives with the archived bcrypt and fingerprint kept exactly, and authenticates with them (#2001/#2230/#2579). | 34 |
310
310
|`rig`| The removable image installs the RigForge role, which mines from the baked binary with no stack containers and follows the A/B update contract. M13 cuts power while it mines, then requires a new boot, unattended mining, and the committed slot to return. | Printed by the run |
311
311
|`media`| The physical-presence config stick shows the exact diff, applies after its countdown, is consumed, and cancels when removed mid-countdown. | Printed by the run |
312
312
|`fault`| three power cuts mid-write; a deliberately corrupted bundle is refused without crashing and without bricking; a power cut inside the commit window; operator rollback after all of it; Fault D cuts an active first-boot baked-image load and requires the wizard and repaired image store afterwards; the box is still updatable afterwards | 15 |
Copy file name to clipboardExpand all lines: docs/dev/appliance-wizard.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -246,7 +246,8 @@ the same "validate before mutating real state" idiom `consume_preseed_config` al
246
246
under a later release's new-configuration preflight policy.
247
247
4. Regenerate `.env` and `Caddyfile` from the validated configuration, retaining only opaque
248
248
generated secrets and Tor identity from the archived environment. The restored dashboard
249
-
password remains in `config.json`; its bcrypt hash and fingerprint are regenerated from it.
249
+
password remains in `config.json`; its archived bcrypt hash and fingerprint are kept exactly
250
+
while the fingerprint matches it and the hash is well-formed, and are regenerated from it otherwise.
250
251
Only on success: install the configuration files at mode `0600`, apply the accepted data
251
252
trees, and publish `applied`. `data/tor` and `data/dashboard` (identity and the dashboard
252
253
database) replace whatever is already there outright. `data/{monero,tari,p2pool}` — optional,
@@ -471,7 +472,7 @@ had a gap between it and the next one.
471
472
| the artifact |`tests/os/verify-image.sh`| both role paths present in the shipped image: the boot script's fork, the unit conditions that admit each role, the baked prebuilt, no swap anywhere |
472
473
| the real thing |`tests/os/run.sh --phase provision`| token from the console → submit → handoff → ack → running stack → built-in miner up and its shares accepted → reboot through a corrupted Caddyfile → no failed units → slot self-commit → miner back |
473
474
| the other real thing |`tests/os/run.sh --phase rig`| the same page answered `RigForge` → rig card with no login → mining from the byte-identical baked binary → **no containers at all** → reboot owned by `pithead-boot`, wizard closed → slot self-commit on an unanswered pool → A/B install, uncommitted rollback, self-commit, persistence |
474
-
| the restore leg |`tests/os/run.sh --phase install`| a checked-in encrypted v1.20.0 fixture generated from the signed compose bundle, uploaded through `/submit-restore` instead of the form onto an existing appliance disk — the running wallet, Tor identity and opaque RPC/onion secrets must match the prior-release fixture, the dashboard hash and fingerprint must be regenerated from the preserved password and authenticate with it, and both its chain sentinel and the target's pre-restore sentinel must survive |
475
+
| the restore leg |`tests/os/run.sh --phase install`| a checked-in encrypted v1.20.0 fixture generated from the signed compose bundle, uploaded through `/submit-restore` instead of the form onto an existing appliance disk — the running wallet, Tor identity and opaque RPC/onion secrets must match the prior-release fixture, the dashboard hash and fingerprint follow the rule in step 4 of the restore flow (kept while the fingerprint matches the preserved password), and both its chain sentinel and the target's pre-restore sentinel must survive |
475
476
476
477
The orchestration row is the one that was missing. pytest proved the endpoint published the
477
478
credentials; a render probe proved the card renders given them; nothing proved the app *asked*.
Copy file name to clipboardExpand all lines: docs/dev/integration-testing.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -224,7 +224,7 @@ Useful flags (full list in `run.sh --help`):
224
224
|`--remote-monero-host <h>`| Bare host or IP for the external Monero node used by the `remote` scenario. Pair it with `--remote-monero-rpc-port` or `--remote-monero-zmq-port` when the node does not use ports 18081 and 18083. `e2e.sh` accepts the same flags and carries them through its read-only pregate and detached harness run. |
225
225
|`--remote-tari-host <h>`| Bare host or IPv4 address for the external Tari node used by the `tari.mode=remote` scenario ([#103](https://github.com/p2pool-starter-stack/pithead/issues/103)). Pithead renders `tari.remote.grpc_port` separately; `e2e.sh` accepts and forwards the host. |
226
226
|`--pruned-data-dir` / `--full-data-dir`| Synced alt DB to enable the opposite prune mode. |
227
-
|`--lifecycle`| Also run the lifecycle phase (restart, apply secret-preservation, backup→restore, then uninstall→setup). Restore command, health, unreadable verification input, or failed restored pool/secrets assertion prevents later fault injection; peer-timing pool warnings remain non-fatal. |
227
+
|`--lifecycle`| Also run the lifecycle phase (restart, apply secret-preservation, backup→restore, which must keep every wallet, proxy, dashboard, RPC, and onion secret category exact (the onion key reads need passwordless sudo), then uninstall→setup). Restore command, health, unreadable verification input, or failed restored pool/secrets assertion prevents later fault injection; peer-timing pool warnings remain non-fatal. |
228
228
|`--fault-injection`| Also break monerod (stop / SIGSTOP / remove) and assert `status`' down/unhealthy/missing verdicts and the failover→recovery cycle, plus a dashboard DB-write fault (data dir made read-only → `/api/state` reports `db_healthy:false` → write access restored, [#202](https://github.com/p2pool-starter-stack/pithead/issues/202)). Destructive-then-restored; SSH or local; slow. The implementation uses the shared target wrapper, but a recorded SSH fault run is still tracked by [#2000](https://github.com/p2pool-starter-stack/pithead/issues/2000). |
229
229
|`--image-upgrade <old-sha> <new-sha>`| Run the supported `pithead upgrade` path and prove old/new image identities, exact persistent mount sources, Monero/Tari captured-prefix anchors and non-regressing heights, durable dashboard table continuity, categorized secrets, returning workers, and resumed hashes. Prefix continuity does not claim that no same-chain bytes were re-downloaded. Requires exact lowercase 40-hex commits, `--candidate-bundle`, `--safety-backup`, and successful private reflink snapshots of every enumerated persistent mount while writers are stopped; no upgrade starts if any trust, backup, derived-state fingerprint, or snapshot check fails. |
230
230
|`--candidate-bundle <tar.gz> <sig> <trusted-cosign.pub>`| Name the private candidate, detached signature, and externally anchored public key. All are absolute local paths; the signed archive's `PITHEAD_COMMIT` must equal `<new-sha>`. Before staging, the harness uses private snapshots to verify the bundle signature and key continuity, requires every Compose image to be digest-pinned, and verifies the five unique Pithead-built images' signatures and exact OCI revisions. Candidate-provided trust roots are rejected. |
@@ -566,6 +566,8 @@ and `--list` prints it).
566
566
For one representative config:
567
567
568
568
-`restart` brings the stack back healthy (`status` → `0`), and backup → restore must do the same before a later fault-injection phase can run.
569
+
The restore must also return every wallet, proxy, dashboard, RPC, and onion secret category
570
+
exactly, the same per-category comparison a safety rollback makes.
569
571
- An `apply` that changes the sidechain recreates only the affected containers and preserves
570
572
secrets; the dashboard reflects the new pool; then it's reverted.
0 commit comments