Support signed deb self-updates with system authorization in 0.1.4
This commit is contained in:
@@ -23,7 +23,8 @@ read-only `https://shacraft.ru/api/online/aoc` endpoint. It is display-only:
|
||||
the result never controls files, versions, URLs, or the launch command.
|
||||
|
||||
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.
|
||||
Version 0.1.4 adds installed deb updates with system administrator confirmation.
|
||||
The first AppImage upgrade from 0.1.2 and deb upgrade from 0.1.3 are 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
|
||||
@@ -195,6 +196,24 @@ 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.
|
||||
|
||||
For installed deb packages, 0.1.4 selects only `linux-x86_64-deb`. The feed also
|
||||
retains the identical legacy `linux-x86_64` and explicit `linux-x86_64-appimage`
|
||||
AppImage entries so installed 0.1.3 readers remain compatible. Remote metadata
|
||||
cannot switch a deb installation into an AppImage installation.
|
||||
|
||||
`deb_updater.rs` checks root ownership of the installed executable and its
|
||||
parents and dpkg's ownership/version record. One `pkexec` invocation starts an
|
||||
early non-GUI mode of `/usr/bin/shacraft-launcher`; no password is collected by
|
||||
the launcher and no fallback prompt runs after cancellation. Bounded stdin
|
||||
framing carries the signed envelope and package bytes, never user paths.
|
||||
The helper authenticates both again as root, checks exact package identity
|
||||
`sha-craft-launcher`, architecture and version, stages the package under a
|
||||
root-only temporary directory and invokes the fixed dpkg installer. An explicit
|
||||
`--refuse-downgrade` protects against another installation winning the version
|
||||
race. Failed/partial package transactions require honest system-package recovery;
|
||||
they are not reported as completed or automatically retried. Restart launches
|
||||
the fixed installed executable even after dpkg replaces the running inode.
|
||||
|
||||
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.
|
||||
@@ -248,3 +267,19 @@ 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.
|
||||
|
||||
The 0.1.4 checkpoint passed 84 native tests (6 ignored), 32 UI tests and 18
|
||||
publisher tests; 12 browser scenarios use mocked IPC. In a disposable Ubuntu
|
||||
26.04 Docker container without network or production mounts, the actual signed
|
||||
deb helper passed 10 scenarios: unprivileged invocation, truncated/trailing
|
||||
input, damaged metadata/package, dpkg lock, unsafe temporary directory,
|
||||
successful installation, replay and downgrade refusal. The fixture installed
|
||||
the genuine old 0.1.3 package and bootstrapped the new verifier binary over its
|
||||
package record; it then installed the genuine signed 0.1.4. It did not relabel
|
||||
signed versions. Dpkg reported 0.1.4 and a fixture profile marker survived.
|
||||
This tests the elevated helper and dpkg, not a real desktop PolicyKit dialog.
|
||||
Cancellation/error rendering is covered by unit/browser scenarios. Published
|
||||
metadata and deb bytes match the locally verified files; both website download
|
||||
buttons target 0.1.4. No user host package installation was performed for QA.
|
||||
The live native AppImage smoke also passed against the published 0.1.4 feed,
|
||||
including corruption rejection and replacement of only a temporary source copy.
|
||||
|
||||
+85
-14
@@ -9,9 +9,13 @@ 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
|
||||
0.1.2 has no code capable of installing this feature itself. AppImage supports
|
||||
self-updates from 0.1.3; deb adds them in 0.1.4. An existing 0.1.3 deb therefore
|
||||
needs one manual upgrade to 0.1.4 before its own update button can work. A deb
|
||||
installation keeps its package format and asks for system administrator
|
||||
authorization when installing an update. The launcher itself runs as the normal
|
||||
user. 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.
|
||||
|
||||
@@ -22,7 +26,7 @@ The archived 2026-09-09 review bundle at
|
||||
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 protocol. The 0.1.3 and 0.1.4 releases follow 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
|
||||
@@ -56,13 +60,65 @@ 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`),
|
||||
platforms: `linux-x86_64` (legacy `.AppImage`), `linux-x86_64-appimage`
|
||||
(`.AppImage`), `linux-x86_64-deb` (`.deb`), `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.
|
||||
|
||||
Format-aware Linux feeds must contain both AppImage keys with exactly the same
|
||||
URL and signature. This keeps 0.1.3 clients on their original AppImage path.
|
||||
New AppImage clients prefer `linux-x86_64-appimage` and can read the legacy key;
|
||||
deb clients require `linux-x86_64-deb` and never fall back to an AppImage.
|
||||
Preserve all three entries when publishing a release that supports both formats.
|
||||
|
||||
After verifying each signature, the publisher also checks Linux package format.
|
||||
AppImage must have the ELF64 little-endian x86_64 and type-2 AppImage header.
|
||||
For deb, `/usr/bin/dpkg-deb` must report package `sha-craft-launcher`, architecture
|
||||
`amd64` and the exact signed release version. Inspection uses fixed arguments,
|
||||
no shell, a cleared environment, a 10-second timeout and a 4 KiB output limit.
|
||||
It does not install a package or execute its maintainer scripts. This protects
|
||||
against accidental publication of the wrong signed package; an installer
|
||||
signature remains mandatory and is checked before package inspection.
|
||||
|
||||
## Debian installation boundary
|
||||
|
||||
The installed launcher must be `/usr/bin/shacraft-launcher`, owned by root in
|
||||
root-owned directories that other users cannot write. The package database
|
||||
must assign that file to an installed `sha-craft-launcher` of the expected
|
||||
architecture. The updater needs the system `pkexec` authorization agent; it
|
||||
does not collect a password or fall back to running a shell with privileges.
|
||||
|
||||
After the normal-user downloader verifies the update, `pkexec` launches the
|
||||
fixed installed binary with `--shacraft-install-deb`. This mode runs before
|
||||
Tauri/GTK initialization. It accepts only length-bounded signed metadata and
|
||||
package bytes over stdin, never a user-provided package path. The root helper
|
||||
independently verifies the metadata, selects only the exact deb target, checks
|
||||
the package signature and requires a higher version than the current dpkg
|
||||
database. It writes the verified bytes to a root-created mode-0700 temporary
|
||||
directory under the validated `/var/tmp`; the file has mode 0600.
|
||||
|
||||
The helper checks the package's exact name, version and architecture with
|
||||
`dpkg-deb`, then invokes fixed `dpkg --refuse-downgrade --install` arguments in
|
||||
an environment without inherited variables. Dpkg's own downgrade refusal
|
||||
protects against a competing newer installation between the version check and
|
||||
the package-manager lock. A successful result also requires the package
|
||||
database to report the intended version as installed. The temporary package
|
||||
is removed on completion. A signed deb may include maintainer scripts, which
|
||||
dpkg runs with administrator privileges as part of normal installation: review
|
||||
release package contents before signing.
|
||||
|
||||
Cancellation of system authorization, missing authorization support, signature
|
||||
rejection, a busy package manager and installation failure have distinct
|
||||
messages. There is no automatic retry with weaker checks. Dpkg installation
|
||||
is not an atomic file replacement: dependency/configuration failures or power
|
||||
loss can require normal package-manager recovery. The launcher reports failure
|
||||
instead of claiming the old installation is intact. Successful deb updates
|
||||
restart the fixed installed binary as the ordinary user. These guarantees
|
||||
are separate from AppImage's same-directory atomic replacement.
|
||||
|
||||
## Keys and builds
|
||||
|
||||
The production private key stays **only on the operator's local machine** at
|
||||
@@ -88,33 +144,40 @@ 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
|
||||
The publisher requires Python 3.10+ and `minisign`; releases containing deb also
|
||||
require `/usr/bin/dpkg-deb` (Debian/Ubuntu's `dpkg` package). 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`
|
||||
example assumes both tested Linux artifacts already exist below
|
||||
`/tmp/shacraft-release/downloads/0.1.4/`
|
||||
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
|
||||
/tmp/shacraft-release/downloads/0.1.4/ShaCraft.Launcher_0.1.4_amd64.AppImage
|
||||
npm run tauri -- signer sign \
|
||||
--private-key-path /home/emil/.local/share/shacraft-updater/production.key \
|
||||
/tmp/shacraft-release/downloads/0.1.4/ShaCraft.Launcher_0.1.4_amd64.deb
|
||||
```
|
||||
|
||||
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:
|
||||
release. Keep the legacy AppImage alias. Do not list a dmg, nonexistent
|
||||
package or untested architecture:
|
||||
|
||||
```bash
|
||||
python3 scripts/publish_launcher_update.py prepare \
|
||||
--version 0.1.3 \
|
||||
--version 0.1.4 \
|
||||
--downloads-root /tmp/shacraft-release/downloads \
|
||||
--artifact linux-x86_64=ShaCraft.Launcher_0.1.3_amd64.AppImage \
|
||||
--artifact linux-x86_64=ShaCraft.Launcher_0.1.4_amd64.AppImage \
|
||||
--artifact linux-x86_64-appimage=ShaCraft.Launcher_0.1.4_amd64.AppImage \
|
||||
--artifact linux-x86_64-deb=ShaCraft.Launcher_0.1.4_amd64.deb \
|
||||
--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
|
||||
@@ -173,8 +236,12 @@ 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.
|
||||
version, including relaunch and retained settings/account state. For deb, also
|
||||
exercise administrator cancellation, package-manager lock conflicts, failed
|
||||
installation and a successful package upgrade/relaunch. Use an isolated system
|
||||
for destructive package-manager failure cases; never modify player data as a
|
||||
test fixture. Unit tests, packaging or a browser mock alone do not establish
|
||||
successful installation or distribution compatibility.
|
||||
If a release is faulty, stop offering it and publish a corrected higher version;
|
||||
do not weaken signature checks or silently downgrade users.
|
||||
|
||||
@@ -187,6 +254,10 @@ 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.
|
||||
Linux tests also build real temporary deb packages with `dpkg-deb`, validate
|
||||
package/version/architecture, ensure inspection never executes maintainer
|
||||
scripts, retain the legacy AppImage feed alias, and reject malformed signed
|
||||
Linux packages. Package-inspection output and time bounds are exercised.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user