mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-08 11:17:17 +02:00
Compare commits
16 Commits
4fa1c6cfbd
...
7f42462514
Author | SHA1 | Date | |
---|---|---|---|
7f42462514 | |||
41cd6b7702 | |||
a40d48bb03 | |||
2fba32d384 | |||
f2a765d69a | |||
c729edb525 | |||
597e9d5222 | |||
db0e030900 | |||
004507e233 | |||
e2014b9b59 | |||
567b1365c0 | |||
e99fa77b91 | |||
80dad1a5ed | |||
03290eafe1 | |||
58c64bd7c6 | |||
e497c001d6 |
@@ -189,7 +189,7 @@ def parse_args():
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
primary_domain = '{{ primary_domain }}'
|
||||
primary_domain = '{{ PRIMARY_DOMAIN }}'
|
||||
become_pwd = '{{ lookup("password", "/dev/null length=42 chars=ascii_letters,digits") }}'
|
||||
|
||||
try:
|
||||
|
@@ -191,13 +191,13 @@ def main():
|
||||
validate_application_ids(args.inventory, args.id)
|
||||
|
||||
modes = {
|
||||
"mode_reset": args.reset,
|
||||
"mode_test": args.test,
|
||||
"mode_update": args.update,
|
||||
"mode_backup": args.backup,
|
||||
"mode_cleanup": args.cleanup,
|
||||
"mode_logs": args.logs,
|
||||
"enable_debug": args.debug,
|
||||
"MODE_RESET": args.reset,
|
||||
"MODE_TEST": args.test,
|
||||
"MODE_UPDATE": args.update,
|
||||
"MODE_BACKUP": args.backup,
|
||||
"MODE_CLEANUP": args.cleanup,
|
||||
"MODE_LOGS": args.logs,
|
||||
"MODE_DEBUG": args.debug,
|
||||
"host_type": args.host_type
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,7 @@ class FilterModule(object):
|
||||
def filters(self):
|
||||
return {'alias_domains_map': self.alias_domains_map}
|
||||
|
||||
def alias_domains_map(self, apps, primary_domain):
|
||||
def alias_domains_map(self, apps, PRIMARY_DOMAIN):
|
||||
"""
|
||||
Build a map of application IDs to their alias domains.
|
||||
|
||||
@@ -42,7 +42,7 @@ class FilterModule(object):
|
||||
domains_cfg = cfg.get('server',{}).get('domains',{})
|
||||
entry = domains_cfg.get('canonical')
|
||||
if entry is None:
|
||||
canonical_map[app_id] = [default_domain(app_id, primary_domain)]
|
||||
canonical_map[app_id] = [default_domain(app_id, PRIMARY_DOMAIN)]
|
||||
elif isinstance(entry, dict):
|
||||
canonical_map[app_id] = list(entry.values())
|
||||
elif isinstance(entry, list):
|
||||
@@ -69,7 +69,7 @@ class FilterModule(object):
|
||||
|
||||
# otherwise, compute aliases
|
||||
aliases = parse_entry(domains_cfg, 'aliases', app_id) or []
|
||||
default = default_domain(app_id, primary_domain)
|
||||
default = default_domain(app_id, PRIMARY_DOMAIN)
|
||||
has_aliases = 'aliases' in domains_cfg
|
||||
has_canon = 'canonical' in domains_cfg
|
||||
|
||||
|
@@ -9,7 +9,7 @@ class FilterModule(object):
|
||||
def filters(self):
|
||||
return {'canonical_domains_map': self.canonical_domains_map}
|
||||
|
||||
def canonical_domains_map(self, apps, primary_domain):
|
||||
def canonical_domains_map(self, apps, PRIMARY_DOMAIN):
|
||||
"""
|
||||
Maps applications to their canonical domains, checking for conflicts
|
||||
and ensuring all domains are valid and unique across applications.
|
||||
@@ -30,7 +30,7 @@ class FilterModule(object):
|
||||
|
||||
domains_cfg = cfg.get('server',{}).get('domains',{})
|
||||
if not domains_cfg or 'canonical' not in domains_cfg:
|
||||
self._add_default_domain(app_id, primary_domain, seen_domains, result)
|
||||
self._add_default_domain(app_id, PRIMARY_DOMAIN, seen_domains, result)
|
||||
continue
|
||||
|
||||
canonical_domains = domains_cfg['canonical']
|
||||
@@ -38,13 +38,13 @@ class FilterModule(object):
|
||||
|
||||
return result
|
||||
|
||||
def _add_default_domain(self, app_id, primary_domain, seen_domains, result):
|
||||
def _add_default_domain(self, app_id, PRIMARY_DOMAIN, seen_domains, result):
|
||||
"""
|
||||
Add the default domain for an application if no canonical domains are defined.
|
||||
Ensures the domain is unique across applications.
|
||||
"""
|
||||
entity_name = get_entity_name(app_id)
|
||||
default_domain = f"{entity_name}.{primary_domain}"
|
||||
default_domain = f"{entity_name}.{PRIMARY_DOMAIN}"
|
||||
if default_domain in seen_domains:
|
||||
raise AnsibleFilterError(
|
||||
f"Domain '{default_domain}' is already configured for "
|
||||
|
@@ -7,7 +7,7 @@ class FilterModule(object):
|
||||
def filters(self):
|
||||
return {'domain_mappings': self.domain_mappings}
|
||||
|
||||
def domain_mappings(self, apps, primary_domain):
|
||||
def domain_mappings(self, apps, PRIMARY_DOMAIN):
|
||||
"""
|
||||
Build a flat list of redirect mappings for all apps:
|
||||
- source: each alias domain
|
||||
@@ -43,7 +43,7 @@ class FilterModule(object):
|
||||
domains_cfg = cfg.get('server',{}).get('domains',{})
|
||||
entry = domains_cfg.get('canonical')
|
||||
if entry is None:
|
||||
canonical_map[app_id] = [default_domain(app_id, primary_domain)]
|
||||
canonical_map[app_id] = [default_domain(app_id, PRIMARY_DOMAIN)]
|
||||
elif isinstance(entry, dict):
|
||||
canonical_map[app_id] = list(entry.values())
|
||||
elif isinstance(entry, list):
|
||||
@@ -61,11 +61,11 @@ class FilterModule(object):
|
||||
alias_map[app_id] = []
|
||||
continue
|
||||
if isinstance(domains_cfg, dict) and not domains_cfg:
|
||||
alias_map[app_id] = [default_domain(app_id, primary_domain)]
|
||||
alias_map[app_id] = [default_domain(app_id, PRIMARY_DOMAIN)]
|
||||
continue
|
||||
|
||||
aliases = parse_entry(domains_cfg, 'aliases', app_id) or []
|
||||
default = default_domain(app_id, primary_domain)
|
||||
default = default_domain(app_id, PRIMARY_DOMAIN)
|
||||
has_aliases = 'aliases' in domains_cfg
|
||||
has_canonical = 'canonical' in domains_cfg
|
||||
|
||||
@@ -84,7 +84,7 @@ class FilterModule(object):
|
||||
mappings = []
|
||||
for app_id, sources in alias_map.items():
|
||||
canon_list = canonical_map.get(app_id, [])
|
||||
target = canon_list[0] if canon_list else default_domain(app_id, primary_domain)
|
||||
target = canon_list[0] if canon_list else default_domain(app_id, PRIMARY_DOMAIN)
|
||||
for src in sources:
|
||||
if src == target:
|
||||
# skip self-redirects
|
||||
|
@@ -19,7 +19,7 @@ class FilterModule(object):
|
||||
Usage in Jinja:
|
||||
{{ redirect_list
|
||||
| add_redirect_if_group('lam',
|
||||
'ldap.' ~ primary_domain,
|
||||
'ldap.' ~ PRIMARY_DOMAIN,
|
||||
domains | get_domain('web-app-lam'),
|
||||
group_names) }}
|
||||
"""
|
||||
|
@@ -1,4 +1,4 @@
|
||||
INFINITO_ENVIRONMENT: "production" # Possible values: production, development
|
||||
ENVIRONMENT: "production" # Possible values: production, development
|
||||
|
||||
# If true, sensitive credentials will be masked or hidden from all Ansible task logs
|
||||
# Recommendet to set to true
|
||||
@@ -19,54 +19,56 @@ HOST_THOUSAND_SEPARATOR: "."
|
||||
HOST_DECIMAL_MARK: ","
|
||||
|
||||
# Deployment mode
|
||||
deployment_mode: "single" # Use single, if you deploy on one server. Use cluster if you setup in cluster mode.
|
||||
DEPLOYMENT_MODE: "single" # Use single, if you deploy on one server. Use cluster if you setup in cluster mode.
|
||||
|
||||
# Web
|
||||
WEB_PROTOCOL: "https" # Web protocol type. Use https or http. If you run local you need to change it to http
|
||||
WEB_PORT: "{{ 443 if WEB_PROTOCOL == 'https' else 80 }}" # Default port web applications will listen to
|
||||
|
||||
## Domain
|
||||
primary_domain_tld: "localhost" # Top Level Domain of the server
|
||||
primary_domain_sld: "infinito" # Second Level Domain of the server
|
||||
primary_domain: "{{primary_domain_sld}}.{{primary_domain_tld}}" # Primary Domain of the server
|
||||
# Domain
|
||||
PRIMARY_DOMAIN: "localhost" # Primary Domain of the server
|
||||
PRIMARY_DOMAIN_tld: "{{ (PRIMARY_DOMAIN == 'localhost') | ternary('localhost', PRIMARY_DOMAIN.split('.')[-1]) }}" # Top Level Domain of the server
|
||||
PRIMARY_DOMAIN_SLD: "{{ (PRIMARY_DOMAIN == 'localhost') | ternary('localhost', PRIMARY_DOMAIN.split('.')[-2]) }}" # Second Level Domain of the server
|
||||
|
||||
# Server Tact Variables
|
||||
|
||||
## Ours in which the server is "awake" (100% working). Rest of the time is reserved for maintanance
|
||||
hours_server_awake: "0..23"
|
||||
HOURS_SERVER_AWAKE: "0..23"
|
||||
|
||||
## Random delay for systemd timers to avoid peak loads.
|
||||
randomized_delay_sec: "5min"
|
||||
RANDOMIZED_DELAY_SEC: "5min"
|
||||
|
||||
# Runtime Variables for Process Control
|
||||
activate_all_timers: false # Activates all timers, independend if the handlers had been triggered
|
||||
ACTIVATE_ALL_TIMERS: false # Activates all timers, independend if the handlers had been triggered
|
||||
|
||||
# This enables debugging in ansible and in the apps
|
||||
# You SHOULD NOT enable this on production servers
|
||||
enable_debug: false
|
||||
|
||||
dns_provider: cloudflare # The DNS Provider\Registrar for the domain
|
||||
DNS_PROVIDER: cloudflare # The DNS Provider\Registrar for the domain
|
||||
|
||||
# Which ACME method to use: webroot, cloudflare, or hetzner
|
||||
certbot_acme_challenge_method: "cloudflare"
|
||||
certbot_credentials_dir: /etc/certbot
|
||||
certbot_credentials_file: "{{ certbot_credentials_dir }}/{{ certbot_acme_challenge_method }}.ini"
|
||||
certbot_dns_api_token: "" # Define in inventory file: More information here: group_vars/all/docs/CLOUDFLARE_API_TOKEN.md
|
||||
certbot_dns_propagation_wait_seconds: 300 # 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), dedicated
|
||||
CERTBOT_ACME_CHALLENGE_METHOD: "cloudflare"
|
||||
CERTBOT_CREDENTIALS_DIR: /etc/certbot
|
||||
CERTBOT_CREDENTIALS_FILE: "{{ CERTBOT_CREDENTIALS_DIR }}/{{ CERTBOT_ACME_CHALLENGE_METHOD }}.ini"
|
||||
CERTBOT_DNS_API_TOKEN: "" # Define in inventory file: More information here: group_vars/all/docs/CLOUDFLARE_API_TOKEN.md
|
||||
CERTBOT_DNS_PROPAGATION_WAIT_SECONDS: 300 # 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), dedicated
|
||||
|
||||
# Path where Certbot stores challenge webroot files
|
||||
letsencrypt_webroot_path: "/var/lib/letsencrypt/"
|
||||
LETSENCRYPT_WEBROOT_PATH: "/var/lib/letsencrypt/"
|
||||
|
||||
# Base directory containing Certbot configuration, account data, and archives
|
||||
letsencrypt_base_path: "/etc/letsencrypt/"
|
||||
LETSENCRYPT_BASE_PATH: "/etc/letsencrypt/"
|
||||
|
||||
# Symlink directory for the current active certificate and private key
|
||||
letsencrypt_live_path: "{{ letsencrypt_base_path }}live/"
|
||||
LETSENCRYPT_LIVE_PATH: "{{ LETSENCRYPT_BASE_PATH }}live/"
|
||||
|
||||
## Docker Role Specific Parameters
|
||||
DOCKER_RESTART_POLICY: "unless-stopped"
|
||||
DOCKER_VARS_FILE: "{{ playbook_dir }}/roles/docker-compose/vars/docker-compose.yml"
|
||||
|
||||
# Asyn Confitguration
|
||||
ASYNC_ENABLED: "{{ not MODE_DEBUG | bool }}" # Activate async, deactivated for debugging
|
||||
ASYNC_TIME: "{{ 300 if ASYNC_ENABLED | bool else omit }}" # Run for mnax 5min
|
||||
ASYNC_POLL: "{{ 0 if ASYNC_ENABLED | bool else 10 }}" # Don't wait for task
|
||||
|
||||
# default value if not set via CLI (-e) or in playbook vars
|
||||
allowed_applications: []
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
# Mode
|
||||
|
||||
# The following modes can be combined with each other
|
||||
mode_reset: false # Cleans up all Infinito.Nexus files. It's necessary to run to whole playbook and not particial roles when using this function.
|
||||
mode_test: false # Executes test routines instead of productive routines
|
||||
mode_update: true # Executes updates
|
||||
mode_backup: true # Activates the backup before the update procedure
|
||||
mode_cleanup: true # Cleanup unused files and configurations
|
||||
MODE_RESET: false # Cleans up all Infinito.Nexus files. It's necessary to run to whole playbook and not particial roles when using this function.
|
||||
MODE_TEST: false # Executes test routines instead of productive routines
|
||||
MODE_UPDATE: true # Executes updates
|
||||
MODE_BACKUP: true # Activates the backup before the update procedure
|
||||
MODE_CLEANUP: true # Cleanup unused files and configurations
|
||||
MODE_DEBUG: false # This enables debugging in ansible and in the apps, You SHOULD NOT enable this on production servers
|
@@ -1,7 +1,7 @@
|
||||
# Email Configuration
|
||||
default_system_email:
|
||||
domain: "{{primary_domain}}"
|
||||
host: "mail.{{primary_domain}}"
|
||||
domain: "{{PRIMARY_DOMAIN}}"
|
||||
host: "mail.{{PRIMARY_DOMAIN}}"
|
||||
port: 465
|
||||
tls: true # true for TLS and false for SSL
|
||||
start_tls: false
|
||||
|
@@ -23,4 +23,5 @@ nginx:
|
||||
cache:
|
||||
general: "/tmp/cache_nginx_general/" # Directory which nginx uses to cache general data
|
||||
image: "/tmp/cache_nginx_image/" # Directory which nginx uses to cache images
|
||||
user: "http" # Default nginx user in ArchLinux
|
||||
user: "http" # Default nginx user in ArchLinux
|
||||
|
@@ -3,10 +3,10 @@
|
||||
on_calendar_health_btrfs: "*-*-* 00:00:00" # Check once per day the btrfs for errors
|
||||
on_calendar_health_journalctl: "*-*-* 00:00:00" # Check once per day the journalctl for errors
|
||||
on_calendar_health_disc_space: "*-*-* 06,12,18,00:00:00" # Check four times per day if there is sufficient disc space
|
||||
on_calendar_health_docker_container: "*-*-* {{ hours_server_awake }}:00:00" # Check once per hour if the docker containers are healthy
|
||||
on_calendar_health_docker_volumes: "*-*-* {{ hours_server_awake }}:15:00" # Check once per hour if the docker volumes are healthy
|
||||
on_calendar_health_csp_crawler: "*-*-* {{ hours_server_awake }}:30:00" # Check once per hour if all CSP are fullfilled available
|
||||
on_calendar_health_nginx: "*-*-* {{ hours_server_awake }}:45:00" # Check once per hour if all webservices are available
|
||||
on_calendar_health_docker_container: "*-*-* {{ HOURS_SERVER_AWAKE }}:00:00" # Check once per hour if the docker containers are healthy
|
||||
on_calendar_health_docker_volumes: "*-*-* {{ HOURS_SERVER_AWAKE }}:15:00" # Check once per hour if the docker volumes are healthy
|
||||
on_calendar_health_csp_crawler: "*-*-* {{ HOURS_SERVER_AWAKE }}:30:00" # Check once per hour if all CSP are fullfilled available
|
||||
on_calendar_health_nginx: "*-*-* {{ HOURS_SERVER_AWAKE }}:45:00" # Check once per hour if all webservices are available
|
||||
on_calendar_health_msmtp: "*-*-* 00:00:00" # Check once per day SMTP Server
|
||||
|
||||
## Schedule for Cleanup Tasks
|
||||
@@ -19,7 +19,7 @@ on_calendar_backup_docker_to_local: "*-*-* 03:30:00"
|
||||
on_calendar_backup_remote_to_local: "*-*-* 21:30:00"
|
||||
|
||||
## Schedule for Maintenance Tasks
|
||||
on_calendar_heal_docker: "*-*-* {{ hours_server_awake }}:30:00" # Heal unhealthy docker instances once per hour
|
||||
on_calendar_heal_docker: "*-*-* {{ HOURS_SERVER_AWAKE }}:30:00" # Heal unhealthy docker instances once per hour
|
||||
on_calendar_renew_lets_encrypt_certificates: "*-*-* 12,00:30:00" # Renew Mailu certificates twice per day
|
||||
on_calendar_deploy_certificates: "*-*-* 13,01:30:00" # Deploy letsencrypt certificates twice per day to docker containers
|
||||
on_calendar_msi_keyboard_color: "*-*-* *:*:00" # Change the keyboard color every minute
|
||||
|
@@ -8,7 +8,7 @@
|
||||
# @see https://en.wikipedia.org/wiki/OpenID_Connect
|
||||
|
||||
## Helper Variables:
|
||||
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else primary_domain }}"
|
||||
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else PRIMARY_DOMAIN }}"
|
||||
_oidc_url: "{{
|
||||
(oidc.url
|
||||
if (oidc is defined and oidc.url is defined)
|
||||
@@ -16,7 +16,7 @@ _oidc_url: "{{
|
||||
)
|
||||
}}"
|
||||
_oidc_client_issuer_url: "{{ _oidc_url }}/realms/{{_oidc_client_realm}}"
|
||||
_oidc_client_id: "{{ oidc.client.id if oidc.client is defined and oidc.client.id is defined else primary_domain }}"
|
||||
_oidc_client_id: "{{ oidc.client.id if oidc.client is defined and oidc.client.id is defined else PRIMARY_DOMAIN }}"
|
||||
|
||||
defaults_oidc:
|
||||
url: "{{ _oidc_url }}"
|
||||
@@ -33,7 +33,7 @@ defaults_oidc:
|
||||
change_credentials: "{{_oidc_client_issuer_url}}account/account-security/signing-in" # URL for managing or changing user credentials
|
||||
certs: "{{_oidc_client_issuer_url}}/protocol/openid-connect/certs" # JSON Web Key Set (JWKS)
|
||||
reset_credentials: "{{_oidc_client_issuer_url}}/login-actions/reset-credentials?client_id={{ _oidc_client_id }}" # Password reset url
|
||||
button_text: "SSO Login ({{primary_domain | upper}})" # Default button text
|
||||
button_text: "SSO Login ({{PRIMARY_DOMAIN | upper}})" # Default button text
|
||||
attributes:
|
||||
# Attribut to identify the user
|
||||
username: "preferred_username"
|
||||
|
@@ -5,12 +5,12 @@
|
||||
|
||||
# Helper Variables:
|
||||
# Keep in mind to mapp this variables if there is ever the possibility for the user to define them in the inventory
|
||||
_ldap_dn_base: "dc={{primary_domain_sld}},dc={{primary_domain_tld}}"
|
||||
_ldap_dn_base: "dc={{PRIMARY_DOMAIN_SLD}},dc={{PRIMARY_DOMAIN_tld}}"
|
||||
_ldap_docker_network_enabled: "{{ applications | get_app_conf('svc-db-openldap', 'network.docker') }}"
|
||||
_ldap_protocol: "{{ 'ldap' if _ldap_docker_network_enabled else 'ldaps' }}"
|
||||
_ldap_server_port: "{{ ports.localhost[_ldap_protocol]['svc-db-openldap'] }}"
|
||||
_ldap_name: "{{ applications | get_app_conf('svc-db-openldap', 'docker.services.openldap.name') }}"
|
||||
_ldap_domain: "{{ primary_domain }}" # LDAP is jsut listening to a port not to a dedicated domain, so primary domain should be sufficient
|
||||
_ldap_domain: "{{ PRIMARY_DOMAIN }}" # LDAP is jsut listening to a port not to a dedicated domain, so primary domain should be sufficient
|
||||
_ldap_user_id: "uid"
|
||||
_ldap_filters_users_all: "(|(objectclass=inetOrgPerson))"
|
||||
|
||||
|
@@ -19,7 +19,7 @@ defaults_service_provider:
|
||||
web-app-bluesky: >-
|
||||
{{ ('@' ~ users.contact.username ~ '.' ~ domains['web-app-bluesky'].api)
|
||||
if 'web-app-bluesky' in group_names else '' }}
|
||||
email: "{{ users.contact.username ~ '@' ~ primary_domain if 'web-app-mailu' in group_names else '' }}"
|
||||
email: "{{ users.contact.username ~ '@' ~ PRIMARY_DOMAIN if 'web-app-mailu' in group_names else '' }}"
|
||||
mastodon: "{{ '@' ~ users.contact.username ~ '@' ~ domains | get_domain('web-app-mastodon') if 'web-app-mastodon' in group_names else '' }}"
|
||||
matrix: "{{ '@' ~ users.contact.username ~ ':' ~ domains['web-app-matrix'].synapse if 'web-app-matrix' in group_names else '' }}"
|
||||
peertube: "{{ '@' ~ users.contact.username ~ '@' ~ domains | get_domain('web-app-peertube') if 'web-app-peertube' in group_names else '' }}"
|
||||
|
@@ -1,10 +1,10 @@
|
||||
# Cloudflare API Token for Ansible (`certbot_dns_api_token`)
|
||||
# Cloudflare API Token for Ansible (`CERTBOT_DNS_API_TOKEN`)
|
||||
|
||||
This document explains how to generate and use a Cloudflare API Token for DNS automation and certificate operations in Ansible (e.g., with Certbot).
|
||||
|
||||
## Purpose
|
||||
|
||||
The `certbot_dns_api_token` variable must contain a valid Cloudflare API Token.
|
||||
The `CERTBOT_DNS_API_TOKEN` variable must contain a valid Cloudflare API Token.
|
||||
This token is used for all DNS operations and ACME (SSL/TLS certificate) challenges that require access to your Cloudflare-managed domains.
|
||||
|
||||
**Never commit your API token to a public repository. Always keep it secure!**
|
||||
@@ -58,4 +58,4 @@ Add the following permissions:
|
||||
Set the token in your Ansible inventory or secrets file:
|
||||
|
||||
```yaml
|
||||
certbot_dns_api_token: "cf_your_generated_token_here"
|
||||
CERTBOT_DNS_API_TOKEN: "cf_your_generated_token_here"
|
||||
|
1
roles/cmp-docker-oauth2/defaults/main.yml
Normal file
1
roles/cmp-docker-oauth2/defaults/main.yml
Normal file
@@ -0,0 +1 @@
|
||||
docker_compose_flush_handlers: false # Set to true in the vars/main.yml of the including role to autoflush after docker compose routine
|
@@ -1,6 +1,6 @@
|
||||
# run_once_cmp_docker_proxy: deactivated
|
||||
|
||||
# To load the proxy first is just implemented due to some issues with BBB
|
||||
# Load the proxy first, so that openresty handlers are flushed before the main docker compose
|
||||
- name: "For '{{ application_id }}': include role srv-proxy-6-6-domain"
|
||||
include_role:
|
||||
name: srv-proxy-6-6-domain
|
||||
|
@@ -15,7 +15,7 @@
|
||||
- name: Warn if repo is not reachable
|
||||
debug:
|
||||
msg: "Warning: Repository is not reachable."
|
||||
when: git_result.failed and enable_debug | bool
|
||||
when: git_result.failed and MODE_DEBUG | bool
|
||||
|
||||
- name: Ensure systemd user directory exists
|
||||
file:
|
||||
|
@@ -8,7 +8,7 @@ Refer to the [Docker Compose documentation](https://docs.docker.com/compose/), t
|
||||
|
||||
## Overview
|
||||
|
||||
This role creates a flexible directory layout for managing Docker Compose projects across environments. It ensures directories are initialized, optionally reset, and kept clean using internal flags like `mode_reset` or `mode_cleanup`.
|
||||
This role creates a flexible directory layout for managing Docker Compose projects across environments. It ensures directories are initialized, optionally reset, and kept clean using internal flags like `MODE_RESET` or `MODE_CLEANUP`.
|
||||
|
||||
## Purpose
|
||||
|
||||
@@ -17,7 +17,7 @@ To offer a centralized, extensible system for managing containerized application
|
||||
## Features
|
||||
|
||||
- **Dynamic Directory Structure:** Creates per-application instance folders for Compose setups.
|
||||
- **Reset Logic:** Cleans previous Compose project files and data when `mode_reset` is enabled.
|
||||
- **Reset Logic:** Cleans previous Compose project files and data when `MODE_RESET` is enabled.
|
||||
- **Handlers for Runtime Control:** Automatically builds, sets up, or restarts containers based on handlers.
|
||||
- **Template-ready Service Files:** Predefined service base and health check templates.
|
||||
- **Integration Support:** Compatible with `srv-proxy-7-4-core` and other Infinito.Nexus service roles.
|
||||
|
@@ -11,14 +11,23 @@
|
||||
- docker compose restart
|
||||
- docker compose just up
|
||||
|
||||
- name: Build docker
|
||||
command:
|
||||
cmd: docker compose build
|
||||
- name: Build docker compose
|
||||
shell: |
|
||||
set -euo pipefail
|
||||
docker compose build || {
|
||||
echo "Retrying without cache and pulling bases...";
|
||||
docker compose build --no-cache --pull;
|
||||
}
|
||||
args:
|
||||
chdir: "{{ docker_compose.directories.instance }}"
|
||||
executable: /bin/bash
|
||||
environment:
|
||||
COMPOSE_HTTP_TIMEOUT: 600
|
||||
DOCKER_CLIENT_TIMEOUT: 600
|
||||
listen:
|
||||
# Faster build
|
||||
DOCKER_BUILDKIT: "1"
|
||||
COMPOSE_DOCKER_CLI_BUILD: "1"
|
||||
listen:
|
||||
- docker compose build
|
||||
|
||||
- name: docker compose up
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
- name: "reset (if enabled)"
|
||||
include_tasks: 01_reset.yml
|
||||
when: mode_reset | bool
|
||||
when: MODE_RESET | bool
|
||||
|
||||
# This could lead to problems in docker-compose directories which are based on a git repository
|
||||
# @todo Verify that this isn't the case. E.g. in accounting
|
||||
|
@@ -16,7 +16,7 @@
|
||||
url: "{{ cf_api_url }}?name={{ domain | to_primary_domain }}"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "Bearer {{ certbot_dns_api_token }}"
|
||||
Authorization: "Bearer {{ CERTBOT_DNS_API_TOKEN }}"
|
||||
Content-Type: "application/json"
|
||||
return_content: yes
|
||||
register: cf_zone_lookup_dev
|
||||
@@ -43,8 +43,8 @@
|
||||
|
||||
- name: activate cloudflare cache development mode
|
||||
include_tasks: "cloudflare/02_enable_cf_dev_mode.yml"
|
||||
when: (INFINITO_ENVIRONMENT | lower) == 'development'
|
||||
when: (ENVIRONMENT | lower) == 'development'
|
||||
|
||||
- name: purge cloudflare domain cache
|
||||
include_tasks: "cloudflare/01_cleanup.yml"
|
||||
when: mode_cleanup | bool
|
||||
when: MODE_CLEANUP | bool
|
@@ -3,10 +3,11 @@
|
||||
url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/purge_cache"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ certbot_dns_api_token }}"
|
||||
Authorization: "Bearer {{ CERTBOT_DNS_API_TOKEN }}"
|
||||
Content-Type: "application/json"
|
||||
body:
|
||||
purge_everything: true
|
||||
body_format: json
|
||||
return_content: yes
|
||||
register: cf_purge
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# roles/srv-proxy-6-6-domain/tasks/02_enable_cf_dev_mode.yml
|
||||
---
|
||||
# Enables Cloudflare Development Mode (bypasses cache for ~3 hours).
|
||||
# Uses the same auth token as in 01_cleanup.yml: certbot_dns_api_token
|
||||
# Uses the same auth token as in 01_cleanup.yml: CERTBOT_DNS_API_TOKEN
|
||||
# Assumes `domain` and (optionally) `cf_zone_id` are available.
|
||||
# Safe to run repeatedly; only changes when the mode is not already "on".
|
||||
|
||||
@@ -10,17 +10,18 @@
|
||||
url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/settings/development_mode"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "Bearer {{ certbot_dns_api_token }}"
|
||||
Authorization: "Bearer {{ CERTBOT_DNS_API_TOKEN }}"
|
||||
Content-Type: "application/json"
|
||||
return_content: yes
|
||||
register: cf_dev_mode_current
|
||||
when: ASYNC_ENABLED | bool
|
||||
|
||||
- name: "Enable Cloudflare Development Mode"
|
||||
ansible.builtin.uri:
|
||||
url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/settings/development_mode"
|
||||
method: PATCH
|
||||
headers:
|
||||
Authorization: "Bearer {{ certbot_dns_api_token }}"
|
||||
Authorization: "Bearer {{ CERTBOT_DNS_API_TOKEN }}"
|
||||
Content-Type: "application/json"
|
||||
body:
|
||||
value: "on"
|
||||
@@ -28,5 +29,8 @@
|
||||
return_content: yes
|
||||
register: cf_dev_mode_enable
|
||||
changed_when: >
|
||||
ASYNC_ENABLED | bool and
|
||||
cf_dev_mode_current.json.result.value is defined and
|
||||
cf_dev_mode_current.json.result.value != 'on'
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
|
@@ -7,7 +7,7 @@
|
||||
when: run_once_srv_proxy_6_6_domain is not defined
|
||||
|
||||
- include_tasks: "01_cloudflare.yml"
|
||||
when: dns_provider == "cloudflare"
|
||||
when: DNS_PROVIDER == "cloudflare"
|
||||
|
||||
- include_tasks: "{{ playbook_dir }}/tasks/utils/load_handlers.yml"
|
||||
vars:
|
||||
|
@@ -49,7 +49,7 @@ This script:
|
||||
|
||||
**Usage:**
|
||||
```sh
|
||||
sh srv-proxy-6-6-tls-deploy.sh primary_domain /path/to/docker/compose
|
||||
sh srv-proxy-6-6-tls-deploy.sh PRIMARY_DOMAIN /path/to/docker/compose
|
||||
```
|
||||
|
||||
---
|
||||
|
@@ -4,33 +4,33 @@ If you enabled `enable_wildcard_certificate`, follow these steps to manually req
|
||||
### **1️⃣ Run the Certbot Command 🖥️**
|
||||
```sh
|
||||
certbot certonly --manual --preferred-challenges=dns --agree-tos \
|
||||
--email administrator@primary_domain -d primary_domain -d "*.primary_domain"
|
||||
--email administrator@PRIMARY_DOMAIN -d PRIMARY_DOMAIN -d "*.PRIMARY_DOMAIN"
|
||||
```
|
||||
|
||||
### **2️⃣ Add DNS TXT Record for Validation 📜**
|
||||
Certbot will prompt you to add a DNS TXT record:
|
||||
```
|
||||
Please create a TXT record under the name:
|
||||
_acme-challenge.primary_domain.
|
||||
_acme-challenge.PRIMARY_DOMAIN.
|
||||
|
||||
with the following value:
|
||||
9oVizYIYVGlZ3VtWQIKRS5UghyXiqGoUNlCtIE7LiA
|
||||
```
|
||||
➡ **Go to your DNS provider** and create a new **TXT record**:
|
||||
- **Host:** `_acme-challenge.primary_domain`
|
||||
- **Host:** `_acme-challenge.PRIMARY_DOMAIN`
|
||||
- **Value:** `"9oVizYIYVGlZ3VtWQIKRS5UghyXiqGoUNlCtIE7LiA"`
|
||||
- **TTL:** Set to **300 seconds (or lowest possible)**
|
||||
|
||||
✅ **Verify the DNS record** before continuing:
|
||||
```sh
|
||||
dig TXT _acme-challenge.primary_domain @8.8.8.8
|
||||
dig TXT _acme-challenge.PRIMARY_DOMAIN @8.8.8.8
|
||||
```
|
||||
|
||||
### **3️⃣ Complete the Certificate Request ✅**
|
||||
Once the DNS changes have propagated, **press Enter** in the Certbot terminal.
|
||||
If successful, Certbot will save the certificates under:
|
||||
```
|
||||
/etc/letsencrypt/live/primary_domain/
|
||||
/etc/letsencrypt/live/PRIMARY_DOMAIN/
|
||||
```
|
||||
- **fullchain.pem** → The certificate
|
||||
- **privkey.pem** → The private key
|
||||
|
@@ -12,11 +12,11 @@ docker_compose_instance_directory="$2"
|
||||
docker_compose_cert_directory="$docker_compose_instance_directory/volumes/certs"
|
||||
|
||||
# Copy certificates
|
||||
cp -RvL "{{ letsencrypt_live_path }}/$ssl_cert_folder/"* "$docker_compose_cert_directory" || exit 1
|
||||
cp -RvL "{{ LETSENCRYPT_LIVE_PATH }}/$ssl_cert_folder/"* "$docker_compose_cert_directory" || exit 1
|
||||
|
||||
# This code is optimized for mailu
|
||||
cp -v "{{ letsencrypt_live_path }}/$ssl_cert_folder/privkey.pem" "$docker_compose_cert_directory/key.pem" || exit 1
|
||||
cp -v "{{ letsencrypt_live_path }}/$ssl_cert_folder/fullchain.pem" "$docker_compose_cert_directory/cert.pem" || exit 1
|
||||
cp -v "{{ LETSENCRYPT_LIVE_PATH }}/$ssl_cert_folder/privkey.pem" "$docker_compose_cert_directory/key.pem" || exit 1
|
||||
cp -v "{{ LETSENCRYPT_LIVE_PATH }}/$ssl_cert_folder/fullchain.pem" "$docker_compose_cert_directory/cert.pem" || exit 1
|
||||
|
||||
# Set correct reading rights
|
||||
chmod a+r -v "$docker_compose_cert_directory/"*
|
||||
|
@@ -6,7 +6,8 @@ location {{location}}
|
||||
{% include 'roles/web-app-oauth2-proxy/templates/following_directives.conf.j2'%}
|
||||
{% endif %}
|
||||
|
||||
proxy_pass http://127.0.0.1:{{ http_port }}{{ location if not location.startswith('@') else '' }};
|
||||
{% set _loc = location|trim %}
|
||||
proxy_pass http://127.0.0.1:{{ http_port }}{{ (_loc|regex_replace('^(?:=|\\^~)\\s*','')) if not (_loc is match('^(@|~)')) else '' }};
|
||||
|
||||
# headers
|
||||
proxy_set_header Host $host;
|
||||
@@ -14,25 +15,27 @@ location {{location}}
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Port {{ WEB_PORT }};
|
||||
proxy_set_header Accept-Encoding "";
|
||||
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/headers/content_security_policy.conf.j2' %}
|
||||
|
||||
# WebSocket specific header
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Activate buffering
|
||||
# Needs to be enabled, so that lua can do str replaces
|
||||
proxy_buffering on;
|
||||
proxy_request_buffering on;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
# timeouts
|
||||
proxy_connect_timeout 1s;
|
||||
proxy_send_timeout 900s;
|
||||
proxy_read_timeout 900s;
|
||||
send_timeout 900s;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_send_timeout 900s;
|
||||
proxy_read_timeout 900s;
|
||||
send_timeout 900s;
|
||||
|
||||
{% set proxy_lua_enabled = proxy_lua_enabled | default(true) | bool %}
|
||||
# Buffering needs to be activ, so that lua can do str replaces
|
||||
proxy_buffering {{ 'on' if proxy_lua_enabled else 'off' }};
|
||||
proxy_request_buffering {{ 'on' if proxy_lua_enabled else 'off' }};
|
||||
|
||||
{% if proxy_lua_enabled %}
|
||||
proxy_set_header Accept-Encoding "";
|
||||
{% include 'roles/srv-web-7-7-inj-compose/templates/location.lua.j2'%}
|
||||
{% endif %}
|
||||
}
|
@@ -4,7 +4,7 @@ location ~* \.(jpg|jpeg|png|gif|webp|ico|svg)$ {
|
||||
add_header Cache-Control "public, max-age=2592000, immutable";
|
||||
|
||||
# Cache on reverse proxy side
|
||||
proxy_pass http://127.0.0.1:{{http_port}};
|
||||
proxy_pass http://127.0.0.1:{{ http_port }};
|
||||
proxy_cache imgcache;
|
||||
proxy_cache_valid 200 302 60m;
|
||||
proxy_cache_valid 404 1m;
|
||||
|
@@ -24,7 +24,7 @@ The Nginx HTTPS Certificate Retrieval role ensures that your Nginx-served domain
|
||||
- **ACME Challenge Selection:** Supports DNS plugins or webroot method automatically.
|
||||
- **Wildcard Certificate Management:** Issues wildcard certificates when configured, saving effort for subdomain-heavy deployments.
|
||||
- **Safe Cleanup:** Ensures that no unused certificates are left behind.
|
||||
- **Flexible Control:** Supports `mode_test` for staging environment testing and `mode_cleanup` for cert cleanup operations.
|
||||
- **Flexible Control:** Supports `MODE_TEST` for staging environment testing and `MODE_CLEANUP` for cert cleanup operations.
|
||||
|
||||
## 🔗 Learn More
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
- name: "Check if certificate already exists for {{ domain }}"
|
||||
cert_check_exists:
|
||||
domain: "{{ domain }}"
|
||||
cert_base_path: "{{ letsencrypt_live_path }}"
|
||||
cert_base_path: "{{ LETSENCRYPT_LIVE_PATH }}"
|
||||
register: cert_check
|
||||
|
||||
- name: "receive certificate for {{ domain }}"
|
||||
@@ -10,21 +10,21 @@
|
||||
--agree-tos
|
||||
--email {{ users.administrator.email }}
|
||||
--non-interactive
|
||||
{% if certbot_acme_challenge_method != "webroot" %}
|
||||
--dns-{{ certbot_acme_challenge_method }}
|
||||
--dns-{{ certbot_acme_challenge_method }}-credentials {{ certbot_credentials_file }}
|
||||
--dns-{{ certbot_acme_challenge_method }}-propagation-seconds {{ certbot_dns_propagation_wait_seconds }}
|
||||
{% if CERTBOT_ACME_CHALLENGE_METHOD != "webroot" %}
|
||||
--dns-{{ CERTBOT_ACME_CHALLENGE_METHOD }}
|
||||
--dns-{{ CERTBOT_ACME_CHALLENGE_METHOD }}-credentials {{ CERTBOT_CREDENTIALS_FILE }}
|
||||
--dns-{{ CERTBOT_ACME_CHALLENGE_METHOD }}-propagation-seconds {{ CERTBOT_DNS_PROPAGATION_WAIT_SECONDS }}
|
||||
{% else %}
|
||||
--webroot
|
||||
-w {{ letsencrypt_webroot_path }}
|
||||
-w {{ LETSENCRYPT_WEBROOT_PATH }}
|
||||
{% endif %}
|
||||
{% if wildcard_domain is defined and ( wildcard_domain | bool ) %}
|
||||
-d {{ primary_domain }}
|
||||
-d *.{{ primary_domain }}
|
||||
-d {{ PRIMARY_DOMAIN }}
|
||||
-d *.{{ PRIMARY_DOMAIN }}
|
||||
{% else %}
|
||||
-d {{ domain }}
|
||||
{% endif %}
|
||||
{{ '--test-cert' if mode_test | bool else '' }}
|
||||
{{ '--test-cert' if MODE_TEST | bool else '' }}
|
||||
register: certbot_result
|
||||
changed_when: "'Certificate not yet due for renewal' not in certbot_result.stdout"
|
||||
when: not cert_check.exists
|
@@ -10,15 +10,15 @@
|
||||
certbundle
|
||||
--domains "{{ current_play_domains_all | join(',') }}"
|
||||
--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' %}
|
||||
--certbot-credentials-file "{{ certbot_credentials_file }}"
|
||||
--certbot-dns-propagation-seconds "{{ certbot_dns_propagation_wait_seconds }}"
|
||||
{% if CERTBOT_ACME_CHALLENGE_METHOD != 'webroot' %}
|
||||
--certbot-credentials-file "{{ CERTBOT_CREDENTIALS_FILE }}"
|
||||
--certbot-dns-propagation-seconds "{{ CERTBOT_DNS_PROPAGATION_WAIT_SECONDS }}"
|
||||
{% else %}
|
||||
--letsencrypt-webroot-path "{{ letsencrypt_webroot_path }}"
|
||||
--letsencrypt-webroot-path "{{ LETSENCRYPT_WEBROOT_PATH }}"
|
||||
{% endif %}
|
||||
{{ '--mode-test' if mode_test | bool else '' }}
|
||||
{{ '--mode-test' if MODE_TEST | bool else '' }}
|
||||
register: certbundle_result
|
||||
changed_when: "'Certificate not yet due for renewal' not in certbundle_result.stdout"
|
||||
failed_when: >
|
||||
|
@@ -3,7 +3,7 @@
|
||||
vars:
|
||||
wildcard_domain: true
|
||||
when:
|
||||
- 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)
|
||||
- run_once_receive_certificate is not defined
|
||||
|
||||
- name: "Load dedicated certificate for domain"
|
||||
@@ -11,7 +11,7 @@
|
||||
vars:
|
||||
wildcard_domain: false
|
||||
when:
|
||||
- not (domain.split('.') | length == (primary_domain.split('.') | length + 1) and domain.endswith(primary_domain))
|
||||
- not (domain.split('.') | length == (PRIMARY_DOMAIN.split('.') | length + 1) and domain.endswith(PRIMARY_DOMAIN))
|
||||
|
||||
- name: run the receive_certificate tasks once
|
||||
set_fact:
|
||||
|
@@ -6,20 +6,20 @@
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_srv_web_6_6_tls_core is not defined
|
||||
|
||||
- name: "Include flavor '{{ certbot_flavor }}' for '{{ domain }}'"
|
||||
include_tasks: "{{ role_path }}/tasks/flavors/{{ certbot_flavor }}.yml"
|
||||
- name: "Include flavor '{{ CERTBOT_FLAVOR }}' for '{{ domain }}'"
|
||||
include_tasks: "{{ role_path }}/tasks/flavors/{{ CERTBOT_FLAVOR }}.yml"
|
||||
|
||||
#- name: "Cleanup dedicated cert for {{ domain }}"
|
||||
# command: >-
|
||||
# certbot delete --cert-name {{ domain }} --non-interactive
|
||||
# when:
|
||||
# - mode_cleanup | bool
|
||||
# - MODE_CLEANUP | bool
|
||||
# # Cleanup mode is enabled
|
||||
# - certbot_flavor != 'dedicated'
|
||||
# - CERTBOT_FLAVOR != 'dedicated'
|
||||
# # 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
|
||||
# - domain != primary_domain
|
||||
# - domain != PRIMARY_DOMAIN
|
||||
# # The domain is not the primary domain
|
||||
# register: certbot_result
|
||||
# failed_when: certbot_result.rc != 0 and ("No certificate found with name" not in certbot_result.stderr)
|
||||
@@ -28,8 +28,8 @@
|
||||
- name: "Find SSL cert folder for '{{ domain }}'"
|
||||
cert_folder_find:
|
||||
domain: "{{ domain }}"
|
||||
cert_base_path: "{{ letsencrypt_live_path }}"
|
||||
debug: "{{ enable_debug | default(false) }}"
|
||||
cert_base_path: "{{ LETSENCRYPT_LIVE_PATH }}"
|
||||
debug: "{{ MODE_DEBUG | default(false) }}"
|
||||
register: cert_folder_result
|
||||
delegate_to: "{{ inventory_hostname }}"
|
||||
changed_when: false
|
||||
|
@@ -14,11 +14,11 @@ This Ansible role installs and configures **Nginx** as a core HTTP/stream server
|
||||
* **Configurable reset and cleanup** modes to purge and recreate directories.
|
||||
* **Custom `nginx.conf`** template with sensible defaults for performance and security.
|
||||
* **Stream proxy support**: includes `stream` block for TCP/UDP proxies.
|
||||
* **Cache directory management**: cleanup and recreation based on `mode_cleanup`.
|
||||
* **Cache directory management**: cleanup and recreation based on `MODE_CLEANUP`.
|
||||
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
* **General logs**: `journalctl -f -u nginx`
|
||||
* **Filter by host**: `journalctl -u nginx -f | grep "{{ inventory_hostname }}"`
|
||||
* **Enable detailed format**: set `enable_debug: true` and reload Nginx.
|
||||
* **Enable detailed format**: set `MODE_DEBUG: true` and reload Nginx.
|
||||
|
@@ -20,7 +20,7 @@
|
||||
|
||||
- name: "reset (if enabled)"
|
||||
include_tasks: 02_reset.yml
|
||||
when: mode_reset | bool
|
||||
when: MODE_RESET | bool
|
||||
|
||||
- name: Ensure nginx configuration directories are present
|
||||
file:
|
||||
@@ -47,9 +47,12 @@
|
||||
mode: '0755'
|
||||
loop: >
|
||||
{{ nginx.directories.data.values() | list }}
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
|
||||
- name: "Include tasks to create cache directories"
|
||||
include_tasks: 03_cache_directories.yml
|
||||
when: run_once_nginx_reverse_proxy is not defined
|
||||
|
||||
- name: create nginx config file
|
||||
template:
|
||||
|
@@ -1,4 +1,4 @@
|
||||
- name: "Delete {{nginx.directories.configuration}} directory, when mode_reset"
|
||||
- name: "Delete {{nginx.directories.configuration}} directory, when MODE_RESET"
|
||||
file:
|
||||
path: "{{ nginx.directories.configuration }}"
|
||||
state: absent
|
@@ -4,8 +4,7 @@
|
||||
path: "{{ item.value }}"
|
||||
state: absent
|
||||
when:
|
||||
- mode_cleanup | bool
|
||||
- run_once_nginx_reverse_proxy is not defined
|
||||
- MODE_CLEANUP | bool
|
||||
loop: "{{ nginx.directories.cache | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
@@ -18,13 +17,12 @@
|
||||
owner: "{{ nginx.user }}"
|
||||
group: "{{ nginx.user }}"
|
||||
mode: '0700'
|
||||
|
||||
when: run_once_nginx_reverse_proxy is not defined
|
||||
loop: "{{ nginx.directories.cache | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
|
||||
- name: run the nginx_reverse_proxy tasks once
|
||||
set_fact:
|
||||
run_once_nginx_reverse_proxy: true
|
||||
when: run_once_nginx_reverse_proxy is not defined
|
||||
run_once_nginx_reverse_proxy: true
|
@@ -24,7 +24,7 @@ http
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
{# logging and debugging #}
|
||||
{% if enable_debug | bool %}
|
||||
{% if MODE_DEBUG | bool %}
|
||||
{# individual log format for better debugging #}
|
||||
log_format debug '$host - $remote_addr [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
|
@@ -45,8 +45,8 @@ All tasks are idempotent—once your certificates are in place and your configur
|
||||
- A working `srv-web-7-4-core` setup.
|
||||
- DNS managed via Cloudflare (for CAA record tasks) or equivalent ACME DNS flow.
|
||||
- Variables:
|
||||
- `letsencrypt_webroot_path`
|
||||
- `letsencrypt_live_path`
|
||||
- `LETSENCRYPT_WEBROOT_PATH`
|
||||
- `LETSENCRYPT_LIVE_PATH`
|
||||
- `on_calendar_renew_lets_encrypt_certificates`
|
||||
|
||||
---
|
||||
|
@@ -6,34 +6,34 @@
|
||||
|
||||
- name: install certbot DNS plugin
|
||||
community.general.pacman:
|
||||
name: "certbot-dns-{{ certbot_acme_challenge_method }}"
|
||||
name: "certbot-dns-{{ CERTBOT_ACME_CHALLENGE_METHOD }}"
|
||||
state: present
|
||||
when:
|
||||
- run_once_srv_web_7_7_certbot is not defined
|
||||
- certbot_acme_challenge_method != 'webroot'
|
||||
- CERTBOT_ACME_CHALLENGE_METHOD != 'webroot'
|
||||
|
||||
- name: Ensure /etc/certbot directory exists
|
||||
file:
|
||||
path: "{{ certbot_credentials_dir }}"
|
||||
path: "{{ CERTBOT_CREDENTIALS_DIR }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
when:
|
||||
- run_once_srv_web_7_7_certbot is not defined
|
||||
- certbot_acme_challenge_method != 'webroot'
|
||||
- CERTBOT_ACME_CHALLENGE_METHOD != 'webroot'
|
||||
|
||||
- name: Install plugin credentials file
|
||||
copy:
|
||||
dest: "{{ certbot_credentials_file }}"
|
||||
dest: "{{ CERTBOT_CREDENTIALS_FILE }}"
|
||||
content: |
|
||||
dns_{{ certbot_acme_challenge_method }}_api_token = {{ certbot_dns_api_token }}
|
||||
dns_{{ CERTBOT_ACME_CHALLENGE_METHOD }}_api_token = {{ CERTBOT_DNS_API_TOKEN }}
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0600'
|
||||
when:
|
||||
- run_once_srv_web_7_7_certbot is not defined
|
||||
- certbot_acme_challenge_method != 'webroot'
|
||||
- CERTBOT_ACME_CHALLENGE_METHOD != 'webroot'
|
||||
|
||||
- name: run the certbot role once
|
||||
set_fact:
|
||||
|
@@ -10,7 +10,7 @@ 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.
|
||||
- Provides an optional debug task (`MODE_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.
|
||||
|
||||
@@ -23,7 +23,7 @@ Cloudflare DNS Records delivers an idempotent, scalable solution for managing A-
|
||||
- **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.
|
||||
- **Debug Support:** Enable `MODE_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.
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
- name: Create or update Cloudflare A-record for {{ item }}
|
||||
community.general.cloudflare_dns:
|
||||
api_token: "{{ certbot_dns_api_token }}"
|
||||
api_token: "{{ CERTBOT_DNS_API_TOKEN }}"
|
||||
zone: "{{ item.split('.')[-2:] | join('.') }}"
|
||||
state: present
|
||||
type: A
|
||||
|
@@ -10,11 +10,17 @@
|
||||
set_fact:
|
||||
inj_enabled:
|
||||
javascript: "{{ applications | get_app_conf(application_id, 'features.javascript', False) }}"
|
||||
logout: "{{ (applications | get_app_conf(application_id, 'features.logout', False) or domain == primary_domain) }}"
|
||||
logout: "{{ (applications | get_app_conf(application_id, 'features.logout', False) or domain == PRIMARY_DOMAIN) }}"
|
||||
css: "{{ applications | get_app_conf(application_id, 'features.css', False) }}"
|
||||
matomo: "{{ applications | get_app_conf(application_id, 'features.matomo', False) }}"
|
||||
port_ui: "{{ applications | get_app_conf(application_id, 'features.port-ui-desktop', False) }}"
|
||||
|
||||
- name: "Activate Portfolio iFrame notifier for {{ domain }}"
|
||||
include_role:
|
||||
name: srv-web-7-7-inj-port-ui-desktop
|
||||
public: true # Expose variables so that they can be used in template
|
||||
when: inj_enabled.port_ui
|
||||
|
||||
- name: "Load CDN for {{domain}}"
|
||||
include_role:
|
||||
name: web-svc-cdn
|
||||
@@ -39,7 +45,7 @@
|
||||
set_fact:
|
||||
inj_enabled:
|
||||
javascript: "{{ applications | get_app_conf(application_id, 'features.javascript', False) }}"
|
||||
logout: "{{ (applications | get_app_conf(application_id, 'features.logout', False) or domain == primary_domain) }}"
|
||||
logout: "{{ (applications | get_app_conf(application_id, 'features.logout', False) or domain == PRIMARY_DOMAIN) }}"
|
||||
css: "{{ applications | get_app_conf(application_id, 'features.css', False) }}"
|
||||
matomo: "{{ applications | get_app_conf(application_id, 'features.matomo', False) }}"
|
||||
port_ui: "{{ applications | get_app_conf(application_id, 'features.port-ui-desktop', False) }}"
|
||||
@@ -56,11 +62,6 @@
|
||||
name: srv-web-7-7-inj-matomo
|
||||
when: inj_enabled.matomo
|
||||
|
||||
- name: "Activate Portfolio iFrame notifier for {{ domain }}"
|
||||
include_role:
|
||||
name: srv-web-7-7-inj-port-ui-desktop
|
||||
when: inj_enabled.port_ui
|
||||
|
||||
- name: "Activate Javascript for {{ domain }}"
|
||||
include_role:
|
||||
name: srv-web-7-7-inj-javascript
|
||||
|
@@ -58,7 +58,7 @@ body_filter_by_lua_block {
|
||||
-- build a list of body-injection snippets
|
||||
local body_snippets = {}
|
||||
|
||||
{% for body_feature in ['matomo', 'logout' ] %}
|
||||
{% for body_feature in ['matomo', 'logout', 'port-ui-desktop'] %}
|
||||
{% if applications | get_app_conf(application_id, 'features.' ~ body_feature, false) | bool %}
|
||||
body_snippets[#body_snippets + 1] = [=[
|
||||
{%- include "roles/srv-web-7-7-inj-" ~ body_feature ~ "/templates/body_sub.j2" -%}
|
||||
|
@@ -1 +1 @@
|
||||
<link rel="stylesheet" type="text/css" href="/global.css?version={{global_css_version}}">
|
||||
<link rel="stylesheet" type="text/css" href="/global.css?version={{ global_css_version }}">
|
@@ -16,7 +16,7 @@ This Ansible role injects a custom JavaScript snippet into all HTML responses se
|
||||
Activates only when you enable the `javascript` feature for a given application, keeping your server blocks clean and performant.
|
||||
|
||||
- **Debug Mode**
|
||||
Supports an `enable_debug` flag that appends optional `console.log` statements for easier troubleshooting in staging or development.
|
||||
Supports an `MODE_DEBUG` flag that appends optional `console.log` statements for easier troubleshooting in staging or development.
|
||||
|
||||
## Author
|
||||
|
||||
|
@@ -2,6 +2,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
initLogoutPatch(
|
||||
'{{ oidc.client.logout_url }}',
|
||||
'{{ WEB_PROTOCOL }}',
|
||||
'{{ primary_domain }}'
|
||||
'{{ PRIMARY_DOMAIN }}'
|
||||
);
|
||||
});
|
@@ -12,7 +12,7 @@
|
||||
domain: "{{ domain }}"
|
||||
base_domain: "{{ base_domain }}"
|
||||
matomo_verification_url: "{{ matomo_verification_url }}"
|
||||
when: enable_debug | bool
|
||||
when: MODE_DEBUG | bool
|
||||
|
||||
- name: "Check if site {{ domain }} is allready registered at Matomo"
|
||||
uri:
|
||||
|
@@ -14,6 +14,6 @@ _paq.push(["enableLinkTracking"]);
|
||||
g.async=true; g.src=u+"matomo.js"; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
|
||||
{% if enable_debug | bool %}
|
||||
{% if MODE_DEBUG | bool %}
|
||||
console.log("Matomo is loaded.");
|
||||
{% endif %}
|
16
roles/srv-web-7-7-inj-port-ui-desktop/tasks/01_deploy.yml
Normal file
16
roles/srv-web-7-7-inj-port-ui-desktop/tasks/01_deploy.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
- name: Deploy iframe-handler.js
|
||||
template:
|
||||
src: iframe-handler.js.j2
|
||||
dest: "{{ inj_port_ui_js_destination }}"
|
||||
owner: "{{ nginx.user }}"
|
||||
group: "{{ nginx.user }}"
|
||||
mode: '0644'
|
||||
|
||||
- name: Get stat for iframe-handler.js
|
||||
stat:
|
||||
path: "{{ inj_port_ui_js_destination }}"
|
||||
register: inj_port_ui_js_stat
|
||||
|
||||
- name: Set inj_port_ui_js_version
|
||||
set_fact:
|
||||
inj_port_ui_js_version: "{{ inj_port_ui_js_stat.stat.mtime }}"
|
@@ -3,18 +3,20 @@
|
||||
include_role:
|
||||
name: srv-web-7-4-core
|
||||
when: run_once_srv_web_7_4_core is not defined
|
||||
- include_tasks: 01_deploy.yml
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_srv_web_7_7_inj_port_ui_desktop is not defined
|
||||
|
||||
- name: "Load iFrame handler JS template for '{{ application_id }}'"
|
||||
# --- Build tiny inline initializer (CSP-hashed) ---
|
||||
- name: "Load iFrame init code for '{{ application_id }}'"
|
||||
set_fact:
|
||||
iframe_code: "{{ lookup('template','iframe-handler.js.j2') }}"
|
||||
iframe_init_code: "{{ lookup('template','iframe-init_one_liner.js.j2') }}"
|
||||
|
||||
- name: "Collapse iFrame code into one-liner for '{{ application_id }}'"
|
||||
- name: "Collapse iFrame init code into one-liner for '{{ application_id }}'"
|
||||
set_fact:
|
||||
iframe_code_one_liner: "{{ iframe_code | to_one_liner }}"
|
||||
iframe_init_code_one_liner: "{{ iframe_init_code | to_one_liner }}"
|
||||
|
||||
- name: "Append iFrame CSP hash for '{{ application_id }}'"
|
||||
- name: "Append iFrame init CSP hash for '{{ application_id }}'"
|
||||
set_fact:
|
||||
applications: "{{ applications | append_csp_hash(application_id, iframe_code_one_liner) }}"
|
||||
applications: "{{ applications | append_csp_hash(application_id, iframe_init_code_one_liner) }}"
|
||||
changed_when: false
|
||||
|
@@ -0,0 +1 @@
|
||||
<script>{{ iframe_init_code_one_liner }}</script>
|
@@ -1 +1 @@
|
||||
<script>{{ iframe_code_one_liner }}</script>
|
||||
<script src="{{ domains | get_url('web-svc-cdn', WEB_PROTOCOL) }}/{{ inj_port_ui_file_name }}?{{ inj_port_ui_js_version }}"></script>
|
@@ -1,48 +1,57 @@
|
||||
(function() {
|
||||
var primary = "{{ primary_domain }}";
|
||||
var allowedOrigin = "https://{{ domains | get_domain('web-app-port-ui') }}";
|
||||
|
||||
function notifyParent() {
|
||||
if (window.self !== window.top) {
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: "iframeLocationChange",
|
||||
href: window.location.href
|
||||
}, allowedOrigin);
|
||||
} catch (e) {}
|
||||
(function (global) {
|
||||
/**
|
||||
* Initializes the iframe sync & external link forcing logic.
|
||||
* @param {string} primary_domain
|
||||
* @param {string} current_domain
|
||||
* @param {string} allowedOrigin - Parent origin for postMessage
|
||||
*/
|
||||
function initIframeHandler(primary_domain, current_domain, allowedOrigin) {
|
||||
function notifyParent() {
|
||||
if (window.self !== window.top) {
|
||||
try {
|
||||
window.parent.postMessage(
|
||||
{ type: "iframeLocationChange", href: window.location.href },
|
||||
allowedOrigin
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function forceExternalLinks() {
|
||||
Array.prototype.forEach.call(document.querySelectorAll("a[href]"), function(a) {
|
||||
try {
|
||||
var url = new URL(a.href, location);
|
||||
if (!url.hostname.endsWith(primary)) {
|
||||
a.target = "_blank";
|
||||
a.rel = "noopener";
|
||||
}
|
||||
} catch (e) {}
|
||||
function forceExternalLinks() {
|
||||
Array.prototype.forEach.call(document.querySelectorAll("a[href]"), function (a) {
|
||||
try {
|
||||
var url = new URL(a.href, location);
|
||||
// open new tab if link goes outside our primary OR current domain
|
||||
if (!(url.hostname.endsWith(primary_domain) || url.hostname.endsWith(current_domain))) {
|
||||
a.target = "_blank";
|
||||
a.rel = "noopener";
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
notifyParent();
|
||||
forceExternalLinks();
|
||||
});
|
||||
window.addEventListener("popstate", function () {
|
||||
notifyParent();
|
||||
forceExternalLinks();
|
||||
});
|
||||
|
||||
// SPA support
|
||||
var _pushState = history.pushState;
|
||||
history.pushState = function () {
|
||||
_pushState.apply(history, arguments);
|
||||
notifyParent();
|
||||
forceExternalLinks();
|
||||
};
|
||||
|
||||
{% if MODE_DEBUG | bool %}
|
||||
try { console.log("[iframe-sync] initIframeHandler installed."); } catch (e) {}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
notifyParent();
|
||||
forceExternalLinks();
|
||||
});
|
||||
window.addEventListener("popstate", function() {
|
||||
notifyParent();
|
||||
forceExternalLinks();
|
||||
});
|
||||
|
||||
// SPA support
|
||||
var _pushState = history.pushState;
|
||||
history.pushState = function() {
|
||||
_pushState.apply(history, arguments);
|
||||
notifyParent();
|
||||
forceExternalLinks();
|
||||
};
|
||||
})();
|
||||
|
||||
{% if enable_debug | bool %}
|
||||
console.log("[iframe-sync] Sender for iframe messages is active.");
|
||||
{% endif %}
|
||||
// expose for inline bootstrap
|
||||
global.initIframeHandler = initIframeHandler;
|
||||
})(window);
|
||||
|
@@ -0,0 +1,10 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
initIframeHandler(
|
||||
'{{ PRIMARY_DOMAIN }}',
|
||||
'{{ domain }}',
|
||||
'{{ domains | get_url("web-app-port-ui", WEB_PROTOCOL) }}'
|
||||
);
|
||||
});
|
||||
{% if MODE_DEBUG | bool %}
|
||||
try { console.log("[iframe-sync] Sender for iframe messages is active."); } catch(e) {}
|
||||
{% endif %}
|
2
roles/srv-web-7-7-inj-port-ui-desktop/vars/main.yml
Normal file
2
roles/srv-web-7-7-inj-port-ui-desktop/vars/main.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
inj_port_ui_file_name: "iframe-handler.js"
|
||||
inj_port_ui_js_destination: "{{ [ nginx.directories.data.cdn, inj_port_ui_file_name ] | path_join }}"
|
@@ -11,4 +11,4 @@
|
||||
|
||||
- name: "Set CAA records for all base domains"
|
||||
include_tasks: 01_set-caa-records.yml
|
||||
when: dns_provider == 'cloudflare'
|
||||
when: DNS_PROVIDER == 'cloudflare'
|
@@ -1,14 +1,14 @@
|
||||
---
|
||||
|
||||
- name: "Validate certbot_dns_api_token"
|
||||
- name: "Validate CERTBOT_DNS_API_TOKEN"
|
||||
fail:
|
||||
msg: >
|
||||
The variable "certbot_dns_api_token" must be defined and cannot be empty!
|
||||
when: (certbot_dns_api_token | default('') | trim) == ''
|
||||
The variable "CERTBOT_DNS_API_TOKEN" must be defined and cannot be empty!
|
||||
when: (CERTBOT_DNS_API_TOKEN | default('') | trim) == ''
|
||||
|
||||
- name: "Ensure all CAA records are present"
|
||||
community.general.cloudflare_dns:
|
||||
api_token: "{{ certbot_dns_api_token }}"
|
||||
api_token: "{{ CERTBOT_DNS_API_TOKEN }}"
|
||||
zone: "{{ item.0 }}"
|
||||
record: "@"
|
||||
type: CAA
|
||||
@@ -19,4 +19,6 @@
|
||||
state: present
|
||||
loop: "{{ base_sld_domains | product(caa_entries) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.0 }} → {{ item.1.tag }}"
|
||||
label: "{{ item.0 }} → {{ item.1.tag }}"
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
@@ -9,7 +9,7 @@ server
|
||||
#letsencrypt
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
root {{ letsencrypt_webroot_path }};
|
||||
root {{ LETSENCRYPT_WEBROOT_PATH }};
|
||||
default_type "text/plain";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
ssl_certificate {{ [ letsencrypt_live_path, ssl_cert_folder] | path_join }}/fullchain.pem;
|
||||
ssl_certificate_key {{ [ letsencrypt_live_path, ssl_cert_folder] | path_join }}/privkey.pem;
|
||||
ssl_trusted_certificate {{ [ letsencrypt_live_path, ssl_cert_folder] | path_join }}/chain.pem;
|
||||
ssl_certificate {{ [ LETSENCRYPT_LIVE_PATH, ssl_cert_folder] | path_join }}/fullchain.pem;
|
||||
ssl_certificate_key {{ [ LETSENCRYPT_LIVE_PATH, ssl_cert_folder] | path_join }}/privkey.pem;
|
||||
ssl_trusted_certificate {{ [ LETSENCRYPT_LIVE_PATH, ssl_cert_folder] | path_join }}/chain.pem;
|
@@ -18,8 +18,8 @@
|
||||
uidNumber: "{{ item.value.uid | int }}"
|
||||
gidNumber: "{{ item.value.gid | int }}"
|
||||
state: present # ↳ creates but never updates
|
||||
async: 60
|
||||
poll: 0
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
loop: "{{ users | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
@@ -37,8 +37,8 @@
|
||||
objectClass: "{{ ldap.user.objects.structural }}"
|
||||
mail: "{{ item.value.email }}"
|
||||
state: exact
|
||||
async: 60
|
||||
poll: 0
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
loop: "{{ users | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
@@ -21,8 +21,8 @@
|
||||
attributes:
|
||||
objectClass: "{{ missing_auxiliary }}"
|
||||
state: present
|
||||
async: 60
|
||||
poll: 0
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
loop: "{{ ldap_users_with_classes.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.dn }}"
|
||||
|
@@ -2,5 +2,8 @@
|
||||
- name: "For '{{ application_id }}': Load docker-compose"
|
||||
include_role:
|
||||
name: docker-compose
|
||||
vars:
|
||||
docker_compose_flush_handlers: true
|
||||
docker_pull_git_repository: false # Deactivated here to deactivate inhirement
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_svc_prx_openresty is not defined
|
@@ -10,6 +10,6 @@
|
||||
- {{ nginx.directories.configuration }}:{{ nginx.directories.configuration }}:ro
|
||||
- {{ nginx.directories.data.www }}:{{ nginx.directories.data.www }}:ro
|
||||
- {{ nginx.directories.data.well_known }}:{{ nginx.directories.data.well_known }}:ro
|
||||
- {{ letsencrypt_webroot_path }}:{{ letsencrypt_webroot_path }}:ro
|
||||
- {{ letsencrypt_base_path }}:{{ letsencrypt_base_path }}:ro
|
||||
- {{ LETSENCRYPT_WEBROOT_PATH }}:{{ LETSENCRYPT_WEBROOT_PATH }}:ro
|
||||
- {{ LETSENCRYPT_BASE_PATH }}:{{ LETSENCRYPT_BASE_PATH }}:ro
|
||||
command: ["openresty", "-g", "daemon off;"]
|
@@ -8,7 +8,3 @@ database_type: ""
|
||||
openresty_image: "openresty/openresty"
|
||||
openresty_version: "alpine"
|
||||
openresty_container: "{{ applications | get_app_conf(application_id, 'docker.services.openresty.name', True) }}"
|
||||
|
||||
# Docker
|
||||
docker_compose_flush_handlers: true
|
||||
docker_pull_git_repository: false # Deactivated here to don't inhire this
|
@@ -12,7 +12,7 @@
|
||||
|
||||
- name: "reset (if enabled)"
|
||||
include_tasks: 03_reset.yml
|
||||
when: mode_reset | bool
|
||||
when: MODE_RESET | bool
|
||||
|
||||
- name: configure sys-bkp-docker-2-loc-everything.infinito.service
|
||||
template:
|
||||
|
@@ -11,7 +11,7 @@
|
||||
database_host: "{{ database_host | default('undefined') }}"
|
||||
database_username: "{{ database_username | default('undefined') }}"
|
||||
database_password: "{{ database_password | default('undefined') }}"
|
||||
when: enable_debug | bool
|
||||
when: MODE_DEBUG | bool
|
||||
|
||||
- name: "fail if not all required database variables are defined"
|
||||
fail:
|
||||
|
@@ -14,7 +14,7 @@
|
||||
vars:
|
||||
domain: "{{ item }}"
|
||||
when:
|
||||
- mode_cleanup | bool
|
||||
- MODE_CLEANUP | bool
|
||||
|
||||
## The revoking just works for the base domain
|
||||
#- name: "Revoke Certbot certificate for {{ item }}"
|
||||
@@ -25,7 +25,7 @@
|
||||
# loop_control:
|
||||
# label: "{{ item }}"
|
||||
# when:
|
||||
# - mode_cleanup | bool
|
||||
# - MODE_CLEANUP | bool
|
||||
# - run_once_sys_cln_domains is not defined
|
||||
# register: certbot_revoke_result
|
||||
# failed_when: >
|
||||
@@ -43,7 +43,7 @@
|
||||
# loop_control:
|
||||
# label: "{{ item }}"
|
||||
# when:
|
||||
# - mode_cleanup | bool
|
||||
# - MODE_CLEANUP | bool
|
||||
# - run_once_sys_cln_domains is not defined
|
||||
# register: certbot_delete_result
|
||||
# failed_when: >
|
||||
|
@@ -4,7 +4,7 @@ This Ansible role handles resetting and cleaning up “Infinito.Nexus” systemd
|
||||
|
||||
## Description
|
||||
|
||||
When enabled via the `mode_reset` flag, this role will:
|
||||
When enabled via the `MODE_RESET` flag, this role will:
|
||||
|
||||
1. Run its reset tasks exactly once per play (`run_once_sys_rst_daemon` guard).
|
||||
2. Find all `/etc/systemd/system/*.infinito.service` units.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
- name: "reset (if enabled)"
|
||||
include_tasks: reset.yml
|
||||
when: mode_reset | bool and run_once_sys_rst_daemon is not defined
|
||||
when: MODE_RESET | bool and run_once_sys_rst_daemon is not defined
|
||||
|
||||
- name: run {{ role_name }} once
|
||||
set_fact:
|
||||
|
@@ -19,7 +19,7 @@ This Ansible role configures the OpenSSH daemon (`sshd`) by deploying a template
|
||||
- **Security Defaults**
|
||||
- Disables password (`PasswordAuthentication no`) and root login (`PermitRootLogin no`)
|
||||
- Enforces public-key authentication (`PubkeyAuthentication yes`)
|
||||
- Conditionally sets `LogLevel` to `DEBUG3` when `enable_debug` is true
|
||||
- Conditionally sets `LogLevel` to `DEBUG3` when `MODE_DEBUG` is true
|
||||
|
||||
- **Systemd Integration**
|
||||
Handles daemon reload and service restart seamlessly on configuration changes.
|
||||
|
@@ -25,7 +25,7 @@
|
||||
|
||||
# Logging
|
||||
#SyslogFacility AUTH
|
||||
LogLevel {% if enable_debug | bool %}DEBUG3{% else %}INFO{% endif %}
|
||||
LogLevel {% if MODE_DEBUG | bool %}DEBUG3{% else %}INFO{% endif %}
|
||||
|
||||
# Authentication:
|
||||
|
||||
|
@@ -9,7 +9,7 @@ This role configures a systemd timer to periodically start a corresponding servi
|
||||
Optimized for automated task scheduling in a [systemd](https://en.wikipedia.org/wiki/Systemd) environment, this role:
|
||||
- Generates a timer unit file for a given service (using the `service_name` variable).
|
||||
- Reloads and restarts the timer using systemd to ensure that changes take effect.
|
||||
- Supports dynamic configuration of scheduling parameters via variables like `on_calendar` and `randomized_delay_sec`.
|
||||
- Supports dynamic configuration of scheduling parameters via variables like `on_calendar` and `RANDOMIZED_DELAY_SEC`.
|
||||
|
||||
## Purpose
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
|
||||
- name: "reset (if enabled)"
|
||||
include_tasks: 01_reset.yml
|
||||
when: mode_reset | bool and run_once_sys_timer is not defined
|
||||
when: MODE_RESET | bool and run_once_sys_timer is not defined
|
||||
|
||||
- name: run {{ role_name }} once
|
||||
set_fact:
|
||||
@@ -20,5 +20,5 @@
|
||||
name: "{{ sys_timer_file }}"
|
||||
state: restarted
|
||||
enabled: yes
|
||||
when: dummy_timer.changed or activate_all_timers | bool
|
||||
when: dummy_timer.changed or ACTIVATE_ALL_TIMERS | bool
|
||||
|
||||
|
@@ -3,7 +3,7 @@ Description=Timer to start {{service_name}}.infinito.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
RandomizedDelaySec={{RANDOMIZED_DELAY_SEC}}
|
||||
Persistent={{ persistent | default('false') }}
|
||||
|
||||
[Install]
|
||||
|
@@ -8,7 +8,7 @@
|
||||
name: sys-bkp-docker-2-loc-everything.infinito.service
|
||||
state: started
|
||||
when:
|
||||
- mode_backup | bool
|
||||
- MODE_BACKUP | bool
|
||||
|
||||
- name: create {{update_docker_script}}
|
||||
template:
|
||||
|
@@ -149,7 +149,7 @@ def update_mastodon():
|
||||
Runs the database migration for Mastodon to ensure all required tables are up to date.
|
||||
"""
|
||||
print("Starting Mastodon database migration.")
|
||||
run_command("docker compose exec -T web bash -c 'RAILS_ENV={{ INFINITO_ENVIRONMENT | lower }} bin/rails db:migrate'")
|
||||
run_command("docker compose exec -T web bash -c 'RAILS_ENV={{ ENVIRONMENT | lower }} bin/rails db:migrate'")
|
||||
print("Mastodon database migration complete.")
|
||||
|
||||
def upgrade_listmonk():
|
||||
|
@@ -2,7 +2,7 @@ users:
|
||||
administrator:
|
||||
description: "System Administrator"
|
||||
username: "administrator"
|
||||
email: "administrator@{{ primary_domain }}"
|
||||
email: "administrator@{{ PRIMARY_DOMAIN }}"
|
||||
password: "{{ ansible_become_password }}"
|
||||
uid: 1001
|
||||
gid: 1001
|
||||
|
@@ -2,10 +2,10 @@
|
||||
users:
|
||||
sld:
|
||||
description: "Auto Generated Account to reserve the SLD"
|
||||
username: "{{ primary_domain.split('.')[0] }}"
|
||||
username: "{{ PRIMARY_DOMAIN.split('.')[0] }}"
|
||||
tld:
|
||||
description: "Auto Generated Account to reserve the TLD"
|
||||
username: "{{ primary_domain.split('.')[1] }}"
|
||||
username: "{{ PRIMARY_DOMAIN.split('.')[1] }}"
|
||||
root:
|
||||
username: root
|
||||
uid: 0
|
||||
|
@@ -1,5 +1,5 @@
|
||||
company:
|
||||
name: "Akaunting on {{ primary_domain | upper }}" # @todo load automatic based on service_provider infos, this will fail
|
||||
name: "Akaunting on {{ PRIMARY_DOMAIN | upper }}" # @todo load automatic based on service_provider infos, this will fail
|
||||
email: "{{ users.administrator.email }}" # @todo load automatic based on service_provider infos, this will fail
|
||||
setup_admin_email: "{{ users.administrator.email }}"
|
||||
features:
|
||||
@@ -11,7 +11,7 @@ features:
|
||||
server:
|
||||
domains:
|
||||
canonical:
|
||||
- "accounting.{{ primary_domain }}"
|
||||
- "accounting.{{ PRIMARY_DOMAIN }}"
|
||||
docker:
|
||||
services:
|
||||
database:
|
||||
|
@@ -16,4 +16,4 @@ docker:
|
||||
server:
|
||||
domains:
|
||||
canonical:
|
||||
- "tickets.{{ primary_domain }}"
|
||||
- "tickets.{{ PRIMARY_DOMAIN }}"
|
||||
|
@@ -1,8 +1,4 @@
|
||||
---
|
||||
- name: "For '{{ application_id }}': load docker and db"
|
||||
include_role:
|
||||
name: cmp-db-docker
|
||||
|
||||
- name: "For '{{ application_id }}': include role to receive certs & do modification routines"
|
||||
include_role:
|
||||
name: srv-web-7-6-composer
|
||||
@@ -12,6 +8,12 @@
|
||||
loop:
|
||||
- "{{ domains | get_domain('web-app-mailu') }}"
|
||||
- "{{ domain }}"
|
||||
|
||||
- name: "For '{{ application_id }}': load docker and db"
|
||||
include_role:
|
||||
name: cmp-db-docker
|
||||
vars:
|
||||
docker_compose_flush_handlers: true
|
||||
|
||||
- name: "For '{{ application_id }}': configure {{domains | get_domain(application_id)}}.conf"
|
||||
template:
|
||||
|
@@ -21,4 +21,4 @@ docker:
|
||||
server:
|
||||
domains:
|
||||
canonical:
|
||||
- baserow.{{ primary_domain }}
|
||||
- baserow.{{ PRIMARY_DOMAIN }}
|
||||
|
@@ -18,7 +18,7 @@ server:
|
||||
unsafe-inline: true
|
||||
domains:
|
||||
canonical:
|
||||
- "meet.{{ primary_domain }}"
|
||||
- "meet.{{ PRIMARY_DOMAIN }}"
|
||||
credentials: {}
|
||||
|
||||
docker:
|
||||
|
@@ -1,11 +1,26 @@
|
||||
---
|
||||
- name: Render HTML-Location-Block in Variable
|
||||
set_fact:
|
||||
proxy_extra_configuration: >-
|
||||
{{ lookup('ansible.builtin.template',
|
||||
playbook_dir ~ '/roles/srv-proxy-7-4-core/templates/location/html.conf.j2') | trim }}
|
||||
vars:
|
||||
location: '^~ /html5client'
|
||||
oauth2_proxy_enabled: false
|
||||
proxy_lua_enabled: false
|
||||
|
||||
- name: "load docker, proxy for '{{application_id}}'"
|
||||
include_role:
|
||||
name: cmp-docker-proxy
|
||||
|
||||
vars:
|
||||
docker_compose_flush_handlers: false
|
||||
- name: "include 04_seed-database-to-backup.yml"
|
||||
include_tasks: "{{ playbook_dir }}/roles/sys-bkp-docker-2-loc/tasks/04_seed-database-to-backup.yml"
|
||||
|
||||
- name: "Unset 'proxy_extra_configuration'"
|
||||
set_fact:
|
||||
proxy_extra_configuration: null
|
||||
|
||||
- name: configure websocket_upgrade.conf
|
||||
copy:
|
||||
src: "websocket_upgrade.conf"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
ENABLE_COTURN=true
|
||||
COTURN_TLS_CERT_PATH={{ [ letsencrypt_live_path, ssl_cert_folder] | path_join }}/fullchain.pem
|
||||
COTURN_TLS_KEY_PATH={{ [ letsencrypt_live_path, ssl_cert_folder] | path_join }}/privkey.pem
|
||||
COTURN_TLS_CERT_PATH={{ [ LETSENCRYPT_LIVE_PATH, ssl_cert_folder] | path_join }}/fullchain.pem
|
||||
COTURN_TLS_KEY_PATH={{ [ LETSENCRYPT_LIVE_PATH, ssl_cert_folder] | path_join }}/privkey.pem
|
||||
ENABLE_GREENLIGHT={{ applications | get_app_conf(application_id, 'enable_greenlight', True) }}
|
||||
|
||||
# Enable Webhooks
|
||||
|
@@ -1,3 +1,3 @@
|
||||
users:
|
||||
administrator:
|
||||
email: "administrator@{{ primary_domain }}"
|
||||
email: "administrator@{{ PRIMARY_DOMAIN }}"
|
@@ -17,4 +17,3 @@ docker_compose_skipp_file_creation: true # Handled in this role
|
||||
docker_repository_address: "{{ applications | get_app_conf(application_id, 'docker.services.bigbluebutton.repository') }}"
|
||||
docker_repository_branch: "{{ applications | get_app_conf(application_id, 'docker.services.bigbluebutton.version') }}"
|
||||
docker_pull_git_repository: true
|
||||
docker_compose_flush_handlers: false
|
@@ -11,8 +11,8 @@ features:
|
||||
server:
|
||||
domains:
|
||||
canonical:
|
||||
web: "bskyweb.{{ primary_domain }}"
|
||||
api: "bluesky.{{ primary_domain }}"
|
||||
web: "bskyweb.{{ PRIMARY_DOMAIN }}"
|
||||
api: "bluesky.{{ PRIMARY_DOMAIN }}"
|
||||
docker:
|
||||
services:
|
||||
database:
|
||||
|
@@ -24,7 +24,7 @@
|
||||
args:
|
||||
REACT_APP_PDS_URL: "{{ WEB_PROTOCOL }}://{{domains[application_id].api}}" # URL des PDS
|
||||
REACT_APP_API_URL: "{{ WEB_PROTOCOL }}://{{domains[application_id].api}}" # API-URL des PDS
|
||||
REACT_APP_SITE_NAME: "{{primary_domain | upper}} - Bluesky"
|
||||
REACT_APP_SITE_NAME: "{{PRIMARY_DOMAIN | upper}} - Bluesky"
|
||||
REACT_APP_SITE_DESCRIPTION: "Decentral Social "
|
||||
ports:
|
||||
- "127.0.0.1:{{ports.localhost.http['web-app-bluesky_web']}}:8100"
|
||||
|
@@ -3,7 +3,7 @@ PDS_ADMIN_EMAIL="{{applications.bluesky.users.administrator.email}}"
|
||||
PDS_SERVICE_DID="did:web:{{domains[application_id].api}}"
|
||||
|
||||
# See https://mattdyson.org/blog/2024/11/self-hosting-bluesky-pds/
|
||||
PDS_SERVICE_HANDLE_DOMAINS=".{{primary_domain}}"
|
||||
PDS_SERVICE_HANDLE_DOMAINS=".{{PRIMARY_DOMAIN}}"
|
||||
PDS_JWT_SECRET="{{ bluesky_jwt_secret }}"
|
||||
PDS_ADMIN_PASSWORD="{{bluesky_admin_password}}"
|
||||
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="{{ bluesky_rotation_key }}"
|
||||
|
@@ -1,3 +1,3 @@
|
||||
users:
|
||||
administrator:
|
||||
email: "administrator@{{ primary_domain }}"
|
||||
email: "administrator@{{ PRIMARY_DOMAIN }}"
|
@@ -1,7 +1,7 @@
|
||||
server:
|
||||
domains:
|
||||
canonical:
|
||||
- "collabora.{{ primary_domain }}"
|
||||
- "collabora.{{ PRIMARY_DOMAIN }}"
|
||||
docker:
|
||||
services:
|
||||
redis:
|
||||
|
@@ -16,10 +16,10 @@ server:
|
||||
unsafe-inline: true
|
||||
whitelist:
|
||||
font-src:
|
||||
- "http://*.{{primary_domain}}"
|
||||
- "http://*.{{PRIMARY_DOMAIN}}"
|
||||
domains:
|
||||
canonical:
|
||||
- "forum.{{ primary_domain }}"
|
||||
- "forum.{{ PRIMARY_DOMAIN }}"
|
||||
docker:
|
||||
services:
|
||||
database:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
- name: "reset (if enabled)"
|
||||
include_tasks: 02_reset.yml
|
||||
when: mode_reset | bool
|
||||
when: MODE_RESET | bool
|
||||
|
||||
# Necessary for building: https://chat.openai.com/share/99d258cc-294b-4924-8eef-02fe419bb838
|
||||
- name: install which
|
||||
|
@@ -3,4 +3,4 @@ features:
|
||||
server:
|
||||
domains:
|
||||
canonical:
|
||||
- elk.{{ primary_domain }}
|
||||
- elk.{{ PRIMARY_DOMAIN }}
|
||||
|
@@ -18,17 +18,17 @@ server:
|
||||
unsafe-eval: true
|
||||
whitelist:
|
||||
connect-src:
|
||||
- wss://espocrm.{{ primary_domain }}
|
||||
- wss://espocrm.{{ PRIMARY_DOMAIN }}
|
||||
- "data:"
|
||||
frame-src:
|
||||
- https://s.espocrm.com/
|
||||
domains:
|
||||
aliases:
|
||||
- "crm.{{ primary_domain }}"
|
||||
- "crm.{{ PRIMARY_DOMAIN }}"
|
||||
canonical:
|
||||
- espocrm.{{ primary_domain }}
|
||||
- espocrm.{{ PRIMARY_DOMAIN }}
|
||||
email:
|
||||
from_name: "Customer Relationship Management ({{ primary_domain }})"
|
||||
from_name: "Customer Relationship Management ({{ PRIMARY_DOMAIN }})"
|
||||
docker:
|
||||
services:
|
||||
database:
|
||||
|
@@ -30,7 +30,7 @@
|
||||
$c = $app->getContainer();
|
||||
$cfg = $c->get("config");
|
||||
$writer = $c->get("injectableFactory")->create("\Espo\Core\Utils\Config\ConfigWriter");
|
||||
$new = "{{ domains | get_url(application_id, WEB_PROTOCOL) }}";
|
||||
$new = "{{ espocrm_url }}";
|
||||
if ($cfg->get("siteUrl") !== $new) {
|
||||
$writer->set("siteUrl", $new);
|
||||
$writer->save();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user