diff options
| author | Roman Ilin <me@romanilin.is> | 2026-06-15 12:59:09 +0300 |
|---|---|---|
| committer | Roman Ilin <me@romanilin.is> | 2026-06-15 22:04:41 +0300 |
| commit | 5e4bf1268c266e63d0e92e845ad910a2103b86ff (patch) | |
| tree | 532c01a9658a05048ef1ba76d4f30fca84005643 /roles | |
| download | infrastructure-5e4bf1268c266e63d0e92e845ad910a2103b86ff.tar.gz | |
Diffstat (limited to 'roles')
| -rw-r--r-- | roles/container_base/handlers/main.yaml | 4 | ||||
| -rw-r--r-- | roles/container_base/tasks/main.yaml | 27 | ||||
| -rw-r--r-- | roles/git/handlers/main.yaml | 7 | ||||
| -rw-r--r-- | roles/git/tasks/main.yaml | 22 | ||||
| -rw-r--r-- | roles/git/templates/git_nginx.conf.j2 | 16 | ||||
| -rw-r--r-- | roles/host/files/60-machinectl-fast-user-auth.rules | 6 | ||||
| -rw-r--r-- | roles/host/handlers/main.yaml | 25 | ||||
| -rw-r--r-- | roles/host/tasks/main.yaml | 195 | ||||
| -rw-r--r-- | roles/host/templates/nginx-reload.sh.j2 | 3 | ||||
| -rw-r--r-- | roles/host/templates/nginx.conf.j2 | 67 | ||||
| -rw-r--r-- | roles/host/templates/nspawn.j2 | 10 | ||||
| -rw-r--r-- | roles/mail/handlers/main.yaml | 15 | ||||
| -rw-r--r-- | roles/mail/tasks/main.yaml | 61 | ||||
| -rw-r--r-- | roles/mail/templates/dovecot.conf.j2 | 22 | ||||
| -rw-r--r-- | roles/mail/templates/opendkim.conf.j2 | 15 | ||||
| -rw-r--r-- | roles/mail/templates/postfix_main.cf.j2 | 27 | ||||
| -rw-r--r-- | roles/mail/templates/postfix_master.cf.j2 | 31 |
17 files changed, 553 insertions, 0 deletions
diff --git a/roles/container_base/handlers/main.yaml b/roles/container_base/handlers/main.yaml new file mode 100644 index 0000000..93df41f --- /dev/null +++ b/roles/container_base/handlers/main.yaml @@ -0,0 +1,4 @@ +- name: Restart Container systemd-networkd + ansible.builtin.systemd: + name: systemd-networkd + state: restarted diff --git a/roles/container_base/tasks/main.yaml b/roles/container_base/tasks/main.yaml new file mode 100644 index 0000000..275a24a --- /dev/null +++ b/roles/container_base/tasks/main.yaml @@ -0,0 +1,27 @@ +- name: Ensure systemd-networkd is running in container + ansible.builtin.systemd: + name: systemd-networkd + state: started + enabled: yes + +- name: Set static IP for container dynamically + ansible.builtin.copy: + dest: /etc/systemd/network/80-container-host0.network + content: | + [Match] + Name=host0 + [Network] + Address={{ containers[inventory_hostname].ip }}/24 + Gateway={{ bridge_ip }} + DNS=1.1.1.1 8.8.8.8 + notify: Restart Container systemd-networkd + +- name: Apply container network changes immediately + ansible.builtin.meta: flush_handlers + +- name: Install Packages + ansible.builtin.dnf: + name: + - epel-release + - python3-passlib + state: present diff --git a/roles/git/handlers/main.yaml b/roles/git/handlers/main.yaml new file mode 100644 index 0000000..3503da0 --- /dev/null +++ b/roles/git/handlers/main.yaml @@ -0,0 +1,7 @@ +- name: Restart Git Services + ansible.builtin.systemd: + name: "{{ item }}" + state: restarted + loop: + - nginx + - fcgiwrap@nginx.socket diff --git a/roles/git/tasks/main.yaml b/roles/git/tasks/main.yaml new file mode 100644 index 0000000..b80989e --- /dev/null +++ b/roles/git/tasks/main.yaml @@ -0,0 +1,22 @@ +- name: Install Git, Nginx, and Fcgiwrap + ansible.builtin.dnf: + name: + - cgit + - nginx + - fcgiwrap + state: present + +- name: Deploy Git Nginx configuration + ansible.builtin.template: + src: git_nginx.conf.j2 + dest: /etc/nginx/conf.d/default.conf + notify: Restart Git Services + +- name: Ensure Git Web Services are Enabled and Running + ansible.builtin.systemd: + name: "{{ item }}" + state: started + enabled: yes + loop: + - nginx + - fcgiwrap@nginx.socket diff --git a/roles/git/templates/git_nginx.conf.j2 b/roles/git/templates/git_nginx.conf.j2 new file mode 100644 index 0000000..cd37f25 --- /dev/null +++ b/roles/git/templates/git_nginx.conf.j2 @@ -0,0 +1,16 @@ +server { + listen 80; + server_name _; + + root /usr/share/cgit; + try_files $uri @cgit; + + location @cgit { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /var/www/cgi-bin/cgit; + fastcgi_param PATH_INFO $uri; + fastcgi_param QUERY_STRING $args; + fastcgi_param HTTP_HOST $server_name; + fastcgi_pass unix:/run/fcgiwrap-nginx.sock; + } +} diff --git a/roles/host/files/60-machinectl-fast-user-auth.rules b/roles/host/files/60-machinectl-fast-user-auth.rules new file mode 100644 index 0000000..eee39ea --- /dev/null +++ b/roles/host/files/60-machinectl-fast-user-auth.rules @@ -0,0 +1,6 @@ +polkit.addRule(function(action, subject) { + if(action.id == "org.freedesktop.machine1.host-shell" && + subject.isInGroup("wheel")) { + return polkit.Result.YES; + } +}); diff --git a/roles/host/handlers/main.yaml b/roles/host/handlers/main.yaml new file mode 100644 index 0000000..e643580 --- /dev/null +++ b/roles/host/handlers/main.yaml @@ -0,0 +1,25 @@ +- name: Reload Firewalld + ansible.builtin.systemd: + name: firewalld + state: reloaded + +- name: Restart Host systemd-networkd + ansible.builtin.systemd: + name: systemd-networkd + state: restarted + +- name: Restart Containers + ansible.builtin.systemd: + name: "systemd-nspawn@{{ item.key }}" + state: restarted + loop: "{{ containers | dict2items }}" + +- name: Restart Nginx + ansible.builtin.systemd: + name: nginx + state: restarted + +- name: Restart Polkit + ansible.builtin.systemd: + name: polkit + state: restarted diff --git a/roles/host/tasks/main.yaml b/roles/host/tasks/main.yaml new file mode 100644 index 0000000..007d44d --- /dev/null +++ b/roles/host/tasks/main.yaml @@ -0,0 +1,195 @@ +- name: Install firewalld and systemd-networkd packages + ansible.builtin.dnf: + name: + - firewalld + - systemd-container + - systemd-networkd + - openssl + - python3-libsemanage + - policycoreutils + state: present + +- name: Configure Polkit to allow machinectl fast user auth + ansible.builtin.copy: + src: 60-machinectl-fast-user-auth.rules + dest: /etc/polkit-1/rules.d/60-machinectl-fast-user-auth.rules + mode: "0644" + notify: Restart Polkit + +- name: Ensure firewalld and systemd-networkd are running + ansible.builtin.systemd: + name: "{{ item }}" + state: started + enabled: yes + loop: + - firewalld + - systemd-networkd + +- name: Enable masquerading for container internet access + ansible.posix.firewalld: + masquerade: yes + state: enabled + permanent: yes + zone: public + notify: Reload Firewalld + +- name: Open HTTP and HTTPS (TCP) for Nginx + ansible.posix.firewalld: + service: "{{ item }}" + state: enabled + permanent: yes + zone: public + loop: + - http + - https + notify: Reload Firewalld + +- name: Open HTTPS (UDP) for HTTP/3 QUIC support + ansible.posix.firewalld: + port: 443/udp + state: enabled + permanent: yes + zone: public + notify: Reload Firewalld + +- name: Configure Host Firewall Port Forwarding dynamically + ansible.posix.firewalld: + port_forward: + - port: "{{ item.1 }}" + proto: tcp + toaddr: "{{ item.0.value.ip }}" + toport: "{{ item.1 }}" + permanent: yes + state: enabled + loop: "{{ containers | dict2items | subelements('value.forward_ports', skip_missing=True) }}" + notify: Reload Firewalld + +- name: Create bridge netdev on host + ansible.builtin.copy: + dest: /etc/systemd/network/10-nspawn-br0.netdev + content: | + [NetDev] + Name=nspawn-br0 + Kind=bridge + notify: Restart Host systemd-networkd + +- name: Assign IP to the host bridge + ansible.builtin.copy: + dest: /etc/systemd/network/10-nspawn-br0.network + content: | + [Match] + Name=nspawn-br0 + [Network] + Address={{ bridge_ip }}/24 + notify: Restart Host systemd-networkd + +- name: Apply networking changes immediately + ansible.builtin.meta: flush_handlers + +- name: Ensure containers are bootstrapped (AlmaLinux 10) + ansible.builtin.dnf: + installroot: "/var/lib/machines/{{ item.key }}" + releasever: "10" + name: + - almalinux-release + - systemd + - dbus + - systemd-networkd + - passwd + - dnf + - iproute + state: present + loop: "{{ containers | dict2items }}" + +- name: Initialize machine-id for containers + ansible.builtin.command: systemd-machine-id-setup --root=/var/lib/machines/{{ item.key }} + args: + creates: "/var/lib/machines/{{ item.key }}/etc/machine-id" + loop: "{{ containers | dict2items }}" + +- name: Fix SELinux contexts in container rootfs + ansible.builtin.command: restorecon -R /var/lib/machines/{{ item.key }} + register: restorecon_result + changed_when: "'Relabeled' in restorecon_result.stdout" + loop: "{{ containers | dict2items }}" + +- name: Ensure systemd-nspawn directory exists + ansible.builtin.file: + path: /etc/systemd/nspawn + state: directory + mode: "0755" + +- name: Ensure Let's Encrypt mock directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: '0755' + loop: + - "/etc/letsencrypt/archive/{{ vault_public_domain }}" + - "/etc/letsencrypt/live/{{ vault_public_domain }}" + +- name: Check if Let's Encrypt certificate exists + ansible.builtin.stat: + path: "/etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem" + register: le_cert + +- name: Generate self-signed fallback cert in Let's Encrypt paths if missing + ansible.builtin.command: > + openssl req -x509 -nodes -days 365 + -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 + -keyout /etc/letsencrypt/live/{{ vault_public_domain }}/privkey.pem + -out /etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem + -subj "/CN={{ vault_public_domain }}" + args: + creates: "/etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem" + when: not le_cert.stat.exists + +- name: Create systemd-nspawn configuration + ansible.builtin.template: + src: nspawn.j2 + dest: "/etc/systemd/nspawn/{{ item.key }}.nspawn" + loop: "{{ containers | dict2items }}" + notify: Restart Containers + +- name: Enable and Start Containers + ansible.builtin.systemd: + name: "systemd-nspawn@{{ item.key }}" + state: started + enabled: yes + loop: "{{ containers | dict2items }}" + +- name: Install Nginx + ansible.builtin.dnf: + name: nginx + state: present + +- name: Allow Nginx to connect to upstream servers (SELinux) + ansible.posix.seboolean: + name: httpd_can_network_connect + state: yes + persistent: yes + +- name: Configure Nginx dynamically + ansible.builtin.template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + validate: nginx -t -c %s + notify: Restart Nginx + +- name: Ensure Nginx is enabled and running + ansible.builtin.systemd: + name: nginx + state: started + enabled: yes + +- name: Ensure Let's Encrypt renewal hook directory exists + ansible.builtin.file: + path: /etc/letsencrypt/renewal-hooks/post + state: directory + mode: '0755' + +- name: Deploy Let's Encrypt post-renewal hook for Nginx + ansible.builtin.template: + src: nginx-reload.sh.j2 + dest: /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh + mode: '0755' diff --git a/roles/host/templates/nginx-reload.sh.j2 b/roles/host/templates/nginx-reload.sh.j2 new file mode 100644 index 0000000..f248776 --- /dev/null +++ b/roles/host/templates/nginx-reload.sh.j2 @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +systemctl reload nginx diff --git a/roles/host/templates/nginx.conf.j2 b/roles/host/templates/nginx.conf.j2 new file mode 100644 index 0000000..7360cae --- /dev/null +++ b/roles/host/templates/nginx.conf.j2 @@ -0,0 +1,67 @@ +user nginx; +worker_processes auto; +worker_rlimit_nofile 8192; +error_log /var/log/nginx/error.log notice; +pid /run/nginx.pid; + +events { + worker_connections 4096; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + keepalive_timeout 65; + types_hash_max_size 4096; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Modern SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + # Redirect all HTTP traffic to HTTPS + server { + listen 80 default_server; + server_name _; + return 301 https://$host$request_uri; + } + +{% for name, config in containers.items() %} +{% if config.web_subdomain is defined %} + server { + listen 443 ssl; # TCP for HTTP/1.1 & HTTP/2 + listen 443 quic; # UDP for HTTP/3 QUIC + http2 on; # Enable HTTP/2 over TCP + + server_name {{ config.web_subdomain }}.{{ vault_public_domain }}; + + # Nginx reads them natively, no combining needed + ssl_certificate /etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ vault_public_domain }}/privkey.pem; + + # Advertise HTTP/3 availability to browsers + add_header Alt-Svc 'h3=":443"; ma=2592000' always; + + location / { + proxy_pass http://{{ config.ip }}:80; + 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; + } + } +{% endif %} +{% endfor %} +} diff --git a/roles/host/templates/nspawn.j2 b/roles/host/templates/nspawn.j2 new file mode 100644 index 0000000..15faf53 --- /dev/null +++ b/roles/host/templates/nspawn.j2 @@ -0,0 +1,10 @@ +[Exec] +Boot=yes +Hostname={{ item.key }}.{{ vault_public_domain }} + +[Files] +BindReadOnly=/etc/letsencrypt/live/{{ vault_public_domain }}:/etc/letsencrypt/live/{{ vault_public_domain }} +BindReadOnly=/etc/letsencrypt/archive/{{ vault_public_domain }}:/etc/letsencrypt/archive/{{ vault_public_domain }} + +[Network] +Bridge=nspawn-br0 diff --git a/roles/mail/handlers/main.yaml b/roles/mail/handlers/main.yaml new file mode 100644 index 0000000..2d6ec58 --- /dev/null +++ b/roles/mail/handlers/main.yaml @@ -0,0 +1,15 @@ +- name: Fix DKIM permissions + ansible.builtin.file: + path: /etc/opendkim/keys/ + owner: opendkim + group: opendkim + recurse: yes + +- name: Restart Mail Services + ansible.builtin.systemd: + name: "{{ item }}" + state: restarted + loop: + - postfix + - dovecot + - opendkim diff --git a/roles/mail/tasks/main.yaml b/roles/mail/tasks/main.yaml new file mode 100644 index 0000000..66b4215 --- /dev/null +++ b/roles/mail/tasks/main.yaml @@ -0,0 +1,61 @@ +- name: Install Mail Packages + ansible.builtin.dnf: + name: + - postfix + - dovecot + - opendkim + - opendkim-tools + state: present + +- name: Ensure OpenDKIM keys directory exists + ansible.builtin.file: + path: "/etc/opendkim/keys/{{ vault_public_domain }}" + state: directory + owner: opendkim + group: opendkim + mode: "0750" + +- name: Generate DKIM Key + ansible.builtin.command: + cmd: "opendkim-genkey -a ed25519 -s default -d {{ vault_public_domain }} -D /etc/opendkim/keys/{{ vault_public_domain }}/" + creates: "/etc/opendkim/keys/{{ vault_public_domain }}/default.private" + notify: Fix DKIM permissions + +- name: Configure OpenDKIM mappings + ansible.builtin.copy: + dest: "{{ item.path }}" + content: "{{ item.content }}" + mode: "0644" + loop: + - { path: /etc/opendkim/KeyTable, content: "default._domainkey.{{ vault_public_domain }} {{ vault_public_domain }}:default:/etc/opendkim/keys/{{ vault_public_domain }}/default.private\n" } + - { path: /etc/opendkim/SigningTable, content: "*@{{ vault_public_domain }} default._domainkey.{{ vault_public_domain }}\n" } + - { path: /etc/opendkim/TrustedHosts, content: "127.0.0.1\nlocalhost\n10.0.0.0/24\n" } + notify: Restart Mail Services + +- name: Deploy Configurations + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: "0644" + loop: + - { src: postfix_main.cf.j2, dest: /etc/postfix/main.cf } + - { src: postfix_master.cf.j2, dest: /etc/postfix/master.cf } + - { src: dovecot.conf.j2, dest: /etc/dovecot/dovecot.conf } + - { src: opendkim.conf.j2, dest: /etc/opendkim.conf } + notify: Restart Mail Services + +- name: Add Mail User + ansible.builtin.user: + name: me + shell: /sbin/nologin + password: "{{ vault_mail_user_password | password_hash('sha512') }}" + +- name: Ensure Services are Enabled and Running + ansible.builtin.systemd: + name: "{{ item }}" + state: started + enabled: yes + loop: + - postfix + - dovecot + - opendkim diff --git a/roles/mail/templates/dovecot.conf.j2 b/roles/mail/templates/dovecot.conf.j2 new file mode 100644 index 0000000..e733bae --- /dev/null +++ b/roles/mail/templates/dovecot.conf.j2 @@ -0,0 +1,22 @@ +protocols = imap +listen = * +mail_location = maildir:~/Maildir +auth_mechanisms = plain login +ssl = required +ssl_cert = </etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem +ssl_key = </etc/letsencrypt/live/{{ vault_public_domain }}/privkey.pem + +passdb { + driver = pam +} +userdb { + driver = passwd +} + +service auth { + unix_listener /var/spool/postfix/private/auth { + group = postfix + mode = 0660 + user = postfix + } +} diff --git a/roles/mail/templates/opendkim.conf.j2 b/roles/mail/templates/opendkim.conf.j2 new file mode 100644 index 0000000..b29fcb3 --- /dev/null +++ b/roles/mail/templates/opendkim.conf.j2 @@ -0,0 +1,15 @@ +PidFile /run/opendkim/opendkim.pid +Mode sv +Syslog yes +SyslogSuccess yes +LogWhy yes +UserID opendkim:opendkim +Socket inet:8891@localhost +Umask 002 +Canonicalization relaxed/relaxed +Selector default +MinimumKeyBits 1024 +KeyTable /etc/opendkim/KeyTable +SigningTable refile:/etc/opendkim/SigningTable +ExternalIgnoreList refile:/etc/opendkim/TrustedHosts +InternalHosts refile:/etc/opendkim/TrustedHosts diff --git a/roles/mail/templates/postfix_main.cf.j2 b/roles/mail/templates/postfix_main.cf.j2 new file mode 100644 index 0000000..df20610 --- /dev/null +++ b/roles/mail/templates/postfix_main.cf.j2 @@ -0,0 +1,27 @@ +myhostname = mail.{{ vault_public_domain }} +mydomain = {{ vault_public_domain }} +myorigin = $mydomain +mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain +inet_interfaces = all +inet_protocols = ipv4 +mynetworks = 127.0.0.0/8, 10.0.0.0/24 +home_mailbox = Maildir/ +message_size_limit = 26214400 +alias_maps = lmdb:/etc/aliases +alias_database = lmdb:/etc/aliases + +smtpd_tls_cert_file = /etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem +smtpd_tls_key_file = /etc/letsencrypt/live/{{ vault_public_domain }}/privkey.pem +smtpd_tls_security_level = may +smtp_tls_security_level = may +smtpd_tls_protocols = >=TLSv1.2 +smtp_tls_protocols = >=TLSv1.2 + +smtpd_sasl_type = dovecot +smtpd_sasl_path = private/auth +smtpd_sasl_auth_enable = yes +smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination + +smtpd_milters = inet:localhost:8891 +non_smtpd_milters = inet:localhost:8891 +milter_default_action = accept diff --git a/roles/mail/templates/postfix_master.cf.j2 b/roles/mail/templates/postfix_master.cf.j2 new file mode 100644 index 0000000..c2648ca --- /dev/null +++ b/roles/mail/templates/postfix_master.cf.j2 @@ -0,0 +1,31 @@ +smtp inet n - y - - smtpd +pickup unix n - y 60 1 pickup +cleanup unix n - y - 0 cleanup +qmgr unix n - n 300 1 qmgr +tlsmgr unix - - y 1000? 1 tlsmgr +rewrite unix - - y - - trivial-rewrite +bounce unix - - y - 0 bounce +defer unix - - y - 0 bounce +trace unix - - y - 0 bounce +verify unix - - y - 1 verify +flush unix n - y 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - y - - smtp +relay unix - - y - - smtp +showq unix n - y - - showq +error unix - - y - - error +retry unix - - y - - error +discard unix - - y - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - y - - lmtp +anvil unix - - y - 1 anvil +scache unix - - y - 1 scache +smtps inet n - y - - smtpd + -o syslog_name=postfix/smtps + -o smtpd_tls_wrappermode=yes + -o smtpd_sasl_auth_enable=yes + -o smtpd_reject_unlisted_recipient=no + -o smtpd_client_restrictions=permit_sasl_authenticated,reject + -o milter_macro_daemon_name=ORIGINATING |