diff --git a/.gitignore b/.gitignore index 23b2616..e87201d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ ente-photos-ref/ repomix-output.txt credentials-velero -velero-credentials \ No newline at end of file +velero-credentials +.env diff --git a/ansible/mailserver/.gitignore b/ansible/mailserver/.gitignore deleted file mode 100644 index b36779c..0000000 --- a/ansible/mailserver/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vault_pass diff --git a/ansible/mailserver/README.md b/ansible/mailserver/README.md deleted file mode 100644 index cca2289..0000000 --- a/ansible/mailserver/README.md +++ /dev/null @@ -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 unbanip ` diff --git a/ansible/mailserver/ansible.cfg b/ansible/mailserver/ansible.cfg deleted file mode 100644 index 71b854a..0000000 --- a/ansible/mailserver/ansible.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[defaults] -inventory = inventory.yml -roles_path = roles -host_key_checking = False -retry_files_enabled = False -vault_password_file = .vault_pass diff --git a/ansible/mailserver/host_vars/mailserver/vars.yml b/ansible/mailserver/host_vars/mailserver/vars.yml deleted file mode 100644 index f95638a..0000000 --- a/ansible/mailserver/host_vars/mailserver/vars.yml +++ /dev/null @@ -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" diff --git a/ansible/mailserver/host_vars/mailserver/vault.yml b/ansible/mailserver/host_vars/mailserver/vault.yml deleted file mode 100644 index d6203a5..0000000 --- a/ansible/mailserver/host_vars/mailserver/vault.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/inventory.yml b/ansible/mailserver/inventory.yml deleted file mode 100644 index 97a260c..0000000 --- a/ansible/mailserver/inventory.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/playbook.yml b/ansible/mailserver/playbook.yml deleted file mode 100644 index 4d8acb3..0000000 --- a/ansible/mailserver/playbook.yml +++ /dev/null @@ -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] } diff --git a/ansible/mailserver/roles/base/defaults/main.yml b/ansible/mailserver/roles/base/defaults/main.yml deleted file mode 100644 index 66b7129..0000000 --- a/ansible/mailserver/roles/base/defaults/main.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -base_timezone: "Europe/Amsterdam" -base_swap_size: "2G" -base_swap_enabled: true diff --git a/ansible/mailserver/roles/base/handlers/main.yml b/ansible/mailserver/roles/base/handlers/main.yml deleted file mode 100644 index 0a81482..0000000 --- a/ansible/mailserver/roles/base/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: reload sysctl - ansible.builtin.command: - cmd: sysctl --system - changed_when: true diff --git a/ansible/mailserver/roles/base/tasks/main.yml b/ansible/mailserver/roles/base/tasks/main.yml deleted file mode 100644 index 3269159..0000000 --- a/ansible/mailserver/roles/base/tasks/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/certbot/defaults/main.yml b/ansible/mailserver/roles/certbot/defaults/main.yml deleted file mode 100644 index 19c84d8..0000000 --- a/ansible/mailserver/roles/certbot/defaults/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -certbot_email: "admin@rubenhensen.nl" -certbot_domains: - - "mail.rubenhensen.nl" - - "stalwart.rubenhensen.nl" -certbot_webroot: "/var/www/acme" diff --git a/ansible/mailserver/roles/certbot/tasks/main.yml b/ansible/mailserver/roles/certbot/tasks/main.yml deleted file mode 100644 index caef20d..0000000 --- a/ansible/mailserver/roles/certbot/tasks/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/fail2ban/defaults/main.yml b/ansible/mailserver/roles/fail2ban/defaults/main.yml deleted file mode 100644 index 150e3bd..0000000 --- a/ansible/mailserver/roles/fail2ban/defaults/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/fail2ban/handlers/main.yml b/ansible/mailserver/roles/fail2ban/handlers/main.yml deleted file mode 100644 index 6f6780a..0000000 --- a/ansible/mailserver/roles/fail2ban/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: restart fail2ban - ansible.builtin.systemd: - name: fail2ban - state: restarted diff --git a/ansible/mailserver/roles/fail2ban/tasks/main.yml b/ansible/mailserver/roles/fail2ban/tasks/main.yml deleted file mode 100644 index 4d499ca..0000000 --- a/ansible/mailserver/roles/fail2ban/tasks/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/fail2ban/templates/jail.local.j2 b/ansible/mailserver/roles/fail2ban/templates/jail.local.j2 deleted file mode 100644 index 5906f7f..0000000 --- a/ansible/mailserver/roles/fail2ban/templates/jail.local.j2 +++ /dev/null @@ -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 %} diff --git a/ansible/mailserver/roles/fail2ban/templates/stalwart-auth.conf.j2 b/ansible/mailserver/roles/fail2ban/templates/stalwart-auth.conf.j2 deleted file mode 100644 index 1d821f3..0000000 --- a/ansible/mailserver/roles/fail2ban/templates/stalwart-auth.conf.j2 +++ /dev/null @@ -1,4 +0,0 @@ -[Definition] -failregex = ^\s*\S+ authentication\s+error\s+.*?remote\.ip= - ^\s*.*?Authentication failed.*?ip= -ignoreregex = diff --git a/ansible/mailserver/roles/nginx/defaults/main.yml b/ansible/mailserver/roles/nginx/defaults/main.yml deleted file mode 100644 index ac673e2..0000000 --- a/ansible/mailserver/roles/nginx/defaults/main.yml +++ /dev/null @@ -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" diff --git a/ansible/mailserver/roles/nginx/handlers/main.yml b/ansible/mailserver/roles/nginx/handlers/main.yml deleted file mode 100644 index e96eb8e..0000000 --- a/ansible/mailserver/roles/nginx/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: reload nginx - ansible.builtin.systemd: - name: nginx - state: reloaded diff --git a/ansible/mailserver/roles/nginx/tasks/main.yml b/ansible/mailserver/roles/nginx/tasks/main.yml deleted file mode 100644 index 335ed54..0000000 --- a/ansible/mailserver/roles/nginx/tasks/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/nginx/templates/k8s-backend-ip.conf.j2 b/ansible/mailserver/roles/nginx/templates/k8s-backend-ip.conf.j2 deleted file mode 100644 index 457eda0..0000000 --- a/ansible/mailserver/roles/nginx/templates/k8s-backend-ip.conf.j2 +++ /dev/null @@ -1 +0,0 @@ -set $k8s_ip 127.0.0.1; diff --git a/ansible/mailserver/roles/nginx/templates/k8s-proxy.conf.j2 b/ansible/mailserver/roles/nginx/templates/k8s-proxy.conf.j2 deleted file mode 100644 index b2474b0..0000000 --- a/ansible/mailserver/roles/nginx/templates/k8s-proxy.conf.j2 +++ /dev/null @@ -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; - } -} diff --git a/ansible/mailserver/roles/nginx/templates/k8s-upstream.conf.j2 b/ansible/mailserver/roles/nginx/templates/k8s-upstream.conf.j2 deleted file mode 100644 index 71c3324..0000000 --- a/ansible/mailserver/roles/nginx/templates/k8s-upstream.conf.j2 +++ /dev/null @@ -1,3 +0,0 @@ -upstream k8s_tls { - server 127.0.0.1:443; -} diff --git a/ansible/mailserver/roles/nginx/templates/mail.conf.j2 b/ansible/mailserver/roles/nginx/templates/mail.conf.j2 deleted file mode 100644 index a42ca84..0000000 --- a/ansible/mailserver/roles/nginx/templates/mail.conf.j2 +++ /dev/null @@ -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; - } -} diff --git a/ansible/mailserver/roles/nginx/templates/nginx.conf.j2 b/ansible/mailserver/roles/nginx/templates/nginx.conf.j2 deleted file mode 100644 index d7b669e..0000000 --- a/ansible/mailserver/roles/nginx/templates/nginx.conf.j2 +++ /dev/null @@ -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; - } -} diff --git a/ansible/mailserver/roles/sogo/defaults/main.yml b/ansible/mailserver/roles/sogo/defaults/main.yml deleted file mode 100644 index 8f3f7cd..0000000 --- a/ansible/mailserver/roles/sogo/defaults/main.yml +++ /dev/null @@ -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" diff --git a/ansible/mailserver/roles/sogo/handlers/main.yml b/ansible/mailserver/roles/sogo/handlers/main.yml deleted file mode 100644 index d9aee25..0000000 --- a/ansible/mailserver/roles/sogo/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: restart sogo - ansible.builtin.systemd: - name: sogo - state: restarted diff --git a/ansible/mailserver/roles/sogo/tasks/main.yml b/ansible/mailserver/roles/sogo/tasks/main.yml deleted file mode 100644 index ecab2d1..0000000 --- a/ansible/mailserver/roles/sogo/tasks/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/sogo/templates/sogo.conf.j2 b/ansible/mailserver/roles/sogo/templates/sogo.conf.j2 deleted file mode 100644 index 340e857..0000000 --- a/ansible/mailserver/roles/sogo/templates/sogo.conf.j2 +++ /dev/null @@ -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; -} diff --git a/ansible/mailserver/roles/ssh/defaults/main.yml b/ansible/mailserver/roles/ssh/defaults/main.yml deleted file mode 100644 index efea399..0000000 --- a/ansible/mailserver/roles/ssh/defaults/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/ssh/handlers/main.yml b/ansible/mailserver/roles/ssh/handlers/main.yml deleted file mode 100644 index 2000127..0000000 --- a/ansible/mailserver/roles/ssh/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: restart sshd - ansible.builtin.systemd: - name: sshd - state: restarted diff --git a/ansible/mailserver/roles/ssh/tasks/main.yml b/ansible/mailserver/roles/ssh/tasks/main.yml deleted file mode 100644 index 59b40be..0000000 --- a/ansible/mailserver/roles/ssh/tasks/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/ssh/templates/sshd_config.j2 b/ansible/mailserver/roles/ssh/templates/sshd_config.j2 deleted file mode 100644 index 064d9ef..0000000 --- a/ansible/mailserver/roles/ssh/templates/sshd_config.j2 +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/stalwart/defaults/main.yml b/ansible/mailserver/roles/stalwart/defaults/main.yml deleted file mode 100644 index 6355999..0000000 --- a/ansible/mailserver/roles/stalwart/defaults/main.yml +++ /dev/null @@ -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: [] diff --git a/ansible/mailserver/roles/stalwart/files/schema.sql b/ansible/mailserver/roles/stalwart/files/schema.sql deleted file mode 100644 index 04468e0..0000000 --- a/ansible/mailserver/roles/stalwart/files/schema.sql +++ /dev/null @@ -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; diff --git a/ansible/mailserver/roles/stalwart/handlers/main.yml b/ansible/mailserver/roles/stalwart/handlers/main.yml deleted file mode 100644 index 28adccc..0000000 --- a/ansible/mailserver/roles/stalwart/handlers/main.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/stalwart/tasks/configure.yml b/ansible/mailserver/roles/stalwart/tasks/configure.yml deleted file mode 100644 index a607de0..0000000 --- a/ansible/mailserver/roles/stalwart/tasks/configure.yml +++ /dev/null @@ -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] diff --git a/ansible/mailserver/roles/stalwart/tasks/install.yml b/ansible/mailserver/roles/stalwart/tasks/install.yml deleted file mode 100644 index 3a7599b..0000000 --- a/ansible/mailserver/roles/stalwart/tasks/install.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/stalwart/tasks/main.yml b/ansible/mailserver/roles/stalwart/tasks/main.yml deleted file mode 100644 index 3b91e70..0000000 --- a/ansible/mailserver/roles/stalwart/tasks/main.yml +++ /dev/null @@ -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" diff --git a/ansible/mailserver/roles/stalwart/tasks/prepare.yml b/ansible/mailserver/roles/stalwart/tasks/prepare.yml deleted file mode 100644 index fe51dc9..0000000 --- a/ansible/mailserver/roles/stalwart/tasks/prepare.yml +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/stalwart/templates/config.toml.j2 b/ansible/mailserver/roles/stalwart/templates/config.toml.j2 deleted file mode 100644 index 67af81a..0000000 --- a/ansible/mailserver/roles/stalwart/templates/config.toml.j2 +++ /dev/null @@ -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 %} diff --git a/ansible/mailserver/roles/stalwart/templates/stalwart.service.j2 b/ansible/mailserver/roles/stalwart/templates/stalwart.service.j2 deleted file mode 100644 index ca52bb2..0000000 --- a/ansible/mailserver/roles/stalwart/templates/stalwart.service.j2 +++ /dev/null @@ -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 diff --git a/ansible/mailserver/roles/stalwart/vars/main.yml b/ansible/mailserver/roles/stalwart/vars/main.yml deleted file mode 100644 index 31c9a55..0000000 --- a/ansible/mailserver/roles/stalwart/vars/main.yml +++ /dev/null @@ -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" diff --git a/ansible/mailserver/roles/ufw/defaults/main.yml b/ansible/mailserver/roles/ufw/defaults/main.yml deleted file mode 100644 index a9f4331..0000000 --- a/ansible/mailserver/roles/ufw/defaults/main.yml +++ /dev/null @@ -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)" } diff --git a/ansible/mailserver/roles/ufw/tasks/main.yml b/ansible/mailserver/roles/ufw/tasks/main.yml deleted file mode 100644 index 33f52bd..0000000 --- a/ansible/mailserver/roles/ufw/tasks/main.yml +++ /dev/null @@ -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 diff --git a/dns/domains/rubenhensen.nl.yaml b/dns/domains/rubenhensen.nl.yaml index 4e621e1..16de918 100644 --- a/dns/domains/rubenhensen.nl.yaml +++ b/dns/domains/rubenhensen.nl.yaml @@ -5,8 +5,8 @@ records: content: "46.224.26.65" - name: mail expire: 86400 - type: CNAME - content: "hensen.io." + type: A + content: "62.41.87.114" - name: stalwart expire: 86400 type: A