- Before bringing up the stack, check if the agent-db volume exists but is missing the expected database (left from a previous broken run with wrong POSTGRES_DB). Offer to wipe and re-init automatically. - After docker compose up, wait for pg_isready then run `alembic upgrade head` inside the agent container so tables are created before the agent attempts to use them. - Restart agent-service after migrations so it connects to a fully initialized database on its first attempt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
429 lines
19 KiB
Bash
429 lines
19 KiB
Bash
#!/usr/bin/env bash
|
|
# setup.sh — Configure ActiveBlue AI agent service
|
|
#
|
|
# Run once (or re-run to update) from /root/odoo/odoo-ai/ on the miaai host.
|
|
# Auto-discovers: Odoo container/DB, Ollama URL/model, Postgres service name.
|
|
# Auto-generates: Postgres password, AGENT_API_KEY, WEBHOOK_SECRET.
|
|
# Preserves : all secrets on re-run (never regenerates existing values).
|
|
# Prompts only : when Odoo API key can't be auto-created.
|
|
|
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ENV_FILE="$SCRIPT_DIR/.env"
|
|
|
|
# ── colours ────────────────────────────────────────────────────────────────────
|
|
G='\033[0;32m' Y='\033[1;33m' R='\033[0;31m' B='\033[0;34m' N='\033[0m'
|
|
info() { echo -e "${G}[✓]${N} $*"; }
|
|
warn() { echo -e "${Y}[!]${N} $*"; }
|
|
ask() { echo -e "${B}[?]${N} $*"; }
|
|
die() { echo -e "${R}[✗]${N} $*" >&2; exit 1; }
|
|
|
|
gen() { openssl rand -hex "${1:-32}"; }
|
|
|
|
# ── load existing .env (preserves secrets on re-run) ──────────────────────────
|
|
declare -A E=()
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
while IFS= read -r line; do
|
|
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
[[ -z "${line// }" ]] && continue
|
|
key="${line%%=*}"
|
|
val="${line#*=}"
|
|
E["$key"]="$val"
|
|
done < "$ENV_FILE"
|
|
info "Loaded existing .env — secrets will be preserved"
|
|
fi
|
|
# Helper: return existing value or empty
|
|
ex() { echo "${E[$1]:-}"; }
|
|
|
|
echo ""
|
|
echo "┌─────────────────────────────────────────────┐"
|
|
echo "│ ActiveBlue AI Agent — Setup │"
|
|
echo "└─────────────────────────────────────────────┘"
|
|
echo ""
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# 1. POSTGRESQL
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
info "Step 1/4 PostgreSQL"
|
|
|
|
POSTGRES_HOST="agent-db" # always the Docker service name on activeblue-net
|
|
POSTGRES_PORT="5432"
|
|
POSTGRES_DB="activeblue_ai"
|
|
POSTGRES_USER="activeblue"
|
|
|
|
if [[ -n "$(ex POSTGRES_PASSWORD)" ]]; then
|
|
POSTGRES_PASSWORD="$(ex POSTGRES_PASSWORD)"
|
|
info " password : (existing, preserved)"
|
|
else
|
|
POSTGRES_PASSWORD="$(gen 24)"
|
|
info " password : (generated)"
|
|
fi
|
|
|
|
POSTGRES_POOL_MIN="${E[POSTGRES_POOL_MIN]:-2}"
|
|
POSTGRES_POOL_MAX="${E[POSTGRES_POOL_MAX]:-10}"
|
|
echo " host=${POSTGRES_HOST} db=${POSTGRES_DB} user=${POSTGRES_USER}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# 2. ODOO
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
info "Step 2/4 Odoo"
|
|
|
|
# ── 2a. URL — find running Odoo container ─────────────────────────────────────
|
|
ODOO_CONTAINER=""
|
|
while IFS= read -r name; do
|
|
[[ -z "$name" ]] && continue
|
|
ODOO_CONTAINER="$name"
|
|
break
|
|
done < <(docker ps --filter name=odoo-web --format '{{.Names}}' 2>/dev/null)
|
|
|
|
if [[ -n "$ODOO_CONTAINER" ]]; then
|
|
ODOO_URL="http://${ODOO_CONTAINER}:8069"
|
|
info " container : $ODOO_CONTAINER → $ODOO_URL"
|
|
elif [[ -n "$(ex ODOO_URL)" ]]; then
|
|
ODOO_URL="$(ex ODOO_URL)"
|
|
info " URL : $ODOO_URL (from existing .env)"
|
|
else
|
|
warn " No running Odoo container found"
|
|
ask " Odoo internal URL [http://odoo-web-1:8069]:"
|
|
read -rp " > " ODOO_URL
|
|
ODOO_URL="${ODOO_URL:-http://odoo-web-1:8069}"
|
|
fi
|
|
|
|
# ── 2b. Database name ─────────────────────────────────────────────────────────
|
|
ODOO_DB="$(ex ODOO_DB)"
|
|
if [[ -z "$ODOO_DB" && -n "$ODOO_CONTAINER" ]]; then
|
|
# Try reading from odoo.conf inside the container
|
|
ODOO_DB="$(docker exec "$ODOO_CONTAINER" \
|
|
grep -oP '(?i)(?<=^db_name\s=\s).*' /etc/odoo/odoo.conf 2>/dev/null \
|
|
| head -1 | tr -d '[:space:]' || true)"
|
|
fi
|
|
if [[ -z "$ODOO_DB" ]]; then
|
|
# Ask Odoo's database list endpoint
|
|
ODOO_DB="$(curl -sf --connect-timeout 3 \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"jsonrpc":"2.0","method":"call","id":1,"params":{}}' \
|
|
"${ODOO_URL}/web/database/list" 2>/dev/null \
|
|
| grep -oP '"result":\["\K[^"]+' | head -1 || true)"
|
|
fi
|
|
if [[ -z "$ODOO_DB" ]]; then
|
|
ask " Odoo database name [odoo]:"
|
|
read -rp " > " ODOO_DB
|
|
ODOO_DB="${ODOO_DB:-odoo}"
|
|
fi
|
|
info " database : $ODOO_DB"
|
|
|
|
ODOO_CALLBACK_URL="${E[ODOO_CALLBACK_URL]:-${ODOO_URL}/activeblue_ai/result}"
|
|
|
|
# ── 2c. API key — auto-create via Odoo HTTP session ───────────────────────────
|
|
ODOO_API_KEY="$(ex ODOO_API_KEY)"
|
|
if [[ -z "$ODOO_API_KEY" ]]; then
|
|
info " API key : attempting auto-creation..."
|
|
ask " Odoo admin username [admin]:"
|
|
read -rp " > " _ODOO_USER
|
|
_ODOO_USER="${_ODOO_USER:-admin}"
|
|
ask " Odoo admin password:"
|
|
read -rsp " > " _ODOO_PASS; echo
|
|
|
|
ODOO_API_KEY="$(python3 - "$ODOO_URL" "$ODOO_DB" "$_ODOO_USER" "$_ODOO_PASS" 2>/dev/null <<'PYEOF' || true
|
|
import json, sys, urllib.request, urllib.error
|
|
|
|
url, db, user, pwd = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
|
|
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor())
|
|
|
|
def rpc(path, payload):
|
|
body = json.dumps(payload).encode()
|
|
req = urllib.request.Request(
|
|
f"{url}{path}", data=body,
|
|
headers={"Content-Type": "application/json"})
|
|
with opener.open(req, timeout=10) as r:
|
|
return json.loads(r.read())
|
|
|
|
# Authenticate
|
|
r = rpc("/web/session/authenticate", {
|
|
"jsonrpc": "2.0", "method": "call", "id": 1,
|
|
"params": {"db": db, "login": user, "password": pwd}})
|
|
uid = (r.get("result") or {}).get("uid")
|
|
if not uid:
|
|
sys.exit(1)
|
|
|
|
# Create an API-key description record (Odoo 16/17/18 transient wizard)
|
|
r2 = rpc("/web/dataset/call_kw", {
|
|
"jsonrpc": "2.0", "method": "call", "id": 2,
|
|
"params": {
|
|
"model": "res.users.apikeys.description",
|
|
"method": "create",
|
|
"args": [[{"name": "activeblue-ai-agent"}]],
|
|
"kwargs": {"context": {}}}})
|
|
desc_id = r2.get("result")
|
|
if not desc_id:
|
|
sys.exit(1)
|
|
|
|
# Generate the key
|
|
r3 = rpc("/web/dataset/call_kw", {
|
|
"jsonrpc": "2.0", "method": "call", "id": 3,
|
|
"params": {
|
|
"model": "res.users.apikeys.description",
|
|
"method": "make_key",
|
|
"args": [[desc_id]],
|
|
"kwargs": {"context": {}}}})
|
|
result = r3.get("result") or {}
|
|
key = (result.get("api_key")
|
|
or result.get("key")
|
|
or (result.get("value") or {}).get("api_key")
|
|
or "")
|
|
if key:
|
|
print(key)
|
|
PYEOF
|
|
)"
|
|
|
|
if [[ -n "$ODOO_API_KEY" ]]; then
|
|
info " API key : created automatically"
|
|
else
|
|
warn " Auto-creation failed — create one manually:"
|
|
echo ""
|
|
echo " 1. Open ${ODOO_URL}/odoo/settings → enable Developer Mode"
|
|
echo " 2. Settings → Technical → API Keys → New"
|
|
echo " 3. Name: activeblue-ai-agent → Generate → copy the key"
|
|
echo ""
|
|
ask " Paste the API key:"
|
|
read -rp " > " ODOO_API_KEY
|
|
[[ -z "$ODOO_API_KEY" ]] && die "ODOO_API_KEY is required"
|
|
fi
|
|
else
|
|
info " API key : (existing, preserved)"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# 3. OLLAMA
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
info "Step 3/4 Ollama"
|
|
|
|
OLLAMA_URL="$(ex OLLAMA_URL)"
|
|
if [[ -z "$OLLAMA_URL" ]]; then
|
|
info " scanning common addresses..."
|
|
for _host in 192.168.2.10 192.168.2.9 192.168.2.1 127.0.0.1; do
|
|
if curl -sf --connect-timeout 2 "http://${_host}:11434/api/tags" >/dev/null 2>&1; then
|
|
OLLAMA_URL="http://${_host}:11434"
|
|
info " found : $OLLAMA_URL"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
if [[ -z "$OLLAMA_URL" ]]; then
|
|
warn " Ollama not found on common addresses"
|
|
ask " Ollama URL [http://192.168.2.10:11434]:"
|
|
read -rp " > " OLLAMA_URL
|
|
OLLAMA_URL="${OLLAMA_URL:-http://192.168.2.10:11434}"
|
|
fi
|
|
|
|
# Verify reachability and get model list
|
|
OLLAMA_MODEL="$(ex OLLAMA_MODEL)"
|
|
_TAGS="$(curl -sf --connect-timeout 3 "${OLLAMA_URL}/api/tags" 2>/dev/null || true)"
|
|
if [[ -n "$_TAGS" ]]; then
|
|
mapfile -t _MODELS < <(echo "$_TAGS" | grep -oP '"name":"\K[^"]+' 2>/dev/null || true)
|
|
if [[ ${#_MODELS[@]} -eq 0 ]]; then
|
|
warn " Ollama reachable but no models pulled"
|
|
echo " Run on the Ollama host: ollama pull llama3.1:8b"
|
|
OLLAMA_MODEL="${OLLAMA_MODEL:-llama3.1:8b}"
|
|
elif [[ -n "$OLLAMA_MODEL" ]]; then
|
|
info " model : $OLLAMA_MODEL (existing, preserved)"
|
|
elif [[ ${#_MODELS[@]} -eq 1 ]]; then
|
|
OLLAMA_MODEL="${_MODELS[0]}"
|
|
info " model : $OLLAMA_MODEL (only model available)"
|
|
else
|
|
info " available models:"
|
|
for i in "${!_MODELS[@]}"; do
|
|
printf " [%d] %s\n" $((i+1)) "${_MODELS[$i]}"
|
|
done
|
|
ask " Select model number [1]:"
|
|
read -rp " > " _CHOICE
|
|
_CHOICE="${_CHOICE:-1}"
|
|
if [[ "$_CHOICE" =~ ^[0-9]+$ ]] && (( _CHOICE >= 1 && _CHOICE <= ${#_MODELS[@]} )); then
|
|
OLLAMA_MODEL="${_MODELS[$((_CHOICE-1))]}"
|
|
else
|
|
OLLAMA_MODEL="${_MODELS[0]}"
|
|
fi
|
|
info " model : $OLLAMA_MODEL"
|
|
fi
|
|
else
|
|
warn " Ollama unreachable at $OLLAMA_URL (check it's running and bound to 0.0.0.0)"
|
|
OLLAMA_MODEL="${OLLAMA_MODEL:-llama3.1:8b}"
|
|
fi
|
|
|
|
OLLAMA_TIMEOUT="${E[OLLAMA_TIMEOUT]:-120}"
|
|
OLLAMA_MAX_CONCURRENT="${E[OLLAMA_MAX_CONCURRENT]:-2}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# 4. SERVICE SECRETS & MISC
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
info "Step 4/4 Service secrets"
|
|
|
|
if [[ -n "$(ex AGENT_API_KEY)" ]]; then
|
|
AGENT_API_KEY="$(ex AGENT_API_KEY)"
|
|
info " AGENT_API_KEY : (existing)"
|
|
else
|
|
AGENT_API_KEY="$(gen 32)"
|
|
info " AGENT_API_KEY : (generated)"
|
|
fi
|
|
|
|
if [[ -n "$(ex WEBHOOK_SECRET)" ]]; then
|
|
WEBHOOK_SECRET="$(ex WEBHOOK_SECRET)"
|
|
info " WEBHOOK_SECRET : (existing)"
|
|
else
|
|
WEBHOOK_SECRET="$(gen 32)"
|
|
info " WEBHOOK_SECRET : (generated)"
|
|
fi
|
|
|
|
LLM_PRIVACY_MODE="${E[LLM_PRIVACY_MODE]:-local}"
|
|
AGENT_SERVICE_PORT="${E[AGENT_SERVICE_PORT]:-8001}"
|
|
ANTHROPIC_API_KEY="${E[ANTHROPIC_API_KEY]:-}"
|
|
CLAUDE_MODEL="${E[CLAUDE_MODEL]:-claude-sonnet-4-6}"
|
|
CLAUDE_TIMEOUT="${E[CLAUDE_TIMEOUT]:-120}"
|
|
CLAUDE_MAX_CONCURRENT="${E[CLAUDE_MAX_CONCURRENT]:-2}"
|
|
LOG_LEVEL="${E[LOG_LEVEL]:-INFO}"
|
|
LOG_FORMAT="${E[LOG_FORMAT]:-json}"
|
|
DISPATCH_RATE_LIMIT_PER_USER="${E[DISPATCH_RATE_LIMIT_PER_USER]:-10}"
|
|
DISPATCH_RATE_WINDOW_SECONDS="${E[DISPATCH_RATE_WINDOW_SECONDS]:-60}"
|
|
ACCESS_CONTROL_ENABLED="${E[ACCESS_CONTROL_ENABLED]:-true}"
|
|
DIRECTIVE_TIMEOUT_MINUTES="${E[DIRECTIVE_TIMEOUT_MINUTES]:-10}"
|
|
LOKI_URL="${E[LOKI_URL]:-}"
|
|
ALLOWED_CALLBACK_IP="${E[ALLOWED_CALLBACK_IP]:-}"
|
|
|
|
# Per-agent backend overrides (empty = use LLM_PRIVACY_MODE default)
|
|
for _AGENT in FINANCE ACCOUNTING CRM SALES PROJECT ELEARNING EXPENSES EMPLOYEES; do
|
|
eval "AGENT_BACKEND_${_AGENT}=\"\${E[AGENT_BACKEND_${_AGENT}]:-}\""
|
|
done
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# WRITE .env
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
cat > "$ENV_FILE" << EOF
|
|
# Generated by setup.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
# Re-run ./setup.sh to update — existing secrets are preserved automatically.
|
|
|
|
# ── PostgreSQL (agent DB) ───────────────────────────────────────────────────
|
|
POSTGRES_HOST=${POSTGRES_HOST}
|
|
POSTGRES_PORT=${POSTGRES_PORT}
|
|
POSTGRES_DB=${POSTGRES_DB}
|
|
POSTGRES_USER=${POSTGRES_USER}
|
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
|
POSTGRES_POOL_MIN=${POSTGRES_POOL_MIN}
|
|
POSTGRES_POOL_MAX=${POSTGRES_POOL_MAX}
|
|
|
|
# ── Odoo ────────────────────────────────────────────────────────────────────
|
|
ODOO_URL=${ODOO_URL}
|
|
ODOO_DB=${ODOO_DB}
|
|
ODOO_API_KEY=${ODOO_API_KEY}
|
|
ODOO_CALLBACK_URL=${ODOO_CALLBACK_URL}
|
|
|
|
# ── Ollama ──────────────────────────────────────────────────────────────────
|
|
OLLAMA_URL=${OLLAMA_URL}
|
|
OLLAMA_MODEL=${OLLAMA_MODEL}
|
|
OLLAMA_TIMEOUT=${OLLAMA_TIMEOUT}
|
|
OLLAMA_MAX_CONCURRENT=${OLLAMA_MAX_CONCURRENT}
|
|
|
|
# ── Privacy mode (HIPAA — keep 'local' for patient-adjacent data) ────────────
|
|
# local = Ollama only | hybrid = Ollama default, Claude per-agent | cloud = Claude only
|
|
LLM_PRIVACY_MODE=${LLM_PRIVACY_MODE}
|
|
|
|
# ── Anthropic Claude (only used when LLM_PRIVACY_MODE != local) ─────────────
|
|
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
|
CLAUDE_MODEL=${CLAUDE_MODEL}
|
|
CLAUDE_TIMEOUT=${CLAUDE_TIMEOUT}
|
|
CLAUDE_MAX_CONCURRENT=${CLAUDE_MAX_CONCURRENT}
|
|
|
|
# ── Per-agent backend overrides (ollama|claude, empty = use LLM_PRIVACY_MODE)
|
|
AGENT_BACKEND_MASTER=${E[AGENT_BACKEND_MASTER]:-}
|
|
AGENT_BACKEND_FINANCE=${AGENT_BACKEND_FINANCE}
|
|
AGENT_BACKEND_ACCOUNTING=${AGENT_BACKEND_ACCOUNTING}
|
|
AGENT_BACKEND_CRM=${AGENT_BACKEND_CRM}
|
|
AGENT_BACKEND_SALES=${AGENT_BACKEND_SALES}
|
|
AGENT_BACKEND_PROJECT=${AGENT_BACKEND_PROJECT}
|
|
AGENT_BACKEND_ELEARNING=${AGENT_BACKEND_ELEARNING}
|
|
AGENT_BACKEND_EXPENSES=${AGENT_BACKEND_EXPENSES}
|
|
AGENT_BACKEND_EMPLOYEES=${AGENT_BACKEND_EMPLOYEES}
|
|
|
|
# ── Agent service ───────────────────────────────────────────────────────────
|
|
AGENT_SERVICE_PORT=${AGENT_SERVICE_PORT}
|
|
AGENT_API_KEY=${AGENT_API_KEY}
|
|
WEBHOOK_SECRET=${WEBHOOK_SECRET}
|
|
ALLOWED_CALLBACK_IP=${ALLOWED_CALLBACK_IP}
|
|
|
|
# ── Logging ─────────────────────────────────────────────────────────────────
|
|
LOG_LEVEL=${LOG_LEVEL}
|
|
LOG_FORMAT=${LOG_FORMAT}
|
|
LOKI_URL=${LOKI_URL}
|
|
|
|
# ── Rate limiting ───────────────────────────────────────────────────────────
|
|
DISPATCH_RATE_LIMIT_PER_USER=${DISPATCH_RATE_LIMIT_PER_USER}
|
|
DISPATCH_RATE_WINDOW_SECONDS=${DISPATCH_RATE_WINDOW_SECONDS}
|
|
ACCESS_CONTROL_ENABLED=${ACCESS_CONTROL_ENABLED}
|
|
DIRECTIVE_TIMEOUT_MINUTES=${DIRECTIVE_TIMEOUT_MINUTES}
|
|
EOF
|
|
|
|
chmod 600 "$ENV_FILE"
|
|
|
|
echo ""
|
|
echo "┌─────────────────────────────────────────────┐"
|
|
echo "│ Setup complete │"
|
|
echo "└─────────────────────────────────────────────┘"
|
|
echo ""
|
|
echo " Postgres : postgresql://${POSTGRES_USER}:***@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
|
|
echo " Odoo : ${ODOO_URL} db=${ODOO_DB}"
|
|
echo " Ollama : ${OLLAMA_URL} model=${OLLAMA_MODEL}"
|
|
echo " Privacy : ${LLM_PRIVACY_MODE}"
|
|
echo ""
|
|
|
|
# ── Optional: bring up the stack now ──────────────────────────────────────────
|
|
ask "Start the agent stack now? [Y/n]"
|
|
read -rp " > " _START
|
|
if [[ "${_START:-Y}" =~ ^[Yy] ]]; then
|
|
cd "$SCRIPT_DIR"
|
|
|
|
# If the DB volume exists but was initialized with a different POSTGRES_DB,
|
|
# the database won't exist inside it. Detect this and offer a clean wipe.
|
|
_VOL="$(docker volume ls -q --filter name=odoo-ai_agent-db-data 2>/dev/null | head -1)"
|
|
if [[ -n "$_VOL" ]]; then
|
|
# Spin up just the DB to check
|
|
docker compose up -d agent-db >/dev/null 2>&1
|
|
sleep 3
|
|
if ! docker exec activeblue-agent-db psql -U "$POSTGRES_USER" -lqt 2>/dev/null \
|
|
| cut -d'|' -f1 | grep -qw "$POSTGRES_DB"; then
|
|
warn "DB volume exists but database '${POSTGRES_DB}' is missing (left from a previous broken run)"
|
|
ask " Wipe the agent-db volume and re-init? [Y/n]"
|
|
read -rp " > " _WIPE
|
|
if [[ "${_WIPE:-Y}" =~ ^[Yy] ]]; then
|
|
docker compose down -v
|
|
info " Volume wiped"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
docker compose up -d
|
|
info "Waiting for agent-db to be ready..."
|
|
for _i in $(seq 1 15); do
|
|
if docker exec activeblue-agent-db pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
|
|
>/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
# Run Alembic migrations so tables exist before the agent needs them
|
|
info "Running database migrations..."
|
|
if docker exec activeblue-agent \
|
|
alembic -c agent_service/migrations/alembic.ini upgrade head 2>&1; then
|
|
info "Migrations applied"
|
|
else
|
|
warn "Migration command failed — tables may be missing. Check alembic output above."
|
|
fi
|
|
|
|
# Restart agent so it picks up a healthy DB on its first connect attempt
|
|
docker compose restart agent-service
|
|
echo ""
|
|
info "Stack started. Tailing logs (Ctrl-C to stop):"
|
|
echo ""
|
|
docker compose logs -f agent-service
|
|
fi
|