doxa-maps/deployment/cloud/entry.sh
Doxa Maps 8ef7d337d2
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Cloud container: a rebuilt Rudder engine replaces the running one once nobody is dictating
The pull loop rebuilt the engine when its source changed, but the old process kept running until the
next container start. Now, when the engine reports it is not in use, the old process is stopped by its
own PID and the dev server starts the new binary.
2026-09-27 22:45:22 +00:00

130 lines
6.6 KiB
Bash
Executable file

#!/bin/bash
# dx-maps cloud dev server, run by the image's /boot.sh from the cloned repo (see Dockerfile).
# 1. install, 2. pull BRANCH every PULL_INTERVAL s (Vite hot-reloads what changed),
# 3. serve: Vite alone, or behind an OIDC sign-in when OIDC_ISSUER is set.
set -u
cd /app/repo
BRANCH="${BRANCH:-main}"; PORT="${PORT:-5173}"
echo "$(date -u +%FT%TZ) START at $(git rev-parse --short HEAD)" | tee -a /tmp/pull.log
bun install --frozen-lockfile || bun install
(
while true; do
sleep "${PULL_INTERVAL:-15}"
OLD=$(git rev-parse HEAD)
if git fetch -q --depth 1 origin "$BRANCH"; then
NEW=$(git rev-parse FETCH_HEAD)
if [ "$OLD" != "$NEW" ]; then
CHANGED=$(git diff --name-only "$OLD" "$NEW" 2>/dev/null)
git reset -q --hard FETCH_HEAD
echo "$(date -u +%FT%TZ) PULLED ${OLD:0:8} -> ${NEW:0:8}" >> /tmp/pull.log
# the engine binary is untracked, so rebuild it when its source changed
if [ "${RUDDER_ENGINE:-0}" = 1 ] && echo "$CHANGED" | grep -q '^backend/rudder-engine/'; then
(cd backend/rudder-engine && go build -o ../bin/rudder-engine .) >> /tmp/pull.log 2>&1 \
&& echo "$(date -u +%FT%TZ) rudder-engine rebuilt" >> /tmp/pull.log && ENGINE_STALE=1
fi
fi
fi
# Swap in a rebuilt engine once nobody is dictating: stop the old one by its own PID (never a process
# group: inside a container that can be PID 1) and the dev server restarts it on the new binary.
if [ "${ENGINE_STALE:-0}" = 1 ] && curl -s --max-time 3 http://127.0.0.1:5193/api/rudder/status | grep -q '"inUse":false'; then
for p in /proc/[0-9]*; do
case "$(readlink "$p/exe" 2>/dev/null)" in
"/app/repo/backend/bin/rudder-engine (deleted)") kill -TERM "${p#/proc/}" \
&& echo "$(date -u +%FT%TZ) rudder-engine ${p#/proc/} stopped for the new build" >> /tmp/pull.log ;;
esac
done
ENGINE_STALE=0
fi
done
) &
CONFIG=/app/repo/deployment/cloud/vite.cloud.config.mjs
# Rudder (optional, RUDDER_ENGINE=1): its voice-to-text engine runs here, fed by the BROWSER's
# microphone (no server mic). The image carries the runtime and the pinned model (/opt/rudder); a copy on
# the persistent volume (/data/rudder) wins when it exists. Setup (checksums + building the engine) runs
# in the BACKGROUND so the site serves at once: until it finishes, /api/rudder answers "starting", then
# the cloud Vite config restarts the dev server once, which starts the engine. /api/rudder stays behind
# the sign-in (it is not a public route). About 1 GB resident.
if [ "${RUDDER_ENGINE:-0}" = 1 ]; then
if [ -z "${RUDDER_HOME:-}" ]; then
if ls /data/rudder/models/*.gguf >/dev/null 2>&1; then RUDDER_HOME=/data/rudder; else RUDDER_HOME=/opt/rudder; fi
fi
export RUDDER_HOME RUDDER_AUDIO=browser
# The engine refuses a non-loopback Host header, and Vite's proxy passes the public host through:
# allow the public host, defaulting to the one in OIDC_REDIRECT_URL.
if [ -z "${RUDDER_ALLOW_HOST:-}" ] && [ -n "${OIDC_REDIRECT_URL:-}" ]; then
RUDDER_ALLOW_HOST=$(printf '%s' "$OIDC_REDIRECT_URL" | sed -E 's#^[a-z]+://([^/:]+).*#\1#')
fi
export RUDDER_ALLOW_HOST="${RUDDER_ALLOW_HOST:-}"
# The state file is read by vite.cloud.config.mjs: running -> done | failed.
echo running > /tmp/rudder-setup.state
# disown: the `wait -n` at the end must not treat this job finishing as the container's exit.
( if bash /app/repo/backend/rudder-engine/setup-runtime.sh >&2; then echo done; else
echo "[rudder] setup failed: the widget will report the engine offline" >&2; echo failed; fi \
> /tmp/rudder-setup.state.new && mv /tmp/rudder-setup.state.new /tmp/rudder-setup.state ) &
disown
fi
if [ -z "${OIDC_ISSUER:-}" ]; then
# Open, like a laptop: no sign-in.
export VITE_HOST=0.0.0.0 VITE_PORT="$PORT"
exec bun x vite --config "$CONFIG"
fi
# OIDC gate: oauth2-proxy owns the public port; Vite listens on localhost behind it, so the Plugin
# Studio, every map and the hot-reload websocket are all behind the same sign-in.
: "${OIDC_CLIENT_ID:?}" "${OIDC_CLIENT_SECRET:?}" "${OIDC_REDIRECT_URL:?}"
if [ -z "${OIDC_ALLOWED_GROUPS:-}" ]; then
echo "refusing to start: set OIDC_ALLOWED_GROUPS (comma-separated). With open sign-up, anyone could log in." >&2
exit 1
fi
ALLOW=(); IFS=',' read -ra G <<< "$OIDC_ALLOWED_GROUPS"
for g in "${G[@]}"; do g="${g// /}"; [ -n "$g" ] && ALLOW+=(--allowed-group="$g"); done
# Secrets reach oauth2-proxy through its environment, never its command line.
export OAUTH2_PROXY_CLIENT_SECRET="$OIDC_CLIENT_SECRET"
export OAUTH2_PROXY_COOKIE_SECRET="${OIDC_COOKIE_SECRET:-$(head -c 32 /dev/urandom | base64 | tr -d '=+/' | head -c 32)}"
# Backend: PocketBase DEV (/pb/, signed-in) and PROD (/api-prod/, public API only). Schema = the
# migrations in the repo; data = the /data volume. The superuser comes from env, never printed.
MIG=/app/repo/backend/pocketbase/pb_migrations; mkdir -p "$MIG" /data/pb-dev /data/pb-prod /data/cdn
for inst in dev prod; do
if [ -n "${PB_SUPERUSER_EMAIL:-}" ] && [ -n "${PB_SUPERUSER_PASSWORD:-}" ]; then
pocketbase superuser upsert "$PB_SUPERUSER_EMAIL" "$PB_SUPERUSER_PASSWORD" --dir "/data/pb-$inst" >/dev/null 2>&1 \
&& echo "[pb-$inst] superuser set" || echo "[pb-$inst] superuser upsert failed"
fi
done
# Server-side JS hooks OFF: point --hooksDir at a path that never exists, so a pb_hooks/ folder
# added to the repo can't run code in either instance (v1 has no per-plugin isolation for hooks).
NOHOOKS=/var/empty/pb_hooks_disabled
pocketbase serve --http 127.0.0.1:8090 --dir /data/pb-dev --migrationsDir "$MIG" --hooksDir "$NOHOOKS" &
pocketbase serve --http 127.0.0.1:8091 --dir /data/pb-prod --migrationsDir "$MIG" --hooksDir "$NOHOOKS" &
bun /app/repo/deployment/cloud/admin.mjs &
VITE_HOST=127.0.0.1 VITE_PORT=5174 bun x vite --config "$CONFIG" &
oauth2-proxy \
--provider=oidc \
--oidc-issuer-url="$OIDC_ISSUER" \
--client-id="$OIDC_CLIENT_ID" \
--redirect-url="$OIDC_REDIRECT_URL" \
--code-challenge-method=S256 \
--scope="${OIDC_SCOPE:-openid profile email groups}" \
--oidc-groups-claim="${OIDC_GROUPS_CLAIM:-groups}" \
"${ALLOW[@]}" \
--email-domain='*' \
--provider-display-name="${OIDC_DISPLAY_NAME:-your identity provider}" \
--upstream=http://127.0.0.1:5174/ \
--upstream=http://127.0.0.1:5175/cdn/ \
--upstream=http://127.0.0.1:5175/api-prod/ \
--upstream=http://127.0.0.1:5175/pb/ \
--upstream=http://127.0.0.1:5175/admin/ \
--skip-auth-route='^/cdn/' \
--skip-auth-route='^/api-prod/api/' \
--http-address="0.0.0.0:$PORT" \
--reverse-proxy=true \
--cookie-secure=true \
--cookie-samesite=lax &
wait -n
exit $?