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,6 +124,7 @@ cp "${SCRIPT_DIR}/build.cfg" "${CLAWDIE_SHARE}/"
if [ -d "${SCRIPT_DIR}/vps" ]; then if [ -d "${SCRIPT_DIR}/vps" ]; then
cp "${SCRIPT_DIR}/vps/firstboot-vps.sh" "${CLAWDIE_SHARE}/firstboot-vps.sh" cp "${SCRIPT_DIR}/vps/firstboot-vps.sh" "${CLAWDIE_SHARE}/firstboot-vps.sh"
chmod +x "${CLAWDIE_SHARE}/firstboot-vps.sh" chmod +x "${CLAWDIE_SHARE}/firstboot-vps.sh"
[ -f "${SCRIPT_DIR}/vps/clawdie.conf.tpl" ] && \
cp "${SCRIPT_DIR}/vps/clawdie.conf.tpl" "${CLAWDIE_SHARE}/clawdie.conf.tpl" cp "${SCRIPT_DIR}/vps/clawdie.conf.tpl" "${CLAWDIE_SHARE}/clawdie.conf.tpl"
fi fi

View file

@ -91,7 +91,7 @@ echo " ssh -p 1022 mfsbsd@$(curl -s ifconfig.me)"
echo " Password: ${MFSBSD_PASSWORD}" echo " Password: ${MFSBSD_PASSWORD}"
echo "" echo ""
echo " Then run:" echo " Then run:"
echo " /usr/local/share/clawdie-iso/firstboot.sh" echo " /usr/local/share/clawdie-iso/firstboot-vps.sh"
echo "============================================" echo "============================================"
echo "" echo ""
read -p "Press Enter to reboot now (or Ctrl+C to abort)..." 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,37 +1,45 @@
#!/bin/sh #!/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. # 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: # Flow:
# 1. Ask: agent name, domain, timezone # 1. Detect target disk
# 2. Auto-generate secrets # 2. Optionally read clawdie.conf for pre-baked values
# 3. zfsinstall → install FreeBSD to disk # 3. Partition disk (GPT: EFI + swap + ZFS)
# 4. Chroot: install packages, extract Clawdie-AI, configure # 4. Create ZFS pool "clawdie" with standard datasets
# 5. Reboot into final system # 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 set -e
SHARE="/usr/local/share/clawdie-iso" SHARE="${SHARE:-/usr/local/share/clawdie-iso}"
LOG="/var/log/clawdie-vps-install.log" LOG="/var/log/clawdie-vps-install.log"
. "${SHARE}/build.cfg" . "${SHARE}/build.cfg"
# --- helpers --- # ── Helpers <20><>──────────────────────────────────────────────────────────────
dialog() { bsddialog --backtitle "Clawdie-VPS Setup" "$@" ; }
die() { echo "ERROR: $1" >&2; exit 1; } die() { echo "ERROR: $1" >&2; exit 1; }
gen_secret() { log_vps() { echo "$(date '+%H:%M:%S') $1" | tee -a "$LOG"; }
openssl rand -base64 32 | tr -d '\n/+=' | head -c 32
} gen_password() { openssl rand -base64 32 | tr -d '\n/+=' | head -c 24; }
# --- detect target disk ---
detect_disk() { detect_disk() {
# Try NVMe first, then SATA for d in /dev/nda0 /dev/nvd0 /dev/da0 /dev/vtbd0 /dev/ada0; do
for d in /dev/nvme0n1 /dev/nda0 /dev/da0 /dev/vtbd0 /dev/vda /dev/sda; do
if [ -e "$d" ]; then if [ -e "$d" ]; then
echo "$d" echo "$d"
return 0 return 0
@ -40,260 +48,227 @@ detect_disk() {
die "Cannot detect target disk" die "Cannot detect target disk"
} }
DISK=$(detect_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 "============================================"
echo " Clawdie-VPS Installer" echo " Clawdie-VPS Installer"
echo " Target disk: ${DISK}" echo " Target disk: ${DISK}"
echo " ALL DATA WILL BE ERASED."
echo "============================================" echo "============================================"
echo "" printf "Continue? (yes/no): "
read _confirm
# --- check if running headless --- [ "$_confirm" = "yes" ] || die "Cancelled."
if [ ! -t 0 ]; then _vps_target="baremetal"
echo "No TTY detected. Reading from ${SHARE}/clawdie.conf..."
if [ -f "${SHARE}/clawdie.conf" ]; then
. "${SHARE}/clawdie.conf"
else else
die "No clawdie.conf found. Run interactively with TTY." # No TTY — require clawdie.conf
fi [ -n "${ASSISTANT_NAME:-}" ] || die "No TTY and no ASSISTANT_NAME in clawdie.conf"
else _vps_target="cloud"
# 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"
fi fi
# --- auto-generate secrets --- # ── Step 1: Partition disk ────────────────────────────────────────────────
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)
# --- summary --- log_vps "[vps] [1/5] Partitioning ${DISK}..."
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
echo "==> Starting installation..." | tee "$LOG"
# --- step 1: partition disk ---
echo "==> [1/6] Partitioning ${DISK}..." | tee -a "$LOG"
# Destroy existing partitions
gpart destroy -F "${DISK}" 2>/dev/null || true gpart destroy -F "${DISK}" 2>/dev/null || true
# Create GPT partition scheme
gpart create -s gpt "${DISK}" gpart create -s gpt "${DISK}"
# EFI boot partition (for UEFI systems)
gpart add -t efi -s 260M -l boot "${DISK}" gpart add -t efi -s 260M -l boot "${DISK}"
# Swap
gpart add -t freebsd-swap -s 2G -l swap "${DISK}" gpart add -t freebsd-swap -s 2G -l swap "${DISK}"
# ZFS (rest of disk)
gpart add -t freebsd-zfs -l zroot "${DISK}" gpart add -t freebsd-zfs -l zroot "${DISK}"
# Format EFI partition
newfs_msdos /dev/gpt/boot newfs_msdos /dev/gpt/boot
# --- step 2: create ZFS pool --- # ── Step 2: Create ZFS pool ──────────────────────────────────────────────
echo "==> [2/6] Creating ZFS pool..." | tee -a "$LOG"
log_vps "[vps] [2/5] Creating ZFS pool 'clawdie'..."
# Get the ZFS partition device
ZFS_PART="${DISK}p3" ZFS_PART="${DISK}p3"
[ -e "/dev/gpt/zroot" ] && ZFS_PART="/dev/gpt/zroot" [ -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 # ── Step 3: Install FreeBSD base ─────────────────────────────────────────
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
# Enable compression log_vps "[vps] [3/5] Installing FreeBSD ${FREEBSD_VERSION} base..."
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"
BASE_URL="https://download.freebsd.org/releases/${FREEBSD_ARCH}/${FREEBSD_VERSION}"
cd /tmp cd /tmp
fetch "${BASE_URL}/base.txz" fetch "${BASE_URL}/base.txz" || die "Failed to fetch base.txz"
fetch "${BASE_URL}/kernel.txz" fetch "${BASE_URL}/kernel.txz" || die "Failed to fetch kernel.txz"
tar -xpf base.txz -C /mnt tar -xpf base.txz -C /mnt
tar -xpf kernel.txz -C /mnt tar -xpf kernel.txz -C /mnt
# --- step 4: configure system ---
echo "==> [4/6] Configuring system..." | tee -a "$LOG"
# fstab # fstab
cat > /mnt/etc/fstab <<EOF cat > /mnt/etc/fstab <<EOF
/dev/gpt/boot /boot/efi msdosfs rw 0 0 /dev/gpt/boot /boot/efi msdosfs rw 0 0
/dev/gpt/swap none swap sw 0 0 /dev/gpt/swap none swap sw 0 0
EOF EOF
# rc.conf
cat > /mnt/etc/rc.conf <<EOF
hostname="${AGENT_NAME}"
zfs_enable="YES"
sshd_enable="YES"
ifconfig_DEFAULT="DHCP"
EOF
# loader.conf # loader.conf
cat > /mnt/boot/loader.conf <<EOF cat > /mnt/boot/loader.conf <<EOF
zfs_load="YES" zfs_load="YES"
EOF EOF
# Timezone # Minimal rc.conf (firstboot.sh will configure the rest)
chroot /mnt tzsetup "$TZ" cat > /mnt/etc/rc.conf <<EOF
zfs_enable="YES"
sshd_enable="YES"
ifconfig_DEFAULT="DHCP"
clawdie_firstboot_enable="YES"
EOF
# Root password # Timezone
echo "root:$(openssl passwd -6 "$(gen_secret)")" | chroot /mnt chpass -H root chroot /mnt tzsetup "${TZ:-UTC}"
# Create clawdie user # Create clawdie user
chroot /mnt pw useradd -n clawdie -u 1001 -m -G wheel -s /bin/sh 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 cat > /mnt/usr/local/etc/sudoers.d/wheel <<EOF
%wheel ALL=(ALL) NOPASSWD: ALL %wheel ALL=(ALL) NOPASSWD: ALL
EOF EOF
chmod 440 /mnt/usr/local/etc/sudoers.d/wheel chmod 440 /mnt/usr/local/etc/sudoers.d/wheel
# --- step 5: install Clawdie-AI --- # ── Step 4: Inject firstboot payload ─────────────────────────────────────
echo "==> [5/6] Installing Clawdie-AI..." | tee -a "$LOG" # Same as installerconfig does for USB — copy firstboot scripts + packages + tarball
# Install packages (if bundled, use those; otherwise fetch) log_vps "[vps] [4/5] Injecting firstboot payload..."
if [ -d "${SHARE}/packages/All" ]; then
echo " Installing from bundled packages..." | tee -a "$LOG" HDD_SHARE="/mnt/usr/local/share/clawdie-iso"
# Set up local repo HDD_RCD="/mnt/usr/local/etc/rc.d"
mkdir -p /mnt/var/cache/pkg
cp -r "${SHARE}/packages" /mnt/var/cache/pkg/clawdie-repo mkdir -p "$HDD_SHARE"
pkg -c /mnt add ${SHARE}/packages/All/*.pkg 2>/dev/null || true cp -r "${SHARE}/firstboot" "${HDD_SHARE}/"
else cp "${SHARE}/build.cfg" "${HDD_SHARE}/"
echo " Installing from network (this requires internet)..." | tee -a "$LOG"
# Configure pkg # Override TARGET in the HDD copy of build.cfg if pre-baked
mkdir -p /mnt/usr/local/etc/pkg/repos if [ "$_vps_target" = "cloud" ]; then
cat > /mnt/usr/local/etc/pkg/repos/FreeBSD.conf <<EOF sed -i '' "s/^TARGET=.*/TARGET=\"cloud\"/" "${HDD_SHARE}/build.cfg"
FreeBSD: { # Bake identity vars
url: "pkg+https://pkg.FreeBSD.org/\${ABI}/latest", [ -n "${ASSISTANT_NAME:-}" ] && sed -i '' "s/^ASSISTANT_NAME=.*/ASSISTANT_NAME=\"${ASSISTANT_NAME}\"/" "${HDD_SHARE}/build.cfg"
mirror_type: "srv", [ -n "${AGENT_DOMAIN:-}" ] && sed -i '' "s/^AGENT_DOMAIN=.*/AGENT_DOMAIN=\"${AGENT_DOMAIN}\"/" "${HDD_SHARE}/build.cfg"
enabled: yes [ -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"
EOF log_vps "[vps] Pre-baked config written to build.cfg (TARGET=cloud)"
# 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
fi fi
# Extract Clawdie-AI # Copy tarball
mkdir -p /mnt/home/clawdie/clawdie-ai if [ -f "${SHARE}/clawdie-ai.tar.gz" ]; then
tar -xzf "${SHARE}/clawdie-ai.tar.gz" -C /mnt/home/clawdie/clawdie-ai --strip-components=1 cp "${SHARE}/clawdie-ai.tar.gz" "${HDD_SHARE}/"
chroot /mnt chown -R clawdie:clawdie /home/clawdie/clawdie-ai 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 # Copy packages if bundled
cat > /mnt/home/clawdie/clawdie-ai/.env <<EOF if [ -d "${SHARE}/packages" ]; then
AGENT_NAME=${AGENT_NAME} cp -r "${SHARE}/packages" "${HDD_SHARE}/"
ASSISTANT_NAME=${ASSISTANT_NAME} log_vps "[vps] Package repo copied"
AGENT_DOMAIN=${AGENT_DOMAIN} fi
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
# --- step 6: install bootloader --- # Make scripts executable
echo "==> [6/6] Installing bootloader..." | tee -a "$LOG" 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 mkdir -p /mnt/boot/efi
mount -t msdosfs /dev/gpt/boot /mnt/boot/efi mount -t msdosfs /dev/gpt/boot /mnt/boot/efi
mkdir -p /mnt/boot/efi/EFI/BOOT mkdir -p /mnt/boot/efi/EFI/BOOT
cp /mnt/boot/loader.efi /mnt/boot/efi/EFI/BOOT/BOOTX64.EFI cp /mnt/boot/loader.efi /mnt/boot/efi/EFI/BOOT/BOOTX64.EFI
umount /mnt/boot/efi umount /mnt/boot/efi
# ZFS boot config zpool set bootfs=clawdie/ROOT/default clawdie
zpool set bootfs=zroot/ROOT/default zroot
# --- done --- log_vps "[vps] Installation complete."
echo "" echo ""
echo "============================================" echo "============================================"
echo " Installation complete!" echo " FreeBSD installed to ${DISK}"
echo " Pool: clawdie"
echo "" echo ""
echo " Agent : ${ASSISTANT_NAME}" echo " On first HDD boot, the Clawdie firstboot"
echo " Domain: ${AGENT_DOMAIN}" echo " wizard will run automatically."
echo "" echo ""
echo " Credentials saved to:" echo " Root password: ${_root_pw}"
echo " /home/clawdie/clawdie-ai/.env" echo " User password: ${_user_pw}"
echo "" echo " (also available in ${LOG})"
echo " Removing in 10 seconds, then reboot..."
echo "============================================" 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 sleep 10
# Unmount and export
umount /mnt/boot/efi 2>/dev/null || true umount /mnt/boot/efi 2>/dev/null || true
zpool export zroot zpool export clawdie
echo "Rebooting..."
reboot reboot