diff --git a/.gitattributes b/.gitattributes index 3b002266..b7852997 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,6 +15,7 @@ build/usb/hooks/* text eol=lf build/usb/package-lists/rigos.list.chroot text eol=lf build/usb/includes.chroot/usr/local/bin/* text eol=lf build/usb/includes.chroot/usr/local/sbin/rigos-* text eol=lf +build/usb/includes.chroot/usr/lib/rigos/rigos-firstboot-* text eol=lf build/usb/includes.chroot/usr/lib/rigos/rigos-randomx-msr text eol=lf build/usb/includes.chroot/usr/lib/rigos/rigos-runtime-* text eol=lf build/usb/includes.chroot/usr/lib/rigos/rigos-miner-* text eol=lf diff --git a/.github/workflows/source-gate.yml b/.github/workflows/source-gate.yml new file mode 100644 index 00000000..98293b3a --- /dev/null +++ b/.github/workflows/source-gate.yml @@ -0,0 +1,60 @@ +name: RIGOS Source Gate + +on: + pull_request: + branches: + - performance/randomx-sustained-hash + - stability/hive-exit-gates + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: source-gate-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: Authoritative Linux source verification + runs-on: ubuntu-24.04 + timeout-minutes: 45 + + steps: + - name: Check out exact pull-request head + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install required host tools + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install --yes --no-install-recommends ripgrep + + - name: Install locked Rust toolchain + run: | + set -euo pipefail + rustup toolchain install 1.85.1 \ + --profile minimal \ + --component rustfmt \ + --component clippy + rustup override set 1.85.1 + rustc --version + cargo --version + cargo fmt --version + cargo clippy --version + + - name: Run authoritative RIGOS source gate + env: + CARGO_TERM_COLOR: always + run: | + set -euo pipefail + bash ./scripts/verify-wsl-entrypoint.sh "$GITHUB_WORKSPACE" + + - name: Record exact verified revision + if: always() + run: | + set -euo pipefail + printf 'RIGOS_VERIFIED_HEAD=%s\n' "$(git rev-parse HEAD)" + git status --short diff --git a/build/usb/hooks/010-rigos.chroot b/build/usb/hooks/010-rigos.chroot index acc1d24b..e944fc5a 100644 --- a/build/usb/hooks/010-rigos.chroot +++ b/build/usb/hooks/010-rigos.chroot @@ -9,7 +9,7 @@ install -d -m 0755 /usr/lib/rigos /usr/local/bin systemd-tmpfiles --create /usr/lib/tmpfiles.d/rigos.conf rm -f /etc/ssh/ssh_host_*_key /etc/ssh/ssh_host_*_key.pub -chmod 0755 /usr/local/bin/rigosd /usr/local/bin/rigosctl /usr/local/sbin/rigos-firstboot /usr/local/sbin/rigos-recovery-access /usr/local/sbin/rigos-state-orchestrate /usr/lib/rigos/rigos-miner-gate /usr/lib/rigos/rigos-miner-health /usr/lib/rigos/rigos-runtime-render /usr/lib/rigos/rigos-runtime-publish /usr/lib/rigos/rigos-runtime-gate /usr/lib/rigos/rigos-randomx-msr /usr/lib/rigos/rigos-ssh-hostkeys /usr/lib/rigos/rigos-state-init /usr/lib/rigos/rigos-state-ready /usr/lib/rigos/rigos-config /usr/lib/rigos/rigos-performance /usr/lib/rigos/rigos-lifecycle-cycles /usr/lib/rigos/rigos-identity-seed /usr/lib/rigos/xmrig +chmod 0755 /usr/local/bin/rigosd /usr/local/bin/rigosctl /usr/local/sbin/rigos-firstboot /usr/local/sbin/rigos-recovery-access /usr/local/sbin/rigos-state-orchestrate /usr/lib/rigos/rigos-firstboot-whiptail /usr/lib/rigos/rigos-miner-gate /usr/lib/rigos/rigos-miner-health /usr/lib/rigos/rigos-runtime-render /usr/lib/rigos/rigos-runtime-publish /usr/lib/rigos/rigos-runtime-gate /usr/lib/rigos/rigos-randomx-msr /usr/lib/rigos/rigos-ssh-hostkeys /usr/lib/rigos/rigos-state-init /usr/lib/rigos/rigos-state-ready /usr/lib/rigos/rigos-config /usr/lib/rigos/rigos-performance /usr/lib/rigos/rigos-lifecycle-cycles /usr/lib/rigos/rigos-identity-seed /usr/lib/rigos/xmrig systemctl enable NetworkManager.service ssh.service rigos-state.service rigos-recovery-access.service rigos-state-ready.service rigos-ssh-hostkeys.service rigos-profile-apply.service rigos-runtime-render.service rigos-hugepages.service rigos-firstboot.service rigos-randomx-msr.service rigos-miner.service rigos-miner-health.timer tmp.mount systemctl disable ssh.socket 2>/dev/null || true systemctl disable apt-daily.timer apt-daily-upgrade.timer logrotate.timer fstrim.timer 2>/dev/null || true diff --git a/build/usb/includes.chroot/etc/systemd/system/rigos-firstboot.service.d/2009-console-theme.conf b/build/usb/includes.chroot/etc/systemd/system/rigos-firstboot.service.d/2009-console-theme.conf new file mode 100644 index 00000000..78da015e --- /dev/null +++ b/build/usb/includes.chroot/etc/systemd/system/rigos-firstboot.service.d/2009-console-theme.conf @@ -0,0 +1,3 @@ +[Service] +Environment=RIGOS_WHIPTAIL=/usr/lib/rigos/rigos-firstboot-whiptail +Environment="RIGOS_FIRSTBOOT_BACKTITLE=RIGOS SETUP UTILITY LOCAL NODE CONFIGURATION" diff --git a/build/usb/includes.chroot/usr/lib/rigos/rigos-firstboot-whiptail b/build/usb/includes.chroot/usr/lib/rigos/rigos-firstboot-whiptail new file mode 100644 index 00000000..ab6ad464 --- /dev/null +++ b/build/usb/includes.chroot/usr/lib/rigos/rigos-firstboot-whiptail @@ -0,0 +1,35 @@ +#!/bin/sh +# RIGOS firstboot presentation wrapper. +# Keeps the configuration engine and dialog return-code contract unchanged. +set -eu + +TERM=${TERM:-linux} +export TERM + +NEWT_COLORS=${NEWT_COLORS:-'root=white,blue:border=blue,lightgray:window=black,lightgray:shadow=black,black:title=white,blue:button=black,lightgray:actbutton=white,blue:compactbutton=black,lightgray:checkbox=black,lightgray:actcheckbox=white,blue:entry=black,white:disentry=gray,lightgray:label=black,lightgray:listbox=black,lightgray:actlistbox=white,blue:sellistbox=black,cyan:actsellistbox=white,blue:textbox=black,lightgray:acttextbox=white,blue:helpline=white,blue:roottext=white,blue'} +export NEWT_COLORS + +backtitle=${RIGOS_FIRSTBOOT_BACKTITLE:-'RIGOS SETUP UTILITY LOCAL NODE CONFIGURATION'} +whiptail_real=${RIGOS_WHIPTAIL_REAL:-/usr/bin/whiptail} + +if [ ! -x "$whiptail_real" ]; then + echo "rigos-firstboot-whiptail: backend is not executable: $whiptail_real" >&2 + exit 127 +fi + +case " $* " in + *' --menu '*) + set -- --ok-button SELECT --cancel-button BACK "$@" + ;; + *' --yesno '*) + set -- --yes-button APPLY --no-button BACK "$@" + ;; + *' --inputbox '*|*' --passwordbox '*) + set -- --ok-button NEXT --cancel-button BACK "$@" + ;; + *' --msgbox '*) + set -- --ok-button CONTINUE "$@" + ;; +esac + +exec "$whiptail_real" --backtitle "$backtitle" "$@" diff --git a/crates/rigos-config/tests/firstboot_theme.rs b/crates/rigos-config/tests/firstboot_theme.rs new file mode 100644 index 00000000..6a494b4d --- /dev/null +++ b/crates/rigos-config/tests/firstboot_theme.rs @@ -0,0 +1,93 @@ +use std::fs; +use std::path::PathBuf; + +const WRAPPER_PATH: &str = "build/usb/includes.chroot/usr/lib/rigos/rigos-firstboot-whiptail"; + +fn repo_path(path: &str) -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join(path) +} + +fn repo_file(path: &str) -> String { + let path = repo_path(path); + fs::read_to_string(&path) + .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display())) +} + +#[test] +fn firstboot_uses_the_dedicated_console_theme_wrapper() { + let drop_in = repo_file( + "build/usb/includes.chroot/etc/systemd/system/rigos-firstboot.service.d/2009-console-theme.conf", + ); + + assert!( + drop_in.contains("Environment=RIGOS_WHIPTAIL=/usr/lib/rigos/rigos-firstboot-whiptail"), + "firstboot must select the dedicated presentation wrapper" + ); + assert!( + drop_in.contains("RIGOS SETUP UTILITY LOCAL NODE CONFIGURATION"), + "firstboot must publish the static non-secret backtitle" + ); + assert!( + !drop_in.contains("//"), + "setup utility titles must not use synthetic slash separators" + ); +} + +#[test] +fn firstboot_wrapper_is_pinned_to_lf_in_git() { + let attributes = repo_file(".gitattributes"); + let required = "build/usb/includes.chroot/usr/lib/rigos/rigos-firstboot-* text eol=lf"; + + assert!( + attributes.lines().any(|line| line == required), + "firstboot runtime wrappers must remain LF on Windows and WSL checkouts" + ); +} + +#[test] +fn firstboot_theme_is_ascii_and_preserves_whiptail_as_the_ui_engine() { + let wrapper = repo_file(WRAPPER_PATH); + + assert!(wrapper.is_ascii(), "console theme must remain ASCII-only"); + assert!(wrapper.contains("NEWT_COLORS=")); + assert!(wrapper.contains("root=white,blue")); + assert!(wrapper.contains("window=black,lightgray")); + assert!(wrapper.contains("actlistbox=white,blue")); + assert!(wrapper.contains("--backtitle")); + assert!(wrapper.contains("--ok-button SELECT --cancel-button BACK")); + assert!(wrapper.contains("--yes-button APPLY --no-button BACK")); + assert!(wrapper.contains("RIGOS_WHIPTAIL_REAL:-/usr/bin/whiptail")); + assert!(wrapper.contains("exec \"$whiptail_real\"")); + assert!(wrapper.contains("backend is not executable")); + assert!(!wrapper.contains("//")); + assert!( + !wrapper.contains("xterm") + && !wrapper.contains("Xorg") + && !wrapper.contains("wayland") + && !wrapper.contains("electron"), + "theme must not introduce a graphical runtime" + ); +} + +#[test] +fn authoritative_wsl_gate_runs_the_wrapper_behavior_test() { + let entrypoint = repo_file("scripts/verify-wsl-entrypoint.sh"); + let verifier = repo_file("scripts/verify-firstboot-theme-wrapper.sh"); + + assert!(entrypoint.contains("bash ./scripts/verify-firstboot-theme-wrapper.sh")); + assert!(verifier.contains("RIGOS firstboot theme wrapper verification passed")); + assert!(verifier.contains("RIGOS_THEME_EXIT=7")); + assert!(verifier.contains("missing-backend")); +} + +#[test] +fn image_hook_installs_the_theme_wrapper_as_executable() { + let hook = repo_file("build/usb/hooks/010-rigos.chroot"); + + assert!( + hook.contains("/usr/lib/rigos/rigos-firstboot-whiptail"), + "image construction must install the wrapper as executable" + ); +} diff --git a/crates/rigos-config/tests/firstboot_theme_preview.rs b/crates/rigos-config/tests/firstboot_theme_preview.rs new file mode 100644 index 00000000..91016fd7 --- /dev/null +++ b/crates/rigos-config/tests/firstboot_theme_preview.rs @@ -0,0 +1,65 @@ +use std::fs; +use std::path::PathBuf; + +fn preview_script() -> String { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join("scripts/preview-firstboot-theme.sh"); + fs::read_to_string(&path) + .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display())) +} + +#[test] +fn preview_covers_every_firstboot_dialog_class() { + let script = preview_script(); + + for required in [ + "preview_menu", + "preview_input", + "preview_confirm", + "preview_message", + "menu|input|confirm|message|all", + ] { + assert!( + script.contains(required), + "preview harness is missing required mode marker: {required}" + ); + } + + for required in ["--menu", "--inputbox", "--yesno", "--msgbox"] { + assert!( + script.contains(required), + "preview harness is missing dialog class: {required}" + ); + } + + assert!( + !script.contains("//"), + "preview titles must not use synthetic slash separators" + ); +} + +#[test] +fn preview_is_read_only_and_does_not_touch_appliance_state() { + let script = preview_script(); + + for forbidden in [ + "/var/lib/rigos", + "systemctl", + "rigos-config", + "rigos-miner", + "rigos-randomx-msr", + "mount ", + "umount ", + "rm -rf", + "sudo ", + ] { + assert!( + !script.contains(forbidden), + "preview harness contains forbidden mutation surface: {forbidden}" + ); + } + + assert!(script.contains("RIGOS_WHIPTAIL_REAL=/usr/bin/whiptail")); + assert!(script.contains("RIGOS_THEME_PREVIEW_EXIT=")); +} diff --git a/docs/developer/firstboot-theme.txt b/docs/developer/firstboot-theme.txt new file mode 100644 index 00000000..bae5b633 --- /dev/null +++ b/docs/developer/firstboot-theme.txt @@ -0,0 +1,93 @@ +RIGOS FIRSTBOOT CONSOLE THEME +============================= + +PURPOSE +------- + +Give the local first-boot selector the look of a late-2000s or early-2010s +firmware utility without adding a graphical desktop, browser, display server, +or background daemon. + +The theme is presentation only. Configuration, identity, persistent state, +miner, MSR, SSH, runtime publication, and activation behavior stay unchanged. + +VISUAL CONTRACT +--------------- + +Root background deep blue +Dialog window light gray / silver +Window border blue +Active row white on blue +Input field black on white +Shadow black +Backtitle static ASCII, no private values +Buttons SELECT BACK NEXT APPLY CONTINUE + +The source remains ASCII-only so it renders on the Linux console without a +Unicode-font dependency. + +ARCHITECTURE +------------ + +Firstboot still executes the Python workflow at: + + /usr/local/sbin/rigos-firstboot + +The service sets RIGOS_WHIPTAIL to: + + /usr/lib/rigos/rigos-firstboot-whiptail + +The wrapper exports NEWT_COLORS, adds a static backtitle, maps button labels, +and execs the existing /usr/bin/whiptail backend. Backend exit status and +selected values retain the original whiptail contract. + +PREVIEW +------- + +Run from a Linux or WSL checkout with whiptail installed: + + bash scripts/preview-firstboot-theme.sh menu + bash scripts/preview-firstboot-theme.sh input + bash scripts/preview-firstboot-theme.sh confirm + bash scripts/preview-firstboot-theme.sh message + bash scripts/preview-firstboot-theme.sh all + +The preview harness does not read or modify /var/lib/rigos and does not call +systemctl, the configuration engine, miner services, or MSR authority. + +VERIFICATION +------------ + +Targeted tests: + + cargo test --locked -p rigos-config --test firstboot_theme + cargo test --locked -p rigos-config --test firstboot_theme_preview + +Authoritative Linux source gate: + + bash scripts/verify-wsl-entrypoint.sh "$PWD" + +A PASS may only be claimed for the exact Git head printed by the gate. + +BRANCH BOUNDARY +--------------- + +UI work belongs on: + + ui/firstboot-2009-theme + +It is stacked on performance/randomx-sustained-hash only for review. Do not +merge it into PR #15 while the physical performance and endurance evidence is +still tied to the exact performance head. + +NON-GOALS +--------- + +No X11. +No Wayland. +No Electron. +No browser UI. +No framebuffer daemon. +No persistent-state format change. +No miner behavior change. +No image-release claim before source and image verification pass. diff --git a/scripts/preview-firstboot-theme.sh b/scripts/preview-firstboot-theme.sh new file mode 100644 index 00000000..19f154de --- /dev/null +++ b/scripts/preview-firstboot-theme.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +wrapper="$root/build/usb/includes.chroot/usr/lib/rigos/rigos-firstboot-whiptail" +mode=${1:-menu} + +if [[ ! -f "$wrapper" ]]; then + echo "preview-firstboot-theme: wrapper is missing: $wrapper" >&2 + exit 1 +fi + +if [[ ! -x /usr/bin/whiptail ]]; then + echo "preview-firstboot-theme: /usr/bin/whiptail is not installed" >&2 + exit 127 +fi + +run_theme() { + set +e + RIGOS_WHIPTAIL_REAL=/usr/bin/whiptail \ + RIGOS_FIRSTBOOT_BACKTITLE='RIGOS SETUP UTILITY LOCAL NODE CONFIGURATION' \ + sh "$wrapper" "$@" + status=$? + set -e + printf 'RIGOS_THEME_PREVIEW_EXIT=%s\n' "$status" +} + +preview_menu() { + run_theme \ + --title 'FLIGHT SHEET SELECTION' \ + --menu $'SELECT FLIGHT SHEET\n\nChoose how this node should be configured.\n\n[UP/DOWN] MOVE [ENTER] SELECT [ESC] BACK' \ + 20 76 6 \ + manual 'Configure this node manually' \ + none 'Leave mining unconfigured' \ + native:xmr 'Use native XMR flight sheet' \ + import:legacy 'Import an external flight sheet' +} + +preview_confirm() { + run_theme \ + --title 'COMMIT CONFIGURATION' \ + --yesno $'CONFIGURATION SUMMARY\n\nNode rig01\nFlight Sheet xmr\nAlgorithm rx/0\nThreads exact 2\nHuge Pages enabled\nStart Policy on boot\n\nApply this configuration to local persistent state?' \ + 20 76 +} + +preview_input() { + run_theme \ + --title 'NODE IDENTITY' \ + --inputbox $'NODE NAME\n\nEnter the local appliance name.\nAllowed: A-Z, a-z, 0-9 and hyphen.' \ + 14 72 'rig01' +} + +preview_message() { + run_theme \ + --title 'SETUP COMPLETE' \ + --msgbox $'CONFIGURATION COMMITTED\n\nPersistent state verified.\nRuntime config published.\nMiner activation requested.\n\nThe local console will now return to the system.' \ + 16 72 +} + +case "$mode" in + menu) + preview_menu + ;; + confirm) + preview_confirm + ;; + input) + preview_input + ;; + message) + preview_message + ;; + all) + preview_menu + preview_input + preview_confirm + preview_message + ;; + -h|--help) + cat <<'EOF' +usage: scripts/preview-firstboot-theme.sh [menu|input|confirm|message|all] + +Displays the RIGOS firstboot theme without reading or changing persistent state. +EOF + ;; + *) + echo "preview-firstboot-theme: unknown mode: $mode" >&2 + exit 64 + ;; +esac diff --git a/scripts/verify-firstboot-theme-wrapper.sh b/scripts/verify-firstboot-theme-wrapper.sh new file mode 100644 index 00000000..a390eca0 --- /dev/null +++ b/scripts/verify-firstboot-theme-wrapper.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +wrapper="$root/build/usb/includes.chroot/usr/lib/rigos/rigos-firstboot-whiptail" + +test -f "$wrapper" + +tmp=$(mktemp -d) +cleanup() { + rm -rf "$tmp" +} +trap cleanup EXIT HUP INT TERM + +backend="$tmp/fake-whiptail" +capture="$tmp/arguments.txt" +expected="$tmp/expected.txt" + +cat >"$backend" <<'EOF' +#!/bin/sh +printf '%s\n' "$@" >"$RIGOS_THEME_CAPTURE" +exit "${RIGOS_THEME_EXIT:-0}" +EOF +chmod 0755 "$backend" + +set +e +RIGOS_WHIPTAIL_REAL="$backend" \ +RIGOS_THEME_CAPTURE="$capture" \ +RIGOS_THEME_EXIT=7 \ +RIGOS_FIRSTBOOT_BACKTITLE='TEST BACKTITLE' \ +sh "$wrapper" \ + --title 'RIGOS FIRST BOOT' \ + --menu 'Select Flight Sheet' \ + 20 76 2 \ + manual 'Configure manually' \ + none 'Leave mining unconfigured' +status=$? +set -e + +if [[ "$status" -ne 7 ]]; then + echo "firstboot theme wrapper did not preserve backend exit status: $status" >&2 + exit 1 +fi + +cat >"$expected" <<'EOF' +--backtitle +TEST BACKTITLE +--ok-button +SELECT +--cancel-button +BACK +--title +RIGOS FIRST BOOT +--menu +Select Flight Sheet +20 +76 +2 +manual +Configure manually +none +Leave mining unconfigured +EOF + +if ! cmp -s "$expected" "$capture"; then + echo "firstboot theme wrapper changed argument order or values" >&2 + diff -u "$expected" "$capture" >&2 || true + exit 1 +fi + +set +e +RIGOS_WHIPTAIL_REAL="$tmp/missing-backend" sh "$wrapper" --msgbox test 8 40 >/dev/null 2>&1 +missing_status=$? +set -e + +if [[ "$missing_status" -ne 127 ]]; then + echo "firstboot theme wrapper did not fail closed for a missing backend: $missing_status" >&2 + exit 1 +fi + +echo "RIGOS firstboot theme wrapper verification passed" diff --git a/scripts/verify-wsl-entrypoint.sh b/scripts/verify-wsl-entrypoint.sh index 55090d9f..27e1693c 100644 --- a/scripts/verify-wsl-entrypoint.sh +++ b/scripts/verify-wsl-entrypoint.sh @@ -20,7 +20,7 @@ if [[ -f "$HOME/.cargo/env" ]]; then fi missing=0 -for tool in cargo rustc python3 bash sh git grep rg mktemp; do +for tool in cargo rustc python3 bash sh git grep rg mktemp cmp diff; do if ! command -v "$tool" >/dev/null 2>&1; then printf 'RIGOS_WSL_TOOL_MISSING=%s\n' "$tool" >&2 missing=1 @@ -55,3 +55,4 @@ export PYTHONDONTWRITEBYTECODE=1 printf 'RIGOS_WSL_REPO=%s\n' "$repo" printf 'RIGOS_WSL_CARGO=%s\n' "$(command -v cargo)" bash ./scripts/verify.sh +bash ./scripts/verify-firstboot-theme-wrapper.sh