mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-08 11:17:17 +02:00
Compare commits
19 Commits
78ee3e3c64
...
8608d89653
Author | SHA1 | Date | |
---|---|---|---|
8608d89653 | |||
a4f39ac732 | |||
9cfb8f3a60 | |||
3e5344a46c | |||
ec07d1a20b | |||
594d9417d1 | |||
dc125e4843 | |||
39a54294dd | |||
a57fe718de | |||
b6aec5fe33 | |||
de07d890dc | |||
e27f355697 | |||
790762d397 | |||
4ce681e643 | |||
55cf3d0d8e | |||
2708b67751 | |||
f477ee3731 | |||
6d70f78989 | |||
b867a52471 |
@@ -2,174 +2,45 @@
|
||||
import os
|
||||
import argparse
|
||||
import json
|
||||
import fnmatch
|
||||
import re
|
||||
from typing import Dict, Any
|
||||
|
||||
import yaml
|
||||
|
||||
from cli.build.graph import build_mappings, output_graph
|
||||
from module_utils.role_dependency_resolver import RoleDependencyResolver
|
||||
|
||||
|
||||
def find_roles(roles_dir: str):
|
||||
"""Yield (role_name, role_path) for every subfolder in roles_dir."""
|
||||
for entry in os.listdir(roles_dir):
|
||||
path = os.path.join(roles_dir, entry)
|
||||
if os.path.isdir(path):
|
||||
yield entry, path
|
||||
|
||||
|
||||
def _is_pure_jinja_var(s: str) -> bool:
|
||||
"""Check if string is exactly a single {{ var }} expression."""
|
||||
return bool(re.fullmatch(r"\s*\{\{\s*[^}]+\s*\}\}\s*", s))
|
||||
|
||||
|
||||
def _jinja_to_glob(s: str) -> str:
|
||||
"""Convert Jinja placeholders {{ ... }} into * for fnmatch."""
|
||||
pattern = re.sub(r"\{\{[^}]+\}\}", "*", s)
|
||||
pattern = re.sub(r"\*{2,}", "*", pattern)
|
||||
return pattern.strip()
|
||||
|
||||
|
||||
def _list_role_dirs(roles_dir: str) -> list[str]:
|
||||
"""Return a list of role directory names inside roles_dir."""
|
||||
return [
|
||||
d for d in os.listdir(roles_dir)
|
||||
if os.path.isdir(os.path.join(roles_dir, d))
|
||||
]
|
||||
|
||||
|
||||
def find_include_role_dependencies(role_path: str, roles_dir: str) -> set[str]:
|
||||
"""
|
||||
Scan all tasks/*.yml(.yaml) files of a role and collect include_role dependencies.
|
||||
|
||||
Rules:
|
||||
- loop/with_items with literal strings -> add those as roles
|
||||
- name contains jinja AND surrounding literals -> convert to glob and match existing roles
|
||||
- name is a pure jinja variable only -> ignore
|
||||
- name is a pure literal -> add as-is
|
||||
"""
|
||||
deps: set[str] = set()
|
||||
tasks_dir = os.path.join(role_path, "tasks")
|
||||
if not os.path.isdir(tasks_dir):
|
||||
return deps
|
||||
|
||||
candidates = []
|
||||
for root, _, files in os.walk(tasks_dir):
|
||||
for f in files:
|
||||
if f.endswith(".yml") or f.endswith(".yaml"):
|
||||
candidates.append(os.path.join(root, f))
|
||||
|
||||
all_roles = _list_role_dirs(roles_dir)
|
||||
|
||||
def add_literal_loop_items(loop_val):
|
||||
if isinstance(loop_val, list):
|
||||
for item in loop_val:
|
||||
if isinstance(item, str) and item.strip():
|
||||
deps.add(item.strip())
|
||||
|
||||
for file_path in candidates:
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
docs = list(yaml.safe_load_all(f))
|
||||
except Exception:
|
||||
# Be tolerant to any parsing issues; skip unreadable files
|
||||
continue
|
||||
|
||||
for doc in docs:
|
||||
if not isinstance(doc, list):
|
||||
continue
|
||||
for task in doc:
|
||||
if not isinstance(task, dict):
|
||||
continue
|
||||
if "include_role" not in task:
|
||||
continue
|
||||
inc = task.get("include_role")
|
||||
if not isinstance(inc, dict):
|
||||
continue
|
||||
name = inc.get("name")
|
||||
if not isinstance(name, str) or not name.strip():
|
||||
continue
|
||||
|
||||
# 1) Handle loop/with_items
|
||||
loop_val = task.get("loop", task.get("with_items"))
|
||||
if loop_val is not None:
|
||||
add_literal_loop_items(loop_val)
|
||||
# still check name for surrounding literals
|
||||
if not _is_pure_jinja_var(name):
|
||||
pattern = (
|
||||
_jinja_to_glob(name)
|
||||
if ("{{" in name and "}}" in name)
|
||||
else name
|
||||
)
|
||||
if "*" in pattern:
|
||||
for r in all_roles:
|
||||
if fnmatch.fnmatch(r, pattern):
|
||||
deps.add(r)
|
||||
continue
|
||||
|
||||
# 2) No loop: evaluate name
|
||||
if "{{" in name and "}}" in name:
|
||||
if _is_pure_jinja_var(name):
|
||||
continue # ignore pure variable
|
||||
pattern = _jinja_to_glob(name)
|
||||
if "*" in pattern:
|
||||
for r in all_roles:
|
||||
if fnmatch.fnmatch(r, pattern):
|
||||
deps.add(r)
|
||||
continue
|
||||
else:
|
||||
deps.add(pattern)
|
||||
else:
|
||||
# pure literal
|
||||
deps.add(name.strip())
|
||||
|
||||
return deps
|
||||
|
||||
|
||||
def main():
|
||||
# default roles dir is ../../roles relative to this script
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
default_roles_dir = os.path.abspath(
|
||||
os.path.join(script_dir, "..", "..", "roles")
|
||||
)
|
||||
default_roles_dir = os.path.abspath(os.path.join(script_dir, "..", "..", "roles"))
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate all graphs for each role and write meta/tree.json"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d", "--role_dir",
|
||||
default=default_roles_dir,
|
||||
help=f"Path to roles directory (default: {default_roles_dir})"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-D", "--depth",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Max recursion depth (>0) or <=0 to stop on cycle"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--output",
|
||||
choices=["yaml", "json", "console"],
|
||||
default="json",
|
||||
help="Output format"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--preview",
|
||||
action="store_true",
|
||||
help="Preview graphs to console instead of writing files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s", "--shadow-folder",
|
||||
type=str,
|
||||
default=None,
|
||||
help="If set, writes tree.json to this shadow folder instead of the role's actual meta/ folder"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="store_true",
|
||||
help="Enable verbose logging"
|
||||
)
|
||||
parser.add_argument("-d", "--role_dir", default=default_roles_dir,
|
||||
help=f"Path to roles directory (default: {default_roles_dir})")
|
||||
parser.add_argument("-D", "--depth", type=int, default=0,
|
||||
help="Max recursion depth (>0) or <=0 to stop on cycle")
|
||||
parser.add_argument("-o", "--output", choices=["yaml", "json", "console"],
|
||||
default="json", help="Output format")
|
||||
parser.add_argument("-p", "--preview", action="store_true",
|
||||
help="Preview graphs to console instead of writing files")
|
||||
parser.add_argument("-s", "--shadow-folder", type=str, default=None,
|
||||
help="If set, writes tree.json to this shadow folder instead of the role's actual meta/ folder")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging")
|
||||
|
||||
# Toggles
|
||||
parser.add_argument("--no-include-role", action="store_true", help="Do not scan include_role")
|
||||
parser.add_argument("--no-import-role", action="store_true", help="Do not scan import_role")
|
||||
parser.add_argument("--no-dependencies", action="store_true", help="Do not read meta/main.yml dependencies")
|
||||
parser.add_argument("--no-run-after", action="store_true",
|
||||
help="Do not read galaxy_info.run_after from meta/main.yml")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
@@ -179,6 +50,8 @@ def main():
|
||||
print(f"Preview mode: {args.preview}")
|
||||
print(f"Shadow folder: {args.shadow_folder}")
|
||||
|
||||
resolver = RoleDependencyResolver(args.role_dir)
|
||||
|
||||
for role_name, role_path in find_roles(args.role_dir):
|
||||
if args.verbose:
|
||||
print(f"Processing role: {role_name}")
|
||||
@@ -189,13 +62,26 @@ def main():
|
||||
max_depth=args.depth
|
||||
)
|
||||
|
||||
# add include_role dependencies from tasks
|
||||
include_deps = find_include_role_dependencies(role_path, args.role_dir)
|
||||
if include_deps:
|
||||
# Direct deps (depth=1) – getrennt erfasst für buckets
|
||||
inc_roles, imp_roles = resolver._scan_tasks(role_path)
|
||||
meta_deps = resolver._extract_meta_dependencies(role_path)
|
||||
run_after = set()
|
||||
if not args.no_run_after:
|
||||
run_after = resolver._extract_meta_run_after(role_path)
|
||||
|
||||
if any([not args.no_include_role and inc_roles,
|
||||
not args.no_import_role and imp_roles,
|
||||
not args.no_dependencies and meta_deps,
|
||||
not args.no_run_after and run_after]):
|
||||
deps_root = graphs.setdefault("dependencies", {})
|
||||
inc_list = set(deps_root.get("include_role", []))
|
||||
inc_list.update(include_deps)
|
||||
deps_root["include_role"] = sorted(inc_list)
|
||||
if not args.no_include_role and inc_roles:
|
||||
deps_root["include_role"] = sorted(inc_roles)
|
||||
if not args.no_import_role and imp_roles:
|
||||
deps_root["import_role"] = sorted(imp_roles)
|
||||
if not args.no_dependencies and meta_deps:
|
||||
deps_root["dependencies"] = sorted(meta_deps)
|
||||
if not args.no_run_after and run_after:
|
||||
deps_root["run_after"] = sorted(run_after)
|
||||
graphs["dependencies"] = deps_root
|
||||
|
||||
if args.preview:
|
||||
@@ -205,13 +91,11 @@ def main():
|
||||
output_graph(data, "console", role_name, key)
|
||||
else:
|
||||
if args.shadow_folder:
|
||||
tree_file = os.path.join(
|
||||
args.shadow_folder, role_name, "meta", "tree.json"
|
||||
)
|
||||
tree_file = os.path.join(args.shadow_folder, role_name, "meta", "tree.json")
|
||||
else:
|
||||
tree_file = os.path.join(role_path, "meta", "tree.json")
|
||||
os.makedirs(os.path.dirname(tree_file), exist_ok=True)
|
||||
with open(tree_file, "w") as f:
|
||||
with open(tree_file, "w", encoding="utf-8") as f:
|
||||
json.dump(graphs, f, indent=2)
|
||||
print(f"Wrote {tree_file}")
|
||||
|
||||
|
@@ -4,45 +4,81 @@ import os
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from module_utils.entity_name_utils import get_entity_name
|
||||
from module_utils.role_dependency_resolver import RoleDependencyResolver
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
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,
|
||||
*,
|
||||
recursive: bool = False,
|
||||
roles_base_dir: str | None = None,
|
||||
seed: Iterable[str] | None = None,
|
||||
):
|
||||
"""
|
||||
Maps applications to their canonical domains, checking for conflicts
|
||||
and ensuring all domains are valid and unique across applications.
|
||||
Build { app_id: [canonical domains...] }.
|
||||
|
||||
Rekursiv werden nur include_role, import_role und meta/main.yml:dependencies verfolgt.
|
||||
'run_after' wird hier absichtlich ignoriert.
|
||||
"""
|
||||
if not isinstance(apps, dict):
|
||||
raise AnsibleFilterError(f"'apps' must be a dict, got {type(apps).__name__}")
|
||||
|
||||
app_keys = set(apps.keys())
|
||||
seed_keys = set(seed) if seed is not None else app_keys
|
||||
|
||||
if recursive:
|
||||
roles_base_dir = roles_base_dir or os.path.join(os.getcwd(), "roles")
|
||||
if not os.path.isdir(roles_base_dir):
|
||||
raise AnsibleFilterError(
|
||||
f"roles_base_dir '{roles_base_dir}' not found or not a directory."
|
||||
)
|
||||
|
||||
resolver = RoleDependencyResolver(roles_base_dir)
|
||||
discovered_roles = resolver.resolve_transitively(
|
||||
start_roles=seed_keys,
|
||||
resolve_include_role=True,
|
||||
resolve_import_role=True,
|
||||
resolve_dependencies=True,
|
||||
resolve_run_after=False,
|
||||
max_depth=None,
|
||||
)
|
||||
# all discovered roles that actually have config entries in `apps`
|
||||
target_apps = discovered_roles & app_keys
|
||||
else:
|
||||
target_apps = seed_keys
|
||||
|
||||
result = {}
|
||||
seen_domains = {}
|
||||
|
||||
for app_id, cfg in apps.items():
|
||||
if app_id.startswith((
|
||||
"web-",
|
||||
"svc-db-" # Database services can also be exposed to the internet. It is just listening to the port, but the domain is used for port mapping
|
||||
)):
|
||||
if not isinstance(cfg, dict):
|
||||
raise AnsibleFilterError(
|
||||
f"Invalid configuration for application '{app_id}': "
|
||||
f"expected a dict, got {cfg!r}"
|
||||
for app_id in sorted(target_apps):
|
||||
cfg = apps.get(app_id)
|
||||
if cfg is None:
|
||||
continue
|
||||
if not str(app_id).startswith(("web-", "svc-db-")):
|
||||
continue
|
||||
if not isinstance(cfg, dict):
|
||||
raise AnsibleFilterError(
|
||||
f"Invalid configuration for application '{app_id}': expected dict, got {cfg!r}"
|
||||
)
|
||||
|
||||
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)
|
||||
continue
|
||||
|
||||
canonical_domains = domains_cfg['canonical']
|
||||
self._process_canonical_domains(app_id, canonical_domains, seen_domains, result)
|
||||
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)
|
||||
continue
|
||||
|
||||
canonical_domains = domains_cfg['canonical']
|
||||
self._process_canonical_domains(app_id, canonical_domains, seen_domains, result)
|
||||
|
||||
return 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}"
|
||||
if default_domain in seen_domains:
|
||||
@@ -54,40 +90,21 @@ class FilterModule(object):
|
||||
result[app_id] = [default_domain]
|
||||
|
||||
def _process_canonical_domains(self, app_id, canonical_domains, seen_domains, result):
|
||||
"""
|
||||
Process the canonical domains for an application, handling both lists and dicts,
|
||||
and ensuring each domain is unique.
|
||||
"""
|
||||
if isinstance(canonical_domains, dict):
|
||||
self._process_canonical_domains_dict(app_id, canonical_domains, seen_domains, result)
|
||||
for _, domain in canonical_domains.items():
|
||||
self._validate_and_check_domain(app_id, domain, seen_domains)
|
||||
result[app_id] = canonical_domains.copy()
|
||||
elif isinstance(canonical_domains, list):
|
||||
self._process_canonical_domains_list(app_id, canonical_domains, seen_domains, result)
|
||||
for domain in canonical_domains:
|
||||
self._validate_and_check_domain(app_id, domain, seen_domains)
|
||||
result[app_id] = list(canonical_domains)
|
||||
else:
|
||||
raise AnsibleFilterError(
|
||||
f"Unexpected type for 'server.domains.canonical' in application '{app_id}': "
|
||||
f"{type(canonical_domains).__name__}"
|
||||
)
|
||||
|
||||
def _process_canonical_domains_dict(self, app_id, domains_dict, seen_domains, result):
|
||||
"""
|
||||
Process a dictionary of canonical domains for an application.
|
||||
"""
|
||||
for name, domain in domains_dict.items():
|
||||
self._validate_and_check_domain(app_id, domain, seen_domains)
|
||||
result[app_id] = domains_dict.copy()
|
||||
|
||||
def _process_canonical_domains_list(self, app_id, domains_list, seen_domains, result):
|
||||
"""
|
||||
Process a list of canonical domains for an application.
|
||||
"""
|
||||
for domain in domains_list:
|
||||
self._validate_and_check_domain(app_id, domain, seen_domains)
|
||||
result[app_id] = list(domains_list)
|
||||
|
||||
def _validate_and_check_domain(self, app_id, domain, seen_domains):
|
||||
"""
|
||||
Validate the domain and check if it has already been assigned to another application.
|
||||
"""
|
||||
if not isinstance(domain, str) or not domain.strip():
|
||||
raise AnsibleFilterError(
|
||||
f"Invalid domain entry in 'canonical' for application '{app_id}': {domain!r}"
|
||||
|
@@ -1,8 +1,9 @@
|
||||
|
||||
# Path Variables for Key Directories and Scripts
|
||||
PATH_ADMINISTRATOR_HOME: "/home/administrator/"
|
||||
PATH_ADMINISTRATOR_SCRIPTS: "/opt/scripts/"
|
||||
PATH_SYSTEMCTL_SCRIPTS: "{{ [ PATH_ADMINISTRATOR_SCRIPTS, 'systemctl' ] | path_join }}"
|
||||
PATH_DOCKER_COMPOSE_INSTANCES: "/opt/docker/"
|
||||
PATH_SYSTEM_LOCK_SCRIPT: "/opt/scripts/sys-lock.py"
|
||||
PATH_SYSTEM_SERVICE_DIR: "/etc/systemd/system"
|
||||
PATH_ADMINISTRATOR_HOME: "/home/administrator/"
|
||||
PATH_ADMINISTRATOR_SCRIPTS: "/opt/scripts/"
|
||||
PATH_SYSTEMCTL_SCRIPTS: "{{ [ PATH_ADMINISTRATOR_SCRIPTS, 'systemctl' ] | path_join }}"
|
||||
PATH_DOCKER_COMPOSE_INSTANCES: "/opt/docker/"
|
||||
PATH_SYSTEM_LOCK_SCRIPT: "/opt/scripts/sys-lock.py"
|
||||
PATH_SYSTEM_SERVICE_DIR: "/etc/systemd/system"
|
||||
PATH_DOCKER_COMPOSE_PULL_LOCK_DIR: "/run/ansible/compose-pull/"
|
@@ -88,7 +88,7 @@ defaults_networks:
|
||||
subnet: 192.168.103.96/28
|
||||
web-svc-simpleicons:
|
||||
subnet: 192.168.103.112/28
|
||||
web-app-libretranslate:
|
||||
web-svc-libretranslate:
|
||||
subnet: 192.168.103.128/28
|
||||
web-app-pretix:
|
||||
subnet: 192.168.103.144/28
|
||||
|
@@ -66,7 +66,7 @@ ports:
|
||||
web-svc-collabora: 8042
|
||||
web-app-mobilizon: 8043
|
||||
web-svc-simpleicons: 8044
|
||||
web-app-libretranslate: 8045
|
||||
web-svc-libretranslate: 8045
|
||||
web-app-pretix: 8046
|
||||
web-app-mig: 8047
|
||||
web-svc-logout: 8048
|
||||
|
296
module_utils/role_dependency_resolver.py
Normal file
296
module_utils/role_dependency_resolver.py
Normal file
@@ -0,0 +1,296 @@
|
||||
import os
|
||||
import fnmatch
|
||||
import re
|
||||
from typing import Dict, Set, Iterable, Tuple, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class RoleDependencyResolver:
|
||||
_RE_PURE_JINJA = re.compile(r"\s*\{\{\s*[^}]+\s*\}\}\s*$")
|
||||
|
||||
def __init__(self, roles_dir: str):
|
||||
self.roles_dir = roles_dir
|
||||
|
||||
# -------------------------- public API --------------------------
|
||||
|
||||
def resolve_transitively(
|
||||
self,
|
||||
start_roles: Iterable[str],
|
||||
*,
|
||||
resolve_include_role: bool = True,
|
||||
resolve_import_role: bool = True,
|
||||
resolve_dependencies: bool = True,
|
||||
resolve_run_after: bool = False,
|
||||
max_depth: Optional[int] = None,
|
||||
) -> Set[str]:
|
||||
to_visit = list(dict.fromkeys(start_roles))
|
||||
visited: Set[str] = set()
|
||||
depth: Dict[str, int] = {}
|
||||
|
||||
for r in to_visit:
|
||||
depth[r] = 0
|
||||
|
||||
while to_visit:
|
||||
role = to_visit.pop()
|
||||
cur_d = depth.get(role, 0)
|
||||
if role in visited:
|
||||
continue
|
||||
visited.add(role)
|
||||
|
||||
if max_depth is not None and cur_d >= max_depth:
|
||||
continue
|
||||
|
||||
for dep in self.get_role_dependencies(
|
||||
role,
|
||||
resolve_include_role=resolve_include_role,
|
||||
resolve_import_role=resolve_import_role,
|
||||
resolve_dependencies=resolve_dependencies,
|
||||
resolve_run_after=resolve_run_after,
|
||||
):
|
||||
if dep not in visited:
|
||||
to_visit.append(dep)
|
||||
depth[dep] = cur_d + 1
|
||||
|
||||
return visited
|
||||
|
||||
def get_role_dependencies(
|
||||
self,
|
||||
role_name: str,
|
||||
*,
|
||||
resolve_include_role: bool = True,
|
||||
resolve_import_role: bool = True,
|
||||
resolve_dependencies: bool = True,
|
||||
resolve_run_after: bool = False,
|
||||
) -> Set[str]:
|
||||
role_path = os.path.join(self.roles_dir, role_name)
|
||||
if not os.path.isdir(role_path):
|
||||
return set()
|
||||
|
||||
deps: Set[str] = set()
|
||||
|
||||
if resolve_include_role or resolve_import_role:
|
||||
includes, imports = self._scan_tasks(role_path)
|
||||
if resolve_include_role:
|
||||
deps |= includes
|
||||
if resolve_import_role:
|
||||
deps |= imports
|
||||
|
||||
if resolve_dependencies:
|
||||
deps |= self._extract_meta_dependencies(role_path)
|
||||
|
||||
if resolve_run_after:
|
||||
deps |= self._extract_meta_run_after(role_path)
|
||||
|
||||
return deps
|
||||
|
||||
# -------------------------- scanning helpers --------------------------
|
||||
|
||||
def _scan_tasks(self, role_path: str) -> Tuple[Set[str], Set[str]]:
|
||||
tasks_dir = os.path.join(role_path, "tasks")
|
||||
include_roles: Set[str] = set()
|
||||
import_roles: Set[str] = set()
|
||||
|
||||
if not os.path.isdir(tasks_dir):
|
||||
return include_roles, import_roles
|
||||
|
||||
all_roles = self._list_role_dirs(self.roles_dir)
|
||||
|
||||
candidates = []
|
||||
for root, _, files in os.walk(tasks_dir):
|
||||
for f in files:
|
||||
if f.endswith(".yml") or f.endswith(".yaml"):
|
||||
candidates.append(os.path.join(root, f))
|
||||
|
||||
for file_path in candidates:
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
docs = list(yaml.safe_load_all(f))
|
||||
except Exception:
|
||||
inc, imp = self._tolerant_scan_file(file_path, all_roles)
|
||||
include_roles |= inc
|
||||
import_roles |= imp
|
||||
continue
|
||||
|
||||
for doc in docs or []:
|
||||
if not isinstance(doc, list):
|
||||
continue
|
||||
for task in doc:
|
||||
if not isinstance(task, dict):
|
||||
continue
|
||||
if "include_role" in task:
|
||||
include_roles |= self._extract_from_task(task, "include_role", all_roles)
|
||||
if "import_role" in task:
|
||||
import_roles |= self._extract_from_task(task, "import_role", all_roles)
|
||||
|
||||
return include_roles, import_roles
|
||||
|
||||
def _extract_from_task(self, task: dict, key: str, all_roles: Iterable[str]) -> Set[str]:
|
||||
roles: Set[str] = set()
|
||||
spec = task.get(key)
|
||||
if not isinstance(spec, dict):
|
||||
return roles
|
||||
|
||||
name = spec.get("name")
|
||||
loop_val = self._collect_loop_values(task)
|
||||
|
||||
if loop_val is not None:
|
||||
for item in self._iter_flat(loop_val):
|
||||
cand = self._role_from_loop_item(item, name_template=name)
|
||||
if cand:
|
||||
roles.add(cand)
|
||||
|
||||
if isinstance(name, str) and name.strip() and not self._is_pure_jinja_var(name):
|
||||
pattern = self._jinja_to_glob(name) if ("{{" in name and "}}" in name) else name
|
||||
self._match_glob_into(pattern, all_roles, roles)
|
||||
return roles
|
||||
|
||||
if isinstance(name, str) and name.strip():
|
||||
if "{{" in name and "}}" in name:
|
||||
if self._is_pure_jinja_var(name):
|
||||
return roles
|
||||
pattern = self._jinja_to_glob(name)
|
||||
self._match_glob_into(pattern, all_roles, roles)
|
||||
else:
|
||||
roles.add(name.strip())
|
||||
|
||||
return roles
|
||||
|
||||
def _collect_loop_values(self, task: dict):
|
||||
for k in ("loop", "with_items", "with_list", "with_flattened"):
|
||||
if k in task:
|
||||
return task[k]
|
||||
return None
|
||||
|
||||
def _iter_flat(self, value):
|
||||
if isinstance(value, list):
|
||||
for v in value:
|
||||
if isinstance(v, list):
|
||||
for x in v:
|
||||
yield x
|
||||
else:
|
||||
yield v
|
||||
|
||||
def _role_from_loop_item(self, item, name_template=None) -> Optional[str]:
|
||||
tmpl = (name_template or "").strip() if isinstance(name_template, str) else ""
|
||||
|
||||
if isinstance(item, str):
|
||||
if tmpl in ("{{ item }}", "{{item}}") or not tmpl or "item" in tmpl:
|
||||
return item.strip()
|
||||
return None
|
||||
|
||||
if isinstance(item, dict):
|
||||
for k in ("role", "name"):
|
||||
v = item.get(k)
|
||||
if isinstance(v, str) and v.strip():
|
||||
if tmpl in (f"{{{{ item.{k} }}}}", f"{{{{item.{k}}}}}") or not tmpl or "item" in tmpl:
|
||||
return v.strip()
|
||||
return None
|
||||
|
||||
def _match_glob_into(self, pattern: str, all_roles: Iterable[str], out: Set[str]):
|
||||
if "*" in pattern or "?" in pattern or "[" in pattern:
|
||||
for r in all_roles:
|
||||
if fnmatch.fnmatch(r, pattern):
|
||||
out.add(r)
|
||||
else:
|
||||
out.add(pattern)
|
||||
|
||||
def test_jinja_mixed_name_glob_matching(self):
|
||||
"""
|
||||
include_role:
|
||||
name: "prefix-{{ item }}-suffix"
|
||||
loop: [x, y]
|
||||
Existing roles: prefix-x-suffix, prefix-y-suffix, prefix-z-suffix
|
||||
|
||||
Expectation:
|
||||
- NO raw loop items ('x', 'y') end up as roles
|
||||
- Glob matching resolves to all three concrete roles
|
||||
"""
|
||||
make_role(self.roles_dir, "A")
|
||||
for rn in ["prefix-x-suffix", "prefix-y-suffix", "prefix-z-suffix"]:
|
||||
make_role(self.roles_dir, rn)
|
||||
|
||||
write(
|
||||
os.path.join(self.roles_dir, "A", "tasks", "main.yml"),
|
||||
"""
|
||||
- name: jinja-mixed glob
|
||||
include_role:
|
||||
name: "prefix-{{ item }}-suffix"
|
||||
loop:
|
||||
- x
|
||||
- y
|
||||
"""
|
||||
)
|
||||
|
||||
r = RoleDependencyResolver(self.roles_dir)
|
||||
deps = r.get_role_dependencies("A")
|
||||
|
||||
# ensure no raw loop items leak into the results
|
||||
self.assertNotIn("x", deps)
|
||||
self.assertNotIn("y", deps)
|
||||
|
||||
# only the resolved role names should be present
|
||||
self.assertEqual(
|
||||
deps,
|
||||
{"prefix-x-suffix", "prefix-y-suffix", "prefix-z-suffix"},
|
||||
)
|
||||
|
||||
|
||||
# -------------------------- meta helpers --------------------------
|
||||
|
||||
def _extract_meta_dependencies(self, role_path: str) -> Set[str]:
|
||||
deps: Set[str] = set()
|
||||
meta_main = os.path.join(role_path, "meta", "main.yml")
|
||||
if not os.path.isfile(meta_main):
|
||||
return deps
|
||||
try:
|
||||
with open(meta_main, "r", encoding="utf-8") as f:
|
||||
meta = yaml.safe_load(f) or {}
|
||||
raw_deps = meta.get("dependencies", [])
|
||||
if isinstance(raw_deps, list):
|
||||
for item in raw_deps:
|
||||
if isinstance(item, str):
|
||||
deps.add(item.strip())
|
||||
elif isinstance(item, dict):
|
||||
r = item.get("role")
|
||||
if isinstance(r, str) and r.strip():
|
||||
deps.add(r.strip())
|
||||
except Exception:
|
||||
pass
|
||||
return deps
|
||||
|
||||
def _extract_meta_run_after(self, role_path: str) -> Set[str]:
|
||||
deps: Set[str] = set()
|
||||
meta_main = os.path.join(role_path, "meta", "main.yml")
|
||||
if not os.path.isfile(meta_main):
|
||||
return deps
|
||||
try:
|
||||
with open(meta_main, "r", encoding="utf-8") as f:
|
||||
meta = yaml.safe_load(f) or {}
|
||||
galaxy_info = meta.get("galaxy_info", {})
|
||||
run_after = galaxy_info.get("run_after", [])
|
||||
if isinstance(run_after, list):
|
||||
for item in run_after:
|
||||
if isinstance(item, str) and item.strip():
|
||||
deps.add(item.strip())
|
||||
except Exception:
|
||||
pass
|
||||
return deps
|
||||
|
||||
# -------------------------- small utils --------------------------
|
||||
|
||||
def _list_role_dirs(self, roles_dir: str) -> list[str]:
|
||||
return [
|
||||
d for d in os.listdir(roles_dir)
|
||||
if os.path.isdir(os.path.join(roles_dir, d))
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _is_pure_jinja_var(cls, s: str) -> bool:
|
||||
return bool(cls._RE_PURE_JINJA.fullmatch(s or ""))
|
||||
|
||||
@staticmethod
|
||||
def _jinja_to_glob(s: str) -> str:
|
||||
pattern = re.sub(r"\{\{[^}]+\}\}", "*", s or "")
|
||||
pattern = re.sub(r"\*{2,}", "*", pattern)
|
||||
return pattern.strip()
|
@@ -8,4 +8,4 @@ This role builds on `cmp-db-docker` by adding a reverse-proxy frontend for HTTP
|
||||
Leverages the `cmp-db-docker` role to stand up your containerized database (PostgreSQL, MariaDB, etc.) with backups and user management.
|
||||
|
||||
- **Reverse Proxy**
|
||||
Includes the `srv-proxy-6-6-domain` role to configure a proxy (e.g. nginx) for routing HTTP(S) traffic to your database UI or management endpoint.
|
||||
Includes the `srv-domain-provision` role to configure a proxy (e.g. nginx) for routing HTTP(S) traffic to your database UI or management endpoint.
|
@@ -1,7 +1,7 @@
|
||||
galaxy_info:
|
||||
author: "Kevin Veen-Birkenbach"
|
||||
description: >
|
||||
Extends cmp-db-docker by adding an HTTP reverse proxy via srv-proxy-6-6-domain.
|
||||
Extends cmp-db-docker by adding an HTTP reverse proxy via srv-domain-provision.
|
||||
company: |
|
||||
Kevin Veen-Birkenbach
|
||||
Consulting & Coaching Solutions
|
||||
|
@@ -8,9 +8,9 @@
|
||||
include_role:
|
||||
name: cmp-db-docker
|
||||
|
||||
- name: "For '{{ application_id }}': include role srv-proxy-6-6-domain"
|
||||
- name: "For '{{ application_id }}': include role srv-domain-provision"
|
||||
include_role:
|
||||
name: srv-proxy-6-6-domain
|
||||
name: srv-domain-provision
|
||||
vars:
|
||||
domain: "{{ domains | get_domain(application_id) }}"
|
||||
http_port: "{{ ports.localhost.http[application_id] }}"
|
||||
|
@@ -8,4 +8,4 @@ This role combines the standard Docker Compose setup with a reverse-proxy for an
|
||||
Brings up containers, networks, and volumes via the `docker-compose` role.
|
||||
|
||||
- **Reverse Proxy**
|
||||
Uses the `srv-proxy-6-6-domain` role to expose your application under a custom domain and port.
|
||||
Uses the `srv-domain-provision` role to expose your application under a custom domain and port.
|
||||
|
@@ -1,7 +1,7 @@
|
||||
galaxy_info:
|
||||
author: "Kevin Veen-Birkenbach"
|
||||
description: >
|
||||
Combines the docker-compose role with srv-proxy-6-6-domain to
|
||||
Combines the docker-compose role with srv-domain-provision to
|
||||
deploy applications behind a reverse proxy.
|
||||
company: |
|
||||
Kevin Veen-Birkenbach
|
||||
|
@@ -1,9 +1,9 @@
|
||||
# run_once_cmp_docker_proxy: deactivated
|
||||
|
||||
# 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"
|
||||
- name: "For '{{ application_id }}': include role srv-domain-provision"
|
||||
include_role:
|
||||
name: srv-proxy-6-6-domain
|
||||
name: srv-domain-provision
|
||||
vars:
|
||||
domain: "{{ domains | get_domain(application_id) }}"
|
||||
http_port: "{{ ports.localhost.http[application_id] }}"
|
||||
|
@@ -20,7 +20,7 @@ To offer a centralized, extensible system for managing containerized application
|
||||
- **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.
|
||||
- **Integration Support:** Compatible with `srv-proxy-core` and other Infinito.Nexus service roles.
|
||||
|
||||
## Administration Tips
|
||||
|
||||
|
@@ -11,6 +11,30 @@
|
||||
- docker compose restart
|
||||
- docker compose just up
|
||||
|
||||
- name: docker compose pull
|
||||
shell: |
|
||||
set -euo pipefail
|
||||
lock="{{ [ PATH_DOCKER_COMPOSE_PULL_LOCK_DIR | docker_compose.directories.instance ] path_join | hash('sha1') }}"
|
||||
if [ ! -e "$lock" ]; then
|
||||
mkdir -p "$(dirname "$lock")"
|
||||
docker compose pull
|
||||
: > "$lock"
|
||||
echo "pulled"
|
||||
fi
|
||||
args:
|
||||
chdir: "{{ docker_compose.directories.instance }}"
|
||||
executable: /bin/bash
|
||||
register: compose_pull
|
||||
changed_when: "'pulled' in compose_pull.stdout"
|
||||
environment:
|
||||
COMPOSE_HTTP_TIMEOUT: 600
|
||||
DOCKER_CLIENT_TIMEOUT: 600
|
||||
when: MODE_UPDATE | bool
|
||||
listen:
|
||||
- docker compose up
|
||||
- docker compose restart
|
||||
- docker compose just up
|
||||
|
||||
- name: Build docker compose
|
||||
shell: |
|
||||
set -euo pipefail
|
||||
|
@@ -1,3 +1,8 @@
|
||||
- name: Remove all docker compose pull locks
|
||||
file:
|
||||
path: "{{ PATH_DOCKER_COMPOSE_PULL_LOCK_DIR }}"
|
||||
state: absent
|
||||
|
||||
- name: "Load docker container role"
|
||||
include_role:
|
||||
name: docker-container
|
||||
|
@@ -1,2 +1,2 @@
|
||||
DOCKER_COMPOSE_VARIABLE_FILE: "{{ role_path }}/vars/docker-compose.yml"
|
||||
DOCKER_COMPOSE_DOWN_ALL_PACKAGE: "docodol"
|
||||
DOCKER_COMPOSE_VARIABLE_FILE: "{{ role_path }}/vars/docker-compose.yml"
|
||||
DOCKER_COMPOSE_DOWN_ALL_PACKAGE: "docodol"
|
@@ -1,10 +1,10 @@
|
||||
# Role: srv-web-7-6-composer
|
||||
# Role: srv-composer
|
||||
|
||||
This Ansible role composes and orchestrates all necessary HTTPS-layer tasks and HTML-content injections for your webserver domains. It integrates two key sub-roles into a unified workflow:
|
||||
|
||||
1. **`sys-srv-web-inj-compose`**
|
||||
Injects global HTML snippets (CSS, Matomo tracking, iFrame notifier, custom JavaScript) into responses using Nginx `sub_filter`.
|
||||
2. **`srv-web-6-6-tls-core`**
|
||||
2. **`srv-tls-core`**
|
||||
Handles issuing, renewing, and managing TLS certificates via ACME/Certbot.
|
||||
|
||||
By combining encryption setup with content enhancements, this role streamlines domain provisioning for secure, fully-featured HTTP/HTTPS delivery.
|
||||
@@ -16,7 +16,7 @@ By combining encryption setup with content enhancements, this role streamlines d
|
||||
* **Content Injection**
|
||||
Adds global theming, analytics, and custom scripts before `</head>` and tracking noscript tags before `</body>`.
|
||||
* **Certificate Management**
|
||||
Automates cert issuance and renewal via `srv-web-6-6-tls-core`.
|
||||
Automates cert issuance and renewal via `srv-tls-core`.
|
||||
* **Idempotent Workflow**
|
||||
Ensures each component runs only once per domain.
|
||||
* **Simplified Playbooks**
|
@@ -27,4 +27,4 @@ galaxy_info:
|
||||
- orchestration
|
||||
repository: "https://s.infinito.nexus/code"
|
||||
issue_tracker_url: "https://s.infinito.nexus/issues"
|
||||
documentation: "https://s.infinito.nexus/code/roles/srv-web-7-6-composer"
|
||||
documentation: "https://s.infinito.nexus/code/roles/srv-composer"
|
9
roles/srv-composer/tasks/main.yml
Normal file
9
roles/srv-composer/tasks/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
# run_once_srv_composer: deactivated
|
||||
|
||||
- name: "include role sys-srv-web-inj-compose for '{{ domain }}'"
|
||||
include_role:
|
||||
name: sys-srv-web-inj-compose
|
||||
|
||||
- name: "include role srv-tls-core for '{{ domain }}'"
|
||||
include_role:
|
||||
name: srv-tls-core
|
@@ -18,4 +18,4 @@ galaxy_info:
|
||||
- performance
|
||||
repository: "https://s.infinito.nexus/code"
|
||||
issue_tracker_url: "https://s.infinito.nexus/issues"
|
||||
documentation: "https://s.infinito.nexus/code/roles/srv-web-7-4-core"
|
||||
documentation: "https://s.infinito.nexus/code/roles/srv-core"
|
@@ -2,4 +2,4 @@
|
||||
- block:
|
||||
- include_tasks: 01_core.yml
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_srv_web_7_4_core is not defined
|
||||
when: run_once_srv_core is not defined
|
@@ -6,11 +6,11 @@ This role bootstraps **per-domain Nginx configuration**: it requests TLS certifi
|
||||
|
||||
## Overview
|
||||
|
||||
A higher-level orchestration wrapper, *srv-proxy-6-6-domain* ties together several lower-level roles:
|
||||
A higher-level orchestration wrapper, *srv-domain-provision* ties together several lower-level roles:
|
||||
|
||||
1. **`sys-srv-web-inj-compose`** – applies global tweaks and includes.
|
||||
2. **`srv-web-6-6-tls-core`** – obtains Let’s Encrypt certificates.
|
||||
3. **Domain template deployment** – copies a Jinja2 vHost from *srv-proxy-7-4-core*.
|
||||
2. **`srv-tls-core`** – obtains Let’s Encrypt certificates.
|
||||
3. **Domain template deployment** – copies a Jinja2 vHost from *srv-proxy-core*.
|
||||
4. **`web-app-oauth2-proxy`** *(optional)* – protects the site with OAuth2.
|
||||
|
||||
The result is a complete, reproducible domain rollout in a single playbook task.
|
@@ -2,4 +2,4 @@
|
||||
vhost_flavour: "basic" # valid: basic | ws_generic
|
||||
|
||||
# build the full template path from the flavour
|
||||
vhost_template_src: "roles/srv-proxy-7-4-core/templates/vhost/{{ vhost_flavour }}.conf.j2"
|
||||
vhost_template_src: "roles/srv-proxy-core/templates/vhost/{{ vhost_flavour }}.conf.j2"
|
@@ -1,4 +1,4 @@
|
||||
# roles/srv-proxy-6-6-domain/tasks/02_enable_cf_dev_mode.yml
|
||||
# roles/srv-domain-provision/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: CLOUDFLARE_API_TOKEN
|
@@ -1,10 +1,10 @@
|
||||
- block:
|
||||
- name: Include dependency 'srv-proxy-7-4-core'
|
||||
- name: Include dependency 'srv-proxy-core'
|
||||
include_role:
|
||||
name: srv-proxy-7-4-core
|
||||
when: run_once_srv_proxy_7_4_core is not defined
|
||||
name: srv-proxy-core
|
||||
when: run_once_srv_proxy_core is not defined
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_srv_proxy_6_6_domain is not defined
|
||||
when: run_once_srv_domain_provision is not defined
|
||||
|
||||
- include_tasks: "01_cloudflare.yml"
|
||||
when: DNS_PROVIDER == "cloudflare"
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
- name: "include role for {{ domain }} to receive certificates and do the modification routines"
|
||||
include_role:
|
||||
name: srv-web-7-6-composer
|
||||
name: srv-composer
|
||||
|
||||
- name: "Copy nginx config to {{ configuration_destination }}"
|
||||
template:
|
@@ -1,23 +1,23 @@
|
||||
# Webserver HTTPS Provisioning 🚀
|
||||
|
||||
## Description
|
||||
The **srv-web-7-6-https** role extends a basic Nginx installation by wiring in everything you need to serve content over HTTPS:
|
||||
The **srv-https-stack** role extends a basic Nginx installation by wiring in everything you need to serve content over HTTPS:
|
||||
|
||||
1. Ensures your Nginx server is configured for SSL/TLS.
|
||||
2. Pulls in Let’s Encrypt ACME challenge handling.
|
||||
3. Applies global cleanup of unused domain configs.
|
||||
|
||||
This role is built on top of your existing `srv-web-7-4-core` role, and it automates the end-to-end process of turning HTTP sites into secure HTTPS sites.
|
||||
This role is built on top of your existing `srv-core` role, and it automates the end-to-end process of turning HTTP sites into secure HTTPS sites.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
When you apply **srv-web-7-6-https**, it will:
|
||||
When you apply **srv-https-stack**, it will:
|
||||
|
||||
1. **Include** the `srv-web-7-4-core` role to install and configure Nginx.
|
||||
1. **Include** the `srv-core` role to install and configure Nginx.
|
||||
2. **Clean up** any stale vHost files under `sys-svc-cln-domains`.
|
||||
3. **Deploy** the Let’s Encrypt challenge-and-redirect snippet from `srv-web-7-7-letsencrypt`.
|
||||
3. **Deploy** the Let’s Encrypt challenge-and-redirect snippet from `srv-letsencrypt`.
|
||||
4. **Reload** Nginx automatically when any template changes.
|
||||
|
||||
All tasks are idempotent—once your certificates are in place and your configuration is set, Ansible will skip unchanged steps on subsequent runs.
|
||||
@@ -42,7 +42,7 @@ All tasks are idempotent—once your certificates are in place and your configur
|
||||
|
||||
## Requirements
|
||||
|
||||
- A working `srv-web-7-4-core` setup.
|
||||
- A working `srv-core` setup.
|
||||
- DNS managed via Cloudflare (for CAA record tasks) or equivalent ACME DNS flow.
|
||||
- Variables:
|
||||
- `LETSENCRYPT_WEBROOT_PATH`
|
@@ -3,8 +3,8 @@
|
||||
include_role:
|
||||
name: '{{ item }}'
|
||||
loop:
|
||||
- srv-web-7-4-core
|
||||
- srv-core
|
||||
- sys-svc-cln-domains
|
||||
- srv-web-7-7-letsencrypt
|
||||
- srv-letsencrypt
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_srv_web_7_6_https is not defined
|
||||
when: run_once_srv_https_stack is not defined
|
@@ -1,4 +1,4 @@
|
||||
- block:
|
||||
- include_tasks: 01_core.yml
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_srv_web_7_7_letsencrypt is not defined
|
||||
when: run_once_srv_letsencrypt is not defined
|
@@ -12,4 +12,4 @@ ssl_session_tickets on;
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_credentials.j2' %}
|
||||
{% include 'roles/srv-letsencrypt/templates/ssl_credentials.j2' %}
|
@@ -16,7 +16,7 @@ The goal of this role is to deliver a **hassle-free, production-ready reverse pr
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic TLS & HSTS** — integrates with the *srv-web-7-6-https* role for certificate management.
|
||||
- **Automatic TLS & HSTS** — integrates with the *srv-https-stack* role for certificate management.
|
||||
- **Flexible vHost templates** — *basic* and *ws_generic* flavours cover standard HTTP and WebSocket applications.
|
||||
- **Security headers** — sensible defaults plus optional X-Frame-Options / CSP based on application settings.
|
||||
- **WebSocket & HTTP/2 aware** — upgrades, keep-alive tuning, and gzip already configured.
|
@@ -3,7 +3,7 @@
|
||||
include_role:
|
||||
name: '{{ item }}'
|
||||
loop:
|
||||
- srv-web-7-6-https
|
||||
- srv-web-7-4-core
|
||||
- srv-https-stack
|
||||
- srv-core
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_srv_proxy_7_4_core is not defined
|
||||
when: run_once_srv_proxy_core is not defined
|
@@ -1,6 +1,6 @@
|
||||
# Nginx Location Templates
|
||||
|
||||
This directory contains Jinja2 templates for different Nginx `location` blocks, each designed to proxy and optimize different types of web traffic. These templates are used by the `srv-proxy-7-4-core` role to modularize and standardize reverse proxy configuration across a wide variety of applications.
|
||||
This directory contains Jinja2 templates for different Nginx `location` blocks, each designed to proxy and optimize different types of web traffic. These templates are used by the `srv-proxy-core` role to modularize and standardize reverse proxy configuration across a wide variety of applications.
|
||||
|
||||
---
|
||||
|
@@ -16,7 +16,7 @@ location {{location}}
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Port {{ WEB_PORT }};
|
||||
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/headers/content_security_policy.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/headers/content_security_policy.conf.j2' %}
|
||||
|
||||
# WebSocket specific header
|
||||
proxy_http_version 1.1;
|
@@ -13,7 +13,7 @@ server
|
||||
{{ proxy_extra_configuration }}
|
||||
{% endif %}
|
||||
|
||||
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
|
||||
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
|
||||
|
||||
{% if applications | get_app_conf(application_id, 'features.oauth2', False) %}
|
||||
{% set acl = applications | get_app_conf(application_id, 'oauth2_proxy.acl', False, {}) %}
|
||||
@@ -22,38 +22,38 @@ server
|
||||
{# 1. Expose everything by default, then protect blacklisted paths #}
|
||||
{% set oauth2_proxy_enabled = false %}
|
||||
{% set location = "/" %}
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/location/html.conf.j2' %}
|
||||
|
||||
{% for loc in acl.blacklist %}
|
||||
{% set oauth2_proxy_enabled = true %}
|
||||
{% set location = loc %}
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/location/html.conf.j2' %}
|
||||
{% endfor %}
|
||||
|
||||
{% elif acl.whitelist is defined %}
|
||||
{# 2. Protect everything by default, then expose whitelisted paths #}
|
||||
{% set oauth2_proxy_enabled = true %}
|
||||
{% set location = "/" %}
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/location/html.conf.j2' %}
|
||||
|
||||
{% for loc in acl.whitelist %}
|
||||
{% set oauth2_proxy_enabled = false %}
|
||||
{% set location = loc %}
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/location/html.conf.j2' %}
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
{# 3. OAuth2 enabled but no (or empty) ACL — protect all #}
|
||||
{% set oauth2_proxy_enabled = true %}
|
||||
{% set location = "/" %}
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/location/html.conf.j2' %}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{# 4. OAuth2 completely disabled — expose all #}
|
||||
{% set oauth2_proxy_enabled = false %}
|
||||
{% set location = "/" %}
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/location/html.conf.j2' %}
|
||||
{% endif %}
|
||||
|
||||
}
|
@@ -6,7 +6,7 @@ map $http_upgrade $connection_upgrade {
|
||||
server {
|
||||
server_name {{ domain }};
|
||||
|
||||
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
|
||||
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
|
||||
|
||||
{% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2' %}
|
||||
|
||||
@@ -25,10 +25,10 @@ server {
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/location/html.conf.j2' %}
|
||||
|
||||
{% if location_ws is defined %}
|
||||
{% include 'roles/srv-proxy-7-4-core/templates/location/ws.conf.j2' %}
|
||||
{% include 'roles/srv-proxy-core/templates/location/ws.conf.j2' %}
|
||||
{% endif %}
|
||||
|
||||
error_page 500 501 502 503 504 /500.html;
|
@@ -1,10 +1,10 @@
|
||||
- block:
|
||||
- name: Include dependency 'srv-web-7-6-https'
|
||||
- name: Include dependency 'srv-https-stack'
|
||||
include_role:
|
||||
name: srv-web-7-6-https
|
||||
when: run_once_srv_web_7_6_https is not defined
|
||||
name: srv-https-stack
|
||||
when: run_once_srv_https_stack is not defined
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_srv_web_6_6_tls_core is not defined
|
||||
when: run_once_srv_tls_core is not defined
|
||||
|
||||
- name: "Include flavor '{{ CERTBOT_FLAVOR }}' for '{{ domain }}'"
|
||||
include_tasks: "{{ role_path }}/tasks/flavors/{{ CERTBOT_FLAVOR }}.yml"
|
@@ -1,9 +0,0 @@
|
||||
# run_once_srv_web_7_6_composer: deactivated
|
||||
|
||||
- name: "include role sys-srv-web-inj-compose for '{{ domain }}'"
|
||||
include_role:
|
||||
name: sys-srv-web-inj-compose
|
||||
|
||||
- name: "include role srv-web-6-6-tls-core for '{{ domain }}'"
|
||||
include_role:
|
||||
name: srv-web-6-6-tls-core
|
@@ -6,4 +6,3 @@ OnFailure={{ SYS_SERVICE_ON_FAILURE_COMPOSE }} {{ SYS_SERVICE_CLEANUP_BACKUPS_FA
|
||||
Type=oneshot
|
||||
ExecStartPre=/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(' ') }} --ignore {{ SYS_SERVICE_GROUP_BACKUPS | join(' ') }} --timeout "{{ SYS_TIMEOUT_BACKUP_SERVICES }}"
|
||||
ExecStart={{ system_service_script_exec }}
|
||||
|
||||
|
@@ -2,5 +2,5 @@ server {
|
||||
listen {{ ports.public.ldaps['svc-db-openldap'] }}ssl;
|
||||
proxy_pass 127.0.0.1:{{ ports.localhost.ldap['svc-db-openldap'] }};
|
||||
|
||||
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_credentials.j2' %}
|
||||
{% include 'roles/srv-letsencrypt/templates/ssl_credentials.j2' %}
|
||||
}
|
||||
|
@@ -14,9 +14,10 @@
|
||||
include_tasks: 03_reset.yml
|
||||
when: MODE_RESET | bool
|
||||
|
||||
- include_role:
|
||||
- name: "Execute system service setup for '{{ system_service_id }}'"
|
||||
include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_copy_files: false
|
||||
system_service_timer_enabled: false
|
||||
system_service_timer_enabled: true
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_BACKUP_DOCKER_TO_LOCAL }}"
|
||||
|
@@ -6,5 +6,6 @@
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_HEALTH_BTRFS}}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_HEALTH_BTRFS}}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
@@ -13,5 +13,6 @@
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_CSP_CRAWLER }}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_CSP_CRAWLER }}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
|
11
roles/sys-ctl-hlth-disc-space/tasks/01_core.yml
Normal file
11
roles/sys-ctl-hlth-disc-space/tasks/01_core.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
- name: Include dependency 'sys-ctl-alm-compose'
|
||||
include_role:
|
||||
name: sys-ctl-alm-compose
|
||||
when: run_once_sys_ctl_alm_compose is not defined
|
||||
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_DISC_SPACE }}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
@@ -1,13 +1,4 @@
|
||||
- block:
|
||||
- name: Include dependency 'sys-ctl-alm-compose'
|
||||
include_role:
|
||||
name: sys-ctl-alm-compose
|
||||
when: run_once_sys_ctl_alm_compose is not defined
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_sys_ctl_hlth_disc_space is not defined
|
||||
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_DISC_SPACE }}"
|
||||
system_service_timer_enabled: true
|
||||
- include_tasks: 01_core.yml
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_sys_ctl_hlth_disc_space is not defined
|
@@ -8,3 +8,4 @@
|
||||
vars:
|
||||
system_service_timer_enabled: true
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_DOCKER_CONTAINER }}"
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
|
@@ -6,5 +6,6 @@
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_HEALTH_DOCKER_VOLUMES}}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_HEALTH_DOCKER_VOLUMES}}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
|
@@ -6,5 +6,6 @@
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_HEALTH_JOURNALCTL}}"
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_HEALTH_JOURNALCTL}}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
|
@@ -6,5 +6,6 @@
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_MSMTP }}"
|
||||
system_service_timer_enabled: true
|
@@ -1,20 +1,20 @@
|
||||
- block:
|
||||
- name: Include dependencies
|
||||
include_role:
|
||||
name: '{{ item }}'
|
||||
loop:
|
||||
- dev-python-pip
|
||||
- sys-ctl-alm-compose
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_sys_ctl_hlth_webserver is not defined
|
||||
- name: Include dependencies
|
||||
include_role:
|
||||
name: '{{ item }}'
|
||||
loop:
|
||||
- dev-python-pip
|
||||
- sys-ctl-alm-compose
|
||||
|
||||
- name: Install required Python modules
|
||||
community.general.pacman:
|
||||
name: python-requests
|
||||
state: present
|
||||
|
||||
- meta: flush_handlers
|
||||
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_HEALTH_NGINX}}"
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_NGINX }}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
|
@@ -1,12 +1,15 @@
|
||||
- name: Include dependency 'sys-ctl-alm-compose'
|
||||
include_role:
|
||||
name: sys-ctl-alm-compose
|
||||
vars:
|
||||
flush_handlers: true
|
||||
when: run_once_sys_ctl_alm_compose is not defined
|
||||
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_state: restarted
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_DEPLOY }}"
|
||||
persistent: "true"
|
||||
system_service_timer_enabled: true
|
||||
system_service_state: restarted
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_DEPLOY }}"
|
||||
persistent: "true"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
@@ -3,7 +3,7 @@
|
||||
name: '{{ item }}'
|
||||
loop:
|
||||
- sys-svc-certbot
|
||||
- srv-web-7-4-core
|
||||
- srv-core
|
||||
- sys-ctl-alm-compose
|
||||
|
||||
- name: install certbot
|
||||
@@ -15,7 +15,8 @@
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_copy_files: false
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_RENEW }}"
|
||||
persistent: true
|
||||
system_service_timer_enabled: true
|
||||
system_service_copy_files: false
|
||||
system_service_on_calendar: "{{ SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_RENEW }}"
|
||||
persistent: true
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
|
@@ -12,6 +12,7 @@
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_copy_files: false
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_REPAIR_BTRFS_AUTO_BALANCER}}"
|
||||
system_service_copy_files: false
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_REPAIR_BTRFS_AUTO_BALANCER}}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
|
@@ -6,5 +6,6 @@
|
||||
- include_role:
|
||||
name: sys-service
|
||||
vars:
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_REPAIR_DOCKER_SOFT}}"
|
||||
system_service_on_calendar: "{{SYS_SCHEDULE_REPAIR_DOCKER_SOFT}}"
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
|
@@ -5,10 +5,8 @@
|
||||
|
||||
- name: "Execute service routines for '{{ system_service_id }}'"
|
||||
block:
|
||||
- name: "Load base routine for '{{ system_service_id }}'"
|
||||
include_tasks: 03_base.yml
|
||||
- include_tasks: utils/run_once.yml
|
||||
vars:
|
||||
# Necessary to flush after every service which uses an 'system_service_id' otherwise wrong one will be used
|
||||
flush_handlers: true
|
||||
- name: "Load base routine for '{{ system_service_id }}'"
|
||||
include_tasks: 03_base.yml
|
||||
- name: "Flush system handlers for '{{ system_service_id }}'"
|
||||
meta: flush_handlers
|
||||
when: system_service_id is defined
|
@@ -3,10 +3,10 @@
|
||||
inj_enabled: "{{ applications | inj_enabled(application_id, SRV_WEB_INJ_COMP_FEATURES_ALL) }}"
|
||||
|
||||
- block:
|
||||
- name: Include dependency 'srv-web-7-4-core'
|
||||
- name: Include dependency 'srv-core'
|
||||
include_role:
|
||||
name: srv-web-7-4-core
|
||||
when: run_once_srv_web_7_4_core is not defined
|
||||
name: srv-core
|
||||
when: run_once_srv_core is not defined
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_sys_srv_web_inj_compose is not defined
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
- name: Include dependency 'srv-web-7-4-core'
|
||||
- name: Include dependency 'srv-core'
|
||||
include_role:
|
||||
name: srv-web-7-4-core
|
||||
when: run_once_srv_web_7_4_core is not defined
|
||||
name: srv-core
|
||||
when: run_once_srv_core is not defined
|
||||
|
||||
- name: Generate color palette with colorscheme-generator
|
||||
set_fact:
|
||||
|
@@ -1,8 +1,8 @@
|
||||
- block:
|
||||
- name: Include dependency 'srv-web-7-4-core'
|
||||
- name: Include dependency 'srv-core'
|
||||
include_role:
|
||||
name: srv-web-7-4-core
|
||||
when: run_once_srv_web_7_4_core is not defined
|
||||
name: srv-core
|
||||
when: run_once_srv_core is not defined
|
||||
- include_tasks: 01_deploy.yml
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_sys_srv_web_inj_desktop is not defined
|
||||
|
@@ -1,9 +1,9 @@
|
||||
- block:
|
||||
|
||||
- name: Include dependency 'srv-web-7-4-core'
|
||||
- name: Include dependency 'srv-core'
|
||||
include_role:
|
||||
name: srv-web-7-4-core
|
||||
when: run_once_srv_web_7_4_core is not defined
|
||||
name: srv-core
|
||||
when: run_once_srv_core is not defined
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_sys_srv_web_inj_javascript is not defined
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
- name: Include dependency 'srv-web-7-4-core'
|
||||
- name: Include dependency 'srv-core'
|
||||
include_role:
|
||||
name: srv-web-7-4-core
|
||||
name: srv-core
|
||||
when:
|
||||
- run_once_srv_web_7_4_core is not defined
|
||||
- run_once_srv_core is not defined
|
||||
|
||||
- name: "deploy the logout.js"
|
||||
include_tasks: "02_deploy.yml"
|
@@ -1,8 +1,8 @@
|
||||
- block:
|
||||
- name: Include dependency 'srv-web-7-4-core'
|
||||
- name: Include dependency 'srv-core'
|
||||
include_role:
|
||||
name: srv-web-7-4-core
|
||||
when: run_once_srv_web_7_4_core is not defined
|
||||
name: srv-core
|
||||
when: run_once_srv_core is not defined
|
||||
- include_tasks: utils/run_once.yml
|
||||
when: run_once_sys_srv_web_inj_matomo is not defined
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
include_role:
|
||||
name: '{{ item }}'
|
||||
loop:
|
||||
- srv-web-7-4-core
|
||||
- srv-core
|
||||
|
||||
- name: Include task to remove deprecated nginx configs
|
||||
include_tasks: remove_deprecated_nginx_configs.yml
|
||||
|
@@ -160,23 +160,6 @@ def upgrade_listmonk():
|
||||
run_command('echo "y" | docker compose run -T application ./listmonk --upgrade')
|
||||
print("Upgrade complete.")
|
||||
|
||||
def update_nextcloud():
|
||||
"""
|
||||
Performs the necessary Nextcloud update procedures, including maintenance and app updates.
|
||||
"""
|
||||
print("Start Nextcloud upgrade procedure.")
|
||||
update_procedure("docker-compose exec -T -u www-data application /var/www/html/occ upgrade")
|
||||
print("Start Nextcloud repairing procedure.")
|
||||
update_procedure("docker-compose exec -T -u www-data application /var/www/html/occ maintenance:repair --include-expensive")
|
||||
print("Start Nextcloud update procedure.")
|
||||
update_procedure("docker-compose exec -T -u www-data application /var/www/html/occ app:update --all")
|
||||
print("Start Nextcloud add-missing procedure.")
|
||||
update_procedure("docker-compose exec -T -u www-data application /var/www/html/occ db:add-missing-columns")
|
||||
update_procedure("docker-compose exec -T -u www-data application /var/www/html/occ db:add-missing-indices")
|
||||
update_procedure("docker-compose exec -T -u www-data application /var/www/html/occ db:add-missing-primary-keys")
|
||||
print("Deactivate Maintanance Mode")
|
||||
update_procedure("docker-compose exec -T -u www-data application /var/www/html/occ maintenance:mode --off")
|
||||
|
||||
def update_procedure(command):
|
||||
"""
|
||||
Attempts to execute a command up to a maximum number of retries.
|
||||
@@ -239,8 +222,6 @@ if __name__ == "__main__":
|
||||
upgrade_listmonk()
|
||||
elif os.path.basename(dir_path) == "mastodon":
|
||||
update_mastodon()
|
||||
elif os.path.basename(dir_path) == "nextcloud":
|
||||
update_nextcloud()
|
||||
|
||||
# @todo implement dedicated procedure for bluesky
|
||||
# @todo implement dedicated procedure for taiga
|
||||
|
@@ -5,7 +5,7 @@ setup_admin_email: "{{ users.administrator.email }}"
|
||||
features:
|
||||
matomo: true
|
||||
css: true
|
||||
desktop: true
|
||||
desktop: true
|
||||
central_database: true
|
||||
logout: true
|
||||
server:
|
||||
|
@@ -4,7 +4,7 @@ image:
|
||||
features:
|
||||
matomo: true
|
||||
css: true
|
||||
desktop: true
|
||||
desktop: true
|
||||
central_database: true
|
||||
logout: true
|
||||
docker:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
---
|
||||
- name: "For '{{ application_id }}': include role to receive certs & do modification routines"
|
||||
include_role:
|
||||
name: srv-web-7-6-composer
|
||||
name: srv-composer
|
||||
vars:
|
||||
domain: "{{ item }}"
|
||||
http_port: "{{ ports.localhost.http[application_id] }}"
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
- name: "For '{{ application_id }}': configure {{ domains | get_domain(application_id) }}.conf"
|
||||
template:
|
||||
src: roles/srv-proxy-7-4-core/templates/vhost/basic.conf.j2
|
||||
src: roles/srv-proxy-core/templates/vhost/basic.conf.j2
|
||||
dest: "{{ NGINX.DIRECTORIES.HTTP.SERVERS }}{{ domains | get_domain(application_id) }}.conf"
|
||||
notify: restart openresty
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
features:
|
||||
matomo: true
|
||||
css: true
|
||||
desktop: true
|
||||
central_database: true
|
||||
logout: true
|
||||
matomo: true
|
||||
css: true
|
||||
desktop: true
|
||||
central_database: true
|
||||
logout: true
|
||||
docker:
|
||||
services:
|
||||
redis:
|
||||
|
@@ -35,7 +35,7 @@ By default, BigBlueButton is deployed with best-practice hardening, modular secr
|
||||
## System Requirements
|
||||
|
||||
- Arch Linux with Docker, Compose, and Nginx roles pre-installed
|
||||
- DNS and reverse proxy configuration using `srv-proxy-7-4-core`
|
||||
- DNS and reverse proxy configuration using `srv-proxy-core`
|
||||
- Functional email system for Greenlight SMTP
|
||||
|
||||
## Important Resources
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user