ソースを参照

template updates

xcad 4 ヶ月 前
コミット
805341b63e

+ 3 - 0
.gitignore

@@ -31,4 +31,7 @@ dist/
 tests/
 config.yaml
 
+# Local testing documentation (homelab specific)
+WARP-LOCAL.md
+
 *~

+ 10 - 2
AGENTS.md

@@ -17,6 +17,14 @@ python3 -m cli
 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.
@@ -569,8 +577,8 @@ To skip the prompt use the `--no-interactive` flag, which will use defaults or e
 - `list` - List all templates
 - `search <query>` - Search templates by ID
 - `show <id>` - Show template details
-- `generate <id> [directory]` - Generate from template (supports `--dry-run`, `--var`, `--no-interactive`)
-- `validate [id]` - Validate templates (Jinja2 + semantic)
+- `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:**

+ 1 - 0
CHANGELOG.md

@@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - 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
 
 ### Deprecated
 - Positional directory argument for `generate` command (#1534) - Use `--output`/`-o` flag instead (will be removed in v0.2.0)

+ 21 - 3
cli/core/module/base_module.py

@@ -241,8 +241,15 @@ class Module(ABC):
 
     def validate(
         self,
-        template_id: str | None = None,
-        path: str | None = None,
+        template_id: Annotated[
+            str | None,
+            Argument(help="Template ID to validate (omit to validate all templates)"),
+        ] = None,
+        *,
+        path: Annotated[
+            str | None,
+            Option("--path", help="Path to template directory for validation"),
+        ] = None,
         verbose: Annotated[bool, Option("--verbose", "-v", help="Show detailed validation information")] = False,
         semantic: Annotated[
             bool,
@@ -252,7 +259,18 @@ class Module(ABC):
             ),
         ] = True,
     ) -> None:
-        """Validate templates for Jinja2 syntax, undefined variables, and semantic correctness."""
+        """Validate templates for Jinja2 syntax, undefined variables, and semantic correctness.
+        
+        Examples:
+            # Validate specific template
+            cli compose validate netbox
+            
+            # Validate all templates
+            cli compose validate
+            
+            # Validate with verbose output
+            cli compose validate netbox --verbose
+        """
         return validate_templates(self, template_id, path, verbose, semantic)
 
     def config_get(

+ 20 - 3
cli/modules/compose/__init__.py

@@ -2,7 +2,7 @@
 
 from typing import Annotated
 
-from typer import Option
+from typer import Argument, Option
 
 from ...core.module import Module
 from ...core.module.base_commands import validate_templates
@@ -35,8 +35,15 @@ class ComposeModule(Module):
 
     def validate(  # noqa: PLR0913
         self,
-        template_id: str | None = None,
-        path: str | None = None,
+        template_id: Annotated[
+            str | None,
+            Argument(help="Template ID to validate (omit to validate all templates)"),
+        ] = None,
+        *,
+        path: Annotated[
+            str | None,
+            Option("--path", help="Path to template directory for validation"),
+        ] = None,
         verbose: Annotated[bool, Option("--verbose", "-v", help="Show detailed validation information")] = False,
         semantic: Annotated[
             bool,
@@ -64,6 +71,16 @@ class ComposeModule(Module):
 
         Extended for Docker Compose with optional docker compose config validation.
         Use --docker for single config test, --docker-test-all for comprehensive testing.
+        
+        Examples:
+            # Validate specific template
+            compose validate netbox
+            
+            # Validate all templates
+            compose validate
+            
+            # Validate with Docker Compose config check
+            compose validate netbox --docker
         """
         # Run standard validation first
         validate_templates(self, template_id, path, verbose, semantic)

+ 6 - 1
cli/modules/compose/validate.py

@@ -47,7 +47,12 @@ def run_docker_validation(
             _test_variable_combinations(module_instance, template, verbose)
         else:
             # Single configuration with template defaults
-            _validate_compose_files(module_instance, template, template.variables, verbose, "Template defaults")
+            success = _validate_compose_files(module_instance, template, template.variables, verbose, "Template defaults")
+            if success:
+                module_instance.display.success("Docker Compose validation passed")
+            else:
+                module_instance.display.error("Docker Compose validation failed")
+                raise Exit(code=1) from None
 
     except FileNotFoundError as e:
         module_instance.display.error(

+ 206 - 0
library/compose/adguardhome/compose.yaml.j2

@@ -0,0 +1,206 @@
+---
+services:
+  {{ service_name }}:
+    image: docker.io/adguard/adguardhome:latest
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if network_mode == 'bridge' %}
+    ports:
+      {% if not traefik_enabled %}
+      {% if swarm_enabled %}
+      - target: 80
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      - target: 443
+        published: {{ ports_https }}
+        protocol: tcp
+        mode: host
+      - target: 3000
+        published: 3000
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:80/tcp"
+      - "{{ ports_https }}:443/tcp"
+      - "3000:3000/tcp"
+      {% endif %}
+      {% endif %}
+      {% if swarm_enabled %}
+      - target: 53
+        published: 53
+        protocol: tcp
+        mode: host
+      - target: 53
+        published: 53
+        protocol: udp
+        mode: host
+      - target: 67
+        published: 67
+        protocol: udp
+        mode: host
+      - target: 68
+        published: 68
+        protocol: udp
+        mode: host
+      - target: 853
+        published: 853
+        protocol: tcp
+        mode: host
+      - target: 853
+        published: 853
+        protocol: udp
+        mode: host
+      - target: 5443
+        published: 5443
+        protocol: tcp
+        mode: host
+      - target: 5443
+        published: 5443
+        protocol: udp
+        mode: host
+      {% else %}
+      - "53:53/tcp"
+      - "53:53/udp"
+      - "67:67/udp"
+      - "68:68/udp"
+      - "853:853/tcp"
+      - "853:853/udp"
+      - "5443:5443/tcp"
+      - "5443:5443/udp"
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/work:/opt/adguardhome/work:rw
+      - {{ volume_mount_path }}/conf:/opt/adguardhome/conf:rw
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-work:/opt/adguardhome/work
+      - {{ service_name }}-conf:/opt/adguardhome/conf
+      {% endif %}
+    cap_add:
+      - NET_ADMIN
+      - NET_BIND_SERVICE
+      - NET_RAW
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: replicated
+      replicas: 1
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=80
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-work:
+    driver: local
+  {{ service_name }}-conf:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-work:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/work"
+  {{ service_name }}-conf:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/conf"
+{% endif %}
+
+{% if network_mode != 'host' %}
+networks:
+  {{ network_name }}:
+    {% if network_external %}
+    external: true
+    {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    name: {{ network_name }}
+    {% elif swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 92 - 0
library/compose/adguardhome/template.yaml

@@ -0,0 +1,92 @@
+---
+kind: compose
+schema: "1.2"
+metadata:
+  name: AdGuard Home
+  description: |
+    Network-wide software for blocking ads and tracking. AdGuard Home operates as a DNS server that
+    re-routes tracking domains to a "black hole", thus preventing your devices from connecting to those servers.
+    It features advanced DNS filtering, parental controls, safe browsing, and HTTPS/DNS-over-TLS/DNS-over-QUIC support.
+    ## Swarm Deployment Warning
+    AdGuard Home uses local storage and configuration files and does NOT support running multiple replicas.
+    This template enforces a single replica with node placement constraints to ensure stable DNS resolution.
+    ## References
+    * **Project:** https://adguard.com/adguard-home/overview.html
+    * **Documentation:** https://github.com/AdguardTeam/AdGuardHome/wiki
+    * **GitHub:** https://github.com/AdguardTeam/AdGuardHome
+  version: latest
+  author: Christian Lempa
+  date: '2025-11-13'
+  tags:
+    - traefik
+    - swarm
+  next_steps: |
+    ### 1. Deploy the Service
+    {% if swarm_enabled -%}
+    Deploy to Docker Swarm:
+    ```bash
+    docker stack deploy -c compose.yaml adguardhome
+    ```
+    {% else -%}
+    Start AdGuard Home using Docker Compose:
+    ```bash
+    docker compose up -d
+    ```
+    {% endif -%}
+    ### 2. Initial Setup
+    {% if traefik_enabled -%}
+    * Navigate to: **http://{{ traefik_host }}.{{ traefik_domain }}:3000**
+    {% else -%}
+    * Navigate to: **http://localhost:3000**
+    {% endif -%}
+    * Complete the initial setup wizard to configure admin credentials and DNS settings.
+    ### 3. Access the Web Interface
+    {% if traefik_enabled -%}
+    * Navigate to: **https://{{ traefik_host }}.{{ traefik_domain }}**
+    {% else -%}
+    * Navigate to: **http://localhost:{{ ports_http }}**
+    {% endif -%}
+    * Login using the credentials configured during initial setup.
+spec:
+  general:
+    vars:
+      service_name:
+        default: "adguardhome"
+      container_name:
+        default: "adguardhome"
+      container_hostname:
+        default: "adguardhome"
+  traefik:
+    vars:
+      traefik_enabled:
+        needs: "network_mode=bridge"
+      traefik_host:
+        default: "adguardhome"
+  network:
+    vars:
+      network_mode:
+        extra: >
+          Use 'host' mode if you need DHCP functionality or want AdGuard Home to bind directly to port 53.
+          NOTE: Swarm only supports 'bridge' mode!
+      network_name:
+        default: "adguardhome_network"
+  ports:
+    vars:
+      ports_http:
+        description: "External HTTP port (admin panel)"
+        type: int
+        default: 3080
+        needs: ["traefik_enabled=false", "network_mode=bridge"]
+      ports_https:
+        description: "External HTTPS port"
+        type: int
+        default: 3443
+        needs: ["traefik_enabled=false", "network_mode=bridge"]
+  swarm:
+    vars:
+      swarm_enabled:
+        needs: "network_mode=bridge"
+      swarm_placement_host:
+        required: true
+        optional: false
+        needs: null

+ 159 - 0
library/compose/komodo/compose.yaml.j2

@@ -0,0 +1,159 @@
+---
+services:
+  {{ service_name }}:
+    image: ghcr.io/moghtech/komodo:latest
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if network_mode == 'bridge' and not traefik_enabled %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 9120
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:9120/tcp"
+      {% endif %}
+    {% endif %}
+    {% if environment_enabled %}
+    environment:
+      KOMODO_DATABASE_ADDRESS: "{{ environment_database_address }}"
+      KOMODO_DATABASE_DB_NAME: "{{ environment_database_name }}"
+      {% if environment_database_username %}
+      KOMODO_DATABASE_USERNAME: "{{ environment_database_username }}"
+      {% endif %}
+      {% if environment_database_password %}
+      KOMODO_DATABASE_PASSWORD: "{{ environment_database_password }}"
+      {% endif %}
+      {% if environment_jwt_secret %}
+      KOMODO_JWT_SECRET: "{{ environment_jwt_secret }}"
+      {% endif %}
+      LOG_LEVEL: "{{ environment_log_level }}"
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/data:/app/data:rw
+      - {{ volume_mount_path }}/repos:/app/repos:rw
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-data:/app/data
+      - {{ service_name }}-repos:/app/repos
+      {% endif %}
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: replicated
+      replicas: 1
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9120
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=9120
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+  {{ service_name }}-repos:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/data"
+  {{ service_name }}-repos:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/repos"
+{% endif %}
+
+{% if network_mode != 'host' %}
+networks:
+  {{ network_name }}:
+    {% if network_external %}
+    external: true
+    {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    name: {{ network_name }}
+    {% elif swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 130 - 0
library/compose/komodo/template.yaml

@@ -0,0 +1,130 @@
+---
+kind: compose
+schema: "1.2"
+metadata:
+  name: Komodo
+  description: |
+    Build and deployment automation tool for managing software across multiple servers. Komodo provides
+    unlimited server connections, flexible API access, and comprehensive management of Docker deployments,
+    stacks, and builds. Features include real-time container monitoring, batch operations, and integration
+    with Docker, Docker Compose, and build systems. Supports both MongoDB and FerretDB as database backends.
+    ## Important Notes
+    * Requires MongoDB or FerretDB for data storage (database not included in this template)
+    * Requires Periphery agent on managed servers for remote operations
+    * Web interface and API accessible through configured ports
+    ## References
+    * **Project:** https://github.com/moghtech/komodo
+    * **Documentation:** https://github.com/moghtech/komodo/tree/main/docsite/docs
+    * **Docker Hub:** https://hub.docker.com/r/moghtech/komodo
+  version: latest
+  author: Christian Lempa
+  date: '2025-11-13'
+  tags:
+    - traefik
+    - swarm
+    - deployment
+    - automation
+  next_steps: |
+    ### 1. Prerequisites
+    * Deploy MongoDB or FerretDB database
+    * Configure database connection in environment variables
+    * Install Periphery agent on servers you want to manage
+    ### 2. Deploy the Service
+    {% if swarm_enabled -%}
+    Deploy to Docker Swarm:
+    ```bash
+    docker stack deploy -c compose.yaml komodo
+    ```
+    {% else -%}
+    Start Komodo using Docker Compose:
+    ```bash
+    docker compose up -d
+    ```
+    {% endif -%}
+    ### 3. Access the Web Interface
+    {% if traefik_enabled -%}
+    * Navigate to: **https://{{ traefik_host }}.{{ traefik_domain }}**
+    {% else -%}
+    * Navigate to: **http://localhost:{{ ports_http }}**
+    {% endif -%}
+    * Complete initial setup and create admin user
+    ### 4. Install Periphery Agent
+    On each server you want to manage:
+    ```bash
+    curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/setup-periphery.py | python3
+    ```
+    ### 5. Configure Servers
+    * Add servers to Komodo through the web interface
+    * Configure API keys for programmatic access
+    * Start managing deployments, stacks, and builds
+spec:
+  general:
+    vars:
+      service_name:
+        default: "komodo"
+      container_name:
+        default: "komodo"
+      container_hostname:
+        default: "komodo"
+  traefik:
+    vars:
+      traefik_host:
+        default: "komodo"
+  network:
+    vars:
+      network_name:
+        default: "komodo_network"
+  ports:
+    vars:
+      ports_http:
+        description: "External HTTP port (web interface and API)"
+        type: int
+        default: 9120
+        needs: ["traefik_enabled=false", "network_mode=bridge"]
+  volume:
+    vars:
+      volume_mount_path:
+        default: "/mnt/storage/komodo"
+  environment:
+    title: "Environment Variables"
+    toggle: environment_enabled
+    required: true
+    vars:
+      environment_enabled:
+        type: bool
+        default: true
+        description: "Configure environment variables (required)"
+      environment_database_address:
+        type: str
+        default: "mongodb://mongo:27017"
+        description: "Database connection address (MongoDB or FerretDB)"
+        needs: "environment_enabled=true"
+      environment_database_name:
+        type: str
+        default: "komodo"
+        description: "Database name"
+        needs: "environment_enabled=true"
+      environment_database_username:
+        type: str
+        default: ""
+        description: "Database username (optional)"
+        needs: "environment_enabled=true"
+      environment_database_password:
+        type: str
+        default: ""
+        sensitive: true
+        description: "Database password (optional)"
+        needs: "environment_enabled=true"
+      environment_jwt_secret:
+        type: str
+        default: ""
+        sensitive: true
+        autogenerated: true
+        description: "JWT secret for authentication (auto-generated if empty)"
+        needs: "environment_enabled=true"
+      environment_log_level:
+        type: enum
+        default: "info"
+        options: ["debug", "info", "warn", "error"]
+        description: "Log level"
+        needs: "environment_enabled=true"

+ 0 - 0
library/compose/n8n-server/compose.yaml.j2 → library/compose/n8n/compose.yaml.j2


+ 1 - 1
library/compose/n8n-server/template.yaml → library/compose/n8n/template.yaml

@@ -2,7 +2,7 @@
 kind: compose
 schema: "1.2"
 metadata:
-  name: N8N Server
+  name: N8N
   description: |
     N8n is a free and source-available workflow automation tool. It enables you to connect
     various apps and services to automate repetitive tasks without coding.

+ 418 - 0
library/compose/netbox/compose.yaml.j2

@@ -0,0 +1,418 @@
+services:
+  {{ service_name }}:
+    image: docker.io/netboxcommunity/netbox:v4.2.3
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    depends_on:
+      - {{ service_name }}-postgres
+      - {{ service_name }}-redis
+      - {{ service_name }}-redis-cache
+    environment:
+      - TZ={{ container_timezone }}
+      - CORS_ORIGIN_ALLOW_ALL={{ netbox_cors_enabled }}
+      {% if netbox_cors_enabled %}
+      - CORS_ORIGIN_WHITELIST={{ netbox_cors_origins }}
+      {% endif %}
+      - DB_HOST={{ service_name }}-postgres
+      - DB_NAME={{ database_name }}
+      - DB_USER={{ database_user }}
+      - DB_PASSWORD=${DATABASE_PASSWORD}
+      - REDIS_HOST={{ service_name }}-redis
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+      - REDIS_CACHE_HOST={{ service_name }}-redis-cache
+      - REDIS_CACHE_PASSWORD=${REDIS_PASSWORD}
+      - SECRET_KEY=${NETBOX_SECRET_KEY}
+      - SKIP_SUPERUSER=true
+      {% if netbox_metrics_enabled %}
+      - METRICS_ENABLED=true
+      {% endif %}
+      {% if email_enabled %}
+      - EMAIL_SERVER={{ email_server }}
+      - EMAIL_PORT={{ email_port }}
+      - EMAIL_FROM={{ email_from }}
+      - EMAIL_USERNAME={{ email_username }}
+      - EMAIL_PASSWORD=${EMAIL_PASSWORD}
+      - EMAIL_USE_SSL={{ email_use_ssl }}
+      - EMAIL_USE_TLS={{ email_use_tls }}
+      {% endif %}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if not traefik_enabled and network_mode == 'bridge' %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 8080
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:8080"
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/opt/netbox/netbox/media
+      - {{ volume_mount_path }}/reports:/opt/netbox/netbox/reports
+      - {{ volume_mount_path }}/scripts:/opt/netbox/netbox/scripts
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-media:/opt/netbox/netbox/media
+      - {{ service_name }}-reports:/opt/netbox/netbox/reports
+      - {{ service_name }}-scripts:/opt/netbox/netbox/scripts
+      {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}.loadBalancer.server.port=8080
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: {{ swarm_placement_mode }}
+      {% if swarm_placement_mode == 'replicated' %}
+      replicas: {{ swarm_replicas }}
+      {% endif %}
+      {% if swarm_placement_host %}
+      placement:
+        constraints:
+          - node.hostname == {{ swarm_placement_host }}
+      {% endif %}
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}.loadBalancer.server.port=8080
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+
+  {{ service_name }}-worker:
+    image: docker.io/netboxcommunity/netbox:v4.2.3
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-worker
+    {% endif %}
+    command:
+      - /opt/netbox/venv/bin/python
+      - /opt/netbox/netbox/manage.py
+      - rqworker
+    depends_on:
+      - {{ service_name }}
+      - {{ service_name }}-postgres
+      - {{ service_name }}-redis
+    environment:
+      - TZ={{ container_timezone }}
+      - DB_HOST={{ service_name }}-postgres
+      - DB_NAME={{ database_name }}
+      - DB_USER={{ database_user }}
+      - DB_PASSWORD=${DATABASE_PASSWORD}
+      - REDIS_HOST={{ service_name }}-redis
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+      - REDIS_CACHE_HOST={{ service_name }}-redis-cache
+      - REDIS_CACHE_PASSWORD=${REDIS_PASSWORD}
+      - SECRET_KEY=${NETBOX_SECRET_KEY}
+      {% if email_enabled %}
+      - EMAIL_SERVER={{ email_server }}
+      - EMAIL_PORT={{ email_port }}
+      - EMAIL_FROM={{ email_from }}
+      - EMAIL_USERNAME={{ email_username }}
+      - EMAIL_PASSWORD=${EMAIL_PASSWORD}
+      - EMAIL_USE_SSL={{ email_use_ssl }}
+      - EMAIL_USE_TLS={{ email_use_tls }}
+      {% endif %}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/opt/netbox/netbox/media
+      - {{ volume_mount_path }}/reports:/opt/netbox/netbox/reports
+      - {{ volume_mount_path }}/scripts:/opt/netbox/netbox/scripts
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-media:/opt/netbox/netbox/media
+      - {{ service_name }}-reports:/opt/netbox/netbox/reports
+      - {{ service_name }}-scripts:/opt/netbox/netbox/scripts
+      {% endif %}
+
+  {{ service_name }}-housekeeping:
+    image: docker.io/netboxcommunity/netbox:v4.2.3
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-housekeeping
+    {% endif %}
+    command:
+      - /opt/netbox/housekeeping.sh
+    depends_on:
+      - {{ service_name }}
+      - {{ service_name }}-postgres
+      - {{ service_name }}-redis
+    environment:
+      - TZ={{ container_timezone }}
+      - DB_HOST={{ service_name }}-postgres
+      - DB_NAME={{ database_name }}
+      - DB_USER={{ database_user }}
+      - DB_PASSWORD=${DATABASE_PASSWORD}
+      - REDIS_HOST={{ service_name }}-redis
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+      - REDIS_CACHE_HOST={{ service_name }}-redis-cache
+      - REDIS_CACHE_PASSWORD=${REDIS_PASSWORD}
+      - SECRET_KEY=${NETBOX_SECRET_KEY}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/media:/opt/netbox/netbox/media
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-media:/opt/netbox/netbox/media
+      {% endif %}
+
+  {{ service_name }}-redis:
+    image: docker.io/library/redis:7.4-alpine
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-redis
+    {% endif %}
+    command:
+      - sh
+      - -c
+      - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD
+    environment:
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/redis:/data
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-redis:/data
+      {% endif %}
+
+  {{ service_name }}-redis-cache:
+    image: docker.io/library/redis:7.4-alpine
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-redis-cache
+    {% endif %}
+    command:
+      - sh
+      - -c
+      - redis-server --requirepass $$REDIS_PASSWORD
+    environment:
+      - REDIS_PASSWORD=${REDIS_PASSWORD}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/redis-cache:/data
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-redis-cache:/data
+      {% endif %}
+
+  {% if not database_external %}
+  {{ service_name }}-postgres:
+    image: docker.io/library/postgres:17.2-alpine
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ service_name }}-postgres
+    {% endif %}
+    environment:
+      - TZ={{ container_timezone }}
+      - POSTGRES_USER={{ database_user }}
+      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
+      - POSTGRES_DB={{ database_name }}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/postgres:/var/lib/postgresql/data
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-postgres:/var/lib/postgresql/data
+      {% endif %}
+  {% endif %}
+
+{% if volume_mode == 'local' %}
+volumes:
+  {% if not database_external %}
+  {{ service_name }}-postgres:
+    driver: local
+  {% endif %}
+  {{ service_name }}-redis:
+    driver: local
+  {{ service_name }}-redis-cache:
+    driver: local
+  {{ service_name }}-media:
+    driver: local
+  {{ service_name }}-reports:
+    driver: local
+  {{ service_name }}-scripts:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {% if not database_external %}
+  {{ service_name }}-postgres:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/postgres"
+  {% endif %}
+  {{ service_name }}-redis:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/redis"
+  {{ service_name }}-redis-cache:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/redis-cache"
+  {{ service_name }}-media:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/media"
+  {{ service_name }}-reports:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/reports"
+  {{ service_name }}-scripts:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/scripts"
+{% endif %}
+
+{% if network_mode != 'host' %}
+networks:
+  {{ network_name }}:
+    {% if network_external %}
+    external: true
+    {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    name: {{ network_name }}
+    {% elif swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 136 - 0
library/compose/netbox/template.yaml

@@ -0,0 +1,136 @@
+---
+kind: compose
+schema: "1.2"
+metadata:
+  name: NetBox
+  description: |
+    Network infrastructure management (IPAM/DCIM) and network automation source of truth.
+    Provides comprehensive API for managing IP addresses, circuits, devices, racks, cables,
+    and other network infrastructure components with powerful automation capabilities.
+    ## References
+    * **Project:** https://netbox.dev/
+    * **Documentation:** https://docs.netbox.dev/
+    * **GitHub:** https://github.com/netbox-community/netbox
+  version: 4.2.3
+  author: Christian Lempa
+  date: '2025-11-13'
+  tags:
+    - traefik
+    - netbox
+    - ipam
+    - dcim
+  next_steps: |
+    ### 1. Start NetBox
+    ```bash
+    docker compose up -d
+    ```
+    ### 2. Wait for Initialization (2-5 minutes)
+    ```bash
+    docker compose logs -f {{ service_name }}
+    ```
+    ### 3. Create Superuser Account
+    ```bash
+    docker compose exec {{ service_name }} /opt/netbox/netbox/manage.py createsuperuser
+    ```
+    ### 4. Access the Web Interface
+    {% if traefik_enabled -%}
+    * Navigate to: **https://{{ traefik_host }}.{{ traefik_domain }}**
+    {% else -%}
+    * Navigate to: **http://localhost:{{ ports_http }}**
+    {% endif -%}
+    ### 5. Initial Configuration
+    * Complete the setup wizard
+    * Configure your network infrastructure (sites, racks, devices)
+    * Set up IPAM (IP ranges, VLANs, prefixes)
+    * Create user accounts and assign permissions
+    ### 6. Security Recommendations
+    * Enable two-factor authentication for admin accounts
+    * Configure LDAP/SSO for user authentication (if needed)
+    * Set up regular database backups
+    * Review and customize user permissions
+    * Configure change logging and webhooks
+spec:
+  general:
+    vars:
+      service_name:
+        default: netbox
+      container_name:
+        default: netbox
+      container_hostname:
+        default: netbox
+  database:
+    required: true
+    vars:
+      database_name:
+        default: netbox
+      database_user:
+        default: netbox
+  redis:
+    title: Redis Configuration
+    description: Configure Redis for caching and task queuing
+    required: true
+    vars:
+      redis_password:
+        description: Redis password for authentication
+        type: str
+        sensitive: true
+        autogenerated: true
+  ports:
+    vars:
+      ports_http:
+        description: Host port for HTTP
+        type: int
+        default: 8000
+  traefik:
+    vars:
+      traefik_host:
+        default: netbox
+  netbox:
+    title: NetBox Configuration
+    description: Configure NetBox application settings
+    required: true
+    vars:
+      netbox_secret_key:
+        description: Secret Key
+        extra: Used for cryptographic signing and session management
+        type: str
+        sensitive: true
+        autogenerated: true
+      netbox_allowed_hosts:
+        description: Allowed hosts (space-separated)
+        extra: Add your domain names or IP addresses
+        type: str
+        default: "*"
+      netbox_cors_enabled:
+        description: Enable CORS (Cross-Origin Resource Sharing)
+        type: bool
+        default: false
+      netbox_cors_origins:
+        description: Allowed CORS origins
+        needs: netbox_cors_enabled=true
+        type: str
+        default: "https://example.com"
+      netbox_metrics_enabled:
+        description: Enable Prometheus metrics endpoint
+        type: bool
+        default: false
+      netbox_webhooks_enabled:
+        description: Enable webhook support
+        type: bool
+        default: true
+      netbox_changelog_retention:
+        description: Days to retain change log entries (0 = forever)
+        type: int
+        default: 90
+      netbox_jobresult_retention:
+        description: Days to retain job result entries (0 = forever)
+        type: int
+        default: 90
+  email:
+    vars:
+      email_server:
+        default: localhost
+      email_port:
+        default: 25
+      email_from:
+        default: netbox@example.com

+ 158 - 0
library/compose/pangolin/compose.yaml.j2

@@ -0,0 +1,158 @@
+---
+services:
+  {{ service_name }}:
+    image: docker.io/fosrl/pangolin:latest
+    {% if not swarm_enabled %}
+    restart: {{ restart_policy }}
+    container_name: {{ container_name }}
+    {% endif %}
+    hostname: {{ container_hostname }}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
+    networks:
+      {% if traefik_enabled %}
+      {{ traefik_network }}:
+      {% endif %}
+      {% if network_mode == 'macvlan' %}
+      {{ network_name }}:
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+      {% elif network_mode == 'bridge' %}
+      {{ network_name }}:
+      {% endif %}
+    {% endif %}
+    {% if network_mode == 'bridge' and not traefik_enabled %}
+    ports:
+      {% if swarm_enabled %}
+      - target: 8080
+        published: {{ ports_http }}
+        protocol: tcp
+        mode: host
+      {% else %}
+      - "{{ ports_http }}:8080/tcp"
+      {% endif %}
+    {% endif %}
+    {% if environment_enabled or postgres_enabled %}
+    environment:
+      {% if postgres_enabled %}
+      POSTGRES_CONNECTION_STRING: "{{ postgres_connection_string }}"
+      {% endif %}
+      {% if environment_enabled %}
+      {% if environment_log_level %}
+      LOG_LEVEL: "{{ environment_log_level }}"
+      {% endif %}
+      {% if environment_crowdsec_enabled %}
+      CROWDSEC_ENABLED: "true"
+      {% endif %}
+      {% endif %}
+    {% endif %}
+    volumes:
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/data:/app/data:rw
+      - {{ volume_mount_path }}/config:/app/config:rw
+      {% elif volume_mode in ['local', 'nfs'] %}
+      - {{ service_name }}-data:/app/data
+      - {{ service_name }}-config:/app/config
+      {% endif %}
+    {% if swarm_enabled or resources_enabled %}
+    deploy:
+      {% if swarm_enabled %}
+      mode: replicated
+      replicas: 1
+      restart_policy:
+        condition: on-failure
+      {% endif %}
+      {% if resources_enabled %}
+      resources:
+        limits:
+          cpus: '{{ resources_cpu_limit }}'
+          memory: {{ resources_memory_limit }}
+        {% if swarm_enabled %}
+        reservations:
+          cpus: '{{ resources_cpu_reservation }}'
+          memory: {{ resources_memory_reservation }}
+        {% endif %}
+      {% endif %}
+      {% if swarm_enabled and traefik_enabled %}
+      labels:
+        - traefik.enable=true
+        - traefik.docker.network={{ traefik_network }}
+        - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=8080
+        - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+        {% if traefik_tls_enabled %}
+        - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+        - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+        - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+        - traefik.http.routers.{{ service_name }}-https.tls=true
+        - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+        {% endif %}
+      {% endif %}
+    {% endif %}
+    {% if traefik_enabled and not swarm_enabled %}
+    labels:
+      - traefik.enable=true
+      - traefik.docker.network={{ traefik_network }}
+      - traefik.http.services.{{ service_name }}-web.loadBalancer.server.port=8080
+      - traefik.http.routers.{{ service_name }}-http.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-http.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-http.entrypoints={{ traefik_entrypoint }}
+      {% if traefik_tls_enabled %}
+      - traefik.http.routers.{{ service_name }}-https.service={{ service_name }}-web
+      - traefik.http.routers.{{ service_name }}-https.rule=Host(`{{ traefik_host }}.{{ traefik_domain }}`)
+      - traefik.http.routers.{{ service_name }}-https.entrypoints={{ traefik_tls_entrypoint }}
+      - traefik.http.routers.{{ service_name }}-https.tls=true
+      - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
+      {% endif %}
+    {% endif %}
+
+{% if volume_mode == 'local' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+  {{ service_name }}-config:
+    driver: local
+{% elif volume_mode == 'nfs' %}
+volumes:
+  {{ service_name }}-data:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/data"
+  {{ service_name }}-config:
+    driver: local
+    driver_opts:
+      type: nfs
+      o: addr={{ volume_nfs_server }},{{ volume_nfs_options }}
+      device: ":{{ volume_nfs_path }}/config"
+{% endif %}
+
+{% if network_mode != 'host' %}
+networks:
+  {{ network_name }}:
+    {% if network_external %}
+    external: true
+    {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    name: {{ network_name }}
+    {% elif swarm_enabled %}
+    driver: overlay
+    attachable: true
+    {% else %}
+    driver: bridge
+    {% endif %}
+    {% endif %}
+  {% if traefik_enabled %}
+  {{ traefik_network }}:
+    external: true
+  {% endif %}
+{% endif %}

+ 113 - 0
library/compose/pangolin/template.yaml

@@ -0,0 +1,113 @@
+---
+kind: compose
+schema: "1.2"
+metadata:
+  name: Pangolin
+  description: |
+    Self-hosted reverse proxy server that securely exposes private resources on distributed networks through
+    encrypted WireGuard tunnels. Pangolin enables access from anywhere without opening ports, using a custom
+    user-space WireGuard client (Newt) for secure connectivity. Features include automatic tunnel management,
+    integrated CrowdSec security, and support for both PostgreSQL and SQLite databases.
+    ## References
+    * **Project:** https://github.com/fosrl/pangolin
+    * **Documentation:** https://github.com/fosrl/pangolin/blob/main/README.md
+    * **Docker Hub:** https://hub.docker.com/r/fosrl/pangolin
+  version: latest
+  author: Christian Lempa
+  date: '2025-11-13'
+  tags:
+    - traefik
+    - swarm
+    - proxy
+    - wireguard
+  next_steps: |
+    ### 1. Configure Database
+    {% if postgres_enabled -%}
+    Make sure PostgreSQL is running and accessible at:
+    * Connection string: {{ postgres_connection_string }}
+    {% else -%}
+    Pangolin will use SQLite database stored in the data volume.
+    {% endif -%}
+    ### 2. Deploy the Service
+    {% if swarm_enabled -%}
+    Deploy to Docker Swarm:
+    ```bash
+    docker stack deploy -c compose.yaml pangolin
+    ```
+    {% else -%}
+    Start Pangolin using Docker Compose:
+    ```bash
+    docker compose up -d
+    ```
+    {% endif -%}
+    ### 3. Access the Web Interface
+    {% if traefik_enabled -%}
+    * Navigate to: **https://{{ traefik_host }}.{{ traefik_domain }}**
+    {% else -%}
+    * Navigate to: **http://localhost:{{ ports_http }}**
+    {% endif -%}
+    ### 4. Configure WireGuard Clients
+    * Use the Pangolin web interface to create and manage WireGuard tunnels
+    * Deploy Newt client on remote machines to establish secure connections
+spec:
+  general:
+    vars:
+      service_name:
+        default: "pangolin"
+      container_name:
+        default: "pangolin"
+      container_hostname:
+        default: "pangolin"
+  traefik:
+    vars:
+      traefik_host:
+        default: "pangolin"
+  network:
+    vars:
+      network_name:
+        default: "pangolin_network"
+  ports:
+    vars:
+      ports_http:
+        description: "External HTTP port (web interface)"
+        type: int
+        default: 8080
+        needs: ["traefik_enabled=false", "network_mode=bridge"]
+  volume:
+    vars:
+      volume_mount_path:
+        default: "/mnt/storage/pangolin"
+  postgres:
+    title: "PostgreSQL Configuration"
+    toggle: postgres_enabled
+    needs: null
+    vars:
+      postgres_enabled:
+        type: bool
+        default: false
+        description: "Use PostgreSQL database (SQLite is default)"
+      postgres_connection_string:
+        type: str
+        default: "postgresql://postgres:postgres@localhost:5432"
+        description: "PostgreSQL connection string"
+        needs: "postgres_enabled=true"
+  environment:
+    title: "Environment Variables"
+    toggle: environment_enabled
+    needs: null
+    vars:
+      environment_enabled:
+        type: bool
+        default: false
+        description: "Configure additional environment variables"
+      environment_crowdsec_enabled:
+        type: bool
+        default: false
+        description: "Enable CrowdSec integration"
+        needs: "environment_enabled=true"
+      environment_log_level:
+        type: enum
+        default: "info"
+        options: ["debug", "info", "warn", "error"]
+        description: "Log level"
+        needs: "environment_enabled=true"

+ 113 - 0
library/helm/netbox/template.yaml

@@ -0,0 +1,113 @@
+---
+kind: helm
+schema: "1.0"
+metadata:
+  name: NetBox
+  description: |
+    Helm values template for NetBox, an open-source network infrastructure management (IPAM/DCIM)
+    solution and network automation source of truth.
+    ## Chart Information
+    * **Chart Repository:** https://charts.boot source.github.io/charts
+    * **Chart Name:** netbox
+    * **Chart Version:** Compatible with NetBox 4.2.3
+    ## References
+    * **Project:** https://netbox.dev/
+    * **Documentation:** https://docs.netbox.dev/
+    * **GitHub:** https://github.com/netbox-community/netbox
+  version: 4.2.3
+  author: Christian Lempa
+  date: '2025-11-13'
+spec:
+  general:
+    vars:
+      release_name:
+        default: netbox
+      namespace:
+        default: netbox
+  database:
+    vars:
+      database_enabled:
+        default: true
+      database_type:
+        default: postgres
+      database_host:
+        default: netbox-postgresql
+      database_port:
+        default: 5432
+      database_name:
+        default: netbox
+      database_user:
+        default: netbox
+  redis:
+    title: Redis Configuration
+    description: Configure Redis for caching and task queuing
+    required: true
+    vars:
+      redis_enabled:
+        description: Enable Redis deployment
+        type: bool
+        default: true
+      redis_host:
+        description: Redis host for tasks
+        type: hostname
+        default: netbox-redis-master
+      redis_cache_host:
+        description: Redis host for caching
+        type: hostname
+        default: netbox-redis-master
+      redis_password:
+        description: Redis password
+        type: str
+        sensitive: true
+        autogenerated: true
+  traefik:
+    vars:
+      traefik_host:
+        default: netbox.home.arpa
+  netbox:
+    title: NetBox Configuration
+    description: Configure NetBox application settings
+    required: true
+    vars:
+      netbox_secret_key:
+        description: Secret Key
+        extra: Used for cryptographic signing and session management
+        type: str
+        sensitive: true
+        autogenerated: true
+      netbox_superuser_name:
+        description: Initial superuser username
+        type: str
+        default: admin
+      netbox_superuser_email:
+        description: Initial superuser email
+        type: email
+        default: admin@example.com
+      netbox_superuser_password:
+        description: Initial superuser password
+        type: str
+        sensitive: true
+        autogenerated: true
+      netbox_superuser_api_token:
+        description: Initial superuser API token
+        type: str
+        sensitive: true
+        autogenerated: true
+      netbox_allowed_hosts:
+        description: Allowed hosts (comma-separated)
+        extra: Add your domain names or IP addresses
+        type: str
+        default: "*"
+      netbox_metrics_enabled:
+        description: Enable Prometheus metrics endpoint
+        type: bool
+        default: false
+      netbox_cors_enabled:
+        description: Enable CORS (Cross-Origin Resource Sharing)
+        type: bool
+        default: false
+      netbox_cors_origins:
+        description: Allowed CORS origins
+        needs: netbox_cors_enabled=true
+        type: str
+        default: "https://example.com"

+ 130 - 0
library/helm/netbox/values.yaml.j2

@@ -0,0 +1,130 @@
+---
+image:
+  repository: docker.io/netboxcommunity/netbox
+  tag: v4.2.3
+  pullPolicy: IfNotPresent
+
+replicaCount: 1
+
+superuser:
+  name: {{ netbox_superuser_name }}
+  email: {{ netbox_superuser_email }}
+  password: {{ netbox_superuser_password }}
+  apiToken: {{ netbox_superuser_api_token }}
+
+allowedHosts:
+  - "{{ netbox_allowed_hosts }}"
+
+{% if database_enabled %}
+postgresql:
+  enabled: false
+
+externalDatabase:
+  host: {{ database_host }}
+  port: {{ database_port }}
+  database: {{ database_name }}
+  username: {{ database_user }}
+  existingSecretPasswordKey: "postgresql-password"
+{% else %}
+postgresql:
+  enabled: true
+  auth:
+    database: {{ database_name }}
+    username: {{ database_user }}
+{% endif %}
+
+{% if redis_enabled %}
+redis:
+  enabled: false
+
+externalRedis:
+  host: {{ redis_host }}
+  port: 6379
+  database: 0
+  existingSecretPasswordKey: "redis-password"
+
+tasksRedis:
+  host: {{ redis_host }}
+  port: 6379
+  database: 0
+  existingSecretPasswordKey: "redis-password"
+
+cachingRedis:
+  host: {{ redis_cache_host }}
+  port: 6379
+  database: 1
+  existingSecretPasswordKey: "redis-password"
+{% else %}
+redis:
+  enabled: true
+  architecture: standalone
+  auth:
+    enabled: true
+{% endif %}
+
+secretKey: {{ netbox_secret_key }}
+
+{% if email_enabled %}
+email:
+  server: {{ email_host }}
+  port: {{ email_port }}
+  username: {{ email_username }}
+  from: {{ email_from }}
+  useSSL: {{ email_use_ssl | lower }}
+  useTLS: {{ email_use_tls | lower }}
+  existingSecretPasswordKey: "email-password"
+{% endif %}
+
+{% if netbox_cors_enabled %}
+cors:
+  originAllowAll: true
+  originWhitelist:
+    - {{ netbox_cors_origins }}
+{% endif %}
+
+{% if netbox_metrics_enabled %}
+metrics:
+  enabled: true
+  serviceMonitor:
+    enabled: false
+{% endif %}
+
+service:
+  type: {{ network_mode }}
+
+{% if traefik_enabled %}
+ingress:
+  enabled: true
+  className: traefik
+{% if traefik_tls_enabled and traefik_tls_certmanager %}
+  annotations:
+    cert-manager.io/cluster-issuer: {{ certmanager_issuer }}
+{% endif %}
+  hosts:
+    - host: {{ traefik_host }}
+      paths:
+        - path: /
+          pathType: Prefix
+{% if traefik_tls_enabled %}
+  tls:
+    - secretName: {{ traefik_tls_secret }}
+      hosts:
+        - {{ traefik_host }}
+{% endif %}
+{% endif %}
+
+persistence:
+  enabled: true
+  {% if volumes_mode == 'existing-pvc' %}
+  existingClaim: {{ volumes_pvc_name }}
+  {% else %}
+  storageClass: ""
+  size: 10Gi
+  {% endif %}
+
+worker:
+  enabled: true
+  replicaCount: 1
+
+housekeeping:
+  enabled: true