308 lines
19 KiB
Markdown
308 lines
19 KiB
Markdown
# Launcher updater releases
|
|
|
|
The updater has its own signing key and fixed repository:
|
|
`emil28092005/shacraft-launcher`. It is independent of the ShaCraft modpack
|
|
manifest key. The committed public key is `src-tauri/updater-public-key.txt`;
|
|
the native client and release validation pin that key. An unset/placeholder key
|
|
cannot produce a release.
|
|
|
|
## Packages and signed metadata
|
|
|
|
The four build targets each produce two distributable files, with canonical names:
|
|
|
|
- Windows x64: `shacraft-launcher_VERSION_windows-x86_64-setup.exe` (NSIS)
|
|
and `shacraft-launcher_VERSION_windows-x86_64.msi`.
|
|
- Linux x64: `shacraft-launcher_VERSION_linux-x86_64.AppImage` and
|
|
`shacraft-launcher_VERSION_linux-x86_64.deb`.
|
|
- macOS arm64: `shacraft-launcher_VERSION_darwin-aarch64.dmg` and
|
|
`shacraft-launcher_VERSION_darwin-aarch64.app.tar.gz`.
|
|
- macOS x64: `shacraft-launcher_VERSION_darwin-x86_64.dmg` and
|
|
`shacraft-launcher_VERSION_darwin-x86_64.app.tar.gz`.
|
|
|
|
Every package has a detached `.sig` from the Tauri signer. AppImage, NSIS, MSI
|
|
and app tar signatures must already exist after `tauri build`; absence is an
|
|
error. The collection step signs deb and DMG explicitly. Renaming a package does
|
|
not alter its bytes or signature. The macOS app archive contains the `.app`
|
|
bundle; the DMG remains the manual installation package. These artifact formats
|
|
follow [Tauri's updater documentation](https://v2.tauri.app/plugin/updater/).
|
|
|
|
`latest.json` contains exactly `schemaVersion` (integer 1), `version`, `tag`,
|
|
`notes`, `pub_date`, `platforms` and `manualPackages`. `version` is stable
|
|
`MAJOR.MINOR.PATCH`; `tag` is exactly `vVERSION`. Windows MSI limits apply:
|
|
major/minor at most 255 and patch at most 65535. `pub_date` uses UTC
|
|
`YYYY-MM-DDTHH:MM:SSZ`, derived from the tagged source commit.
|
|
|
|
`platforms` has exactly `windows-x86_64`, `linux-x86_64`, `darwin-aarch64` and
|
|
`darwin-x86_64`. Their primary packages are NSIS, AppImage and the two app archives.
|
|
`manualPackages` has exactly `windows-x86_64-msi`, `linux-x86_64-deb`,
|
|
`darwin-aarch64-dmg` and `darwin-x86_64-dmg`. Despite the section name, the native
|
|
client also selects the MSI descriptor when updating an MSI installation.
|
|
|
|
Each descriptor has exactly `url`, `signature`, `sha256` and `size`. URLs are
|
|
bound to the exact version/tag and canonical filename under
|
|
`https://github.com/emil28092005/shacraft-launcher/releases/download/vVERSION/`.
|
|
SHA-256 is lowercase hexadecimal; size is a positive integer at most 1 GiB.
|
|
Metadata is at most 32 KiB, notes at most 4096 UTF-8 bytes and signatures at most
|
|
2048 characters.
|
|
|
|
The release script writes deterministic UTF-8 JSON with sorted keys, two-space
|
|
indentation and a trailing newline. It signs those exact bytes into
|
|
`latest.json.sig` using the same updater key. The client verifies this signature
|
|
before parsing: an artifact signature alone cannot bind a version or download
|
|
URL. The signed metadata binds the complete platform set, versions, filenames,
|
|
hashes, sizes and package signatures together.
|
|
|
|
`scripts/release-verifier` uses `minisign-verify` 0.2.5 and the same verification
|
|
sequence as the [Tauri updater 2.11 implementation](https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/updater/src/updater.rs):
|
|
base64-decode the Tauri public key/signature containers, decode the minisign
|
|
objects, then verify the exact file bytes including the trusted comment. Missing,
|
|
nonempty-but-invalid and mismatched-key signatures all fail. The utility does
|
|
not execute installers or replace applications.
|
|
|
|
## CI and protected draft creation
|
|
|
|
`check.yml` runs the usual UI/Rust checks plus release tests. `build.yml` builds
|
|
all four targets with fresh disposable test keys. It has read-only repository
|
|
permissions and receives no production signing secret. Transient build config
|
|
and `SHACRAFT_UPDATER_TEST_BUILD=1` select the test key; no committed pin changes.
|
|
Artifacts are labelled `CI-NOT-FOR-RELEASE-*`, include `CI_NOT_FOR_RELEASE.txt`,
|
|
and cannot pass release validation. Test keys are never uploaded.
|
|
|
|
Before a release, an operator must configure two existing GitHub environments:
|
|
`launcher-release` and `launcher-release-publish`. Both require a reviewer other
|
|
than the dispatcher (`prevent_self_review=true`) and permit protected branches
|
|
only. `main` must be protected. This is a two-person operation: the dispatcher
|
|
cannot approve their own deployment. The workflow gate checks these settings
|
|
through GitHub's [environment API](https://docs.github.com/en/rest/deployments/environments)
|
|
before exposing an environment name to later jobs. Missing environments or
|
|
insufficient API permissions stop the workflow; it never creates an unprotected
|
|
replacement or bypasses the check.
|
|
|
|
Set `SHACRAFT_UPDATER_PUBLIC_KEY` as an environment/repository variable, equal to
|
|
the complete committed base64 public key. `launcher-release` alone needs the
|
|
`TAURI_SIGNING_PRIVATE_KEY` secret (official Tauri encoded key contents) and,
|
|
for an encrypted key, `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`. The password may be
|
|
omitted/empty for an unencrypted key; signing is noninteractive. The publish
|
|
environment needs only the public variable, never the private key.
|
|
|
|
The private key must live outside Git, build artifacts and web roots. Keep the
|
|
key directory owner-only (0700), its key file owner-only (0600), and retain a
|
|
separate secure backup outside Git. Do not print the key, upload it as an
|
|
artifact or keep a purported encryption password beside it. Losing the key
|
|
breaks updates for installed clients; changing the public key is a coordinated
|
|
migration, not routine key regeneration.
|
|
|
|
To prepare a release:
|
|
|
|
1. Merge the reviewed source/version change. Package JSON, npm lock, Cargo
|
|
package and Tauri config versions must match. Create the exact `vVERSION`
|
|
tag on a commit belonging to protected `main`; the workflow never creates or
|
|
moves a tag.
|
|
2. Manually run **Prepare signed release draft** from `main` with that version,
|
|
tag and release notes. Pass the independent environment approval. Every
|
|
matrix runner checks the tag/versions and signs a challenge to prove the
|
|
supplied private key matches the committed public key before packaging.
|
|
3. All four jobs must finish. Only then does the final job assemble all eight
|
|
packages, all eight signatures and signed metadata. It verifies this complete
|
|
18-file set before creating a GitHub draft, uploads it, downloads it again
|
|
and re-verifies the draft. A partial upload remains a draft and cannot pass
|
|
publication validation. An existing draft is never silently overwritten.
|
|
|
|
Before any candidate source runs, trusted workflow Git commands prove that the
|
|
tag commit belongs to protected main. Later jobs check out the validated immutable
|
|
SHA, not the movable tag. Checkout credentials are removed, and the publication
|
|
token exists only in the final explicit publishing step.
|
|
|
|
The tag/release workflow is manual; pushing a tag does not publish an update.
|
|
The scripts do not configure GitHub environments, keys or secrets. Repository
|
|
access alone is not authorization to run the publication workflow.
|
|
|
|
## Explicit final publication
|
|
|
|
After reviewing the draft and completing platform acceptance, an operator runs
|
|
**Publish verified release (operator only)** from `main`, supplies version/tag
|
|
and the exact confirmation `publish vVERSION`, then obtains the separate
|
|
publish-environment approval. This job has no signing key. It checks the source
|
|
versions/tag, current committed public pin, environment policy and monotonic
|
|
stable release version; downloads every draft asset and checks all signatures,
|
|
hashes, sizes, names and signed metadata again. It also checks asset IDs/sizes/
|
|
digests did not change during validation. Only this step changes `draft` to false
|
|
and marks the release latest. Failures leave the draft unpublished.
|
|
|
|
GitHub `latest/download/latest.json` and its detached signature can temporarily
|
|
refer to different releases during CDN propagation. The client must reject that
|
|
mismatch and retry; it must never accept unsigned metadata as a fallback.
|
|
|
|
## Acceptance and bootstrap
|
|
|
|
Updater signatures authenticate update bytes. They are **not** Windows
|
|
Authenticode signatures, Apple Developer ID signatures or Apple notarization.
|
|
This pipeline does not provision those certificates or claim that SmartScreen
|
|
or Gatekeeper will trust a manually downloaded package. Any OS signing/notarization
|
|
step must finish before updater signing and metadata hashing. Never modify a
|
|
package after its updater signature is created.
|
|
|
|
Version 0.1.1 has no updater: users must install the first updater-enabled release
|
|
manually. Keeping application identifiers and installer families stable is
|
|
necessary, but unit tests do not establish upgrade compatibility. Before final
|
|
publication, validate actual old-to-new NSIS and MSI installations separately,
|
|
including per-user/elevated installation and preservation of account/settings/
|
|
Minecraft data. The native updater preserves the MSI/NSIS family.
|
|
|
|
Validate both macOS architectures on real supported systems, including writable
|
|
and protected application locations and process restart. Linux automatic
|
|
replacement is for an AppImage running from a writable AppImage location; a deb
|
|
installation shows availability and opens the fixed official releases page;
|
|
installation then uses the normal OS package installer. Non-AppImage or unwritable installations must not be treated
|
|
as successfully self-updated. Verify interrupted download, wrong signature,
|
|
current/no-update, relaunch and concurrent game/install behavior on each platform.
|
|
|
|
Local release tests use temporary synthetic payloads and newly generated
|
|
throwaway keys. They cover real signature verification, bit flips, wrong keys,
|
|
metadata substitution, exact artifact sets, duplicate fields, version/tag
|
|
bindings, CI promotion rejection and missing environment protection. They do
|
|
not prove any Windows/macOS installer ran, a live GitHub release was published,
|
|
or an end user's application updated successfully.
|
|
|
|
## Installed package migration and recovery
|
|
|
|
The settings drawer reports the installed native package family. Automatic
|
|
updates preserve it:
|
|
|
|
- Windows NSIS x64 → NSIS x64, retaining the existing per-user scope and saved
|
|
installation location. The signed package must contain an EXE, not MSI bytes.
|
|
- Windows MSI x64 → MSI x64, retaining the per-machine installer family. The
|
|
pinned UpgradeCode `2058b1df-56a1-51ef-bd48-d296479cd59a` is the exact value
|
|
Tauri CLI 2.11.4 derived for the existing 0.1.1 product name; ProductCode can
|
|
change for a major upgrade. Administrator permission may be required.
|
|
- MSI ↔ NSIS, simultaneous installations of both, renamed products, changed
|
|
scopes and manually moved Windows installations have no automatic migration
|
|
promise. Close the old application and use an explicit manual installer path;
|
|
verify installed-app registrations and account/settings preservation in beta.
|
|
- macOS Intel → Intel app archive, Apple Silicon → Apple Silicon app archive.
|
|
DMG is the bootstrap/manual distribution. Move the app out of a mounted DMG
|
|
into an appropriate Applications directory before use. A protected destination
|
|
may require OS permission or a manual replacement; an installation error never
|
|
counts as a completed update.
|
|
- Linux x64 AppImage → x64 AppImage at its current writable location. The original
|
|
ordinary, non-symlink AppImage must exist, and the frozen Tauri `APPDIR` must
|
|
match the running `usr/bin/shacraft-launcher`. Extracted AppDirs and an inherited
|
|
environment from another AppImage use manual installation. Debian
|
|
packages, RPM, bare development binaries and unsupported architectures never
|
|
enter the self-replacement path. Install a new deb through the system package
|
|
manager; the launcher does not run privileged package-manager commands.
|
|
|
|
These paths follow the locked [Tauri MSI implementation](https://github.com/tauri-apps/tauri/blob/tauri-cli-v2.11.4/crates/tauri-bundler/src/bundle/windows/msi/mod.rs),
|
|
[NSIS installer template](https://github.com/tauri-apps/tauri/blob/tauri-cli-v2.11.4/crates/tauri-bundler/src/bundle/windows/templates/installer.nsi)
|
|
and [updater implementation](https://github.com/tauri-apps/plugins-workspace/blob/updater-v2.11.0/plugins/updater/src/updater.rs).
|
|
Source inspection and CI packaging do not replace real installation acceptance.
|
|
|
|
The updater writes `launcher-state/pending-update.json` immediately before the
|
|
platform installer. Once handed off, cancellation, power loss and installer
|
|
errors can be ambiguous. Windows does not expose an installer PID/completion
|
|
result through the plugin, so the launcher never guesses that installation has
|
|
finished after a timeout. The exact target version acknowledges the marker on
|
|
startup. Until then, recovery mode blocks native game, account, settings and
|
|
update mutations. Corrupt marker bytes also open this diagnostic mode, never
|
|
normal operation. Its restriction stays latched even if the file is removed
|
|
while that process is open. Unix marker creation/acknowledgment syncs the parent
|
|
directory as well as file contents.
|
|
|
|
For an interrupted update:
|
|
|
|
1. Close Minecraft, all launcher instances and any installer; reboot if their
|
|
termination cannot be established. Do not delete active OS lock files.
|
|
2. Install the marker's expected target version manually from the fixed official
|
|
releases page, preserving the package family above. Replace only the app or
|
|
package, keeping the application data directory. Starting that target version
|
|
acknowledges a valid marker automatically.
|
|
3. If the marker is corrupt, or the target cannot be used, first manually restore
|
|
a complete official package of the intended supported version. With every
|
|
launcher/game/installer closed, rename only `launcher-state/pending-update.json`
|
|
to a backup outside `launcher-state`, then launch again. This is an operator
|
|
recovery after a complete package repair, not a shortcut around an active
|
|
installer. Keep the backup for diagnosis. Never remove settings, sessions,
|
|
profiles, game files, `instance.lock`, `writer.lock` or a live game lease.
|
|
|
|
Default application data roots (or the configured XDG data home on Linux):
|
|
|
|
- Windows: `%APPDATA%\ru.shacraft.launcher`.
|
|
- macOS: `~/Library/Application Support/ru.shacraft.launcher`.
|
|
- Linux: `~/.local/share/ru.shacraft.launcher`.
|
|
|
|
Network/hash/signature failures before installer entry leave the app intact and
|
|
permit an explicit fresh check/retry. They do not create an install handoff or
|
|
require marker recovery. A separate “game still running” error must be resolved
|
|
by closing the game, not removing its lease.
|
|
|
|
|
|
## Package architecture checks
|
|
|
|
Before collection/signing, scripts inspect package bytes without running an
|
|
installer. AppImage must have an ELF64 little-endian AMD64 header and the Type 2
|
|
`AI\x02` marker. The deb ar/control archive must declare `Architecture: amd64`
|
|
and the release version. Each macOS app archive must have exactly one
|
|
`Info.plist`, the same release version, and a regular main executable with a
|
|
thin Mach-O64 CPU type matching its matrix target. DMG checks validate the UDIF
|
|
container trailer; they do not mount or inspect the DMG filesystem.
|
|
|
|
On the Windows build runner, the built launcher must be AMD64 PE32+. NSIS uses
|
|
an x86 installer stub even for an x64 application: the checker accepts that
|
|
wrapper, uses the runner's 7-Zip to list/extract only the named launcher to
|
|
stdout, then requires an x64 payload identical to the built main executable after the
|
|
exact Tauri bundle-type stamp described below.
|
|
It never executes NSIS. Missing/unsupported 7-Zip inspection fails the build;
|
|
there is no silent architecture-check fallback. MSI is checked for a compound
|
|
file header and read using WindowsInstaller COM with `MSIDBOPEN_READONLY`:
|
|
Template Summary must say `x64` and ProductVersion must match. The MSI check also
|
|
checks the built main executable, but does not extract MSI's embedded cabinet or
|
|
prove that cabinet's payload matches the build. Real installer acceptance is
|
|
still required. These distinctions follow the [PE format](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format)
|
|
and [64-bit MSI package requirements](https://learn.microsoft.com/en-us/windows/win32/msi/using-64-bit-windows-installer-packages).
|
|
|
|
The locked [Tauri CLI 2.11.4 bundler](https://github.com/tauri-apps/tauri/blob/tauri-cli-v2.11.4/crates/tauri-bundler/src/bundle.rs)
|
|
replaces the first complete `__TAURI_BUNDLE_TYPE_VAR_UNK` token with
|
|
`__TAURI_BUNDLE_TYPE_VAR_NSS` for NSIS (`MSI` for MSI), packages that binary, then
|
|
restores the original unpatched/unsigned executable on disk after each bundle.
|
|
The NSIS comparator constructs precisely that one replacement in a copy of the
|
|
built bytes and compares the entire extracted payload. It does not mask any PE
|
|
section, checksum, certificate table, padding or other bytes. A missing marker,
|
|
wrong family stamp or any unrelated byte change fails. Both original and
|
|
extracted executables must still be AMD64 PE32+.
|
|
|
|
Authenticode is currently unconfigured. Adding it can also change the PE checksum
|
|
and append a certificate table; this comparison intentionally fails until a
|
|
separate verified signed-baseline procedure is implemented. Do not broaden the
|
|
comparison to ignore all certificate/checksum differences merely to pass CI.
|
|
|
|
Portable format checks repeat when validating signed release assets. Full NSIS
|
|
payload/MSI COM checks run only during collection on the Windows runner; the
|
|
Linux draft/publish verifier rechecks their container headers and signatures.
|
|
Header/metadata inspection is not a runtime, architecture-emulation or installer
|
|
migration test. Synthetic header fixtures exercise these checks; they are never
|
|
installed or executed.
|
|
|
|
|
|
## Local package verification, 2026-09-09
|
|
|
|
Application source `696c6b0` was built on Ubuntu 26.04 x86_64 using a disposable
|
|
CI updater key and the visible test-build flag. Actual AppImage and deb packages
|
|
passed the same collection checks used by the workflow: expected container,
|
|
architecture/version where represented, and cryptographic updater signatures.
|
|
The manual-package collector explicitly signs deb/DMG even when a CLI version
|
|
also emits their signatures. No production package or release was signed.
|
|
|
|
The actual AppImage was started with `--appimage-extract-and-run`, isolated XDG
|
|
roots and a pending update targeting a different version. The native executable
|
|
ran inside its own `APPDIR/usr/bin/shacraft-launcher`, referenced the expected
|
|
`APPIMAGE`, held the instance OS lock and preserved the pending marker during
|
|
startup and after termination. This checks native startup and the runtime binding;
|
|
it does not establish visual/IPC behavior, installation, self-replacement or a
|
|
successful updater restart. The deb was inspected, not installed.
|
|
|
|
This local Ubuntu 26.04 build is not a compatibility result for Ubuntu 22.04 or
|
|
other distributions. The four-platform GitHub matrix, real Windows/macOS
|
|
installations, old-version migration and end-to-end update acceptance remain
|
|
pending. Production signing credentials and release environments are not
|
|
configured on GitHub, and no release has been published.
|