initial commit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthias Johnson 2026-02-27 15:09:25 -07:00
commit 75891c3271
129 changed files with 8046 additions and 0 deletions

View file

@ -0,0 +1,19 @@
# read-only docker exec always reports changed; changed_when: false suppresses spurious output
- name: List existing mail aliases
command: docker exec mailserver setup alias list
register: mail_alias_list
changed_when: false
tags:
- users
- name: Create mail aliases if missing
command: >
docker exec mailserver
setup alias add {{ item.from }}
{{ item.to if item.to is string else item.to | join(',') }}
loop: "{{ mail_aliases }}"
when: item.from not in mail_alias_list.stdout
tags:
- users

139
roles/mail/tasks/main.yml Normal file
View file

@ -0,0 +1,139 @@
- name: Allow SMTP traffic
ufw:
rule: allow
port: 25
proto: tcp
- name: Allow mail submission traffic
ufw:
rule: allow
port: 587
proto: tcp
- name: Allow IMAP over TLS traffic
ufw:
rule: allow
port: 993
proto: tcp
- name: Create docker-mailserver directory
file:
path: "/srv/mail"
state: directory
owner: root
group: docker # to allow access to the compose file
mode: '0755'
- name: Create docker-mailserver directories
file:
path: "/srv/mail/{{ item }}"
state: directory
owner: root
group: docker
mode: '0750'
loop:
- env
- config/rspamd/override.d
- name: Create maillogs directory
file:
path: /srv/mail/maillogs
state: directory
mode: '0755' # container startup script needs to traverse and chown subdirs
# stat+chown: avoids UID/GID lookup warnings for container-internal UIDs not present on host
- name: Stat maillogs directory
stat:
path: /srv/mail/maillogs
register: maillogs_stat
- name: Set maillogs directory ownership
command: chown 113:0 /srv/mail/maillogs
when: maillogs_stat.stat.uid != 113 or maillogs_stat.stat.gid != 0
- name: Create mailstate directory
file:
path: /srv/mail/mailstate
state: directory
owner: root
group: root
mode: '0755' # container startup script needs to traverse and chown subdirs
- name: Create maildata directory
file:
path: /srv/mail/maildata
state: directory
mode: '0751' # container startup script needs to traverse and chown subdirs
- name: Create config directory
file:
path: /srv/mail/config
state: directory
mode: '0751' # container startup script needs to traverse and chown subdirs
- name: Create rainloop data directory
file:
path: /srv/mail/rainloop/data
state: directory
mode: '0755'
# stat+chown: avoids UID/GID lookup warnings for container-internal UIDs not present on host
- name: Stat rainloop data directory
stat:
path: /srv/mail/rainloop/data
register: rainloop_data_stat
- name: Set rainloop data directory ownership
command: chown 991:991 /srv/mail/rainloop/data
when: rainloop_data_stat.stat.uid != 991 or rainloop_data_stat.stat.gid != 991
- name: Ensure certbot is installed
apt:
name: certbot
state: present
- name: Obtain a Let's Encrypt certificate for {{ mail_hostname }}
command: >
certbot certonly --standalone
-d {{ mail_hostname }}
--non-interactive --agree-tos -m postmaster@{{ domain }}
args:
creates: /etc/letsencrypt/live/{{ mail_hostname }}/fullchain.pem
tags: config
- name: Deploy mail compose file
template:
src: compose.yml.j2
dest: /srv/mail/compose.yml
notify: Restart mail stack
tags: config
- name: Deploy mailserver environment file
template:
src: mailserver.env.j2
dest: /srv/mail/env/mailserver.env
mode: '0640'
owner: root
group: docker
notify: Restart mailserver
tags: config
- name: Deploy rspamd web UI config
template:
src: worker-controller.inc.j2
dest: /srv/mail/config/rspamd/override.d/worker-controller.inc
mode: '0644'
notify: Restart mailserver
tags: config
- name: Start mailserver
community.docker.docker_compose_v2:
project_src: /srv/mail
state: present
build: never
tags: config
- import_tasks: users.yml
- import_tasks: aliases.yml
# webmail interface
- import_tasks: rainloop.yml

View file

@ -0,0 +1,21 @@
- name: Ensure Rainloop allowed domains are set
ini_file:
path: /srv/mail/rainloop/data/_data_/_default_/configs/application.ini
section: security
option: AllowedDomains
value: "{{ mail_domains | join(',') }}"
backup: yes
notify:
- Restart Rainloop
- name: Set proper mode of Rainloop data directory
file:
path: /srv/mail/rainloop
state: directory
recurse: yes
mode: u+rwX,g+rX
# chown -R always exits 0; changed_when: false suppresses spurious "changed" in playbook output
- name: Set proper ownership of Rainloop data directory
command: chown -R 991:991 /srv/mail/rainloop
changed_when: false

View file

@ -0,0 +1,26 @@
# read-only docker exec always reports changed; changed_when: false suppresses spurious output
- name: Check if mail user exists
command: docker exec mailserver setup email list
register: mail_user_list
changed_when: false
tags:
- users
- name: Create mail users if missing
ansible.builtin.command:
argv:
- docker
- exec
- mailserver
- setup
- email
- add
- "{{ item.address }}"
- "{{ item.password }}"
loop: "{{ mail_users }}"
when: item.address not in mail_user_list.stdout
no_log: true
ignore_errors: yes
tags:
- users