26 KiB
PristonTale EU Linux Launcher Release Pipeline Workplan
This is the tracked implementation plan for manual Gitea releases today and the deferred automatic publication path, stable and prerelease channels, signed manifests, release history, and channel-aware rollback. A checked item means the repository contains the implementation or the audit evidence named beside it. Publishing tests that require a configured Gitea server remain unchecked until they have run against that server.
Legend: [x] complete, [ ] pending.
Current milestone
- Save the release workplan in the repository.
- Inspect the existing release, update, rollback, settings, UI, and telemetry paths.
- Implement and validate the automatic publication pipeline for future use.
- Switch to temporary manual-publication mode:
release.shnow creates a signed, verifieddist/v<VERSION>/package without commits, tags, pushes, API calls, or uploads. - Disable automatic workflow push triggers so manual Gitea releases cannot race an Actions publication job.
- Store normal update payloads directly at the tar root while retaining
runtime extraction support for historical archives and the
0.9.10–0.9.11compatibility window. - Re-enable automatic Gitea publication when requested.
- P1: add stable/prerelease discovery and launcher channel support.
- P2: harden remote rollback and managed-file reconciliation.
- P3: complete signing migration and release quality improvements.
Phase 0 — Existing-system audit
- Repository structure inspected. Runtime sources are in
ptlinux/andptdiag/; installer assets are inpackaging/; the checked-in channel is inupdates/; local, ignored development tools and tests are intools/andtests/. - Authoritative launcher version identified as
ptlinux/__init__.py.pyproject.tomlis a required synchronized metadata mirror. - Current update archive identified as
pristontale-eu-linux-update-v<VERSION>.tar.gz. - Current archive layout identified. Its root is
pristontale-eu-linux-update-v<VERSION>/, containingptlinux/, a compactptdiag/subset, andrelease.json. - Current update manifest identified as signed JSON schema 1 in
updates/stable.json, with version, channel, minimum updater version, archive URL/size/SHA-256, publication time, and notes URL. - Current update URL identified as the raw
mainbranchupdates/stable.json;PTLINUX_UPDATE_MANIFEST_URLcan override it. - Latest-release discovery identified as the mutable signed stable manifest.
The launcher does not use Gitea
/releases/latest. - Current rollback identified as local retained release directories selected
by atomic
currentandprevioussymlinks. - Cached archives identified in the launcher download cache and reused only when their size and SHA-256 match the authenticated manifest.
- Current staging identified: download to a
.partfile, verify while downloading, safely extract to a staging directory, validate/self-import, move into the immutable release store, then atomically replace symlinks. - Atomic activation confirmed through same-directory temporary symlink plus
os.replace()and directoryfsync. - Removed launcher files are handled because each release is a complete, separate directory. No in-place overlay is used.
- Fixed release asset names are not required by the current launcher; the authenticated manifest supplies the archive URL.
- Existing manually generated files identified: installer tar/zip archives, launcher update tar archive, SHA-256 files, stable manifest/signature, and release uploads.
- Existing manual exclusions identified. The launcher update builder selects Python/runtime files explicitly and excludes installer-only modules; the AIO builder selects runtime, packaging, installer, and README files explicitly.
- SHA-256 generation and verification confirmed for archives.
- Existing asymmetric signature support confirmed. The manifest is signed
and verified with the bundled public key through OpenSSL (currently RSA with
SHA-256); the private key is ignored under
release-keys/. - Existing telemetry inspected and extended with channel, target release, manifest identity, and complete update/rollback lifecycle metadata.
- Decision: extend the existing signed-manifest and atomic release-directory updater instead of introducing a second updater.
Release model and invariants (phases 1–2, 19, 24–25, 32–38, 45–51, 61–63, 77, 84)
- Support exactly
stableandprereleasechannels in the initial model. - Accept stable tags
vMAJOR.MINOR.PATCHand prerelease tags ending in-beta.Nor-rc.N; reject channel/tag mismatches. - Treat the manually selected Gitea release tag as the authoritative, immutable release identity.
- Restore annotated-tag and channel-pointer monotonicity enforcement when automatic publication is re-enabled. Manual publishers must currently check the tag in Gitea and the version ordering before copying a channel pointer.
- Use one semantic-version parser and one channel/update resolver; never use lexical version ordering.
- Preserve ordering from beta to RC to final, including multi-digit fields.
- Keep the internal launcher version without the leading
v. - Build immutable, versioned assets named
PristonTale-EU-<VERSION>.tar.gz; normal releases placeptlinux/,ptdiag/, andrelease.jsondirectly at the archive root. - Accept both new flat archives and legacy version-folder archives in the updated launcher so retained releases and rollback remain compatible.
- Configure stable
0.9.10and its0.9.11UI repair as legacy-wrapped bridge releases so deployed0.9.8launchers can install either one automatically. - Mark flat manifests as requiring updater
0.9.9so legacy clients fail closed before download/extraction rather than entering a doomed transaction. - Keep final manifests/signatures/checksums outside the archive they describe.
- Keep signed metadata mandatory for trusted automatic update operations.
- Stable clients can install stable releases only.
- Prerelease clients resolve the greatest eligible stable or prerelease semantic version.
- Changing prerelease to stable never causes an automatic downgrade.
- Model release status as
active,withdrawn, orblocked; do not offer withdrawn/blocked releases as new automatic updates. - Use each historical release's own signed manifest and immutable versioned archive; verify the embedded archive checksum and every target file hash.
- Persist the signed per-file inventory at install time and re-verify it before reactivating a locally retained release; preserve compatibility for releases installed before integrity records existed.
- Never reuse or overwrite a published tag or its assets.
- Fail rather than duplicating an existing Gitea release.
- Preserve compatibility for existing stable installations during migration.
Release tooling and configuration (phases 3–7, 12–14, 18, 20–23, 26–27, 65–66)
- Add
.gitea/workflows/release.ymlfor the deferred automatic publisher; keep it manual-dispatch only while manual upload mode is active. - Add tracked
scripts/release.sh,scripts/build-release.sh,scripts/generate-manifest.py, andscripts/verify-release.sh. - Add
release/config.jsonwith application, release branch, archive naming, manifest schema, channels, selected payload, required files, and exclusions. - Keep generated
dist/output ignored. - Explicitly exclude Git/IDE metadata, logs, caches, credentials, developer environment files, downloads, test output, and other project-specific files.
- Implement the primary CLI as
./scripts/release.sh CHANNEL VERSIONwith aliases normalized toprerelease. - Validate the repository root and channel/version pair, then run configured preflight checks before creating local artifacts.
- Support
--dry-run,--skip-tests, and an explicit local signing key. - Isolate preflight stdin so nested CLI tests cannot open an interactive
release-note prompt, and prefer the repository
.venvinterpreter when present. - Set the requested version only in the staged package so a manual build does not edit the authoritative source version or synchronized project metadata.
- Leave commit, tag, push, Gitea release creation, and asset upload to the developer while manual publication mode is active.
- Restore remote, branch, clean-worktree, tag-absence, version-commit, annotated-tag, and ordered-push handling when automatic publication resumes.
- Make dry-run perform validation, notes preview, and optional tests without commit, tag, push, or release creation.
- Make
build-release.sh VERSION CHANNEL OUTPUT_DIRindependently build an explicitly selected staging tree and clean generated output. - Normalize archive ordering, timestamps, ownership, group, and permissions where practical.
- Generate archive SHA-256 and a sorted per-file manifest without absolute paths.
- Include schema, application, version, channel, tag, commit, archive metadata, release metadata, timestamp, status, compatibility, and file hashes.
- Verify archive extraction, required entrypoints, forbidden content, checksum, manifest consistency, every managed file hash, and signature before publication.
- Give each archive download a signed SHA-256 query key so a CDN cannot keep serving a pre-upload empty response from the immutable Gitea asset path.
Manual Gitea publication milestone
- Produce the versioned archive, archive checksum, signed manifest, and
release notes together under
dist/v<VERSION>/. - Preserve other locally built versions and clean only the selected version directory when rebuilding it.
- Verify both the normal flat archive and configured legacy-wrapped bridge; the updater installs either payload successfully.
- Print the exact five paths to upload and the matching
v<VERSION>tag. - Print whether Gitea's Pre-release checkbox must be selected for the chosen channel.
- Generate a signed
dist/v<VERSION>/channel/<channel>.json{,.sig}pair for the developer to copy toupdates/after the Gitea assets exist. - Require all five Gitea attachments to be uploaded with nonzero sizes before publishing the generated channel pointer.
- Repair the live
v0.9.11stable pointer after Cloudflare cached a pre-upload zero-byte archive response. The pointer now uses the archive's signed SHA-256 cache key, and the deployed0.9.10updater completed its full remote verification against the repaired channel. - Safely discard generated-only
__pycache__content before validating a signed managed release, while rejecting unexpected cache contents; remove the local diagnostic cache that blocked activation and successfully complete the0.9.10to0.9.11transaction. - Keep the other channel pointer unchanged.
- Manually regenerate signed
updates/history.json{,.sig}from the live manifest-backed Gitea releases so remote history and rollback discovery no longer return HTTP 404; automatic regeneration remains deferred.
Release notes (phases 8–11)
- Support
--descriptionand Markdown-preserving--description-file. - Generate commit notes from the previous stable tag for stable releases.
- For prereleases, prefer the previous prerelease of the same target version; use the previous stable tag for the first prerelease.
- Put developer text before generated commit notes by default.
- Support
--no-auto-changelog,--changelog-only, and--editorusing${EDITOR:-nano}. - Allow interactive Ctrl+D release-note entry only on an interactive stdin; use generated notes in noninteractive environments.
- Preview the final release notes before building and copy them unchanged to
dist/v<VERSION>/release-notes.mdfor manual use as the Gitea release body.
Deferred Gitea Actions publication (phases 15–17, 28–31, 58–60, 64)
- Implement the
v*tag-push publisher and CI tag validation for future use. - Remove its tag-push trigger during manual publication mode; only an intentional manual dispatch can start the retained workflow.
- Derive tag, version, channel, prerelease flag, and dereferenced commit SHA from the exact checked-out annotated tag.
- Prefer
GITHUB_REF_NAME,GITHUB_REPOSITORY,GITHUB_API_URL,GITHUB_SERVER_URL,GITHUB_OUTPUT,GITHUB_ENV, andGITHUB_STEP_SUMMARYcontext values. - Use
actions/checkout@v4and build the exact tagged commit. - Require a minimally scoped
RELEASE_TOKENwithout logging it. - Keep the manifest private signing key only in an Actions secret, write it with mode 0600 when needed, and remove it immediately after signing.
- Run validation/tests, build, manifest generation, signing, and complete verification before creating a Gitea release.
- Create
draft=falsereleases through the Gitea API with the correct prerelease flag, target commit, title, and release notes; require a successful response and release ID. - Upload archive, checksum, manifest, manifest signature, and release notes separately; fail on every required upload error.
- Re-query the release and verify attachment names/sizes and the prerelease flag.
- Print a release summary containing title, channel, tag, commit, archive, SHA-256, manifest, and publication status.
- Document Gitea Actions, repository Actions, isolated
act_runner, required commands, trusted tag-only secret exposure, and minimum permissions inrelease/README.md. - Verify Actions enablement and the registered release runner on the live Gitea.
Channel indexes and history (phases 32–33)
- Publish independently signed
stable.jsonandprerelease.jsonchannel metadata at stable URLs without cross-channel mutation. - Publish signed release history with version, channel, tag, manifest URL, signature URL, date, notes URL, status, and compatibility metadata.
- Follow the Gitea release-list pagination contract so old rollback releases remain in history after the first 100 API results.
- List only real Gitea releases that retain their own manifest and signature.
- Rebuild status-aware channel manifests after a withdrawal/block so legacy channel consumers no longer receive an inactive channel head.
Launcher channels, resolver, and UI (phases 34–44)
- Add validated
update_channelsetting withstabledefault so existing users remain stable. - Add Stable/Prerelease settings UI with clear descriptions.
- Immediately check for updates after changing the selected channel.
- Do not downgrade automatically after switching prerelease to stable.
- Refactor discovery, update eligibility, semantic ordering, channel
transitions, and inactive-release filtering through one
UpdateResolver. - Connect signed remote history and remote rollback choices to
UpdateResolver. - Show current/target versions, target channel, release date/title/summary, notes, and a clear prerelease label in the update UI.
- Populate version history from actual signed release history and show version, channel, date, state, notes, and install/rollback action.
- Add All/Stable/Prerelease history filters with channel-appropriate defaults.
- Stable rollback lists stable releases by default; prerelease rollback lists earlier prereleases and stable releases with labels.
- Require explicit action and a configuration-compatibility warning for prerelease-to-stable rollback.
- Label the channel of each locally retained release and warn on an explicit cross-channel local rollback.
Rollback transaction hardening (phases 46–50, 52–53)
- Reconcile managed files by activating a complete, target-manifest-verified immutable release directory; files absent from the target disappear without deleting any unmanaged path.
- Keep config, logs, saves, downloads, and user-created/unmanaged files outside managed-file deletion.
- Add configuration schema compatibility metadata and reject unsafe rollback.
- Preserve each installed release's signed configuration compatibility in its local integrity record and allow signed release-history overrides for withdrawn or compatibility-reclassified historical releases.
- Precheck release/manifest/signature/archive, free space, processes, writability, and supported schemas.
- Snapshot current managed state through immutable release links, then download/verify/stage/reconcile/apply/health check, and restore the snapshot after local transaction failure.
- Permit automatic local restore after failed update application, while prohibiting telemetry-driven remote downgrade.
- Optionally add advanced latest/pinned update policy.
- Retain 30 bounded local update/rollback history records.
Telemetry and hotfix integration (phases 54–57)
- Include current/target version and channel, selected channel, tag, manifest schema/hash, and rollback flag in update telemetry.
- Add discover/download/apply success and failure event families.
- Add requested/download/apply/restore rollback event families with from/to versions/channels and an enumerated reason.
- Keep full Markdown descriptions out of telemetry; use version/tag/channel and optional release ID only.
- Make hotfix targeting channel-aware and exact-version aware while reusing the release hash/signature verification path.
Failure and security behavior (phases 27–31, 59–63)
- Reject
.env, private keys, SSH keys, Git/IDE metadata, logs, caches, and bytecode in published archives. - Never expose release credentials to pull-request workflows or untrusted code.
- Stop publication after test, build, manifest, signing, release-create, or required-asset failure; never report a partial release as successful.
- Keep failed tags immutable and publish a new version after correcting a failure.
Verification matrix (phases 67–76)
- End-to-end stable release: stable flag, artifacts, signed manifest, stable index update, unchanged prerelease index, and stable discovery.
- End-to-end beta/RC release: prerelease flag, artifacts, prerelease index update, unchanged stable index, prerelease discovery, and stable exclusion.
- Exercise inline/file/multiline Markdown, generated notes, no-auto, changelog-only, and editor modes; compare preview with Gitea body.
- Exercise both channel preferences and resolver transitions, including no implicit downgrade after returning to stable.
- Verify semantic ordering, including
1.10.0 > 1.9.9, stable filtering, and prerelease-to-final transition. - Exercise stable rollback, prerelease rollback, cross-channel rollback, managed-file removal, config/log survival, and failed-apply restoration.
- Exercise withdrawn-release filtering/warnings and valid successor selection.
- Simulate Gitea/API 401, 403, 404, 409, 422, 500, lookup/create/upload timeouts, and attachment failure without false success.
- Seed forbidden path cases and verify exclusion/failure and absence from the deterministic archive.
- Verify valid signatures and reject modified manifest metadata; require signed metadata in CI.
Acceptance criteria (phases 78–83)
./scripts/release.sh stable 1.8.0 --description-file ...validates, previews, builds, signs, and verifies the five files needed for a manual stable Gitea release without changing Git or contacting Gitea../scripts/release.sh prerelease 1.9.0-beta.1 --description ...produces the equivalent prerelease upload set and clearly instructs the developer to select Gitea's Pre-release checkbox.- Stable-to-prerelease checks immediately; prerelease-to-stable retains the installed beta until a greater eligible stable exists.
- Cross-channel rollback fetches and verifies the target release, warns, snapshots, reconciles managed files, applies and health-checks, records telemetry, and preserves user state.
- Normal development remains ordinary commits/pushes. A manual release begins
with
./scripts/release.sh stable VERSIONor./scripts/release.sh prerelease VERSION; archive, checksum, manifest, signature, and release notes generation require no manual work. - Re-enable Gitea release creation, asset upload, and signed channel/history publication after the manual release process has been proven in production.
Implementation priority
P0 — Build first
- Inspect existing updater/release system.
- Semantic-version handling.
- Manual release CLI and all description/changelog/preview modes.
- Stable/prerelease tag validation.
- Retained Gitea publisher implementation with archive, SHA-256, manifest, signature, release creation, prerelease flag, upload, and verification.
- Disable its automatic trigger for the current manual publication milestone.
P1 — Channel support
update_channelsetting and settings UI.- Signed channel metadata and channel-aware discovery.
- Central
UpdateResolverand semantic ordering. - Signed release history, release notes in update UI, and channel-aware rollback candidates.
- Keep the inline release-notes preview in a fixed-height scrollable area so the version table, channel filter, and update controls remain visible.
P2 — Rollback hardening
- Managed-file reconciliation and snapshot/restore validation.
- Cross-channel rollback and rollback telemetry.
- Withdrawn state and bounded local update history.
P3 — Security and quality
- Plan a compatible Ed25519 key migration from the deployed RSA verifier.
- Improve and measure reproducibility for archive and manifest output.
- Integrate channel-aware targeted hotfix verification.
- Optional version pinning and release notifications.
Deployment prerequisites
- Manual publication needs no Actions runner,
RELEASE_TOKEN, or Gitea signing secret; the developer needs permission to create a repository release and upload attachments in the Gitea UI. - Add
RELEASE_TOKENwith repository release/content permissions to Gitea Actions. - Add the manifest signing private key as a protected Gitea Actions secret.
- Confirm Actions, the repository workflow, and an isolated release runner are enabled.
- Confirm the runner image provides Git, Bash, curl, jq, Python 3, tar, gzip, sha256sum, rsync, and OpenSSL/signing support.
The configured SSH remote is reachable and origin/main resolves successfully.
The current development environment does not expose either Actions secret, and
the public Gitea API currently returns empty HTTP 200 responses, so runner,
repository-Actions, and secret state cannot be authenticated from this checkout.
Validation evidence
- 68 release-pipeline, resolver, channel, history, signing, rollback, telemetry, CLI, publisher, status, and API-failure tests pass locally.
- All 575 current project tests pass during the complete manual release preflight.
- The published flat
stable 0.9.9package demonstrated the expected compatibility boundary: deployed0.9.8clients reject its declared minimum updater instead of attempting an archive layout they cannot extract. - A real wrapped
stable 0.9.10bridge underdist/v0.9.10/built, signed, verified, passed updater staging, and is offered automatically to0.9.8. Its only tar root ispristontale-eu-linux-update-v0.9.10; archive SHA-256:76df8942d2f0b3b533eb967f8a2379b2a7bdd44fd58305d0f8bf061778fbd9ea. - A real wrapped
stable 0.9.11UI repair underdist/v0.9.11/built, signed, and verified with minimum updater0.5.0. Its only tar root ispristontale-eu-linux-update-v0.9.11; archive SHA-256:60b13d6fbc8fe27714c7fe1a65fa73785a13962d96d03decfac1aa62c4e3f6db. - Publish the prepared
v0.9.11release and its five assets in Gitea. The remote currently has nov0.9.11tag, so its stable channel still resolves to0.9.10even when the PyCharm checkout is running0.9.11. - After the Gitea assets exist, commit and push the prepared
updates/stable.jsonandupdates/stable.json.sigpair that points stable discovery to0.9.11. - Keep the
0.9.11embedded notes to one concise change line so clients still running the overflowing dialog can reach the install action. - Hidden-window Tk geometry validation at the default scale confirms the notes viewport, channel filter, 124-pixel history table, and footer all fit inside the 820×680 update dialog.
- Reproduce the reported
0.9.10layout from its published archive: the old dialog requests 820×828 pixels while forcing a 760×620 window. Confirm the0.9.11archive uses the repaired dialog and keeps notes, history, and footer in separate non-overlapping bounds; its deliberately short channel notes also leave the old0.9.10install button reachable during the repair upgrade. - Synchronize the workspace version source and project metadata to
0.9.11, and make the shared PyCharm launcher configuration put$PROJECT_DIR$first onPYTHONPATH, so debug runs load the current checkout rather than an older installed release. - Keep update-dialog version labels tied to the running launcher. When a
PyCharm checkout is newer than the active installed symlink, show the checkout
as
Currentand describe the older on-disk release asInstalled. - Prior deterministic-build validation produced byte-identical stable
0.9.8archives with SHA-25672346f216856235df3c6071409e2bbce17853b9f9589bf3f012743ddf7823f4b. - Shell syntax, JSON parsing, Git whitespace checks, PHP telemetry endpoint syntax, Python compilation, and IDE error inspections pass.
- Run stable and prerelease publication acceptance tests against the live Gitea after the protected secrets and isolated runner are configured.