From cf5aed54ccfd8fbc18ed0cac1e9cbfce19d1c201 Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 05:08:34 +0700 Subject: [PATCH 1/9] Keep firstboot reachable when state readiness fails --- .../etc/systemd/system/rigos-firstboot.service | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/build/usb/includes.chroot/etc/systemd/system/rigos-firstboot.service b/build/usb/includes.chroot/etc/systemd/system/rigos-firstboot.service index 4a483155..9b87321a 100644 --- a/build/usb/includes.chroot/etc/systemd/system/rigos-firstboot.service +++ b/build/usb/includes.chroot/etc/systemd/system/rigos-firstboot.service @@ -1,15 +1,14 @@ [Unit] Description=RIGOS first-boot configuration -After=network-online.target rigos-state-ready.service rigos-profile-apply.service -Wants=network-online.target -Requires=rigos-state-ready.service +After=rigos-state.service rigos-state-ready.service rigos-profile-apply.service +Wants=rigos-state-ready.service Before=getty@tty1.service [Service] Type=oneshot ExecCondition=/usr/lib/rigos/rigos-config needs-activation ExecStart=/usr/local/sbin/rigos-firstboot -StandardInput=tty +StandardInput=tty-force StandardOutput=tty StandardError=tty TTYPath=/dev/tty1 From 0426dd86c17ebb29eb7c2948b782bcbef9de9edc Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 05:08:51 +0700 Subject: [PATCH 2/9] Test firstboot diagnostics survive state readiness failure --- crates/rigos-config/tests/firstboot_tty.rs | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/rigos-config/tests/firstboot_tty.rs b/crates/rigos-config/tests/firstboot_tty.rs index 414101a1..4f3d1aa9 100644 --- a/crates/rigos-config/tests/firstboot_tty.rs +++ b/crates/rigos-config/tests/firstboot_tty.rs @@ -41,6 +41,46 @@ fn firstboot_releases_tty1_to_getty_after_exit() { ); } +#[test] +fn firstboot_remains_visible_when_state_readiness_fails() { + let unit = unit("rigos-firstboot.service"); + let after = directive_tokens(&unit, "After="); + let wants = directive_tokens(&unit, "Wants="); + let requires = directive_tokens(&unit, "Requires="); + + for ordered in [ + "rigos-state.service", + "rigos-state-ready.service", + "rigos-profile-apply.service", + ] { + assert!( + after.contains(ordered), + "firstboot must start after {ordered} finishes, including failure" + ); + } + + assert!( + wants.contains("rigos-state-ready.service"), + "firstboot must request state verification without depending on success" + ); + assert!( + !requires.contains("rigos-state-ready.service"), + "state verification failure must not suppress firstboot diagnostics" + ); + assert!( + !unit.contains("network-online.target"), + "local firstboot must remain available without network connectivity" + ); + assert!( + unit.lines().any(|line| line == "StandardInput=tty-force"), + "firstboot must acquire tty1 for the local setup flow" + ); + assert!( + unit.lines().any(|line| line == "TTYPath=/dev/tty1"), + "firstboot must remain bound to tty1" + ); +} + #[test] fn recovery_access_does_not_hang_up_the_following_firstboot_session() { let unit = unit("rigos-recovery-access.service"); From 8b80006ccb0e48bdf891b94b627714439dd5bec1 Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 05:09:44 +0700 Subject: [PATCH 3/9] Add exact-image gate for firstboot configuration path --- scripts/verify-firstboot-image.sh | 113 ++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 scripts/verify-firstboot-image.sh diff --git a/scripts/verify-firstboot-image.sh b/scripts/verify-firstboot-image.sh new file mode 100644 index 00000000..3e73c191 --- /dev/null +++ b/scripts/verify-firstboot-image.sh @@ -0,0 +1,113 @@ +#!/bin/bash +set -euo pipefail + +die() { + printf 'verify-firstboot-image: %s\n' "$*" >&2 + exit 1 +} + +[[ $# -eq 1 ]] || die 'usage: verify-firstboot-image.sh ' +image="$(readlink -f "$1")" +[[ -f "$image" ]] || die "image is missing: $image" +[[ "$(id -u)" -eq 0 ]] || die 'must run as root' + +partition_json="$(sfdisk --json "$image")" +start="$(jq -r '.partitiontable.partitions[1].start' <<<"$partition_json")" +size="$(jq -r '.partitiontable.partitions[1].size' <<<"$partition_json")" +[[ "$start" =~ ^[0-9]+$ && "$size" =~ ^[0-9]+$ ]] || die 'ROOT_A geometry is invalid' + +loop="$( + losetup --find --show --read-only \ + --offset $((start * 512)) \ + --sizelimit $((size * 512)) \ + "$image" +)" +temporary="$(mktemp -d)" +cleanup() { + set +e + mountpoint -q "$temporary/root-a" && umount "$temporary/root-a" + losetup -d "$loop" 2>/dev/null + rm -rf "$temporary" +} +trap cleanup EXIT + +mkdir -p "$temporary/root-a" "$temporary/squash" +mount -o ro "$loop" "$temporary/root-a" +squashfs="$temporary/root-a/live/filesystem.squashfs" +[[ -f "$squashfs" ]] || die 'ROOT_A squashfs is missing' + +unsquashfs -no-progress -d "$temporary/squash" "$squashfs" \ + usr/bin/python3 usr/bin/python3.11 usr/bin/whiptail \ + usr/local/sbin/rigos-firstboot \ + usr/lib/rigos/rigos-firstboot-whiptail \ + etc/systemd/system/rigos-firstboot.service \ + etc/systemd/system/rigos-firstboot.service.d/2009-console-theme.conf \ + etc/systemd/system/multi-user.target.wants/rigos-firstboot.service \ + >/dev/null + +root="$temporary/squash" +service="$root/etc/systemd/system/rigos-firstboot.service" +dropin="$root/etc/systemd/system/rigos-firstboot.service.d/2009-console-theme.conf" +firstboot="$root/usr/local/sbin/rigos-firstboot" +wrapper="$root/usr/lib/rigos/rigos-firstboot-whiptail" + +[[ -L "$root/etc/systemd/system/multi-user.target.wants/rigos-firstboot.service" ]] \ + || die 'firstboot service is not enabled in the appliance' +[[ -x "$root/usr/bin/python3" ]] || die 'Python runtime for firstboot is missing' +[[ -x "$root/usr/bin/whiptail" ]] || die 'whiptail runtime is missing' +[[ -x "$firstboot" ]] || die 'firstboot program is missing or not executable' +[[ -x "$wrapper" ]] || die 'firstboot theme wrapper is missing or not executable' +[[ -f "$dropin" ]] || die 'firstboot theme drop-in is missing' + +python3 -m py_compile "$firstboot" +sh -n "$wrapper" + +for required in \ + 'After=rigos-state.service rigos-state-ready.service rigos-profile-apply.service' \ + 'Wants=rigos-state-ready.service' \ + 'Before=getty@tty1.service' \ + 'ExecCondition=/usr/lib/rigos/rigos-config needs-activation' \ + 'ExecStart=/usr/local/sbin/rigos-firstboot' \ + 'StandardInput=tty-force' \ + 'StandardOutput=tty' \ + 'StandardError=tty' \ + 'TTYPath=/dev/tty1' \ + 'TTYReset=yes' \ + 'TTYVTDisallocate=yes' +do + grep -Fqx "$required" "$service" \ + || die "firstboot service contract is missing: $required" +done + +if grep -Fqx 'Requires=rigos-state-ready.service' "$service"; then + die 'state readiness failure still suppresses firstboot diagnostics' +fi +if grep -Fq 'network-online.target' "$service"; then + die 'firstboot still depends on network-online' +fi + +for required in \ + "stage='state_not_ready'" \ + "raise FirstbootFailure('state_not_ready')" \ + 'def manual_proposal()' \ + "('manual', 'Configure manually')" \ + "raise FirstbootCancelled('mining_left_unconfigured')" +do + grep -Fq "$required" "$firstboot" \ + || die "firstboot recovery/configuration path is missing: $required" +done + +for required in \ + 'Environment=RIGOS_WHIPTAIL=/usr/lib/rigos/rigos-firstboot-whiptail' \ + 'RIGOS SETUP UTILITY LOCAL NODE CONFIGURATION' +do + grep -Fq "$required" "$dropin" \ + || die "firstboot theme drop-in contract is missing: $required" +done + +grep -Fq 'RIGOS_WHIPTAIL_REAL:-/usr/bin/whiptail' "$wrapper" \ + || die 'firstboot theme wrapper does not use packaged whiptail' +grep -Fq 'exec "$whiptail_real"' "$wrapper" \ + || die 'firstboot theme wrapper does not preserve the dialog engine' + +printf 'RIGOS firstboot exact-image verification passed: %s\n' "$image" From a8ca616072c3afc01fef2f79f2719116675bacee Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 05:09:56 +0700 Subject: [PATCH 4/9] Require firstboot source and exact-image gates during builds --- scripts/build-usb-image-entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/build-usb-image-entrypoint.sh b/scripts/build-usb-image-entrypoint.sh index 78e1ef70..2970c3c6 100644 --- a/scripts/build-usb-image-entrypoint.sh +++ b/scripts/build-usb-image-entrypoint.sh @@ -44,9 +44,11 @@ export CARGO_TARGET_DIR=/work/rigos-performance-preflight-target cargo test --locked -p rigos-config --test miner_observer_authority -- --nocapture cargo test --locked -p rigos-config --test randomx_build_entrypoint -- --nocapture cargo test --locked -p rigos-config --test randomx_msr_authority -- --nocapture +cargo test --locked -p rigos-config --test firstboot_tty -- --nocapture ./scripts/build-usb-image.sh image="./dist/usb/${RIGOS_IMAGE_ID}-${RIGOS_IMAGE_VERSION}.img" bash ./scripts/verify-randomx-performance-image.sh "$image" bash ./scripts/verify-miner-observer-image.sh "$image" +bash ./scripts/verify-firstboot-image.sh "$image" From 6e73580b43e3bd4ea376b04e3963d0012f6bf3a4 Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 05:10:11 +0700 Subject: [PATCH 5/9] Advance image version for firstboot recovery fix --- build/usb/version.env | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/usb/version.env b/build/usb/version.env index 9567e611..c85c4412 100644 --- a/build/usb/version.env +++ b/build/usb/version.env @@ -1,8 +1,8 @@ -RIGOS_PRODUCT_VERSION=0.0.4-alpha.8 -RIGOS_IMAGE_VERSION=0.0.4-alpha.8 +RIGOS_PRODUCT_VERSION=0.0.4-alpha.9 +RIGOS_IMAGE_VERSION=0.0.4-alpha.9 RIGOS_IMAGE_ID=rigos-usb-amd64 RIGOS_IMAGE_CHANNEL=alpha -RIGOS_BUILD_ORDINAL=8 +RIGOS_BUILD_ORDINAL=9 RIGOS_XMRIG_VERSION=6.26.0 RIGOS_XMRIG_ARCHIVE_SHA256=fc6f8ae5f64e4f17481f7e3be29a1c56949f216a998414188003eae1db20c9e5 RIGOS_XMRIG_BINARY_SHA256=b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49 From f40a895899c5c6c268cb86e27616f0d6c53b45f0 Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 05:10:31 +0700 Subject: [PATCH 6/9] Lock alpha9 firstboot image gate into source tests --- .../tests/firstboot_image_gate.rs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 crates/rigos-config/tests/firstboot_image_gate.rs diff --git a/crates/rigos-config/tests/firstboot_image_gate.rs b/crates/rigos-config/tests/firstboot_image_gate.rs new file mode 100644 index 00000000..e351a434 --- /dev/null +++ b/crates/rigos-config/tests/firstboot_image_gate.rs @@ -0,0 +1,55 @@ +use std::fs; +use std::path::PathBuf; + +fn repo_file(path: &str) -> String { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join(path); + fs::read_to_string(&path) + .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display())) +} + +#[test] +fn alpha9_build_runs_source_and_exact_image_firstboot_gates() { + let version = repo_file("build/usb/version.env"); + let entrypoint = repo_file("scripts/build-usb-image-entrypoint.sh"); + let verifier = repo_file("scripts/verify-firstboot-image.sh"); + let hook = repo_file("build/usb/hooks/010-rigos.chroot"); + + assert!(version.contains("RIGOS_PRODUCT_VERSION=0.0.4-alpha.9")); + assert!(version.contains("RIGOS_IMAGE_VERSION=0.0.4-alpha.9")); + assert!(version.contains("RIGOS_BUILD_ORDINAL=9")); + + assert!(entrypoint.contains("--test firstboot_tty")); + assert!(entrypoint.contains("bash ./scripts/verify-firstboot-image.sh \"$image\"")); + + for required in [ + "multi-user.target.wants/rigos-firstboot.service", + "firstboot service is not enabled in the appliance", + "state readiness failure still suppresses firstboot diagnostics", + "firstboot still depends on network-online", + "StandardInput=tty-force", + "def manual_proposal()", + ] { + assert!( + verifier.contains(required), + "firstboot exact-image verifier is missing: {required}" + ); + } + + assert!( + hook.contains("rigos-firstboot.service"), + "image construction must enable firstboot" + ); +} + +#[test] +fn firstboot_image_gate_is_read_only_against_the_appliance_image() { + let verifier = repo_file("scripts/verify-firstboot-image.sh"); + + assert!(verifier.contains("losetup --find --show --read-only")); + assert!(verifier.contains("mount -o ro")); + assert!(!verifier.contains("mount -o rw")); + assert!(!verifier.contains("systemctl start")); + assert!(!verifier.contains("systemctl enable")); +} From ea6463349e6685834a5c981ef63a56b2c47f534a Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 05:11:03 +0700 Subject: [PATCH 7/9] Verify firstboot remains reachable after state failure --- scripts/verify-systemd-ordering.py | 148 ++++++++++++++++++++++++----- 1 file changed, 123 insertions(+), 25 deletions(-) diff --git a/scripts/verify-systemd-ordering.py b/scripts/verify-systemd-ordering.py index a6f84438..67e13d1b 100644 --- a/scripts/verify-systemd-ordering.py +++ b/scripts/verify-systemd-ordering.py @@ -42,51 +42,145 @@ def includes(actual, expected, message): def verify(units): names = { - "rigos-state.service", "rigos-recovery-access.service", - "rigos-state-ready.service", "rigos-ssh-hostkeys.service", - "rigos-profile-apply.service", "rigos-firstboot.service", - "rigos-hugepages.service", "rigos-miner.service", + "rigos-state.service", + "rigos-recovery-access.service", + "rigos-state-ready.service", + "rigos-ssh-hostkeys.service", + "rigos-profile-apply.service", + "rigos-firstboot.service", + "rigos-hugepages.service", + "rigos-miner.service", } includes(set(units), names, "RIGOS unit set is incomplete") state = units["rigos-state.service"] - require(state.scalar("Unit", "DefaultDependencies") == "no", "state must retain early boot dependencies") - includes(state.words("Unit", "Before"), {"local-fs.target", "rigos-state-ready.service"}, "state ordering is incomplete") + require( + state.scalar("Unit", "DefaultDependencies") == "no", + "state must retain early boot dependencies", + ) + includes( + state.words("Unit", "Before"), + {"local-fs.target", "rigos-state-ready.service"}, + "state ordering is incomplete", + ) recovery = units["rigos-recovery-access.service"] - includes(recovery.words("Unit", "After"), {"rigos-state.service"}, "recovery must follow state") - includes(recovery.words("Unit", "Before"), {"rigos-state-ready.service", "rigos-firstboot.service"}, "recovery ordering is incomplete") - require(recovery.scalar("Service", "TTYVHangup") != "yes", "recovery must not hang up tty1") + includes( + recovery.words("Unit", "After"), + {"rigos-state.service"}, + "recovery must follow state", + ) + includes( + recovery.words("Unit", "Before"), + {"rigos-state-ready.service", "rigos-firstboot.service"}, + "recovery ordering is incomplete", + ) + require( + recovery.scalar("Service", "TTYVHangup") != "yes", + "recovery must not hang up tty1", + ) ready = units["rigos-state-ready.service"] - require(ready.scalar("Unit", "DefaultDependencies") != "no", "state-ready must use normal dependencies") - includes(ready.words("Unit", "After"), {"rigos-state.service", "rigos-recovery-access.service"}, "state-ready ordering is incomplete") - includes(ready.words("Unit", "Requires"), {"rigos-state.service"}, "state-ready must require state") + require( + ready.scalar("Unit", "DefaultDependencies") != "no", + "state-ready must use normal dependencies", + ) + includes( + ready.words("Unit", "After"), + {"rigos-state.service", "rigos-recovery-access.service"}, + "state-ready ordering is incomplete", + ) + includes( + ready.words("Unit", "Requires"), + {"rigos-state.service"}, + "state-ready must require state", + ) includes( ready.words("Unit", "Before"), { - "rigos-ssh-hostkeys.service", "rigos-profile-apply.service", - "rigos-firstboot.service", "rigos-hugepages.service", + "rigos-ssh-hostkeys.service", + "rigos-profile-apply.service", + "rigos-firstboot.service", + "rigos-hugepages.service", "rigos-miner.service", }, "state-ready downstream ordering is incomplete", ) - require("local-fs.target" not in ready.words("Unit", "Before"), "state-ready must not order before local-fs") - require("local-fs.target" not in ready.words("Install", "WantedBy"), "state-ready must not be installed under local-fs") - includes(ready.words("Install", "WantedBy"), {"multi-user.target"}, "state-ready must be installed under multi-user") + require( + "local-fs.target" not in ready.words("Unit", "Before"), + "state-ready must not order before local-fs", + ) + require( + "local-fs.target" not in ready.words("Install", "WantedBy"), + "state-ready must not be installed under local-fs", + ) + includes( + ready.words("Install", "WantedBy"), + {"multi-user.target"}, + "state-ready must be installed under multi-user", + ) hostkeys = units["rigos-ssh-hostkeys.service"] - includes(hostkeys.words("Unit", "After"), {"rigos-state-ready.service"}, "SSH host-key authority must follow state readiness") - includes(hostkeys.words("Unit", "Requires"), {"rigos-state-ready.service"}, "SSH host-key authority must require state readiness") - includes(hostkeys.words("Unit", "Before"), {"ssh.service"}, "SSH host-key authority must precede sshd") + includes( + hostkeys.words("Unit", "After"), + {"rigos-state-ready.service"}, + "SSH host-key authority must follow state readiness", + ) + includes( + hostkeys.words("Unit", "Requires"), + {"rigos-state-ready.service"}, + "SSH host-key authority must require state readiness", + ) + includes( + hostkeys.words("Unit", "Before"), + {"ssh.service"}, + "SSH host-key authority must precede sshd", + ) require( hostkeys.scalar("Service", "ExecStart") == "/usr/lib/rigos/rigos-ssh-hostkeys", "SSH host-key authority entrypoint is not exact", ) - includes(hostkeys.words("Install", "WantedBy"), {"multi-user.target"}, "SSH host-key authority must be enabled under multi-user") + includes( + hostkeys.words("Install", "WantedBy"), + {"multi-user.target"}, + "SSH host-key authority must be enabled under multi-user", + ) firstboot = units["rigos-firstboot.service"] - includes(firstboot.words("Unit", "Requires"), {"rigos-state-ready.service"}, "firstboot must require state-ready") - require(firstboot.scalar("Service", "TTYVHangup") != "yes", "firstboot must not hang up tty1") + includes( + firstboot.words("Unit", "After"), + { + "rigos-state.service", + "rigos-state-ready.service", + "rigos-profile-apply.service", + }, + "firstboot ordering is incomplete", + ) + includes( + firstboot.words("Unit", "Wants"), + {"rigos-state-ready.service"}, + "firstboot must request state verification", + ) + require( + "rigos-state-ready.service" not in firstboot.words("Unit", "Requires"), + "firstboot diagnostics must survive state readiness failure", + ) + require( + "network-online.target" not in firstboot.words("Unit", "After") + and "network-online.target" not in firstboot.words("Unit", "Wants"), + "firstboot must remain available offline", + ) + require( + firstboot.scalar("Service", "StandardInput") == "tty-force", + "firstboot must acquire tty1", + ) + require( + firstboot.scalar("Service", "TTYPath") == "/dev/tty1", + "firstboot must use tty1", + ) + require( + firstboot.scalar("Service", "TTYVHangup") != "yes", + "firstboot must not hang up tty1", + ) def graph_for(units): @@ -120,7 +214,7 @@ def visit(node): if cycle: return cycle elif state[target] == 1: - return stack[positions[target]:] + [target] + return stack[positions[target] :] + [target] stack.pop() positions.pop(node) state[node] = 2 @@ -136,7 +230,11 @@ def visit(node): def main(): parser = argparse.ArgumentParser() - parser.add_argument("unit_dir", nargs="?", default="build/usb/includes.chroot/etc/systemd/system") + parser.add_argument( + "unit_dir", + nargs="?", + default="build/usb/includes.chroot/etc/systemd/system", + ) directory = Path(parser.parse_args().unit_dir) try: units = {path.name: Unit(path) for path in directory.glob("rigos-*.service")} From b0965f7f9e8a8eade49144a8396e13c517ff615b Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 18:01:13 +0700 Subject: [PATCH 8/9] Require jq in WSL source-gate preflight --- scripts/verify-wsl-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/verify-wsl-entrypoint.sh b/scripts/verify-wsl-entrypoint.sh index 27e1693c..7f50da92 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 cmp diff; do +for tool in cargo rustc python3 bash sh git grep rg mktemp cmp diff jq; do if ! command -v "$tool" >/dev/null 2>&1; then printf 'RIGOS_WSL_TOOL_MISSING=%s\n' "$tool" >&2 missing=1 From 79e769b4b9fe367e918611dd9096a5396d4c42d4 Mon Sep 17 00:00:00 2001 From: DEADBYTE Date: Fri, 10 Jul 2026 18:01:28 +0700 Subject: [PATCH 9/9] Lock jq source-gate dependency into tests --- crates/rigos-config/tests/firstboot_theme.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/rigos-config/tests/firstboot_theme.rs b/crates/rigos-config/tests/firstboot_theme.rs index 6a494b4d..f6f6e9fc 100644 --- a/crates/rigos-config/tests/firstboot_theme.rs +++ b/crates/rigos-config/tests/firstboot_theme.rs @@ -77,6 +77,10 @@ fn authoritative_wsl_gate_runs_the_wrapper_behavior_test() { let verifier = repo_file("scripts/verify-firstboot-theme-wrapper.sh"); assert!(entrypoint.contains("bash ./scripts/verify-firstboot-theme-wrapper.sh")); + assert!( + entrypoint.contains("cmp diff jq"), + "WSL preflight must require jq before runtime publication tests" + ); assert!(verifier.contains("RIGOS firstboot theme wrapper verification passed")); assert!(verifier.contains("RIGOS_THEME_EXIT=7")); assert!(verifier.contains("missing-backend"));