From 6fc8ee5bc1088d650cf236722d1c8652094ccb25 Mon Sep 17 00:00:00 2001 From: prasad-sawantdesai Date: Wed, 24 Jun 2026 13:53:10 +0200 Subject: [PATCH 1/7] allow multple instaces of simdb web server in dev environment for testing --- Dockerfile.dev | 35 +++ README.md | 8 + deploy/nginx.dev.conf.template | 22 ++ deploy/systemd/simdb-develop.service | 11 + deploy/systemd/simdb-develop.timer | 11 + docker-compose-dev.yml | 181 +++++++++++++ docs/multi_instance_deployment.md | 114 ++++++++ scripts/simdb-instance | 376 +++++++++++++++++++++++++++ 8 files changed, 758 insertions(+) create mode 100644 Dockerfile.dev create mode 100644 deploy/nginx.dev.conf.template create mode 100644 deploy/systemd/simdb-develop.service create mode 100644 deploy/systemd/simdb-develop.timer create mode 100644 docker-compose-dev.yml create mode 100644 docs/multi_instance_deployment.md create mode 100755 scripts/simdb-instance diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 00000000..c6b5f507 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,35 @@ +ARG PYVER=3.12 +FROM ghcr.io/astral-sh/uv:python${PYVER}-trixie-slim + +ARG SIMDB_GIT_BRANCH=unknown +ARG SIMDB_GIT_COMMIT=unknown +ARG SIMDB_DEPLOYMENT=local + +LABEL org.simdb.deployment="${SIMDB_DEPLOYMENT}" \ + org.simdb.git.branch="${SIMDB_GIT_BRANCH}" \ + org.simdb.git.commit="${SIMDB_GIT_COMMIT}" + +ENV UV_NO_DEV=1 +ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 +ENV SIMDB_DEPLOYMENT="${SIMDB_DEPLOYMENT}" +ENV SIMDB_GIT_BRANCH="${SIMDB_GIT_BRANCH}" +ENV SIMDB_GIT_COMMIT="${SIMDB_GIT_COMMIT}" + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev \ + libldap2-dev \ + libsasl2-dev \ + libmagic1 \ + && rm -rf /var/lib/apt/lists/* + +COPY uv.lock pyproject.toml alembic.ini ./ +COPY src/ ./src/ +COPY alembic/ ./alembic/ +RUN uv sync --locked --extra all + +ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg + +CMD ["uv", "run", "simdb_server"] diff --git a/README.md b/README.md index 68cd03b8..da233002 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,14 @@ To access data from the ITER remotes outside ITER systems, you'll need to [confi Setting up and maintaining a remote CLI server is documented [here](https://simdb.readthedocs.io/en/latest/maintenance_guide.html). +For isolated branch and pull-request deployments backed by separate PostgreSQL +and Redis volumes, see the +[multi-instance deployment guide](docs/multi_instance_deployment.md). +The standard allocation is `main` on port 5000, `develop` on port 5100, and +pull requests on ports 5101-5999. +These instances use the development-only Docker and Compose files and expose +their branch identity at `/__simdb_instance`. + --- ## Developer Guide diff --git a/deploy/nginx.dev.conf.template b/deploy/nginx.dev.conf.template new file mode 100644 index 00000000..640b7de8 --- /dev/null +++ b/deploy/nginx.dev.conf.template @@ -0,0 +1,22 @@ +server { + listen 80; + server_name _; + + add_header X-SimDB-Deployment "${SIMDB_DEPLOYMENT}" always; + add_header X-SimDB-Branch "${SIMDB_GIT_BRANCH}" always; + add_header X-SimDB-Commit "${SIMDB_GIT_COMMIT}" always; + add_header X-SimDB-Deployed-At "${SIMDB_DEPLOYED_AT}" always; + + location = /__simdb_instance { + default_type application/json; + return 200 '{"deployment":"${SIMDB_DEPLOYMENT}","git_branch":"${SIMDB_GIT_BRANCH}","git_commit":"${SIMDB_GIT_COMMIT}","deployed_at":"${SIMDB_DEPLOYED_AT}"}'; + } + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://web:5000; + } +} diff --git a/deploy/systemd/simdb-develop.service b/deploy/systemd/simdb-develop.service new file mode 100644 index 00000000..1a0fe01a --- /dev/null +++ b/deploy/systemd/simdb-develop.service @@ -0,0 +1,11 @@ +[Unit] +Description=Update and deploy the SimDB develop environment +After=network-online.target docker.service +Wants=network-online.target +Requires=docker.service + +[Service] +Type=oneshot +WorkingDirectory=/opt/simdb/source +Environment=SIMDB_INSTANCE_ROOT=/opt/simdb/instances +ExecStart=/opt/simdb/source/scripts/simdb-instance deploy-develop diff --git a/deploy/systemd/simdb-develop.timer b/deploy/systemd/simdb-develop.timer new file mode 100644 index 00000000..2859d7ae --- /dev/null +++ b/deploy/systemd/simdb-develop.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Periodically update the SimDB develop environment + +[Timer] +OnBootSec=2min +OnUnitActiveSec=5min +Persistent=true +Unit=simdb-develop.service + +[Install] +WantedBy=timers.target diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 00000000..1daf2b97 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,181 @@ +services: + migrations: + build: + context: . + dockerfile: Dockerfile.dev + args: + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + volumes: + - ./config:/app/config:ro + environment: + DATABASE_URL: postgresql+psycopg2://simdb:simdb@postgres:5432/simdb + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYED_AT: ${SIMDB_DEPLOYED_AT:-unknown} + depends_on: + - postgres + command: uv run alembic upgrade head + restart: "no" + + web: + build: + context: . + dockerfile: Dockerfile.dev + args: + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + environment: + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYED_AT: ${SIMDB_DEPLOYED_AT:-unknown} + labels: + org.simdb.deployment: ${SIMDB_DEPLOYMENT:-local} + org.simdb.git.branch: ${SIMDB_GIT_BRANCH:-unknown} + org.simdb.git.commit: ${SIMDB_GIT_COMMIT:-unknown} + volumes: + - ./validation:/app/validation:ro + - ./config:/app/config:ro + - ./tmp/partition_data:/data/simdb/partition:ro + - upload_data:/data/simdb/simulations + depends_on: + redis: + condition: service_healthy + postgres: + condition: service_healthy + migrations: + condition: service_completed_successfully + restart: unless-stopped + healthcheck: + test: + - CMD + - python + - -c + - "import urllib.request; urllib.request.urlopen('http://localhost:5000/', timeout=5)" + interval: 5s + timeout: 5s + retries: 12 + start_period: 10s + + proxy: + image: nginx:1.29-alpine + ports: + - "${SIMDB_WEB_PORT:-5100}:80" + environment: + NGINX_ENVSUBST_FILTER: ^SIMDB_ + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYED_AT: ${SIMDB_DEPLOYED_AT:-unknown} + labels: + org.simdb.deployment: ${SIMDB_DEPLOYMENT:-local} + org.simdb.git.branch: ${SIMDB_GIT_BRANCH:-unknown} + org.simdb.git.commit: ${SIMDB_GIT_COMMIT:-unknown} + volumes: + - ./deploy/nginx.dev.conf.template:/etc/nginx/templates/default.conf.template:ro + depends_on: + web: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: + - CMD + - wget + - --quiet + - --spider + - http://localhost/__simdb_instance + interval: 5s + timeout: 5s + retries: 12 + start_period: 5s + + worker: + profiles: [with_workers] + # docker compose --profile with_workers enables this service + build: + context: . + dockerfile: Dockerfile.dev + args: + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + command: uv run simdb_worker + environment: + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYED_AT: ${SIMDB_DEPLOYED_AT:-unknown} + volumes: + - ./config:/app/config:ro + - ./tmp/partition_data:/data/simdb/partition:ro + - upload_data:/data/simdb/simulations + depends_on: + redis: + condition: service_healthy + postgres: + condition: service_healthy + migrations: + condition: service_completed_successfully + restart: unless-stopped + + beat: + profiles: [with_workers] + # docker compose --profile with_workers enables this service + build: + context: . + dockerfile: Dockerfile.dev + args: + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + command: uv run simdb_beat + environment: + SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} + SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} + SIMDB_DEPLOYED_AT: ${SIMDB_DEPLOYED_AT:-unknown} + volumes: + - ./config:/app/config:ro + depends_on: + redis: + condition: service_healthy + postgres: + condition: service_healthy + migrations: + condition: service_completed_successfully + restart: unless-stopped + + redis: + image: redis:8-alpine + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 1s + timeout: 5s + retries: 5 + restart: unless-stopped + + postgres: + image: postgres:16-alpine + environment: + - POSTGRES_USER=simdb + - POSTGRES_PASSWORD=simdb + - POSTGRES_DB=simdb + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U simdb"] + interval: 1s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + redis_data: + postgres_data: + upload_data: diff --git a/docs/multi_instance_deployment.md b/docs/multi_instance_deployment.md new file mode 100644 index 00000000..e684885f --- /dev/null +++ b/docs/multi_instance_deployment.md @@ -0,0 +1,114 @@ +# Running multiple SimDB server instances + +Each deployment uses a dedicated Git worktree and Docker Compose project. Compose +project names isolate PostgreSQL, Redis, uploaded files, containers, and networks. +The management script only updates worktrees beneath `SIMDB_INSTANCE_ROOT`; it does +not modify the checkout from which it is invoked. All instances use +`docker-compose-dev.yml` and `Dockerfile.dev`; the production Compose and +Dockerfile are not used or modified. + +## Requirements + +- Docker with the Compose plugin +- Git access to the configured remote +- A clean source repository containing `scripts/simdb-instance` + +The default instance directory is a sibling of the repository named +`simdb-instances`. Override it when required: + +```bash +export SIMDB_INSTANCE_ROOT=/srv/simdb/instances +``` + +## Deploy environments + +Keep `main` on port 5000 and `develop` on port 5100: + +```bash +scripts/simdb-instance deploy-main +scripts/simdb-instance deploy-develop +``` + +Deploy a pull request. If no port is supplied, the first unused port in +5101-5999 is assigned: + +```bash +scripts/simdb-instance deploy-pr 123 +scripts/simdb-instance deploy-pr 124 --port 5124 +``` + +Deploy any remote branch: + +```bash +scripts/simdb-instance deploy-branch feature/query-cache +scripts/simdb-instance deploy-branch feature/query-cache \ + --name query-cache --port 5200 +``` + +Updating an existing instance preserves its assigned port and named volumes. +The script refuses to update a managed worktree if it contains local changes. + +## Inspect and remove environments + +```bash +scripts/simdb-instance list +scripts/simdb-instance status pr-123 +scripts/simdb-instance logs pr-123 +scripts/simdb-instance stop pr-123 +``` + +`stop` preserves PostgreSQL, Redis, and uploaded data. `destroy` permanently +removes them along with the managed worktree: + +```bash +scripts/simdb-instance destroy pr-123 +``` + +## Verify the deployed revision + +Open the development-only identity endpoint: + +```bash +curl http://localhost:5100/__simdb_instance +``` + +It returns: + +```json +{ + "deployment": "pr-123", + "git_branch": "pull/123", + "git_commit": "81d992f...", + "deployed_at": "2026-06-24T10:00:00Z" +} +``` + +The development Nginx proxy also adds `X-SimDB-Deployment`, +`X-SimDB-Branch`, `X-SimDB-Commit`, and `X-SimDB-Deployed-At` to every +response: + +```bash +curl -I http://localhost:5100/ +``` + +This identity behavior exists only in `docker-compose-dev.yml`; no SimDB Python +API code is changed. + +## Continuously update develop with systemd + +Copy and edit the provided unit files: + +```bash +sudo cp deploy/systemd/simdb-develop.* /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now simdb-develop.timer +``` + +The provided timer updates `develop` on port 5100. Change `/opt/simdb/source`, +`/opt/simdb/instances`, and the service user or group to match the host. Check +update activity with: + +```bash +systemctl list-timers simdb-develop.timer +journalctl -u simdb-develop.service +``` diff --git a/scripts/simdb-instance b/scripts/simdb-instance new file mode 100755 index 00000000..dee57e49 --- /dev/null +++ b/scripts/simdb-instance @@ -0,0 +1,376 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(git -C "$(dirname "${BASH_SOURCE[0]}")/.." rev-parse --show-toplevel)" +INSTANCE_ROOT="${SIMDB_INSTANCE_ROOT:-$(dirname "$REPO_ROOT")/simdb-instances}" +WORKTREE_ROOT="$INSTANCE_ROOT/worktrees" +STATE_ROOT="$INSTANCE_ROOT/state" +REMOTE="${SIMDB_GIT_REMOTE:-origin}" +PORT_MIN="${SIMDB_PORT_MIN:-5101}" +PORT_MAX="${SIMDB_PORT_MAX:-5999}" +COMPOSE_FILE="docker-compose-dev.yml" + +usage() { + cat <<'EOF' +Manage isolated SimDB Docker Compose deployments. + +Usage: + scripts/simdb-instance deploy-main + scripts/simdb-instance deploy-develop + scripts/simdb-instance deploy-pr PR_NUMBER [--port PORT] + scripts/simdb-instance deploy-branch BRANCH [--name NAME] [--port PORT] + scripts/simdb-instance list + scripts/simdb-instance status NAME + scripts/simdb-instance logs NAME [SERVICE] + scripts/simdb-instance stop NAME + scripts/simdb-instance destroy NAME + +Environment: + SIMDB_INSTANCE_ROOT State/worktree directory (default: ../simdb-instances) + SIMDB_GIT_REMOTE Git remote to fetch (default: origin) + SIMDB_PORT_MIN/MAX Automatic PR/branch range (default: 5101-5999) +EOF +} + +die() { + printf 'error: %s\n' "$*" >&2 + exit 1 +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || die "required command not found: $1" +} + +validate_name() { + [[ "$1" =~ ^[a-zA-Z0-9][a-zA-Z0-9_.-]*$ ]] || + die "invalid instance name '$1'" +} + +state_file() { + printf '%s/%s.env\n' "$STATE_ROOT" "$1" +} + +read_state() { + local file="$1" + local key="$2" + sed -n "s/^${key}=//p" "$file" | head -n 1 +} + +require_state() { + local file + file="$(state_file "$1")" + [[ -f "$file" ]] || die "unknown instance '$1'" + printf '%s\n' "$file" +} + +port_in_use() { + local requested="$1" + local excluded_name="${2:-}" + local file + for file in "$STATE_ROOT"/*.env; do + [[ -e "$file" ]] || continue + [[ "$(basename "$file" .env)" == "$excluded_name" ]] && continue + [[ "$(read_state "$file" SIMDB_WEB_PORT)" == "$requested" ]] && return 0 + done + return 1 +} + +allocate_port() { + local requested="${1:-}" + local excluded_name="${2:-}" + local port + + if [[ -n "$requested" ]]; then + [[ "$requested" =~ ^[0-9]+$ ]] || die "port must be numeric" + ((requested >= 1 && requested <= 65535)) || die "port is out of range" + port_in_use "$requested" "$excluded_name" && + die "port $requested is already assigned" + printf '%s\n' "$requested" + return + fi + + for ((port = PORT_MIN; port <= PORT_MAX; port++)); do + if ! port_in_use "$port" "$excluded_name"; then + printf '%s\n' "$port" + return + fi + done + die "no free port in range $PORT_MIN-$PORT_MAX" +} + +validate_pool_port() { + local port="$1" + ((port >= PORT_MIN && port <= PORT_MAX)) || + die "port $port is outside the PR/branch range $PORT_MIN-$PORT_MAX" +} + +assert_managed_worktree_clean() { + local worktree="$1" + [[ -e "$worktree/.git" ]] || die "$worktree exists but is not a Git worktree" + [[ -z "$(git -C "$worktree" status --porcelain)" ]] || + die "managed worktree is dirty: $worktree" +} + +prepare_worktree() { + local name="$1" + local commit="$2" + local worktree="$WORKTREE_ROOT/$name" + + if [[ -e "$worktree" ]]; then + assert_managed_worktree_clean "$worktree" + git -C "$worktree" checkout --quiet --detach "$commit" + else + git -C "$REPO_ROOT" worktree add --quiet --detach "$worktree" "$commit" + fi + for required_file in \ + Dockerfile.dev \ + docker-compose-dev.yml \ + deploy/nginx.dev.conf.template; do + [[ -f "$worktree/$required_file" ]] || + die "$required_file is missing from commit $commit" + done + mkdir -p "$worktree/tmp/partition_data" + printf '%s\n' "$worktree" +} + +write_state() { + local file="$1" + local name="$2" + local port="$3" + local branch="$4" + local commit="$5" + local deployed_at="$6" + + { + printf 'COMPOSE_PROJECT_NAME=simdb-%s\n' "$name" + printf 'SIMDB_WEB_PORT=%s\n' "$port" + printf 'SIMDB_DEPLOYMENT=%s\n' "$name" + printf 'SIMDB_GIT_BRANCH=%s\n' "$branch" + printf 'SIMDB_GIT_COMMIT=%s\n' "$commit" + printf 'SIMDB_DEPLOYED_AT=%s\n' "$deployed_at" + } >"$file" +} + +compose() { + local name="$1" + shift + local file + local pending_file + local worktree="$WORKTREE_ROOT/$name" + file="$(require_state "$name")" + docker compose \ + --project-name "simdb-$name" \ + --project-directory "$worktree" \ + --file "$worktree/$COMPOSE_FILE" \ + --env-file "$file" \ + "$@" +} + +deploy_commit() { + local name="$1" + local branch="$2" + local commit="$3" + local requested_port="${4:-}" + local use_pool="${5:-false}" + local old_state + local port + local worktree + local file + local pending_file + local deployed_at + + validate_name "$name" + commit="$(git -C "$REPO_ROOT" rev-parse "$commit")" + mkdir -p "$WORKTREE_ROOT" "$STATE_ROOT" + old_state="$(state_file "$name")" + if [[ "$use_pool" == true && -n "$requested_port" ]]; then + validate_pool_port "$requested_port" + fi + if [[ -f "$old_state" && -z "$requested_port" ]]; then + port="$(read_state "$old_state" SIMDB_WEB_PORT)" + if [[ "$use_pool" == true ]] && + ((port < PORT_MIN || port > PORT_MAX)); then + port="$(allocate_port "" "$name")" + fi + else + port="$(allocate_port "$requested_port" "$name")" + fi + + worktree="$(prepare_worktree "$name" "$commit")" + deployed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + file="$(state_file "$name")" + pending_file="$STATE_ROOT/.$name.env.pending" + write_state \ + "$pending_file" "$name" "$port" "$branch" "$commit" "$deployed_at" + + if docker compose \ + --project-name "simdb-$name" \ + --project-directory "$worktree" \ + --file "$worktree/$COMPOSE_FILE" \ + --env-file "$pending_file" \ + up -d --build --remove-orphans --wait; then + mv "$pending_file" "$file" + else + rm -f "$pending_file" + die "deployment failed; previous instance state was preserved" + fi + + printf '%s: http://localhost:%s (%s @ %.12s)\n' \ + "$name" "$port" "$branch" "$commit" +} + +deploy_main() { + git -C "$REPO_ROOT" fetch "$REMOTE" main + deploy_commit main main FETCH_HEAD 5000 +} + +deploy_develop() { + git -C "$REPO_ROOT" fetch "$REMOTE" develop + deploy_commit develop develop FETCH_HEAD 5100 +} + +deploy_pr() { + local number="$1" + local port="$2" + [[ "$number" =~ ^[0-9]+$ ]] || die "PR number must be numeric" + git -C "$REPO_ROOT" fetch "$REMOTE" "pull/$number/head" + deploy_commit "pr-$number" "pull/$number" FETCH_HEAD "$port" true +} + +deploy_branch() { + local branch="$1" + local name="$2" + local port="$3" + git check-ref-format --branch "$branch" >/dev/null + git -C "$REPO_ROOT" fetch "$REMOTE" "$branch" + deploy_commit "$name" "$branch" FETCH_HEAD "$port" true +} + +parse_port() { + local port="" + while (($#)); do + case "$1" in + --port) + (($# >= 2)) || die "--port requires a value" + port="$2" + shift 2 + ;; + *) + die "unknown argument: $1" + ;; + esac + done + printf '%s\n' "$port" +} + +list_instances() { + local file + local name + mkdir -p "$STATE_ROOT" + printf '%-20s %-7s %-28s %-12s %s\n' \ + INSTANCE PORT BRANCH COMMIT URL + for file in "$STATE_ROOT"/*.env; do + [[ -e "$file" ]] || continue + name="$(basename "$file" .env)" + printf '%-20s %-7s %-28s %-12.12s http://localhost:%s\n' \ + "$name" \ + "$(read_state "$file" SIMDB_WEB_PORT)" \ + "$(read_state "$file" SIMDB_GIT_BRANCH)" \ + "$(read_state "$file" SIMDB_GIT_COMMIT)" \ + "$(read_state "$file" SIMDB_WEB_PORT)" + done +} + +main() { + local command="${1:-}" + local name + local port + local branch + local service + + require_command git + case "$command" in + deploy-main) + (($# == 1)) || die "deploy-main does not accept arguments" + require_command docker + deploy_main + ;; + deploy-develop) + (($# == 1)) || die "deploy-develop does not accept arguments" + require_command docker + deploy_develop + ;; + deploy-pr) + (($# >= 2)) || die "deploy-pr requires a PR number" + name="$2" + shift 2 + require_command docker + port="$(parse_port "$@")" + deploy_pr "$name" "$port" + ;; + deploy-branch) + (($# >= 2)) || die "deploy-branch requires a branch" + branch="$2" + name="${branch//\//-}" + shift 2 + port="" + while (($#)); do + case "$1" in + --name) + (($# >= 2)) || die "--name requires a value" + name="$2" + shift 2 + ;; + --port) + (($# >= 2)) || die "--port requires a value" + port="$2" + shift 2 + ;; + *) + die "unknown argument: $1" + ;; + esac + done + require_command docker + deploy_branch "$branch" "$name" "$port" + ;; + list) + list_instances + ;; + status) + (($# == 2)) || die "status requires an instance name" + require_command docker + compose "$2" ps + ;; + logs) + (($# == 2 || $# == 3)) || die "logs requires an instance name" + require_command docker + service="${3:-web}" + compose "$2" logs --tail=200 -f "$service" + ;; + stop) + (($# == 2)) || die "stop requires an instance name" + require_command docker + compose "$2" stop + ;; + destroy) + (($# == 2)) || die "destroy requires an instance name" + require_command docker + name="$2" + validate_name "$name" + compose "$name" down --volumes --remove-orphans + git -C "$REPO_ROOT" worktree remove "$WORKTREE_ROOT/$name" + rm "$(state_file "$name")" + printf 'Destroyed %s, including its database and uploaded data.\n' "$name" + ;; + -h | --help | help) + usage + ;; + *) + usage >&2 + exit 2 + ;; + esac +} + +main "$@" From aae175545e952778478b4d4e05afdf6b295e9e93 Mon Sep 17 00:00:00 2001 From: prasad-sawantdesai Date: Wed, 24 Jun 2026 14:11:32 +0200 Subject: [PATCH 2/7] allow updted files in repo --- docs/multi_instance_deployment.md | 3 ++- scripts/simdb-instance | 15 ++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/multi_instance_deployment.md b/docs/multi_instance_deployment.md index e684885f..5e9e3ce3 100644 --- a/docs/multi_instance_deployment.md +++ b/docs/multi_instance_deployment.md @@ -46,7 +46,8 @@ scripts/simdb-instance deploy-branch feature/query-cache \ ``` Updating an existing instance preserves its assigned port and named volumes. -The script refuses to update a managed worktree if it contains local changes. +Tracked changes inside a managed worktree are discarded because these worktrees +are deployment artifacts. Untracked runtime files are preserved. ## Inspect and remove environments diff --git a/scripts/simdb-instance b/scripts/simdb-instance index dee57e49..eccaa841 100755 --- a/scripts/simdb-instance +++ b/scripts/simdb-instance @@ -104,21 +104,18 @@ validate_pool_port() { die "port $port is outside the PR/branch range $PORT_MIN-$PORT_MAX" } -assert_managed_worktree_clean() { - local worktree="$1" - [[ -e "$worktree/.git" ]] || die "$worktree exists but is not a Git worktree" - [[ -z "$(git -C "$worktree" status --porcelain)" ]] || - die "managed worktree is dirty: $worktree" -} - prepare_worktree() { local name="$1" local commit="$2" local worktree="$WORKTREE_ROOT/$name" if [[ -e "$worktree" ]]; then - assert_managed_worktree_clean "$worktree" - git -C "$worktree" checkout --quiet --detach "$commit" + [[ -e "$worktree/.git" ]] || + die "$worktree exists but is not a Git worktree" + if [[ -n "$(git -C "$worktree" status --porcelain --untracked-files=no)" ]]; then + printf 'warning: discarding tracked changes in %s\n' "$worktree" >&2 + fi + git -C "$worktree" checkout --quiet --detach --force "$commit" else git -C "$REPO_ROOT" worktree add --quiet --detach "$worktree" "$commit" fi From 35b7e79641f5a5e7bf0735380f7a527d9dfa3d07 Mon Sep 17 00:00:00 2001 From: prasad-sawantdesai Date: Wed, 24 Jun 2026 16:49:09 +0200 Subject: [PATCH 3/7] fix issues with postgressql --- docker-compose-dev.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 1daf2b97..f51764bc 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -87,7 +87,7 @@ services: - wget - --quiet - --spider - - http://localhost/__simdb_instance + - http://127.0.0.1/__simdb_instance interval: 5s timeout: 5s retries: 12 @@ -169,10 +169,11 @@ services: volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U simdb"] - interval: 1s + test: ["CMD-SHELL", "pg_isready -U simdb -d simdb"] + interval: 2s timeout: 5s - retries: 5 + retries: 15 + start_period: 30s restart: unless-stopped volumes: From 901dbceb11d26b64f3ee496047438cb6d964a342 Mon Sep 17 00:00:00 2001 From: prasad-sawantdesai Date: Thu, 25 Jun 2026 15:06:08 +0200 Subject: [PATCH 4/7] fix the migartion race --- docker-compose-dev.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index f51764bc..46d8009d 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -16,7 +16,8 @@ services: SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} SIMDB_DEPLOYED_AT: ${SIMDB_DEPLOYED_AT:-unknown} depends_on: - - postgres + postgres: + condition: service_healthy command: uv run alembic upgrade head restart: "no" From bb90a5dc4073b5084f095c397934454c3f177c35 Mon Sep 17 00:00:00 2001 From: prasad-sawantdesai Date: Thu, 25 Jun 2026 16:34:18 +0200 Subject: [PATCH 5/7] added client_max_body_size to 16m --- deploy/nginx.dev.conf.template | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/nginx.dev.conf.template b/deploy/nginx.dev.conf.template index 640b7de8..20dedc38 100644 --- a/deploy/nginx.dev.conf.template +++ b/deploy/nginx.dev.conf.template @@ -1,6 +1,7 @@ server { listen 80; server_name _; + client_max_body_size 16m; add_header X-SimDB-Deployment "${SIMDB_DEPLOYMENT}" always; add_header X-SimDB-Branch "${SIMDB_GIT_BRANCH}" always; From fb56700b2bbd38fa95903381d450a0560562ad62 Mon Sep 17 00:00:00 2001 From: prasad-sawantdesai Date: Thu, 25 Jun 2026 18:42:23 +0200 Subject: [PATCH 6/7] added IMAS_LOCAL_HOSTS option --- docker-compose-dev.yml | 1 + docker-compose.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 46d8009d..e09d481d 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -30,6 +30,7 @@ services: SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} environment: + IMAS_LOCAL_HOSTS: localhost SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} diff --git a/docker-compose.yml b/docker-compose.yml index 4b03422b..eff71ff3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ services: web: build: . + environment: + IMAS_LOCAL_HOSTS: localhost ports: - "5000:5000" volumes: @@ -91,4 +93,3 @@ services: volumes: redis_data: postgres_data: - From b811ac8ed7cce24c12b9716f010dc28dce3783f0 Mon Sep 17 00:00:00 2001 From: prasad-sawantdesai Date: Wed, 1 Jul 2026 10:24:03 +0200 Subject: [PATCH 7/7] cleanup --- README.md | 8 -------- deploy/systemd/simdb-develop.service | 11 ----------- deploy/systemd/simdb-develop.timer | 11 ----------- docs/developer_guide.md | 8 ++++++++ docs/multi_instance_deployment.md | 21 +-------------------- 5 files changed, 9 insertions(+), 50 deletions(-) delete mode 100644 deploy/systemd/simdb-develop.service delete mode 100644 deploy/systemd/simdb-develop.timer diff --git a/README.md b/README.md index da233002..68cd03b8 100644 --- a/README.md +++ b/README.md @@ -101,14 +101,6 @@ To access data from the ITER remotes outside ITER systems, you'll need to [confi Setting up and maintaining a remote CLI server is documented [here](https://simdb.readthedocs.io/en/latest/maintenance_guide.html). -For isolated branch and pull-request deployments backed by separate PostgreSQL -and Redis volumes, see the -[multi-instance deployment guide](docs/multi_instance_deployment.md). -The standard allocation is `main` on port 5000, `develop` on port 5100, and -pull requests on ports 5101-5999. -These instances use the development-only Docker and Compose files and expose -their branch identity at `/__simdb_instance`. - --- ## Developer Guide diff --git a/deploy/systemd/simdb-develop.service b/deploy/systemd/simdb-develop.service deleted file mode 100644 index 1a0fe01a..00000000 --- a/deploy/systemd/simdb-develop.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Update and deploy the SimDB develop environment -After=network-online.target docker.service -Wants=network-online.target -Requires=docker.service - -[Service] -Type=oneshot -WorkingDirectory=/opt/simdb/source -Environment=SIMDB_INSTANCE_ROOT=/opt/simdb/instances -ExecStart=/opt/simdb/source/scripts/simdb-instance deploy-develop diff --git a/deploy/systemd/simdb-develop.timer b/deploy/systemd/simdb-develop.timer deleted file mode 100644 index 2859d7ae..00000000 --- a/deploy/systemd/simdb-develop.timer +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Periodically update the SimDB develop environment - -[Timer] -OnBootSec=2min -OnUnitActiveSec=5min -Persistent=true -Unit=simdb-develop.service - -[Install] -WantedBy=timers.target diff --git a/docs/developer_guide.md b/docs/developer_guide.md index d3e5259f..e49f1a2a 100644 --- a/docs/developer_guide.md +++ b/docs/developer_guide.md @@ -247,3 +247,11 @@ port = 5432 ... ``` + +For isolated branch and pull-request deployments with separate PostgreSQL +and Redis volumes, see the +[multi-instance deployment guide](multi_instance_deployment.md). +The standard allocation is `main` on port 5000, `develop` on port 5100, and +pull requests on ports 5101-5999. +These instances use the development-only Docker and Compose files and expose +the branch identity at `/__simdb_instance`. \ No newline at end of file diff --git a/docs/multi_instance_deployment.md b/docs/multi_instance_deployment.md index 5e9e3ce3..b4a2ac19 100644 --- a/docs/multi_instance_deployment.md +++ b/docs/multi_instance_deployment.md @@ -93,23 +93,4 @@ curl -I http://localhost:5100/ ``` This identity behavior exists only in `docker-compose-dev.yml`; no SimDB Python -API code is changed. - -## Continuously update develop with systemd - -Copy and edit the provided unit files: - -```bash -sudo cp deploy/systemd/simdb-develop.* /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable --now simdb-develop.timer -``` - -The provided timer updates `develop` on port 5100. Change `/opt/simdb/source`, -`/opt/simdb/instances`, and the service user or group to match the host. Check -update activity with: - -```bash -systemctl list-timers simdb-develop.timer -journalctl -u simdb-develop.service -``` +API code is changed. \ No newline at end of file