The SHA-256 double-hash and TOCTOU defenses (O_NOFOLLOW/O_NONBLOCK, S_ISREG, fsync, in-process durable mkdir) guarded against a hostile same-uid process racing the /var/tmp handoff — a threat that does not match this deployment (a small trusted collective, no hostile local users), and the cost showed up as a 10-15s silent gap after the progress bar (two full passes over the archive). Replace the ~60-line python3 publish block with plain shell: mkdir -p, mktemp in the destination dir, cp, atomic mv -f, rm the source. The two-stage handoff and atomic rename stay (the web user cannot write into a root-owned destination, and the rename keeps $out from being partial). The exporter no longer hashes either. README updated to match.
2.8 KiB
mm3-hk-export
Export a private HyperKitty (Mailman 3) list archive to mbox from the command line — the CLI equivalent of the web UI's Download button.
Why
HyperKitty ships no hyperkitty_export command; the only built-in export is the
web Download view. This drives the same Email.as_bytes() path through the
mailman-web wrapper, so the output is byte-for-byte identical — no web login, no
DB reconstruction. It has to be mailman-web, not mailman-wrapper: the archive
lives in the HyperKitty DB, not core.
Requirements
Run as root — mailman-web drops to the web user to read the archive, then
the script publishes the result as root. Uses mailman-wrapper to report a
list's archive_policy when there's nothing to export.
Usage
mm3-hk-export [-z|--gzip] <list@domain> [output]
output defaults to ./<localpart>.mbox (.mbox.gz with -z). -z gzips the
stream to match the web .mbox.gz byte-for-byte (deterministic header, mtime=0).
mm3-hk-export announce@lists.example.com /root/mm3/announce.mbox
mm3-hk-export --gzip announce@lists.example.com /root/mm3/announce.mbox.gz
Exit codes
Never writes a partial or empty file. The exit code reports whether a backup was produced:
| Exit | Meaning |
|---|---|
0 |
archive written (wrote N messages to …) |
3 |
nothing to archive — empty list, or no archive at all (reports archive_policy: never vs nothing-yet) |
2 |
usage / bad address |
1 |
error (e.g. mailman-web failed) |
3 is distinct from 0 so a caller can tell "backed up" from "nothing to back
up" and refuse to destroy an uncaptured list.
Gating a removal on the backup
The script only exports. To remove a list after a verified backup, gate it on the exit code yourself:
mm3-hk-export "$addr" "/root/mm3/$list.mbox" \
|| { echo "no backup; keeping $addr" >&2; exit 1; }
mailman-wrapper members "$addr" > "/root/mm3/$list.members"
mailman-wrapper remove "$addr"
Export before remove, never remove on a non-zero exit. (Core's remove
leaves the HyperKitty archive untouched anyway — separate DB — but a failed
export must still halt the destroy.)
Caveats
- Reconstruction, not the original bytes. HyperKitty rebuilds each message
from parsed fields (
From,To,Subject,Date,Message-ID,In-Reply-To, body, attachments);Cc,References,List-*, andX-*are lost. Inherent to HyperKitty — the web download is identical. - Large lists. Streamed via a DB iterator, so no memory blow-up like the web export.
- Re-import. Valid mbox; re-importable with
hyperkitty_import. - Handoff. The web user builds the archive in
/var/tmp(it can't write into a root-owned destination); root copies it out and renames into place, so$outis never left partial.