feat(install): polish install UX with banner, spinner, progress bar, summary
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>
This commit is contained in:
+255
-69
@@ -33,6 +33,172 @@
|
|||||||
#
|
#
|
||||||
set -euo pipefail
|
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=""
|
BINARY=""
|
||||||
AGENT_BINARY=""
|
AGENT_BINARY=""
|
||||||
CONFIG_DIR="/etc/vetting"
|
CONFIG_DIR="/etc/vetting"
|
||||||
@@ -78,8 +244,21 @@ while [[ $# -gt 0 ]]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "install.sh must be run as root (try: sudo $0)" >&2
|
die "install.sh must be run as root (try: sudo $0)"
|
||||||
exit 1
|
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
|
fi
|
||||||
|
|
||||||
# heal_pxe_config: make sure /etc/vetting/vetting.yaml's pxe.interface
|
# heal_pxe_config: make sure /etc/vetting/vetting.yaml's pxe.interface
|
||||||
@@ -140,18 +319,17 @@ heal_pxe_config() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${detected_iface}" || -z "${detected_subnet}" ]]; then
|
if [[ -z "${detected_iface}" || -z "${detected_subnet}" ]]; then
|
||||||
echo "WARN: pxe is enabled in ${config} but pxe.interface=${cur_iface:-<empty>} / pxe.subnet=${cur_subnet:-<empty>} is stale," >&2
|
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."
|
||||||
echo " and no default-route NIC was found to auto-detect from. Edit the file manually before starting." >&2
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local iface_to_write="${cur_iface}" subnet_to_write="${cur_subnet}"
|
local iface_to_write="${cur_iface}" subnet_to_write="${cur_subnet}"
|
||||||
if (( iface_ok == 0 )); then
|
if (( iface_ok == 0 )); then
|
||||||
echo "==> pxe.interface \"${cur_iface}\" is not present on this host; auto-patching to \"${detected_iface}\""
|
info "pxe.interface \"${cur_iface}\" is not present on this host; auto-patching to \"${detected_iface}\""
|
||||||
iface_to_write="${detected_iface}"
|
iface_to_write="${detected_iface}"
|
||||||
fi
|
fi
|
||||||
if (( subnet_ok == 0 )); then
|
if (( subnet_ok == 0 )); then
|
||||||
echo "==> pxe.subnet \"${cur_subnet:-<empty>}\" is missing/invalid; auto-patching to \"${detected_subnet}\""
|
info "pxe.subnet \"${cur_subnet:-<empty>}\" is missing/invalid; auto-patching to \"${detected_subnet}\""
|
||||||
subnet_to_write="${detected_subnet}"
|
subnet_to_write="${detected_subnet}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -181,7 +359,7 @@ refresh_live_image() {
|
|||||||
local bundle_ver
|
local bundle_ver
|
||||||
bundle_ver="$(tr -d '[:space:]' < "${pointer}" 2>/dev/null || true)"
|
bundle_ver="$(tr -d '[:space:]' < "${pointer}" 2>/dev/null || true)"
|
||||||
if [[ -z "${bundle_ver}" ]]; then
|
if [[ -z "${bundle_ver}" ]]; then
|
||||||
echo "WARN: bundle's ${pointer} is empty; skipping live-image fetch" >&2
|
warn "bundle's ${pointer} is empty; skipping live-image fetch"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -191,27 +369,25 @@ refresh_live_image() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${bundle_ver}" == "${installed_ver}" && "${FORCE_LIVE_IMAGE:-0}" != "1" ]]; then
|
if [[ "${bundle_ver}" == "${installed_ver}" && "${FORCE_LIVE_IMAGE:-0}" != "1" ]]; then
|
||||||
echo "==> live-image already at ${bundle_ver}; skipping fetch (FORCE_LIVE_IMAGE=1 to redownload)"
|
info "live-image already at ${bundle_ver}; skipping fetch (FORCE_LIVE_IMAGE=1 to redownload)"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${REGISTRY_URL:-}" ]]; then
|
if [[ -z "${REGISTRY_URL:-}" ]]; then
|
||||||
echo "WARN: REGISTRY_URL is not set; cannot fetch live-image ${bundle_ver}. Re-run via proxmox-install.sh or export REGISTRY_URL." >&2
|
warn "REGISTRY_URL is not set; cannot fetch live-image ${bundle_ver}. Re-run via proxmox-install.sh or export REGISTRY_URL."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
local owner="${PACKAGE_OWNER:-josh}"
|
local owner="${PACKAGE_OWNER:-josh}"
|
||||||
local base="${REGISTRY_URL%/}/api/packages/${owner}/generic/live-image/${bundle_ver}"
|
local base="${REGISTRY_URL%/}/api/packages/${owner}/generic/live-image/${bundle_ver}"
|
||||||
|
|
||||||
echo "==> fetching live-image ${bundle_ver} (was '${installed_ver:-none}') from ${base}"
|
step "fetching live-image ${bundle_ver} (was '${installed_ver:-none}')"
|
||||||
local tmp
|
local tmp
|
||||||
tmp="$(mktemp -d)"
|
tmp="$(mktemp -d)"
|
||||||
# shellcheck disable=SC2064
|
# shellcheck disable=SC2064
|
||||||
trap "rm -rf '${tmp}'" RETURN
|
trap "rm -rf '${tmp}'" RETURN
|
||||||
|
|
||||||
# Default curl meter shows rate + ETA, which matters for the ~300 MB
|
curl_pretty --label "kernel (vmlinuz)" --url "${base}/vmlinuz" -- -o "${tmp}/vmlinuz"
|
||||||
# initrd on slow links.
|
curl_pretty --label "initrd (initrd.img)" --url "${base}/initrd.img" -- -o "${tmp}/initrd.img"
|
||||||
curl -fL -o "${tmp}/vmlinuz" "${base}/vmlinuz"
|
|
||||||
curl -fL -o "${tmp}/initrd.img" "${base}/initrd.img"
|
|
||||||
|
|
||||||
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LIVE_DIR}"
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LIVE_DIR}"
|
||||||
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
||||||
@@ -235,8 +411,7 @@ if [[ -z "${BINARY}" ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
if [[ -z "${BINARY}" || ! -x "${BINARY}" ]]; then
|
if [[ -z "${BINARY}" || ! -x "${BINARY}" ]]; then
|
||||||
echo "could not find a vetting binary to install; pass --binary PATH or run 'make orchestrator-linux' first" >&2
|
die "could not find a vetting binary to install; pass --binary PATH or run 'make orchestrator-linux' first"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${AGENT_BINARY}" ]]; then
|
if [[ -z "${AGENT_BINARY}" ]]; then
|
||||||
@@ -248,35 +423,41 @@ if [[ -z "${AGENT_BINARY}" ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
if [[ -z "${AGENT_BINARY}" || ! -x "${AGENT_BINARY}" ]]; then
|
if [[ -z "${AGENT_BINARY}" || ! -x "${AGENT_BINARY}" ]]; then
|
||||||
echo "could not find a vetting-agent binary; pass --agent-binary PATH or run 'make agent-linux' first" >&2
|
die "could not find a vetting-agent binary; pass --agent-binary PATH or run 'make agent-linux' first"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> installing runtime dependencies"
|
step "installing runtime dependencies"
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
run_quiet "apt: dnsmasq, iperf3, ca-certificates" -- bash -c '
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -qq -y --no-install-recommends ca-certificates dnsmasq iperf3
|
||||||
ca-certificates dnsmasq iperf3
|
'
|
||||||
|
|
||||||
echo "==> creating ${SERVICE_USER} user"
|
step "creating ${SERVICE_USER} user"
|
||||||
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
|
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
|
||||||
useradd --system \
|
useradd --system \
|
||||||
--home-dir "${STATE_DIR}" \
|
--home-dir "${STATE_DIR}" \
|
||||||
--shell /usr/sbin/nologin \
|
--shell /usr/sbin/nologin \
|
||||||
"${SERVICE_USER}"
|
"${SERVICE_USER}"
|
||||||
|
ok "${SERVICE_USER} created"
|
||||||
|
else
|
||||||
|
info "${SERVICE_USER} user already exists"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> preparing directories"
|
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}" "${STATE_DIR}"
|
||||||
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LOG_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 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${ASSET_DIR}"
|
||||||
install -d -m 0755 "${CONFIG_DIR}"
|
install -d -m 0755 "${CONFIG_DIR}"
|
||||||
|
info "state: ${STATE_DIR} · logs: ${LOG_DIR} · config: ${CONFIG_DIR}"
|
||||||
|
|
||||||
echo "==> installing binary"
|
step "installing binary"
|
||||||
install -m 0755 "${BINARY}" /usr/local/bin/vetting
|
install -m 0755 "${BINARY}" /usr/local/bin/vetting
|
||||||
install -m 0755 "${AGENT_BINARY}" "${ASSET_DIR}/vetting-agent-linux-amd64"
|
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"
|
||||||
|
|
||||||
echo "==> installing config and systemd unit"
|
step "installing config and systemd unit"
|
||||||
# vetting.production.yaml uses absolute /var/lib/vetting + /var/log/vetting
|
# vetting.production.yaml uses absolute /var/lib/vetting + /var/log/vetting
|
||||||
# paths that match the systemd unit's ReadWritePaths. vetting.example.yaml
|
# 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.
|
# uses ./var/... relatives and is only correct for `make run` in a dev tree.
|
||||||
@@ -284,22 +465,21 @@ if [[ ! -f "${CONFIG_DIR}/vetting.yaml" ]]; then
|
|||||||
install -m 0640 -o root -g "${SERVICE_USER}" \
|
install -m 0640 -o root -g "${SERVICE_USER}" \
|
||||||
"${SCRIPT_DIR}/vetting.production.yaml" \
|
"${SCRIPT_DIR}/vetting.production.yaml" \
|
||||||
"${CONFIG_DIR}/vetting.yaml"
|
"${CONFIG_DIR}/vetting.yaml"
|
||||||
echo " -> installed default config at ${CONFIG_DIR}/vetting.yaml"
|
info "installed default config at ${CONFIG_DIR}/vetting.yaml"
|
||||||
else
|
else
|
||||||
echo " -> preserving existing ${CONFIG_DIR}/vetting.yaml"
|
info "preserving existing ${CONFIG_DIR}/vetting.yaml"
|
||||||
fi
|
fi
|
||||||
install -m 0644 "${SCRIPT_DIR}/vetting.service" /etc/systemd/system/vetting.service
|
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
|
# 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.
|
# operator can run `vetting-pxe-setup` after the one-liner install.
|
||||||
# The bundle's tempdir gets wiped by proxmox-install.sh on exit, so
|
|
||||||
# without this the script would be inaccessible.
|
|
||||||
if [[ -f "${SCRIPT_DIR}/pxe-setup.sh" && -f "${SCRIPT_DIR}/ipxe-shas.txt" ]]; then
|
if [[ -f "${SCRIPT_DIR}/pxe-setup.sh" && -f "${SCRIPT_DIR}/ipxe-shas.txt" ]]; then
|
||||||
echo "==> installing pxe-setup.sh and ipxe-shas.txt"
|
step "installing pxe-setup.sh and ipxe-shas.txt"
|
||||||
install -d -m 0755 /usr/local/share/vetting
|
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 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
|
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
|
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
|
fi
|
||||||
|
|
||||||
# Stage the live image into LIVE_DIR. Preference order:
|
# Stage the live image into LIVE_DIR. Preference order:
|
||||||
@@ -322,7 +502,7 @@ if [[ -z "${LIVE_IMAGE_SRC}" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${LIVE_IMAGE_SRC}" ]]; then
|
if [[ -n "${LIVE_IMAGE_SRC}" ]]; then
|
||||||
echo "==> staging live image from ${LIVE_IMAGE_SRC} into ${LIVE_DIR}"
|
step "staging live image from ${LIVE_IMAGE_SRC}"
|
||||||
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LIVE_DIR}"
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LIVE_DIR}"
|
||||||
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
||||||
"${LIVE_IMAGE_SRC}/vmlinuz" "${LIVE_DIR}/vmlinuz"
|
"${LIVE_IMAGE_SRC}/vmlinuz" "${LIVE_DIR}/vmlinuz"
|
||||||
@@ -342,10 +522,11 @@ if [[ -n "${LIVE_IMAGE_SRC}" ]]; then
|
|||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
ok "live image staged into ${LIVE_DIR}"
|
||||||
elif [[ -f "${SCRIPT_DIR}/live-image/VERSION" ]]; then
|
elif [[ -f "${SCRIPT_DIR}/live-image/VERSION" ]]; then
|
||||||
refresh_live_image
|
refresh_live_image
|
||||||
else
|
else
|
||||||
echo "==> no live image found (bundle/live-image or ../live-image/build); skipping live-dir staging"
|
info "no live image found (bundle/live-image or ../live-image/build); skipping live-dir staging"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Disable the distro's dnsmasq so only the orchestrator-supervised
|
# Disable the distro's dnsmasq so only the orchestrator-supervised
|
||||||
@@ -353,50 +534,55 @@ fi
|
|||||||
# something else can re-enable it after configuring a disjoint listen
|
# something else can re-enable it after configuring a disjoint listen
|
||||||
# address.
|
# address.
|
||||||
if systemctl is-enabled --quiet dnsmasq 2>/dev/null; then
|
if systemctl is-enabled --quiet dnsmasq 2>/dev/null; then
|
||||||
echo "==> disabling distro dnsmasq (orchestrator supervises its own)"
|
step "disabling distro dnsmasq (orchestrator supervises its own)"
|
||||||
systemctl disable --now dnsmasq
|
run_quiet "systemctl disable --now dnsmasq" -- systemctl disable --now dnsmasq
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> validating pxe config against this host's interfaces"
|
step "validating pxe config against this host's interfaces"
|
||||||
heal_pxe_config "${CONFIG_DIR}/vetting.yaml"
|
heal_pxe_config "${CONFIG_DIR}/vetting.yaml"
|
||||||
|
|
||||||
systemctl daemon-reload
|
run_quiet "systemctl daemon-reload" -- systemctl daemon-reload
|
||||||
|
|
||||||
# Upgrade path: if vetting.service is already enabled, restart it so the
|
# Upgrade path: if vetting.service is already enabled, restart it so the
|
||||||
# new binary + live image take effect without an explicit second
|
# new binary + live image take effect without an explicit second
|
||||||
# command. First-install path (service not enabled yet) leaves the
|
# command. First-install path (service not enabled yet) leaves the
|
||||||
# service alone so the operator can edit the config before starting.
|
# service alone so the operator can edit the config before starting.
|
||||||
if systemctl is-enabled --quiet vetting.service 2>/dev/null; then
|
if (( _prev_svc_enabled )); then
|
||||||
echo "==> restarting vetting.service (upgrade path)"
|
step "restarting vetting.service (upgrade path)"
|
||||||
systemctl reset-failed vetting.service 2>/dev/null || true
|
systemctl reset-failed vetting.service 2>/dev/null || true
|
||||||
systemctl restart vetting.service
|
run_quiet "systemctl restart vetting.service" -- systemctl restart vetting.service
|
||||||
cat <<EOF
|
fi
|
||||||
|
|
||||||
vetting upgraded and restarted. Tail logs with:
|
# Standalone summary (wrapped runs get the summary from proxmox-install.sh).
|
||||||
journalctl -fu vetting
|
if [[ -z "${VETTING_INSTALL_WRAPPED:-}" ]]; then
|
||||||
|
li_ver="unknown"
|
||||||
EOF
|
[[ -f "${LIVE_DIR}/VERSION" ]] && li_ver="$(tr -d '[:space:]' < "${LIVE_DIR}/VERSION")"
|
||||||
else
|
svc_state="not yet enabled"
|
||||||
cat <<EOF
|
if systemctl is-enabled --quiet vetting.service 2>/dev/null; then
|
||||||
|
if systemctl is-active --quiet vetting.service 2>/dev/null; then
|
||||||
vetting is installed but not yet enabled.
|
svc_state="enabled · running"
|
||||||
|
else
|
||||||
Next steps:
|
svc_state="enabled · stopped"
|
||||||
1. Edit ${CONFIG_DIR}/vetting.yaml and set:
|
fi
|
||||||
- server.bind (127.0.0.1:8080 by default; switch to
|
fi
|
||||||
0.0.0.0:8080 once you're ready to expose
|
rule_open "installed"
|
||||||
it on the LAN)
|
info "live-image ${li_ver}"
|
||||||
- server.public_url (the URL you'll browse to)
|
info "service ${svc_state}"
|
||||||
- pxe.* if you want PXE boot support
|
info "config ${CONFIG_DIR}/vetting.yaml"
|
||||||
- notifiers + routes (optional)
|
printf '\n' >&2
|
||||||
2. Start the service:
|
if (( _prev_svc_enabled )); then
|
||||||
systemctl enable --now vetting
|
info "upgraded · tail logs with:"
|
||||||
3. Watch the logs:
|
info " journalctl -fu vetting"
|
||||||
journalctl -fu vetting
|
else
|
||||||
|
info "next:"
|
||||||
The UI has no built-in auth — it trusts the LAN. If you need a
|
info " edit ${CONFIG_DIR}/vetting.yaml (server.bind, public_url, pxe.*)"
|
||||||
password, front the service with a reverse proxy (Caddy/nginx
|
info " systemctl enable --now vetting"
|
||||||
basic-auth) instead.
|
info " journalctl -fu vetting"
|
||||||
|
printf '\n' >&2
|
||||||
EOF
|
info "pxe support:"
|
||||||
|
info " sudo vetting-pxe-setup --interface <NIC> --subnet <CIDR> \\"
|
||||||
|
info " --orchestrator-url http://<host>:8080"
|
||||||
|
fi
|
||||||
|
rule_close
|
||||||
|
total_elapsed
|
||||||
fi
|
fi
|
||||||
|
|||||||
+217
-35
@@ -22,6 +22,172 @@
|
|||||||
# (useful when the local files got corrupted).
|
# (useful when the local files got corrupted).
|
||||||
set -euo pipefail
|
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 -------------------------------------------------
|
||||||
|
|
||||||
REGISTRY_URL="${REGISTRY_URL:-https://gitea.thewrightserver.net}"
|
REGISTRY_URL="${REGISTRY_URL:-https://gitea.thewrightserver.net}"
|
||||||
PACKAGE_OWNER="${PACKAGE_OWNER:-josh}"
|
PACKAGE_OWNER="${PACKAGE_OWNER:-josh}"
|
||||||
FORCE_LIVE_IMAGE="${FORCE_LIVE_IMAGE:-0}"
|
FORCE_LIVE_IMAGE="${FORCE_LIVE_IMAGE:-0}"
|
||||||
@@ -29,7 +195,7 @@ FORCE_LIVE_IMAGE="${FORCE_LIVE_IMAGE:-0}"
|
|||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "${arg}" in
|
case "${arg}" in
|
||||||
--force-live-image) FORCE_LIVE_IMAGE=1 ;;
|
--force-live-image) FORCE_LIVE_IMAGE=1 ;;
|
||||||
*) echo "unknown arg: ${arg}" >&2; exit 2 ;;
|
*) die "unknown arg: ${arg}" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -41,27 +207,25 @@ export REGISTRY_URL PACKAGE_OWNER FORCE_LIVE_IMAGE
|
|||||||
BUNDLE_URL="${REGISTRY_URL}/api/packages/${PACKAGE_OWNER}/generic/vetting/latest/vetting-bundle.tar.gz"
|
BUNDLE_URL="${REGISTRY_URL}/api/packages/${PACKAGE_OWNER}/generic/vetting/latest/vetting-bundle.tar.gz"
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "proxmox-install.sh must be run as root (try: sudo bash)" >&2
|
die "proxmox-install.sh must be run as root (try: sudo bash)"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> installing prerequisites"
|
banner "TheWrightServer · Vetting"
|
||||||
|
|
||||||
|
step "installing prerequisites"
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
run_quiet "apt: curl, ca-certificates" -- bash -c '
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -qq -y --no-install-recommends curl ca-certificates
|
||||||
curl ca-certificates
|
'
|
||||||
|
|
||||||
tmp="$(mktemp -d)"
|
tmp="$(mktemp -d)"
|
||||||
trap 'rm -rf "${tmp}"' EXIT
|
trap 'rm -rf "${tmp}"' EXIT
|
||||||
|
|
||||||
echo "==> fetching bundle from ${BUNDLE_URL}"
|
curl_pretty --label "vetting bundle" --url "${BUNDLE_URL}" -- -o "${tmp}/vetting-bundle.tar.gz"
|
||||||
# -f fails on HTTP errors; -L follows redirects. Default meter (rate +
|
|
||||||
# ETA) is fine now that the bundle is ~30 MB.
|
|
||||||
curl -fL "${BUNDLE_URL}" -o "${tmp}/vetting-bundle.tar.gz"
|
|
||||||
|
|
||||||
bundle_size="$(du -h "${tmp}/vetting-bundle.tar.gz" | cut -f1)"
|
step "extracting bundle"
|
||||||
echo "==> extracting (${bundle_size})"
|
run_quiet "tar -xzf vetting-bundle.tar.gz" -- tar -C "${tmp}" -xzf "${tmp}/vetting-bundle.tar.gz"
|
||||||
tar -C "${tmp}" -xzf "${tmp}/vetting-bundle.tar.gz"
|
|
||||||
|
|
||||||
# New bundle extracts to vetting-bundle/; legacy bundles used
|
# New bundle extracts to vetting-bundle/; legacy bundles used
|
||||||
# vetting-bundle-<sha>/. Match both so a downgrade-pin still works.
|
# vetting-bundle-<sha>/. Match both so a downgrade-pin still works.
|
||||||
@@ -69,34 +233,52 @@ shopt -s nullglob
|
|||||||
candidates=( "${tmp}"/vetting-bundle "${tmp}"/vetting-bundle-* )
|
candidates=( "${tmp}"/vetting-bundle "${tmp}"/vetting-bundle-* )
|
||||||
shopt -u nullglob
|
shopt -u nullglob
|
||||||
if [[ ${#candidates[@]} -ne 1 || ! -d "${candidates[0]}" ]]; then
|
if [[ ${#candidates[@]} -ne 1 || ! -d "${candidates[0]}" ]]; then
|
||||||
echo "unexpected bundle layout: expected exactly one vetting-bundle* dir" >&2
|
die "unexpected bundle layout: expected exactly one vetting-bundle* dir"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
bundle_dir="${candidates[0]}"
|
bundle_dir="${candidates[0]}"
|
||||||
|
info "bundle: ${bundle_dir##*/}"
|
||||||
|
|
||||||
echo "==> handing off to install.sh (bundle ${bundle_dir##*/})"
|
|
||||||
cd "${bundle_dir}"
|
cd "${bundle_dir}"
|
||||||
|
# install.sh prints its own step/info/ok/warn lines via the same style
|
||||||
|
# helpers, so output flows seamlessly under this banner. VETTING_INSTALL_WRAPPED
|
||||||
|
# tells install.sh to suppress its own banner + summary, since we print
|
||||||
|
# them here.
|
||||||
|
export VETTING_INSTALL_WRAPPED=1
|
||||||
bash install.sh \
|
bash install.sh \
|
||||||
--binary "${bundle_dir}/bin/vetting-linux-amd64" \
|
--binary "${bundle_dir}/bin/vetting-linux-amd64" \
|
||||||
--agent-binary "${bundle_dir}/bin/vetting-agent.linux-amd64"
|
--agent-binary "${bundle_dir}/bin/vetting-agent.linux-amd64"
|
||||||
|
|
||||||
orch_ver="$(cat "${bundle_dir}/VERSION" 2>/dev/null || echo unknown)"
|
# ---- final summary -----------------------------------------------------
|
||||||
li_ver="$(cat "${bundle_dir}/live-image/VERSION" 2>/dev/null || echo unknown)"
|
li_ver="$(tr -d '[:space:]' < "${bundle_dir}/live-image/VERSION" 2>/dev/null)"
|
||||||
|
[[ -z "${li_ver}" ]] && li_ver="unknown"
|
||||||
|
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
|
||||||
|
|
||||||
cat <<EOF
|
rule_open "installed"
|
||||||
|
info "live-image ${li_ver}"
|
||||||
vetting installed: orchestrator ${orch_ver}, live-image ${li_ver}.
|
info "service ${svc_state}"
|
||||||
|
info "config /etc/vetting/vetting.yaml"
|
||||||
To upgrade later, rerun this one-liner. It always pulls the current
|
printf '\n' >&2
|
||||||
latest bundle; the live image is re-downloaded only when its VERSION
|
if [[ "${svc_state}" == "not yet enabled" ]]; then
|
||||||
has bumped (override with --force-live-image).
|
info "next:"
|
||||||
|
info " edit /etc/vetting/vetting.yaml (bind addr, public_url, pxe.*)"
|
||||||
For PXE support, run:
|
info " systemctl enable --now vetting"
|
||||||
sudo vetting-pxe-setup \\
|
info " journalctl -fu vetting"
|
||||||
--interface eth0 \\
|
else
|
||||||
--subnet 192.168.1.0/24 \\
|
info "next:"
|
||||||
--orchestrator-url http://<lxc-lan-ip>:8080
|
info " journalctl -fu vetting"
|
||||||
|
fi
|
||||||
See docs/operations.md for the full flow.
|
printf '\n' >&2
|
||||||
|
info "pxe support:"
|
||||||
EOF
|
info " sudo vetting-pxe-setup --interface <NIC> --subnet <CIDR> \\"
|
||||||
|
info " --orchestrator-url http://<host>:8080"
|
||||||
|
info "upgrade later:"
|
||||||
|
info " rerun the one-liner (--force-live-image to force refetch)"
|
||||||
|
rule_close
|
||||||
|
total_elapsed
|
||||||
|
|||||||
+209
-51
@@ -28,6 +28,172 @@
|
|||||||
# --force overwrite a customised pxe: block
|
# --force overwrite a customised pxe: block
|
||||||
set -euo pipefail
|
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 -------------------------------------------------
|
||||||
|
|
||||||
INTERFACE=""
|
INTERFACE=""
|
||||||
SUBNET=""
|
SUBNET=""
|
||||||
ORCH_URL=""
|
ORCH_URL=""
|
||||||
@@ -63,37 +229,33 @@ while [[ $# -gt 0 ]]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "pxe-setup.sh must be run as root (try: sudo $0 ...)" >&2
|
die "pxe-setup.sh must be run as root (try: sudo $0 ...)"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ -z "${INTERFACE}" ]] && { echo "ERROR: --interface is required" >&2; exit 2; }
|
[[ -z "${INTERFACE}" ]] && die "--interface is required"
|
||||||
[[ -z "${SUBNET}" ]] && { echo "ERROR: --subnet is required (e.g. 192.168.1.0/24)" >&2; exit 2; }
|
[[ -z "${SUBNET}" ]] && die "--subnet is required (e.g. 192.168.1.0/24)"
|
||||||
[[ -z "${ORCH_URL}" ]] && { echo "ERROR: --orchestrator-url is required" >&2; exit 2; }
|
[[ -z "${ORCH_URL}" ]] && die "--orchestrator-url is required"
|
||||||
|
|
||||||
|
banner "TheWrightServer · Vetting · PXE"
|
||||||
|
|
||||||
# --- sanity checks -----------------------------------------------------
|
# --- sanity checks -----------------------------------------------------
|
||||||
|
|
||||||
if ! ip link show "${INTERFACE}" >/dev/null 2>&1; then
|
if ! ip link show "${INTERFACE}" >/dev/null 2>&1; then
|
||||||
echo "ERROR: interface ${INTERFACE} not found on host. Check \`ip link\` — the" >&2
|
die "interface ${INTERFACE} not found on host. Check \`ip link\` — the interface must exist *before* the orchestrator starts dnsmasq."
|
||||||
echo " interface must exist *before* the orchestrator starts dnsmasq." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# CIDR shape check — dnsmasq will re-validate, but catch the obvious
|
# CIDR shape check — dnsmasq will re-validate, but catch the obvious
|
||||||
# errors before we write anything to disk.
|
# errors before we write anything to disk.
|
||||||
if [[ ! "${SUBNET}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then
|
if [[ ! "${SUBNET}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then
|
||||||
echo "ERROR: --subnet must be CIDR form (e.g. 192.168.1.0/24), got '${SUBNET}'" >&2
|
die "--subnet must be CIDR form (e.g. 192.168.1.0/24), got '${SUBNET}'"
|
||||||
exit 2
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "${CONFIG}" ]]; then
|
if [[ ! -f "${CONFIG}" ]]; then
|
||||||
echo "ERROR: ${CONFIG} not found — run deploy/install.sh first." >&2
|
die "${CONFIG} not found — run deploy/install.sh first."
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
|
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
|
||||||
echo "ERROR: ${SERVICE_USER} user not found — run deploy/install.sh first." >&2
|
die "${SERVICE_USER} user not found — run deploy/install.sh first."
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Resolve the bundle dir. When pxe-setup.sh is run from a release
|
# Resolve the bundle dir. When pxe-setup.sh is run from a release
|
||||||
@@ -101,16 +263,11 @@ fi
|
|||||||
# run from the repo tree it's deploy/pxe-setup.sh and the live image is
|
# run from the repo tree it's deploy/pxe-setup.sh and the live image is
|
||||||
# under live-image/build/. Detect both.
|
# under live-image/build/. Detect both.
|
||||||
if [[ -z "${BUNDLE_DIR}" ]]; then
|
if [[ -z "${BUNDLE_DIR}" ]]; then
|
||||||
if [[ -f "${SCRIPT_DIR}/ipxe-shas.txt" ]]; then
|
|
||||||
BUNDLE_DIR="${SCRIPT_DIR}"
|
BUNDLE_DIR="${SCRIPT_DIR}"
|
||||||
else
|
|
||||||
BUNDLE_DIR="${SCRIPT_DIR}"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
SHAS_FILE="${BUNDLE_DIR}/ipxe-shas.txt"
|
SHAS_FILE="${BUNDLE_DIR}/ipxe-shas.txt"
|
||||||
if [[ ! -f "${SHAS_FILE}" ]]; then
|
if [[ ! -f "${SHAS_FILE}" ]]; then
|
||||||
echo "ERROR: ${SHAS_FILE} not found — bundle is incomplete." >&2
|
die "${SHAS_FILE} not found — bundle is incomplete."
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- iPXE binaries: stage, verify, install ----------------------------
|
# --- iPXE binaries: stage, verify, install ----------------------------
|
||||||
@@ -120,8 +277,9 @@ fi
|
|||||||
# install(1) unlink-replaces, which avoids ETXTBSY and makes the whole
|
# install(1) unlink-replaces, which avoids ETXTBSY and makes the whole
|
||||||
# operation atomic per file.
|
# operation atomic per file.
|
||||||
|
|
||||||
echo "==> ensuring ${TFTP_ROOT} exists"
|
step "ensuring ${TFTP_ROOT} exists"
|
||||||
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${TFTP_ROOT}"
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${TFTP_ROOT}"
|
||||||
|
info "tftp root: ${TFTP_ROOT}"
|
||||||
|
|
||||||
STAGE="$(mktemp -d)"
|
STAGE="$(mktemp -d)"
|
||||||
trap 'rm -rf "${STAGE}"' EXIT
|
trap 'rm -rf "${STAGE}"' EXIT
|
||||||
@@ -138,30 +296,28 @@ done
|
|||||||
# we skip the fetch entirely; if not, re-download.
|
# we skip the fetch entirely; if not, re-download.
|
||||||
if (( ! need_fetch )); then
|
if (( ! need_fetch )); then
|
||||||
if ! ( cd "${TFTP_ROOT}" && sha256sum -c --status "${SHAS_FILE}" ); then
|
if ! ( cd "${TFTP_ROOT}" && sha256sum -c --status "${SHAS_FILE}" ); then
|
||||||
echo "==> ${TFTP_ROOT} iPXE binaries don't match pinned SHAs — re-fetching"
|
info "iPXE binaries in ${TFTP_ROOT} don't match pinned SHAs — re-fetching"
|
||||||
need_fetch=1
|
need_fetch=1
|
||||||
else
|
else
|
||||||
echo "==> iPXE binaries already match pins — skipping fetch"
|
info "iPXE binaries already match pins — skipping fetch"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if (( need_fetch )); then
|
if (( need_fetch )); then
|
||||||
echo "==> fetching iPXE binaries from boot.ipxe.org"
|
step "fetching iPXE binaries from boot.ipxe.org"
|
||||||
curl -fsSLo "${STAGE}/ipxe.efi" "https://boot.ipxe.org/x86_64-efi/ipxe.efi"
|
curl_pretty --label "ipxe.efi" --url "https://boot.ipxe.org/x86_64-efi/ipxe.efi" -- -o "${STAGE}/ipxe.efi"
|
||||||
curl -fsSLo "${STAGE}/undionly.kpxe" "https://boot.ipxe.org/undionly.kpxe"
|
curl_pretty --label "undionly.kpxe" --url "https://boot.ipxe.org/undionly.kpxe" -- -o "${STAGE}/undionly.kpxe"
|
||||||
|
|
||||||
echo "==> verifying SHA256 against ${SHAS_FILE}"
|
step "verifying SHA256 against ${SHAS_FILE}"
|
||||||
if ! ( cd "${STAGE}" && sha256sum -c "${SHAS_FILE}" ); then
|
if ! run_quiet "sha256sum -c" -- bash -c "cd '${STAGE}' && sha256sum -c '${SHAS_FILE}'"; then
|
||||||
echo "ERROR: iPXE SHA256 mismatch. Upstream binaries changed, or a MITM." >&2
|
die "iPXE SHA256 mismatch. Upstream binaries changed, or a MITM. To accept the new binaries, regenerate ${SHAS_FILE} after independently verifying the new checksums, then re-run."
|
||||||
echo " To accept the new binaries, regenerate ${SHAS_FILE} after" >&2
|
|
||||||
echo " independently verifying the new checksums, then re-run." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
||||||
"${STAGE}/ipxe.efi" "${TFTP_ROOT}/ipxe.efi"
|
"${STAGE}/ipxe.efi" "${TFTP_ROOT}/ipxe.efi"
|
||||||
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
||||||
"${STAGE}/undionly.kpxe" "${TFTP_ROOT}/undionly.kpxe"
|
"${STAGE}/undionly.kpxe" "${TFTP_ROOT}/undionly.kpxe"
|
||||||
|
ok "iPXE binaries staged into ${TFTP_ROOT}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- live image: copy from bundle into live_dir -----------------------
|
# --- live image: copy from bundle into live_dir -----------------------
|
||||||
@@ -183,20 +339,19 @@ if [[ -z "${LIVE_SRC}" ]]; then
|
|||||||
# the one-liner install, so a missing bundle/live-image/ is expected
|
# the one-liner install, so a missing bundle/live-image/ is expected
|
||||||
# when pxe-setup.sh is run from /usr/local/sbin.
|
# when pxe-setup.sh is run from /usr/local/sbin.
|
||||||
if [[ -f "${LIVE_DIR}/vmlinuz" && -f "${LIVE_DIR}/initrd.img" ]]; then
|
if [[ -f "${LIVE_DIR}/vmlinuz" && -f "${LIVE_DIR}/initrd.img" ]]; then
|
||||||
echo "==> live image already staged in ${LIVE_DIR} (from install.sh)"
|
info "live image already staged in ${LIVE_DIR} (from install.sh)"
|
||||||
else
|
else
|
||||||
echo "WARN: no live image found under ${BUNDLE_DIR}/live-image," >&2
|
warn "no live image found under ${BUNDLE_DIR}/live-image, ${BUNDLE_DIR}/../live-image/build, or ${LIVE_DIR}."
|
||||||
echo " ${BUNDLE_DIR}/../live-image/build, or ${LIVE_DIR}." >&2
|
warn "the orchestrator will fail PXE startup validation until vmlinuz + initrd.img land in ${LIVE_DIR}."
|
||||||
echo " The orchestrator will fail PXE startup validation until" >&2
|
|
||||||
echo " vmlinuz + initrd.img land in ${LIVE_DIR}." >&2
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "==> staging live image from ${LIVE_SRC} into ${LIVE_DIR}"
|
step "staging live image from ${LIVE_SRC}"
|
||||||
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LIVE_DIR}"
|
install -d -m 0755 -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${LIVE_DIR}"
|
||||||
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
||||||
"${LIVE_SRC}/vmlinuz" "${LIVE_DIR}/vmlinuz"
|
"${LIVE_SRC}/vmlinuz" "${LIVE_DIR}/vmlinuz"
|
||||||
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
install -m 0644 -o "${SERVICE_USER}" -g "${SERVICE_USER}" \
|
||||||
"${LIVE_SRC}/initrd.img" "${LIVE_DIR}/initrd.img"
|
"${LIVE_SRC}/initrd.img" "${LIVE_DIR}/initrd.img"
|
||||||
|
ok "live image staged into ${LIVE_DIR}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- patch the pxe: block in vetting.yaml -----------------------------
|
# --- patch the pxe: block in vetting.yaml -----------------------------
|
||||||
@@ -243,16 +398,13 @@ existing_iface="$(extract_yaml_value interface "${CONFIG}")"
|
|||||||
existing_subnet="$(extract_yaml_value subnet "${CONFIG}")"
|
existing_subnet="$(extract_yaml_value subnet "${CONFIG}")"
|
||||||
|
|
||||||
if [[ -n "${existing_iface}" && "${existing_iface}" != "${INTERFACE}" && ${FORCE} -eq 0 ]]; then
|
if [[ -n "${existing_iface}" && "${existing_iface}" != "${INTERFACE}" && ${FORCE} -eq 0 ]]; then
|
||||||
echo "ERROR: pxe.interface in ${CONFIG} is already set to ${existing_iface}, which" >&2
|
die "pxe.interface in ${CONFIG} is already set to ${existing_iface}, which differs from --interface ${INTERFACE}. Pass --force to overwrite."
|
||||||
echo " differs from --interface ${INTERFACE}. Pass --force to overwrite." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
if [[ -n "${existing_subnet}" && "${existing_subnet}" != "${SUBNET}" && ${FORCE} -eq 0 ]]; then
|
if [[ -n "${existing_subnet}" && "${existing_subnet}" != "${SUBNET}" && ${FORCE} -eq 0 ]]; then
|
||||||
echo "ERROR: pxe.subnet in ${CONFIG} is already ${existing_subnet}, which" >&2
|
die "pxe.subnet in ${CONFIG} is already ${existing_subnet}, which differs from --subnet ${SUBNET}. Pass --force to overwrite."
|
||||||
echo " differs from --subnet ${SUBNET}. Pass --force to overwrite." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
step "patching pxe: block in ${CONFIG}"
|
||||||
new_block=$(cat <<EOF
|
new_block=$(cat <<EOF
|
||||||
pxe:
|
pxe:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -286,11 +438,17 @@ orig_owner="$(stat -c '%U:%G' "${CONFIG}")"
|
|||||||
install -m "${orig_mode}" -o "${orig_owner%:*}" -g "${orig_owner#*:}" \
|
install -m "${orig_mode}" -o "${orig_owner%:*}" -g "${orig_owner#*:}" \
|
||||||
"${tmp_yaml}" "${CONFIG}"
|
"${tmp_yaml}" "${CONFIG}"
|
||||||
rm -f "${tmp_yaml}"
|
rm -f "${tmp_yaml}"
|
||||||
|
ok "pxe: block written"
|
||||||
|
|
||||||
echo
|
rule_open "pxe configured"
|
||||||
echo "==> rendered pxe: block in ${CONFIG}:"
|
while IFS= read -r ln; do
|
||||||
echo "${new_block}" | sed 's/^/ /'
|
info "${ln}"
|
||||||
echo
|
done <<<"${new_block}"
|
||||||
echo "Next: systemctl restart vetting && journalctl -fu vetting"
|
printf '\n' >&2
|
||||||
echo "The orchestrator will refuse to start with clear errors if anything"
|
info "next:"
|
||||||
echo "is still missing; you should see dnsmasq come up cleanly."
|
info " systemctl restart vetting"
|
||||||
|
info " journalctl -fu vetting"
|
||||||
|
info "the orchestrator refuses to start with clear errors if anything is still missing;"
|
||||||
|
info "you should see dnsmasq come up cleanly."
|
||||||
|
rule_close
|
||||||
|
total_elapsed
|
||||||
|
|||||||
Reference in New Issue
Block a user