feat(packaging): add Debian packaging and APT repository scripts

debian/DEBIAN/control: package metadata, depends on python3.11+, postgresql-client
debian/DEBIAN/postinst: creates activeblue-ai system user, installs venv, enables service
debian/DEBIAN/prerm: stops and disables service before removal
debian/DEBIAN/postrm: purge removes config, logs, venv, and system user
debian/lib/systemd/system/activeblue-ai.service:
  - Runs as dedicated user with PrivateTmp + ProtectSystem hardening
  - EnvironmentFile=/etc/activeblue-ai/.env
  - Restart=on-failure with 5s backoff
debian/usr/bin/activeblue-ai: CLI with start/stop/restart/status/logs/migrate/health/sweep/privacy/version
build_deb.sh: builds activeblue-ai_X.Y.Z_all.deb in dist/
publish_repo.sh: scans packages, generates Release + checksums, optional GPG signing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ActiveBlue Build
2026-04-12 18:09:48 -04:00
parent 7487fc73f9
commit fb4bf56816
8 changed files with 392 additions and 0 deletions

72
build_deb.sh Normal file
View File

@@ -0,0 +1,72 @@
#!/bin/bash
# Build the ActiveBlue AI Debian package
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VERSION=$(cat "$SCRIPT_DIR/VERSION" 2>/dev/null || echo "0.1.0")
PKG_NAME="activeblue-ai"
PKG_DIR="$SCRIPT_DIR/build/${PKG_NAME}_${VERSION}"
DIST_DIR="$SCRIPT_DIR/dist"
echo "Building $PKG_NAME v$VERSION"
# Clean previous build
rm -rf "$PKG_DIR"
mkdir -p "$PKG_DIR"
# Copy Debian control files
cp -r "$SCRIPT_DIR/debian/DEBIAN" "$PKG_DIR/DEBIAN"
# Update version in control file
sed -i "s/^Version:.*/Version: $VERSION/" "$PKG_DIR/DEBIAN/control"
# Make maintainer scripts executable
chmod 755 "$PKG_DIR/DEBIAN/postinst" \
"$PKG_DIR/DEBIAN/prerm" \
"$PKG_DIR/DEBIAN/postrm"
# Install application files to /usr/lib/activeblue-ai
APP_DIR="$PKG_DIR/usr/lib/activeblue-ai"
mkdir -p "$APP_DIR"
cp -r "$SCRIPT_DIR/agent_service" "$APP_DIR/"
cp "$SCRIPT_DIR/requirements.txt" "$APP_DIR/"
cp "$SCRIPT_DIR/VERSION" "$APP_DIR/"
# Remove Python cache
find "$APP_DIR" -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null || true
find "$APP_DIR" -name '*.pyc' -delete 2>/dev/null || true
# Install systemd unit
mkdir -p "$PKG_DIR/lib/systemd/system"
cp "$SCRIPT_DIR/debian/lib/systemd/system/activeblue-ai.service" \
"$PKG_DIR/lib/systemd/system/"
# Install CLI tool
mkdir -p "$PKG_DIR/usr/bin"
cp "$SCRIPT_DIR/debian/usr/bin/activeblue-ai" "$PKG_DIR/usr/bin/"
chmod 755 "$PKG_DIR/usr/bin/activeblue-ai"
# Install default config
mkdir -p "$PKG_DIR/etc/activeblue-ai"
cp "$SCRIPT_DIR/.env.example" "$PKG_DIR/etc/activeblue-ai/.env.example"
# Install documentation
mkdir -p "$PKG_DIR/usr/share/doc/activeblue-ai"
cp "$SCRIPT_DIR/README.md" "$PKG_DIR/usr/share/doc/activeblue-ai/"
# Calculate installed size (kB)
SIZE=$(du -sk "$PKG_DIR" | cut -f1)
sed -i "s/^Installed-Size:.*/Installed-Size: $SIZE/" "$PKG_DIR/DEBIAN/control" 2>/dev/null || \
echo "Installed-Size: $SIZE" >> "$PKG_DIR/DEBIAN/control"
# Build the .deb
mkdir -p "$DIST_DIR"
DEB_FILE="$DIST_DIR/${PKG_NAME}_${VERSION}_all.deb"
dpkg-deb --build --root-owner-group "$PKG_DIR" "$DEB_FILE"
echo ""
echo "Package built: $DEB_FILE"
echo ""
echo "Install with:"
echo " sudo dpkg -i $DEB_FILE"
echo " sudo apt-get install -f # if there are dependency issues"

15
debian/DEBIAN/control vendored Normal file
View File

@@ -0,0 +1,15 @@
Package: activeblue-ai
Version: 0.1.0
Section: misc
Priority: optional
Architecture: all
Depends: python3 (>= 3.11), python3-pip, python3-venv, postgresql-client
Maintainer: ActiveBlue <admin@activeblue.net>
Homepage: https://activeblue.net
Description: ActiveBlue AI Agent Service
Multi-agent AI system integrated with Odoo 18 Community.
Provides a FastAPI service with 8 specialist AI agents for finance,
accounting, CRM, sales, project management, eLearning, expenses, and HR.
.
Supports local (Ollama), hybrid, and cloud (Claude) LLM backends.
HIPAA-sensitive agents are enforced to use local LLM only.

47
debian/DEBIAN/postinst vendored Normal file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
set -e
SERVICE_DIR="/usr/lib/activeblue-ai"
VENV_DIR="$SERVICE_DIR/venv"
CONFIG_DIR="/etc/activeblue-ai"
LOG_DIR="/var/log/activeblue-ai"
DATA_DIR="/var/lib/activeblue-ai"
SERVICE_USER="activeblue-ai"
# Create service user
if ! id "$SERVICE_USER" &>/dev/null; then
adduser --system --no-create-home --group --disabled-login \
--home "$DATA_DIR" "$SERVICE_USER"
echo "Created service user: $SERVICE_USER"
fi
# Create directories
install -d -m 755 -o "$SERVICE_USER" -g "$SERVICE_USER" "$LOG_DIR"
install -d -m 755 -o "$SERVICE_USER" -g "$SERVICE_USER" "$DATA_DIR"
install -d -m 750 -o "$SERVICE_USER" -g "$SERVICE_USER" "$CONFIG_DIR"
# Create .env if it doesn't exist
if [ ! -f "$CONFIG_DIR/.env" ]; then
cp "$CONFIG_DIR/.env.example" "$CONFIG_DIR/.env" 2>/dev/null || true
chmod 640 "$CONFIG_DIR/.env"
chown "$SERVICE_USER:$SERVICE_USER" "$CONFIG_DIR/.env"
echo "NOTE: Edit $CONFIG_DIR/.env before starting the service."
fi
# Create and populate virtualenv
if [ ! -d "$VENV_DIR" ]; then
python3 -m venv "$VENV_DIR"
fi
"$VENV_DIR/bin/pip" install --quiet --upgrade pip
"$VENV_DIR/bin/pip" install --quiet -r "$SERVICE_DIR/requirements.txt"
# Enable and reload systemd
if command -v systemctl &>/dev/null && systemctl is-system-running --quiet 2>/dev/null; then
systemctl daemon-reload
systemctl enable activeblue-ai.service
echo "Service enabled. Run: systemctl start activeblue-ai"
fi
echo "ActiveBlue AI installed successfully."
echo "Configure: $CONFIG_DIR/.env"
echo "Run migrations: activeblue-ai migrate"

12
debian/DEBIAN/postrm vendored Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
set -e
if [ "$1" = "purge" ]; then
rm -rf /usr/lib/activeblue-ai/venv
rm -rf /var/log/activeblue-ai
rm -rf /etc/activeblue-ai
if id activeblue-ai &>/dev/null; then
deluser --system activeblue-ai || true
fi
echo "ActiveBlue AI purged."
fi

16
debian/DEBIAN/prerm vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
if command -v systemctl &>/dev/null; then
if systemctl is-active --quiet activeblue-ai.service 2>/dev/null; then
systemctl stop activeblue-ai.service || true
fi
if systemctl is-enabled --quiet activeblue-ai.service 2>/dev/null; then
systemctl disable activeblue-ai.service || true
fi
systemctl daemon-reload || true
fi
echo "ActiveBlue AI service stopped."
echo "Configuration preserved in /etc/activeblue-ai/"
echo "Data preserved in /var/lib/activeblue-ai/"

View File

@@ -0,0 +1,35 @@
[Unit]
Description=ActiveBlue AI Agent Service
Documentation=https://activeblue.net
After=network.target postgresql.service
Wants=network.target
[Service]
Type=simple
User=activeblue-ai
Group=activeblue-ai
WorkingDirectory=/usr/lib/activeblue-ai
EnvironmentFile=/etc/activeblue-ai/.env
ExecStart=/usr/lib/activeblue-ai/venv/bin/uvicorn \
agent_service.main:app \
--host 0.0.0.0 \
--port 8001 \
--workers 1 \
--no-access-log
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=activeblue-ai
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/var/log/activeblue-ai /var/lib/activeblue-ai
TimeoutStartSec=60
TimeoutStopSec=30
KillMode=mixed
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target

99
debian/usr/bin/activeblue-ai vendored Normal file
View File

@@ -0,0 +1,99 @@
#!/bin/bash
# ActiveBlue AI CLI tool
set -e
SERVICE_DIR="/usr/lib/activeblue-ai"
VENV="$SERVICE_DIR/venv"
CONFIG="/etc/activeblue-ai/.env"
PYTHON="$VENV/bin/python3"
if [ ! -f "$CONFIG" ]; then
echo "ERROR: Config not found at $CONFIG" >&2
exit 1
fi
export $(grep -v '^#' "$CONFIG" | grep -v '^$' | xargs 2>/dev/null) 2>/dev/null || true
cmd="${1:-help}"
shift || true
case "$cmd" in
start)
echo "Starting ActiveBlue AI service..."
systemctl start activeblue-ai.service
;;
stop)
echo "Stopping ActiveBlue AI service..."
systemctl stop activeblue-ai.service
;;
restart)
echo "Restarting ActiveBlue AI service..."
systemctl restart activeblue-ai.service
;;
status)
systemctl status activeblue-ai.service
;;
logs)
journalctl -u activeblue-ai.service -f "${@}"
;;
migrate)
echo "Running Alembic migrations..."
cd "$SERVICE_DIR"
"$VENV/bin/alembic" -c agent_service/migrations/alembic.ini upgrade head
;;
health)
PORT="${AGENT_SERVICE_PORT:-8001}"
curl -sf "http://localhost:${PORT}/health/detailed" | python3 -m json.tool
;;
sweep)
PORT="${AGENT_SERVICE_PORT:-8001}"
AGENTS="${1:-}"
if [ -n "$AGENTS" ]; then
curl -sf -X POST "http://localhost:${PORT}/sweep" \
-H 'Content-Type: application/json' \
-d "{\"agents\": [\"$AGENTS\"]}" | python3 -m json.tool
else
curl -sf -X POST "http://localhost:${PORT}/sweep" \
-H 'Content-Type: application/json' \
-d '{"agents": []}' | python3 -m json.tool
fi
;;
privacy)
MODE="${1:-}"
if [ -z "$MODE" ]; then
echo "Current privacy mode: ${LLM_PRIVACY_MODE:-local}"
else
sed -i "s/^LLM_PRIVACY_MODE=.*/LLM_PRIVACY_MODE=$MODE/" "$CONFIG"
echo "Privacy mode set to: $MODE"
echo "Restart the service for changes to take effect: activeblue-ai restart"
fi
;;
version)
cat "$SERVICE_DIR/VERSION" 2>/dev/null || echo "0.1.0"
;;
help|--help|-h)
cat <<'HELP'
ActiveBlue AI — CLI tool
Usage: activeblue-ai <command> [options]
Commands:
start Start the agent service
stop Stop the agent service
restart Restart the agent service
status Show systemd service status
logs [flags] Follow service logs (passes flags to journalctl)
migrate Run Alembic database migrations
health Show detailed health status
sweep [agent] Trigger a proactive sweep (all agents or named agent)
privacy [mode] Get or set LLM privacy mode (local|hybrid|cloud)
version Show installed version
help Show this help
HELP
;;
*)
echo "Unknown command: $cmd" >&2
echo "Run 'activeblue-ai help' for usage." >&2
exit 1
;;
esac

96
publish_repo.sh Normal file
View File

@@ -0,0 +1,96 @@
#!/bin/bash
# Publish ActiveBlue AI to a local APT repository
# Run this on the repository host after build_deb.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VERSION=$(cat "$SCRIPT_DIR/VERSION" 2>/dev/null || echo "0.1.0")
PKG_NAME="activeblue-ai"
DEB_FILE="$SCRIPT_DIR/dist/${PKG_NAME}_${VERSION}_all.deb"
# APT repo configuration
REPO_ROOT="${REPO_ROOT:-/srv/apt-repo}"
REPO_DIST="${REPO_DIST:-stable}"
REPO_COMP="${REPO_COMP:-main}"
REPO_ARCH="${REPO_ARCH:-all}"
GPG_KEY="${GPG_KEY:-}"
if [ ! -f "$DEB_FILE" ]; then
echo "ERROR: .deb not found at $DEB_FILE" >&2
echo "Run ./build_deb.sh first." >&2
exit 1
fi
if ! command -v dpkg-scanpackages &>/dev/null; then
echo "Installing dpkg-dev..."
sudo apt-get install -y dpkg-dev apt-utils
fi
# Create repo structure
POOL_DIR="$REPO_ROOT/pool/$REPO_DIST/$REPO_COMP"
DISTS_DIR="$REPO_ROOT/dists/$REPO_DIST/$REPO_COMP/binary-$REPO_ARCH"
mkdir -p "$POOL_DIR" "$DISTS_DIR"
# Copy .deb to pool
cp -v "$DEB_FILE" "$POOL_DIR/"
# Generate Packages index
cd "$REPO_ROOT"
dpkg-scanpackages "pool/$REPO_DIST/$REPO_COMP" /dev/null > "dists/$REPO_DIST/$REPO_COMP/binary-$REPO_ARCH/Packages"
gzip -9 -c "dists/$REPO_DIST/$REPO_COMP/binary-$REPO_ARCH/Packages" > \
"dists/$REPO_DIST/$REPO_COMP/binary-$REPO_ARCH/Packages.gz"
# Generate Release file
SUITE="$REPO_DIST"
DATE=$(date -uR)
cat > "dists/$REPO_DIST/Release" <<RELEASE
Origin: ActiveBlue
Label: ActiveBlue AI
Suite: $SUITE
Version: $VERSION
Codename: $REPO_DIST
Architectures: $REPO_ARCH
Components: $REPO_COMP
Description: ActiveBlue AI APT repository
Date: $DATE
RELEASE
# Add checksums
{
echo "MD5Sum:"
find "dists/$REPO_DIST/$REPO_COMP" -type f | while read f; do
rel="${f#dists/$REPO_DIST/}"
md5=$(md5sum "$f" | cut -d' ' -f1)
size=$(stat -c%s "$f")
echo " $md5 $size $rel"
done
echo "SHA256:"
find "dists/$REPO_DIST/$REPO_COMP" -type f | while read f; do
rel="${f#dists/$REPO_DIST/}"
sha=$(sha256sum "$f" | cut -d' ' -f1)
size=$(stat -c%s "$f")
echo " $sha $size $rel"
done
} >> "dists/$REPO_DIST/Release"
# Sign with GPG if key provided
if [ -n "$GPG_KEY" ]; then
gpg --default-key "$GPG_KEY" --armor --detach-sign \
-o "dists/$REPO_DIST/Release.gpg" "dists/$REPO_DIST/Release"
gpg --default-key "$GPG_KEY" --clearsign \
-o "dists/$REPO_DIST/InRelease" "dists/$REPO_DIST/Release"
echo "Release signed with GPG key: $GPG_KEY"
else
echo "WARNING: No GPG_KEY set — repository is unsigned."
echo "Set GPG_KEY=<key-id> to sign the release."
fi
echo ""
echo "Repository published to: $REPO_ROOT"
echo ""
echo "To use this repository, add to /etc/apt/sources.list.d/activeblue.list:"
echo " deb [trusted=yes] http://<your-server>/apt-repo $REPO_DIST $REPO_COMP"
echo ""
echo "Then:"
echo " sudo apt-get update"
echo " sudo apt-get install activeblue-ai"