mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-10-31 18:29:21 +00:00 
			
		
		
		
	Adapted roles to new architecture
This commit is contained in:
		
							
								
								
									
										113
									
								
								cli/build/inventory/full.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								cli/build/inventory/full.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # cli/build/inventory/full.py | ||||
|  | ||||
| import argparse | ||||
| import sys | ||||
| import os | ||||
|  | ||||
| try: | ||||
|     from filter_plugins.get_all_invokable_apps import get_all_invokable_apps | ||||
| except ImportError: | ||||
|     sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))) | ||||
|     from filter_plugins.get_all_invokable_apps import get_all_invokable_apps | ||||
|  | ||||
| import yaml | ||||
| import json | ||||
|  | ||||
| def build_group_inventory(apps, host): | ||||
|     """ | ||||
|     Builds a group-based Ansible inventory: each app is a group containing the host. | ||||
|     """ | ||||
|     groups = {app: {"hosts": [host]} for app in apps} | ||||
|     inventory = { | ||||
|         "all": { | ||||
|             "hosts": [host], | ||||
|             "children": {app: {} for app in apps}, | ||||
|         }, | ||||
|         **groups | ||||
|     } | ||||
|     return inventory | ||||
|  | ||||
| def build_hostvar_inventory(apps, host): | ||||
|     """ | ||||
|     Alternative: Builds an inventory where all invokables are set as hostvars (as a list). | ||||
|     """ | ||||
|     return { | ||||
|         "all": { | ||||
|             "hosts": [host], | ||||
|         }, | ||||
|         "_meta": { | ||||
|             "hostvars": { | ||||
|                 host: { | ||||
|                     "invokable_applications": apps | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| def main(): | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description='Build a dynamic Ansible inventory for a given host with all invokable applications.' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '--host', | ||||
|         required=True, | ||||
|         help='Hostname to assign to all invokable application groups' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-f', '--format', | ||||
|         choices=['json', 'yaml'], | ||||
|         default='yaml', | ||||
|         help='Output format (yaml [default], json)' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '--inventory-style', | ||||
|         choices=['group', 'hostvars'], | ||||
|         default='group', | ||||
|         help='Inventory style: group (default, one group per app) or hostvars (list as hostvar)' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-c', '--categories-file', | ||||
|         default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles', 'categories.yml')), | ||||
|         help='Path to roles/categories.yml (default: roles/categories.yml at project root)' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-r', '--roles-dir', | ||||
|         default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles')), | ||||
|         help='Path to roles/ directory (default: roles/ at project root)' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-o', '--output', | ||||
|         help='Write output to file instead of stdout' | ||||
|     ) | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     try: | ||||
|         apps = get_all_invokable_apps( | ||||
|             categories_file=args.categories_file, | ||||
|             roles_dir=args.roles_dir | ||||
|         ) | ||||
|     except Exception as e: | ||||
|         sys.stderr.write(f"Error: {e}\n") | ||||
|         sys.exit(1) | ||||
|  | ||||
|     # Select inventory style | ||||
|     if args.inventory_style == 'group': | ||||
|         inventory = build_group_inventory(apps, args.host) | ||||
|     else: | ||||
|         inventory = build_hostvar_inventory(apps, args.host) | ||||
|  | ||||
|     # Output in chosen format | ||||
|     if args.format == 'json': | ||||
|         output = json.dumps(inventory, indent=2) | ||||
|     else: | ||||
|         output = yaml.safe_dump(inventory, default_flow_style=False) | ||||
|  | ||||
|     if args.output: | ||||
|         with open(args.output, 'w') as f: | ||||
|             f.write(output) | ||||
|     else: | ||||
|         print(output) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										49
									
								
								cli/meta/applications/invokable.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								cli/meta/applications/invokable.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # cli/meta/applications/invokable.py | ||||
|  | ||||
| import argparse | ||||
| import sys | ||||
| import os | ||||
|  | ||||
| # Import filter plugin for get_all_invokable_apps | ||||
| try: | ||||
|     from filter_plugins.get_all_invokable_apps import get_all_invokable_apps | ||||
| except ImportError: | ||||
|     # Try to adjust sys.path if running outside Ansible | ||||
|     sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))) | ||||
|     try: | ||||
|         from filter_plugins.get_all_invokable_apps import get_all_invokable_apps | ||||
|     except ImportError: | ||||
|         sys.stderr.write("Could not import filter_plugins.get_all_invokable_apps. Check your PYTHONPATH.\n") | ||||
|         sys.exit(1) | ||||
|  | ||||
| def main(): | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description='List all invokable applications (application_ids) based on invokable paths from categories.yml and available roles.' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-c', '--categories-file', | ||||
|         default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles', 'categories.yml')), | ||||
|         help='Path to roles/categories.yml (default: roles/categories.yml at project root)' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-r', '--roles-dir', | ||||
|         default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles')), | ||||
|         help='Path to roles/ directory (default: roles/ at project root)' | ||||
|     ) | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     try: | ||||
|         result = get_all_invokable_apps( | ||||
|             categories_file=args.categories_file, | ||||
|             roles_dir=args.roles_dir | ||||
|         ) | ||||
|     except Exception as e: | ||||
|         sys.stderr.write(f"Error: {e}\n") | ||||
|         sys.exit(1) | ||||
|  | ||||
|     for app_id in result: | ||||
|         print(app_id) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										54
									
								
								filter_plugins/get_all_invokable_apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								filter_plugins/get_all_invokable_apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| import os | ||||
| import yaml | ||||
|  | ||||
| def get_all_invokable_apps( | ||||
|     categories_file=None, | ||||
|     roles_dir=None | ||||
| ): | ||||
|     """ | ||||
|     Return all application_ids (or role names) for roles whose directory names match invokable paths from categories.yml. | ||||
|     :param categories_file: Path to categories.yml (default: roles/categories.yml at project root) | ||||
|     :param roles_dir: Path to roles directory (default: roles/ at project root) | ||||
|     :return: List of application_ids (or role names) | ||||
|     """ | ||||
|     # Resolve defaults | ||||
|     here = os.path.dirname(os.path.abspath(__file__)) | ||||
|     project_root = os.path.abspath(os.path.join(here, '..')) | ||||
|     if not categories_file: | ||||
|         categories_file = os.path.join(project_root, 'roles', 'categories.yml') | ||||
|     if not roles_dir: | ||||
|         roles_dir = os.path.join(project_root, 'roles') | ||||
|  | ||||
|     # Get invokable paths | ||||
|     from filter_plugins.invokable_paths import get_invokable_paths | ||||
|     invokable_paths = get_invokable_paths(categories_file) | ||||
|     if not invokable_paths: | ||||
|         return [] | ||||
|  | ||||
|     result = [] | ||||
|     if not os.path.isdir(roles_dir): | ||||
|         return [] | ||||
|  | ||||
|     for role in sorted(os.listdir(roles_dir)): | ||||
|         role_path = os.path.join(roles_dir, role) | ||||
|         if not os.path.isdir(role_path): | ||||
|             continue | ||||
|         if any(role == p or role.startswith(p + '-') for p in invokable_paths): | ||||
|             vars_file = os.path.join(role_path, 'vars', 'main.yml') | ||||
|             if os.path.isfile(vars_file): | ||||
|                 try: | ||||
|                     with open(vars_file, 'r', encoding='utf-8') as f: | ||||
|                         data = yaml.safe_load(f) or {} | ||||
|                     app_id = data.get('application_id', role) | ||||
|                 except Exception: | ||||
|                     app_id = role | ||||
|             else: | ||||
|                 app_id = role | ||||
|             result.append(app_id) | ||||
|     return sorted(result) | ||||
|  | ||||
| class FilterModule(object): | ||||
|     def filters(self): | ||||
|         return { | ||||
|             'get_all_invokable_apps': get_all_invokable_apps | ||||
|         } | ||||
							
								
								
									
										55
									
								
								filter_plugins/get_entity_name.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								filter_plugins/get_entity_name.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| import os | ||||
| import yaml | ||||
|  | ||||
| def _load_categories_tree(categories_file): | ||||
|     with open(categories_file, 'r', encoding='utf-8') as f: | ||||
|         categories = yaml.safe_load(f)['roles'] | ||||
|     return categories | ||||
|  | ||||
| def _flatten_categories(tree, prefix=''): | ||||
|     """Flattens nested category tree to all possible category paths.""" | ||||
|     result = [] | ||||
|     for k, v in tree.items(): | ||||
|         current = f"{prefix}-{k}" if prefix else k | ||||
|         result.append(current) | ||||
|         if isinstance(v, dict): | ||||
|             for sk, sv in v.items(): | ||||
|                 if isinstance(sv, dict): | ||||
|                     result.extend(_flatten_categories({sk: sv}, current)) | ||||
|     return result | ||||
|  | ||||
| def get_entity_name(role_name): | ||||
|     """ | ||||
|     Automatically get the entity name from a role name by removing the | ||||
|     longest matching category path from categories.yml. | ||||
|     """ | ||||
|     possible_locations = [ | ||||
|         os.path.join(os.getcwd(), 'roles', 'categories.yml'), | ||||
|         os.path.join(os.path.dirname(__file__), '..', 'roles', 'categories.yml'), | ||||
|         'roles/categories.yml', | ||||
|     ] | ||||
|     categories_file = None | ||||
|     for loc in possible_locations: | ||||
|         if os.path.exists(loc): | ||||
|             categories_file = loc | ||||
|             break | ||||
|     if not categories_file: | ||||
|         return role_name | ||||
|  | ||||
|     categories_tree = _load_categories_tree(categories_file) | ||||
|     all_category_paths = _flatten_categories(categories_tree) | ||||
|  | ||||
|     role_name_lc = role_name.lower() | ||||
|     all_category_paths = [cat.lower() for cat in all_category_paths] | ||||
|     for cat in sorted(all_category_paths, key=len, reverse=True): | ||||
|         if role_name_lc.startswith(cat + "-"): | ||||
|             return role_name[len(cat) + 1:] | ||||
|         if role_name_lc == cat: | ||||
|             return "" | ||||
|     return role_name | ||||
|  | ||||
| class FilterModule(object): | ||||
|     def filters(self): | ||||
|         return { | ||||
|             'get_entity_name': get_entity_name, | ||||
|         } | ||||
| @@ -27,7 +27,7 @@ ports: | ||||
|       web-app-mediawiki: 8004 | ||||
|       web-app-mybb: 8005 | ||||
|       yourls: 8006 | ||||
|       mailu: 8007 | ||||
|       web-app-mailu: 8007 | ||||
|       web-app-elk: 8008 | ||||
|       web-app-mastodon: 8009 | ||||
|       web-app-pixelfed: 8010 | ||||
|   | ||||
| @@ -42,7 +42,7 @@ defaults_networks: | ||||
|       subnet: 192.168.101.240/28 | ||||
|     web-app-matrix: | ||||
|       subnet: 192.168.102.0/28 | ||||
|     mailu: | ||||
|     web-app-mailu: | ||||
|       # Use one of the last container ips for dns resolving so that it isn't used | ||||
|       dns: 192.168.102.29 | ||||
|       subnet: 192.168.102.16/28 | ||||
|   | ||||
| @@ -19,7 +19,7 @@ defaults_service_provider: | ||||
|     bluesky: >- | ||||
|       {{ ('@' ~ users.contact.username ~ '.' ~ domains.bluesky.api) | ||||
|          if 'bluesky' in group_names else '' }} | ||||
|     email:          "{{ users.contact.username ~ '@' ~ primary_domain if '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 '' }}" | ||||
|   | ||||
| @@ -13,9 +13,9 @@ | ||||
|       - database:/var/lib/mysql | ||||
|     healthcheck: | ||||
|       test: [ "CMD", "sh", "-c", "/usr/bin/mariadb --user=$$MYSQL_USER --password=$$MYSQL_PASSWORD --execute 'SHOW DATABASES;'" ] | ||||
|       interval: 3s | ||||
|       timeout: 1s | ||||
|       retries: 5 | ||||
|       interval: 10s | ||||
|       timeout: 5s | ||||
|       retries: 18 | ||||
|     networks: | ||||
|       - default | ||||
| {% endif %} | ||||
|   | ||||
| @@ -1,14 +1,17 @@ | ||||
| # Helper variables | ||||
| _database_id:           "svc-db-{{ database_type }}" | ||||
| _database_central_name: "{{ applications | get_app_conf( _database_id, 'docker.services.' ~ database_type ~ '.name') }}" | ||||
| _database_id:                 "svc-db-{{ database_type }}" | ||||
| _database_central_name:       "{{ applications | get_app_conf( _database_id, 'docker.services.' ~ database_type ~ '.name') }}" | ||||
| _database_consumer_public_id: "{{ database_application_id | get_public_id }}" | ||||
| _database_central_enabled:    "{{ applications | get_app_conf(database_application_id, 'features.central_database', False) }}" | ||||
|  | ||||
| # Definition | ||||
| database_name:      "{{ applications | get_app_conf( database_application_id, 'database.name', false, database_application_id | get_public_id ) }}"               # The overwritte configuration is needed by bigbluebutton | ||||
| database_instance:  "{{ _database_central_name if applications | get_app_conf(database_application_id, 'features.central_database', False) else database_name }}" # This could lead to bugs at dedicated database @todo cleanup | ||||
| database_host:      "{{ _database_central_name if applications | get_app_conf(database_application_id, 'features.central_database', False) else 'database' }}"    # This could lead to bugs at dedicated database @todo cleanup | ||||
| database_username:  "{{ applications | get_app_conf(database_application_id, 'database.username', false, database_application_id | get_public_id)}}"              # The overwritte configuration is needed by bigbluebutton | ||||
| database_name:      "{{ applications | get_app_conf( database_application_id, 'database.name', false, _database_consumer_public_id ) }}"                      # The overwritte configuration is needed by bigbluebutton | ||||
| database_instance:  "{{ _database_central_name if _database_central_enabled else database_name }}"                                                            # This could lead to bugs at dedicated database @todo cleanup | ||||
| database_host:      "{{ _database_central_name if _database_central_enabled else 'database' }}"                                                               # This could lead to bugs at dedicated database @todo cleanup | ||||
| database_username:  "{{ applications | get_app_conf(database_application_id, 'database.username', false, _database_consumer_public_id)}}"                     # The overwritte configuration is needed by bigbluebutton | ||||
| database_password:  "{{ applications | get_app_conf(database_application_id, 'credentials.database_password', true) }}" | ||||
| database_port:      "{{ ports.localhost.database[ _database_id ] }}" | ||||
| 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_full:  "{{database_type}}://{{database_username}}:{{database_password}}@{{database_host}}:{{database_port}}/{{ database_name }}" | ||||
| database_url_full:  "{{database_type}}://{{database_username}}:{{database_password}}@{{database_host}}:{{database_port}}/{{ database_name }}" | ||||
| database_volume:    "{{ _database_consumer_public_id ~ '_' if not _database_central_enabled }}{{ database_host }}" | ||||
|   | ||||
| @@ -2,5 +2,6 @@ | ||||
| {% if not applications | get_app_conf(application_id, 'features.central_database', False)%} | ||||
| volumes: | ||||
|   database: | ||||
|     name: {{ database_volume }} | ||||
| {% endif %} | ||||
| {{ "\n" }} | ||||
| @@ -2,5 +2,6 @@ | ||||
| volumes: | ||||
| {% if not applications | get_app_conf(application_id, 'features.central_database', False)%} | ||||
|   database: | ||||
|     name: {{ database_volume }} | ||||
| {% endif %} | ||||
| {{ "\n" }} | ||||
							
								
								
									
										2
									
								
								roles/docker-core/Todo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								roles/docker-core/Todo.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| # Todos | ||||
| - Add cleanup service for docker system prune -f | ||||
| @@ -24,9 +24,9 @@ | ||||
|     restart_policy: "{{ docker_restart_policy }}" | ||||
|     healthcheck: | ||||
|       test: "/usr/bin/mariadb --user=root --password={{ mariadb_root_pwd }} --execute \"SHOW DATABASES;\"" | ||||
|       interval: 3s | ||||
|       timeout: 1s | ||||
|       retries: 5 | ||||
|       interval: 10s | ||||
|       timeout: 5s | ||||
|       retries: 18 | ||||
|   when: run_once_docker_mariadb is not defined | ||||
|   register: setup_mariadb_container_result | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| {% set redis_version = applications | get_app_conf('svc-db-redis', 'docker.services.redis.version')%} | ||||
|   redis: | ||||
|     image: "{{ redis_image }}:{{ redis_version }}" | ||||
|     container_name: {{ application_id }}-redis | ||||
|     container_name: {{ application_id | get_public_id }}-redis | ||||
|     restart: {{ docker_restart_policy }} | ||||
|     logging: | ||||
|       driver: journald | ||||
|   | ||||
| @@ -43,7 +43,7 @@ for filename in os.listdir(config_path): | ||||
|         url = f"{{ web_protocol }}://{domain}" | ||||
|          | ||||
|         redirected_domains = [domain['source'] for domain in {{ current_play_domain_mappings_redirect}}] | ||||
|         redirected_domains.append("{{domains | get_domain('mailu')}}") | ||||
|         redirected_domains.append("{{domains | get_domain('web-app-mailu')}}") | ||||
|  | ||||
|         expected_statuses = get_expected_statuses(domain, parts, redirected_domains) | ||||
|  | ||||
|   | ||||
| @@ -43,7 +43,7 @@ if __name__ == "__main__": | ||||
|              | ||||
|             if os.path.isfile(docker_compose_file): | ||||
|                 print(f"Found docker-compose.yml in {dir_path}.") | ||||
|                 if dir_name == "mailu": | ||||
|                 if dir_name == "web-app-mailu": | ||||
|                     print(f"Directory {dir_name} detected. Performing hard restart...") | ||||
|                     hard_restart_docker_services(dir_path) | ||||
|                 else: | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|     domain: "{{ item }}" | ||||
|     http_port:   "{{ ports.localhost.http[application_id] }}" | ||||
|   loop: | ||||
|     - "{{ domains | get_domain('mailu') }}" | ||||
|     - "{{ domains | get_domain('web-app-mailu') }}" | ||||
|     - "{{ domain }}" | ||||
|      | ||||
| - name: "For '{{ application_id }}': configure {{domains | get_domain(application_id)}}.conf" | ||||
|   | ||||
| @@ -33,5 +33,5 @@ docker: | ||||
|       image:    "espocrm/espocrm" | ||||
|       version:  "latest" | ||||
|       name:     "espocrm" | ||||
|     volumes: | ||||
|       data: espocrm_data | ||||
|   volumes: | ||||
|     data: espocrm_data | ||||
							
								
								
									
										32
									
								
								roles/web-app-espocrm/tasks/database.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								roles/web-app-espocrm/tasks/database.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| - name: Check if config.php exists in EspoCRM | ||||
|   command: docker exec --user root {{ espocrm_name }} test -f {{ espocrm_config_file }} | ||||
|   register: config_file_exists | ||||
|   changed_when: false | ||||
|   failed_when: false | ||||
|  | ||||
| - name: Patch EspoCRM config.php with updated DB credentials | ||||
|   when: config_file_exists.rc == 0 | ||||
|   block: | ||||
|     - name: Update DB host | ||||
|       command: > | ||||
|         docker exec --user root {{ espocrm_name }} | ||||
|         sed -i "s/'host' => .*/'host' => '{{ database_host }}',/" {{ espocrm_config_file }} | ||||
|       notify: docker compose up | ||||
|  | ||||
|     - name: Update DB name | ||||
|       command: > | ||||
|         docker exec --user root {{ espocrm_name }} | ||||
|         sed -i "s/'dbname' => .*/'dbname' => '{{ database_name }}',/" {{ espocrm_config_file }} | ||||
|       notify: docker compose up | ||||
|  | ||||
|     - name: Update DB user | ||||
|       command: > | ||||
|         docker exec --user root {{ espocrm_name }} | ||||
|         sed -i "s/'user' => .*/'user' => '{{ database_username }}',/" {{ espocrm_config_file }} | ||||
|       notify: docker compose up | ||||
|  | ||||
|     - name: Update DB password | ||||
|       command: > | ||||
|         docker exec --user root {{ espocrm_name }} | ||||
|         sed -i "s/'password' => .*/'password' => '{{ database_password }}',/" {{ espocrm_config_file }} | ||||
|       notify: docker compose up | ||||
| @@ -3,6 +3,13 @@ | ||||
|   include_role:  | ||||
|     name: cmp-db-docker-proxy | ||||
|  | ||||
| - name: Update database credentials | ||||
|   include_tasks: database.yml | ||||
|  | ||||
| - name: Flush handlers to make DB available before password reset | ||||
|   meta: flush_handlers | ||||
|   when: docker_compose_flush_handlers | bool | ||||
|  | ||||
| - name: Set OIDC scopes in EspoCRM config (inside web container) | ||||
|   ansible.builtin.shell: | | ||||
|     docker compose exec -T web php -r ' | ||||
|   | ||||
| @@ -8,4 +8,5 @@ docker_compose_flush_handlers:  true | ||||
| espocrm_version:                "{{ applications | get_app_conf(application_id, 'docker.services.espocrm.version', True) }}" | ||||
| espocrm_image:                  "{{ applications | get_app_conf(application_id, 'docker.services.espocrm.image', True) }}" | ||||
| espocrm_name:                   "{{ applications | get_app_conf(application_id, 'docker.services.espocrm.name', True) }}" | ||||
| espocrm_volume:                 "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}" | ||||
| espocrm_volume:                 "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}" | ||||
| espocrm_config_file:            "/var/www/html/data/config-internal.php" | ||||
							
								
								
									
										2
									
								
								roles/web-app-mailu/Todo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								roles/web-app-mailu/Todo.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| # Todos | ||||
| - Implement hard restart into Backup for mailu | ||||
| @@ -1,4 +1,3 @@ | ||||
| version:                  "2024.06"                         # Docker Image Version | ||||
| oidc: | ||||
|   email_by_username:      true                              # If true, then the mail is set by the username. If wrong then the OIDC user email is used | ||||
|   enable_user_creation:   true                              # Users will be created if not existing | ||||
| @@ -30,4 +29,7 @@ docker: | ||||
|     redis: | ||||
|       enabled: true | ||||
|     database:  | ||||
|       enabled: true | ||||
|       enabled: true | ||||
|     mailu: | ||||
|       version:  "2024.06" # Docker Image Version | ||||
|       name:     mailu | ||||
| @@ -19,7 +19,7 @@ | ||||
|     mailu_compose_dir:        "{{ docker_compose.directories.instance }}" | ||||
|     mailu_domain:             "{{ primary_domain }}" | ||||
|     mailu_api_base_url:       "http://127.0.0.1:8080/api/v1" | ||||
|     mailu_global_api_token:   "{{ applications.mailu.credentials.api_token }}" | ||||
|     mailu_global_api_token:   "{{ applications | get_app_conf(application_id, 'credentials.api_token') }}" | ||||
|     mailu_action: >- | ||||
|       {{ | ||||
|         ( | ||||
|   | ||||
| @@ -2,13 +2,15 @@ | ||||
|  | ||||
|   # Core services | ||||
|   resolver: | ||||
|     image: {{docker_source}}/unbound:{{applications.mailu.version}} | ||||
|     image: {{docker_source}}/unbound:{{ mailu_version }} | ||||
|     container_name: {{mailu_name}}_resolver | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|         ipv4_address: {{networks.local.mailu.dns}} | ||||
|         ipv4_address: {{networks.local['web-app-mailu'].dns}} | ||||
|  | ||||
|   front: | ||||
|     image: {{docker_source}}/nginx:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_front | ||||
|     image: {{docker_source}}/nginx:{{ mailu_version }} | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
|     ports: | ||||
|       - "127.0.0.1:{{ports.localhost.http[application_id]}}:80" | ||||
| @@ -30,10 +32,11 @@ | ||||
|       webmail: | ||||
|       radicale: | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
|        | ||||
|   admin: | ||||
|     image: {{docker_source}}/admin:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_admin | ||||
|     image: {{docker_source}}/admin:{{ mailu_version }} | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
|     volumes: | ||||
|       - "admin_data:/data" | ||||
| @@ -44,11 +47,12 @@ | ||||
|       front: | ||||
|         condition: service_started | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|  | ||||
|   imap: | ||||
|     image: {{docker_source}}/dovecot:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_imap | ||||
|     image: {{docker_source}}/dovecot:{{ mailu_version }} | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
|     volumes: | ||||
|       - "dovecot_mail:/mail" | ||||
| @@ -57,11 +61,12 @@ | ||||
|       - front | ||||
|       - resolver | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|  | ||||
|   smtp: | ||||
|     image: {{docker_source}}/postfix:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_smtp | ||||
|     image: {{docker_source}}/postfix:{{ mailu_version }} | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
|     volumes: | ||||
|       - "{{docker_compose.directories.volumes}}overrides:/overrides:ro" | ||||
| @@ -70,22 +75,24 @@ | ||||
|       - front | ||||
|       - resolver | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|  | ||||
|   oletools: | ||||
|     image: {{docker_source}}/oletools:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_oletools | ||||
|     image: {{docker_source}}/oletools:{{ mailu_version }} | ||||
|     hostname: oletools | ||||
|     restart: {{docker_restart_policy}} | ||||
|     depends_on: | ||||
|       - resolver | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|       noinet: | ||||
|  | ||||
|   antispam: | ||||
|     image: {{docker_source}}/rspamd:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_antispam | ||||
|     image: {{docker_source}}/rspamd:{{ mailu_version }} | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
|     volumes: | ||||
|       - "filter:/var/lib/rspamd" | ||||
| @@ -97,13 +104,14 @@ | ||||
|       - antivirus | ||||
|       - resolver | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|       noinet:   | ||||
|  | ||||
|  | ||||
|   # Optional services | ||||
|   antivirus: | ||||
|     container_name: {{mailu_name}}_antivirus | ||||
|     image: clamav/clamav-debian:latest | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
|     volumes: | ||||
| @@ -111,23 +119,25 @@ | ||||
|     depends_on: | ||||
|       - resolver | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|  | ||||
|   webdav: | ||||
|     image: {{docker_source}}/radicale:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_webdav | ||||
|     image: {{docker_source}}/radicale:{{ mailu_version }} | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
|     volumes: | ||||
|       - "webdav_data:/data" | ||||
|     depends_on: | ||||
|       - resolver | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|       radicale: | ||||
|  | ||||
|   fetchmail: | ||||
|     image: {{docker_source}}/fetchmail:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_fetchmail | ||||
|     image: {{docker_source}}/fetchmail:{{ mailu_version }} | ||||
|     volumes: | ||||
|       - "admin_data:/data" | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
| @@ -137,11 +147,12 @@ | ||||
|       - imap | ||||
|       - resolver | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|  | ||||
|   webmail: | ||||
|     image: {{docker_source}}/webmail:{{applications.mailu.version}} | ||||
|     container_name: {{mailu_name}}_webmail | ||||
|     image: {{docker_source}}/webmail:{{ mailu_version }} | ||||
| {% include 'roles/docker-container/templates/base.yml.j2' %} | ||||
|     volumes: | ||||
|       - "webmail_data:/data" | ||||
| @@ -151,19 +162,27 @@ | ||||
|       - front | ||||
|       - resolver | ||||
|     dns: | ||||
|       - {{networks.local.mailu.dns}} | ||||
|       - {{networks.local['web-app-mailu'].dns}} | ||||
| {% include 'roles/docker-container/templates/networks.yml.j2' %} | ||||
|       webmail: | ||||
|  | ||||
| {% include 'roles/docker-compose/templates/volumes.yml.j2' %} | ||||
|   smtp_queue: | ||||
|     name: {{ mailu_smtp_queue }} | ||||
|   admin_data: | ||||
|     name: {{ mailu_admin_data }} | ||||
|   webdav_data: | ||||
|     name: {{ mailu_webdav_data }} | ||||
|   webmail_data: | ||||
|     name: {{ mailu_webmail_data }} | ||||
|   filter: | ||||
|     name: {{ mailu_filter }} | ||||
|   dkim: | ||||
|     name: {{ mailu_dkim }} | ||||
|   dovecot_mail: | ||||
|     name: {{ mailu_dovecot_mail }} | ||||
|   redis: | ||||
|     name: {{ mailu_redis }} | ||||
|  | ||||
| {% include 'roles/docker-compose/templates/networks.yml.j2' %} | ||||
|   radicale: | ||||
|   | ||||
| @@ -11,13 +11,13 @@ | ||||
| LD_PRELOAD=/usr/lib/libhardened_malloc.so  | ||||
|  | ||||
| # Set to a randomly generated 16 bytes string | ||||
| SECRET_KEY={{applications.mailu.credentials.secret_key}} | ||||
| SECRET_KEY={{applications | get_app_conf(application_id,'credentials.secret_key')}} | ||||
|  | ||||
| # Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!) | ||||
| SUBNET={{networks.local.mailu.subnet}} | ||||
| SUBNET={{networks.local['web-app-mailu'].subnet}} | ||||
|  | ||||
| # Main mail domain | ||||
| DOMAIN={{applications.mailu.domain}} | ||||
| DOMAIN={{ applications | get_app_conf(application_id,'domain') }} | ||||
|  | ||||
| # Hostnames for this server, separated with comas | ||||
| HOSTNAMES={{domains | get_domain(application_id)}} | ||||
| @@ -151,7 +151,7 @@ SQLALCHEMY_DATABASE_URI=mysql+mysqlconnector://{{database_username}}:{{database_ | ||||
| API=true | ||||
| WEB_API=/api | ||||
| # Configures the authentication token. The minimum length is 3 characters. This token must be passed as request header to the API as authentication token. This is a mandatory setting for using the RESTful API. | ||||
| API_TOKEN={{applications.mailu.credentials.api_token}} | ||||
| API_TOKEN={{ applications | get_app_conf(application_id, 'credentials.api_token')}} | ||||
|  | ||||
|  | ||||
| # Activated https://mailu.io/master/configuration.html#advanced-settings | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| application_id:             "mailu" | ||||
| application_id:             "web-app-mailu" | ||||
|  | ||||
| # Database Configuration | ||||
| database_password:          "{{applications.mailu.credentials.database_password}}" | ||||
| database_password:          "{{ applications | get_app_conf(application_id, ' credentials.database_password') }}" | ||||
| database_type:              "mariadb" | ||||
|  | ||||
| cert_mount_directory:       "{{docker_compose.directories.volumes}}certs/" | ||||
| @@ -12,4 +12,14 @@ docker_source:             "{{ 'ghcr.io/heviat' if applications | get_app_conf(a | ||||
|  | ||||
| domain:                    "{{ domains | get_domain(application_id) }}" | ||||
| http_port:                 "{{ ports.localhost.http[application_id] }}" | ||||
| proxy_extra_configuration: "client_max_body_size 31M;" | ||||
| proxy_extra_configuration: "client_max_body_size 31M;" | ||||
| mailu_version:             "{{ applications | get_app_conf(application_id, 'docker.services.mailu.version', True) }}" | ||||
| mailu_name:                "{{ applications | get_app_conf(application_id, 'docker.services.mailu.name', True) }}" | ||||
| mailu_smtp_queue:          "mailu_smtp_queue" | ||||
| mailu_admin_data:          "mailu_admin_data" | ||||
| mailu_webdav_data:         "mailu_webdav_data" | ||||
| mailu_webmail_data:        "mailu_webmail_data" | ||||
| mailu_filter:              "mailu_filter" | ||||
| mailu_dkim:                "mailu_dkim" | ||||
| mailu_dovecot_mail:        "mailu_dovecot_mail" | ||||
| mailu_redis:               "mailu_redis" | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   include_role:  | ||||
|     name: cmp-db-docker-proxy | ||||
|  | ||||
| - name: "Update database credentials" | ||||
| - name: "Patch Matomo config.ini.php with updated DB credentials" | ||||
|   include_tasks: database.yml | ||||
|  | ||||
| - name: flush docker service | ||||
|   | ||||
| @@ -1,25 +1,16 @@ | ||||
| - name: Backup config.ini.php before patching | ||||
| - name: Update DB host | ||||
|   command: > | ||||
|     docker cp {{ matomo_name }}:{{ matomo_config }} {{ matomo_backup_file }} | ||||
|  | ||||
| - name: Patch Matomo config.ini.php with updated DB credentials | ||||
|   block: | ||||
|     - name: Update DB host | ||||
|       command: > | ||||
|         docker exec --user root {{ matomo_name }} | ||||
|         sed -i "s/^host *=.*/host = {{ database_host }}/" {{ matomo_config }} | ||||
|  | ||||
|     - name: Update DB name | ||||
|       command: > | ||||
|         docker exec --user root {{ matomo_name }} | ||||
|         sed -i "s/^dbname *=.*/dbname = {{ database_name }}/" {{ matomo_config }} | ||||
|  | ||||
|     - name: Update DB user | ||||
|       command: > | ||||
|         docker exec --user root {{ matomo_name }} | ||||
|         sed -i "s/^username *=.*/username = {{ database_username }}/" {{ matomo_config }} | ||||
|  | ||||
|     - name: Update DB password | ||||
|       command: > | ||||
|         docker exec --user root {{ matomo_name }} | ||||
|         sed -i "s/^password *=.*/password = {{ database_password }}/" {{ matomo_config }} | ||||
|     docker exec --user root {{ matomo_name }} | ||||
|     sed -i "s/^host *=.*/host = {{ database_host }}/" {{ matomo_config }} | ||||
| - name: Update DB name | ||||
|   command: > | ||||
|     docker exec --user root {{ matomo_name }} | ||||
|     sed -i "s/^dbname *=.*/dbname = {{ database_name }}/" {{ matomo_config }} | ||||
| - name: Update DB user | ||||
|   command: > | ||||
|     docker exec --user root {{ matomo_name }} | ||||
|     sed -i "s/^username *=.*/username = {{ database_username }}/" {{ matomo_config }} | ||||
| - name: Update DB password | ||||
|   command: > | ||||
|     docker exec --user root {{ matomo_name }} | ||||
|     sed -i "s/^password *=.*/password = {{ database_password }}/" {{ matomo_config }} | ||||
|   | ||||
| @@ -8,7 +8,6 @@ matomo_version:       "{{ applications | get_app_conf(application_id, 'docker.se | ||||
| matomo_image:         "{{ applications | get_app_conf(application_id, 'docker.services.matomo.image', True) }}" | ||||
| matomo_name:          "{{ applications | get_app_conf(application_id, 'docker.services.matomo.name', True) }}" | ||||
| matomo_data:          "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}" | ||||
| matomo_backup_file:   "{{ docker_compose.directories.instance }}/config.ini.php.bak" | ||||
| matomo_config:        "/var/www/html/config/config.ini.php" | ||||
|  | ||||
| # I don't know if this is still necessary | ||||
|   | ||||
| @@ -13,10 +13,6 @@ | ||||
|         state: directory | ||||
|         mode: "0755" | ||||
|  | ||||
|     - name: Copy config.php from container to host | ||||
|       command: > | ||||
|         docker cp {{ moodle_container }}:{{ moodle_config }} {{ moodle_backup_file }} | ||||
|  | ||||
| - name: Check if config.php exists | ||||
|   command: docker exec --user root {{ moodle_container }} test -f {{ moodle_config }} | ||||
|   register: config_file_exists | ||||
|   | ||||
| @@ -10,7 +10,6 @@ bitnami_user_group:             "{{ bitnami_user }}:{{ bitnami_user }}" | ||||
|  | ||||
| docker_compose_flush_handlers:  false # Wait for env update | ||||
|  | ||||
| moodle_backup_file:             "{{ docker_compose.directories.instance }}/config.ini.php.bak" | ||||
| moodle_config:                  "/bitnami/moodle/config.php" | ||||
| moodle_version:                 "{{ applications | get_app_conf(application_id, 'docker.services.moodle.version', True) }}" | ||||
| moodle_image:                   "{{ applications | get_app_conf(application_id, 'docker.services.moodle.image', True) }}" | ||||
|   | ||||
| @@ -10,9 +10,12 @@ csp: | ||||
|       - "data:" | ||||
| domains: | ||||
|   canonical: | ||||
|     nextcloud:  "cloud.{{ primary_domain }}" | ||||
|     - "cloud.{{ primary_domain }}" | ||||
|     # nextcloud:  "cloud.{{ primary_domain }}" | ||||
|     # talk:       "talk.{{ primary_domain }}" @todo needs to be activated | ||||
| docker: | ||||
|   volumes: | ||||
|     data: nextcloud_data | ||||
|   services: | ||||
|     redis: | ||||
|       enabled: true | ||||
| @@ -21,7 +24,7 @@ docker: | ||||
|     nextcloud: | ||||
|       name:     "nextcloud" | ||||
|       image:    "nextcloud" | ||||
|       version:  "latest-fpm-alpine" | ||||
|       version:  "production-fpm-alpine" | ||||
|       backup: | ||||
|         no_stop_required: true | ||||
|     proxy:  | ||||
|   | ||||
| @@ -3,16 +3,27 @@ | ||||
|     - name: Add dynamic config merging from Jinja template | ||||
|       template: | ||||
|         src: include.php.j2 | ||||
|         dest: "{{nextcloud_host_include_instructions_file}}" | ||||
|         dest: "{{ nextcloud_host_include_instructions_file }}" | ||||
|       notify: docker compose restart | ||||
|  | ||||
|     - name: Flush handlers so Nextcloud container is restarted and ready | ||||
|       meta: flush_handlers | ||||
|  | ||||
|     - name: "Wait until Nextcloud is reachable on port {{ports.localhost.http[application_id]}}" | ||||
|       wait_for: | ||||
|         host: 127.0.0.1 | ||||
|         port: "{{ports.localhost.http[application_id]}}" | ||||
|         timeout: 120 | ||||
|         delay: 2 | ||||
|         state: started | ||||
|  | ||||
|     - name: Copy include instructions to the container | ||||
|       command: > | ||||
|         docker cp {{ nextcloud_host_include_instructions_file }} {{ nextcloud_name }}:{{nextcloud_docker_include_instructions_file}} | ||||
|         docker cp {{ nextcloud_host_include_instructions_file }} {{ nextcloud_container }}:{{ nextcloud_docker_include_instructions_file }} | ||||
|  | ||||
|     - name: Append generated config to config.php only if not present | ||||
|       command: > | ||||
|         docker exec -u {{nextcloud_docker_user}} {{ nextcloud_name }} sh -c " | ||||
|         docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_container }} sh -c " | ||||
|           grep -q '{{ nextcloud_docker_config_additives_directory }}' {{ nextcloud_docker_config_file }} ||  | ||||
|           cat {{nextcloud_docker_include_instructions_file}} >> {{ nextcloud_docker_config_file }}" | ||||
|       notify: docker compose restart | ||||
|           cat {{ nextcloud_docker_include_instructions_file }} >> {{ nextcloud_docker_config_file }}" | ||||
|       notify: docker compose restart | ||||
|   | ||||
| @@ -65,7 +65,7 @@ | ||||
|  | ||||
| - name: Ensure Nextcloud administrator is in the 'admin' group | ||||
|   command: > | ||||
|     docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_name }} | ||||
|     docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_container }} | ||||
|     php occ group:adduser admin {{ nextcloud_administrator_username }} | ||||
|   register: add_admin_to_group | ||||
|   changed_when: "'Added user' in add_admin_to_group.stdout" | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  | ||||
|   application: | ||||
|     image: "{{ nextcloud_image }}:{{ nextcloud_version }}" | ||||
|     container_name: {{ nextcloud_name }} | ||||
|     container_name: {{ nextcloud_container }} | ||||
|     volumes: | ||||
|       - data:{{nextcloud_docker_work_directory}} | ||||
|       - {{nextcloud_host_config_additives_directory}}:{{nextcloud_docker_config_additives_directory}}:ro | ||||
| @@ -70,6 +70,7 @@ | ||||
|  | ||||
| {% include 'roles/docker-compose/templates/volumes.yml.j2' %} | ||||
|   data: | ||||
|     name: {{ nextcloud_volume }} | ||||
|   redis: | ||||
|  | ||||
| {% include 'roles/docker-compose/templates/networks.yml.j2' %} | ||||
|   | ||||
| @@ -20,16 +20,18 @@ nextcloud_control_node_plugin_tasks_directory:  "{{role_path}}/tasks/plugins/" | ||||
| # Host  | ||||
|  | ||||
| ## Host Paths | ||||
| nextcloud_host_config_additives_directory:      "{{docker_compose.directories.volumes}}cymais/"               # This folder is the path to which the additive configurations will be copied | ||||
| nextcloud_host_include_instructions_file:       "{{docker_compose.directories.volumes}}includes.php"          # Path to the instruction file on the host. Responsible for loading the additional configurations | ||||
| nextcloud_host_config_additives_directory:      "{{ docker_compose.directories.volumes }}cymais/"               # This folder is the path to which the additive configurations will be copied | ||||
| nextcloud_host_include_instructions_file:       "{{ docker_compose.directories.volumes }}includes.php"          # Path to the instruction file on the host. Responsible for loading the additional configurations | ||||
|  | ||||
| nextcloud_domains:                              "{{ domains[application_id].nextcloud }}" | ||||
| nextcloud_domains:                              "{{ domains | get_domain(application_id) }}"                    # This is wrong and should be optimized @todo implement support for multiple domains | ||||
|  | ||||
| # Docker | ||||
|  | ||||
| nextcloud_volume:                               "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}" | ||||
|  | ||||
| nextcloud_version:                              "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.version', True) }}" | ||||
| nextcloud_image:                                "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.image', True) }}" | ||||
| nextcloud_name:                                 "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.name', True) }}" | ||||
| nextcloud_container:                                 "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.name', True) }}" | ||||
|  | ||||
| nextcloud_proxy_name:                           "{{ applications | get_app_conf(application_id, 'docker.services.proxy.name', True) }}" | ||||
| nextcloud_proxy_image:                          "{{ applications | get_app_conf(application_id, 'docker.services.proxy.image', True) }}" | ||||
| @@ -58,5 +60,5 @@ nextcloud_docker_config_additives_directory:    "{{nextcloud_docker_config_direc | ||||
| nextcloud_docker_include_instructions_file:     "/tmp/includes.php"                                           # Path to the temporary file which will be included to the config.php to load the additional configurations | ||||
|  | ||||
| ## Execution | ||||
| nextcloud_docker_exec:                          "docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_name }}" # General execute composition | ||||
| nextcloud_docker_exec:                          "docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_container }}" # General execute composition | ||||
| nextcloud_docker_exec_occ:                      "{{nextcloud_docker_exec}} {{ nextcloud_docker_work_directory }}occ"                            # Execute docker occ command | ||||
| @@ -3,7 +3,7 @@ followus: | ||||
|   description: Follow us to stay up to recieve the newest CyMaIS updates  | ||||
|   icon: | ||||
|     class: fas fa-newspaper | ||||
| {% if ["mastodon", "bluesky"] | any_in(group_names) %} | ||||
| {% if ["web-app-mastodon", "web-app-bluesky"] | any_in(group_names) %} | ||||
|   children: | ||||
| {% if service_provider.contact.mastodon is defined and service_provider.contact.mastodon != "" %} | ||||
|     - name: Mastodon | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| - name: "Include role srv-proxy-6-6-domain for {{ application_id }}" | ||||
|   include_role: | ||||
|     name: srv-proxy-6-6-domain | ||||
|   loop: "{{ applications | get_app_conf(application_id, 'domain', True)s.canonical }}" | ||||
|   loop: "{{ applications | get_app_conf(application_id, 'domains.canonical', True) }}" | ||||
|   loop_control: | ||||
|     loop_var: domain | ||||
|   vars: | ||||
|   | ||||
							
								
								
									
										118
									
								
								tests/unit/filter_plugins/test_get_all_invokable_apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								tests/unit/filter_plugins/test_get_all_invokable_apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| import os | ||||
| import shutil | ||||
| import tempfile | ||||
| import yaml | ||||
| import unittest | ||||
|  | ||||
| from filter_plugins.get_all_invokable_apps import get_all_invokable_apps | ||||
|  | ||||
| class TestGetAllInvokableApps(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         """Create a temporary roles/ directory with categories.yml and some example roles.""" | ||||
|         self.test_dir = tempfile.mkdtemp(prefix="invokable_apps_test_") | ||||
|         self.roles_dir = os.path.join(self.test_dir, "roles") | ||||
|         os.makedirs(self.roles_dir, exist_ok=True) | ||||
|         self.categories_file = os.path.join(self.roles_dir, "categories.yml") | ||||
|  | ||||
|         # Write a categories.yml with nested invokable/non-invokable paths | ||||
|         categories = { | ||||
|             "roles": { | ||||
|                 "web": { | ||||
|                     "title": "Web", | ||||
|                     "invokable": False, | ||||
|                     "app": { | ||||
|                         "title": "Applications", | ||||
|                         "invokable": True | ||||
|                     }, | ||||
|                     "svc": { | ||||
|                         "title": "Services", | ||||
|                         "invokable": False | ||||
|                     } | ||||
|                 }, | ||||
|                 "update": { | ||||
|                     "title": "Update", | ||||
|                     "invokable": True | ||||
|                 }, | ||||
|                 "util": { | ||||
|                     "title": "Utils", | ||||
|                     "invokable": False, | ||||
|                     "desk": { | ||||
|                         "title": "Desktop Utils", | ||||
|                         "invokable": True | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         with open(self.categories_file, 'w') as f: | ||||
|             yaml.safe_dump(categories, f) | ||||
|  | ||||
|         # Create roles: some should match invokable paths, some shouldn't | ||||
|         roles = [ | ||||
|             ('web-app-nextcloud', 'web-app-nextcloud'), | ||||
|             ('web-app-matomo', 'matomo-app'),   # application_id differs | ||||
|             ('web-svc-nginx', None),            # should NOT match any invokable path | ||||
|             ('update', None),                   # exact match to invokable path | ||||
|             ('util-desk-custom', None)          # matches util-desk | ||||
|         ] | ||||
|         for rolename, appid in roles: | ||||
|             role_dir = os.path.join(self.roles_dir, rolename) | ||||
|             os.makedirs(os.path.join(role_dir, 'vars'), exist_ok=True) | ||||
|             vars_path = os.path.join(role_dir, 'vars', 'main.yml') | ||||
|             data = {} | ||||
|             if appid: | ||||
|                 data['application_id'] = appid | ||||
|             with open(vars_path, 'w') as f: | ||||
|                 yaml.safe_dump(data, f) | ||||
|  | ||||
|     def tearDown(self): | ||||
|         """Clean up the temporary test directory after each test.""" | ||||
|         shutil.rmtree(self.test_dir) | ||||
|  | ||||
|     def test_get_all_invokable_apps(self): | ||||
|         """Should return only applications whose role paths match invokable paths.""" | ||||
|         result = get_all_invokable_apps( | ||||
|             categories_file=self.categories_file, | ||||
|             roles_dir=self.roles_dir | ||||
|         ) | ||||
|         expected = sorted([ | ||||
|             'web-app-nextcloud',  # application_id from role | ||||
|             'matomo-app',         # application_id from role | ||||
|             'update',             # role directory name | ||||
|             'util-desk-custom'    # role directory name | ||||
|         ]) | ||||
|         self.assertEqual(sorted(result), expected) | ||||
|  | ||||
|     def test_empty_when_no_invokable(self): | ||||
|         """Should return an empty list if there are no invokable paths in categories.yml.""" | ||||
|         with open(self.categories_file, 'w') as f: | ||||
|             yaml.safe_dump({"roles": {"foo": {"invokable": False}}}, f) | ||||
|         result = get_all_invokable_apps( | ||||
|             categories_file=self.categories_file, | ||||
|             roles_dir=self.roles_dir | ||||
|         ) | ||||
|         self.assertEqual(result, []) | ||||
|  | ||||
|     def test_empty_when_no_roles(self): | ||||
|         """Should return an empty list if there are no roles, but categories.yml exists.""" | ||||
|         shutil.rmtree(self.roles_dir) | ||||
|         os.makedirs(self.roles_dir, exist_ok=True) | ||||
|         # Recreate categories.yml after removing roles_dir | ||||
|         with open(self.categories_file, 'w') as f: | ||||
|             yaml.safe_dump({"roles": {"web": {"app": {"invokable": True}}}}, f) | ||||
|         result = get_all_invokable_apps( | ||||
|             categories_file=self.categories_file, | ||||
|             roles_dir=self.roles_dir | ||||
|         ) | ||||
|         self.assertEqual(result, []) | ||||
|  | ||||
|     def test_error_when_no_categories_file(self): | ||||
|         """Should raise FileNotFoundError if categories.yml is missing.""" | ||||
|         os.remove(self.categories_file) | ||||
|         with self.assertRaises(FileNotFoundError): | ||||
|             get_all_invokable_apps( | ||||
|                 categories_file=self.categories_file, | ||||
|                 roles_dir=self.roles_dir | ||||
|             ) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
							
								
								
									
										93
									
								
								tests/unit/filter_plugins/test_get_entity_name.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								tests/unit/filter_plugins/test_get_entity_name.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| import unittest | ||||
| import tempfile | ||||
| import shutil | ||||
| import os | ||||
| import sys | ||||
| import yaml | ||||
|  | ||||
| class TestGetEntityNameFilter(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         # Create a temporary directory for roles and categories.yml | ||||
|         self.temp_dir = tempfile.mkdtemp() | ||||
|         self.roles_dir = os.path.join(self.temp_dir, 'roles') | ||||
|         os.makedirs(self.roles_dir) | ||||
|         self.categories_file = os.path.join(self.roles_dir, 'categories.yml') | ||||
|  | ||||
|         # Minimal categories.yml for tests | ||||
|         categories = { | ||||
|             'roles': { | ||||
|                 'web': { | ||||
|                     'app': { | ||||
|                         'title': "Applications", | ||||
|                         'invokable': True | ||||
|                     }, | ||||
|                     'svc': { | ||||
|                         'title': "Services", | ||||
|                         'invokable': True | ||||
|                     } | ||||
|                 }, | ||||
|                 'util': { | ||||
|                     'desk': { | ||||
|                         'dev': { | ||||
|                             'title': "Dev Utilities", | ||||
|                             'invokable': True | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 'sys': { | ||||
|                     'bkp': { | ||||
|                         'title': "Backup", | ||||
|                         'invokable': True | ||||
|                     }, | ||||
|                     'hlth': { | ||||
|                         'title': "Health", | ||||
|                         'invokable': True | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         with open(self.categories_file, 'w', encoding='utf-8') as f: | ||||
|             yaml.safe_dump(categories, f, default_flow_style=False) | ||||
|  | ||||
|         # Patch working directory so plugin finds the test categories.yml | ||||
|         self._cwd = os.getcwd() | ||||
|         os.chdir(self.temp_dir) | ||||
|  | ||||
|         # Make sure filter_plugins directory is on sys.path | ||||
|         plugin_path = os.path.join(self._cwd, "filter_plugins") | ||||
|         if plugin_path not in sys.path and os.path.isdir(plugin_path): | ||||
|             sys.path.insert(0, plugin_path) | ||||
|         # Import plugin fresh each time | ||||
|         global get_entity_name | ||||
|         from filter_plugins.get_entity_name import get_entity_name | ||||
|  | ||||
|         self.get_entity_name = get_entity_name | ||||
|  | ||||
|     def tearDown(self): | ||||
|         os.chdir(self._cwd) | ||||
|         shutil.rmtree(self.temp_dir) | ||||
|  | ||||
|     def test_entity_name_web_app(self): | ||||
|         self.assertEqual(self.get_entity_name("web-app-snipe-it"), "snipe-it") | ||||
|         self.assertEqual(self.get_entity_name("web-app-nextcloud"), "nextcloud") | ||||
|         self.assertEqual(self.get_entity_name("web-svc-file"), "file") | ||||
|  | ||||
|     def test_entity_name_util_desk_dev(self): | ||||
|         self.assertEqual(self.get_entity_name("util-desk-dev-arduino"), "arduino") | ||||
|         self.assertEqual(self.get_entity_name("util-desk-dev-shell"), "shell") | ||||
|  | ||||
|     def test_entity_name_sys_bkp(self): | ||||
|         self.assertEqual(self.get_entity_name("sys-bkp-directory-validator"), "directory-validator") | ||||
|  | ||||
|     def test_entity_name_sys_hlth(self): | ||||
|         self.assertEqual(self.get_entity_name("sys-hlth-btrfs"), "btrfs") | ||||
|  | ||||
|     def test_no_category_match(self): | ||||
|         # Unknown category, should return input | ||||
|         self.assertEqual(self.get_entity_name("foobar-role"), "foobar-role") | ||||
|  | ||||
|     def test_exact_category_match(self): | ||||
|         self.assertEqual(self.get_entity_name("web-app"), "") | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     unittest.main() | ||||
		Reference in New Issue
	
	Block a user