# Poudriere + bhyve Build Server Plan — v2.0.0

> Build colibri/clawdie as proper FreeBSD packages instead of raw `cargo build`
> binaries copied into the ISO. Plus bhyve virtualization for build test VMs.
> Target: the **`mother-build`** host (FreeBSD VPS) serving **`pkg.clawdie.si`** —
> the package half of the trusted supply chain (layered-soul `HIVE-ONBOARDING.md §10`).

**Date:** 4 Jun 2026 (v1.0.0) · retargeted 20 Jun 2026 (v2.0.0)
**Target server:** `mother-build` — a FreeBSD VPS with a ZFS pool (OVHcloud candidate;
see layered-soul `HOST-MATRIX.md`). The original ML350p Gen8 target is **retired**
(boot-looping); host provisioning is now provider-specific, not iLO/IPMI. Hardware-
specific sizing below (32 GB split, Xeon VT-x) is kept only as **reference numbers**
from the ML350p — scale them to the chosen `mother-build` plan.

**Scripts:** the reproducible Phase 2–3 steps are implemented in
[`../scripts/poudriere/`](../scripts/poudriere/) — `poudriere-setup.sh`,
`poudriere-build.sh`, `clawdie-repo.conf.in`. This doc is the rationale, host-
provisioning context, and the manual reference those scripts encode.

---

## Why

Current build flow:

```
colibri host (dev machine)
  cargo build --release
  → stage-colibri-iso.sh copies binaries to ISO
  → no version tracking, no pkg metadata, manual
```

Proposed flow:

```
mother-build server (Poudriere)
  → FreeBSD port: sysutils/colibri
  → poudriere builds .pkg files in clean jail
  → signed pkg repo hosted at pkg.clawdie.si
  → ISO build does: pkg install colibri
```

Benefits:

- Versioned packages with dependencies
- Clean-room builds (no host contamination)
- `pkg upgrade colibri` on deployed machines
- No Rust toolchain needed on the ISO build host
- Proper `pkg info colibri` metadata
- Less pressure to treat the OSA ISO builder as disposable package scratch space.
  OSA still needs a meaningful local operator/runtime toolkit (Codex, Hermes/image
  helpers, media/rendering deps, VNC/screenshot helpers). In practice those pull a
  shared GTK/X11/Wayland/media dependency closure. When disk gets tight, deleting
  repo-local build caches is safer than trying to strip that host closure. A real
  `mother-build` shifts bulky package-build churn off OSA instead of forcing the
  builder host to choose between free space and capability.

## Memory split (reference sizing — ML350p 32 GB)

These numbers are the original ML350p reference; on a smaller cloud `mother-build`
plan, poudriere alone needs little (drop `USE_TMPFS` if RAM is tight) and the bhyve
guests are optional. 32GB total — 16GB host, 16GB bhyve:

```
Host (16GB):
  6GB  — ZFS ARC (primary cache)
  4GB  — Poudriere tmpfs (builds in RAM)
  6GB  — headroom (ARC bursts, nginx, clawdie, SSH)

bhyve (16GB):
  4GB  — FreeBSD build jail (Poudriere test guest)
  4GB  — Linux test guest (cross-compile validation)
  4GB  — FreeBSD ISO test VM (boot clawdie-iso after build)
  4GB  — spare / future CI runner
```

CPU: Xeon E5-2400 v1/v2 — all SKUs have VT-x + EPT + VT-d.
bhyve uses hardware-accelerated virt (no emulation penalty).
With 8-10 cores, 2 vCPUs per guest is comfortable.

## Architecture

```
┌──────────────────────────────────────────────────┐
│  mother-build host (FreeBSD 15)                  │
│                                                   │
│  ZFS pool: zroot                                 │
│    zroot/ROOT/default     ← base system           │
│    zroot/poudriere        ← build jails           │
│    zroot/poudriere/data   ← packages              │
│    zroot/poudriere/ports  ← ports tree            │
│    zroot/bhyve            ← VM disk images        │
│    zroot/bhyve/iso-test   ← ISO boot test VM      │
│                                                   │
│  Services:                                        │
│    poudriere (bulk builder)                       │
│    bhyve (VM host: FreeBSD + Linux + ISO test)    │
│    nginx (pkg repo)                               │
│    clawdie agent (self-hosting)                   │
│    tailscale (mesh)                               │
└──────────────────────────────────────────────────┘
```

## Phase 1 - server provision (provider-specific)

### 1.1 Get a booted FreeBSD host

Host provisioning is **provider-specific** and out of scope for the scripts. On
OVHcloud, install FreeBSD via the rescue system or a FreeBSD template/image; the
old ML350p iLO/IPMI virtual-media path is retired. The poudriere scripts assume you
already have a **booted FreeBSD host with a ZFS pool** (default `zroot`).

### 1.2 Disk survey + ZFS

```sh
camcontrol devlist
geom disk list

# Single disk or mirror
zpool create -o ashift=12 zroot /dev/ada0
zfs create -o mountpoint=/ zroot/ROOT/default
zfs create zroot/poudriere
zfs create -o mountpoint=/usr/local/poudriere zroot/poudriere/root
```

### 1.3 Install base system

```sh
# Install FreeBSD 15 base + kernel to zroot
# Set up /boot, /etc, and the future deployed-system service hook
# Configure network (DHCP, tailscale)
```

## Phase 2 - Poudriere setup

> **Scripted (DONE).** `poudriere-setup.sh` performs 2.1–2.3 idempotently
> (install, `poudriere.conf` incl. `PKG_REPO_SIGNING_KEY`, signing-key generation,
> jail, ports tree) after validating root / version / pool. `poudriere-build.sh`
> performs 2.5 (bulk + automatic repo signing). The steps below are the reference
> they encode; only 2.4 (creating the `sysutils/colibri` port) is still manual.

### 2.1 Install Poudriere

```sh
pkg install poudriere
```

### 2.2 Configure

```sh
# /usr/local/etc/poudriere.conf
ZPOOL=zroot
ZROOTFS=/poudriere
FREEBSD_HOST=download.freebsd.org
RESOLV_CONF=/etc/resolv.conf
BASEFS=/usr/local/poudriere
USE_TMPFS=yes         # build in RAM (we have 32GB)
MAX_MEMORY=4          # limit per-jail RAM
MAX_FILES=2048
DISTFILES_CACHE=/usr/local/poudriere/distfiles
PARALLEL_JOBS=8
```

### 2.3 Create jail + ports tree

```sh
# poudriere-setup.sh does this with these names by default:
poudriere jail -c -j clawdie-amd64 -v 15.0-RELEASE -a amd64
poudriere ports -c -p clawdie -m git -B main
```

### 2.4 Colibri port — canonical in the colibri repo

The `sysutils/colibri` port is **owned by the colibri repo**, kept with the code
it builds at `packaging/freebsd/port/sysutils/colibri/`:

```
sysutils/colibri/
├── Makefile      USES=cargo; ships 6 binaries + rc.d services
├── pkg-descr
├── pkg-plist
└── files/        rc.d templates (colibri_daemon.in, colibri_bridge.in)
```

Copy that directory into the poudriere ports tree before building. **This repo
keeps no duplicate** — `build.sh`'s release gate fails if `ports/sysutils/colibri/`
reappears here. Key facts:

- `LICENSE= MIT` (per `colibri/Cargo.toml`; same as layered-soul).
- `USES= cargo`; source from the Forgejo archive (tagged `v${DISTVERSION}`).
- Ships runtime binaries: `clawdie`, `colibri`, `colibri-daemon`,
  `colibri-mcp`, `colibri-tui` — plus the `colibri_daemon` / `colibri_bridge`
  rc.d services. Keep `colibri-test-agent` available for poudriere validation
  builds, but treat it as a development/test helper rather than a default
  production ISO payload.
- `CARGO_CRATES` is committed and kept in sync with `Cargo.lock` by
  `check-cargo-crates.sh` (colibri CI). `distinfo` is generated on the build host
  with `make makesum`.

### 2.5 Build

```sh
cd /usr/local/poudriere/ports/clawdie/sysutils/colibri
make makesum        # generate distinfo
make cargo-crates > Makefile.crates  # generate crate list

# then build via the wrapper (validates + signs):
poudriere-build.sh --jail clawdie-amd64 --ports clawdie sysutils/colibri
```

## Phase 3 - pkg repository

### 3.1 Nginx pkg repo

```sh
pkg install nginx
# /usr/local/etc/nginx/nginx.conf:
#   server { listen 443 ssl; server_name pkg.clawdie.si;
#            root /usr/local/poudriere/data/packages/clawdie-amd64-clawdie; autoindex on; }
# TLS via acme.sh (osa/mother-build already carry nginx + acme).

service nginx enable
service nginx start
```

### 3.2 Client config

On ISO builds and deployed jails, generate from
[`../scripts/poudriere/clawdie-repo.conf.in`](../scripts/poudriere/clawdie-repo.conf.in)
and ship the **public** key from Phase 2. The repo is **signed**, so clients verify:

```sh
sed "s#__PKG_URL__#https://pkg.clawdie.si/#; s#__PUBKEY_PATH__#/usr/share/keys/pkg/clawdie.pub#" \
    clawdie-repo.conf.in > /usr/local/etc/pkg/repos/clawdie.conf
install -m 0444 /usr/local/etc/ssl/clawdie-pkg.pub /usr/share/keys/pkg/clawdie.pub
```

For first-party-only (paid) tenants, lower the stock FreeBSD repo priority so
resolution prefers signed Clawdie packages.

## Phase 4 - ISO integration

Instead of `stage-colibri-iso.sh` copying raw binaries:

```sh
# build.sh - install colibri packages during ISO build
pkg -r ${MOUNT_POINT} install colibri
```

This gives us:

- `colibri`
- `colibri-daemon`
- `colibri-mcp`
- `colibri-tui` (optional in current raw-binary staging, desired for operator USB)
- `colibri-test-agent` only in validation/dev package builds, not default
  production images

All with proper pkg metadata, upgradeable, with dependencies tracked.

## Phase 5 — self-hosted clawdie

Once the deployed-system service implementation lands and the server builds
itself:

```sh
# The mother-build host runs its own deployed-system clawdie service
service clawdie enable
service clawdie start

# clawdie monitors build jails, runs periodic rebuilds
# clawdie skills include "rebuild-colibri" and "poudriere-status"
```

The current live USB does not stage `service clawdie`; it runs
`colibri_daemon` directly.

## Phase 6 — bhyve test VMs

> **Largely DONE (on the ISO-build host, not yet on `mother-build`).** The
> ISO-boot test VM exists and is wired as a **build gate**:
> [`../scripts/bhyve-test.sh`](../scripts/bhyve-test.sh) boots the built image in
> bhyve with `com1,stdio`, and [`../scripts/run-bhyve-test.sh`](../scripts/run-bhyve-test.sh)
> scans the serial console for required boot markers / forbidden failures (panics,
> restart loops, the colibri permission-denied regression) and exits non-zero on a
> critical miss. [`../scripts/bhyve-pf-allow.sh`](../scripts/bhyve-pf-allow.sh) opens
> the guest network. What remains: replicate this on `mother-build` and add the
> Linux cross-compile / poudriere-validation guests (6.4) if wanted.

### 6.1 Enable bhyve kernel module

```sh
kldload vmm
sysrc kld_list+="vmm"
```

### 6.2 Install bhyve packages

```sh
pkg install bhyve-firmware edk2-bhyve vm-bhyve
```

### 6.3 Create ZFS datasets for VMs

```sh
zfs create zroot/bhyve
zfs create zroot/bhyve/iso-test
zfs create zroot/bhyve/linux-test
zfs create zroot/bhyve/freebsd-test
```

### 6.4 Test VMs

**FreeBSD ISO test VM** (boots clawdie-iso after each build):

```sh
vm create -t freebsd iso-test
vm install iso-test clawdie-iso.iso
vm start iso-test
# → verifies ISO boots, colibri starts, clawdie health passes
```

**Linux cross-compile test VM** (validates non-FreeBSD targets):

```sh
vm create -t linux linux-test
# → test colibri builds on Linux target
```

**FreeBSD Poudriere test jail VM** (full pkg build validation):

```sh
vm create -t freebsd freebsd-test
# → clone poudriere setup, run bulk build as validation
```

### 6.5 Packages for ISO

Add to `pkg-list-disk-install-extras.txt` (installed on deployed server, not live USB):

```
bhyve-firmware
edk2-bhyve
vm-bhyve
```

## Timeline

| Step                                   | Effort         | Status / depends on                                 |
| -------------------------------------- | -------------- | --------------------------------------------------- |
| 1. Provision `mother-build` host       | ~1h            | PENDING — OVH FreeBSD VPS + ZFS pool (cost-gated)   |
| 2. Poudriere setup                     | ~30m           | **SCRIPTED** — `poudriere-setup.sh`; needs the host |
| 3. colibri port creation               | ~1h            | PENDING — write `sysutils/colibri` (manual, §2.4)   |
| 4. First pkg build                     | ~30m (compile) | **SCRIPTED** — `poudriere-build.sh`; needs the port |
| 5. pkg repo + nginx (`pkg.clawdie.si`) | ~15m           | PENDING — nginx + acme + DNS                        |
| 6. ISO integration                     | ~15m           | PENDING — `pkg install` in `build.sh` (Phase 4)     |
| 7. bhyve ISO-boot test gate            | done           | **DONE** — `bhyve-test.sh` + `run-bhyve-test.sh`    |

**Critical path:** provision `mother-build` → write the colibri port → run the two
scripts → stand up nginx/DNS. The host purchase is gated on the HOST-MATRIX cost rows.

Why this matters operationally on OSA:

- repo-local ISO artifacts (`tmp/packages`, sparse `work.img`, cached memsticks)
  are the right things to delete under pressure
- host GUI/media/runtime packages are not just "old desktop leftovers" anymore;
  they are shared dependencies for Codex-adjacent tooling, Hermes/image helpers,
  and remote-display/debug paths
- pushing package-build churn to `mother-build` is the clean fix when OSA starts
  trading free space against operator capability

## Notes

- Rust target `x86_64-unknown-freebsd` stays — that's the compiler triple.
  The package name is `colibri` (or `clawdie-colibri`), no "unknown" in
  package branding.
- Poudriere builds each package in a clean jail — no host Rust toolchain
  pollution. The jail installs `lang/rust` from ports automatically.
- `USE_TMPFS=yes` keeps builds in RAM (fast, no SSD wear) when the host has the
  memory; drop it on a small cloud plan.
- Tailscale mesh means the operator USB can install packages from this
  server even after deploying to a different machine.
- bhyve needs a host with VT-x + EPT (the ML350p's Xeon E5-2400 had it; confirm
  the chosen OVH plan exposes nested virt before relying on Phase 6 there).
