feat: migrate secrets to Infisical and add wiki subdomain DNS
- Replace hardcoded secrets in docker-compose.yml with Infisical injection - Add entrypoint.sh for runtime secret fetching from Infisical - Update Dockerfile with Infisical entrypoint wrapper - Externalize POSTGRES_PASSWORD to .env - Configure CNAME: wiki.valleyofthecommons.com → wikivotc2026.netlify.app Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
589a48324d
commit
f1a4da7787
|
|
@ -14,8 +14,13 @@ RUN npm install express
|
|||
# Copy application files
|
||||
COPY . .
|
||||
|
||||
# Add Infisical entrypoint
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Start server
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["node", "server.js"]
|
||||
|
|
|
|||
|
|
@ -4,21 +4,9 @@ services:
|
|||
container_name: votc
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://votc:votc_password@votc-db:5432/votc
|
||||
- SMTP_HOST=mail.rmail.online
|
||||
- SMTP_PORT=587
|
||||
- SMTP_USER=noreply@jeffemmett.com
|
||||
- SMTP_PASS=${SMTP_PASS}
|
||||
- EMAIL_FROM=Valley of the Commons <noreply@jeffemmett.com>
|
||||
- ADMIN_API_KEY=${ADMIN_API_KEY}
|
||||
- ADMIN_EMAILS=${ADMIN_EMAILS:-jeff@jeffemmett.com}
|
||||
- NODE_ENV=production
|
||||
- GOOGLE_SHEET_ID=1uZy21IjIwAES92ki6K33CtiTfEzGoUlxQ8jk3IzA0qI
|
||||
- GOOGLE_SERVICE_ACCOUNT_FILE=/run/secrets/google-service-account.json
|
||||
- MOLLIE_API_KEY=${MOLLIE_API_KEY}
|
||||
- BASE_URL=https://valleyofthecommons.com
|
||||
volumes:
|
||||
- ./google-service-account.json:/run/secrets/google-service-account.json:ro
|
||||
- INFISICAL_CLIENT_ID=${INFISICAL_CLIENT_ID}
|
||||
- INFISICAL_CLIENT_SECRET=${INFISICAL_CLIENT_SECRET}
|
||||
- INFISICAL_PROJECT_SLUG=valley-commons
|
||||
depends_on:
|
||||
votc-db:
|
||||
condition: service_healthy
|
||||
|
|
@ -35,7 +23,7 @@ services:
|
|||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=votc
|
||||
- POSTGRES_PASSWORD=votc_password
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=votc
|
||||
volumes:
|
||||
- votc-postgres-data:/var/lib/postgresql/data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
#!/bin/sh
|
||||
# Infisical secret injection entrypoint (Node.js)
|
||||
# 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, INFISICAL_ENV (default: prod),
|
||||
# INFISICAL_URL (default: http://infisical:8080)
|
||||
|
||||
set -e
|
||||
|
||||
export INFISICAL_URL="${INFISICAL_URL:-http://infisical:8080}"
|
||||
export INFISICAL_ENV="${INFISICAL_ENV:-prod}"
|
||||
# IMPORTANT: Set INFISICAL_PROJECT_SLUG in your docker-compose.yml
|
||||
export INFISICAL_PROJECT_SLUG="${INFISICAL_PROJECT_SLUG:?INFISICAL_PROJECT_SLUG must be set}"
|
||||
|
||||
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 Node.js (already in the image) for reliable JSON parsing and HTTP calls
|
||||
EXPORTS=$(node -e "
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const url = new URL(process.env.INFISICAL_URL);
|
||||
const client = url.protocol === 'https:' ? https : http;
|
||||
|
||||
const post = (path, body) => new Promise((resolve, reject) => {
|
||||
const data = JSON.stringify(body);
|
||||
const req = client.request({ hostname: url.hostname, port: url.port, path, method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Content-Length': data.length }
|
||||
}, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(JSON.parse(d))); });
|
||||
req.on('error', reject);
|
||||
req.end(data);
|
||||
});
|
||||
|
||||
const get = (path, token) => new Promise((resolve, reject) => {
|
||||
const req = client.request({ hostname: url.hostname, port: url.port, path, method: 'GET',
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
}, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(JSON.parse(d))); });
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const auth = await post('/api/v1/auth/universal-auth/login', {
|
||||
clientId: process.env.INFISICAL_CLIENT_ID,
|
||||
clientSecret: process.env.INFISICAL_CLIENT_SECRET
|
||||
});
|
||||
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 get('/api/v3/secrets/raw?workspaceSlug=' + slug + '&environment=' + env + '&secretPath=/&recursive=true', auth.accessToken);
|
||||
|
||||
if (!secrets.secrets) { console.error('[infisical] No secrets returned'); process.exit(1); }
|
||||
|
||||
// Output as shell-safe export statements
|
||||
for (const s of secrets.secrets) {
|
||||
// Single-quote the value to prevent shell expansion, escape existing single quotes
|
||||
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