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>
285 lines
10 KiB
Bash
285 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
# proxmox-install.sh — one-shot installer for a fresh Proxmox LXC (or
|
|
# any Debian/Ubuntu host). Fetches a prebuilt release bundle from the
|
|
# Gitea package registry, extracts it, and hands off to install.sh.
|
|
#
|
|
# The bundle itself is slim (~30 MB: orchestrator + agent + deploy
|
|
# scripts + a live-image/VERSION pointer). install.sh compares that
|
|
# pointer against /var/lib/vetting/live/VERSION and fetches the
|
|
# ~300 MB vmlinuz+initrd.img from the registry only when they differ,
|
|
# so repeated runs cost ~10 s on no-live-image-change releases.
|
|
#
|
|
# Usage:
|
|
# curl -fsSL https://gitea.thewrightserver.net/josh/Vetting/raw/branch/main/deploy/proxmox-install.sh | sudo bash
|
|
#
|
|
# Flags / env overrides:
|
|
# REGISTRY_URL base URL of the Gitea instance hosting the
|
|
# package registry (default: https://gitea.thewrightserver.net)
|
|
# PACKAGE_OWNER Gitea owner of the `vetting` package
|
|
# (default: josh)
|
|
# FORCE_LIVE_IMAGE=1 or --force-live-image — re-download the live
|
|
# image even when the on-disk version matches
|
|
# (useful when the local files got corrupted).
|
|
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}"
|
|
PACKAGE_OWNER="${PACKAGE_OWNER:-josh}"
|
|
FORCE_LIVE_IMAGE="${FORCE_LIVE_IMAGE:-0}"
|
|
|
|
for arg in "$@"; do
|
|
case "${arg}" in
|
|
--force-live-image) FORCE_LIVE_IMAGE=1 ;;
|
|
*) die "unknown arg: ${arg}" ;;
|
|
esac
|
|
done
|
|
|
|
# Exported so install.sh (run as a child process inside the extracted
|
|
# bundle dir) sees them when deciding whether to fetch the live image
|
|
# and where to fetch it from.
|
|
export REGISTRY_URL PACKAGE_OWNER FORCE_LIVE_IMAGE
|
|
|
|
BUNDLE_URL="${REGISTRY_URL}/api/packages/${PACKAGE_OWNER}/generic/vetting/latest/vetting-bundle.tar.gz"
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
die "proxmox-install.sh must be run as root (try: sudo bash)"
|
|
fi
|
|
|
|
banner "TheWrightServer · Vetting"
|
|
|
|
step "installing prerequisites"
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
run_quiet "apt: curl, ca-certificates" -- bash -c '
|
|
apt-get update -qq
|
|
apt-get install -qq -y --no-install-recommends curl ca-certificates
|
|
'
|
|
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "${tmp}"' EXIT
|
|
|
|
curl_pretty --label "vetting bundle" --url "${BUNDLE_URL}" -- -o "${tmp}/vetting-bundle.tar.gz"
|
|
|
|
step "extracting bundle"
|
|
run_quiet "tar -xzf vetting-bundle.tar.gz" -- tar -C "${tmp}" -xzf "${tmp}/vetting-bundle.tar.gz"
|
|
|
|
# New bundle extracts to vetting-bundle/; legacy bundles used
|
|
# vetting-bundle-<sha>/. Match both so a downgrade-pin still works.
|
|
shopt -s nullglob
|
|
candidates=( "${tmp}"/vetting-bundle "${tmp}"/vetting-bundle-* )
|
|
shopt -u nullglob
|
|
if [[ ${#candidates[@]} -ne 1 || ! -d "${candidates[0]}" ]]; then
|
|
die "unexpected bundle layout: expected exactly one vetting-bundle* dir"
|
|
fi
|
|
bundle_dir="${candidates[0]}"
|
|
info "bundle: ${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 \
|
|
--binary "${bundle_dir}/bin/vetting-linux-amd64" \
|
|
--agent-binary "${bundle_dir}/bin/vetting-agent.linux-amd64"
|
|
|
|
# ---- final summary -----------------------------------------------------
|
|
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
|
|
|
|
rule_open "installed"
|
|
info "live-image ${li_ver}"
|
|
info "service ${svc_state}"
|
|
info "config /etc/vetting/vetting.yaml"
|
|
printf '\n' >&2
|
|
if [[ "${svc_state}" == "not yet enabled" ]]; then
|
|
info "next:"
|
|
info " edit /etc/vetting/vetting.yaml (bind addr, public_url, pxe.*)"
|
|
info " systemctl enable --now vetting"
|
|
info " journalctl -fu vetting"
|
|
else
|
|
info "next:"
|
|
info " journalctl -fu vetting"
|
|
fi
|
|
printf '\n' >&2
|
|
info "pxe support:"
|
|
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
|