Add signed launcher self-updates and publish Linux 0.1.3
This commit is contained in:
@@ -22,9 +22,12 @@ The interface also shows a live Aeronautics player count from the fixed,
|
||||
read-only `https://shacraft.ru/api/online/aoc` endpoint. It is display-only:
|
||||
the result never controls files, versions, URLs, or the launch command.
|
||||
|
||||
Not yet implemented: a user-selectable profile directory, a "reset managed
|
||||
files only" recovery action, and signed cross-platform release builds of the
|
||||
launcher itself. Do not represent these as completed in UI or release notes.
|
||||
Version 0.1.3 adds signed application updates, separate from modpack sync.
|
||||
Linux AppImage replacement is supported; the first upgrade from 0.1.2 is manual.
|
||||
Windows/macOS packages still require publication and actual installation tests.
|
||||
Not yet implemented: a user-selectable profile directory and a "reset managed
|
||||
files only" recovery action. OS code signing/notarization is separate from the
|
||||
updater signatures and is not certified by this implementation.
|
||||
|
||||
## Data flow
|
||||
|
||||
@@ -143,7 +146,8 @@ proof with a shared key embedded in distributed binaries.
|
||||
1. User-selectable profile directory and structured launcher logs.
|
||||
2. "Reset managed files only" recovery action that doesn't touch player
|
||||
worlds/screenshots/resourcepacks.
|
||||
3. Signed, cross-platform release builds of the launcher itself.
|
||||
3. Windows Authenticode/macOS signing-notarization and cross-platform release
|
||||
installation testing.
|
||||
4. Cancellation, structured logs and a full cold-install/recovery beta on
|
||||
every target OS. Install progress reports bytes or installer work counts
|
||||
depending on the stage; these units are not interchangeable.
|
||||
@@ -173,6 +177,36 @@ URLs and redirects. Manifest profile identity, size, signature, portable
|
||||
paths and existing symlinks are checked before managed file writes.
|
||||
Hostile same-user TOCTOU is outside this protection; it is not an OS sandbox.
|
||||
|
||||
## Signed application updates (0.1.3)
|
||||
|
||||
`updater.rs` accepts only the fixed HTTPS feed
|
||||
`https://shacraft.ru/launcher/updates/stable.json`. A dedicated embedded Tauri
|
||||
public key authenticates both the metadata payload and the selected package.
|
||||
The signed metadata binds the plain stable version, release notes, date and
|
||||
platform URLs. Artifact URLs are confined to the matching version directory
|
||||
under `https://shacraft.ru/downloads/shacraft-launcher/`. The IPC never accepts
|
||||
a URL, key, destination path or replacement executable from the webview.
|
||||
|
||||
Metadata is downloaded once, with a 192 KiB envelope/64 KiB payload bound;
|
||||
packages are capped at 256 MiB. Redirects and version downgrades are rejected.
|
||||
The small vendored Tauri 2.11.0 `check_metadata` patch constructs its update
|
||||
object without another HTTP request. Linux AppImage installation uses a
|
||||
same-directory temporary file, signature verification, preserved permissions,
|
||||
atomic rename and file/directory fsync. Windows/macOS retain Tauri's platform
|
||||
installers. Unsupported Linux formats show manual installation instructions.
|
||||
|
||||
The native updater holds installation, account and game permits while installing
|
||||
and until restart. The game permit remains held until the launched Java child
|
||||
exits. These guards cover this launcher process, not other launcher instances.
|
||||
Startup checks never silently install; settings expose check, install, progress,
|
||||
errors and restart. A failed check does not prevent using the installed version.
|
||||
|
||||
The private updater key stays on the operator's computer. Normal CI builds are
|
||||
explicitly unsigned; reviewed release artifacts and metadata are signed locally
|
||||
and published only after signature/hash verification. The Caddy feed route uses
|
||||
`Cache-Control: no-store`. See [launcher-updates.md](launcher-updates.md) for
|
||||
the envelope contract, publisher commands and recovery constraints.
|
||||
|
||||
## Verification and distribution
|
||||
|
||||
`npm test` covers asynchronous helpers and state transitions;
|
||||
@@ -180,8 +214,9 @@ Hostile same-user TOCTOU is outside this protection; it is not an OS sandbox.
|
||||
covers native policy and storage. Push/PR CI repeats checks on Linux.
|
||||
The package workflow runs on main pushes or manually and builds Windows
|
||||
x64, Linux x64 and both macOS architectures with named artifacts.
|
||||
Packages are not yet signed release artifacts. Native cold-install and
|
||||
launch tests are required before calling a platform release-ready.
|
||||
CI packages are unsigned build artifacts. Signed updater publication is a
|
||||
separate local operator step. Native cold-install and launch tests are required
|
||||
before calling a platform release-ready.
|
||||
|
||||
The local admission checkpoint passed 64 Rust tests (5 live tests ignored),
|
||||
22 UI unit tests, TypeScript/Vite build and a Linux x86-64 release build with
|
||||
@@ -201,3 +236,15 @@ for older Ubuntu releases from this build. For local AppImage packaging,
|
||||
linuxdeploy's GTK plugin needs `librsvg-2.0.pc` from the matching `librsvg2-dev`
|
||||
package. Extracting that package into a temporary build directory and setting
|
||||
`PKG_CONFIG_PATH` supplied the missing metadata without changing host packages.
|
||||
|
||||
The 0.1.3 updater checkpoint passed 76 native tests (6 live tests ignored),
|
||||
28 UI tests, 11 publisher tests with real minisign and TypeScript/Vite build.
|
||||
Nine browser scenarios used mocked IPC. The signed Linux AppImage/deb were
|
||||
published on 2026-09-10 with a signed stable feed; feed bytes, signatures and
|
||||
public HTTPS responses were verified. A separately invoked live native test
|
||||
downloaded the production release, rejected corrupted bytes without changing
|
||||
the old file, atomically updated a temporary copy of 0.1.2 and compared hashes.
|
||||
The original source AppImage was retained. The installed 0.1.3 AppImage was
|
||||
then started from `~/Applications` and its captured runtime paths verified.
|
||||
This is not a full GUI update/restart cycle or a Windows/macOS installation test.
|
||||
The Linux build host remains Ubuntu 26.04.
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
# Signed launcher updates
|
||||
|
||||
The application updater is separate from the signed Aeronautics modpack
|
||||
manifest. Its only metadata endpoint is
|
||||
`https://shacraft.ru/launcher/updates/stable.json`. Its artifact URLs are confined
|
||||
to `https://shacraft.ru/downloads/shacraft-launcher/<version>/<filename>`.
|
||||
The webview cannot choose a URL, signing key or executable path. The native
|
||||
updater verifies signatures before installing; an unavailable or invalid feed
|
||||
does not prevent playing with the installed launcher.
|
||||
|
||||
The first version containing the updater must be installed manually. Version
|
||||
0.1.2 has no code capable of installing this feature itself. On Linux, automatic
|
||||
replacement is for AppImage installations; deb installations and development
|
||||
binaries use the manual download path. Windows/macOS publication and actual
|
||||
installation tests remain separate release work; supporting a platform in the
|
||||
feed schema does not certify a working release on it.
|
||||
|
||||
## Release-line compatibility
|
||||
|
||||
The archived 2026-09-09 review bundle at
|
||||
`/home/emil/Desktop/shacraft-updater-review/README.md` describes a different,
|
||||
unreleased updater prototype: GitHub-hosted `latest.json`, a different pinned
|
||||
key and the former LoginSystem/Game Bridge proof flow. Its successful CI and
|
||||
prototype version numbers do not establish compatibility with the deployed
|
||||
admission protocol. The current 0.1.3 release follows the deployed 0.1.2
|
||||
admission line based on `bf43254`, using the ShaCraft-hosted feed described here.
|
||||
|
||||
Never publish the archived `CI-NOT-FOR-RELEASE`/`CI_NOT_FOR_RELEASE` packages or
|
||||
substitute the unreleased 0.2.0 prototype for an admission-compatible release.
|
||||
Do not merge its updater key, endpoint or account flow blindly: that can break
|
||||
both update continuity and server login. Future reconciliation requires an
|
||||
explicit compatibility review retaining admission support and the key/feed
|
||||
contract already distributed to players, or a separately designed migration.
|
||||
|
||||
## Authentication contract
|
||||
|
||||
The public key embedded in the application is a **dedicated Tauri updater
|
||||
key**, separate from the existing modpack manifest key. Tauri's minisign format
|
||||
wraps the entire minisign public-key/signature text in standard base64. The
|
||||
contents of a `.sig` file belong in metadata, not its filename or URL.
|
||||
|
||||
The stable feed contains the normal Tauri fields `version`, `notes`, `pub_date`
|
||||
and `platforms`, and two additional fields:
|
||||
|
||||
- `signedPayload`: standard base64 of the exact UTF-8 JSON bytes containing
|
||||
only the four normal fields. The publisher produces these bytes with sorted
|
||||
keys, compact separators, literal UTF-8 and no trailing newline.
|
||||
- `metadataSignature`: the Tauri `.sig` contents for those exact payload bytes,
|
||||
signed with the same updater key that signs the application packages.
|
||||
|
||||
The launcher authenticates the payload, requires it to equal the visible
|
||||
fields and then selects the signed platform artifact. This also authenticates
|
||||
the version and artifact URL: an old signed installer cannot be relabelled as
|
||||
a newer release by modifying unsigned metadata. Every artifact is separately
|
||||
verified through Tauri's built-in updater signature check. The current version
|
||||
must increase; there is no unsigned or automatic downgrade fallback.
|
||||
|
||||
The stable publisher accepts only plain `MAJOR.MINOR.PATCH` versions and these
|
||||
platforms: `linux-x86_64` (`.AppImage`), `windows-x86_64` (`.exe` or `.msi`),
|
||||
`darwin-x86_64` and `darwin-aarch64` (`.app.tar.gz`). Artifact filenames contain
|
||||
only ASCII letters, digits, dots, underscores and hyphens. Files must already
|
||||
exist in the matching version directory, must not be symlinks and must be
|
||||
between 1 byte and 256 MiB. A platform without a tested signed artifact is
|
||||
omitted, never represented by an empty signature or another platform's file.
|
||||
|
||||
## Keys and builds
|
||||
|
||||
The production private key stays **only on the operator's local machine** at
|
||||
`/home/emil/.local/share/shacraft-updater/production.key`, with owner-only
|
||||
permissions. Its public companion is `production.key.pub`. Never transfer the
|
||||
private key to the web server, GitHub, CI, logs, chat, a package or a public
|
||||
artifact. Signing commands below pass the local path, not the key contents.
|
||||
Keep a protected operator-controlled backup: replacing or losing the key will
|
||||
break continuity for installations trusting the existing public key. There is
|
||||
no automatic key rotation mechanism in this release.
|
||||
|
||||
Normal `build.yml` jobs explicitly merge `scripts/tauri-unsigned.json` to disable
|
||||
updater signing. They upload ordinary packages and unsigned macOS `.app.tar.gz`
|
||||
archives. CI does not receive the production key and does not publish the
|
||||
stable feed. A release operator reviews/tests these build artifacts, then signs
|
||||
the chosen packages locally. For a signed local Tauri bundle build, set
|
||||
`TAURI_SIGNING_PRIVATE_KEY` to the protected key path; never disable verification
|
||||
in the application to make a build pass.
|
||||
|
||||
Updater signatures authenticate ShaCraft's update channel. They are separate
|
||||
from Windows Authenticode, Apple signing/notarization, and Linux distribution
|
||||
package signatures; passing updater checks does not establish those assurances.
|
||||
|
||||
## Local preparation and signing
|
||||
|
||||
The publisher requires Python 3.10+ and `minisign`. It performs verification
|
||||
through the standard minisign CLI, without implementing cryptography in Python.
|
||||
`--minisign /absolute/path/to/minisign` supports a locally extracted tool without
|
||||
installing a global package. Run these examples from the launcher repository,
|
||||
substituting the actual release version and tested filenames.
|
||||
|
||||
1. Stage immutable, tested packages below a local downloads root. The following
|
||||
example assumes the Linux artifact already exists at
|
||||
`/tmp/shacraft-release/downloads/0.1.3/ShaCraft.Launcher_0.1.3_amd64.AppImage`
|
||||
and release notes exist at `/tmp/shacraft-release/notes.txt`. Create signatures
|
||||
with the Tauri CLI; `.sig` is written beside each artifact:
|
||||
|
||||
```bash
|
||||
npm run tauri -- signer sign \
|
||||
--private-key-path /home/emil/.local/share/shacraft-updater/production.key \
|
||||
/tmp/shacraft-release/downloads/0.1.3/ShaCraft.Launcher_0.1.3_amd64.AppImage
|
||||
```
|
||||
|
||||
2. Prepare a deterministic payload after verifying every package signature.
|
||||
Repeat `--artifact PLATFORM=FILENAME` for each tested platform included in this
|
||||
release. Do not list a deb, dmg, nonexistent package or untested architecture:
|
||||
|
||||
```bash
|
||||
python3 scripts/publish_launcher_update.py prepare \
|
||||
--version 0.1.3 \
|
||||
--downloads-root /tmp/shacraft-release/downloads \
|
||||
--artifact linux-x86_64=ShaCraft.Launcher_0.1.3_amd64.AppImage \
|
||||
--notes-file /tmp/shacraft-release/notes.txt \
|
||||
--public-key /home/emil/.local/share/shacraft-updater/production.key.pub \
|
||||
--payload /tmp/shacraft-release/release.payload.json
|
||||
```
|
||||
|
||||
3. Inspect the payload and sign its exact bytes locally:
|
||||
|
||||
```bash
|
||||
npm run tauri -- signer sign \
|
||||
--private-key-path /home/emil/.local/share/shacraft-updater/production.key \
|
||||
/tmp/shacraft-release/release.payload.json
|
||||
```
|
||||
|
||||
Editing notes, timestamps, versions, signatures or URLs after this step
|
||||
invalidates the metadata signature. Prepare and sign again after any change.
|
||||
|
||||
## Publication
|
||||
|
||||
Upload **only** the packages, their `.sig` files, `release.payload.json`, its
|
||||
`.sig`, the public key and the publisher script. Stage and hash-check artifacts
|
||||
before publishing metadata. Production paths are:
|
||||
|
||||
- Downloads root: `/root/shacraft/caddy/www/downloads/shacraft-launcher`.
|
||||
- Stable feed: `/root/shacraft/data/launcher/updates/stable.json`.
|
||||
- Public feed: `https://shacraft.ru/launcher/updates/stable.json`.
|
||||
|
||||
Keep previous version directories immutable and save the current feed before
|
||||
replacing it. Run the publisher on the host with a public-key file and minisign
|
||||
available there. Neither operation needs a private key:
|
||||
|
||||
```bash
|
||||
python3 publish_launcher_update.py publish \
|
||||
--downloads-root /root/shacraft/caddy/www/downloads/shacraft-launcher \
|
||||
--public-key /path/to/production.key.pub \
|
||||
--payload /path/to/release.payload.json \
|
||||
--signature /path/to/release.payload.json.sig \
|
||||
--output /root/shacraft/data/launcher/updates/stable.json \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
After that succeeds, repeat without `--dry-run`. The publisher verifies metadata
|
||||
and all artifacts under the public key, authenticates the previous feed before
|
||||
comparing versions, and refuses same-version replacement or downgrade. It holds
|
||||
an exclusive publication lock and writes/fsyncs a temporary sibling before
|
||||
atomically replacing `stable.json`. Dry-run validates everything but does not
|
||||
replace the feed. Do not change staged artifacts concurrently with publication.
|
||||
Do not overwrite a released version to add another platform: publish a higher
|
||||
version containing the complete intended platform set.
|
||||
|
||||
Alternatively, run the same verification locally against byte-for-byte copies
|
||||
of the current feed and staged downloads, then deploy the resulting feed only
|
||||
after checking uploaded package and metadata hashes against those validated
|
||||
files. An initial publication has no previous feed; subsequent publications
|
||||
must validate against the actual deployed feed, not an empty staging directory.
|
||||
|
||||
Caddy should serve this feed as JSON with `Cache-Control: no-store`. Check the
|
||||
public response, decoded metadata, signatures and downloadable artifact hashes
|
||||
after deployment. Exercise a real installed AppImage updating to a higher
|
||||
version, including relaunch and retained settings/account state. Unit tests,
|
||||
packaging or a browser mock alone do not establish successful installation.
|
||||
If a release is faulty, stop offering it and publish a corrected higher version;
|
||||
do not weaken signature checks or silently downgrade users.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
python3 -m unittest discover -s scripts -p 'test_*.py'
|
||||
```
|
||||
|
||||
Publisher tests exercise the real minisign CLI with temporary test keys,
|
||||
including valid publication, modified packages and metadata, authenticated
|
||||
previous-version checks, downgrade refusal, URL/path restrictions and dry-run.
|
||||
No test private key is checked into the repository. CI installs minisign so
|
||||
the signature tests run; locally they explicitly skip if the tool is absent.
|
||||
Set `SHACRAFT_TEST_MINISIGN` to use an extracted executable.
|
||||
|
||||
The artifact formats and signature encoding follow the
|
||||
[official Tauri updater documentation](https://v2.tauri.app/plugin/updater/).
|
||||
Reference in New Issue
Block a user