clawdie-iso/vps/firstboot-vps.sh
Sam & Claude 7ae1e694f9 Align VPS firstboot with modular pipeline (Sam & Claude)
Rewrite vps/firstboot-vps.sh as phase-1 only: partition disk,
create ZFS pool "clawdie", install FreeBSD base, inject firstboot
payload, install bootloader, reboot. On first HDD boot the standard
firstboot.sh modular pipeline runs (zfs detect, wizard, gpu, pkg,
ssh, env, system, tailscale, deploy).

Pre-baked clawdie.conf values get written to build.cfg with
TARGET=cloud so the wizard is skipped. Pool named "clawdie"
(not zroot) for pool detection compatibility.

Remove duplicate clawdie-vps-setup.sh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-04 20:04:22 +02:00

274 lines
9.6 KiB
Bash
Raw Blame History

#!/bin/sh
# firstboot-vps.sh — Phase 1: Install FreeBSD to disk from mfsBSD
#
# Runs on mfsBSD (FreeBSD in RAM) after SSH login.
# Partitions disk, installs base FreeBSD, injects the clawdie-iso
# firstboot payload, and reboots. On first HDD boot, the standard
# firstboot.sh pipeline handles everything else (wizard, packages,
# GPU, .env, deploy).
#
# Usage:
# Interactive: /usr/local/share/clawdie-iso/firstboot-vps.sh
# Headless: copy clawdie.conf.tpl → clawdie.conf, edit, then run
#
# Flow:
# 1. Detect target disk
# 2. Optionally read clawdie.conf for pre-baked values
# 3. Partition disk (GPT: EFI + swap + ZFS)
# 4. Create ZFS pool "clawdie" with standard datasets
# 5. Install FreeBSD base + kernel
# 6. Inject firstboot payload (same as installerconfig on USB)
# 7. Install bootloader + reboot
#
# After reboot, firstboot.sh runs the modular shell-*.sh pipeline:
# zfs detect → wizard → gpu → pkg → ssh → env → system → tailscale → deploy
set -e
SHARE="${SHARE:-/usr/local/share/clawdie-iso}"
LOG="/var/log/clawdie-vps-install.log"
. "${SHARE}/build.cfg"
# ── Helpers <20><>──────────────────────────────────────────────────────────────
die() { echo "ERROR: $1" >&2; exit 1; }
log_vps() { echo "$(date '+%H:%M:%S') $1" | tee -a "$LOG"; }
gen_password() { openssl rand -base64 32 | tr -d '\n/+=' | head -c 24; }
detect_disk() {
for d in /dev/nda0 /dev/nvd0 /dev/da0 /dev/vtbd0 /dev/ada0; do
if [ -e "$d" ]; then
echo "$d"
return 0
fi
done
die "Cannot detect target disk"
}
# ── Pre-baked config (headless) ───────────────────────────────────────────
if [ -f "${SHARE}/clawdie.conf" ]; then
. "${SHARE}/clawdie.conf"
log_vps "[vps] Loaded clawdie.conf"
fi
# ── Detect disk ───────────────────────────────────────────────────────────
DISK=$(detect_disk)
log_vps "[vps] Target disk: ${DISK}"
# ── Interactive confirmation (if TTY available) ──────────────────────────
if [ -t 0 ] && command -v bsddialog >/dev/null 2>&1; then
_dialog() { bsddialog --backtitle "Clawdie-VPS Setup" "$@" 2>&1; }
_dialog --msgbox "\
Clawdie-VPS Installer
Target disk: ${DISK}
ALL DATA ON THIS DISK WILL BE ERASED.
After installation, the system will reboot and
run the standard Clawdie firstboot wizard." 12 60
if ! _dialog --yesno "\
Confirm: ERASE ALL DATA on ${DISK} and install FreeBSD?" 8 60; then
die "Cancelled."
fi
# Optional: pre-set TARGET for cloud (skip wizard on reboot)
if [ -n "${ASSISTANT_NAME:-}" ] && [ -n "${AGENT_DOMAIN:-}" ] && [ -n "${TZ:-}" ]; then
_vps_target="cloud"
_dialog --msgbox "\
Pre-baked config detected:
Agent: ${ASSISTANT_NAME}
Domain: ${AGENT_DOMAIN}
TZ: ${TZ}
Wizard will be skipped on first boot." 12 60
else
_vps_target="baremetal"
fi
elif [ -t 0 ]; then
echo "============================================"
echo " Clawdie-VPS Installer"
echo " Target disk: ${DISK}"
echo " ALL DATA WILL BE ERASED."
echo "============================================"
printf "Continue? (yes/no): "
read _confirm
[ "$_confirm" = "yes" ] || die "Cancelled."
_vps_target="baremetal"
else
# No TTY — require clawdie.conf
[ -n "${ASSISTANT_NAME:-}" ] || die "No TTY and no ASSISTANT_NAME in clawdie.conf"
_vps_target="cloud"
fi
# ── Step 1: Partition disk ────────────────────────────────────────────────
log_vps "[vps] [1/5] Partitioning ${DISK}..."
gpart destroy -F "${DISK}" 2>/dev/null || true
gpart create -s gpt "${DISK}"
gpart add -t efi -s 260M -l boot "${DISK}"
gpart add -t freebsd-swap -s 2G -l swap "${DISK}"
gpart add -t freebsd-zfs -l zroot "${DISK}"
newfs_msdos /dev/gpt/boot
# ── Step 2: Create ZFS pool ──────────────────────────────────────────────
log_vps "[vps] [2/5] Creating ZFS pool 'clawdie'..."
ZFS_PART="${DISK}p3"
[ -e "/dev/gpt/zroot" ] && ZFS_PART="/dev/gpt/zroot"
zpool create -f -m none -R /mnt clawdie "$ZFS_PART"
zfs create -o mountpoint=none clawdie/ROOT
zfs create -o mountpoint=/ clawdie/ROOT/default
zfs create -o mountpoint=/tmp -o setuid=off clawdie/tmp
zfs create -o mountpoint=/usr -o canmount=off clawdie/usr
zfs create -o mountpoint=/home clawdie/home
zfs create -o mountpoint=/var -o canmount=off clawdie/var
zfs create -o mountpoint=/var/cache -o setuid=off clawdie/var/cache
zfs create -o mountpoint=/var/log -o setuid=off clawdie/var/log
zfs create -o mountpoint=/var/tmp -o setuid=off clawdie/var/tmp
zfs create -o mountpoint=/var/db clawdie/var/db
zfs set compression=lz4 clawdie
zfs set atime=off clawdie
# ── Step 3: Install FreeBSD base ─────────────────────────────────────────
log_vps "[vps] [3/5] Installing FreeBSD ${FREEBSD_VERSION} base..."
BASE_URL="https://download.freebsd.org/releases/${FREEBSD_ARCH}/${FREEBSD_VERSION}"
cd /tmp
fetch "${BASE_URL}/base.txz" || die "Failed to fetch base.txz"
fetch "${BASE_URL}/kernel.txz" || die "Failed to fetch kernel.txz"
tar -xpf base.txz -C /mnt
tar -xpf kernel.txz -C /mnt
# fstab
cat > /mnt/etc/fstab <<EOF
/dev/gpt/boot /boot/efi msdosfs rw 0 0
/dev/gpt/swap none swap sw 0 0
EOF
# loader.conf
cat > /mnt/boot/loader.conf <<EOF
zfs_load="YES"
EOF
# Minimal rc.conf (firstboot.sh will configure the rest)
cat > /mnt/etc/rc.conf <<EOF
zfs_enable="YES"
sshd_enable="YES"
ifconfig_DEFAULT="DHCP"
clawdie_firstboot_enable="YES"
EOF
# Timezone
chroot /mnt tzsetup "${TZ:-UTC}"
# Create clawdie user
chroot /mnt pw useradd -n clawdie -u 1001 -m -G wheel -s /bin/sh
_root_pw=$(gen_password)
_user_pw=$(gen_password)
echo "root:$(openssl passwd -6 "$_root_pw")" | chroot /mnt chpass -H root
echo "clawdie:$(openssl passwd -6 "$_user_pw")" | chroot /mnt chpass -H clawdie
# sudo for wheel
mkdir -p /mnt/usr/local/etc/sudoers.d
cat > /mnt/usr/local/etc/sudoers.d/wheel <<EOF
%wheel ALL=(ALL) NOPASSWD: ALL
EOF
chmod 440 /mnt/usr/local/etc/sudoers.d/wheel
# ── Step 4: Inject firstboot payload ─────────────────────────────────────
# Same as installerconfig does for USB — copy firstboot scripts + packages + tarball
log_vps "[vps] [4/5] Injecting firstboot payload..."
HDD_SHARE="/mnt/usr/local/share/clawdie-iso"
HDD_RCD="/mnt/usr/local/etc/rc.d"
mkdir -p "$HDD_SHARE"
cp -r "${SHARE}/firstboot" "${HDD_SHARE}/"
cp "${SHARE}/build.cfg" "${HDD_SHARE}/"
# Override TARGET in the HDD copy of build.cfg if pre-baked
if [ "$_vps_target" = "cloud" ]; then
sed -i '' "s/^TARGET=.*/TARGET=\"cloud\"/" "${HDD_SHARE}/build.cfg"
# Bake identity vars
[ -n "${ASSISTANT_NAME:-}" ] && sed -i '' "s/^ASSISTANT_NAME=.*/ASSISTANT_NAME=\"${ASSISTANT_NAME}\"/" "${HDD_SHARE}/build.cfg"
[ -n "${AGENT_DOMAIN:-}" ] && sed -i '' "s/^AGENT_DOMAIN=.*/AGENT_DOMAIN=\"${AGENT_DOMAIN}\"/" "${HDD_SHARE}/build.cfg"
[ -n "${TZ:-}" ] && sed -i '' "s|^TZ=.*|TZ=\"${TZ}\"|" "${HDD_SHARE}/build.cfg"
[ -n "${AGENT_GENDER:-}" ] && sed -i '' "s/^AGENT_GENDER=.*/AGENT_GENDER=\"${AGENT_GENDER}\"/" "${HDD_SHARE}/build.cfg"
log_vps "[vps] Pre-baked config written to build.cfg (TARGET=cloud)"
fi
# Copy tarball
if [ -f "${SHARE}/clawdie-ai.tar.gz" ]; then
cp "${SHARE}/clawdie-ai.tar.gz" "${HDD_SHARE}/"
log_vps "[vps] Clawdie-AI tarball copied"
else
log_vps "[vps] WARNING: No clawdie-ai.tar.gz found — deploy step will fail"
fi
# Copy packages if bundled
if [ -d "${SHARE}/packages" ]; then
cp -r "${SHARE}/packages" "${HDD_SHARE}/"
log_vps "[vps] Package repo copied"
fi
# Make scripts executable
chmod +x "${HDD_SHARE}/firstboot/firstboot.sh"
for sh in "${HDD_SHARE}/firstboot/shell-"*.sh; do
chmod +x "$sh"
done
chmod +x "${HDD_SHARE}/firstboot/zfs-pool-detect.sh" 2>/dev/null || true
chmod +x "${HDD_SHARE}/firstboot/zfs-pool-migrate.sh" 2>/dev/null || true
chmod +x "${HDD_SHARE}/firstboot/maintenance-mode.sh" 2>/dev/null || true
# Install rc.d service
mkdir -p "$HDD_RCD"
cp "${SHARE}/firstboot/rc.d/clawdie-firstboot" "${HDD_RCD}/clawdie-firstboot"
chmod +x "${HDD_RCD}/clawdie-firstboot"
# ── Step 5: Install bootloader + reboot ──────────────────────────────────
log_vps "[vps] [5/5] Installing EFI bootloader..."
mkdir -p /mnt/boot/efi
mount -t msdosfs /dev/gpt/boot /mnt/boot/efi
mkdir -p /mnt/boot/efi/EFI/BOOT
cp /mnt/boot/loader.efi /mnt/boot/efi/EFI/BOOT/BOOTX64.EFI
umount /mnt/boot/efi
zpool set bootfs=clawdie/ROOT/default clawdie
log_vps "[vps] Installation complete."
echo ""
echo "============================================"
echo " FreeBSD installed to ${DISK}"
echo " Pool: clawdie"
echo ""
echo " On first HDD boot, the Clawdie firstboot"
echo " wizard will run automatically."
echo ""
echo " Root password: ${_root_pw}"
echo " User password: ${_user_pw}"
echo " (also available in ${LOG})"
echo "============================================"
echo ""
log_vps "[vps] root=${_root_pw} clawdie=${_user_pw}"
# Export and reboot
log_vps "[vps] Exporting pool and rebooting in 10 seconds..."
sleep 10
umount /mnt/boot/efi 2>/dev/null || true
zpool export clawdie
reboot