Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ SMTP_USER=
SMTP_PASSWORD=
SMTP_FROM=Beckham Share <[email protected]>
SMTP_USE_TLS=true

# The relay's certificate is valid for the hostname, but its public DNS record
# points at a pool member this network can't reach, so docker-compose.yml pins
# the name to an address that answers. Change these when the pool changes —
# docs/OPERATIONS.md has the procedure. Read by Compose, not by the app.
SMTP_RELAY_HOST=mail.eigbox.net
SMTP_RELAY_IP=66.96.134.48
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ pre-1.0 scheme; dates are when the change reached `main`.
- Template rendering goes through a small `render()`/`error_page()` helper,
which also moves the app onto Starlette's current `TemplateResponse`
signature.
- The SMTP relay's pinned address is now set by `SMTP_RELAY_HOST` /
`SMTP_RELAY_IP` instead of being hard-coded in `docker-compose.yml`, so it can
be corrected from `.env` when the provider's pool changes. Added a runbook
entry for diagnosing it, since the failure looks like nothing else breaking.

### Security
- Updated dependencies to clear published advisories against the pinned
Expand Down
10 changes: 6 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ services:
environment:
DATABASE_URL: postgresql+psycopg2://share:${DB_PASSWORD}@db:5432/share
extra_hosts:
# The SMTP relay presents a shared *.eigbox.net certificate; map the
# cert-valid hostname to its IP so STARTTLS verification succeeds when
# sending share-by-email. (Set SMTP_HOST=mail.eigbox.net in .env.)
- "mail.eigbox.net:66.96.134.48"
# The SMTP relay presents a shared *.eigbox.net certificate, so we have to
# connect by that hostname for STARTTLS verification to succeed — but its
# public DNS record points at a pool member this network cannot reach.
# Pin the name to an address that answers. Override both halves from .env
# when the pool changes; see docs/OPERATIONS.md for how to find a live one.
- "${SMTP_RELAY_HOST:-mail.eigbox.net}:${SMTP_RELAY_IP:-66.96.134.48}"
volumes:
- share-data:/data
networks: [idp_proxy, internal]
Expand Down
18 changes: 15 additions & 3 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,21 @@ and the server-side relay (members only) returns a "not configured" response.
| `SMTP_FROM` | `Beckham Share <[email protected]>` | `From` header on outgoing mail. |
| `SMTP_USE_TLS` | `true` | Use STARTTLS before authenticating. |

> The relay used in production presents a shared `*.eigbox.net` certificate, so
> `docker-compose.yml` maps `mail.eigbox.net` to its IP via `extra_hosts` to keep
> STARTTLS certificate verification valid. Set `SMTP_HOST=mail.eigbox.net`.
The relay used in production presents a shared `*.eigbox.net` certificate, so the
app has to connect by that hostname for STARTTLS verification to succeed — but
the hostname's public DNS record points at a pool member the container network
cannot reach. `docker-compose.yml` therefore pins the name to an address that
answers, via `extra_hosts`. Two variables control the pin:

| Variable | Default | Purpose |
|---|---|---|
| `SMTP_RELAY_HOST` | `mail.eigbox.net` | Hostname the certificate is valid for, and the value `SMTP_HOST` should use. |
| `SMTP_RELAY_IP` | `66.96.134.48` | Address that hostname is pinned to inside the container. |

> Read by Compose when the stack starts, not by `app/config.py` — they shape the
> container's `/etc/hosts` rather than the application's settings. When mail
> starts failing to connect, this pin is the first thing to check;
> [Operations](OPERATIONS.md) has the procedure.

## Derived settings (not environment variables)

Expand Down
28 changes: 28 additions & 0 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,32 @@ certificates are issued and renewed automatically by Caddy.
| `503` on `/login` | OIDC not configured | `OIDC_*` set in `.env`? |
| Anonymous upload `429` | Rate limit hit | Expected; tune `ANON_UPLOADS_PER_*`. |
| Email returns `email_not_configured` | SMTP unset | Set `SMTP_*`; STARTTLS host mapping present. |
| Email returns `send_failed`, logs show a connection timeout | The pinned relay address stopped answering | See "Relay address" below. |
| Member sees "not authorized" | Not in the `dropbox` group | Group membership in Authentik; `groups` scope mapped. |

### Relay address

Share-by-email connects to `mail.eigbox.net` because that is the name its
certificate covers, but the name's public DNS record points at a pool member the
container network cannot reach. `docker-compose.yml` pins the name to a working
address (`SMTP_RELAY_IP`). If the provider retires that address, mail starts
timing out while everything else keeps working.

Confirm what the container is using, and whether it still answers:

```sh
docker compose exec -T app python - <<'PY'
import smtplib, ssl, socket
print("resolves to:", sorted({i[4][0] for i in socket.getaddrinfo("mail.eigbox.net", 587)}))
s = smtplib.SMTP("mail.eigbox.net", 587, timeout=15)
print("banner:", s.ehlo()[1].decode().splitlines()[0])
s.starttls(context=ssl.create_default_context()) # verifies the certificate
print("STARTTLS ok")
s.quit()
PY
```

If it no longer answers, find one that does — the provider publishes several —
set `SMTP_RELAY_IP` in `.env`, and `docker compose up -d app` to rewrite the
container's `/etc/hosts`. Keep `SMTP_RELAY_HOST` as the certificate name;
connecting by address instead would fail verification.
Loading