Separate signed Portal downloads from the Sigil machine bootstrap, publish a fail-closed signed-package installer contract, promote the four streaming hardening tracks, and verify the resulting static delivery surface.
- commit
- 8eb370c851635cd99a3d4fef4586160c3615572e
- parent
- 2a3329ba3bad2b15ad9abdcfcf49164632052f74
- author
- Dami <dami.etoile@gmail.com>
| M | README.md | 18 | +++++- |
| A | docs/ | 74 | ++++++++++++++++++++++ |
| M | scripts/ | 14 | ++++- |
| M | website/ | 5 | + |
| M | website/ | 19 | +---- |
| A | website/ | 102 | ++++++++++++++++++++++++++++++ |
@@ -81,8 +81,8 @@ authentication.
8181 the remaining integration gate is forwarding a physical controller attached8282 to the client. Keyboard injection is hardware-proven, and the conventional8383 relative mouse replaces Gamescope-incompatible absolute motion.84-7. **Live-proven:** bounded PipeWire audio capture, Opus delivery, and client85- playback. Add a persistent headless sink and quantify A/V synchronization.84+7. **Live-proven:** bounded PipeWire audio capture, a persistent headless sink,85+ Opus delivery, and client playback. Quantify longer-run A/V synchronization.8686 8. Replace or supplement FIDO pairing with short-lived capability tickets.8787 8888 ## Development@@ -201,6 +201,20 @@ The static `website/` directory is published through Cloudflare Pages after a
201201 merge to `main`. See the [Cloudflare Pages release setup](docs/cloudflare-pages.md)202202 for the one-time project and GitHub environment configuration.203203 204+Portal is distributed as a compiled, signed desktop application; it is never205+installed through a shell pipe. Sigil uses a separate Bazzite machine bootstrap206+at `https://goq.sh/install-sigil`. That bootstrap intentionally fails closed207+until the Minisign publisher trust root and first signed release exist. The208+[public release delivery contract](docs/public-release-delivery.md) defines the209+remaining signing and asset gates.210+211+The promoted streaming-hardening roadmap is tracked as bounded212+[Iroh/MoQ media objects](https://github.com/FelineStateMachine/goq/issues/7),213+[adaptive bitrate](https://github.com/FelineStateMachine/goq/issues/8),214+[motion-sensitive resolution](https://github.com/FelineStateMachine/goq/issues/9),215+and [Auto Codec](https://github.com/FelineStateMachine/goq/issues/10). H.264 at216+1280×800/60 remains the known-safe first target while that work lands.217+204218 ## License205219 206220 MIT@@ -0,0 +1,74 @@
1+# Public release delivery2+3+Portal and Sigil have different delivery contracts. Portal is a compiled4+desktop download. Sigil is a machine bootstrap for a dedicated Bazzite host.5+Neither path may fall back to an unsigned development build.6+7+## Release asset contract8+9+The first public release is a draft or prerelease until capability-ticket10+onboarding and the release-candidate hardware pass are complete.11+12+Sigil publishes exactly these x86_64 Bazzite assets for a tag such as13+`v0.3.0-alpha.1`:14+15+- `sigil-v0.3.0-alpha.1-bazzite-x86_64.tar.gz`16+- `sigil-v0.3.0-alpha.1-bazzite-x86_64.tar.gz.sha256`17+- `sigil-v0.3.0-alpha.1-bazzite-x86_64.tar.gz.minisig`18+19+Portal preserves the package builder's versioned macOS contract:20+21+- `Portal-0.3.0-alpha.1-arm64.dmg`22+- `Portal-0.3.0-alpha.1-arm64.dmg.sha256`23+- `Portal-0.3.0-alpha.1-arm64.json`24+25+Do not advertise x86_64, universal, or Linux Portal downloads until those exact26+artifacts pass an equivalent signed packaging gate.27+28+## Sigil bootstrap trust boundary29+30+`website/install-sigil` is the public bootstrap source. Before the release31+channel opens, replace its `unconfigured` publisher key with the dedicated32+Minisign public key and review its fingerprint through a separate trusted33+channel. The secret key and its passphrase must never enter the repository or a34+general deployment environment.35+36+The bootstrap:37+38+1. Refuses root, non-Bazzite systems, unsupported architectures, and missing39+ verification tools.40+2. Uses the exact immutable release tag pinned beside the publisher key in the41+ reviewed bootstrap source.42+3. Downloads the versioned archive, checksum, and detached signature from that43+ tag. Opening a new channel therefore requires a reviewed website commit.44+4. Verifies the embedded publisher key before extraction, then verifies the45+ exact asset checksum.46+5. Extracts into a private temporary directory and invokes the package-owned47+ atomic stager as the gaming user.48+6. Does not start or restart Sigil, overwrite identity or host configuration,49+ modify `/etc`, or guess ambiguous hardware.50+51+A clean Bazzite image may not include Minisign. The public-alpha gate must52+either provision a pinned verifier with its own hard-coded digest or make the53+verifier prerequisite explicit; it must never silently downgrade to SHA-only54+verification.55+56+## Portal signing boundary57+58+The existing macOS package gate requires a Developer ID Application identity,59+hardened runtime, notarization, stapling, strict Gatekeeper assessment, and a60+release build without `demo-direct-node`. Store the certificate and App Store61+Connect credentials only in a separately protected GitHub `release`62+environment with required reviewers.63+64+The first release automation must verify that the git tag, every Cargo package65+version, Tauri version, artifact filenames, and manifests agree before it66+publishes the GitHub Release. Upload every required asset to a draft release,67+verify the complete set, and only then mark it as a prerelease.68+69+## Tracked gates70+71+- [Capability tickets and Portal onboarding](https://github.com/FelineStateMachine/goq/issues/3)72+- [Signed Sigil package and install command](https://github.com/FelineStateMachine/goq/issues/4)73+- [Signed and notarized Portal downloads](https://github.com/FelineStateMachine/goq/issues/5)74+- [Public-alpha hardware and network acceptance](https://github.com/FelineStateMachine/goq/issues/6)@@ -5,14 +5,14 @@ script_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
55 repo_dir="$(CDPATH='' cd -- "$script_dir/.." && pwd -P)"66 site_dir="$repo_dir/website"77 8-for command_name in find grep node; do8+for command_name in bash find grep node; do99 if ! command -v "$command_name" >/dev/null 2>&1; then1010 printf 'required command is missing: %s\n' "$command_name" >&21111 exit 11212 fi1313 done1414 15-for required_file in index.html styles.css dither.js _headers; do15+for required_file in index.html styles.css dither.js _headers install-sigil; do1616 if [[ ! -f "$site_dir/$required_file" ]]; then1717 printf 'website asset is missing: %s\n' "$required_file" >&21818 exit 1@@ -25,8 +25,18 @@ if find "$site_dir" -type l -print -quit | grep -q .; then
2525 fi2626 2727 node --check "$site_dir/dither.js"28+bash -n "$site_dir/install-sigil"29+[[ -x "$site_dir/install-sigil" ]] || {30+ echo 'Sigil bootstrap must be executable' >&231+ exit 132+}2833 grep -Fq 'href="./styles.css"' "$site_dir/index.html"2934 grep -Fq 'src="./dither.js"' "$site_dir/index.html"3035 grep -Fq 'Content-Security-Policy:' "$site_dir/_headers"36+grep -Fq 'https://goq.sh/install-sigil' "$site_dir/index.html"37+grep -Fq 'github.com/FelineStateMachine/goq/releases' "$site_dir/index.html"38+for issue_number in 7 8 9 10; do39+ grep -Fq "github.com/FelineStateMachine/goq/issues/$issue_number" "$site_dir/index.html"40+done3141 3242 echo 'website_static_gate=ok'@@ -358,22 +358,13 @@
358358 begin();359359 }360360 361- /* ── Client / host install toggle: swap the command live. ──362- TODO(install-script): confirm exact one-liners once goq.sh363- serves the install scripts (client app + host runtime pkg). */364- var INSTALL_CMD = {365- client: "curl -fsSL https://goq.sh | sh",366- host: "curl -fsSL https://goq.sh/host | sh",367- };368- document.querySelectorAll('input[name="install-target"]').forEach(function (radio) {361+ /* ── Portal is a signed download; Sigil is a machine install command. ── */362+ document.querySelectorAll('input[name="release-target"]').forEach(function (radio) {369363 radio.addEventListener("change", function () {370364 if (!radio.checked) return;371- var cmd = INSTALL_CMD[radio.value] || INSTALL_CMD.client;372- var box = document.querySelector(".install");373- if (!box) return;374- box.setAttribute("data-copy", cmd);375- var textEl = box.querySelector(".install-text");376- if (textEl) textEl.textContent = cmd;365+ document.querySelectorAll("[data-release-target]").forEach(function (panel) {366+ panel.hidden = panel.getAttribute("data-release-target") !== radio.value;367+ });377368 });378369 });379370 @@ -0,0 +1,102 @@
1+2+3+set -euo pipefail4+umask 0775+6+readonly repository="FelineStateMachine/goq"7+readonly publisher_key="unconfigured"8+readonly release_tag="unconfigured"9+10+die() {11+ printf 'Sigil setup failed: %s\n' "$*" >&212+ exit 113+}14+15+if [[ "$publisher_key" == unconfigured || "$release_tag" == unconfigured ]]; then16+ die "the signed Sigil release channel is not open yet"17+fi18+19+[[ "$(uname -s)" == Linux ]] || die "Sigil requires Linux"20+[[ "$(uname -m)" == x86_64 ]] || die "Sigil currently requires x86_64"21+[[ "$(id -u)" -ne 0 ]] || die "run this command as the dedicated gaming user, not root"22+grep -Eq '^ID="?bazzite"?$' /etc/os-release || die "Sigil currently supports Bazzite"23+24+for command_name in awk curl find grep install minisign mktemp rm sha256sum tar tr wc; do25+ command -v "$command_name" >/dev/null 2>&1 \26+ || die "$command_name is required"27+done28+29+[[ "$release_tag" =~ ^v[0-9][0-9A-Za-z._-]*$ ]] || die "the release tag is malformed"30+31+asset_name="sigil-$release_tag-bazzite-x86_64.tar.gz"32+asset_base="https://github.com/$repository/releases/download/$release_tag/$asset_name"33+temp_parent="${TMPDIR:-/tmp}"34+temp_parent="${temp_parent%/}"35+[[ "$temp_parent" == /* && -d "$temp_parent" && ! -L "$temp_parent" ]] \36+ || die "the temporary directory is unsafe"37+temp_root="$(mktemp -d "$temp_parent/goq-sigil-install.XXXXXX")"38+archive_path="$temp_root/$asset_name"39+checksum_path="$archive_path.sha256"40+signature_path="$archive_path.minisig"41+unpack_path="$temp_root/unpack"42+43+cleanup() {44+ local status=$?45+ trap - EXIT INT TERM HUP46+ case "$temp_root" in47+ "$temp_parent"/goq-sigil-install.??????) rm -rf -- "$temp_root" ;;48+ esac49+ exit "$status"50+}51+trap cleanup EXIT INT TERM HUP52+53+for suffix in '' .sha256 .minisig; do54+ curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \55+ --retry 3 --retry-all-errors --output "$archive_path$suffix" "$asset_base$suffix"56+done57+58+minisign -Vm "$archive_path" -x "$signature_path" -P "$publisher_key" \59+ || die "the publisher signature is invalid"60+61+[[ "$(wc -l <"$checksum_path" | tr -d ' ')" == 1 ]] \62+ || die "the release checksum file is malformed"63+expected_hash="$(awk 'NF == 2 { print $1 }' "$checksum_path")"64+expected_name="$(awk 'NF == 2 { print $2 }' "$checksum_path")"65+[[ "$expected_hash" =~ ^[0-9a-f]{64}$ && "$expected_name" == "$asset_name" ]] \66+ || die "the release checksum declaration is malformed"67+actual_hash="$(sha256sum "$archive_path" | awk '{ print $1 }')"68+[[ "$actual_hash" == "$expected_hash" ]] || die "the release checksum does not match"69+70+archive_entries="$(tar --list --gzip --file "$archive_path")" \71+ || die "the signed release archive is unreadable"72+[[ -n "$archive_entries" ]] || die "the signed release archive is empty"73+while IFS= read -r archive_entry; do74+ case "$archive_entry" in75+ payload|payload/|payload/*) ;;76+ *) die "the signed release archive escapes its payload root" ;;77+ esac78+ case "/$archive_entry/" in79+ */../*|*/./*) die "the signed release archive contains an unsafe path" ;;80+ esac81+done <<<"$archive_entries"82+if ! LC_ALL=C tar --list --verbose --gzip --file "$archive_path" \83+ | awk '{ type = substr($1, 1, 1); if (type != "-" && type != "d") exit 1 }'84+then85+ die "the signed release archive contains a special file or link"86+fi87+88+install -d -m 0700 "$unpack_path"89+tar --extract --gzip --file "$archive_path" --directory "$unpack_path" \90+ --no-same-owner --no-same-permissions91+payload_path="$unpack_path/payload"92+[[ -d "$payload_path" && ! -L "$payload_path" ]] || die "the package payload is missing"93+[[ -x "$payload_path/stage-this-release.sh" \94+ && ! -L "$payload_path/stage-this-release.sh" ]] || die "the package installer is missing"95+[[ -z "$(find "$payload_path" -type l -print -quit)" ]] \96+ || die "the package payload contains a symbolic link"97+98+"$payload_path/stage-this-release.sh"99+printf '%s\n' \100+ "Sigil runtime $release_tag is installed." \101+ "Continue with the hardware and identity gates in the fresh Bazzite runbook." \102+ "The installer did not start or restart the active Sigil service."