mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-11-04 12:18:17 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			04deeef385
			...
			e5e394d470
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e5e394d470 | |||
| d796158c61 | 
@@ -79,11 +79,13 @@ activate_all_timers:                       false   # Activates all timers, indep
 | 
				
			|||||||
# You SHOULD NOT enable this on production servers
 | 
					# You SHOULD NOT enable this on production servers
 | 
				
			||||||
enable_debug:                             false
 | 
					enable_debug:                             false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dns_provider:                             cloudflare              # The DNS Provider\Registrar for the domain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Which ACME method to use: webroot, cloudflare, or hetzner
 | 
					# Which ACME method to use: webroot, cloudflare, or hetzner
 | 
				
			||||||
certbot_acme_challenge_method:            "cloudflare"
 | 
					certbot_acme_challenge_method:            "cloudflare"
 | 
				
			||||||
certbot_credentials_dir:                  /etc/certbot
 | 
					certbot_credentials_dir:                  /etc/certbot
 | 
				
			||||||
certbot_credentials_file:                 "{{ certbot_credentials_dir }}/{{ certbot_acme_challenge_method }}.ini"
 | 
					certbot_credentials_file:                 "{{ certbot_credentials_dir }}/{{ certbot_acme_challenge_method }}.ini"
 | 
				
			||||||
# certbot_dns_api_token                   # Define in inventory file
 | 
					# certbot_dns_api_token                                           # Define in inventory file
 | 
				
			||||||
certbot_dns_propagation_wait_seconds:     40                      # How long should the script wait for DNS propagation before continuing
 | 
					certbot_dns_propagation_wait_seconds:     40                      # How long should the script wait for DNS propagation before continuing
 | 
				
			||||||
certbot_flavor:                           san                     # Possible options: san (recommended, with a dns flavor like cloudflare, or hetzner), wildcard(doesn't function with www redirect), deicated
 | 
					certbot_flavor:                           san                     # Possible options: san (recommended, with a dns flavor like cloudflare, or hetzner), wildcard(doesn't function with www redirect), deicated
 | 
				
			||||||
certbot_webroot_path:                     "/var/lib/letsencrypt/" # Path used by Certbot to serve HTTP-01 ACME challenges
 | 
					certbot_webroot_path:                     "/var/lib/letsencrypt/" # Path used by Certbot to serve HTTP-01 ACME challenges
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								library/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								library/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										92
									
								
								library/cert_check_exists.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								library/cert_check_exists.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from __future__ import absolute_import, division, print_function
 | 
				
			||||||
 | 
					__metaclass__ = type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DOCUMENTATION = r'''
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					module: cert_check_exists
 | 
				
			||||||
 | 
					short_description: Check if a SSL certificate exists for a domain
 | 
				
			||||||
 | 
					description:
 | 
				
			||||||
 | 
					  - Checks if any certificate covers the given domain.
 | 
				
			||||||
 | 
					options:
 | 
				
			||||||
 | 
					  domain:
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      - Domain name to check for in the certificates.
 | 
				
			||||||
 | 
					    required: true
 | 
				
			||||||
 | 
					    type: str
 | 
				
			||||||
 | 
					  cert_base_path:
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      - Path where certificates are stored.
 | 
				
			||||||
 | 
					    required: false
 | 
				
			||||||
 | 
					    type: str
 | 
				
			||||||
 | 
					    default: /etc/letsencrypt/live
 | 
				
			||||||
 | 
					  debug:
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      - Enable verbose debug output.
 | 
				
			||||||
 | 
					    required: false
 | 
				
			||||||
 | 
					    type: bool
 | 
				
			||||||
 | 
					    default: false
 | 
				
			||||||
 | 
					author:
 | 
				
			||||||
 | 
					  - Kevin Veen-Birkenbach
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EXAMPLES = r'''
 | 
				
			||||||
 | 
					- name: Check if cert exists
 | 
				
			||||||
 | 
					  cert_check_exists:
 | 
				
			||||||
 | 
					    domain: "matomo.cymais.cloud"
 | 
				
			||||||
 | 
					    cert_base_path: "/etc/letsencrypt/live"
 | 
				
			||||||
 | 
					  register: result
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RETURN = r'''
 | 
				
			||||||
 | 
					exists:
 | 
				
			||||||
 | 
					  description: True if a certificate covering the domain exists, false otherwise.
 | 
				
			||||||
 | 
					  type: bool
 | 
				
			||||||
 | 
					  returned: always
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					from ansible.module_utils.basic import AnsibleModule
 | 
				
			||||||
 | 
					from ansible.module_utils.cert_utils import CertUtils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def cert_exists(domain, cert_files, debug=False):
 | 
				
			||||||
 | 
					    for cert_path in cert_files:
 | 
				
			||||||
 | 
					        cert_text = CertUtils.run_openssl(cert_path)
 | 
				
			||||||
 | 
					        if not cert_text:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        sans = CertUtils.extract_sans(cert_text)
 | 
				
			||||||
 | 
					        if debug:
 | 
				
			||||||
 | 
					            print(f"Checking {cert_path}: {sans}")
 | 
				
			||||||
 | 
					        for entry in sans:
 | 
				
			||||||
 | 
					            if entry == domain or (entry.startswith('*.') and domain.endswith('.' + entry[2:])):
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def cert_check_exists(module):
 | 
				
			||||||
 | 
					    domain = module.params['domain']
 | 
				
			||||||
 | 
					    cert_base_path = module.params['cert_base_path']
 | 
				
			||||||
 | 
					    debug = module.params['debug']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cert_files = CertUtils.list_cert_files(cert_base_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    exists = cert_exists(domain, cert_files, debug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    module.exit_json(exists=exists)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    module_args = dict(
 | 
				
			||||||
 | 
					        domain=dict(type='str', required=True),
 | 
				
			||||||
 | 
					        cert_base_path=dict(type='str', required=False, default='/etc/letsencrypt/live'),
 | 
				
			||||||
 | 
					        debug=dict(type='bool', required=False, default=False),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    module = AnsibleModule(
 | 
				
			||||||
 | 
					        argument_spec=module_args,
 | 
				
			||||||
 | 
					        supports_check_mode=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cert_check_exists(module)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
@@ -5,7 +5,7 @@ __metaclass__ = type
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
DOCUMENTATION = r'''
 | 
					DOCUMENTATION = r'''
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
module: find_cert_folder
 | 
					module: cert_folder_find
 | 
				
			||||||
short_description: Find SSL certificate folder covering a given domain
 | 
					short_description: Find SSL certificate folder covering a given domain
 | 
				
			||||||
description:
 | 
					description:
 | 
				
			||||||
  - Searches through certificates to find a folder that covers the given domain.
 | 
					  - Searches through certificates to find a folder that covers the given domain.
 | 
				
			||||||
@@ -38,7 +38,7 @@ author:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
EXAMPLES = r'''
 | 
					EXAMPLES = r'''
 | 
				
			||||||
- name: Find cert folder for matomo.cymais.cloud
 | 
					- name: Find cert folder for matomo.cymais.cloud
 | 
				
			||||||
  find_cert_folder:
 | 
					  cert_folder_find:
 | 
				
			||||||
    domain: "matomo.cymais.cloud"
 | 
					    domain: "matomo.cymais.cloud"
 | 
				
			||||||
    certbot_flavor: "san"
 | 
					    certbot_flavor: "san"
 | 
				
			||||||
    cert_base_path: "/etc/letsencrypt/live"
 | 
					    cert_base_path: "/etc/letsencrypt/live"
 | 
				
			||||||
@@ -53,42 +53,18 @@ folder:
 | 
				
			|||||||
'''
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import subprocess
 | 
					 | 
				
			||||||
from ansible.module_utils.basic import AnsibleModule
 | 
					from ansible.module_utils.basic import AnsibleModule
 | 
				
			||||||
 | 
					from ansible.module_utils.cert_utils import CertUtils  # IMPORT
 | 
				
			||||||
def run_openssl(cert_path):
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        output = subprocess.check_output(
 | 
					 | 
				
			||||||
            ['openssl', 'x509', '-in', cert_path, '-noout', '-text'],
 | 
					 | 
				
			||||||
            universal_newlines=True
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return output
 | 
					 | 
				
			||||||
    except subprocess.CalledProcessError:
 | 
					 | 
				
			||||||
        return ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def extract_sans(cert_text):
 | 
					 | 
				
			||||||
    dns_entries = []
 | 
					 | 
				
			||||||
    in_san = False
 | 
					 | 
				
			||||||
    for line in cert_text.splitlines():
 | 
					 | 
				
			||||||
        line = line.strip()
 | 
					 | 
				
			||||||
        if 'X509v3 Subject Alternative Name:' in line:
 | 
					 | 
				
			||||||
            in_san = True
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
        if in_san:
 | 
					 | 
				
			||||||
            if not line:
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
            dns_entries += [e.strip().replace('DNS:', '') for e in line.split(',') if e.strip()]
 | 
					 | 
				
			||||||
    return dns_entries
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def find_matching_folders(domain, cert_files, flavor, debug):
 | 
					def find_matching_folders(domain, cert_files, flavor, debug):
 | 
				
			||||||
    exact_matches = []
 | 
					    exact_matches = []
 | 
				
			||||||
    wildcard_matches = []
 | 
					    wildcard_matches = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for cert_path in cert_files:
 | 
					    for cert_path in cert_files:
 | 
				
			||||||
        cert_text = run_openssl(cert_path)
 | 
					        cert_text = CertUtils.run_openssl(cert_path)
 | 
				
			||||||
        if not cert_text:
 | 
					        if not cert_text:
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        sans = extract_sans(cert_text)
 | 
					        sans = CertUtils.extract_sans(cert_text)
 | 
				
			||||||
        if debug:
 | 
					        if debug:
 | 
				
			||||||
            print(f"Checking {cert_path}: {sans}")
 | 
					            print(f"Checking {cert_path}: {sans}")
 | 
				
			||||||
        for entry in sans:
 | 
					        for entry in sans:
 | 
				
			||||||
@@ -106,16 +82,13 @@ def find_matching_folders(domain, cert_files, flavor, debug):
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return []
 | 
					        return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def find_cert_folder(module):
 | 
					def cert_folder_find(module):
 | 
				
			||||||
    domain = module.params['domain']
 | 
					    domain = module.params['domain']
 | 
				
			||||||
    certbot_flavor = module.params['certbot_flavor']
 | 
					    certbot_flavor = module.params['certbot_flavor']
 | 
				
			||||||
    cert_base_path = module.params['cert_base_path']
 | 
					    cert_base_path = module.params['cert_base_path']
 | 
				
			||||||
    debug = module.params['debug']
 | 
					    debug = module.params['debug']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cert_files = []
 | 
					    cert_files = CertUtils.list_cert_files(cert_base_path)
 | 
				
			||||||
    for root, dirs, files in os.walk(cert_base_path):
 | 
					 | 
				
			||||||
        if 'cert.pem' in files:
 | 
					 | 
				
			||||||
            cert_files.append(os.path.join(root, 'cert.pem'))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if debug:
 | 
					    if debug:
 | 
				
			||||||
        print(f"Found {len(cert_files)} cert.pem files under {cert_base_path}")
 | 
					        print(f"Found {len(cert_files)} cert.pem files under {cert_base_path}")
 | 
				
			||||||
@@ -126,7 +99,7 @@ def find_cert_folder(module):
 | 
				
			|||||||
        if debug:
 | 
					        if debug:
 | 
				
			||||||
            print("Fallback: searching SAN matches without SAN structure parsing")
 | 
					            print("Fallback: searching SAN matches without SAN structure parsing")
 | 
				
			||||||
        for cert_path in cert_files:
 | 
					        for cert_path in cert_files:
 | 
				
			||||||
            cert_text = run_openssl(cert_path)
 | 
					            cert_text = CertUtils.run_openssl(cert_path)
 | 
				
			||||||
            if f"DNS:{domain}" in cert_text:
 | 
					            if f"DNS:{domain}" in cert_text:
 | 
				
			||||||
                preferred.append(os.path.dirname(cert_path))
 | 
					                preferred.append(os.path.dirname(cert_path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -151,7 +124,7 @@ def main():
 | 
				
			|||||||
        supports_check_mode=True
 | 
					        supports_check_mode=True
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    find_cert_folder(module)
 | 
					    cert_folder_find(module)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    main()
 | 
					    main()
 | 
				
			||||||
							
								
								
									
										39
									
								
								module_utils/cert_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								module_utils/cert_utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CertUtils:
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def run_openssl(cert_path):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            output = subprocess.check_output(
 | 
				
			||||||
 | 
					                ['openssl', 'x509', '-in', cert_path, '-noout', '-text'],
 | 
				
			||||||
 | 
					                universal_newlines=True
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return output
 | 
				
			||||||
 | 
					        except subprocess.CalledProcessError:
 | 
				
			||||||
 | 
					            return ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def extract_sans(cert_text):
 | 
				
			||||||
 | 
					        dns_entries = []
 | 
				
			||||||
 | 
					        in_san = False
 | 
				
			||||||
 | 
					        for line in cert_text.splitlines():
 | 
				
			||||||
 | 
					            line = line.strip()
 | 
				
			||||||
 | 
					            if 'X509v3 Subject Alternative Name:' in line:
 | 
				
			||||||
 | 
					                in_san = True
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if in_san:
 | 
				
			||||||
 | 
					                if not line:
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                dns_entries += [e.strip().replace('DNS:', '') for e in line.split(',') if e.strip()]
 | 
				
			||||||
 | 
					        return dns_entries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def list_cert_files(cert_base_path):
 | 
				
			||||||
 | 
					        cert_files = []
 | 
				
			||||||
 | 
					        for root, dirs, files in os.walk(cert_base_path):
 | 
				
			||||||
 | 
					            if 'cert.pem' in files:
 | 
				
			||||||
 | 
					                cert_files.append(os.path.join(root, 'cert.pem'))
 | 
				
			||||||
 | 
					        return cert_files
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
collections:
 | 
					collections:
 | 
				
			||||||
  - name: kewlfft.aur
 | 
					  - name: kewlfft.aur
 | 
				
			||||||
 | 
					  - name: community.general
 | 
				
			||||||
pacman:
 | 
					pacman:
 | 
				
			||||||
  - ansible
 | 
					  - ansible
 | 
				
			||||||
  - python-passlib
 | 
					  - python-passlib
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										37
									
								
								roles/dns-records-cloudflare/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								roles/dns-records-cloudflare/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					# Cloudflare DNS Records
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This Ansible role automates the management of DNS A-records in Cloudflare zones. It uses the [community.general.cloudflare_dns](https://docs.ansible.com/ansible/latest/collections/community/general/cloudflare_dns_module.html) module to create or update A-records for a list of domains, automatically detects the correct zone for each record, and supports configurable proxy settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Overview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Looping over a provided list of domains (`cloudflare_domains`), this role:
 | 
				
			||||||
 | 
					- Determines the zone name by extracting the last two labels of each domain.
 | 
				
			||||||
 | 
					- Ensures an A-record for each domain points to the specified IP (`cloudflare_target_ip`).
 | 
				
			||||||
 | 
					- Honors the `proxied` flag to switch between DNS-only and Cloudflare-proxied modes.
 | 
				
			||||||
 | 
					- Provides an optional debug task (`enable_debug`) to output the domain list before changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ideal for environments where bulk or dynamic DNS updates are needed, this role abstracts away the complexity of Cloudflare’s zone and record API.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Purpose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Cloudflare DNS Records delivers an idempotent, scalable solution for managing A-records across multiple Cloudflare zones. Whether you need to onboard hundreds of domains or toggle proxy settings in CI/CD pipelines, this role handles the orchestration and ensures consistency.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **Automatic Zone Detection:** Parses each domain to derive its zone (`example.com`) without manual intervention.  
 | 
				
			||||||
 | 
					- **Bulk Record Management:** Creates or updates A-records for all entries in `cloudflare_domains`.  
 | 
				
			||||||
 | 
					- **Proxy Toggle:** Configure `proxied: true` or `false` per record to switch between DNS-only and proxied modes.  
 | 
				
			||||||
 | 
					- **Debug Support:** Enable `enable_debug` to print the domain list for validation before execution.  
 | 
				
			||||||
 | 
					- **Flexible Authentication:** Supports both API token (`api_token`) and Global API key + email.  
 | 
				
			||||||
 | 
					- **Low-TTL Option:** Use `ttl: 1` for rapid DNS propagation during dynamic updates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Author
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Kevin Veen-Birkenbach
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CyMaIS NonCommercial License (CNCL)  
 | 
				
			||||||
 | 
					<https://s.veen.world/cncl>
 | 
				
			||||||
							
								
								
									
										26
									
								
								roles/dns-records-cloudflare/meta/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								roles/dns-records-cloudflare/meta/main.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					galaxy_info:
 | 
				
			||||||
 | 
					  author: "Kevin Veen-Birkenbach"
 | 
				
			||||||
 | 
					  description: "Manages DNS A-records in Cloudflare zones."
 | 
				
			||||||
 | 
					  license: "CyMaIS NonCommercial License (CNCL)"
 | 
				
			||||||
 | 
					  license_url: "https://s.veen.world/cncl"
 | 
				
			||||||
 | 
					  company: |
 | 
				
			||||||
 | 
					    Kevin Veen-Birkenbach
 | 
				
			||||||
 | 
					    Consulting & Coaching Solutions
 | 
				
			||||||
 | 
					    https://www.veen.world
 | 
				
			||||||
 | 
					  min_ansible_version: "2.9"
 | 
				
			||||||
 | 
					  platforms:
 | 
				
			||||||
 | 
					    - name: "All"
 | 
				
			||||||
 | 
					      versions:
 | 
				
			||||||
 | 
					        - "all"
 | 
				
			||||||
 | 
					  galaxy_tags:
 | 
				
			||||||
 | 
					    - "cloudflare"
 | 
				
			||||||
 | 
					    - "dns"
 | 
				
			||||||
 | 
					    - "records"
 | 
				
			||||||
 | 
					    - "ansible"
 | 
				
			||||||
 | 
					    - "network"
 | 
				
			||||||
 | 
					    - "automation"
 | 
				
			||||||
 | 
					  repository: "https://s.veen.world/cymais"
 | 
				
			||||||
 | 
					  issue_tracker_url: "https://s.veen.world/cymaisissues"
 | 
				
			||||||
 | 
					  documentation: "https://s.veen.world/cymais"
 | 
				
			||||||
 | 
					dependencies: []
 | 
				
			||||||
							
								
								
									
										18
									
								
								roles/dns-records-cloudflare/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								roles/dns-records-cloudflare/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					- name: "Debug: cloudflare_domains"
 | 
				
			||||||
 | 
					  debug:
 | 
				
			||||||
 | 
					    var: cloudflare_domains
 | 
				
			||||||
 | 
					  when: enable_debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name: Create or update Cloudflare A-record for {{ item }}
 | 
				
			||||||
 | 
					  community.general.cloudflare_dns:
 | 
				
			||||||
 | 
					    api_token: "{{ cloudflare_api_token }}"
 | 
				
			||||||
 | 
					    zone: "{{ item.split('.')[-2:] | join('.') }}"
 | 
				
			||||||
 | 
					    state: present
 | 
				
			||||||
 | 
					    type: A
 | 
				
			||||||
 | 
					    name: "{{ item }}"
 | 
				
			||||||
 | 
					    content: "{{ cloudflare_target_ip }}"
 | 
				
			||||||
 | 
					    ttl: 1
 | 
				
			||||||
 | 
					    proxied: "{{ cloudflare_proxied | int }}"
 | 
				
			||||||
 | 
					  loop: "{{ cloudflare_domains }}"
 | 
				
			||||||
 | 
					  loop_control:
 | 
				
			||||||
 | 
					    label: "{{ item }}"
 | 
				
			||||||
@@ -28,4 +28,4 @@ galaxy_info:
 | 
				
			|||||||
  issue_tracker_url: https://s.veen.world/cymaisissues
 | 
					  issue_tracker_url: https://s.veen.world/cymaisissues
 | 
				
			||||||
  documentation: https://s.veen.world/cymais
 | 
					  documentation: https://s.veen.world/cymais
 | 
				
			||||||
  logo:
 | 
					  logo:
 | 
				
			||||||
    class: "fa-solid fa-chalkboard-teacher"
 | 
					    class: "fa-solid fa-chalkboard-teacher"
 | 
				
			||||||
@@ -1,12 +1,21 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
- name: "include role nginx-domain-setup for {{application_id}}"
 | 
					# Docker Central Database Role can't be used here
 | 
				
			||||||
  include_role:
 | 
					 | 
				
			||||||
    name: nginx-domain-setup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: "include docker-compose role"
 | 
					- name: "include docker-compose role"
 | 
				
			||||||
  include_role: 
 | 
					  include_role: 
 | 
				
			||||||
    name: docker-compose
 | 
					    name: docker-compose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name:                 "Seed BigBlueButton Database for Backup"
 | 
				
			||||||
 | 
					  include_tasks:        "{{ playbook_dir }}/roles/backup-docker-to-local/tasks/seed-database-to-backup.yml"
 | 
				
			||||||
 | 
					  vars:
 | 
				
			||||||
 | 
					    database_instance:  "{{ application_id }}"
 | 
				
			||||||
 | 
					    database_password:  "{{ applications[application_id].credentials.postgresql_secret }}"
 | 
				
			||||||
 | 
					    database_username:  "postgres"
 | 
				
			||||||
 | 
					    database_name:      ""                              # Multiple databases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name: "include role nginx-domain-setup for {{application_id}}"
 | 
				
			||||||
 | 
					  include_role:
 | 
				
			||||||
 | 
					    name: nginx-domain-setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- name: pull docker repository
 | 
					- name: pull docker repository
 | 
				
			||||||
  git:
 | 
					  git:
 | 
				
			||||||
    repo: "https://github.com/bigbluebutton/docker.git"
 | 
					    repo: "https://github.com/bigbluebutton/docker.git"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
ENABLE_COTURN=true
 | 
					ENABLE_COTURN=true
 | 
				
			||||||
COTURN_TLS_CERT_PATH={{ certbot_cert_path }}/{{ ssl_cert_folder }}/fullchain.pem
 | 
					COTURN_TLS_CERT_PATH={{ certbot_cert_path }}/{{ ssl_cert_folder }}/fullchain.pem
 | 
				
			||||||
COTURN_TLS_KEY_PATH={{ certbot_cert_path }}/{{ ssl_cert_folder }}/privkey.pem
 | 
					COTURN_TLS_KEY_PATH={{ certbot_cert_path }}/{{ ssl_cert_folder }}/privkey.pem
 | 
				
			||||||
ENABLE_GREENLIGHT={{applications.bigbluebutton.enable_greenlight}}
 | 
					ENABLE_GREENLIGHT={{applications[application_id].enable_greenlight}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Enable Webhooks
 | 
					# Enable Webhooks
 | 
				
			||||||
# used by some integrations
 | 
					# used by some integrations
 | 
				
			||||||
@@ -27,13 +27,11 @@ RECORDING_MAX_AGE_DAYS=365
 | 
				
			|||||||
# SECRETS
 | 
					# SECRETS
 | 
				
			||||||
# ====================================
 | 
					# ====================================
 | 
				
			||||||
# important! change these to any random values
 | 
					# important! change these to any random values
 | 
				
			||||||
SHARED_SECRET={{applications.bigbluebutton.credentials.shared_secret}}
 | 
					SHARED_SECRET={{applications[application_id].credentials.shared_secret}}
 | 
				
			||||||
ETHERPAD_API_KEY={{applications.bigbluebutton.credentials.etherpad_api_key}}
 | 
					ETHERPAD_API_KEY={{applications[application_id].credentials.etherpad_api_key}}
 | 
				
			||||||
RAILS_SECRET={{applications.bigbluebutton.credentials.rails_secret}}
 | 
					RAILS_SECRET={{applications[application_id].credentials.rails_secret}}
 | 
				
			||||||
POSTGRESQL_SECRET={{applications.bigbluebutton.credentials.postgresql_secret}}
 | 
					POSTGRESQL_SECRET={{applications[application_id].credentials.postgresql_secret}}
 | 
				
			||||||
FSESL_PASSWORD={{applications.bigbluebutton.credentials.fsesl_password}}
 | 
					FSESL_PASSWORD={{applications[application_id].credentials.fsesl_password}}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ====================================
 | 
					# ====================================
 | 
				
			||||||
# CONNECTION
 | 
					# CONNECTION
 | 
				
			||||||
@@ -53,7 +51,7 @@ STUN_PORT={{ ports.public.stun[application_id] }}
 | 
				
			|||||||
# TURN SERVER
 | 
					# TURN SERVER
 | 
				
			||||||
# uncomment and adjust following two lines to add an external TURN server
 | 
					# uncomment and adjust following two lines to add an external TURN server
 | 
				
			||||||
TURN_SERVER=turns:{{domains[application_id]}}:{{ ports.public.turn[application_id] }}?transport=tcp
 | 
					TURN_SERVER=turns:{{domains[application_id]}}:{{ ports.public.turn[application_id] }}?transport=tcp
 | 
				
			||||||
TURN_SECRET={{applications.bigbluebutton.credentials.turn_secret}}
 | 
					TURN_SECRET={{applications[application_id].credentials.turn_secret}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Allowed SIP IPs
 | 
					# Allowed SIP IPs
 | 
				
			||||||
# due to high traffic caused by bots, by default the SIP port is blocked.
 | 
					# due to high traffic caused by bots, by default the SIP port is blocked.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,13 @@
 | 
				
			|||||||
application_id:               "bigbluebutton"
 | 
					application_id:                 "bigbluebutton"
 | 
				
			||||||
bbb_repository_directory:     "{{ docker_compose.directories.services }}"
 | 
					bbb_repository_directory:       "{{ docker_compose.directories.services }}"
 | 
				
			||||||
docker_compose_file_origine:  "{{ docker_compose.directories.services }}docker-compose.yml"
 | 
					docker_compose_file_origine:    "{{ docker_compose.directories.services }}docker-compose.yml"
 | 
				
			||||||
docker_compose_file_final:    "{{ docker_compose.directories.instance }}docker-compose.yml"
 | 
					docker_compose_file_final:      "{{ docker_compose.directories.instance }}docker-compose.yml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Database configuration
 | 
					# Database configuration
 | 
				
			||||||
database_instance:            "bigbluebutton"
 | 
					database_type:                  "postgres"
 | 
				
			||||||
database_name:                "multiple_databases"
 | 
					database_password:              "{{ applications.bigbluebutton.credentials.postgresql_secret }}"
 | 
				
			||||||
database_username:            "postgres"
 | 
					 | 
				
			||||||
database_password:            "{{ applications.bigbluebutton.credentials.postgresql_secret }}"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
domain: 	                    "{{ domains[application_id] }}"
 | 
					domain: 	                      "{{ domains[application_id] }}"
 | 
				
			||||||
http_port: 	                  "{{ ports.localhost.http[application_id] }}"
 | 
					http_port: 	                    "{{ ports.localhost.http[application_id] }}"
 | 
				
			||||||
bbb_env_file_link:            "{{ docker_compose.directories.instance }}.env"    
 | 
					bbb_env_file_link:              "{{ docker_compose.directories.instance }}.env"    
 | 
				
			||||||
bbb_env_file_origine:         "{{ bbb_repository_directory }}.env"
 | 
					bbb_env_file_origine:           "{{ bbb_repository_directory }}.env"
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
database_instance:  "{{ 'central-' + database_type if applications[application_id].features.database | bool else application_id }}"
 | 
					database_instance:  "{{ 'central-' + database_type if applications[application_id].features.database | bool else application_id }}"
 | 
				
			||||||
database_host:      "{{ 'central-' + database_type if applications[application_id].features.database | bool else 'database' }}"
 | 
					database_host:      "{{ 'central-' + database_type if applications[application_id].features.database | bool else 'database' }}"
 | 
				
			||||||
database_name:      "{{ application_id }}"
 | 
					database_name:      "{{ applications[application_id].credentials.database.name | default( application_id ) }}"      # The overwritte configuration is needed by bigbluebutton
 | 
				
			||||||
database_username:  "{{ application_id }}"
 | 
					database_username:  "{{ applications[application_id].credentials.database.username | default( application_id )}}"   # The overwritte configuration is needed by bigbluebutton
 | 
				
			||||||
database_port:      "{{ 3306 if database_type == 'mariadb' else 5432 }}"
 | 
					database_port:      "{{ 3306 if database_type == 'mariadb' else 5432 }}"
 | 
				
			||||||
database_env:       "{{docker_compose.directories.env}}{{database_type}}.env"
 | 
					database_env:       "{{docker_compose.directories.env}}{{database_type}}.env"
 | 
				
			||||||
database_url_jdbc:  "jdbc:{{ database_type if database_type == 'mariadb' else 'postgresql' }}://{{ database_host }}:{{ database_port }}/{{ database_name }}"
 | 
					database_url_jdbc:  "jdbc:{{ database_type if database_type == 'mariadb' else 'postgresql' }}://{{ database_host }}:{{ database_port }}/{{ database_name }}"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,6 @@
 | 
				
			|||||||
server {
 | 
					server {
 | 
				
			||||||
    listen {{ports.public.ldaps.ldap}}ssl;
 | 
					    listen {{ports.public.ldaps.ldap}}ssl;
 | 
				
			||||||
    proxy_pass 127.0.0.1:{{ports.localhost.ldap.ldap}};
 | 
					    proxy_pass 127.0.0.1:{{ports.localhost.ldap.ldap}};
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    # SSL Configuration for LDAPS
 | 
					 | 
				
			||||||
    {% include 'roles/letsencrypt/templates/ssl_credentials.j2' %}
 | 
					    {% include 'roles/letsencrypt/templates/ssl_credentials.j2' %}
 | 
				
			||||||
    ssl_protocols TLSv1.2 TLSv1.3;
 | 
					 | 
				
			||||||
    ssl_ciphers HIGH:!aNULL:!MD5;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,15 @@
 | 
				
			|||||||
listen 443 ssl;
 | 
					listen 443 ssl http2;
 | 
				
			||||||
listen [::]:443 ssl;
 | 
					listen [::]:443 ssl http2;
 | 
				
			||||||
http2 on;
 | 
					
 | 
				
			||||||
 | 
					ssl_protocols TLSv1.2 TLSv1.3;
 | 
				
			||||||
 | 
					ssl_ecdh_curve X25519:P-256;
 | 
				
			||||||
 | 
					ssl_prefer_server_ciphers on;
 | 
				
			||||||
 | 
					ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ssl_session_timeout 1d;
 | 
					ssl_session_timeout 1d;
 | 
				
			||||||
ssl_session_cache shared:SSL:50m;
 | 
					ssl_session_cache shared:SSL:50m;
 | 
				
			||||||
ssl_session_tickets on;
 | 
					ssl_session_tickets on;
 | 
				
			||||||
ssl_prefer_server_ciphers on;
 | 
					 | 
				
			||||||
add_header Strict-Transport-Security max-age=15768000;
 | 
					add_header Strict-Transport-Security max-age=15768000;
 | 
				
			||||||
ssl_stapling on;
 | 
					ssl_stapling on;
 | 
				
			||||||
ssl_stapling_verify on;
 | 
					ssl_stapling_verify on;
 | 
				
			||||||
{% include 'roles/letsencrypt/templates/ssl_credentials.j2' %}
 | 
					{% include 'roles/letsencrypt/templates/ssl_credentials.j2' %}
 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1,52 +1,50 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
- name: "Remove Nginx configuration for deprecated domains"
 | 
					- name: Include task to remove deprecated nginx configs
 | 
				
			||||||
  ansible.builtin.command:
 | 
					  include_tasks: remove_deprecated_nginx_configs.yml
 | 
				
			||||||
    cmd: >-
 | 
					 | 
				
			||||||
      rm -fv /etc/nginx/conf.d/http/servers/*.{{ item }}.conf;
 | 
					 | 
				
			||||||
      rm -fv /etc/nginx/conf.d/http/servers/{{ item }}.conf
 | 
					 | 
				
			||||||
  loop: "{{ deprecated_domains }}"
 | 
					  loop: "{{ deprecated_domains }}"
 | 
				
			||||||
  loop_control:
 | 
					  loop_control:
 | 
				
			||||||
    label: "{{ item }}"
 | 
					    label: "{{ item }}"
 | 
				
			||||||
  notify: restart nginx
 | 
					  vars:
 | 
				
			||||||
 | 
					    domain: "{{ item }}"
 | 
				
			||||||
  when:
 | 
					  when:
 | 
				
			||||||
    - mode_cleanup | bool
 | 
					    - mode_cleanup | bool
 | 
				
			||||||
    - run_once_nginx_domains_cleanup is not defined
 | 
					    - run_once_nginx_domains_cleanup is not defined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The revoking just works for the base domain
 | 
					## The revoking just works for the base domain
 | 
				
			||||||
- name: "Revoke Certbot certificate for {{ item }}"
 | 
					#- name: "Revoke Certbot certificate for {{ item }}"
 | 
				
			||||||
  ansible.builtin.command:
 | 
					#  ansible.builtin.command:
 | 
				
			||||||
    cmd: "certbot revoke -n --cert-name {{ item }}"
 | 
					#    cmd: "certbot revoke -n --cert-name {{ item }} --non-interactive"
 | 
				
			||||||
  become: true
 | 
					#  become: true
 | 
				
			||||||
  loop: "{{ deprecated_domains }}"
 | 
					#  loop: "{{ deprecated_domains }}"
 | 
				
			||||||
  loop_control:
 | 
					#  loop_control:
 | 
				
			||||||
    label: "{{ item }}"
 | 
					#    label: "{{ item }}"
 | 
				
			||||||
  when:
 | 
					#  when:
 | 
				
			||||||
    - mode_cleanup | bool
 | 
					#    - mode_cleanup | bool
 | 
				
			||||||
    - run_once_nginx_domains_cleanup is not defined
 | 
					#    - run_once_nginx_domains_cleanup is not defined
 | 
				
			||||||
  register: certbot_revoke_result
 | 
					#  register: certbot_revoke_result
 | 
				
			||||||
  failed_when: >
 | 
					#  failed_when: >
 | 
				
			||||||
    certbot_revoke_result.rc != 0 and
 | 
					#    certbot_revoke_result.rc != 0 and
 | 
				
			||||||
    'No certificate found with name' not in certbot_revoke_result.stderr
 | 
					#    'No certificate found with name' not in certbot_revoke_result.stderr
 | 
				
			||||||
  changed_when: >
 | 
					#  changed_when: >
 | 
				
			||||||
    certbot_revoke_result.rc == 0
 | 
					#    certbot_revoke_result.rc == 0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
# The deleting just works for the base domain
 | 
					## The deleting just works for the base domain
 | 
				
			||||||
- name: "Delete Certbot certificate for {{ item }}"
 | 
					#- name: "Delete Certbot certificate for {{ item }}"
 | 
				
			||||||
  ansible.builtin.command:
 | 
					#  ansible.builtin.command:
 | 
				
			||||||
    cmd: "certbot delete -n --cert-name {{ item }}"
 | 
					#    cmd: "certbot delete -n --cert-name {{ item }} --non-interactive"
 | 
				
			||||||
  become: true
 | 
					#  become: true
 | 
				
			||||||
  loop: "{{ deprecated_domains }}"
 | 
					#  loop: "{{ deprecated_domains }}"
 | 
				
			||||||
  loop_control:
 | 
					#  loop_control:
 | 
				
			||||||
    label: "{{ item }}"
 | 
					#    label: "{{ item }}"
 | 
				
			||||||
  when:
 | 
					#  when:
 | 
				
			||||||
    - mode_cleanup | bool
 | 
					#    - mode_cleanup | bool
 | 
				
			||||||
    - run_once_nginx_domains_cleanup is not defined
 | 
					#    - run_once_nginx_domains_cleanup is not defined
 | 
				
			||||||
  register: certbot_delete_result
 | 
					#  register: certbot_delete_result
 | 
				
			||||||
  failed_when: >
 | 
					#  failed_when: >
 | 
				
			||||||
    certbot_delete_result.rc != 0 and
 | 
					#    certbot_delete_result.rc != 0 and
 | 
				
			||||||
    'No certificate found with name' not in certbot_delete_result.stderr
 | 
					#    'No certificate found with name' not in certbot_delete_result.stderr
 | 
				
			||||||
  changed_when: >
 | 
					#  changed_when: >
 | 
				
			||||||
    certbot_delete_result.rc == 0
 | 
					#    certbot_delete_result.rc == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- name: run the nginx_domains_cleanup role once
 | 
					- name: run the nginx_domains_cleanup role once
 | 
				
			||||||
  set_fact:
 | 
					  set_fact:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					- name: Find matching nginx configs for {{ domain }}
 | 
				
			||||||
 | 
					  ansible.builtin.find:
 | 
				
			||||||
 | 
					    paths: /etc/nginx/conf.d/http/servers
 | 
				
			||||||
 | 
					    patterns: "*.{{ domain }}.conf"
 | 
				
			||||||
 | 
					  register: find_result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name: Remove wildcard nginx configs for {{ domain }}
 | 
				
			||||||
 | 
					  ansible.builtin.file:
 | 
				
			||||||
 | 
					    path: "{{ item.path }}"
 | 
				
			||||||
 | 
					    state: absent
 | 
				
			||||||
 | 
					  loop: "{{ find_result.files | default([]) }}"
 | 
				
			||||||
 | 
					  when: item is defined
 | 
				
			||||||
 | 
					  notify: restart nginx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name: Remove exact nginx config for {{ domain }}
 | 
				
			||||||
 | 
					  ansible.builtin.file:
 | 
				
			||||||
 | 
					    path: "/etc/nginx/conf.d/http/servers/{{ domain }}.conf"
 | 
				
			||||||
 | 
					    state: absent
 | 
				
			||||||
 | 
					  notify: restart nginx
 | 
				
			||||||
@@ -1,3 +1,9 @@
 | 
				
			|||||||
 | 
					- name: "Check if certificate already exists for {{ domain }}"
 | 
				
			||||||
 | 
					  cert_check_exists:
 | 
				
			||||||
 | 
					    domain: "{{ domain }}"
 | 
				
			||||||
 | 
					    cert_base_path: "{{ certbot_cert_path }}"
 | 
				
			||||||
 | 
					  register: cert_check
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- name: "receive certificate for {{ domain }}"
 | 
					- name: "receive certificate for {{ domain }}"
 | 
				
			||||||
  command: >-
 | 
					  command: >-
 | 
				
			||||||
    certbot certonly 
 | 
					    certbot certonly 
 | 
				
			||||||
@@ -21,3 +27,4 @@
 | 
				
			|||||||
    {{ '--test-cert' if mode_test | bool else '' }}
 | 
					    {{ '--test-cert' if mode_test | bool else '' }}
 | 
				
			||||||
  register: certbot_result
 | 
					  register: certbot_result
 | 
				
			||||||
  changed_when: "'Certificate not yet due for renewal' not in certbot_result.stdout"
 | 
					  changed_when: "'Certificate not yet due for renewal' not in certbot_result.stdout"
 | 
				
			||||||
 | 
					  when: not cert_check.exists
 | 
				
			||||||
@@ -11,6 +11,7 @@
 | 
				
			|||||||
    --domains "{{ all_domains | join(',') }}"
 | 
					    --domains "{{ all_domains | join(',') }}"
 | 
				
			||||||
    --certbot-email "{{ users.administrator.email }}"
 | 
					    --certbot-email "{{ users.administrator.email }}"
 | 
				
			||||||
    --certbot-acme-challenge-method "{{ certbot_acme_challenge_method }}"
 | 
					    --certbot-acme-challenge-method "{{ certbot_acme_challenge_method }}"
 | 
				
			||||||
 | 
					    --chunk-size 100
 | 
				
			||||||
    {% if certbot_acme_challenge_method != 'webroot' %}
 | 
					    {% if certbot_acme_challenge_method != 'webroot' %}
 | 
				
			||||||
    --certbot-credentials-file "{{ certbot_credentials_file }}"
 | 
					    --certbot-credentials-file "{{ certbot_credentials_file }}"
 | 
				
			||||||
    --certbot-dns-propagation-seconds "{{ certbot_dns_propagation_wait_seconds }}"
 | 
					    --certbot-dns-propagation-seconds "{{ certbot_dns_propagation_wait_seconds }}"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,24 @@
 | 
				
			|||||||
- name: "Include flavor"
 | 
					- name: "Include flavor"
 | 
				
			||||||
  include_tasks: "{{ role_path }}/tasks/flavors/{{ certbot_flavor }}.yml"
 | 
					  include_tasks: "{{ role_path }}/tasks/flavors/{{ certbot_flavor }}.yml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- name: "Cleanup dedicated cert for {{ domain }}"
 | 
					#- name: "Cleanup dedicated cert for {{ domain }}"
 | 
				
			||||||
  command: >-
 | 
					#  command: >-
 | 
				
			||||||
    certbot delete --cert-name {{ domain }} --non-interactive
 | 
					#    certbot delete --cert-name {{ domain }} --non-interactive
 | 
				
			||||||
  when: 
 | 
					#  when: 
 | 
				
			||||||
    - mode_cleanup | bool
 | 
					#    - mode_cleanup | bool
 | 
				
			||||||
      # Cleanup mode is enabled
 | 
					#      # Cleanup mode is enabled
 | 
				
			||||||
    - certbot_flavor != 'dedicated'
 | 
					#    - certbot_flavor != 'dedicated'
 | 
				
			||||||
      # Wildcard certificate is enabled
 | 
					#      # Wildcard certificate is enabled
 | 
				
			||||||
    - domain.split('.') | length == (primary_domain.split('.') | length + 1) and domain.endswith(primary_domain)
 | 
					#    - domain.split('.') | length == (primary_domain.split('.') | length + 1) and domain.endswith(primary_domain)
 | 
				
			||||||
      # AND: The domain is a direct first-level subdomain of the primary domain
 | 
					#      # AND: The domain is a direct first-level subdomain of the primary domain
 | 
				
			||||||
    - domain != primary_domain  
 | 
					#    - domain != primary_domain  
 | 
				
			||||||
      # The domain is not the primary domain
 | 
					#      # The domain is not the primary domain
 | 
				
			||||||
  register: certbot_result
 | 
					#  register: certbot_result
 | 
				
			||||||
  failed_when: certbot_result.rc != 0 and ("No certificate found with name" not in certbot_result.stderr)
 | 
					#  failed_when: certbot_result.rc != 0 and ("No certificate found with name" not in certbot_result.stderr)
 | 
				
			||||||
  changed_when: certbot_result.rc == 0 and ("No certificate found with name" not in certbot_result.stderr)
 | 
					#  changed_when: certbot_result.rc == 0 and ("No certificate found with name" not in certbot_result.stderr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- name: Find SSL cert folder for domain
 | 
					- name: Find SSL cert folder for domain
 | 
				
			||||||
  find_cert_folder:
 | 
					  cert_folder_find:
 | 
				
			||||||
    domain: "{{ domain }}"
 | 
					    domain: "{{ domain }}"
 | 
				
			||||||
    certbot_flavor: "{{ certbot_flavor }}"
 | 
					    certbot_flavor: "{{ certbot_flavor }}"
 | 
				
			||||||
    cert_base_path: "{{ certbot_cert_path }}"
 | 
					    cert_base_path: "{{ certbot_cert_path }}"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +1,31 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
# 1. Filter all domains with the “www.” prefix
 | 
					- name: "Debug: all_domains"
 | 
				
			||||||
 | 
					  debug:
 | 
				
			||||||
 | 
					    var: all_domains
 | 
				
			||||||
 | 
					  when: enable_debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- name: Filter www-prefixed domains from all_domains
 | 
					- name: Filter www-prefixed domains from all_domains
 | 
				
			||||||
  set_fact:
 | 
					  set_fact:
 | 
				
			||||||
    www_domains: "{{ all_domains | select('match', '^www\\.') | list }}"
 | 
					    www_domains: "{{ all_domains | select('match', '^www\\.') | list }}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 2. Build redirect mappings (www.domain → domain)
 | 
					 | 
				
			||||||
- name: Build redirect mappings for www domains
 | 
					 | 
				
			||||||
  set_fact:
 | 
					 | 
				
			||||||
    domain_mappings: >-
 | 
					 | 
				
			||||||
      {{ www_domains
 | 
					 | 
				
			||||||
         | map('regex_replace', '^www\\.(.+)$', '{ source: \"www.\\1\", target: \"\\1\" }')
 | 
					 | 
				
			||||||
         | map('from_yaml')
 | 
					 | 
				
			||||||
         | list
 | 
					 | 
				
			||||||
      }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# 3. Include the nginx-redirect-domain role to apply these mappings
 | 
					 | 
				
			||||||
- name: Include nginx-redirect-domain role for www-to-bare redirects
 | 
					- name: Include nginx-redirect-domain role for www-to-bare redirects
 | 
				
			||||||
  include_role:
 | 
					  include_role:
 | 
				
			||||||
    name: nginx-redirect-domain
 | 
					    name: nginx-redirect-domain
 | 
				
			||||||
  vars:
 | 
					  vars:
 | 
				
			||||||
    domain_mappings: "{{ domain_mappings }}"
 | 
					    domain_mappings: "{{ www_domains
 | 
				
			||||||
  when: certbot_flavor == 'dedicated'
 | 
					       | map('regex_replace',
 | 
				
			||||||
 | 
					             '^www\\.(.+)$',
 | 
				
			||||||
 | 
					             '{ source: \"www.\\1\", target: \"\\1\" }')
 | 
				
			||||||
 | 
					       | map('from_yaml')
 | 
				
			||||||
 | 
					       | list
 | 
				
			||||||
 | 
					    }}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- name: Include DNS role to set redirects
 | 
				
			||||||
 | 
					  include_role:
 | 
				
			||||||
 | 
					    name: dns-records-cloudflare
 | 
				
			||||||
 | 
					  vars:
 | 
				
			||||||
 | 
					    cloudflare_api_token:     "{{ certbot_dns_api_token }}"
 | 
				
			||||||
 | 
					    cloudflare_domains:       "{{ www_domains }}"
 | 
				
			||||||
 | 
					    cloudflare_target_ip:     "{{ networks.internet.ip4 }}"
 | 
				
			||||||
 | 
					    cloudflare_proxied:       false
 | 
				
			||||||
 | 
					  when: dns_provider == 'cloudflare'
 | 
				
			||||||
@@ -62,22 +62,28 @@
 | 
				
			|||||||
    set_fact:
 | 
					    set_fact:
 | 
				
			||||||
      service_provider: "{{ defaults_service_provider | combine(service_provider | default({}, true), recursive=True) }}"
 | 
					      service_provider: "{{ defaults_service_provider | combine(service_provider | default({}, true), recursive=True) }}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: Collect all domains (domains, redirect sources + www)
 | 
					  - name: Gather base domains (without www)
 | 
				
			||||||
    set_fact:
 | 
					    set_fact:
 | 
				
			||||||
      all_domains: >-
 | 
					      base_domains: >-
 | 
				
			||||||
        {{
 | 
					        {{
 | 
				
			||||||
          (
 | 
					          domains.values()
 | 
				
			||||||
            (
 | 
					          | flatten
 | 
				
			||||||
              domains.values() | flatten
 | 
					          + (redirect_domain_mappings | map(attribute='source') | list)
 | 
				
			||||||
              + (redirect_domain_mappings | map(attribute='source') | list)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            + (
 | 
					 | 
				
			||||||
              domains.values() | flatten
 | 
					 | 
				
			||||||
              + (redirect_domain_mappings | map(attribute='source') | list)
 | 
					 | 
				
			||||||
            ) | map('regex_replace', '^(.*)$', 'www.\\1') | list
 | 
					 | 
				
			||||||
          ) | unique | sort
 | 
					 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: Initialise all_domains as empty list
 | 
				
			||||||
 | 
					    set_fact:
 | 
				
			||||||
 | 
					      all_domains: []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: Build all_domains with base + www via loop
 | 
				
			||||||
 | 
					    set_fact:
 | 
				
			||||||
 | 
					      all_domains: "{{ all_domains + [ item, 'www.' ~ item ] }}"
 | 
				
			||||||
 | 
					    loop: "{{ base_domains }}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: Deduplicate and sort all_domains
 | 
				
			||||||
 | 
					    set_fact:
 | 
				
			||||||
 | 
					      all_domains: "{{ all_domains | unique | sort }}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: "Merged Variables"
 | 
					  - name: "Merged Variables"
 | 
				
			||||||
    # Add new merged variables here
 | 
					    # Add new merged variables here
 | 
				
			||||||
    debug:
 | 
					    debug:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,9 +8,8 @@
 | 
				
			|||||||
    name: cleanup-docker-anonymous-volumes
 | 
					    name: cleanup-docker-anonymous-volumes
 | 
				
			||||||
  when: mode_cleanup | bool
 | 
					  when: mode_cleanup | bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- name: "Show User Configuration (Important when mailu tokens are created automatic)"
 | 
					- name: Show all facts
 | 
				
			||||||
  debug:
 | 
					  debug:
 | 
				
			||||||
    msg:
 | 
					    var: ansible_facts
 | 
				
			||||||
      users: "{{users}}"
 | 
					 | 
				
			||||||
  when: enable_debug | bool
 | 
					  when: enable_debug | bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,7 +66,7 @@ defaults_applications:
 | 
				
			|||||||
  'iframe':   true,
 | 
					  'iframe':   true,
 | 
				
			||||||
  'ldap':     false,
 | 
					  'ldap':     false,
 | 
				
			||||||
  'oidc':     true,
 | 
					  'oidc':     true,
 | 
				
			||||||
  'database': true,
 | 
					  'database': false,
 | 
				
			||||||
}) }}{% raw %}
 | 
					}) }}{% raw %}
 | 
				
			||||||
    credentials:
 | 
					    credentials:
 | 
				
			||||||
#      shared_secret:                                                       # Needs to be defined in inventory file
 | 
					#      shared_secret:                                                       # Needs to be defined in inventory file
 | 
				
			||||||
@@ -75,6 +75,9 @@ defaults_applications:
 | 
				
			|||||||
#      postgresql_secret:                                                   # Needs to be defined in inventory file   
 | 
					#      postgresql_secret:                                                   # Needs to be defined in inventory file   
 | 
				
			||||||
#      fsesl_password:                                                      # Needs to be defined in inventory file
 | 
					#      fsesl_password:                                                      # Needs to be defined in inventory file
 | 
				
			||||||
#      turn_secret:                                                         # Needs to be defined in inventory file  
 | 
					#      turn_secret:                                                         # Needs to be defined in inventory file  
 | 
				
			||||||
 | 
					      database:
 | 
				
			||||||
 | 
					        name:      "multiple_databases"
 | 
				
			||||||
 | 
					        username:  "postgres2"
 | 
				
			||||||
    urls:
 | 
					    urls:
 | 
				
			||||||
      api:                        "{{ web_protocol }}://{{domains.bigbluebutton}}/bigbluebutton/"  # API Address used by Nextcloud Integration
 | 
					      api:                        "{{ web_protocol }}://{{domains.bigbluebutton}}/bigbluebutton/"  # API Address used by Nextcloud Integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user