- Jinja 79.1%
- Python 11.1%
- Shell 9.8%
| .github/workflows | ||
| changelogs | ||
| ci | ||
| docs | ||
| inventories | ||
| meta | ||
| playbooks | ||
| plugins | ||
| roles | ||
| scripts | ||
| tests/integration | ||
| .ansible-lint | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .sops.yaml | ||
| .yamllint | ||
| ansible.cfg | ||
| galaxy.yml | ||
| HANDOFF.md | ||
| README.md | ||
| requirements.yml | ||
albient.mailstack
A production-grade Ansible Collection that deploys a highly available, secure, sovereign e-mail platform for Albient — a UK hosting provider that prioritises privacy, data sovereignty and open source. (v1 infra runs in Hetzner Falkenstein/EU — Hetzner has no UK region; the design is provider-agnostic for a later UK move.)
The stack is built around Stalwart as the mail engine, with PostgreSQL/Patroni for metadata, MinIO for blob storage, 389 Directory Server as the canonical directory, and HAProxy for protocol-aware ingress + failover. Everything is inventory-driven, platform-agnostic (Hetzner Cloud, libvirt VMs, or bare metal), and designed for zero-downtime operations.
Status: active build. See
docs/build-plan.mdfor the per-role implementation status (complete vs stub).
What you get
| Capability | Implementation |
|---|---|
| SMTP (25/465/587), IMAPS (993), JMAP, POP3S, Sieve | Stalwart, strict modern TLS |
| Spam / RBL / greylisting / malware | Stalwart native filter + ClamAV |
| Metadata store | PostgreSQL + Patroni + etcd, R/W VIP via HAProxy, WAL PITR |
| Blob store | MinIO distributed erasure-coded, versioned, S3-compatible |
| Directory & auth | 389-DS multi-supplier replication (OIDC/IdP path documented) |
| Ingress / HA | HAProxy (PROXY protocol) + Hetzner floating-IP / keepalived VRRP |
| Deliverability | SPF/DKIM/DMARC/MTA-STS/TLS-RPT emitted for ansible-dns, egress IP pool |
| PKI | step-ca internal CA (short-lived intra-cluster certs) + Let's Encrypt (DNS-01) |
| Provisioning | API-first service + CLI, single write-path to 389-DS, WHMCS seam |
| Webmail | Roundcube (HA) + Autoconfig / Autodiscover / .mobileconfig |
| Observability | Prometheus + Grafana + Alertmanager + Loki + synthetic mail probes |
| Security | SELinux enforcing, firewalld, CIS baseline, CrowdSec |
| Backups / DR | restic encrypted off-site, PG PITR, MinIO versioning, automated restore drills |
| Governance | quotas, send-rate limits, outbound abuse heuristics, GDPR/DSAR tooling |
Opt-in per-mailbox encryption at rest using a customer-supplied PGP/S-MIME public key (zero-access for Albient) is exposed as a provisioning flag.
Topology (v1)
Three converged nodes, each running Stalwart, PostgreSQL (Patroni member), 389-DS (supplier), MinIO and supporting agents, with HAProxy on all three behind a floating IP. Tolerates the loss of one node with no data loss and minimal disruption.
Node roles are tag/group-driven, so every tier (edge/LB, mail app, Postgres, LDAP,
object store, observability) can later be peeled onto dedicated hosts purely by editing
inventory — no re-platforming. See docs/architecture.md.
Internet
│
┌────────┴─────────┐
│ Hetzner Floating IP (or VRRP VIP)
└────────┬─────────┘
┌─────────────────┼─────────────────┐
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ node-1 │ │ node-2 │ │ node-3 │
│ HAProxy │ │ HAProxy │ │ HAProxy │
│ Stalwart│ │ Stalwart│ │ Stalwart│
│ Patroni │◄─etcd─►│ Patroni │◄─etcd─►│ Patroni │
│ 389-DS │◄─repl─►│ 389-DS │◄─repl─►│ 389-DS │
│ MinIO │◄─EC───►│ MinIO │◄─EC───►│ MinIO │
└─────────┘ └─────────┘ └─────────┘
private network (all inter-node traffic)
Repository layout
galaxy.yml Collection metadata
ansible.cfg Control-node defaults
requirements.yml Galaxy collection dependencies
.sops.yaml SOPS+age encryption rules
roles/ All deployment roles (see docs/build-plan.md)
playbooks/ site.yml, provision.yml, per-tier and ops playbooks
plugins/ modules (emit_dns_records, …), filters, inventory
inventories/{dev,staging,prod}/ group_vars/host_vars, SOPS-encrypted secrets
docs/ architecture, build-plan, DNS contract, runbooks
ci/ Execution Environment definition, CI helpers
molecule/ Shared molecule resources (per-role scenarios live in roles/*)
tests/ Integration harness
Quick start
0. Prerequisites (control node)
python3 -m venv .venv && source .venv/bin/activate
pip install ansible-core ansible-lint yamllint molecule molecule-plugins[docker] sops
ansible-galaxy collection install -r requirements.yml
# age key for SOPS decryption
mkdir -p ~/.config/sops/age && age-keygen -o ~/.config/sops/age/keys.txt
1. Configure inventory & secrets
Edit inventories/<env>/hosts.yml and the SOPS-encrypted
inventories/<env>/group_vars/all/secrets.sops.yaml (real tokens for hcloud,
ClouDNS, DB/LDAP binds, DKIM/TLS keys). Never commit plaintext secrets — see
docs/runbooks/secret-rotation.md.
2. Provision infrastructure (Hetzner) — optional
ansible-playbook -i inventories/prod/hosts.yml playbooks/provision.yml
For metal/VMs set provisioning_provider: none and supply a static inventory.
3. Deploy the platform
ansible-playbook -i inventories/prod/hosts.yml playbooks/site.yml
site.yml is serialised and health-gated for zero-downtime rolling convergence.
4. Validate locally (no live deploy)
yamllint .
ansible-lint
ansible-playbook playbooks/site.yml --syntax-check -i inventories/dev/hosts.yml
molecule test -s default # inside a given role directory
DNS contract
This collection does not manage DNS. It emits the records it requires (SPF, DKIM,
DMARC, MTA-STS, TLS-RPT, MX, A/AAAA, PTR requests, SRV autoconfig, CAA) as a structured
artifact consumed by the separate ansible-dns repo (octoDNS → ClouDNS). The schema and
a worked sample live in docs/dns-contract.md.
Documentation
docs/architecture.md— full design & scaling modeldocs/build-plan.md— every role and its statusdocs/dns-contract.md— emitted DNS-record schema + sampledocs/deploy-guide.md— ordered end-to-end deploydocs/provisioning-api.md— API + CLI + WHMCS seamdocs/runbooks/— operator runbooks
License
AGPL-3.0-or-later. © Albient.