Files
Vetting/internal/web/register/quick.sh.tmpl
T
josh 8b3d9a312e
CI / Lint + build + test (push) Failing after 5m15s
Add quick-register one-liner for target-host registration
Operator pastes `curl -fsSL $ORCH/register/quick.sh | sudo bash` on the
target host (pre-wipe). The script probes MAC + CPU/RAM/disks/NICs/GPUs,
emits an expected-spec YAML, and POSTs to a new LAN-trusted JSON
endpoint /api/v1/hosts. The register page shows the command prefilled
with the orchestrator URL; the manual form moves into a collapsible
"Register manually" disclosure.
2026-04-17 22:50:54 -04:00

147 lines
4.7 KiB
Cheetah

#!/usr/bin/env bash
# Vetting quick-register.
#
# Run on the target host (any Linux with root) before the host is wiped:
# curl -fsSL {{.OrchestratorURL}}/register/quick.sh | sudo bash
#
# Detects the primary NIC's MAC, probes hardware (CPU / RAM / disks /
# NICs / GPUs) into an expected-spec YAML, and POSTs everything to
# {{.OrchestratorURL}}/api/v1/hosts. After registration, go to the
# orchestrator's dashboard and click "Start vetting" for the new host.
#
# Env overrides (all optional):
# NAME Host display name (default: `hostname -s`)
# MAC Force a specific MAC (default: autodetect)
# WOL_BROADCAST WoL broadcast IP (default: primary iface broadcast)
# WOL_PORT WoL UDP port (default: 9)
# NOTES Free-text notes
# ORCH_URL Override orchestrator base URL
set -euo pipefail
ORCH_URL="${ORCH_URL:-{{.OrchestratorURL}}}"
if [[ -z "${ORCH_URL}" ]]; then
echo "ERROR: ORCH_URL is empty; pass it via env." >&2
exit 1
fi
primary_iface() {
# Pick the first physical ethernet-style interface that isn't a loopback,
# bridge, veth, docker, virbr, bond, tun, or tap.
ip -o link show 2>/dev/null \
| awk '$0 ~ /link\/ether/ {
name=$2; sub(":","",name)
if (name ~ /^(lo|docker|br-|veth|virbr|bond|tun|tap|wlan|wlp)/) next
print name; exit
}'
}
IFACE="$(primary_iface || true)"
if [[ -z "${IFACE}" ]]; then
echo "ERROR: could not pick a primary network interface." >&2
exit 1
fi
NAME="${NAME:-$(hostname -s 2>/dev/null || hostname)}"
MAC="${MAC:-$(cat /sys/class/net/${IFACE}/address 2>/dev/null || true)}"
if [[ -z "${MAC}" ]]; then
echo "ERROR: could not read MAC for ${IFACE}." >&2
exit 1
fi
if [[ -z "${WOL_BROADCAST:-}" ]]; then
WOL_BROADCAST="$(ip -o -4 addr show dev "${IFACE}" 2>/dev/null \
| awk '{for(i=1;i<=NF;i++) if($i=="brd") {print $(i+1); exit}}')"
fi
WOL_BROADCAST="${WOL_BROADCAST:-255.255.255.255}"
WOL_PORT="${WOL_PORT:-9}"
# --- Hardware probes ---
cores="$(nproc 2>/dev/null || echo 0)"
cpu_model="$(awk -F: '/^model name/ {sub(/^ */, "", $2); print $2; exit}' /proc/cpuinfo 2>/dev/null || true)"
mem_gib="$(awk '/^MemTotal:/ {printf "%d", ($2/1024/1024) + 0.5; exit}' /proc/meminfo 2>/dev/null || echo 0)"
disk_yaml=""
while read -r name size serial; do
[[ -z "${name}" ]] && continue
[[ "${name}" =~ ^(sd|nvme|vd|hd) ]] || continue
[[ -z "${serial}" ]] && continue
size_gb=$(( size / 1000 / 1000 / 1000 ))
disk_yaml+=" - serial: \"${serial}\"
size_gb: ${size_gb}
"
done < <(lsblk -dn -b -o NAME,SIZE,SERIAL 2>/dev/null || true)
nic_yaml=""
for iface_dir in /sys/class/net/*; do
iface_name="$(basename "${iface_dir}")"
[[ "${iface_name}" =~ ^(lo|docker|br-|veth|virbr|bond|tun|tap)$ ]] && continue
nic_mac="$(cat "${iface_dir}/address" 2>/dev/null || true)"
[[ -z "${nic_mac}" || "${nic_mac}" == "00:00:00:00:00:00" ]] && continue
speed_mbps="$(cat "${iface_dir}/speed" 2>/dev/null || echo 0)"
if [[ "${speed_mbps}" =~ ^[0-9]+$ ]] && (( speed_mbps > 0 )); then
speed_gbps=$(( (speed_mbps + 999) / 1000 ))
else
speed_gbps=0
fi
nic_yaml+=" - mac: \"${nic_mac}\"
speed_gbps: ${speed_gbps}
"
done
gpu_yaml=""
if command -v lspci >/dev/null 2>&1; then
while IFS= read -r gpu; do
[[ -z "${gpu}" ]] && continue
gpu_yaml+=" - model: \"${gpu}\"
"
done < <(lspci -mm 2>/dev/null | awk -F\" '/"(VGA|3D|Display)/ {print $6}')
fi
# Assemble the YAML spec. Empty sections are omitted so the diff engine
# skips them rather than demanding empty arrays on the actual side.
spec="cpu:
model: \"${cpu_model}\"
logical_cores: ${cores}
memory:
total_gib: ${mem_gib}
"
[[ -n "${disk_yaml}" ]] && spec+="disks:
${disk_yaml}"
[[ -n "${nic_yaml}" ]] && spec+="nics:
${nic_yaml}"
[[ -n "${gpu_yaml}" ]] && spec+="gpus:
${gpu_yaml}"
# --- JSON escape (backslash, double-quote, newline, tab, CR) ---
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\t'/\\t}"
s="${s//$'\r'/\\r}"
s="${s//$'\n'/\\n}"
printf '%s' "${s}"
}
payload=$(cat <<EOF
{
"name": "$(json_escape "${NAME}")",
"mac": "$(json_escape "${MAC}")",
"wol_broadcast_ip": "$(json_escape "${WOL_BROADCAST}")",
"wol_port": ${WOL_PORT},
"expected_spec_yaml": "$(json_escape "${spec}")",
"notes": "$(json_escape "${NOTES:-registered via quick-register on $(date -Is)}")"
}
EOF
)
echo "Registering ${NAME} (${MAC}) with ${ORCH_URL}..."
resp="$(curl -fsS -X POST \
-H 'Content-Type: application/json' \
-d "${payload}" \
"${ORCH_URL}/api/v1/hosts")"
echo "OK: ${resp}"
echo
echo "Open ${ORCH_URL}/ and click 'Start vetting' on ${NAME}."