import { execFileSync, execSync } from "node:child_process"; import { existsSync, mkdirSync } from "node:fs"; import { CONFIG } from "./config.js"; import { ensureBridge, ensureNat } from "./network.js"; import { ensureSshKeypair } from "./rootfs.js"; function log(msg: string) { process.stderr.write(`[setup] ${msg}\n`); } function download(url: string, dest: string) { execFileSync("curl", ["-fSL", "-o", dest, url], { stdio: ["pipe", "pipe", "inherit"], timeout: 300_000, }); } export async function runSetup() { log("Setting up fireclaw..."); // Create directories mkdirSync(CONFIG.baseDir, { recursive: true }); mkdirSync(CONFIG.runsDir, { recursive: true }); mkdirSync(CONFIG.socketDir, { recursive: true }); // Download kernel if (existsSync(CONFIG.kernelPath)) { log("Kernel already exists, skipping download."); } else { log("Downloading kernel..."); download(CONFIG.assets.kernelUrl, CONFIG.kernelPath); log("Kernel downloaded."); } // Download and convert rootfs if (existsSync(CONFIG.baseRootfs)) { log("Base rootfs already exists, skipping download."); } else { log("Downloading Alpine Linux minirootfs..."); const arch = execFileSync("uname", ["-m"], { encoding: "utf-8" }).trim(); const alpineTar = `${CONFIG.baseDir}/alpine-minirootfs.tar.gz`; // Find latest Alpine version const listing = execFileSync( "curl", ["-fsSL", "https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/" + arch + "/"], { encoding: "utf-8", timeout: 30_000 } ); const match = listing.match( new RegExp(`alpine-minirootfs-[\\d.]+-${arch}\\.tar\\.gz`, "g") ); if (!match || match.length === 0) throw new Error("Could not find Alpine minirootfs"); const filename = match.sort().pop()!; download( `https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/${arch}/${filename}`, alpineTar ); log("Alpine downloaded. Building ext4 rootfs..."); // Create ext4 image and unpack Alpine const ext4Mount = "/tmp/fireclaw-alpine"; mkdirSync(ext4Mount, { recursive: true }); try { execFileSync("truncate", ["-s", "256M", CONFIG.baseRootfs], { stdio: "pipe", }); execFileSync("sudo", ["/usr/sbin/mkfs.ext4", "-q", CONFIG.baseRootfs], { stdio: "pipe", }); execFileSync("sudo", ["mount", CONFIG.baseRootfs, ext4Mount], { stdio: "pipe", }); execFileSync("sudo", ["tar", "xzf", alpineTar, "-C", ext4Mount], { stdio: "pipe", }); // DNS execSync( `echo "nameserver 8.8.8.8" | sudo tee ${ext4Mount}/etc/resolv.conf > /dev/null` ); // Inittab for serial console execSync(`sudo tee ${ext4Mount}/etc/inittab > /dev/null << 'EOF' ::sysinit:/sbin/openrc sysinit ::sysinit:/sbin/openrc boot ::sysinit:/sbin/openrc default ttyS0::respawn:/sbin/getty -L 115200 ttyS0 vt100 ::ctrlaltdel:/sbin/reboot ::shutdown:/sbin/openrc shutdown EOF`); // Hostname execSync( `echo "fireclaw" | sudo tee ${ext4Mount}/etc/hostname > /dev/null` ); // Allow root login (no password, SSH key auth only) execSync( `sudo sed -i 's/root:x:/root::/' ${ext4Mount}/etc/passwd` ); // Install base packages log("Installing Alpine packages (openssh, python3, curl, ca-certificates)..."); execFileSync( "sudo", ["chroot", ext4Mount, "/bin/sh", "-c", "apk update && apk add --no-cache openssh-server ca-certificates curl jq python3 bash openrc && " + "rc-update add sshd default && ssh-keygen -A && " + "echo 'PermitRootLogin prohibit-password' >> /etc/ssh/sshd_config && " + "mkdir -p /run/openrc && touch /run/openrc/softlevel"], { stdio: "pipe", timeout: 120_000 } ); // Networking init script execSync(`sudo tee ${ext4Mount}/etc/init.d/networking > /dev/null << 'EOF' #!/sbin/openrc-run depend() { need localmount; } start() { ip link set lo up; return 0; } EOF`); execFileSync("sudo", ["chmod", "+x", `${ext4Mount}/etc/init.d/networking`], { stdio: "pipe" }); execFileSync("sudo", ["chroot", ext4Mount, "rc-update", "add", "networking", "boot"], { stdio: "pipe" }); log("Alpine rootfs built."); } finally { try { execFileSync("sudo", ["umount", ext4Mount], { stdio: "pipe" }); } catch {} try { execFileSync("rmdir", [ext4Mount], { stdio: "pipe" }); } catch {} try { execFileSync("rm", ["-f", alpineTar], { stdio: "pipe" }); } catch {} } } // Generate SSH keypair log("Ensuring SSH keypair..."); ensureSshKeypair(); // Set up bridge and NAT log("Setting up network bridge..."); ensureBridge(); ensureNat(); log("Setup complete! Run 'fireclaw run \"uname -a\"' to test."); }