mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-04 18:46:07 +00:00
98 lines
3.4 KiB
Bash
Executable File
98 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
cd /opt/langbot-cloud-prod
|
|
TAG=${1:?usage: deploy.sh prod-<40-char-sha>}
|
|
[[ "$TAG" =~ ^prod-[0-9a-f]{40}$ ]] || { echo 'invalid immutable image tag' >&2; exit 2; }
|
|
[[ -s .env ]] || { echo '/opt/langbot-cloud-prod/.env is missing' >&2; exit 3; }
|
|
|
|
rendered_compose=$(docker compose config)
|
|
grep -Fq 'LANGBOT_SPACE_CONTROL_PLANE_URL: https://space.langbot.app' <<<"$rendered_compose" || {
|
|
echo 'Cloud control-plane URL must be https://space.langbot.app' >&2
|
|
exit 4
|
|
}
|
|
grep -Fq 'SPACE__URL: https://space.langbot.app' <<<"$rendered_compose" || {
|
|
echo 'Cloud user-facing Space URL must be https://space.langbot.app' >&2
|
|
exit 5
|
|
}
|
|
grep -Eq 'LANGBOT_TELEMETRY_INGEST_TOKEN: .+' <<<"$rendered_compose" || {
|
|
echo 'Cloud telemetry ingest token must be configured' >&2
|
|
exit 6
|
|
}
|
|
|
|
update_env() {
|
|
local key=$1 value=$2
|
|
python3 - "$key" "$value" <<'PY'
|
|
from pathlib import Path
|
|
import os
|
|
import sys
|
|
|
|
path = Path('.env')
|
|
key, value = sys.argv[1:]
|
|
lines = path.read_text().splitlines()
|
|
updated = False
|
|
for index, line in enumerate(lines):
|
|
if line.startswith(f'{key}='):
|
|
lines[index] = f'{key}={value}'
|
|
updated = True
|
|
break
|
|
if not updated:
|
|
lines.append(f'{key}={value}')
|
|
temporary = Path('.env.tmp')
|
|
temporary.write_text('\n'.join(lines) + '\n')
|
|
os.chmod(temporary, 0o600)
|
|
temporary.replace(path)
|
|
PY
|
|
}
|
|
update_env LANGBOT_IMAGE_TAG "$TAG"
|
|
set -a
|
|
. ./.env
|
|
set +a
|
|
: "${CLOUD_V2_CONTROL_PLANE_TOKEN:?CLOUD_V2_CONTROL_PLANE_TOKEN is required}"
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if docker compose pull postgres redis migrate plugin-runtime core; then
|
|
break
|
|
fi
|
|
if [ "$attempt" -eq 5 ]; then
|
|
echo "docker compose pull failed after $attempt attempts" >&2
|
|
exit 1
|
|
fi
|
|
delay=$((attempt * 10))
|
|
echo "docker compose pull failed (attempt $attempt/5); retrying in ${delay}s" >&2
|
|
sleep "$delay"
|
|
done
|
|
docker compose up -d postgres redis
|
|
for _ in $(seq 1 60); do
|
|
if docker compose exec -T postgres pg_isready -U langbot_operator -d langbot >/dev/null 2>&1; then break; fi
|
|
sleep 2
|
|
done
|
|
docker compose exec -T postgres pg_isready -U langbot_operator -d langbot >/dev/null
|
|
|
|
docker compose exec -T postgres psql -v ON_ERROR_STOP=1 -U langbot_operator -d langbot \
|
|
-v runtime_password="$POSTGRES_RUNTIME_PASSWORD" <<'SQL'
|
|
SELECT format('CREATE ROLE langbot_runtime LOGIN PASSWORD %L', :'runtime_password')
|
|
WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'langbot_runtime')\gexec
|
|
ALTER ROLE langbot_runtime PASSWORD :'runtime_password';
|
|
GRANT CONNECT ON DATABASE langbot TO langbot_runtime;
|
|
REVOKE CREATE ON SCHEMA public FROM PUBLIC, langbot_runtime;
|
|
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM langbot_runtime;
|
|
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM langbot_runtime;
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE langbot_operator IN SCHEMA public REVOKE ALL ON TABLES FROM langbot_runtime;
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE langbot_operator IN SCHEMA public REVOKE ALL ON SEQUENCES FROM langbot_runtime;
|
|
GRANT USAGE ON SCHEMA public TO langbot_runtime;
|
|
SQL
|
|
|
|
docker compose --profile tools run --rm migrate
|
|
|
|
docker compose up -d --remove-orphans plugin-runtime core
|
|
for _ in $(seq 1 90); do
|
|
if docker compose exec -T core python -c 'import urllib.request; urllib.request.urlopen("http://127.0.0.1:5300/healthz", timeout=3)' >/dev/null 2>&1; then
|
|
docker compose ps
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
docker compose logs --tail=200 core plugin-runtime >&2
|
|
exit 1
|