mirror of
https://github.com/rubenhensen/k8scd.git
synced 2026-09-17 18:22:54 +02:00
Remove ansible
This commit is contained in:
@@ -2,3 +2,4 @@ ente-photos-ref/
|
|||||||
repomix-output.txt
|
repomix-output.txt
|
||||||
credentials-velero
|
credentials-velero
|
||||||
velero-credentials
|
velero-credentials
|
||||||
|
.env
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
.vault_pass
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
# Mailserver Ansible
|
|
||||||
|
|
||||||
Ansible playbook that deploys and hardens a [Stalwart](https://stalw.art) all-in-one mail server on Ubuntu.
|
|
||||||
|
|
||||||
## What it does
|
|
||||||
|
|
||||||
| Role | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `base` | apt upgrades, unattended-upgrades, timezone, swap, sysctl hardening |
|
|
||||||
| `ssh` | Key-only root login, disable password auth, rate limiting |
|
|
||||||
| `ufw` | Firewall — deny all incoming except SSH, SMTP, IMAP, HTTPS |
|
|
||||||
| `fail2ban` | Brute force protection for SSH + Stalwart auth |
|
|
||||||
| `stalwart` | Stalwart mail server with built-in ACME (Let's Encrypt) |
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Ubuntu server with root SSH key access
|
|
||||||
- DNS: `mail.rubenhensen.nl` → server IP (A record)
|
|
||||||
- DNS: MX record for your domain pointing to `mail.rubenhensen.nl`
|
|
||||||
- Ansible installed locally (`brew install ansible`)
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ~/Repos/k8scd/ansible/mailserver
|
|
||||||
|
|
||||||
# 1. Create vault password file (gitignored)
|
|
||||||
echo 'your-vault-password' > .vault_pass
|
|
||||||
chmod 600 .vault_pass
|
|
||||||
|
|
||||||
# 2. Create encrypted secrets
|
|
||||||
ansible-vault create host_vars/mailserver/vault.yml
|
|
||||||
# Add:
|
|
||||||
# ---
|
|
||||||
# vault_stalwart_fallback_admin_password: "your-admin-password"
|
|
||||||
|
|
||||||
# 3. Edit inventory if server IP changed
|
|
||||||
# inventory.yml → ansible_host
|
|
||||||
|
|
||||||
# 4. Run
|
|
||||||
ansible-playbook playbook.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Day-to-day operations
|
|
||||||
|
|
||||||
**Re-run after config changes:**
|
|
||||||
```bash
|
|
||||||
ansible-playbook playbook.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Edit encrypted secrets:**
|
|
||||||
```bash
|
|
||||||
ansible-vault edit host_vars/mailserver/vault.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Run only a specific role:**
|
|
||||||
```bash
|
|
||||||
ansible-playbook playbook.yml --tags stalwart
|
|
||||||
```
|
|
||||||
(Note: tags aren't configured yet — use `--start-at-task "task name"` or add tags if needed)
|
|
||||||
|
|
||||||
**Upgrade Stalwart:**
|
|
||||||
Bump `stalwart_version` in `roles/stalwart/defaults/main.yml` and re-run. It only re-downloads when the version changes.
|
|
||||||
|
|
||||||
## File structure
|
|
||||||
|
|
||||||
```
|
|
||||||
├── ansible.cfg # Ansible settings + vault password file path
|
|
||||||
├── inventory.yml # Server IP, SSH user, python interpreter
|
|
||||||
├── .vault_pass # Vault password (gitignored)
|
|
||||||
├── .gitignore
|
|
||||||
├── host_vars/mailserver/
|
|
||||||
│ ├── vars.yml # Maps variables to vault references
|
|
||||||
│ └── vault.yml # Encrypted secrets (committed as ciphertext)
|
|
||||||
└── roles/
|
|
||||||
├── base/ # OS hardening + swap
|
|
||||||
├── ssh/ # sshd_config template
|
|
||||||
├── ufw/ # Firewall rules
|
|
||||||
├── fail2ban/ # Jails for SSH + Stalwart
|
|
||||||
└── stalwart/ # Mail server install + config.toml template
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stalwart admin
|
|
||||||
|
|
||||||
Web admin: `https://mail.rubenhensen.nl`
|
|
||||||
Login: `admin` / (password from vault)
|
|
||||||
|
|
||||||
From the web UI you can manage domains, accounts, DKIM keys, and other mail settings.
|
|
||||||
|
|
||||||
## TLS certificates
|
|
||||||
|
|
||||||
Managed by certbot (Let's Encrypt). On first run, certbot uses standalone mode (temporarily stops Stalwart to bind port 80/443). Renewals happen automatically via `certbot.timer` and a deploy hook restarts Stalwart. Certs are at `/etc/letsencrypt/live/mail.rubenhensen.nl/`.
|
|
||||||
|
|
||||||
## Ports
|
|
||||||
|
|
||||||
| Port | Service |
|
|
||||||
|------|---------|
|
|
||||||
| 22 | SSH |
|
|
||||||
| 25 | SMTP |
|
|
||||||
| 465 | SMTP submission (implicit TLS) |
|
|
||||||
| 587 | SMTP submission (STARTTLS) |
|
|
||||||
| 993 | IMAP (implicit TLS) |
|
|
||||||
| 443 | HTTPS (web admin + JMAP + ACME) |
|
|
||||||
| 80 | HTTP (ACME + redirect) |
|
|
||||||
|
|
||||||
## If something breaks
|
|
||||||
|
|
||||||
- Stalwart logs: `/opt/stalwart/logs/`
|
|
||||||
- Stalwart config: `/opt/stalwart/etc/config.toml`
|
|
||||||
- Service status: `systemctl status stalwart`
|
|
||||||
- fail2ban status: `fail2ban-client status` / `fail2ban-client status sshd`
|
|
||||||
- Firewall: `ufw status`
|
|
||||||
- Check banned IPs: `fail2ban-client status stalwart-auth`
|
|
||||||
- Unban an IP: `fail2ban-client set <jail> unbanip <ip>`
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[defaults]
|
|
||||||
inventory = inventory.yml
|
|
||||||
roles_path = roles
|
|
||||||
host_key_checking = False
|
|
||||||
retry_files_enabled = False
|
|
||||||
vault_password_file = .vault_pass
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
stalwart_fallback_admin_password: "{{ vault_stalwart_fallback_admin_password }}"
|
|
||||||
stalwart_db_password: "{{ vault_stalwart_db_password }}"
|
|
||||||
sogo_db_password: "{{ vault_sogo_db_password }}"
|
|
||||||
nginx_k8s_updater_ssh_pubkey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII5cMc73rlUCn3mS5FXlu3nO+AUeW2L28jRh22VYIPY4 k8s-ip-updater"
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
$ANSIBLE_VAULT;1.1;AES256
|
|
||||||
37373639633565366339643336366137666465303365363532393364626333306232636661373331
|
|
||||||
6239343934326134386663396366333232613337323266320a303836623065633534653034666661
|
|
||||||
33643731356161656563313330626166613562356434313961313539643636373665373635613635
|
|
||||||
3631636639393563330a666530353733623166313063373439306234613938353366303331336234
|
|
||||||
37616266363434336639373535303038666231656461373061376666636464393233323963366234
|
|
||||||
65383062666162343561353961373230323763323432646364663437313361633331313433613465
|
|
||||||
62663034623330333936623035353235393461333363303435613364643838373164313431636562
|
|
||||||
30323736326632663032616136643430306330366333353565646432343466386564303432616339
|
|
||||||
32303231363835666537386136376532346632343961396334663130376366373566313965383163
|
|
||||||
34396566363830666437303266623437383064396462643132336332643239326364313632386164
|
|
||||||
38363561643038363065313630636437366636623134666564613937613633643835656236623331
|
|
||||||
32393366643433613230646361316539633236353539316162336237373737316136373466663863
|
|
||||||
36646162323266336436343631383837363338336234653063626565653931633862616662666265
|
|
||||||
34396430313132333536363736333434666634333432336639633963393665616163653237343235
|
|
||||||
36626138356230313963383033643432313839333232386265633330666664666262343433396362
|
|
||||||
66646666356563303136636337323333306466373036313333643135643436393165656539396139
|
|
||||||
3861
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
all:
|
|
||||||
hosts:
|
|
||||||
mailserver:
|
|
||||||
ansible_host: 46.224.26.65
|
|
||||||
ansible_user: root
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
|
|
||||||
ansible_python_interpreter: /usr/bin/python3.12
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Configure and harden mail server
|
|
||||||
hosts: mailserver
|
|
||||||
become: true
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- { role: base, tags: [base] }
|
|
||||||
- { role: ssh, tags: [ssh] }
|
|
||||||
- { role: ufw, tags: [ufw] }
|
|
||||||
- { role: fail2ban, tags: [fail2ban] }
|
|
||||||
- { role: certbot, tags: [certbot] }
|
|
||||||
- { role: stalwart, tags: [stalwart] }
|
|
||||||
- { role: sogo, tags: [sogo] }
|
|
||||||
- { role: nginx, tags: [nginx] }
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
base_timezone: "Europe/Amsterdam"
|
|
||||||
base_swap_size: "2G"
|
|
||||||
base_swap_enabled: true
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
- name: reload sysctl
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: sysctl --system
|
|
||||||
changed_when: true
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Update apt cache and upgrade packages
|
|
||||||
ansible.builtin.apt:
|
|
||||||
update_cache: true
|
|
||||||
upgrade: safe
|
|
||||||
cache_valid_time: 3600
|
|
||||||
|
|
||||||
- name: Install essential packages
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name:
|
|
||||||
- unattended-upgrades
|
|
||||||
- apt-listchanges
|
|
||||||
- logrotate
|
|
||||||
- curl
|
|
||||||
- tar
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Enable unattended upgrades
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
|
||||||
content: |
|
|
||||||
APT::Periodic::Update-Package-Lists "1";
|
|
||||||
APT::Periodic::Unattended-Upgrade "1";
|
|
||||||
APT::Periodic::AutocleanInterval "7";
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Set timezone
|
|
||||||
community.general.timezone:
|
|
||||||
name: "{{ base_timezone }}"
|
|
||||||
|
|
||||||
- name: Deploy sysctl hardening config
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /etc/sysctl.d/99-hardening.conf
|
|
||||||
content: |
|
|
||||||
# Prevent IP spoofing
|
|
||||||
net.ipv4.conf.all.rp_filter = 1
|
|
||||||
net.ipv4.conf.default.rp_filter = 1
|
|
||||||
# Ignore ICMP redirects
|
|
||||||
net.ipv4.conf.all.accept_redirects = 0
|
|
||||||
net.ipv6.conf.all.accept_redirects = 0
|
|
||||||
net.ipv4.conf.all.send_redirects = 0
|
|
||||||
# Ignore source-routed packets
|
|
||||||
net.ipv4.conf.all.accept_source_route = 0
|
|
||||||
net.ipv6.conf.all.accept_source_route = 0
|
|
||||||
# SYN flood protection
|
|
||||||
net.ipv4.tcp_syncookies = 1
|
|
||||||
# Log martians
|
|
||||||
net.ipv4.conf.all.log_martians = 1
|
|
||||||
mode: "0644"
|
|
||||||
notify: reload sysctl
|
|
||||||
|
|
||||||
- name: Create swap file
|
|
||||||
when: base_swap_enabled
|
|
||||||
block:
|
|
||||||
- name: Check if swap file exists
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: /swapfile
|
|
||||||
register: swap_file
|
|
||||||
|
|
||||||
- name: Create swap file
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: "fallocate -l {{ base_swap_size }} /swapfile"
|
|
||||||
when: not swap_file.stat.exists
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Set swap file permissions
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /swapfile
|
|
||||||
mode: "0600"
|
|
||||||
when: not swap_file.stat.exists
|
|
||||||
|
|
||||||
- name: Format swap file
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: mkswap /swapfile
|
|
||||||
when: not swap_file.stat.exists
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Enable swap file
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: swapon /swapfile
|
|
||||||
when: not swap_file.stat.exists
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Add swap to fstab
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/fstab
|
|
||||||
line: "/swapfile none swap sw 0 0"
|
|
||||||
state: present
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
certbot_email: "admin@rubenhensen.nl"
|
|
||||||
certbot_domains:
|
|
||||||
- "mail.rubenhensen.nl"
|
|
||||||
- "stalwart.rubenhensen.nl"
|
|
||||||
certbot_webroot: "/var/www/acme"
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install certbot
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: certbot
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Check if certificate already exists
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "/etc/letsencrypt/live/{{ certbot_domains[0] }}/fullchain.pem"
|
|
||||||
register: certbot_cert
|
|
||||||
|
|
||||||
- name: Stop Stalwart for initial cert issuance
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: stalwart
|
|
||||||
state: stopped
|
|
||||||
when: not certbot_cert.stat.exists
|
|
||||||
|
|
||||||
- name: Obtain certificate (standalone)
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: >
|
|
||||||
certbot certonly --standalone
|
|
||||||
--non-interactive
|
|
||||||
--agree-tos
|
|
||||||
--email {{ certbot_email }}
|
|
||||||
{% for domain in certbot_domains %}-d {{ domain }} {% endfor %}
|
|
||||||
when: not certbot_cert.stat.exists
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Start Stalwart after cert issuance
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: stalwart
|
|
||||||
state: started
|
|
||||||
when: not certbot_cert.stat.exists
|
|
||||||
|
|
||||||
- name: Grant stalwart read access to certs
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
loop:
|
|
||||||
- /etc/letsencrypt/live
|
|
||||||
- /etc/letsencrypt/archive
|
|
||||||
|
|
||||||
- name: Deploy Stalwart admin password for renewal hook
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /etc/letsencrypt/.stalwart-admin-pass
|
|
||||||
content: "{{ stalwart_fallback_admin_password }}"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0600"
|
|
||||||
|
|
||||||
- name: Deploy certbot renewal hook
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /etc/letsencrypt/renewal-hooks/deploy/stalwart.sh
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
DOMAIN="{{ certbot_domains[0] }}"
|
|
||||||
PASS=$(cat /etc/letsencrypt/.stalwart-admin-pass)
|
|
||||||
|
|
||||||
python3 -c "
|
|
||||||
import json, urllib.request
|
|
||||||
|
|
||||||
cert = open('/etc/letsencrypt/live/$DOMAIN/fullchain.pem').read()
|
|
||||||
key = open('/etc/letsencrypt/live/$DOMAIN/privkey.pem').read()
|
|
||||||
body = json.dumps([{'type':'insert','assert_empty':False,'values':[
|
|
||||||
['certificate.default.cert', cert],
|
|
||||||
['certificate.default.private-key', key],
|
|
||||||
]}]).encode()
|
|
||||||
|
|
||||||
import base64
|
|
||||||
auth = base64.b64encode(('admin:' + open('/etc/letsencrypt/.stalwart-admin-pass').read().strip()).encode()).decode()
|
|
||||||
|
|
||||||
req = urllib.request.Request('http://127.0.0.1:8080/api/settings', data=body,
|
|
||||||
headers={'Content-Type':'application/json','Authorization':'Basic ' + auth}, method='POST')
|
|
||||||
urllib.request.urlopen(req)
|
|
||||||
|
|
||||||
req = urllib.request.Request('http://127.0.0.1:8080/api/reload',
|
|
||||||
headers={'Authorization':'Basic ' + auth})
|
|
||||||
urllib.request.urlopen(req)
|
|
||||||
"
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Ensure certbot renewal timer is enabled
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: certbot.timer
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
fail2ban_bantime: "1h"
|
|
||||||
fail2ban_findtime: "10m"
|
|
||||||
fail2ban_maxretry: 5
|
|
||||||
|
|
||||||
fail2ban_jails:
|
|
||||||
- name: sshd
|
|
||||||
enabled: true
|
|
||||||
port: "{{ ssh_port | default(22) }}"
|
|
||||||
maxretry: 3
|
|
||||||
bantime: "1h"
|
|
||||||
- name: stalwart-auth
|
|
||||||
enabled: true
|
|
||||||
port: "25,465,587,993,443"
|
|
||||||
maxretry: 5
|
|
||||||
bantime: "1h"
|
|
||||||
logpath: "/opt/stalwart/logs/stalwart.log*"
|
|
||||||
filter: stalwart-auth
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
- name: restart fail2ban
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: fail2ban
|
|
||||||
state: restarted
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install fail2ban
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: fail2ban
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Deploy Stalwart auth filter
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: stalwart-auth.conf.j2
|
|
||||||
dest: /etc/fail2ban/filter.d/stalwart-auth.conf
|
|
||||||
mode: "0644"
|
|
||||||
notify: restart fail2ban
|
|
||||||
|
|
||||||
- name: Deploy jail.local
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: jail.local.j2
|
|
||||||
dest: /etc/fail2ban/jail.local
|
|
||||||
mode: "0644"
|
|
||||||
notify: restart fail2ban
|
|
||||||
|
|
||||||
- name: Enable and start fail2ban
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: fail2ban
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
bantime = {{ fail2ban_bantime }}
|
|
||||||
findtime = {{ fail2ban_findtime }}
|
|
||||||
maxretry = {{ fail2ban_maxretry }}
|
|
||||||
banaction = ufw
|
|
||||||
|
|
||||||
{% for jail in fail2ban_jails %}
|
|
||||||
[{{ jail.name }}]
|
|
||||||
enabled = {{ jail.enabled | lower }}
|
|
||||||
port = {{ jail.port }}
|
|
||||||
maxretry = {{ jail.maxretry | default(fail2ban_maxretry) }}
|
|
||||||
bantime = {{ jail.bantime | default(fail2ban_bantime) }}
|
|
||||||
{% if jail.logpath is defined %}
|
|
||||||
logpath = {{ jail.logpath }}
|
|
||||||
{% endif %}
|
|
||||||
{% if jail.filter is defined %}
|
|
||||||
filter = {{ jail.filter }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
[Definition]
|
|
||||||
failregex = ^\s*\S+ authentication\s+error\s+.*?remote\.ip=<HOST>
|
|
||||||
^\s*.*?Authentication failed.*?ip=<HOST>
|
|
||||||
ignoreregex =
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
nginx_server_name: "mail.rubenhensen.nl"
|
|
||||||
nginx_stalwart_server_name: "stalwart.rubenhensen.nl"
|
|
||||||
nginx_tls_cert: "/etc/letsencrypt/live/mail.rubenhensen.nl/fullchain.pem"
|
|
||||||
nginx_tls_key: "/etc/letsencrypt/live/mail.rubenhensen.nl/privkey.pem"
|
|
||||||
nginx_stalwart_backend: "127.0.0.1:8443"
|
|
||||||
nginx_sogo_backend: "127.0.0.1:20000"
|
|
||||||
|
|
||||||
nginx_local_tls_domains:
|
|
||||||
- mail.rubenhensen.nl
|
|
||||||
- stalwart.rubenhensen.nl
|
|
||||||
nginx_k8s_upstream_conf: "/etc/nginx/stream.d/k8s-upstream.conf"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
- name: reload nginx
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: nginx
|
|
||||||
state: reloaded
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install Nginx
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: nginx
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Install nginx stream modules
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: libnginx-mod-stream
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Create stream.d directory
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/nginx/stream.d
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Deploy nginx.conf
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: nginx.conf.j2
|
|
||||||
dest: /etc/nginx/nginx.conf
|
|
||||||
mode: "0644"
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Remove default site
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/nginx/sites-enabled/default
|
|
||||||
state: absent
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Deploy mail site config
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: mail.conf.j2
|
|
||||||
dest: /etc/nginx/sites-available/mail.conf
|
|
||||||
mode: "0644"
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Enable mail site
|
|
||||||
ansible.builtin.file:
|
|
||||||
src: /etc/nginx/sites-available/mail.conf
|
|
||||||
dest: /etc/nginx/sites-enabled/mail.conf
|
|
||||||
state: link
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Deploy K8s upstream config (initial only)
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: k8s-upstream.conf.j2
|
|
||||||
dest: "{{ nginx_k8s_upstream_conf }}"
|
|
||||||
mode: "0644"
|
|
||||||
force: false
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Deploy K8s backend IP config (initial only)
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: k8s-backend-ip.conf.j2
|
|
||||||
dest: /etc/nginx/k8s-backend-ip.conf
|
|
||||||
mode: "0644"
|
|
||||||
force: false
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Deploy K8s HTTP proxy config
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: k8s-proxy.conf.j2
|
|
||||||
dest: /etc/nginx/sites-available/k8s-proxy.conf
|
|
||||||
mode: "0644"
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Enable K8s proxy site
|
|
||||||
ansible.builtin.file:
|
|
||||||
src: /etc/nginx/sites-available/k8s-proxy.conf
|
|
||||||
dest: /etc/nginx/sites-enabled/k8s-proxy.conf
|
|
||||||
state: link
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Deploy K8s IP update script
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /usr/local/bin/update-k8s-ip
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
NEW_IP="$SSH_ORIGINAL_COMMAND"
|
|
||||||
|
|
||||||
if ! echo "$NEW_IP" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
|
||||||
echo "Invalid IP: $NEW_IP"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
CONF="{{ nginx_k8s_upstream_conf }}"
|
|
||||||
CURRENT_IP=$(grep -oP 'server \K[0-9.]+' "$CONF" 2>/dev/null)
|
|
||||||
|
|
||||||
if [ "$CURRENT_IP" = "$NEW_IP" ]; then
|
|
||||||
echo "IP unchanged: $NEW_IP"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf 'upstream k8s_tls {\n server %s:443;\n}\n' "$NEW_IP" > "$CONF"
|
|
||||||
printf 'set $k8s_ip %s;\n' "$NEW_IP" > /etc/nginx/k8s-backend-ip.conf
|
|
||||||
|
|
||||||
nginx -t && systemctl reload nginx
|
|
||||||
echo "Updated K8s backend IP to $NEW_IP"
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Ensure .ssh directory exists for root
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /root/.ssh
|
|
||||||
state: directory
|
|
||||||
mode: "0700"
|
|
||||||
|
|
||||||
- name: Deploy SSH authorized key for K8s IP updater
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: root
|
|
||||||
key: "{{ nginx_k8s_updater_ssh_pubkey }}"
|
|
||||||
key_options: 'command="/usr/local/bin/update-k8s-ip",no-port-forwarding,no-X11-forwarding,no-agent-forwarding'
|
|
||||||
when: nginx_k8s_updater_ssh_pubkey is defined
|
|
||||||
|
|
||||||
- name: Enable and start Nginx
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: nginx
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
set $k8s_ip 127.0.0.1;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Catch-all for any domain not handled by mail.conf
|
|
||||||
# Proxies ACME challenges to K8s for cert-manager, redirects rest to HTTPS
|
|
||||||
server {
|
|
||||||
listen 80 default_server;
|
|
||||||
listen [::]:80 default_server;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
location /.well-known/acme-challenge/ {
|
|
||||||
include /etc/nginx/k8s-backend-ip.conf;
|
|
||||||
proxy_pass http://$k8s_ip:80;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
upstream k8s_tls {
|
|
||||||
server 127.0.0.1:443;
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name {{ nginx_server_name }};
|
|
||||||
|
|
||||||
# ACME challenge
|
|
||||||
location /.well-known/acme-challenge/ {
|
|
||||||
root /var/www/acme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Redirect everything else to HTTPS
|
|
||||||
location / {
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 127.0.0.1:8443 ssl http2;
|
|
||||||
server_name {{ nginx_server_name }};
|
|
||||||
|
|
||||||
ssl_certificate {{ nginx_tls_cert }};
|
|
||||||
ssl_certificate_key {{ nginx_tls_key }};
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
|
|
||||||
# SOGo
|
|
||||||
location ^~ /SOGo {
|
|
||||||
proxy_pass http://{{ nginx_sogo_backend }};
|
|
||||||
proxy_redirect http://{{ nginx_sogo_backend }} /;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
proxy_set_header x-webobjects-server-protocol HTTP/1.0;
|
|
||||||
proxy_set_header x-webobjects-remote-host $remote_addr;
|
|
||||||
proxy_set_header x-webobjects-server-name $server_name;
|
|
||||||
proxy_set_header x-webobjects-server-url $scheme://$host;
|
|
||||||
proxy_set_header x-webobjects-server-port $server_port;
|
|
||||||
proxy_buffer_size 128k;
|
|
||||||
proxy_buffers 64 512k;
|
|
||||||
proxy_busy_buffers_size 512k;
|
|
||||||
client_max_body_size 100m;
|
|
||||||
}
|
|
||||||
|
|
||||||
# SOGo static resources
|
|
||||||
location ^~ /SOGo.woa/WebServerResources/ {
|
|
||||||
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /SOGo/WebServerResources/ {
|
|
||||||
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Microsoft AutoDiscover / ActiveSync
|
|
||||||
location ^~ /Microsoft-Server-ActiveSync {
|
|
||||||
proxy_pass http://{{ nginx_sogo_backend }}/SOGo/Microsoft-Server-ActiveSync;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
proxy_connect_timeout 360;
|
|
||||||
proxy_send_timeout 360;
|
|
||||||
proxy_read_timeout 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
# CalDAV/CardDAV well-known
|
|
||||||
location /.well-known/caldav {
|
|
||||||
return 301 https://$host/SOGo/dav;
|
|
||||||
}
|
|
||||||
location /.well-known/carddav {
|
|
||||||
return 301 https://$host/SOGo/dav;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Default: SOGo
|
|
||||||
location / {
|
|
||||||
return 301 https://$host/SOGo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Stalwart web admin
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name {{ nginx_stalwart_server_name }};
|
|
||||||
|
|
||||||
location /.well-known/acme-challenge/ {
|
|
||||||
root /var/www/acme;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 127.0.0.1:8443 ssl http2;
|
|
||||||
server_name {{ nginx_stalwart_server_name }};
|
|
||||||
|
|
||||||
ssl_certificate {{ nginx_tls_cert }};
|
|
||||||
ssl_certificate_key {{ nginx_tls_key }};
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass https://{{ nginx_stalwart_backend }};
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
user www-data;
|
|
||||||
worker_processes auto;
|
|
||||||
pid /run/nginx.pid;
|
|
||||||
include /etc/nginx/modules-enabled/*.conf;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 768;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log;
|
|
||||||
error_log /var/log/nginx/error.log;
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
|
|
||||||
include /etc/nginx/sites-enabled/*;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream {
|
|
||||||
log_format stream '$remote_addr [$time_local] '
|
|
||||||
'$protocol $status $bytes_sent $bytes_received '
|
|
||||||
'$session_time "$ssl_preread_server_name"';
|
|
||||||
access_log /var/log/nginx/stream.log stream;
|
|
||||||
|
|
||||||
map $ssl_preread_server_name $tls_backend {
|
|
||||||
{% for domain in nginx_local_tls_domains %}
|
|
||||||
{{ domain }} local_tls;
|
|
||||||
{% endfor %}
|
|
||||||
default k8s_tls;
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream local_tls {
|
|
||||||
server 127.0.0.1:8443;
|
|
||||||
}
|
|
||||||
|
|
||||||
include /etc/nginx/stream.d/*.conf;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443;
|
|
||||||
listen [::]:443;
|
|
||||||
ssl_preread on;
|
|
||||||
proxy_pass $tls_backend;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
sogo_db_name: "sogo"
|
|
||||||
sogo_db_user: "sogo"
|
|
||||||
sogo_db_password: "{{ vault_sogo_db_password }}"
|
|
||||||
|
|
||||||
sogo_imap_server: "127.0.0.1"
|
|
||||||
sogo_smtp_server: "127.0.0.1"
|
|
||||||
sogo_mail_domain: "rubenhensen.nl"
|
|
||||||
|
|
||||||
sogo_time_zone: "Europe/Amsterdam"
|
|
||||||
sogo_page_title: "Mail - rubenhensen.nl"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
- name: restart sogo
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: sogo
|
|
||||||
state: restarted
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install PostgreSQL
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name:
|
|
||||||
- postgresql
|
|
||||||
- postgresql-client
|
|
||||||
- python3-psycopg2
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Ensure PostgreSQL is running
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: postgresql
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
|
|
||||||
- name: Create SOGo database user
|
|
||||||
become: true
|
|
||||||
become_user: postgres
|
|
||||||
community.postgresql.postgresql_user:
|
|
||||||
name: "{{ sogo_db_user }}"
|
|
||||||
password: "{{ sogo_db_password }}"
|
|
||||||
|
|
||||||
- name: Create SOGo database
|
|
||||||
become: true
|
|
||||||
become_user: postgres
|
|
||||||
community.postgresql.postgresql_db:
|
|
||||||
name: "{{ sogo_db_name }}"
|
|
||||||
owner: "{{ sogo_db_user }}"
|
|
||||||
|
|
||||||
- name: Grant SOGo user read access to Stalwart accounts
|
|
||||||
become: true
|
|
||||||
become_user: postgres
|
|
||||||
community.postgresql.postgresql_privs:
|
|
||||||
database: "stalwart"
|
|
||||||
roles: "{{ sogo_db_user }}"
|
|
||||||
type: table
|
|
||||||
objs: accounts,emails
|
|
||||||
privs: SELECT
|
|
||||||
|
|
||||||
- name: Download SOGo repository key
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: >
|
|
||||||
wget -qO- "https://keys.openpgp.org/vks/v1/by-fingerprint/74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9"
|
|
||||||
| gpg --dearmor > /etc/apt/trusted.gpg.d/sogo.gpg
|
|
||||||
creates: /etc/apt/trusted.gpg.d/sogo.gpg
|
|
||||||
|
|
||||||
- name: Add SOGo nightly repository
|
|
||||||
ansible.builtin.apt_repository:
|
|
||||||
repo: "deb https://packages.sogo.nu/nightly/5/ubuntu/ {{ ansible_facts['distribution_release'] }} {{ ansible_facts['distribution_release'] }}"
|
|
||||||
filename: sogo
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Install SOGo and dependencies
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name:
|
|
||||||
- sogo
|
|
||||||
- sogo-activesync
|
|
||||||
- sope4.9-gdl1-postgresql
|
|
||||||
state: present
|
|
||||||
update_cache: true
|
|
||||||
|
|
||||||
- name: Disable Apache (installed as SOGo dependency, Nginx is used instead)
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: apache2
|
|
||||||
enabled: false
|
|
||||||
state: stopped
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Deploy SOGo configuration
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: sogo.conf.j2
|
|
||||||
dest: /etc/sogo/sogo.conf
|
|
||||||
owner: sogo
|
|
||||||
group: sogo
|
|
||||||
mode: "0640"
|
|
||||||
notify: restart sogo
|
|
||||||
|
|
||||||
- name: Enable and start SOGo
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: sogo
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
|
|
||||||
- name: Enable and start memcached
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: memcached
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
SOGoTimeZone = "{{ sogo_time_zone }}";
|
|
||||||
SOGoPageTitle = "{{ sogo_page_title }}";
|
|
||||||
SOGoLanguage = "English";
|
|
||||||
|
|
||||||
SOGoMailDomain = "{{ sogo_mail_domain }}";
|
|
||||||
SOGoIMAPServer = "imaps://{{ sogo_imap_server }}:993/?tls=YES&tlsVerifyMode=none";
|
|
||||||
SOGoSMTPServer = "smtp://{{ sogo_smtp_server }}:587/?tls=YES&tlsVerifyMode=none";
|
|
||||||
SOGoSMTPAuthenticationType = "PLAIN";
|
|
||||||
SOGoIMAPAclConformsToIMAPExt = YES;
|
|
||||||
SOGoForceExternalLoginWithEmail = YES;
|
|
||||||
|
|
||||||
SOGoUserSources = (
|
|
||||||
{
|
|
||||||
type = sql;
|
|
||||||
id = directory;
|
|
||||||
viewURL = "postgresql://{{ sogo_db_user }}:{{ sogo_db_password }}@127.0.0.1:5432/stalwart/sogo_users";
|
|
||||||
canAuthenticate = YES;
|
|
||||||
isAddressBook = NO;
|
|
||||||
LoginFieldNames = ("mail");
|
|
||||||
userPasswordAlgorithm = "sha512-crypt";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
SOGoProfileURL = "postgresql://{{ sogo_db_user }}:{{ sogo_db_password }}@127.0.0.1:5432/{{ sogo_db_name }}/sogo_user_profile";
|
|
||||||
OCSFolderInfoURL = "postgresql://{{ sogo_db_user }}:{{ sogo_db_password }}@127.0.0.1:5432/{{ sogo_db_name }}/sogo_folder_info";
|
|
||||||
OCSSessionsFolderURL = "postgresql://{{ sogo_db_user }}:{{ sogo_db_password }}@127.0.0.1:5432/{{ sogo_db_name }}/sogo_sessions_folder";
|
|
||||||
OCSEMailAlarmsFolderURL = "postgresql://{{ sogo_db_user }}:{{ sogo_db_password }}@127.0.0.1:5432/{{ sogo_db_name }}/sogo_alarms_folder";
|
|
||||||
OCSStoreURL = "postgresql://{{ sogo_db_user }}:{{ sogo_db_password }}@127.0.0.1:5432/{{ sogo_db_name }}/sogo_store";
|
|
||||||
OCSAclURL = "postgresql://{{ sogo_db_user }}:{{ sogo_db_password }}@127.0.0.1:5432/{{ sogo_db_name }}/sogo_acl";
|
|
||||||
OCSCacheFolderURL = "postgresql://{{ sogo_db_user }}:{{ sogo_db_password }}@127.0.0.1:5432/{{ sogo_db_name }}/sogo_cache_folder";
|
|
||||||
|
|
||||||
SOGoAppointmentSendEMailNotifications = YES;
|
|
||||||
SOGoEnableEMailAlarms = YES;
|
|
||||||
|
|
||||||
SOGoMemcachedHost = "127.0.0.1";
|
|
||||||
|
|
||||||
SOGoCalendarDefaultRoles = (
|
|
||||||
PublicViewer,
|
|
||||||
ConfidentialDAccessor
|
|
||||||
);
|
|
||||||
|
|
||||||
SOGoSuperUsernames = ("admin");
|
|
||||||
SOGoFirstDayOfWeek = 1;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
ssh_port: 22
|
|
||||||
ssh_permit_root_login: "prohibit-password" # allows key-based root login
|
|
||||||
ssh_password_authentication: "no"
|
|
||||||
ssh_max_auth_tries: 3
|
|
||||||
ssh_allowed_users: "root" # space-separated list
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
- name: restart sshd
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: sshd
|
|
||||||
state: restarted
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Deploy hardened sshd config
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: sshd_config.j2
|
|
||||||
dest: /etc/ssh/sshd_config
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0600"
|
|
||||||
validate: "sshd -t -f %s"
|
|
||||||
notify: restart sshd
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
Port {{ ssh_port }}
|
|
||||||
AddressFamily any
|
|
||||||
ListenAddress 0.0.0.0
|
|
||||||
ListenAddress ::
|
|
||||||
|
|
||||||
# Authentication
|
|
||||||
PermitRootLogin {{ ssh_permit_root_login }}
|
|
||||||
PasswordAuthentication {{ ssh_password_authentication }}
|
|
||||||
ChallengeResponseAuthentication no
|
|
||||||
KbdInteractiveAuthentication no
|
|
||||||
UsePAM yes
|
|
||||||
MaxAuthTries {{ ssh_max_auth_tries }}
|
|
||||||
PubkeyAuthentication yes
|
|
||||||
AuthorizedKeysFile .ssh/authorized_keys
|
|
||||||
|
|
||||||
# Disable unused auth methods
|
|
||||||
GSSAPIAuthentication no
|
|
||||||
KerberosAuthentication no
|
|
||||||
HostbasedAuthentication no
|
|
||||||
PermitEmptyPasswords no
|
|
||||||
|
|
||||||
# Session
|
|
||||||
X11Forwarding no
|
|
||||||
PrintMotd no
|
|
||||||
TCPKeepAlive yes
|
|
||||||
ClientAliveInterval 300
|
|
||||||
ClientAliveCountMax 2
|
|
||||||
LoginGraceTime 30
|
|
||||||
|
|
||||||
# Restrict users
|
|
||||||
AllowUsers {{ ssh_allowed_users }}
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
LogLevel VERBOSE
|
|
||||||
SyslogFacility AUTH
|
|
||||||
|
|
||||||
# SFTP
|
|
||||||
Subsystem sftp /usr/lib/openssh/sftp-server
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
---
|
|
||||||
stalwart_version: "0.15.5"
|
|
||||||
|
|
||||||
stalwart_system_user: "stalwart"
|
|
||||||
stalwart_system_group: "stalwart"
|
|
||||||
|
|
||||||
stalwart_service_state: "started"
|
|
||||||
stalwart_service_enabled: true
|
|
||||||
|
|
||||||
stalwart_server_hostname: "mail.rubenhensen.nl"
|
|
||||||
stalwart_server_max_connections: 8192
|
|
||||||
|
|
||||||
# Storage backend
|
|
||||||
stalwart_storage_data: "rocksdb"
|
|
||||||
stalwart_storage_fts: "rocksdb"
|
|
||||||
stalwart_storage_blob: "rocksdb"
|
|
||||||
stalwart_storage_lookup: "rocksdb"
|
|
||||||
|
|
||||||
# Admin credentials
|
|
||||||
stalwart_fallback_admin_login: "admin"
|
|
||||||
stalwart_fallback_admin_password: "changeme!" # override via vault in host_vars
|
|
||||||
|
|
||||||
# Listeners
|
|
||||||
stalwart_server_listeners:
|
|
||||||
- name: "smtp"
|
|
||||||
bind: "[::]:25"
|
|
||||||
protocol: "smtp"
|
|
||||||
- name: "submission"
|
|
||||||
bind: "[::]:587"
|
|
||||||
protocol: "smtp"
|
|
||||||
- name: "submissions"
|
|
||||||
bind: "[::]:465"
|
|
||||||
protocol: "smtp"
|
|
||||||
options:
|
|
||||||
tls.implicit: true
|
|
||||||
- name: "imaptls"
|
|
||||||
bind: "[::]:993"
|
|
||||||
protocol: "imap"
|
|
||||||
options:
|
|
||||||
tls.implicit: true
|
|
||||||
- name: "https"
|
|
||||||
bind: "127.0.0.1:8443"
|
|
||||||
protocol: "http"
|
|
||||||
options:
|
|
||||||
tls.implicit: true
|
|
||||||
- name: "http"
|
|
||||||
bind: "127.0.0.1:8080"
|
|
||||||
protocol: "http"
|
|
||||||
|
|
||||||
# Stores
|
|
||||||
stalwart_stores:
|
|
||||||
- name: "rocksdb"
|
|
||||||
type: "rocksdb"
|
|
||||||
options:
|
|
||||||
path: "{{ stalwart_data_path }}"
|
|
||||||
compression: "lz4"
|
|
||||||
- name: "postgresql"
|
|
||||||
type: "postgresql"
|
|
||||||
options:
|
|
||||||
host: "127.0.0.1"
|
|
||||||
port: 5432
|
|
||||||
database: "{{ stalwart_db_name }}"
|
|
||||||
user: "{{ stalwart_db_user }}"
|
|
||||||
password: "{{ stalwart_db_password }}"
|
|
||||||
|
|
||||||
# Directory
|
|
||||||
stalwart_directory_type: "sql"
|
|
||||||
stalwart_directory_options:
|
|
||||||
store: "postgresql"
|
|
||||||
|
|
||||||
# Database
|
|
||||||
stalwart_db_name: "stalwart"
|
|
||||||
stalwart_db_user: "stalwart"
|
|
||||||
stalwart_db_password: "{{ vault_stalwart_db_password }}"
|
|
||||||
|
|
||||||
# Tracers
|
|
||||||
stalwart_tracers:
|
|
||||||
- type: "stdout"
|
|
||||||
options:
|
|
||||||
level: "info"
|
|
||||||
ansi: false
|
|
||||||
enable: true
|
|
||||||
- type: "log"
|
|
||||||
options:
|
|
||||||
level: "info"
|
|
||||||
path: "{{ stalwart_logs_path }}"
|
|
||||||
prefix: "stalwart.log"
|
|
||||||
rotate: "daily"
|
|
||||||
ansi: false
|
|
||||||
enable: true
|
|
||||||
|
|
||||||
# TLS certificate (managed by certbot)
|
|
||||||
stalwart_tls_cert: "/etc/letsencrypt/live/{{ stalwart_server_hostname }}/fullchain.pem"
|
|
||||||
stalwart_tls_key: "/etc/letsencrypt/live/{{ stalwart_server_hostname }}/privkey.pem"
|
|
||||||
|
|
||||||
# Extra config sections (list of {name, options} dicts)
|
|
||||||
stalwart_additional_configs: []
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS accounts (
|
|
||||||
name VARCHAR(255) PRIMARY KEY,
|
|
||||||
password VARCHAR(255) NOT NULL,
|
|
||||||
description VARCHAR(255) DEFAULT '',
|
|
||||||
type VARCHAR(20) NOT NULL DEFAULT 'individual',
|
|
||||||
email VARCHAR(255) NOT NULL,
|
|
||||||
quota INTEGER DEFAULT 0,
|
|
||||||
active BOOLEAN DEFAULT true
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS group_members (
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
member_of VARCHAR(255) NOT NULL,
|
|
||||||
PRIMARY KEY (name, member_of)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS emails (
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
address VARCHAR(255) NOT NULL,
|
|
||||||
type VARCHAR(20) NOT NULL DEFAULT 'primary',
|
|
||||||
PRIMARY KEY (name, address)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- View for SOGo (expects c_ prefixed columns)
|
|
||||||
CREATE OR REPLACE VIEW sogo_users AS
|
|
||||||
SELECT
|
|
||||||
name AS c_uid,
|
|
||||||
name AS c_name,
|
|
||||||
password AS c_password,
|
|
||||||
description AS c_cn,
|
|
||||||
email AS mail
|
|
||||||
FROM accounts
|
|
||||||
WHERE active = true;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
- name: reload systemd
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
daemon_reload: true
|
|
||||||
|
|
||||||
- name: restart stalwart
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: "{{ stalwart_service_name }}"
|
|
||||||
state: restarted
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Initialize Stalwart
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: "{{ stalwart_executable_path }} --init {{ stalwart_install_path }}"
|
|
||||||
creates: "{{ stalwart_config_file_path }}"
|
|
||||||
become: true
|
|
||||||
become_user: "{{ stalwart_system_user }}"
|
|
||||||
|
|
||||||
- name: Deploy configuration
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: "config.toml.j2"
|
|
||||||
dest: "{{ stalwart_config_file_path }}"
|
|
||||||
owner: "{{ stalwart_system_user }}"
|
|
||||||
group: "{{ stalwart_system_group }}"
|
|
||||||
mode: "0640"
|
|
||||||
notify: restart stalwart
|
|
||||||
|
|
||||||
- name: Ensure service is in correct state
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: "{{ stalwart_service_name }}"
|
|
||||||
state: "{{ stalwart_service_state }}"
|
|
||||||
enabled: "{{ stalwart_service_enabled }}"
|
|
||||||
|
|
||||||
- name: Flush handlers to ensure Stalwart is running
|
|
||||||
ansible.builtin.meta: flush_handlers
|
|
||||||
|
|
||||||
- name: Wait for Stalwart API
|
|
||||||
ansible.builtin.wait_for:
|
|
||||||
port: 8080
|
|
||||||
host: 127.0.0.1
|
|
||||||
delay: 2
|
|
||||||
timeout: 30
|
|
||||||
|
|
||||||
- name: Read TLS certificate
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: "{{ stalwart_tls_cert }}"
|
|
||||||
register: tls_cert_content
|
|
||||||
|
|
||||||
- name: Read TLS private key
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: "{{ stalwart_tls_key }}"
|
|
||||||
register: tls_key_content
|
|
||||||
|
|
||||||
- name: Push TLS certificate to Stalwart
|
|
||||||
ansible.builtin.uri:
|
|
||||||
url: "http://127.0.0.1:8080/api/settings"
|
|
||||||
method: POST
|
|
||||||
user: "{{ stalwart_fallback_admin_login }}"
|
|
||||||
password: "{{ stalwart_fallback_admin_password }}"
|
|
||||||
force_basic_auth: true
|
|
||||||
body_format: json
|
|
||||||
body:
|
|
||||||
- type: "insert"
|
|
||||||
assert_empty: false
|
|
||||||
values:
|
|
||||||
- - "certificate.default.cert"
|
|
||||||
- "{{ tls_cert_content.content | b64decode }}"
|
|
||||||
- - "certificate.default.private-key"
|
|
||||||
- "{{ tls_key_content.content | b64decode }}"
|
|
||||||
- - "certificate.default.default"
|
|
||||||
- "true"
|
|
||||||
- - "server.tls.certificate"
|
|
||||||
- "default"
|
|
||||||
status_code: [200, 204]
|
|
||||||
|
|
||||||
- name: Reload Stalwart to apply certificate
|
|
||||||
ansible.builtin.uri:
|
|
||||||
url: "http://127.0.0.1:8080/api/reload"
|
|
||||||
method: GET
|
|
||||||
user: "{{ stalwart_fallback_admin_login }}"
|
|
||||||
password: "{{ stalwart_fallback_admin_password }}"
|
|
||||||
force_basic_auth: true
|
|
||||||
status_code: [200, 204]
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Ensure directories exist
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ directory_to_create }}"
|
|
||||||
state: "directory"
|
|
||||||
owner: "{{ stalwart_system_user }}"
|
|
||||||
group: "{{ stalwart_system_group }}"
|
|
||||||
mode: "0750"
|
|
||||||
loop:
|
|
||||||
- "{{ stalwart_install_path }}"
|
|
||||||
- "{{ stalwart_bin_path }}"
|
|
||||||
- "{{ stalwart_config_path }}"
|
|
||||||
- "{{ stalwart_logs_path }}"
|
|
||||||
- "{{ stalwart_data_path }}"
|
|
||||||
loop_control:
|
|
||||||
loop_var: "directory_to_create"
|
|
||||||
|
|
||||||
- name: Download and extract Stalwart
|
|
||||||
ansible.builtin.unarchive:
|
|
||||||
src: "{{ stalwart_download_url }}"
|
|
||||||
dest: "{{ stalwart_bin_path }}"
|
|
||||||
owner: "{{ stalwart_system_user }}"
|
|
||||||
group: "{{ stalwart_system_group }}"
|
|
||||||
mode: "0750"
|
|
||||||
remote_src: true
|
|
||||||
notify: restart stalwart
|
|
||||||
when: (not stalwart_exec.stat.exists) or (stalwart_current_version != stalwart_version)
|
|
||||||
|
|
||||||
- name: Install systemd service file
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: "stalwart.service.j2"
|
|
||||||
dest: "{{ stalwart_service_file_path }}"
|
|
||||||
mode: "0644"
|
|
||||||
notify:
|
|
||||||
- reload systemd
|
|
||||||
- restart stalwart
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Import prepare tasks
|
|
||||||
ansible.builtin.import_tasks: "prepare.yml"
|
|
||||||
|
|
||||||
- name: Import install tasks
|
|
||||||
ansible.builtin.import_tasks: "install.yml"
|
|
||||||
|
|
||||||
- name: Import configure tasks
|
|
||||||
ansible.builtin.import_tasks: "configure.yml"
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Create Stalwart group
|
|
||||||
ansible.builtin.group:
|
|
||||||
name: "{{ stalwart_system_group }}"
|
|
||||||
system: true
|
|
||||||
|
|
||||||
- name: Create Stalwart user
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ stalwart_system_user }}"
|
|
||||||
group: "{{ stalwart_system_group }}"
|
|
||||||
create_home: false
|
|
||||||
home: "{{ stalwart_install_path }}"
|
|
||||||
shell: "/usr/sbin/nologin"
|
|
||||||
system: true
|
|
||||||
|
|
||||||
- name: Create Ansible remote_tmp for stalwart user
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ stalwart_install_path }}/.ansible/tmp"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ stalwart_system_user }}"
|
|
||||||
group: "{{ stalwart_system_group }}"
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Create Stalwart database user
|
|
||||||
become: true
|
|
||||||
become_user: postgres
|
|
||||||
community.postgresql.postgresql_user:
|
|
||||||
name: "{{ stalwart_db_user }}"
|
|
||||||
password: "{{ stalwart_db_password }}"
|
|
||||||
when: stalwart_directory_type == "sql"
|
|
||||||
|
|
||||||
- name: Create Stalwart database
|
|
||||||
become: true
|
|
||||||
become_user: postgres
|
|
||||||
community.postgresql.postgresql_db:
|
|
||||||
name: "{{ stalwart_db_name }}"
|
|
||||||
owner: "{{ stalwart_db_user }}"
|
|
||||||
when: stalwart_directory_type == "sql"
|
|
||||||
|
|
||||||
- name: Deploy database schema
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: schema.sql
|
|
||||||
dest: /tmp/stalwart-schema.sql
|
|
||||||
mode: "0644"
|
|
||||||
when: stalwart_directory_type == "sql"
|
|
||||||
|
|
||||||
- name: Apply database schema
|
|
||||||
become: true
|
|
||||||
become_user: postgres
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: psql -d {{ stalwart_db_name }} -f /tmp/stalwart-schema.sql
|
|
||||||
changed_when: false
|
|
||||||
when: stalwart_directory_type == "sql"
|
|
||||||
|
|
||||||
- name: Check if already installed
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "{{ stalwart_executable_path }}"
|
|
||||||
register: stalwart_exec
|
|
||||||
|
|
||||||
- name: Get version if stalwart is installed
|
|
||||||
when: stalwart_exec.stat.exists
|
|
||||||
block:
|
|
||||||
- name: Get version
|
|
||||||
ansible.builtin.command: "{{ stalwart_executable_path }} -V"
|
|
||||||
failed_when: false
|
|
||||||
changed_when: false
|
|
||||||
check_mode: false
|
|
||||||
register: stalwart_output
|
|
||||||
|
|
||||||
- name: Set current installed version
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
stalwart_current_version: "{{ stalwart_output.stdout_lines[0] }}"
|
|
||||||
|
|
||||||
- name: Check if config exists
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "{{ stalwart_config_file_path }}"
|
|
||||||
register: stalwart_config_file_st
|
|
||||||
|
|
||||||
- name: Preserve admin password salt from existing config
|
|
||||||
when: stalwart_config_file_st.stat.exists
|
|
||||||
block:
|
|
||||||
- name: Get config file content
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: "{{ stalwart_config_file_path }}"
|
|
||||||
register: config_file_content
|
|
||||||
|
|
||||||
- name: Get existing password hash
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
hash_stored: "{{ config_file_content['content'] | b64decode | regex_search('(\\n|^)(secret|authentication\\.fallback-admin\\.secret) = \"(.*)\"\\n', '\\3') }}"
|
|
||||||
|
|
||||||
- name: Get existing salt
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
stalwart_fallback_admin_password_salt: "{{ (hash_stored[0] | split('$'))[2] }}"
|
|
||||||
|
|
||||||
- name: Create salt if it does not exist
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
stalwart_fallback_admin_password_salt: "{{ lookup('password', '/dev/null length=16 chars=ascii_letters,digits') }}"
|
|
||||||
when: stalwart_fallback_admin_password_salt is undefined
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
[server]
|
|
||||||
hostname = "{{ stalwart_server_hostname }}"
|
|
||||||
max-connections = {{ stalwart_server_max_connections }}
|
|
||||||
|
|
||||||
{% for listener in stalwart_server_listeners %}
|
|
||||||
[server.listener.{{ listener.name }}]
|
|
||||||
bind = "{{ listener.bind }}"
|
|
||||||
protocol = "{{ listener.protocol }}"
|
|
||||||
{% for option_name, option_value in (listener.options | default({})).items() %}
|
|
||||||
{{ option_name }} =
|
|
||||||
{%- if option_value is string %} "{{ option_value }}"
|
|
||||||
{%- elif option_value is boolean %} {{ option_value | lower }}
|
|
||||||
{%- else %} {{ option_value }}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
[storage]
|
|
||||||
data = "{{ stalwart_storage_data }}"
|
|
||||||
fts = "{{ stalwart_storage_fts }}"
|
|
||||||
blob = "{{ stalwart_storage_blob }}"
|
|
||||||
lookup = "{{ stalwart_storage_lookup }}"
|
|
||||||
directory = "{{ stalwart_directory_type }}"
|
|
||||||
|
|
||||||
{% for store in stalwart_stores %}
|
|
||||||
[store.{{ store.name }}]
|
|
||||||
type = "{{ store.type }}"
|
|
||||||
{% for option_name, option_value in (store.options | default({})).items() %}
|
|
||||||
{{ option_name }} =
|
|
||||||
{%- if option_value is string %} "{{ option_value }}"
|
|
||||||
{%- elif option_value is boolean %} {{ option_value | lower }}
|
|
||||||
{%- else %} {{ option_value }}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
[directory.{{ stalwart_directory_type }}]
|
|
||||||
type = "{{ stalwart_directory_type }}"
|
|
||||||
{% for option_name, option_value in stalwart_directory_options.items() %}
|
|
||||||
{{ option_name }} =
|
|
||||||
{%- if option_value is string %} "{{ option_value }}"
|
|
||||||
{%- elif option_value is boolean %} {{ option_value | lower }}
|
|
||||||
{%- else %} {{ option_value }}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
{% if stalwart_directory_type == "sql" %}
|
|
||||||
columns.secret = "password"
|
|
||||||
columns.description = "description"
|
|
||||||
columns.email = "email"
|
|
||||||
columns.quota = "quota"
|
|
||||||
columns.class = "type"
|
|
||||||
|
|
||||||
[store.postgresql.query]
|
|
||||||
name = "SELECT name, type, password, description, email, quota FROM accounts WHERE name = $1 AND active = true"
|
|
||||||
members = "SELECT member_of FROM group_members WHERE name = $1"
|
|
||||||
recipients = "SELECT name FROM emails WHERE address = $1"
|
|
||||||
emails = "SELECT address FROM emails WHERE name = $1 ORDER BY type DESC, address ASC"
|
|
||||||
secrets = "SELECT password FROM accounts WHERE name = $1 AND active = true"
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
{% for tracer in stalwart_tracers %}
|
|
||||||
[tracer.{{ tracer.type }}]
|
|
||||||
type = "{{ tracer.type }}"
|
|
||||||
{% for option_name, option_value in (tracer.options | default({})).items() %}
|
|
||||||
{{ option_name }} =
|
|
||||||
{%- if option_value is string %} "{{ option_value }}"
|
|
||||||
{%- elif option_value is boolean %} {{ option_value | lower }}
|
|
||||||
{%- else %} {{ option_value }}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
[authentication.fallback-admin]
|
|
||||||
user = "{{ stalwart_fallback_admin_login }}"
|
|
||||||
secret = "{{ stalwart_fallback_admin_password | ansible.builtin.password_hash(salt=stalwart_fallback_admin_password_salt, rounds=5000) }}"
|
|
||||||
|
|
||||||
{% for config in stalwart_additional_configs | default([]) %}
|
|
||||||
[{{ config.name }}]
|
|
||||||
{% for option_name, option_value in (config.options | default({})).items() %}
|
|
||||||
{{ option_name }} =
|
|
||||||
{%- if option_value is string %} "{{ option_value }}"
|
|
||||||
{%- elif option_value is boolean %} {{ option_value | lower }}
|
|
||||||
{%- else %} {{ option_value }}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Stalwart Mail Server
|
|
||||||
Conflicts=postfix.service sendmail.service exim4.service
|
|
||||||
ConditionPathExists={{ stalwart_config_file_path }}
|
|
||||||
After=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
LimitNOFILE=65536
|
|
||||||
KillMode=process
|
|
||||||
KillSignal=SIGINT
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
ExecStart={{ stalwart_executable_path }} --config={{ stalwart_config_file_path }}
|
|
||||||
SyslogIdentifier=stalwart
|
|
||||||
User={{ stalwart_system_user }}
|
|
||||||
Group={{ stalwart_system_group }}
|
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
stalwart_download_url: "https://github.com/stalwartlabs/stalwart/releases/download/v{{ stalwart_version }}/stalwart-{{ ansible_facts['architecture'] }}-unknown-linux-gnu.tar.gz"
|
|
||||||
|
|
||||||
stalwart_install_path: "/opt/stalwart"
|
|
||||||
stalwart_bin_path: "{{ stalwart_install_path }}/bin"
|
|
||||||
stalwart_config_path: "{{ stalwart_install_path }}/etc"
|
|
||||||
stalwart_data_path: "{{ stalwart_install_path }}/data"
|
|
||||||
stalwart_logs_path: "{{ stalwart_install_path }}/logs"
|
|
||||||
stalwart_executable_path: "{{ stalwart_bin_path }}/stalwart"
|
|
||||||
stalwart_config_file_path: "{{ stalwart_config_path }}/config.toml"
|
|
||||||
|
|
||||||
stalwart_service_name: "stalwart"
|
|
||||||
stalwart_service_file_path: "/etc/systemd/system/{{ stalwart_service_name }}.service"
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
ufw_default_incoming: "deny"
|
|
||||||
ufw_default_outgoing: "allow"
|
|
||||||
ufw_allowed_ports:
|
|
||||||
- { port: "{{ ssh_port | default(22) }}", proto: "tcp", comment: "SSH" }
|
|
||||||
- { port: "25", proto: "tcp", comment: "SMTP" }
|
|
||||||
- { port: "465", proto: "tcp", comment: "SMTP submissions (implicit TLS)" }
|
|
||||||
- { port: "587", proto: "tcp", comment: "SMTP submission (STARTTLS)" }
|
|
||||||
- { port: "993", proto: "tcp", comment: "IMAP (implicit TLS)" }
|
|
||||||
- { port: "443", proto: "tcp", comment: "HTTPS (web admin + JMAP)" }
|
|
||||||
- { port: "80", proto: "tcp", comment: "HTTP (ACME + redirect)" }
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install ufw
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: ufw
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Set default incoming policy
|
|
||||||
community.general.ufw:
|
|
||||||
direction: incoming
|
|
||||||
default: "{{ ufw_default_incoming }}"
|
|
||||||
|
|
||||||
- name: Set default outgoing policy
|
|
||||||
community.general.ufw:
|
|
||||||
direction: outgoing
|
|
||||||
default: "{{ ufw_default_outgoing }}"
|
|
||||||
|
|
||||||
- name: Allow configured ports
|
|
||||||
community.general.ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "{{ item.port }}"
|
|
||||||
proto: "{{ item.proto }}"
|
|
||||||
comment: "{{ item.comment | default(omit) }}"
|
|
||||||
loop: "{{ ufw_allowed_ports }}"
|
|
||||||
|
|
||||||
- name: Enable ufw
|
|
||||||
community.general.ufw:
|
|
||||||
state: enabled
|
|
||||||
@@ -5,8 +5,8 @@ records:
|
|||||||
content: "46.224.26.65"
|
content: "46.224.26.65"
|
||||||
- name: mail
|
- name: mail
|
||||||
expire: 86400
|
expire: 86400
|
||||||
type: CNAME
|
type: A
|
||||||
content: "hensen.io."
|
content: "62.41.87.114"
|
||||||
- name: stalwart
|
- name: stalwart
|
||||||
expire: 86400
|
expire: 86400
|
||||||
type: A
|
type: A
|
||||||
|
|||||||
Reference in New Issue
Block a user