Files

4.9 KiB

AGENTS.md

Docker Mailserver (DMS) is a single-container mail server (Postfix, Dovecot, Rspamd, Amavis, ClamAV, Fail2Ban, ...). It is configured entirely through environment variables and a config volume mounted at /tmp/docker-mailserver. Nearly all logic is Bash.

Layout

Path Purpose
Dockerfile Image build; copies files from target/ into the image
target/scripts/start-mailserver.sh Container entrypoint; registers and runs check, setup, and daemon functions
target/scripts/startup/ variables-stack.sh (ENV defaults), check-stack.sh, setup-stack.sh, daemons-stack.sh
target/scripts/startup/setup.d/ Per-feature _setup_* functions, all sourced at startup
target/scripts/helpers/ Shared Bash helpers (logging, errors, Postfix, TLS, accounts, ...)
target/scripts/check-for-changes.sh Change detector; reloads services when files in the config volume change
target/bin/ In-container commands (setup and its subcommands); setup.sh in the root wraps them via docker exec
target/<service>/ Static config files for each bundled service
test/tests/{parallel,serial}/ BATS integration tests
test/helper/ Test helpers; read their doc comments before writing tests
docs/content/ MkDocs Material sources (docs/site/ is build output, do not edit)
mailserver.env User-facing ENV reference with inline documentation

Startup Flow

start-mailserver.sh registers functions conditionally based on ENV, then runs: variables, then checks, then setup, then daemons (managed by supervisord, see target/supervisor/). Registration order matters and is documented inline. On container restart (/CONTAINER_START exists) most setup is skipped, so setup code only runs on a fresh container.

Commands

All tooling runs in Docker. Requires git submodule update --init --recursive and jq, parallel, file on the host. macOS is unsupported.

make lint                                     # eclint, hadolint, bashcheck, shellcheck
make build                                    # builds mailserver-testing:ci
make clean generate-accounts test/<name>      # single test file, without .bats; comma-separate for multiple
make clean generate-accounts tests/parallel/set1
make clean tests                              # full suite (slow)

Tests run against the built image. After changing anything under target/ or the Dockerfile, run make build before testing, or the tests exercise stale code.

Bash Conventions

ShellCheck runs with --enable=all (see test/linting/lint.sh), and the code base is deliberately uniform. Match it:

  • shebang #!/bin/bash, 2-space indent, LF; follow .editorconfig
  • always brace and quote expansions ("${VAR}"), use [[ ]], never [ ]
  • upper-case variable names, including local ones
  • declare functions as function _name() { ... }; a __ prefix marks file-internal helpers
  • log with _log '<trace|debug|info|warn|error>' '<message>', never bare echo
  • fail startup with the _dms_panic__* functions from helpers/error.sh
  • reuse helpers before writing new ones, e.g. _add_to_or_update_postfix_main, _replace_by_env_in_file, _env_var_expect_zero_or_one
  • use sedfile -i (or sedfile --strict -i) when an edit is expected to change a file; it fails if the file was left untouched

Adding or Changing an Environment Variable

A complete change touches all of these:

  1. default value in target/scripts/startup/variables-stack.sh (VARS[NAME]="${NAME:=default}"), plus validation if needed
  2. documented entry in mailserver.env
  3. entry in docs/content/config/environment.md
  4. CHANGELOG.md under Unreleased
  5. a test covering the new behavior

When renaming or removing a variable, add an error to __environment_variables_backwards_compatibility and list it under Breaking in the changelog.

Tests

  • put new tests in test/tests/parallel/setN/; use serial/ only if the test cannot run concurrently
  • start from test/tests/parallel/set2/template.bats
  • CONTAINER_NAME must be unique and prefixed with dms-test_; set BATS_TEST_NAME_PREFIX
  • wait for readiness with helpers such as _wait_for_service or _repeat_until_success_or_timeout, never with sleep
  • per-container config copies go to TEST_TMP_CONFIG; do not modify test/config/ from within a test

Docs

  • use MkDocs Material admonitions (!!! note) and reference-style links collected at the bottom of each page, as existing pages do
  • preview locally: docker run --rm -it -p 8000:8000 -v "./docs:/docs" docker.io/squidfunk/mkdocs-material:9.7

Commits and PRs

  • commit subjects use Conventional Commit prefixes: feat:, fix:, docs:, refactor:, tests:, chore:, ci:, breaking:
  • add user-facing changes to CHANGELOG.md under Unreleased in the matching section (Breaking, Added, Fixed, Documentation, CI), with a link to the PR
  • fill in .github/pull_request_template.md