scripts/compose-smoke.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SMOKE_ROOT="${CVN_COMPOSE_SMOKE_ROOT:-${TMPDIR:-/tmp}/channel-vault-compose-smoke}"
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-channel-vault-nas-smoke}"
export CVN_WEB_PORT="${CVN_WEB_PORT:-15173}"
export CVN_API_PORT="${CVN_API_PORT:-18000}"
export CVN_METADATA_HOST_DIR="${CVN_METADATA_HOST_DIR:-$SMOKE_ROOT/metadata}"
export CVN_DOWNLOAD_HOST_DIR="${CVN_DOWNLOAD_HOST_DIR:-$SMOKE_ROOT/downfolder}"
export CVN_RUNTIME_HOST_DIR="${CVN_RUNTIME_HOST_DIR:-$SMOKE_ROOT/runtime}"
export CVN_DB_BACKUP_ON_STARTUP="${CVN_DB_BACKUP_ON_STARTUP:-true}"
export CVN_DB_MIGRATE_ON_STARTUP="${CVN_DB_MIGRATE_ON_STARTUP:-true}"
export CVN_DOWNLOAD_WORKER_ENABLED="${CVN_DOWNLOAD_WORKER_ENABLED:-false}"
export CVN_DOWNLOAD_WORKER_SCHEDULER_ENABLED="${CVN_DOWNLOAD_WORKER_SCHEDULER_ENABLED:-false}"
export CVN_METADATA_SYNC_SCHEDULER_ENABLED="${CVN_METADATA_SYNC_SCHEDULER_ENABLED:-false}"
export CVN_RESTART_ADAPTER="${CVN_RESTART_ADAPTER:-docker-compose}"
export CVN_RESTART_SERVICE_NAME="${CVN_RESTART_SERVICE_NAME:-api}"
export CVN_RESTART_ADAPTER_EXECUTE="${CVN_RESTART_ADAPTER_EXECUTE:-false}"
published_host() {
local value="$1"
if [[ "$value" == *:* ]]; then
local host="${value%:*}"
if [[ "$host" == "0.0.0.0" || "$host" == "::" || "$host" == "[::]" ]]; then
printf "127.0.0.1"
else
printf "%s" "$host"
fi
else
printf "127.0.0.1"
fi
}
published_port() {
local value="$1"
if [[ "$value" == *:* ]]; then
printf "%s" "${value##*:}"
else
printf "%s" "$value"
fi
}
WEB_HOST="$(published_host "$CVN_WEB_PORT")"
WEB_PORT="$(published_port "$CVN_WEB_PORT")"
API_HOST="$(published_host "$CVN_API_PORT")"
API_PORT="$(published_port "$CVN_API_PORT")"
check_port_available() {
local label="$1"
local host="$2"
local port="$3"
if [[ "${CVN_COMPOSE_SMOKE_SKIP_PORT_CHECK:-false}" == "true" ]]; then
return 0
fi
if command -v python3 >/dev/null 2>&1; then
python3 - "$label" "$host" "$port" <<'PY'
import socket
import sys
label, host, port = sys.argv[1], sys.argv[2], int(sys.argv[3])
connect_host = "127.0.0.1" if host in {"0.0.0.0", "::", "[::]"} else host
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(0.25)
if sock.connect_ex((connect_host, port)) == 0:
print(f"failed: {label} port is already in use at {connect_host}:{port}", file=sys.stderr)
print("Use overrides, for example:", file=sys.stderr)
print(" CVN_WEB_PORT=15174 CVN_API_PORT=18001 scripts/compose-smoke.sh", file=sys.stderr)
sys.exit(1)
PY
fi
}
mkdir -p "$CVN_METADATA_HOST_DIR" "$CVN_DOWNLOAD_HOST_DIR" "$CVN_RUNTIME_HOST_DIR"
cd "$ROOT_DIR"
if ! docker compose version >/dev/null 2>&1; then
echo "docker compose is not available." >&2
exit 1
fi
check_port_available "web" "$WEB_HOST" "$WEB_PORT"
check_port_available "api" "$API_HOST" "$API_PORT"
if [[ "${CVN_COMPOSE_SMOKE_BUILD:-true}" == "false" ]]; then
docker compose up -d
else
docker compose up -d --build
fi
wait_for_url() {
local label="$1"
local url="$2"
local attempts="${3:-90}"
for ((attempt = 1; attempt <= attempts; attempt += 1)); do
if curl -fsS "$url" >/dev/null; then
echo "ok: $label $url"
return 0
fi
sleep 1
done
echo "failed: $label did not become ready at $url" >&2
docker compose ps >&2
docker compose logs --no-color --tail=120 api web >&2
return 1
}
wait_for_url "api health" "http://${API_HOST}:${API_PORT}/api/health"
wait_for_url "proxied api health" "http://${WEB_HOST}:${WEB_PORT}/api/health"
wait_for_url "web root" "http://${WEB_HOST}:${WEB_PORT}/"
runtime_curl_args=(-fsS)
if [[ -n "${CVN_AUTH_TOKEN:-}" ]]; then
runtime_curl_args+=(-H "Authorization: Bearer ${CVN_AUTH_TOKEN}")
fi
restart_adapter_json="$(curl "${runtime_curl_args[@]}" "http://${WEB_HOST}:${WEB_PORT}/api/settings/runtime/restart")"
if command -v python3 >/dev/null 2>&1; then
RESTART_ADAPTER_JSON="$restart_adapter_json" python3 - <<'PY'
import json
import os
import sys
data = json.loads(os.environ["RESTART_ADAPTER_JSON"])
expected_adapter = os.environ.get("CVN_RESTART_ADAPTER", "docker-compose").replace("_", "-")
expected_service = os.environ.get("CVN_RESTART_SERVICE_NAME", "api")
adapter = data.get("adapter")
service_name = data.get("service_name")
command = data.get("command") or ""
if adapter != expected_adapter:
print(f"failed: restart adapter expected {expected_adapter!r}, got {adapter!r}", file=sys.stderr)
sys.exit(1)
if expected_service and service_name != expected_service:
print(f"failed: restart service expected {expected_service!r}, got {service_name!r}", file=sys.stderr)
sys.exit(1)
if "restart" not in command:
print(f"failed: restart command does not look like a restart command: {command!r}", file=sys.stderr)
sys.exit(1)
manual = "manual" if data.get("manual_required") else "executable"
available = "available" if data.get("command_available") else "copy-only"
print(f"ok: restart adapter {adapter} service={service_name} mode={manual} command={available}")
PY
else
if ! printf "%s" "$restart_adapter_json" | grep -q '"adapter":"docker-compose"'; then
echo "failed: restart adapter did not report docker-compose" >&2
echo "$restart_adapter_json" >&2
exit 1
fi
echo "ok: restart adapter docker-compose"
fi
docker compose ps
cat <<EOF
Compose smoke passed.
Web: http://${WEB_HOST}:${WEB_PORT}/
API: http://${API_HOST}:${API_PORT}/api/health
Restart adapter: ${CVN_RESTART_ADAPTER} service=${CVN_RESTART_SERVICE_NAME}
Project: ${COMPOSE_PROJECT_NAME}
Data root: ${SMOKE_ROOT}
Stop it with:
COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME} \\
CVN_WEB_PORT=${CVN_WEB_PORT} \\
CVN_API_PORT=${CVN_API_PORT} \\
CVN_METADATA_HOST_DIR=${CVN_METADATA_HOST_DIR} \\
CVN_DOWNLOAD_HOST_DIR=${CVN_DOWNLOAD_HOST_DIR} \\
CVN_RUNTIME_HOST_DIR=${CVN_RUNTIME_HOST_DIR} \\
docker compose down
EOF
if [[ "${CVN_COMPOSE_SMOKE_CLEANUP:-false}" == "true" ]]; then
docker compose down
fi