updated app data storage and AIO docker setup
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Copy to .env (in this docker-deploy/ folder) and adjust if the defaults below clash
|
||||
# with something else on the host, or if you want to run both the "standalone" and
|
||||
# "with-rspamd" profiles side by side (give each its own set of host ports).
|
||||
# with something else on the host, or if you want to run more than one of the
|
||||
# "standalone" / "with-rspamd" / "all-in-one" profiles side by side (give each its own
|
||||
# set of host ports).
|
||||
SMTP_PORT=25
|
||||
SMTP_TLS_PORT=465
|
||||
IMAP_PORT=143
|
||||
@@ -0,0 +1,79 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
#
|
||||
# All-in-one: mailgoserver + rspamd + redis, bundled in one container — the full spam-
|
||||
# filtering stack, not just rspamd's SPF/DKIM/RBL/regexp scoring. Redis backs rspamd's
|
||||
# Bayes classifier (learns from mail marked as spam/ham) and its greylisting module,
|
||||
# neither of which work without it (see docker-deploy/README.md's original note on
|
||||
# Dockerfile.rspamd, which intentionally skips redis for a lighter image — use that one
|
||||
# instead if you don't want Bayes/greylisting). mailgoserver itself has no direct use
|
||||
# for redis — it's a single-instance app already backed by SQLite for everything, so
|
||||
# there's nothing here for redis to cache or coordinate; it exists purely to make
|
||||
# rspamd's scoring meaningfully better.
|
||||
#
|
||||
# Build from the repo root:
|
||||
# docker build -f docker-deploy/Dockerfile.aio -t mailgoserver-aio .
|
||||
|
||||
FROM golang:1.26-bookworm AS build
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/mailgoserver .
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
# rspamd from its own APT repo (rspamd.com), same as Dockerfile.rspamd, for the latest
|
||||
# stable release rather than Debian's older bundled version. redis-server is Debian's
|
||||
# own package — no third-party repo needed there, and bookworm's version (7.0.x) is
|
||||
# recent enough for everything rspamd's Bayes/greylist modules need.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates tzdata curl gnupg lsb-release libcap2-bin redis-server \
|
||||
&& mkdir -p /usr/share/keyrings \
|
||||
&& curl -fsSL https://rspamd.com/apt-stable/gpg.key | gpg --dearmor -o /usr/share/keyrings/rspamd.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/rspamd.gpg] https://rspamd.com/apt-stable/ $(lsb_release -cs) main" \
|
||||
> /etc/apt/sources.list.d/rspamd.list \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends rspamd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Point rspamd's Bayes classifier and greylisting module at the redis instance this same
|
||||
# container runs (loopback-only, see entrypoint-aio.sh) — both are otherwise inert
|
||||
# without a redis backend. Every other redis-capable module (ratelimit, etc.) picks up
|
||||
# the same servers = ... from this one redis.conf too, rspamd's own convention for
|
||||
# sharing one connection config across modules. Also redirects rspamd's own dbdir (fuzzy
|
||||
# storage, DNS/maps cache) into server_data/rspamd — created/chowned at container start
|
||||
# by entrypoint-aio.sh, since it lives on the volume, not the image — so this whole
|
||||
# bundle only ever needs one thing mounted: server_data/.
|
||||
RUN mkdir -p /etc/rspamd/local.d \
|
||||
&& printf 'servers = "127.0.0.1:6379";\n' > /etc/rspamd/local.d/redis.conf \
|
||||
&& printf 'backend = "redis";\nservers = "127.0.0.1:6379";\n' > /etc/rspamd/local.d/classifier-bayes.conf \
|
||||
&& printf 'enabled = true;\nservers = "127.0.0.1:6379";\n' > /etc/rspamd/local.d/greylist.conf \
|
||||
&& printf 'dbdir = "/app/server_data/rspamd";\n' > /etc/rspamd/local.d/options.inc
|
||||
|
||||
COPY --from=build /out/mailgoserver /usr/local/bin/mailgoserver
|
||||
RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/mailgoserver
|
||||
COPY docker-deploy/entrypoint-aio.sh /usr/local/bin/entrypoint-aio.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint-aio.sh
|
||||
|
||||
# rspamd and redis's own .deb postinsts already create their system users;
|
||||
# mailgoserver gets one here for the same reason as the other images — none of the
|
||||
# three processes run as root (see entrypoint-aio.sh for how each is dropped).
|
||||
RUN useradd --system --create-home --home-dir /app --shell /usr/sbin/nologin mailgoserver \
|
||||
&& mkdir -p /app/server_data \
|
||||
&& chown -R mailgoserver:mailgoserver /app \
|
||||
&& chmod 755 /app
|
||||
WORKDIR /app
|
||||
VOLUME ["/app/server_data"]
|
||||
# Deliberately stays root here (unlike the standalone Dockerfile's USER mailgoserver) —
|
||||
# the entrypoint script itself needs root just long enough to chown server_data's
|
||||
# rspamd/redis subdirectories and drop privileges for each child individually.
|
||||
|
||||
# 11334 (rspamd's own web UI, controller worker) is intentionally NOT exposed here —
|
||||
# it has no authentication configured by default, and this bundle doesn't need it for
|
||||
# anything mailgoserver itself uses. Add a `password` to rspamd's controller worker
|
||||
# config and publish it yourself if you want it. Port 6379 (redis) is never exposed at
|
||||
# all — loopback-only, see entrypoint-aio.sh.
|
||||
EXPOSE 25 465 143 993 80 5000 5001
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD curl -fs http://127.0.0.1:5000/health || exit 1
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint-aio.sh"]
|
||||
@@ -59,21 +59,24 @@ the binary itself has no runtime dependencies (pure-Go SQLite driver, no cgo).
|
||||
## What to expect on first run
|
||||
|
||||
There's no config to write up front. The first time it starts in a given working
|
||||
directory, it generates:
|
||||
directory, it generates everything under `server_data/` — one directory to back up or
|
||||
volume-mount and every persistent thing this app owns is in it:
|
||||
|
||||
- `settings.ini` — every setting with an inline comment explaining it (SMTP/IMAP ports,
|
||||
hostname, TLS, DKIM key size, mailbox quotas, MFA enforcement, rate limits, and so
|
||||
on). Regenerated only if missing — it's never overwritten or merged into on later
|
||||
runs, so edits stick.
|
||||
- A self-signed TLS certificate (used until you either supply your own or enable Let's
|
||||
Encrypt from the admin dashboard).
|
||||
- An empty SQLite database, with one seeded admin account: username `admin`, password
|
||||
`Password123!`. Logging in with it **immediately forces** a username + password
|
||||
change before anything else in the dashboard is reachable — the default credentials
|
||||
can never be left in place.
|
||||
- A mailstore master key and a CSRF-signing app secret, both generated once and reused
|
||||
on every subsequent start — back these up like any other secret (losing the mailstore
|
||||
master key makes all stored mail unrecoverable, even for admins).
|
||||
- `server_data/settings.ini` — every setting with an inline comment explaining it
|
||||
(SMTP/IMAP ports, hostname, TLS, DKIM key size, mailbox quotas, MFA enforcement, rate
|
||||
limits, and so on). Regenerated only if missing — it's never overwritten or merged
|
||||
into on later runs, so edits stick. Override the path with `-config` if you want it
|
||||
somewhere else.
|
||||
- A self-signed TLS certificate under `server_data/ssl_certs/` (used until you either
|
||||
supply your own or enable Let's Encrypt from the admin dashboard).
|
||||
- `server_data/smtp_server.db` — an empty SQLite database, with one seeded admin
|
||||
account: username `admin`, password `Password123!`. Logging in with it **immediately
|
||||
forces** a username + password change before anything else in the dashboard is
|
||||
reachable — the default credentials can never be left in place.
|
||||
- A mailstore master key and a CSRF-signing app secret (also under `server_data/`),
|
||||
both generated once and reused on every subsequent start — back these up like any
|
||||
other secret (losing the mailstore master key makes all stored mail unrecoverable,
|
||||
even for admins).
|
||||
|
||||
From there: log into `/smtp-server`, add a domain (and complete its DNS ownership
|
||||
verification), add a mailbox or sender, and you're sending/receiving. The bare `/` root
|
||||
@@ -136,13 +139,19 @@ AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/opt/mailgoserver
|
||||
# Everything this process ever writes lives under server_data/ (settings.ini, the DB,
|
||||
# mailstore, TLS certs, keys) — narrower than the whole install directory.
|
||||
ReadWritePaths=/opt/mailgoserver/server_data
|
||||
ProtectHome=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
(Widen `ReadWritePaths` if you've pointed `attachments_path`, `TLS_CERT_FILE`/
|
||||
`TLS_KEY_FILE`, or `-config` itself at somewhere outside `server_data/` — everything
|
||||
else this process writes stays inside it by default.)
|
||||
|
||||
If you do want the SMTP and web parts as separate services (matching the Python
|
||||
split exactly), run two units with `--smtp-only` and `--web-only` respectively —
|
||||
both flags exist for this.
|
||||
@@ -152,13 +161,14 @@ both flags exist for this.
|
||||
No changes needed. `script_nginx_setup.sh` reverse-proxies to `http://127.0.0.1:5000`
|
||||
and terminates its own TLS for the web UI — mailgoserver listens on the same host:port
|
||||
by default, so the existing nginx config works unmodified. The SMTP TLS listener still
|
||||
consumes `ssl_certs/server.crt`/`server.key`, same as before.
|
||||
consumes `server_data/ssl_certs/server.crt`/`server.key`, same as before.
|
||||
|
||||
## Docker
|
||||
|
||||
See [`docker-deploy/`](docker-deploy/) — a standalone image and one that bundles the
|
||||
latest rspamd in the same container, both via a single `docker-compose.yml` using
|
||||
Compose profiles.
|
||||
See [`docker-deploy/`](docker-deploy/) — a standalone image, one that bundles the
|
||||
latest rspamd in the same container, and an all-in-one image that adds redis too (for
|
||||
rspamd's Bayes learning and greylisting), all via a single `docker-compose.yml` using
|
||||
Compose profiles. None of the three run as root.
|
||||
|
||||
## Certificates
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
services:
|
||||
mailserver-aio:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile.aio
|
||||
container_name: mailgoserver
|
||||
hostname: mailgoserver
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
backend:
|
||||
ports:
|
||||
- "${SMTP_PORT:-25}:25"
|
||||
- "${SMTP_TLS_PORT:-465}:465"
|
||||
- "${IMAP_PORT:-143}:143"
|
||||
- "${IMAP_TLS_PORT:-993}:993"
|
||||
- "${ACME_HTTP_PORT:-80}:80"
|
||||
- "${WEB_HTTP_PORT:-5000}:5000"
|
||||
- "${WEB_HTTPS_PORT:-5001}:5001"
|
||||
volumes:
|
||||
- mailserver-aio-data:/app/server_data
|
||||
|
||||
volumes:
|
||||
mailserver-aio-data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: /opt/settings/mailgoserver/data
|
||||
|
||||
# This is external network what I use, you can use your own or remove it.
|
||||
networks:
|
||||
backend:
|
||||
external: true
|
||||
+25
-12
@@ -3,7 +3,8 @@
|
||||
# Standalone mailgoserver image — SMTP + IMAP + admin dashboard + webmail, no rspamd.
|
||||
# The built-in heuristic spam score (internal/mailstore/spam.go) always runs regardless;
|
||||
# this is for anyone who doesn't want rspamd's extra dependency. See Dockerfile.rspamd
|
||||
# for the bundled variant.
|
||||
# (mailgoserver + rspamd) and Dockerfile.aio (mailgoserver + rspamd + redis, full spam
|
||||
# stack) for the bundled variants.
|
||||
#
|
||||
# Build from the repo root:
|
||||
# docker build -f docker-deploy/Dockerfile -t mailgoserver .
|
||||
@@ -22,24 +23,36 @@ FROM debian:bookworm-slim
|
||||
# tzdata: [Server] time_zone (e.g. "Europe/London") needs the real IANA zone
|
||||
# database — time.LoadLocation silently falls back to UTC without it.
|
||||
# curl: used by the HEALTHCHECK below.
|
||||
# libcap2-bin: provides setcap, used below so the binary can bind ports 25/465/80
|
||||
# without running the process itself as root.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates tzdata curl \
|
||||
ca-certificates tzdata curl libcap2-bin \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=build /out/mailgoserver /usr/local/bin/mailgoserver
|
||||
# CAP_NET_BIND_SERVICE is one of Docker's default capabilities (no --cap-add needed at
|
||||
# `docker run`/compose time) — granting it to the binary itself, rather than running the
|
||||
# whole container as root, means a bug in mailgoserver can't do anything a root process
|
||||
# could that an unprivileged one couldn't, beyond binding these specific low ports.
|
||||
RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/mailgoserver
|
||||
|
||||
# Everything persistent — the auto-generated settings.ini, the SQLite DB, encrypted
|
||||
# mailbox storage, DKIM/mailstore master keys, TLS certs, the app secret — lives under
|
||||
# whatever directory the process is started from (see config.Load / main.go's
|
||||
# os.Getwd()). One volume here covers all of it; no need to enumerate subpaths.
|
||||
WORKDIR /app/data
|
||||
VOLUME ["/app/data"]
|
||||
# Everything persistent — settings.ini, the SQLite DB, encrypted mailbox storage,
|
||||
# DKIM/mailstore master keys, TLS certs, the app secret — lives under server_data/,
|
||||
# relative to wherever the process is started from (see config.Load / main.go's
|
||||
# os.Getwd()). One volume covers all of it; no need to enumerate subpaths, and nothing
|
||||
# persistent is ever written outside it.
|
||||
RUN useradd --system --create-home --home-dir /app --shell /usr/sbin/nologin mailgoserver \
|
||||
&& mkdir -p /app/server_data \
|
||||
&& chown -R mailgoserver:mailgoserver /app \
|
||||
&& chmod 755 /app
|
||||
WORKDIR /app
|
||||
VOLUME ["/app/server_data"]
|
||||
USER mailgoserver
|
||||
|
||||
# Defaults from internal/config/config.go's generated settings.ini: SMTP 25, direct-TLS
|
||||
# SMTP 465, IMAP 143, direct-TLS IMAP 993 (the actual standard ports — binding them
|
||||
# needs no setcap/capability here since this container runs as root), admin/webmail
|
||||
# HTTP 5000, HTTPS 5001 (deliberately non-privileged; put a reverse proxy or the host's
|
||||
# own 80/443 in front if you want those too). Port 80 is only actually bound while
|
||||
# SMTP 465, IMAP 143, direct-TLS IMAP 993, admin/webmail HTTP 5000, HTTPS 5001
|
||||
# (deliberately non-privileged; put a reverse proxy or the host's own 80/443 in front of
|
||||
# those too if you want them there). Port 80 is only actually bound while
|
||||
# [LetsEncrypt] challenge_type=http-01 is enabled and an obtain/renew is in flight —
|
||||
# harmless to expose even when unused.
|
||||
EXPOSE 25 465 143 993 80 5000 5001
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
# (127.0.0.1:11333, the /checkv2 scanning API — see internal/mailstore/rspamd.go) is
|
||||
# already exactly what [Rspamd] url defaults to in settings.ini. Just set
|
||||
# [Rspamd] enabled = true after first boot (see docker-deploy/README.md) and restart.
|
||||
# No redis here by design — see Dockerfile.aio for the variant that adds it (Bayes
|
||||
# learning + greylisting).
|
||||
#
|
||||
# Build from the repo root:
|
||||
# docker build -f docker-deploy/Dockerfile.rspamd -t mailgoserver-rspamd .
|
||||
@@ -21,7 +23,7 @@ FROM debian:bookworm-slim
|
||||
# package, which tends to lag several releases behind — this is genuinely the latest
|
||||
# stable release, matching what was asked for.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates tzdata curl gnupg lsb-release \
|
||||
ca-certificates tzdata curl gnupg lsb-release libcap2-bin \
|
||||
&& mkdir -p /usr/share/keyrings \
|
||||
&& curl -fsSL https://rspamd.com/apt-stable/gpg.key | gpg --dearmor -o /usr/share/keyrings/rspamd.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/rspamd.gpg] https://rspamd.com/apt-stable/ $(lsb_release -cs) main" \
|
||||
@@ -29,15 +31,30 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends rspamd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Redirect rspamd's own persistent state (fuzzy storage, DNS/maps cache) into
|
||||
# server_data/rspamd at container-start (see entrypoint-rspamd.sh, which creates and
|
||||
# chowns that directory — it doesn't exist yet at build time, since it lives on the
|
||||
# volume) rather than the package default /var/lib/rspamd, so this whole image only
|
||||
# ever needs one volume mounted: server_data/, matching mailgoserver's own layout.
|
||||
RUN mkdir -p /etc/rspamd/local.d && printf 'dbdir = "/app/server_data/rspamd";\n' > /etc/rspamd/local.d/options.inc
|
||||
|
||||
COPY --from=build /out/mailgoserver /usr/local/bin/mailgoserver
|
||||
RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/mailgoserver
|
||||
COPY docker-deploy/entrypoint-rspamd.sh /usr/local/bin/entrypoint-rspamd.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint-rspamd.sh
|
||||
|
||||
# /app/data: same as the standalone image (settings.ini, DB, mailstore, keys, certs).
|
||||
# /var/lib/rspamd: rspamd's own Bayes/fuzzy-hash storage, kept persistent separately so
|
||||
# rebuilding the image doesn't reset spam-learning state.
|
||||
WORKDIR /app/data
|
||||
VOLUME ["/app/data", "/var/lib/rspamd"]
|
||||
# rspamd's own .deb postinst already creates the "rspamd" system user; mailgoserver
|
||||
# gets one here for the same reason as the standalone Dockerfile — neither process runs
|
||||
# as root (see entrypoint-rspamd.sh for how each is dropped to its own user).
|
||||
RUN useradd --system --create-home --home-dir /app --shell /usr/sbin/nologin mailgoserver \
|
||||
&& mkdir -p /app/server_data \
|
||||
&& chown -R mailgoserver:mailgoserver /app \
|
||||
&& chmod 755 /app
|
||||
WORKDIR /app
|
||||
VOLUME ["/app/server_data"]
|
||||
# Deliberately stays root here (unlike the standalone Dockerfile's USER mailgoserver) —
|
||||
# the entrypoint script itself needs root just long enough to chown server_data/rspamd
|
||||
# and drop privileges for each child process individually; see entrypoint-rspamd.sh.
|
||||
|
||||
# Port 80 is only actually bound while [LetsEncrypt] challenge_type=http-01 is enabled
|
||||
# and an obtain/renew is in flight — harmless to expose even when unused.
|
||||
|
||||
+88
-34
@@ -1,50 +1,68 @@
|
||||
# Docker deployment
|
||||
|
||||
Two independent images, both built from the same source tree:
|
||||
Three independent images, all built from the same source tree:
|
||||
|
||||
- **`Dockerfile`** — mailgoserver only.
|
||||
- **`Dockerfile.rspamd`** — mailgoserver + the latest [rspamd](https://rspamd.com) in the
|
||||
*same* container, already wired together (see below). Use this one if you want
|
||||
stronger spam filtering than the built-in heuristic score alone.
|
||||
*same* container, already wired together. Use this if you want stronger spam
|
||||
filtering than the built-in heuristic score alone, but don't need Bayes learning or
|
||||
greylisting.
|
||||
- **`Dockerfile.aio`** — mailgoserver + rspamd + redis, the full spam-filtering stack:
|
||||
redis backs rspamd's Bayes classifier and greylisting module, neither of which work
|
||||
without it. mailgoserver itself has no direct use for redis — it's a single-instance
|
||||
app already backed by SQLite for everything, so this exists purely to make rspamd's
|
||||
scoring meaningfully better, not to make mailgoserver itself faster or more scalable.
|
||||
|
||||
`docker-compose.yml` defines both as Compose **profiles** so a plain `docker compose up`
|
||||
can't accidentally start both at once:
|
||||
`docker-compose.yml` defines all three as Compose **profiles** so a plain `docker compose
|
||||
up` can't accidentally start more than one at once:
|
||||
|
||||
```bash
|
||||
cd docker-deploy
|
||||
|
||||
# mailserver only
|
||||
docker compose --profile standalone up -d --build
|
||||
|
||||
# mailserver + rspamd, bundled
|
||||
docker compose --profile with-rspamd up -d --build
|
||||
docker compose --profile standalone up -d --build # mailserver only
|
||||
docker compose --profile with-rspamd up -d --build # mailserver + rspamd
|
||||
docker compose --profile all-in-one up -d --build # mailserver + rspamd + redis
|
||||
```
|
||||
|
||||
Either way, the app itself now binds the real standard mail ports by default — 25
|
||||
Either way, the app itself binds the real standard mail ports by default — 25
|
||||
(SMTP), 465 (direct-TLS SMTP), 143 (IMAP), 993 (direct-TLS IMAP) — and the admin/webmail
|
||||
UI on its usual non-privileged 5000/5001 (HTTP/HTTPS); put your own reverse proxy or a
|
||||
`80:5000`/`443:5001` port mapping in front if you want those on 80/443 too (note port 80
|
||||
is already published here for Let's Encrypt HTTP-01, see below — pick a different host
|
||||
port for the web UI's 80 mapping if you use both). Binding the low mail ports needs no
|
||||
extra capability here since the container runs as root. Copy `.env.example` to `.env` in
|
||||
this folder to change any host-side port — useful if something else on the host already
|
||||
owns 25/143/etc., or if you want to run both profiles side by side.
|
||||
port for the web UI's 80 mapping if you use both). Copy `.env.example` to `.env` in this
|
||||
folder to change any host-side port — useful if something else on the host already owns
|
||||
25/143/etc., or if you want to run more than one profile side by side.
|
||||
|
||||
## Security: nothing here runs as root
|
||||
|
||||
None of the three images run mailgoserver, rspamd, or redis as root. Each gets its own
|
||||
unprivileged system user (rspamd's and redis's own `.deb` packages create theirs;
|
||||
mailgoserver's is created in the Dockerfile), and mailgoserver's binary is granted just
|
||||
the one Linux capability it actually needs — `CAP_NET_BIND_SERVICE`, to bind ports
|
||||
25/465/80 — via `setcap` at build time, rather than the whole container running as root
|
||||
to get the same effect. `CAP_NET_BIND_SERVICE` is one of Docker's default capabilities,
|
||||
so this needs no extra `--cap-add` at `docker run`/compose time.
|
||||
|
||||
The `Dockerfile.rspamd`/`Dockerfile.aio` entrypoint scripts (`entrypoint-rspamd.sh`,
|
||||
`entrypoint-aio.sh`) do still start as root — only long enough to `chown` the
|
||||
persistent subdirectories under `server_data/` for whichever user needs to write there,
|
||||
then drop to that user (via `runuser`) for every actual process, mailgoserver included.
|
||||
|
||||
## What happens on first boot
|
||||
|
||||
There's no baked-in config. On first start, the binary generates a fresh
|
||||
`settings.ini` with defaults (mirroring `internal/config/config.go`), a self-signed TLS
|
||||
certificate, DKIM/mailstore master keys, and an empty SQLite database — all inside the
|
||||
`/app/data` volume, so it survives container restarts/rebuilds. The web UI seeds one
|
||||
admin account: username `admin`, password `Password123!`, and forces an immediate
|
||||
username + password change on first login — see the main [README](../README.md) for
|
||||
the full first-login walkthrough.
|
||||
`server_data/settings.ini` with defaults (mirroring `internal/config/config.go`), a
|
||||
self-signed TLS certificate, DKIM/mailstore master keys, and an empty SQLite database —
|
||||
all inside the `server_data/` volume, so it survives container restarts/rebuilds. The
|
||||
web UI seeds one admin account: username `admin`, password `Password123!`, and forces an
|
||||
immediate username + password change on first login — see the main
|
||||
[README](../README.md) for the full first-login walkthrough.
|
||||
|
||||
**Before using this for real mail**, exec into the container (or edit the volume from
|
||||
the host) and update `settings.ini`:
|
||||
|
||||
```bash
|
||||
docker exec -it mailgoserver sh -c 'vi /app/data/settings.ini'
|
||||
docker exec -it mailgoserver sh -c 'vi /app/server_data/settings.ini'
|
||||
docker restart mailgoserver
|
||||
```
|
||||
|
||||
@@ -54,7 +72,7 @@ dashboard is reached at (`rp_id` can't be `localhost` once you're on a real doma
|
||||
see the main README's WebAuthn note). There's no environment-variable override
|
||||
mechanism — `settings.ini` in the volume is the one source of config truth.
|
||||
|
||||
## Enabling rspamd (the `with-rspamd` profile)
|
||||
## Enabling rspamd (`with-rspamd` and `all-in-one`)
|
||||
|
||||
The bundled rspamd's default config already listens on `127.0.0.1:11333` for scanning
|
||||
requests — exactly what `[Rspamd] url` defaults to in `settings.ini`, and since both
|
||||
@@ -73,9 +91,15 @@ reject_score = 15
|
||||
additive, not a replacement, and if it's ever unreachable, mail still flows on the
|
||||
heuristic score alone (rspamd errors are logged, never fatal to delivery).
|
||||
|
||||
This bundle intentionally skips Redis — rspamd runs fine without it for SPF/DKIM/RBL/
|
||||
regexp-based scoring, but Bayes learning and greylisting need it. Add a `redis` service
|
||||
to `docker-compose.yml` and point rspamd's `redis.conf` at it if you need those.
|
||||
`with-rspamd` intentionally skips redis — rspamd runs fine without it for SPF/DKIM/RBL/
|
||||
regexp-based scoring, but Bayes learning and greylisting need it. If you want those,
|
||||
use `all-in-one` instead: its rspamd is already pre-configured (`local.d/redis.conf`,
|
||||
`classifier-bayes.conf`, `greylist.conf` baked into the image) to use the redis running
|
||||
alongside it — nothing to turn on beyond `[Rspamd] enabled = true` above. That redis
|
||||
instance is loopback-only and has no exposed port or password: the only two processes
|
||||
that can ever reach it are rspamd and mailgoserver, both inside the same container's
|
||||
network namespace. If you publish 6379 yourself for some other reason, add a
|
||||
`requirepass` to the `redis-server` command in `entrypoint-aio.sh` first.
|
||||
|
||||
## Let's Encrypt HTTP-01 (no DNS provider needed)
|
||||
|
||||
@@ -98,21 +122,51 @@ SMTP/IMAP, DNS-01 (or a real custom cert) for the web UI.
|
||||
|
||||
## Persistence
|
||||
|
||||
Every profile mounts exactly one volume:
|
||||
|
||||
| Volume | What's in it |
|
||||
|---|---|
|
||||
| `mailserver-data` / `mailserver-rspamd-data` | `settings.ini`, the SQLite DB, encrypted mailbox storage, DKIM/mailstore master keys, TLS certs, the CSRF app secret — everything mailgoserver itself owns. |
|
||||
| `rspamd-data` (rspamd profile only) | rspamd's own Bayes/fuzzy-hash storage, so spam-learning state survives image rebuilds. |
|
||||
| `mailserver-data` (`standalone`) | `settings.ini`, the SQLite DB, encrypted mailbox storage, DKIM/mailstore master keys, TLS certs, the CSRF app secret — everything mailgoserver itself owns. |
|
||||
| `mailserver-rspamd-data` (`with-rspamd`) | The above, plus `rspamd/` — rspamd's own fuzzy-hash storage and DNS/maps cache. |
|
||||
| `mailserver-aio-data` (`all-in-one`) | The above, plus `redis/` — redis's RDB snapshot, so Bayes-learning and greylist state survive restarts too. |
|
||||
|
||||
Back up the `*-data` volume like you would the equivalent bare-metal `server_data/`
|
||||
directory — losing the mailstore master key makes all stored mail unrecoverable, same
|
||||
as a non-Docker install.
|
||||
Back up the volume like you would the equivalent bare-metal `server_data/` directory —
|
||||
losing the mailstore master key makes all stored mail unrecoverable, same as a
|
||||
non-Docker install. (Redis's own state isn't precious the same way — losing it just
|
||||
means rspamd re-learns Bayes classifications and greylist history from scratch, not
|
||||
anything mail-data-critical.)
|
||||
|
||||
## Keeping the base image and packages patched
|
||||
|
||||
None of these images auto-update their own OS packages (Debian, rspamd, redis) while
|
||||
running — a container's filesystem is meant to be rebuilt from a fresh base, not patched
|
||||
in place, so the reliable way to pick up security fixes is rebuilding periodically:
|
||||
|
||||
```bash
|
||||
docker compose --profile <profile> build --pull --no-cache
|
||||
docker compose --profile <profile> up -d
|
||||
```
|
||||
|
||||
Put that on a host-level cron/systemd timer (weekly is reasonable), or use a tool like
|
||||
[Renovate](https://docs.renovatebot.com/) or Watchtower against this repo/image if you
|
||||
want it automated end-to-end. Running `apt-get upgrade` on a schedule *inside* the
|
||||
running container was considered and deliberately left out — it would only patch that
|
||||
one running instance until its next recreation (at which point the image's original,
|
||||
un-patched packages come back anyway), doesn't rebuild the Go binary itself, and adds a
|
||||
cron daemon + package-manager attack surface to a container that's otherwise trying to
|
||||
run three unprivileged, single-purpose processes. Rebuilding the image is both more
|
||||
thorough and simpler.
|
||||
|
||||
## Logs / health
|
||||
|
||||
```bash
|
||||
docker compose --profile standalone logs -f # or --profile with-rspamd
|
||||
docker compose --profile standalone logs -f # or --profile with-rspamd / all-in-one
|
||||
docker inspect --format '{{.State.Health.Status}}' mailgoserver
|
||||
```
|
||||
|
||||
Both images expose `GET /health` (used by the container `HEALTHCHECK`), matching the
|
||||
JSON the admin dashboard's own health check reads.
|
||||
All three images log to stdout/stderr only (no log file inside the container) — this is
|
||||
the standard Docker pattern (`docker logs`, or point your log driver/aggregator at the
|
||||
container) rather than something to volume-mount or rotate yourself.
|
||||
|
||||
All three images expose `GET /health` (used by the container `HEALTHCHECK`), matching
|
||||
the JSON the admin dashboard's own health check reads.
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
# Two independent setups, selected with --profile so they don't both start by accident:
|
||||
# Three independent setups, selected with --profile so they don't both start by accident:
|
||||
#
|
||||
# docker compose --profile standalone up -d --build # mailserver only
|
||||
# docker compose --profile with-rspamd up -d --build # mailserver + rspamd, same container
|
||||
# docker compose --profile all-in-one up -d --build # mailserver + rspamd + redis (Bayes/greylisting)
|
||||
#
|
||||
# Both default to the standard mail ports on the host (25/465/143/993) — the app itself
|
||||
# now binds those directly, no port remapping needed — plus port 80 (only actually used
|
||||
# while Let's Encrypt HTTP-01 is enabled, see internal/webui's Let's Encrypt page) and
|
||||
# the app's own non-privileged web ports (5000/5001; put a reverse proxy or your own
|
||||
# 80/443 mapping in front of those if you want the dashboard on standard web ports too —
|
||||
# note port 80 is already claimed here for HTTP-01 if you enable it). Only run one profile at a time
|
||||
# unless you've overridden the host ports for one of them (see .env.example) — they'd
|
||||
# otherwise both try to bind the same host ports.
|
||||
# Every profile mounts exactly one volume — server_data/ — since every persistent thing
|
||||
# any of these processes own (mailgoserver's own data, rspamd's dbdir, redis's RDB
|
||||
# snapshot) lives under that single directory now; see the Dockerfiles' comments.
|
||||
#
|
||||
# All three default to the standard mail ports on the host (25/465/143/993) — the app
|
||||
# itself binds those directly (via a Linux file capability, not by running as root — see
|
||||
# the Dockerfiles), no port remapping needed — plus port 80 (only actually used while
|
||||
# Let's Encrypt HTTP-01 is enabled, see internal/webui's Let's Encrypt page) and the
|
||||
# app's own non-privileged web ports (5000/5001; put a reverse proxy or your own 80/443
|
||||
# mapping in front of those if you want the dashboard on standard web ports too — note
|
||||
# port 80 is already claimed here for HTTP-01 if you enable it). Only run one profile at
|
||||
# a time unless you've overridden the host ports for it (see .env.example) — they'd
|
||||
# otherwise all try to bind the same host ports.
|
||||
services:
|
||||
mailserver:
|
||||
build:
|
||||
@@ -28,7 +34,7 @@ services:
|
||||
- "${WEB_HTTP_PORT:-5000}:5000"
|
||||
- "${WEB_HTTPS_PORT:-5001}:5001"
|
||||
volumes:
|
||||
- mailserver-data:/app/data
|
||||
- mailserver-data:/app/server_data
|
||||
|
||||
mailserver-rspamd:
|
||||
build:
|
||||
@@ -46,10 +52,27 @@ services:
|
||||
- "${WEB_HTTP_PORT:-5000}:5000"
|
||||
- "${WEB_HTTPS_PORT:-5001}:5001"
|
||||
volumes:
|
||||
- mailserver-rspamd-data:/app/data
|
||||
- rspamd-data:/var/lib/rspamd
|
||||
- mailserver-rspamd-data:/app/server_data
|
||||
|
||||
mailserver-aio:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker-deploy/Dockerfile.aio
|
||||
container_name: mailgoserver-aio
|
||||
restart: unless-stopped
|
||||
profiles: ["all-in-one"]
|
||||
ports:
|
||||
- "${SMTP_PORT:-25}:25"
|
||||
- "${SMTP_TLS_PORT:-465}:465"
|
||||
- "${IMAP_PORT:-143}:143"
|
||||
- "${IMAP_TLS_PORT:-993}:993"
|
||||
- "${ACME_HTTP_PORT:-80}:80"
|
||||
- "${WEB_HTTP_PORT:-5000}:5000"
|
||||
- "${WEB_HTTPS_PORT:-5001}:5001"
|
||||
volumes:
|
||||
- mailserver-aio-data:/app/server_data
|
||||
|
||||
volumes:
|
||||
mailserver-data:
|
||||
mailserver-rspamd-data:
|
||||
rspamd-data:
|
||||
mailserver-aio-data:
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Starts rspamd in the background, then hands off to mailgoserver as the container's
|
||||
# main (foreground) process.
|
||||
# Starts rspamd (under its own unprivileged system user, created by the rspamd .deb's
|
||||
# own postinst) in the background, then hands off to mailgoserver — also dropped to its
|
||||
# own unprivileged user — as the container's main (foreground) process. This script
|
||||
# itself keeps running as root only long enough to create/chown the persistent
|
||||
# directories below and drop privileges for each child; neither rspamd nor mailgoserver
|
||||
# ever run as root themselves (mailgoserver's binary carries just the
|
||||
# CAP_NET_BIND_SERVICE file capability it needs for ports 25/465/80 — see the
|
||||
# Dockerfile — which a non-root exec of it still picks up).
|
||||
#
|
||||
# rspamd's own persistent state (fuzzy storage, DNS/maps cache — its "dbdir") is
|
||||
# redirected into server_data/rspamd so the whole container only has one thing to
|
||||
# volume-mount: server_data/, matching mailgoserver's own layout.
|
||||
mkdir -p /app/server_data/rspamd
|
||||
chown -R rspamd:rspamd /app/server_data/rspamd
|
||||
|
||||
# ponytail: no supervisor / restart-on-crash for rspamd here — if it dies mid-run, mail
|
||||
# keeps flowing without the extra scoring rather than the container crashing (the
|
||||
# built-in heuristic score in internal/mailstore/spam.go always runs regardless, and
|
||||
# CheckRspamd swallows connection errors rather than rejecting mail over them — see
|
||||
# internal/mailstore/rspamd.go). Add s6-overlay or supervisord if unattended rspamd
|
||||
# uptime matters more than that.
|
||||
rspamd -f &
|
||||
runuser -u rspamd -- rspamd -f &
|
||||
|
||||
# Give rspamd's normal worker a moment to bind :11333 before the first message can
|
||||
# possibly arrive — purely cosmetic, mailgoserver degrades gracefully either way.
|
||||
sleep 1
|
||||
|
||||
exec mailgoserver --host 0.0.0.0 "$@"
|
||||
exec runuser -u mailgoserver -- mailgoserver --host 0.0.0.0 "$@"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
Implemented for real 2026-08-16 — see `docker-deploy/`:
|
||||
|
||||
- `Dockerfile.aio` + `entrypoint-aio.sh` — mailgoserver + rspamd + redis in one
|
||||
container (redis backs rspamd's Bayes classifier + greylisting; mailgoserver itself
|
||||
has no use for redis, it's a single-instance app already backed by SQLite).
|
||||
- `docker-compose.yml`'s `all-in-one` profile: `docker compose --profile all-in-one up -d --build`
|
||||
- Single volume everywhere now (`server_data/`) — settings.ini, TLS certs, DKIM/mailstore
|
||||
keys, the SQLite DB, rspamd's dbdir, and redis's RDB snapshot all live under it (see
|
||||
`internal/config/config.go` + `main.go`'s server_data-relative defaults).
|
||||
- Nothing runs as root — each process (mailgoserver, rspamd, redis) has its own
|
||||
unprivileged user; mailgoserver's binary gets just `CAP_NET_BIND_SERVICE` via `setcap`
|
||||
instead of the whole container running as root.
|
||||
- Auto-updating installed packages on a schedule *inside* the running container was
|
||||
considered and left out (see `docker-deploy/README.md`'s "Keeping the base image and
|
||||
packages patched" section) — rebuilding the image periodically is the recommended
|
||||
approach instead; happy to build a scheduled rebuild/CI job if wanted.
|
||||
|
||||
The draft supervisord/Alpine sketch that used to be here is superseded by the above.
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# All-in-one: redis, then rspamd (which uses that redis for Bayes classification and
|
||||
# greylisting — see the local.d config baked into Dockerfile.aio), then mailgoserver as
|
||||
# the container's main (foreground) process. Each runs under its own unprivileged
|
||||
# system user (redis/rspamd from their own .deb postinst; mailgoserver's created in the
|
||||
# Dockerfile) — this script itself keeps running as root only long enough to
|
||||
# create/chown the persistent directories below and drop privileges for each child; see
|
||||
# Dockerfile/Dockerfile.aio's comments on why that's still safe for the one process
|
||||
# (mailgoserver) that actually needs a privileged port.
|
||||
#
|
||||
# Everything persistent — mailgoserver's own data, rspamd's dbdir, and redis's RDB
|
||||
# snapshot — lives under server_data/, so this whole bundle only has one thing to
|
||||
# volume-mount.
|
||||
mkdir -p /app/server_data/rspamd /app/server_data/redis
|
||||
chown -R rspamd:rspamd /app/server_data/rspamd
|
||||
chown -R redis:redis /app/server_data/redis
|
||||
|
||||
# Loopback-only (never published as a container port — see Dockerfile.aio) and no
|
||||
# password: the only two processes that can ever reach it are rspamd and mailgoserver,
|
||||
# both inside this same container's network namespace. If you publish 6379 yourself for
|
||||
# some reason, add `requirepass` here first.
|
||||
runuser -u redis -- redis-server --bind 127.0.0.1 -::1 --protected-mode yes \
|
||||
--dir /app/server_data/redis --daemonize no &
|
||||
|
||||
# Give redis a moment to start listening before rspamd (which needs it for Bayes/
|
||||
# greylist) connects.
|
||||
sleep 1
|
||||
|
||||
# ponytail: no supervisor / restart-on-crash for redis/rspamd here — if either dies
|
||||
# mid-run, mail keeps flowing on the built-in heuristic score alone rather than the
|
||||
# container crashing (internal/mailstore/spam.go always runs regardless, and
|
||||
# CheckRspamd swallows connection errors rather than rejecting mail over them — see
|
||||
# internal/mailstore/rspamd.go). Add s6-overlay or supervisord if unattended uptime for
|
||||
# the scoring stack matters more than that.
|
||||
runuser -u rspamd -- rspamd -f &
|
||||
|
||||
# Give rspamd's normal worker a moment to bind :11333 before the first message can
|
||||
# possibly arrive — purely cosmetic, mailgoserver degrades gracefully either way.
|
||||
sleep 1
|
||||
|
||||
exec runuser -u mailgoserver -- mailgoserver --host 0.0.0.0 "$@"
|
||||
@@ -72,8 +72,8 @@ var defaults = []struct {
|
||||
{"TLS", []defaultKV{
|
||||
{"", "", "TLS/SSL certificate configuration"},
|
||||
{"", "", "The 'custom' certificate: self-signed on first run, or your own uploaded cert/key"},
|
||||
{"TLS_CERT_FILE", "ssl_certs/server.crt", ""},
|
||||
{"TLS_KEY_FILE", "ssl_certs/server.key", ""},
|
||||
{"TLS_CERT_FILE", "server_data/ssl_certs/server.crt", ""},
|
||||
{"TLS_KEY_FILE", "server_data/ssl_certs/server.key", ""},
|
||||
{"", "", "Which certificate each TLS-serving listener uses: custom, letsencrypt_dns"},
|
||||
{"", "", "(the [LetsEncrypt] DNS-01 cert), or letsencrypt_http (the [LetsEncryptHTTP]"},
|
||||
{"", "", "HTTP-01 cert). Independent per listener - e.g. run the HTTP-01 cert on"},
|
||||
|
||||
@@ -151,7 +151,7 @@ func (a *App) uploadGCloudServiceAccount(w http.ResponseWriter, r *http.Request)
|
||||
writeJSON(w, http.StatusBadRequest, M{"status": "error", "message": "Expected a .json service account key file"})
|
||||
return
|
||||
}
|
||||
acmeDir := filepath.Join(filepath.Dir(a.ConfigPath), "server_data", "acme")
|
||||
acmeDir := filepath.Join(a.Root, "server_data", "acme")
|
||||
os.MkdirAll(acmeDir, 0o755)
|
||||
filePath := filepath.Join(acmeDir, fmt.Sprintf("gcloud-sa-%d.json", time.Now().Unix()))
|
||||
out, err := os.Create(filePath)
|
||||
|
||||
@@ -163,7 +163,7 @@ func (a *App) uploadTLSFile(w http.ResponseWriter, r *http.Request, field, force
|
||||
writeJSON(w, http.StatusBadRequest, M{"status": "error", "message": "Invalid file extension"})
|
||||
return
|
||||
}
|
||||
sslDir := filepath.Join(filepath.Dir(a.ConfigPath), "ssl_certs")
|
||||
sslDir := filepath.Join(a.Root, "server_data", "ssl_certs")
|
||||
os.MkdirAll(sslDir, 0o755)
|
||||
filePath := filepath.Join(sslDir, fmt.Sprintf("server%d.%s", time.Now().Unix(), forcedExt))
|
||||
out, err := os.Create(filePath)
|
||||
@@ -192,7 +192,7 @@ func (a *App) testAttachmentsPath(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(filepath.Dir(a.ConfigPath), path)
|
||||
path = filepath.Join(a.Root, path)
|
||||
}
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
writeJSON(w, http.StatusOK, M{"success": false, "message": err.Error()})
|
||||
|
||||
+10
-4
@@ -31,6 +31,13 @@ type App struct {
|
||||
Relay *relay.Relay // used by the webmail client's compose/send (see webmail_compose.go)
|
||||
Cfg *ini.File
|
||||
ConfigPath string
|
||||
// Root is the app's working directory at startup (os.Getwd() in main.go) — every
|
||||
// relative path this package resolves on its own (uploaded cert/key files, the
|
||||
// ACME data dir, the attachments-path tester) is joined against this, not against
|
||||
// ConfigPath's own directory, so it always matches how main.go resolves the same
|
||||
// kind of path (mailstore, attachments, TLS certs) regardless of where -config
|
||||
// happens to point.
|
||||
Root string
|
||||
Logger *toolbox.Logger
|
||||
SMTPUp func() bool // reports whether the SMTP listeners are currently running
|
||||
|
||||
@@ -58,12 +65,11 @@ type App struct {
|
||||
// filesystem (embed.go), not disk, so no directory paths are needed for them.
|
||||
// appSecret is loaded by the caller via LoadOrCreateAppSecret, mirroring how
|
||||
// mailstore's master key is loaded in main.go and threaded in rather than resolved
|
||||
// internally (both are file paths relative to the app's root working directory,
|
||||
// which this package doesn't otherwise know).
|
||||
func New(database *db.DB, dkimMgr *dkim.Manager, mstore *mailstore.Store, acmeMgr, acmeHTTPMgr *acmecert.Manager, relayer *relay.Relay, cfg *ini.File, configPath string, logger *toolbox.Logger, smtpUp func() bool, appSecret []byte) (*App, error) {
|
||||
// internally (both are file paths relative to root — see the App.Root doc comment).
|
||||
func New(database *db.DB, dkimMgr *dkim.Manager, mstore *mailstore.Store, acmeMgr, acmeHTTPMgr *acmecert.Manager, relayer *relay.Relay, cfg *ini.File, configPath, root string, logger *toolbox.Logger, smtpUp func() bool, appSecret []byte) (*App, error) {
|
||||
trustedProxies := parseTrustedProxies(cfg.Section("Server").Key("trusted_proxies").MustString(""), logger)
|
||||
a := &App{
|
||||
DB: database, DKIM: dkimMgr, Mailstore: mstore, ACME: acmeMgr, ACMEHTTP: acmeHTTPMgr, Relay: relayer, Cfg: cfg, ConfigPath: configPath, Logger: logger, SMTPUp: smtpUp,
|
||||
DB: database, DKIM: dkimMgr, Mailstore: mstore, ACME: acmeMgr, ACMEHTTP: acmeHTTPMgr, Relay: relayer, Cfg: cfg, ConfigPath: configPath, Root: root, Logger: logger, SMTPUp: smtpUp,
|
||||
pgpKeys: newPGPKeyCache(), trustedProxies: trustedProxies, loginLimiter: newIPRateLimiter(20, time.Minute), appSecret: appSecret,
|
||||
}
|
||||
if err := a.loadTemplates(); err != nil {
|
||||
|
||||
@@ -140,7 +140,7 @@ func newTestApp(t *testing.T) *App {
|
||||
if err != nil {
|
||||
t.Fatalf("LoadOrCreateAppSecret: %v", err)
|
||||
}
|
||||
app, err := New(database, dkimMgr, mstore, acmeMgr, acmeHTTPMgr, relayer, cfg, configPath, toolbox.GetLogger("test"), func() bool { return true }, appSecret)
|
||||
app, err := New(database, dkimMgr, mstore, acmeMgr, acmeHTTPMgr, relayer, cfg, configPath, dir, toolbox.GetLogger("test"), func() bool { return true }, appSecret)
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ func main() {
|
||||
port := flag.Int("port", 5000, "Web server port")
|
||||
debug := flag.Bool("debug", false, "Enable debug mode")
|
||||
initData := flag.Bool("init-data", false, "Initialize sample data and exit")
|
||||
configFlag := flag.String("config", "settings.ini", "Configuration file path")
|
||||
// Defaults inside server_data/ (along with the DB, mailstore, keys, and certs
|
||||
// generated below) so a Docker deployment only needs to volume-mount that one
|
||||
// directory to persist everything — nothing is left sitting next to the binary.
|
||||
configFlag := flag.String("config", "server_data/settings.ini", "Configuration file path")
|
||||
flag.Parse()
|
||||
portFlagSet := false
|
||||
flag.Visit(func(f *flag.Flag) {
|
||||
@@ -120,10 +123,10 @@ func main() {
|
||||
// manager has obtained anything yet.
|
||||
customCertFile := absPath(root, cfg.Section("TLS").Key("TLS_CERT_FILE").String())
|
||||
customKeyFile := absPath(root, cfg.Section("TLS").Key("TLS_KEY_FILE").String())
|
||||
dnsCertFile := absPath(root, "ssl_certs/letsencrypt_dns.crt")
|
||||
dnsKeyFile := absPath(root, "ssl_certs/letsencrypt_dns.key")
|
||||
httpCertFile := absPath(root, "ssl_certs/letsencrypt_http.crt")
|
||||
httpKeyFile := absPath(root, "ssl_certs/letsencrypt_http.key")
|
||||
dnsCertFile := absPath(root, "server_data/ssl_certs/letsencrypt_dns.crt")
|
||||
dnsKeyFile := absPath(root, "server_data/ssl_certs/letsencrypt_dns.key")
|
||||
httpCertFile := absPath(root, "server_data/ssl_certs/letsencrypt_http.crt")
|
||||
httpKeyFile := absPath(root, "server_data/ssl_certs/letsencrypt_http.key")
|
||||
for _, pair := range [][2]string{
|
||||
{customCertFile, customKeyFile}, {dnsCertFile, dnsKeyFile}, {httpCertFile, httpKeyFile},
|
||||
} {
|
||||
@@ -300,7 +303,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
app, err := webui.New(database, dkimMgr, mstore, acmeMgr, acmeHTTPMgr, relayer, cfg, configPath, toolbox.GetLogger("web"), smtpRunning.Load, appSecret)
|
||||
app, err := webui.New(database, dkimMgr, mstore, acmeMgr, acmeHTTPMgr, relayer, cfg, configPath, root, toolbox.GetLogger("web"), smtpRunning.Load, appSecret)
|
||||
if err != nil {
|
||||
logger.Error("init web UI: %v", err)
|
||||
os.Exit(1)
|
||||
|
||||
Reference in New Issue
Block a user