diff --git a/roles/deluge/README.md b/roles/deluge/README.md index 1b33347..becbddb 100644 --- a/roles/deluge/README.md +++ b/roles/deluge/README.md @@ -18,8 +18,8 @@ Set the inbound port to `{{ deluge_torrent_port }}` under Preferences → Networ ## Storage -- `deluge_config_dir` - torrent state, auth and web settings. Backed up. -- `deluge_downloads_dir` - default save path for completed torrents. User data, not backed up. +- `deluge_config_dir` - torrent state, auth and web settings. Must stay on local storage (SQLite over NFS corrupts under concurrent access). Backed up. +- `deluge_downloads_dir` - default save path for completed torrents. May be a network mount (see `roles/nfs_client`). User data, not backed up. ## Ports @@ -30,9 +30,21 @@ Set the inbound port to `{{ deluge_torrent_port }}` under Preferences → Networ The thin-client daemon port (58846) is not mapped; add it to the pod manifest if a native Deluge client needs to connect. +## Firewall + +The role opens `deluge_torrent_port` (tcp+udp) in ufw for +`deluge_firewall_allowed_sources` (default `0.0.0.0/0` — peers must reach it for +a seedbox to work). The web UI port is not opened: expose it via nginx or reach +it over the VPN. + ## Nginx Reverse Proxy -Set `deluge_nginx_enabled: true` and `deluge_nginx_hostname` to expose the web UI over HTTPS. Certificates are provisioned via the shared `nginx` role's certbot tasks. If the hostname is not the apex, point a DNS record at the host first. +Set `deluge_nginx_enabled: true` and `deluge_nginx_hostname` to expose the web UI over HTTPS. TLS is opt-in: + +- `deluge_nginx_letsencrypt: false` (default) — use the shared self-signed cert (`nginx_default_ssl_cert`, generated by the `nginx` role). Required for internal names like `*.nas.local`, which Let's Encrypt cannot issue. +- `deluge_nginx_letsencrypt: true` — provision a Let's Encrypt cert via the shared `nginx` role's certbot tasks. Requires a publicly-resolvable hostname with a DNS record pointing at the host. + +The web UI login has no fail2ban coverage (Deluge uses form auth, not HTTP Basic), so don't leave it public. Set `deluge_nginx_allowed_sources` to the VPN subnet (e.g. `["192.168.30.0/27"]`) to restrict the vhost to the tunnel; everything else gets a 403. Empty (the default) leaves it open. ## Backup diff --git a/roles/deluge/defaults/main.yml b/roles/deluge/defaults/main.yml index ca93c37..deca0db 100644 --- a/roles/deluge/defaults/main.yml +++ b/roles/deluge/defaults/main.yml @@ -12,10 +12,17 @@ deluge_timezone: UTC deluge_loglevel: error # Storage. Config holds torrent state, auth and web settings; downloads is the -# default save path. Both must live on local storage, not NFS. +# default save path. Config MUST stay on local storage (SQLite over NFS corrupts +# under concurrent access); downloads may be a network mount (see roles/nfs_client). deluge_config_dir: "{{ podman_projects_dir }}/deluge/config" deluge_downloads_dir: "{{ podman_projects_dir }}/deluge/downloads" +# Inbound firewall. The torrent port must be reachable by peers, so it is open +# to the world by default. The web UI is not opened here: expose it via nginx +# (deluge_nginx_enabled) or reach it over the VPN. +deluge_firewall_allowed_sources: + - "0.0.0.0/0" + # Backup manifest - consumed by roles/backup via inventory `backup_jobs`. # Only the config (torrent state, auth, web settings) is worth backing up; # downloads are user data, not Ansible-reproducible state. @@ -25,3 +32,20 @@ deluge_backup_paths: # Nginx reverse proxy configuration deluge_nginx_enabled: false deluge_nginx_hostname: deluge.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +deluge_nginx_letsencrypt: false +deluge_nginx_ssl_certificate: >- + {{ deluge_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ deluge_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +deluge_nginx_ssl_certificate_key: >- + {{ deluge_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ deluge_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} +# Optional client allow-list for the vhost. Empty = reachable from anywhere. +# Set to the VPN subnet (e.g. ["192.168.30.0/27"]) to keep the web UI off the +# public internet while still reaching it over the tunnel. +deluge_nginx_allowed_sources: [] diff --git a/roles/deluge/tasks/main.yml b/roles/deluge/tasks/main.yml index cd90f40..685fea9 100644 --- a/roles/deluge/tasks/main.yml +++ b/roles/deluge/tasks/main.yml @@ -83,11 +83,27 @@ become: false become_user: "{{ ansible_user }}" +- name: Allow torrent traffic through firewall + community.general.ufw: + rule: allow + port: "{{ deluge_torrent_port }}" + proto: any + from: "{{ item }}" + direction: in + comment: "Deluge torrent" + loop: "{{ deluge_firewall_allowed_sources }}" + loop_control: + label: "{{ item }}" + retries: 5 + delay: 2 + register: _deluge_ufw + until: _deluge_ufw is succeeded + - name: Provision TLS certificate for Deluge ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ deluge_nginx_hostname }}" - when: deluge_nginx_enabled + when: deluge_nginx_enabled and deluge_nginx_letsencrypt - name: Deploy nginx vhost configuration for Deluge ansible.builtin.template: diff --git a/roles/deluge/templates/nginx-vhost.conf.j2 b/roles/deluge/templates/nginx-vhost.conf.j2 index 73e3cb8..4bd18aa 100644 --- a/roles/deluge/templates/nginx-vhost.conf.j2 +++ b/roles/deluge/templates/nginx-vhost.conf.j2 @@ -1,4 +1,4 @@ -# Deluge vhost with Let's Encrypt (Certbot) +# Deluge vhost (Let's Encrypt when deluge_nginx_letsencrypt, else self-signed) # Managed by Ansible - DO NOT EDIT MANUALLY server { @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ deluge_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ deluge_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ deluge_nginx_hostname }}/privkey.pem; + ssl_certificate {{ deluge_nginx_ssl_certificate }}; + ssl_certificate_key {{ deluge_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; @@ -41,6 +40,12 @@ server { client_max_body_size 20M; location / { +{% if deluge_nginx_allowed_sources | length > 0 %} +{% for source in deluge_nginx_allowed_sources %} + allow {{ source }}; +{% endfor %} + deny all; +{% endif %} proxy_pass http://127.0.0.1:{{ deluge_web_port }}; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; diff --git a/roles/fdroid/defaults/main.yml b/roles/fdroid/defaults/main.yml index 2f8c532..8d4e0d3 100644 --- a/roles/fdroid/defaults/main.yml +++ b/roles/fdroid/defaults/main.yml @@ -31,3 +31,16 @@ fdroid_update_interval: "24h" # Nginx reverse proxy configuration fdroid_nginx_enabled: false fdroid_nginx_hostname: apk.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +fdroid_nginx_letsencrypt: false +fdroid_nginx_ssl_certificate: >- + {{ fdroid_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ fdroid_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +fdroid_nginx_ssl_certificate_key: >- + {{ fdroid_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ fdroid_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/fdroid/tasks/main.yml b/roles/fdroid/tasks/main.yml index d5453dd..5f6a735 100644 --- a/roles/fdroid/tasks/main.yml +++ b/roles/fdroid/tasks/main.yml @@ -168,7 +168,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ fdroid_nginx_hostname }}" - when: fdroid_nginx_enabled + when: fdroid_nginx_enabled and fdroid_nginx_letsencrypt - name: Deploy nginx vhost configuration for fdroid ansible.builtin.template: diff --git a/roles/fdroid/templates/nginx-vhost.conf.j2 b/roles/fdroid/templates/nginx-vhost.conf.j2 index d0080b6..462c6e3 100644 --- a/roles/fdroid/templates/nginx-vhost.conf.j2 +++ b/roles/fdroid/templates/nginx-vhost.conf.j2 @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ fdroid_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ fdroid_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ fdroid_nginx_hostname }}/privkey.pem; + ssl_certificate {{ fdroid_nginx_ssl_certificate }}; + ssl_certificate_key {{ fdroid_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/gitea/defaults/main.yml b/roles/gitea/defaults/main.yml index 642f128..5d55d39 100644 --- a/roles/gitea/defaults/main.yml +++ b/roles/gitea/defaults/main.yml @@ -31,3 +31,16 @@ gitea_disable_registration: false # Nginx reverse proxy configuration gitea_nginx_enabled: true gitea_nginx_hostname: "{{ gitea_domain }}" +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +gitea_nginx_letsencrypt: false +gitea_nginx_ssl_certificate: >- + {{ gitea_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ gitea_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +gitea_nginx_ssl_certificate_key: >- + {{ gitea_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ gitea_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/gitea/tasks/main.yml b/roles/gitea/tasks/main.yml index 8e7d0ee..4f18bf0 100644 --- a/roles/gitea/tasks/main.yml +++ b/roles/gitea/tasks/main.yml @@ -147,7 +147,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ gitea_nginx_hostname }}" - when: gitea_nginx_enabled + when: gitea_nginx_enabled and gitea_nginx_letsencrypt - name: Deploy nginx vhost configuration for Gitea ansible.builtin.template: diff --git a/roles/gitea/templates/nginx-vhost.conf.j2 b/roles/gitea/templates/nginx-vhost.conf.j2 index 2f1a24d..3d6eec2 100644 --- a/roles/gitea/templates/nginx-vhost.conf.j2 +++ b/roles/gitea/templates/nginx-vhost.conf.j2 @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ gitea_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ gitea_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ gitea_nginx_hostname }}/privkey.pem; + ssl_certificate {{ gitea_nginx_ssl_certificate }}; + ssl_certificate_key {{ gitea_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/grafana/defaults/main.yml b/roles/grafana/defaults/main.yml index 8195c69..0da9322 100644 --- a/roles/grafana/defaults/main.yml +++ b/roles/grafana/defaults/main.yml @@ -31,6 +31,19 @@ grafana_check_for_updates: false # Nginx reverse proxy grafana_nginx_enabled: true grafana_nginx_hostname: "{{ grafana_domain }}" +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +grafana_nginx_letsencrypt: false +grafana_nginx_ssl_certificate: >- + {{ grafana_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ grafana_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +grafana_nginx_ssl_certificate_key: >- + {{ grafana_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ grafana_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} # Alerting (Grafana Unified Alerting → ntfy webhook) # When enabled, provisions disk alert rules (HDD temperature + ZFS/filesystem diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml index 8d669c9..42c7cdc 100644 --- a/roles/grafana/tasks/main.yml +++ b/roles/grafana/tasks/main.yml @@ -346,7 +346,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ grafana_nginx_hostname }}" - when: grafana_nginx_enabled + when: grafana_nginx_enabled and grafana_nginx_letsencrypt - name: Deploy nginx vhost ansible.builtin.template: diff --git a/roles/grafana/templates/nginx-vhost.conf.j2 b/roles/grafana/templates/nginx-vhost.conf.j2 index 78dcbf2..ae2a922 100644 --- a/roles/grafana/templates/nginx-vhost.conf.j2 +++ b/roles/grafana/templates/nginx-vhost.conf.j2 @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ grafana_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ grafana_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ grafana_nginx_hostname }}/privkey.pem; + ssl_certificate {{ grafana_nginx_ssl_certificate }}; + ssl_certificate_key {{ grafana_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/immich/defaults/main.yml b/roles/immich/defaults/main.yml index d5767a9..ce23f67 100644 --- a/roles/immich/defaults/main.yml +++ b/roles/immich/defaults/main.yml @@ -68,3 +68,16 @@ immich_timezone: UTC # Nginx reverse proxy configuration immich_nginx_enabled: false immich_nginx_hostname: photos.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +immich_nginx_letsencrypt: false +immich_nginx_ssl_certificate: >- + {{ immich_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ immich_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +immich_nginx_ssl_certificate_key: >- + {{ immich_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ immich_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/immich/tasks/main.yml b/roles/immich/tasks/main.yml index 23edcc8..743236c 100644 --- a/roles/immich/tasks/main.yml +++ b/roles/immich/tasks/main.yml @@ -173,7 +173,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ immich_nginx_hostname }}" - when: immich_nginx_enabled + when: immich_nginx_enabled and immich_nginx_letsencrypt - name: Deploy nginx vhost configuration for Immich ansible.builtin.template: diff --git a/roles/immich/templates/nginx-vhost.conf.j2 b/roles/immich/templates/nginx-vhost.conf.j2 index 09c2287..89f53ef 100644 --- a/roles/immich/templates/nginx-vhost.conf.j2 +++ b/roles/immich/templates/nginx-vhost.conf.j2 @@ -23,9 +23,8 @@ server { http2 on; server_name {{ immich_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ immich_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ immich_nginx_hostname }}/privkey.pem; + ssl_certificate {{ immich_nginx_ssl_certificate }}; + ssl_certificate_key {{ immich_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/metabase/defaults/main.yml b/roles/metabase/defaults/main.yml index 6c1d7d2..8f4e695 100644 --- a/roles/metabase/defaults/main.yml +++ b/roles/metabase/defaults/main.yml @@ -16,3 +16,16 @@ metabase_timezone: UTC metabase_nginx_enabled: false metabase_nginx_hostname: metabase.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +metabase_nginx_letsencrypt: false +metabase_nginx_ssl_certificate: >- + {{ metabase_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ metabase_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +metabase_nginx_ssl_certificate_key: >- + {{ metabase_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ metabase_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/metabase/tasks/main.yml b/roles/metabase/tasks/main.yml index 5a68dce..4d69270 100644 --- a/roles/metabase/tasks/main.yml +++ b/roles/metabase/tasks/main.yml @@ -119,7 +119,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ metabase_nginx_hostname }}" - when: metabase_nginx_enabled + when: metabase_nginx_enabled and metabase_nginx_letsencrypt - name: Deploy nginx vhost configuration for Metabase ansible.builtin.template: diff --git a/roles/metabase/templates/nginx-vhost.conf.j2 b/roles/metabase/templates/nginx-vhost.conf.j2 index 0dfb04e..54182cf 100644 --- a/roles/metabase/templates/nginx-vhost.conf.j2 +++ b/roles/metabase/templates/nginx-vhost.conf.j2 @@ -20,8 +20,8 @@ server { http2 on; server_name {{ metabase_nginx_hostname }}; - ssl_certificate /etc/letsencrypt/live/{{ metabase_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ metabase_nginx_hostname }}/privkey.pem; + ssl_certificate {{ metabase_nginx_ssl_certificate }}; + ssl_certificate_key {{ metabase_nginx_ssl_certificate_key }}; ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; ssl_prefer_server_ciphers on; diff --git a/roles/nginx/README.md b/roles/nginx/README.md index 05dd541..8595690 100644 --- a/roles/nginx/README.md +++ b/roles/nginx/README.md @@ -48,6 +48,10 @@ prometheus_scrape_configs: - targets: ['127.0.0.1:9113'] ``` +## TLS certificates + +Let's Encrypt (via the shared `certbot.yml` task) only works for publicly-resolvable hostnames. Each service role exposes a `_nginx_letsencrypt` flag (default `false`) and two computed paths (`_nginx_ssl_certificate` / `_key`): when the flag is off the vhost uses the shared self-signed cert (`nginx_default_ssl_cert`), when on it uses `/etc/letsencrypt/live//`. Gate the certbot include on the flag and reference the path variables from the vhost template. Set the flag `true` in inventory only once a public DNS record points at the host. + ## Service Integration Pattern Each service role should deploy its own vhost config: diff --git a/roles/nginx/tasks/certbot.yml b/roles/nginx/tasks/certbot.yml index e5849d2..b3e6fc9 100644 --- a/roles/nginx/tasks/certbot.yml +++ b/roles/nginx/tasks/certbot.yml @@ -5,12 +5,16 @@ # - certbot_hostname: the domain to provision (e.g. "apk.jokester.fr") # - acme_email: Let's Encrypt account email (typically from host_vars) # +# Let's Encrypt only issues for publicly-resolvable hostnames, so gate the +# include on the service's `_nginx_letsencrypt` flag (default false; +# internal names fall back to the shared self-signed cert). +# # Usage from a service role: # - name: Provision TLS certificate # ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" # vars: # certbot_hostname: "{{ myservice_nginx_hostname }}" -# when: myservice_nginx_enabled +# when: myservice_nginx_enabled and myservice_nginx_letsencrypt - name: Validate certbot requirements ansible.builtin.assert: diff --git a/roles/ntfy/defaults/main.yml b/roles/ntfy/defaults/main.yml index f56e552..977628c 100644 --- a/roles/ntfy/defaults/main.yml +++ b/roles/ntfy/defaults/main.yml @@ -43,3 +43,16 @@ ntfy_enable_reservations: true # Only authenticated users can reserve topics # Nginx reverse proxy configuration ntfy_nginx_enabled: false ntfy_nginx_hostname: ntfy.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +ntfy_nginx_letsencrypt: false +ntfy_nginx_ssl_certificate: >- + {{ ntfy_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ ntfy_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +ntfy_nginx_ssl_certificate_key: >- + {{ ntfy_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ ntfy_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/ntfy/tasks/main.yml b/roles/ntfy/tasks/main.yml index f03081f..1ae5f5c 100644 --- a/roles/ntfy/tasks/main.yml +++ b/roles/ntfy/tasks/main.yml @@ -147,7 +147,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ ntfy_nginx_hostname }}" - when: ntfy_nginx_enabled + when: ntfy_nginx_enabled and ntfy_nginx_letsencrypt - name: Deploy nginx vhost configuration for ntfy ansible.builtin.template: diff --git a/roles/ntfy/templates/nginx-vhost.conf.j2 b/roles/ntfy/templates/nginx-vhost.conf.j2 index 619ff27..ba97902 100644 --- a/roles/ntfy/templates/nginx-vhost.conf.j2 +++ b/roles/ntfy/templates/nginx-vhost.conf.j2 @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ ntfy_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ ntfy_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ ntfy_nginx_hostname }}/privkey.pem; + ssl_certificate {{ ntfy_nginx_ssl_certificate }}; + ssl_certificate_key {{ ntfy_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/prowlarr/README.md b/roles/prowlarr/README.md index 355aea9..bea1ea8 100644 --- a/roles/prowlarr/README.md +++ b/roles/prowlarr/README.md @@ -22,7 +22,10 @@ See `defaults/main.yml` for all available variables and their default values. ## Nginx Reverse Proxy -Set `prowlarr_nginx_enabled: true` and `prowlarr_nginx_hostname` to expose the web UI over HTTPS. Certificates are provisioned via the shared `nginx` role's certbot tasks. If the hostname is not the apex, point a DNS record at the host first. +Set `prowlarr_nginx_enabled: true` and `prowlarr_nginx_hostname` to expose the web UI over HTTPS. TLS is opt-in: + +- `prowlarr_nginx_letsencrypt: false` (default) — use the shared self-signed cert (`nginx_default_ssl_cert`, generated by the `nginx` role). Required for internal names like `*.nas.local`, which Let's Encrypt cannot issue. +- `prowlarr_nginx_letsencrypt: true` — provision a Let's Encrypt cert via the shared `nginx` role's certbot tasks. Requires a publicly-resolvable hostname with a DNS record pointing at the host. ## Backup diff --git a/roles/prowlarr/defaults/main.yml b/roles/prowlarr/defaults/main.yml index 5078017..96160d5 100644 --- a/roles/prowlarr/defaults/main.yml +++ b/roles/prowlarr/defaults/main.yml @@ -19,3 +19,16 @@ prowlarr_backup_paths: # Nginx reverse proxy configuration prowlarr_nginx_enabled: false prowlarr_nginx_hostname: prowlarr.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +prowlarr_nginx_letsencrypt: false +prowlarr_nginx_ssl_certificate: >- + {{ prowlarr_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ prowlarr_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +prowlarr_nginx_ssl_certificate_key: >- + {{ prowlarr_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ prowlarr_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/prowlarr/tasks/main.yml b/roles/prowlarr/tasks/main.yml index a218d36..8cd2933 100644 --- a/roles/prowlarr/tasks/main.yml +++ b/roles/prowlarr/tasks/main.yml @@ -84,7 +84,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ prowlarr_nginx_hostname }}" - when: prowlarr_nginx_enabled + when: prowlarr_nginx_enabled and prowlarr_nginx_letsencrypt - name: Deploy nginx vhost configuration for Prowlarr ansible.builtin.template: diff --git a/roles/prowlarr/templates/nginx-vhost.conf.j2 b/roles/prowlarr/templates/nginx-vhost.conf.j2 index e6962df..8804792 100644 --- a/roles/prowlarr/templates/nginx-vhost.conf.j2 +++ b/roles/prowlarr/templates/nginx-vhost.conf.j2 @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ prowlarr_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ prowlarr_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ prowlarr_nginx_hostname }}/privkey.pem; + ssl_certificate {{ prowlarr_nginx_ssl_certificate }}; + ssl_certificate_key {{ prowlarr_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/radarr/README.md b/roles/radarr/README.md index a7e8d77..96a50b3 100644 --- a/roles/radarr/README.md +++ b/roles/radarr/README.md @@ -26,7 +26,10 @@ See `defaults/main.yml` for all available variables and their default values. ## Nginx Reverse Proxy -Set `radarr_nginx_enabled: true` and `radarr_nginx_hostname` to expose the web UI over HTTPS. Certificates are provisioned via the shared `nginx` role's certbot tasks. If the hostname is not the apex, point a DNS record at the host first. +Set `radarr_nginx_enabled: true` and `radarr_nginx_hostname` to expose the web UI over HTTPS. TLS is opt-in: + +- `radarr_nginx_letsencrypt: false` (default) — use the shared self-signed cert (`nginx_default_ssl_cert`, generated by the `nginx` role). Required for internal names like `*.nas.local`, which Let's Encrypt cannot issue. +- `radarr_nginx_letsencrypt: true` — provision a Let's Encrypt cert via the shared `nginx` role's certbot tasks. Requires a publicly-resolvable hostname with a DNS record pointing at the host. ## Backup diff --git a/roles/radarr/defaults/main.yml b/roles/radarr/defaults/main.yml index b553b27..0eb057c 100644 --- a/roles/radarr/defaults/main.yml +++ b/roles/radarr/defaults/main.yml @@ -25,3 +25,16 @@ radarr_backup_paths: # Nginx reverse proxy configuration radarr_nginx_enabled: false radarr_nginx_hostname: radarr.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +radarr_nginx_letsencrypt: false +radarr_nginx_ssl_certificate: >- + {{ radarr_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ radarr_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +radarr_nginx_ssl_certificate_key: >- + {{ radarr_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ radarr_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/radarr/tasks/main.yml b/roles/radarr/tasks/main.yml index 90749b5..5266feb 100644 --- a/roles/radarr/tasks/main.yml +++ b/roles/radarr/tasks/main.yml @@ -87,7 +87,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ radarr_nginx_hostname }}" - when: radarr_nginx_enabled + when: radarr_nginx_enabled and radarr_nginx_letsencrypt - name: Deploy nginx vhost configuration for Radarr ansible.builtin.template: diff --git a/roles/radarr/templates/nginx-vhost.conf.j2 b/roles/radarr/templates/nginx-vhost.conf.j2 index 445b0c0..442237b 100644 --- a/roles/radarr/templates/nginx-vhost.conf.j2 +++ b/roles/radarr/templates/nginx-vhost.conf.j2 @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ radarr_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ radarr_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ radarr_nginx_hostname }}/privkey.pem; + ssl_certificate {{ radarr_nginx_ssl_certificate }}; + ssl_certificate_key {{ radarr_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/seerr/README.md b/roles/seerr/README.md index b8b72f1..eb86bb6 100644 --- a/roles/seerr/README.md +++ b/roles/seerr/README.md @@ -28,7 +28,10 @@ The image runs as the unprivileged `node` user (UID/GID 1000). The pod maps the ## Nginx Reverse Proxy -Set `seerr_nginx_enabled: true` and `seerr_nginx_hostname` to expose the web UI over HTTPS. Certificates are provisioned via the shared `nginx` role's certbot tasks. If the hostname is not the apex, point a DNS record at the host first. +Set `seerr_nginx_enabled: true` and `seerr_nginx_hostname` to expose the web UI over HTTPS. TLS is opt-in: + +- `seerr_nginx_letsencrypt: false` (default) — use the shared self-signed cert (`nginx_default_ssl_cert`, generated by the `nginx` role). Required for internal names like `*.nas.local`, which Let's Encrypt cannot issue. +- `seerr_nginx_letsencrypt: true` — provision a Let's Encrypt cert via the shared `nginx` role's certbot tasks. Requires a publicly-resolvable hostname with a DNS record pointing at the host. ## Backup diff --git a/roles/seerr/defaults/main.yml b/roles/seerr/defaults/main.yml index 6cfdba3..7c84d94 100644 --- a/roles/seerr/defaults/main.yml +++ b/roles/seerr/defaults/main.yml @@ -26,3 +26,16 @@ seerr_backup_paths: # Nginx reverse proxy configuration seerr_nginx_enabled: false seerr_nginx_hostname: seerr.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +seerr_nginx_letsencrypt: false +seerr_nginx_ssl_certificate: >- + {{ seerr_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ seerr_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +seerr_nginx_ssl_certificate_key: >- + {{ seerr_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ seerr_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/seerr/tasks/main.yml b/roles/seerr/tasks/main.yml index 66045b9..ed44b1c 100644 --- a/roles/seerr/tasks/main.yml +++ b/roles/seerr/tasks/main.yml @@ -84,7 +84,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ seerr_nginx_hostname }}" - when: seerr_nginx_enabled + when: seerr_nginx_enabled and seerr_nginx_letsencrypt - name: Deploy nginx vhost configuration for Seerr ansible.builtin.template: diff --git a/roles/seerr/templates/nginx-vhost.conf.j2 b/roles/seerr/templates/nginx-vhost.conf.j2 index c79fc98..caa5245 100644 --- a/roles/seerr/templates/nginx-vhost.conf.j2 +++ b/roles/seerr/templates/nginx-vhost.conf.j2 @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ seerr_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ seerr_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ seerr_nginx_hostname }}/privkey.pem; + ssl_certificate {{ seerr_nginx_ssl_certificate }}; + ssl_certificate_key {{ seerr_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/static_web/defaults/main.yml b/roles/static_web/defaults/main.yml index 93d211a..6cdb2b0 100644 --- a/roles/static_web/defaults/main.yml +++ b/roles/static_web/defaults/main.yml @@ -9,7 +9,10 @@ # git_depth: 1 # optional, shallow clone depth # build_command: "" # optional, command to run after git clone (e.g., npm build) # root_dir: "" # optional, subdirectory to serve (e.g., "dist" or "build") -# ssl_enabled: true # optional, enable HTTPS with Let's Encrypt (default: true) +# ssl_enabled: true # optional, enable HTTPS (default: true) +# letsencrypt: false # optional, use Let's Encrypt for TLS (default: false, +# # falls back to the shared self-signed cert). Requires +# # a publicly-resolvable hostname. static_web_sites: {} diff --git a/roles/static_web/tasks/main.yml b/roles/static_web/tasks/main.yml index 9af4a77..f681659 100644 --- a/roles/static_web/tasks/main.yml +++ b/roles/static_web/tasks/main.yml @@ -62,6 +62,7 @@ when: - static_web_sites | length > 0 - item.value.ssl_enabled | default(true) + - item.value.letsencrypt | default(false) - name: Deploy nginx vhost configurations ansible.builtin.template: diff --git a/roles/static_web/templates/nginx-vhost.conf.j2 b/roles/static_web/templates/nginx-vhost.conf.j2 index 3c70ab0..21ea030 100644 --- a/roles/static_web/templates/nginx-vhost.conf.j2 +++ b/roles/static_web/templates/nginx-vhost.conf.j2 @@ -23,9 +23,12 @@ server { listen [::]:443 ssl; server_name {{ hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ hostname }}/privkey.pem; + ssl_certificate {{ (site_config.letsencrypt | default(false)) | ternary( + '/etc/letsencrypt/live/' ~ hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }}; + ssl_certificate_key {{ (site_config.letsencrypt | default(false)) | ternary( + '/etc/letsencrypt/live/' ~ hostname ~ '/privkey.pem', + nginx_default_ssl_key) }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }}; diff --git a/roles/uptime_kuma/defaults/main.yml b/roles/uptime_kuma/defaults/main.yml index 1ec5381..90d6353 100644 --- a/roles/uptime_kuma/defaults/main.yml +++ b/roles/uptime_kuma/defaults/main.yml @@ -31,3 +31,16 @@ uptime_kuma_timezone: UTC # Nginx reverse proxy configuration uptime_kuma_nginx_enabled: false uptime_kuma_nginx_hostname: uptime.nas.local +# Let's Encrypt (certbot) requires a publicly-resolvable hostname. Internal +# names (e.g. *.nas.local) cannot get a public cert: leave this false to use +# the shared self-signed cert (nginx_default_ssl_cert). Set true once a public +# DNS record points at the host. +uptime_kuma_nginx_letsencrypt: false +uptime_kuma_nginx_ssl_certificate: >- + {{ uptime_kuma_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ uptime_kuma_nginx_hostname ~ '/fullchain.pem', + nginx_default_ssl_cert) }} +uptime_kuma_nginx_ssl_certificate_key: >- + {{ uptime_kuma_nginx_letsencrypt | ternary( + '/etc/letsencrypt/live/' ~ uptime_kuma_nginx_hostname ~ '/privkey.pem', + nginx_default_ssl_key) }} diff --git a/roles/uptime_kuma/tasks/main.yml b/roles/uptime_kuma/tasks/main.yml index 30f82f8..8e9e802 100644 --- a/roles/uptime_kuma/tasks/main.yml +++ b/roles/uptime_kuma/tasks/main.yml @@ -83,7 +83,7 @@ ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml" vars: certbot_hostname: "{{ uptime_kuma_nginx_hostname }}" - when: uptime_kuma_nginx_enabled + when: uptime_kuma_nginx_enabled and uptime_kuma_nginx_letsencrypt - name: Deploy nginx vhost configuration for uptime-kuma ansible.builtin.template: diff --git a/roles/uptime_kuma/templates/nginx-vhost.conf.j2 b/roles/uptime_kuma/templates/nginx-vhost.conf.j2 index af395dc..ea1bd0f 100644 --- a/roles/uptime_kuma/templates/nginx-vhost.conf.j2 +++ b/roles/uptime_kuma/templates/nginx-vhost.conf.j2 @@ -22,9 +22,8 @@ server { listen [::]:443 ssl; server_name {{ uptime_kuma_nginx_hostname }}; - # Let's Encrypt certificates (managed by Certbot) - ssl_certificate /etc/letsencrypt/live/{{ uptime_kuma_nginx_hostname }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ uptime_kuma_nginx_hostname }}/privkey.pem; + ssl_certificate {{ uptime_kuma_nginx_ssl_certificate }}; + ssl_certificate_key {{ uptime_kuma_nginx_ssl_certificate_key }}; # SSL configuration ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }};