serveur_stalwart : Phase 1 (install + démarrage), code prod
Stalwart Mail Server v0.16.11, binaire unique natif. Mécaniques réelles
validées empiriquement sur le binaire :
- config.json = objet typé {"@type":"RocksDb","path":…} (DataStore seul) ;
- démarrage IaC en mode récupération (STALWART_RECOVERY_MODE +
STALWART_RECOVERY_ADMIN), admin depuis la voûte (vault_stalwart_admin).
Rôle : install version-épinglée, user système, unité systemd durcie
(CAP_NET_BIND_SERVICE, ProtectSystem), EnvironmentFile pour le secret.
Phase 2 (écouteurs/TLS step_ca/annuaire LDAP/DKIM via API d'admin) à venir.
Validé statiquement (ansible-lint, syntax, rendu config.json).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1a59af4d5b
commit
e3efbb5b1a
10 changed files with 195 additions and 0 deletions
|
|
@ -3,6 +3,15 @@
|
|||
## 2026-07-02
|
||||
|
||||
### Ajouté
|
||||
- **Rôle `serveur_stalwart` — Phase 1 (install + démarrage).** Stalwart Mail Server
|
||||
v0.16.11 (binaire unique natif, AGPL). Mécaniques réelles validées sur le binaire :
|
||||
`config.json` = objet typé `{"@type":"RocksDb","path":…}` (DataStore seul), démarrage
|
||||
IaC en **mode récupération** (`STALWART_RECOVERY_MODE` + `STALWART_RECOVERY_ADMIN`,
|
||||
admin depuis la voûte `vault_stalwart_admin`). Install version-épinglée, user système,
|
||||
unité systemd durcie (`CAP_NET_BIND_SERVICE`, `ProtectSystem`), EnvironmentFile pour le
|
||||
secret. **Phase 2 à venir** (écouteurs SMTP/IMAP/JMAP, TLS step_ca, annuaire LDAP, DKIM —
|
||||
provisionnés via l'API d'admin, le binaire n'ayant pas de CLI `apply`). Validé
|
||||
statiquement ; déploiement réel à suivre.
|
||||
- **`serveur_openldap` durci pour la prod : TLS via step_ca + organisation en intrant.**
|
||||
- **TLS (LDAPS + STARTTLS)** : le certificat d'hôte step_ca (déposé par `client_pki`,
|
||||
`root:root 600`) est synchronisé vers un emplacement lisible par `openldap` (`/etc/ldap/tls`)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ vault_forgejo_admin: ""
|
|||
vault_forgejo_secret_key: ""
|
||||
vault_forgejo_internal_token: ""
|
||||
|
||||
# --- Courriel (Stalwart) ---
|
||||
vault_stalwart_admin: ""
|
||||
|
||||
# --- Observabilité / divers ---
|
||||
vault_grafana_admin: ""
|
||||
vault_redis: ""
|
||||
|
|
|
|||
15
playbooks/groupes/serveur_stalwart.yml
Normal file
15
playbooks/groupes/serveur_stalwart.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
- name: Appliquer le groupe serveur_stalwart
|
||||
hosts: serveur_stalwart
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
pre_tasks:
|
||||
- name: Vérifier que la cible est Debian
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts.distribution == "Debian"
|
||||
fail_msg: "Ce playbook est prévu pour Debian."
|
||||
|
||||
roles:
|
||||
- serveur_stalwart
|
||||
29
roles/serveur_stalwart/defaults/main.yml
Normal file
29
roles/serveur_stalwart/defaults/main.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
# Stalwart Mail Server (binaire unique natif, AGPL). Rôle en 2 phases :
|
||||
# Phase 1 (ce fichier) : installer + démarrer (mode récupération pour l'IaC).
|
||||
# Phase 2 (à venir) : provisionner écouteurs/TLS/annuaire LDAP/DKIM.
|
||||
# Cf. docs/courriel-conception.md.
|
||||
|
||||
serveur_stalwart_version: "0.16.11"
|
||||
serveur_stalwart_arch: "x86_64-unknown-linux-gnu"
|
||||
serveur_stalwart_url: "https://github.com/stalwartlabs/stalwart/releases/download/v{{ serveur_stalwart_version }}/stalwart-{{ serveur_stalwart_arch }}.tar.gz"
|
||||
serveur_stalwart_binaire: "/usr/local/bin/stalwart"
|
||||
|
||||
serveur_stalwart_utilisateur: "stalwart"
|
||||
serveur_stalwart_etc: "/etc/stalwart"
|
||||
serveur_stalwart_data: "/var/lib/stalwart"
|
||||
serveur_stalwart_store: "{{ serveur_stalwart_data }}/data"
|
||||
serveur_stalwart_config: "{{ serveur_stalwart_etc }}/config.json"
|
||||
serveur_stalwart_env: "{{ serveur_stalwart_etc }}/stalwart.env"
|
||||
|
||||
# DataStore de config.json. RocksDb embarqué par défaut (v1, cf. §4 du doc conception).
|
||||
serveur_stalwart_store_type: "RocksDb"
|
||||
|
||||
# Mode récupération : expose l'API d'admin (port ci-dessous) pour provisionner les
|
||||
# réglages (Phase 2). À basculer à false une fois la configuration chargée.
|
||||
serveur_stalwart_recovery_mode: true
|
||||
serveur_stalwart_recovery_port: 8080
|
||||
|
||||
# Identifiant admin de récupération. Le mot de passe vient de la voûte.
|
||||
serveur_stalwart_admin_user: "admin"
|
||||
serveur_stalwart_admin_password: "{{ vault_stalwart_admin | default('') }}"
|
||||
7
roles/serveur_stalwart/handlers/main.yml
Normal file
7
roles/serveur_stalwart/handlers/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
- name: Redémarrer stalwart
|
||||
ansible.builtin.systemd:
|
||||
name: stalwart
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
when: not ansible_check_mode
|
||||
8
roles/serveur_stalwart/meta/empreinte.yml
Normal file
8
roles/serveur_stalwart/meta/empreinte.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Empreinte ressources du logiciel — dimensionnement VM derive (scripts/instancier.py).
|
||||
# Sommee au socle SE + marge pour estimer coeurs/RAM/disque de la VM hote.
|
||||
# Override possible par hote dans le plan (serveurs.yml: coeurs/memoire/disque).
|
||||
setops_empreinte:
|
||||
coeurs: 2
|
||||
memoire_mo: 2048
|
||||
disque_go: 10
|
||||
92
roles/serveur_stalwart/tasks/main.yml
Normal file
92
roles/serveur_stalwart/tasks/main.yml
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
- name: Exiger le mot de passe admin Stalwart (Vault)
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- serveur_stalwart_admin_password | length > 0
|
||||
fail_msg: "serveur_stalwart_admin_password est requis (via Ansible Vault : vault_stalwart_admin)."
|
||||
|
||||
- name: Créer l'utilisateur système stalwart
|
||||
ansible.builtin.user:
|
||||
name: "{{ serveur_stalwart_utilisateur }}"
|
||||
system: true
|
||||
home: "{{ serveur_stalwart_data }}"
|
||||
create_home: false
|
||||
shell: /usr/sbin/nologin
|
||||
|
||||
- name: Créer les répertoires stalwart
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ serveur_stalwart_utilisateur }}"
|
||||
group: "{{ serveur_stalwart_utilisateur }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ serveur_stalwart_etc }}"
|
||||
- "{{ serveur_stalwart_data }}"
|
||||
- "{{ serveur_stalwart_store }}"
|
||||
|
||||
- name: Lire la version de Stalwart installée
|
||||
ansible.builtin.command: "{{ serveur_stalwart_binaire }} --version"
|
||||
register: serveur_stalwart_version_installee
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Installer le binaire Stalwart (absent ou version différente)
|
||||
when: serveur_stalwart_version not in (serveur_stalwart_version_installee.stdout | default(''))
|
||||
block:
|
||||
- name: Télécharger l'archive Stalwart
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ serveur_stalwart_url }}"
|
||||
dest: "/tmp/stalwart-{{ serveur_stalwart_version }}.tar.gz"
|
||||
mode: "0644"
|
||||
|
||||
- name: Extraire le binaire stalwart vers /usr/local/bin
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/stalwart-{{ serveur_stalwart_version }}.tar.gz"
|
||||
dest: /usr/local/bin
|
||||
remote_src: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
notify: Redémarrer stalwart
|
||||
|
||||
- name: Nettoyer l'archive téléchargée
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/stalwart-{{ serveur_stalwart_version }}.tar.gz"
|
||||
state: absent
|
||||
|
||||
- name: Déployer config.json (DataStore)
|
||||
ansible.builtin.template:
|
||||
src: config.json.j2
|
||||
dest: "{{ serveur_stalwart_config }}"
|
||||
owner: "{{ serveur_stalwart_utilisateur }}"
|
||||
group: "{{ serveur_stalwart_utilisateur }}"
|
||||
mode: "0640"
|
||||
notify: Redémarrer stalwart
|
||||
|
||||
- name: Déployer l'EnvironmentFile (secret admin de récupération)
|
||||
ansible.builtin.template:
|
||||
src: stalwart.env.j2
|
||||
dest: "{{ serveur_stalwart_env }}"
|
||||
owner: root
|
||||
group: "{{ serveur_stalwart_utilisateur }}"
|
||||
mode: "0640"
|
||||
no_log: true
|
||||
notify: Redémarrer stalwart
|
||||
|
||||
- name: Déployer l'unité systemd stalwart
|
||||
ansible.builtin.template:
|
||||
src: stalwart.service.j2
|
||||
dest: /etc/systemd/system/stalwart.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Redémarrer stalwart
|
||||
|
||||
- name: Activer et démarrer stalwart
|
||||
when: not ansible_check_mode
|
||||
ansible.builtin.systemd:
|
||||
name: stalwart
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
1
roles/serveur_stalwart/templates/config.json.j2
Normal file
1
roles/serveur_stalwart/templates/config.json.j2
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"@type":"{{ serveur_stalwart_store_type }}","path":"{{ serveur_stalwart_store }}"}
|
||||
5
roles/serveur_stalwart/templates/stalwart.env.j2
Normal file
5
roles/serveur_stalwart/templates/stalwart.env.j2
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{% if serveur_stalwart_recovery_mode %}
|
||||
STALWART_RECOVERY_MODE=1
|
||||
STALWART_RECOVERY_MODE_PORT={{ serveur_stalwart_recovery_port }}
|
||||
STALWART_RECOVERY_ADMIN={{ serveur_stalwart_admin_user }}:{{ serveur_stalwart_admin_password }}
|
||||
{% endif %}
|
||||
26
roles/serveur_stalwart/templates/stalwart.service.j2
Normal file
26
roles/serveur_stalwart/templates/stalwart.service.j2
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Géré par Set-OPS (rôle serveur_stalwart). Ne pas éditer à la main.
|
||||
[Unit]
|
||||
Description=Stalwart Mail Server
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ serveur_stalwart_utilisateur }}
|
||||
Group={{ serveur_stalwart_utilisateur }}
|
||||
EnvironmentFile=-{{ serveur_stalwart_env }}
|
||||
ExecStart={{ serveur_stalwart_binaire }} --config {{ serveur_stalwart_config }}
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
LimitNOFILE=65536
|
||||
# Autorise l'écoute sur les ports privilégiés (25/443/465/587/993) sans être root.
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths={{ serveur_stalwart_data }}
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Reference in a new issue