Configure FreeBSD 15 installs to load mac_do with an empty rule set for future narrowly scoped UID transitions.
290 lines
10 KiB
Bash
290 lines
10 KiB
Bash
#!/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 setup.txt.tpl → setup.txt, edit, then run
|
||
#
|
||
# Flow:
|
||
# 1. Detect target disk
|
||
# 2. Optionally read setup.txt 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"
|
||
SETUP_IMPORT_TEST=1
|
||
. "${SHARE}/firstboot/setup-import.sh"
|
||
|
||
# ── 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}/setup.txt" ]; then
|
||
clawdie_setup_import_parse_file "${SHARE}/setup.txt" setup
|
||
[ -f "${SHARE}/system.env" ] && clawdie_setup_import_parse_file "${SHARE}/system.env" system
|
||
clawdie_setup_import_apply_defaults
|
||
log_vps "[vps] Loaded setup.txt"
|
||
elif [ -f "${SHARE}/clawdie.conf" ]; then
|
||
. "${SHARE}/clawdie.conf"
|
||
log_vps "[vps] Loaded legacy 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 vps (skip wizard on reboot)
|
||
if [ -n "${ASSISTANT_NAME:-}" ] && [ -n "${AGENT_DOMAIN:-}" ] && [ -n "${TZ:-}" ]; then
|
||
_vps_target="vps"
|
||
_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 setup.txt (legacy clawdie.conf still accepted temporarily)
|
||
[ -n "${ASSISTANT_NAME:-}" ] || die "No TTY and no ASSISTANT_NAME in setup.txt"
|
||
_vps_target="vps"
|
||
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"
|
||
mac_do_load="YES"
|
||
EOF
|
||
|
||
# mac_do framework starts with no credential grants. Specific rules are added
|
||
# only when a concrete operator workflow needs them.
|
||
cat > /mnt/etc/sysctl.conf <<EOF
|
||
security.mac.do.rules=
|
||
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" = "vps" ]; then
|
||
sed -i '' "s/^TARGET=.*/TARGET=\"vps\"/" "${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=vps)"
|
||
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..."
|
||
|
||
[ -f /mnt/boot/loader.efi ] || die "loader.efi not found after base extraction"
|
||
|
||
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 ""
|
||
echo " SAVE THESE PASSWORDS NOW — they are not logged."
|
||
echo "============================================"
|
||
echo ""
|
||
|
||
# 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
|