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
|
# Create data directories
|
||||||
RUN mkdir -p /data/communities /data/books /data/swag-artifacts /data/files /data/splats
|
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
|
# Set environment
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV STORAGE_DIR=/data/communities
|
ENV STORAGE_DIR=/data/communities
|
||||||
|
|
@ -66,4 +70,5 @@ VOLUME /data/splats
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
CMD ["bun", "run", "server/index.ts"]
|
CMD ["bun", "run", "server/index.ts"]
|
||||||
|
|
|
||||||
|
|
@ -18,33 +18,24 @@ services:
|
||||||
- BOOKS_DIR=/data/books
|
- BOOKS_DIR=/data/books
|
||||||
- SWAG_ARTIFACTS_DIR=/data/swag-artifacts
|
- SWAG_ARTIFACTS_DIR=/data/swag-artifacts
|
||||||
- PORT=3000
|
- 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
|
- DATABASE_URL=postgres://rspace:${POSTGRES_PASSWORD}@rspace-db:5432/rspace
|
||||||
- FLOW_SERVICE_URL=http://payment-flow:3010
|
- FLOW_SERVICE_URL=http://payment-flow:3010
|
||||||
- FLOW_ID=a79144ec-e6a2-4e30-a42a-6d8237a5953d
|
- FLOW_ID=a79144ec-e6a2-4e30-a42a-6d8237a5953d
|
||||||
- FUNNEL_ID=0ff6a9ac-1667-4fc7-9a01-b1620810509f
|
- 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_URL=https://analytics.rspace.online
|
||||||
- UMAMI_WEBSITE_ID=292f6ac6-79f8-497b-ba6a-7a51e3b87b9f
|
- UMAMI_WEBSITE_ID=292f6ac6-79f8-497b-ba6a-7a51e3b87b9f
|
||||||
- MAPS_SYNC_URL=wss://sync.rmaps.online
|
- MAPS_SYNC_URL=wss://sync.rmaps.online
|
||||||
- IMAP_HOST=mail.rmail.online
|
- IMAP_HOST=mail.rmail.online
|
||||||
- IMAP_PORT=993
|
- IMAP_PORT=993
|
||||||
- IMAP_TLS_REJECT_UNAUTHORIZED=false
|
- 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_URL=https://rnetwork.online
|
||||||
- TWENTY_API_TOKEN=${TWENTY_API_TOKEN}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
rspace-db:
|
rspace-db:
|
||||||
condition: service_healthy
|
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