fix(wake): #913 installer adoption gaps — _lib dep-check fail-loud + mosaic-wake.service systemd-search-path link+validate (#930)
Co-authored-by: jason.woltje <jason@diversecanvas.com> Co-committed-by: jason.woltje <jason@diversecanvas.com>
This commit was merged in pull request #930.
This commit is contained in:
@@ -98,7 +98,7 @@
|
|||||||
# seen-ledger nor observed_seq, so the source is re-enumerated next
|
# seen-ledger nor observed_seq, so the source is re-enumerated next
|
||||||
# cycle — no obligation loss). Files changed: _wake-common.sh,
|
# cycle — no obligation loss). Files changed: _wake-common.sh,
|
||||||
# store.sh, detector.sh (+ tests).
|
# store.sh, detector.sh (+ tests).
|
||||||
# 0.6.6 #924 digest.sh dead-letter QUARANTINE alarm — G2a fix (dragon-lin
|
# 0.6.6 #924 digest.sh dead-letter QUARANTINE alarm — G2a fix (wake-pilot
|
||||||
# cure-verification follow-up on #920/PR #922). The #920 per-entry
|
# cure-verification follow-up on #920/PR #922). The #920 per-entry
|
||||||
# quarantine alarm was stderr/journal-LOCAL only; a dead-lettered
|
# quarantine alarm was stderr/journal-LOCAL only; a dead-lettered
|
||||||
# entry is STORE-ACCOUNTED (§2.3) so the reconciler never re-flags
|
# entry is STORE-ACCOUNTED (§2.3) so the reconciler never re-flags
|
||||||
@@ -122,8 +122,34 @@
|
|||||||
# wedge — #920's core property is preserved). digest.sh is the
|
# wedge — #920's core property is preserved). digest.sh is the
|
||||||
# ONLY file changed; store.sh/beacon.sh/reconcile.sh are
|
# ONLY file changed; store.sh/beacon.sh/reconcile.sh are
|
||||||
# UNCHANGED (beacon.sh's adapter contract is reused, not modified).
|
# UNCHANGED (beacon.sh's adapter contract is reused, not modified).
|
||||||
|
# 0.6.7 #913 wake-install.sh installer ADOPTION-GAP fixes (wake-pilot,
|
||||||
|
# non-blocking, ADDITIVE per #869). (a) DEP-CHECK: the installer
|
||||||
|
# sourced _lib/manifest.sh (the shared framework-manifest reader it
|
||||||
|
# needs for Gate A) unconditionally, so an older host seed that
|
||||||
|
# predates that helper aborted with a bare, obscure
|
||||||
|
# `source: No such file or directory`. It now checks the library
|
||||||
|
# FIRST and FAILS LOUD naming the missing file + the remedy (re-seed
|
||||||
|
# the framework, then retry --component wake) — the dependency is
|
||||||
|
# genuinely required (Gate A cannot be skipped on an enforcement
|
||||||
|
# path), so it fails loud rather than degrading. (b) SYSTEMD SEARCH
|
||||||
|
# PATH: wi_install copies mosaic-wake.service under mosaic home
|
||||||
|
# (systemd/user/, framework-owned) but `systemctl --user` searches
|
||||||
|
# ~/.config/systemd/user/, so the unit was invisible and could not be
|
||||||
|
# enabled/started. install now LINKS the unit into the user systemd
|
||||||
|
# search path (symlink -> the mosaic-home SSOT copy, so upgrades
|
||||||
|
# propagate) and VALIDATES it resolves (search-path entry exists,
|
||||||
|
# dereferences to a readable, well-formed unit; an opportunistic
|
||||||
|
# `systemctl --user cat` probe runs only behind a guard, since the
|
||||||
|
# installer may run where no user manager is live). Both steps are
|
||||||
|
# idempotent (a re-install neither duplicates nor breaks the link).
|
||||||
|
# #869 ADDITIVE: the link target lives OUTSIDE mosaic home, so it is
|
||||||
|
# not a framework-manifest path; ownership of the SSOT unit stays
|
||||||
|
# systemd/** in the single framework-manifest.txt authority — NO new
|
||||||
|
# owned path, NO second ownership authority. framework-manifest.txt,
|
||||||
|
# the install-ordering-guard, and the manifest parity contract are all
|
||||||
|
# UNCHANGED. Only wake-install.sh (+ test-wake-install.sh) changed.
|
||||||
component=wake
|
component=wake
|
||||||
version=0.6.6
|
version=0.6.7
|
||||||
|
|
||||||
# Watch-list schema this component consumes, and the INCLUSIVE range of
|
# Watch-list schema this component consumes, and the INCLUSIVE range of
|
||||||
# schema_version values it supports. A wake-watch-list.json whose schema_version
|
# schema_version values it supports. A wake-watch-list.json whose schema_version
|
||||||
@@ -183,7 +209,11 @@ schema_max=1
|
|||||||
# timer, the snapshot-guard (no reap without a snapshot), and
|
# timer, the snapshot-guard (no reap without a snapshot), and
|
||||||
# fail-closed alarm-target + HMAC-key install-validation (the
|
# fail-closed alarm-target + HMAC-key install-validation (the
|
||||||
# installer wires + install-validates the beacon target that
|
# installer wires + install-validates the beacon target that
|
||||||
# beacon.sh's fail-loud primitive is designed for). (W7)
|
# beacon.sh's fail-loud primitive is designed for). Fails loud
|
||||||
|
# if the required _lib/manifest.sh helper is absent (older host
|
||||||
|
# seed) instead of a bare source error, and LINKS the unit into
|
||||||
|
# the user systemd search path + post-install-validates it
|
||||||
|
# resolves (#913). (W7, #913)
|
||||||
# Companion (framework subtree, not under tools/wake/): systemd/user/mosaic-wake.service
|
# Companion (framework subtree, not under tools/wake/): systemd/user/mosaic-wake.service
|
||||||
# — the long-lived detector daemon unit (per-class SLO lives in
|
# — the long-lived detector daemon unit (per-class SLO lives in
|
||||||
# the daemon, NOT a systemd interval). (W7)
|
# the daemon, NOT a systemd interval). (W7)
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ command -v jq >/dev/null 2>&1 || { echo "SKIP: jq not available" >&2; exit 0; }
|
|||||||
TMP_ROOT="$(mktemp -d)"
|
TMP_ROOT="$(mktemp -d)"
|
||||||
trap 'rm -rf "$TMP_ROOT"' EXIT
|
trap 'rm -rf "$TMP_ROOT"' EXIT
|
||||||
|
|
||||||
|
# Isolation default: no group may ever touch the operator's REAL user systemd dir
|
||||||
|
# (~/.config/systemd/user). The (b/#913) search-path link step defaults there, so
|
||||||
|
# pin an isolated per-run dir here; groups that need their own re-export it.
|
||||||
|
export WAKE_SYSTEMD_USER_DIR="$TMP_ROOT/systemd-user-default"
|
||||||
|
mkdir -p "$WAKE_SYSTEMD_USER_DIR"
|
||||||
|
|
||||||
FAILFILE="$TMP_ROOT/failures"
|
FAILFILE="$TMP_ROOT/failures"
|
||||||
: >"$FAILFILE"
|
: >"$FAILFILE"
|
||||||
pass=0
|
pass=0
|
||||||
@@ -248,6 +254,67 @@ else
|
|||||||
) && ok
|
) && ok
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── (a #913) missing _lib/manifest.sh dependency — FAIL LOUD, not a bare source error ──
|
||||||
|
|
||||||
|
echo "== I9: dep-check — a host seed missing _lib/manifest.sh FAILS LOUD (names dep + remedy), not a bare source error =="
|
||||||
|
(
|
||||||
|
# Simulate an OLDER host seed that predates the shared manifest reader: copy the
|
||||||
|
# framework tree the installer needs, but OMIT tools/_lib/manifest.sh. Running the
|
||||||
|
# copied wake-install.sh reproduces the adoption gap on a not-yet-updated host.
|
||||||
|
FAKE="$(fresh i9-fw)"
|
||||||
|
mkdir -p "$FAKE/tools/wake" "$FAKE/tools/_lib" "$FAKE/systemd/user"
|
||||||
|
cp "$FRAMEWORK_ROOT/tools/wake/"* "$FAKE/tools/wake/" 2>/dev/null || true
|
||||||
|
# Copy _lib EXCEPT manifest.sh — the exact file older seeds lack.
|
||||||
|
for f in "$FRAMEWORK_ROOT/tools/_lib/"*; do
|
||||||
|
[ -e "$f" ] || continue
|
||||||
|
case "$(basename "$f")" in manifest.sh) continue ;; esac
|
||||||
|
cp "$f" "$FAKE/tools/_lib/" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
cp "$FRAMEWORK_ROOT/systemd/user/mosaic-wake.service" "$FAKE/systemd/user/" 2>/dev/null || true
|
||||||
|
cp "$FRAMEWORK_ROOT/framework-manifest.txt" "$FAKE/" 2>/dev/null || true
|
||||||
|
[ ! -e "$FAKE/tools/_lib/manifest.sh" ] || fail_msg "I9: fixture setup — manifest.sh must be absent to simulate an older seed"
|
||||||
|
WI_FAKE="$FAKE/tools/wake/wake-install.sh"
|
||||||
|
TGT="$(fresh i9-target)"
|
||||||
|
out="$(WAKE_INSTALL_SOURCE="$FAKE" WAKE_INSTALL_TARGET="$TGT" bash "$WI_FAKE" install 2>&1)"; rc=$?
|
||||||
|
[ "$rc" -ne 0 ] || fail_msg "I9: install MUST fail when _lib/manifest.sh is missing (rc=$rc)"
|
||||||
|
echo "$out" | grep -qi 'manifest.sh' || fail_msg "I9: the failure must NAME the missing library (manifest.sh) [$out]"
|
||||||
|
echo "$out" | grep -qiE 'REMEDY|mosaic update|re-seed|framework install' \
|
||||||
|
|| fail_msg "I9: the failure must give an actionable REMEDY, not just an error [$out]"
|
||||||
|
# It must be a CLEAR fail-loud, NOT the obscure bare `source: No such file or directory`.
|
||||||
|
echo "$out" | grep -qi 'No such file or directory' \
|
||||||
|
&& fail_msg "I9: must not fail with a bare source error (obscure) [$out]"
|
||||||
|
# Positive control: with the helper present (real framework root) install succeeds.
|
||||||
|
TGT_OK="$(fresh i9-target-ok)"
|
||||||
|
WAKE_INSTALL_SOURCE="$FRAMEWORK_ROOT" WAKE_INSTALL_TARGET="$TGT_OK" \
|
||||||
|
bash "$WI" install >/dev/null 2>&1 || fail_msg "I9: with _lib/manifest.sh present the install must succeed"
|
||||||
|
) && ok
|
||||||
|
|
||||||
|
# ── (b #913) mosaic-wake.service lands IN the user systemd search path + validates ──
|
||||||
|
|
||||||
|
echo "== I10: systemd search-path — install links mosaic-wake.service into the search path; validate catches a miss; idempotent =="
|
||||||
|
(
|
||||||
|
TGT="$(fresh i10-target)"
|
||||||
|
UD="$(fresh i10-systemd)" # isolated stand-in for ~/.config/systemd/user
|
||||||
|
export WAKE_INSTALL_SOURCE="$FRAMEWORK_ROOT"
|
||||||
|
export WAKE_INSTALL_TARGET="$TGT"
|
||||||
|
export WAKE_SYSTEMD_USER_DIR="$UD"
|
||||||
|
bash "$WI" install >/dev/null 2>&1 || fail_msg "I10: install must succeed"
|
||||||
|
# (a) the unit is resolvable in the user systemd search path.
|
||||||
|
[ -e "$UD/mosaic-wake.service" ] || fail_msg "I10: mosaic-wake.service must be linked into the user systemd search path ($UD)"
|
||||||
|
out="$(bash "$WI" validate-systemd-path 2>&1)"; rc=$?
|
||||||
|
[ "$rc" -eq 0 ] || fail_msg "I10: post-install validate must confirm the unit resolves (rc=$rc) [$out]"
|
||||||
|
# (b) NEGATIVE CONTROL: not-in-search-path is CAUGHT (fail loud, names the miss).
|
||||||
|
rm -f "$UD/mosaic-wake.service"
|
||||||
|
out2="$(bash "$WI" validate-systemd-path 2>&1)"; rc2=$?
|
||||||
|
[ "$rc2" -ne 0 ] || fail_msg "I10: validate must FAIL when the unit is not in the search path (rc=$rc2) [$out2]"
|
||||||
|
echo "$out2" | grep -qi 'search path' || fail_msg "I10: validate failure must name the search-path miss [$out2]"
|
||||||
|
# (c) idempotent re-install: exactly one search-path entry, still resolvable.
|
||||||
|
bash "$WI" install >/dev/null 2>&1 || fail_msg "I10: re-run install must succeed"
|
||||||
|
n="$(find "$UD" -maxdepth 1 -name 'mosaic-wake.service' | grep -c .)"
|
||||||
|
[ "$n" -eq 1 ] || fail_msg "I10: re-install must not duplicate the search-path entry (found $n)"
|
||||||
|
bash "$WI" validate-systemd-path >/dev/null 2>&1 || fail_msg "I10: re-install must keep the unit resolvable"
|
||||||
|
) && ok
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake install harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
echo "wake install harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
|
|||||||
@@ -49,9 +49,14 @@ WAKE_SYSTEMD_USER_DIR="${WAKE_SYSTEMD_USER_DIR:-$HOME/.config/systemd/user}"
|
|||||||
# Retained snapshot store for the snapshot-guard (iv) — outside any repo.
|
# Retained snapshot store for the snapshot-guard (iv) — outside any repo.
|
||||||
WAKE_SNAPSHOT_DIR="${WAKE_SNAPSHOT_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/mosaic/wake/unit-snapshots}"
|
WAKE_SNAPSHOT_DIR="${WAKE_SNAPSHOT_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/mosaic/wake/unit-snapshots}"
|
||||||
|
|
||||||
# shellcheck source=../_lib/manifest.sh disable=SC1091
|
# systemd user unit NAME this installer deploys + links (overridable for the harness).
|
||||||
. "$SCRIPT_DIR/../_lib/manifest.sh"
|
WAKE_UNIT_NAME="${WAKE_UNIT_NAME:-mosaic-wake.service}"
|
||||||
|
# The shared framework-manifest reader this installer needs for Gate A ownership
|
||||||
|
# validation. Overridable so the red-first harness can point it at a missing path
|
||||||
|
# to prove the fail-loud without disturbing the real tree (#913a).
|
||||||
|
WAKE_MANIFEST_LIB="${WAKE_MANIFEST_LIB:-$SCRIPT_DIR/../_lib/manifest.sh}"
|
||||||
|
|
||||||
|
# ─── output helpers (defined BEFORE the dependency check so it can fail loud) ──
|
||||||
if [[ -t 2 ]]; then
|
if [[ -t 2 ]]; then
|
||||||
_C_G='\033[0;32m' _C_Y='\033[0;33m' _C_R='\033[0;31m' _C_0='\033[0m'
|
_C_G='\033[0;32m' _C_Y='\033[0;33m' _C_R='\033[0;31m' _C_0='\033[0m'
|
||||||
else
|
else
|
||||||
@@ -61,6 +66,28 @@ wi_ok() { printf " ${_C_G}OK${_C_0} %s\n" "$1" >&2; }
|
|||||||
wi_warn() { printf " ${_C_Y}WARN${_C_0} %s\n" "$1" >&2; }
|
wi_warn() { printf " ${_C_Y}WARN${_C_0} %s\n" "$1" >&2; }
|
||||||
wi_fail() { printf " ${_C_R}FAIL${_C_0} %s\n" "$1" >&2; }
|
wi_fail() { printf " ${_C_R}FAIL${_C_0} %s\n" "$1" >&2; }
|
||||||
|
|
||||||
|
# ─── required framework library — fail LOUD if a stale host seed lacks it (#913a) ──
|
||||||
|
# The wake component installer sources the shared framework-manifest reader
|
||||||
|
# (_lib/manifest.sh) for Gate A ownership validation. Host seeds that predate that
|
||||||
|
# helper would otherwise abort here with a bare, obscure
|
||||||
|
# `source: No such file or directory` and no guidance. This dependency is GENUINELY
|
||||||
|
# REQUIRED (Gate A cannot be skipped on an enforcement path), so fail LOUD — naming
|
||||||
|
# the missing file AND the remedy — instead of degrading: a not-yet-updated host
|
||||||
|
# must be told exactly how to become installable.
|
||||||
|
if [[ ! -r "$WAKE_MANIFEST_LIB" ]]; then
|
||||||
|
wi_fail "wake-install: required framework library missing — $WAKE_MANIFEST_LIB"
|
||||||
|
{
|
||||||
|
printf ' %s\n' "This host's framework seed predates the shared manifest reader (_lib/manifest.sh)"
|
||||||
|
printf ' %s\n' "that the wake component installer needs for Gate A ownership validation (#913)."
|
||||||
|
printf ' %s\n' "REMEDY: re-seed the framework first, then retry the component install:"
|
||||||
|
printf ' %s\n' " mosaic update # or: bash <framework>/install.sh"
|
||||||
|
printf ' %s\n' " install.sh --component wake"
|
||||||
|
} >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck source=../_lib/manifest.sh disable=SC1091
|
||||||
|
. "$WAKE_MANIFEST_LIB"
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
# (i) Idempotent component-manifest install + Gate A
|
# (i) Idempotent component-manifest install + Gate A
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -128,10 +155,106 @@ wi_install() {
|
|||||||
done < <(_wi_component_candidates)
|
done < <(_wi_component_candidates)
|
||||||
|
|
||||||
wi_ok "wake component install: $written written, $skipped unchanged, $missing not-shipped (Gate A: all writes framework-owned)."
|
wi_ok "wake component install: $written written, $skipped unchanged, $missing not-shipped (Gate A: all writes framework-owned)."
|
||||||
|
|
||||||
|
# (b #913) A10 canon step: place the deployed unit into the USER SYSTEMD SEARCH
|
||||||
|
# PATH and validate it resolves. wi_install copies the unit under mosaic home
|
||||||
|
# (systemd/user/, framework-owned) — but `systemctl --user` searches
|
||||||
|
# ~/.config/systemd/user/, so without this link the service can never be
|
||||||
|
# enabled/started. Both steps are idempotent; either failing fails the install.
|
||||||
|
wi_link_systemd_unit || return 1
|
||||||
|
wi_validate_systemd_path || return 1
|
||||||
|
|
||||||
# Machine-readable summary for the harness (idempotency assertion keys off it).
|
# Machine-readable summary for the harness (idempotency assertion keys off it).
|
||||||
printf 'wake-install: written=%s skipped=%s missing=%s\n' "$written" "$skipped" "$missing"
|
printf 'wake-install: written=%s skipped=%s missing=%s\n' "$written" "$skipped" "$missing"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# (b #913) systemd user-search-path link + post-install validate
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# ADDITIVE / #869: the link target (~/.config/systemd/user/<unit>) lives OUTSIDE
|
||||||
|
# mosaic home, so it is not a framework-manifest path (the manifest is
|
||||||
|
# mosaic-home-relative). Ownership of the SSOT unit stays `systemd/**` in the single
|
||||||
|
# framework-manifest.txt authority — this step adds NO new owned path and creates NO
|
||||||
|
# second ownership authority. The link points BACK to the mosaic-home SSOT copy, so
|
||||||
|
# a later framework upgrade of the unit propagates with no re-link.
|
||||||
|
|
||||||
|
# wi_link_systemd_unit — idempotently place the deployed wake unit into the user
|
||||||
|
# systemd search path so `systemctl --user` can resolve it. Symlinks to the
|
||||||
|
# mosaic-home SSOT copy installed by wi_install. Idempotent: an already-correct link
|
||||||
|
# (or a byte-identical regular file) is left untouched; a stale entry is replaced.
|
||||||
|
wi_link_systemd_unit() {
|
||||||
|
local unit="$WAKE_UNIT_NAME"
|
||||||
|
local ssot="$WAKE_INSTALL_TARGET/systemd/user/$unit"
|
||||||
|
local link="$WAKE_SYSTEMD_USER_DIR/$unit"
|
||||||
|
if [[ ! -f "$ssot" ]]; then
|
||||||
|
wi_fail "systemd-link — the deployed unit is missing at $ssot; run 'wake-install.sh install' first (fail-closed)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
mkdir -p "$WAKE_SYSTEMD_USER_DIR"
|
||||||
|
# Idempotent no-op if the search-path entry already resolves to the SSOT copy.
|
||||||
|
if [[ -L "$link" ]]; then
|
||||||
|
if [[ "$(readlink "$link")" == "$ssot" ]]; then
|
||||||
|
wi_ok "systemd-link — '$unit' already linked into the search path ($link -> $ssot)."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
elif [[ -f "$link" ]] && cmp -s "$ssot" "$link"; then
|
||||||
|
wi_ok "systemd-link — '$unit' already present in the search path ($link, byte-identical)."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Replace whatever is there (stale link / old copy) with a fresh symlink to SSOT.
|
||||||
|
if ! ln -sfn "$ssot" "$link"; then
|
||||||
|
wi_fail "systemd-link — could not link '$unit' into the user systemd search path ($link). FAIL LOUD (fail-closed)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
wi_ok "systemd-link — '$unit' linked into the user systemd search path ($link -> $ssot)."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# wi_validate_systemd_path — post-install validate that the unit RESOLVES in the
|
||||||
|
# user systemd search path. The installer may run where the user systemd manager is
|
||||||
|
# NOT live (containers, no login session), so the AUTHORITATIVE check is
|
||||||
|
# path + well-formedness: the search-path entry exists, dereferences to a readable
|
||||||
|
# file, and parses as a unit ([Unit]/[Service]/[Install] + an ExecStart). A live
|
||||||
|
# `systemctl --user cat` probe runs ONLY opportunistically behind a guard.
|
||||||
|
wi_validate_systemd_path() {
|
||||||
|
local unit="$WAKE_UNIT_NAME"
|
||||||
|
local link="$WAKE_SYSTEMD_USER_DIR/$unit" resolved
|
||||||
|
if [[ ! -e "$link" ]]; then
|
||||||
|
wi_fail "systemd-validate — '$unit' is NOT in the user systemd search path ($link). systemctl --user cannot resolve it, so the wake service cannot be enabled/started. Run 'wake-install.sh link-systemd-unit' (part of install) (fail-closed)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ -L "$link" ]]; then
|
||||||
|
resolved="$(readlink -f "$link" 2>/dev/null || true)"
|
||||||
|
else
|
||||||
|
resolved="$link"
|
||||||
|
fi
|
||||||
|
if [[ -z "$resolved" || ! -r "$resolved" ]]; then
|
||||||
|
wi_fail "systemd-validate — '$unit' search-path entry ($link) does not dereference to a readable unit file. FAIL LOUD (fail-closed)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# Well-formed check: a wake detector unit must carry the core sections + ExecStart.
|
||||||
|
local -a miss=()
|
||||||
|
grep -q '^\[Unit\]' "$resolved" || miss+=("[Unit]")
|
||||||
|
grep -q '^\[Service\]' "$resolved" || miss+=("[Service]")
|
||||||
|
grep -q '^\[Install\]' "$resolved" || miss+=("[Install]")
|
||||||
|
grep -q '^ExecStart=' "$resolved" || miss+=("ExecStart=")
|
||||||
|
if [[ ${#miss[@]} -ne 0 ]]; then
|
||||||
|
wi_fail "systemd-validate — '$unit' ($resolved) is malformed: missing ${miss[*]}. Refusing to certify an ill-formed unit (fail-closed)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# Opportunistic live probe ONLY when explicitly requested AND a user manager is
|
||||||
|
# reachable — never a hard requirement (the installer often runs without one).
|
||||||
|
if [[ "${WAKE_VERIFY_USE_SYSTEMCTL:-0}" == "1" ]] && command -v systemctl >/dev/null 2>&1; then
|
||||||
|
if systemctl --user cat "$unit" >/dev/null 2>&1; then
|
||||||
|
wi_ok "systemd-validate — 'systemctl --user cat $unit' resolves (live user manager confirmed)."
|
||||||
|
else
|
||||||
|
wi_warn "systemd-validate — file is in the search path + well-formed, but 'systemctl --user cat $unit' did not resolve (no live user manager / not daemon-reloaded). Non-fatal: run 'systemctl --user daemon-reload' in a live session."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
wi_ok "systemd-validate — '$unit' resolves in the user systemd search path ($link -> $resolved, well-formed)."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
# (v) Fail-closed alarm-target + HMAC-key install-validation (G1/G2a)
|
# (v) Fail-closed alarm-target + HMAC-key install-validation (G1/G2a)
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -409,6 +532,8 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|||||||
cmd="${1:-}"; shift || true
|
cmd="${1:-}"; shift || true
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
install) wi_install "$@" ;;
|
install) wi_install "$@" ;;
|
||||||
|
link-systemd-unit) wi_link_systemd_unit "$@" ;;
|
||||||
|
validate-systemd-path) wi_validate_systemd_path "$@" ;;
|
||||||
validate-targets) wi_validate_targets "$@" ;;
|
validate-targets) wi_validate_targets "$@" ;;
|
||||||
validate-hmac-key) wi_validate_hmac_key "$@" ;;
|
validate-hmac-key) wi_validate_hmac_key "$@" ;;
|
||||||
validate-alarm-target) wi_validate_alarm_target "$@" ;;
|
validate-alarm-target) wi_validate_alarm_target "$@" ;;
|
||||||
@@ -422,7 +547,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|||||||
Usage: wake-install.sh <command> [args]
|
Usage: wake-install.sh <command> [args]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
install Idempotent component-manifest install + Gate A.
|
install Idempotent component-manifest install + Gate A,
|
||||||
|
then link the unit into the user systemd search
|
||||||
|
path and validate it resolves (b, #913).
|
||||||
|
link-systemd-unit Link mosaic-wake.service into ~/.config/systemd/user/.
|
||||||
|
validate-systemd-path Validate the unit resolves in the user systemd search path.
|
||||||
validate-targets Fail-closed HMAC-key + alarm-target validate (v).
|
validate-targets Fail-closed HMAC-key + alarm-target validate (v).
|
||||||
validate-hmac-key HMAC key resolves by-name, else fail loud.
|
validate-hmac-key HMAC key resolves by-name, else fail loud.
|
||||||
validate-alarm-target Alarm sink configured + reachable, else fail loud.
|
validate-alarm-target Alarm sink configured + reachable, else fail loud.
|
||||||
|
|||||||
Reference in New Issue
Block a user