浏览代码

template improvements and standardization

xcad 5 月之前
父节点
当前提交
3509f509e9
共有 100 个文件被更改,包括 1457 次插入1030 次删除
  1. 75 55
      AGENTS.md
  2. 67 10
      cli/core/display.py
  3. 196 144
      cli/core/module.py
  4. 8 2
      cli/core/prompt.py
  5. 99 7
      cli/core/template.py
  6. 53 14
      cli/core/variables.py
  7. 13 0
      cli/modules/ci.py
  8. 18 10
      cli/modules/compose.py
  9. 0 13
      cli/modules/github_actions.py
  10. 0 13
      cli/modules/gitlab_ci.py
  11. 0 13
      cli/modules/kestra.py
  12. 0 0
      library/ci/github-actions-kubectl/kubernetes-deploy.yml
  13. 0 0
      library/ci/github-actions-scp/copy-config-files.yml
  14. 0 0
      library/ci/github-actions-ssh/restart-docker.yml
  15. 0 0
      library/ci/gitlab-ci-ansible/run.yml
  16. 0 0
      library/ci/gitlab-ci-ansible/test.yml
  17. 0 0
      library/ci/gitlab-ci-docker/config.yml
  18. 0 0
      library/ci/gitlab-ci-docker/deploy.yml
  19. 0 0
      library/ci/gitlab-ci-docker/test.yml
  20. 0 0
      library/ci/gitlab-ci-terraform/apply.yml
  21. 0 0
      library/ci/gitlab-ci-terraform/validate.yml
  22. 0 0
      library/ci/kestra-ansible/ansible-playbook-git.yaml
  23. 0 0
      library/ci/kestra-ansible/ansible-playbook-inline.yaml
  24. 0 0
      library/ci/kestra-docker/docker-build-git.yaml
  25. 0 0
      library/ci/kestra-docker/docker-build-inline.yaml
  26. 0 0
      library/ci/kestra-inputs/kestra-inputs.yaml
  27. 0 0
      library/ci/kestra-python/python_command.yaml
  28. 0 0
      library/ci/kestra-python/python_script.yaml
  29. 0 0
      library/ci/kestra-variables/kestra-variables.yaml
  30. 0 0
      library/ci/kestra-webhook/kestra-webhook.yaml
  31. 0 31
      library/compose/alloy.docker-logs/config/config.alloy.j2
  32. 0 11
      library/compose/alloy.docker-logs/template.yaml
  33. 0 18
      library/compose/alloy.docker-metrics/config/config.alloy.j2
  34. 0 15
      library/compose/alloy.docker-metrics/template.yaml
  35. 0 53
      library/compose/alloy.system-logs/config/config.alloy.j2
  36. 0 15
      library/compose/alloy.system-logs/template.yaml
  37. 0 41
      library/compose/alloy.system-metrics/config/config.alloy.j2
  38. 0 15
      library/compose/alloy.system-metrics/template.yaml
  39. 9 1
      library/compose/alloy/compose.yaml.j2
  40. 0 16
      library/compose/alloy/config/config.alloy
  41. 179 0
      library/compose/alloy/config/config.alloy.j2
  42. 44 2
      library/compose/alloy/template.yaml
  43. 31 0
      library/compose/authentik/.env.authentik.j2
  44. 9 0
      library/compose/authentik/.env.postgres.j2
  45. 9 52
      library/compose/authentik/compose.yaml.j2
  46. 40 12
      library/compose/authentik/template.yaml
  47. 0 2
      library/compose/bind9/template.yaml
  48. 0 2
      library/compose/cadvisor/template.yaml
  49. 0 2
      library/compose/checkmk/template.yaml
  50. 0 2
      library/compose/clamav/template.yaml
  51. 0 2
      library/compose/dockge/template.yaml
  52. 0 8
      library/compose/gitea/.env.example
  53. 20 0
      library/compose/gitea/.env.gitea.j2
  54. 9 0
      library/compose/gitea/.env.postgres.j2
  55. 62 77
      library/compose/gitea/compose.yaml.j2
  56. 46 13
      library/compose/gitea/template.yaml
  57. 0 2
      library/compose/gitlab-runner/template.yaml
  58. 1 0
      library/compose/gitlab/template.yaml
  59. 6 2
      library/compose/grafana/template.yaml
  60. 0 2
      library/compose/heimdall/template.yaml
  61. 0 2
      library/compose/homeassistant/template.yaml
  62. 0 2
      library/compose/homepage/template.yaml
  63. 6 2
      library/compose/homer/template.yaml
  64. 37 2
      library/compose/influxdb/template.yaml
  65. 0 2
      library/compose/loki/template.yaml
  66. 0 2
      library/compose/mariadb/template.yaml
  67. 6 2
      library/compose/n8n/template.yaml
  68. 23 0
      library/compose/nextcloud/.env.nextcloud.j2
  69. 6 8
      library/compose/nextcloud/compose.yaml.j2
  70. 42 13
      library/compose/nextcloud/template.yaml
  71. 4 1
      library/compose/nginx/template.yaml
  72. 0 2
      library/compose/nginxproxymanager/template.yaml
  73. 0 2
      library/compose/nodeexporter/template.yaml
  74. 0 2
      library/compose/openwebui/template.yaml
  75. 0 2
      library/compose/passbolt/template.yaml
  76. 35 2
      library/compose/pihole/template.yaml
  77. 14 2
      library/compose/portainer/template.yaml
  78. 1 1
      library/compose/postgres/compose.yaml.j2
  79. 12 2
      library/compose/postgres/template.yaml
  80. 0 2
      library/compose/prometheus/template.yaml
  81. 0 2
      library/compose/promtail/template.yaml
  82. 0 2
      library/compose/teleport/template.yaml
  83. 0 18
      library/compose/traefik.authentik-middleware/middleware.yaml.j2
  84. 0 26
      library/compose/traefik.authentik-middleware/template.yaml
  85. 0 21
      library/compose/traefik.external-service/external-service.yaml.j2
  86. 0 17
      library/compose/traefik.external-service/router.yaml.j2
  87. 0 41
      library/compose/traefik.external-service/template.yaml
  88. 0 26
      library/compose/traefik.grafana/router.yaml.j2
  89. 0 15
      library/compose/traefik.guacamole/router.yaml.j2
  90. 0 6
      library/compose/traefik.ldap-middleware/middleware.yaml.j2
  91. 0 31
      library/compose/traefik.nextcloud/router.yaml.j2
  92. 0 23
      library/compose/traefik.pihole/router.yaml.j2
  93. 0 26
      library/compose/traefik.proxmox/router.yaml.j2
  94. 0 15
      library/compose/traefik.vaultwarden/router.yaml.j2
  95. 15 0
      library/compose/traefik/.env.j2
  96. 8 7
      library/compose/traefik/compose.yaml.j2
  97. 73 0
      library/compose/traefik/config/files/external-services.yaml
  98. 66 0
      library/compose/traefik/config/files/middlewares.yaml.j2
  99. 96 0
      library/compose/traefik/config/files/tls.yaml
  100. 19 24
      library/compose/traefik/config/traefik.yaml.j2

+ 75 - 55
AGENTS.md

@@ -12,13 +12,14 @@ The CLI is a Python application built with Typer for the command-line interface
 
 - `cli/` - Python CLI application source code
   - `cli/core/` - Core functionality (app, config, commands, logging)
-  - `cli/modules/` - Technology-specific modules (terraform, docker, compose, etc.)
-- `library/` - Template collections organized by technology
-  - `library/terraform/` - OpenTofu/Terraform templates and examples
-  - `library/compose/` - Docker Compose configurations
-  - `library/proxmox/` - Packer templates for Proxmox
+  - `cli/modules/` - Technology-specific modules (terraform, docker, compose, config, etc.)
+- `library/` - Template collections organized by module
   - `library/ansible/` - Ansible playbooks and configurations
+  - `library/ci/` - CI/CD automation templates (GitHub Actions, GitLab CI, Kestra)
+  - `library/config/` - Application-specific configuration templates
+  - `library/compose/` - Docker Compose configurations
   - `library/kubernetes/` - Kubernetes deployments
+  - `library/terraform/` - OpenTofu/Terraform templates and examples
   - And more...
 
 ## Development Setup
@@ -26,11 +27,36 @@ The CLI is a Python application built with Typer for the command-line interface
 ### Running the CLI
 
 ```bash
-# Running commands
+# List available commands
 python3 -m cli
 
+# List templates for a module
+python3 -m cli compose list
+
 # Debugging commands
 python3 -m cli --log-level DEBUG compose list
+
+# Generate template interactively (default - prompts for variables)
+python3 -m cli compose generate authentik --out /tmp/my-project
+
+# Generate template non-interactively (skips prompts, uses defaults and CLI variables)
+python3 -m cli compose generate authentik --out /tmp/my-project --no-interactive
+
+# Generate with variable overrides (non-interactive)
+python3 -m cli compose generate authentik \
+  --var service_name=auth \
+  --var ports_enabled=false \
+  --var database_type=postgres \
+  --out /tmp/my-project \
+  --no-interactive
+
+# Show template details
+python3 -m cli compose show authentik
+
+# Managing default values (renamed from 'config' to 'defaults')
+python3 -m cli compose defaults set service_name my-app
+python3 -m cli compose defaults get
+python3 -m cli compose defaults list
 ```
 
 ## Common Development Tasks
@@ -114,43 +140,6 @@ library/compose/my-nginx-template/
     └── README.md
 ```
 
-#### Sub-Templates
-
-Sub-templates are specialized templates that use dot notation in their directory names to create related template variations or components. They provide a way to organize templates hierarchically and create focused, reusable configurations.
-
-**Directory Naming Convention:**
-- Sub-templates use dot notation: `parent.sub-name`
-- Example: `traefik.authentik-middleware`, `traefik.external-service`
-- The parent name should match an existing template for logical grouping
-
-**Visibility:**
-- By default, sub-templates are hidden from the standard `list` command
-- Use `list --all` to show all templates including sub-templates
-- This keeps the default view clean while providing access to specialized templates
-
-**Usage Examples:**
-```bash
-# Show only main templates (default behavior)
-python3 -m cli compose list
-
-# Show all templates including sub-templates
-python3 -m cli compose list --all
-
-# Search for templates by ID
-python3 -m cli compose search traefik
-
-# Search for templates including sub-templates
-python3 -m cli compose search authentik --all
-
-# Generate a sub-template
-python3 -m cli compose generate traefik.authentik-middleware
-```
-
-**Common Use Cases:**
-- Configuration variations (e.g., `service.production`, `service.development`)
-- Component templates (e.g., `traefik.middleware`, `traefik.router`)
-- Environment-specific templates (e.g., `app.docker`, `app.kubernetes`)
-
 #### Variables
 
 Variables are a cornerstone of the CLI, allowing for dynamic and customizable template generation. They are defined and processed with a clear precedence and logic.
@@ -217,29 +206,60 @@ After creating the issue, update the TODO line in the `AGENTS.md` file with the
 * TODO Template Validation Command: A command to validate the structure and variable definitions of a template without generating it.
 * TODO Interactive Variable Prompt Improvements: The interactive prompt could be improved with better navigation, help text, and validation feedback.
 * TODO Better Error Recovery in Jinja2 Rendering
+* TODO Icon Management Class in DisplayManager: Create a centralized icon management system in `cli/core/display.py` to standardize icons used throughout the CLI for file types (.yaml, .j2, .json, etc.), status indicators (success, warning, error, info), and UI elements. This would improve consistency, make icons easier to maintain, and allow for theme customization.
 
 ## Best Practices for Template Development
 
 ### Template Structure
 - Always include a main `template.yaml` or `template.yml` file
 - Use descriptive template IDs (directory names) with lowercase and hyphens
-- Use dot notation for sub-templates (e.g., `parent.sub-name`)
 - Place Jinja2 templates in subdirectories when appropriate (e.g., `config/`)
+- For application-specific configs, create templates in the `config` module instead of using complex directory structures
 
 ### Variable Definitions
-- Define variables in module specs for common, reusable settings
-- Define variables in template specs for template-specific settings
-- Only override specific fields in templates (don't redefine entire variables)
-- Use descriptive variable names with underscores (e.g., `external_url`, `smtp_port`)
-- Always specify `type` for new variables
-- Provide sensible `default` values when possible
+- **Prefer module spec variables first**: Always check if a variable exists in the module spec before adding template-specific variables. Use existing module variables where possible to maintain consistency across templates.
+- **Override module variables when needed**: If a module variable needs different behavior for a specific template, override its `description`, `default`, or `extra` properties in the template spec rather than creating a new variable.
+- **Add template-specific variables only when necessary**: Only create new variables in the template spec when they are truly unique to that template and don't fit into existing module variables.
+- Use descriptive names with underscores (e.g., `external_url`, `smtp_port`)
+- Always specify `type` and provide sensible `default` values
+- Mark sensitive data with `sensitive: true` and `autogenerated: true` for auto-generated secrets
+- Use `pwgen` filter for password generation: `{{ secret_key if secret_key else (none | pwgen(50)) }}`
+
+**Example**: For the Traefik template, use the existing `authentik` section from the module spec instead of creating custom `authentik_middleware_*` variables. Override the section's `description` and `extra` to provide Traefik-specific guidance.
 
 ### Jinja2 Templates
-- Use `.j2` extension for all Jinja2 template files
-- Use conditional blocks (`{% if %}`) for optional features
-- Keep template logic simple and readable
-- Use comments to explain complex logic
-- Test with different variable combinations
+- Use `.j2` extension and always use `| default()` filter for safe fallbacks
+- Use conditional blocks for optional features and keep logic simple
+- Add descriptive comments in generated files
+
+### Docker Compose Specific Practices
+
+#### Variable Naming Conventions
+- **Service/Container**: `service_name`, `container_name`, `container_timezone`, `restart_policy`
+- **Application**: Prefix with app name (e.g., `authentik_secret_key`, `gitea_root_url`)
+- **Database**: `database_type`, `database_enabled`, `database_external`, `database_host`, `database_port`, `database_name`, `database_user`, `database_password`
+- **Network**: `network_enabled`, `network_name`, `network_external`
+- **Traefik**: `traefik_enabled`, `traefik_host`, `traefik_tls_enabled`, `traefik_tls_entrypoint`, `traefik_tls_certresolver`
+- **Ports**: `ports_enabled`, `ports_http`, `ports_https`, `ports_ssh`
+- **Email**: `email_enabled`, `email_host`, `email_port`, `email_username`, `email_password`, `email_from`
+
+#### Scoped Environment Files
+Use separate `.env.{service}.j2` files for different services (e.g., `.env.authentik.j2`, `.env.postgres.j2`):
+
+- Benefits: Better security, cleaner organization, easier management, reusable configs
+- Usage: Reference via `env_file` directive in `compose.yaml.j2`
+- Structure: Group related settings with comments (e.g., `# Database Connection`)
+
+#### Common Toggle Patterns
+- `database_enabled`, `email_enabled`, `traefik_enabled`, `ports_enabled`, `network_enabled`
+
+#### Docker Compose Template Patterns
+- Always define `depends_on` for startup ordering and use named volumes for persistence
+- Include health checks for database services
+- Use `{% if not database_external %}` for conditional service creation
+- Group Traefik labels logically with proper service/router configuration
+- Always include `restart: {{ restart_policy | default('unless-stopped') }}`
+- Support both internal and external databases/services with conditionals
 
 ### Module Specs
 - Define common sections that apply to all templates of that kind

+ 67 - 10
cli/core/display.py

@@ -19,9 +19,15 @@ class DisplayManager:
     """Handles all rich rendering for the CLI."""
 
     def display_templates_table(
-        self, templates: list[dict], module_name: str, title: str
+        self, templates: list, module_name: str, title: str
     ) -> None:
-        """Display a table of templates."""
+        """Display a table of templates.
+        
+        Args:
+            templates: List of Template objects
+            module_name: Name of the module
+            title: Title for the table
+        """
         if not templates:
             logger.info(f"No templates found for module '{module_name}'")
             return
@@ -34,17 +40,14 @@ class DisplayManager:
         table.add_column("Version", no_wrap=True)
         table.add_column("Library", no_wrap=True)
 
-        for template_info in templates:
-            template = template_info["template"]
-            indent = template_info["indent"]
+        for template in templates:
             name = template.metadata.name or "Unnamed Template"
             tags_list = template.metadata.tags or []
             tags = ", ".join(tags_list) if tags_list else "-"
             version = template.metadata.version or ""
             library = template.metadata.library or ""
 
-            template_id = f"{indent}{template.id}"
-            table.add_row(template_id, name, tags, version, library)
+            table.add_row(template.id, name, tags, version, library)
 
         console.print(table)
 
@@ -56,9 +59,10 @@ class DisplayManager:
 
     def display_section_header(self, title: str, description: str | None) -> None:
         """Display a section header."""
-        console.print(f"\n[bold cyan]{title}[/bold cyan]")
         if description:
-            console.print(f"[dim]{description}[/dim]")
+            console.print(f"\n[bold cyan]{title}[/bold cyan] [dim]- {description}[/dim]")
+        else:
+            console.print(f"\n[bold cyan]{title}[/bold cyan]")
         console.print("─" * 40, style="dim")
 
     def display_validation_error(self, message: str) -> None:
@@ -161,9 +165,13 @@ class DisplayManager:
                 row_style = "dim" if is_dimmed else None
                 # Use variable's native get_display_value() method
                 default_val = variable.get_display_value(mask_sensitive=True, max_length=30)
+                
+                # Add lock icon for sensitive variables
+                sensitive_icon = " \uf084" if variable.sensitive else ""
+                var_display = f"  {var_name}{sensitive_icon}"
 
                 variables_table.add_row(
-                    f"  {var_name}",
+                    var_display,
                     variable.type or "str",
                     default_val,
                     variable.description or "",
@@ -173,6 +181,55 @@ class DisplayManager:
 
         console.print(variables_table)
 
+    def display_file_generation_confirmation(
+        self, 
+        output_dir: Path, 
+        files: dict[str, str], 
+        existing_files: list[Path] | None = None
+    ) -> None:
+        """Display files to be generated with confirmation prompt.
+        
+        Args:
+            output_dir: The output directory path
+            files: Dictionary of file paths to content
+            existing_files: List of existing files that will be overwritten (if any)
+        """
+        console.print()
+        console.print("[bold]Files to be generated:[/bold]")
+        
+        # Create a tree view of files
+        file_tree = Tree(f"\uf07b [cyan]{output_dir.resolve()}[/cyan]")
+        tree_nodes = {Path("."): file_tree}
+        
+        # Sort files for better display
+        sorted_files = sorted(files.keys())
+        
+        for file_path_str in sorted_files:
+            file_path = Path(file_path_str)
+            parts = file_path.parts
+            current_path = Path(".")
+            current_node = file_tree
+            
+            # Build directory structure
+            for part in parts[:-1]:
+                current_path = current_path / part
+                if current_path not in tree_nodes:
+                    new_node = current_node.add(f"\uf07b [white]{part}[/white]")
+                    tree_nodes[current_path] = new_node
+                current_node = tree_nodes[current_path]
+            
+            # Add file with indicator if it will be overwritten
+            file_name = parts[-1]
+            full_path = output_dir / file_path
+            
+            if existing_files and full_path in existing_files:
+                current_node.add(f"\uf15c [yellow]{file_name}[/yellow] [red](will overwrite)[/red]")
+            else:
+                current_node.add(f"\uf15c [green]{file_name}[/green]")
+        
+        console.print(file_tree)
+        console.print()
+
     def display_config_tree(self, spec: dict, module_name: str, show_all: bool = False) -> None:
         """Display configuration spec as a tree view.
         

+ 196 - 144
cli/core/module.py

@@ -77,12 +77,9 @@ class Module(ABC):
   # SECTION: Public Commands
   # --------------------------
 
-  def list(
-    self, 
-    all_templates: bool = Option(False, "--all", "-a", help="Show all templates including sub-templates")
-  ) -> list[Template]:
+  def list(self) -> list[Template]:
     """List all templates."""
-    logger.debug(f"Listing templates for module '{self.name}' with all={all_templates}")
+    logger.debug(f"Listing templates for module '{self.name}'")
     templates = []
 
     entries = self.libraries.find(self.name, sort_results=True)
@@ -94,15 +91,11 @@ class Module(ABC):
         logger.error(f"Failed to load template from {template_dir}: {exc}")
         continue
     
-    # Apply filtering logic
-    filtered_templates = self._filter_templates(templates, None, all_templates)
+    filtered_templates = templates
     
     if filtered_templates:
-      # Group templates for hierarchical display
-      grouped_templates = self._group_templates(filtered_templates)
-      
       self.display.display_templates_table(
-        grouped_templates,
+        filtered_templates,
         self.name,
         f"{self.name.capitalize()} templates"
       )
@@ -113,11 +106,10 @@ class Module(ABC):
 
   def search(
     self,
-    query: str = Argument(..., help="Search string to filter templates by ID"),
-    all_templates: bool = Option(False, "--all", "-a", help="Show all templates including sub-templates")
+    query: str = Argument(..., help="Search string to filter templates by ID")
   ) -> list[Template]:
     """Search for templates by ID containing the search string."""
-    logger.debug(f"Searching templates for module '{self.name}' with query='{query}', all={all_templates}")
+    logger.debug(f"Searching templates for module '{self.name}' with query='{query}'")
     templates = []
 
     entries = self.libraries.find(self.name, sort_results=True)
@@ -130,15 +122,12 @@ class Module(ABC):
         continue
     
     # Apply search filtering
-    filtered_templates = self._search_templates(templates, query, all_templates)
+    filtered_templates = [t for t in templates if query.lower() in t.id.lower()]
     
     if filtered_templates:
-      # Group templates for hierarchical display
-      grouped_templates = self._group_templates(filtered_templates)
-      
       logger.info(f"Found {len(filtered_templates)} templates matching '{query}' for module '{self.name}'")
       self.display.display_templates_table(
-        grouped_templates,
+        filtered_templates,
         self.name,
         f"{self.name.capitalize()} templates matching '{query}'"
       )
@@ -243,17 +232,44 @@ class Module(ABC):
         template.variables.validate_all()
       
       rendered_files = template.render(template.variables)
+      
+      # Safety check for render result
+      if not rendered_files:
+        console.print("[red]Error: Template rendering returned no files[/red]")
+        raise Exit(code=1)
+      
       logger.info(f"Successfully rendered template '{id}'")
       output_dir = out or Path(".")
-
-      # Check if the directory is empty and confirm overwrite if necessary
-      if output_dir.exists() and any(output_dir.iterdir()):
-        if interactive:
-          if not Confirm.ask(f"Output directory '{output_dir}' is not empty. Overwrite files?", default=False):
-            console.print("[yellow]Generation cancelled.[/yellow]")
-            return
+      
+      # Check which files already exist
+      existing_files = []
+      if output_dir.exists():
+        for file_path in rendered_files.keys():
+          full_path = output_dir / file_path
+          if full_path.exists():
+            existing_files.append(full_path)
+      
+      # Display file generation confirmation
+      if interactive:
+        self.display.display_file_generation_confirmation(
+          output_dir, 
+          rendered_files, 
+          existing_files if existing_files else None
+        )
+        
+        # Ask for confirmation
+        if existing_files:
+          prompt_msg = f"[yellow][/yellow]  {len(existing_files)} file(s) will be overwritten. Continue?"
         else:
-          logger.warning(f"Output directory '{output_dir}' is not empty. Existing files may be overwritten.")
+          prompt_msg = "Generate these files?"
+        
+        if not Confirm.ask(prompt_msg, default=True):
+          console.print("[yellow]Generation cancelled.[/yellow]")
+          return
+      else:
+        # Non-interactive mode: warn if files will be overwritten
+        if existing_files:
+          logger.warning(f"{len(existing_files)} file(s) will be overwritten in '{output_dir}'")
       
       # Create the output directory if it doesn't exist
       output_dir.mkdir(parents=True, exist_ok=True)
@@ -264,7 +280,7 @@ class Module(ABC):
         full_path.parent.mkdir(parents=True, exist_ok=True)
         with open(full_path, 'w', encoding='utf-8') as f:
           f.write(content)
-        console.print(f"[green]Generated file: {full_path}[/green]")
+        console.print(f"[green]Generated file: {file_path}[/green]")
       
       logger.info(f"Template written to directory: {output_dir}")
 
@@ -289,14 +305,14 @@ class Module(ABC):
     self,
     var_name: Optional[str] = Argument(None, help="Variable name to get (omit to show all defaults)"),
   ) -> None:
-    """Get config default value(s) for this module.
+    """Get default value(s) for this module.
     
     Examples:
         # Get all defaults for module
-        cli compose config get
+        cli compose defaults get
         
         # Get specific variable default
-        cli compose config get service_name
+        cli compose defaults get service_name
     """
     from .config import ConfigManager
     config = ConfigManager()
@@ -323,24 +339,24 @@ class Module(ABC):
     var_name: str = Argument(..., help="Variable name to set default for"),
     value: str = Argument(..., help="Default value"),
   ) -> None:
-    """Set a default value for a variable in config.
+    """Set a default value for a variable.
     
     This only sets the DEFAULT VALUE, not the variable spec.
     The variable must be defined in the module or template spec.
     
     Examples:
         # Set default value
-        cli compose config set service_name my-awesome-app
+        cli compose defaults set service_name my-awesome-app
         
         # Set author for all compose templates
-        cli compose config set author "Christian Lempa"
+        cli compose defaults set author "Christian Lempa"
     """
     from .config import ConfigManager
     config = ConfigManager()
     
     # Set the default value
     config.set_default_value(self.name, var_name, value)
-    console.print(f"[green] Set default:[/green] [cyan]{var_name}[/cyan] = [yellow]{value}[/yellow]")
+    console.print(f"[green] Set default:[/green] [cyan]{var_name}[/cyan] = [yellow]{value}[/yellow]")
     console.print(f"\n[dim]This will be used as the default value when generating templates with this module.[/dim]")
 
   def config_remove(
@@ -351,7 +367,7 @@ class Module(ABC):
     
     Examples:
         # Remove a default value
-        cli compose config remove service_name
+        cli compose defaults remove service_name
     """
     from .config import ConfigManager
     config = ConfigManager()
@@ -364,7 +380,7 @@ class Module(ABC):
     if var_name in defaults:
       del defaults[var_name]
       config.set_defaults(self.name, defaults)
-      console.print(f"[green] Removed default for '{var_name}'[/green]")
+      console.print(f"[green] Removed default for '{var_name}'[/green]")
     else:
       console.print(f"[red]No default found for variable '{var_name}'[/red]")
 
@@ -373,14 +389,14 @@ class Module(ABC):
     var_name: Optional[str] = Argument(None, help="Variable name to clear (omit to clear all defaults)"),
     force: bool = Option(False, "--force", "-f", help="Skip confirmation prompt"),
   ) -> None:
-    """Clear config default value(s) for this module.
+    """Clear default value(s) for this module.
     
     Examples:
         # Clear specific variable default
-        cli compose config clear service_name
+        cli compose defaults clear service_name
         
         # Clear all defaults for module
-        cli compose config clear --force
+        cli compose defaults clear --force
     """
     from .config import ConfigManager
     config = ConfigManager()
@@ -395,13 +411,13 @@ class Module(ABC):
       if var_name in defaults:
         del defaults[var_name]
         config.set_defaults(self.name, defaults)
-        console.print(f"[green] Cleared default for '{var_name}'[/green]")
+        console.print(f"[green] Cleared default for '{var_name}'[/green]")
       else:
         console.print(f"[red]No default found for variable '{var_name}'[/red]")
     else:
       # Clear all defaults
       if not force:
-        console.print(f"[bold yellow]⚠️  Warning:[/bold yellow] This will clear ALL defaults for module '[cyan]{self.name}[/cyan]'")
+        console.print(f"[bold yellow]  Warning:[/bold yellow] This will clear ALL defaults for module '[cyan]{self.name}[/cyan]'")
         console.print()
         # Show what will be cleared
         for var_name, var_value in defaults.items():
@@ -412,7 +428,135 @@ class Module(ABC):
           return
       
       config.clear_defaults(self.name)
-      console.print(f"[green]✓ Cleared all defaults for module '{self.name}'[/green]")
+      console.print(f"[green] Cleared all defaults for module '{self.name}'[/green]")
+
+  def config_list(self) -> None:
+    """Display the defaults for this specific module in YAML format.
+    
+    Examples:
+        # Show the defaults for the current module
+        cli compose defaults list
+    """
+    from .config import ConfigManager
+    import yaml
+    
+    config = ConfigManager()
+    
+    # Get only the defaults for this module
+    defaults = config.get_defaults(self.name)
+    
+    if not defaults:
+      console.print(f"[yellow]No configuration found for module '{self.name}'[/yellow]")
+      console.print(f"\n[dim]Config file location: {config.get_config_path()}[/dim]")
+      return
+    
+    # Create a minimal config structure with only this module's defaults
+    module_config = {
+      "defaults": {
+        self.name: defaults
+      }
+    }
+    
+    # Convert config to YAML string
+    yaml_output = yaml.dump(module_config, default_flow_style=False, sort_keys=False)
+    
+    console.print(f"[bold]Configuration for module:[/bold] [cyan]{self.name}[/cyan]")
+    console.print(f"[dim]Config file: {config.get_config_path()}[/dim]\n")
+    console.print(Panel(yaml_output, title=f"{self.name.capitalize()} Config", border_style="blue"))
+
+  def validate(
+    self,
+    template_id: str = Argument(None, help="Template ID to validate (if omitted, validates all templates)"),
+    verbose: bool = Option(False, "--verbose", "-v", help="Show detailed validation information")
+  ) -> None:
+    """Validate templates for Jinja2 syntax errors and undefined variables.
+    
+    Examples:
+        # Validate all templates in this module
+        cli compose validate
+        
+        # Validate a specific template
+        cli compose validate gitlab
+        
+        # Validate with verbose output
+        cli compose validate --verbose
+    """
+    from rich.table import Table
+    
+    if template_id:
+      # Validate a specific template
+      try:
+        template = self._load_template_by_id(template_id)
+        console.print(f"[bold]Validating template:[/bold] [cyan]{template_id}[/cyan]\n")
+        
+        try:
+          # Trigger validation by accessing used_variables
+          _ = template.used_variables
+          # Trigger variable definition validation by accessing variables
+          _ = template.variables
+          console.print(f"[green] Template '{template_id}' is valid[/green]")
+          
+          if verbose:
+            console.print(f"\n[dim]Template path: {template.template_dir}[/dim]")
+            console.print(f"[dim]Found {len(template.used_variables)} variables[/dim]")
+        except ValueError as e:
+          console.print(f"[red] Validation failed for '{template_id}':[/red]")
+          console.print(f"\n{e}")
+          raise Exit(code=1)
+          
+      except Exception as e:
+        console.print(f"[red]Error loading template '{template_id}': {e}[/red]")
+        raise Exit(code=1)
+    else:
+      # Validate all templates
+      console.print(f"[bold]Validating all {self.name} templates...[/bold]\n")
+      
+      entries = self.libraries.find(self.name, sort_results=True)
+      total = len(entries)
+      valid_count = 0
+      invalid_count = 0
+      errors = []
+      
+      for template_dir, library_name in entries:
+        template_id = template_dir.name
+        try:
+          template = Template(template_dir, library_name=library_name)
+          # Trigger validation
+          _ = template.used_variables
+          _ = template.variables
+          valid_count += 1
+          if verbose:
+            console.print(f"[green][/green] {template_id}")
+        except ValueError as e:
+          invalid_count += 1
+          errors.append((template_id, str(e)))
+          if verbose:
+            console.print(f"[red][/red] {template_id}")
+        except Exception as e:
+          invalid_count += 1
+          errors.append((template_id, f"Load error: {e}"))
+          if verbose:
+            console.print(f"[yellow]?[/yellow] {template_id}")
+      
+      # Summary
+      console.print(f"\n[bold]Validation Summary:[/bold]")
+      summary_table = Table(show_header=False, box=None, padding=(0, 2))
+      summary_table.add_column(style="bold")
+      summary_table.add_column()
+      summary_table.add_row("Total templates:", str(total))
+      summary_table.add_row("[green]Valid:[/green]", str(valid_count))
+      summary_table.add_row("[red]Invalid:[/red]", str(invalid_count))
+      console.print(summary_table)
+      
+      # Show errors if any
+      if errors:
+        console.print(f"\n[bold red]Validation Errors:[/bold red]")
+        for template_id, error_msg in errors:
+          console.print(f"\n[yellow]Template:[/yellow] [cyan]{template_id}[/cyan]")
+          console.print(f"[dim]{error_msg}[/dim]")
+        raise Exit(code=1)
+      else:
+        console.print(f"\n[green] All templates are valid![/green]")
 
   # !SECTION
 
@@ -432,120 +576,28 @@ class Module(ABC):
     module_app.command("list")(module_instance.list)
     module_app.command("search")(module_instance.search)
     module_app.command("show")(module_instance.show)
+    module_app.command("validate")(module_instance.validate)
     
     module_app.command(
       "generate", 
       context_settings={"allow_extra_args": True, "ignore_unknown_options": True}
     )(module_instance.generate)
     
-    # Add config commands (simplified - only manage default values)
-    config_app = Typer(help="Manage default values for template variables")
-    config_app.command("get", help="Get default value(s)")(module_instance.config_get)
-    config_app.command("set", help="Set a default value")(module_instance.config_set)
-    config_app.command("remove", help="Remove a specific default value")(module_instance.config_remove)
-    config_app.command("clear", help="Clear default value(s)")(module_instance.config_clear)
-    module_app.add_typer(config_app, name="config")
+    # Add defaults commands (simplified - only manage default values)
+    defaults_app = Typer(help="Manage default values for template variables")
+    defaults_app.command("get", help="Get default value(s)")(module_instance.config_get)
+    defaults_app.command("set", help="Set a default value")(module_instance.config_set)
+    defaults_app.command("remove", help="Remove a specific default value")(module_instance.config_remove)
+    defaults_app.command("clear", help="Clear default value(s)")(module_instance.config_clear)
+    defaults_app.command("list", help="Display the config for this module in YAML format")(module_instance.config_list)
+    module_app.add_typer(defaults_app, name="defaults")
     
     app.add_typer(module_app, name=cls.name, help=cls.description)
     logger.info(f"Module '{cls.name}' CLI commands registered")
 
   # !SECTION
 
-  # --------------------------
-  # SECTION: Template Organization Methods
-  # --------------------------
-
-  def _filter_templates(self, templates: list[Template], filter_name: Optional[str], all_templates: bool) -> list[Template]:
-    """Filter templates based on name and sub-template visibility."""
-    filtered = []
-    
-    for template in templates:
-      template_id = template.id
-      is_sub_template = '.' in template_id
-      
-      # No filter - include based on all_templates flag
-      if not all_templates and is_sub_template:
-        continue
-      filtered.append(template)
-    
-    return filtered
-  
-  def _search_templates(self, templates: list[Template], query: str, all_templates: bool) -> list[Template]:
-    """Search templates by ID containing the query string."""
-    filtered = []
-    query_lower = query.lower()
-    
-    for template in templates:
-      template_id = template.id
-      is_sub_template = '.' in template_id
-      
-      # Skip sub-templates if not showing all
-      if not all_templates and is_sub_template:
-        continue
-      
-      # Check if query is contained in the template ID
-      if query_lower in template_id.lower():
-        filtered.append(template)
-    
-    return filtered
-
-  def _group_templates(self, templates: list[Template]) -> list[dict]:
-    """Group templates hierarchically for display."""
-    grouped = []
-    main_templates = {}
-    sub_templates = []
-    
-    # Separate main templates and sub-templates
-    for template in templates:
-      if '.' in template.id:
-        sub_templates.append(template)
-      else:
-        main_templates[template.id] = template
-        grouped.append({
-          'template': template,
-          'indent': '',
-          'is_main': True
-        })
-    
-    # Sort sub-templates by parent
-    sub_templates.sort(key=lambda t: t.id)
-    
-    # Group sub-templates by parent for proper indentation
-    sub_by_parent = {}
-    for sub_template in sub_templates:
-      parent_name = sub_template.id.split('.')[0]
-      if parent_name not in sub_by_parent:
-        sub_by_parent[parent_name] = []
-      sub_by_parent[parent_name].append(sub_template)
-    
-    # Insert sub-templates after their parents with proper indentation
-    for parent_name, parent_subs in sub_by_parent.items():
-      # Find the parent in the grouped list
-      insert_index = -1
-      for i, item in enumerate(grouped):
-        if item['template'].id == parent_name:
-          insert_index = i + 1
-          break
-      
-      # Add each sub-template with proper indentation
-      for idx, sub_template in enumerate(parent_subs):
-        is_last = (idx == len(parent_subs) - 1)
-        sub_template_info = {
-          'template': sub_template,
-          'indent': '└─ ' if is_last else '├─ ',
-          'is_main': False
-        }
-        
-        if insert_index >= 0:
-          grouped.insert(insert_index, sub_template_info)
-          insert_index += 1
-        else:
-          # Parent not found, add at end
-          grouped.append(sub_template_info)
-    
-    return grouped
 
-  # !SECTION
 
   # --------------------------
   # SECTION: Private Methods

+ 8 - 2
cli/core/prompt.py

@@ -57,7 +57,8 @@ class PromptHandler:
       elif section.toggle:
         toggle_var = section.variables.get(section.toggle)
         if toggle_var:
-          prompt_text = section.prompt or f"Enable {section.title}?"
+          # Use description for prompt if available, otherwise use title
+          prompt_text = section.description if section.description else f"Enable {section.title}?"
           current_value = toggle_var.get_typed_value()
           new_value = self._prompt_bool(prompt_text, current_value)
           
@@ -101,7 +102,8 @@ class PromptHandler:
     default_value = variable.get_normalized_default()
 
     # If variable is required and there's no default, mark it in the prompt
-    if required and default_value is None:
+    # (but skip this for autogenerated variables since they can be empty)
+    if required and default_value is None and not variable.autogenerated:
       prompt_text = f"{prompt_text} [bold red]*required[/bold red]"
 
     handler = self._get_prompt_handler(variable)
@@ -117,6 +119,10 @@ class PromptHandler:
         # Convert/validate the user's input using the Variable conversion
         converted = variable.convert(raw)
 
+        # Allow empty values for autogenerated variables
+        if variable.autogenerated and (converted is None or (isinstance(converted, str) and converted == "")):
+          return None  # Return None to indicate auto-generation should happen
+        
         # If this variable is required, do not accept None/empty values
         if required and (converted is None or (isinstance(converted, str) and converted == "")):
           raise ValueError("value cannot be empty for required variable")

+ 99 - 7
cli/core/template.py

@@ -234,8 +234,14 @@ class Template:
     self.__template_files = template_files
 
   def _extract_all_used_variables(self) -> Set[str]:
-    """Extract all undeclared variables from all .j2 files in the template directory."""
+    """Extract all undeclared variables from all .j2 files in the template directory.
+    
+    Raises:
+        ValueError: If any Jinja2 template has syntax errors
+    """
     used_variables: Set[str] = set()
+    syntax_errors = []
+    
     for template_file in self.template_files: # Iterate over TemplateFile objects
       if template_file.file_type == 'j2':
         file_path = self.template_dir / template_file.relative_path
@@ -245,7 +251,20 @@ class Template:
             ast = self.jinja_env.parse(content) # Use lazy-loaded jinja_env
             used_variables.update(meta.find_undeclared_variables(ast))
         except Exception as e:
-          logger.warning(f"Could not parse Jinja2 variables from {file_path}: {e}")
+          # Collect syntax errors instead of just warning
+          relative_path = file_path.relative_to(self.template_dir)
+          syntax_errors.append(f"  - {relative_path}: {e}")
+    
+    # Raise error if any syntax errors were found
+    if syntax_errors:
+      error_msg = (
+        f"Jinja2 syntax errors found in template '{self.id}':\n" +
+        "\n".join(syntax_errors) +
+        "\n\nPlease fix the syntax errors in the template files."
+      )
+      logger.error(error_msg)
+      raise ValueError(error_msg)
+    
     return used_variables
 
   def _extract_jinja_default_values(self) -> dict[str, object]:
@@ -300,13 +319,23 @@ class Template:
     """Filter specs to only include variables used in templates using VariableCollection.
     
     Uses VariableCollection's native filter_to_used() method.
-    Keeps sensitive variables even if not used.
+    Keeps sensitive variables only if they're defined in the template spec or actually used.
     """
+    # Build set of variables explicitly defined in template spec
+    template_defined_vars = set()
+    for section_data in (template_specs or {}).values():
+      if isinstance(section_data, dict) and 'vars' in section_data:
+        template_defined_vars.update(section_data['vars'].keys())
+    
     # Create VariableCollection from merged specs
     merged_collection = VariableCollection(merged_specs)
     
-    # Filter to only used variables (and sensitive ones)
-    filtered_collection = merged_collection.filter_to_used(used_variables, keep_sensitive=True)
+    # Filter to only used variables (and sensitive ones that are template-defined)
+    # We keep sensitive variables that are either:
+    # 1. Actually used in template files, OR
+    # 2. Explicitly defined in the template spec (even if not yet used)
+    variables_to_keep = used_variables | template_defined_vars
+    filtered_collection = merged_collection.filter_to_used(variables_to_keep, keep_sensitive=False)
     
     # Convert back to dict format
     filtered_specs = {}
@@ -370,13 +399,74 @@ class Template:
 
   @staticmethod
   def _create_jinja_env(searchpath: Path) -> Environment:
-    """Create standardized Jinja2 environment for consistent template processing."""
-    return Environment(
+    """Create standardized Jinja2 environment for consistent template processing.
+    
+    Includes custom filters for generating random values:
+    - random_string(length): Generate random alphanumeric string
+    - random_hex(length): Generate random hexadecimal string
+    - random_base64(length): Generate random base64 string
+    - random_uuid: Generate a UUID4
+    """
+    import secrets
+    import string
+    import base64
+    import uuid
+    
+    env = Environment(
       loader=FileSystemLoader(searchpath),
       trim_blocks=True,
       lstrip_blocks=True,
       keep_trailing_newline=False,
     )
+    
+    # Add custom filters for generating random values
+    def random_string(value: str = '', length: int = 32) -> str:
+      """Generate a random alphanumeric string of specified length.
+      
+      Usage: {{ '' | random_string(64) }} or {{ 'ignored' | random_string(32) }}
+      """
+      alphabet = string.ascii_letters + string.digits
+      return ''.join(secrets.choice(alphabet) for _ in range(length))
+    
+    def pwgen(value: str = '', length: int = 50) -> str:
+      """Generate a secure random string (mimics pwgen -s).
+      
+      Default length is 50 (matching Authentik recommendation).
+      Usage: {{ '' | pwgen }} or {{ '' | pwgen(64) }}
+      """
+      alphabet = string.ascii_letters + string.digits
+      return ''.join(secrets.choice(alphabet) for _ in range(length))
+    
+    def random_hex(value: str = '', length: int = 32) -> str:
+      """Generate a random hexadecimal string of specified length.
+      
+      Usage: {{ '' | random_hex(64) }}
+      """
+      return secrets.token_hex(length // 2)
+    
+    def random_base64(value: str = '', length: int = 32) -> str:
+      """Generate a random base64 string of specified length.
+      
+      Usage: {{ '' | random_base64(64) }}
+      """
+      num_bytes = (length * 3) // 4  # Convert length to approximate bytes
+      return base64.b64encode(secrets.token_bytes(num_bytes)).decode('utf-8')[:length]
+    
+    def random_uuid(value: str = '') -> str:
+      """Generate a random UUID4.
+      
+      Usage: {{ '' | random_uuid }}
+      """
+      return str(uuid.uuid4())
+    
+    # Register filters
+    env.filters['random_string'] = random_string
+    env.filters['pwgen'] = pwgen
+    env.filters['random_hex'] = random_hex
+    env.filters['random_base64'] = random_base64
+    env.filters['random_uuid'] = random_uuid
+    
+    return env
 
   def render(self, variables: VariableCollection) -> Dict[str, str]:
     """Render all .j2 files in the template directory."""
@@ -490,4 +580,6 @@ class Template:
             pass
 
           self.__variables = VariableCollection(filtered_specs)
+          # Sort sections: required first, then enabled, then disabled
+          self.__variables.sort_sections()
       return self.__variables

+ 53 - 14
cli/core/variables.py

@@ -18,6 +18,7 @@ FALSE_VALUES = {"false", "0", "no", "off"}
 HOSTNAME_REGEX = re.compile(r"^(?=.{1,253}$)(?!-)[A-Za-z0-9_-]{1,63}(?<!-)(\.(?!-)[A-Za-z0-9_-]{1,63}(?<!-))*$")
 EMAIL_REGEX = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
 
+
 # !SECTION
 
 # ----------------------
@@ -59,6 +60,8 @@ class Variable:
     self.sensitive: bool = data.get("sensitive", False)
     # Optional extra explanation used by interactive prompts
     self.extra: Optional[str] = data.get("extra")
+    # Flag indicating this variable should be auto-generated when empty
+    self.autogenerated: bool = data.get("autogenerated", False)
 
     # Validate and convert the default/initial value if present
     if self.value is not None:
@@ -222,6 +225,9 @@ class Variable:
     if self.extra:
       var_dict["extra"] = self.extra
     
+    if self.autogenerated:
+      var_dict["autogenerated"] = self.autogenerated
+    
     if self.options:
       var_dict["options"] = self.options
     
@@ -356,6 +362,7 @@ class Variable:
       'origin': self.origin,
       'sensitive': self.sensitive,
       'extra': self.extra,
+      'autogenerated': self.autogenerated,
     }
     
     # Apply updates if provided
@@ -401,7 +408,6 @@ class VariableSection:
     self.key: str = data["key"]
     self.title: str = data["title"]
     self.variables: OrderedDict[str, Variable] = OrderedDict()
-    self.prompt: Optional[str] = data.get("prompt")
     self.description: Optional[str] = data.get("description")
     self.toggle: Optional[str] = data.get("toggle")
     # Default "general" section to required=True, all others to required=False
@@ -424,9 +430,6 @@ class VariableSection:
     if self.description:
       section_dict["description"] = self.description
     
-    if self.prompt:
-      section_dict["prompt"] = self.prompt
-    
     if self.toggle:
       section_dict["toggle"] = self.toggle
     
@@ -498,7 +501,6 @@ class VariableSection:
     cloned = VariableSection({
       'key': self.key,
       'title': self.title,
-      'prompt': self.prompt,
       'description': self.description,
       'toggle': self.toggle,
       'required': self.required,
@@ -574,7 +576,6 @@ class VariableCollection:
     section_init_data = {
       "key": key,
       "title": data.get("title", key.replace("_", " ").title()),
-      "prompt": data.get("prompt"),
       "description": data.get("description"),
       "toggle": data.get("toggle"),
       "required": data.get("required", key == "general")
@@ -604,22 +605,24 @@ class VariableCollection:
     #   - Validate that default values are compatible with their type definitions
 
   def _validate_section_toggle(self, section: VariableSection) -> None:
-    """Validate that toggle variable exists and is of type bool.
+    """Validate that toggle variable is of type bool if it exists.
+    
+    If the toggle variable doesn't exist (e.g., filtered out), removes the toggle.
     
     Args:
         section: The section to validate
         
     Raises:
-        ValueError: If toggle variable doesn't exist or is not boolean type
+        ValueError: If toggle variable exists but is not boolean type
     """
     if not section.toggle:
       return
     
     toggle_var = section.variables.get(section.toggle)
     if not toggle_var:
-      raise ValueError(
-        f"Section '{section.key}' has toggle '{section.toggle}' but variable does not exist"
-      )
+      # Toggle variable doesn't exist (e.g., was filtered out) - remove toggle metadata
+      section.toggle = None
+      return
     
     if toggle_var.type != "bool":
       raise ValueError(
@@ -627,6 +630,40 @@ class VariableCollection:
         f"but is type '{toggle_var.type}'"
       )
 
+  def sort_sections(self) -> None:
+    """Sort sections with the following priority:
+    
+    1. Required sections first (in their original order)
+    2. Enabled sections next (in their original order)
+    3. Disabled sections last (in their original order)
+    
+    This maintains the original ordering within each group while organizing
+    sections logically for display and user interaction.
+    """
+    # Convert to list to maintain order during sorting
+    section_items = list(self._sections.items())
+    
+    # Define sort key: (priority, original_index)
+    # Priority: 0 = required, 1 = enabled, 2 = disabled
+    def get_sort_key(item_with_index):
+      index, (key, section) = item_with_index
+      if section.required:
+        priority = 0
+      elif section.is_enabled():
+        priority = 1
+      else:
+        priority = 2
+      return (priority, index)
+    
+    # Sort with original index to maintain order within each priority group
+    sorted_items = sorted(
+      enumerate(section_items),
+      key=get_sort_key
+    )
+    
+    # Rebuild _sections dict in new order
+    self._sections = {key: section for _, (key, section) in sorted_items}
+
   # -------------------------
   # SECTION: Public API Methods
   # -------------------------
@@ -722,6 +759,11 @@ class VariableCollection:
       # Validate each variable in the section
       for var_name, variable in section.variables.items():
         try:
+          # Skip validation for autogenerated variables when empty/None
+          if variable.autogenerated and (variable.value is None or variable.value == ""):
+            logger.debug(f"Skipping validation for autogenerated variable: '{section.key}.{var_name}'")
+            continue
+          
           # If value is None, treat as missing
           if variable.value is None:
             errors.append(f"{section.key}.{var_name} (missing)")
@@ -825,8 +867,6 @@ class VariableCollection:
     # Update section metadata from other (other takes precedence)
     if other_section.title:
       merged_section.title = other_section.title
-    if other_section.prompt:
-      merged_section.prompt = other_section.prompt
     if other_section.description:
       merged_section.description = other_section.description
     if other_section.toggle:
@@ -899,7 +939,6 @@ class VariableCollection:
       filtered_section = VariableSection({
         'key': section.key,
         'title': section.title,
-        'prompt': section.prompt,
         'description': section.description,
         'toggle': section.toggle,
         'required': section.required,

+ 13 - 0
cli/modules/ci.py

@@ -0,0 +1,13 @@
+from __future__ import annotations
+
+from ..core.module import Module
+from ..core.registry import registry
+
+class CIModule(Module):
+  """Module for managing CI/CD automation templates."""
+  
+  name: str = "ci"
+  description: str = "Manage CI/CD automation templates (GitHub Actions, GitLab CI, Kestra)"
+
+# Register the module
+registry.register(CIModule)

+ 18 - 10
cli/modules/compose.py

@@ -41,7 +41,6 @@ spec = OrderedDict(
       },
       "network": {
         "title": "Network",
-        "prompt": "Enable custom network block?",
         "toggle": "network_enabled",
         "vars": {
           "network_enabled": {
@@ -63,7 +62,6 @@ spec = OrderedDict(
       },
       "ports": {
         "title": "Ports",
-        "prompt": "Expose ports via 'ports' mapping?",
         "toggle": "ports_enabled",
         "vars": {
           "ports_enabled": {
@@ -75,7 +73,6 @@ spec = OrderedDict(
       },
       "traefik": {
         "title": "Traefik",
-        "prompt": "Enable Traefik reverse proxy integration?",
         "toggle": "traefik_enabled",
         "description": "Traefik routes external traffic to your service.",
         "vars": {
@@ -116,7 +113,6 @@ spec = OrderedDict(
       },
       "swarm": {
         "title": "Docker Swarm",
-        "prompt": "Enable Docker Swarm deployment?",
         "toggle": "swarm_enabled",
         "description": "Deploy service in Docker Swarm mode with replicas.",
         "vars": {
@@ -134,7 +130,6 @@ spec = OrderedDict(
       },
       "database": {
         "title": "Database",
-        "prompt": "Configure external database connection?",
         "toggle": "database_enabled",
         "description": "Connect to external database (PostgreSQL, MySQL, MariaDB, etc.)",
         "vars": {
@@ -143,6 +138,18 @@ spec = OrderedDict(
             "type": "bool",
             "default": False,
           },
+          "database_type": {
+            "description": "Database type",
+            "type": "enum",
+            "options": ["postgres", "mysql", "mariadb"],
+            "default": "postgres",
+          },
+          "database_external": {
+            "description": "Use an external database server?",
+            "extra": "If 'no', a database container will be created in the compose project.",
+            "type": "bool",
+            "default": False,
+          },
           "database_host": {
             "description": "Database host",
             "type": "str",
@@ -163,14 +170,14 @@ spec = OrderedDict(
           "database_password": {
             "description": "Database password",
             "type": "str",
+            "sensitive": True,
           },
         },
       },
       "email": {
         "title": "Email Server",
-        "prompt": "Configure email server for notifications and user management?",
         "toggle": "email_enabled",
-        "description": "Used for notifications, sign-ups, password resets, and alerts.",
+        "description": "Configure email server for notifications and user management.",
         "vars": {
           "email_enabled": {
             "description": "Enable email server configuration",
@@ -193,6 +200,7 @@ spec = OrderedDict(
           "email_password": {
             "description": "SMTP password",
             "type": "str",
+            "sensitive": True,
           },
           "email_from": {
             "description": "From email address",
@@ -207,14 +215,13 @@ spec = OrderedDict(
             "description": "Use SSL encryption",
             "type": "bool",
             "default": False,
-          },
+          }
         },
       },
       "authentik": {
         "title": "Authentik SSO",
-        "prompt": "Configure Authentik SSO integration?",
         "toggle": "authentik_enabled",
-        "description": "Single Sign-On using Authentik identity provider.",
+        "description": "Integrate with Authentik for Single Sign-On authentication.",
         "vars": {
           "authentik_enabled": {
             "description": "Enable Authentik SSO integration",
@@ -236,6 +243,7 @@ spec = OrderedDict(
           "authentik_client_secret": {
             "description": "OAuth client secret from Authentik provider",
             "type": "str",
+            "sensitive": True,
           },
         },
       },

+ 0 - 13
cli/modules/github_actions.py

@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ..core.module import Module
-from ..core.registry import registry
-
-class GitHubActionsModule(Module):
-  """Module for managing GitHub Actions workflows."""
-  
-  name: str = "github-actions"
-  description: str = "Manage GitHub Actions workflows"
-
-# Register the module
-registry.register(GitHubActionsModule)

+ 0 - 13
cli/modules/gitlab_ci.py

@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ..core.module import Module
-from ..core.registry import registry
-
-class GitLabCIModule(Module):
-  """Module for managing GitLab CI/CD pipelines."""
-  
-  name: str = "gitlab-ci"
-  description: str = "Manage GitLab CI/CD pipelines"
-
-# Register the module
-registry.register(GitLabCIModule)

+ 0 - 13
cli/modules/kestra.py

@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ..core.module import Module
-from ..core.registry import registry
-
-class KestraModule(Module):
-  """Module for managing Kestra workflows and configurations."""
-  
-  name: str = "kestra"
-  description: str = "Manage Kestra workflows and configurations"
-
-# Register the module
-registry.register(KestraModule)

+ 0 - 0
library/github-actions/kubectl/kubernetes-deploy.yml → library/ci/github-actions-kubectl/kubernetes-deploy.yml


+ 0 - 0
library/github-actions/scp-action/copy-config-files.yml → library/ci/github-actions-scp/copy-config-files.yml


+ 0 - 0
library/github-actions/ssh-action/restart-docker.yml → library/ci/github-actions-ssh/restart-docker.yml


+ 0 - 0
library/gitlab-ci/ansible/run.yml → library/ci/gitlab-ci-ansible/run.yml


+ 0 - 0
library/gitlab-ci/ansible/test.yml → library/ci/gitlab-ci-ansible/test.yml


+ 0 - 0
library/gitlab-ci/docker/config.yml → library/ci/gitlab-ci-docker/config.yml


+ 0 - 0
library/gitlab-ci/docker/deploy.yml → library/ci/gitlab-ci-docker/deploy.yml


+ 0 - 0
library/gitlab-ci/docker/test.yml → library/ci/gitlab-ci-docker/test.yml


+ 0 - 0
library/gitlab-ci/terraform/apply.yml → library/ci/gitlab-ci-terraform/apply.yml


+ 0 - 0
library/gitlab-ci/terraform/validate.yml → library/ci/gitlab-ci-terraform/validate.yml


+ 0 - 0
library/kestra/ansible/ansible-playbook-git.yaml → library/ci/kestra-ansible/ansible-playbook-git.yaml


+ 0 - 0
library/kestra/ansible/ansible-playbook-inline.yaml → library/ci/kestra-ansible/ansible-playbook-inline.yaml


+ 0 - 0
library/kestra/docker/docker-build-git.yaml → library/ci/kestra-docker/docker-build-git.yaml


+ 0 - 0
library/kestra/docker/docker-build-inline.yaml → library/ci/kestra-docker/docker-build-inline.yaml


+ 0 - 0
library/kestra/inputs.yaml → library/ci/kestra-inputs/kestra-inputs.yaml


+ 0 - 0
library/kestra/python/python_command.yaml → library/ci/kestra-python/python_command.yaml


+ 0 - 0
library/kestra/python/python_script.yaml → library/ci/kestra-python/python_script.yaml


+ 0 - 0
library/kestra/variables.yaml → library/ci/kestra-variables/kestra-variables.yaml


+ 0 - 0
library/kestra/webhook.yaml → library/ci/kestra-webhook/kestra-webhook.yaml


+ 0 - 31
library/compose/alloy.docker-logs/config/config.alloy.j2

@@ -1,31 +0,0 @@
-discovery.docker "dockerlogs" {
-  host = "unix:///var/run/docker.sock"
-}
-
-discovery.relabel "dockerlogs" {
-      targets = []
-  
-      rule {
-          source_labels = ["__meta_docker_container_name"]
-          regex = "/(.*)"
-          target_label = "service_name"
-      }
-
-  }
-
-loki.source.docker "default" {
-  host       = "unix:///var/run/docker.sock"
-  targets    = discovery.docker.dockerlogs.targets
-  labels     = {"platform" = "docker"}
-  relabel_rules = discovery.relabel.dockerlogs.rules
-  forward_to = [loki.write.default.receiver]
-}
-
-/* Targets: define remote endpoints used by these fragments */
-
-loki.write "default" {
-  endpoint {
-    url = "{{ loki_url }}"
-  }
-  external_labels = {}
-}

+ 0 - 11
library/compose/alloy.docker-logs/template.yaml

@@ -1,11 +0,0 @@
----
-kind: compose
-metadata:
-  name: Alloy - Docker Logs
-  description: Minimal fragment to collect Docker container logs for Alloy.
-  version: 0.0.1
-  author: Christian Lempa
-  date: '2025-09-30'
-spec:
-  general:
-    vars: {}

+ 0 - 18
library/compose/alloy.docker-metrics/config/config.alloy.j2

@@ -1,18 +0,0 @@
-/* Targets: define remote write endpoint(s) used by these fragments */
-
-prometheus.remote_write "default" {
-  endpoint {
-    url = "{{ prometheus_url }}"
-  }
-}
-
-prometheus.exporter.cadvisor "dockermetrics" {
-  docker_host = "unix:///var/run/docker.sock"
-  storage_duration = "5m"
-}
-
-prometheus.scrape "dockermetrics" {
-  targets    = prometheus.exporter.cadvisor.dockermetrics.targets
-  forward_to = [ prometheus.remote_write.default.receiver ]
-  scrape_interval = "10s"
-}

+ 0 - 15
library/compose/alloy.docker-metrics/template.yaml

@@ -1,15 +0,0 @@
----
-kind: compose
-metadata:
-  name: Alloy - Docker Metrics
-  description: Minimal fragment to collect Docker metrics (cAdvisor) for Alloy.
-  version: 0.0.1
-  author: Christian Lempa
-  date: '2025-09-30'
-spec:
-  general:
-    vars:
-      prometheus_url:
-        type: url
-        description: Prometheus remote write endpoint (http or https)
-        default: "http://prometheus:9090/api/v1/write"

+ 0 - 53
library/compose/alloy.system-logs/config/config.alloy.j2

@@ -1,53 +0,0 @@
-/* Targets: define remote endpoints used by these fragments */
-
-loki.write "default" {
-  endpoint {
-    url = "{{ loki_url }}"
-  }
-  external_labels = {}
-}
-
-// SECTION: SYSTEM LOGS & JOURNAL
-
-loki.source.journal "journal" {
-  max_age       = "24h0m0s"
-  relabel_rules = discovery.relabel.journal.rules
-  forward_to    = [loki.write.default.receiver]
-  labels        = {component = string.format("%s-journal", constants.hostname)}
-  // NOTE: This is important to fix https://github.com/grafana/alloy/issues/924
-  path          = "/var/log/journal"
-}
-
-local.file_match "system" {
-  path_targets = [{
-    __address__ = "localhost",
-    __path__    = "/var/log/{syslog,messages,*.log}",
-    instance    = constants.hostname,
-    job         = string.format("%s-logs", constants.hostname),
-  }]
-}
-
-discovery.relabel "journal" {
-  targets = []
-  rule {
-    source_labels = ["__journal__systemd_unit"]
-    target_label  = "unit"
-  }
-  rule {
-    source_labels = ["__journal__boot_id"]
-    target_label  = "boot_id"
-  }
-  rule {
-    source_labels = ["__journal__transport"]
-    target_label  = "transport"
-  }
-  rule {
-    source_labels = ["__journal_priority_keyword"]
-    target_label  = "level"
-  }
-}
-
-loki.source.file "system" {
-  targets    = local.file_match.system.targets
-  forward_to = [loki.write.default.receiver]
-}

+ 0 - 15
library/compose/alloy.system-logs/template.yaml

@@ -1,15 +0,0 @@
----
-kind: compose
-metadata:
-  name: Alloy - System Logs & Journal
-  description: Minimal fragment to collect system logs and journal for Alloy.
-  version: 0.0.1
-  author: Christian Lempa
-  date: '2025-09-30'
-spec:
-  general:
-    vars:
-      loki_url:
-        type: url
-        description: URL for Loki write endpoint (http or https)
-        default: "http://loki:3100/loki/api/v1/push"

+ 0 - 41
library/compose/alloy.system-metrics/config/config.alloy.j2

@@ -1,41 +0,0 @@
-/* Targets: define remote write endpoint(s) used by these fragments */
-
-prometheus.remote_write "default" {
-  endpoint {
-    url = "{{ prometheus_url }}"
-  }
-}
-
-discovery.relabel "metrics" {
-  targets = prometheus.exporter.unix.metrics.targets
-  rule {
-    target_label = "instance"
-    replacement  = constants.hostname
-  }
-  rule {
-    target_label = "job"
-    replacement = string.format("%s-metrics", constants.hostname)
-  }
-}
-
-prometheus.exporter.unix "metrics" {
-  disable_collectors = ["ipvs", "btrfs", "infiniband", "xfs", "zfs"]
-  enable_collectors = ["meminfo"]
-  filesystem {
-    fs_types_exclude     = "^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|tmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$"
-    mount_points_exclude = "^/(dev|proc|run/credentials/.+|sys|var/lib/docker/.+)($|/)"
-    mount_timeout        = "5s"
-  }
-  netclass {
-    ignored_devices = "^(veth.*|cali.*|[a-f0-9]{15})$"
-  }
-  netdev {
-    device_exclude = "^(veth.*|cali.*|[a-f0-9]{15})$"
-  }
-}
-
-prometheus.scrape "metrics" {
-scrape_interval = "15s"
-  targets    = discovery.relabel.metrics.output
-  forward_to = [prometheus.remote_write.default.receiver]
-}

+ 0 - 15
library/compose/alloy.system-metrics/template.yaml

@@ -1,15 +0,0 @@
----
-kind: compose
-metadata:
-  name: Alloy - System Metrics
-  description: Minimal fragment to collect system (node) metrics for Alloy.
-  version: 0.0.1
-  author: Christian Lempa
-  date: '2025-09-30'
-spec:
-  general:
-    vars:
-      prometheus_url:
-        type: url
-        description: Prometheus remote write endpoint (http or https)
-        default: "http://prometheus:9090/api/v1/write"

+ 9 - 1
library/compose/alloy/compose.yaml.j2

@@ -15,12 +15,20 @@ services:
     volumes:
       - ./config/config.alloy:/etc/alloy/config.alloy
       - alloy_data:/var/lib/alloy/data
+      {% if logs_enabled or metrics_enabled %}
       - /:/rootfs:ro
+      - /sys:/sys:ro
+      {% endif %}
+      {% if logs_enabled and logs_system %}
       - /run:/run:ro
       - /var/log:/var/log:ro
-      - /sys:/sys:ro
+      {% endif %}
+      {% if (logs_enabled and logs_docker) or (metrics_enabled and metrics_docker) %}
       - /var/lib/docker/:/var/lib/docker/:ro
+      {% endif %}
+      {% if metrics_enabled and metrics_system %}
       - /run/udev/data:/run/udev/data:ro
+      {% endif %}
     {% if network_enabled %}
     networks:
       - {{ network_name | default("bridge") }}

+ 0 - 16
library/compose/alloy/config/config.alloy

@@ -1,16 +0,0 @@
-/* Grafana Alloy Configuration Examples
- * ---
- * LINK: For more details, visit https://github.com/grafana/alloy-scenarios
-
- * This file intentionally contains no pipeline/function definitions.
- * Use the sub-templates to generate specific configuration fragments:
- * - alloy.system-logs
- * - alloy.system-metrics
- * - alloy.docker-metrics
- * - alloy.docker-logs
- *
- * When generating a full configuration you can concatenate the generated
- * fragments into `config/config.alloy` or manage them as separate files.
- */
-
-// Minimal placeholder - no functions here by design.

+ 179 - 0
library/compose/alloy/config/config.alloy.j2

@@ -0,0 +1,179 @@
+/* Grafana Alloy Configuration
+ * ---
+ * Generated by boilerplates CLI
+ * 
+ * This configuration file is dynamically generated based on your selected
+ * log and metrics collection options.
+ *
+ * For more details, visit https://github.com/grafana/alloy-scenarios
+ */
+
+{% if logs_enabled or metrics_enabled %}
+// ============================================================================
+// REMOTE ENDPOINTS
+// ============================================================================
+
+{% if logs_enabled %}
+/* Loki Write Endpoint */
+loki.write "default" {
+  endpoint {
+    url = "{{ logs_loki_url }}"
+  }
+  external_labels = {}
+}
+{% endif %}
+
+{% if metrics_enabled %}
+/* Prometheus Remote Write Endpoint */
+prometheus.remote_write "default" {
+  endpoint {
+    url = "{{ metrics_prometheus_url }}"
+  }
+}
+{% endif %}
+
+{% if logs_enabled %}
+// ============================================================================
+// LOG COLLECTION
+// ============================================================================
+
+{% if logs_docker %}
+/* Docker Container Logs */
+discovery.docker "dockerlogs" {
+  host = "unix:///var/run/docker.sock"
+}
+
+discovery.relabel "dockerlogs" {
+  targets = []
+
+  rule {
+    source_labels = ["__meta_docker_container_name"]
+    regex = "/(.*)"
+    target_label = "service_name"
+  }
+}
+
+loki.source.docker "default" {
+  host       = "unix:///var/run/docker.sock"
+  targets    = discovery.docker.dockerlogs.targets
+  labels     = {"platform" = "docker"}
+  relabel_rules = discovery.relabel.dockerlogs.rules
+  forward_to = [loki.write.default.receiver]
+}
+{% endif %}
+
+{% if logs_system %}
+/* System Logs & Journal */
+loki.source.journal "journal" {
+  max_age       = "24h0m0s"
+  relabel_rules = discovery.relabel.journal.rules
+  forward_to    = [loki.write.default.receiver]
+  labels        = {component = string.format("%s-journal", constants.hostname)}
+  // NOTE: This is important to fix https://github.com/grafana/alloy/issues/924
+  path          = "/var/log/journal"
+}
+
+local.file_match "system" {
+  path_targets = [{
+    __address__ = "localhost",
+    __path__    = "/var/log/{syslog,messages,*.log}",
+    instance    = constants.hostname,
+    job         = string.format("%s-logs", constants.hostname),
+  }]
+}
+
+discovery.relabel "journal" {
+  targets = []
+  rule {
+    source_labels = ["__journal__systemd_unit"]
+    target_label  = "unit"
+  }
+  rule {
+    source_labels = ["__journal__boot_id"]
+    target_label  = "boot_id"
+  }
+  rule {
+    source_labels = ["__journal__transport"]
+    target_label  = "transport"
+  }
+  rule {
+    source_labels = ["__journal_priority_keyword"]
+    target_label  = "level"
+  }
+}
+
+loki.source.file "system" {
+  targets    = local.file_match.system.targets
+  forward_to = [loki.write.default.receiver]
+}
+{% endif %}
+{% endif %}
+
+{% if metrics_enabled %}
+// ============================================================================
+// METRICS COLLECTION
+// ============================================================================
+
+{% if metrics_docker %}
+/* Docker Container Metrics (cAdvisor) */
+prometheus.exporter.cadvisor "dockermetrics" {
+  docker_host = "unix:///var/run/docker.sock"
+  storage_duration = "5m"
+}
+
+prometheus.scrape "dockermetrics" {
+  targets    = prometheus.exporter.cadvisor.dockermetrics.targets
+  forward_to = [prometheus.remote_write.default.receiver]
+  scrape_interval = "10s"
+}
+{% endif %}
+
+{% if metrics_system %}
+/* System (Node) Metrics */
+discovery.relabel "metrics" {
+  targets = prometheus.exporter.unix.metrics.targets
+  rule {
+    target_label = "instance"
+    replacement  = constants.hostname
+  }
+  rule {
+    target_label = "job"
+    replacement = string.format("%s-metrics", constants.hostname)
+  }
+}
+
+prometheus.exporter.unix "metrics" {
+  disable_collectors = ["ipvs", "btrfs", "infiniband", "xfs", "zfs"]
+  enable_collectors = ["meminfo"]
+  filesystem {
+    fs_types_exclude     = "^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|tmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$"
+    mount_points_exclude = "^/(dev|proc|run/credentials/.+|sys|var/lib/docker/.+)($|/)"
+    mount_timeout        = "5s"
+  }
+  netclass {
+    ignored_devices = "^(veth.*|cali.*|[a-f0-9]{15})$"
+  }
+  netdev {
+    device_exclude = "^(veth.*|cali.*|[a-f0-9]{15})$"
+  }
+}
+
+prometheus.scrape "metrics" {
+  scrape_interval = "15s"
+  targets    = discovery.relabel.metrics.output
+  forward_to = [prometheus.remote_write.default.receiver]
+}
+{% endif %}
+{% endif %}
+
+{% else %}
+// ============================================================================
+// NO COLLECTION ENABLED
+// ============================================================================
+// 
+// No log or metrics collection is currently enabled.
+// To enable collection, set logs_enabled or metrics_enabled to true when
+// generating this template.
+//
+// For more information, visit: https://grafana.com/docs/alloy/latest/
+{% endif %}

+ 44 - 2
library/compose/alloy/template.yaml

@@ -14,9 +14,9 @@ metadata:
     Source: https://github.com/grafana/alloy
 
     Documentation: https://grafana.com/docs/alloy/latest/
-  version: 0.0.1
+  version: 0.1.0
   author: Christian Lempa
-  date: '2025-09-28'
+  date: '2025-10-02'
   tags:
     - monitoring
     - grafana
@@ -31,3 +31,45 @@ spec:
         type: int
         description: Main port for Alloy HTTP server
         default: 12345
+  logs:
+    name: Log Collection
+    description: Configure log collection for Docker containers and system logs
+    toggle: logs_enabled
+    vars:
+      logs_enabled:
+        type: bool
+        description: Enable log collection
+        default: false
+      logs_loki_url:
+        type: url
+        description: Loki endpoint URL for sending logs
+        default: "http://loki:3100/loki/api/v1/push"
+      logs_docker:
+        type: bool
+        description: Enable Docker container log collection
+        default: true
+      logs_system:
+        type: bool
+        description: Enable system and journalctl log collection
+        default: true
+  metrics:
+    name: Metrics Collection
+    description: Configure metrics collection for Docker containers and system metrics
+    toggle: metrics_enabled
+    vars:
+      metrics_enabled:
+        type: bool
+        description: Enable metrics collection
+        default: false
+      metrics_prometheus_url:
+        type: url
+        description: Prometheus remote write endpoint
+        default: "http://prometheus:9090/api/v1/write"
+      metrics_docker:
+        type: bool
+        description: Enable Docker container metrics collection (cAdvisor)
+        default: true
+      metrics_system:
+        type: bool
+        description: Enable system (node) metrics collection
+        default: true

+ 31 - 0
library/compose/authentik/.env.authentik.j2

@@ -0,0 +1,31 @@
+# Authentik Application Configuration
+# Contains sensitive application secrets and connection strings
+
+# Timezone
+TZ={{ container_timezone | default('UTC') }}
+
+# Secret Key (used for cookie signing and unique user IDs)
+AUTHENTIK_SECRET_KEY={{ authentik_secret_key if authentik_secret_key else (none | pwgen(50)) }}
+
+# Error Reporting
+AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting | default(false) }}
+
+# Redis Connection
+AUTHENTIK_REDIS__HOST={{ service_name | default('authentik') }}-redis
+
+# PostgreSQL Connection
+AUTHENTIK_POSTGRESQL__HOST={{ service_name | default('authentik') }}-postgres
+AUTHENTIK_POSTGRESQL__USER={{ database_user | default('authentik') }}
+AUTHENTIK_POSTGRESQL__NAME={{ database_name | default('authentik') }}
+AUTHENTIK_POSTGRESQL__PASSWORD={{ database_password | default('authentik') }}
+
+{% if email_enabled -%}
+# Email Server Configuration
+AUTHENTIK_EMAIL__HOST={{ email_host }}
+AUTHENTIK_EMAIL__PORT={{ email_port | default(25) }}
+AUTHENTIK_EMAIL__USERNAME={{ email_username }}
+AUTHENTIK_EMAIL__PASSWORD={{ email_password }}
+AUTHENTIK_EMAIL__USE_TLS={{ email_use_tls | default(false) }}
+AUTHENTIK_EMAIL__USE_SSL={{ email_use_ssl | default(false) }}
+AUTHENTIK_EMAIL__FROM={{ email_from }}
+{% endif %}

+ 9 - 0
library/compose/authentik/.env.postgres.j2

@@ -0,0 +1,9 @@
+# PostgreSQL Database Configuration
+# Contains only database credentials needed by Postgres container
+
+# Timezone
+TZ={{ container_timezone | default('UTC') }}
+
+POSTGRES_USER={{ database_user | default('authentik') }}
+POSTGRES_PASSWORD={{ database_password | default('authentik') }}
+POSTGRES_DB={{ database_name | default('authentik') }}

+ 9 - 52
library/compose/authentik/compose.yaml.j2

@@ -3,27 +3,8 @@ services:
     image: ghcr.io/goauthentik/server:2025.6.3
     container_name: {{ container_name | default('authentik-server') }}
     command: server
-    environment:
-      - TZ={{ container_timezone | default('UTC') }}
-      - AUTHENTIK_REDIS__HOST={{ service_name | default('authentik') }}-redis
-      - AUTHENTIK_POSTGRESQL__HOST={{ service_name | default('authentik') }}-postgres
-      - AUTHENTIK_POSTGRESQL__USER={{ database_user | default('authentik') }}
-      - AUTHENTIK_POSTGRESQL__NAME={{ database_name | default('authentik') }}
-      - AUTHENTIK_POSTGRESQL__PASSWORD={{ database_password | default('authentik') }}
-      {% if authentik_secret_key -%}
-      - AUTHENTIK_SECRET_KEY={{ authentik_secret_key }}
-      {% endif %}
-      - AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting | default(false) }}
-      {% if email_enabled -%}
-      - AUTHENTIK_EMAIL__HOST={{ email_host }}
-      - AUTHENTIK_EMAIL__PORT={{ email_port | default(25) }}
-      - AUTHENTIK_EMAIL__USERNAME={{ email_username }}
-      - AUTHENTIK_EMAIL__PASSWORD={{ email_password }}
-      - AUTHENTIK_EMAIL__USE_TLS={{ email_use_tls | default(false) }}
-      - AUTHENTIK_EMAIL__USE_SSL={{ email_use_ssl | default(false) }}
-      - AUTHENTIK_EMAIL__TIMEOUT={{ email_timeout | default(10) }}
-      - AUTHENTIK_EMAIL__FROM={{ email_from }}
-      {% endif %}
+    env_file:
+      - .env.authentik
     {% if ports_enabled %}
     ports:
       - "{{ ports_http | default(9000) }}:9000"
@@ -59,27 +40,8 @@ services:
     image: ghcr.io/goauthentik/server:2025.6.3
     container_name: {{ service_name | default('authentik') }}-worker
     command: worker
-    environment:
-      - TZ={{ container_timezone | default('UTC') }}
-      - AUTHENTIK_REDIS__HOST={{ service_name | default('authentik') }}-redis
-      - AUTHENTIK_POSTGRESQL__HOST={{ service_name | default('authentik') }}-postgres
-      - AUTHENTIK_POSTGRESQL__USER={{ database_user | default('authentik') }}
-      - AUTHENTIK_POSTGRESQL__NAME={{ database_name | default('authentik') }}
-      - AUTHENTIK_POSTGRESQL__PASSWORD={{ database_password | default('authentik') }}
-      {% if authentik_secret_key -%}
-      - AUTHENTIK_SECRET_KEY={{ authentik_secret_key }}
-      {% endif %}
-      - AUTHENTIK_ERROR_REPORTING__ENABLED={{ authentik_error_reporting | default(false) }}
-      {% if email_enabled -%}
-      - AUTHENTIK_EMAIL__HOST={{ email_host }}
-      - AUTHENTIK_EMAIL__PORT={{ email_port | default(25) }}
-      - AUTHENTIK_EMAIL__USERNAME={{ email_username }}
-      - AUTHENTIK_EMAIL__PASSWORD={{ email_password }}
-      - AUTHENTIK_EMAIL__USE_TLS={{ email_use_tls | default(false) }}
-      - AUTHENTIK_EMAIL__USE_SSL={{ email_use_ssl | default(false) }}
-      - AUTHENTIK_EMAIL__TIMEOUT={{ email_timeout | default(10) }}
-      - AUTHENTIK_EMAIL__FROM={{ email_from }}
-      {% endif %}
+    env_file:
+      - .env.authentik
     user: root
     volumes:
       - /run/docker.sock:/run/docker.sock
@@ -99,8 +61,6 @@ services:
     image: docker.io/library/redis:8.2.1
     container_name: {{ service_name | default('authentik') }}-redis
     command: --save 60 1 --loglevel warning
-    environment:
-      - TZ={{ container_timezone | default('UTC') }}
     healthcheck:
       test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
       start_period: 20s
@@ -115,17 +75,14 @@ services:
     {% endif %}
     restart: {{ restart_policy | default('unless-stopped') }}
 
-  {{ if not database_external }}
+  {% if not database_external %}
   {{ service_name | default('authentik') }}-postgres:
     image: docker.io/library/postgres:17.6
     container_name: {{ service_name | default('authentik') }}-db
-    environment:
-      - POSTGRES_USER={{ database_user | default('authentik') }}
-      - POSTGRES_PASSWORD={{ database_password | default('authentik') }}
-      - POSTGRES_DB={{ database_name | default('authentik') }}
-      - TZ={{ container_timezone | default('UTC') }}
+    env_file:
+      - .env.postgres
     healthcheck:
-      test: ['CMD-SHELL', 'pg_isready -U "{{ database_user | default('authentik') }}"']
+      test: ["CMD-SHELL", "pg_isready -U {{ database_user | default('authentik') }}"]
       start_period: 30s
       interval: 10s
       timeout: 10s
@@ -137,7 +94,7 @@ services:
       - {{ network_name | default('bridge') }}
     {% endif %}
     restart: {{ restart_policy | default('unless-stopped') }}
-  {{ endif }}
+  {% endif %}
 
 volumes:
   database_data:

+ 40 - 12
library/compose/authentik/template.yaml

@@ -1,21 +1,49 @@
 ---
 kind: compose
 metadata:
-  name: Authentik-Server
-  description: Docker compose setup for authentik-server
+  name: Authentik
+  description: >
+    Integrate Authentik Single Sign-On (SSO) for secure and streamlined user authentication.
+    Authentik is an open-source identity provider that supports various authentication protocols.
+    This configuration enables OAuth-based SSO, allowing users to log in using their Authentik
+    credentials, enhancing security and user experience.
+
+
+    Project: https://goauthentik.io/
+
+    Documentation: https://goauthentik.io/docs/
+
+    GitHub: https://github.com/goauthentik/authentik
   version: 0.1.0
   author: Christian Lempa
   date: '2025-09-28'
   tags:
-  - authentik-server
-  - docker
-  - compose
+    - authentication
 spec:
-  general:
+  database:
+    required: true
+  ports:
     vars:
-      authentik-server_version:
-        type: string
-        description: Authentik-Server version
-        default: latest
-
----
+      ports_http:
+        description: "Host port for HTTP (80)"
+        type: int
+        default: 8000
+      ports_https:
+        description: "Host port for HTTPS (443)"
+        type: int
+        default: 8443
+  authentik:
+    description: "Configure Authentik application settings"
+    required: true
+    vars:
+      authentik_error_reporting:
+        description: "Enable error reporting to Authentik developers"
+        type: bool
+        default: false
+      authentik_secret_key:
+        description: "Secret key used for cookie signing and unique user IDs"
+        extra: "Leave empty for auto-generated 50-character secure key"
+        type: str
+        sensitive: true
+        autogenerated: true
+        default: ""

+ 0 - 2
library/compose/bind9/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Bind9 version
         default: latest
-
----

+ 0 - 2
library/compose/cadvisor/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Cadvisor version
         default: latest
-
----

+ 0 - 2
library/compose/checkmk/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Monitoring version
         default: latest
-
----

+ 0 - 2
library/compose/clamav/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Clamav version
         default: latest
-
----

+ 0 - 2
library/compose/dockge/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Dockge version
         default: latest
-
----

+ 0 - 8
library/compose/gitea/.env.example

@@ -1,8 +0,0 @@
-# Environment Variable Example File
-# ---
-# Add internal database credentials here...
-# POSTGRES_HOST     = "your-database-host"
-# POSTGRES_PORT     = "your-database-port"
-POSTGRES_DB       = "your-database-name"
-POSTGRES_USER     = "your-database-user"
-POSTGRES_PASSWORD = "your-database-password"

+ 20 - 0
library/compose/gitea/.env.gitea.j2

@@ -0,0 +1,20 @@
+# Gitea Application Configuration
+# Contains Gitea-specific settings and database connection strings
+
+# Timezone
+TZ={{ container_timezone | default('UTC') }}
+
+# User/Group IDs
+USER_UID={{ user_uid | default(1000) }}
+USER_GID={{ user_gid | default(1000) }}
+
+# Database Configuration
+GITEA__database__DB_TYPE=postgres
+GITEA__database__HOST={{ service_name | default('gitea') }}-postgres:5432
+GITEA__database__NAME={{ database_name | default('gitea') }}
+GITEA__database__USER={{ database_user | default('gitea') }}
+GITEA__database__PASSWD={{ database_password | default('gitea') }}
+
+# Server Configuration
+GITEA__server__SSH_PORT={{ gitea_ssh_port | default(2221) }}
+GITEA__server__ROOT_URL={{ gitea_root_url | default('http://localhost:3000') }}

+ 9 - 0
library/compose/gitea/.env.postgres.j2

@@ -0,0 +1,9 @@
+# PostgreSQL Database Configuration
+# Contains only database credentials needed by Postgres container
+
+# Timezone
+TZ={{ container_timezone | default('UTC') }}
+
+POSTGRES_USER={{ database_user | default('gitea') }}
+POSTGRES_PASSWORD={{ database_password | default('gitea') }}
+POSTGRES_DB={{ database_name | default('gitea') }}

+ 62 - 77
library/compose/gitea/compose.yaml.j2

@@ -1,89 +1,74 @@
 services:
-  server:
+  {{ service_name | default('gitea') }}:
     image: docker.io/gitea/gitea:1.24.5
-    container_name: gitea-server
-    environment:
-      - USER_UID=1000
-      - USER_GID=1000
-      # -- Change your database settings here...
-      # --> PostgreSQL
-      - GITEA__database__DB_TYPE=postgres
-      - GITEA__database__HOST=${POSTGRES_HOST:-db}:${POSTGRES_PORT:-5432}
-      - GITEA__database__NAME=${POSTGRES_DB:?POSTGRES_DB not set}
-      - GITEA__database__USER=${POSTGRES_USER:?POSTGRES_USER not set}
-      - GITEA__database__PASSWD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD not set}
-      # <--
-      # --> OR MySQL
-      # - GITEA__database__DB_TYPE=mysql
-      # - GITEA__database__HOST=db:3306
-      # - GITEA__database__NAME=${MYSQL_DATABASE:?MYSQL_DATABASE not set}
-      # - GITEA__database__USER=${MYSQL_USER:?MYSQL_USER not set}
-      # - GITEA__database__PASSWD=${MYSQL_PASSWORD:?MYSQL_PASSWORD not set}
-      # <--
-      # -- (Optional) Change your server settings here...
-      - GITEA__server__SSH_PORT=2221  # <-- (Optional) Replace with your desired SSH port
-      - GITEA__server__ROOT_URL=http://your-fqdn  # <-- Replace with your FQDN
-    # --> (Optional) When using traefik...
-    # networks:
-    #   - frontend
-    # <--
-    # --> (Optional) When using an internal database...
-    #   - backend
-    # <--
+    container_name: {{ container_name | default('gitea-server') }}
+    env_file:
+      - .env.gitea
+    {% if ports_enabled %}
+    ports:
+      - "{{ ports_http | default(3000) }}:3000"
+      - "{{ ports_ssh | default(2221) }}:22"
+    {% endif %}
+    {% if network_enabled %}
+    networks:
+      - {{ network_name | default('bridge') }}
+    {% endif %}
+    {% if traefik_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.http.services.{{ service_name | default('gitea') }}.loadbalancer.server.port=3000
+      - traefik.http.services.{{ service_name | default('gitea') }}.loadbalancer.server.scheme=http
+      - traefik.http.routers.{{ service_name | default('gitea') }}.rule=Host(`{{ traefik_host }}`)
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name | default('gitea') }}.entrypoints={{ traefik_tls_entrypoint | default('websecure') }}
+      - traefik.http.routers.{{ service_name | default('gitea') }}.tls=true
+      - traefik.http.routers.{{ service_name | default('gitea') }}.tls.certresolver={{ traefik_tls_certresolver }}
+      {% else %}
+      - traefik.http.routers.{{ service_name | default('gitea') }}.entrypoints={{ traefik_entrypoint | default('web') }}
+      {% endif %}
+    {% endif %}
     volumes:
       - gitea-data:/data
       - /etc/timezone:/etc/timezone:ro
       - /etc/localtime:/etc/localtime:ro
-    ports:
-      # --> (Optional) Remove when using traefik...
-      - "3000:3000"
-      # <--
-      - "2221:22"  # <-- (Optional) Replace with your desired SSH port
-      # --> (Optional) When using internal database...
-    # depends_on:
-    #   - db
-    # <--
-    # --> (Optional) When using traefik...
-    # labels:
-    #   - traefik.enable=true
-    #   - traefik.http.services.gitea.loadbalancer.server.port=3000
-    #   - traefik.http.services.gitea.loadbalancer.server.scheme=http
-    #   - traefik.http.routers.gitea-https.entrypoints=websecure
-    #   - traefik.http.routers.gitea-https.rule=Host(`your-fqdn`)  # <-- Replace with your FQDN
-    #   - traefik.http.routers.gitea-https.tls=true
-    #   - traefik.http.routers.gitea-https.tls.certresolver=your-certresolver  # <-- Replace with your certresolver
-    # <--
-    restart: unless-stopped
+    depends_on:
+      - {{ service_name | default('gitea') }}-postgres
+    restart: {{ restart_policy | default('unless-stopped') }}
 
-# --> When using internal database
-# db:
-#   image: docker.io/library/postgres:17.5
-#   container_name: gitea-db
-#   environment:
-#     - POSTGRES_USER=${POSTGRES_USER:?POSTGRES_USER not set}
-#     - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD not set}
-#     - POSTGRES_DB=${POSTGRES_DB:?POSTGRES_DB not set}
-#   networks:
-#     - backend
-#   volumes:
-#     - gitea-db:/var/lib/postgresql/data
-#   restart: unless-stopped
-# <--
+  {% if not database_external %}
+  {{ service_name | default('gitea') }}-postgres:
+    image: docker.io/library/postgres:17.6
+    container_name: {{ service_name | default('gitea') }}-db
+    env_file:
+      - .env.postgres
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U {{ database_user | default('gitea') }}"]
+      start_period: 30s
+      interval: 10s
+      timeout: 10s
+      retries: 5
+    volumes:
+      - gitea-db:/var/lib/postgresql/data
+    {% if network_enabled %}
+    networks:
+      - {{ network_name | default('bridge') }}
+    {% endif %}
+    restart: {{ restart_policy | default('unless-stopped') }}
+  {% endif %}
 
 volumes:
   gitea-data:
     driver: local
-# --> When using internal database
-# gitea-db:
-#   driver: local
-# <--
+  {% if not database_external %}
+  gitea-db:
+    driver: local
+  {% endif %}
+
+{% if network_enabled %}
+networks:
+  {{ network_name | default('bridge') }}:
+    {% if network_external %}
+    external: true
+    {% endif %}
+{% endif %}
 
-# --> (Optional) When using traefik...
-# networks:
-#   frontend:
-#     external: true
-# <--
-# --> (Optional) When using an internal database...
-#   backend:
-#     external: true
-# <--

+ 46 - 13
library/compose/gitea/template.yaml

@@ -1,21 +1,54 @@
 ---
 kind: compose
 metadata:
-  name: Server
-  description: Docker compose setup for server
+  name: Gitea
+  description: >
+    Self-hosted Git service with web interface. Gitea is a painless, self-hosted Git service 
+    written in Go. It's similar to GitHub, Bitbucket, and GitLab, providing Git repository 
+    hosting, code review, team collaboration, and more.
+
+
+    Project: https://gitea.io/
+
+    Documentation: https://docs.gitea.io/
+
+    GitHub: https://github.com/go-gitea/gitea
   version: 0.1.0
   author: Christian Lempa
-  date: '2025-09-28'
+  date: '2025-10-02'
   tags:
-  - server
-  - docker
-  - compose
+    - git
+    - version-control
+    - development
 spec:
-  general:
+  database:
+    required: true
+  ports:
     vars:
-      server_version:
-        type: string
-        description: Server version
-        default: latest
-
----
+      ports_http:
+        description: "Host port for HTTP web interface"
+        type: int
+        default: 3000
+      ports_ssh:
+        description: "Host port for SSH Git access"
+        type: int
+        default: 2221
+  gitea:
+    description: "Configure Gitea application settings"
+    required: true
+    vars:
+      gitea_root_url:
+        description: "Public URL for your Gitea instance (e.g., https://git.example.com)"
+        type: str
+      gitea_ssh_port:
+        description: "SSH port number (should match ports_ssh)"
+        type: int
+        default: 2221
+      user_uid:
+        description: "User UID for Gitea process"
+        type: int
+        default: 1000
+      user_gid:
+        description: "User GID for Gitea process"
+        type: int
+        default: 1000

+ 0 - 2
library/compose/gitlab-runner/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Gitlab-Runner version
         default: latest
-
----

+ 1 - 0
library/compose/gitlab/template.yaml

@@ -7,6 +7,7 @@ metadata:
     manager providing wiki, issue-tracking, and CI/CD pipeline features, using
     an open-source license, developed by GitLab Inc.
 
+
     Project: https://about.gitlab.com/
 
     Source: https://gitlab.com/gitlab-org/gitlab

+ 6 - 2
library/compose/grafana/template.yaml

@@ -12,11 +12,15 @@ metadata:
   - observability
   - dashboard
 spec:
+  ports:
+    vars:
+      ports_http:
+        description: "Host port for HTTP (3000)"
+        type: int
+        default: 3000
   general:
     vars:
       grafana_version:
         type: string
         description: Grafana version
         default: latest
-
----

+ 0 - 2
library/compose/heimdall/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Heimdall version
         default: latest
-
----

+ 0 - 2
library/compose/homeassistant/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Homeassistant version
         default: latest
-
----

+ 0 - 2
library/compose/homepage/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Homepage version
         default: latest
-
----

+ 6 - 2
library/compose/homer/template.yaml

@@ -11,11 +11,15 @@ metadata:
   - docker
   - compose
 spec:
+  ports:
+    vars:
+      ports_http:
+        description: "Host port for HTTP (8080)"
+        type: int
+        default: 8080
   general:
     vars:
       homer_version:
         type: string
         description: Homer version
         default: latest
-
----

+ 37 - 2
library/compose/influxdb/template.yaml

@@ -11,11 +11,46 @@ metadata:
   - docker
   - compose
 spec:
+  ports:
+    vars:
+      ports_http:
+        description: "Host port for HTTP API (8086)"
+        type: int
+        default: 8086
+  influxdb:
+    description: "InfluxDB initialization settings"
+    required: true
+    vars:
+      influxdb_init_username:
+        description: "Initial admin username"
+        type: str
+        default: "admin"
+      influxdb_init_password:
+        description: "Initial admin password"
+        type: str
+        sensitive: true
+        default: "changeme"
+      influxdb_init_org:
+        description: "Initial organization name"
+        type: str
+        default: "myorg"
+      influxdb_init_bucket:
+        description: "Initial bucket name"
+        type: str
+        default: "mybucket"
+      influxdb_init_retention:
+        description: "Data retention period (e.g., 30d, 1y, 0 for infinite)"
+        type: str
+        default: ""
+      influxdb_init_token:
+        description: "Initial admin API token (leave empty to auto-generate)"
+        type: str
+        sensitive: true
+        autogenerated: true
+        default: ""
   general:
     vars:
       influxdb_version:
         type: string
         description: Influxdb version
         default: latest
-
----

+ 0 - 2
library/compose/loki/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Loki version
         default: latest
-
----

+ 0 - 2
library/compose/mariadb/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Volumes version
         default: latest
-
----

+ 6 - 2
library/compose/n8n/template.yaml

@@ -11,11 +11,15 @@ metadata:
   - docker
   - compose
 spec:
+  ports:
+    vars:
+      ports_http:
+        description: "Host port for HTTP (5678)"
+        type: int
+        default: 5678
   general:
     vars:
       n8n_version:
         type: string
         description: N8N version
         default: latest
-
----

+ 23 - 0
library/compose/nextcloud/.env.nextcloud.j2

@@ -0,0 +1,23 @@
+# Nextcloud Application Configuration
+# Contains Nextcloud-specific settings and database connection strings
+
+# Timezone
+TZ={{ container_timezone | default('UTC') }}
+
+{% if database_type == 'mysql' -%}
+# MySQL Database Connection
+MYSQL_PASSWORD={{ database_password | default('nextcloud') }}
+MYSQL_DATABASE={{ database_name | default('nextcloud') }}
+MYSQL_USER={{ database_user | default('nextcloud') }}
+MYSQL_HOST={{ service_name | default('nextcloud') }}-db
+{% elif database_type == 'postgres' -%}
+# PostgreSQL Database Connection
+POSTGRES_PASSWORD={{ database_password | default('nextcloud') }}
+POSTGRES_DB={{ database_name | default('nextcloud') }}
+POSTGRES_USER={{ database_user | default('nextcloud') }}
+POSTGRES_HOST={{ service_name | default('nextcloud') }}-db
+{% endif %}
+
+# Nextcloud Admin Credentials
+NEXTCLOUD_ADMIN_USER={{ admin_user | default('admin') }}
+NEXTCLOUD_ADMIN_PASSWORD={{ admin_password if admin_password else (none | pwgen(32)) }}

+ 6 - 8
library/compose/nextcloud/compose.yaml.j2

@@ -5,9 +5,9 @@ services:
     environment:
       - TZ={{ container_timezone | default('UTC') }}
       {% if database_type == 'mysql' -%}
-      - MYSQL_PASSWORD={{ mysql_password | default('nextcloud') }}
-      - MYSQL_DATABASE={{ mysql_database | default('nextcloud') }}
-      - MYSQL_USER={{ mysql_user | default('nextcloud') }}
+      - MYSQL_PASSWORD={{ database_password | default('nextcloud') }}
+      - MYSQL_DATABASE={{ database_name | default('nextcloud') }}
+      - MYSQL_USER={{ database_user | default('nextcloud') }}
       - MYSQL_HOST={{ service_name | default('nextcloud') }}-db
       {% elif database_type == 'postgres' -%}
       - POSTGRES_PASSWORD={{ database_password | default('nextcloud') }}
@@ -51,12 +51,10 @@ services:
     command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
     environment:
       - TZ={{ container_timezone | default('UTC') }}
-      {% if mysql_root_password_random -%}
       - MYSQL_RANDOM_ROOT_PASSWORD=true
-      {% endif %}
-      - MYSQL_PASSWORD={{ mysql_password | default('nextcloud') }}
-      - MYSQL_DATABASE={{ mysql_database | default('nextcloud') }}
-      - MYSQL_USER={{ mysql_user | default('nextcloud') }}
+      - MYSQL_PASSWORD={{ database_password | default('nextcloud') }}
+      - MYSQL_DATABASE={{ database_name | default('nextcloud') }}
+      - MYSQL_USER={{ database_user | default('nextcloud') }}
     volumes:
       - nextcloud-db:/var/lib/mysql
     {% elif database_type == 'postgres' -%}

+ 42 - 13
library/compose/nextcloud/template.yaml

@@ -1,21 +1,50 @@
 ---
 kind: compose
 metadata:
-  name: Nextcloud-App
-  description: Docker compose setup for nextcloud-app
+  name: Nextcloud
+  description: >
+    Self-hosted file sync and share platform. Nextcloud is a suite of client-server 
+    software for creating and using file hosting services. It provides functionality 
+    similar to Dropbox, with the added benefit of being self-hosted and open-source.
+
+
+    Project: https://nextcloud.com/
+
+    Documentation: https://docs.nextcloud.com/
+
+    GitHub: https://github.com/nextcloud/server
   version: 0.1.0
   author: Christian Lempa
-  date: '2025-09-28'
+  date: '2025-10-02'
   tags:
-  - nextcloud-app
-  - docker
-  - compose
+    - cloud
+    - file-sharing
+    - collaboration
 spec:
-  general:
+  database:
+    required: true
     vars:
-      nextcloud-app_version:
-        type: string
-        description: Nextcloud-App version
-        default: latest
-
----
+      database_type:
+        description: "Database type (Nextcloud supports PostgreSQL or MySQL/MariaDB)"
+        type: enum
+        options: ["postgres", "mysql"]
+        default: "postgres"
+  ports:
+    vars:
+      ports_http:
+        description: "Host port for HTTP"
+        type: int
+        default: 80
+  nextcloud:
+    description: "Configure Nextcloud application settings"
+    vars:
+      admin_user:
+        description: "Nextcloud admin username"
+        type: str
+        default: "admin"
+      admin_password:
+        description: "Nextcloud admin password"
+        type: str
+        sensitive: true
+        autogenerated: true
+        default: ""

+ 4 - 1
library/compose/nginx/template.yaml

@@ -23,4 +23,7 @@ spec:
         default: 8443
   nginx:
     vars:
----
+      nginx_version:
+        description: "Nginx version"
+        type: str
+        default: "latest"

+ 0 - 2
library/compose/nginxproxymanager/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Volumes version
         default: latest
-
----

+ 0 - 2
library/compose/nodeexporter/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Node_Exporter version
         default: latest
-
----

+ 0 - 2
library/compose/openwebui/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Openwebui version
         default: latest
-
----

+ 0 - 2
library/compose/passbolt/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Volumes version
         default: latest
-
----

+ 35 - 2
library/compose/pihole/template.yaml

@@ -11,11 +11,44 @@ metadata:
   - docker
   - compose
 spec:
+  ports:
+    vars:
+      ports_http:
+        description: "Host port for HTTP web interface (80)"
+        type: int
+        default: 8080
+      ports_https:
+        description: "Host port for HTTPS web interface (443)"
+        type: int
+        default: 8443
+      ports_dns_udp:
+        description: "Host port for DNS over UDP (53)"
+        type: int
+        default: 53
+      ports_dns_tcp:
+        description: "Host port for DNS over TCP (53)"
+        type: int
+        default: 53
+      ports_dhcp:
+        description: "Host port for DHCP (67)"
+        type: int
+        default: 67
+  pihole:
+    description: "Pi-hole configuration settings"
+    required: true
+    vars:
+      pihole_webpassword:
+        description: "Web interface admin password"
+        type: str
+        sensitive: true
+        default: "changeme"
+      pihole_dns_upstreams:
+        description: "Upstream DNS servers (semicolon separated)"
+        type: str
+        default: "1.1.1.1;1.0.0.1"
   general:
     vars:
       pihole_version:
         type: string
         description: Pihole version
         default: latest
-
----

+ 14 - 2
library/compose/portainer/template.yaml

@@ -11,11 +11,23 @@ metadata:
   - docker
   - compose
 spec:
+  ports:
+    vars:
+      ports_http:
+        description: "Host port for HTTP (9000)"
+        type: int
+        default: 9000
+      ports_https:
+        description: "Host port for HTTPS (9443)"
+        type: int
+        default: 9443
+      ports_edge:
+        description: "Host port for Edge agent (8000)"
+        type: int
+        default: 8000
   general:
     vars:
       portainer_version:
         type: string
         description: Portainer version
         default: latest
-
----

+ 1 - 1
library/compose/postgres/compose.yaml.j2

@@ -20,7 +20,7 @@ services:
       - "{{ database_port | default(5432) }}:5432"
     {% endif %}
     healthcheck:
-      test: ['CMD-SHELL', 'pg_isready -U "{{ database_user | default('postgres') }}"']
+      test: ["CMD-SHELL", "pg_isready -U {{ database_user | default('postgres') }}"]
       start_period: 30s
       interval: 10s
       timeout: 10s

+ 12 - 2
library/compose/postgres/template.yaml

@@ -18,5 +18,15 @@ spec:
         type: string
         description: PostgreSQL version
         default: latest
-
----
+      postgres_secrets_enabled:
+        type: bool
+        description: "Enable Docker secrets for sensitive data"
+        default: false
+      postgres_initdb_args:
+        type: str
+        description: "PostgreSQL initialization arguments"
+        default: "--data-checksums"
+      postgres_host_auth_method:
+        type: str
+        description: "PostgreSQL host authentication method (leave empty for password-based)"
+        default: ""

+ 0 - 2
library/compose/prometheus/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Volumes version
         default: latest
-
----

+ 0 - 2
library/compose/promtail/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Promtail version
         default: latest
-
----

+ 0 - 2
library/compose/teleport/template.yaml

@@ -17,5 +17,3 @@ spec:
         type: string
         description: Teleport version
         default: latest
-
----

+ 0 - 18
library/compose/traefik.authentik-middleware/middleware.yaml.j2

@@ -1,18 +0,0 @@
-http:
-  middlewares:
-    {{ middleware_name }}:
-      forwardAuth:
-        address: {{ authentik_outpost_url }}/outpost.goauthentik.io/auth/traefik
-        trustForwardHeader: true
-        authResponseHeaders:
-          - X-authentik-username
-          - X-authentik-groups
-          - X-authentik-email
-          - X-authentik-name
-          - X-authentik-uid
-          - X-authentik-jwt
-          - X-authentik-meta-jwks
-          - X-authentik-meta-outpost
-          - X-authentik-meta-provider
-          - X-authentik-meta-app
-          - X-authentik-meta-version

+ 0 - 26
library/compose/traefik.authentik-middleware/template.yaml

@@ -1,26 +0,0 @@
----
-kind: "compose"
-metadata:
-  name: "Traefik Authentik Middleware"
-  description: "Authentication middleware for Traefik using Authentik forward auth"
-  version: "0.1.0"
-  author: "Christian Lempa"
-  date: "2025-09-28"
-  tags:
-    - traefik
-    - authentik
-    - middleware
-    - authentication
-    - forward-auth
-spec:
-  general:
-    vars:
-      authentik_outpost_url:
-        type: "url"
-        description: "Authentik outpost URL"
-        default: "http://your-authentik-outpost-fqdn:9000"
-      middleware_name:
-        type: "string"
-        description: "Name of the middleware"
-        default: "authentik-middleware"
----

+ 0 - 21
library/compose/traefik.external-service/external-service.yaml.j2

@@ -1,21 +0,0 @@
-http:
-  # Router Configuration
-  routers:
-    {{ service_name }}-router:
-      rule: "Host(`{{ service_host }}`)"
-      service: {{ service_name }}
-      priority: {{ router_priority }}
-      entryPoints:
-        - web
-        {% if tls_enabled %}
-        - websecure
-      tls:
-        certResolver: {{ cert_resolver }}
-        {% endif %}
-
-  # Service Configuration
-  services:
-    {{ service_name }}:
-      loadBalancer:
-        servers:
-          - url: "{{ service_url }}"

+ 0 - 17
library/compose/traefik.external-service/router.yaml.j2

@@ -1,17 +0,0 @@
-http:
-  routers:
-    {{ service_name }}:
-      rule: "Host(`{{ hostname }}`)"
-      entryPoints:
-        - https
-      tls:
-        certResolver: {{ cert_resolver }}
-      service: {{ service_name }}
-      middlewares:
-        - {{ middleware_name }}@file
-
-  services:
-    {{ service_name }}:
-      loadBalancer:
-        servers:
-          - url: "http://{{ backend_ip }}:{{ backend_port }}"

+ 0 - 41
library/compose/traefik.external-service/template.yaml

@@ -1,41 +0,0 @@
----
-kind: "compose"
-metadata:
-  name: "Traefik External Service Config"
-  description: "Configuration for routing external services through Traefik"
-  version: "0.1.0"
-  author: "Christian Lempa"
-  date: "2025-09-28"
-  tags:
-    - traefik
-    - external-service
-    - routing
-    - config
-spec:
-  general:
-    vars:
-      service_name:
-        type: "string"
-        description: "Name of the external service"
-        default: "your-local-service"
-      service_host:
-        type: "hostname"
-        description: "Domain for the service (e.g., service.yourdomain.com)"
-        default: "your-service.your-domain.com"
-      service_url:
-        type: "url"
-        description: "URL of the external service"
-        default: "http://your-local-service:port"
-      router_priority:
-        type: "int"
-        description: "Router priority (higher = more precedence)"
-        default: 1000
-      tls_enabled:
-        type: "bool"
-        description: "Enable TLS/SSL"
-        default: true
-      cert_resolver:
-        type: "string"
-        description: "Certificate resolver name"
-        default: "cloudflare"
----

+ 0 - 26
library/compose/traefik.grafana/router.yaml.j2

@@ -1,26 +0,0 @@
-http:
-  routers:
-    {{ service_name }}:
-      rule: "Host(`{{ hostname }}`)"
-      entryPoints:
-        - https
-      tls:
-        certResolver: {{ cert_resolver }}
-      service: {{ service_name }}
-      middlewares:
-{% if enable_auth_middleware %}
-        - {{ auth_middleware_name }}@file
-{% endif %}
-        - {{ service_name }}-headers
-
-  services:
-    {{ service_name }}:
-      loadBalancer:
-        servers:
-          - url: "http://{{ grafana_backend_ip }}:{{ grafana_backend_port }}/"
-
-  middlewares:
-    {{ service_name }}-headers:
-      headers:
-        customRequestHeaders:
-          X-WEBAUTH-USER: "{{ grafana_username }}"

+ 0 - 15
library/compose/traefik.guacamole/router.yaml.j2

@@ -1,15 +0,0 @@
-http:
-  routers:
-    {{ service_name }}:
-      rule: "Host(`{{ hostname }}`)"
-      entryPoints:
-        - https
-      tls:
-        certResolver: {{ cert_resolver }}
-      service: {{ service_name }}
-
-  services:
-    {{ service_name }}:
-      loadBalancer:
-        servers:
-          - url: "http://{{ guacamole_backend_ip }}:{{ guacamole_backend_port }}/"

+ 0 - 6
library/compose/traefik.ldap-middleware/middleware.yaml.j2

@@ -1,6 +0,0 @@
-http:
-  middlewares:
-    {{ middleware_name }}:
-      forwardAuth:
-        address: "http://{{ ldap_auth_backend_ip }}:{{ ldap_auth_backend_port }}/auth"
-        trustForwardHeader: true

+ 0 - 31
library/compose/traefik.nextcloud/router.yaml.j2

@@ -1,31 +0,0 @@
-http:
-  routers:
-    {{ service_name }}:
-      rule: "Host(`{{ hostname }}`)"
-      entryPoints:
-        - https
-      tls:
-        certResolver: {{ cert_resolver }}
-      service: {{ service_name }}
-      middlewares:
-        - {{ service_name }}-dav
-        - {{ service_name }}-secure-headers
-
-  services:
-    {{ service_name }}:
-      loadBalancer:
-        servers:
-          - url: "http://{{ nextcloud_backend_ip }}:{{ nextcloud_backend_port }}/"
-
-  middlewares:
-    {{ service_name }}-dav:
-      redirectRegex:
-        regex: "https://(.*)/.well-known/(?:card|cal)dav"
-        replacement: "https://${1}/remote.php/dav/"
-
-    {{ service_name }}-secure-headers:
-      headers:
-        accessControlMaxAge: 100
-        hostsProxyHeaders:
-          - "X-Forwarded-Host"
-        referrerPolicy: "same-origin"

+ 0 - 23
library/compose/traefik.pihole/router.yaml.j2

@@ -1,23 +0,0 @@
-http:
-  routers:
-    {{ service_name }}:
-      rule: "Host(`{{ hostname }}`)"
-      entryPoints:
-        - https
-      tls:
-        certResolver: {{ cert_resolver }}
-      service: {{ service_name }}
-      middlewares:
-        - {{ service_name }}-headers
-
-  services:
-    {{ service_name }}:
-      loadBalancer:
-        servers:
-          - url: "http://{{ pihole_backend_ip }}:{{ pihole_backend_port }}/"
-
-  middlewares:
-    {{ service_name }}-headers:
-      headers:
-        customRequestHeaders:
-          Host: "{{ hostname }}"

+ 0 - 26
library/compose/traefik.proxmox/router.yaml.j2

@@ -1,26 +0,0 @@
-http:
-  routers:
-    {{ service_name }}:
-      rule: "Host(`{{ hostname }}`)"
-      entryPoints:
-        - https
-      tls:
-        certResolver: {{ cert_resolver }}
-      service: {{ service_name }}
-      middlewares:
-{% if enable_auth_middleware %}
-        - {{ auth_middleware_name }}@file
-{% endif %}
-        - {{ service_name }}-headers
-
-  services:
-    {{ service_name }}:
-      loadBalancer:
-        servers:
-          - url: "https://{{ proxmox_backend_ip }}:{{ proxmox_backend_port }}/"
-
-  middlewares:
-    {{ service_name }}-headers:
-      headers:
-        customRequestHeaders:
-          Host: "{{ hostname }}"

+ 0 - 15
library/compose/traefik.vaultwarden/router.yaml.j2

@@ -1,15 +0,0 @@
-http:
-  routers:
-    {{ service_name }}:
-      rule: "Host(`{{ hostname }}`)"
-      entryPoints:
-        - https
-      tls:
-        certResolver: {{ cert_resolver }}
-      service: {{ service_name }}
-
-  services:
-    {{ service_name }}:
-      loadBalancer:
-        servers:
-          - url: "http://{{ vaultwarden_backend_ip }}:{{ vaultwarden_backend_port }}/"

+ 15 - 0
library/compose/traefik/.env.j2

@@ -0,0 +1,15 @@
+# Traefik Environment Variables
+# This file contains sensitive credentials for ACME DNS providers
+
+{% if traefik_tls_enabled %}
+# ACME DNS Challenge Configuration
+{% if traefik_tls_acme_provider == "cloudflare" %}
+# Cloudflare API Token
+# Required permissions: Zone:DNS:Edit
+# Create token at: https://dash.cloudflare.com/profile/api-tokens
+CF_API_TOKEN={{ traefik_tls_acme_token }}
+{% endif %}
+
+{% else %}
+# ACME/TLS is disabled - no DNS provider credentials needed
+{% endif %}

+ 8 - 7
library/compose/traefik/compose.yaml.j2

@@ -1,12 +1,12 @@
 services:
-  {{ service_name }}:
-    image: docker.io/library/traefik:{{ traefik_version }}
-    container_name: {{ container_name }}
+  {{ service_name | default("traefik") }}:
+    image: docker.io/library/traefik:v3.2
+    container_name: {{ container_name | default("traefik") }}
     {% if ports_enabled %}
     ports:
       - "80:80"
       - "443:443"
-      {% if dashboard_enabled %}
+      {% if traefik_dashboard_enabled %}
       - "8080:8080"  # Dashboard (don't use in production)
       {% endif %}
     {% endif %}
@@ -14,11 +14,12 @@ services:
       - /var/run/docker.sock:/var/run/docker.sock:ro
       - ./config/:/etc/traefik/:ro
       - ./certs/:/var/traefik/certs/:rw
+    {% if traefik_tls_enabled %}
+    env_file:
+      - ./.env.
+    {% endif %}
     environment:
       - TZ={{ container_timezone }}
-      {% if acme_email %}
-      - ACME_EMAIL={{ acme_email }}
-      {% endif %}
     {% if network_enabled %}
     networks:
       - {{ network_name }}

+ 73 - 0
library/compose/traefik/config/files/external-services.yaml

@@ -0,0 +1,73 @@
+---
+# Example: External Service Configuration
+# This is a commented example showing how to proxy to external services
+# Uncomment and customize for your needs
+
+# http:
+#   routers:
+#     # Example: Proxmox Router
+#     proxmox-router:
+#       rule: "Host(`proxmox.example.com`)"
+#       service: proxmox-service
+#       priority: 10
+#       entryPoints:
+#         - websecure
+#       tls:
+#         certResolver: cloudflare
+#       middlewares:
+#         # Optional: Add authentication
+#         # - authentik@file
+#         # Optional: Add custom headers
+#         # - custom-headers@file
+#
+#     # Example: Router for a local service (Home Assistant, etc.)
+#     homeassistant-router:
+#       rule: "Host(`homeassistant.example.com`)"
+#       service: homeassistant-service
+#       entryPoints:
+#         - websecure
+#       tls:
+#         certResolver: cloudflare
+#
+#     # Example: Router with path-based routing
+#     api-router:
+#       rule: "Host(`example.com`) && PathPrefix(`/api`)"
+#       service: api-service
+#       priority: 20
+#       entryPoints:
+#         - websecure
+#       tls:
+#         certResolver: cloudflare
+#       middlewares:
+#         - rate-limit@file
+#
+#   services:
+#     # Example: Proxmox Service (HTTPS backend)
+#     proxmox-service:
+#       loadBalancer:
+#         servers:
+#           - url: "https://192.168.1.100:8006"
+#         serversTransport: insecure-transport
+#
+#     # Example: Home Assistant Service (HTTP backend)
+#     homeassistant-service:
+#       loadBalancer:
+#         servers:
+#           - url: "http://192.168.1.50:8123"
+#
+#     # Example: API Service with multiple backends (load balancing)
+#     api-service:
+#       loadBalancer:
+#         servers:
+#           - url: "http://192.168.1.10:8080"
+#           - url: "http://192.168.1.11:8080"
+#         sticky:
+#           cookie:
+#             name: api-sticky
+#             httpOnly: true
+#
+# # Example: Server Transport for insecure backends (skip TLS verification)
+# # Useful for services with self-signed certificates
+# serversTransports:
+#   insecure-transport:
+#     insecureSkipVerify: true

+ 66 - 0
library/compose/traefik/config/files/middlewares.yaml.j2

@@ -0,0 +1,66 @@
+---
+# Traefik Dynamic Middleware Configuration
+# This file is watched by Traefik and changes are applied automatically
+
+http:
+  middlewares:
+{% if authentik_enabled %}
+    # Authentik Forward Auth Middleware
+    # Use this middleware in your service labels: traefik.http.routers.myservice.middlewares={{ traefik_authentik_middleware_name }}@file
+    {{ traefik_authentik_middleware_name }}:
+      forwardAuth:
+        address: {{ authentik_outpost_url }}/outpost.goauthentik.io/auth/traefik
+        trustForwardHeader: true
+        authResponseHeaders:
+          - X-authentik-username
+          - X-authentik-groups
+          - X-authentik-email
+          - X-authentik-name
+          - X-authentik-uid
+          - X-authentik-jwt
+          - X-authentik-meta-jwks
+          - X-authentik-meta-outpost
+          - X-authentik-meta-provider
+          - X-authentik-meta-app
+          - X-authentik-meta-version
+
+{% endif %}
+    # Example: Custom Headers Middleware
+    # Uncomment and customize as needed
+    # custom-headers:
+    #   headers:
+    #     customRequestHeaders:
+    #       X-Custom-Header: "value"
+    #     customResponseHeaders:
+    #       X-Custom-Response: "value"
+
+    # Example: Rate Limiting Middleware
+    # Uncomment and customize as needed
+    # rate-limit:
+    #   rateLimit:
+    #     average: 100
+    #     burst: 50
+    #     period: 1s
+
+    # Example: IP Whitelist Middleware
+    # Uncomment and customize as needed
+    # ip-whitelist:
+    #   ipWhiteList:
+    #     sourceRange:
+    #       - "192.168.1.0/24"
+    #       - "10.0.0.0/8"
+
+    # Example: Basic Auth Middleware
+    # Uncomment and customize as needed
+    # Generate passwords with: htpasswd -nb user password
+    # basic-auth:
+    #   basicAuth:
+    #     users:
+    #       - "admin:$apr1$..."
+
+    # Example: Redirect Scheme Middleware
+    # Uncomment and customize as needed
+    # redirect-to-https:
+    #   redirectScheme:
+    #     scheme: https
+    #     permanent: true

+ 96 - 0
library/compose/traefik/config/files/tls.yaml

@@ -0,0 +1,96 @@
+---
+# Traefik TLS Configuration
+# This file is watched by Traefik and changes are applied automatically
+
+# TLS Options Configuration
+# Define custom TLS settings for different security requirements
+tls:
+  options:
+    # Example: Modern TLS Configuration (Recommended)
+    # Uncomment and customize as needed
+    # modern:
+    #   minVersion: VersionTLS13
+    #   cipherSuites:
+    #     - TLS_AES_128_GCM_SHA256
+    #     - TLS_AES_256_GCM_SHA384
+    #     - TLS_CHACHA20_POLY1305_SHA256
+    #   curvePreferences:
+    #     - CurveP521
+    #     - CurveP384
+
+    # Example: Intermediate TLS Configuration (Balanced)
+    # Good balance between security and compatibility
+    # intermediate:
+    #   minVersion: VersionTLS12
+    #   cipherSuites:
+    #     - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+    #     - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+    #     - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
+    #   curvePreferences:
+    #     - CurveP521
+    #     - CurveP384
+    #   sniStrict: true
+
+    # Example: Old TLS Configuration (Maximum Compatibility)
+    # Use only if you need to support very old clients
+    # old:
+    #   minVersion: VersionTLS10
+    #   maxVersion: VersionTLS13
+
+  # Certificate Stores
+  # Define custom certificate stores for dynamic certificates
+  # stores:
+  #   default:
+  #     defaultCertificate:
+  #       certFile: /path/to/cert.pem
+  #       keyFile: /path/to/key.pem
+
+  # Dynamic Certificates
+  # Load certificates from files (alternative to ACME)
+  # certificates:
+  #   - certFile: /path/to/domain1.cert
+  #     keyFile: /path/to/domain1.key
+  #     stores:
+  #       - default
+  #   - certFile: /path/to/domain2.cert
+  #     keyFile: /path/to/domain2.key
+
+# Server Transports
+# Configure how Traefik communicates with backend services
+# serversTransports:
+#   # Example: Skip TLS Verification for Self-Signed Certificates
+#   # Useful for internal services with self-signed certs
+#   insecure:
+#     insecureSkipVerify: true
+#
+#   # Example: Custom Root CA
+#   # Use a custom CA to verify backend certificates
+#   custom-ca:
+#     rootCAs:
+#       - /path/to/ca.crt
+#
+#   # Example: Client Certificate Authentication
+#   # Use client certificates to authenticate to backend
+#   mtls:
+#     certificates:
+#       - certFile: /path/to/client.crt
+#         keyFile: /path/to/client.key
+
+# Usage Examples:
+#
+# 1. Apply TLS options to a router:
+#    http:
+#      routers:
+#        my-router:
+#          rule: "Host(`example.com`)"
+#          tls:
+#            options: modern@file
+#
+# 2. Use custom server transport:
+#    http:
+#      services:
+#        my-service:
+#          loadBalancer:
+#            servers:
+#              - url: "https://backend:443"
+#            serversTransport: insecure@file

+ 19 - 24
library/compose/traefik/config/traefik.yaml.j2

@@ -3,63 +3,58 @@ global:
   checkNewVersion: false
   sendAnonymousUsage: false
 
-# --> (Optional) Change log level and format here ...
-#     - level: [TRACE, DEBUG, INFO, WARN, ERROR, FATAL]
-{% if traefik_log_level is defined %}
+{% if container_loglevel is defined %}
 log:
-  level: {{ traefik_log_level }}
+  level: {{ container_loglevel | upper }}
 {% endif %}
 
-# --> (Optional) Enable accesslog here ...
-{% if traefik_accesslog_enabled %}
+{% if accesslog_enabled %}
 accesslog: {}
 {% endif %}
 
-# --> (Optional) Enable API and Dashboard here, don't do in production
-{% if dashboard_enabled %}
+{% if traefik_dashboard_enabled %}
 api:
   dashboard: true
   insecure: true
 {% endif %}
 
-# -- Change EntryPoints here...
 entryPoints:
-  web:
+  {{ traefik_entrypoint }}:
     address: :80
-    # --> (Optional) Redirect all HTTP to HTTPS
-    {% if traefik_redirect_http_to_https %}
+    {% if traefik_tls_redirect %}
     http:
       redirections:
         entryPoint:
-          to: websecure
+          to: {{ traefik_tls_entrypoint }}
           scheme: https
     {% endif %}
-  websecure:
+  {{ traefik_tls_entrypoint }}:
     address: :443
 
-# -- Configure your CertificateResolver here...
+{% if traefik_tls_enabled %}
 certificatesResolvers:
-  {{ traefik_acme_dns_provider }}:
+  {{ traefik_tls_certresolver | default('cloudflare') }}:
     acme:
-      email: {{ acme_email }}
+      email: {{ traefik_tls_acme_email }}
       storage: /var/traefik/certs/acme.json
       caServer: "https://acme-v02.api.letsencrypt.org/directory"
       dnsChallenge:
-        provider: {{ traefik_acme_dns_provider }}
+        provider: {{ traefik_tls_acme_provider | default('cloudflare') }}
         resolvers:
-{% for resolver in traefik_acme_dns_resolvers %}
-          - "{{ resolver }}"
-{% endfor %}
+          - 1.1.1.1:53
+          - 8.8.8.8:53
+{% endif %}
 
-# --> (Optional) Disable TLS Cert verification check
+# NOTE: If using self-signed certificates in your backend services, uncomment the following section
+# to disable certificate verification (not recommended for production use).
+# ---
 # serversTransport:
 #   insecureSkipVerify: true
-# <--
 
 providers:
   docker:
     exposedByDefault: false
     network: {{ network_name }}
   file:
-    directory: /etc/traefik
+    directory: /etc/traefik/files
     watch: true

部分文件因为文件数量过多而无法显示