veilor-os/overlay/usr/local/bin/veilor-postinstall

179 lines
7 KiB
Text
Raw Normal View History

#!/usr/bin/bash
# veilor-postinstall — first-login TUI on v0.7+ atomic systems.
#
# Runs ONCE on first SDDM login via the user-mode systemd unit
# `veilor-postinstall.service`. Asks the operator for the small set
# of decisions we deliberately defer from install time:
# - keyboard / locale
# - hostname override
# - GPU drivers (NVIDIA layered via rpm-ostree, mesa = no-op)
# - package preset (dev / media / homelab — additive, opt-out)
# - bluetooth opt-in
# - USBGuard policy snapshot
# - veilor-doctor first run
# Writes /var/lib/veilor/postinstall-complete on success and disables
# its own autostart unit. Idempotent: safe to re-run.
#
# Style: gum if present, plain bash read fallback. No decorative ASCII.
set -uo pipefail
export TERM="${TERM:-linux}"
STATE_DIR=/var/lib/veilor
DONE_MARKER="$STATE_DIR/postinstall-complete"
LOG=/var/log/veilor-postinstall.log
have() { command -v "$1" >/dev/null 2>&1; }
GUM=$(have gum && echo gum || echo "")
# Always log + tee to stdout for live progress.
mkdir -p "$STATE_DIR" 2>/dev/null || true
exec > >(tee -a "$LOG") 2>&1
if [[ -e $DONE_MARKER && ${1:-} != "--force" ]]; then
echo "veilor-postinstall already ran (marker: $DONE_MARKER). Pass --force to re-run."
exit 0
fi
# ── Wrappers ────────────────────────────────────────────────────────
choose() {
local header=$1; shift
if [[ -n $GUM ]]; then
gum choose --header "$header" "$@"
else
echo
echo "$header"
local i=1
for opt in "$@"; do printf ' %d) %s\n' "$i" "$opt"; ((i++)); done
local n
read -rp " choice (1-$#): " n
[[ $n -ge 1 && $n -le $# ]] || return 1
eval "echo \${$n}"
fi
}
ask() {
local prompt=$1 default=${2:-}
if [[ -n $GUM ]]; then
gum input --header "$prompt" --value "$default"
else
local v
read -rp "$prompt [$default] " v
echo "${v:-$default}"
fi
}
confirm() {
local prompt=$1
if [[ -n $GUM ]]; then
gum confirm "$prompt" && return 0 || return 1
else
read -rp "$prompt [y/N] " y
[[ ${y,,} == y* ]]
fi
}
say() {
if [[ -n $GUM ]]; then
gum style --foreground 212 --bold "$1"
else
printf '\n=== %s ===\n' "$1"
fi
}
# Need root for several actions; re-exec under sudo if not root.
if [[ $EUID -ne 0 ]]; then
say "veilor-postinstall: sudo required"
exec sudo -E bash "$0" "$@"
fi
say "veilor-postinstall — one-time setup"
echo " This runs once. Each step is skippable. Defaults are sane."
echo
# ── 1. Keyboard layout ──────────────────────────────────────────────
KB=$(choose "Keyboard layout" us gb de fr es ru "skip") || KB=skip
if [[ $KB != skip ]]; then
localectl set-keymap "$KB" 2>/dev/null || true
echo " [OK] keymap = $KB"
fi
# ── 2. Locale ───────────────────────────────────────────────────────
LOC=$(choose "Locale" en_US.UTF-8 en_GB.UTF-8 de_DE.UTF-8 fr_FR.UTF-8 "skip") || LOC=skip
if [[ $LOC != skip ]]; then
localectl set-locale LANG="$LOC" 2>/dev/null || true
echo " [OK] locale = $LOC"
fi
# ── 3. Hostname ─────────────────────────────────────────────────────
HN=$(ask "Hostname" "veilor")
if [[ -n $HN && $HN != $(hostnamectl --static 2>/dev/null) ]]; then
hostnamectl set-hostname "$HN"
echo " [OK] hostname = $HN"
fi
# ── 4. GPU drivers ──────────────────────────────────────────────────
GPU=$(choose "GPU drivers" "Skip (use mesa defaults)" "NVIDIA proprietary (akmod-nvidia)" "Intel/AMD mesa (no-op)") || GPU=skip
case "$GPU" in
*NVIDIA*)
say "Layering NVIDIA driver — this takes a few minutes"
rpm-ostree install --idempotent akmod-nvidia xorg-x11-drv-nvidia-cuda \
&& echo " [OK] NVIDIA driver layered (reboot to use)" \
|| echo " [WARN] NVIDIA layer failed; check rpm-ostree status"
;;
*) echo " (skipped GPU layering)" ;;
esac
# ── 5. Package presets (multi-select) ───────────────────────────────
say "Package presets — pick any combination (skip = none)"
PRESET_DEV="git tmux vim-enhanced htop podman skopeo"
PRESET_MEDIA="vlc obs-studio"
PRESET_HOMELAB="wireguard-tools jq yq tmux"
PICKED=()
confirm "Install dev preset? ($PRESET_DEV)" && PICKED+=($PRESET_DEV) || true
confirm "Install media preset? ($PRESET_MEDIA)" && PICKED+=($PRESET_MEDIA) || true
confirm "Install homelab preset? ($PRESET_HOMELAB)" && PICKED+=($PRESET_HOMELAB) || true
if (( ${#PICKED[@]} > 0 )); then
# de-dupe
UNIQ=$(printf '%s\n' "${PICKED[@]}" | sort -u | tr '\n' ' ')
say "Layering: $UNIQ"
rpm-ostree install --idempotent $UNIQ \
&& echo " [OK] preset packages layered (reboot to use)" \
|| echo " [WARN] preset layer failed; check rpm-ostree status"
fi
# ── 6. Bluetooth ────────────────────────────────────────────────────
if confirm "Enable Bluetooth?"; then
systemctl enable --now bluetooth.service 2>/dev/null || true
echo " [OK] bluetooth enabled"
else
echo " (skipped bluetooth)"
fi
# ── 7. USBGuard snapshot ────────────────────────────────────────────
say "USBGuard policy snapshot"
echo " Plug in EVERY USB device you trust right now (keyboard,"
echo " mouse, dock, yubikey, etc.) before continuing."
if confirm "Snapshot current USB devices into the allowlist?"; then
usbguard generate-policy > /etc/usbguard/rules.conf \
&& echo " [OK] policy written to /etc/usbguard/rules.conf" \
|| echo " [WARN] generate-policy failed"
systemctl restart usbguard 2>/dev/null || true
fi
# ── 8. veilor-doctor ────────────────────────────────────────────────
if confirm "Run veilor-doctor now?"; then
veilor-doctor || true
fi
# ── Done ────────────────────────────────────────────────────────────
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$DONE_MARKER"
say "veilor-postinstall complete"
echo " Marker written: $DONE_MARKER"
echo " Disabling autostart unit so this never runs again."
systemctl --user --global disable veilor-postinstall.service 2>/dev/null || true
systemctl disable veilor-postinstall.service 2>/dev/null || true
echo
echo " If you layered any packages or drivers, reboot to activate."