diff --git a/README.md b/README.md index 4530d01..a84d7d7 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,29 @@ This repository contains all the files I use to manage services hosted on [viyur ## Requirements -Ansible: +### Ansible +Install Ansible: ``` sudo apt install -y ansible ``` -Setup SSL certificates with Certbot beforehand: +### SSL certificates +Install Certbot: ``` -sudo apt install -y certbot python3-certbot-dns-ovh +sudo apt install -y certbot python3-certbot-dns-ovh python3-certbot-nginx +``` + +Request certificates: +``` +# For the NGINX reverse proxy +sudo certbot certonly --nginx -d viyurz.fr,*.viyurz.fr + +# For Coturn +bash <(wget -q -O - https://github.com/zerossl/zerossl-bot/raw/master/get-zerosslbot.sh) +sudo zerossl-bot certonly --nginx -m viyurz@viyurz.fr -d turn.viyurz.fr + +# For the mailserver +sudo certbot certonly --nginx -d mail.viyurz.fr ``` @@ -30,3 +45,30 @@ After that, you can create a root cronjob to run this playbook without requiring ``` Here we leave `selected_projects` empty to backup all projects. + + +## Mailserver +When starting the container for the first time, run the initial setup: +``` +docker exec -it mailserver /bin/sh /usr/local/bin/configure.sh +``` + +After that you need to tell Stalwart where the SSL certificate files are in: +``` +/opt/stalwart-mail/etc/common/tls.toml + +[certificate."default"] +cert = "file:///etc/fullchain.pem" +private-key = "file:///etc/privkey.pem" +``` + +And configure the user Stalwart will run as: +``` +/opt/stalwart-mail/etc/common/server.toml + +[server.run-as] +user = "mail" +group = "mail" +``` + +Then follow the end of the [Official Installation Guide](https://stalw.art/docs/install/docker#take-note-of-the-administrator-password). diff --git a/env.yml b/env.yml index ae82ed2..2c65425 100644 --- a/env.yml +++ b/env.yml @@ -42,6 +42,7 @@ projects: - etebase - hedgedoc - homepage + - mailserver - reverse-proxy - searxng - synapse @@ -53,6 +54,7 @@ projects: projects_to_backup: - etebase - hedgedoc + - mailserver - synapse - uptime-kuma - vaultwarden @@ -79,6 +81,10 @@ ports: etebase: 3735 hedgedoc: 8086 homepage: 8082 + mailserver_smtp: 1025 + mailserver_smtps: 1465 + mailserver_imaps: 1993 + mailserver_jmap: 1443 searxng: 8083 synapse: 8008 syncthing_discosrv: 8443 @@ -98,6 +104,7 @@ users: hedgedoc: 1004 hedgedoc_mysql: 1005 homepage: 8686 + mailserver: 8 searxng: 977 searxng_redis: 999 synapse: 991 @@ -115,6 +122,9 @@ volumes: etebase_datadir: /mnt/etebasedata hedgedoc_mysql_datadir: /mnt/hedgedoc/mysql-data hedgedoc_configdir: /mnt/hedgedoc/config + mailserver_datadir: /mnt/mailserverdata + mailserver_tls_certificate_file: "/etc/letsencrypt/live/mail.{{ domain }}/fullchain.pem" + mailserver_tls_certificate_key_file: "/etc/letsencrypt/live/mail.{{ domain }}/privkey.pem" synapse_datadir: /mnt/synapsedata synapse_postgres_datadir: /mnt/synapsepgdata syncthing_datadir: "{{ cifs_mounts['syncthing']['path'] }}" diff --git a/roles/mailserver/tasks/backup.yml b/roles/mailserver/tasks/backup.yml new file mode 100644 index 0000000..ae28954 --- /dev/null +++ b/roles/mailserver/tasks/backup.yml @@ -0,0 +1,22 @@ +- name: + become: true + block: + - name: Create borg backup + command: + cmd: | + borg create + --compression=lzma + "{{ borg_repodir }}::{{ role_name }}-{now:%Y-%m-%d_%H-%M-%S}" + {{ volumes['mailserver_datadir'] }} + environment: + BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}" + + - name: Prune borg repository + command: + cmd: | + borg prune + --glob-archives='{{ role_name }}-*' + {{ borg_prune_options }} + {{ borg_repodir }} + environment: + BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}" diff --git a/roles/mailserver/tasks/main.yml b/roles/mailserver/tasks/main.yml new file mode 100644 index 0000000..ed95f38 --- /dev/null +++ b/roles/mailserver/tasks/main.yml @@ -0,0 +1,9 @@ +- name: Include backup tasks + include_tasks: + file: backup.yml + when: run_backup | default(false) | bool + +- name: Include update tasks + include_tasks: + file: update.yml + when: run_update | default(false) | bool diff --git a/roles/mailserver/tasks/update.yml b/roles/mailserver/tasks/update.yml new file mode 100644 index 0000000..463a48d --- /dev/null +++ b/roles/mailserver/tasks/update.yml @@ -0,0 +1,78 @@ +- name: "Create {{ mailserver_project_dir }} project directory" + file: + path: "{{ mailserver_project_dir }}" + state: directory + +- name: Template docker-compose.yaml to project directory + template: + src: docker-compose.yaml + dest: "{{ mailserver_project_dir }}/docker-compose.yaml" + owner: "{{ ansible_env['USER'] }}" + group: "{{ ansible_env['USER'] }}" + mode: '640' + +- name: "Create directory {{ volumes['mailserver_datadir'] }} with correct permissions" + file: + path: "{{ volumes['mailserver_datadir'] }}" + state: directory + owner: "{{ users['mailserver'] + uid_shift }}" + group: "{{ users['mailserver'] + uid_shift }}" + mode: '770' + become: true + +- name: Set limited permissions on certificate directories + file: + path: "/etc/{{ item }}" + state: directory + owner: root + group: root + mode: '751' + become: true + loop: + - letsencrypt + - letsencrypt/live + - letsencrypt/archive + +- name: Set limited permissions on certificate directories + file: + path: "/etc/letsencrypt/{{ item }}/mail.{{ domain }}" + state: directory + owner: root + group: "{{ host_uid }}" + mode: '550' + become: true + loop: + - live + - archive + +- name: Set limited permissions on certificate key file + file: + path: "/etc/letsencrypt/live/mail.{{ domain }}/privkey.pem" + owner: root + group: "{{ host_uid }}" + mode: '640' + become: true + +- name: Pull project services + community.docker.docker_compose: + project_src: "{{ mailserver_project_dir }}" + recreate: never + pull: true + debug: true + when: docker_pull_images | bool + register: mailserver_docker_compose_pull_result + +- name: Display pulled image(s) name + set_fact: + mailserver_pulled_images: "{{ mailserver_pulled_images | default([]) + [item.pulled_image.name] }}" + loop: "{{ mailserver_docker_compose_pull_result['actions'] | default([]) | selectattr('pulled_image', 'defined') }}" + +- name: Include backup tasks + include_tasks: + file: backup.yml + # Make a backup if we didn't already make one and we pulled a new image + when: not run_backup and mailserver_pulled_images is defined + +- name: Create/Restart project services + community.docker.docker_compose: + project_src: "{{ mailserver_project_dir }}" diff --git a/roles/mailserver/templates/docker-compose.yaml b/roles/mailserver/templates/docker-compose.yaml new file mode 100644 index 0000000..e49c021 --- /dev/null +++ b/roles/mailserver/templates/docker-compose.yaml @@ -0,0 +1,14 @@ +services: + mailserver: + image: docker.io/stalwartlabs/mail-server:latest + container_name: mailserver + restart: always + ports: + - "{{ ports['mailserver_smtp'] }}:25" + - {{ ports['mailserver_smtps'] }}:465 + - {{ ports['mailserver_imaps'] }}:993 + - {{ ports['mailserver_jmap'] }}:443 + volumes: + - {{ volumes['mailserver_tls_certificate_file'] }}:/etc/fullchain.pem + - {{ volumes['mailserver_tls_certificate_key_file'] }}:/etc/privkey.pem + - {{ volumes['mailserver_datadir' ] }}:/opt/stalwart-mail diff --git a/roles/mailserver/vars/main.yml b/roles/mailserver/vars/main.yml new file mode 100644 index 0000000..460e423 --- /dev/null +++ b/roles/mailserver/vars/main.yml @@ -0,0 +1 @@ +mailserver_project_dir: "{{ docker_projects_dir }}/{{ role_name }}" diff --git a/roles/nftables/templates/nftables.conf b/roles/nftables/templates/nftables.conf index 5fedc2c..4fd2205 100755 --- a/roles/nftables/templates/nftables.conf +++ b/roles/nftables/templates/nftables.conf @@ -2,11 +2,13 @@ flush ruleset -# Forward Syncthing relay traffic from port {{ ports['syncthing_relaysrv'] }} to 22067 table inet nat { chain prerouting { type nat hook prerouting priority dstnat; iif eth0 tcp dport {{ ports['syncthing_relaysrv'] }} redirect to :22067 + iif eth0 tcp dport 25 redirect to :{{ ports['mailserver_smtp'] }} + iif eth0 tcp dport 465 redirect to :{{ ports['mailserver_smtps'] }} + iif eth0 tcp dport 993 redirect to :{{ ports['mailserver_imaps'] }} } } @@ -57,7 +59,10 @@ table inet filter { tcp dport { http, https } accept # SSH - tcp dport 995 accept + tcp dport ssh accept + + # SMTP/IMAP + tcp dport { {{ ports['mailserver_smtp'] }}, {{ ports['mailserver_smtps'] }}, {{ ports['mailserver_imaps'] }} } accept # Syncthing tcp dport { {{ ports['syncthing_tcp'] }}, 22067 } accept diff --git a/roles/reverse-proxy/templates/reverse-proxy.conf b/roles/reverse-proxy/templates/reverse-proxy.conf index d0b0715..f16b375 100644 --- a/roles/reverse-proxy/templates/reverse-proxy.conf +++ b/roles/reverse-proxy/templates/reverse-proxy.conf @@ -113,6 +113,24 @@ server { } +# JMAP +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name mail.{{ domain }}; + + location / { + proxy_pass https://127.0.0.1:{{ ports['mailserver_jmap'] }}; + + # Websocket + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } +} + + # SearXNG server { listen 443 ssl http2; diff --git a/secrets.yml.example b/secrets.yml.example index 0cabba8..11efda9 100644 --- a/secrets.yml.example +++ b/secrets.yml.example @@ -25,3 +25,5 @@ synapse_secrets: vaultwarden_secrets: # Generate with: docker exec --rm -ti docker.io/vaultwarden/server:alpine /vaultwarden hash admin_token_hash: + smtp_username: + smtp_password: