; SelfPost process supervision. ; ; Start ORDER is enforced by priority=: OpenDKIM, then the panel (which opens ; the sender check's and the journal-milter's sockets), then the Postfix ; wrapper — which additionally blocks until the milter sockets are ready ; before starting Postfix. (supervisor 4.2.5, docs/configuration.rst, ; [program:x] "priority": "the start and shutdown ordering"; supervisor/ ; supervisord.py, runforever(), transitions every group in that order in one ; pass, with no wait for a program to be ready.) ; ; If a managed process of the mail path exhausts its restart retries (FATAL), ; the crashexit event listener brings the whole container down so Docker's ; restart policy starts it again (the same container: docs.docker.com, "Start ; containers automatically", restart policies), rather than lingering with a ; dead component. A program reaches FATAL only through BACKOFF, by exits that ; come before its startsecs (supervisor 4.2.5, supervisor/process.py, ; Subprocess.finish() and transition()): an exit after it is EXITED and simply ; restarted, for ever. logrotate's FATAL is left on Server > Health ; (build/crashexit.py). [supervisord] nodaemon=true user=root logfile=/dev/null logfile_maxbytes=0 pidfile=/run/supervisord.pid loglevel=info ; The control socket is reachable by the shared `selfpost` group so the panel ; (running as the unprivileged `panel` user) can ask supervisord to send ; OpenDKIM a reload signal after it rewrites the DKIM tables. It is still ; unreachable by world; only root and the two SelfPost services are in the ; group. No HTTP auth is configured because access is gated by socket ; permissions, not the network. (supervisor 4.2.5, docs/configuration.rst, ; [unix_http_server]: chmod and chown set the socket's mode and owner; without ; username and password no authentication is asked.) [unix_http_server] file=/run/supervisor.sock chown=root:selfpost chmod=0770 [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] serverurl=unix:///run/supervisor.sock [program:opendkim] command=/usr/sbin/opendkim -f -x /etc/opendkim.conf priority=100 autostart=true autorestart=true startretries=3 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ; user=panel also gives the panel its supplementary groups, selfpost and ; postdrop (build/Dockerfile): supervisor 4.2.5, supervisor/options.py, ; drop_privileges(), os.setgroups() from the user's memberships in ; grp.getgrall(). [program:panel] command=/usr/local/bin/panel user=panel priority=200 autostart=true autorestart=true startretries=3 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ; startsecs is MILTER_WAIT_TIMEOUT plus ten seconds (build/entrypoint.sh): the ; wrapper's exit when the milter sockets never come counts as too quick, and ; so does a Postfix that will not start. The price: for that long after each ; start the program is STARTING, not RUNNING (supervisor 4.2.5, ; supervisor/options.py, processes_from_section(): startsecs is expanded like ; any value, %(ENV_x)s from supervisord's environment; assumptions C-V6). ; stopwaitsecs covers "postfix stop", which waits five seconds for master ; before it kills (build/postfix-wrapper.sh). [program:postfix] command=/usr/local/bin/postfix-wrapper.sh priority=300 autostart=true autorestart=true startsecs=%(ENV_POSTFIX_STARTSECS)s startretries=3 stopwaitsecs=15 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ; One-shot reload helper. The panel runs unprivileged and cannot run `postfix ; reload` itself, and it cannot signal the Postfix master directly: `postfix ; start-fg` forks a separate master process, so a signal to the supervised ; foreground process never reaches it (unlike OpenDKIM, which runs in the ; foreground as the supervised process itself). Instead the panel asks ; supervisord — over the group-accessible control socket — to run this program, ; which runs the canonical `postfix reload` as root - or exits 75 when no ; master runs (build/postfix-reload.sh). autostart is off (it only runs on ; demand). A program that cannot even be spawned twice in a row ends FATAL ; (startretries=1); crashexit leaves that to the page that asked for the ; reload, which reports it (build/crashexit.py; review of stage 2c, F6). (start-fg: see build/postfix-wrapper.sh. supervisor ; 4.2.5, supervisor/process.py, Subprocess.finish(): with startsecs=0 no exit ; counts as "too quickly", so even a failed run ends EXITED - never BACKOFF, ; never FATAL - with its exit status kept, which the panel reads ; (internal/supervisor, RunOnce). Its resting state is STOPPED until the first ; run, EXITED after it; assumptions C-V10.) [program:postfix-reload] command=/usr/local/bin/postfix-reload.sh autostart=false autorestart=false startsecs=0 startretries=1 exitcodes=0 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ; TLS-certificate refresh. Once a day it compares the certificate the ; reverse-proxy renews in the read-only mount with the copy Postfix reads, and ; when they differ copies it again and runs `postfix reload`. ; Long-running (it loops), runs as root so it can reload Postfix, and never exits ; non-zero, so it neither trips the crashexit listener nor needs restarting. [program:cert-reload] command=/usr/local/bin/postfix-cert-reload.sh priority=400 autostart=true autorestart=true startsecs=0 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ; Periodic logrotate for /data/log/mail.log (daily, 14 files kept). Runs as ; root so logrotate can read/rotate the log. Exits non-zero at once when the ; config is group/other-writable (logrotate would ignore it silently): with ; startsecs=1 that exit is too quick; once startretries are spent the program ; is FATAL, and Server > Health shows it. With startsecs=0 it was restarted ; every second for ever, RUNNING and EXITED in turn (supervisor 4.2.5, ; supervisor/process.py, Subprocess.finish(); assumptions C-V5). crashexit ; leaves this FATAL alone: logrotate is not on the mail path. [program:logrotate] command=/usr/local/bin/logrotate-loop.sh priority=400 autostart=true autorestart=true startsecs=1 startretries=3 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ; supervisor 4.2.5, supervisor/process.py: a program enters FATAL only from ; BACKOFF, once startretries are spent; a stop asked of supervisord ends in ; STOPPED (Subprocess.finish(), self.killing), which this listener never sees. [eventlistener:crashexit] command=/usr/local/bin/crashexit.py events=PROCESS_STATE_FATAL stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0