6d50f3a804
Wrap the three install scripts in a shared inline style block (TTY/UTF-8/ NO_COLOR-aware) so the one-liner install looks and feels intentional: banner on start, timed step lines, braille spinner over silent apt/ systemctl calls with failure log dumps, single-line curl progress bars with size-prefixed headers, and a summary box at the end with live-image version + service state + next steps. install.sh defers banner/summary to proxmox-install.sh when VETTING_INSTALL_WRAPPED is set so the two scripts compose without duplication. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
589 lines
24 KiB
Bash
Executable File
589 lines
24 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install.sh — one-shot installer for the vetting orchestrator on a
|
|
# Proxmox LXC (or any Debian/Ubuntu host).
|
|
#
|
|
# What it does:
|
|
# 1. apt-installs runtime dependencies (dnsmasq, iperf3, ca-certs).
|
|
# 2. Creates the `vetting` system user with /var/lib/vetting homedir.
|
|
# 3. Copies the pre-built `vetting` binary into /usr/local/bin.
|
|
# 4. Drops the systemd unit and example config into /etc/vetting.
|
|
# 5. Reminds the operator to edit the config before enabling
|
|
# the service — we don't auto-start because the default bind
|
|
# is loopback-only and needs at least a tweak to be useful.
|
|
#
|
|
# What it deliberately does NOT do:
|
|
# - Build the orchestrator (this script assumes you ran
|
|
# `make orchestrator-linux` beforehand and that bin/vetting-linux-amd64
|
|
# exists alongside this script, or pass --binary to locate it).
|
|
# - Fetch TFTP iPXE payloads (that's pxe-setup.sh's job — it also
|
|
# writes the pxe: block of vetting.yaml with first-time args).
|
|
#
|
|
# Live-image staging has two modes:
|
|
# - Release bundle (new format): the bundle carries only a
|
|
# live-image/VERSION pointer. We compare it to ${LIVE_DIR}/VERSION
|
|
# and, on mismatch, fetch vmlinuz+initrd.img from the Gitea
|
|
# generic registry at live-image/<VERSION>/. Matched versions
|
|
# skip the fetch (set FORCE_LIVE_IMAGE=1 to override).
|
|
# - Repo checkout / legacy bundle: if vmlinuz+initrd.img are present
|
|
# next to this script (${SCRIPT_DIR}/live-image/) or under
|
|
# ${REPO_ROOT}/live-image/build/, they're copied straight in.
|
|
#
|
|
# Usage:
|
|
# sudo ./install.sh [--binary PATH] [--config-dir /etc/vetting]
|
|
#
|
|
set -euo pipefail
|
|
|
|
# ---- style helpers -----------------------------------------------------
|
|
# Identical block across proxmox-install.sh, install.sh, pxe-setup.sh.
|
|
# Inlined (not sourced) so the curl|bash entrypoint renders without a
|
|
# prior fetch. Respects NO_COLOR, non-TTY, and non-UTF-8 locales.
|
|
_color=0; _unicode=0
|
|
if [[ -t 2 && -z "${NO_COLOR:-}" && "${TERM:-dumb}" != "dumb" ]]; then
|
|
_color=1
|
|
fi
|
|
case "${LC_ALL:-}${LANG:-}" in
|
|
*UTF-8*|*utf8*|*UTF8*) _unicode=1 ;;
|
|
esac
|
|
if (( _color )); then
|
|
_B=$'\033[1m'; _D=$'\033[2m'; _R=$'\033[0m'
|
|
_C=$'\033[1;36m'; _G=$'\033[32m'; _Y=$'\033[33m'; _E=$'\033[31m'
|
|
else
|
|
_B=""; _D=""; _R=""; _C=""; _G=""; _Y=""; _E=""
|
|
fi
|
|
if (( _unicode )); then
|
|
_S_STEP="▸"; _S_OK="✓"; _S_WARN="⚠"; _S_FAIL="✗"; _S_INFO="·"
|
|
_B_TL="╭"; _B_TR="╮"; _B_BL="╰"; _B_BR="╯"; _B_H="─"; _B_V="│"
|
|
else
|
|
_S_STEP="-->"; _S_OK="[ok]"; _S_WARN="[!]"; _S_FAIL="[x]"; _S_INFO="*"
|
|
_B_TL="+"; _B_TR="+"; _B_BL="+"; _B_BR="+"; _B_H="-"; _B_V="|"
|
|
fi
|
|
_start_epoch="$(date +%s)"; _step_epoch=""; _quiet_log=""; _spin_pid=""
|
|
_fmt_dur() {
|
|
local s=$1
|
|
if (( s < 60 )); then printf '%ds' "$s"
|
|
elif (( s < 3600 )); then printf '%dm %ds' "$((s/60))" "$((s%60))"
|
|
else printf '%dh %dm' "$((s/3600))" "$(((s%3600)/60))"
|
|
fi
|
|
}
|
|
banner() {
|
|
local inner=" $1 " w line="" i=0
|
|
w=${#inner}
|
|
while (( i < w )); do line+="${_B_H}"; i=$((i+1)); done
|
|
printf '\n %s%s%s%s%s\n' "${_C}" "${_B_TL}" "${line}" "${_B_TR}" "${_R}" >&2
|
|
printf ' %s%s%s%s%s%s%s%s%s\n' "${_C}" "${_B_V}" "${_R}" "${_B}" "${inner}" "${_R}" "${_C}" "${_B_V}" "${_R}" >&2
|
|
printf ' %s%s%s%s%s\n\n' "${_C}" "${_B_BL}" "${line}" "${_B_BR}" "${_R}" >&2
|
|
}
|
|
step() {
|
|
_step_epoch="$(date +%s)"
|
|
printf '%s%s%s %s%s%s\n' "${_C}" "${_S_STEP}" "${_R}" "${_B}" "$1" "${_R}" >&2
|
|
}
|
|
ok() {
|
|
local tail=""
|
|
if [[ -n "${_step_epoch}" ]]; then
|
|
tail=" ${_D}($(_fmt_dur $(( $(date +%s) - _step_epoch ))))${_R}"
|
|
fi
|
|
printf ' %s%s%s %s%s\n' "${_G}" "${_S_OK}" "${_R}" "$1" "${tail}" >&2
|
|
_step_epoch=""
|
|
}
|
|
info() { printf ' %s%s %s%s\n' "${_D}" "${_S_INFO}" "$1" "${_R}" >&2; }
|
|
warn() { printf ' %s%s %s%s\n' "${_Y}" "${_S_WARN}" "$1" "${_R}" >&2; }
|
|
die() {
|
|
printf '\n%s%s %s%s\n' "${_E}" "${_S_FAIL}" "$1" "${_R}" >&2
|
|
if [[ -n "${_quiet_log:-}" && -s "${_quiet_log}" ]]; then
|
|
printf ' %s── last 40 lines of output ──%s\n' "${_D}" "${_R}" >&2
|
|
tail -n 40 "${_quiet_log}" | sed 's/^/ /' >&2
|
|
printf ' %sfull log: %s%s\n' "${_D}" "${_quiet_log}" "${_R}" >&2
|
|
fi
|
|
exit 1
|
|
}
|
|
_SPIN=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
|
|
_start_spin() {
|
|
(( _color && _unicode )) || return 0
|
|
local label="$1"
|
|
(
|
|
local i=0
|
|
while :; do
|
|
printf '\r %s%s%s %s ' "${_C}" "${_SPIN[i]}" "${_R}" "${label}" >&2
|
|
i=$(( (i+1) % ${#_SPIN[@]} ))
|
|
sleep 0.1
|
|
done
|
|
) &
|
|
_spin_pid=$!
|
|
}
|
|
_stop_spin() {
|
|
[[ -n "${_spin_pid}" ]] || return 0
|
|
kill "${_spin_pid}" 2>/dev/null || true
|
|
wait "${_spin_pid}" 2>/dev/null || true
|
|
_spin_pid=""
|
|
printf '\r\033[2K' >&2
|
|
}
|
|
# run_quiet "<label>" -- <cmd ...>
|
|
run_quiet() {
|
|
local label="$1"; shift
|
|
[[ "${1:-}" == "--" ]] && shift
|
|
local log start rc=0
|
|
log="$(mktemp -t vetting-install.XXXXXX)"
|
|
_quiet_log="${log}"
|
|
start="$(date +%s)"
|
|
_start_spin "${label}"
|
|
"$@" >"${log}" 2>&1 || rc=$?
|
|
_stop_spin
|
|
local dt=$(( $(date +%s) - start ))
|
|
if (( rc == 0 )); then
|
|
printf ' %s%s%s %s %s(%s)%s\n' "${_G}" "${_S_OK}" "${_R}" "${label}" "${_D}" "$(_fmt_dur "$dt")" "${_R}" >&2
|
|
rm -f "${log}"; _quiet_log=""
|
|
return 0
|
|
fi
|
|
printf ' %s%s %s failed (exit %d)%s\n' "${_E}" "${_S_FAIL}" "${label}" "${rc}" "${_R}" >&2
|
|
printf ' %s── last 40 lines of output ──%s\n' "${_D}" "${_R}" >&2
|
|
tail -n 40 "${log}" | sed 's/^/ /' >&2
|
|
printf ' %sfull log: %s%s\n' "${_D}" "${log}" "${_R}" >&2
|
|
exit "${rc}"
|
|
}
|
|
# curl_pretty --label LABEL --url URL -- [extra curl args, including -o PATH]
|
|
curl_pretty() {
|
|
local label="download" url=""
|
|
local args=()
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--label) label="$2"; shift 2 ;;
|
|
--url) url="$2"; args+=("$2"); shift 2 ;;
|
|
--) shift; args+=("$@"); break ;;
|
|
*) args+=("$1"); shift ;;
|
|
esac
|
|
done
|
|
local size_str=""
|
|
if [[ -n "${url}" ]]; then
|
|
local size_bytes
|
|
size_bytes="$(curl -fsSLI --max-time 5 "${url}" 2>/dev/null \
|
|
| awk 'tolower($1) ~ /^content-length:/ {print $2}' \
|
|
| tr -d '\r' | tail -n1)"
|
|
if [[ "${size_bytes}" =~ ^[0-9]+$ ]]; then
|
|
local human
|
|
human="$(numfmt --to=iec --suffix=B --format='%.1f' "${size_bytes}" 2>/dev/null || echo "${size_bytes}B")"
|
|
size_str=" ${_D}(${human})${_R}"
|
|
fi
|
|
fi
|
|
printf '%s%s%s %sdownloading %s%s%s\n' "${_C}" "${_S_STEP}" "${_R}" "${_B}" "${label}" "${_R}" "${size_str}" >&2
|
|
local start rc=0
|
|
start="$(date +%s)"
|
|
if [[ -t 2 ]]; then
|
|
curl -fL --progress-bar "${args[@]}" || rc=$?
|
|
else
|
|
curl -fsSL "${args[@]}" || rc=$?
|
|
fi
|
|
local dt=$(( $(date +%s) - start ))
|
|
if (( rc == 0 )); then
|
|
printf ' %s%s%s %s ready %s(%s)%s\n' "${_G}" "${_S_OK}" "${_R}" "${label}" "${_D}" "$(_fmt_dur "$dt")" "${_R}" >&2
|
|
return 0
|
|
fi
|
|
printf ' %s%s %s download failed (exit %d)%s\n' "${_E}" "${_S_FAIL}" "${label}" "${rc}" "${_R}" >&2
|
|
exit "${rc}"
|
|
}
|
|
rule_open() {
|
|
local title="$1"
|
|
local dashes=$(( 46 - ${#title} - 4 ))
|
|
local tail="" i=0
|
|
(( dashes < 2 )) && dashes=2
|
|
while (( i < dashes )); do tail+="${_B_H}"; i=$((i+1)); done
|
|
printf '\n%s%s%s %s %s%s\n' "${_C}" "${_B_TL}" "${_B_H}" "${title}" "${tail}" "${_R}" >&2
|
|
}
|
|
rule_close() {
|
|
local line="" i=0
|
|
while (( i < 46 )); do line+="${_B_H}"; i=$((i+1)); done
|
|
printf '%s%s%s%s\n' "${_C}" "${_B_BL}" "${line:1}" "${_R}" >&2
|
|
}
|
|
total_elapsed() {
|
|
local dt=$(( $(date +%s) - _start_epoch ))
|
|
printf '%sinstalled in %s%s\n\n' "${_D}" "$(_fmt_dur "$dt")" "${_R}" >&2
|
|
}
|
|
# ---- end style helpers -------------------------------------------------
|
|
|
|
BINARY=""
|
|
AGENT_BINARY=""
|
|
CONFIG_DIR="/etc/vetting"
|
|
STATE_DIR="/var/lib/vetting"
|
|
LOG_DIR="/var/log/vetting"
|
|
ASSET_DIR="/var/lib/vetting/assets"
|
|
LIVE_DIR="/var/lib/vetting/live"
|
|
LIVE_IMAGE_SRC=""
|
|
SERVICE_USER="vetting"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $0 [--binary PATH] [--agent-binary PATH] [--config-dir DIR]
|
|
|
|
--binary PATH Path to a pre-built vetting binary (default:
|
|
auto-detect ../bin/vetting-linux-amd64 relative to
|
|
this script).
|
|
--agent-binary PATH Path to a pre-built vetting-agent linux-amd64 binary
|
|
served at /assets/vetting-agent-linux-amd64 for the
|
|
quick-register one-liner (default: auto-detect).
|
|
--config-dir DIR Where to install vetting.yaml + systemd unit drop
|
|
(default: /etc/vetting).
|
|
--live-dir DIR Where to stage vmlinuz + initrd.img for PXE boots
|
|
(default: /var/lib/vetting/live). Must match
|
|
pxe.live_dir in vetting.yaml.
|
|
--live-image-src DIR Directory containing vmlinuz + initrd.img to stage
|
|
into --live-dir. Default: auto-detect the bundle's
|
|
live-image/ subdir or the repo-tree build output.
|
|
-h, --help Print this message.
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--binary) BINARY="$2"; shift 2 ;;
|
|
--agent-binary) AGENT_BINARY="$2"; shift 2 ;;
|
|
--config-dir) CONFIG_DIR="$2"; shift 2 ;;
|
|
--live-dir) LIVE_DIR="$2"; shift 2 ;;
|
|
--live-image-src) LIVE_IMAGE_SRC="$2"; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "unknown arg: $1" >&2; usage; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
die "install.sh must be run as root (try: sudo $0)"
|
|
fi
|
|
|
|
# Standalone invocation gets its own banner; wrapped runs share the one
|
|
# from proxmox-install.sh. Tracked in VETTING_INSTALL_WRAPPED so we don't
|
|
# render the banner or summary twice.
|
|
if [[ -z "${VETTING_INSTALL_WRAPPED:-}" ]]; then
|
|
banner "TheWrightServer · Vetting"
|
|
fi
|
|
|
|
# Snapshot service state so the summary can pick between fresh-install
|
|
# and upgrade-path wording even after we restart the service below.
|
|
_prev_svc_enabled=0
|
|
if systemctl is-enabled --quiet vetting.service 2>/dev/null; then
|
|
_prev_svc_enabled=1
|
|
fi
|
|
|
|
# heal_pxe_config: make sure /etc/vetting/vetting.yaml's pxe.interface
|
|
# and pxe.subnet reference things that actually exist on this host. Stale
|
|
# values (common after an LXC rebuild renames the NIC, or after pxe-setup
|
|
# was pointed at a NIC that later got removed) block vetting.service
|
|
# startup with "pxe.interface X not found on host".
|
|
#
|
|
# Only runs when pxe.enabled is true — a disabled pxe block doesn't gate
|
|
# startup. Only rewrites fields that are currently invalid; a good
|
|
# interface/subnet pair is preserved exactly as the operator had it.
|
|
heal_pxe_config() {
|
|
local config="$1"
|
|
[[ -f "${config}" ]] || return 0
|
|
|
|
# Minimal one-key reader for the pxe: block. Mirrors pxe-setup.sh's
|
|
# extract_yaml_value so the two scripts stay independent.
|
|
_pxe_val() {
|
|
awk -v key="$1" '
|
|
/^pxe:/ { in_pxe=1; next }
|
|
in_pxe && /^[A-Za-z_][A-Za-z0-9_]*:/ { in_pxe=0 }
|
|
in_pxe {
|
|
re = "^[[:space:]]+" key ":[[:space:]]*"
|
|
if ($0 ~ re) {
|
|
line = $0
|
|
sub(re, "", line)
|
|
if (match(line, /"[^"]*"/)) {
|
|
print substr(line, RSTART+1, RLENGTH-2); exit
|
|
}
|
|
sub(/[[:space:]]*#.*$/, "", line)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
|
|
print line; exit
|
|
}
|
|
}
|
|
' "${config}"
|
|
}
|
|
|
|
local enabled cur_iface cur_subnet
|
|
enabled="$(_pxe_val enabled)"
|
|
cur_iface="$(_pxe_val interface)"
|
|
cur_subnet="$(_pxe_val subnet)"
|
|
|
|
[[ "${enabled}" == "true" ]] || return 0
|
|
|
|
local iface_ok=0 subnet_ok=0
|
|
if [[ -n "${cur_iface}" ]] && ip link show "${cur_iface}" >/dev/null 2>&1; then
|
|
iface_ok=1
|
|
fi
|
|
if [[ "${cur_subnet}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then
|
|
subnet_ok=1
|
|
fi
|
|
(( iface_ok && subnet_ok )) && return 0
|
|
|
|
local detected_iface detected_subnet
|
|
detected_iface="$(ip -4 -o route show default 2>/dev/null | awk '{print $5; exit}')"
|
|
if [[ -n "${detected_iface}" ]]; then
|
|
detected_subnet="$(ip -4 -o route show dev "${detected_iface}" proto kernel scope link 2>/dev/null | awk '{print $1; exit}')"
|
|
fi
|
|
|
|
if [[ -z "${detected_iface}" || -z "${detected_subnet}" ]]; then
|
|
warn "pxe enabled but pxe.interface=${cur_iface:-<empty>} / pxe.subnet=${cur_subnet:-<empty>} is stale, and no default-route NIC was found to auto-detect from. Edit ${config} manually before starting."
|
|
return 0
|
|
fi
|
|
|
|
local iface_to_write="${cur_iface}" subnet_to_write="${cur_subnet}"
|
|
if (( iface_ok == 0 )); then
|
|
info "pxe.interface \"${cur_iface}\" is not present on this host; auto-patching to \"${detected_iface}\""
|
|
iface_to_write="${detected_iface}"
|
|
fi
|
|
if (( subnet_ok == 0 )); then
|
|
info "pxe.subnet \"${cur_subnet:-<empty>}\" is missing/invalid; auto-patching to \"${detected_subnet}\""
|
|
subnet_to_write="${detected_subnet}"
|
|
fi
|
|
|
|
local tmp
|
|
tmp="$(mktemp)"
|
|
IFACE="${iface_to_write}" SUBNET="${subnet_to_write}" awk '
|
|
/^pxe:/ { in_pxe=1; print; next }
|
|
in_pxe && /^[A-Za-z_][A-Za-z0-9_]*:/ { in_pxe=0 }
|
|
in_pxe && /^[[:space:]]+interface:/ { print " interface: \"" ENVIRON["IFACE"] "\""; next }
|
|
in_pxe && /^[[:space:]]+subnet:/ { print " subnet: \"" ENVIRON["SUBNET"] "\""; next }
|
|
{ print }
|
|
' "${config}" > "${tmp}"
|
|
|
|
chown --reference="${config}" "${tmp}"
|
|
chmod --reference="${config}" "${tmp}"
|
|
mv "${tmp}" "${config}"
|
|
}
|
|
|
|
# refresh_live_image: pull vmlinuz+initrd.img from the Gitea generic
|
|
# package registry when the bundle's live-image/VERSION pointer differs
|
|
# from ${LIVE_DIR}/VERSION. Skips the fetch when versions match unless
|
|
# FORCE_LIVE_IMAGE=1 (useful when on-disk files got corrupted). Set by
|
|
# proxmox-install.sh; on a direct `install.sh` invocation the caller
|
|
# must export REGISTRY_URL (and optionally PACKAGE_OWNER).
|
|
refresh_live_image() {
|
|
local pointer="${SCRIPT_DIR}/live-image/VERSION"
|
|
local bundle_ver
|
|
bundle_ver="$(tr -d '[:space:]' < "${pointer}" 2>/dev/null || true)"
|
|
if [[ -z "${bundle_ver}" ]]; then
|
|
warn "bundle's ${pointer} is empty; skipping live-image fetch"
|
|
return 0
|
|
fi
|
|
|
|
local installed_ver=""
|
|
if [[ -f "${LIVE_DIR}/VERSION" ]]; then
|
|
installed_ver="$(tr -d '[:space:]' < "${LIVE_DIR}/VERSION")"
|
|
fi
|
|
|
|
if [[ "${bundle_ver}" == "${installed_ver}" && "${FORCE_LIVE_IMAGE:-0}" != "1" ]]; then
|
|
info "live-image already at ${bundle_ver}; skipping fetch (FORCE_LIVE_IMAGE=1 to redownload)"
|
|
return 0
|
|
fi
|
|
|
|
if [[ -z "${REGISTRY_URL:-}" ]]; then
|
|
warn "REGISTRY_URL is not set; cannot fetch live-image ${bundle_ver}. Re-run via proxmox-install.sh or export REGISTRY_URL."
|
|
return 0
|
|
fi
|
|
local owner="${PACKAGE_OWNER:-josh}"
|
|
local base="${REGISTRY_URL%/}/api/packages/${owner}/generic/live-image/${bundle_ver}"
|
|
|
|
step "fetching live-image ${bundle_ver} (was '${installed_ver:-none}')"
|
|
local tmp
|
|
tmp="$(mktemp -d)"
|
|
# shellcheck disable=SC2064
|
|
trap "rm -rf '${tmp}'" RETURN
|
|
|
|
curl_pretty --label "kernel (vmlinuz)" --url "${base}/vmlinuz" -- -o "${tmp}/vmlinuz"
|
|
curl_pretty --label "initrd (initrd.img)" --url "${base}/initrd.img" -- -o "${tmp}/initrd.img"
|
|
|
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LIVE_DIR}"
|
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
|
"${tmp}/vmlinuz" "${LIVE_DIR}/vmlinuz"
|
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
|
"${tmp}/initrd.img" "${LIVE_DIR}/initrd.img"
|
|
printf '%s\n' "${bundle_ver}" > "${LIVE_DIR}/VERSION"
|
|
chown "${SERVICE_USER}:${SERVICE_USER}" "${LIVE_DIR}/VERSION"
|
|
chmod 0644 "${LIVE_DIR}/VERSION"
|
|
}
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
|
|
if [[ -z "${BINARY}" ]]; then
|
|
for cand in \
|
|
"${REPO_ROOT}/bin/vetting-linux-amd64" \
|
|
"${REPO_ROOT}/bin/vetting" \
|
|
"${SCRIPT_DIR}/vetting"; do
|
|
if [[ -x "${cand}" ]]; then BINARY="${cand}"; break; fi
|
|
done
|
|
fi
|
|
if [[ -z "${BINARY}" || ! -x "${BINARY}" ]]; then
|
|
die "could not find a vetting binary to install; pass --binary PATH or run 'make orchestrator-linux' first"
|
|
fi
|
|
|
|
if [[ -z "${AGENT_BINARY}" ]]; then
|
|
for cand in \
|
|
"${REPO_ROOT}/bin/vetting-agent.linux-amd64" \
|
|
"${REPO_ROOT}/bin/vetting-agent-linux-amd64" \
|
|
"${SCRIPT_DIR}/vetting-agent-linux-amd64"; do
|
|
if [[ -x "${cand}" ]]; then AGENT_BINARY="${cand}"; break; fi
|
|
done
|
|
fi
|
|
if [[ -z "${AGENT_BINARY}" || ! -x "${AGENT_BINARY}" ]]; then
|
|
die "could not find a vetting-agent binary; pass --agent-binary PATH or run 'make agent-linux' first"
|
|
fi
|
|
|
|
step "installing runtime dependencies"
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
run_quiet "apt: dnsmasq, iperf3, ca-certificates" -- bash -c '
|
|
apt-get update -qq
|
|
apt-get install -qq -y --no-install-recommends ca-certificates dnsmasq iperf3
|
|
'
|
|
|
|
step "creating ${SERVICE_USER} user"
|
|
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
|
|
useradd --system \
|
|
--home-dir "${STATE_DIR}" \
|
|
--shell /usr/sbin/nologin \
|
|
"${SERVICE_USER}"
|
|
ok "${SERVICE_USER} created"
|
|
else
|
|
info "${SERVICE_USER} user already exists"
|
|
fi
|
|
|
|
step "preparing directories"
|
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${STATE_DIR}"
|
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LOG_DIR}"
|
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${ASSET_DIR}"
|
|
install -d -m 0755 "${CONFIG_DIR}"
|
|
info "state: ${STATE_DIR} · logs: ${LOG_DIR} · config: ${CONFIG_DIR}"
|
|
|
|
step "installing binary"
|
|
install -m 0755 "${BINARY}" /usr/local/bin/vetting
|
|
install -m 0755 "${AGENT_BINARY}" "${ASSET_DIR}/vetting-agent-linux-amd64"
|
|
info "orchestrator: /usr/local/bin/vetting"
|
|
info "agent asset: ${ASSET_DIR}/vetting-agent-linux-amd64"
|
|
|
|
step "installing config and systemd unit"
|
|
# vetting.production.yaml uses absolute /var/lib/vetting + /var/log/vetting
|
|
# paths that match the systemd unit's ReadWritePaths. vetting.example.yaml
|
|
# uses ./var/... relatives and is only correct for `make run` in a dev tree.
|
|
if [[ ! -f "${CONFIG_DIR}/vetting.yaml" ]]; then
|
|
install -m 0640 -o root -g "${SERVICE_USER}" \
|
|
"${SCRIPT_DIR}/vetting.production.yaml" \
|
|
"${CONFIG_DIR}/vetting.yaml"
|
|
info "installed default config at ${CONFIG_DIR}/vetting.yaml"
|
|
else
|
|
info "preserving existing ${CONFIG_DIR}/vetting.yaml"
|
|
fi
|
|
install -m 0644 "${SCRIPT_DIR}/vetting.service" /etc/systemd/system/vetting.service
|
|
|
|
# Install pxe-setup.sh + its pinned iPXE SHAs into a stable path so the
|
|
# operator can run `vetting-pxe-setup` after the one-liner install.
|
|
if [[ -f "${SCRIPT_DIR}/pxe-setup.sh" && -f "${SCRIPT_DIR}/ipxe-shas.txt" ]]; then
|
|
step "installing pxe-setup.sh and ipxe-shas.txt"
|
|
install -d -m 0755 /usr/local/share/vetting
|
|
install -m 0755 "${SCRIPT_DIR}/pxe-setup.sh" /usr/local/share/vetting/pxe-setup.sh
|
|
install -m 0644 "${SCRIPT_DIR}/ipxe-shas.txt" /usr/local/share/vetting/ipxe-shas.txt
|
|
ln -sfn /usr/local/share/vetting/pxe-setup.sh /usr/local/sbin/vetting-pxe-setup
|
|
info "vetting-pxe-setup -> /usr/local/share/vetting/pxe-setup.sh"
|
|
fi
|
|
|
|
# Stage the live image into LIVE_DIR. Preference order:
|
|
# 1. --live-image-src explicitly given, or local files found in the
|
|
# bundle/repo — copy straight in (dev and legacy bundle layouts).
|
|
# 2. Bundle carries only live-image/VERSION — fetch from the Gitea
|
|
# generic registry when the pointer differs from ${LIVE_DIR}/VERSION.
|
|
# 3. Neither — skip quietly (no-PXE installs don't need a live image,
|
|
# and dev checkouts that haven't run `make live-image` shouldn't
|
|
# fail the install).
|
|
if [[ -z "${LIVE_IMAGE_SRC}" ]]; then
|
|
for cand in \
|
|
"${SCRIPT_DIR}/live-image" \
|
|
"${REPO_ROOT}/live-image/build"; do
|
|
if [[ -f "${cand}/vmlinuz" && -f "${cand}/initrd.img" ]]; then
|
|
LIVE_IMAGE_SRC="${cand}"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ -n "${LIVE_IMAGE_SRC}" ]]; then
|
|
step "staging live image from ${LIVE_IMAGE_SRC}"
|
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LIVE_DIR}"
|
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
|
"${LIVE_IMAGE_SRC}/vmlinuz" "${LIVE_DIR}/vmlinuz"
|
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
|
"${LIVE_IMAGE_SRC}/initrd.img" "${LIVE_DIR}/initrd.img"
|
|
# Record the version that produced these files if the source has
|
|
# one (bundle with legacy layout carrying VERSION alongside the
|
|
# kernel; dev tree has live-image/VERSION at repo root). Lets a
|
|
# future bundle-based install decide whether to refetch.
|
|
for vcand in \
|
|
"${LIVE_IMAGE_SRC}/VERSION" \
|
|
"${SCRIPT_DIR}/live-image/VERSION" \
|
|
"${REPO_ROOT}/live-image/VERSION"; do
|
|
if [[ -f "${vcand}" ]]; then
|
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
|
"${vcand}" "${LIVE_DIR}/VERSION"
|
|
break
|
|
fi
|
|
done
|
|
ok "live image staged into ${LIVE_DIR}"
|
|
elif [[ -f "${SCRIPT_DIR}/live-image/VERSION" ]]; then
|
|
refresh_live_image
|
|
else
|
|
info "no live image found (bundle/live-image or ../live-image/build); skipping live-dir staging"
|
|
fi
|
|
|
|
# Disable the distro's dnsmasq so only the orchestrator-supervised
|
|
# instance owns DHCP/TFTP. Operators who want to keep dnsmasq for
|
|
# something else can re-enable it after configuring a disjoint listen
|
|
# address.
|
|
if systemctl is-enabled --quiet dnsmasq 2>/dev/null; then
|
|
step "disabling distro dnsmasq (orchestrator supervises its own)"
|
|
run_quiet "systemctl disable --now dnsmasq" -- systemctl disable --now dnsmasq
|
|
fi
|
|
|
|
step "validating pxe config against this host's interfaces"
|
|
heal_pxe_config "${CONFIG_DIR}/vetting.yaml"
|
|
|
|
run_quiet "systemctl daemon-reload" -- systemctl daemon-reload
|
|
|
|
# Upgrade path: if vetting.service is already enabled, restart it so the
|
|
# new binary + live image take effect without an explicit second
|
|
# command. First-install path (service not enabled yet) leaves the
|
|
# service alone so the operator can edit the config before starting.
|
|
if (( _prev_svc_enabled )); then
|
|
step "restarting vetting.service (upgrade path)"
|
|
systemctl reset-failed vetting.service 2>/dev/null || true
|
|
run_quiet "systemctl restart vetting.service" -- systemctl restart vetting.service
|
|
fi
|
|
|
|
# Standalone summary (wrapped runs get the summary from proxmox-install.sh).
|
|
if [[ -z "${VETTING_INSTALL_WRAPPED:-}" ]]; then
|
|
li_ver="unknown"
|
|
[[ -f "${LIVE_DIR}/VERSION" ]] && li_ver="$(tr -d '[:space:]' < "${LIVE_DIR}/VERSION")"
|
|
svc_state="not yet enabled"
|
|
if systemctl is-enabled --quiet vetting.service 2>/dev/null; then
|
|
if systemctl is-active --quiet vetting.service 2>/dev/null; then
|
|
svc_state="enabled · running"
|
|
else
|
|
svc_state="enabled · stopped"
|
|
fi
|
|
fi
|
|
rule_open "installed"
|
|
info "live-image ${li_ver}"
|
|
info "service ${svc_state}"
|
|
info "config ${CONFIG_DIR}/vetting.yaml"
|
|
printf '\n' >&2
|
|
if (( _prev_svc_enabled )); then
|
|
info "upgraded · tail logs with:"
|
|
info " journalctl -fu vetting"
|
|
else
|
|
info "next:"
|
|
info " edit ${CONFIG_DIR}/vetting.yaml (server.bind, public_url, pxe.*)"
|
|
info " systemctl enable --now vetting"
|
|
info " journalctl -fu vetting"
|
|
printf '\n' >&2
|
|
info "pxe support:"
|
|
info " sudo vetting-pxe-setup --interface <NIC> --subnet <CIDR> \\"
|
|
info " --orchestrator-url http://<host>:8080"
|
|
fi
|
|
rule_close
|
|
total_elapsed
|
|
fi
|