6 Commits

Author SHA1 Message Date
root 8f15646cf0 Add Tab completion to mailctl
The command tree lives in Go, in complete.go, next to the commands it
describes: the hidden __complete subcommand is handed the words typed so far
and answers with what may follow the last of them. The zsh function installed
alongside the binary does nothing but pass the line in and hand the answer
back, so it needs no change when a command is added, and it cannot describe a
grammar the tool no longer has.

Two rules hold throughout:

- Completion never writes. It reads through completeValues, which opens the
  database read-only, and not through openDB, which would create it from
  schema.sql and run migrate() on every call. Checked: after repeated
  completions the mtime and size of mail.db are unchanged and no -wal or -shm
  file appears beside it.
- Completion never fails out loud. Every error ends as an empty list, and
  __complete is dispatched before the root check, so as an unprivileged user
  the structure still completes, the values stay empty, nothing reaches
  stderr and the status is 0.

The values come out of the database, so only what exists is offered: `user
enable` lists the locked mailboxes and `user disable` the unlocked ones,
`alias del <source>` only the targets of that alias, `sieve edit <address>`
that mailbox's scripts via doveadm, and `lang allow` the languages Rspamd has
a model for, minus those already on the line. For a path argument the answer
is the single line ":files", which hands over to zsh's own file completion.

Narrowing the answer down to what has been typed is left to the shell rather
than done in Go, so that the matcher-list styles keep working - completing
"MARTIN@" to "martin@example.com" is zsh's business, and a filter here would
defeat it.

Two details on the shell side are worth recording, both found by driving a
real interactive zsh through a pseudo-terminal rather than by reading:

- The binary is located with `whence -p mailctl`, not through $commands.
  That parameter is not populated inside a completion function, so the
  lookup yields nothing and every candidate list silently comes back empty.
- zsh writes "--" between a value and its description. The function sets
  list-separator to a single hyphen, scoped to mailctl's own context so no
  other completion changes, and only when nothing has been set for it
  already.

`make install` now also puts the function in
/usr/local/share/zsh/site-functions. The installer needs no change for it:
stage 8 already builds through `make install`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 10:48:06 +02:00
root 420831dd43 Document ARM support
arm64 needs no change to anything here, and the claim is backed by
checks rather than assumption:

- mailctl was cross-compiled for linux/arm64 and linux/arm - both
  produce a statically linked binary. modernc.org/sqlite ships code for
  linux_arm64, linux_arm, linux_riscv64, linux_ppc64le and s390x, and
  with CGO_ENABLED=0 there is nothing else to worry about.
- Every package is built for arm64 in Debian 13, including golang-go at
  1.24 - exactly the version go.mod asks for. fail2ban and certbot are
  Architecture: all.
- Regex acceleration survives: the amd64 rspamd package depends on
  libhyperscan5, the arm64 one on libvectorscan5, the ARM port of the
  same library. This setup leans on regexes for the block, allow and
  language filters, so that mattered.

armhf is documented as possible but not advisable: the armhf rspamd
package depends on neither, so every expression falls back to PCRE.

The real constraint is memory, not the instruction set. Rspamd holds
around 510 MB resident in normal operation here, which rules out 1 GB
boards whatever their architecture. Measured figures are in the table.
Also noted that Maildir means one file per message - over 20,000 on this
server - which is the wrong workload for an SD card.

The only hardcoded path in the installer, /usr/lib/go-1.24/bin/go, is a
fallback behind "command -v go" and is the same on every Debian
architecture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 11:13:57 +02:00
root e09f7618f6 Document the ports needed when the host is behind a firewall
New section 2 in docs/installation.md, split into inbound and outbound,
because the outbound half is the one that gets forgotten.

Inbound is the easy part and matches the nftables ruleset exactly: 25,
465/587, 993/143, 80, 4190 and the SSH port. Noted that port 80 is not
needed for mail and therefore tends to get closed again after the
installation - two months later the certificate renewal fails quietly.
DNS-01 is the way out if it cannot stay open.

Outbound: 25 for sending, 443 and 80 for Rspamd maps, certificates,
packages and the Go build, UDP 11335 for fuzzy hashes, UDP 123 for the
clock. And DNS on UDP *and* TCP 53 - TCP matters because DNSSEC answers
are large and get truncated, which produces intermittent resolution
failures that are hard to place.

The trap gets its own block: unbound here is a full resolver and asks
the root and authoritative servers directly. Corporate firewalls
routinely permit DNS to the internal resolver only. That does not break
the setup outright, it breaks it silently - DNSBL operators refuse
queries from shared resolvers, and a blocklist that answers nothing
looks exactly like a clean sender. The forward-zone workaround is
documented together with what it costs.

Also documented: behind NAT the A record must name the DNAT address and
the PTR the SNAT address. If they differ, receiving works while sending
fails on PTR and SPF - a miserable diagnosis, because mail arrives fine.

Every verification command in the section was run on the live server
first. Two matching entries in operations.md troubleshooting, for "mail
arrives but nothing goes out" and for blocklists going quiet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 09:20:49 +02:00
root 2b84a8a493 Everything in English: documentation, comments, identifiers
The user works in German but wants the artefacts in English throughout.
Translated were roughly 1,500 lines: every comment in the configuration
files, all comments in the Go sources, both shell scripts, and the three
manuals.

Renamed along with it, so nothing is left half-translated:

- install/vorlagen-pruefen      -> install/verify-templates
- install/mailserver.conf.beispiel -> install/mailserver.conf.example
- docs/betrieb.md               -> docs/operations.md
- shell functions and variables (schritt/abbruch/einsetzen/ZIEL/BEHALTEN
  -> stage/die/deploy/DEST/KEEP), the ten installer stages, the Dovecot
  quota root "Postfach" -> "Mailbox" and the sieve_script names
  lernspam/lernham -> learnspam/learnham
- the comment headers that mailctl writes into the generated map files

Two things this dug up while translating:

- Perl treats $) and $/ as variables. A careless s{}{} put a NUL byte into
  the regular expression documented in lang.go and mangled a line in
  list.go. Both repaired; the sources were checked for NUL bytes and the
  generated maps compared against the previous ones - the expressions
  themselves are unchanged.
- The map files are only rewritten on a change, so their German headers
  survived the first pass. Regenerated and verified line by line.

The origin server was brought along in the same step: configuration
deployed, Sieve scripts recompiled, mailctl rebuilt, services reloaded,
maps regenerated. Checked afterwards: postfix check, doveconf -n,
nft -c, unbound-checkconf, rspamadm configtest all pass; block list and
language filter still fire; a test message went through the full chain
into the mailbox. install/verify-templates reports 37 files identical,
no differences at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 12:23:20 +02:00
mike e6ce180bf1 Ursprungsserver angeglichen, Dokumentation nur noch hier
Der Server, aus dem diese Anlage stammt, laeuft jetzt mit genau diesen
Dateien: portables mailctl aus /etc/mailserver/server.conf,
verbessertes mailbackup, angeglichene Kopfzeilen. install/vorlagen-pruefen
meldet 37 Dateien identisch, keine Abweichung.

Die Dokumentation wird ab jetzt ausschliesslich in diesem Repository
gepflegt. Auf dem Server liegt unter /etc/mailserver/README.md nur noch
ein Verweis darauf, mit den paar Befehlen, die man beim Anmelden sofort
braucht - der Installer legt ihn aus config/mailserver/README.md an.

Ausserdem:

- mailbackup sichert /etc/mailserver/server.conf mit. Ohne sie weiss eine
  Wiederherstellung nicht, fuer welchen Namen die Zertifikate galten und
  welche Adresse in die DNS-Empfehlungen gehoert. Die Aufraeumlogik ist
  gegen 35 Sicherungen und gegen ein leeres Verzeichnis geprueft, unter
  /bin/sh, nicht nur unter zsh.
- betrieb.md erklaert server.conf und was passiert, wenn sie fehlt:
  mailctl raet dann ueber /etc/mailname und die Route nach aussen und
  weist in "mailctl dns" sichtbar darauf hin. Hinter NAT ist der geratene
  Wert immer falsch.
- Spaltenausrichtung des A-Eintrags in "mailctl dns" berichtigt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:59:53 +02:00
mike c9e5494992 Mailserver: Konfiguration, Installationsskript und Anleitung
Der vollstaendige Aufbau eines Mailservers auf Debian 13, herausgezogen
aus einer laufenden Anlage: Postfix mit postscreen und DANE, Dovecot 2.4
mit Maildir, Sieve und Kontingenten, Rspamd als Milter mit Bayes,
Greylisting und DKIM, dazu Redis, unbound, nftables, fail2ban und
certbot. Domains, Postfaecher, Aliases und Absenderlisten liegen in
einer SQLite-Datei, die Postfix und Dovecot direkt lesen.

install/mailserver-install fuehrt das in zehn wiederholbaren Stufen aus.
Jede Stufe laesst sich einzeln nachholen; ersetzte Dateien werden
vorher gesichert. Die riskanteste Stelle - die Firewall - prueft vorher,
ob auf dem angegebenen Port wirklich ein sshd lauscht, weil eine falsche
Nummer die laufende Sitzung aussperren wuerde.

install/vorlagen-pruefen vergleicht die Vorlagen mit dem, was auf einem
Server tatsaechlich laeuft. Gegen den Ursprungsserver gepruefte Bilanz:
30 Dateien Byte fuer Byte gleich, 5 Abweichungen nur in Kommentaren.

mailctl ist portabel gemacht: Servername, Adresse und Zertifikatspfad
standen fest im Quelltext und kommen jetzt aus /etc/mailserver/server.conf,
mit Rueckfallebene ueber /etc/mailname und die Route nach aussen. Die
Go-Abhaengigkeiten sind auf Fassungen zurueckgezogen, die mit dem Go aus
Debian 13 uebersetzen - vorher verlangte go.mod eine Toolchain, die sich
Go im Hintergrund nachgeladen hat.

Kommentare deutsch, Benutzerfuehrung englisch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:43:50 +02:00