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>
This commit is contained in:
Sam & Claude 2026-04-05 11:31:59 +00:00 committed by 123kupola
parent 8cc7b2dcaf
commit 7ae1e694f9
4 changed files with 190 additions and 462 deletions

View file

@ -124,7 +124,8 @@ cp "${SCRIPT_DIR}/build.cfg" "${CLAWDIE_SHARE}/"
if [ -d "${SCRIPT_DIR}/vps" ]; then
cp "${SCRIPT_DIR}/vps/firstboot-vps.sh" "${CLAWDIE_SHARE}/firstboot-vps.sh"
chmod +x "${CLAWDIE_SHARE}/firstboot-vps.sh"
cp "${SCRIPT_DIR}/vps/clawdie.conf.tpl" "${CLAWDIE_SHARE}/clawdie.conf.tpl"
[ -f "${SCRIPT_DIR}/vps/clawdie.conf.tpl" ] && \
cp "${SCRIPT_DIR}/vps/clawdie.conf.tpl" "${CLAWDIE_SHARE}/clawdie.conf.tpl"
fi
# Copy Clawdie-AI tarball

View file

@ -91,7 +91,7 @@ echo " ssh -p 1022 mfsbsd@$(curl -s ifconfig.me)"
echo " Password: ${MFSBSD_PASSWORD}"
echo ""
echo " Then run:"
echo " /usr/local/share/clawdie-iso/firstboot.sh"
echo " /usr/local/share/clawdie-iso/firstboot-vps.sh"
echo "============================================"
echo ""
read -p "Press Enter to reboot now (or Ctrl+C to abort)..."

View file

@ -1,248 +0,0 @@
#!/bin/sh
# clawdie-vps-setup.sh — Install Clawdie-AI on VPS from mfsBSD
#
# This script runs on mfsBSD (FreeBSD in RAM) and installs
# FreeBSD + Clawdie-AI to disk.
#
# Usage (on Vultr with mfsBSD ISO mounted):
# 1. Upload mfsBSD ISO from https://depenguin.me/files/mfsbsd-15.0-RELEASE-amd64.iso
# 2. Boot server from ISO
# 3. SSH: ssh mfsbsd@<your-ip> (password: mfsroot)
# 4. Run: fetch -o - https://clawdie.si/vps-setup.sh | sh
#
# Or with pre-config:
# fetch -o - https://clawdie.si/vps-setup.sh > setup.sh
# edit setup.sh to set ASSISTANT_NAME etc at the top
# sh setup.sh
set -e
# ============================================
# CONFIGURATION (edit before running)
# ============================================
ASSISTANT_NAME="${ASSISTANT_NAME:-Clawdie}"
AGENT_NAME="${AGENT_NAME:-$(echo "$ASSISTANT_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/-*$//')}"
AGENT_DOMAIN="${AGENT_DOMAIN:-${AGENT_NAME}.local}"
TZ="${TZ:-UTC}"
PI_PROVIDER="${PI_PROVIDER:-anthropic}"
CLAWDIE_VERSION="${CLAWDIE_VERSION:-0.8.2}"
# ============================================
# HELPERS
# ============================================
log() { echo "==> $1"; }
die() { echo "ERROR: $1" >&2; exit 1; }
gen_secret() { openssl rand -base64 32 | tr -d '\n/+=' | head -c 32; }
detect_disk() {
for d in /dev/nvme0n1 /dev/nda0 /dev/da0 /dev/vtbd0 /dev/vda /dev/sda; do
[ -e "$d" ] && echo "$d" && return 0
done
die "Cannot detect target disk"
}
# ============================================
# MAIN
# ============================================
DISK=$(detect_disk)
clear
cat <<'BANNER'
╔═══════════════════════════════════════════════════════════════╗
║ Clawdie-VPS Installer ║
╠═══════════════════════════════════════════════════════════════╣
║ This will install FreeBSD + Clawdie-AI to disk. ║
║ ALL DATA ON DISK WILL BE ERASED. ║
╚═══════════════════════════════════════════════════════════════╝
BANNER
echo ""
echo "Target disk: ${DISK}"
echo "Agent name : ${ASSISTANT_NAME} (${AGENT_NAME})"
echo "Domain : ${AGENT_DOMAIN}"
echo "Timezone : ${TZ}"
echo "Provider : ${PI_PROVIDER}"
echo ""
if [ -z "$CLAWDIE_YES" ]; then
printf "Continue? ALL DATA ON ${DISK} WILL BE LOST! (yes/no): "
read CONFIRM
[ "$CONFIRM" = "yes" ] || die "Aborted."
fi
# --- generate secrets ---
log "Generating secrets..."
POSTGRES_ADMIN_PASSWORD=$(gen_secret)
SKILLS_DB_PASSWORD=$(gen_secret)
MEMORY_DB_PASSWORD=$(gen_secret)
STRAPI_DB_PASSWORD=$(gen_secret)
STRAPI_APP_KEYS="$(gen_secret),$(gen_secret)"
STRAPI_API_TOKEN_SALT=$(gen_secret)
STRAPI_ADMIN_JWT_SECRET=$(gen_secret)
STRAPI_TRANSFER_TOKEN_SALT=$(gen_secret)
STRAPI_JWT_SECRET=$(gen_secret)
SCREENSHOTS_PASSWORD=$(gen_secret)
ROOT_HASH=$(openssl passwd -6 "$(gen_secret)")
CLAWDIE_HASH=$(openssl passwd -6 "$(gen_secret)")
# --- partition disk ---
log "[1/6] 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
# --- create ZFS pool ---
log "[2/6] Creating ZFS pool..."
ZFS_PART="${DISK}p3"
[ -e "/dev/gpt/zroot" ] && ZFS_PART="/dev/gpt/zroot"
zpool create -f -m none -R /mnt zroot "$ZFS_PART"
zfs create -o mountpoint=none zroot/ROOT
zfs create -o mountpoint=/ zroot/ROOT/default
zfs create -o mountpoint=/tmp -o setuid=off zroot/tmp
zfs create -o mountpoint=/usr -o canmount=off zroot/usr
zfs create -o mountpoint=/home zroot/home
zfs create -o mountpoint=/var -o canmount=off zroot/var
zfs create -o mountpoint=/var/cache -o setuid=off zroot/var/cache
zfs create -o mountpoint=/var/log -o setuid=off zroot/var/log
zfs create -o mountpoint=/var/tmp -o setuid=off zroot/var/tmp
zfs create -o mountpoint=/var/db zroot/var/db
zfs set compression=lz4 zroot
zfs set atime=off zroot
# --- install base system ---
log "[3/6] Installing FreeBSD base system..."
BASE_URL="https://download.freebsd.org/releases/amd64/15.0-RELEASE"
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
# --- configure system ---
log "[4/6] Configuring system..."
# fstab
cat > /mnt/etc/fstab <<EOF
/dev/gpt/boot /boot/efi msdosfs rw 0 0
/dev/gpt/swap none swap sw 0 0
EOF
# rc.conf
cat > /mnt/etc/rc.conf <<EOF
hostname="${AGENT_NAME}"
zfs_enable="YES"
sshd_enable="YES"
clawdie_enable="YES"
ifconfig_DEFAULT="DHCP"
EOF
# loader.conf
cat > /mnt/boot/loader.conf <<EOF
zfs_load="YES"
EOF
# Timezone
chroot /mnt tzsetup "$TZ"
# Root password
echo "root:${ROOT_HASH}" | chroot /mnt chpass -H root
# Create clawdie user
chroot /mnt pw useradd -n clawdie -u 1001 -m -G wheel -s /bin/sh
echo "clawdie:${CLAWDIE_HASH}" | chroot /mnt chpass -H clawdie
# Enable sudo
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
# --- install packages ---
log "[5/6] Installing packages..."
mkdir -p /mnt/usr/local/etc/pkg/repos
cat > /mnt/usr/local/etc/pkg/repos/FreeBSD.conf <<EOF
FreeBSD: {
url: "pkg+https://pkg.FreeBSD.org/\${ABI}/latest",
mirror_type: "srv",
enabled: yes
}
EOF
chroot /mnt env ASSUME_ALWAYS_YES=YES pkg bootstrap
chroot /mnt pkg install -y sudo node24 npm git tmux bash postgresql17-client
# --- install Clawdie-AI ---
log "[6/6] Installing Clawdie-AI v${CLAWDIE_VERSION}..."
mkdir -p /mnt/home/clawdie/clawdie-ai
cd /tmp
fetch -o clawdie-ai.tar.gz "https://codeberg.org/Clawdie/Clawdie-AI/archive/v${CLAWDIE_VERSION}.tar.gz" || \
die "Failed to fetch Clawdie-AI"
tar -xzf clawdie-ai.tar.gz -C /mnt/home/clawdie/clawdie-ai --strip-components=1
chroot /mnt chown -R clawdie:clawdie /home/clawdie/clawdie-ai
# Create .env
cat > /mnt/home/clawdie/clawdie-ai/.env <<EOF
AGENT_NAME=${AGENT_NAME}
ASSISTANT_NAME=${ASSISTANT_NAME}
AGENT_DOMAIN=${AGENT_DOMAIN}
AGENT_INTERNAL_DOMAIN=${AGENT_NAME}.home.arpa
TZ=${TZ}
PI_TUI_PROVIDER=${PI_PROVIDER}
POSTGRES_ADMIN_PASSWORD=${POSTGRES_ADMIN_PASSWORD}
SKILLS_DB_PASSWORD=${SKILLS_DB_PASSWORD}
MEMORY_DB_PASSWORD=${MEMORY_DB_PASSWORD}
STRAPI_DB_PASSWORD=${STRAPI_DB_PASSWORD}
STRAPI_APP_KEYS=${STRAPI_APP_KEYS}
STRAPI_API_TOKEN_SALT=${STRAPI_API_TOKEN_SALT}
STRAPI_ADMIN_JWT_SECRET=${STRAPI_ADMIN_JWT_SECRET}
STRAPI_TRANSFER_TOKEN_SALT=${STRAPI_TRANSFER_TOKEN_SALT}
STRAPI_JWT_SECRET=${STRAPI_JWT_SECRET}
SCREENSHOTS_PASSWORD=${SCREENSHOTS_PASSWORD}
EOF
chmod 600 /mnt/home/clawdie/clawdie-ai/.env
chroot /mnt chown clawdie:clawdie /home/clawdie/clawdie-ai/.env
# npm prefix for clawdie user
echo "prefix=/home/clawdie/.npm-global" > /mnt/home/clawdie/.npmrc
mkdir -p /mnt/home/clawdie/.npm-global/bin
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> /mnt/home/clawdie/.shrc
chroot /mnt chown -R clawdie:clawdie /home/clawdie/.npmrc /home/clawdie/.npm-global /home/clawdie/.shrc
# --- install bootloader ---
log "Installing 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=zroot/ROOT/default zroot
# --- summary ---
cat <<SUMMARY
╔═══════════════════════════════════════════════════════════════╗
║ Installation Complete! ║
╠═══════════════════════════════════════════════════════════════╣
║ ║
║ Agent : ${ASSISTANT_NAME} (${AGENT_NAME})
║ Domain: ${AGENT_DOMAIN}
║ ║
║ Credentials saved to: ║
║ /home/clawdie/clawdie-ai/.env ║
║ ║
║ After reboot: ║
║ ssh clawdie@<this-ip> ║
cd ~/clawdie-ai && npm run install-all ║
║ ║
╚═══════════════════════════════════════════════════════════════╝
SUMMARY
log "Exporting ZFS pool and rebooting in 10 seconds..."
sleep 10
zpool export zroot
reboot

View file

@ -1,299 +1,274 @@
#!/bin/sh
# firstboot-vps.sh — Clawdie-AI first-boot setup for VPS
# firstboot-vps.sh — Phase 1: Install FreeBSD to disk from mfsBSD
#
# Runs on mfsBSD (FreeBSD in RAM) after SSH login.
# Uses bsddialog for interactive prompts, or reads from clawdie.conf.
# 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. Ask: agent name, domain, timezone
# 2. Auto-generate secrets
# 3. zfsinstall → install FreeBSD to disk
# 4. Chroot: install packages, extract Clawdie-AI, configure
# 5. Reboot into final system
# 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="/usr/local/share/clawdie-iso"
SHARE="${SHARE:-/usr/local/share/clawdie-iso}"
LOG="/var/log/clawdie-vps-install.log"
. "${SHARE}/build.cfg"
# --- helpers ---
dialog() { bsddialog --backtitle "Clawdie-VPS Setup" "$@" ; }
# ── Helpers <20><>──────────────────────────────────────────────────────────────
die() { echo "ERROR: $1" >&2; exit 1; }
gen_secret() {
openssl rand -base64 32 | tr -d '\n/+=' | head -c 32
}
log_vps() { echo "$(date '+%H:%M:%S') $1" | tee -a "$LOG"; }
gen_password() { openssl rand -base64 32 | tr -d '\n/+=' | head -c 24; }
# --- detect target disk ---
detect_disk() {
# Try NVMe first, then SATA
for d in /dev/nvme0n1 /dev/nda0 /dev/da0 /dev/vtbd0 /dev/vda /dev/sda; do
if [ -e "$d" ]; then
echo "$d"
return 0
fi
done
die "Cannot detect target 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"
}
DISK=$(detect_disk)
# ── Pre-baked config (headless) ───────────────────────────────────────────
echo "============================================"
echo " Clawdie-VPS Installer"
echo " Target disk: ${DISK}"
echo "============================================"
echo ""
# --- check if running headless ---
if [ ! -t 0 ]; then
echo "No TTY detected. Reading from ${SHARE}/clawdie.conf..."
if [ -f "${SHARE}/clawdie.conf" ]; then
. "${SHARE}/clawdie.conf"
else
die "No clawdie.conf found. Run interactively with TTY."
fi
else
# Interactive wizard
dialog --msgbox \
"\nWelcome to Clawdie-VPS Setup.\n\nThis will install FreeBSD + Clawdie-AI to disk.\nALL DATA ON ${DISK} WILL BE ERASED.\n\nPress Enter to begin." \
12 70
# --- agent identity ---
ASSISTANT_NAME=$(dialog --inputbox \
"Assistant name (displayed to users):" 8 50 "Clawdie" \
3>&1 1>&2 2>&3) || die "Cancelled."
AGENT_NAME=$(echo "$ASSISTANT_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/-*$//')
AGENT_DOMAIN=$(dialog --inputbox \
"Domain name for this agent:" 8 50 "${AGENT_NAME}.local" \
3>&1 1>&2 2>&3) || die "Cancelled."
TZ=$(dialog --inputbox \
"Timezone (IANA format):" 8 50 "UTC" \
3>&1 1>&2 2>&3) || die "Cancelled."
# --- provider selection ---
PI_PROVIDER=$(dialog --radiolist \
"LLM provider (key will be requested later):" 14 60 5 \
"anthropic" "Anthropic (Claude)" on \
"openai" "OpenAI (GPT-4)" off \
"openrouter" "OpenRouter" off \
"groq" "Groq" off \
"zai" "ZAI / GLM" off \
3>&1 1>&2 2>&3) || PI_PROVIDER="anthropic"
if [ -f "${SHARE}/clawdie.conf" ]; then
. "${SHARE}/clawdie.conf"
log_vps "[vps] Loaded clawdie.conf"
fi
# --- auto-generate secrets ---
POSTGRES_ADMIN_PASSWORD=$(gen_secret)
SKILLS_DB_PASSWORD=$(gen_secret)
MEMORY_DB_PASSWORD=$(gen_secret)
STRAPI_DB_PASSWORD=$(gen_secret)
STRAPI_APP_KEYS="$(gen_secret),$(gen_secret)"
STRAPI_API_TOKEN_SALT=$(gen_secret)
STRAPI_ADMIN_JWT_SECRET=$(gen_secret)
STRAPI_TRANSFER_TOKEN_SALT=$(gen_secret)
STRAPI_JWT_SECRET=$(gen_secret)
SCREENSHOTS_PASSWORD=$(gen_secret)
# ── Detect disk ───────────────────────────────────────────────────────────
# --- summary ---
dialog --msgbox \
"\nReady to install. Summary:\n\
\n Agent : ${ASSISTANT_NAME} (${AGENT_NAME})\
\n Domain : ${AGENT_DOMAIN}\
\n Timezone : ${TZ}\
\n Disk : ${DISK}\
\n Provider : ${PI_PROVIDER}\
\n\nALL DATA ON ${DISK} WILL BE ERASED.\
\n\nInstallation will take 1020 minutes.\nPress Enter to begin." \
18 70
DISK=$(detect_disk)
log_vps "[vps] Target disk: ${DISK}"
echo "==> Starting installation..." | tee "$LOG"
# ── Interactive confirmation (if TTY available) ──────────────────────────
# --- step 1: partition disk ---
echo "==> [1/6] Partitioning ${DISK}..." | tee -a "$LOG"
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}..."
# Destroy existing partitions
gpart destroy -F "${DISK}" 2>/dev/null || true
# Create GPT partition scheme
gpart create -s gpt "${DISK}"
# EFI boot partition (for UEFI systems)
gpart add -t efi -s 260M -l boot "${DISK}"
# Swap
gpart add -t freebsd-swap -s 2G -l swap "${DISK}"
# ZFS (rest of disk)
gpart add -t freebsd-zfs -l zroot "${DISK}"
# Format EFI partition
newfs_msdos /dev/gpt/boot
# --- step 2: create ZFS pool ---
echo "==> [2/6] Creating ZFS pool..." | tee -a "$LOG"
# ── Step 2: Create ZFS pool ──────────────────────────────────────────────
log_vps "[vps] [2/5] Creating ZFS pool 'clawdie'..."
# Get the ZFS partition device
ZFS_PART="${DISK}p3"
[ -e "/dev/gpt/zroot" ] && ZFS_PART="/dev/gpt/zroot"
zpool create -f -m none -R /mnt zroot "$ZFS_PART"
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
# Create datasets
zfs create -o mountpoint=none zroot/ROOT
zfs create -o mountpoint=/ zroot/ROOT/default
zfs create -o mountpoint=/tmp -o setuid=off zroot/tmp
zfs create -o mountpoint=/usr -o canmount=off zroot/usr
zfs create -o mountpoint=/home zroot/home
zfs create -o mountpoint=/var -o canmount=off zroot/var
zfs create -o mountpoint=/var/cache -o setuid=off zroot/var/cache
zfs create -o mountpoint=/var/log -o setuid=off zroot/var/log
zfs create -o mountpoint=/var/tmp -o setuid=off zroot/var/tmp
# ── Step 3: Install FreeBSD base ─────────────────────────────────────────
# Enable compression
zfs set compression=lz4 zroot
zfs set atime=off zroot
# --- step 3: install base system ---
echo "==> [3/6] Installing FreeBSD base system..." | tee -a "$LOG"
# Download and extract base and kernel (mfsBSD has fetch)
BASE_URL="https://download.freebsd.org/releases/amd64/15.0-RELEASE"
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"
fetch "${BASE_URL}/kernel.txz"
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
# --- step 4: configure system ---
echo "==> [4/6] Configuring system..." | tee -a "$LOG"
# fstab
cat > /mnt/etc/fstab <<EOF
/dev/gpt/boot /boot/efi msdosfs rw 0 0
/dev/gpt/swap none swap sw 0 0
EOF
# rc.conf
cat > /mnt/etc/rc.conf <<EOF
hostname="${AGENT_NAME}"
zfs_enable="YES"
sshd_enable="YES"
ifconfig_DEFAULT="DHCP"
EOF
# loader.conf
cat > /mnt/boot/loader.conf <<EOF
zfs_load="YES"
EOF
# Timezone
chroot /mnt tzsetup "$TZ"
# 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
# Root password
echo "root:$(openssl passwd -6 "$(gen_secret)")" | chroot /mnt chpass -H root
# Timezone
chroot /mnt tzsetup "${TZ:-UTC}"
# Create clawdie user
chroot /mnt pw useradd -n clawdie -u 1001 -m -G wheel -s /bin/sh
echo "clawdie:$(openssl passwd -6 "$(gen_secret)")" | chroot /mnt chpass -H clawdie
_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
# Enable sudo for wheel group
# 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 5: install Clawdie-AI ---
echo "==> [5/6] Installing Clawdie-AI..." | tee -a "$LOG"
# ── Step 4: Inject firstboot payload ─────────────────────────────────────
# Same as installerconfig does for USB — copy firstboot scripts + packages + tarball
# Install packages (if bundled, use those; otherwise fetch)
if [ -d "${SHARE}/packages/All" ]; then
echo " Installing from bundled packages..." | tee -a "$LOG"
# Set up local repo
mkdir -p /mnt/var/cache/pkg
cp -r "${SHARE}/packages" /mnt/var/cache/pkg/clawdie-repo
pkg -c /mnt add ${SHARE}/packages/All/*.pkg 2>/dev/null || true
else
echo " Installing from network (this requires internet)..." | tee -a "$LOG"
# Configure pkg
mkdir -p /mnt/usr/local/etc/pkg/repos
cat > /mnt/usr/local/etc/pkg/repos/FreeBSD.conf <<EOF
FreeBSD: {
url: "pkg+https://pkg.FreeBSD.org/\${ABI}/latest",
mirror_type: "srv",
enabled: yes
}
EOF
# Bootstrap pkg
chroot /mnt env ASSUME_ALWAYS_YES=YES pkg bootstrap
# Install essential packages
chroot /mnt pkg install -y node24 npm git tmux postgresql17-client
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
# Extract Clawdie-AI
mkdir -p /mnt/home/clawdie/clawdie-ai
tar -xzf "${SHARE}/clawdie-ai.tar.gz" -C /mnt/home/clawdie/clawdie-ai --strip-components=1
chroot /mnt chown -R clawdie:clawdie /home/clawdie/clawdie-ai
# 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
# Create .env
cat > /mnt/home/clawdie/clawdie-ai/.env <<EOF
AGENT_NAME=${AGENT_NAME}
ASSISTANT_NAME=${ASSISTANT_NAME}
AGENT_DOMAIN=${AGENT_DOMAIN}
AGENT_INTERNAL_DOMAIN=${AGENT_NAME}.home.arpa
TZ=${TZ}
PI_TUI_PROVIDER=${PI_PROVIDER}
POSTGRES_ADMIN_PASSWORD=${POSTGRES_ADMIN_PASSWORD}
SKILLS_DB_PASSWORD=${SKILLS_DB_PASSWORD}
MEMORY_DB_PASSWORD=${MEMORY_DB_PASSWORD}
STRAPI_DB_PASSWORD=${STRAPI_DB_PASSWORD}
STRAPI_APP_KEYS=${STRAPI_APP_KEYS}
STRAPI_API_TOKEN_SALT=${STRAPI_API_TOKEN_SALT}
STRAPI_ADMIN_JWT_SECRET=${STRAPI_ADMIN_JWT_SECRET}
STRAPI_TRANSFER_TOKEN_SALT=${STRAPI_TRANSFER_TOKEN_SALT}
STRAPI_JWT_SECRET=${STRAPI_JWT_SECRET}
SCREENSHOTS_PASSWORD=${SCREENSHOTS_PASSWORD}
EOF
chmod 600 /mnt/home/clawdie/clawdie-ai/.env
chroot /mnt chown clawdie:clawdie /home/clawdie/clawdie-ai/.env
# Copy packages if bundled
if [ -d "${SHARE}/packages" ]; then
cp -r "${SHARE}/packages" "${HDD_SHARE}/"
log_vps "[vps] Package repo copied"
fi
# --- step 6: install bootloader ---
echo "==> [6/6] Installing bootloader..." | tee -a "$LOG"
# 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..."
# Install 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
# ZFS boot config
zpool set bootfs=zroot/ROOT/default zroot
zpool set bootfs=clawdie/ROOT/default clawdie
# --- done ---
log_vps "[vps] Installation complete."
echo ""
echo "============================================"
echo " Installation complete!"
echo " FreeBSD installed to ${DISK}"
echo " Pool: clawdie"
echo ""
echo " Agent : ${ASSISTANT_NAME}"
echo " Domain: ${AGENT_DOMAIN}"
echo " On first HDD boot, the Clawdie firstboot"
echo " wizard will run automatically."
echo ""
echo " Credentials saved to:"
echo " /home/clawdie/clawdie-ai/.env"
echo ""
echo " Removing in 10 seconds, then reboot..."
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
# Unmount and export
umount /mnt/boot/efi 2>/dev/null || true
zpool export zroot
echo "Rebooting..."
zpool export clawdie
reboot