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