#!/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 "$@"