diff --git a/bastion/bastion/.gitignore b/bastion/bastion/.gitignore new file mode 100644 index 0000000..c6a6369 --- /dev/null +++ b/bastion/bastion/.gitignore @@ -0,0 +1,4 @@ + +# Asahi build artifacts (large) +.asahi-cache/ +asahi-repo/*.zip diff --git a/bastion/src/bastion/src/routes/asahi.ts b/bastion/src/bastion/src/routes/asahi.ts index 3622cab..ebdbfff 100644 --- a/bastion/src/bastion/src/routes/asahi.ts +++ b/bastion/src/bastion/src/routes/asahi.ts @@ -102,7 +102,8 @@ echo " - Standard Asahi boot infrastructure (m1n1 + U-Boot)" echo " - Fedora Asahi Remix root partition" echo " - LVM data partition (remaining space)" echo "" -echo " On first boot, LVM volumes are created automatically." +echo " After first boot, SSH in and set up LVM:" +echo " ssh lab@ 'curl -sf \${BASTION}/asahi/firstboot.sh | sudo bash'" echo "" # Run the installer @@ -150,7 +151,7 @@ fi app.get<{ Querystring: { hostname?: string; role?: string; mac?: string; user?: string }; }>("/asahi/firstboot.sh", async (request, reply) => { - const hostname = request.query.hostname ?? "mac-studio"; + const hostname = request.query.hostname ?? "unknown"; const role = (request.query.role ?? "infra") as Role; const mac = request.query.mac ?? "unknown"; const user = request.query.user ?? config.adminUser; diff --git a/bastion/src/bastion/src/templates/asahi-firstboot.sh.ts b/bastion/src/bastion/src/templates/asahi-firstboot.sh.ts index 49af7ab..cbc1062 100644 --- a/bastion/src/bastion/src/templates/asahi-firstboot.sh.ts +++ b/bastion/src/bastion/src/templates/asahi-firstboot.sh.ts @@ -222,21 +222,32 @@ lvs labvg fi # end if/else for reprovision vs fresh install -# ── Set hostname ───────────────────────────────────────────────── -hostnamectl set-hostname "${hostname}" +# ── Set hostname (use configured value, or keep existing) ──────── +CONF_HOSTNAME="${hostname}" +if [ "$CONF_HOSTNAME" != "unknown" ] && [ -n "$CONF_HOSTNAME" ]; then + hostnamectl set-hostname "$CONF_HOSTNAME" +fi +ACTUAL_HOSTNAME=$(hostname) + +# ── Detect MAC address ─────────────────────────────────────────── +CONF_MAC="${mac}" +if [ "$CONF_MAC" = "unknown" ] || [ -z "$CONF_MAC" ]; then + CONF_MAC=$(ip -o link show | grep -v "lo:" | grep "state UP" | head -1 | grep -oP 'link/ether \\K[^ ]+' || echo "unknown") +fi # ── Configure admin user ───────────────────────────────────────── -if ! id "${adminUser}" &>/dev/null; then - useradd -m -G wheel "${adminUser}" - echo "${adminUser} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/${adminUser} - chmod 440 /etc/sudoers.d/${adminUser} +ADMIN="${adminUser}" +if ! id "$ADMIN" &>/dev/null; then + useradd -m -G wheel "$ADMIN" + echo "$ADMIN ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$ADMIN + chmod 440 /etc/sudoers.d/$ADMIN fi -ADMIN_SSH="/home/${adminUser}/.ssh" +ADMIN_SSH="/home/$ADMIN/.ssh" mkdir -p "$ADMIN_SSH" chmod 700 "$ADMIN_SSH" ${sshKeyBlock} chmod 600 "$ADMIN_SSH/authorized_keys" -chown -R ${adminUser}:${adminUser} "$ADMIN_SSH" +chown -R $ADMIN:$ADMIN "$ADMIN_SSH" # Also authorize root mkdir -p /root/.ssh @@ -250,9 +261,9 @@ sed -i 's/^#\\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/ssh # ── Write provisioning metadata ────────────────────────────────── cat > /etc/lab-provisioned << LABMETA -hostname=${hostname} +hostname=$ACTUAL_HOSTNAME role=${role} -mac=${mac} +mac=$CONF_MAC provisioned_at=$(date -Iseconds) method=asahi-firstboot LABMETA @@ -262,9 +273,9 @@ IP=$(hostname -I | awk '{print $1}') echo "Registering with bastion at ${serverIp}:${httpPort}..." curl -sf -X POST "http://${serverIp}:${httpPort}/api/register" \\ -H "Content-Type: application/json" \\ - -d "{\\"mac\\":\\"${mac}\\",\\"hostname\\":\\"${hostname}\\",\\"role\\":\\"${role}\\",\\"ip\\":\\"$IP\\"}" \\ - 2>/dev/null && echo " Registered as ${hostname} ($IP)" \\ - || echo " WARNING: Could not reach bastion — register manually with: labctl provision register ${mac} ${hostname} --role ${role} --ip $IP" + -d "{\\"mac\\":\\"$CONF_MAC\\",\\"hostname\\":\\"$ACTUAL_HOSTNAME\\",\\"role\\":\\"${role}\\",\\"ip\\":\\"$IP\\"}" \\ + 2>/dev/null && echo " Registered as $ACTUAL_HOSTNAME ($IP)" \\ + || echo " WARNING: Could not reach bastion — register manually with: labctl provision register $CONF_MAC $ACTUAL_HOSTNAME --role ${role} --ip $IP" # ── Mark done ──────────────────────────────────────────────────── touch "$MARKER" diff --git a/bastion/src/bastion/tests/asahi.test.ts b/bastion/src/bastion/tests/asahi.test.ts index ffe0ec5..b3f223c 100644 --- a/bastion/src/bastion/tests/asahi.test.ts +++ b/bastion/src/bastion/tests/asahi.test.ts @@ -184,7 +184,8 @@ describe("renderFirstbootScript", () => { it("sets hostname", () => { const script = renderFirstbootScript({ ...baseParams, role: "worker" }); - expect(script).toContain('hostnamectl set-hostname "test-node"'); + expect(script).toContain('CONF_HOSTNAME="test-node"'); + expect(script).toContain("hostnamectl set-hostname"); }); it("includes bastion self-registration", () => { diff --git a/bastion/src/cli/src/commands/asahi.ts b/bastion/src/cli/src/commands/asahi.ts index 7cee58c..3d334c8 100644 --- a/bastion/src/cli/src/commands/asahi.ts +++ b/bastion/src/cli/src/commands/asahi.ts @@ -59,9 +59,9 @@ export function registerAsahiCommand(parent: Command): void { console.log(` labvg/longhorn (remaining space)${RESET}`); console.log(""); console.log(` After first boot, SSH in and run the firstboot script:`); - console.log(` ${BOLD}ssh root@ 'curl -sf ${bastionUrl}/asahi/firstboot.sh?hostname=\\&role=infra | bash'${RESET}`); + console.log(` ${BOLD}ssh lab@ 'curl -sf ${bastionUrl}/asahi/firstboot.sh | sudo bash'${RESET}`); console.log(""); - console.log(` This sets up LVM and self-registers with the bastion.`); + console.log(` This sets up LVM, detects hostname/MAC, and self-registers.`); console.log(` Then install k3s:`); console.log(` ${BOLD}labctl app k3s install --role infra${RESET}`); console.log("");