fix: enable Google Sheets with file-based credentials and fix SMTP TLS

Switch from GOOGLE_CREDENTIALS env var (JSON string) to file-based
service account loading via GOOGLE_SERVICE_ACCOUNT_FILE, with fallback
to the env var for backwards compatibility. Mount the service account
JSON as a read-only volume. Add tls.rejectUnauthorized=false for
Mailcow self-signed cert.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-01 10:49:00 -08:00
parent a5ff3bed07
commit a4fadbb1c7
2 changed files with 23 additions and 3 deletions

View File

@ -14,8 +14,8 @@ services:
- SMTP_PORT=${SMTP_PORT:-587}
- SMTP_USER=${SMTP_USER:-noreply@jeffemmett.com}
- SMTP_PASS=${SMTP_PASS}
- GOOGLE_SHEET_ID=${GOOGLE_SHEET_ID}
- GOOGLE_CREDENTIALS=${GOOGLE_CREDENTIALS}
- GOOGLE_SHEET_ID=13_J3VFnDeuge57emi9Ob-Icm_J6RhCwRkLc9cPHk8bE
- GOOGLE_SERVICE_ACCOUNT_FILE=/app/secrets/google-service-account.json
# Listmonk newsletter integration (direct DB access)
- LISTMONK_DB_HOST=listmonk-db
- LISTMONK_DB_PORT=5432
@ -25,6 +25,7 @@ services:
- LISTMONK_LIST_ID=20
volumes:
- worldplay-data:/app/data
- ./google-service-account.json:/app/secrets/google-service-account.json:ro
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-public"

View File

@ -19,6 +19,7 @@ const smtp = process.env.SMTP_PASS ? nodemailer.createTransport({
user: process.env.SMTP_USER || 'noreply@jeffemmett.com',
pass: process.env.SMTP_PASS,
},
tls: { rejectUnauthorized: false },
}) : null;
// Listmonk PostgreSQL configuration for newsletter management
@ -33,7 +34,25 @@ const listmonkPool = process.env.LISTMONK_DB_HOST ? new Pool({
// Google Sheets configuration
const GOOGLE_SHEET_ID = process.env.GOOGLE_SHEET_ID;
const GOOGLE_CREDENTIALS = process.env.GOOGLE_CREDENTIALS ? JSON.parse(process.env.GOOGLE_CREDENTIALS) : null;
function loadGoogleCredentials() {
// Prefer file-based credentials (cleaner for Docker)
const filePath = process.env.GOOGLE_SERVICE_ACCOUNT_FILE;
if (filePath) {
try {
return JSON.parse(require('fs').readFileSync(filePath, 'utf8'));
} catch (err) {
console.error('Failed to read Google credentials file:', err.message);
return null;
}
}
// Fall back to JSON string in env var
if (process.env.GOOGLE_CREDENTIALS) {
try { return JSON.parse(process.env.GOOGLE_CREDENTIALS); }
catch { console.error('Failed to parse GOOGLE_CREDENTIALS env var'); return null; }
}
return null;
}
const GOOGLE_CREDENTIALS = loadGoogleCredentials();
// Initialize Google Sheets API
let sheets = null;