feat(infra): staging dashboard_api deploy script (Phase 1)
deploy_dashboard_api_staging.sh — standalone bridge twin of the prod
~/deploy_dashboard_api.sh for the fivetitude.com staging umbrella:
- container dashboard_api_staging on port 8891
- Traefik Host(fleetapi.fivetitude.com), router/service names suffixed -staging
- CORS = fleetnow.fivetitude.com, fleetops.fivetitude.com
- DATABASE_URL derived on-host as a READ-ONLY grafana_ro URL (never printed)
- VTRIPS_REFRESH_INTERVAL_S=0 so the read-only instance never REFRESHes
(prod owns the v_trips materialized-view refresh)
Reuses the webhook_receiver image + app network + WIP bind-mount, exactly like
prod. Not yet executed on the host — awaiting go-ahead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6cf0905b31
commit
478fb0e707
1 changed files with 95 additions and 0 deletions
95
deploy_dashboard_api_staging.sh
Executable file
95
deploy_dashboard_api_staging.sh
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/env bash
|
||||
# deploy_dashboard_api_staging.sh — STAGING twin of ~/deploy_dashboard_api.sh
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Stands up a SECOND dashboard_api bridge for the staging umbrella
|
||||
# (fleetapi.fivetitude.com). It mirrors the prod script but with four
|
||||
# deliberate differences:
|
||||
#
|
||||
# 1. Container name dashboard_api_staging (prod: dashboard_api)
|
||||
# 2. Port / Traefik 8891 + Host(fleetapi.fivetitude.com) (prod: 8890 + rahamafresh)
|
||||
# 3. DB role READ-ONLY grafana_ro DATABASE_URL, derived on-host from the
|
||||
# webhook env (prod: the app's read/write DATABASE_URL).
|
||||
# 4. Refresher OFF VTRIPS_REFRESH_INTERVAL_S=0 — prod owns the v_trips refresh;
|
||||
# a read-only instance must never attempt REFRESH.
|
||||
#
|
||||
# Staging reads the SAME production DB (over the internal Docker network) as
|
||||
# grafana_ro, so it is physically incapable of writing. See
|
||||
# docs/STAGING_FLEETOPS_ARCHITECTURE.md §6.
|
||||
#
|
||||
# Like prod, this is a STANDALONE bridge container (NOT Coolify-managed): it
|
||||
# reuses the webhook_receiver image + app network, bind-mounts the WIP API file,
|
||||
# and an env/CORS change needs a container RECREATE (this script does that).
|
||||
#
|
||||
# Deploy:
|
||||
# scp dashboard_api_rev.py kianiadee@twala.rahamafresh.com:~/dashboard_api_staging_rev.py
|
||||
# scp deploy_dashboard_api_staging.sh kianiadee@twala.rahamafresh.com:~/
|
||||
# ssh kianiadee@twala.rahamafresh.com 'bash ~/deploy_dashboard_api_staging.sh'
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
NAME=dashboard_api_staging
|
||||
PORT=8891
|
||||
MOUNT_DIR=/home/kianiadee/dashboard_api_staging
|
||||
ENV_FILE="$MOUNT_DIR/dapi.staging.env"
|
||||
STAGED_SRC=/home/kianiadee/dashboard_api_staging_rev.py
|
||||
CORS='https://fleetnow.fivetitude.com,https://fleetops.fivetitude.com'
|
||||
|
||||
WH=$(docker ps --filter name=webhook_receiver --format "{{.Names}}" | head -1)
|
||||
IMG=$(docker inspect "$WH" --format "{{.Image}}")
|
||||
APPNET=$(docker inspect "$WH" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}')
|
||||
echo "Reusing image $IMG on network $APPNET (from $WH)"
|
||||
|
||||
mkdir -p "$MOUNT_DIR"
|
||||
# Stage a fresh copy only if one was scp'd to ~; otherwise keep the existing mount.
|
||||
if [ -f "$STAGED_SRC" ]; then
|
||||
mv -f "$STAGED_SRC" "$MOUNT_DIR/dashboard_api_rev.py"
|
||||
fi
|
||||
test -f "$MOUNT_DIR/dashboard_api_rev.py" \
|
||||
|| { echo "ERROR: dashboard_api_rev.py missing in $MOUNT_DIR; scp it to ~/dashboard_api_staging_rev.py first"; exit 1; }
|
||||
|
||||
# Derive a READ-ONLY DATABASE_URL on the host (never printed): take the app's
|
||||
# DATABASE_URL host:port/dbname and swap the credentials for grafana_ro.
|
||||
SRC_DB_URL=$(docker inspect "$WH" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^DATABASE_URL=//p' | head -1)
|
||||
RO_PW=$(docker inspect "$WH" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^GRAFANA_DB_RO_PASSWORD=//p' | head -1)
|
||||
[ -n "$SRC_DB_URL" ] || { echo "ERROR: DATABASE_URL not found in $WH env"; exit 1; }
|
||||
[ -n "$RO_PW" ] || { echo "ERROR: GRAFANA_DB_RO_PASSWORD not found in $WH env"; exit 1; }
|
||||
HOSTPART="${SRC_DB_URL#*@}" # host:port/dbname[?params]
|
||||
RO_DB_URL="postgresql://grafana_ro:${RO_PW}@${HOSTPART}"
|
||||
|
||||
# Reuse the webhook env, stripping runtime noise AND anything we override below
|
||||
# (DATABASE_URL -> read-only, CORS -> staging origins, refresher -> disabled).
|
||||
docker inspect "$WH" --format '{{range .Config.Env}}{{println .}}{{end}}' \
|
||||
| grep -vE '^(PATH=|HOSTNAME=|HOME=|PWD=|TERM=|SHLVL=|_=|LANG=|GPG_KEY=|PYTHON_VERSION=|PYTHON_PIP_VERSION=|PYTHONUNBUFFERED=|DATABASE_URL=|DASHBOARD_CORS_ORIGINS=|VTRIPS_REFRESH_INTERVAL_S=)' \
|
||||
> "$ENV_FILE"
|
||||
{
|
||||
echo "DATABASE_URL=${RO_DB_URL}"
|
||||
echo "DASHBOARD_CORS_ORIGINS=${CORS}"
|
||||
echo "VTRIPS_REFRESH_INTERVAL_S=0"
|
||||
} >> "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
|
||||
docker rm -f "$NAME" 2>/dev/null || true
|
||||
docker run -d --name "$NAME" --restart unless-stopped \
|
||||
--network "$APPNET" \
|
||||
--env-file "$ENV_FILE" \
|
||||
-v "$MOUNT_DIR/dashboard_api_rev.py:/app/dashboard_api_rev.py:ro" \
|
||||
--label 'traefik.enable=true' \
|
||||
--label 'traefik.docker.network=coolify' \
|
||||
--label 'traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https' \
|
||||
--label 'traefik.http.routers.http-0-fleetapi-staging.entryPoints=http' \
|
||||
--label 'traefik.http.routers.http-0-fleetapi-staging.middlewares=redirect-to-https' \
|
||||
--label 'traefik.http.routers.http-0-fleetapi-staging.rule=Host(`fleetapi.fivetitude.com`)' \
|
||||
--label 'traefik.http.routers.https-0-fleetapi-staging.entryPoints=https' \
|
||||
--label 'traefik.http.routers.https-0-fleetapi-staging.rule=Host(`fleetapi.fivetitude.com`)' \
|
||||
--label 'traefik.http.routers.https-0-fleetapi-staging.tls=true' \
|
||||
--label 'traefik.http.routers.https-0-fleetapi-staging.tls.certresolver=letsencrypt' \
|
||||
--label "traefik.http.services.fleetapi-staging.loadbalancer.server.port=${PORT}" \
|
||||
"$IMG" sh -c "uvicorn dashboard_api_rev:app --host 0.0.0.0 --port ${PORT} --workers 2"
|
||||
|
||||
docker network connect coolify "$NAME" 2>/dev/null || true
|
||||
sleep 5
|
||||
echo "== container =="; docker ps --filter name="$NAME" --format "{{.Names}} | {{.Status}}"
|
||||
echo "== CORS origins in effect =="; docker exec "$NAME" printenv DASHBOARD_CORS_ORIGINS
|
||||
echo "== refresher (expect 0 = disabled) =="; docker exec "$NAME" printenv VTRIPS_REFRESH_INTERVAL_S
|
||||
echo "== DB role (expect grafana_ro) =="; docker exec "$NAME" sh -lc 'printenv DATABASE_URL | sed -E "s#://([^:]+):[^@]+@#://\1:<pw>@#"'
|
||||
echo "== internal health =="; docker exec "$NAME" sh -lc "curl -s http://localhost:${PORT}/health" 2>&1 | head
|
||||
Loading…
Reference in a new issue