Files
ubuntu-post-install/setup.sh
T
Claude d7b9f935c2 Add modular setup framework (lib + services + dispatcher) and glow
Introduce the modular post-install structure chosen for reconciling 'one
source of truth' with 'run just the service I want':

- lib/common.sh: shared helpers (logging, prompts, ownership, Caddy wiring) and
  a service registry. Single implementation of each helper.
- setup.sh: dispatcher — interactive menu, run-one (./setup.sh <name>), --list,
  --dry-run, --unattended. Sources lib + services/*.sh (self-registering).
- services/base.sh: essential CLI packages incl. glow (Charm apt repo).
- services/homeassistant.sh: first migrated service (bridge/host networking,
  trusted_proxies, Caddy integration).
- MODULAR.md: architecture, how to add a module, migration status.
- Groups: base/homelab/gaming/backup. Gaming group makes this a base for
  homelab OR gaming boxes.

Also add glow as a default app to the live -crowdsec scripts' essential
packages so it's installed today regardless of entry point.

Verified: bash -n on all new files; ./setup.sh --list groups services;
dry-run run-one routes correctly.

https://claude.ai/code/session_017eA2qqq9jfF2tNtpUYL8vK
2026-06-03 12:57:12 +00:00

129 lines
4.1 KiB
Bash
Executable File

#!/bin/bash
# setup.sh — modular post-install dispatcher.
#
# One source of truth, two ways to run it:
# sudo ./setup.sh interactive menu (pick any services)
# sudo ./setup.sh <service> ... install one or more services directly
# ./setup.sh --list list available services (grouped)
#
# Flags:
# --dry-run preview actions without making changes
# --unattended use defaults, no prompts
#
# Every service lives in services/<name>.sh, registers itself, and defines
# install_<name>. Adding a service = adding one file. Updating a service =
# editing one file. Nothing is duplicated or generated.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ── Parse global flags, collect service names ────────────────────────────────
DRY_RUN=false
UNATTENDED=false
DO_LIST=false
REQUESTED=()
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--unattended) UNATTENDED=true ;;
--list|-l) DO_LIST=true ;;
-h|--help)
sed -n '2,18p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
exit 0 ;;
-*) echo "Unknown flag: $arg" >&2; exit 1 ;;
*) REQUESTED+=("$arg") ;;
esac
done
export DRY_RUN UNATTENDED
# ── Load helpers + all service modules (they self-register) ──────────────────
# shellcheck source=lib/common.sh
source "$HERE/lib/common.sh"
shopt -s nullglob
for _mod in "$HERE"/services/*.sh; do
# shellcheck source=/dev/null
source "$_mod"
done
shopt -u nullglob
# Ordered list of unique groups, in first-seen order.
groups_in_order() {
local seen=" " g
for name in "${SERVICE_ORDER[@]}"; do
g="${SERVICE_GROUP[$name]}"
case "$seen" in *" $g "*) : ;; *) echo "$g"; seen="$seen$g " ;; esac
done
}
list_services() {
local g name
while IFS= read -r g; do
echo ""
echo "── ${g^^} ──"
for name in "${SERVICE_ORDER[@]}"; do
[ "${SERVICE_GROUP[$name]}" = "$g" ] || continue
printf " %-16s %s\n" "$name" "${SERVICE_DESC[$name]}"
done
done < <(groups_in_order)
echo ""
}
run_service() {
local name="$1"
if [ -z "${SERVICE_GROUP[$name]:-}" ]; then
log_error "Unknown service: $name (try: $0 --list)"
return 1
fi
if ! declare -F "install_${name}" >/dev/null; then
log_error "Service '$name' has no install_${name} function."
return 1
fi
log_info "=== ${name} (${SERVICE_DESC[$name]}) ==="
"install_${name}"
}
# ── --list ───────────────────────────────────────────────────────────────────
if [ "$DO_LIST" = true ]; then
list_services
exit 0
fi
# ── Direct service install: ./setup.sh minecraft homeassistant ─────────────
if [ "${#REQUESTED[@]}" -gt 0 ]; then
require_root
rc=0
for name in "${REQUESTED[@]}"; do
run_service "$name" || rc=1
done
exit "$rc"
fi
# ── Interactive menu ─────────────────────────────────────────────────────────
require_root
SELECTED=()
if command -v whiptail >/dev/null 2>&1; then
_items=()
for name in "${SERVICE_ORDER[@]}"; do
_items+=("$name" "${SERVICE_DESC[$name]}" "OFF")
done
_choice=$(whiptail --title "Ubuntu Post-Install — Services" \
--checklist "Select services to install (space to toggle):" 25 78 16 \
"${_items[@]}" 3>&1 1>&2 2>&3) || { echo "Cancelled."; exit 0; }
# whiptail returns space-separated, quoted names
eval "SELECTED=($_choice)"
else
echo "Available services:"
list_services
read -rp "Enter service names to install (space-separated): " -a SELECTED
fi
[ "${#SELECTED[@]}" -eq 0 ] && { echo "Nothing selected."; exit 0; }
rc=0
for name in "${SELECTED[@]}"; do
run_service "$name" || rc=1
done
exit "$rc"