#!/usr/bin/env bash # Deploy AI Router stack to Docker Swarm (IFT) set -euo pipefail STACK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" STACK_NAME="${STACK_NAME:-ai-router}" cd "$STACK_DIR" if [[ ! -f .env ]]; then echo "ERROR: copy .env.example to .env and fill secrets" >&2 exit 1 fi set -a # shellcheck disable=SC1091 source .env set +a ensure_secret() { local name="$1" local value="$2" if docker secret inspect "$name" >/dev/null 2>&1; then echo "secret exists: $name" else if [[ -z "$value" ]]; then value="_" fi echo -n "$value" | docker secret create "$name" - echo "created secret: $name" fi } echo "== Ensure Swarm secrets ==" PRIMARY="${PRIMARY_PROVIDER:-hybrid}" if [[ "$PRIMARY" == "hybrid" || "$PRIMARY" == "novita" ]]; then ensure_secret novita_api_key "${NOVITA_API_KEY:?NOVITA_API_KEY required for PRIMARY_PROVIDER=${PRIMARY}}" else ensure_secret novita_api_key "${NOVITA_API_KEY:-}" fi ensure_secret anthropic_api_key "${ANTHROPIC_API_KEY:-}" ensure_secret litellm_master_key "${LITELLM_MASTER_KEY:?LITELLM_MASTER_KEY required}" ensure_secret litellm_salt_key "${LITELLM_SALT_KEY:?LITELLM_SALT_KEY required}" ensure_secret router_api_key "${ROUTER_API_KEY:?ROUTER_API_KEY required}" ensure_secret postgres_password "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required}" ensure_secret groq_api_key "${GROQ_API_KEY:-}" ensure_secret xai_api_key "${XAI_API_KEY:-}" ensure_secret gemini_api_key "${GEMINI_API_KEY:-}" ensure_secret together_api_key "${TOGETHER_API_KEY:-}" ensure_secret openrouter_api_key "${OPENROUTER_API_KEY:-}" ensure_secret gigachat_credentials "${GIGACHAT_CREDENTIALS:-}" vless_conf_is_stub() { local f="${1:-vless/vless.conf}" [[ ! -f "$f" ]] && return 0 grep -qE "0\.0\.0\.0|example\.com|UUID@host|^#" "$f" && return 0 grep -qE "^vless://" "$f" || return 0 return 1 } if [[ "${VPN_ENABLED:-false}" == "true" ]] && [[ -n "${VLESS_SUB_URL:-}" ]]; then if vless_conf_is_stub "vless/vless.conf"; then echo "== Fetch vless.conf from subscription ==" bash scripts/fetch-vless-subscription.sh vless/vless.conf fi fi if [[ ! -f vless/vless.conf ]]; then echo "WARN: vless/vless.conf missing — stub for secret (VPN off until configured)" cp vless/vless.conf.example vless/vless.conf 2>/dev/null || echo "# stub" > vless/vless.conf fi ensure_secret vless_conf "$(cat vless/vless.conf)" echo "== Build images ==" docker build -f router/Dockerfile -t "${ROUTER_IMAGE:-git.sabilin.com/eventhub/ai-router-gateway:ift}" . if [[ "${VPN_ENABLED:-false}" == "true" ]]; then docker build -t ai-router/vless-proxy:local ./vless docker build -t ai-router/vpn-watchdog:local ./watchdog else echo "VPN off — stub images (replicas=0)" docker build -f vless/Dockerfile.stub -t ai-router/vless-proxy:local ./vless docker build -t ai-router/vpn-watchdog:local ./watchdog fi echo "== Sync routing config (PRIMARY_PROVIDER=${PRIMARY_PROVIDER:-hybrid}) ==" python3 scripts/gen-litellm-config.py echo "== Deploy stack: ${STACK_NAME} ==" docker stack deploy -c docker-stack.yml --with-registry-auth "${STACK_NAME}" echo "== Wait for services ==" sleep 8 docker stack services "${STACK_NAME}" echo "" echo "Done." echo " Zed URL: ${TEST_BASE_URL:-https://ai-router.ift.calentiq.com}/v1" echo " LiteLLM: ${TEST_LITELLM_URL:-https://litellm.ift.calentiq.com}/ui" echo " Smoke: bash scripts/smoke-test.sh" echo " VPN on: bash scripts/vpn-enable.sh"