# Bastille on FreeBSD 15

This project uses Bastille as the host-side jail manager for Clawdie's FreeBSD runtime.

## Recommendation

Start with one native FreeBSD control-plane jail backed by ZFS. For Warden,
the control-plane jail should be thick and attached to the canonical VNET
bridge `warden0`. Worker jails can be thin later.

## Required Host Assumptions

- FreeBSD 15 host
- ZFS pool `zroot`
- Bastille installed from packages
- Clawdie runtime tested on the host first

## Recommended `bastille.conf`

Keep the stock Bastille structure intact and customize only the lines that matter for this host.

```sh
#####################
## [ BastilleBSD ] ##
#####################

## Default paths
bastille_prefix="/usr/local/bastille"
bastille_backupsdir="${bastille_prefix}/backups"
bastille_cachedir="${bastille_prefix}/cache"
bastille_jailsdir="${bastille_prefix}/jails"
bastille_releasesdir="${bastille_prefix}/releases"
bastille_templatesdir="${bastille_prefix}/templates"
bastille_logsdir="/var/log/bastille"

## pf configuration path
bastille_pf_conf="/etc/pf.conf"

## Bastille commands directory
bastille_sharedir="/usr/local/share/bastille"

## Bootstrap archives
bastille_bootstrap_archives="base"

## Pkgbase package sets
bastille_pkgbase_packages="base-jail"

## Default timezone
bastille_tzdata="Europe/Ljubljana"

## Default jail resolv.conf
bastille_resolv_conf="/etc/resolv.conf"

## Bootstrap URLs
bastille_url_freebsd="http://ftp.freebsd.org/pub/FreeBSD/releases/"
bastille_url_hardenedbsd="https://installers.hardenedbsd.org/pub/"
bastille_url_midnightbsd="https://www.midnightbsd.org/ftp/MidnightBSD/releases/"

## ZFS options
bastille_zfs_enable="YES"
bastille_zfs_zpool="zroot"
bastille_zfs_prefix="clawdie-runtime"
bastille_zfs_options="-o compress=on -o atime=off"

## Export/Import options
bastille_compress_xz_options="-0 -v"
bastille_decompress_xz_options="-c -d -v"
bastille_compress_gz_options="-1 -v"
bastille_decompress_gz_options="-k -d -c -v"
bastille_compress_zst_options="-3 -v"
bastille_decompress_zst_options="-k -d -c -v"
bastille_export_options=""

## Networking
bastille_network_vnet_type="if_bridge"
bastille_network_loopback="clawdie0"
bastille_network_pf_ext_if="ext_if"
bastille_network_pf_table="clawdie_jails"

bastille_network_shared=""
bastille_network_gateway=""
bastille_network_gateway6=""

## Default Templates
bastille_template_base="default/base"
bastille_template_empty=""
bastille_template_thick="default/thick"
bastille_template_clone="default/clone"
bastille_template_thin="default/thin"
bastille_template_vnet="default/vnet"
bastille_template_vlan="default/vlan"

## Monitoring
bastille_monitor_cron_path="/usr/local/etc/cron.d/bastille-monitor"
bastille_monitor_cron="*/5 * * * * root /usr/local/bin/bastille monitor ALL >/dev/null 2>&1"
bastille_monitor_logfile="${bastille_logsdir}/monitor.log"
bastille_monitor_healthchecks=""
```

## Why These Settings Matter

- `bastille_sharedir` must be set, otherwise Bastille cannot find `bootstrap.sh`
- `bastille_zfs_enable="YES"` tells Bastille to create datasets instead of plain directories
- `bastille_zfs_zpool="zroot"` matches the active host pool
- `bastille_zfs_prefix="clawdie-runtime"` keeps the ZFS namespace project-specific
- `bastille_tzdata="Europe/Ljubljana"` makes jail-local time explicit
- `bastille_network_loopback="clawdie0"` and `bastille_network_pf_table="clawdie_jails"` keep Bastille naming aligned with Warden

## Bootstrap

```sh
pkg install bastille
bastille bootstrap -p 15.0-RELEASE
```

## Create the First Jail

Create the canonical thick Warden control-plane jail.

```sh
bastille create -T -B -g 192.168.100.1 controlplane 15.0-RELEASE 192.168.100.100/24 warden0
bastille config controlplane set host.hostname ai.clawdie.si
bastille restart controlplane
bastille console controlplane
```

If you need to destroy and recreate it during bootstrap, use:

```sh
bastille stop controlplane
bastille destroy -y controlplane
```

Operational notes:

- `-y` avoids the interactive destroy confirmation prompt
- Bastille archives console logs on destroy under `/var/log/bastille/`
- `pfctl: Table does not exist.` during stop or destroy means the host `pf`
  table has not been configured yet; it is a firewall setup gap, not a jail
  deletion failure
- if a VNET jail repeatedly comes up without a `default` route, the Bastille
  create command is missing the explicit `-g 192.168.100.1` gateway flag

The intended host-side network is:

- bridge: `warden0`
- gateway: `192.168.100.1`
- control-plane IP: `192.168.100.100`
- hostname: `ai.clawdie.si`

Then verify outbound access from inside the jail:

```sh
fetch -qo- https://api.telegram.org >/dev/null && echo ok
```

## Install Runtime Dependencies in the Jail

```sh
pkg install -y node npm git python312 gmake
npm install -g @mariozechner/pi-coding-agent
```

## ZFS Layout

With the configuration above, Bastille datasets should appear under:

```sh
zroot/clawdie-runtime
zroot/clawdie-runtime/jails
zroot/clawdie-runtime/releases
zroot/clawdie-runtime/templates
```

Check with:

```sh
zfs list | grep clawdie-runtime
```

## Snapshots and Rollback

Snapshot before risky changes:

```sh
zfs snapshot zroot/clawdie-runtime/jails/controlplane@fresh-08-mar-2026-180000
```

Rollback if needed:

```sh
zfs rollback zroot/clawdie-runtime/jails/controlplane@fresh-08-mar-2026-180000
```

Manual snapshot names should use this baseline:

```text
<purpose>-DD-mon-YYYY-HHMMSS
```

Fixed month abbreviations:

```text
jan feb mar apr maj jun jul avg sep okt nov dec
```

Example:

```sh
zfs snapshot zroot/clawdie-runtime/jails/db@postgres18-ready-08-mar-2026-183600
```

## Validation Note

The `controlplane` and `db` jail bring-up documented here was validated on
`08.03.2026` by Sam & Codex on a FreeBSD 15 host with Bastille, ZFS, `warden0`,
host forwarding, and `pf` NAT enabled.

## Optional Advanced Snapshot Automation

If you want policy-driven automated snapshots instead of only manual snapshots,
use `sanoid` as the first advanced package.

Recommended first active targets:

- `zroot/clawdie-runtime/jails/db`
- `zroot/clawdie-runtime/jails/controlplane`

Suggested starter policy:

```ini
[template_warden_db]
hourly = 48
daily = 14
monthly = 3
autosnap = yes
autoprune = yes

[template_warden_control_plane]
hourly = 24
daily = 7
monthly = 1
autosnap = yes
autoprune = yes

[zroot/clawdie-runtime/jails/db]
use_template = warden_db

[zroot/clawdie-runtime/jails/controlplane]
use_template = warden_control_plane
```

Why `sanoid` first:

- simple policy-driven retention
- good fit for the persistent `controlplane` jail
- easier first step than full replication tooling

Host-side files:

- config: `/usr/local/etc/sanoid/sanoid.conf`
- sample: `/usr/local/etc/sanoid/sanoid.conf.sample`

## What Not to Optimize Yet

Do not add these before the control-plane jailed runtime is proven:

- worker NAT rules
- Tailscale inside the jail
- Linux jails
- `bhyve` VM integration

Those are later architecture layers, not bootstrap requirements.

## Warden Provisioning Policy

The intended split is:

- `controlplane`: thick, VNET networking on `warden0`, persistent
- worker jails: thin, shared networking, ephemeral
- networked workers: thin, VNET, ephemeral
- browser automation desktop: Linux VM, image-backed, outside Bastille jail provisioning

That policy is defined in code so provisioning decisions are explicit before Bastille automation is added.

## Bastille Template Mapping

For Warden, Bastille templates map cleanly to only part of the future runtime matrix:

- `default/thick` → `controlPlane`
- `default/thin` → `worker`
- `default/thin` + `default/vnet` → `networkedWorker`
- `default/clone` → future optimization for fast worker spawning from a prepared base

The future `browserVm` profile is different:

- not a Bastille jail
- not a FreeBSD jail template problem
- should be implemented as a `bhyve` Linux VM with snapshot or image-based lifecycle

So no extra `bastille.conf` changes are needed now to prepare for the browser VM path.
