#!/usr/bin/env bash # ============================================================================== # deploy.sh — Deploy Meridian LXC # # Usage: # ./deploy.sh # full deploy # ./deploy.sh --tags meridian # meridian role only # ./deploy.sh --tags litellm # litellm role only # ./deploy.sh -v # verbose output # # Secrets: # LITELLM_MASTER_KEY is pulled from Infisical (/meridian/vault_litellm_master_key) # on the controller and exported into the env for the playbook to read. # For Semaphore deploys, set LITELLM_MASTER_KEY as an env var on the template. # ============================================================================== set -euo pipefail HOST_IP="$(grep -E '^[0-9]' inventory.ini | head -1 | awk '{print $1}')" HOST_USER="$(grep -o 'ansible_user=[^ ]*' inventory.ini | head -1 | cut -d= -f2)" if [[ -z "${LITELLM_MASTER_KEY:-}" ]]; then echo "==> Pulling LITELLM_MASTER_KEY from Infisical ..." LITELLM_MASTER_KEY="$(infisical secrets get vault_litellm_master_key \ --projectId 50062d7c-06ff-4d5c-8ca3-6c0cdba9f270 \ --env prod --path /meridian --plain 2>/dev/null)" if [[ -z "$LITELLM_MASTER_KEY" ]]; then echo " ERROR: couldn't fetch LITELLM_MASTER_KEY. Is the infisical CLI logged in?" >&2 exit 1 fi export LITELLM_MASTER_KEY fi # Optional provider keys for direct_* models. Non-fatal: if a key isn't in # Infisical /meridian yet, litellm.env falls back to a placeholder and the # direct_* model 401s on call (proxy_* + local_* keep working). Drop the secret # into Infisical /meridian to activate, then re-deploy. for keyvar in OPENAI_API_KEY GEMINI_API_KEY; do if [[ -z "${!keyvar:-}" ]]; then secret_name="vault_$(echo "$keyvar" | tr '[:upper:]' '[:lower:]')" val="$(infisical secrets get "$secret_name" \ --projectId 50062d7c-06ff-4d5c-8ca3-6c0cdba9f270 \ --env prod --path /meridian --plain 2>/dev/null || true)" if [[ -n "$val" ]]; then echo "==> Pulled ${keyvar} from Infisical (direct_* enabled)." export "$keyvar=$val" else echo "==> ${keyvar} not in Infisical /meridian — direct_* for this provider stays scaffolded (401 until set)." fi fi done echo "==> Checking connectivity to ${HOST_USER}@${HOST_IP} ..." if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "${HOST_USER}@${HOST_IP}" true 2>/dev/null; then echo " Cannot SSH to ${HOST_IP} — refreshing host key ..." ssh-keygen -R "$HOST_IP" 2>/dev/null || true ssh-keyscan -H "$HOST_IP" >> ~/.ssh/known_hosts 2>/dev/null fi echo "==> Installing Ansible collections ..." ansible-galaxy collection install -r requirements.yml --force 2>/dev/null echo "==> Running deploy playbook ..." ansible-playbook -i inventory.ini site.yml "$@" echo "==> Verifying ..." ssh "${HOST_USER}@${HOST_IP}" bash -s <<'VERIFY' echo "Node: $(node --version 2>/dev/null || echo missing)" echo "Meridian binary: $(which meridian 2>/dev/null || echo missing)" echo "Services:" for svc in meridian litellm; do printf " %-10s enabled=%s active=%s\n" "$svc" "$(systemctl is-enabled $svc 2>/dev/null)" "$(systemctl is-active $svc 2>/dev/null)" done echo "Endpoints:" curl -sf --max-time 3 http://127.0.0.1:3456/v1/messages -X POST -H 'Content-Type: application/json' -d '{}' >/dev/null 2>&1 \ && echo " meridian :3456 reachable" || echo " meridian :3456 not responding" curl -sf --max-time 3 http://127.0.0.1:4000/health/liveliness >/dev/null 2>&1 \ && echo " litellm :4000 healthy" || echo " litellm :4000 not responding" VERIFY echo "==> Done."