178 lines
4.7 KiB
Bash
Executable File
178 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
ENV_FILE="${1:-$ROOT_DIR/.env}"
|
|
COMPOSE_FILE="$ROOT_DIR/docker-compose.yml"
|
|
DATA_DIR="$ROOT_DIR/data"
|
|
INIT_DB_SCRIPT="$ROOT_DIR/offline/init-full-db-offline.sh"
|
|
AGENT_TEMPLATES_FILE="$DATA_DIR/templates/agent_md_templates.json"
|
|
TOPIC_PRESETS_FILE="$DATA_DIR/templates/topic_presets.json"
|
|
SKILLS_DIR="$DATA_DIR/skills"
|
|
|
|
if [[ ! -f "$ENV_FILE" && -f "$ROOT_DIR/.env.full" ]]; then
|
|
ENV_FILE="$ROOT_DIR/.env.full"
|
|
fi
|
|
|
|
if [[ ! -f "$COMPOSE_FILE" && -f "$ROOT_DIR/docker-compose.full.yml" ]]; then
|
|
COMPOSE_FILE="$ROOT_DIR/docker-compose.full.yml"
|
|
fi
|
|
|
|
require_file() {
|
|
local path="$1"
|
|
local hint="${2:-}"
|
|
if [[ -f "$path" ]]; then
|
|
return 0
|
|
fi
|
|
echo "Missing file: $path"
|
|
[[ -n "$hint" ]] && echo "$hint"
|
|
exit 1
|
|
}
|
|
|
|
require_dir() {
|
|
local path="$1"
|
|
local hint="${2:-}"
|
|
if [[ -d "$path" ]]; then
|
|
return 0
|
|
fi
|
|
echo "Missing directory: $path"
|
|
[[ -n "$hint" ]] && echo "$hint"
|
|
exit 1
|
|
}
|
|
|
|
require_env() {
|
|
local name="$1"
|
|
[[ -n "${!name:-}" ]] || { echo "Missing required env: $name"; exit 1; }
|
|
}
|
|
|
|
read_env_value() {
|
|
local key="$1"
|
|
local line=""
|
|
local value=""
|
|
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
line="${line%$'\r'}"
|
|
[[ -z "${line//[[:space:]]/}" ]] && continue
|
|
[[ "${line#\#}" != "$line" ]] && continue
|
|
[[ "${line#export }" != "$line" ]] && line="${line#export }"
|
|
[[ "$line" == "$key="* ]] || continue
|
|
value="${line#*=}"
|
|
if [[ "$value" =~ ^\"(.*)\"$ ]]; then
|
|
value="${BASH_REMATCH[1]}"
|
|
elif [[ "$value" =~ ^\'(.*)\'$ ]]; then
|
|
value="${BASH_REMATCH[1]}"
|
|
fi
|
|
printf '%s' "$value"
|
|
return 0
|
|
done < "$ENV_FILE"
|
|
|
|
return 1
|
|
}
|
|
|
|
load_env_var() {
|
|
local name="$1"
|
|
local default_value="${2:-}"
|
|
local value=""
|
|
value="$(read_env_value "$name" || true)"
|
|
if [[ -z "$value" ]]; then
|
|
value="$default_value"
|
|
fi
|
|
printf -v "$name" '%s' "$value"
|
|
}
|
|
|
|
wait_for_health() {
|
|
local container_name="$1"
|
|
local timeout_seconds="$2"
|
|
local elapsed=0
|
|
local status=""
|
|
|
|
while (( elapsed < timeout_seconds )); do
|
|
status="$(
|
|
docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_name" 2>/dev/null || true
|
|
)"
|
|
if [[ "$status" == "healthy" || "$status" == "running" ]]; then
|
|
echo "[deploy-full-offline] $container_name is $status"
|
|
return 0
|
|
fi
|
|
sleep 2
|
|
elapsed=$((elapsed + 2))
|
|
done
|
|
|
|
echo "[deploy-full-offline] timed out waiting for $container_name (last status: ${status:-unknown})"
|
|
docker logs --tail 80 "$container_name" 2>/dev/null || true
|
|
exit 1
|
|
}
|
|
|
|
require_file "$ENV_FILE" "Expected bundle config file such as .env"
|
|
require_file "$COMPOSE_FILE"
|
|
require_file "$INIT_DB_SCRIPT"
|
|
require_file "$AGENT_TEMPLATES_FILE"
|
|
require_file "$TOPIC_PRESETS_FILE"
|
|
require_dir "$SKILLS_DIR"
|
|
|
|
load_env_var HOST_BOTS_WORKSPACE_ROOT
|
|
load_env_var POSTGRES_SUPERUSER postgres
|
|
load_env_var POSTGRES_SUPERPASSWORD
|
|
load_env_var POSTGRES_BOOTSTRAP_DB postgres
|
|
load_env_var POSTGRES_APP_DB
|
|
load_env_var POSTGRES_APP_USER
|
|
load_env_var POSTGRES_APP_PASSWORD
|
|
load_env_var NGINX_PORT 8080
|
|
load_env_var PUBLIC_HOST ""
|
|
|
|
require_env HOST_BOTS_WORKSPACE_ROOT
|
|
require_env POSTGRES_SUPERUSER
|
|
require_env POSTGRES_SUPERPASSWORD
|
|
require_env POSTGRES_BOOTSTRAP_DB
|
|
require_env POSTGRES_APP_DB
|
|
require_env POSTGRES_APP_USER
|
|
require_env POSTGRES_APP_PASSWORD
|
|
require_env NGINX_PORT
|
|
|
|
case "$HOST_BOTS_WORKSPACE_ROOT" in
|
|
/*) ;;
|
|
*)
|
|
echo "HOST_BOTS_WORKSPACE_ROOT must be an absolute host path: $HOST_BOTS_WORKSPACE_ROOT"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [[ ! -S /var/run/docker.sock ]]; then
|
|
echo "Missing required mount source: /var/run/docker.sock"
|
|
exit 1
|
|
fi
|
|
|
|
echo "[deploy-full-offline] using env: $ENV_FILE"
|
|
mkdir -p \
|
|
"$DATA_DIR" \
|
|
"$DATA_DIR/postgres" \
|
|
"$DATA_DIR/redis" \
|
|
"$DATA_DIR/model" \
|
|
"$HOST_BOTS_WORKSPACE_ROOT"
|
|
|
|
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" config -q
|
|
|
|
echo "[deploy-full-offline] starting postgres and redis"
|
|
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" up -d postgres redis
|
|
|
|
wait_for_health "dashboard-nanobot-postgres" 120
|
|
wait_for_health "dashboard-nanobot-redis" 60
|
|
|
|
echo "[deploy-full-offline] initializing application database"
|
|
"$INIT_DB_SCRIPT" "$ENV_FILE"
|
|
|
|
echo "[deploy-full-offline] starting backend and nginx"
|
|
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" up -d backend nginx
|
|
|
|
wait_for_health "dashboard-nanobot-backend" 180
|
|
wait_for_health "dashboard-nanobot-nginx" 120
|
|
|
|
echo "[deploy-full-offline] service status"
|
|
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" ps
|
|
|
|
if [[ -n "$PUBLIC_HOST" ]]; then
|
|
echo "[deploy-full-offline] open: http://${PUBLIC_HOST}:${NGINX_PORT}"
|
|
fi
|
|
|
|
echo "[deploy-full-offline] done"
|