feat: close #54 V2 install safety, #33 rofi scrollbar, #43 MOTD tip
All checks were successful
Check / eval (push) Successful in 3m0s
All checks were successful
Check / eval (push) Successful in 3m0s
#54: permanent checks.installer-safety; test-install-safety harness with offline compose-fail, LUKS-signature disk warn, and HM-hint FORCE paths. #33: enable themed rofi scrollbars (generated + all whole-swap .rasi). #43: MOTD + first-boot tip for System › Firmware (V3 LVFS stays hw-queue). V2: compose-fail/disk-warn/hm-hint logs green offline; theme-shot boreal+summer-night menus OK. V0 flake/installer-safety.
This commit is contained in:
132
tools/check-install-safety.py
Executable file
132
tools/check-install-safety.py
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Installer safety guards contract (flake check `installer-safety`).
|
||||
|
||||
Usage: check-install-safety.py <nomarchy-install.sh>
|
||||
|
||||
Locks the three BACKLOG #54 guards against silent regression:
|
||||
|
||||
1. Offline compose-lock failure fail-closes (no network `flake lock`).
|
||||
2. Disk signature warn for NTFS / BitLocker / Microsoft / LUKS.
|
||||
3. HM pre-activate failure drops NOMARCHY-DESKTOP-NOT-THEMED.txt.
|
||||
|
||||
Also re-checks the password ≥8 interactive floors (V0-complete).
|
||||
Pure — no VM, no ISO.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
script = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def check(cond: bool, msg: str) -> None:
|
||||
if not cond:
|
||||
sys.exit(f"installer-safety: {msg}")
|
||||
|
||||
|
||||
# ── 1. Offline compose-lock fail-closed ─────────────────────────────────
|
||||
# The offline branch must fail hard; `nix flake lock` only on the online path.
|
||||
check(
|
||||
"Offline lock composition failed and there is no network to fall back to"
|
||||
in script,
|
||||
"missing offline compose-lock fail-closed message",
|
||||
)
|
||||
# Fail-closed is gated on OFFLINE=true.
|
||||
check(
|
||||
re.search(
|
||||
r'if \[\[ "\$OFFLINE" == true \]\]; then\s*\n'
|
||||
r'\s*fail "Offline lock composition failed',
|
||||
script,
|
||||
),
|
||||
"offline fail-closed is not gated on OFFLINE=true",
|
||||
)
|
||||
# Network flake lock must sit in the non-offline fallback, not the offline arm.
|
||||
flake_lock = script.find('nix --extra-experimental-features "nix-command flakes" flake lock')
|
||||
offline_fail = script.find("Offline lock composition failed and there is no network")
|
||||
check(flake_lock > 0 and offline_fail > 0, "compose-lock / flake lock markers missing")
|
||||
check(
|
||||
flake_lock > offline_fail,
|
||||
"network `flake lock` appears before the offline fail-closed arm",
|
||||
)
|
||||
# Between offline fail and flake lock there must not be an unguarded lock call
|
||||
# earlier in the file for this recovery path.
|
||||
check(
|
||||
"Offline lock composition failed — resolving over the network." in script,
|
||||
"missing online-only compose-lock fallback warn",
|
||||
)
|
||||
|
||||
# ── 2. Disk signature warn ──────────────────────────────────────────────
|
||||
sig_re = re.search(
|
||||
r"grep -qiE '([^']+)'\s*<<<\s*\"\$existing_sig\"",
|
||||
script,
|
||||
)
|
||||
check(sig_re is not None, "disk signature grep missing")
|
||||
pat = sig_re.group(1)
|
||||
check(
|
||||
all(tok in pat for tok in ("ntfs", "bitlocker", "microsoft", "crypto_luks")),
|
||||
f"signature pattern incomplete: {pat!r}",
|
||||
)
|
||||
check(
|
||||
"Windows/BitLocker/NTFS or LUKS" in script,
|
||||
"missing user-facing disk-signature warn text",
|
||||
)
|
||||
|
||||
# Pure behavioural: the same regex the installer uses.
|
||||
rx = re.compile(pat, re.I)
|
||||
samples_hit = [
|
||||
"ntfs\n",
|
||||
"TYPE=\"ntfs\"",
|
||||
"BitLocker",
|
||||
"Microsoft reserved",
|
||||
"crypto_LUKS",
|
||||
"TYPE=\"crypto_LUKS\"",
|
||||
]
|
||||
samples_miss = [
|
||||
"",
|
||||
"ext4\n",
|
||||
"btrfs\nvfat",
|
||||
"TYPE=\"xfs\"",
|
||||
]
|
||||
for s in samples_hit:
|
||||
check(bool(rx.search(s)), f"signature regex should match {s!r}")
|
||||
for s in samples_miss:
|
||||
check(not rx.search(s), f"signature regex should NOT match {s!r}")
|
||||
|
||||
# ── 3. Pre-activate recovery hint ───────────────────────────────────────
|
||||
check(
|
||||
"NOMARCHY-DESKTOP-NOT-THEMED.txt" in script,
|
||||
"missing pre-activate hint filename",
|
||||
)
|
||||
check(
|
||||
"home-manager switch --flake ~/.nomarchy -b bak" in script,
|
||||
"hint must tell the user the recovery command",
|
||||
)
|
||||
check(
|
||||
'hint_file="/mnt/home/$USERNAME/NOMARCHY-DESKTOP-NOT-THEMED.txt"' in script
|
||||
or 'hint_file="/mnt/home/$USERNAME/NOMARCHY-DESKTOP-NOT-THEMED.txt"'
|
||||
in script.replace(" ", ""),
|
||||
"hint must land on the target home under /mnt",
|
||||
)
|
||||
# Hint is only written on the failure arm (else of nixos-enter activate).
|
||||
activate_if = script.find("if nixos-enter --root /mnt -- bash /root/nomarchy-hm-activate.sh")
|
||||
hint_pos = script.find("NOMARCHY-DESKTOP-NOT-THEMED.txt")
|
||||
check(activate_if > 0 and hint_pos > activate_if, "hint is not on the activate-failure arm")
|
||||
|
||||
# ── Password floors (V0-complete, keep the floor) ───────────────────────
|
||||
check(
|
||||
re.search(r"password for \$USERNAME \(min 8 chars\)", script) is not None,
|
||||
"account password min-8 prompt missing",
|
||||
)
|
||||
check(
|
||||
re.search(r"LUKS passphrase \(min 8 chars\)", script) is not None,
|
||||
"LUKS passphrase min-8 prompt missing",
|
||||
)
|
||||
check(
|
||||
script.count("[[ ${#") >= 2 and " -ge 8 ]]" in script,
|
||||
"min-8 length checks missing",
|
||||
)
|
||||
|
||||
print("installer-safety: all contracts hold")
|
||||
Reference in New Issue
Block a user