cat / goq
clone

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

1-n-sigls
goq / scripts / package-bazzite-release.sh
package-bazzite-release.sh 15.2K executable
1#!/usr/bin/env bash23set -euo pipefail45script_dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)"6repo_dir="$(CDPATH='' cd -- "$script_dir/.." && pwd -P)"7host_binary=""8probe_binary=""9output_path=""10allow_dirty=false11allow_unsigned=false12release_tag=""13linux_target="x86_64-unknown-linux-gnu.2.17"14linux_output_target="x86_64-unknown-linux-gnu"15asset_target_contract_file="$repo_dir/release/sigil-target-contract.txt"16asset_target_contract=""1718usage() {19 cat <<'EOF'20Usage: scripts/package-bazzite-release.sh --output /absolute/path/package.tar.gz [options]2122Create an allowlisted, deterministic Bazzite host runtime package. Product mode23exports the clean source HEAD and builds both generic Linux release binaries in24an isolated Cargo target directory. Its public asset name describes that25Linux/glibc/architecture contract rather than the validated host distribution.26The package includes atomic install,27upgrade/rollback support, the systemd user unit, PipeWire sink, staged udev28rule, complete checksums, and build provenance. It never includes the source29tree, host identity, hardware configuration, environment files, or evidence.30Product mode builds Sigil with the default feature set plus the approved31in-process-gstreamer backend. The manifest records that exact feature contract.3233Options:34 --output PATH New .tar.gz bundle path (required; must not exist)35 --host-binary PATH Development-only prebuilt generic Linux sigil36 --probe-binary PATH Development-only prebuilt generic Linux sigil-probe37 --release-tag TAG Immutable vVERSION tag (required in product mode)38 --allow-dirty Permit development packaging from a dirty worktree39 --allow-unsigned Permit a package without a detached publisher signature40 --help Show this help4142The two prebuilt-binary options are an all-or-none development escape hatch and43require both --allow-dirty and --allow-unsigned. Product mode emits a clean,44unsigned release candidate. Sign it on the offline publisher machine with45scripts/sign-bazzite-release.sh; this builder never receives the secret key.46EOF47}4849die() {50 printf 'package failed: %s\n' "$*" >&251 exit 152}5354sha256_file() {55 local path="$1"56 if command -v sha256sum >/dev/null 2>&1; then57 sha256sum "$path" | awk '{print $1}'58 else59 shasum -a 256 "$path" | awk '{print $1}'60 fi61}6263while [[ $# -gt 0 ]]; do64 case "$1" in65 --output)66 [[ $# -ge 2 ]] || die "--output requires a value"67 output_path="$2"68 shift 269 ;;70 --host-binary)71 [[ $# -ge 2 ]] || die "--host-binary requires a value"72 host_binary="$2"73 shift 274 ;;75 --probe-binary)76 [[ $# -ge 2 ]] || die "--probe-binary requires a value"77 probe_binary="$2"78 shift 279 ;;80 --release-tag)81 [[ $# -ge 2 ]] || die "--release-tag requires a value"82 release_tag="$2"83 shift 284 ;;85 --allow-dirty)86 allow_dirty=true87 shift88 ;;89 --allow-unsigned)90 allow_unsigned=true91 shift92 ;;93 --help|-h)94 usage95 exit 096 ;;97 *) die "unknown argument: $1" ;;98 esac99done100101[[ -n "$output_path" ]] || die "--output is required"102[[ -f "$asset_target_contract_file" && ! -L "$asset_target_contract_file" ]] \103 || die "Sigil asset target contract is missing or unsafe"104[[ "$(wc -l <"$asset_target_contract_file" | tr -d ' ')" == 1 ]] \105 || die "Sigil asset target contract must contain exactly one line"106asset_target_contract="$(<"$asset_target_contract_file")"107[[ "$asset_target_contract" == linux-glibc2.17-x86_64 ]] \108 || die "Sigil asset target contract does not match the approved Linux build target"109case "$output_path" in110 /*.tar.gz) ;;111 *) die "--output must be an absolute .tar.gz path" ;;112esac113[[ ! -e "$output_path" ]] || die "output already exists: $output_path"114[[ ! -e "$output_path.sha256" ]] || die "digest output already exists: $output_path.sha256"115[[ ! -e "$output_path.minisig" ]] || die "signature output already exists: $output_path.minisig"116117caller_supplied_binaries=false118if [[ -n "$host_binary" || -n "$probe_binary" ]]; then119 [[ -n "$host_binary" && -n "$probe_binary" ]] \120 || die "--host-binary and --probe-binary must be supplied together"121 if ! $allow_dirty || ! $allow_unsigned; then122 die "caller-supplied binaries require both --allow-dirty and --allow-unsigned; product mode builds both binaries from clean HEAD"123 fi124 caller_supplied_binaries=true125fi126127development_mode=false128if $allow_dirty || $allow_unsigned || $caller_supplied_binaries; then129 development_mode=true130 if ! $allow_dirty || ! $allow_unsigned; then131 die "development packaging requires both --allow-dirty and --allow-unsigned"132 fi133 [[ -z "$release_tag" ]] || die "development packages must not claim a release tag"134else135 [[ "$release_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z][0-9A-Za-z.-]*)?$ ]] \136 || die "product mode requires --release-tag vVERSION"137 expected_asset="sigil-$release_tag-$asset_target_contract.tar.gz"138 [[ "$(basename -- "$output_path")" == "$expected_asset" ]] \139 || die "product output must be named $expected_asset"140fi141142[[ -x "$script_dir/install-bazzite-package.sh" ]] || die "package installer is missing"143[[ -x "$script_dir/rollback-bazzite-release.sh" ]] || die "rollback helper is missing"144[[ -f "$script_dir/sigil-host.service" ]] || die "systemd user unit is missing"145[[ -f "$script_dir/50-sigil-spark-audio.conf" ]] || die "PipeWire audio drop-in is missing"146[[ -f "$script_dir/70-sigil-remote-input.rules" ]] || die "udev rule is missing"147[[ -f "$script_dir/72-sigil-uinput.rules" ]] || die "early uinput rule is missing"148[[ -f "$script_dir/99-sigil-uinput.rules" ]] || die "final uinput rule is missing"149[[ -f "$repo_dir/docs/sigil-host-activation.md" ]] || die "host activation guide is missing"150command -v git >/dev/null 2>&1 || die "git is required"151command -v python3 >/dev/null 2>&1 || die "python3 is required for deterministic archives"152command -v tar >/dev/null 2>&1 || die "tar is required"153154git_commit="$(git -C "$repo_dir" rev-parse --verify HEAD)"155[[ "$git_commit" =~ ^[0-9a-f]{40}$ ]] || die "could not resolve the source commit"156git_dirty=false157if [[ -n "$(git -C "$repo_dir" status --porcelain=v1)" ]]; then158 git_dirty=true159 $allow_dirty || die "worktree is dirty; commit the release or pass --allow-dirty for a development package"160fi161if ! $development_mode; then162 git -C "$repo_dir" show-ref --verify --quiet "refs/tags/$release_tag" \163 || die "release tag does not exist: $release_tag"164 tag_commit="$(git -C "$repo_dir" rev-parse "refs/tags/$release_tag^{commit}" 2>/dev/null || true)"165 [[ "$tag_commit" == "$git_commit" ]] \166 || die "release tag $release_tag must resolve exactly to clean HEAD"167fi168product_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$repo_dir/crates/sigil-host/Cargo.toml" | head -n 1)"169[[ -n "$product_version" ]] || die "could not read the Sigil product version"170if ! $development_mode; then171 [[ "$release_tag" == "v$product_version" ]] \172 || die "release tag $release_tag does not match Sigil version v$product_version"173fi174175output_parent="$(dirname -- "$output_path")"176[[ -d "$output_parent" ]] || die "output parent does not exist: $output_parent"177temp_root="$(mktemp -d "$output_parent/.sigil-bazzite-package.XXXXXX")"178case "$temp_root" in179 "$output_parent"/.sigil-bazzite-package.??????) ;;180 *) die "mktemp returned an unexpected path: $temp_root" ;;181esac182183cleanup() {184 local status=$?185 trap - EXIT INT TERM HUP186 case "$temp_root" in187 "$output_parent"/.sigil-bazzite-package.??????) rm -rf -- "$temp_root" ;;188 esac189 exit "$status"190}191trap cleanup EXIT INT TERM HUP192193if $caller_supplied_binaries; then194 [[ -f "$host_binary" && -x "$host_binary" ]] \195 || die "host binary is not executable: $host_binary"196 [[ -f "$probe_binary" && -x "$probe_binary" ]] \197 || die "probe binary is not executable: $probe_binary"198 binary_provenance="caller-supplied-unverified"199 binary_provenance_verified=false200else201 command -v cargo >/dev/null 2>&1 || die "cargo is required to build product binaries"202 command -v cargo-zigbuild >/dev/null 2>&1 \203 || die "cargo-zigbuild is required to build product binaries"204205 build_source="$temp_root/source"206 build_target="$temp_root/cargo-target"207 install -d -m 0700 "$build_source" "$build_target"208 git -C "$repo_dir" archive --format=tar "$git_commit" | tar -xf - -C "$build_source"209 (210 cd "$build_source"211 # Zig uses no host system-library fallback for explicit targets. Ask the212 # GStreamer build scripts to retain pkg-config's native -L directories;213 # these are link-time paths only and do not add an rpath to the binaries.214 PKG_CONFIG_ALLOW_CROSS=1 PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 \215 CARGO_TARGET_DIR="$build_target" \216 cargo zigbuild --locked -p sigil-host --bins \217 --target "$linux_target" --release --no-default-features \218 --features in-process-gstreamer219 )220 host_binary="$build_target/$linux_output_target/release/sigil"221 probe_binary="$build_target/$linux_output_target/release/sigil-probe"222 [[ -f "$host_binary" && -x "$host_binary" ]] \223 || die "product build did not produce sigil"224 [[ -f "$probe_binary" && -x "$probe_binary" ]] \225 || die "product build did not produce sigil-probe"226 binary_provenance="self-built-clean-head"227 binary_provenance_verified=true228fi229230payload="$temp_root/payload"231release_tree="$payload/release"232install -d -m 0700 \233 "$payload" "$release_tree" "$release_tree/assets" "$release_tree/docs" \234 "$release_tree/tools"235install -m 0755 "$host_binary" "$release_tree/sigil"236# Retain one byte-identical compatibility executable for existing automation.237install -m 0755 "$host_binary" "$release_tree/sigil-host"238install -m 0755 "$probe_binary" "$release_tree/sigil-probe"239install -m 0755 "$script_dir/rollback-bazzite-release.sh" \240 "$release_tree/tools/rollback-bazzite-release.sh"241install -m 0644 "$script_dir/sigil-host.service" "$release_tree/assets/sigil-host.service"242install -m 0600 "$script_dir/50-sigil-spark-audio.conf" \243 "$release_tree/assets/50-sigil-spark-audio.conf"244install -m 0644 "$script_dir/70-sigil-remote-input.rules" \245 "$release_tree/assets/70-sigil-remote-input.rules"246install -m 0644 "$script_dir/72-sigil-uinput.rules" \247 "$release_tree/assets/72-sigil-uinput.rules"248install -m 0644 "$script_dir/99-sigil-uinput.rules" \249 "$release_tree/assets/99-sigil-uinput.rules"250install -m 0644 "$repo_dir/docs/sigil-host-activation.md" \251 "$release_tree/docs/sigil-host-activation.md"252install -m 0644 "$repo_dir/LICENSE" "$release_tree/LICENSE"253install -m 0755 "$script_dir/install-bazzite-package.sh" "$payload/install-bazzite-package.sh"254255if ! $development_mode; then256 manifest_release_tag="$release_tag"257 manifest_asset_name="$(basename -- "$output_path")"258 release_kind="product-candidate"259else260 manifest_release_tag="development"261 manifest_asset_name="$(basename -- "$output_path")"262 release_kind="development"263fi264lock_sha256="$(sha256_file "$repo_dir/Cargo.lock")"265toolchain_sha256="$(sha256_file "$repo_dir/rust-toolchain.toml")"266zigbuild_version="$(cargo-zigbuild --version 2>/dev/null | head -n 1 || true)"267[[ -n "$zigbuild_version" ]] || zigbuild_version="unavailable"268python3 - "$release_tree/release-manifest.json" "$product_version" "$git_commit" \269 "$git_dirty" "$lock_sha256" "$toolchain_sha256" "$zigbuild_version" \270 "$binary_provenance" "$binary_provenance_verified" "$linux_target" \271 "$manifest_release_tag" "$manifest_asset_name" "$release_kind" <<'PY'272import json273import pathlib274import sys275276(277 path,278 version,279 commit,280 dirty,281 lock_sha,282 toolchain_sha,283 zigbuild,284 binary_provenance,285 binary_provenance_verified,286 target,287 release_tag,288 asset_name,289 release_kind,290) = sys.argv[1:]291manifest = {292 "format": 2,293 "product": "sigil-host",294 "primary_executable": "sigil",295 "compatibility_executable": "sigil-host",296 "version": version,297 "target": target,298 "profile": "release",299 "features": ["in-process-gstreamer"],300 "demo_auth_bypass": False,301 "demo_direct_node": False,302 "git_commit": commit,303 "git_dirty": dirty == "true",304 "cargo_lock_sha256": lock_sha,305 "rust_toolchain_sha256": toolchain_sha,306 "cargo_zigbuild": zigbuild,307 "binary_provenance": binary_provenance,308 "binary_provenance_verified": binary_provenance_verified == "true",309 "release_tag": release_tag,310 "asset_name": asset_name,311 "release_kind": release_kind,312}313pathlib.Path(path).write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")314PY315chmod 0644 "$release_tree/release-manifest.json"316317release_sums="$release_tree/SHA256SUMS"318: >"$release_sums"319for relative in \320 sigil \321 sigil-host \322 sigil-probe \323 assets/50-sigil-spark-audio.conf \324 assets/70-sigil-remote-input.rules \325 assets/72-sigil-uinput.rules \326 assets/99-sigil-uinput.rules \327 assets/sigil-host.service \328 docs/sigil-host-activation.md \329 tools/rollback-bazzite-release.sh \330 LICENSE \331 release-manifest.json332do333 printf '%s %s\n' "$(sha256_file "$release_tree/$relative")" "$relative" >>"$release_sums"334done335chmod 0644 "$release_sums"336release_id="$(sha256_file "$release_sums")"337printf '%s\n' "$release_id" >"$payload/release-id"338339printf '%s\n' \340 '#!/usr/bin/env bash' \341 'set -euo pipefail' \342 "script_dir=\"\$(cd -- \"\$(dirname -- \"\${BASH_SOURCE[0]}\")\" && pwd -P)\"" \343 "exec \"\$script_dir/install-bazzite-package.sh\" --payload-dir \"\$script_dir\" \"\$@\"" \344 >"$payload/stage-this-release.sh"345chmod 0755 "$payload/stage-this-release.sh"346347package_sums="$payload/PACKAGE-SHA256SUMS"348: >"$package_sums"349for relative in release-id install-bazzite-package.sh stage-this-release.sh release/SHA256SUMS350do351 printf '%s %s\n' "$(sha256_file "$payload/$relative")" "$relative" >>"$package_sums"352done353chmod 0644 "$package_sums"354355archive_tmp="$temp_root/package.tar.gz"356python3 - "$temp_root" "$archive_tmp" <<'PY'357import gzip358import pathlib359import sys360import tarfile361362root = pathlib.Path(sys.argv[1])363output = pathlib.Path(sys.argv[2])364payload = root / "payload"365with output.open("wb") as raw:366 with gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0, compresslevel=9) as compressed:367 with tarfile.open(fileobj=compressed, mode="w", format=tarfile.PAX_FORMAT) as archive:368 for path in sorted(payload.rglob("*"), key=lambda item: item.as_posix()):369 if path.is_symlink():370 raise SystemExit(f"refusing symlink in package: {path}")371 relative = path.relative_to(root).as_posix()372 info = archive.gettarinfo(str(path), arcname=relative)373 info.uid = 0374 info.gid = 0375 info.uname = "root"376 info.gname = "root"377 info.mtime = 0378 if path.is_file():379 with path.open("rb") as stream:380 archive.addfile(info, stream)381 else:382 archive.addfile(info)383PY384tar -tzf "$archive_tmp" >/dev/null385mv -- "$archive_tmp" "$output_path"386387package_sha256="$(sha256_file "$output_path")"388printf '%s %s\n' "$package_sha256" "$(basename -- "$output_path")" >"$output_path.sha256"389if $development_mode; then390 signature_status="absent-development"391else392 signature_status="pending-offline"393fi394395printf 'package=%s\n' "$output_path"396printf 'package_sha256=%s\n' "$package_sha256"397printf 'release_id=%s\n' "$release_id"398printf 'git_commit=%s\n' "$git_commit"399printf 'git_dirty=%s\n' "$git_dirty"400printf 'publisher_signature=%s\n' "$signature_status"