[1.0.0]
Added:
- A **Delivery log** on each delivery's page (`/deliveries/{id}`): the
- A **History** block on the same page: the journal's two timestamps stated as
Fixed:
- Postfix config: copy TLS cert/key from the (often `:ro`, host-owned) mount
- Postfix config: allow `maillog_file` under `/data` via
- E2e: read `/data/setup-token` via `docker compose exec` (file is `0600`
- Entrypoint: check `SELFPOST_HOSTNAME` before `/data` setup (so a bad identity
- E2e gate: wait for host-published `/healthz` before panel setup, and stop
… (3 more in CHANGELOG)
Changed:
- Document what `.spbk` and `.spde` stand for (SelfPost backup / SelfPost domain
- Docs aligned with the code: setup URL is `/setup/<token>` (README and
- Full backups no longer carry `/data/log`. It is Postfix's raw log plus its
- Monitoring screens (status, mail queue, system log, deliveries) use adaptive
- Documentation package consolidated into `docs/development.md`: Documentation
… (4 more in CHANGELOG)
[1.0.1]
A documentation and packaging release: no change to the mail path, the database, or the on-disk layout. Upgrading is a tag bump.
Added:
- `SECURITY.md` — how to report a vulnerability privately (GitHub private
- [docs/plans/](docs/plans/) — one document per agreed extension: the optional
Changed:
- The panel's **Account** entry is now called **Settings** — nav link, page
- The signed-in name in the panel's nav is now labelled `User:`, so it reads as
- [docs/product.md](docs/product.md) reframes the future line: the inbound
- [docs/security.md](docs/security.md) is now in English, matching the rest of
- The README documentation table now points at `SECURITY.md` for reporting a
… (2 more in CHANGELOG)
Fixed:
- The panel's static assets are served with a content ETag and
Squashed CHANGELOG sections: [1.0.0], [1.0.1].
Full detail: CHANGELOG.md (1.0.0, 1.0.1).
65 lines
2.3 KiB
Go
65 lines
2.3 KiB
Go
package e2e
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// wantRunning are the supervisord programs that must be RUNNING once the
|
|
// container is up (build/supervisord.conf). postfix-reload is deliberately
|
|
// excluded: autostart=false, its healthy resting state is STOPPED.
|
|
var wantRunning = []string{"opendkim", "panel", "postfix", "cert-reload", "logrotate"}
|
|
|
|
// checkSupervisorProcesses shells into the container directly (not through the
|
|
// panel's /status page) so it works before an administrator account even
|
|
// exists — this is the first thing the harness checks after `docker compose
|
|
// up` (plan C.4). Programs take a moment to leave STARTING right after the
|
|
// container starts, so this polls rather than checking once.
|
|
func checkSupervisorProcesses(s *stack) error {
|
|
var lastErr error
|
|
err := waitFor("all supervised programs to reach their steady state", 20*time.Second, 500*time.Millisecond, func() (bool, error) {
|
|
out, execErr := s.execIn("selfpost", "supervisorctl", "-c", "/etc/supervisor/supervisord.conf", "status")
|
|
// supervisorctl exits non-zero when any program is not RUNNING, which
|
|
// is expected for postfix-reload — parse the output regardless of exit
|
|
// status.
|
|
states := parseSupervisorStatus(out)
|
|
if len(states) == 0 {
|
|
lastErr = fmt.Errorf("supervisorctl status produced nothing to parse: %v\n%s", execErr, out)
|
|
return false, lastErr
|
|
}
|
|
for _, name := range wantRunning {
|
|
state, ok := states[name]
|
|
if !ok {
|
|
lastErr = fmt.Errorf("program %q not reported by supervisorctl:\n%s", name, out)
|
|
return false, lastErr
|
|
}
|
|
if state != "RUNNING" {
|
|
lastErr = fmt.Errorf("program %q is %s, want RUNNING:\n%s", name, state, out)
|
|
return false, lastErr
|
|
}
|
|
}
|
|
if state, ok := states["postfix-reload"]; ok && state != "STOPPED" {
|
|
lastErr = fmt.Errorf("program postfix-reload is %s, want STOPPED (autostart=false):\n%s", state, out)
|
|
return false, lastErr
|
|
}
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("%w\n==== selfpost logs ====\n%s", err, s.logs("selfpost"))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseSupervisorStatus(out string) map[string]string {
|
|
states := make(map[string]string)
|
|
for _, line := range strings.Split(out, "\n") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
states[fields[0]] = fields[1]
|
|
}
|
|
return states
|
|
}
|