7.3 KiB
Public application update owner
The public profile updates the backend, dashboard and their packaged runtime
helpers. It does not run a Debian/kernel transaction. The original root-signed
manifest must authorize application_update: {version: 2, mode: "signed-application-runtime"} and exactly the backend/frontend component pair.
Existing or partial D09/C01 ownership always selects its protected admission
route; missing managed file descriptors or failed transport cannot authorize a
public fallback. The earlier version 1 pair-only policy remains recognized for
compatibility and focused transaction qualification.
Transaction boundary
The installed root-owned verifier authenticates the original signed JSON. Root
preparation copies the authenticated downloads into immutable snapshots, then
submission installs the current owner's embedded Python as an independent PID1
worker. Its journal and snapshots live below
/var/lib/archipelago-public-transactions. The manager's response acknowledges
submission; it does not acknowledge successful installation.
Before replacement the worker verifies the running manager, stops it, drains
maintenance timers, freezes both code preimages and the fixed runtime domain
listed in scripts/public_update_runtime.py, and records their identities.
The domain contains application code, radio helpers, relevant systemd units,
existing nginx routing, and the public-runtime ownership record. Optional kiosk,
audio and gamepad helpers remain absent when the previous installation omitted
them. The worker preserves service enablement decisions and restores only timers
that were already active. An in-flight maintenance job refuses the attempt.
The incoming dashboard retains the existing validated nginx routing template. A root-owned record binds the executing backend hash and release version to the transaction. On that startup, the backend skips the older nontransactional host runtime promotion and repair routines. Normal application startup, container reconciliation and API behavior continue after the code owner releases startup.
All code mutation follows durable preparation. Boot recovery is durably linked before
the transaction becomes current. It restores interrupted transactions before the
manager, doctor and NPM maintenance units can start. A submission interrupted
before publishing its pointer can recover only while its original predecessor
remains the current completed transaction. Old recovery units cannot operate on
a newer transaction. Retried upgrades after rollback reuse only the previous
owner's exact known service dependencies. Every forward or
reverse replacement checks the complete installed and saved identity domains;
foreign edits and corrupt preimages are retained for reconciliation. Success
requires the actual /proc/PID/exe hash, backend health/version, exact served
frontend entry bytes, and exact installed code/runtime identities. Recovery is
resumable and must verify the old running pair before reporting restored.
The manager namespace hides the frontend's runtime bootstrap input after a runtime transaction. This prevents an authenticated historical binary from repromoting that payload on rollback and deleting restored private app sources. The host owner and nginx retain the complete authenticated frontend. A durable transaction marker also holds maintenance services and timers while startup is being verified. If the historical binary re-enables a previously disabled timer, recovery removes only its exact standard enablement link before releasing the pause; foreign links remain a reconciliation error.
No worker destination is a wallet, container store, app data directory or install/uninstall/stopped record. The application prefix directory itself gains root custody so an application cannot rename installation paths; this does not recursively change ownership of its children or data. Original downloads, retired trees and failures remain available; there is no evidence garbage collection.
First upgrade from a historical installer
An older updater such as 1.9.1 replaces its code pair, writes the pending
verification marker, deletes staging, then restarts the newly installed daemon.
The successor handles that marker before host bootstrap or application recovery.
It remains in Type=notify startup, extending the bounded startup timeout while
an independent root operation admits the migration.
That operation fetches manifest.json and legacy-manifest-VERSION.json from
the successor's fixed HTTPS release attachment origin. Both original documents
must verify under the pinned release root. It fetches the signed incoming
artifacts again because historical staging no longer exists. The historical
backend backup must match its old signed artifact; the frontend backup must
match its signed archive, allowing only the documented existing routing-template
mutation and Companion bytes already present in the signed incoming release.
Signed old static extras are preserved when the new archive does not supersede
them. Arbitrary backup scripts are never accepted as executable recovery input.
The installed incoming pair is checked against those authenticated images before queuing the same runtime transaction. Its frozen old pair can be restored even if the worker dies before runtime preparation. The exact pending marker is retained inside the transaction on completion or recovery. A changed marker is not deleted. Interruption before the independent owner has admitted the upgrade still belongs to the historical installer's recovery behavior; that limitation must be tested with the actual older artifact, not inferred from worker fixtures.
The new installed binary is already the older authenticated installer's chosen executable. This startup handoff is distinct from executing an uninstalled candidate to establish its own verification authority. Publication uses a separate appointed verifier and clean build receipt.
Protected and custom installations
The fixed public layout is /usr/local/bin/archipelago,
/opt/archipelago/web-ui, User=archipelago, no extra ExecStart arguments,
backend port 5678 and dashboard port 80. Custom layouts, special preimage inodes
and unresolved foreign ownership refuse conversion. Protected nodes retain their
release-bound D09/OS plans and admission; a generic public manifest does not
replace that authorization.
A source bundle can also be emitted with
python3 scripts/package-public-update-worker.py --output /new/output/worker.py.
This creates no installation or authority. A separately reviewed root-custody
installation is required before an older client without the successor's dispatch
can invoke its fixed apply command with original signed JSON on stdin. No
frontend-downloaded bundle is automatically executed as root. Status and manual
recovery stay available through the independent retained worker after rollback.
Qualification
The PID1 fixture uses a private disconnected container store without host mounts.
It retains each frozen attempt, journal and transaction. It has explicit fixture
ELF, signature and download doubles, and exercises real service replacement,
runtime files, startup handoff, crash boundaries and a full PID1 restart. These
results do not qualify a production signature, actual product startup, public
availability or physical acceptance. Those gates require their own evidence.
Backend tests run through scripts/test-backend-isolated.sh.