2026-05-09 09:21:24 +00:00
#!/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 "
2026-05-09 09:30:48 +00:00
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"
2026-05-09 09:21:24 +00:00
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"