Installation Guide
Setup Wizard · modeled after bsdinstall
The setup wizard guides you through first-time configuration of your self-hosted AI assistant. Paths are auto-detected, secrets are protected, and screenshots are captured at every step.
On 8 March 2026, pi-tui completed the first full test installation on osa.smilepowered.org — creating jails, generating credentials, setting up PostgreSQL, configuring nginx authentication, and taking ZFS snapshots. The screenshots below were captured during that session.
Prerequisites
Before running the setup wizard, ensure your system meets these requirements. Everything is designed for FreeBSD 15 with Bastille jails on ZFS.
Required
- FreeBSD 15.0 (amd64)
- Bastille 0.10+
- ZFS root pool
- 8 GB RAM minimum
- 50 GB disk space
- Node.js v22+
Network
- Static IP or DHCP reservation
- Internet access for packages
- Optional: Tailscale for mesh VPN
- Port 443 open for HTTPS
- Telegram bot token (from @BotFather)
- AI provider API key
# Quick start — clone and run
git clone https://codeberg.org/Clawdie/Clawdie-AI.git
cd Clawdie-AI
npm install
npm run wizard
Wizard Steps
The curses-style wizard auto-detects your environment and walks you through the installer flow. Architecture choices stay early so jail roles and reserved IPs remain consistent.
This wizard locks in the operator account, SSH bootstrap, deployment profile, local code hosting, service jails, and provider settings before writing .env.
Architecture choices stay early so jail roles and reserved IPs remain stable.
Choose the operator account that will exist inside controlplane for SSH access, repo checkout, screenshots, and operator tooling.
Username:clawdie
Default is clawdie. Replace it if you want to use your own operator account name.
Host → controlplane SSH is bootstrapped separately from the later controlplane → host automation key.
Recommended when the operator differs from the installer user.
Only .pub files are listed. Private keys are never copied.
Fallback when autodetect finds nothing useful.
Controlplane + database only.
Controlplane + database + local git + CMS.
Standard + full GUI desktop.
Bootstrap and clone from a remote Git URL.
Bare repositories in a dedicated git jail.
Plain local git plus a lightweight web UI.
This step only appears when External only is selected for code hosting.
Remote URL:https://codeberg.org/Clawdie/Clawdie-AI.git
Use this when bootstrap should come from Codeberg, a LAN mirror, or another self-hosted remote.
Astro + Strapi in the dedicated cms jail.
Optional local inference and embeddings in a separate jail.
Only available for the Full profile.
Lower IP numbers are reserved for more foundational services.
Operator runtime and glasspane.
PostgreSQL + pgvector.
Local repositories.
Astro + Strapi.
Optional local inference.
Reserved browser automation / GUI desktop.
Set the assistant name, choose the provider, and add the matching API key before Telegram and protected-path credentials.
Default name is Clawdie.
z.ai, OpenRouter, OpenAI, Anthropic, or Claude subscription.
Stored in .env, never in source code.
Finish the operator-facing services:
Bot token from @BotFather goes into .env.
Generate or set the password for /screenshots/ and similar endpoints.
Review the final configuration, write .env, then hand over to the setup steps that create jails, install packages, and configure services.
Operator user, SSH bootstrap, profile, code hosting, service toggles, network, and provider.
Environment, network, jail, mounts, register, service, verify.
IP Address Scheme
Jails use a logical numbering convention — lower numbers for more
foundational services. The subnet base is configurable (default: 10.0.0).
IPs are computed from WARDEN_SUBNET_BASE in .env.
The default base is 10.0.0 and all jail IPs
derive automatically. See setup/network.ts for offset definitions.
First Test Install
Screenshots from the first successful test installation by pi-tui. Full deployment cycle — from credential generation to ZFS snapshots.
Step 1 — Credential Generation
Three database passwords generated with Python secrets,
appended to .env. Passwords are visible once in the screenshot,
then read from environment variables in all subsequent steps.
Step 2 — Database Setup
PostgreSQL roles created inside the db jail via jexec:
postgres admin, clawdie_app, and strapi_user.
Passwords sourced from .env — no hardcoding.
Verified with \du and \l showing 3 roles and 2 databases.
Step 3 — ZFS Snapshots
Pre-security ZFS snapshot taken on zroot/clawdie-runtime/jails/db
before applying hardening changes. Snapshot naming follows
@pre-security_YYYY-MM-DD_HH:MM:SS convention for clean rollback.
Step 4 — Screenshot Skill Setup
Screenshot paths updated for production: output goes to
/usr/local/www/clawdie/screenshots/, manifest served
at clawdie.si/screenshots/. Font paths adjusted for
FreeBSD package locations.
Step 5 — nginx + Authentication
nginx configuration validated (syntax is ok), screenshot
credentials added to .env: SCREENSHOTS_USER
and SCREENSHOTS_PASSWORD. The .env.example
template updated with empty placeholders.
Step 6 — Security Hardening
nginx basic auth location block configured for
/screenshots/ with htpasswd. Final
security-configured ZFS snapshot taken. Two snapshots
now protect the database jail: pre-security and
security-configured.
Setup Steps (Post-Wizard)
After the wizard writes .env, the remaining setup runs as individual steps.
Each can be invoked independently:
# Full sequence after wizard
npm run setup --step environment # Detect platform, check prereqs
npm run setup --step network # Write IP configuration to .env
npm run setup --step jail # Create Bastille jails on ZFS
npm run setup --step groups # Sync Telegram groups to DB
npm run setup --step register # Register agent-controlled channels
npm run setup --step mounts # Configure nullfs mounts for jail
npm run setup --step telegram-auth # Authenticate with Telegram bot
npm run setup --step service # Create rc.d service file
npm run setup --step verify # Final health check
The jail step runs Bastille with thin jails (-T)
and ZFS backing (-B). Jails are created at
/usr/local/bastille/jails/controlplane/, each with a static IP
on the warden0 bridge, a hostname like
controlplane.agent.local, and FreeBSD 15.0-RELEASE as the base.
After Installation
Verify Jails
bastille list should show controlplane and db running. Jails live at /usr/local/bastille/jails/. Access with bastille console controlplane.
Test Telegram
Send a message to your bot. Clawdie should respond. Check logs at /var/log/clawdie/telegram.log if not.
Glasspane Console
Attach with tmux attach -t clawdie to see the 3-pane console: gateway, shell, and btop.
Configure nginx
Set up vhosts for your domain. Let's Encrypt certificates auto-renew via acme.sh.
Troubleshooting
Gateway missing: Jails created without default gateway. Fix with bastille cmd controlplane route add default 10.0.0.1
PostgreSQL SYSVIPC: Database jail needs allow.sysvipc=1 in /usr/local/bastille/jails/db/jail.conf. Restart jail after change.
Sharp/image errors: Astro builds fail on FreeBSD due to native deps. Install vips inside CMS jail: bastille pkg cms install graphics/vips
pf blocking: Ensure jail subnet (10.0.0.0/24) is in nat rules. Check with pfctl -s nat.
Passwords lost: All secrets live in .env (gitignored). If lost, regenerate with python3 -c "import secrets; print(secrets.token_urlsafe(32))" and update PostgreSQL roles.
Jail not starting: Check /usr/local/bastille/jails/controlplane/ exists. If not, re-run npm run setup --step jail.