Add channel-aware release tooling and updater recovery
This commit is contained in:
216
release/README.md
Normal file
216
release/README.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# Manual Gitea launcher releases
|
||||
|
||||
Manual publication mode is currently active. Build a complete local package with:
|
||||
|
||||
```text
|
||||
./scripts/release.sh stable 1.8.0
|
||||
./scripts/release.sh prerelease 1.9.0-beta.1
|
||||
```
|
||||
|
||||
The command creates `dist/v<VERSION>/`, validates the channel/version pair,
|
||||
builds and previews release notes, runs the configured preflight, creates a
|
||||
deterministic archive, signs the manifest, and verifies the complete result. It
|
||||
changes the requested version only inside the staged package, so the source
|
||||
checkout is unchanged. It does not commit, tag, push, create a Gitea release,
|
||||
or upload anything.
|
||||
|
||||
The default archive layout is flat: extracting the tar directly produces
|
||||
`ptlinux/`, `ptdiag/`, and `release.json`. Versions `0.9.10` and `0.9.11` form a
|
||||
short compatibility window and use the legacy enclosing directory. That lets
|
||||
deployed `0.9.8` launchers install either release automatically, even if they
|
||||
missed `0.9.10`. The updater inside both releases accepts both layouts, so
|
||||
releases after `0.9.11` return to the flat layout.
|
||||
|
||||
Use `--description` for short Markdown or `--description-file` for a prepared
|
||||
Markdown document. Generated commit notes are appended by default. Run
|
||||
`./scripts/release.sh --help` for dry-run, editor, changelog-only, signing-key,
|
||||
and test options.
|
||||
|
||||
After a successful build, create a new release in Gitea with tag `v<VERSION>`.
|
||||
Leave **Pre-release** clear for `stable`; select it for `prerelease`. Use
|
||||
`dist/v<VERSION>/release-notes.md` as the description and upload exactly these
|
||||
five files from that version directory:
|
||||
|
||||
- `PristonTale-EU-<VERSION>.tar.gz`
|
||||
- `PristonTale-EU-<VERSION>.tar.gz.sha256`
|
||||
- `manifest.json`
|
||||
- `manifest.json.sig`
|
||||
- `release-notes.md`
|
||||
|
||||
Uploading a release does not by itself move the launcher's channel pointer.
|
||||
Upload and save all five assets before publishing the channel pointer. The
|
||||
signed archive URL includes its SHA-256 as a query parameter so Cloudflare and
|
||||
other CDNs cannot reuse an empty response cached before the attachment existed.
|
||||
After the Gitea release exists and each attachment has a nonzero size, publish
|
||||
the matching files generated under
|
||||
`dist/v<VERSION>/channel/` to the repository:
|
||||
|
||||
```text
|
||||
dist/v<VERSION>/channel/stable.json -> updates/stable.json
|
||||
dist/v<VERSION>/channel/stable.json.sig -> updates/stable.json.sig
|
||||
|
||||
dist/v<VERSION>/channel/prerelease.json -> updates/prerelease.json
|
||||
dist/v<VERSION>/channel/prerelease.json.sig -> updates/prerelease.json.sig
|
||||
```
|
||||
|
||||
Commit and push only the selected channel pair. Publishing a prerelease must not
|
||||
change `updates/stable.json`.
|
||||
|
||||
Manual mode does not rebuild `updates/history.json`. The uploaded version can be
|
||||
discovered through its channel pointer, but it will not appear as a remote
|
||||
rollback candidate until signed history publication is run manually or the
|
||||
automatic publisher is re-enabled.
|
||||
|
||||
The published `0.9.9` release is flat and therefore correctly asks deployed
|
||||
`0.9.8` launchers for a manual update. Releases `0.9.10` and `0.9.11` use the
|
||||
wrapped bridge layout and minimum updater `0.5.0`, so those launchers can install
|
||||
either one automatically. Flat manifests after this compatibility window declare
|
||||
`0.9.9` as their minimum updater version.
|
||||
|
||||
For direct artifact tooling without release notes or manual-upload guidance:
|
||||
|
||||
```text
|
||||
PTLINUX_PYTHON=/path/to/python \
|
||||
./scripts/build-release.sh 0.9.11 stable ./dist/v0.9.11
|
||||
./scripts/verify-release.sh 0.9.11 stable ./dist/v0.9.11
|
||||
```
|
||||
|
||||
The builder selects the runtime payload declared in `config.json`; it does not
|
||||
archive the repository. It emits a deterministic versioned archive, checksum,
|
||||
and external per-file manifest. When `PTLINUX_SIGNING_KEY` is set, it also
|
||||
emits and immediately verifies `manifest.json.sig`.
|
||||
|
||||
`repository_url` is the public HTTPS base used in signed download links.
|
||||
`allowed_git_remotes` is retained for the deferred automatic release validation;
|
||||
it is not consulted by the current local packaging command.
|
||||
|
||||
## Deferred automatic publication
|
||||
|
||||
The automatic publisher and status publisher remain in the repository for a
|
||||
later migration, but their push triggers are disabled. Both workflows are
|
||||
manual-dispatch only, so creating a tag or Gitea release cannot race the manual
|
||||
upload process. The remainder of this section applies only when automatic
|
||||
publication is re-enabled.
|
||||
|
||||
## Repository and runner setup
|
||||
|
||||
Enable Gitea Actions for the instance and repository, then register an isolated
|
||||
Docker-backed `act_runner` for trusted repository workflows. The runner image
|
||||
must provide Git, Bash, curl, jq, Python 3.11 or newer, tar, gzip, coreutils
|
||||
(`base64`, `realpath`, `sha256sum`, and `stat`), and OpenSSL. The release job
|
||||
uses `actions/checkout@v4` and checks that the checked-out commit is the exact
|
||||
commit referenced by the annotated tag.
|
||||
|
||||
Configure these protected Actions secrets:
|
||||
|
||||
- `RELEASE_TOKEN`: a dedicated token with only the repository release and
|
||||
repository-content write permissions needed to create releases, upload
|
||||
attachments, and update signed channel metadata.
|
||||
- `MANIFEST_SIGNING_KEY`: the private key corresponding to
|
||||
`ptlinux/launcher-update-public.pem`.
|
||||
|
||||
Secrets are available only to trusted tag and release-status workflows. Do not
|
||||
add the release secrets to pull-request workflows. The signing key is written under
|
||||
`RUNNER_TEMP` with mode `0600`, used for signing, and removed by an `always()`
|
||||
cleanup step. Shell tracing is intentionally disabled around all secret-bearing
|
||||
commands.
|
||||
|
||||
The current installed population verifies the deployed RSA/SHA-256 public key.
|
||||
Release tooling can use OpenSSL Ed25519 signing, but changing the production
|
||||
key or algorithm requires a staged key-rotation release first. Until that
|
||||
migration is deployed, configure the matching existing RSA private key.
|
||||
|
||||
## Automatic publication behavior
|
||||
|
||||
The workflow validates the tag in CI, runs tests, builds and verifies every
|
||||
artifact, creates a non-draft Gitea release with the derived prerelease flag,
|
||||
and uploads these immutable assets separately:
|
||||
|
||||
- `PristonTale-EU-<version>.tar.gz`
|
||||
- `PristonTale-EU-<version>.tar.gz.sha256`
|
||||
- `manifest.json`
|
||||
- `manifest.json.sig`
|
||||
- `release-notes.md`
|
||||
|
||||
It re-queries the release and checks every attachment name and size before
|
||||
updating metadata. A stable tag updates only `updates/stable.json` and its
|
||||
signature. A beta or RC tag updates only `updates/prerelease.json` and its
|
||||
signature. Both rebuild `updates/history.json` and its signature from real
|
||||
Gitea releases, following API pagination so older rollback entries are retained.
|
||||
The four metadata files are committed atomically through the Gitea contents API.
|
||||
|
||||
Existing release detection is fail-closed. Never delete, move, or reuse a tag
|
||||
after publication. Fix a failed release with a new patch, beta, or RC number.
|
||||
|
||||
## Withdrawing or blocking a release
|
||||
|
||||
Edit `release/statuses.json` on `main` and set the immutable tag to
|
||||
`withdrawn` or `blocked`, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"releases": {
|
||||
"v1.9.0-beta.2": "withdrawn"
|
||||
},
|
||||
"schema": 1
|
||||
}
|
||||
```
|
||||
|
||||
The trusted `release-status.yml` workflow rebuilds and signs `updates/history.json`
|
||||
from real Gitea releases. It also rebuilds both channel manifests: each channel
|
||||
points to its greatest active release, or carries an authenticated inactive
|
||||
status when that channel has no active release. The workflow verifies every
|
||||
selected release manifest before signing and atomically publishing the channel
|
||||
and history files. This prevents a withdrawn channel head from continuing to be
|
||||
offered by launchers that read `updates/stable.json` or
|
||||
`updates/prerelease.json` directly.
|
||||
|
||||
The release assets remain available for support, while new automatic update and
|
||||
rollback selection excludes the inactive release. Deleting the tag's override
|
||||
returns it to `active`. A status entry can also declare compatibility when a
|
||||
future release changes the persistent settings schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"releases": {
|
||||
"v2.0.0": {
|
||||
"status": "active",
|
||||
"config_schema": 2,
|
||||
"minimum_config_schema": 1
|
||||
}
|
||||
},
|
||||
"schema": 1
|
||||
}
|
||||
```
|
||||
|
||||
The workflow can be run manually after a runner or API outage.
|
||||
|
||||
## Launcher history and rollback
|
||||
|
||||
The launcher fetches and verifies `updates/history.json` and its detached
|
||||
signature for the version-history window. An explicit historical rollback uses
|
||||
that release's immutable `manifest.json`, `manifest.json.sig`, and versioned
|
||||
archive:
|
||||
|
||||
```text
|
||||
pristontale-eu launcher-update --rollback 1.8.0
|
||||
```
|
||||
|
||||
Running `--rollback` without a version keeps the original fast path to the
|
||||
locally retained previous release. Every remote target is signature-, size-,
|
||||
archive-hash-, and per-file-hash checked before it enters the immutable release
|
||||
store. The launcher also persists that authenticated file inventory and verifies
|
||||
it again before reactivating a locally retained release. Releases installed by an
|
||||
older launcher remain usable through the compatibility path when no persisted
|
||||
inventory exists. Complete release directories make reconciliation exact: files
|
||||
managed by the current release but absent from the target disappear when
|
||||
`current` changes, while settings, logs, saves, and downloads remain outside the
|
||||
release tree.
|
||||
|
||||
Activation records the original `current` and `previous` links in a durable
|
||||
transaction journal. Link or post-activation validation failure restores those
|
||||
links, and the next launcher run recovers an interrupted transaction before any
|
||||
new mutation. The local operation history retains the latest 30 update and
|
||||
rollback results.
|
||||
|
||||
See `SIGNING_MIGRATION.md` before rotating the deployed manifest key.
|
||||
Reference in New Issue
Block a user