Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f3779e3
alpha11: advance immutable image version
Deadbytes101 Jul 10, 2026
3a6b7e6
alpha11: recover bounded resize2fs timeout truthfully
Deadbytes101 Jul 10, 2026
11e4ddf
alpha11: allow bounded USB filesystem recovery window
Deadbytes101 Jul 10, 2026
cabee4b
alpha11: make recovery credential persistence truthful
Deadbytes101 Jul 10, 2026
0b51a4d
alpha11: accept explicit boot-scoped recovery credentials
Deadbytes101 Jul 10, 2026
353d750
alpha11: test persistent and boot-scoped recovery gates
Deadbytes101 Jul 10, 2026
c6e631a
alpha11: test truthful recovery credential persistence
Deadbytes101 Jul 10, 2026
30f1e24
alpha11: verify truthful recovery credential scope
Deadbytes101 Jul 10, 2026
7120fb5
alpha11: advance diagnostic SSH image contract
Deadbytes101 Jul 10, 2026
69e15e1
alpha11: advance exact-image gate contract
Deadbytes101 Jul 10, 2026
9b58f25
alpha11: add state resize recovery regression tests
Deadbytes101 Jul 10, 2026
5209a84
alpha11: gate state resize and recovery credential paths
Deadbytes101 Jul 10, 2026
7872316
alpha11: add exact-image state recovery verifier
Deadbytes101 Jul 10, 2026
a983bca
alpha11: run exact-image state recovery gate
Deadbytes101 Jul 10, 2026
76b4a12
alpha11: lock exact-image state recovery contract
Deadbytes101 Jul 10, 2026
194b1ad
alpha11: check recovery gate as interpreted Python source
Deadbytes101 Jul 10, 2026
5794192
alpha11: rustfmt recovery gate test
Deadbytes101 Jul 10, 2026
c8775ae
alpha11: rustfmt state resize recovery test
Deadbytes101 Jul 10, 2026
6a11d1c
alpha11: recover e2fsck and repeated resize timeouts truthfully
Deadbytes101 Jul 10, 2026
b3737de
Format alpha11 recovery gate test
Deadbytes101 Jul 10, 2026
5d9e5b2
Format alpha11 state recovery test
Deadbytes101 Jul 10, 2026
15f1a2c
Fix alpha11 recovery message verifier contract
Deadbytes101 Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Before=local-fs.target rigos-state-ready.service
Type=oneshot
ExecStartPre=/usr/bin/systemd-tmpfiles --create /usr/lib/tmpfiles.d/rigos.conf
ExecStart=/usr/local/sbin/rigos-state-orchestrate
TimeoutStartSec=12min
RemainAfterExit=yes

[Install]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,25 @@ def main() -> int:
return deny("recovery_status_stale")
if status.get("local_console_access") is not True:
return deny("local_credential_unavailable")
if status.get("credential_persisted") is not True:
return deny("credential_not_persisted")
if status.get("credential_action") not in ("existing", "created", "restored"):
return deny("credential_action_invalid")

scope = status.get("credential_scope")
persisted = status.get("credential_persisted")
state_outcome = status.get("state_outcome")
if scope == "persistent":
if persisted is not True:
return deny("persistent_credential_missing")
if state_outcome != "ready":
return deny("persistent_credential_without_ready_state")
elif scope == "boot":
if persisted is not False:
return deny("boot_credential_claims_persistence")
if state_outcome == "ready":
return deny("ready_state_without_persistent_credential")
else:
return deny("credential_scope_invalid")

emit("allowed")
return 0

Expand Down
134 changes: 103 additions & 31 deletions build/usb/includes.chroot/usr/local/sbin/rigos-recovery-access
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ STATE = Path("/var/lib/rigos")
CREDENTIAL_DIRECTORY = STATE / "recovery"
CREDENTIAL_FILE = CREDENTIAL_DIRECTORY / "rigosadmin-password.hash"
BOOT_ID = Path("/proc/sys/kernel/random/boot_id")
STATE_STATUS = RUNTIME / "state-status.json"
MAX_HASH_BYTES = 512
MODULAR_CRYPT = re.compile(r"^\$[A-Za-z0-9][A-Za-z0-9./,_=-]*(?:\$[A-Za-z0-9./,_=-]+){2,}$")

Expand Down Expand Up @@ -52,6 +53,60 @@ def valid_password_hash(value: str) -> bool:
return MODULAR_CRYPT.fullmatch(value) is not None


def read_state_status() -> dict:
try:
raw = STATE_STATUS.read_bytes()
if not raw or len(raw) > 64 * 1024:
return {}
value = json.loads(raw)
return value if isinstance(value, dict) else {}
except (OSError, UnicodeError, json.JSONDecodeError):
return {}


def persistent_store_ready(status: dict) -> bool:
try:
boot_id = BOOT_ID.read_text(encoding="ascii").strip()
except OSError:
return False
if (
status.get("schema") != "rigos.state-status/v1"
or status.get("boot_id") != boot_id
or status.get("outcome") != "ready"
or status.get("mountpoint") != str(STATE)
):
return False
result = subprocess.run(
[
"/usr/bin/findmnt",
"--json",
"--target",
str(STATE),
"--output",
"TARGET,FSTYPE,OPTIONS",
],
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
if result.returncode != 0:
return False
try:
document = json.loads(result.stdout)
filesystems = document.get("filesystems", [])
mount = filesystems[0] if isinstance(filesystems, list) and filesystems else {}
except (TypeError, json.JSONDecodeError):
return False
options = set(str(mount.get("options", "")).split(","))
required = {"rw", "nosuid", "nodev", "noexec", "noatime"}
return (
mount.get("target") == str(STATE)
and mount.get("fstype") == "ext4"
and required.issubset(options)
)


def ensure_credential_directory() -> None:
CREDENTIAL_DIRECTORY.mkdir(mode=0o700, parents=True, exist_ok=True)
info = CREDENTIAL_DIRECTORY.lstat()
Expand Down Expand Up @@ -116,15 +171,25 @@ def restore_password_hash(value: str) -> bool:
return result.returncode == 0 and password_ready()


def prompt_for_password(invalid_stored_hash: bool) -> None:
message = (
"The saved local credential is invalid and will be replaced. Set the local rig "
"administrator password next."
if invalid_stored_hash
else "Set the local rig administrator password next. This enables tty1 diagnostics only."
)
def prompt_for_password(invalid_stored_hash: bool, persistent: bool) -> None:
if invalid_stored_hash:
message = (
"The saved local credential is invalid and will be replaced. "
"Set the local rig administrator password next."
)
elif persistent:
message = (
"Set the local rig administrator password next. The encrypted password hash "
"will be stored in verified persistent state."
)
else:
message = (
"Persistent state is unavailable. Set a temporary rig administrator password "
"for this boot so local and SSH diagnostics remain available. "
"This password is not persistent and may be requested again after reboot."
)
subprocess.run(
["/usr/bin/whiptail", "--title", "RIGOS RECOVERY ACCESS", "--msgbox", message, "11", "72"],
["/usr/bin/whiptail", "--title", "RIGOS RECOVERY ACCESS", "--msgbox", message, "12", "76"],
check=False,
)
subprocess.run(["/usr/bin/passwd", "rigosadmin"], check=True)
Expand Down Expand Up @@ -162,49 +227,56 @@ def write_status(value: dict) -> None:


def main() -> int:
ensure_credential_directory()
stored_hash, stored_hash_invalid = load_persistent_hash()
credential_action = "existing"
state_status = read_state_status()
state_outcome = str(state_status.get("outcome") or "unavailable")
persistent = persistent_store_ready(state_status)
stored_hash = None
stored_hash_invalid = False
if persistent:
ensure_credential_directory()
stored_hash, stored_hash_invalid = load_persistent_hash()

credential_action = "existing"
if password_ready():
live_hash = current_password_hash()
if live_hash is None:
raise RuntimeError("usable local credential hash is unavailable")
if stored_hash != live_hash:
if persistent and stored_hash != live_hash:
persist_password_hash(live_hash)
elif stored_hash is not None and restore_password_hash(stored_hash):
elif persistent and stored_hash is not None and restore_password_hash(stored_hash):
credential_action = "restored"
else:
prompt_for_password(stored_hash_invalid)
prompt_for_password(stored_hash_invalid, persistent)
if not password_ready():
raise RuntimeError("local credential setup did not produce a usable password")
live_hash = current_password_hash()
if live_hash is None:
raise RuntimeError("new local credential hash is unavailable")
persist_password_hash(live_hash)
if persistent:
persist_password_hash(live_hash)
credential_action = "created"

local_access = password_ready()
try:
state_outcome = json.loads((RUNTIME / "state-status.json").read_text(encoding="utf-8")).get("outcome", "unavailable")
except (OSError, json.JSONDecodeError):
state_outcome = "unavailable"
remote_active = any(unit_active(name) for name in ("ssh.service", "sshd.service"))
remote_enabled = any(unit_enabled(name) for name in ("ssh.service", "sshd.service"))
remote_inactive = not remote_active and not remote_enabled
configured = (STATE / "current").is_symlink()
configured = persistent and (STATE / "current").is_symlink()
mode = "operational" if state_outcome == "ready" and remote_inactive else "recovery"
write_status({
"schema": "rigos.recovery-access-status/v1",
"boot_id": BOOT_ID.read_text(encoding="ascii").strip(),
"mode": mode,
"local_console_access": local_access,
"state_outcome": state_outcome,
"remote_access": "inactive" if remote_inactive else "unexpected_active",
"configuration_commit": "present" if configured else "absent",
"credential_action": credential_action,
"credential_persisted": CREDENTIAL_FILE.is_file(),
})
credential_persisted = persistent and CREDENTIAL_FILE.is_file()
write_status(
{
"schema": "rigos.recovery-access-status/v1",
"boot_id": BOOT_ID.read_text(encoding="ascii").strip(),
"mode": mode,
"local_console_access": local_access,
"state_outcome": state_outcome,
"remote_access": "inactive" if remote_inactive else "unexpected_active",
"configuration_commit": "present" if configured else "absent",
"credential_action": credential_action,
"credential_scope": "persistent" if persistent else "boot",
"credential_persisted": credential_persisted,
}
)
return 0 if local_access and remote_inactive else 1


Expand Down
94 changes: 81 additions & 13 deletions build/usb/includes.chroot/usr/local/sbin/rigos-state-orchestrate
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ ATTESTATION = RUNTIME / "boot-device.json"
BOOT_ID = Path("/proc/sys/kernel/random/boot_id")
PARTUUID_ROOT = Path("/dev/disk/by-partuuid")
PARTUUID_RE = re.compile(r"^[0-9A-Fa-f-]{1,128}$")
FILESYSTEM_TIMEOUT_SECONDS = 300
MAX_COMMAND_OUTPUT = 64 * 1024


def read_json(path: Path) -> dict:
Expand Down Expand Up @@ -128,26 +130,78 @@ def mark_repair_required(message: str) -> None:
write_status(status)


def bounded_output(value: str | bytes | None) -> str:
if value is None:
return ""
if isinstance(value, bytes):
value = value.decode("utf-8", errors="replace")
encoded = value.encode("utf-8", errors="replace")
if len(encoded) > MAX_COMMAND_OUTPUT:
encoded = encoded[:MAX_COMMAND_OUTPUT]
value = encoded.decode("utf-8", errors="replace") + " [truncated]"
return value.strip()


def run_repair_command(argv: list[str], accepted: tuple[int, ...]) -> tuple[bool, str | None]:
program = Path(argv[0]).name
try:
result = subprocess.run(
argv,
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=FILESYSTEM_TIMEOUT_SECONDS,
)
except subprocess.TimeoutExpired as error:
detail = bounded_output(error.stderr) or bounded_output(error.stdout)
suffix = f": {detail}" if detail else ""
return False, f"{program}: timeout after {FILESYSTEM_TIMEOUT_SECONDS}s{suffix}"
if result.returncode not in accepted:
detail = bounded_output(result.stderr) or bounded_output(result.stdout)
suffix = f": {detail}" if detail else ""
return False, f"{program}: exit {result.returncode}{suffix}"
return True, None


def forced_check() -> bool:
device, error = verified_state_device()
if error is not None or device is None:
mark_repair_required(error or "verified state device is unavailable")
return False
result = subprocess.run(
["/usr/sbin/e2fsck", "-f", "-p", str(device)],
check=False,
ok, failure = run_repair_command(
["/usr/sbin/e2fsck", "-f", "-p", str(device)], (0, 1)
)
if result.returncode in (0, 1):
if ok:
return True
mark_repair_required(
f"forced ext4 check requires owner intervention with exit code {result.returncode}"
)
mark_repair_required(f"forced ext4 check failed: {failure}")
return False


def complete_resize_after_timeout() -> bool:
device, error = verified_state_device()
if error is not None or device is None:
mark_repair_required(error or "verified state device is unavailable")
return False
checked, check_failure = run_repair_command(
["/usr/sbin/e2fsck", "-f", "-p", str(device)], (0, 1)
)
if not checked:
mark_repair_required(f"post-timeout ext4 check failed: {check_failure}")
return False
resized, resize_failure = run_repair_command(
["/usr/sbin/resize2fs", str(device)], (0,)
)
if not resized:
mark_repair_required(f"state filesystem resize failed: {resize_failure}")
return False
return True


def main() -> int:
retried_missing_device = False
repaired_filesystem = False
completed_resize_timeout = False
for _attempt in range(4):
result = run_core()
if result != 0:
Expand All @@ -156,22 +210,36 @@ def main() -> int:
if status.get("outcome") == "ready":
return 0
message = str(status.get("message") or "")
outcome = status.get("outcome")
recoverable = outcome in ("limited_capacity", "repair_required")
if (
status.get("outcome") == "limited_capacity"
outcome == "limited_capacity"
and not retried_missing_device
and "No such file or directory" in message
):
retried_missing_device = True
time.sleep(1)
continue
if (
status.get("outcome") == "limited_capacity"
and not repaired_filesystem
and "e2fsck -f" in message
):
if recoverable and ("e2fsck -f" in message or "e2fsck: timeout" in message):
if repaired_filesystem:
mark_repair_required(
"state core ext4 check remained incomplete after bounded repair"
)
return 0
repaired_filesystem = True
if forced_check():
continue
return 0
if recoverable and "resize2fs: timeout" in message:
if completed_resize_timeout:
mark_repair_required(
"state core resize remained incomplete after bounded repair"
)
return 0
completed_resize_timeout = True
if complete_resize_after_timeout():
continue
return 0
return 0
return 0

Expand Down
6 changes: 3 additions & 3 deletions build/usb/version.env
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
RIGOS_PRODUCT_VERSION=0.0.4-alpha.10
RIGOS_IMAGE_VERSION=0.0.4-alpha.10
RIGOS_PRODUCT_VERSION=0.0.4-alpha.11
RIGOS_IMAGE_VERSION=0.0.4-alpha.11
RIGOS_IMAGE_ID=rigos-usb-amd64
RIGOS_IMAGE_CHANNEL=alpha
RIGOS_BUILD_ORDINAL=10
RIGOS_BUILD_ORDINAL=11
RIGOS_XMRIG_VERSION=6.26.0
RIGOS_XMRIG_ARCHIVE_SHA256=fc6f8ae5f64e4f17481f7e3be29a1c56949f216a998414188003eae1db20c9e5
RIGOS_XMRIG_BINARY_SHA256=b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49
Loading