Files
command-center/scripts/install.sh
T
Frank 06f45e83c1 refactor(packages): one manifest parser, so a name means one thing to both tools
install.sh and pkg-inventory.sh carried byte-identical copies of read_manifest.
That is the duplicated-code half of the problem; the worse half is untested —
neither script had any test for the grammar that decides what gets installed.

- scripts/lib/manifest.sh is the parser; both scripts source it
- tests/manifest-parser.bats writes the contract first (comments, CRLF, first
  field only, unreadable file fails loudly instead of yielding "nothing desired")
- an unreadable manifest now fails inside the helper rather than depending on
  whichever `set` options the caller happens to run under
- install.sh check mode verified end to end against a stubbed pacman: a CRLF
  manifest with trailing comments resolves to bare names, installed names drop

Refs #60
2026-10-21 00:54:31 -07:00

119 lines
4.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# install.sh — idempotent fleet package installer for the ~/.omp repo deployment.
#
# Model: packages/*.pkgs declare DESIRED state per host. This script computes
# (desired − installed) and installs only that delta via pacman/yay. It NEVER
# removes anything. Re-running after a fresh install, or casually months later,
# is safe by construction.
#
# Modes:
# install.sh check (default): print the plan for this host, write nothing
# install.sh --apply install the delta + run host-gated enablement actions
#
# Host selection: --host <name> overrides $(hostname); an unknown host gets the
# common manifest only — wrong-host runs are a no-op, not a hazard.
#
# Sudo: set FRANK_SUDO_PW in the environment for non-interactive runs;
# otherwise sudo prompts normally. The value is never stored in this repo.
set -euo pipefail
HERE="$(dirname "$(realpath "$0")")"
REPO="$(git -C "$HERE" rev-parse --show-toplevel 2>/dev/null || true)"
REPO="${INSTALL_REPO:-$REPO}"
[ -n "$REPO" ] && [ -d "$REPO/packages" ] || { echo "install.sh: no packages/ under ${REPO:-<repo>}" >&2; exit 1; }
MODE="check"
HOST="$(hostname)"
while [[ $# -gt 0 ]]; do
case "$1" in
--apply) MODE="apply"; shift ;;
--host) HOST="$2"; shift 2 ;;
*) echo "usage: install.sh [--apply] [--host <name>]" >&2; exit 2 ;;
esac
done
SUDO() {
if [[ -n "${FRANK_SUDO_PW:-}" ]]; then printf '%s' "$FRANK_SUDO_PW" | sudo -S -p '' "$@"
else sudo "$@"; fi
}
source "$HERE/lib/manifest.sh" # the same parser pkg-inventory.sh reads with
# ---- desired-minus-installed delta ------------------------------------------
PKGS=()
for m in common.pkgs "$HOST.pkgs"; do
[[ -f "$REPO/packages/$m" ]] || continue
mapfile -t _pkgs < <(read_manifest "$REPO/packages/$m")
PKGS+=("${_pkgs[@]}")
done
# AUR phase only when the helper exists or can be bootstrapped from a repo pkg
AUR_PKGS=()
if [[ -f "$REPO/packages/aur.pkgs" ]]; then
mapfile -t AUR_PKGS < <(read_manifest "$REPO/packages/aur.pkgs")
fi
# Membership via direct DB query per package — deterministic, no set-diff plumbing.
DELTAC=()
for p in "${PKGS[@]}"; do
pacman -Qq --quiet -- "$p" >/dev/null || DELTAC+=("$p")
done
echo "install.sh [$MODE] host=$HOST"
if [[ ${#DELTAC[@]} -eq 0 ]]; then
echo "repo pkgs: delta empty — nothing to install"
else
echo "repo pkgs to install: ${DELTAC[*]}"
fi
for p in "${AUR_PKGS[@]}"; do
pacman -Qq --quiet -- "$p" >/dev/null || echo "aur pkg pending: $p"
done
[[ "$MODE" == "check" ]] && exit 0
# ---- apply ------------------------------------------------------------------
if [[ ${#DELTAC[@]} -gt 0 ]]; then
SUDO pacman -S --needed --noconfirm -- "${DELTAC[@]}"
fi
# Helper bootstrap: yay itself ships in the cachyos repo (no chicken-and-egg).
if [[ ${#AUR_PKGS[@]} -gt 0 ]] && ! pacman -Qq yay >/dev/null 2>&1; then
SUDO pacman -S --needed --noconfirm -- yay
fi
# AUR builds must NOT run as root (makepkg refuses); yay escalates only for
# the final package install via its internal sudo — prime the timestamp cache.
if [[ ${#AUR_PKGS[@]} -gt 0 ]]; then
if [[ -n "${FRANK_SUDO_PW:-}" ]]; then printf '%s' "$FRANK_SUDO_PW" | sudo -S -p '' true; fi
for p in "${AUR_PKGS[@]}"; do
printf '%s\n' "${INSTALLED[@]}" | grep -qx -- "$p" || {
YAYFLAGS="--answerclean None --answerdiff None" yay -S --needed --noconfirm --aur -- "$p"
}
done
fi
# ---- host-gated enablement ---------------------------------------------------
case "$HOST" in
dankware)
# nvidia sleep fix: package hooks already ship /usr/lib/modprobe.d/nvidia-sleep.conf
# (UseKernelSuspendNotifiers=1 + TemporaryFilePath); enable the units idempotently.
SUDO systemctl enable --now nvidia-suspend nvidia-resume 2>/dev/null || true
;;
blankpad)
# Cezanne iGPU: amdgpu is in-kernel, amd-ucode carried by kernel pkg; no module options.
:
;;
esac
# sshd + firewall — same posture on both hosts (decision 2026-09: ssh open in default zone)
if command -v sshd >/dev/null 2>&1 || pacman -Qq openssh >/dev/null 2>&1; then
SUDO systemctl enable --now sshd
fi
if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
if ! firewall-cmd --query-service ssh >/dev/null 2>&1; then
SUDO firewall-cmd --permanent --zone=public --add-service=ssh
SUDO firewall-cmd --reload
fi
fi
echo "install.sh [$MODE] host=$HOST done"