--- - name: Install nginx ansible.builtin.apt: name: nginx state: present - name: Install certbot and nginx plugin ansible.builtin.apt: name: - certbot - python3-certbot-nginx state: present - name: Install fail2ban ansible.builtin.apt: name: fail2ban state: present - name: Create certbot webroot directory ansible.builtin.file: path: "{{ certbot_webroot }}" state: directory owner: www-data group: www-data mode: "0755" - name: Create snippets directory ansible.builtin.file: path: /etc/nginx/snippets state: directory owner: root group: root mode: "0755" - name: Deploy shared security snippet ansible.builtin.template: src: pw-security.conf.j2 dest: /etc/nginx/snippets/pw-security.conf owner: root group: root mode: "0644" notify: Reload nginx # ── Phase 1: HTTP-only configs for certbot bootstrap ───────────────────────── - name: Deploy initial HTTP-only site config ansible.builtin.template: src: pw-site.conf.j2 dest: /etc/nginx/sites-available/pw-site.conf owner: root group: root mode: "0644" - name: Enable HTTP-only site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-site.conf dest: /etc/nginx/sites-enabled/pw-site.conf state: link - name: Remove default nginx site ansible.builtin.file: path: /etc/nginx/sites-enabled/default state: absent - name: Reload nginx for HTTP configs ansible.builtin.systemd: name: nginx state: reloaded # ── Phase 2: Obtain TLS certificates ───────────────────────────────────────── - name: Obtain certificate for performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d performancewest.net -d www.performancewest.net --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/performancewest.net/fullchain.pem - name: Obtain certificate for api.performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ api_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ api_domain }}/fullchain.pem - name: Obtain certificate for portal.performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ portal_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ portal_domain }}/fullchain.pem - name: Obtain certificate for crm.performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ crm_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ crm_domain }}/fullchain.pem - name: Obtain certificate for lists.performancewest.net (Listmonk) ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ listmonk_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ listmonk_domain }}/fullchain.pem - name: Obtain certificate for analytics.performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ analytics_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ analytics_domain }}/fullchain.pem - name: Obtain certificate for dev.performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ dev_domain }} -d www.{{ dev_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ dev_domain }}/fullchain.pem - name: Obtain certificate for api.dev.performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ dev_api_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ dev_api_domain }}/fullchain.pem - name: Obtain certificate for pay.performancewest.net (SHKeeper API) ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ shkeeper_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ shkeeper_domain }}/fullchain.pem - name: Obtain certificate for crypto.performancewest.net (SHKeeper Admin) ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ shkeeper_admin_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ shkeeper_admin_domain }}/fullchain.pem - name: Obtain certificate for minio.performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ minio_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ minio_domain }}/fullchain.pem - name: Obtain certificate for minio-console.performancewest.net ansible.builtin.command: cmd: >- certbot certonly --webroot -w {{ certbot_webroot }} -d {{ minio_console_domain }} --email {{ certbot_email }} --agree-tos --non-interactive creates: /etc/letsencrypt/live/{{ minio_console_domain }}/fullchain.pem # ── Phase 3: Deploy TLS configs ─────────────────────────────────────────────── - name: Deploy TLS config for performancewest.net ansible.builtin.template: src: pw-site-tls.conf.j2 dest: /etc/nginx/sites-available/pw-site.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Deploy TLS config for api.performancewest.net ansible.builtin.template: src: pw-api-tls.conf.j2 dest: /etc/nginx/sites-available/pw-api.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable API site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-api.conf dest: /etc/nginx/sites-enabled/pw-api.conf state: link - name: Deploy TLS config for portal.performancewest.net ansible.builtin.template: src: pw-portal-tls.conf.j2 dest: /etc/nginx/sites-available/pw-portal.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable portal site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-portal.conf dest: /etc/nginx/sites-enabled/pw-portal.conf state: link - name: Deploy TLS config for crm.performancewest.net (ERPNext internal CRM) ansible.builtin.template: src: pw-support-tls.conf.j2 dest: /etc/nginx/sites-available/pw-crm.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable CRM site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-crm.conf dest: /etc/nginx/sites-enabled/pw-crm.conf state: link - name: Remove deprecated Zammad/Mautic/support site configs ansible.builtin.file: path: "{{ item }}" state: absent loop: - /etc/nginx/sites-available/pw-support.conf - /etc/nginx/sites-enabled/pw-support.conf - /etc/nginx/sites-available/pw-mautic.conf - /etc/nginx/sites-enabled/pw-mautic.conf notify: Reload nginx - name: Deploy TLS config for lists.performancewest.net (Listmonk) ansible.builtin.template: src: pw-listmonk-tls.conf.j2 dest: /etc/nginx/sites-available/pw-listmonk.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable Listmonk site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-listmonk.conf dest: /etc/nginx/sites-enabled/pw-listmonk.conf state: link - name: Deploy TLS config for analytics.performancewest.net ansible.builtin.template: src: pw-analytics-tls.conf.j2 dest: /etc/nginx/sites-available/pw-analytics.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable analytics site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-analytics.conf dest: /etc/nginx/sites-enabled/pw-analytics.conf state: link - name: Deploy TLS config for dev.performancewest.net ansible.builtin.template: src: pw-dev-tls.conf.j2 dest: /etc/nginx/sites-available/pw-dev-site.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable dev site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-dev-site.conf dest: /etc/nginx/sites-enabled/pw-dev-site.conf state: link - name: Deploy TLS config for api.dev.performancewest.net ansible.builtin.template: src: pw-dev-api-tls.conf.j2 dest: /etc/nginx/sites-available/pw-dev-api.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable dev API site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-dev-api.conf dest: /etc/nginx/sites-enabled/pw-dev-api.conf state: link - name: Deploy TLS config for pay.performancewest.net (SHKeeper API) ansible.builtin.template: src: pw-btcpay-tls.conf.j2 dest: /etc/nginx/sites-available/pw-btcpay.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable SHKeeper API site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-btcpay.conf dest: /etc/nginx/sites-enabled/pw-btcpay.conf state: link - name: Deploy TLS config for crypto.performancewest.net (SHKeeper Admin) ansible.builtin.template: src: pw-crypto-tls.conf.j2 dest: /etc/nginx/sites-available/pw-crypto.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable SHKeeper Admin site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-crypto.conf dest: /etc/nginx/sites-enabled/pw-crypto.conf state: link - name: Deploy TLS config for minio.performancewest.net + console ansible.builtin.template: src: pw-minio-tls.conf.j2 dest: /etc/nginx/sites-available/pw-minio.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable MinIO site config ansible.builtin.file: src: /etc/nginx/sites-available/pw-minio.conf dest: /etc/nginx/sites-enabled/pw-minio.conf state: link # ── Phase 4: Firewall & fail2ban ───────────────────────────────────────────── - name: Allow HTTP through UFW community.general.ufw: rule: allow port: "80" proto: tcp comment: HTTP - name: Allow HTTPS through UFW community.general.ufw: rule: allow port: "443" proto: tcp comment: HTTPS - name: Deploy fail2ban nginx filter ansible.builtin.copy: content: | [Definition] failregex = ^ .* "(GET|POST|HEAD) .*(\.php|\.asp|wp-admin|wp-login|\.env|\.git).*" (400|403|404|444) ignoreregex = dest: /etc/fail2ban/filter.d/nginx-badbots.conf owner: root group: root mode: "0644" notify: Restart fail2ban - name: Deploy fail2ban nginx jail ansible.builtin.copy: content: | [nginx-badbots] enabled = true port = http,https filter = nginx-badbots logpath = /var/log/nginx/access.log maxretry = 5 bantime = 3600 findtime = 600 dest: /etc/fail2ban/jail.d/nginx-badbots.conf owner: root group: root mode: "0644" notify: Restart fail2ban - name: Deploy fail2ban pw-api filter ansible.builtin.copy: src: "{{ playbook_dir }}/../../../fail2ban/filter.d/pw-api.conf" dest: /etc/fail2ban/filter.d/pw-api.conf owner: root group: root mode: "0644" notify: Restart fail2ban - name: Deploy fail2ban pw-api jail ansible.builtin.copy: src: "{{ playbook_dir }}/../../../fail2ban/jail.d/pw.conf" dest: /etc/fail2ban/jail.d/pw-api.conf owner: root group: root mode: "0644" notify: Restart fail2ban - name: Enable and start fail2ban ansible.builtin.systemd: name: fail2ban enabled: true state: started - name: Enable and start nginx ansible.builtin.systemd: name: nginx enabled: true state: started # ── Phase 5: Certbot renewal ────────────────────────────────────────────────── - name: Set up certbot renewal cron ansible.builtin.cron: name: "Certbot renewal" minute: "30" hour: "3" job: "certbot renew --quiet --post-hook 'systemctl reload nginx'" user: root