docs: D6-D9 — HEALTHCHECK, env regression test, new docs, archive spec

Add Docker HEALTHCHECK and mail-path /healthz liveness; env-doc regression
test; architecture.md and development.md; product.md and expanded security.md;
retire live specification.md to docs/archive/.

Co-Authored-By: Claude <claude-opus-5-thinking-high@noreply@anthropic.com>
This commit is contained in:
2026-08-05 00:33:49 +03:00
parent 7f24b2923c
commit baaed5991b
16 changed files with 703 additions and 28 deletions
+25
View File
@@ -163,3 +163,28 @@ func writeCert(t *testing.T, path, cn string, validFor time.Duration) {
t.Fatal(err)
}
}
func TestLivenessFromParsedProcesses(t *testing.T) {
allRunning := `opendkim RUNNING pid 21, uptime 0:04:10
panel RUNNING pid 22, uptime 0:04:09
postfix RUNNING pid 23, uptime 0:04:08
postfix-reload STOPPED Not started
`
procs := parseProcesses(allRunning)
for _, p := range procs {
if mailPathPrograms[p.Name] && p.Status != StatusOK {
t.Fatalf("%s should be ok for liveness, got %q", p.Name, p.Status)
}
}
postfixDead := `opendkim RUNNING pid 21, uptime 0:04:10
panel RUNNING pid 22, uptime 0:04:09
postfix FATAL Exited too quickly
`
procs = parseProcesses(postfixDead)
for _, p := range procs {
if p.Name == "postfix" && p.Status == StatusOK {
t.Fatal("postfix FATAL should not grade as OK")
}
}
}
+46
View File
@@ -0,0 +1,46 @@
package health
import (
"fmt"
"strings"
)
// mailPathPrograms are the supervised processes whose absence means the
// container should not report healthy to orchestrators.
var mailPathPrograms = map[string]bool{
"opendkim": true,
"panel": true,
"postfix": true,
}
// Liveness reports whether the mail path is healthy enough for container
// probes. It requires opendkim, panel, and postfix to be RUNNING under
// supervisord.
func Liveness() error {
procs, err := Processes()
if err != nil {
return err
}
seen := make(map[string]Status, len(mailPathPrograms))
for _, p := range procs {
if mailPathPrograms[p.Name] {
seen[p.Name] = p.Status
}
}
var unhealthy []string
for name := range mailPathPrograms {
switch seen[name] {
case StatusOK:
case StatusUnknown:
unhealthy = append(unhealthy, name+": missing")
default:
unhealthy = append(unhealthy, fmt.Sprintf("%s: %s", name, seen[name]))
}
}
if len(unhealthy) > 0 {
return fmt.Errorf("unhealthy: %s", strings.Join(unhealthy, ", "))
}
return nil
}