diff --git a/build_deb.sh b/build_deb.sh new file mode 100644 index 0000000..77d8b44 --- /dev/null +++ b/build_deb.sh @@ -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" diff --git a/debian/DEBIAN/control b/debian/DEBIAN/control new file mode 100644 index 0000000..d0750e1 --- /dev/null +++ b/debian/DEBIAN/control @@ -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 +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. diff --git a/debian/DEBIAN/postinst b/debian/DEBIAN/postinst new file mode 100644 index 0000000..9c52bcb --- /dev/null +++ b/debian/DEBIAN/postinst @@ -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" diff --git a/debian/DEBIAN/postrm b/debian/DEBIAN/postrm new file mode 100644 index 0000000..2697012 --- /dev/null +++ b/debian/DEBIAN/postrm @@ -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 diff --git a/debian/DEBIAN/prerm b/debian/DEBIAN/prerm new file mode 100644 index 0000000..82d9bb8 --- /dev/null +++ b/debian/DEBIAN/prerm @@ -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/" diff --git a/debian/lib/systemd/system/activeblue-ai.service b/debian/lib/systemd/system/activeblue-ai.service new file mode 100644 index 0000000..2dc0909 --- /dev/null +++ b/debian/lib/systemd/system/activeblue-ai.service @@ -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 diff --git a/debian/usr/bin/activeblue-ai b/debian/usr/bin/activeblue-ai new file mode 100644 index 0000000..3da49a0 --- /dev/null +++ b/debian/usr/bin/activeblue-ai @@ -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 [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 diff --git a/publish_repo.sh b/publish_repo.sh new file mode 100644 index 0000000..bcaa01a --- /dev/null +++ b/publish_repo.sh @@ -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" <> "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= 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:///apt-repo $REPO_DIST $REPO_COMP" +echo "" +echo "Then:" +echo " sudo apt-get update" +echo " sudo apt-get install activeblue-ai"