Full rewrite of the bash bastion.sh into a TypeScript application: - Fastify HTTP server with typed routes (dispatch, kickstart, API) - Commander CLI (serve, install, list, reprovision) - Kickstart templates as TypeScript template literals (no more heredoc hell) - dnsmasq management via execa subprocess - Merged machine list view (hardware + install info in one table) - Containerized via podman-compose (Dockerfile + docker-compose.yml) - All partition logic preserved (LVM, reprovision detection, role-based) Not yet tested end-to-end — needs VM validation before replacing bash version. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
// Discovery kickstart template.
|
|
// Boots Fedora installer, collects hardware info, POSTs to bastion, reboots.
|
|
// Never touches the disk.
|
|
|
|
export interface DiscoverKickstartParams {
|
|
serverIp: string;
|
|
httpPort: number;
|
|
}
|
|
|
|
export function renderDiscoverKickstart(params: DiscoverKickstartParams): string {
|
|
const bastionUrl = `http://${params.serverIp}:${params.httpPort}`;
|
|
|
|
return `# Lab Bastion -- Discovery Mode
|
|
# Collects hardware inventory and reboots. Does NOT install anything.
|
|
|
|
%pre --erroronfail --log=/tmp/discover.log
|
|
#!/bin/bash
|
|
set -x
|
|
|
|
# -- Collect hardware info from /proc, /sys, and available tools --
|
|
|
|
MAC=$(ip link show | awk '/ether/ && !/00:00:00:00/ {print $2; exit}')
|
|
PRODUCT=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo "unknown")
|
|
BOARD=$(cat /sys/class/dmi/id/board_name 2>/dev/null || echo "unknown")
|
|
SERIAL=$(cat /sys/class/dmi/id/product_serial 2>/dev/null || echo "unknown")
|
|
MANUFACTURER=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null || echo "unknown")
|
|
CPUMODEL=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2 | sed 's/^ //')
|
|
CPUCORES=$(grep -c '^processor' /proc/cpuinfo)
|
|
MEMGB=$(awk '/MemTotal/ {printf "%d", $2/1024/1024}' /proc/meminfo)
|
|
ARCHTYPE=$(uname -m)
|
|
|
|
# Disk info
|
|
DISKS_JSON=$(lsblk -Jb -o NAME,SIZE,TYPE,MODEL 2>/dev/null | python3 -c "
|
|
import sys, json
|
|
data = json.load(sys.stdin)
|
|
disks = [d for d in data.get('blockdevices', []) if d.get('type') == 'disk']
|
|
result = []
|
|
for d in disks:
|
|
size_gb = round(int(d.get('size', 0)) / 1073741824, 1)
|
|
result.append({
|
|
'name': d.get('name', '?'),
|
|
'size_gb': size_gb,
|
|
'model': (d.get('model') or 'unknown').strip()
|
|
})
|
|
print(json.dumps(result))
|
|
" 2>/dev/null || echo '[]')
|
|
|
|
# Network interfaces
|
|
NICS_JSON=$(ip -j link show 2>/dev/null | python3 -c "
|
|
import sys, json
|
|
nics = json.load(sys.stdin)
|
|
result = []
|
|
for n in nics:
|
|
if n.get('link_type') == 'loopback':
|
|
continue
|
|
result.append({
|
|
'name': n.get('ifname', '?'),
|
|
'mac': n.get('address', '?'),
|
|
'state': n.get('operstate', '?')
|
|
})
|
|
print(json.dumps(result))
|
|
" 2>/dev/null || echo '[]')
|
|
|
|
# -- Build and POST discovery payload --
|
|
|
|
PAYLOAD=$(python3 -c "
|
|
import json
|
|
print(json.dumps({
|
|
'mac': '$MAC',
|
|
'product': '$PRODUCT',
|
|
'board': '$BOARD',
|
|
'serial': '$SERIAL',
|
|
'manufacturer': '$MANUFACTURER',
|
|
'cpu_model': '$CPUMODEL',
|
|
'cpu_cores': int('$CPUCORES' or 0),
|
|
'memory_gb': int('$MEMGB' or 0),
|
|
'arch': '$ARCHTYPE',
|
|
'disks': $DISKS_JSON,
|
|
'nics': $NICS_JSON
|
|
}))
|
|
")
|
|
|
|
# POST to bastion
|
|
BASTION_URL="${bastionUrl}/api/discover"
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -sf -X POST "$BASTION_URL" \\
|
|
-H "Content-Type: application/json" \\
|
|
-d "$PAYLOAD" || true
|
|
else
|
|
python3 -c "
|
|
import urllib.request
|
|
req = urllib.request.Request('$BASTION_URL',
|
|
data=b'''$PAYLOAD''',
|
|
headers={'Content-Type': 'application/json'})
|
|
try:
|
|
urllib.request.urlopen(req, timeout=10)
|
|
except Exception as e:
|
|
print(f'POST failed: {e}')
|
|
"
|
|
fi
|
|
|
|
# -- Reboot -- do NOT let Anaconda proceed --
|
|
echo ""
|
|
echo "=== Discovery complete, rebooting ==="
|
|
echo ""
|
|
sleep 3
|
|
echo 1 > /proc/sys/kernel/sysrq
|
|
echo b > /proc/sysrq-trigger
|
|
sleep 5
|
|
reboot -f
|
|
|
|
%end
|
|
|
|
# Anaconda should never get here, but just in case:
|
|
reboot
|
|
`;
|
|
}
|