17 KiB
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) andshacraft-launcher_VERSION_windows-x86_64.msi. - Linux x64:
shacraft-launcher_VERSION_linux-x86_64.AppImageandshacraft-launcher_VERSION_linux-x86_64.deb. - macOS arm64:
shacraft-launcher_VERSION_darwin-aarch64.dmgandshacraft-launcher_VERSION_darwin-aarch64.app.tar.gz. - macOS x64:
shacraft-launcher_VERSION_darwin-x86_64.dmgandshacraft-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.
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:
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
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:
- Merge the reviewed source/version change. Package JSON, npm lock, Cargo
package and Tauri config versions must match. Create the exact
vVERSIONtag on a commit belonging to protectedmain; the workflow never creates or moves a tag. - Manually run Prepare signed release draft from
mainwith 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. - 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-d296479cd59ais 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
APPDIRmust match the runningusr/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, NSIS installer template and updater implementation. 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:
- Close Minecraft, all launcher instances and any installer; reboot if their termination cannot be established. Do not delete active OS lock files.
- 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.
- 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.jsonto a backup outsidelauncher-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.lockor 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
and 64-bit MSI package requirements.
The locked Tauri CLI 2.11.4 bundler
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.