new-site/infra/ansible/roles/nginx/tasks/main.yml
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

447 lines
13 KiB
YAML

---
- 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 = ^<HOST> .* "(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