packaging/linux: preflight (Ubuntu 24.04, NVIDIA driver 580 floor with libcuda and libnvrtc.so.12, AMD ROCm OpenCL ICD, Intel compute runtime, nvidia-smi and the OpenCL list printed, RAM, free disk, ports, ufw), the wallet and rig name asked once, the igneum system user, the Ed25519 check of dl/public/igneum-app-latest.json with the OTA public key (OpenSSL 3 pkeyutl -rawin, python3 cryptography fallback, never skipped), consensus.override written from the verified manifest and refreshed hourly (the HiveOS override rule without a package republish), the HiveOS package downloaded with size and sha256 checked (the signed linux entry when the manifest has one, else the .sha256 sidecar behind --allow-sidecar-sha256, said in capitals), releases under /opt/igneum with a current symlink and a 90-s rollback, one miner unit per card with CUDA_DEVICE_ORDER=PCI_BUS_ID and the OpenCL ordinal mapping, the integrated GPU and the BMC VGA excluded by the inventory, the prover unit as the proving-v1 loop in bash (12 GB gate, 20 GB mine-and-prove line with the card's miner paused per shard through a sudoers rule, idles in state setup while no Linux prover binary is published), telemetry in the app's line shapes with the relay upload under nodelog-linux/miner-<vendor>/linux labels, the identities rule with its 8 GiB threshold. Tested on the Mac: shellcheck -x -S style clean, bash -n, check-units.sh (6 units, the systemd-analyze stand-in; no systemd or Docker here), the inventory on a fake sysfs tree, the rules, the live manifest verified by both verifiers and tampered copies refused, the installer dry run with the 0.3.9 package downloaded and verified. Untested until a rig exists: listed in README.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
80 lines
6.3 KiB
Bash
Executable file
80 lines
6.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Checks the unit files in units/ the way `systemd-analyze verify` would where it can be run (no systemd on this Mac,
|
|
# no Docker on 5 October 2026): sections, every key against the directive lists of systemd.unit(5), systemd.service(5),
|
|
# systemd.timer(5) and systemd.exec(5) (the subset used here, copied from the man pages; a key not in the list is a
|
|
# failure, so a typo never reaches a rig), the values of the enumerated keys, the ExecStart paths against the scripts
|
|
# that install-rig.sh installs, the template specifier, and each unit's references to the others. On a machine with
|
|
# systemd it also runs `systemd-analyze verify` on copies of the units. Exit 1 on any failure.
|
|
set -euo pipefail
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
python3 - "$HERE" <<'PY'
|
|
import os, re, sys
|
|
here = sys.argv[1]
|
|
UNIT = {"Description", "Documentation", "After", "Before", "Wants", "Requires", "BindsTo", "PartOf", "Conflicts", "ConditionPathExists", "StartLimitIntervalSec", "StartLimitBurst"}
|
|
INSTALL = {"WantedBy", "RequiredBy", "Alias", "Also"}
|
|
EXEC = {"User", "Group", "SupplementaryGroups", "WorkingDirectory", "Environment", "EnvironmentFile", "LimitNOFILE", "Nice", "ProtectSystem", "ProtectHome", "PrivateTmp", "NoNewPrivileges", "ReadWritePaths", "ReadOnlyPaths", "RuntimeDirectory", "RuntimeDirectoryPreserve", "StateDirectory", "StandardOutput", "StandardError", "SyslogIdentifier", "UMask", "OOMScoreAdjust", "TimeoutStartSec", "TimeoutStopSec"}
|
|
SERVICE = {"Type", "ExecStart", "ExecStartPre", "ExecStop", "ExecReload", "Restart", "RestartSec", "SuccessExitStatus", "KillMode", "KillSignal", "RemainAfterExit", "TimeoutSec"} | EXEC
|
|
TIMER = {"OnBootSec", "OnUnitActiveSec", "OnCalendar", "RandomizedDelaySec", "Persistent", "Unit", "AccuracySec"}
|
|
ENUM = {"Type": {"simple", "exec", "forking", "oneshot", "notify", "idle"}, "Restart": {"no", "always", "on-success", "on-failure", "on-abnormal", "on-abort", "on-watchdog"},
|
|
"KillMode": {"control-group", "mixed", "process", "none"}, "ProtectSystem": {"true", "false", "full", "strict"}, "ProtectHome": {"true", "false", "read-only", "tmpfs"},
|
|
"StandardOutput": {"journal", "inherit", "null", "tty", "kmsg", "journal+console"}, "StandardError": {"journal", "inherit", "null", "tty", "kmsg", "journal+console"},
|
|
"RuntimeDirectoryPreserve": {"yes", "no", "restart"}, "Persistent": {"true", "false", "yes", "no"}, "PrivateTmp": {"true", "false", "yes", "no"}, "NoNewPrivileges": {"true", "false", "yes", "no"}}
|
|
units = sorted(f for f in os.listdir(os.path.join(here, "units")) if f.endswith((".service", ".timer")))
|
|
scripts = set(os.listdir(os.path.join(here, "bin")))
|
|
fails = 0
|
|
def fail(u, msg):
|
|
global fails; fails += 1; print(f" FAIL {u}: {msg}")
|
|
names = set(units)
|
|
for u in units:
|
|
text = open(os.path.join(here, "units", u)).read()
|
|
section = None; seen = {}
|
|
kind = u.rsplit(".", 1)[1]
|
|
template = "@" in u
|
|
for n, raw in enumerate(text.splitlines(), 1):
|
|
line = raw.strip()
|
|
if not line or line.startswith("#"): continue
|
|
m = re.fullmatch(r"\[(\w+)\]", line)
|
|
if m:
|
|
section = m.group(1)
|
|
if section not in {"Unit", "Service", "Timer", "Install"}: fail(u, f"line {n}: unknown section [{section}]")
|
|
if section == "Service" and kind != "service": fail(u, f"line {n}: [Service] in a {kind}")
|
|
if section == "Timer" and kind != "timer": fail(u, f"line {n}: [Timer] in a {kind}")
|
|
seen.setdefault(section, {}); continue
|
|
if section is None: fail(u, f"line {n}: a key before any section"); continue
|
|
if "=" not in line: fail(u, f"line {n}: not key=value: {line}"); continue
|
|
k, v = line.split("=", 1); k = k.strip(); v = v.strip()
|
|
allowed = {"Unit": UNIT, "Service": SERVICE, "Timer": TIMER, "Install": INSTALL}[section]
|
|
if k not in allowed: fail(u, f"line {n}: [{section}] does not take {k}")
|
|
if k in ENUM and v not in ENUM[k]: fail(u, f"line {n}: {k}={v} is not one of {sorted(ENUM[k])}")
|
|
if k.startswith("Exec"):
|
|
path = v.split()[0].lstrip("-@+!")
|
|
if not path.startswith("/"): fail(u, f"line {n}: {k} is not an absolute path: {v}")
|
|
if path.startswith("/opt/igneum/bin/") and os.path.basename(path) not in scripts: fail(u, f"line {n}: {k} names {path}, which bin/ does not hold")
|
|
if "%i" in v and not template: fail(u, f"line {n}: %i in a unit that is not a template")
|
|
if k in {"After", "Before", "Wants", "Requires", "BindsTo", "PartOf", "Unit"}:
|
|
for ref in v.split():
|
|
if ref.startswith("igneum-") and ref not in names and not (ref.endswith(".service") and ref.replace(".service", "") + ".service" in names):
|
|
fail(u, f"line {n}: {k} references {ref}, not in units/")
|
|
if k in {"RestartSec", "TimeoutStopSec", "TimeoutStartSec", "OnBootSec", "OnUnitActiveSec", "RandomizedDelaySec"} and not re.fullmatch(r"\d+(s|min|h|ms)?(\s+\d+(s|min|h|ms)?)*", v):
|
|
fail(u, f"line {n}: {k}={v} is not a time span")
|
|
seen[section][k] = v
|
|
if kind == "service":
|
|
if "Service" not in seen or "ExecStart" not in seen["Service"]: fail(u, "no ExecStart")
|
|
if "Install" not in seen and u != "igneum-update.service": fail(u, "no [Install] (the installer enables it)")
|
|
if template and "%i" not in seen.get("Service", {}).get("ExecStart", ""): fail(u, "a template whose ExecStart ignores %i")
|
|
if seen.get("Service", {}).get("Type") == "oneshot" and "Restart" in seen["Service"] and seen["Service"]["Restart"] != "no": fail(u, "Restart= on a oneshot service")
|
|
if kind == "timer":
|
|
if "Timer" not in seen: fail(u, "no [Timer]")
|
|
unit = seen["Timer"].get("Unit", u.replace(".timer", ".service"))
|
|
if unit not in names: fail(u, f"fires {unit}, not in units/")
|
|
print(f" ok {u}: sections {', '.join(seen)}; {sum(len(s) for s in seen.values())} keys")
|
|
print(f" units: {len(units)} checked, {fails} failure(s)")
|
|
sys.exit(1 if fails else 0)
|
|
PY
|
|
if command -v systemd-analyze >/dev/null 2>&1; then
|
|
tmp="$(mktemp -d)"; cp "$HERE"/units/* "$tmp/"
|
|
if systemd-analyze verify "$tmp"/*.service "$tmp"/*.timer; then echo " ok systemd-analyze verify"; else echo " FAIL systemd-analyze verify"; exit 1; fi
|
|
rm -rf "$tmp"
|
|
else
|
|
echo " note systemd-analyze is not on this machine; only the static check above ran (run this script on the rig for the real one)"
|
|
fi
|