8b3d9a312e
CI / Lint + build + test (push) Failing after 5m15s
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.
147 lines
4.7 KiB
Cheetah
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}."
|