Files

Manual Gitea launcher releases

Manual publication mode is currently active. Build a complete local package with:

./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:

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:

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:

{
  "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:

{
  "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:

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.