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:
parent
05fc9d142a
commit
f6bb47bb8b
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "$@"
|
||||
Loading…
Reference in New Issue