pediatric-ai-scribe-v3/docker-entrypoint.sh
Daniel b7a2e15107 fix(entrypoint): docker-compose env overrides win over OpenBao-fetched values
The previous entrypoint unconditionally exported every key from
kv/ped-ai/prod. This broke the e2e container, which needs
TURNSTILE_SECRET_KEY="" and SMTP_HOST="" set via docker-compose
environment block so login works without bot challenge and register
auto-verifies. OpenBao's real values were overriding those empties,
re-enabling Turnstile and email on e2e.

Fix: before the OpenBao fetch, snapshot every env var name already
defined (env_file + environment: block). During the export loop,
skip any OpenBao key that's already in the snapshot. Docker-compose
wins, OpenBao fills in the rest.

Impact:
- Prod container: no change (env_file only has OPENBAO_* bootstrap
  vars, which aren't in the KV payload anyway)
- E2e container: TURNSTILE_SECRET_KEY="" and SMTP_HOST="" preserved
  even when the image is rebuilt from the current source tree
- Any future per-container override via docker-compose environment:
  block just works

Log line now reports counts: "applied N secrets; M already set by
docker (kept override)".
2026-04-22 21:04:46 +02:00

79 lines
3.3 KiB
Bash
Executable file

#!/bin/sh
# Container entrypoint. Optionally fetches secrets from OpenBao before
# starting the app. Backwards compatible: if OPENBAO_ADDR is unset (e.g. e2e
# container, local dev with a populated .env), the vault step is skipped
# and the process starts with whatever's already in the environment.
#
# When OPENBAO_ADDR is set, OPENBAO_ROLE_ID + OPENBAO_SECRET_ID are required.
# The entrypoint logs in via AppRole, fetches kv/ped-ai/prod, exports each
# key as an env var, and then unsets the auth material before execing the
# real command so the Node process doesn't carry them.
set -eu
if [ -n "${OPENBAO_ADDR:-}" ]; then
if [ -z "${OPENBAO_ROLE_ID:-}" ] || [ -z "${OPENBAO_SECRET_ID:-}" ]; then
echo "[entrypoint] FATAL: OPENBAO_ADDR is set but OPENBAO_ROLE_ID or OPENBAO_SECRET_ID is missing." >&2
exit 1
fi
export BAO_ADDR="${OPENBAO_ADDR}"
echo "[entrypoint] authenticating to OpenBao at ${OPENBAO_ADDR} via AppRole..."
BAO_TOKEN="$(bao write -field=token auth/approle/login \
role_id="${OPENBAO_ROLE_ID}" \
secret_id="${OPENBAO_SECRET_ID}" 2>&1)"
if [ -z "${BAO_TOKEN}" ] || printf '%s' "${BAO_TOKEN}" | grep -qi error; then
echo "[entrypoint] FATAL: AppRole authentication failed:" >&2
echo "${BAO_TOKEN}" >&2
exit 1
fi
export BAO_TOKEN
SECRET_PATH="${OPENBAO_KV_PATH:-kv/ped-ai/prod}"
echo "[entrypoint] fetching secrets from ${SECRET_PATH}..."
SECRET_JSON="$(bao kv get -format=json "${SECRET_PATH}" 2>/dev/null | jq -c '.data.data' 2>/dev/null || true)"
if [ -z "${SECRET_JSON}" ] || [ "${SECRET_JSON}" = "null" ]; then
echo "[entrypoint] FATAL: no secrets returned from ${SECRET_PATH}." >&2
exit 1
fi
# Export each key/value as a shell-safe env var — but ONLY if the key
# isn't already set by docker (env_file / environment: block). This
# lets a docker-compose override win over the OpenBao value, which is
# needed for e2e (TURNSTILE_SECRET_KEY="" / SMTP_HOST="") and any
# environment-specific override.
#
# Pattern: write jq output to a temp file, then while-read in the main
# shell so exports persist (pipes into while run in a subshell and lose
# them). Pre-snapshot env keys and skip those already defined.
_PRESET_KEYS_FILE=$(mktemp)
env | cut -d= -f1 | sort -u > "$_PRESET_KEYS_FILE"
_SECRET_ASSIGNS=$(mktemp)
printf '%s' "${SECRET_JSON}" | jq -r 'to_entries[] | "\(.key)\t\(.value | @sh)"' > "$_SECRET_ASSIGNS"
_APPLIED_COUNT=0
_SKIPPED_COUNT=0
while IFS="$(printf '\t')" read -r _K _VAL_QUOTED; do
if [ -z "$_K" ]; then continue; fi
if grep -qxF "$_K" "$_PRESET_KEYS_FILE"; then
_SKIPPED_COUNT=$((_SKIPPED_COUNT + 1))
else
eval "export $_K=$_VAL_QUOTED"
_APPLIED_COUNT=$((_APPLIED_COUNT + 1))
fi
done < "$_SECRET_ASSIGNS"
rm -f "$_PRESET_KEYS_FILE" "$_SECRET_ASSIGNS"
echo "[entrypoint] applied ${_APPLIED_COUNT} secrets; ${_SKIPPED_COUNT} already set by docker (kept override)"
# Bootstrap credentials are no longer needed in the Node process env.
unset OPENBAO_ROLE_ID OPENBAO_SECRET_ID BAO_TOKEN
SECRET_COUNT="$(printf '%s' "${SECRET_JSON}" | jq -r 'keys | length')"
echo "[entrypoint] ✅ loaded ${SECRET_COUNT} secrets from OpenBao"
else
echo "[entrypoint] OPENBAO_ADDR not set — using existing environment (legacy .env path)"
fi
exec "$@"