dashboard-nanobot/offline/deploy-prod-offline.sh

143 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ENV_FILE="${1:-$ROOT_DIR/.env}"
DATA_DIR="$ROOT_DIR/data"
COMPOSE_FILE="$ROOT_DIR/docker-compose.yml"
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.prod" ]]; then
ENV_FILE="$ROOT_DIR/.env.prod"
fi
if [[ ! -f "$COMPOSE_FILE" && -f "$ROOT_DIR/docker-compose.prod.yml" ]]; then
COMPOSE_FILE="$ROOT_DIR/docker-compose.prod.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"
}
is_truthy() {
local value="${1:-}"
value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')"
[[ "$value" =~ ^(1|true|yes|on)$ ]]
}
require_file "$ENV_FILE" "Expected bundle config file such as .env"
require_file "$COMPOSE_FILE"
require_file "$AGENT_TEMPLATES_FILE"
require_file "$TOPIC_PRESETS_FILE"
require_dir "$SKILLS_DIR"
load_env_var HOST_BOTS_WORKSPACE_ROOT
load_env_var DATABASE_URL
load_env_var NGINX_PORT 8080
load_env_var REDIS_ENABLED false
load_env_var REDIS_URL
load_env_var PUBLIC_HOST ""
require_env HOST_BOTS_WORKSPACE_ROOT
require_env DATABASE_URL
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
if [[ "$DATABASE_URL" != postgresql* ]]; then
echo "Unsupported DATABASE_URL for deploy-prod-offline.sh: $DATABASE_URL"
exit 1
fi
if is_truthy "$REDIS_ENABLED" && [[ -z "$REDIS_URL" ]]; then
echo "Missing required env: REDIS_URL"
exit 1
fi
echo "[deploy-prod-offline] using env: $ENV_FILE"
mkdir -p "$DATA_DIR" "$DATA_DIR/model" "$HOST_BOTS_WORKSPACE_ROOT"
echo "[deploy-prod-offline] expecting external PostgreSQL to be pre-initialized with sql/create-tables.sql and sql/init-data.sql, or by running ./init-db.sh"
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" config -q
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" up -d
echo "[deploy-prod-offline] service status"
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" ps
if [[ -n "$PUBLIC_HOST" ]]; then
echo "[deploy-prod-offline] open: http://${PUBLIC_HOST}:${NGINX_PORT}"
fi
echo "[deploy-prod-offline] done"