Skip to content

Upgrade the installation volume in init - #711

Merged
SaladDay merged 2 commits into
mainfrom
upgrade-the-installation-volume-in-init
Oct 15, 2026
Merged

SaladDay merged 2 commits into
mainfrom
upgrade-the-installation-volume-in-init

Conversation

@SaladDay

@SaladDay SaladDay commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

What

Lane A of in-place upgrades: the volume and schema side, plus a CI upgrade smoke.

  • oac init (services/core/cmd/oac/init.go) upgrades an existing volume in place. On an existing receipt it verifies every listed file, then:
    • same release: rewrites active.json from the receipt, syncs the Core key digest, done (no fetch, no copy);
    • another release: reads source_time from the installed release's receipt-verified manifest.json (absent = 0) and refuses unless its own is newer;
    • publishes its own release under node-payload/releases/<own>/, keeps the old releases, keeps existing secrets and the installation ID;
    • writes the receipt as the single commit point: every retained release's files plus the secrets. active.json is no longer in the receipt; init writes it after the receipt and on every start, so a crash between the two heals.
    • v0.0.10 and v0.0.11 receipts still list active.json: it is verified once and dropped because the new receipt keeps only node-payload/releases/* from the old one. No special case.
  • Core (services/core/migrations/migrations.go) refuses a schema version above its embedded maximum after Up. No goose Down.
  • Web computes the node installer digest per request from the active release instead of caching it at startup. Startup still refuses a payload without a valid pointer and installer.
  • Node manifest gains source_time (the build's existing source_epoch, Unix seconds).
  • Compose smoke builds the checkout's node metadata (manifest, node-install.pyz from the bootstraps builder, the repo's seccomp profile) instead of downloading the 384 MB v0.0.3 archive, and adds an upgrade phase: start v0.0.11 from its published compose.yaml (pinned by tag and SHA-256, OAC_IMAGE_* unset), seed a Project, Project key and file, run the installer preflight with the checkout's images (core check-config, then init), up -d --wait --pull never --remove-orphans, and assert that the installation ID, Core key, Project, Project key and file survive, that Core and /node-install/manifest.json report the checkout's commit, and that the schema is at the latest migration.
  • Maintainer guide (EN + zh, source_hash refreshed): smoke description, manifest source_time, and two release-checklist lines (protocol bumps with operator actions in the release notes; bump the smoke pin after publishing).

Messages

  • Init downgrade: This installation runs release <installed12> (<YYYY-MM-DD HH:MM UTC>); this release, <own12> (<YYYY-MM-DD HH:MM UTC>), is not newer. Downgrades are not supported: run that release or a newer one, or restore the backup taken before the upgrade.
  • Init, receipt names a release whose metadata is missing: installation receipt names release <installed12>, whose node metadata is missing; restore the matching data directory
  • Core schema: Agents API database schema version N is newer than this Core supports (M). Downgrades are not supported: run the release that upgraded this database or a newer one, or restore the backup taken before the upgrade. (Core prefixes its startup error with Agents API database migration failed: .)

Interface for Lane B (installers)

  • Preflight, in this order, with the staged files: docker compose -f <stage>/compose.yaml [-f <dir>/compose.override.yaml] --project-directory <dir> run --rm --no-deps --entrypoint /usr/local/bin/oac-core core check-config, then ... run --rm --no-deps init. The smoke runs exactly these two commands against the v0.0.11 volume.
  • Init exits non-zero on a refused downgrade and changes nothing in the volume (covered by TestInitializeRefusesAnOlderRelease). The message is in init's structured log line Initialization failed (error field) on the container output; oac init does not print errors separately, so the installer should show the run output when it refuses.
  • Init is idempotent: a rerun after an interrupted upgrade finishes it; a rerun of the same release only verifies and rewrites the pointer. The later up runs init again and it only verifies.
  • Init owns only the volume's node-payload. The bundle's host node-payload (Web's bind) stays B's publish/activate.

Verification

  • go test ./services/core/cmd/oac -count=1: pass (new TestInitializeUpgradesAnOlderInstallationInPlace, TestInitializeRefusesAnOlderRelease; the "another release" case is removed with its refusal).
  • go test ./services/core/migrations -count=1 -v with OAC_TEST_DATABASE_URL on a local Postgres 16: TestApplyRefusesANewerSchema pass.
  • go test ./services/web -count=1 -run 'Retained|Console|Paired|Offline': pass (the digest follows active.json without a restart).
  • python3 scripts/core-distribution-manifest.test.py: 23 tests pass (source_time in the node and bundle manifests).
  • go vet ./... (module-wide): clean.
  • python3 scripts/core-distribution-manifest.test.py BundledDocsTests (repo Markdown link guard), node --test website/tests/translations.test.mjs, python3 scripts/check-names.py: pass.
  • python3 scripts/compose-smoke.py locally on amd64: pass in 2m56s, both phases. This host's Docker daemon has no registry DNS, so the v0.0.11 images were preloaded by digest through crane and the images were built with a proxy-enabled buildx builder; CI pulls them directly.

Review

Batched blind review over #710 and #711. The finding for this PR (raw open … error for a missing installed release; refusal text claiming "newer" on equal source_time) is fixed in the second commit.

Skipped

  • Full make check (focused checks only).
  • Blind review: A and B get one batched review per the plan.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Init now upgrades a volume from an older release in place: it verifies the receipt, refuses a release that is not newer by the node manifest's new source_time, publishes its own release beside the old ones, keeps the secrets and installation ID, and writes the receipt as the single commit point. The receipt lists every retained release and no longer pins active.json; init rewrites the pointer from the receipt on every start, so a crash between the two heals. Receipts from v0.0.10 and v0.0.11 upgrade without special code.

Core refuses a database schema newer than its migrations. Web computes the node installer digest per request from the active release. The Compose smoke builds the checkout's node metadata instead of downloading the v0.0.3 archive, and upgrades a published v0.0.11 installation in place to the checkout.
@SaladDay
SaladDay merged commit b603d7e into main Oct 15, 2026
20 checks passed
@SaladDay
SaladDay deleted the upgrade-the-installation-volume-in-init branch October 15, 2026 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant