Files
call-center/scripts/deploy_gitlab.sh
T
Yera AllandClaude Opus 4.6 436821737e feat(asterisk): add telecom.kz IP trunk for DID 87273901485
Wires the production number 87273901485 (SIP account 86205414@sip.telecom.kz)
into the voice stack as a no-registration IP trunk. Inbound INVITEs are
identified by source IP 92.46.61.21 and routed straight into the AI-first
entrypoint (7100), with the existing AudioSocket -> live operator fallback
preserved.

The Asterisk container was also never started by the source-based CI deploy
(only the registry-based path brought it up). Patch scripts/deploy_gitlab.sh
to also run docker-compose.asterisk.server.yml so the voice channel stays in
sync with the rest of the platform on every push to the default branch.

- deployment/asterisk/pjsip.conf: add telecom-kz aor/auth/endpoint/identify
- deployment/asterisk/entrypoint.sh: inject ASTERISK_TELECOM_KZ_PASSWORD
- deployment/asterisk/extensions.conf: new [from-telecom] context -> 7100
- .env.production.template: document ASTERISK_TELECOM_KZ_PASSWORD
- scripts/deploy_gitlab.sh: also bring up the asterisk compose file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 01:19:06 +05:00

152 lines
4.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
unset DOCKER_HOST DOCKER_TLS_VERIFY DOCKER_CERT_PATH DOCKER_TLS_CERTDIR
DEPLOY_DIR="${DEPLOY_DIR:-/home/gitlab-runner/deploy/call-center}"
APP_IMAGE_NAME="${APP_IMAGE_NAME:-call-center-app}"
APP_IMAGE_TAG="${APP_IMAGE_TAG:-${CI_COMMIT_SHORT_SHA:-latest}}"
COMPOSE_FILE="${COMPOSE_FILE:-$DEPLOY_DIR/deployment/docker-compose.server.yml}"
ASTERISK_COMPOSE_FILE="${ASTERISK_COMPOSE_FILE:-$DEPLOY_DIR/deployment/docker-compose.asterisk.server.yml}"
HEALTHCHECK_URL="${HEALTHCHECK_URL:-http://127.0.0.1:8080/health}"
HEALTHCHECK_TIMEOUT_SECONDS="${HEALTHCHECK_TIMEOUT_SECONDS:-120}"
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Missing required command: $1" >&2
exit 1
fi
}
wait_for_health() {
python3 - "$HEALTHCHECK_URL" "$HEALTHCHECK_TIMEOUT_SECONDS" <<'PY'
import sys
import time
import urllib.request
url = sys.argv[1]
timeout_seconds = int(sys.argv[2])
deadline = time.time() + timeout_seconds
last_error = None
while time.time() < deadline:
try:
with urllib.request.urlopen(url, timeout=5) as response:
if response.getcode() == 200:
print(f"Healthcheck passed: {url}")
sys.exit(0)
last_error = f"unexpected status {response.getcode()}"
except Exception as exc:
last_error = str(exc)
time.sleep(3)
print(f"Healthcheck failed for {url}: {last_error}", file=sys.stderr)
sys.exit(1)
PY
}
require_command docker
require_command rsync
require_command python3
if docker compose version >/dev/null 2>&1; then
COMPOSE_CMD=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD=(docker-compose)
else
echo "Missing docker compose or docker-compose" >&2
exit 1
fi
if [[ -z "${CI_PROJECT_DIR:-}" || ! -d "${CI_PROJECT_DIR:-}" ]]; then
echo "CI_PROJECT_DIR is not set or does not exist" >&2
exit 1
fi
if ! docker info >/dev/null 2>&1; then
echo "Docker daemon is not reachable for the runner user" >&2
exit 1
fi
install -d "$DEPLOY_DIR"
cd "$DEPLOY_DIR"
if [[ "$CI_PROJECT_DIR" != "$DEPLOY_DIR" ]]; then
rsync -a --delete \
--exclude '.git/' \
--exclude '.env.production' \
--exclude '.env.production.*' \
--exclude '.data/' \
--exclude '.data_local/' \
--exclude '.data_gate3/' \
--exclude '.data_gate4/' \
--exclude '.data_uat_dry_run/' \
--exclude '.data_uat_preflight/' \
--exclude '.venv/' \
--exclude '.pytest_cache/' \
--exclude '.local_stack/' \
--exclude '.models/' \
--exclude 'test-results/' \
--exclude '.codex_backup/' \
--exclude '.db_backups/' \
--exclude '.deploy_backups/' \
"$CI_PROJECT_DIR/" "$DEPLOY_DIR/"
fi
if [[ ! -f "$DEPLOY_DIR/.env.production" ]]; then
if [[ -n "${DEPLOY_ENV_FILE:-}" && -f "${DEPLOY_ENV_FILE}" ]]; then
install -m 600 "$DEPLOY_ENV_FILE" "$DEPLOY_DIR/.env.production"
else
echo "Missing $DEPLOY_DIR/.env.production. Create it once on the server or set DEPLOY_ENV_FILE." >&2
exit 1
fi
fi
install -d "$DEPLOY_DIR/.data_local"
install -d "$DEPLOY_DIR/.data_local/generated_ivr_yandex/ivr"
if [[ -d "$DEPLOY_DIR/.asterisk_assets/ivr" ]]; then
rsync -a --delete \
"$DEPLOY_DIR/.asterisk_assets/ivr/" \
"$DEPLOY_DIR/.data_local/generated_ivr_yandex/ivr/"
fi
export CALL_CENTER_IMAGE="${APP_IMAGE_NAME}:${APP_IMAGE_TAG}"
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-call-center}"
echo "Deploy directory: $DEPLOY_DIR"
echo "Building image: $CALL_CENTER_IMAGE"
docker build -t "$CALL_CENTER_IMAGE" -t "${APP_IMAGE_NAME}:latest" "$DEPLOY_DIR"
if docker ps -a --format '{{.Names}}' | grep -Fxq 'call-center-api-gateway' \
&& ! docker ps -a --format '{{.Names}}' | grep -Fxq 'call-center-app'; then
echo "Removing legacy gateway container call-center-api-gateway before rename to call-center-app"
docker rm -f call-center-api-gateway
fi
"${COMPOSE_CMD[@]}" -f "$COMPOSE_FILE" config -q
"${COMPOSE_CMD[@]}" -f "$COMPOSE_FILE" up -d --remove-orphans --force-recreate
wait_for_health
docker ps --filter "name=^call-center-app$" --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
# ---------------------------------------------------------------------------
# Asterisk PBX container.
#
# The asterisk compose file is kept separate because it uses network_mode:host
# and a dedicated Dockerfile. Source-based deploy now also brings it up so the
# voice channel (SIP trunks, WebRTC softphones) stays in sync with the rest of
# the platform on every push to the default branch.
# ---------------------------------------------------------------------------
if [[ -f "$ASTERISK_COMPOSE_FILE" ]]; then
echo "Deploying Asterisk PBX via $ASTERISK_COMPOSE_FILE"
"${COMPOSE_CMD[@]}" -f "$ASTERISK_COMPOSE_FILE" config -q
"${COMPOSE_CMD[@]}" -f "$ASTERISK_COMPOSE_FILE" up -d --build --force-recreate
docker ps --filter "name=^call-center-asterisk$" --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
else
echo "Asterisk compose file not found at $ASTERISK_COMPOSE_FILE, skipping" >&2
fi