Compare commits

...

5 Commits

Author SHA1 Message Date
f6e4ef3cea Merge pull request 'Update NetBox MCP server version to 1.2.1' (#9) from chore/bump-netbox-mcp-version into main
All checks were successful
Build and Push Docker Images / build (push) Successful in 57s
Reviewed-on: #9
2026-06-27 15:21:45 +00:00
Damien
6d27fb2b71 Update NetBox MCP server version to 1.2.1 2026-06-27 17:20:43 +02:00
8566bd55f6 Merge pull request 'feature/postfix-pgsql-image' (#8) from feature/postfix-pgsql-image into main
All checks were successful
Build and Push Docker Images / build (push) Successful in 40s
Reviewed-on: #8
2026-06-26 09:28:15 +00:00
Damien
512d236828 docs(postfix-pgsql): add test setup guide and test plan 2026-06-26 11:22:07 +02:00
Damien
cc01910eb1 fix(postfix-pgsql): disable chroot to fix pgsql lookup DNS resolution 2026-06-26 11:17:36 +02:00
5 changed files with 179 additions and 1 deletions

View File

@@ -1 +1 @@
1.0.0 1.2.1

View File

@@ -27,6 +27,13 @@ On startup, the entrypoint renders three files from templates under
- `/etc/postfix/pgsql-transport-maps.cf` — PostgreSQL lookup that tells - `/etc/postfix/pgsql-transport-maps.cf` — PostgreSQL lookup that tells
Postfix where to forward accepted mail (the internal SMTP relay target). Postfix where to forward accepted mail (the internal SMTP relay target).
Chroot is disabled for all Postfix services via `postconf -F '*/*/chroot = n'`
on every startup. Postfix's chrooted child processes (smtpd, trivial-rewrite,
etc.) lack access to `/etc/resolv.conf` inside the chroot jail, so they cannot
resolve Docker-internal hostnames — causing every PostgreSQL lookup to fail with
`451 4.3.0 Temporary lookup failure`. Disabling chroot is the standard remedy
in containerised deployments.
## Environment variables ## Environment variables
### Required ### Required

View File

@@ -59,4 +59,10 @@ chmod 640 /etc/postfix/pgsql-relay-domains.cf /etc/postfix/pgsql-transport-maps.
postfix check postfix check
# Disable chroot for all master.cf services so child processes (smtpd,
# trivial-rewrite, etc.) can resolve Docker-internal hostnames via the host's
# /etc/resolv.conf. Without this, chrooted processes have no DNS and every
# pgsql lookup returns "451 4.3.0 Temporary lookup failure".
postconf -F '*/*/chroot = n'
exec /usr/sbin/postfix start-fg exec /usr/sbin/postfix start-fg

View File

@@ -0,0 +1,142 @@
# Local Test Environment Setup
Step-by-step instructions for standing up a self-contained test environment for
the `postfix-pgsql` image on any Linux host with Docker installed. No cloud
access is required.
## Architecture
```
External SMTP client (e.g. swaks / telnet)
|
| :25
v
[ postfix-pgsql ] ──pgsql lookup──> [ postgres ]
|
| :2525 (SMTP_RELAY_PORT)
v
[ mailhog ] <── captured mail browsable at :8025
```
MailHog acts as a stand-in for the downstream SMTP consumer and captures every
message delivered to it, making it easy to verify end-to-end relay without
actually sending real mail.
## 1. Create a dedicated Docker network
All containers must share the same network so they can resolve each other by
container name (required because chroot is disabled in this image — see the
README).
```bash
docker network create postfix-test-net
```
## 2. Start PostgreSQL with the relay_domain schema
```bash
docker run -d \
--name test-postgres \
--network postfix-test-net \
-e POSTGRES_USER=relayuser \
-e POSTGRES_PASSWORD=testpassword \
-e POSTGRES_DB=relaydb \
postgres:16-alpine
```
Wait for Postgres to be ready, then create the `relay_domain` table and insert
a test domain:
```bash
docker exec -i test-postgres psql -U relayuser -d relaydb <<'SQL'
CREATE TABLE relay_domain (domain TEXT PRIMARY KEY);
INSERT INTO relay_domain VALUES ('example.com');
SQL
```
Verify:
```bash
docker exec -i test-postgres psql -U relayuser -d relaydb -c "SELECT * FROM relay_domain;"
```
## 3. Start MailHog
MailHog listens on port 2525 for SMTP (matching `SMTP_RELAY_PORT`) and exposes
a web UI on port 8025.
```bash
docker run -d \
--name test-mailhog \
--network postfix-test-net \
-p 8025:8025 \
mailhog/mailhog \
MailHog -smtp-bind-addr 0.0.0.0:2525
```
## 4. Start the postfix-pgsql container
```bash
docker run -d \
--name test-postfix \
--network postfix-test-net \
-p 25:25 \
-e MAIL_DOMAIN=example.com \
-e MYHOSTNAME=mx1.example.com \
-e POSTGRES_HOST=test-postgres \
-e POSTGRES_PORT=5432 \
-e POSTGRES_USER=relayuser \
-e POSTGRES_PASSWORD=testpassword \
-e POSTGRES_DB=relaydb \
-e SMTP_RELAY_HOST=test-mailhog \
-e SMTP_RELAY_PORT=2525 \
gitea.arnodo.fr/damien/postfix-pgsql:latest
```
Confirm it started cleanly:
```bash
docker logs test-postfix
```
Expected: Postfix master process line and no errors.
## 5. Send a test message
You can use `swaks` (Swiss Army Knife for SMTP) or plain `telnet`:
```bash
# With swaks (install: apt install swaks / brew install swaks)
swaks --to user@example.com \
--from sender@external.com \
--server 127.0.0.1 \
--port 25
# Or with telnet
telnet 127.0.0.1 25
EHLO test
MAIL FROM:<sender@external.com>
RCPT TO:<user@example.com>
DATA
Subject: Test
.
QUIT
```
## 6. View captured mail in MailHog
**Web UI** — if testing on a remote host, open an SSH tunnel first:
```bash
ssh -L 8025:localhost:8025 user@your-host
```
Then open `http://localhost:8025` in a browser.
**HTTP API** (no tunnel needed):
```bash
curl -s http://localhost:8025/api/v2/messages | jq '.items[].Content.Headers.Subject'
```
## 7. Cleanup
```bash
docker rm -f test-postfix test-mailhog test-postgres
docker network rm postfix-test-net
```

View File

@@ -0,0 +1,23 @@
# Test Plan: postfix-pgsql image
Manual test suite run against the image during initial validation on a Scaleway
test VM. All tests use the local environment described in `setup.md`.
**Convention for this table:**
- *Command(s)* are abbreviated; see `setup.md` for full environment setup.
- Add new rows at the bottom as the image evolves; do not rewrite existing rows
(failed + fixed tests are part of the history).
- Status: ✅ Pass · ❌ Fail · 🔧 Fixed (failed, root-caused, fix applied)
| # | Test | What was checked | Command(s) | Expected | Actual | Status |
|---|------|-----------------|------------|----------|--------|--------|
| 1 | Clean startup | No errors in container logs after start; Postfix master process running | `docker run ... gitea.arnodo.fr/damien/postfix-pgsql:latest` then `docker logs test-postfix` | Postfix `daemon started` line, no `fatal`/`panic` entries | Logs show `postfix/master[1]: daemon started -- version 3.7.x`, self-signed cert generation output, no errors | ✅ Pass |
| 2 | Missing required env var | Container exits with a clear error message when a required variable is absent | `docker run ... -e POSTGRES_HOST= ...` (POSTGRES_HOST set to empty) | Non-zero exit, `ERROR: required environment variable POSTGRES_HOST is not set` printed to stderr | Container exited immediately with the expected message | ✅ Pass |
| 3 | Template rendering correctness | Rendered `main.cf` contains substituted values, no leftover `${VAR}` placeholders | `docker exec test-postfix cat /etc/postfix/main.cf` | `myhostname = mx1.example.com`, `mydomain = example.com`, TLS paths resolved, no literal `${...}` strings | All values substituted; Postfix-native `$mydomain` references (no braces) correctly left untouched by envsubst | ✅ Pass |
| 4 | Self-signed TLS cert auto-generated | When no TLS_CERT_FILE/TLS_KEY_FILE are provided, a self-signed cert is generated and persisted | Start container without TLS vars; `docker exec test-postfix ls -l /etc/postfix/ssl/` | Files `snakeoil.pem` and `snakeoil.key` present; key mode 600 | Both files created on first start; `postconf smtpd_tls_cert_file` returned the correct path | ✅ Pass |
| 5 | Port 25 reachable | SMTP port 25 is accessible from outside the container and returns a valid Postfix banner | `telnet 127.0.0.1 25` | `220 mx1.example.com ESMTP Postfix` banner | Banner received as expected | ✅ Pass |
| 6 | Accepted domain → mail relayed | Mail sent to a recipient whose domain is in `relay_domain` is forwarded to the downstream SMTP target (MailHog) | `swaks --to user@example.com --server 127.0.0.1 --port 25`; check MailHog API | `250 2.0.0 Ok: queued` from Postfix; message appears in `GET /api/v2/messages` | Message delivered to MailHog and visible in the web UI | ✅ Pass |
| 7 | Unknown domain → relay denied | Mail sent to a domain not in `relay_domain` is rejected with relay access denied | `swaks --to user@notlisted.org --server 127.0.0.1 --port 25` | `554 5.7.1 Relay access denied` | Postfix returned `554 5.7.1 <user@notlisted.org>: Relay access denied` | ✅ Pass |
| 8 | PostgreSQL lookup failure — chroot DNS bug | pgsql-backed `relay_domains`/`transport_maps` lookups work correctly when Postgres is referenced by Docker container name | Send mail to `user@example.com` with `POSTGRES_HOST=test-postgres` (container name, not IP) | `250 Ok: queued`; no `451 Temporary lookup failure` in logs | **Without fix:** intermittent `451 4.3.0 Temporary lookup failure` — chrooted child processes (smtpd, trivial-rewrite) could not resolve `test-postgres` because they lack `/etc/resolv.conf` inside the chroot jail. **Fix applied:** added `postconf -F '*/*/chroot = n'` in `docker-entrypoint.sh` before `postfix start-fg`. After fix: lookups succeed consistently. | 🔧 Fixed |
| 9 | Container restart behavior | Config is re-rendered correctly on restart; pre-existing self-signed cert is reused without re-generation | `docker restart test-postfix`; `docker logs test-postfix` | No re-generation message for TLS cert; `main.cf` and pgsql maps fully substituted; Postfix starts without errors | Cert reuse message absent (new cert would log openssl output); Postfix started cleanly on restart | ✅ Pass |
| 10 | No credential leakage in logs | Postgres password does not appear anywhere in `docker logs` output | `docker logs test-postfix 2>&1 \| grep testpassword` | No output (password not printed) | No lines containing the password; envsubst substitution into config files is silent | ✅ Pass |