2026-05-28 00:46:24 +02:00
#!/bin/sh
# Stage prebuilt Colibri FreeBSD service artifacts into an ISO/image root.
#
# This script does not build Colibri. Build or provide artifacts first:
2026-06-05 00:38:25 +02:00
# (cd /home/clawdie/ai/colibri && cargo build --workspace --release)
2026-05-28 00:46:24 +02:00
#
# Usage:
2026-06-05 00:38:25 +02:00
# COLIBRI_REPO=/home/clawdie/ai/colibri scripts/stage-colibri-iso.sh /path/to/image-root
2026-05-28 00:46:24 +02:00
# COLIBRI_ARTIFACT_DIR=/path/to/release scripts/stage-colibri-iso.sh /path/to/image-root
set -eu
if [ " ${ 1 :- } " = "" ] ; then
echo " usage: $0 DESTDIR " >& 2
exit 64
fi
DESTDIR = $1
SCRIPT_DIR = $( CDPATH = cd -- " $( dirname -- " $0 " ) " && pwd )
REPO_ROOT = $( CDPATH = cd -- " ${ SCRIPT_DIR } /.. " && pwd )
2026-06-05 00:38:25 +02:00
COLIBRI_REPO = ${ COLIBRI_REPO :- "/home/clawdie/ai/colibri" }
2026-05-28 00:46:24 +02:00
COLIBRI_ARTIFACT_DIR = ${ COLIBRI_ARTIFACT_DIR :- " ${ COLIBRI_REPO } /target/release " }
2026-06-04 12:58:24 +02:00
COLIBRI_STAGE_ENABLE = ${ COLIBRI_STAGE_ENABLE :- YES }
2026-05-28 00:46:24 +02:00
COLIBRI_STAGE_INCLUDE_TUI = ${ COLIBRI_STAGE_INCLUDE_TUI :- 1 }
2026-06-21 07:55:24 +02:00
COLIBRI_STAGE_TEST_AGENT = ${ COLIBRI_STAGE_TEST_AGENT :- NO }
2026-05-28 00:46:24 +02:00
COLIBRI_COST_MODE = ${ COLIBRI_COST_MODE :- smart }
BIN_DIR = " ${ DESTDIR } /usr/local/bin "
RC_DIR = " ${ DESTDIR } /usr/local/etc/rc.d "
ETC_DIR = " ${ DESTDIR } /usr/local/etc/colibri "
DB_DIR = " ${ DESTDIR } /var/db/colibri "
RUN_DIR = " ${ DESTDIR } /var/run/colibri "
LOG_DIR = " ${ DESTDIR } /var/log/colibri "
2026-06-02 09:00:33 +02:00
NEWSYSLOG_DIR = " ${ DESTDIR } /usr/local/etc/newsyslog.conf.d "
2026-05-28 00:46:24 +02:00
RC_SOURCE = " ${ COLIBRI_REPO } /packaging/freebsd/colibri_daemon.in "
2026-06-02 09:00:33 +02:00
NEWSYSLOG_SOURCE = " ${ COLIBRI_REPO } /packaging/freebsd/newsyslog-colibri.conf "
2026-05-28 00:46:24 +02:00
require_file( ) {
if [ ! -f " $1 " ] ; then
echo " missing required Colibri artifact: $1 " >& 2
exit 66
fi
}
require_exec( ) {
if [ ! -x " $1 " ] ; then
echo " missing executable Colibri artifact: $1 " >& 2
echo " hint: (cd ${ COLIBRI_REPO } && cargo build --workspace --release) " >& 2
exit 66
fi
}
copy_bin( ) {
require_exec " ${ COLIBRI_ARTIFACT_DIR } / $1 "
install -m 0555 " ${ COLIBRI_ARTIFACT_DIR } / $1 " " ${ BIN_DIR } / $1 "
}
require_file " ${ RC_SOURCE } "
2026-06-02 09:00:33 +02:00
require_file " ${ NEWSYSLOG_SOURCE } "
mkdir -p " ${ BIN_DIR } " " ${ RC_DIR } " " ${ ETC_DIR } " " ${ NEWSYSLOG_DIR } " " ${ DB_DIR } " " ${ RUN_DIR } " " ${ LOG_DIR } "
2026-05-28 00:46:24 +02:00
copy_bin colibri-daemon
copy_bin colibri
2026-06-13 19:29:31 +02:00
copy_bin colibri-mcp
2026-05-28 00:46:24 +02:00
2026-06-21 07:55:24 +02:00
if [ " ${ COLIBRI_STAGE_TEST_AGENT } " = "YES" ] ; then
copy_bin colibri-test-agent
fi
2026-05-28 00:46:24 +02:00
if [ " ${ COLIBRI_STAGE_INCLUDE_TUI } " != "0" ] && [ -x " ${ COLIBRI_ARTIFACT_DIR } /colibri-tui " ] ; then
copy_bin colibri-tui
fi
install -m 0555 " ${ RC_SOURCE } " " ${ RC_DIR } /colibri_daemon "
2026-06-02 09:00:33 +02:00
install -m 0644 " ${ NEWSYSLOG_SOURCE } " " ${ NEWSYSLOG_DIR } /colibri.conf "
if ! grep -q '^command="/usr/sbin/daemon"' " ${ RC_DIR } /colibri_daemon " || \
2026-06-14 23:24:41 +02:00
! grep -q -- '-o .*colibri_daemon_binary' " ${ RC_DIR } /colibri_daemon " || \
2026-06-15 07:37:16 +02:00
! grep -q 'colibri_daemon_provider_env' " ${ RC_DIR } /colibri_daemon " || \
2026-06-15 09:10:52 +02:00
! grep -q 'colibri_daemon_cost_mode' " ${ RC_DIR } /colibri_daemon " || \
! grep -q 'rm -f "${pidfile}" "${supervisor_pidfile}"' " ${ RC_DIR } /colibri_daemon " || \
! grep -q 'export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"' " ${ RC_DIR } /colibri_daemon " ; then
2026-06-14 23:24:41 +02:00
echo "ERROR: staged colibri_daemon rc.d script is missing required live USB supervision hooks" >& 2
echo " Update COLIBRI_REPO ( ${ COLIBRI_REPO } ) before building. " >& 2
2026-06-02 09:00:33 +02:00
exit 66
fi
2026-05-28 00:46:24 +02:00
2026-06-14 23:25:22 +02:00
if grep -q -- '-u \${colibri_daemon_user}' " ${ RC_DIR } /colibri_daemon " ; then
2026-06-14 23:24:41 +02:00
echo "ERROR: staged colibri_daemon rc.d script has unsupported live USB command wiring" >& 2
echo " Update COLIBRI_REPO ( ${ COLIBRI_REPO } ) before building. " >& 2
exit 66
2026-06-14 22:09:54 +02:00
fi
2026-06-04 12:58:24 +02:00
2026-05-28 00:46:24 +02:00
cat > " ${ ETC_DIR } /rc.conf.sample " <<EOF
# Colibri control plane service defaults for the Clawdie ISO.
# Merge into /etc/rc.conf or /etc/rc.conf.d/colibri_daemon.
colibri_daemon_enable = " ${ COLIBRI_STAGE_ENABLE } "
colibri_daemon_user = "colibri"
colibri_daemon_group = "colibri"
colibri_daemon_data_dir = "/var/db/colibri"
colibri_daemon_run_dir = "/var/run/colibri"
colibri_daemon_socket = "/var/run/colibri/colibri.sock"
colibri_daemon_db_path = "/var/db/colibri/colibri.sqlite"
colibri_daemon_logfile = "/var/log/colibri/daemon.log"
2026-06-15 07:37:16 +02:00
colibri_daemon_provider_env = "/usr/local/etc/colibri/provider.env"
2026-06-15 09:10:52 +02:00
colibri_daemon_host = "\$(/bin/hostname)"
colibri_daemon_cost_mode = " ${ COLIBRI_COST_MODE } "
2026-05-28 00:46:24 +02:00
EOF
2026-06-20 07:27:51 +02:00
cat > " ${ ETC_DIR } /provider.env " <<'EOF'
# Non-secret Clawdie defaults. Keep this file mode 0600: operators may add
# provider keys and Vaultwarden bootstrap credentials here after boot.
VAULT_SERVER = "https://vault.smilepowered.org"
BW_SERVER = "https://vault.smilepowered.org"
2026-06-21 18:53:41 +02:00
2026-06-23 11:07:54 +02:00
# Auto-spawn one zot agent on daemon startup once a DeepSeek key is present
2026-06-21 18:53:41 +02:00
# (the Operator Image OOTB flow). The daemon sources this file, so the spawned
2026-06-23 11:07:54 +02:00
# agent inherits the provider keys set here.
2026-06-23 11:13:47 +02:00
COLIBRI_AUTOSPAWN = "YES"
2026-06-23 11:07:54 +02:00
# zot is the default harness — pi is available but zot has richer provider
# support (DeepSeek native, OpenRouter, ~25 providers) and a built-in
2026-06-23 11:13:47 +02:00
# Telegram bot mode. Set COLIBRI_AUTOSPAWN_BINARY=pi to switch back.
COLIBRI_AUTOSPAWN_BINARY = "zot"
2026-06-25 04:38:06 +02:00
# RPC prompt sent to the autospawned zot agent on first boot.
# zot is in RPC mode and blocks on stdin; this prompt kicks it off.
# The daemon collected hw-probe at boot (CLAWDIE_HW_PROFILE); the seed
# importer installed AGENTS.md with operational context; the provider
# keys are either baked or seeded. The agent's first job: register
# this node with mother, then report its assigned capabilities.
COLIBRI_AUTOSPAWN_RPC_PROMPT = "You just booted. Read CLAWDIE_HW_PROFILE from your environment and call node_register on the mother MCP server with that JSON payload. Then report what capabilities mother assigned and what tasks are available. Your AGENTS.md context explains the verbs and infrastructure."
2026-06-23 11:07:54 +02:00
# Telegram bot token — set this to enable the bot channel (@your_bot).
# Leave blank to use CLI/TUI/Dashboard channels only.
# TELEGRAM_BOT_TOKEN=""
2026-06-24 11:04:36 +02:00
# Enable external MCP server calls so the daemon can connect to mother
# (OSA) for hive-node registration. The SSH key comes from the seed
# partition; without it the connection fails gracefully.
COLIBRI_MCP_EXTERNAL_CALL = "1"
2026-06-20 07:27:51 +02:00
EOF
chmod 0600 " ${ ETC_DIR } /provider.env " 2>/dev/null || true
2026-06-24 11:04:36 +02:00
# External MCP server registry. The mother server entry is pre-configured so
2026-06-24 11:19:21 +02:00
# the daemon (running as user colibri) connects to mother OOTB via the
# "mother" SSH config alias. The SSH key, known_hosts, and Host definition
# for "mother" come from the CLAWDIESEED seed partition and are installed
# to /var/db/colibri/.ssh/ by the importer (see clawdie-live-seed.README.txt).
# Without the seed the connection fails gracefully — the daemon keeps running.
2026-06-24 11:04:36 +02:00
# Path matches colibri-mcp's default COLIBRI_MCP_EXTERNAL_CONFIG.
2026-06-21 18:53:41 +02:00
cat > " ${ ETC_DIR } /external-mcp.json " <<'EOF'
{
2026-06-24 11:04:36 +02:00
"servers" : {
"mother" : {
"command" : "ssh" ,
"args" : [
2026-06-24 11:19:21 +02:00
"-i" , "/var/db/colibri/.ssh/mother-mcp" ,
2026-06-24 11:25:18 +02:00
"-F" , "/var/db/colibri/.ssh/config" ,
2026-06-24 11:04:36 +02:00
"-o" , "StrictHostKeyChecking=accept-new" ,
2026-06-24 11:19:21 +02:00
"mother"
2026-06-24 11:04:36 +02:00
] ,
"env" : { }
}
}
2026-06-21 18:53:41 +02:00
}
EOF
chmod 0644 " ${ ETC_DIR } /external-mcp.json " 2>/dev/null || true
2026-06-15 07:37:16 +02:00
cat > " ${ ETC_DIR } /provider.env.sample " <<'EOF'
2026-06-20 07:27:51 +02:00
# Optional provider keys and Vaultwarden bootstrap credentials for
# colibri_daemon. The ISO already stages provider.env with the non-secret
# Clawdie Vaultwarden endpoint; copy values from here into provider.env,
# keep it mode 0600, then restart the service.
#
# Baked non-secret defaults:
VAULT_SERVER = "https://vault.smilepowered.org"
BW_SERVER = "https://vault.smilepowered.org"
#
# Vaultwarden bootstrap credentials (secret; operator-provided):
# BW_CLIENTID="..."
# BW_CLIENTSECRET="..."
# BW_PASSWORD="..."
2026-06-15 07:37:16 +02:00
#
2026-06-20 07:27:51 +02:00
# Direct provider keys (optional when Vaultwarden provisioning is used):
2026-06-15 07:37:16 +02:00
# DEEPSEEK_API_KEY="sk-..."
# OPENROUTER_API_KEY="sk-or-..."
# ANTHROPIC_API_KEY="sk-ant-..."
#
# Optional endpoints/models:
# DEEPSEEK_ENDPOINT="https://api.deepseek.com/chat/completions"
2026-06-23 10:49:38 +02:00
# DEEPSEEK_MODEL="deepseek-v4-pro"
2026-06-21 18:53:41 +02:00
#
2026-06-23 11:07:54 +02:00
# Telegram bot (optional):
# TELEGRAM_BOT_TOKEN="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
#
2026-06-21 18:53:41 +02:00
# Behavior toggles (non-secret):
2026-06-23 11:13:47 +02:00
# COLIBRI_AUTOSPAWN="YES" # auto-spawn one agent on daemon startup
# COLIBRI_AUTOSPAWN_BINARY="zot" # agent harness: zot (default) | pi
2026-06-23 18:08:58 +02:00
# COLIBRI_AUTOSPAWN_ARGS="rpc" # agent argv; default is harness-derived
# # (zot -> "rpc", pi -> "--mode json")
2026-06-23 11:07:54 +02:00
# COLIBRI_MCP_EXTERNAL_CALL="1" # allow agent (via colibri-mcp) to call
2026-06-21 18:53:41 +02:00
# # external MCP servers; set by
# # clawdie-enable-mother
2026-06-15 07:37:16 +02:00
EOF
2026-05-28 00:46:24 +02:00
cat > " ${ ETC_DIR } /README.iso " <<'EOF'
Colibri ISO staging notes
= = = = = = = = = = = = = = = = = = = = = = = = =
2026-06-02 09:00:33 +02:00
The ISO build creates the colibri user/group and stages the rc.d service.
2026-06-04 12:58:24 +02:00
The colibri-daemon runs under daemon( 8) supervision and is enabled at boot.
If the daemon fails, it restarts automatically without blocking SDDM/XFCE.
2026-06-15 07:37:16 +02:00
Provider keys are optional and live in /usr/local/etc/colibri/provider.env.
Keep that file root-owned and mode 0600, then restart colibri_daemon.
2026-06-04 12:58:24 +02:00
Runtime validation:
2026-05-28 00:46:24 +02:00
service colibri_daemon start
colibri status
2026-06-15 07:37:16 +02:00
colibri create-task --title "iso check"
2026-05-28 00:46:24 +02:00
colibri list-tasks --status queued
2026-06-13 19:29:31 +02:00
colibri-mcp tools
COLIBRI_MCP_WRITE = 1 colibri-mcp tools # trusted write-capable MCP profile
2026-05-28 00:46:24 +02:00
service colibri_daemon stop
EOF
2026-06-04 12:58:24 +02:00
chmod 0755 " ${ DB_DIR } " " ${ RUN_DIR } " " ${ LOG_DIR } "
2026-05-28 00:46:24 +02:00
cat <<EOF
Staged Colibri into ${ DESTDIR }
artifacts: ${ COLIBRI_ARTIFACT_DIR }
rc.d : ${ RC_SOURCE }
enable : ${ COLIBRI_STAGE_ENABLE }
cost : ${ COLIBRI_COST_MODE }
2026-06-21 07:55:24 +02:00
test bin : ${ COLIBRI_STAGE_TEST_AGENT }
2026-05-28 00:46:24 +02:00
EOF