From f6a11b6620ab6f895f7bc231ac76af010b178575 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Sat, 20 Jun 2026 21:35:06 +0200 Subject: [PATCH] feat(port): bundle colibri rc.d services into the canonical port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the canonical sysutils/colibri port install its rc.d services so `pkg install colibri` registers them — the one functional bit the (now-retiring) clawdie-iso port duplicate carried. Poudriere builds in a clean jail that only sees the port dir, so the rc.d templates live in files/ (mirrored from the canonical packaging/freebsd/ copies). - files/colibri_daemon.in, files/colibri_bridge.in (rc.d templates) - do-install: INSTALL_SCRIPT both into PREFIX/etc/rc.d/ (binary path PREFIX/bin/colibri-daemon already matches the daemon rc.d expectation) - pkg-plist: add the two etc/rc.d entries - README: document files/ + that this is the single canonical port Validation: rc.d sh -n clean; CARGO_CRATES drift check green (346); markdown gate clean. Port remains poudriere-build-unproven until the first mother-build run. Co-Authored-By: Claude Opus 4.8 --- packaging/freebsd/port/README.md | 12 +- .../freebsd/port/sysutils/colibri/Makefile | 6 + .../sysutils/colibri/files/colibri_bridge.in | 135 ++++++++++++ .../sysutils/colibri/files/colibri_daemon.in | 198 ++++++++++++++++++ .../freebsd/port/sysutils/colibri/pkg-plist | 2 + 5 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 packaging/freebsd/port/sysutils/colibri/files/colibri_bridge.in create mode 100644 packaging/freebsd/port/sysutils/colibri/files/colibri_daemon.in diff --git a/packaging/freebsd/port/README.md b/packaging/freebsd/port/README.md index ce95160..faa0c8c 100644 --- a/packaging/freebsd/port/README.md +++ b/packaging/freebsd/port/README.md @@ -8,11 +8,19 @@ port tracks the source. See clawdie-iso `docs/POUDRIERE-BUILD-SERVER.md`. ``` sysutils/colibri/ -├── Makefile port recipe (cargo; ships 6 runtime/operator binaries) +├── Makefile port recipe (cargo; ships 6 runtime/operator binaries + rc.d) ├── pkg-descr package description -└── pkg-plist installed file list +├── pkg-plist installed file list +└── files/ rc.d service templates installed into etc/rc.d/ + ├── colibri_daemon.in + └── colibri_bridge.in ``` +The `files/` rc.d templates mirror the canonical live-image copies in +`packaging/freebsd/`; poudriere builds in a clean jail and only sees the port +directory, so the package carries its own copy. This is the single canonical +port — the clawdie-iso repo consumes it from here and keeps no duplicate. + ## Generated content - **`CARGO_CRATES`** (committed, in the `Makefile`) — the crates.io dependency diff --git a/packaging/freebsd/port/sysutils/colibri/Makefile b/packaging/freebsd/port/sysutils/colibri/Makefile index ab1fad5..e6fc97c 100644 --- a/packaging/freebsd/port/sysutils/colibri/Makefile +++ b/packaging/freebsd/port/sysutils/colibri/Makefile @@ -392,5 +392,11 @@ do-install: ${CARGO_TARGET_DIR}/${CARGO_BUILD_TARGET}/release/${b} \ ${STAGEDIR}${PREFIX}/bin/${b} .endfor + # rc.d services (the daemon's rc.d expects the binary at PREFIX/bin/colibri-daemon). + @${MKDIR} ${STAGEDIR}${PREFIX}/etc/rc.d + ${INSTALL_SCRIPT} ${FILESDIR}/colibri_daemon.in \ + ${STAGEDIR}${PREFIX}/etc/rc.d/colibri_daemon + ${INSTALL_SCRIPT} ${FILESDIR}/colibri_bridge.in \ + ${STAGEDIR}${PREFIX}/etc/rc.d/colibri_bridge .include diff --git a/packaging/freebsd/port/sysutils/colibri/files/colibri_bridge.in b/packaging/freebsd/port/sysutils/colibri/files/colibri_bridge.in new file mode 100644 index 0000000..465c414 --- /dev/null +++ b/packaging/freebsd/port/sysutils/colibri/files/colibri_bridge.in @@ -0,0 +1,135 @@ +#!/bin/sh +# +# Colibri TCP bridge — FreeBSD rc.d service +# +# Bridges the colibri-daemon Unix socket to a TCP port on the Tailscale +# interface so remote hosts (debby, domedog) can reach the Colibri control +# plane through the mesh VPN. +# +# socat runs in the FOREGROUND — it does not self-daemonize. rc.d runs it +# under daemon(8), which backgrounds it, writes a pidfile, restarts on crash, +# and redirects stdout/stderr to a logfile. +# +# Setup (one-time, as root): +# cp /home/clawdie/ai/hermes-bsd/packaging/freebsd/colibri_bridge.in \ +# /usr/local/etc/rc.d/colibri_bridge +# chmod 555 /usr/local/etc/rc.d/colibri_bridge +# sysrc colibri_bridge_enable=YES +# +# Requires: +# - socat installed (pkg install socat) +# - colibri_daemon running (socket at /var/run/colibri/colibri.sock) +# - pf rule for the bridge port on tailscale0 + +# PROVIDE: colibri_bridge +# REQUIRE: LOGIN cleanvar colibri_daemon +# BEFORE: hermes_dashboard +# KEYWORD: shutdown + +. /etc/rc.subr + +name="colibri_bridge" +rcvar="colibri_bridge_enable" + +load_rc_config $name + +: ${colibri_bridge_enable:="NO"} +: ${colibri_bridge_user:="clawdie"} +: ${colibri_bridge_group:="clawdie"} +: ${colibri_bridge_listen_addr:="100.72.229.63"} +: ${colibri_bridge_listen_port:="9190"} +: ${colibri_bridge_socket:="/var/run/colibri/colibri.sock"} +: ${colibri_bridge_run_dir:="/var/run/colibri"} +: ${colibri_bridge_logfile:="/var/log/colibri/bridge.log"} +: ${colibri_bridge_socat:="/usr/local/bin/socat"} + +pidfile="${colibri_bridge_run_dir}/colibri-bridge.pid" +supervisor_pidfile="${colibri_bridge_run_dir}/colibri-bridge-supervisor.pid" + +command="/usr/sbin/daemon" +command_args="-P ${supervisor_pidfile} -p ${pidfile} -t ${name} \ + -o ${colibri_bridge_logfile} ${colibri_bridge_socat} \ + TCP-LISTEN:${colibri_bridge_listen_port},bind=${colibri_bridge_listen_addr},fork,reuseaddr \ + UNIX-CONNECT:${colibri_bridge_socket}" + +# procname must match the actual child process so rc.subr finds the right PID +# and doesn't kill the wrong daemon(8) child. +procname="socat" + +start_precmd="colibri_bridge_prestart" +stop_cmd="colibri_bridge_stop" +status_cmd="colibri_bridge_status" +extra_commands="health" + +colibri_bridge_prestart() +{ + install -d -o "${colibri_bridge_user}" -g "${colibri_bridge_group}" -m 0750 \ + "${colibri_bridge_run_dir}" + install -d -o "${colibri_bridge_user}" -g "${colibri_bridge_group}" -m 0750 \ + "$(/usr/bin/dirname "${colibri_bridge_logfile}")" + + rm -f "${pidfile}" "${supervisor_pidfile}" + + if [ ! -x "${colibri_bridge_socat}" ]; then + echo "ERROR: socat not found at ${colibri_bridge_socat}" + return 1 + fi + if [ ! -S "${colibri_bridge_socket}" ]; then + echo "ERROR: colibri socket not found at ${colibri_bridge_socket}" + echo " Start colibri_daemon first: service colibri_daemon start" + return 1 + fi +} + +colibri_bridge_stop() +{ + local _sup="" + [ -f "${supervisor_pidfile}" ] && _sup=$(cat "${supervisor_pidfile}" 2>/dev/null) + if [ -n "${_sup}" ] && kill -0 "${_sup}" 2>/dev/null; then + echo "Stopping ${name} (daemon(8) supervisor pid ${_sup})." + kill -TERM "${_sup}" 2>/dev/null + local _n=0 + while kill -0 "${_sup}" 2>/dev/null && [ ${_n} -lt 30 ]; do + sleep 1 + _n=$((_n + 1)) + done + if kill -0 "${_sup}" 2>/dev/null; then + echo "Supervisor did not exit in time; sending SIGKILL." + kill -KILL "${_sup}" 2>/dev/null + fi + else + echo "${name} is not running." + fi + local _ch="" + [ -f "${pidfile}" ] && _ch=$(cat "${pidfile}" 2>/dev/null) + if [ -n "${_ch}" ] && kill -0 "${_ch}" 2>/dev/null; then + kill -TERM "${_ch}" 2>/dev/null + fi + rm -f "${supervisor_pidfile}" "${pidfile}" +} + +health_cmd="colibri_bridge_health" +colibri_bridge_health() + +colibri_bridge_status() +{ +tcolibri_bridge_health +} +{ + if ! pgrep -f "socat.*${colibri_bridge_listen_port}" >/dev/null 2>&1; then + echo "colibri-bridge socat process not found" + return 1 + fi + + _resp=$(printf '{"cmd":"status"}\n' | \ + nc -w 2 "${colibri_bridge_listen_addr}" "${colibri_bridge_listen_port}" 2>/dev/null) + if [ -n "${_resp}" ]; then + echo "colibri-bridge is healthy (port ${colibri_bridge_listen_port} responding)" + return 0 + else + echo "colibri-bridge socat running but no response on port ${colibri_bridge_listen_port}" + return 1 + fi +} + +run_rc_command "$1" diff --git a/packaging/freebsd/port/sysutils/colibri/files/colibri_daemon.in b/packaging/freebsd/port/sysutils/colibri/files/colibri_daemon.in new file mode 100644 index 0000000..0dae534 --- /dev/null +++ b/packaging/freebsd/port/sysutils/colibri/files/colibri_daemon.in @@ -0,0 +1,198 @@ +#!/bin/sh +# +# Colibri daemon — FreeBSD rc.d service +# +# colibri-daemon runs in the FOREGROUND — it does not self-daemonize or write a +# pidfile. rc.d runs it under daemon(8), which backgrounds it, writes the +# child pidfile (colibri-daemon PID), restarts on crash, and redirects +# stdout/stderr (tracing) to a logfile. rc.subr performs the privilege drop +# through ${name}_user. +# +# Setup (one-time, as root): +# pw groupadd colibri +# pw useradd colibri -g colibri -d /var/db/colibri -s /usr/sbin/nologin +# cp packaging/freebsd/colibri_daemon.in /usr/local/etc/rc.d/colibri_daemon +# chmod 555 /usr/local/etc/rc.d/colibri_daemon +# sysrc colibri_daemon_enable=YES # or NO during dual-run +# cp packaging/freebsd/provider.env.example /usr/local/etc/colibri/provider.env +# chmod 600 /usr/local/etc/colibri/provider.env +# $EDITOR /usr/local/etc/colibri/provider.env # fill in vault credentials +# +# Runtime: +# service colibri_daemon start +# service colibri_daemon status +# service colibri_daemon stop +# +# Requires: +# - colibri-daemon binary at /usr/local/bin/colibri-daemon +# - colibri user/group (privilege drop target) + +# PROVIDE: colibri_daemon +# REQUIRE: LOGIN cleanvar +# KEYWORD: shutdown + +. /etc/rc.subr + +name="colibri_daemon" +rcvar="colibri_daemon_enable" + +load_rc_config $name + +: ${colibri_daemon_enable:="NO"} +: ${colibri_daemon_user:="colibri"} +: ${colibri_daemon_group:="colibri"} +: ${colibri_daemon_binary:="/usr/local/bin/colibri-daemon"} +: ${colibri_daemon_data_dir:="/var/db/colibri"} +: ${colibri_daemon_run_dir:="/var/run/colibri"} +: ${colibri_daemon_socket:="${colibri_daemon_run_dir}/colibri.sock"} +: ${colibri_daemon_db_path:="${colibri_daemon_data_dir}/colibri.sqlite"} +: ${colibri_daemon_logfile:="/var/log/colibri/daemon.log"} +: ${colibri_daemon_provider_env:="/usr/local/etc/colibri/provider.env"} +: ${colibri_daemon_host:="$(/bin/hostname)"} +: ${colibri_daemon_cost_mode:="smart"} + +pidfile="${colibri_daemon_run_dir}/colibri-daemon.pid" +# Supervisor pidfile (the daemon(8) parent). Kept distinct from the child +# pidfile so `stop` can target the supervisor — see colibri_daemon_stop. +supervisor_pidfile="${colibri_daemon_run_dir}/colibri-daemon-supervisor.pid" + +# Run colibri-daemon under daemon(8): +# -P supervisor pidfile (the daemon(8) parent — used by stop) +# -p child pidfile (writes colibri-daemon PID — used by start/status) +# -r restart on crash, -t process title, +# -o append stdout/stderr to log. +# User selection is handled by rc.subr through colibri_daemon_user. +command="/usr/sbin/daemon" +command_args="-P ${supervisor_pidfile} -p ${pidfile} -r -t ${name} \ + -o ${colibri_daemon_logfile} ${colibri_daemon_binary}" + +# Use the child's process name so rc.subr can find the right process via the +# child pidfile. Using the daemon(8) supervisor path would collide with +# tailscaled and other daemon(8)-managed services on the system. +procname="colibri-daemon" + +start_precmd="colibri_daemon_prestart" +start_postcmd="colibri_daemon_poststart" +stop_cmd="colibri_daemon_stop" +stop_postcmd="colibri_daemon_poststop" +extra_commands="health" + +colibri_daemon_prestart() +{ + # /var/run is tmpfs on FreeBSD (wiped each boot) — recreate every start. + install -d -o "${colibri_daemon_user}" -g "${colibri_daemon_group}" -m 0750 \ + "${colibri_daemon_run_dir}" + install -d -o "${colibri_daemon_user}" -g "${colibri_daemon_group}" -m 0750 \ + "${colibri_daemon_data_dir}" + install -d -o "${colibri_daemon_user}" -g "${colibri_daemon_group}" -m 0750 \ + "$(/usr/bin/dirname "${colibri_daemon_logfile}")" + + # Clear stale pidfiles before starting so a previous hard-killed run does + # not trip rc.subr's "already running" check. The socket is left to the + # daemon: it removes a stale socket on bind but refuses to start if a live + # daemon is already listening, so an rc-side rm here could only clobber a + # running instance. + rm -f "${pidfile}" "${supervisor_pidfile}" + + # Provider keys are optional. Keep them in a root-owned env file instead of + # rc.conf so they are easy to rotate and not world-readable. + if [ -r "${colibri_daemon_provider_env}" ]; then + set -a + . "${colibri_daemon_provider_env}" + set +a + fi + + # Config is passed to the child via the environment. + export COLIBRI_DAEMON_DATA_DIR="${colibri_daemon_data_dir}" + export COLIBRI_DAEMON_SOCKET="${colibri_daemon_socket}" + export COLIBRI_DB_PATH="${colibri_daemon_db_path}" + export COLIBRI_HOST="${colibri_daemon_host}" + export COLIBRI_COST_MODE="${colibri_daemon_cost_mode}" + export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +} + +colibri_daemon_poststart() +{ + # Wait for the socket to appear (daemon forks, child binds socket). + local timeout=10 + local waited=0 + while [ ! -S "${colibri_daemon_socket}" ] && [ $waited -lt $timeout ]; do + sleep 1 + waited=$((waited + 1)) + done + + if [ -S "${colibri_daemon_socket}" ]; then + echo "colibri-daemon socket ready after ${waited}s" + else + echo "WARNING: colibri-daemon socket not ready after ${timeout}s" + fi +} + +colibri_daemon_stop() +{ + # daemon(8) -r restarts the child if it is killed directly, so a plain + # SIGTERM to the child pidfile would just be undone. Stop the supervisor + # instead: on SIGTERM it forwards the signal to the child and exits without + # restarting it. + local _sup="" + [ -f "${supervisor_pidfile}" ] && _sup=$(cat "${supervisor_pidfile}" 2>/dev/null) + if [ -n "${_sup}" ] && kill -0 "${_sup}" 2>/dev/null; then + echo "Stopping ${name} (daemon(8) supervisor pid ${_sup})." + kill -TERM "${_sup}" 2>/dev/null + local _n=0 + while kill -0 "${_sup}" 2>/dev/null && [ ${_n} -lt 30 ]; do + sleep 1 + _n=$((_n + 1)) + done + if kill -0 "${_sup}" 2>/dev/null; then + echo "Supervisor did not exit in time; sending SIGKILL." + kill -KILL "${_sup}" 2>/dev/null + fi + else + echo "${name} is not running." + fi + # Belt-and-suspenders: terminate the child if it somehow outlived the + # supervisor (e.g. supervisor SIGKILLed before it could clean up). + local _ch="" + [ -f "${pidfile}" ] && _ch=$(cat "${pidfile}" 2>/dev/null) + if [ -n "${_ch}" ] && kill -0 "${_ch}" 2>/dev/null; then + kill -TERM "${_ch}" 2>/dev/null + fi + rm -f "${supervisor_pidfile}" "${pidfile}" +} + +colibri_daemon_poststop() +{ + # Clean up tmpfs artifacts on graceful shutdown. + # The pidfile is managed by daemon(8); socket is the child's. + if [ -S "${colibri_daemon_socket}" ]; then + rm -f "${colibri_daemon_socket}" + fi +} + +health_cmd="colibri_daemon_health" +status_cmd="colibri_daemon_status" + +colibri_daemon_health() +{ + if [ ! -S "${colibri_daemon_socket}" ]; then + echo "colibri-daemon socket not found" + return 1 + fi + + _resp=$(printf '{"cmd":"status"}\n' | nc -U "${colibri_daemon_socket}" -w 2 2>/dev/null) + if [ -n "${_resp}" ]; then + echo "colibri-daemon is healthy (socket responding)" + return 0 + else + echo "colibri-daemon socket exists but no response" + return 1 + fi +} + +colibri_daemon_status() +{ + colibri_daemon_health +} + +run_rc_command "$1" diff --git a/packaging/freebsd/port/sysutils/colibri/pkg-plist b/packaging/freebsd/port/sysutils/colibri/pkg-plist index da2fd1b..89ac5f5 100644 --- a/packaging/freebsd/port/sysutils/colibri/pkg-plist +++ b/packaging/freebsd/port/sysutils/colibri/pkg-plist @@ -4,3 +4,5 @@ bin/colibri-daemon bin/colibri-mcp bin/colibri-test-agent bin/colibri-tui +etc/rc.d/colibri_daemon +etc/rc.d/colibri_bridge -- 2.45.3