feat: wire rswag backend to pull secrets from Infisical at startup

Add Python-based entrypoint.sh that authenticates with Infisical via
universal-auth, fetches secrets, and exports them as env vars before
starting uvicorn. Secrets like MOLLIE_API_KEY, PRODIGI_API_KEY,
PRINTFUL_API_TOKEN, JWT_SECRET, GEMINI_API_KEY, and FLOW_* vars are
now pulled from Infisical instead of being passed through docker-compose.

Gracefully degrades: if no INFISICAL_CLIENT_ID/SECRET are set or if
the fetch fails, the container starts with whatever env vars exist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-23 19:36:35 -08:00
parent d8a58f8eb4
commit b35df552f7
3 changed files with 71 additions and 11 deletions

View File

@ -36,6 +36,10 @@ COPY --chown=appuser:appgroup app/ ./app/
COPY --chown=appuser:appgroup alembic/ ./alembic/
COPY --chown=appuser:appgroup alembic.ini ./
# Copy Infisical entrypoint
COPY --chown=appuser:appgroup entrypoint.sh ./entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Create directories for mounted volumes
RUN mkdir -p /app/designs /app/config && \
chown -R appuser:appgroup /app
@ -44,4 +48,5 @@ USER appuser
EXPOSE 8000
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

61
backend/entrypoint.sh Executable file
View File

@ -0,0 +1,61 @@
#!/bin/sh
# Infisical secret injection entrypoint (Python version)
set -e
INFISICAL_URL="${INFISICAL_URL:-http://infisical:8080}"
INFISICAL_ENV="${INFISICAL_ENV:-prod}"
INFISICAL_PROJECT_SLUG="${INFISICAL_PROJECT_SLUG:-rswag}"
if [ -z "$INFISICAL_CLIENT_ID" ] || [ -z "$INFISICAL_CLIENT_SECRET" ]; then
echo "[infisical] No credentials set, starting without secret injection"
exec "$@"
fi
echo "[infisical] Fetching secrets from ${INFISICAL_PROJECT_SLUG}/${INFISICAL_ENV}..."
EXPORTS=$(python3 -c "
import urllib.request, json, os, sys
base = os.environ['INFISICAL_URL']
slug = os.environ['INFISICAL_PROJECT_SLUG']
env = os.environ['INFISICAL_ENV']
try:
data = json.dumps({'clientId': os.environ['INFISICAL_CLIENT_ID'], 'clientSecret': os.environ['INFISICAL_CLIENT_SECRET']}).encode()
req = urllib.request.Request(f'{base}/api/v1/auth/universal-auth/login', data=data, headers={'Content-Type': 'application/json'})
auth = json.loads(urllib.request.urlopen(req).read())
token = auth.get('accessToken')
if not token:
print('[infisical] Auth failed', file=sys.stderr)
sys.exit(1)
req = urllib.request.Request(f'{base}/api/v3/secrets/raw?workspaceSlug={slug}&environment={env}&secretPath=/&recursive=true')
req.add_header('Authorization', f'Bearer {token}')
secrets = json.loads(urllib.request.urlopen(req).read())
if 'secrets' not in secrets:
print('[infisical] No secrets returned', file=sys.stderr)
sys.exit(1)
for s in secrets['secrets']:
key = s['secretKey']
val = s['secretValue'].replace(\"'\", \"'\\\\'\")
print(f\"export {key}='{val}'\")
except Exception as e:
print(f'[infisical] Error: {e}', file=sys.stderr)
sys.exit(1)
" 2>&1) || {
echo "[infisical] WARNING: Failed to fetch secrets, starting with existing env vars"
exec "$@"
}
if echo "$EXPORTS" | grep -q "^export "; then
COUNT=$(echo "$EXPORTS" | grep -c "^export ")
eval "$EXPORTS"
echo "[infisical] Injected ${COUNT} secrets"
else
echo "[infisical] WARNING: $EXPORTS"
echo "[infisical] Starting with existing env vars"
fi
exec "$@"

View File

@ -36,22 +36,16 @@ services:
container_name: rswag-backend
restart: unless-stopped
environment:
- INFISICAL_CLIENT_ID=${INFISICAL_CLIENT_ID}
- INFISICAL_CLIENT_SECRET=${INFISICAL_CLIENT_SECRET}
- INFISICAL_PROJECT_SLUG=rswag
- INFISICAL_ENV=prod
- INFISICAL_URL=http://infisical:8080
- DATABASE_URL=postgresql://rswag:${DB_PASSWORD:-devpassword}@db:5432/rswag
- REDIS_URL=redis://redis:6379
- MOLLIE_API_KEY=${MOLLIE_API_KEY}
- PRODIGI_API_KEY=${PRODIGI_API_KEY}
- PRINTFUL_API_TOKEN=${PRINTFUL_API_TOKEN}
- POD_SANDBOX_MODE=${POD_SANDBOX_MODE:-true}
- JWT_SECRET=${JWT_SECRET:-dev-secret-change-me}
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000}
- DESIGNS_PATH=/app/designs
- CONFIG_PATH=/app/config
- SPACES_PATH=/app/spaces
- GEMINI_API_KEY=${GEMINI_API_KEY}
- FLOW_SERVICE_URL=${FLOW_SERVICE_URL:-}
- FLOW_ID=${FLOW_ID:-}
- FLOW_FUNNEL_ID=${FLOW_FUNNEL_ID:-}
- FLOW_REVENUE_SPLIT=${FLOW_REVENUE_SPLIT:-0.5}
volumes:
- ./designs:/app/designs
- ./config:/app/config:ro