Cache pip/npm dependency downloads via BuildKit cache mounts and skip the postgres pull when the image is already present locally, so a repeat ./build.sh bundle run does not download dependencies twice.
1092 lines
40 KiB
Bash
Executable File
1092 lines
40 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# #region Tooling.Build [C:2] [TYPE Module]
|
||
# @PURPOSE: Unified build script — local docker compose + individual image builds + release bundles
|
||
# @COMPLEXITY: 2
|
||
#
|
||
# Usage: ./build.sh <command> [options]
|
||
#
|
||
# Commands (local docker):
|
||
# up [profile] Build and start local docker compose (default: current)
|
||
# down [profile] Stop services
|
||
# restart [profile] Rebuild and restart services
|
||
# logs [profile] Tail logs
|
||
# status Show running containers for all profiles
|
||
#
|
||
# Commands (individual image build — no export):
|
||
# build:backend <tag> Build backend image only (superset-tools-backend:<tag>)
|
||
# build:frontend <tag> Build frontend image only (superset-tools-frontend:<tag>)
|
||
# build:agent <tag> Build agent image only (superset-tools-agent:<tag>)
|
||
#
|
||
# Commands (single-image bundle — build + .tar.xz export):
|
||
# bundle:backend <tag> Build + export backend .tar.xz only
|
||
# bundle:frontend <tag> Build + export frontend .tar.xz only
|
||
# bundle:agent <tag> Build + export agent .tar.xz only
|
||
#
|
||
# Commands (release smoke):
|
||
# smoke:bundle <backend-image> <postgres-image>
|
||
# Validate clean PostgreSQL migration + backend boot/restart.
|
||
#
|
||
# Commands (full bundles — backend + frontend + agent + postgres):
|
||
# bundle <tag> Slim enterprise bundle (no embeddings agent). Default.
|
||
# bundle:embeddings <tag> Enterprise bundle WITH semantic embedding routing (larger agent)
|
||
# bundle:light <tag> Lightweight all-in-one bundle (<200 MB, .tar.xz, no Playwright)
|
||
#
|
||
# help Show this help
|
||
#
|
||
# Profiles: current (default), master, enterprise-clean
|
||
# #endregion Tooling.Build
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
cd "$SCRIPT_DIR"
|
||
|
||
export DOCKER_BUILDKIT=1
|
||
|
||
BACKEND_ENV_FILE="$SCRIPT_DIR/backend/.env"
|
||
DIST_ROOT="$SCRIPT_DIR/dist/docker"
|
||
|
||
# ---- Docker Compose detection ----
|
||
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 "Error: docker compose is not available."
|
||
exit 1
|
||
fi
|
||
|
||
# ======================================================================
|
||
# HELPERS
|
||
# ======================================================================
|
||
|
||
# shellcheck disable=SC2206
|
||
setup_compose_profile() {
|
||
local profile="$1"
|
||
case "$profile" in
|
||
master)
|
||
PROFILE_ENV_FILE="$SCRIPT_DIR/.env.master"
|
||
PROJECT_NAME="superset-tools-master"
|
||
COMPOSE_FILE="docker-compose.yml"
|
||
;;
|
||
current|"")
|
||
PROFILE_ENV_FILE="$SCRIPT_DIR/.env.current"
|
||
PROJECT_NAME="superset-tools-current"
|
||
COMPOSE_FILE="docker-compose.yml"
|
||
;;
|
||
enterprise-clean)
|
||
PROFILE_ENV_FILE="$SCRIPT_DIR/.env.enterprise-clean"
|
||
PROJECT_NAME="superset-tools-enterprise"
|
||
COMPOSE_FILE="docker-compose.enterprise-clean.yml"
|
||
;;
|
||
*)
|
||
echo "Error: unknown profile '$profile'. Use: current, master, enterprise-clean."
|
||
exit 1
|
||
;;
|
||
esac
|
||
COMPOSE_ARGS=(-p "$PROJECT_NAME" -f "$COMPOSE_FILE")
|
||
if [[ -f "$PROFILE_ENV_FILE" ]]; then
|
||
COMPOSE_ARGS+=(--env-file "$PROFILE_ENV_FILE")
|
||
fi
|
||
}
|
||
|
||
ensure_backend_encryption_key() {
|
||
if command -v python3 >/dev/null 2>&1; then
|
||
python3 - "$BACKEND_ENV_FILE" <<'PY'
|
||
import base64
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
from typing import List, Optional
|
||
|
||
|
||
def is_valid_fernet_key(raw_value: str) -> bool:
|
||
value = raw_value.strip()
|
||
if not value:
|
||
return False
|
||
try:
|
||
decoded = base64.urlsafe_b64decode(value.encode())
|
||
except Exception:
|
||
return False
|
||
return len(decoded) == 32
|
||
|
||
|
||
def generate_fernet_key() -> str:
|
||
return base64.urlsafe_b64encode(os.urandom(32)).decode()
|
||
|
||
|
||
env_path = Path(sys.argv[1])
|
||
env_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
existing_lines: List[str] = []
|
||
existing_key: Optional[str] = None
|
||
|
||
if env_path.exists():
|
||
existing_lines = env_path.read_text(encoding="utf-8").splitlines()
|
||
for line in existing_lines:
|
||
if line.startswith("ENCRYPTION_KEY="):
|
||
candidate = line.partition("=")[2].strip()
|
||
if is_valid_fernet_key(candidate):
|
||
existing_key = candidate
|
||
break
|
||
|
||
if existing_key is None:
|
||
generated_key = generate_fernet_key()
|
||
filtered_lines = [line for line in existing_lines if not line.startswith("ENCRYPTION_KEY=")]
|
||
filtered_lines.append(f"ENCRYPTION_KEY={generated_key}")
|
||
env_path.write_text("\n".join(filtered_lines) + "\n", encoding="utf-8")
|
||
print(f"[build] ENCRYPTION_KEY ensured in {env_path}")
|
||
else:
|
||
print(f"[build] Existing ENCRYPTION_KEY reused from {env_path}")
|
||
PY
|
||
return
|
||
fi
|
||
|
||
echo "Error: python3 is required to generate backend/.env with ENCRYPTION_KEY."
|
||
exit 1
|
||
}
|
||
|
||
get_image_id() {
|
||
docker image inspect --format '{{.Id}}' "$1"
|
||
}
|
||
|
||
get_repo_digest() {
|
||
local digest
|
||
digest="$(docker image inspect --format '{{join .RepoDigests ","}}' "$1" 2>/dev/null || true)"
|
||
if [[ -n "${digest}" && "${digest}" != "<no value>" ]]; then
|
||
printf '%s' "${digest}"
|
||
else
|
||
printf '%s' "unavailable"
|
||
fi
|
||
}
|
||
|
||
# Pull an image only if it is not already present locally.
|
||
ensure_image_present() {
|
||
local source="$1"
|
||
if docker image inspect "$source" >/dev/null 2>&1; then
|
||
echo "[bundle] Image ${source} already present, skipping pull"
|
||
return 0
|
||
fi
|
||
docker pull "$source"
|
||
}
|
||
|
||
|
||
resolve_app_version() {
|
||
local tag="$1"
|
||
local git_commit
|
||
git_commit="$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
|
||
echo "${tag}+${git_commit}"
|
||
}
|
||
|
||
# ======================================================================
|
||
# SINGLE-IMAGE BUILD FUNCTIONS
|
||
# ======================================================================
|
||
|
||
build_backend() {
|
||
local tag="$1"
|
||
local image="superset-tools-backend:${tag}"
|
||
echo "[build] Building backend image ${image}..."
|
||
docker build -f docker/backend.Dockerfile -t "${image}" .
|
||
echo "[build] ✅ Backend image ${image} built"
|
||
}
|
||
|
||
build_frontend() {
|
||
local tag="$1"
|
||
local image="superset-tools-frontend:${tag}"
|
||
local app_version
|
||
app_version="$(resolve_app_version "${tag}")"
|
||
echo "[build] Building frontend image ${image} (APP_VERSION=${app_version})..."
|
||
docker build --build-arg "APP_VERSION=${app_version}" -f docker/frontend.Dockerfile -t "${image}" .
|
||
echo "[build] ✅ Frontend image ${image} built"
|
||
}
|
||
|
||
build_agent() {
|
||
local tag="$1"
|
||
local image="superset-tools-agent:${tag}"
|
||
echo "[build] Building agent image ${image}..."
|
||
docker build -f docker/Dockerfile.agent -t "${image}" .
|
||
echo "[build] ✅ Agent image ${image} built"
|
||
}
|
||
|
||
build_agent_embeddings() {
|
||
local tag="$1"
|
||
local image="superset-tools-agent:${tag}"
|
||
echo "[build] Building agent image ${image} (WITH_EMBEDDINGS=true)..."
|
||
docker build \
|
||
--build-arg WITH_EMBEDDINGS=true \
|
||
-f docker/Dockerfile.agent \
|
||
-t "${image}" .
|
||
echo "[build] ✅ Agent image ${image} built (embeddings)"
|
||
}
|
||
|
||
# ======================================================================
|
||
# SINGLE-IMAGE EXPORT
|
||
# ======================================================================
|
||
|
||
export_image() {
|
||
local image="$1"
|
||
local archive="$2"
|
||
echo "[bundle] Compressing ${image} -> ${archive}..."
|
||
mkdir -p "$(dirname "${archive}")"
|
||
docker save "${image}" | xz -T0 -3 > "${archive}"
|
||
echo "[bundle] ✅ ${archive} saved"
|
||
}
|
||
|
||
# ======================================================================
|
||
# RELEASE SMOKE TESTS
|
||
# ======================================================================
|
||
|
||
run_bundle_smoke() {
|
||
local backend_image="$1"
|
||
local postgres_image="$2"
|
||
local timeout_seconds="${BUNDLE_SMOKE_TIMEOUT_SECONDS:-180}"
|
||
local suffix="$(date +%s)-${RANDOM}"
|
||
local network="ss-tools-release-smoke-${suffix}"
|
||
local db_container="ss-tools-release-smoke-db-${suffix}"
|
||
local backend_container="ss-tools-release-smoke-backend-${suffix}"
|
||
local database_url="postgresql+psycopg2://postgres:postgres@${db_container}:5432/release_smoke"
|
||
|
||
(
|
||
set -euo pipefail
|
||
|
||
cleanup_bundle_smoke() {
|
||
docker rm -f "${backend_container}" "${db_container}" >/dev/null 2>&1 || true
|
||
docker network rm "${network}" >/dev/null 2>&1 || true
|
||
}
|
||
trap cleanup_bundle_smoke EXIT
|
||
|
||
wait_for_postgres() {
|
||
local elapsed=0
|
||
while (( elapsed < timeout_seconds )); do
|
||
if docker exec "${db_container}" pg_isready -U postgres -d release_smoke >/dev/null 2>&1; then
|
||
return 0
|
||
fi
|
||
sleep 2
|
||
((elapsed += 2))
|
||
done
|
||
echo "[smoke] ❌ PostgreSQL did not become ready within ${timeout_seconds}s" >&2
|
||
docker logs "${db_container}" >&2 || true
|
||
return 1
|
||
}
|
||
|
||
wait_for_backend_health() {
|
||
local phase="$1"
|
||
local elapsed=0
|
||
while (( elapsed < timeout_seconds )); do
|
||
if docker exec "${backend_container}" curl -fsS http://127.0.0.1:8000/ >/dev/null 2>&1; then
|
||
echo "[smoke] ✅ Backend health check passed (${phase})"
|
||
return 0
|
||
fi
|
||
if [[ "$(docker inspect --format '{{.State.Running}}' "${backend_container}" 2>/dev/null || true)" != "true" ]]; then
|
||
echo "[smoke] ❌ Backend exited during ${phase}" >&2
|
||
docker logs "${backend_container}" >&2 || true
|
||
return 1
|
||
fi
|
||
sleep 2
|
||
((elapsed += 2))
|
||
done
|
||
echo "[smoke] ❌ Backend health check timed out during ${phase}" >&2
|
||
docker logs "${backend_container}" >&2 || true
|
||
return 1
|
||
}
|
||
|
||
echo "[smoke] Starting clean PostgreSQL release gate..."
|
||
docker network create "${network}" >/dev/null
|
||
docker run -d --name "${db_container}" --network "${network}" \
|
||
--security-opt seccomp=unconfined \
|
||
-e POSTGRES_DB=release_smoke \
|
||
-e POSTGRES_USER=postgres \
|
||
-e POSTGRES_PASSWORD=postgres \
|
||
"${postgres_image}" >/dev/null
|
||
wait_for_postgres
|
||
|
||
echo "[smoke] Verifying fresh migration, ORM schema initialization, and backend health..."
|
||
docker run -d --name "${backend_container}" --network "${network}" \
|
||
-e DATABASE_URL="${database_url}" \
|
||
-e TASKS_DATABASE_URL="${database_url}" \
|
||
-e AUTH_DATABASE_URL="${database_url}" \
|
||
-e AUTH_SECRET_KEY=release-smoke-auth-secret \
|
||
-e ENCRYPTION_KEY=change-me-generate-a-fernet-key= \
|
||
-e SERVICE_JWT=release-smoke-service-token \
|
||
-e INITIAL_ADMIN_CREATE=false \
|
||
"${backend_image}" >/dev/null
|
||
wait_for_backend_health "initial start"
|
||
|
||
echo "[smoke] Verifying idempotent migration and restart..."
|
||
docker restart "${backend_container}" >/dev/null
|
||
wait_for_backend_health "restart"
|
||
echo "[smoke] ✅ Clean PostgreSQL release gate passed"
|
||
)
|
||
}
|
||
|
||
# ======================================================================
|
||
# COMPOSE COMMANDS
|
||
# ======================================================================
|
||
|
||
compose_up() {
|
||
local profile="${1:-current}"
|
||
setup_compose_profile "$profile"
|
||
ensure_backend_encryption_key
|
||
|
||
echo "[build] Profile: $profile (project: $PROJECT_NAME)"
|
||
if [[ -f "$PROFILE_ENV_FILE" ]]; then
|
||
echo "[build] Env file: $PROFILE_ENV_FILE"
|
||
fi
|
||
|
||
echo "[1/2] Building project images..."
|
||
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" build
|
||
|
||
echo "[2/2] Starting Docker services..."
|
||
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" up -d
|
||
|
||
echo "Done. Services are running."
|
||
echo " ${COMPOSE_CMD[*]} ${COMPOSE_ARGS[*]} ps"
|
||
echo " ${COMPOSE_CMD[*]} ${COMPOSE_ARGS[*]} logs -f"
|
||
}
|
||
|
||
compose_down() {
|
||
local profile="${1:-current}"
|
||
setup_compose_profile "$profile"
|
||
|
||
echo "[build] Stopping $profile (project: $PROJECT_NAME)..."
|
||
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" down
|
||
echo "[build] Done."
|
||
}
|
||
|
||
compose_restart() {
|
||
local profile="${1:-current}"
|
||
setup_compose_profile "$profile"
|
||
ensure_backend_encryption_key
|
||
|
||
echo "[build] Restarting $profile (project: $PROJECT_NAME)..."
|
||
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" build
|
||
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" up -d --force-recreate
|
||
echo "[build] Done."
|
||
}
|
||
|
||
compose_logs() {
|
||
local profile="${1:-current}"
|
||
setup_compose_profile "$profile"
|
||
|
||
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" logs -f
|
||
}
|
||
|
||
compose_status() {
|
||
echo "=== superset-tools docker status ==="
|
||
for profile in current master enterprise-clean; do
|
||
setup_compose_profile "$profile"
|
||
echo "--- $profile (project: $PROJECT_NAME) ---"
|
||
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" ps 2>/dev/null || echo "(not running)"
|
||
done
|
||
}
|
||
|
||
# ======================================================================
|
||
# BUNDLE: full release (backend + frontend + agent .tar.xz's)
|
||
# ======================================================================
|
||
|
||
# ======================================================================
|
||
# SINGLE-IMAGE BUNDLE FUNCTIONS
|
||
# ======================================================================
|
||
|
||
bundle_single_backend() {
|
||
if [[ $# -lt 1 ]]; then
|
||
echo "Error: tag is required."
|
||
echo "Usage: ./build.sh bundle:backend <tag>"
|
||
exit 1
|
||
fi
|
||
local tag="$1"
|
||
local image="superset-tools-backend:${tag}"
|
||
mkdir -p "$DIST_ROOT"
|
||
build_backend "${tag}"
|
||
export_image "${image}" "${DIST_ROOT}/superset-tools-backend.${tag}.tar.xz"
|
||
(cd "${DIST_ROOT}" && sha256sum "superset-tools-backend.${tag}.tar.xz" > "sha256sums-backend.${tag}.txt")
|
||
echo "[bundle:backend] ✅ Backend bundle ${tag} created"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-backend.${tag}.tar.xz | docker load"
|
||
}
|
||
|
||
bundle_single_frontend() {
|
||
if [[ $# -lt 1 ]]; then
|
||
echo "Error: tag is required."
|
||
echo "Usage: ./build.sh bundle:frontend <tag>"
|
||
exit 1
|
||
fi
|
||
local tag="$1"
|
||
local image="superset-tools-frontend:${tag}"
|
||
mkdir -p "$DIST_ROOT"
|
||
build_frontend "${tag}"
|
||
export_image "${image}" "${DIST_ROOT}/superset-tools-frontend.${tag}.tar.xz"
|
||
(cd "${DIST_ROOT}" && sha256sum "superset-tools-frontend.${tag}.tar.xz" > "sha256sums-frontend.${tag}.txt")
|
||
echo "[bundle:frontend] ✅ Frontend bundle ${tag} created"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-frontend.${tag}.tar.xz | docker load"
|
||
}
|
||
|
||
bundle_single_agent() {
|
||
if [[ $# -lt 1 ]]; then
|
||
echo "Error: tag is required."
|
||
echo "Usage: ./build.sh bundle:agent <tag>"
|
||
exit 1
|
||
fi
|
||
local tag="$1"
|
||
local image="superset-tools-agent:${tag}"
|
||
mkdir -p "$DIST_ROOT"
|
||
build_agent "${tag}"
|
||
export_image "${image}" "${DIST_ROOT}/superset-tools-agent.${tag}.tar.xz"
|
||
(cd "${DIST_ROOT}" && sha256sum "superset-tools-agent.${tag}.tar.xz" > "sha256sums-agent.${tag}.txt")
|
||
echo "[bundle:agent] ✅ Agent bundle ${tag} created"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-agent.${tag}.tar.xz | docker load"
|
||
}
|
||
|
||
# ======================================================================
|
||
# FULL BUNDLE: backend + frontend + agent
|
||
# ======================================================================
|
||
|
||
generate_deploy_compose() {
|
||
local tag="$1" backend_tag="$2" frontend_tag="$3" agent_tag="$4" postgres_tag="$5"
|
||
cat > "${DIST_ROOT}/docker-compose.enterprise-clean.yml" <<DEPLOY
|
||
services:
|
||
db:
|
||
image: ${postgres_tag}
|
||
pull_policy: never
|
||
restart: unless-stopped
|
||
security_opt:
|
||
- seccomp=unconfined
|
||
environment:
|
||
POSTGRES_DB: \${POSTGRES_DB:-ss_tools}
|
||
POSTGRES_USER: \${POSTGRES_USER:-postgres}
|
||
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
||
ports:
|
||
- "\${POSTGRES_HOST_PORT:-5432}:5432"
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-postgres} -d \${POSTGRES_DB:-ss_tools}"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 10
|
||
|
||
backend:
|
||
image: ${backend_tag}
|
||
pull_policy: never
|
||
restart: unless-stopped
|
||
depends_on:
|
||
db:
|
||
condition: service_healthy
|
||
environment:
|
||
DATABASE_URL: postgresql+psycopg2://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@db:5432/\${POSTGRES_DB:-ss_tools}
|
||
TASKS_DATABASE_URL: postgresql+psycopg2://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@db:5432/\${POSTGRES_DB:-ss_tools}
|
||
AUTH_DATABASE_URL: postgresql+psycopg2://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@db:5432/\${POSTGRES_DB:-ss_tools}
|
||
BACKEND_PORT: 8000
|
||
AUTH_SECRET_KEY: \${AUTH_SECRET_KEY:?Set AUTH_SECRET_KEY in .env}
|
||
ENCRYPTION_KEY: \${ENCRYPTION_KEY:?Set ENCRYPTION_KEY in .env}
|
||
ENABLE_BELIEF_STATE_LOGGING: \${ENABLE_BELIEF_STATE_LOGGING:-true}
|
||
TASK_LOG_LEVEL: \${TASK_LOG_LEVEL:-INFO}
|
||
INITIAL_ADMIN_CREATE: \${INITIAL_ADMIN_CREATE:-false}
|
||
INITIAL_ADMIN_USERNAME: \${INITIAL_ADMIN_USERNAME:-admin}
|
||
INITIAL_ADMIN_PASSWORD: \${INITIAL_ADMIN_PASSWORD:-}
|
||
INITIAL_ADMIN_EMAIL: \${INITIAL_ADMIN_EMAIL:-}
|
||
OPENAI_API_KEY: \${OPENAI_API_KEY:-}
|
||
ANTHROPIC_API_KEY: \${ANTHROPIC_API_KEY:-}
|
||
FEATURES__DATASET_REVIEW: \${FEATURES__DATASET_REVIEW:-true}
|
||
FEATURES__HEALTH_MONITOR: \${FEATURES__HEALTH_MONITOR:-true}
|
||
CERTS_PATH: /opt/certs
|
||
ports:
|
||
- "\${BACKEND_HOST_PORT:-8001}:8000"
|
||
volumes:
|
||
- ./storage:/app/storage
|
||
- \${CERTS_PATH:-./certs}:/opt/certs:ro
|
||
|
||
frontend:
|
||
image: ${frontend_tag}
|
||
pull_policy: never
|
||
restart: unless-stopped
|
||
depends_on:
|
||
- backend
|
||
- agent
|
||
environment:
|
||
SSL_KEY_PASSPHRASE: \${SSL_KEY_PASSPHRASE:-}
|
||
ports:
|
||
- "\${FRONTEND_HOST_PORT:-8000}:80"
|
||
- "\${FRONTEND_SSL_PORT:-443}:443"
|
||
volumes:
|
||
- \${CERTS_PATH:-./certs}:/opt/certs:ro
|
||
|
||
agent:
|
||
image: ${agent_tag}
|
||
pull_policy: never
|
||
restart: unless-stopped
|
||
depends_on:
|
||
db:
|
||
condition: service_healthy
|
||
backend:
|
||
condition: service_started
|
||
environment:
|
||
LLM_API_KEY: \${LLM_API_KEY:-}
|
||
LLM_BASE_URL: \${LLM_BASE_URL:-https://api.openai.com/v1}
|
||
LLM_MODEL: \${LLM_MODEL:-gpt-4o}
|
||
FASTAPI_URL: http://backend:8000
|
||
AUTH_SECRET_KEY: \${AUTH_SECRET_KEY:?Set AUTH_SECRET_KEY in .env}
|
||
SERVICE_JWT: \${SERVICE_JWT:?Set SERVICE_JWT in .env}
|
||
DATABASE_URL: postgresql+psycopg2://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@db:5432/\${POSTGRES_DB:-ss_tools}
|
||
GRADIO_SERVER_PORT: 7860
|
||
GRADIO_ROOT_PATH: /api/agent/gradio
|
||
ports:
|
||
- "\${AGENT_HOST_PORT:-7860}:7860"
|
||
volumes:
|
||
- \${CERTS_PATH:-./certs}:/opt/certs:ro
|
||
|
||
volumes:
|
||
postgres_data:
|
||
DEPLOY
|
||
}
|
||
|
||
generate_manifest() {
|
||
local tag="$1" backend_tag="$2" frontend_tag="$3" agent_tag="$4" postgres_tag="$5"
|
||
local suffix="${6:-}"
|
||
|
||
local backend_id frontend_id agent_id postgres_id
|
||
backend_id="$(get_image_id "${backend_tag}")"
|
||
frontend_id="$(get_image_id "${frontend_tag}")"
|
||
agent_id="$(get_image_id "${agent_tag}")"
|
||
postgres_id="$(get_image_id "${postgres_tag}")"
|
||
|
||
cat > "${DIST_ROOT}/manifest.${tag}${suffix}.txt" <<EOF
|
||
release_tag=${tag}
|
||
backend_image=${backend_tag}
|
||
backend_image_id=${backend_id}
|
||
backend_repo_digest=$(get_repo_digest "${backend_tag}")
|
||
backend_archive=superset-tools-backend.${tag}${suffix}.tar.xz
|
||
frontend_image=${frontend_tag}
|
||
frontend_image_id=${frontend_id}
|
||
frontend_repo_digest=$(get_repo_digest "${frontend_tag}")
|
||
frontend_archive=superset-tools-frontend.${tag}${suffix}.tar.xz
|
||
agent_image=${agent_tag}
|
||
agent_image_id=${agent_id}
|
||
agent_repo_digest=$(get_repo_digest "${agent_tag}")
|
||
agent_archive=superset-tools-agent.${tag}${suffix}.tar.xz
|
||
postgres_image=${postgres_tag}
|
||
postgres_image_id=${postgres_id}
|
||
postgres_repo_digest=$(get_repo_digest "${postgres_tag}")
|
||
postgres_archive=superset-tools-postgres.${tag}${suffix}.tar.xz
|
||
compose_file=docker-compose.enterprise-clean.yml
|
||
env_template=.env.enterprise-clean.example
|
||
env_bootstrap_fields=INITIAL_ADMIN_CREATE,INITIAL_ADMIN_USERNAME,INITIAL_ADMIN_PASSWORD,INITIAL_ADMIN_EMAIL
|
||
checksums_file=sha256sums.${tag}${suffix}.txt
|
||
generated_at_utc=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||
EOF
|
||
|
||
cat > "${DIST_ROOT}/manifest.${tag}${suffix}.json" <<EOF
|
||
{
|
||
"release_tag": "${tag}",
|
||
"generated_at_utc": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||
"images": [
|
||
{
|
||
"role": "postgres",
|
||
"image": "${postgres_tag}",
|
||
"image_id": "${postgres_id}",
|
||
"repo_digest": "$(get_repo_digest "${postgres_tag}")",
|
||
"archive": "superset-tools-postgres.${tag}${suffix}.tar.xz"
|
||
},
|
||
{
|
||
"role": "backend",
|
||
"image": "${backend_tag}",
|
||
"image_id": "${backend_id}",
|
||
"repo_digest": "$(get_repo_digest "${backend_tag}")",
|
||
"archive": "superset-tools-backend.${tag}${suffix}.tar.xz"
|
||
},
|
||
{
|
||
"role": "frontend",
|
||
"image": "${frontend_tag}",
|
||
"image_id": "${frontend_id}",
|
||
"repo_digest": "$(get_repo_digest "${frontend_tag}")",
|
||
"archive": "superset-tools-frontend.${tag}${suffix}.tar.xz"
|
||
},
|
||
{
|
||
"role": "agent",
|
||
"image": "${agent_tag}",
|
||
"image_id": "${agent_id}",
|
||
"repo_digest": "$(get_repo_digest "${agent_tag}")",
|
||
"archive": "superset-tools-agent.${tag}${suffix}.tar.xz"
|
||
}
|
||
],
|
||
"compose_file": "docker-compose.enterprise-clean.yml",
|
||
"env_template": ".env.enterprise-clean.example",
|
||
"env_bootstrap_fields": [
|
||
"INITIAL_ADMIN_CREATE",
|
||
"INITIAL_ADMIN_USERNAME",
|
||
"INITIAL_ADMIN_PASSWORD",
|
||
"INITIAL_ADMIN_EMAIL"
|
||
],
|
||
"checksums_file": "sha256sums.${tag}${suffix}.txt"
|
||
}
|
||
EOF
|
||
}
|
||
|
||
# ======================================================================
|
||
# GENERATE BUNDLE ENV TEMPLATE (with bundled postgres defaults)
|
||
# ======================================================================
|
||
|
||
generate_bundle_env() {
|
||
local tag="$1"
|
||
cat > "${DIST_ROOT}/.env.enterprise-clean.example" << 'ENV'
|
||
# #region env.enterprise-clean.bundle [C:2] [TYPE Module] [SEMANTICS env,docker,bundle]
|
||
# @BRIEF Переменные окружения для docker-compose.enterprise-clean.yml (bundled postgres).
|
||
# Все образы предварительно загружены через xz -dc ... | docker load.
|
||
# PostgreSQL запускается в контейнере (сервис db) — внешний БД не требуется.
|
||
# @LAYER Infrastructure
|
||
# #endregion env.enterprise-clean.bundle
|
||
|
||
# ======================================================================
|
||
# PostgreSQL (контейнер) — настройки БД
|
||
# ======================================================================
|
||
POSTGRES_DB=ss_tools
|
||
POSTGRES_USER=postgres
|
||
POSTGRES_PASSWORD=change-me
|
||
POSTGRES_HOST_PORT=5432
|
||
|
||
# ======================================================================
|
||
# Порты хоста
|
||
# ======================================================================
|
||
BACKEND_HOST_PORT=8001
|
||
FRONTEND_HOST_PORT=8000
|
||
FRONTEND_SSL_PORT=443
|
||
AGENT_HOST_PORT=7860
|
||
|
||
# ======================================================================
|
||
# Безопасность (ОБЯЗАТЕЛЬНО)
|
||
# ======================================================================
|
||
# JWT-ключ подписи токенов — единый для backend и agent.
|
||
# Сгенерировать: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
|
||
AUTH_SECRET_KEY=change-me-to-a-random-secret-32-chars-min
|
||
|
||
# Fernet-ключ шифрования паролей подключений и API-ключей.
|
||
# Сгенерировать: python3 -c "import base64,os; print(base64.urlsafe_b64encode(os.urandom(32)).decode())"
|
||
ENCRYPTION_KEY=change-me-generate-a-fernet-key=
|
||
|
||
# Сервисный токен для agent→backend вызовов.
|
||
# Сгенерировать: python3 -c "import secrets; print('svc-' + secrets.token_urlsafe(24))"
|
||
SERVICE_JWT=agent-service-secret
|
||
|
||
# ======================================================================
|
||
# Сертификаты (корпоративные, опционально)
|
||
# ======================================================================
|
||
CERTS_PATH=./certs
|
||
SSL_KEY_PASSPHRASE=
|
||
|
||
# ======================================================================
|
||
# LLM / AI провайдеры
|
||
# ======================================================================
|
||
OPENAI_API_KEY=
|
||
ANTHROPIC_API_KEY=
|
||
LLM_API_KEY=
|
||
LLM_BASE_URL=https://api.openai.com/v1
|
||
LLM_MODEL=gpt-4o
|
||
|
||
# ======================================================================
|
||
# Логирование
|
||
# ======================================================================
|
||
ENABLE_BELIEF_STATE_LOGGING=true
|
||
TASK_LOG_LEVEL=INFO
|
||
|
||
# ======================================================================
|
||
# Admin bootstrap (первый запуск)
|
||
# ======================================================================
|
||
INITIAL_ADMIN_CREATE=false
|
||
INITIAL_ADMIN_USERNAME=admin
|
||
INITIAL_ADMIN_PASSWORD=change-me
|
||
INITIAL_ADMIN_EMAIL=
|
||
|
||
# ======================================================================
|
||
# CORS / Безопасность деплоя
|
||
# ======================================================================
|
||
ALLOWED_ORIGINS=http://localhost:8000
|
||
FORCE_HTTPS=false
|
||
APP_TIMEZONE=Europe/Moscow
|
||
|
||
# ======================================================================
|
||
# Features
|
||
# ======================================================================
|
||
FEATURES__DATASET_REVIEW=true
|
||
FEATURES__HEALTH_MONITOR=true
|
||
ENV
|
||
echo "[bundle] ✅ Generated bundle env template: ${DIST_ROOT}/.env.enterprise-clean.example"
|
||
}
|
||
|
||
bundle_release() {
|
||
if [[ $# -lt 1 ]]; then
|
||
echo "Error: tag is required."
|
||
echo "Usage: ./build.sh bundle <tag> (e.g. v1.0.0)"
|
||
exit 1
|
||
fi
|
||
local tag="$1"
|
||
local backend_tag="superset-tools-backend:${tag}"
|
||
local frontend_tag="superset-tools-frontend:${tag}"
|
||
local agent_tag="superset-tools-agent:${tag}"
|
||
local postgres_tag="superset-tools-postgres:${tag}"
|
||
local postgres_source="${POSTGRES_IMAGE:-postgres:16-alpine}"
|
||
|
||
mkdir -p "$DIST_ROOT"
|
||
|
||
# Build application images
|
||
build_backend "${tag}"
|
||
build_frontend "${tag}"
|
||
build_agent "${tag}"
|
||
|
||
# Pull and tag postgres image
|
||
echo "[bundle] Ensuring postgres image ${postgres_source}..."
|
||
ensure_image_present "${postgres_source}"
|
||
docker tag "${postgres_source}" "${postgres_tag}"
|
||
|
||
# Release gate: fresh PostgreSQL migration + backend boot + restart must pass.
|
||
run_bundle_smoke "${backend_tag}" "${postgres_tag}"
|
||
|
||
# Export .tar.xz archives
|
||
echo "[bundle] Exporting .tar.xz archives..."
|
||
export_image "${backend_tag}" "${DIST_ROOT}/superset-tools-backend.${tag}.tar.xz"
|
||
export_image "${frontend_tag}" "${DIST_ROOT}/superset-tools-frontend.${tag}.tar.xz"
|
||
export_image "${agent_tag}" "${DIST_ROOT}/superset-tools-agent.${tag}.tar.xz"
|
||
export_image "${postgres_tag}" "${DIST_ROOT}/superset-tools-postgres.${tag}.tar.xz"
|
||
|
||
# Checksums
|
||
echo "[bundle] Calculating checksums..."
|
||
(cd "${DIST_ROOT}" && sha256sum \
|
||
"superset-tools-backend.${tag}.tar.xz" \
|
||
"superset-tools-frontend.${tag}.tar.xz" \
|
||
"superset-tools-agent.${tag}.tar.xz" \
|
||
"superset-tools-postgres.${tag}.tar.xz" > "sha256sums.${tag}.txt")
|
||
|
||
# Generate deploy compose + manifest
|
||
generate_deploy_compose "${tag}" "${backend_tag}" "${frontend_tag}" "${agent_tag}" "${postgres_tag}"
|
||
generate_manifest "${tag}" "${backend_tag}" "${frontend_tag}" "${agent_tag}" "${postgres_tag}"
|
||
|
||
# Generate env template for the bundle
|
||
generate_bundle_env "${tag}"
|
||
|
||
echo "[bundle] ✅ Bundle created in ${DIST_ROOT}"
|
||
echo " # Load images (decompress stream):"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-backend.${tag}.tar.xz | docker load"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-frontend.${tag}.tar.xz | docker load"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-agent.${tag}.tar.xz | docker load"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-postgres.${tag}.tar.xz | docker load"
|
||
echo " # Edit .env.enterprise-clean.example -> .env.enterprise-clean, then:"
|
||
echo " docker compose -f ${DIST_ROOT}/docker-compose.enterprise-clean.yml up -d"
|
||
echo ""
|
||
echo " NOTE: Bundle includes postgres container (db) — no external PostgreSQL needed."
|
||
echo " Corporate certs: mount .crt files via CERTS_PATH=./certs (optional)."
|
||
}
|
||
|
||
# ======================================================================
|
||
# BUNDLE: lightweight all-in-one (single image, no Playwright, <200 MB)
|
||
# ======================================================================
|
||
|
||
bundle_light() {
|
||
if [[ $# -lt 1 ]]; then
|
||
echo "Error: tag is required."
|
||
echo "Usage: ./build.sh bundle:light <tag> (e.g. v1.0.0)"
|
||
exit 1
|
||
fi
|
||
local tag="$1"
|
||
local image_tag="superset-tools:${tag}"
|
||
|
||
# Git commit hash for APP_VERSION (e.g. "0.5.2+58d06fb2")
|
||
local git_commit
|
||
git_commit="$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
|
||
local app_version="${tag}+${git_commit}"
|
||
|
||
mkdir -p "$DIST_ROOT"
|
||
|
||
echo "[bundle:light] Building all-in-one image ${image_tag}..."
|
||
docker build --build-arg "APP_VERSION=${app_version}" -f docker/all-in-one.Dockerfile -t "${image_tag}" .
|
||
|
||
echo "[bundle:light] Exporting .tar.xz..."
|
||
docker save "${image_tag}" | xz -T0 -3 > "${DIST_ROOT}/superset-tools.${tag}.tar.xz"
|
||
|
||
echo "[bundle:light] Calculating checksums..."
|
||
(
|
||
cd "${DIST_ROOT}"
|
||
sha256sum "superset-tools.${tag}.tar.xz" > "sha256sums-light.${tag}.txt"
|
||
)
|
||
|
||
local img_id repo_digest
|
||
img_id="$(get_image_id "${image_tag}")"
|
||
repo_digest="$(get_repo_digest "${image_tag}")"
|
||
|
||
cat > "${DIST_ROOT}/manifest-light.${tag}.txt" <<EOF
|
||
release_tag=${tag}
|
||
image=${image_tag}
|
||
image_id=${img_id}
|
||
repo_digest=${repo_digest}
|
||
archive=superset-tools.${tag}.tar.xz
|
||
compose_file=docker-compose.light.yml
|
||
checksums_file=sha256sums-light.${tag}.txt
|
||
generated_at_utc=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||
EOF
|
||
|
||
cat > "${DIST_ROOT}/manifest-light.${tag}.json" <<EOF
|
||
{
|
||
"release_tag": "${tag}",
|
||
"generated_at_utc": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||
"image": "${image_tag}",
|
||
"image_id": "${img_id}",
|
||
"repo_digest": "${repo_digest}",
|
||
"archive": "superset-tools.${tag}.tar.xz",
|
||
"compose_file": "docker-compose.light.yml",
|
||
"checksums_file": "sha256sums-light.${tag}.txt"
|
||
}
|
||
EOF
|
||
|
||
cat > "${DIST_ROOT}/docker-compose.light.yml" <<'COMPOSE'
|
||
services:
|
||
db:
|
||
image: postgres:16-alpine
|
||
pull_policy: never
|
||
restart: unless-stopped
|
||
environment:
|
||
POSTGRES_DB: ${POSTGRES_DB:-ss_tools}
|
||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
|
||
ports:
|
||
- "${POSTGRES_HOST_PORT:-5432}:5432"
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-ss_tools}"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 10
|
||
|
||
app:
|
||
image: ${IMAGE:-superset-tools:latest}
|
||
pull_policy: never
|
||
restart: unless-stopped
|
||
depends_on:
|
||
db:
|
||
condition: service_healthy
|
||
environment:
|
||
DATABASE_URL: postgresql+psycopg2://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-ss_tools}
|
||
BACKEND_PORT: 8000
|
||
INITIAL_ADMIN_CREATE: ${INITIAL_ADMIN_CREATE:-false}
|
||
INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin}
|
||
INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD:-}
|
||
INITIAL_ADMIN_EMAIL: ${INITIAL_ADMIN_EMAIL:-}
|
||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||
FEATURES__DATASET_REVIEW: ${FEATURES__DATASET_REVIEW:-true}
|
||
FEATURES__HEALTH_MONITOR: ${FEATURES__HEALTH_MONITOR:-true}
|
||
ports:
|
||
- "${APP_HOST_PORT:-8000}:8000"
|
||
volumes:
|
||
- ./storage:/app/storage
|
||
|
||
volumes:
|
||
postgres_data:
|
||
COMPOSE
|
||
|
||
cp "${SCRIPT_DIR}/.env.enterprise-clean.example" "${DIST_ROOT}/.env.light.example"
|
||
|
||
echo "[bundle:light] ✅ Lightweight bundle created in ${DIST_ROOT}"
|
||
echo " # Load image:"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools.${tag}.tar.xz | docker load"
|
||
echo " docker compose -f ${DIST_ROOT}/docker-compose.light.yml up"
|
||
echo ""
|
||
echo " Single container (without compose):"
|
||
echo " docker run -p 8000:8000 --env-file backend/.env ${image_tag}"
|
||
}
|
||
|
||
# ======================================================================
|
||
# BUNDLE: enterprise with embeddings (agent + sentence-transformers)
|
||
# ======================================================================
|
||
|
||
bundle_embeddings() {
|
||
if [[ $# -lt 1 ]]; then
|
||
echo "Error: tag is required."
|
||
echo "Usage: ./build.sh bundle:embeddings <tag> (e.g. v1.0.0)"
|
||
exit 1
|
||
fi
|
||
local tag="$1"
|
||
local suffix="-embeddings"
|
||
local backend_tag="superset-tools-backend:${tag}${suffix}"
|
||
local frontend_tag="superset-tools-frontend:${tag}${suffix}"
|
||
local agent_tag="superset-tools-agent:${tag}${suffix}"
|
||
local postgres_tag="superset-tools-postgres:${tag}${suffix}"
|
||
local postgres_source="${POSTGRES_IMAGE:-postgres:16-alpine}"
|
||
|
||
mkdir -p "$DIST_ROOT"
|
||
|
||
# Build application images
|
||
build_backend "${tag}${suffix}"
|
||
build_frontend "${tag}${suffix}"
|
||
build_agent_embeddings "${tag}${suffix}"
|
||
|
||
# Pull and tag postgres image
|
||
echo "[bundle:embeddings] Ensuring postgres image ${postgres_source}..."
|
||
ensure_image_present "${postgres_source}"
|
||
docker tag "${postgres_source}" "${postgres_tag}"
|
||
|
||
# Release gate: fresh PostgreSQL migration + backend boot + restart must pass.
|
||
run_bundle_smoke "${backend_tag}" "${postgres_tag}"
|
||
|
||
# Export .tar.xz archives
|
||
echo "[bundle:embeddings] Exporting .tar.xz archives..."
|
||
export_image "${backend_tag}" "${DIST_ROOT}/superset-tools-backend.${tag}${suffix}.tar.xz"
|
||
export_image "${frontend_tag}" "${DIST_ROOT}/superset-tools-frontend.${tag}${suffix}.tar.xz"
|
||
export_image "${agent_tag}" "${DIST_ROOT}/superset-tools-agent.${tag}${suffix}.tar.xz"
|
||
export_image "${postgres_tag}" "${DIST_ROOT}/superset-tools-postgres.${tag}${suffix}.tar.xz"
|
||
|
||
# Checksums
|
||
echo "[bundle:embeddings] Calculating checksums..."
|
||
(cd "${DIST_ROOT}" && sha256sum \
|
||
"superset-tools-backend.${tag}${suffix}.tar.xz" \
|
||
"superset-tools-frontend.${tag}${suffix}.tar.xz" \
|
||
"superset-tools-agent.${tag}${suffix}.tar.xz" \
|
||
"superset-tools-postgres.${tag}${suffix}.tar.xz" > "sha256sums.${tag}${suffix}.txt")
|
||
|
||
# Generate deploy compose + manifest
|
||
generate_deploy_compose "${tag}${suffix}" "${backend_tag}" "${frontend_tag}" "${agent_tag}" "${postgres_tag}"
|
||
generate_manifest "${tag}" "${backend_tag}" "${frontend_tag}" "${agent_tag}" "${postgres_tag}" "${suffix}"
|
||
|
||
# Add variant field to manifest for embeddings
|
||
cat >> "${DIST_ROOT}/manifest.${tag}${suffix}.txt" <<EOF
|
||
agent_embeddings=true
|
||
EOF
|
||
|
||
# Generate env template for the bundle
|
||
generate_bundle_env "${tag}${suffix}"
|
||
|
||
echo "[bundle:embeddings] ✅ Embeddings bundle created in ${DIST_ROOT}"
|
||
echo " # Load images:"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-backend.${tag}${suffix}.tar.xz | docker load"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-frontend.${tag}${suffix}.tar.xz | docker load"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-agent.${tag}${suffix}.tar.xz | docker load"
|
||
echo " xz -dc ${DIST_ROOT}/superset-tools-postgres.${tag}${suffix}.tar.xz | docker load"
|
||
echo " # Edit .env.enterprise-clean.example -> .env.enterprise-clean, then:"
|
||
echo " docker compose -f ${DIST_ROOT}/docker-compose.enterprise-clean.yml up -d"
|
||
echo ""
|
||
echo " NOTE: Agent image includes sentence-transformers+torch (~500 MB+)."
|
||
echo " NOTE: Bundle includes postgres container (db) — no external PostgreSQL needed."
|
||
echo " Use 'bundle' for the default slim agent (no embedding model)."
|
||
}
|
||
|
||
# ======================================================================
|
||
# HELP
|
||
# ======================================================================
|
||
|
||
show_help() {
|
||
cat <<'HELP'
|
||
Usage: ./build.sh <command> [options]
|
||
|
||
Commands for local docker:
|
||
up [profile] Build and start services (default: current)
|
||
down [profile] Stop and remove containers
|
||
restart [profile] Rebuild and restart services
|
||
logs [profile] Tail logs from all services
|
||
status Show running containers for all profiles
|
||
|
||
Commands for individual image build (docker build only, no export):
|
||
build:backend <tag> Build backend image (superset-tools-backend:<tag>)
|
||
build:frontend <tag> Build frontend image (superset-tools-frontend:<tag>)
|
||
build:agent <tag> Build agent image (superset-tools-agent:<tag>)
|
||
|
||
Commands for single-image bundle (build + .tar.xz export):
|
||
bundle:backend <tag> Build + export backend .tar.xz only
|
||
bundle:frontend <tag> Build + export frontend .tar.xz only
|
||
bundle:agent <tag> Build + export agent .tar.xz only
|
||
|
||
Release verification:
|
||
smoke:bundle <backend-image> <postgres-image>
|
||
Run clean PostgreSQL migration + backend boot/restart gate.
|
||
|
||
Commands for full bundles (backend + frontend + agent + postgres):
|
||
bundle <tag> Default enterprise bundle (4 .tar.xz archives).
|
||
Includes: backend + frontend + agent (slim) + postgres (16-alpine).
|
||
PostgreSQL работает в докер-контейнере — внешний БД не требуется.
|
||
Before export: clean PostgreSQL migration + backend boot/restart smoke gate.
|
||
REQUIRED: AUTH_SECRET_KEY, ENCRYPTION_KEY, POSTGRES_PASSWORD, SERVICE_JWT.
|
||
Example: ./build.sh bundle v1.0.0
|
||
|
||
bundle:embeddings <tag>
|
||
Enterprise bundle WITH semantic embedding routing.
|
||
Agent built WITH sentence-transformers+torch (larger image).
|
||
Includes: backend + frontend + agent (embeddings) + postgres.
|
||
Before export: clean PostgreSQL migration + backend boot/restart smoke gate.
|
||
REQUIRED: AUTH_SECRET_KEY, ENCRYPTION_KEY, POSTGRES_PASSWORD, SERVICE_JWT.
|
||
Example: ./build.sh bundle:embeddings v1.0.0
|
||
|
||
bundle:light <tag> Lightweight all-in-one image (.tar.xz, <200 MB, no Playwright)
|
||
Example: ./build.sh bundle:light v1.0.0
|
||
|
||
Profiles: current (default), master, enterprise-clean
|
||
|
||
Backward-compatible shortcuts:
|
||
./build.sh -> ./build.sh up current
|
||
./build.sh master -> ./build.sh up master
|
||
./build.sh enterprise-clean -> ./build.sh up enterprise-clean
|
||
|
||
Output:
|
||
Local docker: docker compose -p superset-tools-{profile}
|
||
Bundle artifacts: dist/docker/
|
||
|
||
Notes:
|
||
- 'current' and 'master' use docker-compose.yml (dev, with local postgres container).
|
||
- 'enterprise-clean' uses docker-compose.enterprise-clean.yml (build from source,
|
||
external postgres, corporate certs, optional nginx SSL).
|
||
- Individual build commands (build:backend|frontend|agent) produce local docker
|
||
images without touching dist/docker/.
|
||
- Single-image bundle commands (bundle:backend|frontend|agent) build + export
|
||
only one .tar.xz, useful for incremental deployments.
|
||
- Bundle commands (bundle, bundle:embeddings) now include postgres:16-alpine
|
||
as superset-tools-postgres:<tag> — loaded together with other images
|
||
via 'xz -dc ... | docker load'. No external PostgreSQL required.
|
||
- pip/npm dependency downloads are cached via BuildKit cache mounts and the
|
||
postgres pull is skipped when the image is already present locally.
|
||
Clear the dependency cache with: docker buildx prune
|
||
HELP
|
||
}
|
||
|
||
# ======================================================================
|
||
# MAIN DISPATCH
|
||
# ======================================================================
|
||
|
||
main() {
|
||
local CMD="${1:-up}"
|
||
shift 2>/dev/null || true
|
||
|
||
case "$CMD" in
|
||
up|down|restart|logs|status|help|-h|--help|bundle|bundle:embeddings|bundle:light|smoke:bundle|\
|
||
build:backend|build:frontend|build:agent|\
|
||
bundle:backend|bundle:frontend|bundle:agent)
|
||
# Valid commands — proceed
|
||
;;
|
||
*)
|
||
# Backward compat: treat unknown arg as profile for "up"
|
||
local profile="$CMD"
|
||
CMD="up"
|
||
set -- "$profile" "$@"
|
||
;;
|
||
esac
|
||
|
||
case "$CMD" in
|
||
up) compose_up "${1:-current}" ;;
|
||
down) compose_down "${1:-current}" ;;
|
||
restart) compose_restart "${1:-current}" ;;
|
||
logs) compose_logs "${1:-current}" ;;
|
||
bundle) bundle_release "$@" ;;
|
||
bundle:embeddings) bundle_embeddings "$@" ;;
|
||
bundle:light) bundle_light "$@" ;;
|
||
smoke:bundle) run_bundle_smoke "$@" ;;
|
||
build:backend) build_backend "$@" ;;
|
||
build:frontend) build_frontend "$@" ;;
|
||
build:agent) build_agent "$@" ;;
|
||
bundle:backend) bundle_single_backend "$@" ;;
|
||
bundle:frontend) bundle_single_frontend "$@" ;;
|
||
bundle:agent) bundle_single_agent "$@" ;;
|
||
status) compose_status ;;
|
||
help|-h|--help) show_help ;;
|
||
*)
|
||
echo "Error: unknown command '$CMD'. See ./build.sh help"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
main "$@"
|