Phase 10: deployment (Apache compose + proxy fragments, CI release) + docs
- deploy/docker-compose.yml: pinned-tag ghcr image, hardened (cap_drop ALL + minimal cap_add, no-new-privileges, panel bound to 127.0.0.1 only). Apache itself runs on the host (spec 10.5), fragment at deploy/apache/. - Alternative reverse-proxy fragments: nginx (+certbot sidecar), Caddy (automatic ACME), Traefik (+acme.json PEM extraction script). - .github/workflows/release.yml: tag-triggered ghcr.io publish, version piped from the git tag into both the binary ldflags and the image tag (spec 10.1). - Closed a gap from Phase 1: logrotate was installed but never invoked; wired up build/logrotate-mail.conf + logrotate-loop.sh + a supervisor program (copytruncate, since postlogd holds mail.log open with nothing to signal on rotation). - README rewritten: site requirements checklist, reverse-proxy comparison, DNS setup (server- vs domain-level), IP warmup, backup/restore vs domain export/import, fixed-tag rationale, machine requirements. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
|
||||
# Alternative reverse-proxy: Traefik (spec 10.3). Traefik does NOT write plain
|
||||
# PEM files — everything (cert + key) lives bundled inside acme.json. SelfPost
|
||||
# needs separate PEM files for Postfix, so an extraction step is required; see
|
||||
# extract-cert.sh in this directory and the cron/systemd-timer note below.
|
||||
#
|
||||
# Merge with the base file — run this from the deploy/ directory (Compose
|
||||
# resolves every relative path in both files against the directory of the
|
||||
# FIRST -f file, i.e. deploy/, which is why paths below are ./traefik/...):
|
||||
#
|
||||
# docker compose -f docker-compose.yml -f traefik/docker-compose.traefik.yml up -d
|
||||
|
||||
services:
|
||||
selfpost:
|
||||
ports: !override
|
||||
- "465:465"
|
||||
- "587:587"
|
||||
volumes: !override
|
||||
- ./data:/data
|
||||
# Populated by extract-cert.sh from traefik's acme.json — see below.
|
||||
- ./traefik/extracted-certs:/etc/postfix/tls:ro
|
||||
labels:
|
||||
# Traefik only proxies the panel; it never sees the mail ports.
|
||||
# These live on the selfpost service (not traefik's) because the
|
||||
# docker provider reads routing labels off the container being routed
|
||||
# to, not off traefik itself.
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.selfpost.rule=Host(`mail.example.com`)
|
||||
- traefik.http.routers.selfpost.entrypoints=websecure
|
||||
- traefik.http.routers.selfpost.tls.certresolver=le
|
||||
- traefik.http.services.selfpost.loadbalancer.server.port=8080
|
||||
|
||||
traefik:
|
||||
image: traefik:v3.1
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- selfpost
|
||||
command:
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --certificatesresolvers.le.acme.email=you@example.com
|
||||
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
|
||||
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./traefik/letsencrypt:/letsencrypt
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Extracts a PEM cert/key pair for one domain out of Traefik's acme.json
|
||||
# (spec 10.3: "Traefik — сертификаты в acme.json, потребуется шаг извлечения
|
||||
# PEM"). Run this on the host, after Traefik has issued or renewed the
|
||||
# certificate, and again on a schedule (cron/systemd timer) since acme.json
|
||||
# is not itself watched by SelfPost/Postfix.
|
||||
#
|
||||
# Requires jq. Usage: ./extract-cert.sh <acme.json path> <domain> <output dir>
|
||||
set -eu
|
||||
|
||||
ACME_JSON="${1:?path to acme.json}"
|
||||
DOMAIN="${2:?domain name, e.g. mail.example.com}"
|
||||
OUT_DIR="${3:?output directory, e.g. ./extracted-certs}"
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
jq -r --arg domain "$DOMAIN" '
|
||||
.le.Certificates[]
|
||||
| select(.domain.main == $domain)
|
||||
| .certificate' "$ACME_JSON" | base64 -d > "$OUT_DIR/fullchain.pem"
|
||||
|
||||
jq -r --arg domain "$DOMAIN" '
|
||||
.le.Certificates[]
|
||||
| select(.domain.main == $domain)
|
||||
| .key' "$ACME_JSON" | base64 -d > "$OUT_DIR/privkey.pem"
|
||||
|
||||
chmod 0640 "$OUT_DIR/fullchain.pem" "$OUT_DIR/privkey.pem"
|
||||
echo "extracted $DOMAIN to $OUT_DIR/{fullchain,privkey}.pem"
|
||||
Reference in New Issue
Block a user