fix(deploy): pass SES config through to the app stack
The stack env is assembled from Gitea repo secrets by the deploy workflows' `env_data` block — there is no .env file on the host for the app stack. SES was in neither, so `MailService` came up unconfigured on every deployment and, with NODE_ENV=production killing the stdout dev fallback, every notification and renewal aviso failed. Wire SES_REGION / SES_FROM / SES_FROM_NAME / SES_ACCESS_KEY / SES_SECRET_KEY / SES_CONFIGURATION_SET / NOTIFICATION_ADMIN_EMAILS through both galactus and cubex. No `_GALACTUS` suffix: one SES identity serves every deployment. Kept out of the required-secrets preflight — mail is not needed to boot, and failing a deploy over it would be wrong. Preflight warns instead, since the failure is otherwise invisible until someone clicks "Ejecutar". Also corrects the comments added in the previous commit, which claimed these belonged in a host env file rather than in CI secrets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,18 @@
|
||||
# SESSION_SECRET_GALACTUS 64-hex (openssl rand -hex 32)
|
||||
# MINIO_ROOT_USER / MINIO_ROOT_PASSWORD
|
||||
# MYSQL_PASSWORD / MYSQL_ROOT_PASSWORD
|
||||
# Optional — outbound mail. Not needed to deploy; needed for
|
||||
# /notificaciones to send anything at all (the image sets
|
||||
# NODE_ENV=production, which disables MailService's stdout fallback, so
|
||||
# a blank config fails every send loudly):
|
||||
# SES_REGION e.g. us-west-2
|
||||
# SES_FROM a VERIFIED SES sending identity
|
||||
# SES_FROM_NAME display name, optional
|
||||
# SES_ACCESS_KEY / SES_SECRET_KEY
|
||||
# SES_CONFIGURATION_SET optional, for bounce/complaint events
|
||||
# NOTIFICATION_ADMIN_EMAILS comma-separated summary recipients
|
||||
# These are NOT galactus-specific (no _GALACTUS suffix) — one SES identity
|
||||
# serves every deployment.
|
||||
# - The runner (which lives on cubex) must be able to reach BOTH
|
||||
# galactus:9443 (Portainer) and galactus:3306 (MySQL, for migrate deploy).
|
||||
# If it cannot reach 3306, run the migration by hand from a host that can
|
||||
@@ -116,6 +128,13 @@ jobs:
|
||||
MINIO_ROOT_PASSWORD: ${{ secrets.MINIO_ROOT_PASSWORD }}
|
||||
MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
|
||||
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
|
||||
# Not required — the app boots fine without mail. Warned about below,
|
||||
# because the failure mode is remote: everything looks healthy until
|
||||
# someone clicks "Ejecutar" and every send fails.
|
||||
SES_REGION: ${{ secrets.SES_REGION }}
|
||||
SES_FROM: ${{ secrets.SES_FROM }}
|
||||
SES_ACCESS_KEY: ${{ secrets.SES_ACCESS_KEY }}
|
||||
SES_SECRET_KEY: ${{ secrets.SES_SECRET_KEY }}
|
||||
SCOPE: ${{ github.event.inputs.scope }}
|
||||
run: |
|
||||
REQUIRED="PORTAINER_URL_GALACTUS PORTAINER_API_KEY_GALACTUS
|
||||
@@ -140,6 +159,20 @@ jobs:
|
||||
fi
|
||||
echo "all required secrets present for scope=$SCOPE"
|
||||
|
||||
# Mail is optional to deploy but not optional to work. Say so loudly
|
||||
# rather than letting /notificaciones fail one send at a time.
|
||||
mail_missing=""
|
||||
for name in SES_REGION SES_FROM SES_ACCESS_KEY SES_SECRET_KEY; do
|
||||
eval "value=\${$name}"
|
||||
[ -z "$value" ] && mail_missing="$mail_missing $name"
|
||||
done
|
||||
if [ -n "$mail_missing" ]; then
|
||||
echo "::warning::outbound mail is NOT configured, missing:$mail_missing"
|
||||
echo "::warning::the deploy will succeed, but every notification and"
|
||||
echo "::warning::renewal aviso will fail with 'El envío de correo no"
|
||||
echo "::warning::está configurado.' See docs/MASS_EMAIL_NOTIFICATIONS.md"
|
||||
fi
|
||||
|
||||
# --- full only: database ---------------------------------------------
|
||||
- name: Deploy database stack
|
||||
if: ${{ github.event.inputs.scope == 'full' }}
|
||||
@@ -260,7 +293,14 @@ jobs:
|
||||
"OPS_DB_ADMIN_USER": "root",
|
||||
"OPS_DB_ADMIN_PASSWORD": "${{ secrets.MYSQL_ROOT_PASSWORD }}",
|
||||
"MINIO_ROOT_USER": "${{ secrets.MINIO_ROOT_USER }}",
|
||||
"MINIO_ROOT_PASSWORD": "${{ secrets.MINIO_ROOT_PASSWORD }}"
|
||||
"MINIO_ROOT_PASSWORD": "${{ secrets.MINIO_ROOT_PASSWORD }}",
|
||||
"SES_REGION": "${{ secrets.SES_REGION }}",
|
||||
"SES_FROM": "${{ secrets.SES_FROM }}",
|
||||
"SES_FROM_NAME": "${{ secrets.SES_FROM_NAME }}",
|
||||
"SES_ACCESS_KEY": "${{ secrets.SES_ACCESS_KEY }}",
|
||||
"SES_SECRET_KEY": "${{ secrets.SES_SECRET_KEY }}",
|
||||
"SES_CONFIGURATION_SET": "${{ secrets.SES_CONFIGURATION_SET }}",
|
||||
"NOTIFICATION_ADMIN_EMAILS": "${{ secrets.NOTIFICATION_ADMIN_EMAILS }}"
|
||||
}
|
||||
|
||||
# --- prove it ----------------------------------------------------------
|
||||
|
||||
@@ -267,7 +267,14 @@ jobs:
|
||||
"OPS_DB_ADMIN_USER": "root",
|
||||
"OPS_DB_ADMIN_PASSWORD": "${{ secrets.MYSQL_ROOT_PASSWORD }}",
|
||||
"MINIO_ROOT_USER": "${{ secrets.MINIO_ROOT_USER }}",
|
||||
"MINIO_ROOT_PASSWORD": "${{ secrets.MINIO_ROOT_PASSWORD }}"
|
||||
"MINIO_ROOT_PASSWORD": "${{ secrets.MINIO_ROOT_PASSWORD }}",
|
||||
"SES_REGION": "${{ secrets.SES_REGION }}",
|
||||
"SES_FROM": "${{ secrets.SES_FROM }}",
|
||||
"SES_FROM_NAME": "${{ secrets.SES_FROM_NAME }}",
|
||||
"SES_ACCESS_KEY": "${{ secrets.SES_ACCESS_KEY }}",
|
||||
"SES_SECRET_KEY": "${{ secrets.SES_SECRET_KEY }}",
|
||||
"SES_CONFIGURATION_SET": "${{ secrets.SES_CONFIGURATION_SET }}",
|
||||
"NOTIFICATION_ADMIN_EMAILS": "${{ secrets.NOTIFICATION_ADMIN_EMAILS }}"
|
||||
}
|
||||
|
||||
# --- prove it ----------------------------------------------------------
|
||||
|
||||
@@ -73,13 +73,16 @@ services:
|
||||
S3_BUCKET: ${S3_BUCKET:-jorgecuadros-documents}
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER must be set}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD must be set}
|
||||
# Outbound mail (SES). Runtime config, never baked into the image and
|
||||
# never a CI secret — the build does not send mail, this container does.
|
||||
# Outbound mail (SES). Runtime config — read at container boot, never
|
||||
# baked into the image; the build does not send mail, this container
|
||||
# does. Values arrive the same way DATABASE_URL does: as Gitea repo
|
||||
# secrets, injected into this stack's env by the `env_data` block of
|
||||
# .gitea/workflows/deploy-galactus.yml.
|
||||
#
|
||||
# The image sets NODE_ENV=production, which disables MailService's
|
||||
# stdout dev fallback: leave these blank and every notification and
|
||||
# renewal aviso fails with "El envío de correo no está configurado."
|
||||
# rather than silently going nowhere. Values live in this stack's env
|
||||
# file on galactus (deploy/.env.prod), same as DATABASE_URL.
|
||||
# rather than silently going nowhere.
|
||||
SES_REGION: ${SES_REGION:-}
|
||||
SES_FROM: ${SES_FROM:-}
|
||||
SES_FROM_NAME: ${SES_FROM_NAME:-}
|
||||
|
||||
@@ -34,9 +34,13 @@ MINIO_ROOT_USER=jc_minio
|
||||
MINIO_ROOT_PASSWORD=CHANGE_ME
|
||||
|
||||
# --- Outbound mail (Amazon SES) ----------------------------------------------
|
||||
# Belongs HERE, in the stack's env file on the host — not in Gitea Actions
|
||||
# secrets. The build never sends mail; the running container does, and it reads
|
||||
# these at boot (apps/api/src/mail/mail.service.ts).
|
||||
# NOTE: for the Portainer-deployed stacks these do NOT come from a file on the
|
||||
# host — the deploy workflows build the stack env from Gitea repo secrets (see
|
||||
# the `env_data` blocks in .gitea/workflows/deploy*.yml). This file documents
|
||||
# the full variable set and is what you fill in for a hand-run stack.
|
||||
#
|
||||
# Either way they are RUNTIME config, read at container boot
|
||||
# (apps/api/src/mail/mail.service.ts) — never baked into the image.
|
||||
#
|
||||
# The production image sets NODE_ENV=production, which turns OFF the stdout dev
|
||||
# fallback. Leaving these blank does not silently swallow mail — every send
|
||||
|
||||
@@ -138,11 +138,17 @@ Without SES_* the API still boots and `MailService` falls back to stdout
|
||||
in dev (`NODE_ENV !== "production"`). In production every send throws
|
||||
`ServiceUnavailableException` and the row is recorded as `FAILED`.
|
||||
|
||||
These are **runtime** config, set in the deployed stack's env file on the
|
||||
host (`deploy/jorgecuadros-app.env.example` documents the full set) — not
|
||||
Gitea Actions secrets. The build never sends mail; only the running
|
||||
container does, and the production image sets `NODE_ENV=production`, so a
|
||||
blank SES config fails loudly rather than falling back to stdout.
|
||||
These are **runtime** config — read at container boot, never baked into the
|
||||
image. For the Portainer deployments they are set as **Gitea repo secrets**
|
||||
and injected into the stack env by the `env_data` block of
|
||||
`.gitea/workflows/deploy-galactus.yml` (and `deploy.yml`), exactly like
|
||||
`DATABASE_URL` and `SESSION_SECRET`. Unlike most secrets there they carry no
|
||||
`_GALACTUS` suffix: one SES identity serves every deployment.
|
||||
|
||||
They are optional to *deploy* — the preflight only warns — but the
|
||||
production image sets `NODE_ENV=production`, which disables the stdout dev
|
||||
fallback, so a blank SES config makes every send fail loudly rather than
|
||||
quietly going nowhere.
|
||||
|
||||
## UI
|
||||
|
||||
|
||||
Reference in New Issue
Block a user