- commit
- 8d2730e29da4b324a47a942a3ead9ea3b9f40d46
- parent
- f8679be7a4c84a5f3ee0774fb08a1307f1b06973
- author
- Dami <dami.etoile@gmail.com>
@@ -183,9 +183,12 @@ Portal, then use **client -> reset enrollment** and confirm the displayed host.
183183 The reset is intentionally explicit and controller reachable; importing a new184184 invitation never silently replaces a working enrollment.185185 186-Provision a dedicated AMD host with the187-[fresh Bazzite host runbook](docs/fresh-bazzite-host.md). The runbook also188-defines the temporary `slate` stand-in used for protocol and daemon extraction.186+Activate an installed dedicated AMD host with the portable, package-owned187+[Sigil host activation guide](docs/sigil-host-activation.md). It uses the188+current gaming user, dynamically discovered hardware, and installed package189+assets without assuming a maintainer hostname or checkout path. The190+[fresh Bazzite host runbook](docs/fresh-bazzite-host.md) remains the engineering191+and hardware-acceptance log, including the temporary `slate` stand-in.189192 The backward-compatible [appliance status v1 contract](docs/sigil-appliance-status-v1.md)190193 and explicit [v2 contract](docs/sigil-appliance-status-v2.md) define the local,191194 redacted interface intended for the Decky management surface.@@ -217,7 +220,7 @@ scripts/stage-bazzite-release.sh \
217220 The stager atomically updates the user-owned `current` symlink only after both218221 binaries, their dynamic libraries, and bounded startup commands validate. Host219222 identity, hardware-specific configuration, and service activation remain220-separate gates in the Bazzite runbook.223+separate gates in the package-owned activation guide.221224 222225 The thin binary stager is only for unmanaged development layouts. It refuses223226 activation when package-managed service, audio, rollback, or udev links follow@@ -255,13 +258,13 @@ The GitHub workflow and package builder never receive the Minisign secret.
255258 256259 The package contains the primary `sigil` host executable, its byte-identical257260 `sigil-host` compatibility copy, the `sigil-probe` diagnostic, installer and258-rollback tools, service/audio/udev assets, license, checksums, and build259-provenance. It cannot contain the source tree, `.env` files, identities, host260-configuration, or test evidence. Its release ID is the SHA-256 of the complete261-installed-file checksum manifest. Identical inputs produce a byte-identical262-archive. The manifest marks product binaries as built from clean `HEAD`;263-caller-supplied development binaries are explicitly marked as having264-unverified provenance.261+rollback tools, service/audio/udev assets, the portable activation guide,262+license, checksums, and build provenance. It cannot contain the source tree,263+`.env` files, identities, host configuration, or test evidence. Its release ID264+is the SHA-256 of the complete installed-file checksum manifest. Identical265+inputs produce a byte-identical archive. The manifest marks product binaries266+as built from clean `HEAD`; caller-supplied development binaries are explicitly267+marked as having unverified provenance.265268 266269 During the legacy rollback window, `sigil-host.service` deliberately starts267270 the byte-identical `sigil-host` compatibility filename. Interactive and@@ -1,4 +1,10 @@
1-# Fresh Bazzite AMD host setup1+# Fresh Bazzite AMD host engineering setup2+3+This document is the maintainer lab log and hardware-acceptance source. It4+intentionally contains historical machine names, checkout paths, evidence, and5+development workflows. A user activating an installed package should follow6+the portable [Sigil host activation guide](sigil-host-activation.md) shipped in7+that package instead.28 39 This runbook takes a dedicated AMD x86_64 machine from a fresh Bazzite install410 to a remotely managed Sigil host. It also defines how `slate` is used as@@ -0,0 +1,294 @@
1+# Activate an installed Sigil host2+3+This is the portable post-install path for a dedicated Bazzite or SteamOS-like4+AMD gaming host. Run it as the gaming user that owns the Gamescope session, not5+as root. The commands use only `$HOME` and `$USER`; no hostname or account name6+is assumed.7+8+The package installer deliberately stops before these steps. Identity creation,9+system input permissions, PipeWire restart, hardware configuration, and service10+enablement each remain visible operator decisions. Re-running a package install11+or rollback preserves the identity and `host.toml` created here.12+13+## 1. Grant the gaming user access to only uinput14+15+The package stages its virtual-input udev rule but does not write `/etc` or16+change group membership. Install that exact package asset together with the two17+rules that bound `/dev/uinput` to a dedicated group:18+19+```bash20+set -euo pipefail21+22+current="$HOME/.local/libexec/sigil-spark/current"23+remote_input_rule="$current/assets/70-sigil-remote-input.rules"24+early_uinput_rule="$current/assets/72-sigil-uinput.rules"25+final_uinput_rule="$current/assets/99-sigil-uinput.rules"26+test -x "$current/sigil"27+for rule in "$remote_input_rule" "$early_uinput_rule" "$final_uinput_rule"; do28+ test -f "$rule" && test ! -L "$rule"29+done30+31+getent group sigil-uinput >/dev/null || sudo groupadd --system sigil-uinput32+sudo usermod --append --groups sigil-uinput "$USER"33+sudo install -o root -g root -m 0644 \34+ "$remote_input_rule" /etc/udev/rules.d/70-sigil-remote-input.rules35+sudo install -o root -g root -m 0644 \36+ "$early_uinput_rule" /etc/udev/rules.d/72-sigil-uinput.rules37+sudo install -o root -g root -m 0644 \38+ "$final_uinput_rule" /etc/udev/rules.d/99-sigil-uinput.rules39+sudo modprobe uinput40+sudo udevadm control --reload-rules41+sudo udevadm trigger --action=add --subsystem-match=misc --sysname-match=uinput42+```43+44+Reboot, or fully sign out and back in, before continuing. A new login is45+required for the user service to inherit the `sigil-uinput` group.46+47+After that login, fail closed unless the device has the expected Linux uinput48+identity, ownership, mode, and no extended ACL:49+50+```bash51+set -euo pipefail52+53+uinput_gid="$(getent group sigil-uinput | cut -d: -f3)"54+test -n "$uinput_gid"55+id -G | tr ' ' '\n' | grep -Fx "$uinput_gid"56+test ! -L /dev/uinput57+test -c /dev/uinput && test -r /dev/uinput && test -w /dev/uinput58+test "$(stat -Lc '%u:%g:%a:%t:%T' /dev/uinput)" = "0:$uinput_gid:660:a:df"59+if getfacl -cp /dev/uinput | grep -Eq '^(user|group):[^:]|^mask:'; then60+ echo '/dev/uinput has an extended access ACL' >&261+ exit 162+fi63+```64+65+Do not add this account to the broad `input` group and do not run Sigil as66+root. Access to uinput is equivalent to local controller, keyboard, and pointer67+control.68+69+## 2. Create the host identity once70+71+```bash72+set -euo pipefail73+umask 07774+75+sigil="$HOME/.local/libexec/sigil-spark/current/sigil"76+identity="$HOME/.local/share/sigil-spark/identity/host.key"77+install -d -m 0700 "$(dirname "$identity")" \78+ "$HOME/.config/sigil-spark" "$HOME/.local/state/sigil-spark"79+if ! test -e "$identity"; then80+ "$sigil" identity init --output "$identity"81+fi82+test -f "$identity" && test ! -L "$identity"83+chmod 0600 "$identity"84+"$sigil" identity show --identity "$identity"85+```86+87+Never copy this seed to a client or rotate it during an upgrade. Portal pairs88+to the host identity represented by this file.89+90+## 3. Select the exact AMD render node and GstVA H.264 factory91+92+Sigil binds the encoder factory to its inspected read-only `device-path`; a93+factory name alone is not enough on a multi-GPU machine. The following chooses94+automatically only when there is one usable AMD node/factory pair. Set95+`RENDER_NODE` and `VA_ENCODER` explicitly before running it when the printed96+inventory is intentionally ambiguous.97+98+```bash99+set -euo pipefail100+101+mapfile -t amd_nodes < <(102+ for sysfs_node in /sys/class/drm/renderD*; do103+ test -e "$sysfs_node/device/driver" || continue104+ test "$(basename "$(readlink -f "$sysfs_node/device/driver")")" = amdgpu \105+ || continue106+ node="/dev/dri/$(basename "$sysfs_node")"107+ test -c "$node" && test -r "$node" && test -w "$node" && printf '%s\n' "$node"108+ done109+)110+printf 'AMD render node: %s\n' "${amd_nodes[@]}"111+if test -n "${RENDER_NODE:-}"; then112+ render_node="$RENDER_NODE"113+else114+ test "${#amd_nodes[@]}" -eq 1115+ render_node="${amd_nodes[0]}"116+fi117+printf '%s\n' "${amd_nodes[@]}" | grep -Fx "$render_node"118+119+mapfile -t va_factories < <(120+ gst-inspect-1.0 | awk '$2 ~ /^va(renderD[0-9]+)?h264(lp)?enc:$/ {121+ sub(/:$/, "", $2); print $2122+ }' | sort -u123+)124+matching_factories=()125+for factory in "${va_factories[@]}"; do126+ inspection="$(gst-inspect-1.0 "$factory")" || continue127+ grep -Fq "Default: \"$render_node\"" <<<"$inspection" || continue128+ grep -Eq '\([0-9]+\): cbr([[:space:]]|$)' <<<"$inspection" || continue129+ matching_factories+=("$factory")130+done131+printf 'matching CBR GstVA factory: %s\n' "${matching_factories[@]}"132+if test -n "${VA_ENCODER:-}"; then133+ va_encoder="$VA_ENCODER"134+else135+ test "${#matching_factories[@]}" -eq 1136+ va_encoder="${matching_factories[0]}"137+fi138+printf '%s\n' "${matching_factories[@]}" | grep -Fx "$va_encoder"139+140+printf 'export SIGIL_RENDER_NODE=%q\n' "$render_node"141+printf 'export SIGIL_VA_ENCODER=%q\n' "$va_encoder"142+```143+144+Keep the two printed export commands for the next step. If automatic selection145+fails, inspect every printed node and factory and rerun with an exact pair, for146+example `RENDER_NODE=/dev/dri/renderD129 VA_ENCODER=varenderD129h264enc`.147+Never substitute a software encoder just to make preflight pass.148+149+## 4. Restart the package-owned audio sink150+151+The package already linked its PipeWire drop-in into the gaming user's config.152+Restart only the PulseAudio-compatible PipeWire service during provisioning,153+then select the persistent headless sink:154+155+```bash156+set -euo pipefail157+158+test -f "$HOME/.config/pipewire/pipewire-pulse.conf.d/50-sigil-spark-audio.conf"159+systemctl --user restart pipewire-pulse.service160+audio_sink_id="$(wpctl status -n | awk '161+ $0 ~ /sigil_spark/ {162+ for (field = 1; field <= NF; field++) {163+ if ($field ~ /^[0-9]+\.$/) {164+ gsub("\\.", "", $field); print $field; exit165+ }166+ }167+ }168+')"169+test "$audio_sink_id" -gt 0170+wpctl set-default "$audio_sink_id"171+pactl get-default-sink | grep -Fx sigil_spark172+```173+174+This restart can interrupt existing local playback. Perform it before starting175+a game.176+177+## 5. Write and validate the hardware configuration178+179+First run the two `export` commands printed by step 3 in this shell. Width and180+height are intentionally omitted so Sigil follows the bounded native Gamescope181+mode instead of assuming one panel resolution.182+183+```bash184+set -euo pipefail185+: "${SIGIL_RENDER_NODE:?run the render-node exports from step 3}"186+: "${SIGIL_VA_ENCODER:?run the encoder exports from step 3}"187+188+sigil="$HOME/.local/libexec/sigil-spark/current/sigil"189+uinput_gid="$(getent group sigil-uinput | cut -d: -f3)"190+config="$HOME/.config/sigil-spark/host.toml"191+ffmpeg_path="$(command -v ffmpeg)"192+pw_dump_path="$(command -v pw-dump)"193+gst_launch_path="$(command -v gst-launch-1.0)"194+gst_inspect_path="$(command -v gst-inspect-1.0)"195+test -n "$uinput_gid"196+197+umask 077198+if test -e "$config" || test -L "$config"; then199+ echo "refusing to replace existing host configuration: $config" >&2200+ exit 1201+fi202+cat >"$config" <<EOF203+identity_path = "$HOME/.local/share/sigil-spark/identity/host.key"204+state_path = "$HOME/.local/state/sigil-spark/runtime"205+source = "gamescope-pipewire"206+framerate = 60207+codec = "h264"208+input_mode = "uinput"209+ffmpeg_path = "$ffmpeg_path"210+211+[uinput]212+device_path = "/dev/uinput"213+expected_owner_uid = 0214+expected_group_gid = $uinput_gid215+expected_mode = 0o660216+217+[gamescope_pipewire]218+node_name = "gamescope"219+media_class = "Video/Source"220+xwayland_display = ":0"221+pw_dump_path = "$pw_dump_path"222+gst_launch_path = "$gst_launch_path"223+gst_inspect_path = "$gst_inspect_path"224+encoder_backend = "external-gst-launch"225+vaapi_encoder = "$SIGIL_VA_ENCODER"226+vaapi_render_node = "$SIGIL_RENDER_NODE"227+rate_control = "cbr"228+bitrate_kbps = 12000229+230+[audio]231+node_name = "sigil_spark"232+media_class = "Audio/Sink"233+bitrate_bps = 96000234+EOF235+chmod 0600 "$config"236+237+"$sigil" config check --config "$config"238+"$sigil" capture probe \239+ --source gamescope-pipewire --config "$config" --frames 300240+```241+242+Both commands must exit zero. The capture probe must report 300 encoded frames,243+zero post-encode drops, and the actual resolved dimensions. Do not enable the244+service after a failed preflight or by weakening the device/factory checks.245+246+## 6. Start, inspect, and only then enable Sigil247+248+```bash249+set -euo pipefail250+251+systemctl --user daemon-reload252+systemctl --user start sigil-host.service253+systemctl --user is-active --quiet sigil-host.service254+invocation="$(systemctl --user show -p InvocationID --value sigil-host.service)"255+test -n "$invocation"256+ready=false257+for _attempt in $(seq 1 60); do258+ if journalctl --user _SYSTEMD_INVOCATION_ID="$invocation" \259+ _SYSTEMD_USER_UNIT=sigil-host.service --no-pager -o cat \260+ | grep -Fxq status=ready261+ then262+ ready=true263+ break264+ fi265+ sleep 1266+done267+test "$ready" = true268+systemctl --user status sigil-host.service --no-pager269+journalctl --user _SYSTEMD_INVOCATION_ID="$invocation" --no-pager -o cat270+271+sigil="$HOME/.local/libexec/sigil-spark/current/sigil"272+"$sigil" appliance status \273+ --config "$HOME/.config/sigil-spark/host.toml" --json274+275+systemctl --user enable sigil-host.service276+```277+278+The exact service invocation must report `status=ready` before enablement. The279+unit runs in the gaming user's ordinary user manager and retries safely if its280+configured Gamescope or PipeWire source is not ready yet; no root daemon or281+interactive desktop capture portal is involved.282+283+Create a one-time, short-lived invitation only after Portal shows its peer ID:284+285+```bash286+"$HOME/.local/libexec/sigil-spark/current/sigil" invitation create \287+ --config "$HOME/.config/sigil-spark/host.toml" \288+ --peer '<PORTAL_IROH_PEER_ID>' \289+ --expires-in-seconds 900 --pointer-keyboard --gamepad \290+ --output "$HOME/sigil.goq-invite"291+```292+293+Move that owner-only file to Portal and confirm the displayed host and grants.294+After the one-time enrollment, ordinary startup remains **PIN -> tap -> play**.@@ -66,7 +66,10 @@ if ! diff -u \
6666 release/SHA256SUMS \6767 release/assets/50-sigil-spark-audio.conf \6868 release/assets/70-sigil-remote-input.rules \69+ release/assets/72-sigil-uinput.rules \70+ release/assets/99-sigil-uinput.rules \6971 release/assets/sigil-host.service \72+ release/docs/sigil-host-activation.md \7073 release/release-manifest.json \7174 release/sigil \7275 release/sigil-host \@@ -82,7 +85,10 @@ if ! diff -u \
8285 LICENSE \8386 assets/50-sigil-spark-audio.conf \8487 assets/70-sigil-remote-input.rules \88+ assets/72-sigil-uinput.rules \89+ assets/99-sigil-uinput.rules \8590 assets/sigil-host.service \91+ docs/sigil-host-activation.md \8692 release-manifest.json \8793 sigil \8894 sigil-host \@@ -465,4 +471,5 @@ printf 'pipewire_restart=not-attempted\n'
465471 printf 'service_activation=not-attempted\n'466472 printf 'system_asset=%s\n' "$asset_dir/70-sigil-remote-input.rules"467473 printf '%s\n' \468- 'next: complete the explicit identity, hardware config, uinput-group/udev, PipeWire restart, and service-enable gates from docs/fresh-bazzite-host.md'474+ "activation_guide=$install_root/current/docs/sigil-host-activation.md" \475+ "next: follow $install_root/current/docs/sigil-host-activation.md as the dedicated gaming user"@@ -133,6 +133,9 @@ fi
133133 [[ -f "$script_dir/sigil-host.service" ]] || die "systemd user unit is missing"134134 [[ -f "$script_dir/50-sigil-spark-audio.conf" ]] || die "PipeWire audio drop-in is missing"135135 [[ -f "$script_dir/70-sigil-remote-input.rules" ]] || die "udev rule is missing"136+[[ -f "$script_dir/72-sigil-uinput.rules" ]] || die "early uinput rule is missing"137+[[ -f "$script_dir/99-sigil-uinput.rules" ]] || die "final uinput rule is missing"138+[[ -f "$repo_dir/docs/sigil-host-activation.md" ]] || die "host activation guide is missing"136139 command -v git >/dev/null 2>&1 || die "git is required"137140 command -v python3 >/dev/null 2>&1 || die "python3 is required for deterministic archives"138141 command -v tar >/dev/null 2>&1 || die "tar is required"@@ -214,7 +217,9 @@ fi
214217 215218 payload="$temp_root/payload"216219 release_tree="$payload/release"217-install -d -m 0700 "$payload" "$release_tree" "$release_tree/assets" "$release_tree/tools"220+install -d -m 0700 \221+ "$payload" "$release_tree" "$release_tree/assets" "$release_tree/docs" \222+ "$release_tree/tools"218223 install -m 0755 "$host_binary" "$release_tree/sigil"219224 # Retain one byte-identical compatibility executable for existing automation.220225 install -m 0755 "$host_binary" "$release_tree/sigil-host"@@ -226,6 +231,12 @@ install -m 0600 "$script_dir/50-sigil-spark-audio.conf" \
226231 "$release_tree/assets/50-sigil-spark-audio.conf"227232 install -m 0644 "$script_dir/70-sigil-remote-input.rules" \228233 "$release_tree/assets/70-sigil-remote-input.rules"234+install -m 0644 "$script_dir/72-sigil-uinput.rules" \235+ "$release_tree/assets/72-sigil-uinput.rules"236+install -m 0644 "$script_dir/99-sigil-uinput.rules" \237+ "$release_tree/assets/99-sigil-uinput.rules"238+install -m 0644 "$repo_dir/docs/sigil-host-activation.md" \239+ "$release_tree/docs/sigil-host-activation.md"229240 install -m 0644 "$repo_dir/LICENSE" "$release_tree/LICENSE"230241 install -m 0755 "$script_dir/install-bazzite-package.sh" "$payload/install-bazzite-package.sh"231242 @@ -298,7 +309,10 @@ for relative in \
298309 sigil-probe \299310 assets/50-sigil-spark-audio.conf \300311 assets/70-sigil-remote-input.rules \312+ assets/72-sigil-uinput.rules \313+ assets/99-sigil-uinput.rules \301314 assets/sigil-host.service \315+ docs/sigil-host-activation.md \302316 tools/rollback-bazzite-release.sh \303317 LICENSE \304318 release-manifest.json@@ -39,7 +39,8 @@ make_payload() {
3939 true_binary="$(readlink -f /usr/bin/true 2>/dev/null || readlink -f /bin/true)"4040 [[ -x "$true_binary" ]]4141 42- install -d -m 0700 "$root" "$release/assets" "$release/tools"42+ install -d -m 0700 \43+ "$root" "$release/assets" "$release/docs" "$release/tools"4344 install -m 0755 "$true_binary" "$release/sigil"4445 install -m 0755 "$true_binary" "$release/sigil-host"4546 install -m 0755 "$true_binary" "$release/sigil-probe"@@ -47,10 +48,19 @@ make_payload() {
4748 printf '[Service]\nExecStart=/fixture serve\n' >"$release/assets/sigil-host.service"4849 printf 'audio fixture\n' >"$release/assets/50-sigil-spark-audio.conf"4950 printf 'udev fixture\n' >"$release/assets/70-sigil-remote-input.rules"51+ printf 'early uinput fixture\n' >"$release/assets/72-sigil-uinput.rules"52+ printf 'final uinput fixture\n' >"$release/assets/99-sigil-uinput.rules"53+ printf '# Activation fixture %s\n' "$tag" \54+ >"$release/docs/sigil-host-activation.md"5055 printf 'MIT\n' >"$release/LICENSE"5156 chmod 0600 "$release/assets/50-sigil-spark-audio.conf"52- chmod 0644 "$release/assets/70-sigil-remote-input.rules" \53- "$release/assets/sigil-host.service" "$release/LICENSE"57+ chmod 0644 \58+ "$release/assets/70-sigil-remote-input.rules" \59+ "$release/assets/72-sigil-uinput.rules" \60+ "$release/assets/99-sigil-uinput.rules" \61+ "$release/assets/sigil-host.service" \62+ "$release/docs/sigil-host-activation.md" \63+ "$release/LICENSE"5464 5565 python3 - "$release/release-manifest.json" "$tag" "$commit" "$asset_name" <<'PY'5666 import json@@ -88,7 +98,10 @@ PY
8898 sigil sigil-host sigil-probe \8999 assets/50-sigil-spark-audio.conf \90100 assets/70-sigil-remote-input.rules \101+ assets/72-sigil-uinput.rules \102+ assets/99-sigil-uinput.rules \91103 assets/sigil-host.service \104+ docs/sigil-host-activation.md \92105 tools/rollback-bazzite-release.sh \93106 LICENSE release-manifest.json94107 do@@ -147,6 +160,14 @@ run_installer "$payload_a" >"$temp_root/install-a.log"
147160 install_root="$test_home/.local/libexec/sigil-spark"148161 [[ "$(basename -- "$(readlink -f "$install_root/current")")" == "$release_a" ]]149162 [[ ! -e "$install_root/previous" && ! -L "$install_root/previous" ]]163+grep -Fqx '# Activation fixture v0.1.0' \164+ "$install_root/current/docs/sigil-host-activation.md"165+grep -Fq "activation_guide=$install_root/current/docs/sigil-host-activation.md" \166+ "$temp_root/install-a.log"167+if grep -Fq 'fresh-bazzite-host.md' "$temp_root/install-a.log"; then168+ printf 'FAIL: package installer delegated activation to the lab runbook\n' >&2169+ exit 1170+fi150171 151172 run_installer "$payload_a" >"$temp_root/repeat-a.log"152173 [[ "$(basename -- "$(readlink -f "$install_root/current")")" == "$release_a" ]]@@ -164,6 +185,8 @@ config_sha="$(sha256sum "$config" | awk '{print $1}')"
164185 run_installer "$payload_b" >"$temp_root/install-b.log"165186 [[ "$(basename -- "$(readlink -f "$install_root/current")")" == "$release_b" ]]166187 [[ "$(basename -- "$(readlink -f "$install_root/previous")")" == "$release_a" ]]188+grep -Fqx '# Activation fixture v0.1.1' \189+ "$install_root/current/docs/sigil-host-activation.md"167190 [[ "$(sha256sum "$identity" | awk '{print $1}')" == "$identity_sha" ]]168191 [[ "$(sha256sum "$config" | awk '{print $1}')" == "$config_sha" ]]169192 @@ -27,6 +27,9 @@ for path in \
2727 scripts/sigil-host.service \2828 scripts/50-sigil-spark-audio.conf \2929 scripts/70-sigil-remote-input.rules \30+ scripts/72-sigil-uinput.rules \31+ scripts/99-sigil-uinput.rules \32+ docs/sigil-host-activation.md \3033 crates/sigil-host/Cargo.toml \3134 Cargo.lock rust-toolchain.toml LICENSE3235 do@@ -0,0 +1,62 @@
1+2+3+set -euo pipefail4+5+script_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"6+repo_dir="$(CDPATH='' cd -- "$script_dir/../.." && pwd -P)"7+guide="$repo_dir/docs/sigil-host-activation.md"8+installer="$repo_dir/scripts/install-bazzite-package.sh"9+bootstrap="$repo_dir/website/install-sigil"10+temp_parent="${TMPDIR:-/tmp}"11+temp_parent="${temp_parent%/}"12+temp_root="$(mktemp -d "$temp_parent/sigil-activation-guide.XXXXXX")"13+14+cleanup() {15+ local status=$?16+ trap - EXIT INT TERM HUP17+ case "$temp_root" in18+ "$temp_parent"/sigil-activation-guide.??????) rm -rf -- "$temp_root" ;;19+ esac20+ exit "$status"21+}22+trap cleanup EXIT INT TERM HUP23+24+fail() {25+ printf 'FAIL: %s\n' "$1" >&226+ exit 127+}28+29+[[ -f "$guide" && ! -L "$guide" ]] || fail 'the activation guide is missing or unsafe'30+31+awk '32+ /^```bash$/ { inside = 1; next }33+ /^```$/ && inside { inside = 0; print ""; next }34+ inside { print }35+' "$guide" >"$temp_root/guide-code.sh"36+bash -n "$temp_root/guide-code.sh"37+38+if grep -Eq 'tank@|/Users/dami|Developer/sigil-spark|scripts/' "$guide"; then39+ fail 'the product activation guide contains a maintainer or source-checkout path'40+fi41+if grep -Eq '/dev/dri/renderD128|vaapi_encoder = "vah264enc"' "$guide"; then42+ fail 'the activation guide pins the original test host GPU contract'43+fi44+45+grep -Fq 'refusing to replace existing host configuration' "$guide" \46+ || fail 'the activation guide can overwrite an existing host configuration'47+grep -Fq 'current/assets/72-sigil-uinput.rules' "$guide" \48+ || fail 'the activation guide does not use its packaged early uinput rule'49+grep -Fq 'current/assets/99-sigil-uinput.rules' "$guide" \50+ || fail 'the activation guide does not use its packaged final uinput rule'51+grep -Fq 'native Gamescope' "$guide" \52+ || fail 'the activation guide does not preserve dynamic native resolution'53+54+for output_script in "$installer" "$bootstrap"; do55+ grep -Fq 'current/docs/sigil-host-activation.md' "$output_script" \56+ || fail "$(basename "$output_script") does not print the persistent guide path"57+ if grep -Fq 'fresh-bazzite-host.md' "$output_script"; then58+ fail "$(basename "$output_script") still delegates activation to the lab runbook"59+ fi60+done61+62+printf 'sigil_host_activation_guide_tests=ok\n'@@ -71,7 +71,10 @@ release = {
7171 "sigil-probe": b"fixture probe\n",7272 "assets/50-sigil-spark-audio.conf": b"audio\n",7373 "assets/70-sigil-remote-input.rules": b"udev\n",74+ "assets/72-sigil-uinput.rules": b"early uinput\n",75+ "assets/99-sigil-uinput.rules": b"final uinput\n",7476 "assets/sigil-host.service": b"[Service]\nExecStart=/fixture\n",77+ "docs/sigil-host-activation.md": b"# Activation fixture\n",7578 "tools/rollback-bazzite-release.sh": b"#!/usr/bin/env bash\nexit 0\n",7679 "LICENSE": b"MIT\n",7780 "release-manifest.json": (json.dumps(manifest, sort_keys=True, indent=2) + "\n").encode(),@@ -92,7 +95,12 @@ package_sums = sums(payload)
9295 files = {"payload/PACKAGE-SHA256SUMS": package_sums}9396 files.update({f"payload/{name}": data for name, data in payload.items()})9497 files.update({f"payload/release/{name}": data for name, data in release.items()})95-dirs = ["payload/release", "payload/release/assets", "payload/release/tools"]98+dirs = [99+ "payload/release",100+ "payload/release/assets",101+ "payload/release/docs",102+ "payload/release/tools",103+]96104 if variant == "unexpected":97105 files["payload/unexpected"] = b"unexpected\n"98106 @@ -618,6 +618,11 @@ done
618618 self.assertEqual(self.minisign_marker.read_text(encoding="utf-8"), "invoked\n")619619 self.assertFalse(self.forbidden_marker.exists())620620 self.assertIn(f"Sigil runtime {RELEASE_TAG} is installed.", result.stdout)621+ self.assertIn(622+ ".local/libexec/sigil-spark/current/docs/sigil-host-activation.md",623+ result.stdout,624+ )625+ self.assertNotIn("fresh Bazzite runbook", result.stdout)621626 self.assertEqual(list(self.tmp_dir.glob("goq-sigil-install.*")), [])622627 623628 @@ -133,6 +133,7 @@ source_commit = sys.argv[4]
133133 expected_dirs = {134134 "payload/release",135135 "payload/release/assets",136+ "payload/release/docs",136137 "payload/release/tools",137138 }138139 release_files = {@@ -140,7 +141,10 @@ release_files = {
140141 "SHA256SUMS",141142 "assets/50-sigil-spark-audio.conf",142143 "assets/70-sigil-remote-input.rules",144+ "assets/72-sigil-uinput.rules",145+ "assets/99-sigil-uinput.rules",143146 "assets/sigil-host.service",147+ "docs/sigil-host-activation.md",144148 "release-manifest.json",145149 "sigil",146150 "sigil-host",@@ -162,5 +162,5 @@ payload_path="$unpack_path/payload"
162162 "$payload_path/stage-this-release.sh"163163 printf '%s\n' \164164 "Sigil runtime $release_tag is installed." \165- "Continue with the hardware and identity gates in the fresh Bazzite runbook." \165+ "Continue with $HOME/.local/libexec/sigil-spark/current/docs/sigil-host-activation.md." \166166 "The installer did not start or restart the active Sigil service."