a7a5ad3f91
Add/list/delete of sending domains with per-domain DKIM keys and the OpenDKIM tables that drive signing (spec 6, 7.2.2-4, 7.2.10). internal/domain: - Pure-Go RSA-2048 keygen; PKCS#1 PEM written atomically at 0640; the published DNS TXT record is derived from the key on disk (single source of truth) rather than persisted. No os/exec for key generation. - KeyTable/SigningTable fully regenerated from the registry on every add/delete (idempotent), written atomically; SigningTable via refile: with *@domain, KeyTable with absolute key paths. Table writer refuses any unsafe character as a backstop (spec 7.6.4). - Reload without root: the unprivileged panel signals OpenDKIM through supervisord (`supervisorctl signal USR1 opendkim`, fixed args, no shell, no user input — spec 7.6.3). An existing key is reused, never overwritten, so re-adding a domain keeps its published DNS valid. - Service orchestrates registry -> key -> table rebuild -> reload, with rollback of the row if a downstream step fails; delete cascades apps via the DB FK and removes the key + table entries. Infra: - Shared `selfpost` group bridges panel (writes keys) and opendkim (reads them); /data/opendkim is setgid so panel-created files inherit the group, keys are 0640, RequireSafeKeys is disabled by design. - opendkim.conf moves from verify-only (Mode v) to signing (Mode s). - entrypoint.sh normalises the DKIM tree on every start (ownership, setgid, perms, empty tables before opendkim starts) — self-healing after a restore. - supervisord control socket opened to the `selfpost` group so the panel can request the reload. web/store: - Strict domain-name validation (whitelist [a-z0-9.-], DNS shape, >=2 labels), lower-case normalisation (spec 7.6.2). - Domain queries with application counts; delete relies on ON DELETE CASCADE. Dashboard lists domains + add form; domain page shows the DKIM record; a dedicated confirm page warns about the app cascade before deletion (spec 7.2.4); manual reload button (spec 7.2.12, OpenDKIM side; Postfix reload lands in Phase 5). - Authenticated routes moved to a sub-mux using Go 1.22 method/wildcard patterns. Tests: validateDomain, DKIM keygen/record roundtrip, table rendering + injection-safety, key reuse, store cascade. Verified on the dev server: gofmt/vet/test green, image builds, container e2e (add/delete a domain, DKIM record shown, OpenDKIM reads panel keys and reloads, keys and tables persist across a restart). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
3.6 KiB
Docker
92 lines
3.6 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. go.sum arrived in Phase 2 with the
|
|
# SQLite driver and bcrypt.
|
|
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 codeberg.org/mix/selfpost/internal/buildinfo.Version=${VERSION}" \
|
|
-o /out/panel ./cmd/panel \
|
|
&& go build -trimpath \
|
|
-ldflags "-X codeberg.org/mix/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 in Phase 5.
|
|
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 \
|
|
supervisor \
|
|
logrotate \
|
|
ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Postfix logs to a plain file (via its built-in postlogd) so the panel's
|
|
# log-tailer has something to follow and container logging works without syslog.
|
|
RUN postconf -e "maillog_file=/var/log/mail.log"
|
|
|
|
# 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 two unprivileged services (spec 6): the panel
|
|
# generates per-domain DKIM keys and rewrites the OpenDKIM tables, while
|
|
# OpenDKIM (a different user) must read them. Membership in this group — plus
|
|
# setgid dirs under /data/opendkim (set up in entrypoint.sh) — lets OpenDKIM
|
|
# read the panel-owned keys, and lets the panel reach the supervisor control
|
|
# socket to signal an OpenDKIM reload without any process running as root.
|
|
RUN groupadd --system selfpost \
|
|
&& usermod -aG selfpost panel \
|
|
&& usermod -aG selfpost opendkim
|
|
|
|
# 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
|
|
|
|
COPY build/opendkim.conf /etc/opendkim.conf
|
|
COPY build/postfix-wrapper.sh /usr/local/bin/postfix-wrapper.sh
|
|
COPY build/crashexit.py /usr/local/bin/crashexit.py
|
|
COPY build/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
COPY build/supervisord.conf /etc/supervisor/supervisord.conf
|
|
RUN chmod +x /usr/local/bin/postfix-wrapper.sh /usr/local/bin/crashexit.py /usr/local/bin/entrypoint.sh
|
|
|
|
# 8080 panel; 25 outbound; 465/587 inbound submission (used from Phase 5).
|
|
EXPOSE 8080 25 465 587
|
|
|
|
# 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"]
|