144 lines
4.9 KiB
Rust
144 lines
4.9 KiB
Rust
// Chaos contract for interrupted or partially failed installs.
|
|
//
|
|
// Scope: preserve best-effort cleanup and explicit recovery boundaries in
|
|
// install scripts without executing privileged operations.
|
|
// Targets: client/server install scripts.
|
|
// Why: failed installs should leave the Pi and desktop in a recoverable state,
|
|
// especially around capture power, temporary PKI material, and USB gadget reset.
|
|
|
|
const SERVER_INSTALL: &str = include_str!(concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/scripts/install/server.sh"
|
|
));
|
|
const CLIENT_INSTALL: &str = include_str!(concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/scripts/install/client.sh"
|
|
));
|
|
|
|
#[test]
|
|
fn server_restores_capture_power_after_discovery_failures() {
|
|
for marker in [
|
|
"trap restore_capture_power_after_discovery EXIT",
|
|
"restore_capture_power_after_discovery",
|
|
"borrowing relay GPIO power for capture discovery",
|
|
"trap - EXIT",
|
|
] {
|
|
assert!(
|
|
SERVER_INSTALL.contains(marker),
|
|
"server install should preserve capture-power recovery marker {marker}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn attached_host_gadget_rebuilds_require_two_explicit_knobs() {
|
|
for marker in [
|
|
"LESAVKA_FORCE_GADGET_REBUILD",
|
|
"LESAVKA_ALLOW_GADGET_RESET",
|
|
"EXPLICIT_GADGET_REBUILD=1",
|
|
"Preserving the attached gadget to avoid wedging the Pi USB controller",
|
|
"Run during a maintenance window",
|
|
] {
|
|
assert!(
|
|
SERVER_INSTALL.contains(marker),
|
|
"server install should preserve attached-gadget safety marker {marker}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn client_tls_bundle_tempfiles_are_removed_on_failure_and_success() {
|
|
for marker in [
|
|
"tmp_bundle=$(run_as_user mktemp",
|
|
"rm -f \"$tmp_bundle\"",
|
|
"tmp=$(mktemp -d)",
|
|
"sudo rm -rf \"$tmp\"",
|
|
"rm -f \"$bundle\"",
|
|
] {
|
|
assert!(
|
|
CLIENT_INSTALL.contains(marker),
|
|
"client install should preserve temporary bundle cleanup marker {marker}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn server_install_refuses_to_kill_unrelated_port_owners() {
|
|
for marker in [
|
|
"unexpected process",
|
|
"no owning PID could be identified",
|
|
"refusing to start a duplicate server",
|
|
"clear_stale_server_listener",
|
|
] {
|
|
assert!(
|
|
SERVER_INSTALL.contains(marker),
|
|
"stale listener cleanup should preserve fail-safe marker {marker}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn installers_refuse_to_replace_live_entrypoints_with_empty_artifacts() {
|
|
for marker in [
|
|
"install_verified_executable()",
|
|
"source '$src' is missing or empty",
|
|
"staged install output was not a non-empty executable",
|
|
"Preserving the existing installed executable",
|
|
] {
|
|
assert!(
|
|
SERVER_INSTALL.contains(marker),
|
|
"server installer should preserve verified executable install marker {marker}"
|
|
);
|
|
assert!(
|
|
CLIENT_INSTALL.contains(marker),
|
|
"client installer should preserve verified executable install marker {marker}"
|
|
);
|
|
}
|
|
|
|
for marker in [
|
|
"install_verified_executable \"$SRC_DIR/target/release/lesavka-server\" /usr/local/bin/lesavka-server",
|
|
"install_verified_executable \"$SRC_DIR/target/release/lesavka-uvc\" /usr/local/bin/lesavka-uvc",
|
|
"install_verified_executable \"$SRC_DIR/scripts/daemon/lesavka-core.sh\" /usr/local/bin/lesavka-core.sh",
|
|
"install_verified_executable \"$SRC_DIR/scripts/daemon/lesavka-uvc.sh\" /usr/local/bin/lesavka-uvc.sh",
|
|
"install_verified_executable \"$SRC_DIR/scripts/daemon/lesavka-recovery-ladder.sh\" /usr/local/bin/lesavka-recovery-ladder",
|
|
] {
|
|
assert!(
|
|
SERVER_INSTALL.contains(marker),
|
|
"server installer should protect systemd entrypoint {marker}"
|
|
);
|
|
}
|
|
|
|
assert!(
|
|
CLIENT_INSTALL.contains(
|
|
"install_verified_executable \"$SRC/target/release/lesavka-client\" /usr/local/bin/lesavka-client"
|
|
),
|
|
"client installer should protect the launchable desktop binary"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn recovery_ladder_restores_before_rebooting_or_touching_the_core_gadget() {
|
|
let ladder = include_str!(concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/scripts/daemon/lesavka-recovery-ladder.sh"
|
|
));
|
|
for marker in [
|
|
"restore_last_good",
|
|
"restart_server_only",
|
|
"restart_uvc_and_server",
|
|
"LESAVKA_RECOVERY_ALLOW_CORE_RESTART:-0",
|
|
"LESAVKA_RECOVERY_ALLOW_REBOOT:-0",
|
|
"core restart disabled; preserving attached USB gadget",
|
|
"reboot disabled; leaving host online for operator inspection",
|
|
] {
|
|
assert!(
|
|
ladder.contains(marker),
|
|
"recovery ladder should preserve soft-recovery marker {marker}"
|
|
);
|
|
}
|
|
assert!(
|
|
ladder.find("restore_last_good").unwrap() < ladder.find("reboot_if_allowed").unwrap(),
|
|
"the ladder should try last-known-good restore before any optional reboot"
|
|
);
|
|
}
|