#!/bin/bash # felhom-bundle-bootstrap.sh — the ONE by-hand act that lets an installed box take config bundles (R-840, `11` §5.4.2). # # Why it exists: a signed agent_config_update is installed by the box's ROOT-OWNED felhom-os-apply. A box installed # before agent v0.143.0 has an older felhom-os-apply that has no bundle mode, and no signed job can write a root file # on such a box (that gap IS R-840). So the first bundle needs this one step, as root, once per box. After it, every # later change to the box's root files arrives by the signed route. # # What it does: downloads the config bundle of AGENT_VERSION, checks its sha256 against the one you pass (the vouched # one — the hub's Configuration page or the release output), takes out felhom-os-apply, checks it against the bundle's # own entry and that it parses, installs it (0755 root:root, the old copy kept beside it), and runs its self-check. # It changes NOTHING else: no sudoers, no unit, no restart, no app, no Docker. # # Usage (as root on the Proxmox host): bash felhom-bundle-bootstrap.sh set -euo pipefail VER="${1:-}"; SHA="${2:-}" [[ "$VER" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "usage: $0 " >&2; exit 2; } [[ "$SHA" =~ ^[0-9a-f]{64}$ ]] || { echo "the bundle sha256 must be 64 lowercase hex characters" >&2; exit 2; } [[ $EUID -eq 0 ]] || { echo "run as root" >&2; exit 2; } URL="https://gitea.dooplex.hu/api/packages/admin/generic/felhom-agent/$VER/felhom-config-bundle.json" DST=/usr/local/sbin/felhom-os-apply T=$(mktemp -d); trap 'rm -rf "$T"' EXIT echo "1/4 download $URL" curl -fsS -o "$T/bundle.json" "$URL" got=$(sha256sum "$T/bundle.json" | awk '{print $1}') [[ "$got" == "$SHA" ]] || { echo "STOP: the bundle's sha256 is $got, not $SHA — nothing changed" >&2; exit 1; } echo " sha256 OK ($SHA)" echo "2/4 take felhom-os-apply out of the bundle and check it" python3 - "$T/bundle.json" "$T/os-apply" "$VER" <<'PY' import ast, base64, hashlib, json, sys b = json.load(open(sys.argv[1])) assert b.get("agent_version") == sys.argv[3], f"the bundle is for {b.get('agent_version')}, not {sys.argv[3]}" e = [f for f in b["files"] if f["path"] == "/usr/local/sbin/felhom-os-apply"][0] data = base64.b64decode(e["content_b64"]) assert hashlib.sha256(data).hexdigest() == e["sha256"], "felhom-os-apply does not match its own sha in the bundle" text = data.decode() ast.parse(text) assert 'BUNDLE_OP = "agent_config_update"' in text, "this felhom-os-apply has no bundle mode" open(sys.argv[2], "wb").write(data) print(" felhom-os-apply sha256", e["sha256"]) PY echo "3/4 install it (the previous copy is kept as $DST.pre-bundle)" [[ -f "$DST" ]] && cp -p "$DST" "$DST.pre-bundle" install -m 0755 -o root -g root "$T/os-apply" "$DST.new.$$" mv "$DST.new.$$" "$DST" echo "4/4 self-check" out=$(python3 "$DST" --self-check) echo " $out" [[ "$out" == *"bundle-format=1"* ]] || { echo "STOP: the self-check failed — put the old copy back: mv $DST.pre-bundle $DST" >&2; exit 1; } echo "DONE. This box can now take signed config bundles. Nothing else was changed."