test / test (push) Waiting to run
Owner, 2026-10-18: "Надо почистить документы от устаревшего и сделанного." - Deleted, shipped: plans/delivery-log-storage, from-header-binding, inbound-antispam-panel, inbound-upstream-result, panel-dkim-signing, panel-notifications, and the own-mta-builds analysis (its three rows shipped as 2.6.1, 2.7.0, 2.8.0). What outlived them moved first: when a filter-list counter is worth a migration, and the notifications' "when" table, into architecture.md; the sender check's own-process trigger into security.md. Code comments that cited them point at architecture.md or security.md; CHANGELOG keeps their names, unlinked. - Kept, marked closed: audit-2026-10-12, assumptions-2026-10-14 and security-audit-2026-10-17 - the code cites their finding ids in 50-84 files. Their file:line refs are of the tree each audited. - roadmap.md: open work only - stage H of the security audit (the owner's), go-milter-upstream (the owner posts), and queue-table, which the redesign left undecided and only its own shipped section still named; Dropped and Ideas as they were. 2.x versioning instead of 1.x. - development.md: the 47-row planned-version-cuts table (history, in the CHANGELOG) gone; a stage's last step now removes its roadmap item and plan. Docs map lists the closed audits. - panel-redesign.md: the done checklist out; Room for roadmap items says which drawn screens are unbuilt and why. - Stale facts fixed: schema-migrations.md and architecture.md at schema 14 (0013's users columns and user_links, 0014's secure TLS mode); security.md's accepted risk "profile saved without the password" - the e-mail asks for it since 2.14.0. Tests: go build, go vet; go test of cmd and the touched internal packages pass except logtail's three Windows-bound rotation tests (rename of an open file; CI is Linux). Only comments changed in Go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
205 lines
11 KiB
Docker
205 lines
11 KiB
Docker
# syntax=docker/dockerfile:1
|
|
#
|
|
# SelfPost — single Debian-slim image running postfix + panel under
|
|
# supervisord. Build from the repository root:
|
|
#
|
|
# docker build -f build/Dockerfile -t selfpost:dev --build-arg VERSION=dev .
|
|
|
|
# ---- build stage -------------------------------------------------------------
|
|
FROM golang:1.26-trixie AS build
|
|
|
|
WORKDIR /src
|
|
|
|
# Version stamped into both binaries; MUST match the image tag.
|
|
ARG VERSION=dev
|
|
|
|
# Module metadata first for layer caching. third_party/go-milter is a module
|
|
# go.mod replaces go-milter with (third_party/go-milter/PATCHES.md): the
|
|
# download needs it.
|
|
COPY go.mod go.sum ./
|
|
COPY third_party ./third_party
|
|
RUN go mod download
|
|
|
|
COPY cmd ./cmd
|
|
COPY internal ./internal
|
|
|
|
ENV CGO_ENABLED=0
|
|
RUN go vet ./... \
|
|
&& go build -trimpath \
|
|
-ldflags "-X github.com/mixeme/selfpost/internal/buildinfo.Version=${VERSION}" \
|
|
-o /out/panel ./cmd/panel \
|
|
&& go build -trimpath \
|
|
-ldflags "-X github.com/mixeme/selfpost/internal/buildinfo.Version=${VERSION}" \
|
|
-o /out/selfpost-backup ./cmd/selfpost-backup
|
|
|
|
# ---- runtime stage -----------------------------------------------------------
|
|
FROM debian:trixie-slim AS runtime
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
|
|
# Preseed Postfix so its install is non-interactive and yields a working
|
|
# main.cf. The real relay configuration is generated by the panel.
|
|
# (postfix 3.10.13-0+deb13u1, debian/templates:14 and :50:
|
|
# postfix/main_mailer_type and postfix/mailname are the questions the install
|
|
# asks; debian/postfix.postinst:156-171 writes main.cf for any answer but "No
|
|
# configuration".)
|
|
# openssl is named because postfix-cert-reload.sh runs it, not left to arrive
|
|
# with ca-certificates. python3 arrives with supervisor (supervisor 4.2.5-3,
|
|
# debian/control:29, ${python3:Depends}) - crashexit.py. sasl2-bin brings
|
|
# saslpasswd2 (cyrus-sasl2 2.1.28+dfsg1-9, debian/rules:207,
|
|
# debian/sasl2-bin.install:3, usr/sbin) and depends on db-util
|
|
# (debian/control:36); libsasl2-modules brings the PLAIN and LOGIN plugins
|
|
# (debian/libsasl2-modules.install:4 and :6); the sasldb auxprop plugin is in
|
|
# libsasl2-modules-db (debian/libsasl2-modules-db.install:1), a Depends of
|
|
# libsasl2-2 (debian/control:65). db-util is named for /usr/bin/db_dump, which
|
|
# the panel runs (internal/app/sasl.go); that db-util ships that path on
|
|
# trixie is not in a source read here - the image build and the e2e sasldb
|
|
# test say whether it does.
|
|
# No DKIM package is installed: the panel signs itself (internal/milter,
|
|
# sign.go) and makes the keys in Go (internal/domain/dkim.go); OpenDKIM left
|
|
# the image in 2.8.0 (docs/architecture.md § DKIM signing).
|
|
# systemd-standalone-sysusers is named so that cron-daemon-common, which
|
|
# logrotate brings (logrotate 3.22.0-1, debian/control: "Depends: cron |
|
|
# anacron | cron-daemon | systemd-sysv"), creates its group with it rather
|
|
# than with the whole of systemd, the first alternative of its "Depends:
|
|
# systemd | systemd-standalone-sysusers | systemd-sysusers" (dpkg -s in the
|
|
# image, 2026-10-15). Nothing here runs systemd.
|
|
# tzdata is named for TZ (local-time): the zone of every time the panel shows
|
|
# and of the stamps Postfix writes in mail.log. supervisor's python3.13 brings
|
|
# it today (apt-cache rdepends --installed tzdata in the image, 2026-10-17);
|
|
# a TZ the image did not have would leave both in UTC without a word.
|
|
RUN echo "postfix postfix/mailname string localhost" | debconf-set-selections \
|
|
&& echo "postfix postfix/main_mailer_type string Internet Site" | debconf-set-selections \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
postfix \
|
|
sasl2-bin \
|
|
libsasl2-modules \
|
|
db-util \
|
|
supervisor \
|
|
logrotate \
|
|
ca-certificates \
|
|
openssl \
|
|
curl \
|
|
systemd-standalone-sysusers \
|
|
tzdata \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Debian's postfix runs its chroot sync on every "postfix check" and "postfix
|
|
# start-fg" (postfix 3.10.13-0+deb13u1, debian/patches/
|
|
# debian-run-configure-instance-from-create-missing.patch); with no service
|
|
# chrooted (build/postfix-config.sh) it would only copy /etc/resolv.conf and
|
|
# the like into the queue under /data on each start. SYNC_CHROOT empty turns
|
|
# it off (debian/configure-instance.in:22-24 and :44; debian/README.Debian:136-138).
|
|
RUN printf 'SYNC_CHROOT=\n' > /etc/default/postfix
|
|
|
|
# Unprivileged user for the panel process, with ids of its own outside the
|
|
# system range for the same reason as selfpost's below: its files under
|
|
# /data show on the host by number, and a number from that range is a
|
|
# system group of the host's too (useradd gave the panel group 998 in the
|
|
# trixie image, systemd-network on the test server's host; bookworm's
|
|
# 999:999 was the host's systemd-journal group). The entrypoint gives /data
|
|
# back to it by name on every start, so files of the old ids follow.
|
|
RUN groupadd --system --gid 10999 panel && useradd --system --uid 10999 --gid panel --no-create-home --shell /usr/sbin/nologin panel
|
|
|
|
# Shared group bridging the unprivileged services: the panel writes the
|
|
# application SASL accounts (sasldb2) and the Postfix sender map and opens the
|
|
# milter sockets, while Postfix (another user) must read and reach them.
|
|
# Membership in this group - plus setgid dirs under /data and /run/selfpost
|
|
# (set up in entrypoint.sh) - lets Postfix read the sasldb2/sender map and
|
|
# connect to the panel's milters. The DKIM keys are the panel's alone. (The
|
|
# supervisor control socket has a group of its own, `supervisor`, below.)
|
|
# The id is fixed, because the host saw it: until 2.14.4 an operator gave the
|
|
# TLS private key on the host to this group by number - now to backupkey,
|
|
# below, which Postfix is not in - and a key given then still names it. It
|
|
# lies outside the
|
|
# range the distribution hands out to system groups, 100-999 (adduser 3.152,
|
|
# /etc/adduser.conf: "Default: FIRST_SYSTEM_GID=100, LAST_SYSTEM_GID=999";
|
|
# passwd 1:4.17.4-2, /etc/login.defs: SYS_GID_MIN 101, SYS_GID_MAX 999 - both
|
|
# read in the image, 2026-10-15), so no package can take it, and far above
|
|
# the 1000 a host starts its users' groups at, so a host is unlikely to have
|
|
# a group of that number that would read the key too. Until 2.7.0 it was
|
|
# 998, inside that range: in the trixie image systemd took 998 for
|
|
# systemd-network and the build failed, and on the test server's host 998
|
|
# is systemd-network too (seen 2026-10-15).
|
|
# groupadd(8) refuses a --gid already in use, so an id taken after all would
|
|
# fail this build rather than share the group.
|
|
RUN groupadd --system --gid 10998 selfpost \
|
|
&& usermod -aG selfpost panel \
|
|
&& usermod -aG selfpost postfix
|
|
|
|
# The host's TLS private key is given to this group, not to selfpost, so that
|
|
# the panel can put it into a full backup (guide § Taking a full backup) and
|
|
# no Postfix daemon can read it: postfix is in selfpost, and a memory-safety
|
|
# bug in an smtpd reached from the internet would otherwise yield the key of
|
|
# the mail and of the panel (security audit 2026-10-17, I1). Postfix itself
|
|
# serves a root-only copy (postfix-config.sh). The panel is the one member.
|
|
# Fixed for the same reason as selfpost's, next to it.
|
|
RUN groupadd --system --gid 10997 backupkey \
|
|
&& usermod -aG backupkey panel
|
|
|
|
# The panel reads the mail queue with `postqueue -p` (and the status page's
|
|
# queue card). postqueue is setgid postdrop, which normally gives it the group
|
|
# needed to reach Postfix's showq socket - but the documented deployment runs
|
|
# with `no-new-privileges`, which disables setgid transitions, so the panel
|
|
# would always see "Permission denied". Making `panel` a real member of postdrop
|
|
# grants the same access without relying on a setgid escalation the hardening
|
|
# deliberately forbids. The same membership is what lets the panel submit its
|
|
# own notifications with sendmail(1) - postdrop, too, is setgid and would fail
|
|
# to write the maildrop directory (internal/notify/mailer.go). It does not let
|
|
# the panel bypass any Postfix restriction that a local user does not already
|
|
# have through the world-executable sendmail.
|
|
# (Postfix 3.10.13, conf/postfix-files:140-141: postdrop and postqueue are
|
|
# root:$setgid_group 2755; Debian sets them root:postdrop 02555, postfix
|
|
# 3.10.13-0+deb13u1, debian/postfix.postinst:134-141. Linux,
|
|
# Documentation/userspace-api/no_new_privs.rst:
|
|
# with no_new_privs, execve() no longer changes the gid for a setgid file.)
|
|
RUN usermod -aG postdrop panel
|
|
# The supervisord control socket's group (supervisord.conf, [unix_http_server]):
|
|
# the panel alone - not Postfix's user, which shares `selfpost` for the files
|
|
# under /data.
|
|
RUN groupadd --system supervisor \
|
|
&& usermod -aG supervisor panel
|
|
|
|
# Runtime directories: milter sockets and the consolidated persistent root.
|
|
RUN mkdir -p /run/selfpost /data \
|
|
&& chown panel:panel /run/selfpost /data
|
|
|
|
COPY --from=build /out/panel /usr/local/bin/panel
|
|
COPY --from=build /out/selfpost-backup /usr/local/bin/selfpost-backup
|
|
|
|
# Licence text shipped with the image (AGPL-3.0 conveyance). The panel also
|
|
# serves the same text at /license from an embedded copy. OFL.txt is the SIL
|
|
# Open Font License 1.1 that must travel with the IBM Plex WOFF2 files
|
|
# embedded in the panel binary (NOTICE).
|
|
COPY --chmod=0644 LICENSE NOTICE /usr/share/doc/selfpost/
|
|
COPY --chmod=0644 internal/web/view/static/OFL.txt /usr/share/doc/selfpost/OFL.txt
|
|
|
|
# File modes are pinned here so a build context copied from a checkout without
|
|
# POSIX permissions (e.g. Windows tar sync) cannot land group-writable config
|
|
# that logrotate would silently ignore — see docs/development.md § Building
|
|
# binaries and the image.
|
|
COPY --chmod=0644 build/logrotate-mail.conf /etc/logrotate.d/mail
|
|
COPY --chmod=0755 build/postfix-wrapper.sh /usr/local/bin/postfix-wrapper.sh
|
|
COPY --chmod=0755 build/postfix-reload.sh /usr/local/bin/postfix-reload.sh
|
|
COPY --chmod=0755 build/postfix-config.sh /usr/local/bin/postfix-config.sh
|
|
COPY --chmod=0755 build/postfix-cert-reload.sh /usr/local/bin/postfix-cert-reload.sh
|
|
COPY --chmod=0755 build/logrotate-loop.sh /usr/local/bin/logrotate-loop.sh
|
|
COPY --chmod=0755 build/crashexit.py /usr/local/bin/crashexit.py
|
|
COPY --chmod=0755 build/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
COPY --chmod=0644 build/supervisord.conf /etc/supervisor/supervisord.conf
|
|
|
|
# Published submission ports: 465 (smtps, primary) and 587 (submission, optional),
|
|
# inbound SMTP on 25 when INBOUND_RELAY_ENABLE=true, plus the panel on 8080.
|
|
# Outbound delivery dials remote MXs on 25 as a client even when inbound is off.
|
|
EXPOSE 8080 465 587 25
|
|
|
|
# Liveness probe: panel HTTP plus mail-path processes (panel, postfix).
|
|
# Does not verify TLS, DNS, or end-to-end delivery — see docs/guide.md Operations.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \
|
|
CMD curl -fsS http://127.0.0.1:8080/healthz || exit 1
|
|
|
|
# The entrypoint fixes /data ownership (bind mount) as root, then execs
|
|
# supervisord, which becomes PID 1 and owns process supervision.
|
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|