From a4fadbb1c705908f9a784a8eda7652d20a741010 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sun, 1 Mar 2026 10:49:00 -0800 Subject: [PATCH] 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 --- docker-compose.yml | 5 +++-- server.js | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 543c1b2..d018e65 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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" diff --git a/server.js b/server.js index 951f912..b7903c4 100644 --- a/server.js +++ b/server.js @@ -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;