Enables stage-2: the prod dashboard_api request pool connects as the READ-ONLY dashboard_ro role (DATABASE_URL) while the v_trips refresher keeps a privileged connection via REFRESH_DATABASE_URL (falls back to DATABASE_URL when unset, so single-role/staging deploys are unchanged). Avoids the FIX-D02 trap (a read-only role cannot REFRESH). Adds deploy_dashboard_api.sh (the prod bridge deploy, now version-controlled): strips inherited DATABASE_URL, sets REFRESH_DATABASE_URL=<app role> + DATABASE_URL=dashboard_ro, CORS incl. fleetops.rahamafresh.com. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
4.5 KiB
Bash
Executable file
70 lines
4.5 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# deploy_dashboard_api.sh — PROD dashboard_api bridge (fleetapi.rahamafresh.com).
|
|
# Standalone Traefik-labelled bridge (NOT Coolify-managed): reuses the
|
|
# webhook_receiver image + app network, bind-mounts the WIP API file. An env/CORS
|
|
# change needs a container RECREATE (this script does that).
|
|
#
|
|
# Stage-2 least privilege: the request pool connects as the READ-ONLY dashboard_ro
|
|
# role (DATABASE_URL), while the v_trips refresher keeps the privileged app role
|
|
# (REFRESH_DATABASE_URL) — REFRESH needs write perms that dashboard_ro lacks.
|
|
set -euo pipefail
|
|
|
|
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 /home/kianiadee/dashboard_api
|
|
# Stage a fresh copy only if one was scp'd to ~; otherwise keep the existing mount.
|
|
if [ -f /home/kianiadee/dashboard_api_rev.py ]; then
|
|
mv -f /home/kianiadee/dashboard_api_rev.py /home/kianiadee/dashboard_api/dashboard_api_rev.py
|
|
fi
|
|
test -f /home/kianiadee/dashboard_api/dashboard_api_rev.py \
|
|
|| { echo "ERROR: dashboard_api_rev.py missing in mount dir; scp it to ~ first"; exit 1; }
|
|
|
|
# Reuse the webhook container's env, stripping runtime noise AND any inherited
|
|
# DATABASE_URL + DASHBOARD_CORS_ORIGINS (both set explicitly below).
|
|
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=)' \
|
|
> /home/kianiadee/dashboard_api/dapi.env
|
|
echo 'DASHBOARD_CORS_ORIGINS=https://liveposition.rahamafresh.com,https://fleetintelligence.rahamafresh.com,https://fleetnow.rahamafresh.com,https://fleetops.rahamafresh.com' \
|
|
>> /home/kianiadee/dashboard_api/dapi.env
|
|
|
|
# Split roles: REFRESH_DATABASE_URL = the inherited privileged URL (for the
|
|
# refresher); DATABASE_URL = read-only dashboard_ro (for request handling).
|
|
SRC_DB_URL=$(docker inspect "$WH" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^DATABASE_URL=//p' | head -1)
|
|
RO_PW=$(cat /home/kianiadee/.dashboard_ro.pw 2>/dev/null || true)
|
|
[ -n "$SRC_DB_URL" ] || { echo "ERROR: DATABASE_URL not found in $WH env"; exit 1; }
|
|
[ -n "$RO_PW" ] || { echo "ERROR: ~/.dashboard_ro.pw missing — run bootstrap_dashboard_ro.sh first"; exit 1; }
|
|
HOSTPART="${SRC_DB_URL#*@}"
|
|
{
|
|
echo "REFRESH_DATABASE_URL=${SRC_DB_URL}"
|
|
echo "DATABASE_URL=postgresql://dashboard_ro:${RO_PW}@${HOSTPART}"
|
|
} >> /home/kianiadee/dashboard_api/dapi.env
|
|
chmod 600 /home/kianiadee/dashboard_api/dapi.env
|
|
|
|
docker rm -f dashboard_api 2>/dev/null || true
|
|
docker run -d --name dashboard_api --restart unless-stopped \
|
|
--network "$APPNET" \
|
|
--env-file /home/kianiadee/dashboard_api/dapi.env \
|
|
-v /home/kianiadee/dashboard_api/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.entryPoints=http' \
|
|
--label 'traefik.http.routers.http-0-fleetapi.middlewares=redirect-to-https' \
|
|
--label 'traefik.http.routers.http-0-fleetapi.rule=Host(`fleetapi.rahamafresh.com`)' \
|
|
--label 'traefik.http.routers.https-0-fleetapi.entryPoints=https' \
|
|
--label 'traefik.http.routers.https-0-fleetapi.rule=Host(`fleetapi.rahamafresh.com`)' \
|
|
--label 'traefik.http.routers.https-0-fleetapi.tls=true' \
|
|
--label 'traefik.http.routers.https-0-fleetapi.tls.certresolver=letsencrypt' \
|
|
--label 'traefik.http.services.fleetapi.loadbalancer.server.port=8890' \
|
|
"$IMG" sh -c 'uvicorn dashboard_api_rev:app --host 0.0.0.0 --port 8890 --workers 2'
|
|
|
|
docker network connect coolify dashboard_api 2>/dev/null || true
|
|
sleep 8
|
|
echo "== container =="; docker ps --filter name=dashboard_api --format "{{.Names}} | {{.Status}}"
|
|
echo "== CORS origins in effect =="; docker exec dashboard_api printenv DASHBOARD_CORS_ORIGINS
|
|
echo "== request role (expect dashboard_ro) =="; docker exec dashboard_api sh -lc 'printenv DATABASE_URL | sed -E "s#://([^:]+):[^@]+@#://\1:<pw>@#"'
|
|
echo "== refresh role set? =="; docker exec dashboard_api sh -lc 'printenv REFRESH_DATABASE_URL | sed -E "s#://([^:]+):[^@]+@#://\1:<pw>@#"'
|
|
echo "== internal health =="; docker exec dashboard_api sh -lc 'curl -s http://localhost:8890/health' 2>&1 | head || true
|