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
|
||
|
|
`;
|
||
|
|
}
|