diff --git a/.gitea/workflows/dev-deploy.yaml b/.gitea/workflows/dev-deploy.yaml index b9b3c94..9e74fbd 100644 --- a/.gitea/workflows/dev-deploy.yaml +++ b/.gitea/workflows/dev-deploy.yaml @@ -148,6 +148,31 @@ jobs: -v "${{ gitea.workspace }}/pkg/geoip/test-data/test-data:/src:ro" \ alpine sh -c 'cp /src/GeoIP2-Country-Test.mmdb /dst/geoip.mmdb' + - name: Seed mailpit relay config + env: + GALAXY_DEV_MAIL_RELAY_USERNAME: ${{ secrets.GALAXY_DEV_MAIL_RELAY_USERNAME }} + GALAXY_DEV_MAIL_RELAY_PASSWORD: ${{ secrets.GALAXY_DEV_MAIL_RELAY_PASSWORD }} + run: | + # Render the Mailpit relay upstream config from the template, + # substituting the Gmail App Password from a Gitea secret, then + # seed it into a named volume (same rationale as the geoip seed: + # a workspace bind-mount would vanish with the runner workspace). + # The secret never lands in git or a committed file; it is + # rendered to a tmpfile outside the repo and removed after. Gmail + # App Passwords are [a-z]{16}, so the `|` sed delimiter is safe. + # When the secret is unset the creds render empty and the compose + # default relay-match is non-routable, so the stack only captures. + rendered="$(mktemp)" + sed -e "s|\${GALAXY_DEV_MAIL_RELAY_USERNAME}|${GALAXY_DEV_MAIL_RELAY_USERNAME}|g" \ + -e "s|\${GALAXY_DEV_MAIL_RELAY_PASSWORD}|${GALAXY_DEV_MAIL_RELAY_PASSWORD}|g" \ + "${{ gitea.workspace }}/tools/dev-deploy/mailpit/relay.conf.tmpl" > "$rendered" + docker volume create galaxy-dev-mailpit-config >/dev/null + docker run --rm \ + -v galaxy-dev-mailpit-config:/dst \ + -v "$rendered:/src/relay.conf:ro" \ + alpine sh -c 'cp /src/relay.conf /dst/relay.conf && chmod 600 /dst/relay.conf' + rm -f "$rendered" + - name: Recycle engine containers on image drift run: | # Compare the freshly-built `galaxy-engine:dev` SHA against @@ -231,6 +256,11 @@ jobs: - name: Bring up the stack working-directory: tools/dev-deploy + env: + # Recipient regex Mailpit auto-relays to the owner's Gmail. + # Unset/empty → the compose default (non-routable) keeps the + # stack capture-only. + GALAXY_DEV_MAIL_RELAY_MATCH: ${{ vars.GALAXY_DEV_MAIL_RELAY_MATCH }} run: | # Resolve in the shell, not in YAML expressions — `env.HOME` # is empty at the workflow-evaluation stage. diff --git a/tools/dev-deploy/README.md b/tools/dev-deploy/README.md index 4f66960..2f77efa 100644 --- a/tools/dev-deploy/README.md +++ b/tools/dev-deploy/README.md @@ -117,13 +117,37 @@ and the dev-deploy compose ships with it enabled by default: 1. Enter your email address in the login form. 2. Submit `123456` as the code — the docker-compose default for `BACKEND_AUTH_DEV_FIXED_CODE` is `123456`, so the bcrypt-hashed - email code stays a fallback. To force real Mailpit codes (e.g. for - mail-flow QA), set `BACKEND_AUTH_DEV_FIXED_CODE=` (empty) in a - local `.env` and `make rebuild`. + email code stays a fallback. To force the real email code (which + Mailpit then relays to your Gmail — see **Mail** below), set + `BACKEND_AUTH_DEV_FIXED_CODE=` (empty) and redeploy. The fixed-code override is rejected by production env loaders, so it cannot leak into the prod environment. +## Mail + +The backend always submits mail to **Mailpit** (`galaxy-mailpit:1025`), +exactly as it would to a production SMTP server. Mailpit captures every +message in its UI (internal `:8025`) and, when configured, **relays** +the ones whose recipient matches `GALAXY_DEV_MAIL_RELAY_MATCH` up to a +real Gmail account — so an OTP addressed to you lands in your real inbox +while everything else stays captured-only. + +Configure the relay through Gitea Actions secrets/vars (never +committed); the `dev-deploy.yaml` workflow renders Mailpit's +`relay.conf` (from `tools/dev-deploy/mailpit/relay.conf.tmpl`) and seeds +it into the `galaxy-dev-mailpit-config` volume: + +| Name | Kind | Purpose | +| --- | --- | --- | +| `GALAXY_DEV_MAIL_RELAY_USERNAME` | secret | Gmail address used as the relay login + From. | +| `GALAXY_DEV_MAIL_RELAY_PASSWORD` | secret | Gmail **App Password** (requires 2FA; not the account password). | +| `GALAXY_DEV_MAIL_RELAY_MATCH` | var | Recipient regex to auto-relay (e.g. your Gmail address). Unset → capture-only. | + +With none set the stack only captures mail (the compose relay-match +defaults to a non-routable address), so it can never email third +parties. + ## Networking ``` diff --git a/tools/dev-deploy/docker-compose.yml b/tools/dev-deploy/docker-compose.yml index 02551dd..d969f34 100644 --- a/tools/dev-deploy/docker-compose.yml +++ b/tools/dev-deploy/docker-compose.yml @@ -66,10 +66,20 @@ services: image: axllent/mailpit:v1.21 container_name: galaxy-dev-mailpit restart: unless-stopped + # Mailpit is both the SMTP submission point and a relay: it captures + # every message in its UI and auto-relays the ones whose recipient + # matches GALAXY_DEV_MAIL_RELAY_MATCH to the Gmail account in the + # secret-rendered relay config. The default match is non-routable, so + # a stack brought up without the relay secret only captures, never sends. + command: + - "--smtp-relay-config=/etc/mailpit/relay.conf" + - "--smtp-relay-matching=${GALAXY_DEV_MAIL_RELAY_MATCH:-nobody@invalid.example}" labels: galaxy.stack: dev-deploy networks: - galaxy-internal + volumes: + - galaxy-dev-mailpit-config:/etc/mailpit:ro healthcheck: test: ["CMD", "wget", "-q", "-O-", "http://localhost:8025/livez"] interval: 3s @@ -283,3 +293,5 @@ volumes: name: galaxy-dev-site-dist galaxy-dev-geoip-data: name: galaxy-dev-geoip-data + galaxy-dev-mailpit-config: + name: galaxy-dev-mailpit-config diff --git a/tools/dev-deploy/mailpit/relay.conf.tmpl b/tools/dev-deploy/mailpit/relay.conf.tmpl new file mode 100644 index 0000000..2c4de2e --- /dev/null +++ b/tools/dev-deploy/mailpit/relay.conf.tmpl @@ -0,0 +1,18 @@ +# Mailpit SMTP relay upstream — RENDERED AT DEPLOY TIME by +# .gitea/workflows/dev-deploy.yaml from Gitea Actions secrets, then +# seeded into the `galaxy-dev-mailpit-config` volume. The Gmail App +# Password is a secret and MUST NOT be committed: this template only +# carries ${PLACEHOLDER}s that the workflow substitutes. See +# tools/dev-deploy/README.md ("Mail"). +# +# Mailpit captures every message; the `--smtp-relay-matching` flag (set +# from GALAXY_DEV_MAIL_RELAY_MATCH in the compose) decides which +# recipients are actually relayed up to this Gmail account. +host: smtp.gmail.com +port: 587 +starttls: true +allow-insecure: false +auth: login +username: ${GALAXY_DEV_MAIL_RELAY_USERNAME} +password: ${GALAXY_DEV_MAIL_RELAY_PASSWORD} +return-path: ${GALAXY_DEV_MAIL_RELAY_USERNAME}