feat: wire rspace to pull secrets from Infisical at startup

Add entrypoint.sh that authenticates with Infisical via universal-auth
and injects secrets as env vars before starting the Bun server.
Uses Bun's built-in fetch API instead of Node.js http module.

Secrets removed from docker-compose.yml (now fetched at runtime):
INTERNAL_API_KEY, HETZNER_API_TOKEN, CLOUDFLARE_API_TOKEN,
CLOUDFLARE_ZONE_ID, TWENTY_API_TOKEN, R2_*, X402_*, SMTP_PASS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-23 19:36:27 -08:00
parent 05fc9d142a
commit f6bb47bb8b
3 changed files with 77 additions and 16 deletions

View File

@ -48,6 +48,10 @@ RUN bun install --production
# Create data directories
RUN mkdir -p /data/communities /data/books /data/swag-artifacts /data/files /data/splats
# Copy entrypoint for Infisical secret injection
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Set environment
ENV NODE_ENV=production
ENV STORAGE_DIR=/data/communities
@ -66,4 +70,5 @@ VOLUME /data/splats
EXPOSE 3000
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["bun", "run", "server/index.ts"]

View File

@ -18,33 +18,24 @@ services:
- BOOKS_DIR=/data/books
- SWAG_ARTIFACTS_DIR=/data/swag-artifacts
- PORT=3000
- INTERNAL_API_KEY=${INTERNAL_API_KEY}
- FILES_DIR=/data/files
- SPLATS_DIR=/data/splats
- INFISICAL_CLIENT_ID=${INFISICAL_CLIENT_ID}
- INFISICAL_CLIENT_SECRET=${INFISICAL_CLIENT_SECRET}
- INFISICAL_PROJECT_SLUG=rspace
- INFISICAL_ENV=prod
- INFISICAL_URL=http://infisical:8080
- DATABASE_URL=postgres://rspace:${POSTGRES_PASSWORD}@rspace-db:5432/rspace
- FLOW_SERVICE_URL=http://payment-flow:3010
- FLOW_ID=a79144ec-e6a2-4e30-a42a-6d8237a5953d
- FUNNEL_ID=0ff6a9ac-1667-4fc7-9a01-b1620810509f
- FILES_DIR=/data/files
- SPLATS_DIR=/data/splats
- X402_PAY_TO=${X402_PAY_TO:-}
- X402_NETWORK=${X402_NETWORK:-eip155:84532}
- X402_UPLOAD_PRICE=${X402_UPLOAD_PRICE:-0.01}
- X402_FACILITATOR_URL=${X402_FACILITATOR_URL:-https://x402.org/facilitator}
- R2_ENDPOINT=${R2_ENDPOINT}
- R2_BUCKET=${R2_BUCKET:-rtube-videos}
- R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
- R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
- R2_PUBLIC_URL=${R2_PUBLIC_URL}
- UMAMI_URL=https://analytics.rspace.online
- UMAMI_WEBSITE_ID=292f6ac6-79f8-497b-ba6a-7a51e3b87b9f
- MAPS_SYNC_URL=wss://sync.rmaps.online
- IMAP_HOST=mail.rmail.online
- IMAP_PORT=993
- IMAP_TLS_REJECT_UNAUTHORIZED=false
- HETZNER_API_TOKEN=${HETZNER_API_TOKEN}
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
- CLOUDFLARE_ZONE_ID=${CLOUDFLARE_ZONE_ID}
- TWENTY_API_URL=https://rnetwork.online
- TWENTY_API_TOKEN=${TWENTY_API_TOKEN}
depends_on:
rspace-db:
condition: service_healthy

65
entrypoint.sh Normal file
View File

@ -0,0 +1,65 @@
#!/bin/sh
# Infisical secret injection entrypoint
# Fetches secrets from Infisical API and injects them as env vars before starting the app.
# Required env vars: INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET
# Optional: INFISICAL_PROJECT_SLUG (default: rspace), INFISICAL_ENV (default: prod),
# INFISICAL_URL (default: http://infisical:8080)
set -e
INFISICAL_URL="${INFISICAL_URL:-http://infisical:8080}"
INFISICAL_ENV="${INFISICAL_ENV:-prod}"
INFISICAL_PROJECT_SLUG="${INFISICAL_PROJECT_SLUG:-rspace}"
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}..."
# Use Bun's built-in fetch API for HTTP calls and JSON parsing
EXPORTS=$(bun -e "
(async () => {
try {
const base = process.env.INFISICAL_URL;
const auth = await fetch(base + '/api/v1/auth/universal-auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: process.env.INFISICAL_CLIENT_ID,
clientSecret: process.env.INFISICAL_CLIENT_SECRET
})
}).then(r => r.json());
if (!auth.accessToken) { console.error('[infisical] Auth failed'); process.exit(1); }
const slug = process.env.INFISICAL_PROJECT_SLUG;
const env = process.env.INFISICAL_ENV;
const secrets = await fetch(base + '/api/v3/secrets/raw?workspaceSlug=' + slug + '&environment=' + env + '&secretPath=/&recursive=true', {
headers: { 'Authorization': 'Bearer ' + auth.accessToken }
}).then(r => r.json());
if (!secrets.secrets) { console.error('[infisical] No secrets returned'); process.exit(1); }
for (const s of secrets.secrets) {
const escaped = s.secretValue.replace(/'/g, \"'\\\\''\" );
console.log('export ' + s.secretKey + \"='\" + escaped + \"'\");
}
} catch (e) { console.error('[infisical] Error:', e.message); process.exit(1); }
})();
" 2>&1) || {
echo "[infisical] WARNING: Failed to fetch secrets, starting with existing env vars"
exec "$@"
}
# Check if we got export statements or error messages
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 "$@"