[1.2.0] The second MINOR after 1.0.0: domain administrators with per-domain scope, a panel visual refresh on the SelfPost palette, and refinements to navigation and the send log. Upgrading runs one SQLite migration (`0005_panel_users`); the single administrator becomes a global user. Added: - panel: **domain-admin role** — global administrators manage panel users and Fixed: - Docker build: `LICENSE` is no longer excluded by `.dockerignore`, so the - panel: on signed-in pages with a narrow card (**Settings**, the user form) Changed: - panel: navigation session icons are distinct — **Settings** uses a gear, - panel: `/` redirects domain administrators to `/domains`; global users still - panel: **Sign out** in the navigation column uses the same type size and - panel: **visual style** brought in line with the SelfPost mark — brick accent - panel: the send log's **status is a badge**, in the same ok/warn/error/unknown … (1 more in CHANGELOG) [1.2.1] Panel refinements after 1.2.0: navigation icons, status-badge centreing, drill-down back-link placement, and safeguards for the sole global administrator. Upgrading is a tag bump; no migration. Fixed: - panel: status badge text sat low in the box (and below the heading or label - panel: the **Users** navigation icon was two full silhouettes with staggered - panel: the **Settings** navigation icon was a sun-with-rays (circle plus - panel: the user create/edit form placed «Back to users» at the bottom of the Changed: - panel: the user edit form disables role change and delete for the only global - panel: the user create/edit form hides **Assigned domains** when the role is - panel: **Settings** shows panel credentials and DMARC aggregate reports side [1.2.2] Status page layout after 1.2.1: paired cards in a wide column, denser machine and check copy, and a user-form checkbox fix. Upgrading is a tag bump; no migration. Changed: - panel: **Status** is wide again so paired cards fill the column. Layout: Fixed: - panel: cards inside `.split` used `margin: 0 auto`, which in a CSS grid - panel: on the user create/edit form, **Assigned domains** checkboxes stacked [1.2.3] Domain detail layout and panel polish after 1.2.2. Upgrading is a tag bump; no migration. Changed: - panel: the domain detail page is wide with paired cards (DKIM+SPF ‖ DMARC; - panel: Domains list — **Add domain** sits beside the domain field; the - panel: page URLs, browser titles, and headings are aligned — **Settings** is Fixed: - image: `mail.log` rotation no longer silently stops when the build context [1.2.4] Level-2 rate-limit semantics inverted after 1.2.3, plus a small DNS field height fix. Upgrading is a tag bump; no migration. Changed: - rate limiting (level 2): domain ceilings apply to every client IP (no IP Fixed: - panel: on the domain DNS status grid, the Type (TXT) field height matches [1.2.5] Rate-limit form polish after 1.2.4. Upgrading is a tag bump; no migration. Changed: - panel: rate-limit forms refined — level-1 backstop as muted copy on the Squashed CHANGELOG sections: [1.2.0], [1.2.1], [1.2.2], [1.2.3], [1.2.4], [1.2.5]. Full detail: CHANGELOG.md (1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5).
119 lines
5.2 KiB
Docker
119 lines
5.2 KiB
Docker
# syntax=docker/dockerfile:1
|
|
#
|
|
# SelfPost — single Debian-slim image running postfix + opendkim + panel under
|
|
# supervisord (spec 4). Build from the repository root:
|
|
#
|
|
# docker build -f build/Dockerfile -t selfpost:dev --build-arg VERSION=dev .
|
|
|
|
# ---- build stage -------------------------------------------------------------
|
|
FROM golang:1.26-bookworm AS build
|
|
|
|
WORKDIR /src
|
|
|
|
# Version stamped into both binaries; MUST match the image tag (spec 7.5.A).
|
|
ARG VERSION=dev
|
|
|
|
# Module metadata first for layer caching.
|
|
COPY go.mod go.sum ./
|
|
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:bookworm-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.
|
|
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 \
|
|
opendkim \
|
|
opendkim-tools \
|
|
sasl2-bin \
|
|
libsasl2-modules \
|
|
db-util \
|
|
supervisor \
|
|
logrotate \
|
|
ca-certificates \
|
|
curl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Unprivileged user for the panel process (spec 7.6.8).
|
|
RUN useradd --system --no-create-home --shell /usr/sbin/nologin panel
|
|
|
|
# Shared group bridging the unprivileged services (spec 5.1, 6): the panel
|
|
# generates per-domain DKIM keys, application SASL accounts (sasldb2) and the
|
|
# Postfix sender map, while OpenDKIM and Postfix (different users) must read
|
|
# them. Membership in this group — plus setgid dirs under /data (set up in
|
|
# entrypoint.sh) — lets OpenDKIM read the panel-owned keys and lets Postfix read
|
|
# the sasldb2/sender map, and lets the panel reach the supervisor control socket
|
|
# to signal OpenDKIM/Postfix reloads without any process running as root.
|
|
RUN groupadd --system selfpost \
|
|
&& usermod -aG selfpost panel \
|
|
&& usermod -aG selfpost opendkim \
|
|
&& usermod -aG selfpost postfix
|
|
|
|
# The panel reads the mail queue with `postqueue -p` (spec 7.2.11, 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. postdrop membership is
|
|
# read-side only: it does not let the panel bypass any Postfix restriction that
|
|
# a local user does not already have through the world-executable sendmail.
|
|
RUN usermod -aG postdrop panel
|
|
|
|
# Runtime directories: milter sockets and the consolidated persistent root.
|
|
RUN mkdir -p /run/opendkim /run/selfpost /data \
|
|
&& chown opendkim:opendkim /run/opendkim \
|
|
&& 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.
|
|
COPY --chmod=0644 LICENSE NOTICE /usr/share/doc/selfpost/
|
|
|
|
# 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/plans/logrotate-mode.md.
|
|
COPY --chmod=0644 build/opendkim.conf /etc/opendkim.conf
|
|
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-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)
|
|
# plus the panel on 8080. Outbound delivery dials remote MXs on 25 as a client,
|
|
# which needs no inbound listener or EXPOSE.
|
|
EXPOSE 8080 465 587
|
|
|
|
# Liveness probe: panel HTTP plus mail-path processes (opendkim, 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 (spec 4).
|
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|