cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

release/v0.1.0-pins
goq / docs / public-release-delivery.md
public-release-delivery.md 10.5K source

Public release delivery

Portal and Sigil have different delivery contracts. Portal is a compiled desktop download. Sigil is a machine bootstrap for a dedicated Bazzite host. Neither path may fall back to an unsigned development build.

Release asset contract

The first public release is a draft or prerelease until capability-ticket onboarding and the release-candidate hardware pass are complete.

Sigil publishes exactly these Linux glibc 2.17 x86_64 assets for a tag such as v0.3.0-alpha.1:

  • sigil-v0.3.0-alpha.1-linux-glibc2.17-x86_64.tar.gz
  • sigil-v0.3.0-alpha.1-linux-glibc2.17-x86_64.tar.gz.sha256
  • sigil-v0.3.0-alpha.1-linux-glibc2.17-x86_64.tar.gz.minisig

The Sigil asset suffix names the binary target contract, not a support claim. The public bootstrap and package-owned activation path remain restricted to the Bazzite appliance until SteamOS or another host environment passes its own packaging, capability, cold-boot, and hardware acceptance gates. The canonical suffix is pinned in release/sigil-target-contract.txt.

Portal preserves the package builder's versioned macOS contract:

  • Portal-0.3.0-alpha.1-arm64.dmg
  • Portal-0.3.0-alpha.1-arm64.dmg.sha256
  • Portal-0.3.0-alpha.1-arm64.json

Do not advertise x86_64, universal, or Linux Portal downloads until those exact artifacts pass an equivalent signed packaging gate.

macOS x86_64, Linux x86_64, and Windows x86_64 are built and attested on every release as unpublished preview artifacts, retained only as short-lived Actions artifacts. They are unsigned, are not release assets, and are not a download channel. The published Portal contract remains exactly the three macOS arm64 files above.

Sigil bootstrap trust boundary

website/install-sigil is the public bootstrap source. Before the release channel opens, replace its unconfigured publisher key with the dedicated Minisign public key and review its fingerprint through a separate trusted channel. The secret key and its passphrase must never enter the repository or a general deployment environment.

The bootstrap:

  1. Refuses root, non-Bazzite systems, unsupported architectures, and missing bootstrap tools.
  2. Uses the exact immutable release tag pinned beside the publisher key in the reviewed bootstrap source.
  3. Downloads the versioned archive, checksum, and detached signature from that tag. Opening a new channel therefore requires a reviewed website commit.
  4. Verifies the embedded publisher key before extraction, then verifies the exact asset checksum.
  5. Extracts into a private temporary directory and invokes the package-owned atomic stager as the gaming user.
  6. Does not start or restart Sigil, overwrite identity or host configuration, modify /etc, or guess ambiguous hardware.

scripts/verify-sigil-bootstrap.py keeps the channel fully closed while the sentinels are present. Once opened, it requires the bootstrap's embedded key to exactly match release/sigil-minisign.pub and rejects partial configuration or a malformed immutable release tag before the website can deploy.

A clean Bazzite image does not need a preinstalled Minisign package. The bootstrap downloads the exact official Minisign 0.12 Linux archive and checks its hard-coded SHA-256 before archive inspection, extraction, or execution. It accepts only the reviewed archive layout (including its inert AppleDouble metadata members), extracts only the x86_64 verifier into private temporary state, and invokes that verifier by absolute path rather than trusting PATH. Changing the verifier version, URL, or independently computed content digest requires a reviewed website commit. The Sigil publisher key and release tag remain separate pins and keep the release channel closed while either is unconfigured.

Sigil candidate and offline signing ceremony

The repository stores only the reviewed public key in release/sigil-minisign.pub. Its committed unconfigured sentinel keeps every promotion fail-closed until the offline publisher key exists. Never replace the sentinel with a temporary or CI-generated key.

For an immutable tag that exactly matches crates/sigil-host/Cargo.toml, run the Sigil Release workflow with build-candidate. The protected main environment builds clean tagged source, runs the complete repository gate, creates exactly these unsigned draft assets, and retains the same bytes as a short-lived Actions artifact:

  • sigil-$tag-linux-glibc2.17-x86_64.tar.gz
  • sigil-$tag-linux-glibc2.17-x86_64.tar.gz.sha256

Move those bytes to the offline publisher machine. After independently confirming the tag commit and reviewed public key, sign without exporting the secret key or its passphrase:

scripts/sign-bazzite-release.sh \
  --tag "$tag" \
  --archive "/absolute/offline/path/sigil-$tag-linux-glibc2.17-x86_64.tar.gz" \
  --source-commit "$tag_commit" \
  --minisign-key /absolute/offline/path/sigil-release.key \
  --public-key-file /absolute/offline/path/sigil-minisign.pub

Before promotion, run the Portal release workflow for the same tag. It requires the untouched two-asset Sigil draft, attaches exactly the ad-hoc signed Portal DMG, checksum, and manifest, verifies the five-asset combined draft, and leaves it unpublished. Transfer only the resulting Sigil .minisig back to the online release operator and attach it to that draft. Do not use --clobber for any asset.

Run Sigil Release again with promote-signed-draft. It requires the exact six-asset public-alpha set, verifies the detached Sigil signature with the committed public key, checks the outer digest, inspects the archive without extraction, binds both products to the same tag and commit, re-verifies the Portal digest and release manifest, then mounts the downloaded DMG on a native arm64 macOS runner and repeats the ad-hoc signature, architecture, identifier, and payload checks, including proof that no notarization ticket is present. Only then does it replace the incomplete draft notes and publish the prerelease. The workflow has no input, environment, or secret capable of receiving the offline secret key.

scripts/package-bazzite-release.sh intentionally cannot sign. Its product mode requires a clean worktree, an existing tag resolving exactly to HEAD, a tag equal to Sigil's Cargo version, and the stable public asset name. The two development flags are an inseparable escape hatch and cannot claim a release tag. scripts/verify-sigil-release.sh is the common candidate, offline, and promotion verifier.

No public goq release exists under the former distro-named suffix. Unpublished drafts or locally staged candidates using bazzite-x86_64 must be discarded and rebuilt; release verification intentionally provides no compatibility alias that could make two names identify the same public bytes.

Public Bazzite acceptance record

Repository tests exercise a complete package fixture through fresh install, same-release rerun, upgrade, rollback, identity/config preservation, hostile payload rejection, and proof that no systemctl start, restart, or enable was attempted. That is not a substitute for the public-command gate.

Closing issue #4 still requires operator evidence from clean Bazzite using two distinct signed candidates. Record, without node IDs or credentials:

  1. The Bazzite image/version, x86_64 architecture, public bootstrap SHA-256, release asset SHA-256, and release tag/commit.
  2. A clean curl -fsSL https://goq.sh/install-sigil | bash installation with service active/enabled state captured before and after.
  3. An idempotent rerun with unchanged current, previous, identity, and host configuration.
  4. Promotion to the second signed candidate followed by the same public command, proving new current, old previous, and no implicit service interruption.
  5. sigil-spark-host-rollback without --restart, proving the links reverse, all release checksums revalidate, and service state remains unchanged.

Portal signing boundary

Portal is not signed with an Apple Developer ID and is not notarized. The project has no Apple Developer Program membership, and there is no free path to notarization. This is a deliberate, recorded decision rather than an unfinished step.

The DMG carries only the ad-hoc signature that arm64 macOS requires in order to execute a binary at all. An ad-hoc signature asserts nothing about origin, so the packaging gate additionally proves that no certificate authority chain, no Apple TeamIdentifier, and no notarization ticket crept into a build that claims to be ad-hoc.

Trust therefore comes from provenance, not from Apple:

  • a GitHub/Sigstore build-provenance attestation bound to the exact workflow, tag ref, and source commit;
  • the published SHA-256 beside the DMG;
  • the release manifest, which records signing: "adhoc" and notarized: false rather than claiming verification the artifact does not have.

Promotion requires each of the three Portal assets to verify against the exact workflow, tag ref, and source commit. Users are expected to check the digest and, ideally, gh attestation verify.

The consequence for users is real and must never be hidden: macOS blocks the first launch, and the user has to allow Portal under System Settings, Privacy and Security. The website says so, and the release notes say so.

scripts/package-macos-client.sh --signing developer-id still implements the full Developer ID, notarization, stapling, and Gatekeeper gate for the day a membership exists. It is not exercised by CI and must be re-validated end to end before it is trusted; enabling it also means restoring the release/portal-apple-team-id.txt pin.

The first release automation must verify that the git tag, every Cargo package version, Tauri version, artifact filenames, and manifests agree before it publishes the GitHub Release. Upload every required asset to a draft release, verify the complete set, and only then mark it as a prerelease.

goq.sh reads website/portal-release.json, which is deliberately committed as unavailable until an operator verifies the promoted prerelease and submits a reviewed manifest change. Invalid, missing, or unavailable manifest data never falls back to a development build. See the Portal release runbook for the issue #5 procedure.

Tracked gates