Full Code of ChristianLempa/boilerplates for AI

main c7e46f7762f8 cached
246 files
894.6 KB
219.3k tokens
471 symbols
1 requests
Download .txt
Showing preview only (962K chars total). Download the full file or copy to clipboard to get everything.
Repository: ChristianLempa/boilerplates
Branch: main
Commit: c7e46f7762f8
Files: 246
Total size: 894.6 KB

Directory structure:
gitextract_wuyffx3g/

├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   ├── issue_template.md
│   ├── pull_request_template.md
│   ├── scripts/
│   │   ├── generate_wiki_docs.py
│   │   └── sync-template-version.sh
│   └── workflows/
│       ├── codequality-ruff.yaml
│       ├── codequality-yamllint.yaml
│       ├── docs-update-wiki.yaml
│       ├── release-create-cli-release.yaml
│       └── renovate-sync-versions.yaml
├── .gitignore
├── .wiki/
│   ├── Core-Concepts-Defaults.md
│   ├── Core-Concepts-Libraries.md
│   ├── Core-Concepts-Templates.md
│   ├── Core-Concepts-Variables.md
│   ├── Getting-Started.md
│   ├── Home.md
│   ├── Installation.md
│   ├── Variables-Ansible.md
│   ├── Variables-Compose.md
│   ├── Variables-Helm.md
│   ├── Variables-Kubernetes.md
│   ├── Variables-Packer.md
│   ├── Variables-Terraform.md
│   ├── Variables.md
│   └── _Sidebar.md
├── .yamllint
├── AGENTS.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── SECURITY.md
├── cli/
│   ├── __init__.py
│   ├── __main__.py
│   ├── core/
│   │   ├── config/
│   │   │   ├── __init__.py
│   │   │   └── config_manager.py
│   │   ├── display/
│   │   │   ├── __init__.py
│   │   │   ├── display_base.py
│   │   │   ├── display_icons.py
│   │   │   ├── display_settings.py
│   │   │   ├── display_status.py
│   │   │   ├── display_table.py
│   │   │   ├── display_template.py
│   │   │   └── display_variable.py
│   │   ├── exceptions.py
│   │   ├── input/
│   │   │   ├── __init__.py
│   │   │   ├── input_manager.py
│   │   │   ├── input_settings.py
│   │   │   └── prompt_manager.py
│   │   ├── library.py
│   │   ├── module/
│   │   │   ├── __init__.py
│   │   │   ├── base_commands.py
│   │   │   ├── base_module.py
│   │   │   ├── config_commands.py
│   │   │   └── helpers.py
│   │   ├── prompt.py
│   │   ├── registry.py
│   │   ├── repo.py
│   │   ├── template/
│   │   │   ├── __init__.py
│   │   │   ├── template.py
│   │   │   ├── variable.py
│   │   │   ├── variable_collection.py
│   │   │   └── variable_section.py
│   │   ├── validators.py
│   │   └── version.py
│   └── modules/
│       ├── __init__.py
│       ├── ansible/
│       │   └── __init__.py
│       ├── compose/
│       │   ├── __init__.py
│       │   └── validate.py
│       ├── helm/
│       │   └── __init__.py
│       ├── kubernetes/
│       │   └── __init__.py
│       ├── packer/
│       │   └── __init__.py
│       └── terraform/
│           └── __init__.py
├── flake.nix
├── library/
│   ├── ansible/
│   │   ├── checkmk-install-agent/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── checkmk-manage-host/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── docker-certs/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── docker-certs-enable/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── docker-install-ubuntu/
│   │   │   ├── main.yml.j2
│   │   │   └── template.yaml
│   │   ├── docker-prune/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── ubuntu-add-sshkey/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── ubuntu-apt-update/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   └── ubuntu-vm-core/
│   │       ├── playbook.yaml.j2
│   │       └── template.yaml
│   ├── compose/
│   │   ├── adguardhome/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── alloy/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   ├── common.alloy.j2
│   │   │   │   ├── logs_docker.alloy.j2
│   │   │   │   ├── logs_system.alloy.j2
│   │   │   │   ├── metrics_docker.alloy.j2
│   │   │   │   ├── metrics_system.alloy.j2
│   │   │   │   └── targets.alloy.j2
│   │   │   └── template.yaml
│   │   ├── authentik/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── bind9/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   ├── named.conf.j2
│   │   │   │   ├── named.conf.zones.j2
│   │   │   │   └── tsig.key.j2
│   │   │   └── template.yaml
│   │   ├── checkmk/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── dockge/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── gitea/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── gitlab/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── gitlab.rb.j2
│   │   │   └── template.yaml
│   │   ├── gitlab-runner/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── config.toml
│   │   │   └── template.yaml
│   │   ├── grafana/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── homeassistant/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── homepage/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── homer/
│   │   │   ├── assets/
│   │   │   │   └── config.yml.j2
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── influxdb/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── komodo/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── loki/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── config.yaml.j2
│   │   │   └── template.yaml
│   │   ├── mariadb/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── n8n/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── netbox/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── nextcloud/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── nginx/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── default.conf
│   │   │   ├── data/
│   │   │   │   └── index.html
│   │   │   └── template.yaml
│   │   ├── openwebui/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── passbolt/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── pihole/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── portainer/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── postgres/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── compose.yaml.j2.final
│   │   │   └── template.yaml
│   │   ├── prometheus/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── prometheus.yaml
│   │   │   └── template.yaml
│   │   ├── renovate/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── semaphoreui/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── traefik/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── twingate-connector/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── uptimekuma/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   └── whoami/
│   │       ├── compose.yaml.j2
│   │       └── template.yaml
│   ├── helm/
│   │   ├── authentik/
│   │   │   ├── secrets.yaml.j2
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   ├── certmanager/
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   ├── longhorn/
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   ├── netbox/
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   ├── portainer/
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   └── traefik/
│   │       ├── template.yaml
│   │       └── values.yaml.j2
│   ├── kubernetes/
│   │   ├── certmanager-certificate/
│   │   │   ├── certificate.yaml.j2
│   │   │   └── template.yaml
│   │   ├── certmanager-clusterissuer/
│   │   │   ├── clusterissuer.yaml.j2
│   │   │   └── template.yaml
│   │   ├── certmanager-issuer/
│   │   │   ├── issuer.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-configmap/
│   │   │   ├── configmap.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-ingress/
│   │   │   ├── ingress.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-ingressclass/
│   │   │   ├── ingressclass.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-persistentvolume/
│   │   │   ├── pv.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-persistentvolumeclaim/
│   │   │   ├── pvc.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-secret/
│   │   │   └── template.yaml
│   │   ├── core-service/
│   │   │   ├── service.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-serviceaccount/
│   │   │   ├── serviceaccount.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-storageclass/
│   │   │   ├── storageclass.yaml.j2
│   │   │   └── template.yaml
│   │   ├── traefik-ingressroute/
│   │   │   ├── ingressroute.yaml.j2
│   │   │   └── template.yaml
│   │   ├── traefik-ingressroutetcp/
│   │   │   ├── ingressroutetcp.yaml.j2
│   │   │   └── template.yaml
│   │   ├── traefik-middleware/
│   │   │   ├── middleware.yaml.j2
│   │   │   └── template.yaml
│   │   └── twingate-connector/
│   │       ├── connector.yaml.j2
│   │       └── template.yaml
│   ├── packer/
│   │   └── proxmox-iso-ubuntu/
│   │       ├── files/
│   │       │   └── 99-pve.cfg
│   │       ├── http/
│   │       │   ├── meta-data
│   │       │   └── user-data.j2
│   │       ├── proxmox-iso-ubuntu.pkr.hcl.j2
│   │       ├── template.yaml
│   │       └── variables.pkrvars.hcl.example
│   └── terraform/
│       ├── cloudflare-dns-record/
│       │   ├── cloudflare_dns_record.tf.j2
│       │   ├── cloudflare_zone.tf.j2
│       │   └── template.yaml
│       ├── cloudflare-ztna-application/
│       │   ├── cloudflare_account_zone.tf.j2
│       │   ├── cloudflare_zero_trust_access_application.tf.j2
│       │   ├── cloudflare_zero_trust_access_policy.tf.j2
│       │   └── template.yaml
│       ├── dns-a-record/
│       │   ├── dns_a_record_set.tf.j2
│       │   └── template.yaml
│       └── netbox-vm/
│           ├── netbox_virtual_machine.tf.j2
│           └── template.yaml
├── pyproject.toml
├── renovate.json
├── requirements.txt
├── scripts/
│   └── install.sh
└── tests/
    ├── __init__.py
    ├── test_variable.py
    └── test_version.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# https://editorconfig.org/
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.json]
indent_size = 2

[*.{js,jsx,ts,tsx}]
indent_size = 2

[*.md]
indent_size = unset
trim_trailing_whitespace = false

[*.py]
indent_size = 4

[{*.{yaml,yml},.yamllint}]
indent_size = 2


================================================
FILE: .github/FUNDING.yml
================================================
---
# These are supported funding model platforms

patreon: christianlempa


================================================
FILE: .github/issue_template.md
================================================
### Issue Reporting

*Please write all text in English in order to facilitate communication and collaboration. Thank you!*

#### Description

[Provide a clear and concise description of the issue]

#### Steps to Reproduce

1. [First step]
2. [Second step]
3. [Any subsequent steps]

#### Expected Behavior

[Describe what you expected to happen]

#### Actual Behavior

[Describe what actually happened]

#### Screenshots

[If applicable, add screenshots to help explain the issue]

#### Additional Information

[Any additional information or context that can be helpful in resolving the issue]

#### Environment

[Include as many relevant details about the environment you experienced the bug in.]


================================================
FILE: .github/pull_request_template.md
================================================
### Pull Request

*Please write all text in English in order to facilitate communication and collaboration. Thank you!*

---


================================================
FILE: .github/scripts/generate_wiki_docs.py
================================================
#!/usr/bin/env python3
"""Generate GitHub Wiki documentation for module variables.

This script auto-generates variable documentation in GitHub Wiki markdown format
for all registered modules, using the latest schema version for each.
"""

import sys
from pathlib import Path

# Add project root to path (script is in .github/scripts, so go up twice)
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))

# ruff: noqa: E402
# Import all modules to register them
import cli.modules.ansible
import cli.modules.compose
import cli.modules.helm
import cli.modules.kubernetes
import cli.modules.packer
import cli.modules.terraform  # noqa: F401
from cli.core.registry import registry  # Module import after path manipulation


def format_value(value):
    """Format value for markdown display."""
    if value is None or value == "":
        return "_none_"
    if isinstance(value, bool):
        return "✓" if value else "✗"
    if isinstance(value, list):
        return ", ".join(f"`{v}`" for v in value)
    return f"`{value}`"


def generate_module_docs(module_name: str, output_dir: Path):  # noqa: PLR0912, PLR0915
    """Generate wiki documentation for a single module."""
    # Get module class from registry
    module_classes = dict(registry.iter_module_classes())

    if module_name not in module_classes:
        sys.stderr.write(f"Warning: Module '{module_name}' not found, skipping\n")
        return False

    module_cls = module_classes[module_name]
    schema_version = module_cls.schema_version

    # Get the spec for the latest schema version
    if hasattr(module_cls, "schemas") and schema_version in module_cls.schemas:
        spec = module_cls.schemas[schema_version]
    elif hasattr(module_cls, "spec"):
        spec = module_cls.spec
    else:
        sys.stderr.write(f"Warning: No spec found for module '{module_name}', skipping\n")
        return False

    # Generate markdown content
    lines = []

    # Header
    lines.append(f"# {module_name.title()} Variables")
    lines.append("")
    lines.append(f"**Module:** `{module_name}`  ")
    lines.append(f"**Schema Version:** `{schema_version}`  ")
    lines.append(f"**Description:** {module_cls.description}")
    lines.append("")
    lines.append("---")
    lines.append("")
    lines.append(
        "This page documents all available variables for the "
        + f"{module_name} module. Variables are organized into sections "
        + "that can be enabled/disabled based on your configuration needs."
    )
    lines.append("")

    # Table of contents
    lines.append("## Table of Contents")
    lines.append("")
    for section_key, section_data in spec.items():
        section_title = section_data.get("title", section_key)
        anchor = section_title.lower().replace(" ", "-").replace("/", "")
        lines.append(f"- [{section_title}](#{anchor})")
    lines.append("")
    lines.append("---")
    lines.append("")

    # Process each section
    for section_key, section_data in spec.items():
        section_title = section_data.get("title", section_key)
        section_desc = section_data.get("description", "")
        section_toggle = section_data.get("toggle", "")
        section_needs = section_data.get("needs", "")
        section_required = section_data.get("required", False)
        section_vars = section_data.get("vars", {})

        # Section header
        lines.append(f"## {section_title}")
        lines.append("")

        # Section metadata
        metadata = []
        if section_required:
            metadata.append("**Required:** Yes")
        if section_toggle:
            metadata.append(f"**Toggle Variable:** `{section_toggle}`")
        if section_needs:
            if isinstance(section_needs, list):
                needs_str = ", ".join(f"`{n}`" for n in section_needs)
            else:
                needs_str = f"`{section_needs}`"
            metadata.append(f"**Depends On:** {needs_str}")

        if metadata:
            lines.append("  \n".join(metadata))
            lines.append("")

        if section_desc:
            lines.append(section_desc)
            lines.append("")

        # Skip sections with no variables
        if not section_vars:
            lines.append("_No variables defined in this section._")
            lines.append("")
            continue

        # Variables table
        lines.append("| Variable | Type | Default | Description |")
        lines.append("|----------|------|---------|-------------|")

        for var_name, var_data in section_vars.items():
            var_type = var_data.get("type", "str")
            var_default = format_value(var_data.get("default"))
            var_description = var_data.get("description", "").replace("\n", " ")

            # Add extra metadata to description
            extra_parts = []
            if var_data.get("sensitive"):
                extra_parts.append("**Sensitive**")
            if var_data.get("autogenerated"):
                extra_parts.append("**Auto-generated**")
            if "options" in var_data:
                opts = ", ".join(f"`{o}`" for o in var_data["options"])
                extra_parts.append(f"**Options:** {opts}")
            if "needs" in var_data:
                extra_parts.append(f"**Needs:** `{var_data['needs']}`")
            if "extra" in var_data:
                extra_parts.append(var_data["extra"])

            if extra_parts:
                var_description += "<br>" + " • ".join(extra_parts)

            lines.append(f"| `{var_name}` | `{var_type}` | {var_default} | {var_description} |")

        lines.append("")
        lines.append("---")
        lines.append("")

    # Footer
    lines.append("## Notes")
    lines.append("")
    lines.append("- **Required sections** must be configured")
    lines.append("- **Toggle variables** enable/disable entire sections")
    lines.append("- **Dependencies** (`needs`) control when sections/variables are available")
    lines.append("- **Sensitive variables** are masked during prompts")
    lines.append("- **Auto-generated variables** are populated automatically if not provided")
    lines.append("")
    lines.append("---")
    lines.append("")
    lines.append(f"_Last updated: Schema version {schema_version}_")

    # Write to file
    output_file = output_dir / f"Variables-{module_name.title()}.md"
    output_file.write_text("\n".join(lines))

    sys.stdout.write(f"Generated: {output_file.name}\n")
    return True


def generate_variables_index(modules: list[str], output_dir: Path):
    """Generate index page for all variable documentation."""
    lines = []

    lines.append("# Variables Documentation")
    lines.append("")
    lines.append("This section contains auto-generated documentation for all " + "available variables in each module.")
    lines.append("")
    lines.append("## Available Modules")
    lines.append("")

    for module_name in sorted(modules):
        lines.append(f"- [{module_name.title()}](Variables-{module_name.title()})")

    lines.append("")
    lines.append("---")
    lines.append("")
    lines.append("Each module page includes:")
    lines.append("")
    lines.append("- Schema version information")
    lines.append("- Complete list of sections and variables")
    lines.append("- Variable types, defaults, and descriptions")
    lines.append("- Section dependencies and toggle configurations")
    lines.append("")
    lines.append("---")
    lines.append("")
    lines.append("_This documentation is auto-generated from module schemas._")

    output_file = output_dir / "Variables.md"
    output_file.write_text("\n".join(lines))

    sys.stdout.write(f"Generated: {output_file.name}\n")


# Minimum required arguments
MIN_ARGS = 2


def main():
    """Main entry point."""
    if len(sys.argv) < MIN_ARGS:
        sys.stderr.write("Usage: python3 scripts/generate_wiki_docs.py <output_directory>\n")
        sys.exit(1)

    output_dir = Path(sys.argv[1])
    output_dir.mkdir(parents=True, exist_ok=True)

    sys.stdout.write(f"Generating wiki documentation in: {output_dir}\n")
    sys.stdout.write("\n")

    # Get all registered modules
    module_classes = dict(registry.iter_module_classes())
    successful_modules = []

    for module_name in sorted(module_classes.keys()):
        if generate_module_docs(module_name, output_dir):
            successful_modules.append(module_name)

    sys.stdout.write("\n")

    # Generate index page
    if successful_modules:
        generate_variables_index(successful_modules, output_dir)
        sys.stdout.write("\n")
        sys.stdout.write(f"✓ Successfully generated documentation for {len(successful_modules)} module(s)\n")
    else:
        sys.stderr.write("Error: No documentation generated\n")
        sys.exit(1)


if __name__ == "__main__":
    main()


================================================
FILE: .github/scripts/sync-template-version.sh
================================================
#!/usr/bin/env bash
# Sync Docker image versions to template.yaml metadata
# Triggered by GitHub Actions when Renovate updates dependencies

set -euo pipefail

# Extract version from different file types
extract_version() {
    local file="$1"
    local filename=$(basename "$file")
    
    case "$filename" in
        compose.yaml.j2|*.j2)
            # Docker Compose or K8s manifest: extract from image: line
            grep -E '^\s*image:\s*[^{]*:[^{}\s]+' "$file" | head -n1 | sed -E 's/.*:([^:]+)$/\1/' | tr -d ' ' || true
            ;;
        values.yaml|values.yml)
            # Helm values: extract from repository + tag
            grep -A1 'repository:' "$file" | grep 'tag:' | sed -E 's/.*tag:\s*['\''"]?([^'\''"]+)['\''"]?/\1/' | tr -d ' ' || true
            ;;
    esac
}

# Update template.yaml if version differs
update_template() {
    local template_file="$1"
    local new_version="$2"
    local current_date=$(date +%Y-%m-%d)
    
    local current_version=$(grep -E '^\s*version:\s*' "$template_file" | sed -E 's/.*version:\s*['\''"]?([^'\''"]+)['\''"]?/\1/' | tr -d ' ' || true)
    
    if [ -n "$current_version" ] && [ "$new_version" != "$current_version" ]; then
        echo "✓ Updating $template_file: $current_version → $new_version (date: $current_date)"
        sed -i "s/version: .*/version: $new_version/" "$template_file"
        sed -i "s/date: .*/date: '$current_date'/" "$template_file"
        return 0
    fi
    return 1
}

# Main processing
updated=0
files=("${@:-$(find library -type f \( -name 'compose.yaml.j2' -o -name 'values.yaml' -o -name 'values.yml' \) 2>/dev/null)}")

for file in "${files[@]}"; do
    [ ! -f "$file" ] && continue
    
    template_file="$(dirname "$file")/template.yaml"
    [ ! -f "$template_file" ] && continue
    
    version=$(extract_version "$file")
    [ -z "$version" ] || [[ "$version" =~ \{\{ ]] && continue
    
    update_template "$template_file" "$version" && ((updated++)) || true
done

echo "Processed ${#files[@]} file(s), updated $updated template(s)"
exit 0


================================================
FILE: .github/workflows/codequality-ruff.yaml
================================================
---
name: Code Quality - Ruff

'on':
  pull_request:
    branches:
      - main
  push:
    branches:
      - main
      - 'release/**'

permissions:
  contents: read

jobs:
  ruff:
    name: Python Linting and Formatting
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.14'

      - name: Install Ruff
        run: pip install ruff

      - name: Run Ruff Linting
        run: ruff check .

      - name: Run Ruff Formatting Check
        run: ruff format --check .


================================================
FILE: .github/workflows/codequality-yamllint.yaml
================================================
---
name: Code Quality - yamllint

'on':
  pull_request:
    branches:
      - main

permissions:
  contents: read

jobs:
  lint:
    name: Linters
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - run: yamllint --strict -- $(git ls-files '*.yaml' '*.yml')


================================================
FILE: .github/workflows/docs-update-wiki.yaml
================================================
---
name: Docs - Update Wiki

'on':
  push:
    branches:
      - main
    paths:
      - 'cli/core/schema/**/*.json'  # JSON schema files
      - '.wiki/**'  # Static wiki pages
      - '.github/scripts/generate_wiki_docs.py'  # Wiki generation script
      - '.github/workflows/docs-update-wiki.yaml'  # This workflow
  workflow_dispatch:  # Allow manual trigger

permissions:
  contents: write

jobs:
  update-wiki:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 1

      - name: Checkout wiki repository
        uses: actions/checkout@v6
        with:
          repository: ${{ github.repository }}.wiki
          path: wiki
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.14'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e .

      - name: Generate variable documentation
        run: |
          python3 .github/scripts/generate_wiki_docs.py wiki/

      - name: Sync wiki pages from .wiki directory
        run: |
          # Copy all markdown files from .wiki/ to wiki/ (except Variables-*.md which are auto-generated)
          if [ -d ".wiki" ]; then
            echo "Syncing wiki pages from .wiki/ directory..."
            for file in .wiki/*.md; do
              filename=$(basename "$file")
              # Skip auto-generated variable documentation files
              if [[ ! "$filename" =~ ^Variables- ]]; then
                echo "  Copying $filename"
                cp "$file" "wiki/$filename"
              fi
            done
          else
            echo "No .wiki directory found, skipping static wiki pages sync"
          fi

      - name: Check for changes
        id: changes
        working-directory: wiki
        run: |
          git add .
          if git diff --staged --quiet; then
            echo "has_changes=false" >> $GITHUB_OUTPUT
            echo "No changes detected in wiki documentation"
          else
            echo "has_changes=true" >> $GITHUB_OUTPUT
            echo "Changes detected in wiki documentation"
          fi

      - name: Commit and push changes
        if: steps.changes.outputs.has_changes == 'true'
        working-directory: wiki
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git commit -m "Auto-update wiki pages"

          # Pull with rebase to handle any remote changes, then push
          # GitHub wikis use master as default branch
          git pull --rebase origin master
          git push origin master

      - name: Summary
        run: |
          if [ "${{ steps.changes.outputs.has_changes }}" == "true" ]; then
            echo "Wiki variable documentation updated successfully"
            echo "View at: https://github.com/${{ github.repository }}/wiki"
          else
            echo "No changes to wiki documentation"
          fi


================================================
FILE: .github/workflows/release-create-cli-release.yaml
================================================
---
name: Release - Create CLI GitHub Release

'on':
  push:
    tags:
      - 'v*.*.*'  # Trigger on version tags like v1.0.0, v2.1.3, etc.

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Extract version from tag
        id: version
        run: |
          # Remove 'v' prefix if present (e.g., v1.0.0 -> 1.0.0)
          VERSION="${GITHUB_REF#refs/tags/}"
          VERSION="${VERSION#v}"
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "tag=$GITHUB_REF_NAME" >> $GITHUB_OUTPUT
          echo "Extracted version: $VERSION from tag $GITHUB_REF_NAME"

      - name: Validate version consistency
        run: |
          TAG_VERSION="${{ steps.version.outputs.version }}"

          # Extract version from pyproject.toml
          PYPROJECT_VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')

          # Extract version from cli/__init__.py
          CLI_VERSION=$(grep '^__version__ = ' cli/__init__.py | sed 's/__version__ = "\(.*\)"/\1/')

          echo "Tag version:        $TAG_VERSION"
          echo "pyproject.toml:     $PYPROJECT_VERSION"
          echo "cli/__init__.py:    $CLI_VERSION"
          echo ""

          # Check if all versions match
          if [ "$TAG_VERSION" != "$PYPROJECT_VERSION" ]; then
            echo "Error: Tag version ($TAG_VERSION) does not match pyproject.toml version ($PYPROJECT_VERSION)"
            echo "Please update pyproject.toml to version $TAG_VERSION before creating the release."
            exit 1
          fi

          if [ "$TAG_VERSION" != "$CLI_VERSION" ]; then
            echo "Error: Tag version ($TAG_VERSION) does not match cli/__init__.py version ($CLI_VERSION)"
            echo "Please update cli/__init__.py to version $TAG_VERSION before creating the release."
            exit 1
          fi

          echo "Version consistency check passed"
          echo "All version strings match: $TAG_VERSION"

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.14'

      - name: Install build dependencies
        run: |
          python -m pip install --upgrade pip
          pip install build twine

      - name: Build package
        run: python -m build

      - name: Check distribution
        run: |
          echo "Built packages:"
          ls -lh dist/
          echo ""
          echo "Checking package integrity:"
          twine check dist/*

      # PyPI publishing disabled for now - install via GitHub releases
      # - name: Publish to PyPI
      #   if: >
      #     ${{ !contains(steps.version.outputs.version, 'alpha') &&
      #     !contains(steps.version.outputs.version, 'beta') &&
      #     !contains(steps.version.outputs.version, 'rc') }}
      #   env:
      #     TWINE_USERNAME: __token__
      #     TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
      #   run: |
      #     echo "Publishing to PyPI..."
      #     twine upload dist/*

      - name: Extract changelog for this version
        id: changelog
        run: |
          # Extract the changelog for this version from CHANGELOG.md
          VERSION="${{ steps.version.outputs.version }}"

          # First try to extract the section for this specific version
          CHANGELOG=$(awk -v ver="$VERSION" '/^## \[/{if($0 ~ "\\[" ver "\\]"){flag=1; next} else if(flag){exit}} flag' CHANGELOG.md)
          # If empty, fall back to [Unreleased] section
          if [ -z "$CHANGELOG" ]; then
            CHANGELOG=$(awk '/^## \[Unreleased\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md)
          fi

          if [ -z "$CHANGELOG" ]; then
            echo "No changelog entries found for this release"
            CHANGELOG="See commit history for details."
          fi

          # Save to output using heredoc to handle multiline
          {
            echo 'content<<EOF'
            echo "$CHANGELOG"
            echo EOF
          } >> $GITHUB_OUTPUT

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ steps.version.outputs.tag }}
          name: Release ${{ steps.version.outputs.tag }}
          body: |
            ## Boilerplates CLI ${{ steps.version.outputs.tag }}

            ${{ steps.changelog.outputs.content }}

            ---

            ### Installation

            Install using the installation script:
            ```bash
            curl -fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh | bash
            ```

            Or install a specific version:
            ```bash
            curl -fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh |
              bash -s -- --version ${{ steps.version.outputs.tag }}
            ```
          draft: false
          prerelease: >
            ${{ contains(steps.version.outputs.version, 'alpha') ||
            contains(steps.version.outputs.version, 'beta') ||
            contains(steps.version.outputs.version, 'rc') }}
          files: |
            dist/*.whl
            dist/*.tar.gz
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/renovate-sync-versions.yaml
================================================
---
name: Renovate - Sync Template Versions

'on':
  pull_request:
    branches:
      - main
    paths:
      - 'library/**'

permissions:
  contents: write
  pull-requests: write

jobs:
  sync-versions:
    name: Sync Template Versions
    # Only run on Renovate PRs
    if: startsWith(github.head_ref, 'renovate/')
    runs-on: ubuntu-latest
    steps:
      - name: Checkout PR branch
        uses: actions/checkout@v6
        with:
          ref: ${{ github.head_ref }}
          fetch-depth: 0  # Fetch all history to compare with main
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Detect changed template files
        id: changes
        run: |
          # Fetch main branch for comparison
          git fetch origin main:main

          # Get list of changed files in library/
          CHANGED_FILES=$(git diff --name-only main...HEAD | grep '^library/' | grep -E '\.(j2|yaml|yml)$' || true)

          if [ -n "$CHANGED_FILES" ]; then
            echo "Changed template files:"
            echo "$CHANGED_FILES"
            echo "files<<EOF" >> $GITHUB_OUTPUT
            echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
            echo "EOF" >> $GITHUB_OUTPUT
            echo "has_files=true" >> $GITHUB_OUTPUT
          else
            echo "No template files changed"
            echo "has_files=false" >> $GITHUB_OUTPUT
          fi

      - name: Run template version sync
        id: sync
        if: steps.changes.outputs.has_files == 'true'
        run: |
          echo "Running template version sync script..."
          chmod +x .github/scripts/sync-template-version.sh

          # Pass changed files to the script
          .github/scripts/sync-template-version.sh ${{ steps.changes.outputs.files }}

      - name: Check for template.yaml changes
        id: template_changes
        run: |
          if [[ -n $(git status --porcelain library/**/template.yaml) ]]; then
            echo "has_changes=true" >> $GITHUB_OUTPUT
            echo "Template version changes detected"
          else
            echo "has_changes=false" >> $GITHUB_OUTPUT
            echo "No template version changes needed"
          fi

      - name: Commit and push changes
        if: steps.template_changes.outputs.has_changes == 'true'
        run: |
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"

          git add library/**/template.yaml
          git commit -m "chore: sync template versions with image updates"
          git push


================================================
FILE: .gitignore
================================================
# Ignore local development files
**/.vscode
**/.DS_Store

# Docker Secrets, Environment Files
**/secret.*
**/.env
**/.envrc
**/.direnv

# Ignore Ansible
**/.ansible

# Python
**/__pycache__/
**/*.py[cod]
**/*.pyo
**/*.pyd
**/.venv
**/venv/
**/.ruff_cache/

# Packaging
*.egg-info/
build/
dist/

# Installation tracking
.installed-version

# Test outputs
config.yaml


================================================
FILE: .wiki/Core-Concepts-Defaults.md
================================================
# Default Variables

Save time by setting default values for variables you use frequently. This page explains how to manage your default variable configuration.

## What are Default Variables?

**Default variables** are user-defined values that override module and template defaults. They allow you to:
- Avoid repetitive typing during template generation
- Standardize values across multiple templates
- Customize your environment once, use everywhere

## Precedence Order

Variables are resolved in this order (lowest to highest priority):

1. Module spec (module-wide defaults)
2. Template spec (template-specific defaults)
3. **User config** (your saved defaults) ← This page
4. CLI arguments (`--var` flags)

Your defaults override module and template values but can be overridden by CLI arguments.

## Managing Defaults

All default management commands follow this pattern:
```bash
boilerplates <module> defaults <command> [args]
```

### View Defaults

List all saved defaults for a module:

```bash
boilerplates compose defaults list
```

Output:
```
Default Variables (Compose):
  container_timezone: America/New_York
  restart_policy: unless-stopped
  traefik_network: traefik
  network_external: true
```

### Set a Default

Save a default value:

```bash
boilerplates compose defaults set container_timezone="America/New_York"
```

Response:
```
Set default: container_timezone = America/New_York
```

**Tips:**
- Use quotes for values with spaces
- Booleans: `true` or `false`
- Numbers: no quotes needed

**Examples:**
```bash
# String
boilerplates compose defaults set restart_policy="unless-stopped"

# Integer
boilerplates compose defaults set user_uid=1000

# Boolean
boilerplates compose defaults set traefik_enabled=true

# String with spaces
boilerplates compose defaults set container_hostname="my app server"
```

### Get a Default

View a single default value:

```bash
boilerplates compose defaults get container_timezone
```

Output:
```
container_timezone: America/New_York
```

### Remove a Default

Delete a saved default:

```bash
boilerplates compose defaults rm container_timezone
```

Response:
```
Removed default: container_timezone
```

The variable will now use module/template defaults again.

### Clear All Defaults

Remove all saved defaults for a module:

```bash
boilerplates compose defaults clear
```

Response:
```
Cleared all defaults for compose
```

**Warning:** This cannot be undone. Consider backing up your config first.

## Configuration Storage

Defaults are stored in:
```
~/.config/boilerplates/config.yaml
```

Example content:
```yaml
libraries:
  - name: default
    type: git
    url: https://github.com/christianlempa/boilerplates
    branch: main
    directory: library

defaults:
  compose:
    container_timezone: America/New_York
    restart_policy: unless-stopped
    traefik_network: traefik
    network_external: true
  terraform:
    region: us-east-1
    instance_type: t3.micro
```

### Manual Editing

You can manually edit the config file:

```bash
# Edit configuration
nano ~/.config/boilerplates/config.yaml

# Verify defaults
boilerplates compose defaults list
```

## Common Use Cases

### Timezone Configuration

Set your local timezone once:

```bash
boilerplates compose defaults set container_timezone="Europe/Berlin"
```

Now all Docker containers use your timezone by default.

### Network Configuration

Standardize network settings:

```bash
boilerplates compose defaults set network_external=true
boilerplates compose defaults set network_name="docker-network"
```

### Traefik Configuration

Set common Traefik values:

```bash
boilerplates compose defaults set traefik_network="traefik"
boilerplates compose defaults set traefik_domain="example.com"
boilerplates compose defaults set traefik_tls_certresolver="cloudflare"
```

### User IDs

Match your host user:

```bash
boilerplates compose defaults set user_uid=$(id -u)
boilerplates compose defaults set user_gid=$(id -g)
```

### Restart Policy

Standardize container behavior:

```bash
boilerplates compose defaults set restart_policy="unless-stopped"
```

## Overriding Defaults

Even with defaults set, you can override them:

### Interactive Mode

During interactive generation, defaults appear as pre-filled values. Press Enter to accept or type a new value:

```
Container timezone [America/New_York]: Europe/London
```

### CLI Arguments

Override with `--var`:

```bash
boilerplates compose generate nginx \
  --var container_timezone="UTC" \
  --no-interactive
```

The CLI argument takes precedence over your saved default.

## Per-Module Defaults

Each module has its own defaults:

```bash
# Compose defaults
boilerplates compose defaults set restart_policy="unless-stopped"

# Terraform defaults (separate)
boilerplates terraform defaults set region="us-east-1"

# Ansible defaults (separate)
boilerplates ansible defaults set become=true
```

Defaults don't transfer between modules—they're module-specific.

## Backup and Restore

### Backup Configuration

Save your configuration:

```bash
cp ~/.config/boilerplates/config.yaml ~/boilerplates-config-backup.yaml
```

### Restore Configuration

Restore from backup:

```bash
cp ~/boilerplates-config-backup.yaml ~/.config/boilerplates/config.yaml
```

### Share Configuration

Share defaults with your team:

```bash
# Export defaults
cat ~/.config/boilerplates/config.yaml | grep -A 100 "defaults:" > team-defaults.yaml

# Share file with team
# Team members can merge into their config.yaml
```

## Advanced Usage

### Environment-Specific Defaults

Maintain multiple configurations:

```bash
# Production defaults
cp ~/.config/boilerplates/config.yaml ~/.config/boilerplates/config-prod.yaml

# Development defaults
cp ~/.config/boilerplates/config.yaml ~/.config/boilerplates/config-dev.yaml

# Switch between them
cp ~/.config/boilerplates/config-prod.yaml ~/.config/boilerplates/config.yaml
```

### Scripted Configuration

Set defaults programmatically:

```bash
#!/bin/bash

# Set common defaults
boilerplates compose defaults set container_timezone="$(cat /etc/timezone)"
boilerplates compose defaults set user_uid="$(id -u)"
boilerplates compose defaults set user_gid="$(id -g)"
boilerplates compose defaults set restart_policy="unless-stopped"
```

## Troubleshooting

### Defaults Not Applied

If defaults aren't being used:

```bash
# Verify defaults are set
boilerplates compose defaults list

# Check config file
cat ~/.config/boilerplates/config.yaml

# Ensure module name matches
# "compose" not "docker-compose"
```

### Config File Errors

If config file is corrupted:

```bash
# Validate YAML syntax
python3 -c "import yaml; yaml.safe_load(open('~/.config/boilerplates/config.yaml'))"

# Or remove and recreate
mv ~/.config/boilerplates/config.yaml ~/.config/boilerplates/config.yaml.bak
boilerplates repo update  # Recreates config
```

### Wrong Values

If wrong values appear:

```bash
# Check precedence
# 1. Module spec
# 2. Template spec
# 3. User defaults ← Check here
# 4. CLI --var

# Verify your defaults
boilerplates compose defaults list

# Check template spec
boilerplates compose show <template-name>
```

## Best Practices

### Essential Defaults

Set these common defaults:

```bash
# System
boilerplates compose defaults set container_timezone="$(cat /etc/timezone)"
boilerplates compose defaults set user_uid=$(id -u)
boilerplates compose defaults set user_gid=$(id -g)

# Containers
boilerplates compose defaults set restart_policy="unless-stopped"

# Networking (if using external networks)
boilerplates compose defaults set network_external=true
boilerplates compose defaults set network_name="docker-network"
```

### Don't Over-Configure

Only set defaults for values you use consistently:

**Good:**
- Timezone (same everywhere)
- User UID/GID (same everywhere)
- Network settings (if standardized)

**Bad:**
- Service names (unique per service)
- Hostnames (unique per service)
- Port numbers (conflict-prone)

### Document Your Defaults

Keep a list of your defaults for reference:

```bash
# Save to file
boilerplates compose defaults list > ~/my-defaults.txt
```

### Review Periodically

Check your defaults occasionally:

```bash
boilerplates compose defaults list
```

Remove obsolete or unused values.

## Next Steps

- [Libraries](Core-Concepts-Libraries) - Managing template libraries
- [Variables](Core-Concepts-Variables) - Understanding variable types and behavior
- [Getting Started](Getting-Started) - Using defaults in template generation

## See Also

- [Installation](Installation) - CLI setup
- [Concepts](Core-Concepts-Templates) - How templates work


================================================
FILE: .wiki/Core-Concepts-Libraries.md
================================================
# Libraries

Libraries are collections of templates that can be synced from Git repositories or loaded from local directories. This page explains how to manage template libraries.

## What is a Library?

A **library** is a collection of templates organized by module type (compose, terraform, ansible, etc.). Libraries can be:
- **Git-based** - Synced from remote repositories
- **Static** - Local directories on your filesystem

## Default Library

By default, Boilerplates uses the official template library:

```
Name: default
URL: https://github.com/christianlempa/boilerplates
Branch: main
Directory: library
```

This provides production-ready templates for various services and infrastructure.

## Library Location

Libraries are stored locally at:
```
~/.config/boilerplates/libraries/
└── default/
    └── library/
        ├── compose/
        ├── terraform/
        └── ansible/
```

## Managing Libraries

### List Libraries

View all configured libraries:

```bash
boilerplates repo list
```

Output:
```
Libraries:
  default (git)
    URL: https://github.com/christianlempa/boilerplates
    Branch: main
    Directory: library
    Status: Synced
```

### Update Libraries

Sync all Git-based libraries:

```bash
boilerplates repo update
```

This:
- Pulls latest changes from Git repositories
- Uses sparse-checkout (only downloads template directories)
- Updates metadata cache

### Add Custom Library

Add your own template library:

```bash
boilerplates repo add my-templates https://github.com/user/templates \
  --directory library \
  --branch main
```

Parameters:
- **name** - Unique library identifier
- **url** - Git repository URL
- **--directory** - Path to templates within repository (default: `.`)
- **--branch** - Git branch to use (default: `main`)

### Remove Library

Remove a library from configuration:

```bash
boilerplates repo remove my-templates
```

This removes the configuration but keeps downloaded files. To fully clean up:

```bash
rm -rf ~/.config/boilerplates/libraries/my-templates
```

## Library Types

### Git Libraries

Synced from remote Git repositories:

```yaml
libraries:
  - name: default
    type: git
    url: https://github.com/christianlempa/boilerplates
    branch: main
    directory: library
```

**Benefits:**
- Always up-to-date
- Version controlled
- Easy to share
- Automatic updates

**Use cases:**
- Official templates
- Team-shared templates
- Public template collections

### Static Libraries

Local directories on your filesystem:

```yaml
libraries:
  - name: local
    type: static
    path: ~/my-templates
```

**Benefits:**
- No network required
- Full control
- Fast access
- Development/testing

**Use cases:**
- Local development
- Private templates
- Custom modifications
- Testing new templates

## Library Priority

When multiple libraries contain the same template, **priority** determines which is used:

```yaml
libraries:
  - name: local      # Priority 1 (highest)
    type: static
    path: ~/my-templates
  - name: default    # Priority 2
    type: git
    url: https://github.com/christianlempa/boilerplates
```

### Simple IDs

Use the template name without qualification:

```bash
boilerplates compose generate nginx
```

The CLI uses the first matching template (from `local` in the example above).

### Qualified IDs

Target a specific library:

```bash
boilerplates compose generate nginx.local    # Uses local library
boilerplates compose generate nginx.default  # Uses default library
```

## Configuration File

Library configuration is stored in:
```
~/.config/boilerplates/config.yaml
```

Example:
```yaml
libraries:
  - name: default
    type: git
    url: https://github.com/christianlempa/boilerplates
    branch: main
    directory: library
  - name: local
    type: static
    path: /Users/me/my-templates
```

### Manual Editing

You can manually edit `config.yaml`:

```bash
# Edit configuration
nano ~/.config/boilerplates/config.yaml

# Verify changes
boilerplates repo list
```

## Advanced Usage

### Multiple Git Branches

Use different branches for stable vs. development templates:

```yaml
libraries:
  - name: stable
    type: git
    url: https://github.com/user/templates
    branch: main
    directory: library
  - name: dev
    type: git
    url: https://github.com/user/templates
    branch: development
    directory: library
```

### Sparse Checkout

Git libraries use sparse-checkout to minimize disk usage:

```
# Only downloads:
library/compose/
library/terraform/
library/ansible/

# Ignores:
.github/
docs/
tests/
README.md
```

This keeps library downloads fast and disk usage low.

### Private Repositories

For private Git repositories, ensure SSH or HTTPS authentication is configured:

**SSH:**
```bash
boilerplates repo add private git@github.com:user/private-templates.git \
  --directory library \
  --branch main
```

Requires SSH key configured with GitHub/GitLab.

**HTTPS with credentials:**
```bash
# Configure Git credential helper
git config --global credential.helper store

# Add library (will prompt for credentials on first sync)
boilerplates repo add private https://github.com/user/private-templates.git \
  --directory library \
  --branch main
```

## Template Discovery

After adding libraries, templates are discovered automatically:

```bash
# Sync libraries
boilerplates repo update

# List templates from all libraries
boilerplates compose list

# Show template details (uses priority order)
boilerplates compose show nginx

# Show from specific library
boilerplates compose show nginx.local
```

## Troubleshooting

### Library Not Syncing

If `repo update` fails:

```bash
# Check network connectivity
ping github.com

# Verify Git access
git ls-remote https://github.com/christianlempa/boilerplates

# Remove and re-add library
boilerplates repo remove default
boilerplates repo add default https://github.com/christianlempa/boilerplates \
  --directory library \
  --branch main
```

### Templates Not Found

If templates don't appear:

```bash
# Verify library is configured
boilerplates repo list

# Update libraries
boilerplates repo update

# Check library directory structure
ls -la ~/.config/boilerplates/libraries/default/library/compose/
```

### Duplicate Template Names

If two libraries have the same template:

```bash
# Check which library provides it
boilerplates compose show nginx

# Use qualified ID to target specific library
boilerplates compose generate nginx.local
```

## Best Practices

### Library Organization

Structure your libraries consistently:

```
my-templates/
├── library/
│   ├── compose/
│   │   ├── app1/
│   │   └── app2/
│   ├── terraform/
│   └── ansible/
└── README.md
```

### Version Control

For Git libraries:
- Use semantic versioning tags
- Maintain a CHANGELOG
- Test templates before merging
- Use branches for development

### Naming

- Use descriptive library names
- Avoid special characters
- Keep names short but meaningful

**Good:** `production`, `dev`, `team-infra`  
**Bad:** `my-lib-123`, `temp`, `new`

### Documentation

Each library should have:
- README.md with overview
- Template documentation
- Usage examples
- Contribution guidelines

## Next Steps

- [Default Variables](Core-Concepts-Defaults) - Managing variable defaults
- [Templates](Core-Concepts-Templates) - Understanding template structure
- [Developer Guide](Developers-Templates) - Creating templates for libraries

## See Also

- [Getting Started](Getting-Started) - Your first template
- [Installation](Installation) - Installing the CLI


================================================
FILE: .wiki/Core-Concepts-Templates.md
================================================
# Templates

Templates are the core building blocks of the Boilerplates CLI. This page explains what templates are, how they work, and how to use them effectively.

## What is a Template?

A template is a **directory-based configuration package** that contains:
- **Metadata** - Name, description, version, author information
- **Variable specifications** - Configurable parameters
- **Template files** - Jinja2 templates that generate your configuration
- **Static files** - Files copied as-is (optional)

When you generate a template, the CLI:
1. Prompts you for variable values (or uses defaults/CLI overrides)
2. Renders template files using Jinja2
3. Writes the generated files to your specified directory

## Template Structure

Every template is a directory containing at minimum a `template.yaml` file:

```
template-name/
├── template.yaml          # Template definition and metadata
├── docker-compose.yml.j2  # Jinja2 template files
├── .env.j2               # Environment configuration
└── README.md             # Static file (copied as-is)
```

### The template.yaml File

This file defines everything about your template:

```yaml
---
kind: compose              # Module type (compose, terraform, ansible, etc.)
schema: "X.Y"             # Schema version (affects available features)
metadata:
  name: My Service
  description: Service description with Markdown support
  version: 1.0.0          # Application/service version
  author: Your Name
  date: '2025-01-12'
  tags:
    - docker
    - service
spec:
  # Variable specifications (see Variables page)
```

## Template Discovery

Templates are organized in **libraries**. A library is a collection of templates for a specific module type.

### Default Library Structure

```
~/.config/boilerplates/libraries/
└── default/
    └── library/
        ├── compose/
        │   ├── nginx/
        │   ├── traefik/
        │   └── whoami/
        ├── terraform/
        └── ansible/
```

### Finding Templates

```bash
# List all templates for a module
boilerplates compose list

# Search templates by name
boilerplates compose search proxy

# Show details about a template
boilerplates compose show nginx
```

## Template Metadata

### Required Fields

```yaml
metadata:
  name: Template Name        # Display name
  description: Description   # What the template does
  version: 1.0.0            # Application version
  author: Your Name         # Template author
  date: '2025-01-12'       # Last update date
```

### Optional Fields

```yaml
metadata:
  tags:                     # Searchable tags
    - docker
    - web-server
  draft: false             # Hide from listings if true
  next_steps: |            # Post-generation instructions
    ## What's Next
    1. Review the generated files
    2. Customize as needed
    3. Deploy!
```

### Description Markdown

The `description` field supports Markdown:

```yaml
metadata:
  description: |
    A **powerful reverse proxy** and load balancer.
    
    ## Features
    - Automatic HTTPS
    - Load balancing
    - Let's Encrypt integration
    
    ## Resources
    - **Project**: https://traefik.io
    - **Documentation**: https://doc.traefik.io
```

This renders nicely when you run `boilerplates compose show <template>`.

## Template Files

### Jinja2 Templates

Files ending in `.j2` are processed by Jinja2:

**docker-compose.yml.j2:**
```yaml
services:
  {{ service_name }}:
    image: nginx:{{ nginx_version }}
    ports:
      - "{{ nginx_port }}:80"
    {% if enable_ssl %}
    volumes:
      - ./ssl:/etc/nginx/ssl
    {% endif %}
```

After rendering with variables:
- `service_name=web`
- `nginx_version=1.25`
- `nginx_port=8080`
- `enable_ssl=true`

**Generated docker-compose.yml:**
```yaml
services:
  web:
    image: nginx:1.25
    ports:
      - "8080:80"
    volumes:
      - ./ssl:/etc/nginx/ssl
```

### Static Files

Files without `.j2` extension are copied as-is:
- `README.md` - Copied unchanged
- `scripts/setup.sh` - Copied unchanged

### File Includes

Templates can include other template files:

**main.j2:**
```jinja2
{% include 'common/header.j2' %}

services:
  {{ service_name }}:
    image: nginx:latest
```

**common/header.j2:**
```yaml
version: '3.8'
name: {{ project_name }}
```

## Schema Versioning

Templates declare a schema version that determines available features:

```yaml
schema: "X.Y"  # Use schema version X.Y (e.g., "1.0", "1.2")
```

**Why Schema Versions?**
- Modules evolve with new features over time
- Older templates continue working (backward compatibility)
- Templates opt-into new features by upgrading schema version

**Checking Current Schema:**

To find the latest schema version and available features for each module, refer to the module-specific variable documentation:
- [Compose Variables](Variables-Compose) - Shows current schema version at bottom
- [Terraform Variables](Variables-Terraform)
- [Ansible Variables](Variables-Ansible)
- [Kubernetes Variables](Variables-Kubernetes)
- [Helm Variables](Variables-Helm)
- [Packer Variables](Variables-Packer)

Each Variables page documents the current schema and which features are available.

## Template Lifecycle

### 1. Discovery

```bash
boilerplates repo update    # Sync libraries
boilerplates compose list   # Discover templates
```

### 2. Preview

```bash
boilerplates compose show nginx
```

Shows:
- Metadata
- Variable specifications
- File structure

### 3. Generation

```bash
# Interactive mode
boilerplates compose generate nginx

# Non-interactive mode
boilerplates compose generate nginx ./my-nginx \
  --var service_name=my-nginx \
  --no-interactive
```

### 4. Validation (Optional)

```bash
# Validate template structure
boilerplates compose validate nginx

# Validate all templates
boilerplates compose validate
```

## Template Identification

Templates are identified by their directory name:

```
library/compose/nginx/   → template ID: nginx
library/compose/traefik/ → template ID: traefik
```

### Qualified IDs

When using multiple libraries, templates can have qualified IDs:

```bash
# Simple ID (uses first matching template from priority order)
boilerplates compose generate nginx

# Qualified ID (targets specific library)
boilerplates compose generate nginx.local
boilerplates compose generate nginx.default
```

## Template Inheritance

Templates inherit variables from module specifications. You only need to override what's different.

**Module spec defines:**
- `service_name` (default: empty)
- `container_port` (default: 8080)
- `restart_policy` (default: unless-stopped)

**Template overrides:**
```yaml
spec:
  general:
    vars:
      service_name:
        default: nginx  # Override default
      # container_port inherits 8080
      # restart_policy inherits unless-stopped
```

This keeps templates concise—you only specify what's unique.

## Best Practices

### Naming Conventions

- **Template directories**: lowercase, hyphen-separated (`my-service`, `nginx-proxy`)
- **Service names**: match template name by default
- **File names**: descriptive and clear (`docker-compose.yml.j2`, not `dc.j2`)

### Version Management

**Application Versions:**
- Hardcode in template files: `image: nginx:1.25.3`
- Update `metadata.version` to match application
- Don't create version variables unless necessary

**Template Updates:**
- Increment `metadata.version` when updating
- Update `metadata.date` to current date
- Document changes in commit messages

### Documentation

- Use Markdown in `description`
- Provide `next_steps` for post-generation instructions
- Include links to official documentation
- Add usage examples

### Testing

Before publishing:
```bash
# Validate template
boilerplates compose validate my-template

# Test generation (dry run)
boilerplates compose generate my-template --dry-run

# Test with real generation
boilerplates compose generate my-template /tmp/test

# Verify generated files
cd /tmp/test && docker compose config
```

## Advanced Features

### Conditional File Generation

Use Jinja2 conditionals to skip entire sections:

```jinja2
{% if traefik_enabled %}
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.{{ service_name }}.rule=Host(`{{ traefik_host }}`)"
{% endif %}
```

### Dynamic File Names

Template file names can use variables (though this is rare):

```
config-{{ environment }}.yml.j2  # Generates: config-prod.yml
```

### Template Validation

Templates are validated on load:
- Jinja2 syntax errors detected
- Undefined variables reported
- Schema compatibility checked

## Next Steps

- [Variables](Core-Concepts-Variables) - Learn about variable types and configuration
- [Configuration](Core-Concepts-Libraries) - Manage template libraries
- [Variable Reference](Variables-Compose) - Complete variable documentation for modules
- [Developer Guide](Developers-Templates) - Create your own templates

## See Also

- [Getting Started](Getting-Started) - Generate your first template
- [Installation](Installation) - Install the CLI


================================================
FILE: .wiki/Core-Concepts-Variables.md
================================================
# Variables

Variables are the configurable parameters that customize your templates. This page explains variable types, sections, dependencies, and how to work with them effectively.

## What are Variables?

Variables are **parameters** that control template generation. They can be:
- Simple values (strings, numbers, booleans)
- Selections from options (enums)
- Validated inputs (emails, URLs, hostnames)

**Example:**
```yaml
service_name: my-app        # String
container_port: 8080        # Integer
traefik_enabled: true       # Boolean
restart_policy: unless-stopped  # Enum (selection from options)
```

## Variable Types

### String (`str`)

Text values with optional constraints:

```yaml
service_name:
  type: str
  description: Service name
  default: my-service
```

**Usage:**
- Service names
- Hostnames
- Paths
- General text

### Integer (`int`)

Whole numbers:

```yaml
container_port:
  type: int
  description: Container port
  default: 8080
```

**Usage:**
- Port numbers
- UIDs/GIDs
- Counts and limits

### Float (`float`)

Decimal numbers:

```yaml
cpu_limit:
  type: float
  description: CPU limit in cores
  default: 1.5
```

**Usage:**
- Resource limits
- Ratios and percentages

### Boolean (`bool`)

True/false values:

```yaml
traefik_enabled:
  type: bool
  description: Enable Traefik integration
  default: false
```

**Usage:**
- Feature toggles
- Conditional configuration
- Enable/disable options

### Enum (`enum`)

Selection from predefined options:

```yaml
restart_policy:
  type: enum
  description: Container restart policy
  options: [unless-stopped, always, on-failure, no]
  default: unless-stopped
```

**Usage:**
- Network modes (bridge, host, macvlan)
- Log levels (debug, info, warn, error)
- Policies and strategies

### Email (`email`)

Email addresses with validation:

```yaml
admin_email:
  type: email
  description: Administrator email
  default: admin@example.com
```

**Validation:**
- Must match email format (user@domain.com)
- Rejects invalid email addresses

### URL (`url`)

Full URLs with scheme validation:

```yaml
api_endpoint:
  type: url
  description: API endpoint URL
  default: https://api.example.com
```

**Validation:**
- Must include scheme (http://, https://)
- Must have valid host

### Hostname (`hostname`)

Domain names or hostnames:

```yaml
traefik_host:
  type: hostname
  description: Service hostname
  default: app.example.com
```

**Validation:**
- Valid DNS hostname format
- Accepts subdomains and domains

## Variable Properties

Every variable can have these properties:

```yaml
variable_name:
  type: str                    # Variable type (required)
  description: Description     # Help text
  default: value               # Default value
  prompt: Custom prompt text   # Override description in prompts
  extra: Additional help       # Extended help text
  sensitive: false             # Mask input (for passwords)
  autogenerated: false         # Auto-generate if empty
  needs: condition             # Dependency constraint
```

### Sensitive Variables

Mask input for passwords and secrets:

```yaml
admin_password:
  type: str
  description: Administrator password
  sensitive: true
```

**Behavior:**
- Input is masked in prompts (`********`)
- Displayed as `***` in output
- Suitable for passwords, API keys, tokens

### Autogenerated Variables

Automatically generate values if not provided:

```yaml
secret_key:
  type: str
  description: Secret key
  autogenerated: true
```

**Behavior:**
- Shows `*auto` placeholder in prompts
- Generates value during rendering if empty
- Press Enter to accept auto-generation

### Custom Prompts

Override the description text in interactive prompts:

```yaml
service_name:
  description: Service name (used in docker-compose)
  prompt: Enter service name
```

## Variable Sections

Variables are organized into **sections** that group related configuration:

```yaml
spec:
  general:        # Required by default
    title: General Settings
    vars:
      service_name: {...}
      container_port: {...}
  
  networking:     # Optional section
    title: Network Configuration
    toggle: networking_enabled
    vars:
      network_name: {...}
      network_mode: {...}
```

### Required Sections

Sections marked as `required: true` must be configured:

```yaml
general:
  title: General
  required: true
  vars:
    service_name: {...}
```

**Behavior:**
- Users must provide values
- No way to skip
- `general` section is implicitly required

### Optional Sections

Sections with a toggle variable:

```yaml
traefik:
  title: Traefik
  toggle: traefik_enabled
  vars:
    traefik_host: {...}
    traefik_network: {...}
```

**Behavior:**
- User chooses whether to enable
- If disabled, section variables are skipped
- Toggle variable is auto-created as boolean

### Section Dependencies

Sections can depend on other sections:

```yaml
traefik_tls:
  title: Traefik TLS/SSL
  needs: traefik
  vars:
    traefik_tls_enabled: {...}
```

**Behavior:**
- Only shown if dependency section is enabled
- Supports multiple dependencies: `needs: [traefik, networking]`
- Automatically sorted in dependency order

## Variable Dependencies

Variables can depend on other variables using `needs` constraints:

### Simple Constraint

Variable only visible when condition is met:

```yaml
network_name:
  type: str
  description: Network name
  needs: network_mode=bridge
```

**Behavior:**
- Only shown when `network_mode` equals `bridge`
- Hidden for other network modes

### Multiple Values (OR)

Variable visible for multiple values:

```yaml
network_name:
  type: str
  description: Network name
  needs: network_mode=bridge,macvlan
```

**Behavior:**
- Shown when `network_mode` is `bridge` OR `macvlan`
- Hidden when `network_mode` is `host`

### Multiple Constraints (AND)

Variable requires multiple conditions:

```yaml
traefik_tls_certresolver:
  type: str
  description: Certificate resolver
  needs: traefik_enabled=true;network_mode=bridge
```

**Behavior:**
- Requires ALL conditions to be true
- Semicolon (`;`) separates conditions
- Comma (`,`) within a condition is OR

## Variable Precedence

Variables are resolved in priority order (lowest to highest):

1. **Module spec** - Default values for all templates
2. **Template spec** - Template-specific overrides
3. **User config** - Saved defaults in `~/.config/boilerplates/config.yaml`
4. **CLI arguments** - `--var` flags

**Example:**

```bash
# Module default: restart_policy=unless-stopped
# Template override: restart_policy=always
# User config: restart_policy=on-failure
# CLI override: --var restart_policy=no

# Result: restart_policy=no (CLI wins)
```

### Setting Default Values

Save frequently used values:

```bash
# Set a default
boilerplates compose defaults set container_timezone="America/New_York"

# View all defaults
boilerplates compose defaults list

# Remove a default
boilerplates compose defaults rm container_timezone

# Clear all defaults
boilerplates compose defaults clear
```

## Interactive Prompts

When generating templates interactively, the CLI prompts for each variable:

### Text Input

```
Service name: |my-app|
```

- Type your value or press Enter for default
- Default shown in brackets

### Boolean Input

```
Enable Traefik? (y/n) [n]:
```

- `y` or `yes` for true
- `n` or `no` for false
- Press Enter for default

### Enum Selection

```
Restart policy:
  1) unless-stopped (default)
  2) always
  3) on-failure
  4) no
Select [1]:
```

- Enter number to select
- Press Enter for default

### Sensitive Input

```
Admin password: ********
```

- Input is masked
- Not echoed to terminal

## Non-Interactive Mode

Skip prompts entirely using `--no-interactive`:

```bash
boilerplates compose generate nginx ./output \
  --var service_name=my-nginx \
  --var container_port=8080 \
  --no-interactive
```

**Behavior:**
- Uses defaults for all variables
- No user interaction required
- Suitable for automation and CI/CD

## Variable Validation

### At Prompt Time

Variables are validated during prompts:
- Type checking (int must be number)
- Format validation (email, URL, hostname)
- Option validation (enum must be in options list)

**Example:**
```
Container port: abc
Error: Must be a valid integer
Container port:
```

### At Template Load

Templates are validated when loaded:
- Check for undefined variables used in templates
- Verify variable dependencies are valid
- Ensure no circular dependencies

## Advanced Features

### Template Variable Inheritance

Templates inherit ALL variables from their module schema. You only override what's different:

**Module defines:**
```yaml
general:
  vars:
    service_name:
      type: str
    container_port:
      type: int
      default: 8080
```

**Template overrides:**
```yaml
spec:
  general:
    vars:
      service_name:
        default: nginx  # Only override default
      # container_port inherits 8080
```

### Dynamic Visibility

Variables with `needs` constraints are dynamically shown/hidden based on other values:

```bash
# If user selects network_mode=host
# → Network name prompt is skipped (needs: network_mode=bridge)

# If user selects network_mode=bridge
# → Network name prompt is shown
```

### Dependency Resolution

The CLI automatically:
- Topologically sorts sections based on dependencies
- Validates no circular dependencies exist
- Reports errors for missing dependencies

## Best Practices

### Naming Conventions

- Use **snake_case** for variable names
- Group related variables with common prefixes:
  - `traefik_*` for Traefik variables
  - `network_*` for networking variables
  - `ports_*` for port configuration

### Provide Good Defaults

- Choose sensible defaults for common use cases
- Use empty defaults when user input is required
- Document why a default was chosen

### Use Descriptions

- Clear, concise descriptions
- Explain what the variable controls
- Include examples if helpful

**Good:**
```yaml
traefik_host:
  description: Service subdomain or full hostname (e.g., 'app' or 'app.example.com')
```

**Bad:**
```yaml
traefik_host:
  description: Host
```

### Group Related Variables

Use sections to organize configuration:

```yaml
spec:
  general:        # Core configuration
  network:        # Network settings
  traefik:        # Reverse proxy
  traefik_tls:    # TLS/SSL configuration
```

### Use Dependencies Wisely

- Keep dependency chains short
- Avoid overly complex conditions
- Test dependency behavior thoroughly

## Next Steps

- [Configuration](Core-Concepts-Defaults) - Managing default values
- [Variable Reference](Variables-Compose) - Complete variable list for each module
- [Templates](Core-Concepts-Templates) - How variables are used in templates

## See Also

- [Getting Started](Getting-Started) - Your first template generation
- [Developer Guide](Developers-Templates) - Creating templates with variables


================================================
FILE: .wiki/Getting-Started.md
================================================
# Getting Started

Welcome to Boilerplates! This guide will help you get up and running in just a few minutes.

## Overview

Boilerplates provides two main components:

### Template Library

A collection of ready-to-use templates for common infrastructure components:
- **Docker Compose**: Containerized applications (Nginx, Traefik, Grafana, etc.)
- **Terraform**: Cloud infrastructure (AWS, Azure, GCP)
- **Ansible**: Configuration management and automation
- **Kubernetes**: Container orchestration deployments
- **Packer**: Machine image builders

Templates include:
- Pre-configured defaults for common use cases
- Documentation and usage examples
- Variable specifications for customization
- Best practices baked in

### Management CLI

A Python-based command-line tool to work with templates:
- Browse and search the template library
- Interactive configuration with validation
- Generate customized templates
- Manage multiple template libraries (official + custom)
- Sync updates from repositories

## Prerequisites

Before you begin, ensure you have:

- Python 3.10 or higher installed
- Git installed (for syncing template libraries)
- Basic command-line knowledge
- Internet connection (for downloading templates)

## Installation

The quickest way to install the management CLI is using the automated installer:

```bash
curl -fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh | bash
```

This installs the `boilerplates` command and configures access to the official template library.

For detailed installation instructions including platform-specific guidance, see the [Installation](Installation) page.

## Your First Template

Once installed, let's generate your first template!

### 1. Sync the Template Library

Download the latest templates from the library:

```bash
boilerplates repo update
```

This syncs the official template library to `~/.config/boilerplates/libraries/default/`.

### 2. Browse the Template Library

Explore available Docker Compose templates:

```bash
boilerplates compose list
```

You'll see a table showing available templates from the library with their descriptions.

### 3. Inspect a Template

Before generating, preview a template's structure and variables:

```bash
boilerplates compose show nginx
```

This shows:
- Template metadata (name, version, author, description)
- Available configuration variables and defaults
- Template file structure
- Variable dependencies and sections

### 4. Generate Files from Template

Now, let's use the CLI to generate customized files from a template! You have two options:

**Interactive Mode** (Recommended for beginners):

```bash
boilerplates compose generate nginx
```

The tool prompts you for each variable. You can:
- Press Enter to accept defaults from the template
- Type custom values
- Navigate with arrow keys for selections
- Skip optional sections

**Non-Interactive Mode** (For automation):

```bash
boilerplates compose generate nginx my-nginx \
  --var service_name=my-nginx \
  --var container_port=8080 \
  --no-interactive
```

This uses template defaults and provided variables without prompts.

### 5. Review Generated Files

After generation, you'll find:

```
my-nginx/
├── docker-compose.yml
└── .env
```

Review the files and adjust as needed for your environment.

## Basic Commands

Here are the essential commands you'll use regularly:

### Library Management

Manage template library repositories:

```bash
# Sync official template library
boilerplates repo update

# List all configured libraries
boilerplates repo list

# Add a custom template library
boilerplates repo add my-templates https://github.com/user/templates \
  --directory library \
  --branch main
```

### Working with Templates

Discover and use templates from the library:

```bash
# Browse available templates
boilerplates compose list

# Search the library
boilerplates compose search nginx

# Inspect template structure
boilerplates compose show nginx

# Generate files from template
boilerplates compose generate nginx ./output

# Validate template syntax
boilerplates compose validate
```

### Working with Defaults

Save frequently used values to avoid repetitive typing:

```bash
# Set a default value
boilerplates compose defaults set container_timezone="America/New_York"

# View all defaults
boilerplates compose defaults list

# Remove a default
boilerplates compose defaults rm container_timezone

# Clear all defaults
boilerplates compose defaults clear
```

## Common Workflows

### Workflow 1: Quick Generation with Defaults

Use template defaults without customization:

```bash
boilerplates compose generate portainer --no-interactive
```

### Workflow 2: Interactive Customization

Customize template variables interactively:

```bash
boilerplates compose show traefik         # Review template structure
boilerplates compose generate traefik     # Customize via prompts
```

### Workflow 3: Automation

For scripts and CI/CD pipelines:

```bash
boilerplates compose generate authentik ./auth \
  --var service_name=authentik \
  --var traefik_enabled=true \
  --var traefik_host=auth.example.com \
  --no-interactive \
  --dry-run  # Preview first
```

## Advanced Features

### Dry Run

Preview generated files without writing them:

```bash
boilerplates compose generate nginx --dry-run
```

### Debug Mode

Enable detailed logging for troubleshooting:

```bash
boilerplates --log-level DEBUG compose generate nginx
```

### Variable Override

Override specific variables without interactive prompts:

```bash
boilerplates compose generate grafana \
  --var service_name=monitoring-grafana \
  --var grafana_port=3000
```

## Next Steps

Now that you know the basics, explore more:

- [Templates](Core-Concepts-Templates) - Learn how templates work
- [Variables](Core-Concepts-Variables) - Understand variable types and dependencies
- [Configuration](Core-Concepts-Libraries) - Customize your setup
- [Variable Reference](Variables-Compose) - Complete variable documentation

## Troubleshooting

### CLI Command Not Found

If the `boilerplates` command is not found after installation:

```bash
# Ensure pipx binaries are in PATH
export PATH="$HOME/.local/bin:$PATH"

# Add to your shell profile (.bashrc, .zshrc, etc.)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
```

### Templates Not Available

If templates aren't showing up after installation:

```bash
# Sync the template library
boilerplates repo update

# Verify library is configured
boilerplates repo list
```

### Permission Issues

If you encounter permission errors:

```bash
# Ensure output directory is writable
chmod +w ./output-directory

# Or generate to a different location
boilerplates compose generate nginx ~/my-projects/nginx
```

## Getting Help

- **Documentation:** Browse the [Wiki](Home) for comprehensive guides
- **Discord:** Join the [community](https://christianlempa.de/discord) for real-time help
- **GitHub Issues:** Report bugs or request features
- **YouTube:** Watch [video tutorials](https://www.youtube.com/@christianlempa)

Happy templating!


================================================
FILE: .wiki/Home.md
================================================
# Boilerplates Documentation

Instant access to battle-tested templates for Docker, Terraform, Ansible, Kubernetes, and more.

Each template includes sensible defaults, best practices, and common configuration patterns—so you can focus on customizing for your environment.

## Boilerplates CLI Tool

The Boilerplates CLI is a Python-based tool that streamlines infrastructure template management. It provides an interactive interface for browsing, customizing, and generating configuration files from a curated template library. The tool handles variable validation, dependency resolution, and multi-source template management—giving you a consistent workflow whether you're deploying a single container or orchestrating complex infrastructure.

## User Documentation

**Getting Started**

- [Getting Started](Getting-Started) - Quick introduction and first steps
- [Installation](Installation) - Install the Boilerplates CLI tool on Linux, MacOS, or NixOS

**Core Concepts**

- [Templates](Core-Concepts-Templates) - Understanding templates and how they work
- [Variables](Core-Concepts-Variables) - Variable types, sections, and dependencies
- [Libraries](Core-Concepts-Libraries) - Managing template libraries
- [Defaults](Core-Concepts-Defaults) - Setting and managing default values

**Variable Reference**

- [Ansible Variables](Variables-Ansible)
- [Compose Variables](Variables-Compose)
- [Helm Variables](Variables-Helm)
- [Kubernetes Variables](Variables-Kubernetes)
- [Packer Variables](Variables-Packer)
- [Terraform Variables](Variables-Terraform)

## Developer Documentation

**Architecture & Development**

- [Architecture Overview](Developers-Architecture) - System design and core components
- [Module Development](Developers-Modules) - Creating new modules
- [Template Development](Developers-Templates) - Building templates
- [Contributing Guide](Developers-Contributing) - Detailed contribution workflow

**Contributing**

Before contributing, please read our [Contributing Guidelines](https://github.com/ChristianLempa/boilerplates/blob/main/CONTRIBUTING.md)


================================================
FILE: .wiki/Installation.md
================================================
# Installation

This guide covers installing the Boilerplates CLI on various platforms.

## Prerequisites

Before installing, ensure you have:

- **Python 3.10 or higher** - Check with `python3 --version`
- **Git** - Required for syncing template libraries
- **Internet connection** - For downloading dependencies and templates

### Checking Python Version

```bash
python3 --version
```

If you see version 3.10 or higher, you're ready to proceed. If not, see the platform-specific instructions below for installing Python.

## Quick Install (Recommended)

The automated installer script handles all dependencies and setup:

```bash
curl -fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh | bash
```

### Install Specific Version

```bash
curl -fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh | bash -s -- --version v0.1.0
```

The installer will:
1. Check for required dependencies (Python, pipx)
2. Install pipx if not present
3. Install the Boilerplates CLI in an isolated environment
4. Add the `boilerplates` command to your PATH

## Platform-Specific Installation

### Linux

#### Ubuntu / Debian

1. **Install Python and dependencies:**

```bash
sudo apt update
sudo apt install -y python3 python3-pip python3-venv git
```

2. **Install pipx:**

```bash
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```

3. **Install Boilerplates:**

```bash
pipx install boilerplates-cli
```

4. **Verify installation:**

```bash
boilerplates --version
```

#### Fedora / RHEL / CentOS

1. **Install Python and dependencies:**

```bash
sudo dnf install -y python3 python3-pip git
```

2. **Install pipx:**

```bash
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```

3. **Install Boilerplates:**

```bash
pipx install boilerplates-cli
```

#### Arch Linux

1. **Install Python and dependencies:**

```bash
sudo pacman -S python python-pip git
```

2. **Install pipx:**

```bash
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```

3. **Install Boilerplates:**

```bash
pipx install boilerplates-cli
```

### MacOS

#### Using Homebrew (Recommended)

1. **Install Homebrew** (if not already installed):

```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

2. **Install Python and pipx:**

```bash
brew install python pipx
pipx ensurepath
```

3. **Install Boilerplates:**

```bash
pipx install boilerplates-cli
```

#### Using Python from python.org

1. Download and install Python 3.10+ from [python.org](https://www.python.org/downloads/macos/)

2. **Install pipx:**

```bash
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```

3. **Install Boilerplates:**

```bash
pipx install boilerplates-cli
```

### NixOS

Boilerplates is available as a Nix Flake for NixOS and Nix users.

#### Run Without Installing

```bash
nix run github:christianlempa/boilerplates -- --help
```

#### Install to Profile

```bash
nix profile install github:christianlempa/boilerplates
```

#### Use in Flake

Add to your `flake.nix`:

```nix
{
  inputs.boilerplates.url = "github:christianlempa/boilerplates";

  outputs = { self, nixpkgs, boilerplates }: {
    # Use boilerplates.packages.${system}.default
    packages.x86_64-linux.default = boilerplates.packages.x86_64-linux.default;
  };
}
```

#### Temporary Shell

```bash
nix shell github:christianlempa/boilerplates
```

### Windows (WSL Recommended)

While Boilerplates can run on Windows, we recommend using Windows Subsystem for Linux (WSL) for the best experience.

#### Install WSL (Windows 10/11)

1. **Install WSL:**

```powershell
wsl --install
```

2. **Restart** your computer

3. **Follow Linux installation** instructions above (Ubuntu is the default distribution)

#### Native Windows (Not Recommended)

1. Install Python 3.10+ from [python.org](https://www.python.org/downloads/windows/)

2. Install pipx:

```powershell
python -m pip install --user pipx
python -m pipx ensurepath
```

3. Install Boilerplates:

```powershell
pipx install boilerplates-cli
```

## Manual Installation

For development or custom installations:

### Using pip (Not Recommended for End Users)

```bash
pip install --user boilerplates-cli
```

Note: This installs globally and may conflict with system packages. Use pipx instead.

### From Source

1. **Clone the repository:**

```bash
git clone https://github.com/ChristianLempa/boilerplates.git
cd boilerplates
```

2. **Create virtual environment:**

```bash
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
```

3. **Install in development mode:**

```bash
pip install -e .
```

4. **Run the CLI:**

```bash
python3 -m cli --help
```

## Post-Installation Setup

### Verify Installation

```bash
boilerplates --version
```

Expected output:
```
Boilerplates CLI v0.1.0
```

### Initialize Template Library

Sync the default template library:

```bash
boilerplates repo update
```

This downloads all available templates to:
```
~/.config/boilerplates/libraries/
```

### Shell Completion (Optional)

Enable tab completion for your shell:

#### Bash

```bash
echo 'eval "$(_BOILERPLATES_COMPLETE=bash_source boilerplates)"' >> ~/.bashrc
source ~/.bashrc
```

#### Zsh

```bash
echo 'eval "$(_BOILERPLATES_COMPLETE=zsh_source boilerplates)"' >> ~/.zshrc
source ~/.zshrc
```

#### Fish

```bash
echo '_BOILERPLATES_COMPLETE=fish_source boilerplates | source' >> ~/.config/fish/completions/boilerplates.fish
```

## Updating

### Update to Latest Version

```bash
pipx upgrade boilerplates-cli
```

### Update Template Library

```bash
boilerplates repo update
```

## Uninstalling

### Remove the CLI

```bash
pipx uninstall boilerplates-cli
```

### Remove Configuration and Templates

```bash
rm -rf ~/.config/boilerplates
```

## Troubleshooting

### Command Not Found After Installation

If `boilerplates` is not found, ensure pipx binaries are in your PATH:

```bash
# Add to your shell profile (.bashrc, .zshrc, etc.)
export PATH="$HOME/.local/bin:$PATH"
```

Then reload your shell:
```bash
source ~/.bashrc  # or ~/.zshrc
```

### Python Version Too Old

If you have Python < 3.10, install a newer version:

**Ubuntu/Debian:**
```bash
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.11
```

**Fedora:**
```bash
sudo dnf install python3.11
```

**MacOS (Homebrew):**
```bash
brew install python@3.11
```

### Permission Denied Errors

If you encounter permission errors during installation:

```bash
# Use --user flag
python3 -m pip install --user pipx

# Or use virtual environments
python3 -m venv ~/venvs/boilerplates
source ~/venvs/boilerplates/bin/activate
pip install boilerplates-cli
```

### SSL Certificate Errors

If you encounter SSL errors:

```bash
# Ubuntu/Debian
sudo apt install ca-certificates

# Update certificates
sudo update-ca-certificates
```

## Next Steps

Now that you've installed Boilerplates:

- [Getting Started](Getting-Started) - Generate your first template
- [Configuration](Core-Concepts-Libraries) - Customize your setup
- [Templates](Core-Concepts-Templates) - Learn about template structure

## Getting Help

- **Discord:** [Join the community](https://christianlempa.de/discord)
- **GitHub Issues:** [Report installation problems](https://github.com/ChristianLempa/boilerplates/issues)
- **Documentation:** [Browse the Wiki](Home)


================================================
FILE: .wiki/Variables-Ansible.md
================================================
# Ansible Variables

**Module:** `ansible`  
**Schema Version:** `1.0`  
**Description:** Manage Ansible playbooks

---

This page documents all available variables for the ansible module. Variables are organized into sections that can be enabled/disabled based on your configuration needs.

## Table of Contents

- [General](#general)
- [Options](#options)
- [Secrets](#secrets)

---

## General

**Required:** Yes

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `playbook_name` | `str` | _none_ | Ansible playbook name |
| `target_hosts` | `str` | `{{ my_hosts | d([]) }}` | Target hosts pattern (e.g., 'all', 'webservers', or '{{ my_hosts | d([]) }}') |
| `become` | `bool` | ✗ | Run tasks with privilege escalation (sudo) |

---

## Options

**Toggle Variable:** `options_enabled`

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `options_enabled` | `bool` | ✗ | Enable additional playbook options |
| `gather_facts` | `bool` | ✓ | Gather facts about target hosts |

---

## Secrets

**Toggle Variable:** `secrets_enabled`

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `secrets_enabled` | `bool` | ✗ | Use external secrets file |
| `secrets_file` | `str` | `secrets.yaml` | Path to secrets file |

---

## Notes

- **Required sections** must be configured
- **Toggle variables** enable/disable entire sections
- **Dependencies** (`needs`) control when sections/variables are available
- **Sensitive variables** are masked during prompts
- **Auto-generated variables** are populated automatically if not provided

---

_Last updated: Schema version 1.0_

================================================
FILE: .wiki/Variables-Compose.md
================================================
# Compose Variables

**Module:** `compose`  
**Schema Version:** `1.2`  
**Description:** Manage Docker Compose configurations

---

This page documents all available variables for the compose module. Variables are organized into sections that can be enabled/disabled based on your configuration needs.

## Table of Contents

- [General](#general)
- [Network](#network)
- [Ports](#ports)
- [Traefik](#traefik)
- [Traefik TLS/SSL](#traefik-tlsssl)
- [Volume Storage](#volume-storage)
- [Resource Limits](#resource-limits)
- [Docker Swarm](#docker-swarm)
- [Database](#database)
- [Email Server](#email-server)
- [Authentik SSO](#authentik-sso)

---

## General

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `service_name` | `str` | _none_ | Service name |
| `container_name` | `str` | _none_ | Container name |
| `container_hostname` | `str` | _none_ | Container internal hostname |
| `container_timezone` | `str` | `UTC` | Container timezone (e.g., Europe/Berlin) |
| `user_uid` | `int` | `1000` | User UID for container process |
| `user_gid` | `int` | `1000` | User GID for container process |
| `container_loglevel` | `enum` | `info` | Container log level<br>**Options:** `debug`, `info`, `warn`, `error` |
| `restart_policy` | `enum` | `unless-stopped` | Container restart policy<br>**Options:** `unless-stopped`, `always`, `on-failure`, `no` |

---

## Network

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `network_mode` | `enum` | `bridge` | Docker network mode<br>**Options:** `bridge`, `host`, `macvlan` |
| `network_name` | `str` | `bridge` | Docker network name<br>**Needs:** `network_mode=bridge,macvlan` |
| `network_external` | `bool` | ✗ | Use existing Docker network (external)<br>**Needs:** `network_mode=bridge,macvlan` |
| `network_macvlan_ipv4_address` | `str` | `192.168.1.253` | Static IP address for container<br>**Needs:** `network_mode=macvlan` |
| `network_macvlan_parent_interface` | `str` | `eth0` | Host network interface name<br>**Needs:** `network_mode=macvlan` |
| `network_macvlan_subnet` | `str` | `192.168.1.0/24` | Network subnet in CIDR notation<br>**Needs:** `network_mode=macvlan` |
| `network_macvlan_gateway` | `str` | `192.168.1.1` | Network gateway IP address<br>**Needs:** `network_mode=macvlan` |

---

## Ports

**Toggle Variable:** `ports_enabled`  
**Depends On:** `network_mode=bridge`

Expose service ports to the host.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `ports_http` | `int` | `8080` | HTTP port on host |
| `ports_https` | `int` | `8443` | HTTPS port on host |
| `ports_ssh` | `int` | `22` | SSH port on host |
| `ports_dns` | `int` | `53` | DNS port on host |
| `ports_dhcp` | `int` | `67` | DHCP port on host |
| `ports_smtp` | `int` | `25` | SMTP port on host |

---

## Traefik

**Toggle Variable:** `traefik_enabled`  
**Depends On:** `network_mode=bridge`

Traefik routes external traffic to your service.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `traefik_enabled` | `bool` | ✗ | Enable Traefik reverse proxy integration |
| `traefik_network` | `str` | `traefik` | Traefik network name |
| `traefik_host` | `str` | _none_ | Service subdomain or full hostname (e.g., 'app' or 'app.example.com') |
| `traefik_domain` | `str` | `home.arpa` | Base domain (e.g., example.com) |
| `traefik_entrypoint` | `str` | `web` | HTTP entrypoint (non-TLS) |

---

## Traefik TLS/SSL

**Toggle Variable:** `traefik_tls_enabled`  
**Depends On:** `traefik_enabled=true;network_mode=bridge`

Enable HTTPS/TLS for Traefik with certificate management.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `traefik_tls_enabled` | `bool` | ✓ | Enable HTTPS/TLS |
| `traefik_tls_entrypoint` | `str` | `websecure` | TLS entrypoint |
| `traefik_tls_certresolver` | `str` | `cloudflare` | Traefik certificate resolver name |

---

## Volume Storage

Configure persistent storage for your service.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `volume_mode` | `enum` | `local` | Volume storage backend<br>**Options:** `local`, `mount`, `nfs` • local: Docker-managed volumes | mount: Bind mount from host | nfs: Network filesystem |
| `volume_mount_path` | `str` | `/mnt/storage` | Host path for bind mounts<br>**Needs:** `volume_mode=mount` |
| `volume_nfs_server` | `str` | `192.168.1.1` | NFS server address<br>**Needs:** `volume_mode=nfs` |
| `volume_nfs_path` | `str` | `/export` | NFS export path<br>**Needs:** `volume_mode=nfs` |
| `volume_nfs_options` | `str` | `rw,nolock,soft` | NFS mount options (comma-separated)<br>**Needs:** `volume_mode=nfs` |

---

## Resource Limits

**Toggle Variable:** `resources_enabled`

Set CPU and memory limits for the service.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `resources_enabled` | `bool` | ✗ | Enable resource limits |
| `resources_cpu_limit` | `str` | `1.0` | Maximum CPU cores (e.g., 0.5, 1.0, 2.0) |
| `resources_cpu_reservation` | `str` | `0.25` | Reserved CPU cores<br>**Needs:** `swarm_enabled=true` |
| `resources_memory_limit` | `str` | `1G` | Maximum memory (e.g., 512M, 1G, 2G) |
| `resources_memory_reservation` | `str` | `512M` | Reserved memory<br>**Needs:** `swarm_enabled=true` |

---

## Docker Swarm

**Toggle Variable:** `swarm_enabled`  
**Depends On:** `network_mode=bridge`

Deploy service in Docker Swarm mode.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `swarm_enabled` | `bool` | ✗ | Enable Docker Swarm mode |
| `swarm_placement_mode` | `enum` | `replicated` | Swarm placement mode<br>**Options:** `replicated`, `global` |
| `swarm_replicas` | `int` | `1` | Number of replicas<br>**Needs:** `swarm_placement_mode=replicated` |
| `swarm_placement_host` | `str` | _none_ | Target hostname for placement constraint<br>**Needs:** `swarm_placement_mode=replicated` • Constrains service to run on specific node by hostname |

---

## Database

**Toggle Variable:** `database_enabled`

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `database_type` | `enum` | `default` | Database type<br>**Options:** `default`, `sqlite`, `postgres`, `mysql` |
| `database_external` | `bool` | ✗ | Use an external database server?<br>skips creation of internal database container |
| `database_host` | `str` | `database` | Database host |
| `database_port` | `int` | _none_ | Database port |
| `database_name` | `str` | _none_ | Database name |
| `database_user` | `str` | _none_ | Database user |
| `database_password` | `str` | _none_ | Database password<br>**Sensitive** • **Auto-generated** |

---

## Email Server

**Toggle Variable:** `email_enabled`

Configure email server for notifications and user management.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `email_enabled` | `bool` | ✗ | Enable email server configuration |
| `email_host` | `str` | _none_ | SMTP server hostname |
| `email_port` | `int` | `587` | SMTP server port |
| `email_username` | `str` | _none_ | SMTP username |
| `email_password` | `str` | _none_ | SMTP password<br>**Sensitive** |
| `email_from` | `str` | _none_ | From email address |
| `email_use_tls` | `bool` | ✓ | Use TLS encryption |
| `email_use_ssl` | `bool` | ✗ | Use SSL encryption |

---

## Authentik SSO

**Toggle Variable:** `authentik_enabled`

Integrate with Authentik for Single Sign-On authentication.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `authentik_enabled` | `bool` | ✗ | Enable Authentik SSO integration |
| `authentik_url` | `str` | _none_ | Authentik base URL (e.g., https://auth.example.com) |
| `authentik_slug` | `str` | _none_ | Authentik application slug |
| `authentik_client_id` | `str` | _none_ | OAuth client ID from Authentik provider |
| `authentik_client_secret` | `str` | _none_ | OAuth client secret from Authentik provider<br>**Sensitive** |
| `authentik_traefik_middleware` | `str` | `authentik-middleware@file` | Traefik middleware name for Authentik authentication<br>**Needs:** `traefik_enabled=true` |

---

## Notes

- **Required sections** must be configured
- **Toggle variables** enable/disable entire sections
- **Dependencies** (`needs`) control when sections/variables are available
- **Sensitive variables** are masked during prompts
- **Auto-generated variables** are populated automatically if not provided

---

_Last updated: Schema version 1.2_

================================================
FILE: .wiki/Variables-Helm.md
================================================
# Helm Variables

**Module:** `helm`  
**Schema Version:** `1.0`  
**Description:** Manage Helm charts

---

This page documents all available variables for the helm module. Variables are organized into sections that can be enabled/disabled based on your configuration needs.

## Table of Contents

- [General](#general)
- [Networking](#networking)
- [Traefik Ingress](#traefik-ingress)
- [Traefik TLS/SSL](#traefik-tlsssl)
- [Volumes](#volumes)
- [Database](#database)
- [Email Server](#email-server)

---

## General

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `release_name` | `str` | _none_ | Helm release name |
| `namespace` | `str` | `default` | Kubernetes namespace for the Helm release |

---

## Networking

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `network_mode` | `enum` | `ClusterIP` | Kubernetes service type<br>**Options:** `ClusterIP`, `NodePort`, `LoadBalancer` |

---

## Traefik Ingress

**Toggle Variable:** `traefik_enabled`  
**Depends On:** `network_mode=ClusterIP`

Traefik routes external traffic to your service.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `traefik_enabled` | `bool` | ✗ | Enable Traefik Ingress/IngressRoute |
| `traefik_host` | `hostname` | _none_ | Hostname for Traefik ingress |

---

## Traefik TLS/SSL

**Toggle Variable:** `traefik_tls_enabled`  
**Depends On:** `traefik_enabled=true;network_mode=ClusterIP`

Enable HTTPS/TLS for Traefik with certificate management.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `traefik_tls_enabled` | `bool` | ✓ | Enable HTTPS/TLS |
| `traefik_tls_secret` | `str` | `traefik-tls` | TLS secret name |
| `traefik_tls_certmanager` | `bool` | ✗ | Use cert-manager for automatic certificate provisioning |
| `certmanager_issuer` | `str` | `cloudflare` | Cert-manager ClusterIssuer or Issuer name<br>**Needs:** `traefik_tls_certmanager=true` |
| `certmanager_issuer_kind` | `enum` | `ClusterIssuer` | Issuer kind<br>**Options:** `ClusterIssuer`, `Issuer` • **Needs:** `traefik_tls_certmanager=true` |

---

## Volumes

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `volumes_mode` | `enum` | `default` | Persistent volume mode<br>**Options:** `default`, `existing-pvc` |
| `volumes_pvc_name` | `str` | _none_ | Name of existing PVC<br>**Needs:** `volumes_mode=existing-pvc` |

---

## Database

**Toggle Variable:** `database_enabled`

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `database_enabled` | `bool` | ✗ | Enable database configuration |
| `database_type` | `enum` | `postgres` | Database type<br>**Options:** `postgres`, `mysql`, `mariadb` |
| `database_host` | `hostname` | _none_ | Database host |
| `database_port` | `int` | _none_ | Database port |
| `database_name` | `str` | _none_ | Database name |
| `database_user` | `str` | _none_ | Database user |
| `database_password` | `str` | _none_ | Database password<br>**Sensitive** • **Auto-generated** |

---

## Email Server

**Toggle Variable:** `email_enabled`

Configure email server for notifications and user management.

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `email_enabled` | `bool` | ✗ | Enable email server configuration |
| `email_host` | `hostname` | _none_ | SMTP server hostname |
| `email_port` | `int` | `587` | SMTP server port |
| `email_username` | `str` | _none_ | SMTP username |
| `email_password` | `str` | _none_ | SMTP password<br>**Sensitive** |
| `email_from` | `email` | _none_ | From email address |
| `email_use_tls` | `bool` | ✓ | Use TLS encryption |
| `email_use_ssl` | `bool` | ✗ | Use SSL encryption |

---

## Notes

- **Required sections** must be configured
- **Toggle variables** enable/disable entire sections
- **Dependencies** (`needs`) control when sections/variables are available
- **Sensitive variables** are masked during prompts
- **Auto-generated variables** are populated automatically if not provided

---

_Last updated: Schema version 1.0_

================================================
FILE: .wiki/Variables-Kubernetes.md
================================================
# Kubernetes Variables

**Module:** `kubernetes`  
**Schema Version:** `1.0`  
**Description:** Manage Kubernetes configurations

---

This page documents all available variables for the kubernetes module. Variables are organized into sections that can be enabled/disabled based on your configuration needs.

## Table of Contents

- [General](#general)
- [Traefik](#traefik)
- [Traefik TLS/SSL](#traefik-tlsssl)
- [Cert-Manager](#cert-manager)

---

## General

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `resource_name` | `str` | _none_ | Resource name (metadata.name) |
| `namespace` | `str` | `default` | Kubernetes namespace |

---

## Traefik

**Toggle Variable:** `traefik_enabled`

Traefik IngressRoute configuration for HTTP/HTTPS routing

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `traefik_enabled` | `bool` | ✗ | Enable Traefik IngressRoute |
| `traefik_entrypoint` | `str` | `web` | Traefik entrypoint (non-TLS) |
| `traefik_host` | `hostname` | _none_ | Domain name for the service (e.g., app.example.com) |
| `traefik_service_name` | `str` | _none_ | Backend Kubernetes service name |
| `traefik_service_port` | `int` | `80` | Backend service port |

---

## Traefik TLS/SSL

**Toggle Variable:** `traefik_tls_enabled`  
**Depends On:** `traefik`

Enable HTTPS/TLS for Traefik with certificate management

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `traefik_tls_enabled` | `bool` | ✓ | Enable HTTPS/TLS |
| `traefik_tls_entrypoint` | `str` | `websecure` | TLS entrypoint |
| `traefik_tls_certresolver` | `str` | `cloudflare` | Traefik certificate resolver name |

---

## Cert-Manager

**Toggle Variable:** `certmanager_enabled`

Cert-manager certificate management configuration

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `certmanager_enabled` | `bool` | ✗ | Enable cert-manager certificate |
| `certmanager_issuer` | `str` | `cloudflare` | ClusterIssuer or Issuer name |
| `certmanager_issuer_kind` | `enum` | `ClusterIssuer` | Issuer kind<br>**Options:** `ClusterIssuer`, `Issuer` |

---

## Notes

- **Required sections** must be configured
- **Toggle variables** enable/disable entire sections
- **Dependencies** (`needs`) control when sections/variables are available
- **Sensitive variables** are masked during prompts
- **Auto-generated variables** are populated automatically if not provided

---

_Last updated: Schema version 1.0_

================================================
FILE: .wiki/Variables-Packer.md
================================================
# Packer Variables

**Module:** `packer`  
**Schema Version:** `1.0`  
**Description:** Manage Packer templates

---

This page documents all available variables for the packer module. Variables are organized into sections that can be enabled/disabled based on your configuration needs.

## Table of Contents

- [General](#general)

---

## General

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `image_name` | `str` | _none_ | Image name |

---

## Notes

- **Required sections** must be configured
- **Toggle variables** enable/disable entire sections
- **Dependencies** (`needs`) control when sections/variables are available
- **Sensitive variables** are masked during prompts
- **Auto-generated variables** are populated automatically if not provided

---

_Last updated: Schema version 1.0_

================================================
FILE: .wiki/Variables-Terraform.md
================================================
# Terraform Variables

**Module:** `terraform`  
**Schema Version:** `1.0`  
**Description:** Manage Terraform configurations

---

This page documents all available variables for the terraform module. Variables are organized into sections that can be enabled/disabled based on your configuration needs.

## Table of Contents

- [General](#general)

---

## General

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `resource_name` | `str` | _none_ | Resource name prefix |
| `backend_mode` | `enum` | `local` | Terraform backend mode<br>**Options:** `local`, `http` |

---

## Notes

- **Required sections** must be configured
- **Toggle variables** enable/disable entire sections
- **Dependencies** (`needs`) control when sections/variables are available
- **Sensitive variables** are masked during prompts
- **Auto-generated variables** are populated automatically if not provided

---

_Last updated: Schema version 1.0_

================================================
FILE: .wiki/Variables.md
================================================
# Variables Documentation

This section contains auto-generated documentation for all available variables in each module.

## Available Modules

- [Ansible](Variables-Ansible)
- [Compose](Variables-Compose)
- [Helm](Variables-Helm)
- [Kubernetes](Variables-Kubernetes)
- [Packer](Variables-Packer)
- [Terraform](Variables-Terraform)

---

Each module page includes:

- Schema version information
- Complete list of sections and variables
- Variable types, defaults, and descriptions
- Section dependencies and toggle configurations

---

_This documentation is auto-generated from module schemas._

================================================
FILE: .wiki/_Sidebar.md
================================================
## Boilerplates Wiki

- **[Home](Home)**

### Getting Started
- [Getting Started](Getting-Started)
- [Installation](Installation)

### Core Concepts
- [Templates](Core-Concepts-Templates)
- [Variables](Core-Concepts-Variables)
- [Libraries](Core-Concepts-Libraries)
- [Defaults](Core-Concepts-Defaults)

### Variables Reference
- [All Modules](Variables)
- [Ansible](Variables-Ansible)
- [Compose](Variables-Compose)
- [Helm](Variables-Helm)
- [Kubernetes](Variables-Kubernetes)
- [Packer](Variables-Packer)
- [Terraform](Variables-Terraform)

### Developer Docs
- [Architecture](Developers-Architecture)
- [Modules](Developers-Modules)
- [Templates](Developers-Templates)
- [Contributing](Developers-Contributing)


================================================
FILE: .yamllint
================================================
---
extends: default

rules:
  comments-indentation: disable
  indentation:
    spaces: 2
    indent-sequences: true
  line-length:
    max: 160
    level: warning


================================================
FILE: AGENTS.md
================================================
# AGENTS.md

Guidance for AI Agents working with this repository.

## Project Overview

A sophisticated collection of infrastructure templates (boilerplates) with a Python CLI for management. Supports Terraform, Docker, Ansible, Kubernetes, etc. Built with Typer (CLI) and Jinja2 (templating).

## Development Setup

### Running and Testing

```bash
# Run the CLI application
python3 -m cli
# Debugging and Testing commands
python3 -m cli --log-level DEBUG compose list
```

### Production-Ready Testing

For detailed information about testing boilerplates in a production-like environment before release, see **WARP-LOCAL.md** (local file, not in git). This document covers:
- Test server infrastructure and Docker contexts
- Step-by-step testing procedures for Docker Compose, Swarm, and Kubernetes
- Comprehensive testing checklists
- Production release criteria

### Linting and Formatting

Should **always** happen before pushing anything to the repository.

- Use `yamllint` for YAML files
- Use `ruff` for Python code:
  - `ruff check --fix .` - Check and auto-fix linting errors (including unused imports)
  - `ruff format .` - Format code according to style guidelines
  - Both commands must be run before committing

### Project Management and Git

The project is stored in a public GitHub Repository, use issues, and branches for features/bugfixes and open PRs for merging.

**Naming Conventions and Best-Practices:**
- Branches, PRs: `feature/2314-add-feature`, `problem/1249-fix-bug`
- Issues should have clear titles and descriptions, link related issues/PRs, and have appropriate labels like (problem, feature, discussion, question).
- Commit messages should be clear and concise, following the format: `type(scope): subject` (e.g., `fix(compose): correct variable parsing`).

## Architecture

### File Structure

- `cli/` - Python CLI application source code
  - `cli/core/` - Core Components of the CLI application
  - `cli/core/schema/` - JSON schema definitions for all modules
  - `cli/modules/` - Modules implementing technology-specific functions
  - `cli/__main__.py` - CLI entry point, auto-discovers modules and registers commands
- `library/` - Template collections organized by module
  - `library/ansible/` - Ansible playbooks and configurations
  - `library/compose/` - Docker Compose configurations
  - `library/docker/` - Docker templates
  - `library/kubernetes/` - Kubernetes deployments
  - `library/packer/` - Packer templates
  - `library/terraform/` - OpenTofu/Terraform templates and examples
- `archetypes/` - Testing tool for template snippets (archetype development)
  - `archetypes/__init__.py` - Package initialization
  - `archetypes/__main__.py` - CLI tool entry point
  - `archetypes/<module>/` - Module-specific archetype snippets (e.g., `archetypes/compose/`)

### Core Components

- `cli/core/collection.py` - VariableCollection class (manages sections and variables)
  - **Key Attributes**: `_sections` (dict of VariableSection objects), `_variable_map` (flat lookup dict)
  - **Key Methods**: `get_satisfied_values()` (returns enabled variables), `apply_defaults()`, `sort_sections()`
- `cli/core/config.py` - Configuration management (loading, saving, validation)
- `cli/core/display/` - Centralized CLI output rendering package (**Always use DisplayManager - never print directly**)
  - `__init__.py` - Package exports (DisplayManager, DisplaySettings, IconManager)
  - `display_manager.py` - Main DisplayManager facade
  - `display_settings.py` - DisplaySettings configuration class
  - `icon_manager.py` - IconManager for Nerd Font icons
  - `variable_display.py` - VariableDisplayManager for variable rendering
  - `template_display.py` - TemplateDisplayManager for template display
  - `status_display.py` - StatusDisplayManager for status messages
  - `table_display.py` - TableDisplayManager for table rendering
- `cli/core/exceptions.py` - Custom exceptions for error handling (**Always use this for raising errors**)
- `cli/core/library.py` - LibraryManager for template discovery from git-synced libraries and static file paths
- `cli/core/module.py` - Abstract base class for modules (defines standard commands)
- `cli/core/prompt.py` - Interactive CLI prompts using rich library
- `cli/core/registry.py` - Central registry for module classes (auto-discovers modules)
- `cli/core/repo.py` - Repository management for syncing git-based template libraries
- `cli/core/schema/` - Schema management package (**JSON-based schema system**)
  - `loader.py` - SchemaLoader class for loading and validating JSON schemas
  - `<module>/` - Module-specific schema directories (e.g., `compose/`, `terraform/`)
  - `<module>/v*.json` - Version-specific JSON schema files (e.g., `v1.0.json`, `v1.2.json`)
- `cli/core/section.py` - VariableSection class (stores section metadata and variables)
  - **Key Attributes**: `key`, `title`, `toggle`, `needs`, `variables` (dict of Variable objects)
- `cli/core/template.py` - Template Class for parsing, managing and rendering templates
- `cli/core/variable.py` - Variable class (stores variable metadata and values)
  - **Key Attributes**: `name`, `type`, `value` (stores default or current value), `description`, `sensitive`, `needs`
  - **Note**: Default values are stored in `value` attribute, NOT in a separate `default` attribute
- `cli/core/validators.py` - Semantic validators for template content (Docker Compose, YAML, etc.)
- `cli/core/version.py` - Version comparison utilities for semantic versioning

### Modules

**Module Structure:**
Modules can be either single files or packages:
- **Single file**: `cli/modules/modulename.py` (for simple modules)
- **Package**: `cli/modules/modulename/` with `__init__.py` (for multi-schema modules)

**Creating Modules:**
- Subclass `Module` from `cli/core/module.py`
- Define `name`, `description`, and `schema_version` class attributes
- For multi-schema modules: organize specs in separate files (e.g., `spec_v1_0.py`, `spec_v1_1.py`)
- Call `registry.register(YourModule)` at module bottom
- Auto-discovered and registered at CLI startup

**Module Discovery and Registration:**

The system automatically discovers and registers modules at startup:

1. **Discovery**: CLI `__main__.py` imports all Python files in `cli/modules/` directory
2. **Registration**: Each module file calls `registry.register(ModuleClass)` at module level
3. **Storage**: Registry stores module classes in a central dictionary by module name
4. **Command Generation**: CLI framework auto-generates subcommands for each registered module
5. **Instantiation**: Modules are instantiated on-demand when commands are invoked

**Benefits:**
- No manual registration needed - just add a file to `cli/modules/`
- Modules are self-contained - can be added/removed without modifying core code
- Type-safe - registry validates module interfaces at registration time

**Module Schema System:**

**JSON Schema Architecture** (Refactored from Python specs):

All module schemas are now defined as **JSON files** in `cli/core/schema/<module>/v*.json`. This provides:
- **Version control**: Easy schema comparison and diffs in git
- **Language-agnostic**: Schemas can be consumed by tools outside Python
- **Validation**: Built-in JSON schema validation
- **Documentation**: Self-documenting schema structure

**Schema File Location:**
```
cli/core/schema/
  compose/
    v1.0.json
    v1.1.json
    v1.2.json
  terraform/
    v1.0.json
  ansible/
    v1.0.json
  ...other modules...
```

**JSON Schema Structure:**

Schemas are arrays of section objects, where each section contains:

```json
[
  {
    "key": "section_key",
    "title": "Section Title",
    "description": "Optional section description",
    "toggle": "optional_toggle_variable_name",
    "needs": "optional_dependency",
    "required": true,
    "vars": [
      {
        "name": "variable_name",
        "type": "str",
        "description": "Variable description",
        "default": "default_value",
        "required": true,
        "sensitive": false,
        "autogenerated": false,
        "options": ["option1", "option2"],
        "needs": "other_var=value",
        "extra": "Additional help text"
      }
    ]
  }
]
```

**Schema Loading in Modules:**

Modules load JSON schemas on-demand using the SchemaLoader:

```python
from cli.core.schema import load_schema, has_schema, list_versions

class MyModule(Module):
    name = "mymodule"
    schema_version = "1.2"  # Latest version supported
    
    def get_spec(self, template_schema: str) -> OrderedDict:
        """Load JSON schema and convert to dict format."""
        json_spec = load_schema(self.name, template_schema)
        # Convert JSON array to OrderedDict format
        return self._convert_json_to_dict(json_spec)
```

**Schema Design Principles:**
- **Backward compatibility**: Newer module versions can load older template schemas
- **Auto-created toggle variables**: Sections with `toggle` automatically create boolean variables
- **Conditional visibility**: Variables use `needs` constraints to show/hide based on other variable values
- **Mode-based organization**: Related settings grouped by operational mode (e.g., network_mode, volume_mode)
- **Incremental evolution**: New schemas add features without breaking existing templates

**Working with Schemas:**
- **View available versions**: Check `cli/core/schema/<module>/` directory or use `list_versions(module)`
- **Add new schema version**: Create new JSON file following naming convention (e.g., `v1.3.json`)
- **Update module**: Increment `schema_version` in module class when adding new schema
- **Validate schemas**: SchemaLoader automatically validates JSON structure on load

**Migration from Python Specs:**

Older Python-based `spec_v*.py` files have been migrated to JSON. The module `__init__.py` now:
1. Loads JSON schemas using SchemaLoader
2. Converts JSON array format to OrderedDict for backward compatibility
3. Provides lazy loading via `_SchemaDict` class

**Existing Modules:**
- `cli/modules/compose/` - Docker Compose (JSON schemas: v1.0, v1.1, v1.2)
- Other modules (ansible, terraform, kubernetes, helm, packer) - Work in Progress

**(Work in Progress):** terraform, docker, ansible, kubernetes, packer modules

### LibraryManager

- Loads libraries from config file
- Stores Git Libraries under: `~/.config/boilerplates/libraries/{name}/`
- Uses sparse-checkout to clone only template directories for git-based libraries (avoiding unnecessary files)
- Supports two library types: **git** (synced from repos) and **static** (local directories)
- Priority determined by config order (first = highest)

**Library Types:**
- `git`: Requires `url`, `branch`, `directory` fields
- `static`: Requires `path` field (absolute or relative to config)

**Duplicate Handling:**
- Within same library: Raises `DuplicateTemplateError`
- Across libraries: Uses qualified IDs (e.g., `alloy.default`, `alloy.local`)
- Simple IDs use priority: `compose show alloy` loads from first library
- Qualified IDs target specific library: `compose show alloy.local`

**Config Example:**
```yaml
libraries:
  - name: default       # Highest priority (checked first)
    type: git
    url: https://github.com/user/templates.git
    branch: main
    directory: library
  - name: local         # Lower priority
    type: static
    path: ~/my-templates
    url: ''             # Backward compatibility fields
    branch: main
    directory: .
```

**Note:** Static libraries include dummy `url`/`branch`/`directory` fields for backward compatibility with older CLI versions.

### ConfigManager

- User Config stored in `~/.config/boilerplates/config.yaml`

### DisplayManager and IconManager

**CRITICAL RULE - NEVER violate this:**
- NEVER use `console.print()` outside of display manager classes (`cli/core/display/` directory)
- NEVER import `Console` from `rich.console` except in display manager classes or `cli/__main__.py`
- ALWAYS use `module_instance.display.display_*()` or `display.display_*()` methods for ALL output
- Display managers (`cli/core/display/*.py`) are the ONLY exception - they implement console output

**Rationale:**
- `DisplayManager` provides a **centralized interface** for ALL CLI output rendering
- Direct console usage bypasses formatting standards, icon management, and output consistency
- `IconManager` provides **Nerd Font icons** internally for DisplayManager - never use emojis or direct icons

**DisplayManager Architecture** (Refactored for Single Responsibility Principle):

`DisplayManager` acts as a facade that delegates to specialized manager classes:

1. **VariableDisplayManager** - Handles all variable-related rendering
   - `render_variable_value()` - Variable value formatting with context awareness
   - `render_section()` - Section header display
   - `render_variables_table()` - Complete variables table with dependencies

2. **TemplateDisplayManager** - Handles all template-related rendering
   - `render_template()` - Main template display coordinator
   - `render_template_header()` - Template metadata display
   - `render_file_tree()` - Template file structure visualization
   - `render_file_generation_confirmation()` - Files preview before generation

3. **StatusDisplayManager** - Handles status messages and error display
   - `display_message()` - Core message formatting with level-based routing
   - `display_error()`, `display_warning()`, `display_success()`, `display_info()` - Convenience methods
   - `display_template_render_error()` - Detailed render error display
   - `display_warning_with_confirmation()` - Interactive warning prompts

4. **TableDisplayManager** - Handles table rendering
   - `render_templates_table()` - Templates list with library indicators
   - `render_status_table()` - Status tables with success/error indicators
   - `render_config_tree()` - Configuration tree visualization

**Usage Pattern:**
```python
# External code uses DisplayManager methods (backward compatible)
display = DisplayManager()
display.display_template(template, template_id)

# Internally, DisplayManager delegates to specialized managers
# display.templates.render_template(template, template_id)
```

**Design Principles:**
- External code calls `DisplayManager` methods only
- `DisplayManager` delegates to specialized managers internally
- Each specialized manager has a single, focused responsibility
- Backward compatibility maintained through delegation methods
- All managers can access parent DisplayManager via `self.parent`

## Templates

Templates are directory-based. Each template is a directory containing all the necessary files and subdirectories for the boilerplate.

### Template Rendering Flow

**How templates are loaded and rendered:**

1. **Discovery**: LibraryManager finds template directories containing `template.yaml`/`template.yml`
2. **Parsing**: Template class loads and parses the template metadata and spec
3. **Schema Resolution**: Module's `get_spec()` loads appropriate spec based on template's `schema` field
4. **Variable Inheritance**: Template inherits ALL variables from module schema
5. **Variable Merging**: Template spec overrides are merged with module spec (precedence: module < template < user config < CLI)
6. **Collection Building**: VariableCollection is constructed with merged variables and sections
7. **Dependency Resolution**: Sections are topologically sorted based on `needs` constraints
8. **Variable Resolution**: Variables with `needs` constraints are evaluated for visibility
9. **Jinja2 Rendering**: Template files (`.j2`) are rendered with final variable values
10. **Sanitization**: Rendered output is cleaned (whitespace, blank lines, trailing newline)
11. **Validation**: Optional semantic validation (YAML structure, Docker Compose schema, etc.)

**Key Architecture Points:**
- Templates don't "call" module specs - they declare a schema version and inherit from it
- Variable visibility is dynamic based on `needs` constraints (evaluated at prompt/render time)
- Jinja2 templates support `{% include %}` and `{% import %}` for composition

### Template Structure

Requires `template.yaml` or `template.yml` with metadata and variables:

```yaml
---
kind: compose
schema: "X.Y"  # Optional: Defaults to "1.0" if not specified (e.g., "1.0", "1.2")
metadata:
  name: My Service Template
  description: A template for a service.
  version: 1.0.0
  author: Your Name
  date: '2024-01-01'
spec:
  general:
    vars:
      service_name:
        type: str
        description: Service name
```

### Template Metadata Versioning

**Template Version Field:**
The `metadata.version` field in `template.yaml` should reflect the version of the underlying application or resource:
- **Compose templates**: Match the Docker image version (e.g., `nginx:1.25.3` → `version: 1.25.3`)
- **Terraform templates**: Match the provider version (e.g., AWS provider 5.23.0 → `version: 5.23.0`)
- **Other templates**: Match the primary application/tool version being deployed
- Use `latest` or increment template-specific version (e.g., `0.1.0`, `0.2.0`) only when no specific application version applies

**Rationale:** This helps users identify which version of the application/provider the template is designed for and ensures template versions track upstream changes.

**Application Version Variables:**
- **IMPORTANT**: Application/image versions should be **hardcoded** in template files (e.g., `image: nginx:1.25.3`)
- Do NOT create template variables for application versions (e.g., no `nginx_version` variable)
- Users should update the template file directly when they need a different version
- This prevents version mismatches and ensures templates are tested with specific, known versions
- Exception: Only create version variables if there's a strong technical reason (e.g., multi-component version pinning)

### Template Schema Versioning

**Version Format:** Schemas use 2-level versioning in `MAJOR.MINOR` format (e.g., "1.0", "1.2", "2.0").

Templates and modules use schema versioning to ensure compatibility. Each module defines a supported schema version, and templates declare which schema version they use.

```yaml
---
kind: compose
schema: "X.Y"  # Optional: Defaults to "1.0" if not specified (e.g., "1.0", "1.2")
metadata:
  name: My Template
  version: 1.0.0
  # ... other metadata fields
spec:
  # ... variable specifications
```

**How It Works:**
- **Module Schema Version**: Each module defines `schema_version` (e.g., "1.0", "1.2", "2.0")
- **Module Spec Loading**: Modules load appropriate spec based on template's schema version
- **Template Schema Version**: Each template declares `schema` at the top level (defaults to "1.0")
- **Compatibility Check**: Template schema ≤ Module schema → Compatible
- **Incompatibility**: Template schema > Module schema → `IncompatibleSchemaVersionError`

**Behavior:**
- Templates without `schema` field default to "1.0" (backward compatible)
- Older templates work with newer module versions (backward compatibility)
- Templates with newer schema versions fail on older modules with `IncompatibleSchemaVersionError`
- Version comparison uses MAJOR.MINOR format (e.g., "1.0" < "1.2" < "2.0")

**When to Use:**
- Increment module schema version when adding new features (new variable types, sections, etc.)
- Set template schema when using features from a specific schema version
- Templates using features from newer schemas must declare the appropriate schema version

**Single-File Module Example:**
```python
class SimpleModule(Module):
  name = "simple"
  description = "Simple module"
  schema_version = "X.Y"  # e.g., "1.0", "1.2"
  spec = VariableCollection.from_dict({...})  # Single spec
```

**Multi-Schema Module Example:**
```python
# cli/modules/modulename/__init__.py
class ExampleModule(Module):
  name = "modulename"
  description = "Module description"
  schema_version = "X.Y"  # Highest schema version supported (e.g., "1.2", "2.0")
  
  def get_spec(self, template_schema: str) -> VariableCollection:
    """Load spec based on template schema version."""
    # Dynamically load the appropriate spec version
    # template_schema will be like "1.0", "1.2", etc.
    version_file = f"spec_v{template_schema.replace('.', '_')}"
    spec_module = importlib.import_module(f".{version_file}", package=__package__)
    return spec_module.get_spec()
```

**Version Management:**
- CLI version is defined in `cli/__init__.py` as `__version__`
- pyproject.toml version must match `__version__` for releases
- GitHub release workflow validates version consistency

### Template Files

- **Jinja2 Templates (`.j2`)**: Rendered by Jinja2, `.j2` extension removed in output. Support `{% include %}` and `{% import %}`.
- **Static Files**: Non-`.j2` files copied as-is.
- **Sanitization**: Auto-sanitized (single blank lines, no leading blanks, trimmed whitespace, single trailing newline).
- **Shortcodes**: Template descriptions support emoji-style shortcodes (e.g., `:warning:`, `:info:`, `:docker:`) which are automatically replaced with Nerd Font icons during display. Add new shortcodes to `IconManager.SHORTCODES` dict.

### Docker Compose Best Practices

**Traefik Integration:**

When using Traefik with Docker Compose, the `traefik.docker.network` label is **CRITICAL** for stacks with multiple networks. When containers are connected to multiple networks, Traefik must know which network to use for routing.

**Implementation:**
- Review `archetypes/compose/` directory for reference implementations of Traefik integration patterns
- The `traefik.docker.network={{ traefik_network }}` label must be present in both standard `labels:` and `deploy.labels:` sections
- Standard mode and Swarm mode require different label configurations - check archetypes for examples

### Variables

**How Templates Inherit Variables:**

Templates automatically inherit ALL variables from the module schema version they declare. The template's `schema: "X.Y"` field determines which module spec is loaded, and all variables from that schema are available.

**When to Define Template Variables:**

You only need to define variables in your template's `spec` section when:
1. **Overriding defaults**: Change default values for module variables (e.g., hardcode `service_name` for your specific app)
2. **Adding custom variables**: Define template-specific variables not present in the module schema
3. **Upgrading to newer schema**: To use new features, update `schema: "X.Y"` to a higher version - no template spec changes needed

**Variable Precedence** (lowest to highest):
1. Module `spec` (defaults for all templates of that kind)
2. Template `spec` (overrides module defaults)
3. User `config.yaml` (overrides template and module defaults)
4. CLI `--var` (highest priority)

**Template Variable Override Rules:**
- **Override module defaults**: Only specify properties that differ from module spec (e.g., change `default` value)
- **Create new variables**: Define template-specific variables not in module spec
- **Minimize duplication**: Do NOT re-specify `type`, `description`, or other properties if they remain unchanged from module spec

**Example:**
```yaml
# Template declares schema: "1.2" → inherits ALL variables from compose schema 1.2
# Template spec ONLY needs to override specific defaults:
spec:
  general:
    vars:
      service_name:
        default: whoami  # Only override the default, type already defined in module
      # All other schema 1.2 variables (network_mode, volume_mode, etc.) are automatically available
```

**Variable Types:**
- `str` (default), `int`, `float`, `bool`
- `email` - Email validation with regex
- `url` - URL validation (requires scheme and host)
- `hostname` - Hostname/domain validation
- `enum` - Choice from `options` list

**Variable Properties:**
- `sensitive: true` - Masked in prompts/display (e.g., passwords)
- `autogenerated: true` - Auto-generates value if empty (shows `*auto` placeholder)
- `default` - Default value
- `description` - Variable description
- `prompt` - Custom prompt text (overrides description)
- `extra` - Additional help text
- `options` - List of valid values (for enum type)

**Section Features:**
- **Toggle Settings**: Conditional sections via `toggle: "bool_var_name"`. If false, section is skipped.
  - **IMPORTANT**: When a section has `toggle: "var_name"`, that boolean variable is AUTO-CREATED by the system
  - Toggle variable behavior may vary by schema version - check current schema documentation
  - Example: `ports` section with `toggle: "ports_enabled"` automatically provides `ports_enabled` boolean
- **Dependencies**: Use `needs: "section_name"` or `needs: ["sec1", "sec2"]`. Dependent sections only shown when dependencies are enabled.

**Dependency Resolution Architecture:**

Sections and variables support `needs` constraints to control visibility based on other variables.

**Section-Level Dependencies:**
- Format: `needs: "section_name"` or `needs: ["sec1", "sec2"]`
- Section only appears when all required sections are enabled (their toggle variables are true)
- Automatically validated: detects circular, missing, and self-dependencies
- Topologically sorted: ensures dependencies are prompted/processed before dependents

**Variable-Level Dependencies:**
- Format: `needs: "var_name=value"` or `needs: "var1=val1;var2=val2"` (semicolon-separated)
- Variable only visible when constraint is satisfied (e.g., `needs: "network_mode=bridge"`)
- Supports multiple values: `needs: "network_mode=bridge,macvlan"` (comma = OR)
- Evaluated dynamically at prompt and render time

**Validation:**
- Circular dependencies: Raises error if A needs B and B needs A
- Missing dependencies: Raises error if referencing non-existent sections/variables
- Self-dependencies: Raises error if section depends on itself

**Example Section with Dependencies:**

```yaml
spec:
  traefik:
    title: Traefik
    required: false
    toggle: traefik_enabled
    vars:
      traefik_enabled:
        type: bool
        default: false
      traefik_host:
        type: hostname
  
  traefik_tls:
    title: Traefik TLS/SSL
    needs: traefik
    toggle: traefik_tls_enabled
    vars:
      traefik_tls_enabled:
        type: bool
        default: true
      traefik_tls_certresolver:
        type: str
        sensitive: false
        default: myresolver
```

## Validation

**Jinja2 Validation:**
- Templates validated for Jinja2 syntax errors during load
- Checks for undefined variables (variables used but not declared in spec)
- Built into Template class

**Semantic Validation:**
- Validator registry system in `cli/core/validators.py`
- Extensible: `ContentValidator` abstract base class
- Built-in validators: `DockerComposeValidator`, `YAMLValidator`
- Validates rendered output (YAML structure, Docker Compose schema, etc.)
- Triggered via `compose validate` command with `--semantic` flag (enabled by default)

## Prompt

Uses `rich` library for interactive prompts. Supports:
- Text input
- Password input (masked, for `sensitive: true` variables)
- Selection from list (single/multiple)
- Confirmation (yes/no)
- Default values
- Autogenerated variables (show `*auto` placeholder, generate on render)

To skip the prompt use the `--no-interactive` flag, which will use defaults or empty values.

## Commands

**Standard Module Commands** (auto-registered for all modules):
- `list` - List all templates
- `search <query>` - Search templates by ID
- `show <id>` - Show template details
- `generate <id> -o <directory>` - Generate from template (supports `--dry-run`, `--var`, `--no-interactive`)
- `validate [template_id]` - Validate template(s) (Jinja2 + semantic). Omit template_id to validate all templates
- `defaults` - Manage config defaults (`get`, `set`, `rm`, `clear`, `list`)

**Core Commands:**
- `repo sync` - Sync git-based libraries
- `repo list` - List configured libraries

## Archetypes

The `archetypes` package provides reusable, standardized template building blocks for creating boilerplates. Archetypes are modular Jinja2 snippets that represent specific configuration sections.

### Purpose

1. **Template Development**: Provide standardized, tested building blocks for creating new templates
2. **Testing & Validation**: Enable testing of specific configuration sections in isolation with different variable combinations

### Usage

```bash
# List available archetypes for a module
python3 -m archetypes compose list

# Preview an archetype component
python3 -m archetypes compose generate <archetype-name>

# Test with variable overrides
python3 -m archetypes compose generate <archetype-name> \
  --var traefik_enabled=true \
  --var swarm_enabled=true

# Validate templates against archetypes
python3 -m archetypes compose validate            # All templates
python3 -m archetypes compose validate <template> # Single template
```

### Archetype Validation

The `validate` command compares templates against archetypes to measure coverage and identify which archetype patterns are being used.

**What it does:**
- Compares each template file against all available archetypes using **structural pattern matching**
- Abstracts away specific values to focus on:
  - **Jinja2 control flow**: `{% if %}`, `{% elif %}`, `{% else %}`, `{% for %}` structures
  - **YAML structure**: Key names, indentation, and nesting patterns
  - **Variable usage patterns**: Presence of `{{ }}` placeholders (not specific names)
  - **Wildcard placeholders**: `__ANY__`, `__ANYSTR__`, `__ANYINT__`, `__ANYBOOL__`
  - **Repeat markers**: `{# @repeat-start #}` / `{# @repeat-end #}`
  - **Optional markers**: `{# @optional-start #}` / `{# @optional-end #}`
- This allows detection of archetypes even when specific values differ (e.g., `grafana_data` vs `alloy_data`)
- Calculates **containment ratio**: what percentage of each archetype structure is found within the template
- Reports usage status: **exact** (≥95%), **high** (≥70%), **partial** (≥30%), or **none** (<30%)
- Provides coverage metrics: (exact + high matches) / total archetypes

### Advanced Pattern Matching in Archetypes

Archetypes support special annotations for flexible pattern matching:

**Wildcard Placeholders** (match any value):
- `__ANY__` - Matches anything
- `__ANYSTR__` - Matches any string
- `__ANYINT__` - Matches any integer
- `__ANYBOOL__` - Matches any boolean

**Repeat Markers** (pattern can appear 1+ times):
```yaml
{# @repeat-start #}
  pattern
{# @repeat-end #}
```

**Optional Markers** (section may or may not exist):
```yaml
{# @optional-start #}
  pattern
{# @optional-end #}
```

**Example:**
```yaml
volumes:
  {# @repeat-start #}
  __ANY__:
    driver: local
  {# @repeat-end #}
```
Matches any number of volumes with `driver: local`

**Usage:**
```bash
# Validate all templates in library - shows summary table
python3 -m archetypes compose validate

# Validate specific template - shows detailed archetype breakdown
python3 -m archetypes compose validate whoami

# Validate templates in custom location
python3 -m archetypes compose validate --library /path/to/templates
```

**Output:**
- **Summary mode** (all templates): Table showing exact/high/partial/none counts and coverage % per template
- **Detail mode** (single template): Table showing each archetype's status, similarity %, and matching file

**Use cases:**
- **Quality assurance**: Ensure templates follow established patterns
- **Refactoring**: Identify templates that could benefit from archetype alignment
- **Documentation**: Track which archetypes are most/least used across templates

### Template Development Workflow

1. **Discover**: Use `list` command to see available archetype components for your module
2. **Review**: Preview archetypes to understand implementation patterns
3. **Copy**: Copy relevant archetype components to your template directory
4. **Customize**: Modify as needed (hardcode image, add custom labels, etc.)
5. **Validate**: Use `compose validate` to check Jinja2 syntax and semantic correctness

### Architecture

**Key Concepts:**
- Each module can have its own `archetypes/<module>/` directory with reusable components
- `archetypes.yaml` configures schema version and variable overrides for testing
- Components are modular Jinja2 files that can be tested in isolation or composition
- **Testing only**: The `generate` command NEVER writes files - always shows preview output

**How it works:**
- Loads module spec based on schema version from `archetypes.yaml`
- Merges variable sources: module spec → archetypes.yaml → CLI --var
- Renders using Jinja2 with support for `{% include %}` directives


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed
- Dependency update: typer to v0.21.1 (#1627)

### Fixed
- Python 3.10+ requirement enforcement - Install script now validates Python version and provides clear upgrade instructions for AlmaLinux/RHEL 9 users
- Python 3.9 compatibility - Added `from __future__ import annotations` to compose module to support union type syntax on Python 3.9+ (requires Python 3.10+ at runtime)

## [0.1.3] - 2026-01-06

### Added
- Draft template visibility in list command (#1610) - Draft templates now shown with dimmed styling and status indicator instead of being hidden

### Changed
- Schema deprecation postponed (#1636) - Template-only variable definitions deferred to maintain backward compatibility and reduce migration complexity

## [0.1.2] - 2025-12-11

### Fixed
- Nix flake missing `email-validator` dependency causing build failures (#1573)

## [0.1.0] - 2025-12-10

### Added
- Variable file support with `--var-file` flag (#1331) - Load variables from YAML file for non-interactive deployments
- Variable override support for `show` command with `--var` and `--var-file` flags (#1421) - Preview variable overrides before generating
- Terraform template support (#1422) - Manage Terraform configurations with schema 1.0
- Kubernetes template support (#1423) - Manage Kubernetes configurations with schema 1.0
- Helm template support (#1424) - Manage Helm charts with schema 1.0
- Ansible template support (#1426) - Manage Ansible playbooks with schema 1.0
- Packer template support (#1427) - Manage Packer templates with schema 1.0
- Alphabetically sorted commands in help output with grouped panels for better organization
- Separate help panels for "Template Commands" and "Configuration Commands"
- Compose Schema 1.2: Port variables (http, https, ssh, dns, dhcp, smtp) - Templates only prompt for ports they use
- Compose Schema 1.2: Dedicated `volume` section for storage configuration (replaces swarm_volume_* variables)
- Compose Schema 1.2: `resources` section for CPU and memory limits
- Compose Schema 1.2: `traefik_domain` variable for base domain configuration (#1362) - Set once, use across all services
- Compose Schema 1.2: `database_host` now requires `database_external=true`
- Compose Schema 1.2: `email_encryption` replaces `email_tls` and `email_ssl` with options: none, ssl, tls
- Markdown formatting support for template descriptions and next steps (#1471)
- Output directory flag `--output`/`-o` for `generate` command (#1534) - Replaces positional directory argument
- Variable property `autogenerated_length` to specify custom length for auto-generated values (default: 32 characters)
- Nerd Font icon support with shortcode replacement in template descriptions - Rich visual feedback using standardized icon system

### Changed
- Schema is now managed in JSON for better standardization and clarity (#1555)
- Compose Schema 1.2: Removed `traefik_entrypoint` and `traefik_tls_entrypoint` variables
- Removed Jinja2 `| default()` filter extraction and merging (#1410) - All defaults must now be defined in template/module specs
- Refactored code quality (#1364) for all core modules from single files to package structure with specific submodules
- Improved debug logging to capture module discovery and registration during initialization
- Enhanced debug logging for better troubleshooting
- Simplified dry-run output to show only essential information (files, sizes, status)
- Traefik template now uses module spec variable `authentik_traefik_middleware` instead of template-specific `traefik_authentik_middleware_name`
- `validate` command now accepts template ID as positional argument (e.g., `compose validate netbox`) - Consistent with archetypes command pattern
- Sections can't be required anymore, only variables can be required - Simplifies logic and improves usability
- Variables are now optional by default - only explicitly marked `required: true` variables are required, display shows `(*)` indicator instead of `(required)`

### Deprecated
- Positional directory argument for `generate` command (#1534) - Use `--output`/`-o` flag instead (will be removed in v0.2.0)

### Fixed
- CLI --var flag now properly converts boolean and numeric strings to appropriate Python types (#1522)
- Empty template files are no longer created during generation (#1518)
- Enhanced user confirmation flow for template generation (#1428)

## [0.0.7] - 2025-10-28

### Added
- Multiple Library Support (#1314) for git and local libraries
- Multi-Schema Module Support and Backward Compatibility (Schema-1.0)
- Schema-1.1 `network_mode` with options: bridge, host, macvlan
- Schema-1.1 `swarm` module support
- Variable-level and Section-level depenendencies `needs` with multiple values support
- Optional Variables `optional: true` to allow empty/None values
- PEP 8 formatting alignment
- CLI variable dependency validation - raises error when CLI-provided variables have unsatisfied dependencies
- Support for required variables independent of section state (#1355)
  - Variables can now be marked with `required: true` in template specs
  - Required variables are always prompted, validated, and included in rendering
  - Display shows yellow `(required)` indicator for required variables
  - Required variables from disabled sections are still collected and available

### Changed
- Schema-1.1 Unified Docker Swarm Placement (#1359) - Simplified swarm placement constraints into a single variable
- Refactored compose module from single file to package structure
- Dependency validation moved to `validate_all()` for better error reporting
- Schema-1.1 removed `network_enabled`, `ports_enabled` and `database_enabled` toggles (no longer optional)
- Improved error handling and display output consistency
- Updated dependency PyYAML to v6.0.3 (Python 3.14 compatibility)
- Updated dependency rich to v14.2.0 (Python 3.14 compatibility)
- Pinned all dependencies to specific tested versions for consistent installations

### Fixed
- Required sections now ignore toggle and are always enabled
- Module spec loading based on correct template schema version
- Interactive prompts now skip all variables (including required) when parent section is disabled
- Absolute paths without leading slash treated as relative paths in generate command (#1357)
  - Paths like `Users/xcad/Projects/test` are now correctly normalized to `/Users/xcad/Projects/test`
  - Supports common Unix/Linux root directories: Users/, home/, usr/, opt/, var/, tmp/
- Repository fetch fails when library directory already exists (#1279)
- **Critical:** Python 3.9 compatibility - removed Context type annotations causing RuntimeError
- Context access now uses click.get_current_context() for better compatibility

## [0.0.6] - 2025-10-14

### Changed
- Pinned all dependencies to specific tested versions for consistent installations
  - typer==0.19.2
  - rich==14.1.0
  - PyYAML==6.0.2
  - python-frontmatter==1.1.0
  - Jinja2==3.1.6

### Fixed
- **Critical:** Python 3.9 compatibility - removed Context type annotations causing RuntimeError
- Context access now uses click.get_current_context() for better compatibility
- Added tests directory to .gitignore

## [0.0.4] - 2025-01-XX

Initial public release with core CLI functionality.

[unreleased]: https://github.com/christianlempa/boilerplates/compare/v0.1.2...HEAD
[0.1.2]: https://github.com/christianlempa/boilerplates/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/christianlempa/boilerplates/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/christianlempa/boilerplates/compare/v0.0.7...v0.1.0
[0.0.7]: https://github.com/christianlempa/boilerplates/compare/v0.0.6...v0.0.7
[0.0.6]: https://github.com/christianlempa/boilerplates/releases/tag/v0.0.6
[0.0.4]: https://github.com/christianlempa/boilerplates/releases/tag/v0.0.4


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Boilerplates

Thank you for your interest in contributing to the Boilerplates project! This document provides guidelines and instructions for contributing.

## Table of Contents

- [Code of Conduct](#code-of-conduct)
- [How to Contribute](#how-to-contribute)
- [CLI Development](#cli-development)
- [Template Contributions](#template-contributions)
- [Development Setup](#development-setup)
- [Code Standards](#code-standards)
- [Testing Guidelines](#testing-guidelines)
- [Pull Request Process](#pull-request-process)

## Code of Conduct

Be respectful and constructive in all interactions. We're here to build great tools together.

## How to Contribute

### CLI Development

**IMPORTANT:** Any changes to the CLI application (`cli/` directory) require coordination.

**Before making CLI changes:**
1. Join the [Discord server](https://christianlempa.de/discord)
2. Reach out to discuss your proposed changes
3. Wait for approval before opening a PR

**Rationale:** The CLI architecture is complex and tightly integrated. Coordinating changes ensures consistency and prevents conflicts.

### Template Contributions

Template contributions are welcome and encouraged! You can:
- Add new templates to `library/`
- Improve existing templates
- Fix bugs in templates
- Update template documentation

**Process:**
1. Read the [Developer Documentation](../../wiki/Developers) in the Wiki
2. Create a new branch: `feature/###-template-name` or `problem/###-fix-description`
3. Add or modify templates following the structure in `library/`
4. Test your template thoroughly
5. Open a pull request

**No prior approval needed** for template contributions, but feel free to open an issue first to discuss larger changes.

## Development Setup

### Prerequisites

- Python 3.10 or higher
- Git
- pipx (recommended) or pip

### Installation

1. Clone the repository:
```bash
git clone https://github.com/ChristianLempa/boilerplates.git
cd boilerplates
```

2. Create a virtual environment:
```bash
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
```

3. Install dependencies:
```bash
pip install -e .
```

4. Run the CLI in development mode:
```bash
python3 -m cli --help
```

### Development Commands

```bash
# Run CLI with debug logging
python3 -m cli --log-level DEBUG compose list

# Test template generation
python3 -m cli compose generate template-name --dry-run

# Validate templates
python3 -m cli compose validate
```

## Code Standards

### Python Style Guide

- Follow PEP 8 conventions
- Use **2-space indentation** (project standard)
- Maximum line length: 100 characters
- Use type hints where appropriate

### Naming Conventions

- **Files:** lowercase with underscores (`variable_display.py`)
- **Classes:** PascalCase (`VariableCollection`, `DisplayManager`)
- **Functions/Methods:** snake_case (`render_template`, `get_spec`)
- **Constants:** UPPER_SNAKE_CASE (`DEFAULT_TIMEOUT`, `MAX_RETRIES`)
- **Private methods:** prefix with underscore (`_parse_section`)

### Comment Anchors

Use standardized comment anchors for important notes:

```python
# TODO: Implement feature X
# FIXME: Bug in validation logic
# NOTE: This is a workaround for issue #123
# LINK: https://docs.python.org/3/library/typing.html
```

### DisplayManager Usage

**CRITICAL RULE:**
- NEVER use `console.print()` outside of display manager classes
- NEVER import `Console` from `rich.console` except in display manager classes
- ALWAYS use `display.display_*()` methods for ALL output

```python
# GOOD
display = DisplayManager()
display.display_success("Template generated successfully")

# BAD
from rich.console import Console
console = Console()
console.print("Template generated")  # Don't do this!
```

### Docstrings

Use docstrings for all public classes and methods:

```python
def render_template(self, template: Template, template_id: str) -> None:
  """Render a complete template display.
  
  Args:
    template: The Template object to render
    template_id: The template identifier
  """
  pass
```

## Testing Guidelines

### Linting and Formatting

**REQUIRED before committing:**

```bash
# YAML files
yamllint library/

# Python code - check and auto-fix
ruff check --fix .

# Python code - format
ruff format .
```

### Validation Commands

```bash
# Validate all templates
python3 -m cli compose validate

# Validate specific template
python3 -m cli compose validate template-name

# Validate with semantic checks
python3 -m cli compose validate --semantic
```

### Manual Testing

Before submitting a PR, test your changes:

```bash
# Test template generation
python3 -m cli compose generate your-template --dry-run

# Test interactive mode
python3 -m cli compose generate your-template

# Test non-interactive mode
python3 -m cli compose generate your-template output-dir \
  --var service_name=test \
  --no-interactive
```

## Pull Request Process

### Branch Naming

- **Features:** `feature/###-description` (e.g., `feature/1234-add-nginx-template`)
- **Bug fixes:** `problem/###-description` (e.g., `problem/1235-fix-validation`)

### Commit Messages

Follow the format: `type(scope): subject`

**Types:**
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation changes
- `refactor`: Code refactoring
- `test`: Adding tests
- `chore`: Maintenance tasks

**Examples:**
```
feat(compose): add nginx template
fix(display): correct variable rendering for enum types
docs(wiki): update installation instructions
refactor(template): simplify Jinja2 rendering logic
```

### PR Checklist

Before submitting a pull request:

- [ ] Code follows style guidelines (run `ruff check` and `ruff format`)
- [ ] YAML files pass `yamllint`
- [ ] All templates validate successfully
- [ ] Changes are tested manually
- [ ] Commit messages follow conventions
- [ ] PR description explains the changes
- [ ] Related issues are referenced (e.g., "Closes #1234")

### PR Review

- PRs require approval before merging
- Address review comments promptly
- Keep PRs focused and reasonably sized
- Squash commits if requested

## Issue Labels

When creating issues, use appropriate labels:

- `feature` - New feature requests
- `problem` - Bug reports
- `discussion` - General discussions
- `question` - Questions about usage
- `documentation` - Documentation improvements

## Getting Help

- Check the [Wiki](../../wiki) for documentation
- Join [Discord](https://christianlempa.de/discord) for discussions
- Open an issue for bugs or feature requests
- Watch [YouTube tutorials](https://www.youtube.com/@christianlempa)

## License

By contributing, you agree that your contributions will be licensed under the same license as the project.

Thank you for contributing to Boilerplates!


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Christian

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: MANIFEST.in
================================================
# Include library directory with all templates
recursive-include library *

# Include JSON schema files
recursive-include cli/core/schema *.json

# Include documentation
include README.md
include LICENSE

# Exclude unnecessary files
global-exclude *.pyc
global-exclude __pycache__
global-exclude .DS_Store


================================================
FILE: README.md
================================================
# Christian's `Boilerplates`

[![Welcome](https://cnd-prod-1.s3.us-west-004.backblazeb2.com/new-banner4-scaled-for-github.jpg)](https://youtu.be/apgp9egIKK8)

**Hey, there!**

**I'm Christian, and I'm passionate about creating educational tech content for IT Pros and Homelab nerds.**

## What are Boilerplates?

**Boilerplates** is a curated collection of production-ready templates for your homelab and infrastructure projects. Stop copying configurations from random GitHub repos or starting from scratch every time you spin up a new service!

## Boilerplates CLI

The Boilerplates CLI tool gives you instant access to battle-tested templates for Docker, Terraform, Ansible, Kubernetes, and more.

Each template includes sensible defaults, best practices, and common configuration patterns—so you can focus on customizing for your environment.

**Key Features:**
- 🚀 **Quick Setup** - Generate complete project structures in seconds
- 🔧 **Fully Customizable** - Interactive prompts or non-interactive mode with variable overrides
- 💾 **Smart Defaults** - Save your preferred values and reuse across projects

> **Note:** Technologies evolve rapidly. While I actively maintain these templates, always review generated configurations before deploying to production.

### Requirements

- **Python 3.10 or higher** is required
- Git
- pipx (automatically installed by the installer script)

> **Note for RHEL/AlmaLinux/Rocky Linux 9 users:** These distributions ship with Python 3.9 by default. You need to install Python 3.11 or later:
> ```bash
> sudo dnf install python3.11
> pipx reinstall --python python3.11 boilerplates
> ```

### Installation

#### Automated installer script

Install the Boilerplates CLI using the automated installer:

```bash
# Install latest version
curl -fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh | bash

# Install specific version
curl -fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh | bash -s -- --version v1.2.3
```

The installer uses `pipx` to create an isolated environment for the CLI tool. Once installed, the `boilerplates` command will be available in your terminal.

#### Nixos

If you are using nix flakes

```bash
# Run without installing
nix run github:christianlempa/boilerplates -- --help

# Install to your profile
nix profile install github:christianlempa/boilerplates

# Or directly in your flake
{
  inputs.boilerplates.url = "github:christianlempa/boilerplates";

  outputs = { self, nixpkgs, boilerplates }: {
    # Use boilerplates.packages.${system}.default
  };
}

# Use in a temporary shell
nix shell github:christianlempa/boilerplates
```

### Quick Start

```bash
# Explore
boilerplates --help

# Update Repository Library
boilerplates repo update

# List all available templates for a docker compose
boilerplates compose list

# Show details about a specific template
boilerplates compose show nginx

# Generate a template (interactive mode)
boilerplates compose generate authentik

# Generate with custom output directory
boilerplates compose generate nginx my-nginx-server

# Non-interactive mode with variable overrides
boilerplates compose generate traefik my-proxy \
  --var service_name=traefik \
  --var traefik_enabled=true \
  --var traefik_host=proxy.example.com \
  --no-interactive
```

### Managing Defaults

Save time by setting default values for variables you use frequently:

```bash
# Set a default value
boilerplates compose defaults set container_timezone="America/New_York"
boilerplates compose defaults set restart_policy="unless-stopped"

```

### Template Libraries

Boilerplates uses git-based libraries to manage templates. You can add custom repositories:

```bash
# List configured libraries
boilerplates repo list

# Update all libraries
boilerplates repo update

# Add a custom library
boilerplates repo add my-templates https://github.com/user/templates \
  --directory library \
  --branch main

# Remove a library
boilerplates repo remove my-templates
```

## Documentation

For comprehensive documentation, advanced usage, and template development guides, check out the **[Wiki](../../wiki)** _(coming soon)_.

If you're looking for detailed tutorials on specific tools and technologies, visit my [YouTube Channel](https://www.youtube.com/@christianlempa).

## Contribution

If you’d like to contribute to this project, reach out to me on social media or [Discord](https://christianlempa.de/discord), or create a pull request for the necessary changes.

## Other Resources

- [Dotfiles](https://github.com/christianlempa/dotfiles) - My personal configuration files on macOS
- [Cheat-Sheets](https://github.com/christianlempa/cheat-sheets) - Command Reference for various tools and technologies

## Support me

Creating high-quality videos and valuable resources that are accessible to everyone, free of charge, is a huge challenge. With your contribution, I can dedicate more time and effort into the creation process, which ultimately enhances the quality of the content. So, all your support, by becoming a member, truly makes a significant impact on what I do. And you’ll also get some cool benefits and perks in return, as a recognition of your support.

Remember, ***supporting me is entirely optional.*** Your choice to become a member or not won't change your access to my videos and resources. You are also welcome to reach out to me on Discord, if you have any questions or feedback.

[https://www.patreon.com/christianlempa](https://www.patreon.com/christianlempa)

---

## Legacy Templates (v1)

Looking for templates from my older videos? The original boilerplates collection is still available in the [`backup/boilerplates-v1`](https://github.com/ChristianLempa/boilerplates/tree/backup/boilerplates-v1) branch. These templates haven't been migrated to the new CLI tool yet, but you can still access and use them directly from that branch.


================================================
FILE: SECURITY.md
================================================
# Security Policy

I take the security of my projects seriously. If you discover any security vulnerabilities or have concerns regarding the security of this repository, please reach out to me immediately. I appreciate your efforts in responsibly disclosing the issue and will make every effort to address it promptly.

## Reporting a Vulnerability

To report a security vulnerability, please follow these steps:

1. Go to the **Security** tab of this repository on GitHub.
2. Click on **"Report a vulnerability"**.
3. Provide a clear description of the vulnerability and its potential impact. Be as detailed as possible.
4. If applicable, include steps or a PoC (Proof of Concept) to reproduce the vulnerability.
5. Submit the report.

Once I receive the private report notification, I will promptly investigate and assess the reported vulnerability.

Please do not disclose any potential vulnerabilities in public repositories, issue trackers, or forums until we have had a chance to review and address the issue.

## Scope

This security policy applies to all the code and files within this repository and its dependencies actively maintained by me. If you encounter a security issue in a dependency that is not directly maintained by me, please follow responsible disclosure practices and report it to the respective project.

While I strive to ensure the security of this project, please note that as an individual developer, there may be limitations on resources, response times, and mitigations.

Thank you for your help in making this project more secure.


================================================
FILE: cli/__init__.py
================================================
"""
Boilerplates CLI - A sophisticated command-line tool for managing infrastructure boilerplates.
"""

__version__ = "0.1.3"
__author__ = "Christian Lempa"
__description__ = "CLI tool for managing infrastructure boilerplates"


================================================
FILE: cli/__main__.py
================================================
#!/usr/bin/env python3
"""
Main entry point for the Boilerplates CLI application.
This file serves as the primary executable when running the CLI.
"""

from __future__ import annotations

import importlib
import logging
import pkgutil
import sys
from pathlib import Path

import click
from rich.console import Console
from typer import Option, Typer
from typer.core import TyperGroup

import cli.modules
from cli import __version__
from cli.core import repo
from cli.core.display import DisplayManager
from cli.core.registry import registry


class OrderedGroup(TyperGroup):
    """Typer Group that lists commands in alphabetical order."""

    def list_commands(self, ctx: click.Context) -> list[str]:
        return sorted(super().list_commands(ctx))


app = Typer(
    help=(
        "CLI tool for managing infrastructure boilerplates.\n\n"
        "[dim]Easily generate, customize, and deploy templates for Docker Compose, "
        "Terraform, Kubernetes, and more.\n\n "
        "[white]Made with 💜 by [bold]Christian Lempa[/bold]"
    ),
    add_completion=True,
    rich_markup_mode="rich",
    pretty_exceptions_enable=False,
    no_args_is_help=True,
    cls=OrderedGroup,
)
console = Console()
display = DisplayManager()


def setup_logging(log_level: str = "WARNING") -> None:
    """Configure the logging system with the specified log level.

    Args:
        log_level: The logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)

    Raises:
        ValueError: If the log level is invalid
        RuntimeError: If logging configuration fails
    """
    numeric_level = getattr(logging, log_level.upper(), None)
    if not isinstance(numeric_level, int):
        raise ValueError(f"Invalid log level '{log_level}'. Valid levels: DEBUG, INFO, WARNING, ERROR, CRITICAL")

    try:
        logging.basicConfig(
            level=numeric_level,
            format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )

        logger = logging.getLogger(__name__)
        logger.setLevel(numeric_level)
    except Exception as e:
        raise RuntimeError(f"Failed to configure logging: {e}") from e


@app.callback(invoke_without_command=True)
def main(
    _version: bool | None = Option(
        None,
        "--version",
        "-v",
        help="Show the application version and exit.",
        is_flag=True,
        callback=lambda v: console.print(f"boilerplates version {__version__}") or sys.exit(0) if v else None,
        is_eager=True,
    ),
    log_level: str | None = Option(
        None,
        "--log-level",
        help=("Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL). If omitted, logging is disabled."),
    ),
) -> None:
    """CLI tool for managing infrastructure boilerplates."""
    # Disable logging by default; only enable when user provides --log-level
    if log_level:
        # Re-enable logging and configure
        logging.disable(logging.NOTSET)
        setup_logging(log_level)
    else:
        # Silence all logging (including third-party) unless user explicitly requests it
        logging.disable(logging.CRITICAL)

    # Get context without type annotation (compatible with all Typer versions)
    ctx = click.get_current_context()

    # Store log level in context for potential use by other commands
    ctx.ensure_object(dict)
    ctx.obj["log_level"] = log_level

    # If no subcommand is provided, show help and friendly intro
    if ctx.invoked_subcommand is None:
        console.print(ctx.get_help())
        sys.exit(0)


def _import_modules(modules_path: Path, logger: logging.Logger) -> list[str]:
    """Import all modules and return list of failures."""
    failed_imports = []
    for _finder, name, ispkg in pkgutil.iter_modules([str(modules_path)]):
        if not name.startswith("_") and name != "base":
            try:
                logger.debug(f"Importing module: {name} ({'package' if ispkg else 'file'})")
                importlib.import_module(f"cli.modules.{name}")
            except ImportError as e:
                error_info = f"Import failed for '{name}': {e!s}"
                failed_imports.append(error_info)
                logger.warning(error_info)
            except Exception as e:
                error_info = f"Unexpected error importing '{name}': {e!s}"
                failed_imports.append(error_info)
                logger.error(error_info)
    return failed_imports


def _register_repo_command(logger: logging.Logger) -> list[str]:
    """Register repo command and return list of failures."""
    failed = []
    try:
        logger.debug("Registering repo command")
        repo.register_cli(app)
    except Exception as e:
        error_info = f"Repo command registration failed: {e!s}"
        failed.append(error_info)
        logger.warning(error_info)
    return failed


def _register_module_classes(logger: logging.Logger) -> tuple[list, list[str]]:
    """Register template-based modules and return (module_classes, failures)."""
    failed_registrations = []
    module_classes = list(registry.iter_module_classes())
    logger.debug(f"Registering {len(module_classes)} template-based modules")

    for _name, module_cls in module_classes:
        try:
            logger.debug(f"Registering module class: {module_cls.__name__}")
            module_cls.register_cli(app)
        except Exception as e:
            error_info = f"Registration failed for '{module_cls.__name__}': {e!s}"
            failed_registrations.append(error_info)
            logger.warning(error_info)
            display.warning(error_info)

    return module_classes, failed_registrations


def _build_error_details(failed_imports: list[str], failed_registrations: list[str]) -> str:
    """Build detailed error message from failures."""
    error_details = []
    if failed_imports:
        error_details.extend(["Import failures:"] + [f"  - {err}" for err in failed_imports])
    if failed_registrations:
        error_details.extend(["Registration failures:"] + [f"  - {err}" for err in failed_registrations])
    return "\n".join(error_details) if error_details else ""


def init_app() -> None:
    """Initialize the application by discovering and registering modules.

    Raises:
        ImportError: If critical module import operations fail
        RuntimeError: If application initialization fails
    """
    logger = logging.getLogger(__name__)
    failed_imports = []
    failed_registrations = []

    try:
        # Auto-discover and import all modules
        modules_path = Path(cli.modules.__file__).parent
        logger.debug(f"Discovering modules in {modules_path}")
        failed_imports = _import_modules(modules_path, logger)

        # Register core repo command
        repo_failures = _register_repo_command(logger)

        # Register template-based modules
        module_classes, failed_registrations = _register_module_classes(logger)
        failed_registrations.extend(repo_failures)

        # Validate we have modules
        if not module_classes and not failed_imports:
            raise RuntimeError("No modules found to register")

        # Log summary
        successful_modules = len(module_classes) - len(failed_registrations)
        logger.info(f"Application initialized: {successful_modules} modules registered successfully")
        if failed_imports:
            logger.info(f"Module import failures: {len(failed_imports)}")
        if failed_registrations:
            logger.info(f"Module registration failures: {len(failed_registrations)}")

    except Exception as e:
        details = _build_error_details(failed_imports, failed_registrations) or str(e)
        raise RuntimeError(f"Application initialization failed: {details}") from e


def run() -> None:
    """Run the CLI application."""
    # Configure logging early if --log-level is provided
    if "--log-level" in sys.argv:
        try:
            log_level_index = sys.argv.index("--log-level") + 1
            if log_level_index < len(sys.argv):
                log_level = sys.argv[log_level_index]
                logging.disable(logging.NOTSET)
                setup_logging(log_level)
        except (ValueError, IndexError):
            pass  # Let Typer handle argument parsing errors

    try:
        init_app()
        app()
    except (ValueError, RuntimeError) as e:
        # Handle configuration and initialization errors cleanly
        display.error(str(e))
        sys.exit(1)
    except ImportError as e:
        # Handle module import errors with detailed info
        display.error(f"Module Import Error: {e}")
        sys.exit(1)
    except KeyboardInterrupt:
        # Handle Ctrl+C gracefully
        display.warning("Operation cancelled by user")
        sys.exit(130)
    except Exception as e:
        # Handle unexpected errors - show simplified message
        display.error(str(e))
        display.info("Use --log-level DEBUG for more details")
        sys.exit(1)


if __name__ == "__main__":
    run()


================================================
FILE: cli/core/config/__init__.py
================================================
"""Config package for configuration management.

This package provides the ConfigManager class for managing application configuration,
including defaults, preferences, and library configurations.
"""

from .config_manager import ConfigManager, LibraryConfig

__all__ = ["ConfigManager", "LibraryConfig"]


================================================
FILE: cli/core/config/config_manager.py
================================================
from __future__ import annotations

import logging
import shutil
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import yaml

from ..exceptions import ConfigError, ConfigValidationError, YAMLParseError

logger = logging.getLogger(__name__)


@dataclass
class LibraryConfig:
    """Configuration for a template library."""

    name: str
    library_type: str = "git"
    url: str | None = None
    directory: str | None = None
    branch: str = "main"
    path: str | None = None
    enabled: bool = True


class ConfigManager:
    """Manages configuration for the CLI application."""

    def __init__(self, config_path: str | Path | None = None) -> None:
        """Initialize the configuration manager.

        Args:
            config_path: Path to the configuration file. If None, auto-detects:
                        1. Checks for ./config.yaml (local project config)
                        2. Falls back to ~/.config/boilerplates/config.yaml (global config)
        """
        if config_path is None:
            # Check for local config.yaml in current directory first
            local_config = Path.cwd() / "config.yaml"
            if local_config.exists() and local_config.is_file():
                self.config_path = local_config
                self.is_local = True
                logger.debug(f"Using local config: {local_config}")
            else:
                # Fall back to global config
                config_dir = Path.home() / ".config" / "boilerplates"
                config_dir.mkdir(parents=True, exist_ok=True)
                self.config_path = config_dir / "config.yaml"
                self.is_local = False
        else:
            self.config_path = Path(config_path)
            self.is_local = False

        # Create default config if it doesn't exist (only for global config)
        if not self.config_path.exists():
            if not self.is_local:
                self._create_default_config()
            else:
                raise ConfigError(f"Local config file not found: {self.config_path}")
        else:
            # Migrate existing config if needed
            self._migrate_config_if_needed()

    def _create_default_config(self) -> None:
        """Create a default configuration file."""
        default_config = {
            "defaults": {},
            "preferences": {"editor": "vim", "output_dir": None, "library_paths": []},
            "libraries": [
                {
                    "name": "default",
                    "type": "git",
                    "url": "https://github.com/christianlempa/boilerplates.git",
                    "branch": "main",
                    "directory": "library",
                    "enabled": True,
                }
            ],
        }
        self._write_config(default_config)
        logger.info(f"Created default configuration at {self.config_path}")

    def _migrate_config_if_needed(self) -> None:
        """Migrate existing config to add missing sections and library types."""
        try:
            config = self._read_config()
            needs_migration = False

            # Add libraries section if missing
            if "libraries" not in config:
                logger.info("Migrating config: adding libraries section")
                config["libraries"] = [
                    {
                        "name": "default",
                        "type": "git",
                        "url": "https://github.com/christianlempa/boilerplates.git",
                        "branch": "refactor/boilerplates-v2",
                        "directory": "library",
                        "enabled": True,
                    }
                ]
                needs_migration = True
            else:
                # Migrate existing libraries to add 'type' field if missing
                # For backward compatibility, assume all old libraries without
                # 'type' are git libraries
                libraries = config.get("libraries", [])
                for library in libraries:
                    if "type" not in library:
                        lib_name = library.get("name", "unknown")
                        logger.info(f"Migrating library '{lib_name}': adding type: git")
                        library["type"] = "git"
                        needs_migration = True

            # Write back if migration was needed
            if needs_migration:
                self._write_config(config)
                logger.info("Config migration completed successfully")
        except Exception as e:
            logger.warning(f"Config migration failed: {e}")

    def _read_config(self) -> dict[str, Any]:
        """Read configuration from file.

        Returns:
            Dictionary containing the configuration.

        Raises:
            YAMLParseError: If YAML parsing fails.
            ConfigValidationError: If configuration structure is invalid.
            ConfigError: If reading fails for other reasons.
        """
        try:
            with self.config_path.open() as f:
                config = yaml.safe_load(f) or {}

            # Validate config structure
            self._validate_config_structure(config)

            return config
        except yaml.YAMLError as e:
            logger.error(f"Failed to parse YAML configuration: {e}")
            raise YAMLParseError(str(self.config_path), e) from e
        except ConfigValidationError:
            # Re-raise validation errors as-is
            raise
        except OSError as e:
            logger.error(f"Failed to read configuration file: {e}")
            raise ConfigError(f"Failed to read configuration file '{self.config_path}': {e}") from e

    def _write_config(self, config: dict[str, Any]) -> None:
        """Write configuration to file atomically using temp file + rename pattern.

        This prevents config file corruption if write operation fails partway through.

        Args:
            config: Dictionary containing the configuration to write.

        Raises:
            ConfigValidationError: If configuration structure is invalid.
            ConfigError: If writing fails for any reason.
        """
        tmp_path = None
        try:
            # Validate config structure before writing
            self._validate_config_structure(config)

            # Ensure parent directory exists
            self.config_path.parent.mkdir(parents=True, exist_ok=True)

            # Write to temporary file in same directory for atomic rename
            with tempfile.NamedTemporaryFile(
                mode="w",
                delete=False,
                dir=self.config_path.parent,
                prefix=".config_",
                suffix=".tmp",
            ) as tmp_file:
                yaml.dump(config, tmp_file, default_flow_style=False)
                tmp_path = tmp_file.name

            # Atomic rename (overwrites existing file on POSIX systems)
            shutil.move(tmp_path, self.config_path)
            logger.debug(f"Configuration written atomically to {self.config_path}")

        except ConfigValidationError:
            # Re-raise validation errors as-is
            if tmp_path:
                Path(tmp_path).unlink(missing_ok=True)
            raise
        except (OSError, yaml.YAMLError) as e:
            # Clean up temp file if it exists
            if tmp_path:
                try:
                    Path(tmp_path).unlink(missing_ok=True)
                except OSError:
                    logger.warning(f"Failed to clean up temporary file: {tmp_path}")
            logger.error(f"Failed to write configuration file: {e}")
            raise ConfigError(f"Failed to write configuration to '{self.config_path}': {e}") from e

    def _validate_config_structure(self, config: dict[str, Any]) -> None:
        """Validate the configuration structure - basic type checking.

        Args:
            config: Configuration dictionary to validate.

        Raises:
            ConfigValidationError: If configuration structure is invalid.
        """
        if not isinstance(config, dict):
            raise ConfigValidationError("Configuration must be a dictionary")

        # Validate top-level types
        self._validate_top_level_types(config)

        # Validate defaults structure
        self._validate_defaults_types(config)

        # Validate libraries structure
        self._validate_libraries_fields(config)

    def _validate_top_level_types(self, config: dict[str, Any]) -> None:
        """Validate top-level config section types."""
        if "defaults" in config and not isinstance(config["defaults"], dict):
            raise ConfigValidationError("'defaults' must be a dictionary")

        if "preferences" in config and not isinstance(config["preferences"], dict):
            raise ConfigValidationError("'preferences' must be a dictionary")

        if "libraries" in config and not isinstance(config["libraries"], list):
            raise ConfigValidationError("'libraries' must be a list")

    def _validate_defaults_types(self, config: dict[str, Any]) -> None:
        """Validate defaults section has correct types."""
        if "defaults" not in config:
            return

        for module_name, module_defaults in config["defaults"].items():
            if not isinstance(module_defaults, dict):
                raise ConfigValidationError(f"Defaults for module '{module_name}' must be a dictionary")

    def _validate_libraries_fields(self, config: dict[str, Any]) -> None:
        """Validate libraries have required fields."""
        if "libraries" not in config:
            return

        for i, library in enumerate(config["libraries"]):
            if not isinstance(library, dict):
                raise ConfigValidationError(f"Library at index {i} must be a dictionary")

            if "name" not in library:
                raise ConfigValidationError(f"Library at index {i} missing required field 'name'")

            lib_type = library.get("type", "git")
            if lib_type == "git" and ("url" not in library or "directory" not in library):
                raise ConfigValidationError(
                    f"Git library at index {i} missing required fields 'url' and/or 'directory'"
                )
            if lib_type == "static" and "path" not in library:
                raise ConfigValidationError(f"Static library at index {i} missing required field 'path'")

    def get_config_path(self) -> Path:
        """Get the path to the configuration file being used.

        Returns:
            Path to the configuration file (global or local).
        """
        return self.config_path

    def is_using_local_config(self) -> bool:
        """Check if a local configuration file is being used.

        Returns:
            True if using local config, False if using global config.
        """
        return self.is_local

    def get_defaults(self, module_name: str) -> dict[str, Any]:
        """Get default variable values for a module.

        Returns defaults in a flat format:
        {
            "var_name": "value",
            "var2_name": "value2"
        }

        Args:
            module_name: Name of the module

        Returns:
            Dictionary of default values (flat key-value pairs)
        """
        config = self._read_config()
        defaults = config.get("defaults", {})
        return defaults.get(module_name, {})

    def set_defaults(self, module_name: str, defaults: dict[str, Any]) -> None:
        """Set default variable values for a module with comprehensive validation.

        Args:
            module_name: Name of the module
            defaults: Dictionary of defaults (flat key-value pairs):
                      {"var_name": "value", "var2_name": "value2"}

        Raises:
            ConfigValidationError: If module name or variable names are invalid.
        """
        # Basic validation
        if not isinstance(module_name, str) or not module_name:
            raise ConfigValidationError("Module name must be a non-empty string")

        if not isinstance(defaults, dict):
            raise ConfigValidationError("Defaults must be a dictionary")

        config = self._read_config()

        if "defaults" not in config:
            config["defaults"] = {}

        config["defaults"][module_name] = defaults
        self._write_config(config)
        logger.info(f"Updated defaults for module '{module_name}'")

    def set_default_value(self, module_name: str, var_name: str, value: Any) -> None:
        """Set a single default variable value with comprehensive validation.

        Args:
            module_name: Name of the module
            var_name: Name of the variable
            value: Default value to set

        Raises:
            ConfigValidationError: If module name or variable name is invalid.
        """
        # Basic validation
        if not isinstance(module_name, str) or not module_name:
            raise ConfigValidationError("Module name must be a non-empty string")

        if not isinstance(var_name, str) or not var_name:
            raise ConfigValidationError("Variable name must be a non-empty string")

        defaults = self.get_defaults(module_name)
        defaults[var_name] = value
        self.set_defaults(module_name, defaults)
        logger.info(f"Set default for '{module_name}.{var_name}' = '{value}'")

    def get_default_value(self, module_name: str, var_name: str) -> Any | None:
        """Get a single default variable value.

        Args:
            module_name: Name of the module
            var_name: Name of the variable

        Returns:
            Default value or None if not set
        """
        defaults = self.get_defaults(module_name)
        return defaults.get(var_name)

    def clear_defaults(self, module_name: str) -> None:
        """Clear all defaults for a module.

        Args:
            module_name: Name of the module
        """
        config = self._read_config()

        if "defaults" in config and module_name in config["defaults"]:
            del config["defaults"][module_name]
            self._write_config(config)
            logger.info(f"Cleared defaults for module '{module_name}'")

    def get_preference(self, key: str) -> Any | None:
        """Get a user preference value.

        Args:
            key: Preference key (e.g., 'editor', 'output_dir', 'library_paths')

        Returns:
            Preference value or None if not set
        """
        config = self._read_config()
        preferences = config.get("preferences", {})
        return preferences.get(key)

    def set_preference(self, key: str, value: Any) -> None:
        """Set a user preference value with comprehensive validation.

        Args:
            key: Preference key
            value: Preference value

        Raises:
            ConfigValidationError: If key or value is invalid for known preference types.
        """
        # Basic validation
        if not isinstance(key, str) or not key:
            raise ConfigValidationError("Preference key must be a non-empty string")

        config = self._read_config()

        if "preferences" not in config:
            config["preferences"] = {}

        config["preferences"][key] = value
        self._write_config(config)
        logger.info(f"Set preference '{key}' = '{value}'")

    def get_all_preferences(self) -> dict[str, Any]:
        """Get all user preferences.

        Returns:
            Dictionary of all preferences
        """
        config = self._read_config()
        return config.get("preferences", {})

    def get_libraries(self) -> list[dict[str, Any]]:
        """Get all configured libraries.

        Returns:
            List of library configurations
        """
        config = self._read_config()
        return config.get("libraries", [])

    def get_library_by_name(self, name: str) -> dict[str, Any] | None:
        """Get a specific library by name.

        Args:
            name: Name of the library

        Returns:
            Library configuration dictionary or None if not found
        """
        libraries = self.get_libraries()
        for library in libraries:
            if library.get("name") == name:
                return library
        return None

    def add_library(self, lib_config: LibraryConfig) -> None:
        """Add a new library to the configuration.

        Args:
            lib_config: Library configuration

        Raises:
            ConfigValidationError: If library with the same name already exists or validation fails
        """
        # Basic validation
        if not isinstance(lib_config.name, str) or not lib_config.name:
            raise ConfigValidationError("Library name must be a non-empty string")

        if lib_config.library_type not in ("git", "static"):
            raise ConfigValidationError(f"Library type must be 'git' or 'static', got '{lib_config.library_type}'")

        if self.get_library_by_name(lib_config.name):
            raise ConfigValidationError(f"Library '{lib_config.name}' already exists")

        # Type-specific validation
        if lib_config.library_type == "git":
            if not lib_config.url or not lib_config.directory:
                raise ConfigValidationError("Git libraries require 'url' and 'directory' parameters")

            library_dict = {
                "name": lib_config.name,
                "type": "git",
                "url": lib_config.url,
                "branch": lib_config.branch,
                "directory": lib_config.directory,
                "enabled": lib_config.enabled,
            }

        else:  # static
            if not lib_config.path:
                raise ConfigValidationError("Static libraries require 'path' parameter")

            # For backward compatibility with older CLI versions,
            # add dummy values for git-specific fields
            library_dict = {
                "name": lib_config.name,
                "type": "static",
                "url": "",  # Empty string for backward compatibility
                "branch": "main",  # Default value for backward compatibility
                "directory": ".",  # Default value for backward compatibility
                "path": lib_config.path,
                "enabled": lib_config.enabled,
            }

        config = self._read_config()

        if "libraries" not in config:
            config["libraries"] = []

        config["libraries"].append(library_dict)

        self._write_config(config)
        logger.info(f"Added {lib_config.library_type} library '{lib_config.name}'")

    def remove_library(self, name: str) -> None:
        """Remove a library from the configuration.

        Args:
            name: Name of the library to remove

        Raises:
            ConfigError: If library is not found
        """
        config = self._read_config()
        libraries = config.get("libraries", [])

        # Find and remove the library
        new_libraries = [lib for lib in libraries if lib.get("name") != name]

        if len(new_libraries) == len(libraries):
            raise ConfigError(f"Library '{name}' not found")

        config["libraries"] = new_libraries
        self._write_config(config)
        logger.info(f"Removed library '{name}'")

    def update_library(self, name: str, **kwargs: Any) -> None:
        """Update a library's configuration.

        Args:
            name: Name of the library to update
            **kwargs: Fields to update (url, branch, directory, enabled)

        Raises:
            ConfigError: If library is not found
            ConfigValidationError: If validation fails
        """
        config = self._read_config()
        libraries = config.get("libraries", [])

        # Find the library
        library_found = False
        for library in libraries:
            if library.get("name") == name:
                library_found = True

                # Update allowed fields
                if "url" in kwargs:
                    library["url"] = kwargs["url"]

                if "branch" in kwargs:
                    library["branch"] = kwargs["branch"]

                if "directory" in kwargs:
                    library["directory"] = kwargs["directory"]

                if "enabled" in kwargs:
                    library["enabled"] = kwargs["enabled"]

                break

        if not library_found:
            raise ConfigError(f"Library '{name}' not found")

        config["libraries"] = libraries
        self._write_config(config)
        logger.info(f"Updated library '{name}'")

    def get_libraries_path(self) -> Path:
        """Get the path to the libraries directory.

        Returns:
            Path to the libraries directory (same directory as config file)
        """
        return self.config_path.parent / "libraries"


================================================
FILE: cli/core/display/__init__.py
================================================
"""Display module for CLI output rendering.

This package provides centralized display management with mixin-based architecture.
DisplayManager inherits from multiple mixins to provide a flat, cohesive API.
"""

from __future__ import annotations

from rich.console import Console

from .display_base import BaseDisplay
from .display_icons import IconManager
from .display_settings import DisplaySettings
from .display_status import StatusDisplay
from .display_table import TableDisplay
from .display_template import TemplateDisplay
from .display_variable import VariableDisplay

# Console instances for stdout and stderr
console = Console()
console_err = Console(stderr=True)


class DisplayManager:
    """Main display coordinator using composition.

    This class composes specialized display components to provide a unified API.
    Each component handles a specific concern (status, tables, templates, variables).

    Design Principles:
    - Composition over inheritance
    - Explicit dependencies
    - Clear separation of concerns
    - Easy to test and extend
    """

    def __init__(self, quiet: bool = False, settings: DisplaySettings | None = None):
        """Initialize DisplayManager with composed display components.

        Args:
            quiet: If True, suppress all non-error output
            settings: Optional DisplaySettings instance for customization
        """
        self.quiet = quiet
        self.settings = settings or DisplaySettings()

        # Create base display component (includes utilities)
        self.base = BaseDisplay(self.settings, quiet)

        # Create specialized display components
        self.status = StatusDisplay(self.settings, quiet, self.base)
        self.variables = VariableDisplay(self.settings, self.base)
        self.templates = TemplateDisplay(self.settings, self.base, self.variables, self.status)
        self.tables = TableDisplay(self.settings, self.base)

    # ===== Delegate to base display =====
    def text(self, text: str, style: str | None = None) -> None:
        """Display plain text."""
        return self.base.text(text, style)

    def heading(self, text: str, style: str | None = None) -> None:
        """Display a heading."""
        return self.base.heading(text, style)

    def section(self, title: str, description: str | None = None) -> None:
        """Display a section header with optional description.

        Args:
            title: Section title
            description: Optional section description
        """
        self.base.text("")
        self.base.text(f"[bold cyan]{title}[/bold cyan]")
        if description:
            self.base.text(f"[dim]{description}[/dim]")

    def table(
        self,
        headers: list[str] | None = None,
        rows: list[tuple] | None = None,
        title: str | None = None,
        show_header: bool = True,
        borderless: bool = False,
    ) -> None:
        """Display a table."""
        return self.base.table(headers, rows, title, show_header, borderless)

    def tree(self, root_label: str, nodes: dict | list) -> None:
        """Display a tree."""
        return self.base.tree(root_label, nodes)

    def code(self, code_text: str, language: str | None = None) -> None:
        """Display code."""
        return self.base.code(code_text, language)

    def progress(self, *columns):
        """Create a progress bar."""
        return self.base.progress(*columns)

    def file_tree(
        self,
        root_label: str,
        files: list,
        file_info_fn: callable,
        title: str | None = None,
    ) -> None:
        """Display a file tree structure."""
        return self.base.file_tree(root_label, files, file_info_fn, title)

    # ===== Formatting utilities =====
    def truncate(self, value: str, max_length: int | None = None) -> str:
        """Truncate string value."""
        return self.base.truncate(value, max_length)

    def format_file_size(self, size_bytes: int) -> str:
        """Format file size in human-readable format."""
        return self.base.format_file_size(size_bytes)

    def data_table(
        self,
        columns: list[dict],
        rows: list,
        title: str | None = None,
        row_formatter: callable | None = None,
    ) -> None:
        """Display a data table with configurable columns."""
        return self.tables.data_table(columns, rows, title, row_formatter)

    def display_status_table(
        self,
        title: str,
        rows: list[tuple[str, str, bool]],
        columns: tuple[str, str] = ("Item", "Status"),
    ) -> None:
        """Display a status table with success/error indicators."""
        return self.tables.render_status_table(title, rows, columns)

    # ===== Delegate to status display =====
    def error(self, message: str, context: str | None = None, details: str | None = None) -> None:
        """Display an error message."""
        return self.status.error(message, context, details)

    def warning(self, message: str, context: str | None = None, details: str | None = None) -> None:
        """Display a warning message."""
        return self.status.warning(message, context, details)

    def success(self, message: str, context: str | None = None) -> None:
        """Display a success message."""
        return self.status.success(message, context)

    def info(self, message: str, context: str | None = None) -> None:
        """Display an info message."""
        return self.status.info(message, context)

    def skipped(self, message: str, reason: str | None = None) -> None:
        """Display skipped message."""
        return self.status.skipped(message, reason)

    # ===== Helper methods =====
    def get_lock_icon(self) -> str:
        """Get lock icon."""
        return self.base.get_lock_icon()

    def print_table(self, table) -> None:
        """Print a pre-built Rich Table object.

        Args:
            table: Rich Table object to print
        """
        return self.base._print_table(table)


# Export public API
__all__ = [
    "DisplayManager",
    "DisplaySettings",
    "IconManager",
    "console",
    "console_err",
]


================================================
FILE: cli/core/display/display_base.py
================================================
"""Base display methods for DisplayManager."""

from __future__ import annotations

import logging
from pathlib import Path

from rich.console import Console
from rich.progress import Progress
from rich.syntax import Syntax
from rich.table import Table
from rich.tree import Tree

from .display_icons import IconManager
from .display_settings import DisplaySettings

logger = logging.getLogger(__name__)
console = Console()


class BaseDisplay:
    """Base display methods and utilities.

    Provides fundamental display methods (text, heading, table, tree, code, progress)
    and utility/helper methods for formatting.
    """

    def __init__(self, settings: DisplaySettings, quiet: bool = False):
        """Initialize BaseDisplay.

        Args:
            settings: Display settings for formatting
            quiet: If True, suppress non-error output
        """
        self.settings = settings
        self.quiet = quiet

    def heading(self, text: str, style: str | None = None) -> None:
        """Display a standardized heading.

        Args:
            text: Heading text
            style: Optional style override (defaults to STYLE_HEADER from settings)
        """
        if style is None:
            style = self.settings.STYLE_HEADER
        console.print(f"[{style}]{text}[/{style}]")
        console.print("")  # Add newline after heading

    def text(self, text: str, style: str | None = None) -> None:
        """Display plain text with optional styling.

        Args:
            text: Text to display
            style: Optional Rich style markup
        """
        if style:
            console.print(f"[{style}]{text}[/{style}]")
        else:
            console.print(text)

    def table(
        self,
        headers: list[str] | None = None,
        rows: list[tuple] | None = None,
        title: str | None = None,
        show_header: bool = True,
        borderless: bool = False,
    ) -> None:
        """Display a standardized table.

        Args:
            headers: Column headers (if None, no headers)
            rows: List of tuples, one per row
            title: Optional table title
            show_header: Whether to show header row
            borderless: If True, use borderless style (box=None)
        """
        table = Table(
            title=title,
            show_header=show_header and headers is not None,
            header_style=self.settings.STYLE_TABLE_HEADER,
            box=None,
            padding=self.settings.PADDING_TABLE_NORMAL if borderless else (0, 1),
        )

        # Add columns
        if headers:
            for header in headers:
                table.add_column(header)
        elif rows and len(rows) > 0:
            # No headers, but need columns for data
            for _ in range(len(rows[0])):
                table.add_column()

        # Add rows
        if rows:
            for row in rows:
                table.add_row(*[str(cell) for cell in row])

        console.print(table)

    def tree(self, root_label: str, nodes: dict | list | Tree) -> None:
        """Display a tree structure.

        Args:
            root_label: Label for the root node
            nodes: Hierarchical structure (dict, list, or pre-built Tree)
        """
        if isinstance(nodes, Tree):
            console.print(nodes)
        else:
            tree = Tree(root_label)
            self._build_tree_nodes(tree, nodes)
            console.print(tree)

    def _build_tree_nodes(self, parent, nodes):
        """Recursively build tree nodes.

        Args:
            parent: Parent tree node
            nodes: Dict or list of child nodes
        """
        if isinstance(nodes, dict):
            for key, value in nodes.items():
                if isinstance(value, (dict, list)):
                    branch = parent.add(str(key))
                    self._build_tree_nodes(branch, value)
                else:
                    parent.add(f"{key}: {value}")
        elif isinstance(nodes, list):
            for item in nodes:
                if isinstance(item, (dict, list)):
                    self._build_tree_nodes(parent, item)
                else:
                    parent.add(str(item))

    def _print_tree(self, tree) -> None:
        """Print a pre-built Rich Tree object.

        Arg
Download .txt
gitextract_wuyffx3g/

├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   ├── issue_template.md
│   ├── pull_request_template.md
│   ├── scripts/
│   │   ├── generate_wiki_docs.py
│   │   └── sync-template-version.sh
│   └── workflows/
│       ├── codequality-ruff.yaml
│       ├── codequality-yamllint.yaml
│       ├── docs-update-wiki.yaml
│       ├── release-create-cli-release.yaml
│       └── renovate-sync-versions.yaml
├── .gitignore
├── .wiki/
│   ├── Core-Concepts-Defaults.md
│   ├── Core-Concepts-Libraries.md
│   ├── Core-Concepts-Templates.md
│   ├── Core-Concepts-Variables.md
│   ├── Getting-Started.md
│   ├── Home.md
│   ├── Installation.md
│   ├── Variables-Ansible.md
│   ├── Variables-Compose.md
│   ├── Variables-Helm.md
│   ├── Variables-Kubernetes.md
│   ├── Variables-Packer.md
│   ├── Variables-Terraform.md
│   ├── Variables.md
│   └── _Sidebar.md
├── .yamllint
├── AGENTS.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── SECURITY.md
├── cli/
│   ├── __init__.py
│   ├── __main__.py
│   ├── core/
│   │   ├── config/
│   │   │   ├── __init__.py
│   │   │   └── config_manager.py
│   │   ├── display/
│   │   │   ├── __init__.py
│   │   │   ├── display_base.py
│   │   │   ├── display_icons.py
│   │   │   ├── display_settings.py
│   │   │   ├── display_status.py
│   │   │   ├── display_table.py
│   │   │   ├── display_template.py
│   │   │   └── display_variable.py
│   │   ├── exceptions.py
│   │   ├── input/
│   │   │   ├── __init__.py
│   │   │   ├── input_manager.py
│   │   │   ├── input_settings.py
│   │   │   └── prompt_manager.py
│   │   ├── library.py
│   │   ├── module/
│   │   │   ├── __init__.py
│   │   │   ├── base_commands.py
│   │   │   ├── base_module.py
│   │   │   ├── config_commands.py
│   │   │   └── helpers.py
│   │   ├── prompt.py
│   │   ├── registry.py
│   │   ├── repo.py
│   │   ├── template/
│   │   │   ├── __init__.py
│   │   │   ├── template.py
│   │   │   ├── variable.py
│   │   │   ├── variable_collection.py
│   │   │   └── variable_section.py
│   │   ├── validators.py
│   │   └── version.py
│   └── modules/
│       ├── __init__.py
│       ├── ansible/
│       │   └── __init__.py
│       ├── compose/
│       │   ├── __init__.py
│       │   └── validate.py
│       ├── helm/
│       │   └── __init__.py
│       ├── kubernetes/
│       │   └── __init__.py
│       ├── packer/
│       │   └── __init__.py
│       └── terraform/
│           └── __init__.py
├── flake.nix
├── library/
│   ├── ansible/
│   │   ├── checkmk-install-agent/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── checkmk-manage-host/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── docker-certs/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── docker-certs-enable/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── docker-install-ubuntu/
│   │   │   ├── main.yml.j2
│   │   │   └── template.yaml
│   │   ├── docker-prune/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── ubuntu-add-sshkey/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   ├── ubuntu-apt-update/
│   │   │   ├── playbook.yaml.j2
│   │   │   └── template.yaml
│   │   └── ubuntu-vm-core/
│   │       ├── playbook.yaml.j2
│   │       └── template.yaml
│   ├── compose/
│   │   ├── adguardhome/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── alloy/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   ├── common.alloy.j2
│   │   │   │   ├── logs_docker.alloy.j2
│   │   │   │   ├── logs_system.alloy.j2
│   │   │   │   ├── metrics_docker.alloy.j2
│   │   │   │   ├── metrics_system.alloy.j2
│   │   │   │   └── targets.alloy.j2
│   │   │   └── template.yaml
│   │   ├── authentik/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── bind9/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   ├── named.conf.j2
│   │   │   │   ├── named.conf.zones.j2
│   │   │   │   └── tsig.key.j2
│   │   │   └── template.yaml
│   │   ├── checkmk/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── dockge/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── gitea/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── gitlab/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── gitlab.rb.j2
│   │   │   └── template.yaml
│   │   ├── gitlab-runner/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── config.toml
│   │   │   └── template.yaml
│   │   ├── grafana/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── homeassistant/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── homepage/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── homer/
│   │   │   ├── assets/
│   │   │   │   └── config.yml.j2
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── influxdb/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── komodo/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── loki/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── config.yaml.j2
│   │   │   └── template.yaml
│   │   ├── mariadb/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── n8n/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── netbox/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── nextcloud/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── nginx/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── default.conf
│   │   │   ├── data/
│   │   │   │   └── index.html
│   │   │   └── template.yaml
│   │   ├── openwebui/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── passbolt/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── pihole/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── portainer/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── postgres/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── compose.yaml.j2.final
│   │   │   └── template.yaml
│   │   ├── prometheus/
│   │   │   ├── compose.yaml.j2
│   │   │   ├── config/
│   │   │   │   └── prometheus.yaml
│   │   │   └── template.yaml
│   │   ├── renovate/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── semaphoreui/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── traefik/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── twingate-connector/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   ├── uptimekuma/
│   │   │   ├── compose.yaml.j2
│   │   │   └── template.yaml
│   │   └── whoami/
│   │       ├── compose.yaml.j2
│   │       └── template.yaml
│   ├── helm/
│   │   ├── authentik/
│   │   │   ├── secrets.yaml.j2
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   ├── certmanager/
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   ├── longhorn/
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   ├── netbox/
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   ├── portainer/
│   │   │   ├── template.yaml
│   │   │   └── values.yaml.j2
│   │   └── traefik/
│   │       ├── template.yaml
│   │       └── values.yaml.j2
│   ├── kubernetes/
│   │   ├── certmanager-certificate/
│   │   │   ├── certificate.yaml.j2
│   │   │   └── template.yaml
│   │   ├── certmanager-clusterissuer/
│   │   │   ├── clusterissuer.yaml.j2
│   │   │   └── template.yaml
│   │   ├── certmanager-issuer/
│   │   │   ├── issuer.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-configmap/
│   │   │   ├── configmap.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-ingress/
│   │   │   ├── ingress.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-ingressclass/
│   │   │   ├── ingressclass.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-persistentvolume/
│   │   │   ├── pv.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-persistentvolumeclaim/
│   │   │   ├── pvc.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-secret/
│   │   │   └── template.yaml
│   │   ├── core-service/
│   │   │   ├── service.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-serviceaccount/
│   │   │   ├── serviceaccount.yaml.j2
│   │   │   └── template.yaml
│   │   ├── core-storageclass/
│   │   │   ├── storageclass.yaml.j2
│   │   │   └── template.yaml
│   │   ├── traefik-ingressroute/
│   │   │   ├── ingressroute.yaml.j2
│   │   │   └── template.yaml
│   │   ├── traefik-ingressroutetcp/
│   │   │   ├── ingressroutetcp.yaml.j2
│   │   │   └── template.yaml
│   │   ├── traefik-middleware/
│   │   │   ├── middleware.yaml.j2
│   │   │   └── template.yaml
│   │   └── twingate-connector/
│   │       ├── connector.yaml.j2
│   │       └── template.yaml
│   ├── packer/
│   │   └── proxmox-iso-ubuntu/
│   │       ├── files/
│   │       │   └── 99-pve.cfg
│   │       ├── http/
│   │       │   ├── meta-data
│   │       │   └── user-data.j2
│   │       ├── proxmox-iso-ubuntu.pkr.hcl.j2
│   │       ├── template.yaml
│   │       └── variables.pkrvars.hcl.example
│   └── terraform/
│       ├── cloudflare-dns-record/
│       │   ├── cloudflare_dns_record.tf.j2
│       │   ├── cloudflare_zone.tf.j2
│       │   └── template.yaml
│       ├── cloudflare-ztna-application/
│       │   ├── cloudflare_account_zone.tf.j2
│       │   ├── cloudflare_zero_trust_access_application.tf.j2
│       │   ├── cloudflare_zero_trust_access_policy.tf.j2
│       │   └── template.yaml
│       ├── dns-a-record/
│       │   ├── dns_a_record_set.tf.j2
│       │   └── template.yaml
│       └── netbox-vm/
│           ├── netbox_virtual_machine.tf.j2
│           └── template.yaml
├── pyproject.toml
├── renovate.json
├── requirements.txt
├── scripts/
│   └── install.sh
└── tests/
    ├── __init__.py
    ├── test_variable.py
    └── test_version.py
Download .txt
SYMBOL INDEX (471 symbols across 38 files)

FILE: .github/scripts/generate_wiki_docs.py
  function format_value (line 26) | def format_value(value):
  function generate_module_docs (line 37) | def generate_module_docs(module_name: str, output_dir: Path):  # noqa: P...
  function generate_variables_index (line 181) | def generate_variables_index(modules: list[str], output_dir: Path):
  function main (line 219) | def main():

FILE: cli/__main__.py
  class OrderedGroup (line 27) | class OrderedGroup(TyperGroup):
    method list_commands (line 30) | def list_commands(self, ctx: click.Context) -> list[str]:
  function setup_logging (line 51) | def setup_logging(log_level: str = "WARNING") -> None:
  function main (line 79) | def main(
  function _import_modules (line 118) | def _import_modules(modules_path: Path, logger: logging.Logger) -> list[...
  function _register_repo_command (line 137) | def _register_repo_command(logger: logging.Logger) -> list[str]:
  function _register_module_classes (line 150) | def _register_module_classes(logger: logging.Logger) -> tuple[list, list...
  function _build_error_details (line 169) | def _build_error_details(failed_imports: list[str], failed_registrations...
  function init_app (line 179) | def init_app() -> None:
  function run (line 220) | def run() -> None:

FILE: cli/core/config/config_manager.py
  class LibraryConfig (line 18) | class LibraryConfig:
  class ConfigManager (line 30) | class ConfigManager:
    method __init__ (line 33) | def __init__(self, config_path: str | Path | None = None) -> None:
    method _create_default_config (line 68) | def _create_default_config(self) -> None:
    method _migrate_config_if_needed (line 87) | def _migrate_config_if_needed(self) -> None:
    method _read_config (line 126) | def _read_config(self) -> dict[str, Any]:
    method _write_config (line 155) | def _write_config(self, config: dict[str, Any]) -> None:
    method _validate_config_structure (line 205) | def _validate_config_structure(self, config: dict[str, Any]) -> None:
    method _validate_top_level_types (line 226) | def _validate_top_level_types(self, config: dict[str, Any]) -> None:
    method _validate_defaults_types (line 237) | def _validate_defaults_types(self, config: dict[str, Any]) -> None:
    method _validate_libraries_fields (line 246) | def _validate_libraries_fields(self, config: dict[str, Any]) -> None:
    method get_config_path (line 266) | def get_config_path(self) -> Path:
    method is_using_local_config (line 274) | def is_using_local_config(self) -> bool:
    method get_defaults (line 282) | def get_defaults(self, module_name: str) -> dict[str, Any]:
    method set_defaults (line 301) | def set_defaults(self, module_name: str, defaults: dict[str, Any]) -> ...
    method set_default_value (line 328) | def set_default_value(self, module_name: str, var_name: str, value: An...
    method get_default_value (line 351) | def get_default_value(self, module_name: str, var_name: str) -> Any | ...
    method clear_defaults (line 364) | def clear_defaults(self, module_name: str) -> None:
    method get_preference (line 377) | def get_preference(self, key: str) -> Any | None:
    method set_preference (line 390) | def set_preference(self, key: str, value: Any) -> None:
    method get_all_preferences (line 413) | def get_all_preferences(self) -> dict[str, Any]:
    method get_libraries (line 422) | def get_libraries(self) -> list[dict[str, Any]]:
    method get_library_by_name (line 431) | def get_library_by_name(self, name: str) -> dict[str, Any] | None:
    method add_library (line 446) | def add_library(self, lib_config: LibraryConfig) -> None:
    method remove_library (line 505) | def remove_library(self, name: str) -> None:
    method update_library (line 527) | def update_library(self, name: str, **kwargs: Any) -> None:
    method get_libraries_path (line 569) | def get_libraries_path(self) -> Path:

FILE: cli/core/display/__init__.py
  class DisplayManager (line 24) | class DisplayManager:
    method __init__ (line 37) | def __init__(self, quiet: bool = False, settings: DisplaySettings | No...
    method text (line 57) | def text(self, text: str, style: str | None = None) -> None:
    method heading (line 61) | def heading(self, text: str, style: str | None = None) -> None:
    method section (line 65) | def section(self, title: str, description: str | None = None) -> None:
    method table (line 77) | def table(
    method tree (line 88) | def tree(self, root_label: str, nodes: dict | list) -> None:
    method code (line 92) | def code(self, code_text: str, language: str | None = None) -> None:
    method progress (line 96) | def progress(self, *columns):
    method file_tree (line 100) | def file_tree(
    method truncate (line 111) | def truncate(self, value: str, max_length: int | None = None) -> str:
    method format_file_size (line 115) | def format_file_size(self, size_bytes: int) -> str:
    method data_table (line 119) | def data_table(
    method display_status_table (line 129) | def display_status_table(
    method error (line 139) | def error(self, message: str, context: str | None = None, details: str...
    method warning (line 143) | def warning(self, message: str, context: str | None = None, details: s...
    method success (line 147) | def success(self, message: str, context: str | None = None) -> None:
    method info (line 151) | def info(self, message: str, context: str | None = None) -> None:
    method skipped (line 155) | def skipped(self, message: str, reason: str | None = None) -> None:
    method get_lock_icon (line 160) | def get_lock_icon(self) -> str:
    method print_table (line 164) | def print_table(self, table) -> None:

FILE: cli/core/display/display_base.py
  class BaseDisplay (line 21) | class BaseDisplay:
    method __init__ (line 28) | def __init__(self, settings: DisplaySettings, quiet: bool = False):
    method heading (line 38) | def heading(self, text: str, style: str | None = None) -> None:
    method text (line 50) | def text(self, text: str, style: str | None = None) -> None:
    method table (line 62) | def table(
    method tree (line 103) | def tree(self, root_label: str, nodes: dict | list | Tree) -> None:
    method _build_tree_nodes (line 117) | def _build_tree_nodes(self, parent, nodes):
    method _print_tree (line 138) | def _print_tree(self, tree) -> None:
    method _print_table (line 146) | def _print_table(self, table) -> None:
    method _print_markdown (line 158) | def _print_markdown(self, markdown) -> None:
    method code (line 166) | def code(self, code_text: str, language: str | None = None) -> None:
    method progress (line 180) | def progress(self, *columns):
    method truncate (line 201) | def truncate(self, value: str, max_length: int | None = None) -> str:
    method format_file_size (line 218) | def format_file_size(self, size_bytes: int) -> str:
    method file_tree (line 235) | def file_tree(
    method _get_icon_by_type (line 284) | def _get_icon_by_type(self, icon_type: str) -> str:
    method get_lock_icon (line 302) | def get_lock_icon(self) -> str:

FILE: cli/core/display/display_icons.py
  class IconManager (line 9) | class IconManager:
    method get_file_icon (line 84) | def get_file_icon(cls, file_path: str | Path) -> str:
    method get_status_icon (line 130) | def get_status_icon(cls, status: str) -> str:
    method folder (line 155) | def folder(cls) -> str:
    method config (line 160) | def config(cls) -> str:
    method lock (line 165) | def lock(cls) -> str:
    method arrow_right (line 170) | def arrow_right(cls) -> str:
    method replace_shortcodes (line 175) | def replace_shortcodes(cls, text: str) -> str:

FILE: cli/core/display/display_settings.py
  class DisplaySettings (line 4) | class DisplaySettings:

FILE: cli/core/display/display_status.py
  class LeftAlignedHeading (line 25) | class LeftAlignedHeading(Heading):
    method __rich_console__ (line 28) | def __rich_console__(self, console: Console, options: ConsoleOptions) ...
  class IconListItem (line 43) | class IconListItem(ListItem):
    method render_bullet (line 46) | def render_bullet(self, console: Console, options: ConsoleOptions) -> ...
  class LeftAlignedMarkdown (line 109) | class LeftAlignedMarkdown(Markdown):
    method __init__ (line 112) | def __init__(self, markup: str, **kwargs):
  class StatusDisplay (line 123) | class StatusDisplay:
    method __init__ (line 130) | def __init__(self, settings: DisplaySettings, quiet: bool, base: BaseD...
    method _display_message (line 142) | def _display_message(self, level: str, message: str, context: str | No...
    method error (line 199) | def error(self, message: str, context: str | None = None, details: str...
    method warning (line 223) | def warning(self, message: str, context: str | None = None, details: s...
    method success (line 247) | def success(self, message: str, context: str | None = None) -> None:
    method info (line 256) | def info(self, message: str, context: str | None = None) -> None:
    method skipped (line 265) | def skipped(self, message: str, reason: str | None = None) -> None:
    method markdown (line 277) | def markdown(self, content: str) -> None:

FILE: cli/core/display/display_table.py
  class TableDisplay (line 18) | class TableDisplay:
    method __init__ (line 24) | def __init__(self, settings: DisplaySettings, base: BaseDisplay):
    method data_table (line 34) | def data_table(
    method render_templates_table (line 77) | def render_templates_table(self, templates: list, module_name: str, _t...
    method render_status_table (line 116) | def render_status_table(
    method render_summary_table (line 140) | def render_summary_table(self, title: str, items: dict[str, str]) -> N...
    method render_file_operation_table (line 162) | def render_file_operation_table(self, files: list[tuple[str, int, str]...
    method _build_section_label (line 185) | def _build_section_label(
    method _build_variable_label (line 207) | def _build_variable_label(
    method _add_section_variables (line 233) | def _add_section_variables(self, section_node, section_vars: dict, sho...
    method render_config_tree (line 243) | def render_config_tree(self, spec: dict, module_name: str, show_all: b...

FILE: cli/core/display/display_template.py
  class TemplateDisplay (line 21) | class TemplateDisplay:
    method __init__ (line 28) | def __init__(
    method render_template (line 48) | def render_template(self, template: Template, template_id: str) -> None:
    method render_template_header (line 59) | def render_template_header(self, template: Template, template_id: str)...
    method render_file_tree (line 100) | def render_file_tree(self, template: Template) -> None:
    method render_file_generation_confirmation (line 124) | def render_file_generation_confirmation(

FILE: cli/core/display/display_variable.py
  class VariableDisplay (line 18) | class VariableDisplay:
    method __init__ (line 25) | def __init__(self, settings: DisplaySettings, base: BaseDisplay):
    method render_variable_value (line 35) | def render_variable_value(
    method _format_value (line 93) | def _format_value(self, variable, value, max_length: int | None = None...
    method render_section (line 114) | def render_section(self, title: str, description: str | None) -> None:
    method _render_section_header (line 134) | def _render_section_header(self, section, is_dimmed: bool) -> str:
    method _render_variable_row (line 153) | def _render_variable_row(self, var_name: str, variable, is_dimmed: boo...
    method render_variables_table (line 187) | def render_variables_table(self, template: Template) -> None:

FILE: cli/core/exceptions.py
  class BoilerplatesError (line 12) | class BoilerplatesError(Exception):
  class ConfigError (line 18) | class ConfigError(BoilerplatesError):
  class ConfigValidationError (line 24) | class ConfigValidationError(ConfigError):
  class TemplateError (line 30) | class TemplateError(BoilerplatesError):
  class TemplateNotFoundError (line 36) | class TemplateNotFoundError(TemplateError):
    method __init__ (line 39) | def __init__(self, template_id: str, module_name: str | None = None):
  class TemplateDraftError (line 48) | class TemplateDraftError(TemplateError):
    method __init__ (line 51) | def __init__(self, template_id: str, module_name: str | None = None):
  class DuplicateTemplateError (line 64) | class DuplicateTemplateError(TemplateError):
    method __init__ (line 67) | def __init__(self, template_id: str, library_name: str):
  class TemplateLoadError (line 76) | class TemplateLoadError(TemplateError):
  class TemplateSyntaxError (line 82) | class TemplateSyntaxError(TemplateError):
    method __init__ (line 85) | def __init__(self, template_id: str, errors: list[str]):
  class TemplateValidationError (line 92) | class TemplateValidationError(TemplateError):
  class IncompatibleSchemaVersionError (line 98) | class IncompatibleSchemaVersionError(TemplateError):
    method __init__ (line 101) | def __init__(
  class RenderErrorContext (line 123) | class RenderErrorContext:
  class TemplateRenderError (line 135) | class TemplateRenderError(TemplateError):
    method __init__ (line 138) | def __init__(self, message: str, context: RenderErrorContext | None = ...
  class VariableError (line 164) | class VariableError(BoilerplatesError):
  class VariableValidationError (line 170) | class VariableValidationError(VariableError):
    method __init__ (line 173) | def __init__(self, variable_name: str, message: str):
  class VariableTypeError (line 179) | class VariableTypeError(VariableError):
    method __init__ (line 182) | def __init__(self, variable_name: str, expected_type: str, actual_type...
  class LibraryError (line 190) | class LibraryError(BoilerplatesError):
  class ModuleError (line 196) | class ModuleError(BoilerplatesError):
  class ModuleNotFoundError (line 202) | class ModuleNotFoundError(ModuleError):
    method __init__ (line 205) | def __init__(self, module_name: str):
  class ModuleLoadError (line 211) | class ModuleLoadError(ModuleError):
  class SchemaError (line 217) | class SchemaError(BoilerplatesError):
    method __init__ (line 220) | def __init__(self, message: str, details: str | None = None):
  class FileOperationError (line 228) | class FileOperationError(BoilerplatesError):
  class RenderError (line 234) | class RenderError(BoilerplatesError):
  class YAMLParseError (line 240) | class YAMLParseError(BoilerplatesError):
    method __init__ (line 243) | def __init__(self, file_path: str, original_error: Exception):

FILE: cli/core/input/input_manager.py
  class InputManager (line 22) | class InputManager:
    method __init__ (line 29) | def __init__(self, settings: InputSettings | None = None):
    method text (line 37) | def text(
    method password (line 74) | def password(self, prompt: str, default: str | None = None) -> str:
    method confirm (line 91) | def confirm(self, prompt: str, default: bool | None = None) -> bool:
    method integer (line 110) | def integer(
    method choice (line 160) | def choice(self, prompt: str, choices: list[str], default: str | None ...
    method validate_email (line 191) | def validate_email(self, email: str) -> bool:
    method validate_url (line 203) | def validate_url(self, url: str) -> bool:
    method validate_hostname (line 215) | def validate_hostname(self, hostname: str) -> bool:

FILE: cli/core/input/input_settings.py
  class InputSettings (line 8) | class InputSettings:

FILE: cli/core/input/prompt_manager.py
  class PromptHandler (line 16) | class PromptHandler:
    method __init__ (line 19) | def __init__(self) -> None:
    method _handle_section_toggle (line 23) | def _handle_section_toggle(self, section, collected: dict[str, Any]) -...
    method _should_skip_variable (line 41) | def _should_skip_variable(
    method _collect_variable_value (line 62) | def _collect_variable_value(self, variable: Variable, collected: dict[...
    method collect_variables (line 74) | def collect_variables(self, variables: VariableCollection) -> dict[str...
    method _prompt_variable (line 106) | def _prompt_variable(self, variable: Variable, _required: bool = False...
    method _get_prompt_handler (line 163) | def _get_prompt_handler(self, variable: Variable) -> Callable:
    method _show_validation_error (line 182) | def _show_validation_error(self, message: str) -> None:
    method _prompt_string (line 186) | def _prompt_string(self, prompt_text: str, default: Any = None, is_sen...
    method _prompt_bool (line 196) | def _prompt_bool(self, prompt_text: str, default: Any = None) -> bool ...
    method _prompt_int (line 203) | def _prompt_int(self, prompt_text: str, default: Any = None) -> int | ...
    method _prompt_enum (line 212) | def _prompt_enum(

FILE: cli/core/library.py
  class Library (line 17) | class Library:
    method __init__ (line 20) | def __init__(self, name: str, path: Path, priority: int = 0, library_t...
    method _is_template_draft (line 37) | def _is_template_draft(self, template_path: Path) -> bool:
    method find_by_id (line 55) | def find_by_id(self, module_name: str, template_id: str) -> tuple[Path...
    method find (line 94) | def find(self, module_name: str, sort_results: bool = False) -> list[t...
  class LibraryManager (line 146) | class LibraryManager:
    method __init__ (line 149) | def __init__(self) -> None:
    method _resolve_git_library_path (line 154) | def _resolve_git_library_path(self, name: str, lib_config: dict, libra...
    method _resolve_static_library_path (line 162) | def _resolve_static_library_path(self, name: str, lib_config: dict) ->...
    method _warn_missing_library (line 174) | def _warn_missing_library(self, name: str, library_path: Path, lib_typ...
    method _load_libraries_from_config (line 183) | def _load_libraries_from_config(self) -> list[Library]:
    method find_by_id (line 235) | def find_by_id(self, module_name: str, template_id: str) -> tuple[Path...
    method find (line 286) | def find(self, module_name: str, sort_results: bool = False) -> list[t...

FILE: cli/core/module/base_commands.py
  class GenerationConfig (line 41) | class GenerationConfig:
  class ConfirmationContext (line 56) | class ConfirmationContext:
  function list_templates (line 68) | def list_templates(module_instance, raw: bool = False) -> list:
  function search_templates (line 141) | def search_templates(module_instance, query: str) -> list:
  function show_template (line 207) | def show_template(module_instance, id: str, var: list[str] | None = None...
  function check_output_directory (line 236) | def check_output_directory(
  function get_generation_confirmation (line 278) | def get_generation_confirmation(_ctx: ConfirmationContext) -> bool:
  function _collect_subdirectories (line 284) | def _collect_subdirectories(rendered_files: dict[str, str]) -> set[Path]:
  function _analyze_file_operations (line 294) | def _analyze_file_operations(
  function _format_size (line 320) | def _format_size(total_size: int) -> str:
  function execute_dry_run (line 329) | def execute_dry_run(
  function write_rendered_files (line 358) | def write_rendered_files(
  function _prepare_template (line 376) | def _prepare_template(
  function _render_template (line 398) | def _render_template(template, id: str, display: DisplayManager, interac...
  function _determine_output_dir (line 419) | def _determine_output_dir(directory: str | None, output: str | None, id:...
  function _display_template_error (line 446) | def _display_template_error(display: DisplayManager, template_id: str, e...
  function _display_generic_error (line 463) | def _display_generic_error(display: DisplayManager, template_id: str, er...
  function generate_template (line 479) | def generate_template(module_instance, config: GenerationConfig) -> None...
  function validate_templates (line 577) | def validate_templates(
  function _load_template_for_validation (line 594) | def _load_template_for_validation(module_instance, template_id: str, pat...
  function _validate_single_template (line 624) | def _validate_single_template(module_instance, template, template_id: st...
  function _run_semantic_validation (line 652) | def _run_semantic_validation(module_instance, template, verbose: bool) -...
  function _display_validation_details (line 679) | def _display_validation_details(module_instance, template, semantic: boo...
  function _validate_all_templates (line 689) | def _validate_all_templates(module_instance, verbose: bool) -> None:

FILE: cli/core/module/base_module.py
  class Module (line 36) | class Module(ABC):
    method __init__ (line 51) | def __init__(self) -> None:
    method _load_all_templates (line 61) | def _load_all_templates(self, filter_fn=None) -> list:
    method _load_template_by_id (line 96) | def _load_template_by_id(self, id: str):
    method list (line 124) | def list(
    method search (line 131) | def search(
    method show (line 138) | def show(
    method generate (line 161) | def generate(
    method validate (line 235) | def validate(
    method config_get (line 269) | def config_get(
    method config_set (line 276) | def config_set(
    method config_remove (line 284) | def config_remove(
    method config_clear (line 291) | def config_clear(
    method config_list (line 299) | def config_list(self) -> None:
    method register_cli (line 304) | def register_cli(cls, app: Typer) -> None:

FILE: cli/core/module/config_commands.py
  function config_get (line 15) | def config_get(module_instance, var_name: str | None = None) -> None:
  function config_set (line 40) | def config_set(module_instance, var_name: str, value: str | None = None)...
  function config_remove (line 67) | def config_remove(module_instance, var_name: str) -> None:
  function config_clear (line 84) | def config_clear(module_instance, var_name: str | None = None, force: bo...
  function config_list (line 125) | def config_list(module_instance) -> None:

FILE: cli/core/module/helpers.py
  function parse_var_inputs (line 19) | def parse_var_inputs(var_options: list[str], extra_args: list[str]) -> d...
  function _convert_string_to_type (line 56) | def _convert_string_to_type(value: str) -> Any:
  function load_var_file (line 87) | def load_var_file(var_file_path: str) -> dict:
  function apply_variable_defaults (line 125) | def apply_variable_defaults(template, config_manager, module_name: str) ...
  function apply_var_file (line 145) | def apply_var_file(template, var_file_path: str | None, display: Display...
  function apply_cli_overrides (line 184) | def apply_cli_overrides(template, var: list[str] | None, ctx=None) -> None:
  function collect_variable_values (line 212) | def collect_variable_values(template, interactive: bool) -> dict[str, Any]:

FILE: cli/core/prompt.py
  class PromptHandler (line 16) | class PromptHandler:
    method __init__ (line 19) | def __init__(self) -> None:
    method collect_variables (line 23) | def collect_variables(self, variables: VariableCollection) -> dict[str...
    method _check_section_dependencies (line 61) | def _check_section_dependencies(self, variables: VariableCollection, s...
    method _handle_section_toggle (line 78) | def _handle_section_toggle(self, section, collected: dict[str, Any]) -...
    method _collect_section_variables (line 99) | def _collect_section_variables(
    method _prompt_and_update_variable (line 126) | def _prompt_and_update_variable(self, variable: Variable, collected: d...
    method _prompt_variable (line 139) | def _prompt_variable(self, variable: Variable, _required: bool = False...
    method _get_prompt_handler (line 195) | def _get_prompt_handler(self, variable: Variable) -> Callable:
    method _show_validation_error (line 214) | def _show_validation_error(self, message: str) -> None:
    method _prompt_string (line 218) | def _prompt_string(self, prompt_text: str, default: Any = None, is_sen...
    method _prompt_bool (line 228) | def _prompt_bool(self, prompt_text: str, default: Any = None) -> bool ...
    method _prompt_int (line 234) | def _prompt_int(self, prompt_text: str, default: Any = None) -> int | ...
    method _prompt_enum (line 243) | def _prompt_enum(

FILE: cli/core/registry.py
  class ModuleRegistry (line 11) | class ModuleRegistry:
    method __init__ (line 14) | def __init__(self) -> None:
    method register (line 18) | def register(self, module_class: type) -> None:
    method iter_module_classes (line 30) | def iter_module_classes(self) -> Iterator[tuple[str, type]]:

FILE: cli/core/repo.py
  function _run_git_command (line 24) | def _run_git_command(args: list[str], cwd: Path | None = None) -> tuple[...
  function _clone_or_pull_repo (line 52) | def _clone_or_pull_repo(
  function _pull_repo_updates (line 76) | def _pull_repo_updates(name: str, target_path: Path, branch: str | None)...
  function _clone_new_repo (line 93) | def _clone_new_repo(
  function _clone_sparse_repo (line 107) | def _clone_sparse_repo(url: str, target_path: Path, branch: str | None, ...
  function _clone_full_repo (line 142) | def _clone_full_repo(name: str, url: str, target_path: Path, branch: str...
  function _process_library_update (line 158) | def _process_library_update(lib: dict, libraries_path: Path, progress, v...
  function _display_update_summary (line 193) | def _display_update_summary(results: list[tuple[str, str, bool]]) -> None:
  function update (line 211) | def update(
  function _get_library_path_for_git (line 250) | def _get_library_path_for_git(lib: dict, libraries_path: Path, name: str...
  function _get_library_path_for_static (line 259) | def _get_library_path_for_static(lib: dict, config: ConfigManager) -> Path:
  function _get_library_info (line 268) | def _get_library_info(lib: dict, config: ConfigManager, libraries_path: ...
  function list (line 314) | def list() -> None:
  function add (line 349) | def add(
  function remove (line 411) | def remove(
  function register_cli (line 438) | def register_cli(parent_app: Typer) -> None:

FILE: cli/core/template/template.py
  class TemplateErrorHandler (line 47) | class TemplateErrorHandler:
    method extract_error_context (line 57) | def extract_error_context(file_path: Path, line_number: int | None, co...
    method get_common_jinja_suggestions (line 89) | def get_common_jinja_suggestions(error_msg: str, available_vars: set) ...
    method parse_jinja_error (line 159) | def parse_jinja_error(
  class TemplateFile (line 210) | class TemplateFile:
  class TemplateMetadata (line 219) | class TemplateMetadata:
    method __init__ (line 234) | def __init__(
    method _validate_metadata (line 277) | def _validate_metadata(template_data: dict) -> None:
  class Template (line 299) | class Template:
    method __init__ (line 302) | def __init__(self, template_dir: Path, library_name: str, library_type...
    method set_qualified_id (line 367) | def set_qualified_id(self, library_name: str | None = None) -> None:
    method _find_main_template_file (line 377) | def _find_main_template_file(self) -> Path:
    method _warn_about_unused_variables (line 385) | def _warn_about_unused_variables(self, template_specs: dict) -> None:
    method _collect_template_files (line 416) | def _collect_template_files(self) -> None:
    method _extract_all_used_variables (line 446) | def _extract_all_used_variables(self) -> set[str]:
    method _filter_specs_to_used (line 486) | def _filter_specs_to_used(
    method _validate_kind (line 520) | def _validate_kind(template_data: dict) -> None:
    method _validate_variable_definitions (line 532) | def _validate_variable_definitions(self, used_variables: set[str], mer...
    method _create_jinja_env (line 579) | def _create_jinja_env(searchpath: Path) -> Environment:
    method _generate_autogenerated_values (line 597) | def _generate_autogenerated_values(self, variables: VariableCollection...
    method _log_render_start (line 627) | def _log_render_start(self, debug: bool, variable_values: dict) -> None:
    method _render_jinja2_file (line 636) | def _render_jinja2_file(self, template_file, variable_values: dict, _a...
    method _handle_jinja2_error (line 650) | def _handle_jinja2_error(
    method _render_static_file (line 675) | def _render_static_file(self, template_file, debug: bool) -> str:
    method render (line 696) | def render(self, variables: VariableCollection, debug: bool = False) -...
    method _sanitize_content (line 755) | def _sanitize_content(self, content: str, _file_path: Path) -> str:
    method template_files (line 775) | def template_files(self) -> list[TemplateFile]:
    method template_specs (line 781) | def template_specs(self) -> dict:
    method jinja_env (line 786) | def jinja_env(self) -> Environment:
    method used_variables (line 792) | def used_variables(self) -> set[str]:
    method variables (line 798) | def variables(self) -> VariableCollection:
    method status (line 816) | def status(self) -> str:

FILE: cli/core/template/variable.py
  class Variable (line 20) | class Variable:
    method __init__ (line 23) | def __init__(self, data: dict[str, Any]) -> None:
    method convert (line 96) | def convert(self, value: Any) -> Any:
    method validate_and_convert (line 127) | def validate_and_convert(self, value: Any, check_required: bool = True...
    method _convert_bool (line 171) | def _convert_bool(self, value: Any) -> bool:
    method _convert_int (line 183) | def _convert_int(self, value: Any) -> int | None:
    method _convert_float (line 194) | def _convert_float(self, value: Any) -> float | None:
    method _convert_enum (line 205) | def _convert_enum(self, value: Any) -> str | None:
    method _convert_url (line 213) | def _convert_url(self, value: Any) -> str:
    method _convert_email (line 222) | def _convert_email(self, value: Any) -> str:
    method to_dict (line 233) | def to_dict(self) -> dict[str, Any]:
    method get_display_value (line 272) | def get_display_value(self, mask_sensitive: bool = True, max_length: i...
    method get_normalized_default (line 302) | def get_normalized_default(self) -> Any:
    method get_prompt_text (line 333) | def get_prompt_text(self) -> str:
    method get_validation_hint (line 347) | def get_validation_hint(self) -> str | None:
    method is_required (line 365) | def is_required(self) -> bool:
    method get_parent (line 378) | def get_parent(self) -> VariableSection | None:
    method clone (line 386) | def clone(self, update: dict[str, Any] | None = None) -> Variable:

FILE: cli/core/template/variable_collection.py
  class VariableCollection (line 13) | class VariableCollection:
    method __init__ (line 16) | def __init__(self, spec: dict[str, Any]) -> None:
    method from_json (line 52) | def from_json(cls, json_spec: list[dict[str, Any]]) -> VariableCollect...
    method _validate_and_extract_section_key (line 100) | def _validate_and_extract_section_key(section_data: Any) -> str:
    method _build_section_dict (line 124) | def _build_section_dict(section_data: dict[str, Any]) -> dict[str, Any]:
    method _convert_vars_to_dict (line 143) | def _convert_vars_to_dict(section_data: dict[str, Any], section_key: s...
    method _initialize_sections (line 174) | def _initialize_sections(self, spec: dict[str, Any]) -> None:
    method _create_section (line 189) | def _create_section(self, key: str, data: dict[str, Any]) -> VariableS...
    method _initialize_variables (line 208) | def _initialize_variables(self, section: VariableSection, vars_data: d...
    method _validate_unique_variable_names (line 229) | def _validate_unique_variable_names(self) -> None:
    method _validate_section_toggle (line 251) | def _validate_section_toggle(self, section: VariableSection) -> None:
    method _parse_need (line 278) | def _parse_need(need_str: str) -> tuple[str, bool, Any | None]:
    method _is_need_satisfied (line 333) | def _is_need_satisfied(self, need_str: str) -> bool:
    method _check_section_need (line 371) | def _check_section_need(self, section_name: str) -> bool:
    method _check_variable_need (line 379) | def _check_variable_need(self, var_name: str, is_positive: bool, expec...
    method _matches_any_value (line 418) | def _matches_any_value(self, variable: Variable, actual_value: Any, ex...
    method _matches_single_value (line 426) | def _matches_single_value(self, variable: Variable, actual_value: Any,...
    method _values_match (line 431) | def _values_match(self, variable: Variable, actual: Any, expected: Any...
    method _validate_dependencies (line 437) | def _validate_dependencies(self) -> None:
    method is_section_satisfied (line 514) | def is_section_satisfied(self, section_key: str) -> bool:
    method is_variable_satisfied (line 543) | def is_variable_satisfied(self, var_name: str) -> bool:
    method reset_disabled_bool_variables (line 571) | def reset_disabled_bool_variables(self) -> list[str]:
    method sort_sections (line 624) | def sort_sections(self) -> None:
    method _topological_sort (line 667) | def _topological_sort(self) -> list[str]:
    method get_sections (line 705) | def get_sections(self) -> dict[str, VariableSection]:
    method get_section (line 709) | def get_section(self, key: str) -> VariableSection | None:
    method has_sections (line 713) | def has_sections(self) -> bool:
    method get_all_values (line 717) | def get_all_values(self) -> dict[str, Any]:
    method get_satisfied_values (line 722) | def get_satisfied_values(self) -> dict[str, Any]:
    method get_sensitive_variables (line 754) | def get_sensitive_variables(self) -> dict[str, Any]:
    method apply_defaults (line 758) | def apply_defaults(self, defaults: dict[str, Any], origin: str = "cli"...
    method validate_all (line 823) | def validate_all(self) -> None:
    method _validate_cli_bool_variables (line 844) | def _validate_cli_bool_variables(self, errors: list[str]) -> None:
    method _is_cli_bool_variable (line 864) | def _is_cli_bool_variable(self, variable: Variable) -> bool:
    method _collect_unmet_needs (line 868) | def _collect_unmet_needs(
    method _validate_section_variables (line 883) | def _validate_section_variables(self, errors: list[str]) -> None:
    method _validate_single_variable (line 901) | def _validate_single_variable(self, section, var_name: str, variable: ...
    method merge (line 933) | def merge(
    method _merge_sections (line 1003) | def _merge_sections(
    method filter_to_used (line 1063) | def filter_to_used(self, used_variables: set[str], keep_sensitive: boo...
    method get_all_variable_names (line 1118) | def get_all_variable_names(self) -> set[str]:

FILE: cli/core/template/variable_section.py
  class VariableSection (line 10) | class VariableSection:
    method __init__ (line 13) | def __init__(self, data: dict[str, Any]) -> None:
    method to_dict (line 50) | def to_dict(self) -> dict[str, Any]:
    method is_enabled (line 67) | def is_enabled(self) -> bool:
    method clone (line 85) | def clone(self, origin_update: str | None = None) -> VariableSection:
    method _build_dependency_graph (line 119) | def _build_dependency_graph(self, var_list: list[str]) -> dict[str, li...
    method _topological_sort (line 138) | def _topological_sort(self, var_list: list[str], dependencies: dict[st...
    method sort_variables (line 164) | def sort_variables(self, _is_need_satisfied_func=None) -> None:

FILE: cli/core/validators.py
  class ValidationResult (line 24) | class ValidationResult:
    method __init__ (line 27) | def __init__(self):
    method add_error (line 32) | def add_error(self, message: str) -> None:
    method add_warning (line 37) | def add_warning(self, message: str) -> None:
    method add_info (line 42) | def add_info(self, message: str) -> None:
    method is_valid (line 48) | def is_valid(self) -> bool:
    method has_warnings (line 53) | def has_warnings(self) -> bool:
    method display (line 57) | def display(self, context: str = "Validation") -> None:
  class ContentValidator (line 80) | class ContentValidator(ABC):
    method validate (line 84) | def validate(self, content: str, _file_path: str) -> ValidationResult:
    method can_validate (line 97) | def can_validate(self, file_path: str) -> bool:
  class DockerComposeValidator (line 109) | class DockerComposeValidator(ContentValidator):
    method can_validate (line 119) | def can_validate(self, file_path: str) -> bool:
    method validate (line 124) | def validate(self, content: str, _file_path: str) -> ValidationResult:
    method _validate_service (line 176) | def _validate_service(self, name: str, config: Any, result: Validation...
  class YAMLValidator (line 217) | class YAMLValidator(ContentValidator):
    method can_validate (line 220) | def can_validate(self, file_path: str) -> bool:
    method validate (line 224) | def validate(self, content: str, _file_path: str) -> ValidationResult:
  class ValidatorRegistry (line 237) | class ValidatorRegistry:
    method __init__ (line 240) | def __init__(self):
    method _register_default_validators (line 244) | def _register_default_validators(self) -> None:
    method register (line 249) | def register(self, validator: ContentValidator) -> None:
    method get_validator (line 258) | def get_validator(self, file_path: str) -> ContentValidator | None:
    method validate_file (line 273) | def validate_file(self, content: str, file_path: str) -> ValidationRes...
  function get_validator_registry (line 299) | def get_validator_registry() -> ValidatorRegistry:

FILE: cli/core/version.py
  function parse_version (line 15) | def parse_version(version_str: str) -> tuple[int, int]:
  function compare_versions (line 50) | def compare_versions(version1: str, version2: str) -> int:
  function is_compatible (line 83) | def is_compatible(current_version: str, required_version: str) -> bool:

FILE: cli/modules/ansible/__init__.py
  class AnsibleModule (line 11) | class AnsibleModule(Module):

FILE: cli/modules/compose/__init__.py
  class ComposeModule (line 18) | class ComposeModule(Module):
    method validate (line 24) | def validate(  # noqa: PLR0913

FILE: cli/modules/compose/validate.py
  function run_docker_validation (line 17) | def run_docker_validation(
  function _validate_compose_files (line 72) | def _validate_compose_files(module_instance, template, variables, verbos...
  function _test_variable_combinations (line 140) | def _test_variable_combinations(module_instance, template, verbose: bool...
  function _find_toggle_variables (line 217) | def _find_toggle_variables(template) -> list[str]:
  function _get_variables_with_toggles (line 233) | def _get_variables_with_toggles(module_instance, template, toggle_config...

FILE: cli/modules/helm/__init__.py
  class HelmModule (line 11) | class HelmModule(Module):

FILE: cli/modules/kubernetes/__init__.py
  class KubernetesModule (line 11) | class KubernetesModule(Module):

FILE: cli/modules/packer/__init__.py
  class PackerModule (line 11) | class PackerModule(Module):

FILE: cli/modules/terraform/__init__.py
  class TerraformModule (line 11) | class TerraformModule(Module):

FILE: tests/test_variable.py
  class TestVariableInitialization (line 13) | class TestVariableInitialization:
    method test_create_simple_variable (line 16) | def test_create_simple_variable(self):
    method test_create_variable_with_default (line 23) | def test_create_variable_with_default(self):
    method test_create_bool_variable_without_default (line 29) | def test_create_bool_variable_without_default(self):
    method test_create_variable_with_description (line 34) | def test_create_variable_with_description(self):
    method test_missing_name_raises_error (line 39) | def test_missing_name_raises_error(self):
    method test_invalid_data_type_raises_error (line 44) | def test_invalid_data_type_raises_error(self):
  class TestVariableTypes (line 50) | class TestVariableTypes:
    method test_string_type (line 53) | def test_string_type(self):
    method test_int_type (line 58) | def test_int_type(self):
    method test_bool_type_true (line 63) | def test_bool_type_true(self):
    method test_bool_type_false (line 68) | def test_bool_type_false(self):
    method test_float_type (line 73) | def test_float_type(self):
  class TestVariableProperties (line 79) | class TestVariableProperties:
    method test_sensitive_flag (line 82) | def test_sensitive_flag(self):
    method test_autogenerated_flag (line 87) | def test_autogenerated_flag(self):
    method test_required_flag (line 92) | def test_required_flag(self):
    method test_options_list (line 97) | def test_options_list(self):
    method test_extra_help_text (line 102) | def test_extra_help_text(self):
  class TestVariableNeeds (line 108) | class TestVariableNeeds:
    method test_needs_single_string (line 111) | def test_needs_single_string(self):
    method test_needs_semicolon_separated (line 116) | def test_needs_semicolon_separated(self):
    method test_needs_list (line 121) | def test_needs_list(self):
    method test_needs_empty (line 126) | def test_needs_empty(self):
  class TestVariableConversion (line 132) | class TestVariableConversion:
    method test_convert_string_to_int (line 135) | def test_convert_string_to_int(self):
    method test_convert_string_to_bool_true (line 142) | def test_convert_string_to_bool_true(self):
    method test_convert_string_to_bool_false (line 149) | def test_convert_string_to_bool_false(self):
    method test_convert_string_to_float (line 156) | def test_convert_string_to_float(self):
  function test_variable_types_parametrized (line 173) | def test_variable_types_parametrized(var_type, default_value, expected):

FILE: tests/test_version.py
  class TestParseVersion (line 8) | class TestParseVersion:
    method test_parse_simple_version (line 11) | def test_parse_simple_version(self):
    method test_parse_version_with_v_prefix (line 17) | def test_parse_version_with_v_prefix(self):
    method test_parse_version_empty_string (line 22) | def test_parse_version_empty_string(self):
    method test_parse_version_invalid_format (line 27) | def test_parse_version_invalid_format(self):
  class TestCompareVersions (line 37) | class TestCompareVersions:
    method test_compare_equal_versions (line 40) | def test_compare_equal_versions(self):
    method test_compare_major_version_difference (line 45) | def test_compare_major_version_difference(self):
    method test_compare_minor_version_difference (line 50) | def test_compare_minor_version_difference(self):
    method test_compare_with_v_prefix (line 55) | def test_compare_with_v_prefix(self):
    method test_compare_versions_parametrized (line 70) | def test_compare_versions_parametrized(self, v1, v2, expected):
  class TestIsCompatible (line 75) | class TestIsCompatible:
    method test_compatible_equal_versions (line 78) | def test_compatible_equal_versions(self):
    method test_compatible_newer_version (line 82) | def test_compatible_newer_version(self):
    method test_incompatible_older_version (line 87) | def test_incompatible_older_version(self):
    method test_incompatible_invalid_versions (line 92) | def test_incompatible_invalid_versions(self):
    method test_is_compatible_parametrized (line 107) | def test_is_compatible_parametrized(self, current, required, expected):
Condensed preview — 246 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (973K chars).
[
  {
    "path": ".editorconfig",
    "chars": 367,
    "preview": "# https://editorconfig.org/\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninser"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 75,
    "preview": "---\n# These are supported funding model platforms\n\npatreon: christianlempa\n"
  },
  {
    "path": ".github/issue_template.md",
    "chars": 698,
    "preview": "### Issue Reporting\n\n*Please write all text in English in order to facilitate communication and collaboration. Thank you"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 125,
    "preview": "### Pull Request\n\n*Please write all text in English in order to facilitate communication and collaboration. Thank you!*\n"
  },
  {
    "path": ".github/scripts/generate_wiki_docs.py",
    "chars": 8841,
    "preview": "#!/usr/bin/env python3\n\"\"\"Generate GitHub Wiki documentation for module variables.\n\nThis script auto-generates variable "
  },
  {
    "path": ".github/scripts/sync-template-version.sh",
    "chars": 2054,
    "preview": "#!/usr/bin/env bash\n# Sync Docker image versions to template.yaml metadata\n# Triggered by GitHub Actions when Renovate u"
  },
  {
    "path": ".github/workflows/codequality-ruff.yaml",
    "chars": 623,
    "preview": "---\nname: Code Quality - Ruff\n\n'on':\n  pull_request:\n    branches:\n      - main\n  push:\n    branches:\n      - main\n     "
  },
  {
    "path": ".github/workflows/codequality-yamllint.yaml",
    "chars": 310,
    "preview": "---\nname: Code Quality - yamllint\n\n'on':\n  pull_request:\n    branches:\n      - main\n\npermissions:\n  contents: read\n\njobs"
  },
  {
    "path": ".github/workflows/docs-update-wiki.yaml",
    "chars": 3115,
    "preview": "---\nname: Docs - Update Wiki\n\n'on':\n  push:\n    branches:\n      - main\n    paths:\n      - 'cli/core/schema/**/*.json'  #"
  },
  {
    "path": ".github/workflows/release-create-cli-release.yaml",
    "chars": 5306,
    "preview": "---\nname: Release - Create CLI GitHub Release\n\n'on':\n  push:\n    tags:\n      - 'v*.*.*'  # Trigger on version tags like "
  },
  {
    "path": ".github/workflows/renovate-sync-versions.yaml",
    "chars": 2551,
    "preview": "---\nname: Renovate - Sync Template Versions\n\n'on':\n  pull_request:\n    branches:\n      - main\n    paths:\n      - 'librar"
  },
  {
    "path": ".gitignore",
    "chars": 366,
    "preview": "# Ignore local development files\n**/.vscode\n**/.DS_Store\n\n# Docker Secrets, Environment Files\n**/secret.*\n**/.env\n**/.en"
  },
  {
    "path": ".wiki/Core-Concepts-Defaults.md",
    "chars": 8623,
    "preview": "# Default Variables\n\nSave time by setting default values for variables you use frequently. This page explains how to man"
  },
  {
    "path": ".wiki/Core-Concepts-Libraries.md",
    "chars": 7531,
    "preview": "# Libraries\n\nLibraries are collections of templates that can be synced from Git repositories or loaded from local direct"
  },
  {
    "path": ".wiki/Core-Concepts-Templates.md",
    "chars": 8992,
    "preview": "# Templates\n\nTemplates are the core building blocks of the Boilerplates CLI. This page explains what templates are, how "
  },
  {
    "path": ".wiki/Core-Concepts-Variables.md",
    "chars": 10895,
    "preview": "# Variables\n\nVariables are the configurable parameters that customize your templates. This page explains variable types,"
  },
  {
    "path": ".wiki/Getting-Started.md",
    "chars": 7104,
    "preview": "# Getting Started\n\nWelcome to Boilerplates! This guide will help you get up and running in just a few minutes.\n\n## Overv"
  },
  {
    "path": ".wiki/Home.md",
    "chars": 2080,
    "preview": "# Boilerplates Documentation\n\nInstant access to battle-tested templates for Docker, Terraform, Ansible, Kubernetes, and "
  },
  {
    "path": ".wiki/Installation.md",
    "chars": 7422,
    "preview": "# Installation\n\nThis guide covers installing the Boilerplates CLI on various platforms.\n\n## Prerequisites\n\nBefore instal"
  },
  {
    "path": ".wiki/Variables-Ansible.md",
    "chars": 1686,
    "preview": "# Ansible Variables\n\n**Module:** `ansible`  \n**Schema Version:** `1.0`  \n**Description:** Manage Ansible playbooks\n\n---\n"
  },
  {
    "path": ".wiki/Variables-Compose.md",
    "chars": 8637,
    "preview": "# Compose Variables\n\n**Module:** `compose`  \n**Schema Version:** `1.2`  \n**Description:** Manage Docker Compose configur"
  },
  {
    "path": ".wiki/Variables-Helm.md",
    "chars": 4165,
    "preview": "# Helm Variables\n\n**Module:** `helm`  \n**Schema Version:** `1.0`  \n**Description:** Manage Helm charts\n\n---\n\nThis page d"
  },
  {
    "path": ".wiki/Variables-Kubernetes.md",
    "chars": 2544,
    "preview": "# Kubernetes Variables\n\n**Module:** `kubernetes`  \n**Schema Version:** `1.0`  \n**Description:** Manage Kubernetes config"
  },
  {
    "path": ".wiki/Variables-Packer.md",
    "chars": 840,
    "preview": "# Packer Variables\n\n**Module:** `packer`  \n**Schema Version:** `1.0`  \n**Description:** Manage Packer templates\n\n---\n\nTh"
  },
  {
    "path": ".wiki/Variables-Terraform.md",
    "chars": 965,
    "preview": "# Terraform Variables\n\n**Module:** `terraform`  \n**Schema Version:** `1.0`  \n**Description:** Manage Terraform configura"
  },
  {
    "path": ".wiki/Variables.md",
    "chars": 597,
    "preview": "# Variables Documentation\n\nThis section contains auto-generated documentation for all available variables in each module"
  },
  {
    "path": ".wiki/_Sidebar.md",
    "chars": 715,
    "preview": "## Boilerplates Wiki\n\n- **[Home](Home)**\n\n### Getting Started\n- [Getting Started](Getting-Started)\n- [Installation](Inst"
  },
  {
    "path": ".yamllint",
    "chars": 164,
    "preview": "---\nextends: default\n\nrules:\n  comments-indentation: disable\n  indentation:\n    spaces: 2\n    indent-sequences: true\n  l"
  },
  {
    "path": "AGENTS.md",
    "chars": 32602,
    "preview": "# AGENTS.md\n\nGuidance for AI Agents working with this repository.\n\n## Project Overview\n\nA sophisticated collection of in"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 8067,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 6729,
    "preview": "# Contributing to Boilerplates\n\nThank you for your interest in contributing to the Boilerplates project! This document p"
  },
  {
    "path": "LICENSE",
    "chars": 1066,
    "preview": "MIT License\n\nCopyright (c) 2021 Christian\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
  },
  {
    "path": "MANIFEST.in",
    "chars": 306,
    "preview": "# Include library directory with all templates\nrecursive-include library *\n\n# Include JSON schema files\nrecursive-includ"
  },
  {
    "path": "README.md",
    "chars": 5938,
    "preview": "# Christian's `Boilerplates`\n\n[![Welcome](https://cnd-prod-1.s3.us-west-004.backblazeb2.com/new-banner4-scaled-for-githu"
  },
  {
    "path": "SECURITY.md",
    "chars": 1564,
    "preview": "# Security Policy\n\nI take the security of my projects seriously. If you discover any security vulnerabilities or have co"
  },
  {
    "path": "cli/__init__.py",
    "chars": 227,
    "preview": "\"\"\"\nBoilerplates CLI - A sophisticated command-line tool for managing infrastructure boilerplates.\n\"\"\"\n\n__version__ = \"0"
  },
  {
    "path": "cli/__main__.py",
    "chars": 9016,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nMain entry point for the Boilerplates CLI application.\nThis file serves as the primary execut"
  },
  {
    "path": "cli/core/config/__init__.py",
    "chars": 304,
    "preview": "\"\"\"Config package for configuration management.\n\nThis package provides the ConfigManager class for managing application "
  },
  {
    "path": "cli/core/config/config_manager.py",
    "chars": 20944,
    "preview": "from __future__ import annotations\n\nimport logging\nimport shutil\nimport tempfile\nfrom dataclasses import dataclass\nfrom "
  },
  {
    "path": "cli/core/display/__init__.py",
    "chars": 6151,
    "preview": "\"\"\"Display module for CLI output rendering.\n\nThis package provides centralized display management with mixin-based archi"
  },
  {
    "path": "cli/core/display/display_base.py",
    "chars": 10072,
    "preview": "\"\"\"Base display methods for DisplayManager.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom pathlib import P"
  },
  {
    "path": "cli/core/display/display_icons.py",
    "chars": 6220,
    "preview": "\"\"\"Icon management for consistent CLI display.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom typ"
  },
  {
    "path": "cli/core/display/display_settings.py",
    "chars": 1880,
    "preview": "\"\"\"Display configuration settings for the CLI.\"\"\"\n\n\nclass DisplaySettings:\n    \"\"\"Centralized display configuration sett"
  },
  {
    "path": "cli/core/display/display_status.py",
    "chars": 11474,
    "preview": "from __future__ import annotations\n\nimport logging\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom rich import box\nfrom"
  },
  {
    "path": "cli/core/display/display_table.py",
    "chars": 9979,
    "preview": "from __future__ import annotations\n\nimport logging\nfrom typing import TYPE_CHECKING\n\nfrom rich.table import Table\nfrom r"
  },
  {
    "path": "cli/core/display/display_template.py",
    "chars": 5574,
    "preview": "from __future__ import annotations\n\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nfrom rich import box\nfrom"
  },
  {
    "path": "cli/core/display/display_variable.py",
    "chars": 9034,
    "preview": "from __future__ import annotations\n\nimport logging\nfrom typing import TYPE_CHECKING\n\nfrom rich.table import Table\n\nfrom "
  },
  {
    "path": "cli/core/exceptions.py",
    "chars": 7441,
    "preview": "\"\"\"Custom exception classes for the boilerplates CLI.\n\nThis module defines specific exception types for better error han"
  },
  {
    "path": "cli/core/input/__init__.py",
    "chars": 376,
    "preview": "\"\"\"Input management package for CLI user input operations.\n\nThis package provides centralized input handling with standa"
  },
  {
    "path": "cli/core/input/input_manager.py",
    "chars": 7226,
    "preview": "\"\"\"Input Manager for standardized user input handling.\n\nThis module provides a centralized interface for all user input "
  },
  {
    "path": "cli/core/input/input_settings.py",
    "chars": 1156,
    "preview": "\"\"\"Input configuration settings for the CLI.\n\nThis module defines all configurable input parameters including prompt sty"
  },
  {
    "path": "cli/core/input/prompt_manager.py",
    "chars": 9325,
    "preview": "from __future__ import annotations\n\nimport logging\nfrom typing import Any, Callable\n\nfrom rich.console import Console\nfr"
  },
  {
    "path": "cli/core/library.py",
    "chars": 14502,
    "preview": "from __future__ import annotations\n\nimport logging\nfrom pathlib import Path\n\nimport yaml\n\nfrom .config import ConfigMana"
  },
  {
    "path": "cli/core/module/__init__.py",
    "chars": 228,
    "preview": "\"\"\"Module package for template management.\n\nThis package provides the base Module class and related functionality for ma"
  },
  {
    "path": "cli/core/module/base_commands.py",
    "chars": 28142,
    "preview": "\"\"\"Base commands for module: list, search, show, validate, generate.\"\"\"\n\nfrom __future__ import annotations\n\nimport logg"
  },
  {
    "path": "cli/core/module/base_module.py",
    "chars": 11605,
    "preview": "\"\"\"Base module class for template management.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom abc import ABC"
  },
  {
    "path": "cli/core/module/config_commands.py",
    "chars": 5727,
    "preview": "\"\"\"Config/defaults management commands for module.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\n\nfrom typer im"
  },
  {
    "path": "cli/core/module/helpers.py",
    "chars": 7683,
    "preview": "\"\"\"Helper methods for module variable application and template generation.\"\"\"\n\nfrom __future__ import annotations\n\nimpor"
  },
  {
    "path": "cli/core/prompt.py",
    "chars": 11289,
    "preview": "from __future__ import annotations\n\nimport logging\nfrom typing import Any, Callable\n\nfrom rich.console import Console\nfr"
  },
  {
    "path": "cli/core/registry.py",
    "chars": 1365,
    "preview": "\"\"\"Module registry system.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom collections.abc import Iterator\n\n"
  },
  {
    "path": "cli/core/repo.py",
    "chars": 15241,
    "preview": "\"\"\"Repository management module for syncing library repositories.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging"
  },
  {
    "path": "cli/core/template/__init__.py",
    "chars": 781,
    "preview": "\"\"\"Template package for template and variable management.\n\nThis package provides Template, VariableCollection, VariableS"
  },
  {
    "path": "cli/core/template/template.py",
    "chars": 35524,
    "preview": "from __future__ import annotations\n\nimport base64\nimport logging\nimport os\nimport re\nimport secrets\nimport string\nfrom d"
  },
  {
    "path": "cli/core/template/variable.py",
    "chars": 16540,
    "preview": "from __future__ import annotations\n\nimport logging\nfrom typing import TYPE_CHECKING, Any\nfrom urllib.parse import urlpar"
  },
  {
    "path": "cli/core/template/variable_collection.py",
    "chars": 50160,
    "preview": "from __future__ import annotations\n\nimport logging\nfrom collections import defaultdict\nfrom typing import Any\n\nfrom .var"
  },
  {
    "path": "cli/core/template/variable_section.py",
    "chars": 7402,
    "preview": "from __future__ import annotations\n\nfrom collections import OrderedDict\nfrom typing import Any\n\nfrom ..exceptions import"
  },
  {
    "path": "cli/core/validators.py",
    "chars": 10048,
    "preview": "\"\"\"Semantic validators for template content.\n\nThis module provides validators for specific file types and formats,\nenabl"
  },
  {
    "path": "cli/core/version.py",
    "chars": 2787,
    "preview": "\"\"\"Version comparison utilities for semantic versioning.\n\nThis module provides utilities for parsing and comparing seman"
  },
  {
    "path": "cli/modules/__init__.py",
    "chars": 23,
    "preview": "\"\"\"Modules package.\"\"\"\n"
  },
  {
    "path": "cli/modules/ansible/__init__.py",
    "chars": 313,
    "preview": "\"\"\"Ansible module.\"\"\"\n\nimport logging\n\nfrom ...core.module import Module\nfrom ...core.registry import registry\n\nlogger ="
  },
  {
    "path": "cli/modules/compose/__init__.py",
    "chars": 2628,
    "preview": "\"\"\"Docker Compose module.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom typing import Annotated\n\nfrom type"
  },
  {
    "path": "cli/modules/compose/validate.py",
    "chars": 9364,
    "preview": "\"\"\"Docker Compose validation functionality.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport subprocess\nimp"
  },
  {
    "path": "cli/modules/helm/__init__.py",
    "chars": 295,
    "preview": "\"\"\"Helm module.\"\"\"\n\nimport logging\n\nfrom ...core.module import Module\nfrom ...core.registry import registry\n\nlogger = lo"
  },
  {
    "path": "cli/modules/kubernetes/__init__.py",
    "chars": 331,
    "preview": "\"\"\"Kubernetes module.\"\"\"\n\nimport logging\n\nfrom ...core.module import Module\nfrom ...core.registry import registry\n\nlogge"
  },
  {
    "path": "cli/modules/packer/__init__.py",
    "chars": 307,
    "preview": "\"\"\"Packer module.\"\"\"\n\nimport logging\n\nfrom ...core.module import Module\nfrom ...core.registry import registry\n\nlogger = "
  },
  {
    "path": "cli/modules/terraform/__init__.py",
    "chars": 325,
    "preview": "\"\"\"Terraform module.\"\"\"\n\nimport logging\n\nfrom ...core.module import Module\nfrom ...core.registry import registry\n\nlogger"
  },
  {
    "path": "flake.nix",
    "chars": 1554,
    "preview": "{\n  description = \"A curated collection of production-ready templates for your homelab and infrastructure projects\";\n\n  "
  },
  {
    "path": "library/ansible/checkmk-install-agent/playbook.yaml.j2",
    "chars": 557,
    "preview": "---\n- name: Install Checkmk agent on all hosts\n  hosts: {{ target_hosts }}\n  become: true\n  roles:\n    - checkmk.general"
  },
  {
    "path": "library/ansible/checkmk-install-agent/template.yaml",
    "chars": 1621,
    "preview": "---\nkind: ansible\nmetadata:\n  name: Install Checkmk Agent\n  description: |-\n    Ansible playbook to install Checkmk moni"
  },
  {
    "path": "library/ansible/checkmk-manage-host/playbook.yaml.j2",
    "chars": 492,
    "preview": "---\n- name: Manage Checkmk host\n  hosts: localhost\n  gather_facts: false\n  tasks:\n    - name: \"Create or update host in "
  },
  {
    "path": "library/ansible/checkmk-manage-host/template.yaml",
    "chars": 1730,
    "preview": "---\nkind: ansible\nmetadata:\n  name: Manage Checkmk Host\n  description: |-\n    Ansible playbook to manage hosts in Checkm"
  },
  {
    "path": "library/ansible/docker-certs/playbook.yaml.j2",
    "chars": 6298,
    "preview": "---\n- name: {{ playbook_name }}\n  hosts: {{ target_hosts }}\n{% if become %}\n  become: true\n{% endif %}\n{% if options_ena"
  },
  {
    "path": "library/ansible/docker-certs/template.yaml",
    "chars": 1165,
    "preview": "---\nkind: ansible\nmetadata:\n  icon:\n    provider: selfh\n    id: docker\n  name: Generate Docker TLS Certificates\n  descri"
  },
  {
    "path": "library/ansible/docker-certs-enable/playbook.yaml.j2",
    "chars": 2011,
    "preview": "---\n- name: {{ playbook_name }}\n  hosts: {{ target_hosts }}\n{% if become %}\n  become: true\n{% endif %}\n{% if options_ena"
  },
  {
    "path": "library/ansible/docker-certs-enable/template.yaml",
    "chars": 886,
    "preview": "---\nkind: ansible\nmetadata:\n  icon:\n    provider: selfh\n    id: docker\n  name: Enable Docker TLS\n  description: >\n    An"
  },
  {
    "path": "library/ansible/docker-install-ubuntu/main.yml.j2",
    "chars": 1185,
    "preview": "---\n- name: Install Docker on Ubuntu\n  hosts: {{ target_hosts }}\n  become: true\n  gather_facts: true\n\n  tasks:\n    - nam"
  },
  {
    "path": "library/ansible/docker-install-ubuntu/template.yaml",
    "chars": 574,
    "preview": "---\nkind: ansible\nmetadata:\n  icon:\n    provider: selfh\n    id: docker\n  name: Install Docker on Ubuntu\n  description: >"
  },
  {
    "path": "library/ansible/docker-prune/playbook.yaml.j2",
    "chars": 517,
    "preview": "---\n- name: {{ playbook_name }}\n  hosts: {{ target_hosts }}\n{% if become %}\n  become: true\n{% endif %}\n{% if options_ena"
  },
  {
    "path": "library/ansible/docker-prune/template.yaml",
    "chars": 641,
    "preview": "---\nkind: ansible\nmetadata:\n  icon:\n    provider: selfh\n    id: docker\n  name: Docker Prune\n  description: >\n    Ansible"
  },
  {
    "path": "library/ansible/ubuntu-add-sshkey/playbook.yaml.j2",
    "chars": 719,
    "preview": "---\n- name: {{ playbook_name }}\n  hosts: {{ target_hosts }}\n{% if become %}\n  become: true\n{% endif %}\n{% if options_ena"
  },
  {
    "path": "library/ansible/ubuntu-add-sshkey/template.yaml",
    "chars": 643,
    "preview": "---\nkind: ansible\nmetadata:\n  icon:\n    provider: selfh\n    id: ansible\n  name: Add SSH Key and Configure Sudoers\n  desc"
  },
  {
    "path": "library/ansible/ubuntu-apt-update/playbook.yaml.j2",
    "chars": 522,
    "preview": "---\n- name: {{ playbook_name }}\n  hosts: {{ target_hosts }}\n{% if become %}\n  become: true\n{% endif %}\n{% if options_ena"
  },
  {
    "path": "library/ansible/ubuntu-apt-update/template.yaml",
    "chars": 601,
    "preview": "---\nkind: ansible\nmetadata:\n  icon:\n    provider: selfh\n    id: ansible\n  name: Update and Upgrade Ubuntu Packages\n  des"
  },
  {
    "path": "library/ansible/ubuntu-vm-core/playbook.yaml.j2",
    "chars": 604,
    "preview": "---\n- name: {{ playbook_name }}\n  hosts: {{ target_hosts }}\n{% if become %}\n  become: true\n{% endif %}\n{% if options_ena"
  },
  {
    "path": "library/ansible/ubuntu-vm-core/template.yaml",
    "chars": 698,
    "preview": "---\nkind: ansible\nmetadata:\n  icon:\n    provider: selfh\n    id: ansible\n  name: Install Ubuntu VM Core Packages\n  descri"
  },
  {
    "path": "library/compose/adguardhome/compose.yaml.j2",
    "chars": 3775,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/adguard/adguardhome:v0.107.71\n    restart: {{ restart_policy }}"
  },
  {
    "path": "library/compose/adguardhome/template.yaml",
    "chars": 5668,
    "preview": "kind: compose\nmetadata:\n  name: AdGuard Home\n  description: 'Network-wide software for blocking ads and tracking. AdGuar"
  },
  {
    "path": "library/compose/alloy/compose.yaml.j2",
    "chars": 2169,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/grafana/alloy:v1.13.1\n    restart: {{ restart_policy }}\n    {% if c"
  },
  {
    "path": "library/compose/alloy/config/common.alloy.j2",
    "chars": 49,
    "preview": "logging {\n  level = \"info\"\n  format = \"logfmt\"\n}\n"
  },
  {
    "path": "library/compose/alloy/config/logs_docker.alloy.j2",
    "chars": 562,
    "preview": "{% if logs_enabled and logs_docker %}\ndiscovery.docker \"dockerlogs\" {\n  host = \"unix:///var/run/docker.sock\"\n}\n\ndiscover"
  },
  {
    "path": "library/compose/alloy/config/logs_system.alloy.j2",
    "chars": 1177,
    "preview": "{% if logs_enabled and logs_system %}\nloki.source.journal \"journal\" {\n  max_age       = \"24h0m0s\"\n  relabel_rules = disc"
  },
  {
    "path": "library/compose/alloy/config/metrics_docker.alloy.j2",
    "chars": 753,
    "preview": "{% if metrics_enabled and metrics_docker %}\nprometheus.exporter.cadvisor \"dockermetrics\" {\n  docker_host = \"unix:///var/"
  },
  {
    "path": "library/compose/alloy/config/metrics_system.alloy.j2",
    "chars": 1160,
    "preview": "{% if metrics_enabled and metrics_system %}\ndiscovery.relabel \"metrics\" {\n  targets = prometheus.exporter.unix.metrics.t"
  },
  {
    "path": "library/compose/alloy/config/targets.alloy.j2",
    "chars": 502,
    "preview": "{% if logs_enabled  %}\nloki.write \"default\" {\n  endpoint {\n    url = \"{{ logs_loki_url }}\"\n    \n    // Batching configur"
  },
  {
    "path": "library/compose/alloy/template.yaml",
    "chars": 3377,
    "preview": "---\nkind: compose\nmetadata:\n  name: Grafana Alloy\n  description: |-\n    Grafana Alloy is an open telemetry collector tha"
  },
  {
    "path": "library/compose/authentik/compose.yaml.j2",
    "chars": 8040,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: ghcr.io/goauthentik/server:2025.12.4\n    restart: {{ restart_policy }}\n  "
  },
  {
    "path": "library/compose/authentik/template.yaml",
    "chars": 4988,
    "preview": "kind: compose\nmetadata:\n  name: Authentik\n  description: 'Integrate Authentik Single Sign-On (SSO) for secure and stream"
  },
  {
    "path": "library/compose/bind9/compose.yaml.j2",
    "chars": 3038,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/ubuntu/bind9:9.20-24.10_edge\n    restart: {{ restart_policy }}\n    "
  },
  {
    "path": "library/compose/bind9/config/named.conf.j2",
    "chars": 1274,
    "preview": "// BIND9 Main Configuration File\n// Documentation: https://bind9.readthedocs.io/\n\n{% if tsig_enabled %}\ninclude \"/etc/bi"
  },
  {
    "path": "library/compose/bind9/config/named.conf.zones.j2",
    "chars": 329,
    "preview": "zone \"{{ domain_name }}\" {\n    type master;\n    file \"/var/lib/bind/db.{{ domain_name }}\";\n\n    {% if tsig_enabled %}\n  "
  },
  {
    "path": "library/compose/bind9/config/tsig.key.j2",
    "chars": 126,
    "preview": "{% if tsig_enabled %}\nkey \"tsig-transfer-key\" {\n    algorithm hmac-sha256;\n    secret \"{{ tsig_key_secret }}\";\n};\n{% end"
  },
  {
    "path": "library/compose/bind9/template.yaml",
    "chars": 2331,
    "preview": "kind: compose\nmetadata:\n  name: BIND9\n  description: 'BIND9 is the most widely used DNS server on the Internet.\n\n    Thi"
  },
  {
    "path": "library/compose/checkmk/compose.yaml.j2",
    "chars": 1991,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: checkmk/check-mk-raw:2.4.0-latest\n    restart: {{ restart_policy }}\n    e"
  },
  {
    "path": "library/compose/checkmk/template.yaml",
    "chars": 2681,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: checkmk\n  name: Checkmk\n  description: 'Checkmk is a compreh"
  },
  {
    "path": "library/compose/dockge/compose.yaml.j2",
    "chars": 1657,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/louislam/dockge:1.5.0\n    restart: {{ restart_policy }}\n    env"
  },
  {
    "path": "library/compose/dockge/template.yaml",
    "chars": 1913,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: dockge\n  name: Dockge\n  description: 'Dockge is a powerful D"
  },
  {
    "path": "library/compose/gitea/compose.yaml.j2",
    "chars": 4200,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/gitea/gitea:1.25.4\n    restart: {{ restart_policy }}\n    environmen"
  },
  {
    "path": "library/compose/gitea/template.yaml",
    "chars": 3397,
    "preview": "kind: compose\nmetadata:\n  name: Gitea\n  description: 'Self-hosted Git service with web interface. Gitea is a painless, s"
  },
  {
    "path": "library/compose/gitlab/compose.yaml.j2",
    "chars": 2873,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/gitlab/gitlab-ce:18.8.4-ce.0\n    restart: {{ restart_policy }}\n    "
  },
  {
    "path": "library/compose/gitlab/config/gitlab.rb.j2",
    "chars": 3203,
    "preview": "# GitLab Configuration\nexternal_url '{{ external_url }}'\n\n# Initial root user configuration (only used on first initiali"
  },
  {
    "path": "library/compose/gitlab/template.yaml",
    "chars": 7637,
    "preview": "kind: compose\nmetadata:\n  name: GitLab\n  description: 'A **complete DevOps platform** that provides Git repository manag"
  },
  {
    "path": "library/compose/gitlab-runner/compose.yaml.j2",
    "chars": 546,
    "preview": "{#\n  GitLab Runner: CI/CD job executor for GitLab\n  Executes pipeline jobs using Docker executor\n#}\nservices:\n  gitlab-r"
  },
  {
    "path": "library/compose/gitlab-runner/config/config.toml",
    "chars": 981,
    "preview": "concurrent = 10\nlog_level = \"warning\"\nlog_format = \"json\"\ncheck_interval = 5\n\n[[runners]]\n  name = \"gitlab-runner-1\"\n  u"
  },
  {
    "path": "library/compose/gitlab-runner/template.yaml",
    "chars": 818,
    "preview": "---\nkind: compose\nmetadata:\n  name: Gitlab-Runner\n  description: |-\n    Docker compose setup for Gitlab-Runner, a build "
  },
  {
    "path": "library/compose/grafana/compose.yaml.j2",
    "chars": 4140,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/grafana/grafana-oss:12.3.3\n    restart: {{ restart_policy }}\n    {%"
  },
  {
    "path": "library/compose/grafana/template.yaml",
    "chars": 4036,
    "preview": "kind: compose\nmetadata:\n  name: Grafana\n  description: 'Grafana is an open-source platform for monitoring and observabil"
  },
  {
    "path": "library/compose/homeassistant/compose.yaml.j2",
    "chars": 378,
    "preview": "services:\n  {{ service_name }}:\n    container_name: {{ container_name }}\n    image: ghcr.io/home-assistant/home-assistan"
  },
  {
    "path": "library/compose/homeassistant/template.yaml",
    "chars": 673,
    "preview": "---\nkind: compose\nmetadata:\n  name: Homeassistant\n  description: |-\n    Home Assistant is an open-source platform for sm"
  },
  {
    "path": "library/compose/homepage/compose.yaml.j2",
    "chars": 3737,
    "preview": "services:\n  {{ service_name }}:\n    image: ghcr.io/gethomepage/homepage:v1.10.1\n    {% if not swarm_enabled %}\n    resta"
  },
  {
    "path": "library/compose/homepage/template.yaml",
    "chars": 3289,
    "preview": "kind: compose\nmetadata:\n  name: Homepage\n  description: 'Homepage is a modern, fully static, fast, secure fully customiz"
  },
  {
    "path": "library/compose/homer/assets/config.yml.j2",
    "chars": 2218,
    "preview": "---\n# Homepage configuration\n# See https://fontawesome.com/icons for icons options\n\ntitle: \"{{ homer_title }}\"\nsubtitle:"
  },
  {
    "path": "library/compose/homer/compose.yaml.j2",
    "chars": 3663,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/b4bz/homer:v25.11.1\n    {% if not swarm_enabled %}\n    restart: {{ "
  },
  {
    "path": "library/compose/homer/template.yaml",
    "chars": 4964,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: homer\n  name: Homer\n  description: 'A very simple static hom"
  },
  {
    "path": "library/compose/influxdb/compose.yaml.j2",
    "chars": 1823,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/library/influxdb:2.8.0-alpine\n    restart: {{ restart_policy }}"
  },
  {
    "path": "library/compose/influxdb/template.yaml",
    "chars": 2538,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: influxdb\n  name: Influxdb\n  description: 'InfluxDB is a powe"
  },
  {
    "path": "library/compose/komodo/compose.yaml.j2",
    "chars": 5697,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: ghcr.io/moghtech/komodo:latest\n    {% if not swarm_enabled %}\n    restart"
  },
  {
    "path": "library/compose/komodo/template.yaml",
    "chars": 6699,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: komodo\n  name: Komodo\n  description: 'Build and deployment a"
  },
  {
    "path": "library/compose/loki/compose.yaml.j2",
    "chars": 1561,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/grafana/loki:3.6.5\n    restart: {{ restart_policy }}\n    comman"
  },
  {
    "path": "library/compose/loki/config/config.yaml.j2",
    "chars": 675,
    "preview": "---\nauth_enabled: false\n\nserver:\n  http_listen_port: 3100\n\ncommon:\n  instance_addr: 127.0.0.1\n  path_prefix: /loki\n  sto"
  },
  {
    "path": "library/compose/loki/template.yaml",
    "chars": 1844,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: loki\n  name: Loki\n  description: 'Loki is a horizontally sca"
  },
  {
    "path": "library/compose/mariadb/compose.yaml.j2",
    "chars": 484,
    "preview": "\n---\nservices:\n  {{ service_name }}:\n    image: docker.io/library/mariadb:12.2.2\n    restart: {{ restart_policy }}\n    e"
  },
  {
    "path": "library/compose/mariadb/template.yaml",
    "chars": 1374,
    "preview": "---\nkind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: mariadb\n  name: MariaDB\n  description: |-\n    MariaDB is"
  },
  {
    "path": "library/compose/n8n/compose.yaml.j2",
    "chars": 10185,
    "preview": "{% if queue_enabled and not queue_redis_external -%}\nservices:\n  redis:\n    image: redis:8-alpine\n    {% if not swarm_en"
  },
  {
    "path": "library/compose/n8n/template.yaml",
    "chars": 7971,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: n8n\n  name: N8N\n  description: 'N8n is a free and source-ava"
  },
  {
    "path": "library/compose/netbox/compose.yaml.j2",
    "chars": 6526,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/netboxcommunity/netbox:v4.5.2\n    restart: {{ restart_policy }}"
  },
  {
    "path": "library/compose/netbox/template.yaml",
    "chars": 4089,
    "preview": "---\nkind: compose\nmetadata:\n  name: NetBox\n  description: 'Network infrastructure management (IPAM/DCIM) and network aut"
  },
  {
    "path": "library/compose/nextcloud/compose.yaml.j2",
    "chars": 4761,
    "preview": "services:\n  {{ service_name }}-app:\n    image: docker.io/library/nextcloud:32.0.6-apache\n    {% if not swarm_enabled %}\n"
  },
  {
    "path": "library/compose/nextcloud/template.yaml",
    "chars": 2963,
    "preview": "kind: compose\nmetadata:\n  name: Nextcloud\n  description: 'Self-hosted file sync and share platform. Nextcloud is a suite"
  },
  {
    "path": "library/compose/nginx/compose.yaml.j2",
    "chars": 4197,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/library/nginx:1.28.2-alpine\n    {#\n      If not in swarm mode, appl"
  },
  {
    "path": "library/compose/nginx/config/default.conf",
    "chars": 134,
    "preview": "server {\n  listen 80;\n  server_name  _;\n  location / {\n    root /usr/share/nginx/html;\n    index index.html;\n    access_"
  },
  {
    "path": "library/compose/nginx/data/index.html",
    "chars": 26,
    "preview": "this is a test website...\n"
  },
  {
    "path": "library/compose/nginx/template.yaml",
    "chars": 2306,
    "preview": "---\nkind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: nginx\n  name: Nginx\n  description: 'Nginx is a high-perf"
  },
  {
    "path": "library/compose/openwebui/compose.yaml.j2",
    "chars": 2877,
    "preview": "services:\n  {{ service_name }}:\n    image: ghcr.io/open-webui/open-webui:v0.8.2\n    container_name: {{ container_name }}"
  },
  {
    "path": "library/compose/openwebui/template.yaml",
    "chars": 3369,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: open-webui\n  name: Openwebui\n  description: 'OpenWebUI is an"
  },
  {
    "path": "library/compose/passbolt/compose.yaml.j2",
    "chars": 1500,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/passbolt/passbolt:5.9.0-1-ce\n    restart: unless-stopped\n    en"
  },
  {
    "path": "library/compose/passbolt/template.yaml",
    "chars": 905,
    "preview": "---\nkind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: passbolt\n  name: Passbolt\n  description: |-\n    Passbolt"
  },
  {
    "path": "library/compose/pihole/compose.yaml.j2",
    "chars": 9763,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/pihole/pihole:2025.11.1\n    {#\n      If not in swarm mode, chec"
  },
  {
    "path": "library/compose/pihole/template.yaml",
    "chars": 5389,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: pi-hole\n  name: Pihole\n  description: 'Network-wide advertis"
  },
  {
    "path": "library/compose/portainer/compose.yaml.j2",
    "chars": 3391,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/portainer/portainer-ce:2.38.1-alpine\n    {% if not swarm_enabled %}"
  },
  {
    "path": "library/compose/portainer/template.yaml",
    "chars": 3409,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: portainer\n  name: Portainer\n  description: 'Portainer is a p"
  },
  {
    "path": "library/compose/postgres/compose.yaml.j2",
    "chars": 4475,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/library/postgres:18.2\n    {#\n      If not in swarm mode, apply rest"
  },
  {
    "path": "library/compose/postgres/compose.yaml.j2.final",
    "chars": 4475,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/library/postgres:17.6\n    {#\n      If not in swarm mode, apply rest"
  },
  {
    "path": "library/compose/postgres/template.yaml",
    "chars": 3057,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: postgresql\n  name: PostgreSQL\n  description: 'PostgreSQL is "
  },
  {
    "path": "library/compose/prometheus/compose.yaml.j2",
    "chars": 2125,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/prom/prometheus:v3.9.1\n    restart: {{ restart_policy }}\n    co"
  },
  {
    "path": "library/compose/prometheus/config/prometheus.yaml",
    "chars": 915,
    "preview": "---\nglobal:\n  scrape_interval: 15s  # By default, scrape targets every 15 seconds.\n\n  # Attach these labels to any time "
  },
  {
    "path": "library/compose/prometheus/template.yaml",
    "chars": 3761,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: prometheus\n  name: Prometheus\n  description: 'Prometheus is "
  },
  {
    "path": "library/compose/renovate/compose.yaml.j2",
    "chars": 4639,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: ghcr.io/mend/renovate-ce:13.6.0-full\n    {#\n      If not in swarm mode, a"
  },
  {
    "path": "library/compose/renovate/template.yaml",
    "chars": 3904,
    "preview": "---\nkind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: mend-renovate\n  name: Renovate\n  description: |-\n    **R"
  },
  {
    "path": "library/compose/semaphoreui/compose.yaml.j2",
    "chars": 3682,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/semaphoreui/semaphore:v2.17.2\n    restart: {{ restart_policy }}\n   "
  },
  {
    "path": "library/compose/semaphoreui/template.yaml",
    "chars": 3762,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: semaphore-ui\n  name: Semaphore UI\n  description: 'Modern UI "
  },
  {
    "path": "library/compose/traefik/compose.yaml.j2",
    "chars": 8933,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/library/traefik:v3.6.8\n    {% if not swarm_enabled %}\n    {% if"
  },
  {
    "path": "library/compose/traefik/template.yaml",
    "chars": 8185,
    "preview": "kind: compose\nmetadata:\n  name: Traefik\n  description: 'Traefik is a modern HTTP reverse proxy and load balancer that ma"
  },
  {
    "path": "library/compose/twingate-connector/compose.yaml.j2",
    "chars": 2735,
    "preview": "services:\n  {{ service_name }}:\n    image: docker.io/twingate/connector:{{ twingate_version }}\n    {#\n      If not in sw"
  },
  {
    "path": "library/compose/twingate-connector/template.yaml",
    "chars": 2564,
    "preview": "kind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: twingate\n  name: Twingate_Connector\n  description: 'The Twin"
  },
  {
    "path": "library/compose/uptimekuma/compose.yaml.j2",
    "chars": 3473,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: docker.io/louislam/uptime-kuma:2.1.3\n    environment:\n      - TZ={{ conta"
  },
  {
    "path": "library/compose/uptimekuma/template.yaml",
    "chars": 3157,
    "preview": "---\nkind: compose\nmetadata:\n  icon:\n    provider: simple-icons\n    id: uptimekuma\n  name: Uptimekuma\n  description: |\n  "
  },
  {
    "path": "library/compose/whoami/compose.yaml.j2",
    "chars": 2428,
    "preview": "---\nservices:\n  {{ service_name }}:\n    image: traefik/whoami:v1.11.0\n    {% if not swarm_enabled %}\n    restart: {{ res"
  },
  {
    "path": "library/compose/whoami/template.yaml",
    "chars": 2585,
    "preview": "---\nkind: compose\nmetadata:\n  icon:\n    provider: selfh\n    id: traefik\n  name: Whoami\n  description: |\n    A **simple w"
  },
  {
    "path": "library/helm/authentik/secrets.yaml.j2",
    "chars": 187,
    "preview": "---\nauthentik:\n  secret_key: {{ authentik_secret_key }}\n  postgresql:\n    password: {{ database_password }}\n{% if email_"
  },
  {
    "path": "library/helm/authentik/template.yaml",
    "chars": 4190,
    "preview": "---\nkind: helm\nmetadata:\n  icon:\n    provider: selfh\n    id: authentik\n  name: Authentik\n  description: >\n    Helm value"
  },
  {
    "path": "library/helm/authentik/values.yaml.j2",
    "chars": 1096,
    "preview": "---\nglobal:\n  image:\n    repository: \"ghcr.io/goauthentik/server\"\n    tag: \"2025.6.3\"\n    pullPolicy: IfNotPresent\nauthe"
  },
  {
    "path": "library/helm/certmanager/template.yaml",
    "chars": 2370,
    "preview": "---\nkind: helm\nmetadata:\n  name: Cert-Manager\n  description: |-\n    Helm values template for cert-manager, a Kubernetes "
  },
  {
    "path": "library/helm/certmanager/values.yaml.j2",
    "chars": 471,
    "preview": "---\nimage:\n  repository: quay.io/jetstack/cert-manager-controller\n  tag: v1.18.2\nwebhook:\n  image:\n    repository: quay."
  },
  {
    "path": "library/helm/longhorn/template.yaml",
    "chars": 2366,
    "preview": "---\nkind: helm\nmetadata:\n  name: Longhorn\n  description: |-\n    Helm values template for Longhorn, a distributed block s"
  },
  {
    "path": "library/helm/longhorn/values.yaml.j2",
    "chars": 1286,
    "preview": "---\nimage:\n  longhorn:\n    engine:\n      repository: \"longhornio/longhorn-engine\"\n      tag: \"v1.9.1\"\n    manager:\n     "
  },
  {
    "path": "library/helm/netbox/template.yaml",
    "chars": 5920,
    "preview": "---\nkind: helm\nmetadata:\n  icon:\n    provider: selfh\n    id: netbox\n  name: NetBox\n  description: |\n    Helm values temp"
  },
  {
    "path": "library/helm/netbox/values.yaml.j2",
    "chars": 2485,
    "preview": "---\nimage:\n  repository: docker.io/netboxcommunity/netbox\n  tag: v4.2.3\n  pullPolicy: IfNotPresent\n\nreplicaCount: 1\n\nsup"
  },
  {
    "path": "library/helm/portainer/template.yaml",
    "chars": 2557,
    "preview": "---\nkind: helm\nmetadata:\n  icon:\n    provider: selfh\n    id: portainer\n  name: Portainer CE\n  description: >\n    Helm va"
  },
  {
    "path": "library/helm/portainer/values.yaml.j2",
    "chars": 522,
    "preview": "---\nimage:\n  repository: portainer/portainer-ce\n  tag: 2.34.0\n  pullPolicy: IfNotPresent\n\nservice:\n  type: {{ network_mo"
  },
  {
    "path": "library/helm/traefik/template.yaml",
    "chars": 3273,
    "preview": "---\nkind: helm\nmetadata:\n  name: Traefik Ingress Controller\n  description: |-\n    Helm values template for Traefik v3, a"
  },
  {
    "path": "library/helm/traefik/values.yaml.j2",
    "chars": 862,
    "preview": "---\nimage:\n  repository: traefik\n  tag: v3.5.3\n  pullPolicy: IfNotPresent\n{% if http_redirect_enabled %}\n\nports:\n  web:\n"
  },
  {
    "path": "library/kubernetes/certmanager-certificate/certificate.yaml.j2",
    "chars": 345,
    "preview": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: {{ resource_name }}\n  namespace: {{ namespace }}\n"
  },
  {
    "path": "library/kubernetes/certmanager-certificate/template.yaml",
    "chars": 2509,
    "preview": "---\nkind: kubernetes\nmetadata:\n  icon:\n    provider: selfh\n    id: lets-encrypt\n  name: Cert-Manager Certificate\n  descr"
  },
  {
    "path": "library/kubernetes/certmanager-clusterissuer/clusterissuer.yaml.j2",
    "chars": 406,
    "preview": "---\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: {{ resource_name }}\nspec:\n  acme:\n    email: {{"
  },
  {
    "path": "library/kubernetes/certmanager-clusterissuer/template.yaml",
    "chars": 2686,
    "preview": "---\nkind: kubernetes\nmetadata:\n  icon:\n    provider: selfh\n    id: lets-encrypt\n  name: Cert-Manager ClusterIssuer (Clou"
  },
  {
    "path": "library/kubernetes/certmanager-issuer/issuer.yaml.j2",
    "chars": 428,
    "preview": "---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: {{ resource_name }}\n  namespace: {{ namespace }}\nspec:"
  },
  {
    "path": "library/kubernetes/certmanager-issuer/template.yaml",
    "chars": 2626,
    "preview": "---\nkind: kubernetes\nmetadata:\n  icon:\n    provider: selfh\n    id: lets-encrypt\n  name: Cert-Manager Issuer (Cloudflare)"
  },
  {
    "path": "library/kubernetes/core-configmap/configmap.yaml.j2",
    "chars": 234,
    "preview": "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ resource_name }}\n  namespace: {{ namespace }}\ndata:\n  # Add your"
  },
  {
    "path": "library/kubernetes/core-configmap/template.yaml",
    "chars": 1770,
    "preview": "---\nkind: kubernetes\nmetadata:\n  icon:\n    provider: selfh\n    id: kubernetes\n  name: Kubernetes ConfigMap\n  description"
  },
  {
    "path": "library/kubernetes/core-ingress/ingress.yaml.j2",
    "chars": 432,
    "preview": "---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: {{ resource_name }}\n  namespace: {{ namespace }}\nsp"
  }
]

// ... and 46 more files (download for full content)

About this extraction

This page contains the full source code of the ChristianLempa/boilerplates GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 246 files (894.6 KB), approximately 219.3k tokens, and a symbol index with 471 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!