# Templates\n\nJinja2 and Nickel templates for configuration and deployment generation.\n\n## Purpose\n\nTemplates provide:\n- **Nickel output generation** - Jinja2 templates for TypeDialog nickel-roundtrip\n- **Docker Compose generation** - Infrastructure-as-code for containerized deployment\n- **Kubernetes manifests** - Declarative deployment manifests\n- **TOML export** - Service configuration generation for Rust codebase\n\n## File Organization\n\n```\ntemplates/\n├── README.md                           # This file\n├── orchestrator-config.ncl.j2          # Nickel output template (Jinja2)\n├── control-center-config.ncl.j2        # Nickel output template (Jinja2)\n├── mcp-server-config.ncl.j2            # Nickel output template (Jinja2)\n├── installer-config.ncl.j2             # Nickel output template (Jinja2)\n├── docker-compose/                     # Docker Compose templates\n│   ├── platform-stack.solo.yml.ncl\n│   ├── platform-stack.multiuser.yml.ncl\n│   ├── platform-stack.cicd.yml.ncl\n│   └── platform-stack.enterprise.yml.ncl\n├── kubernetes/                         # Kubernetes templates\n│   ├── orchestrator-deployment.yaml.ncl\n│   ├── orchestrator-service.yaml.ncl\n│   ├── control-center-deployment.yaml.ncl\n│   ├── control-center-service.yaml.ncl\n│   └── platform-ingress.yaml.ncl\n└── configs/                            # Service config templates (optional)\n    ├── orchestrator-config.toml.ncl\n    ├── control-center-config.toml.ncl\n    └── mcp-server-config.toml.ncl\n```\n\n## Jinja2 Config Templates\n\n**Critical for TypeDialog nickel-roundtrip workflow**:\n\n```\ntypedialog-web nickel-roundtrip "$CONFIG" "forms/{service}-form.toml" --output "$CONFIG" --template "templates/{service}-config.ncl.j2"\n```\n\n### Template Pattern: orchestrator-config.ncl.j2\n\n```\n# Orchestrator Configuration - Nickel Format\n# Auto-generated by provisioning TypeDialog\n# Edit via: nu scripts/configure.nu orchestrator {mode}\n\n{\n  orchestrator = {\n    # Workspace Configuration\n    workspace = {\n      {%- if workspace_name %}\n      name = "{{ workspace_name }}",\n      {%- endif %}\n      {%- if workspace_path %}\n      path = "{{ workspace_path }}",\n      {%- endif %}\n      {%- if workspace_enabled is defined %}\n      enabled = {{ workspace_enabled | lower }},\n      {%- endif %}\n      {%- if multi_workspace is defined %}\n      multi_workspace = {{ multi_workspace | lower }},\n      {%- endif %}\n    },\n\n    # Server Configuration\n    server = {\n      {%- if server_host %}\n      host = "{{ server_host }}",\n      {%- endif %}\n      {%- if server_port %}\n      port = {{ server_port }},\n      {%- endif %}\n      {%- if server_workers %}\n      workers = {{ server_workers }},\n      {%- endif %}\n      {%- if server_keep_alive %}\n      keep_alive = {{ server_keep_alive }},\n      {%- endif %}\n    },\n\n    # Storage Configuration\n    storage = {\n      {%- if storage_backend %}\n      backend = '{{ storage_backend }},\n      {%- endif %}\n      {%- if storage_path %}\n      path = "{{ storage_path }}",\n      {%- endif %}\n      {%- if surrealdb_url %}\n      surrealdb_url = "{{ surrealdb_url }}",\n      {%- endif %}\n    },\n\n    # Queue Configuration\n    queue = {\n      {%- if max_concurrent_tasks %}\n      max_concurrent_tasks = {{ max_concurrent_tasks }},\n      {%- endif %}\n      {%- if retry_attempts %}\n      retry_attempts = {{ retry_attempts }},\n      {%- endif %}\n      {%- if retry_delay %}\n      retry_delay = {{ retry_delay }},\n      {%- endif %}\n      {%- if task_timeout %}\n      task_timeout = {{ task_timeout }},\n      {%- endif %}\n    },\n\n    # Monitoring Configuration (optional)\n    {%- if enable_monitoring is defined and enable_monitoring %}\n    monitoring = {\n      enabled = true,\n      {%- if metrics_interval %}\n      metrics_interval = {{ metrics_interval }},\n      {%- endif %}\n      {%- if health_check_interval %}\n      health_check_interval = {{ health_check_interval }},\n      {%- endif %}\n    },\n    {%- endif %}\n  },\n}\n```\n\n### Key Jinja2 Patterns\n\n**Conditional blocks** (only include if field is set):\n\n```\n{%- if workspace_name %}\nname = "{{ workspace_name }}",\n{%- endif %}\n```\n\n**String values** (with quotes):\n\n```\n{%- if storage_backend %}\nbackend = '{{ storage_backend }},  # Enum (atom syntax)\n{%- endif %}\n```\n\n**Numeric values** (no quotes):\n\n```\n{%- if server_port %}\nport = {{ server_port }},  # Number\n{%- endif %}\n```\n\n**Boolean values** (lower case):\n\n```\n{%- if workspace_enabled is defined %}\nenabled = {{ workspace_enabled | lower }},  # Boolean (true/false)\n{%- endif %}\n```\n\n**Comments** (for generated files):\n\n```\n# Auto-generated by provisioning TypeDialog\n# Edit via: nu scripts/configure.nu orchestrator {mode}\n```\n\n## Docker Compose Templates\n\nNickel templates that import from `values/*.ncl`:\n\n```\n# templates/docker-compose/platform-stack.solo.yml.ncl\n# Docker Compose Platform Stack - Solo Mode\n# Imports config from values/orchestrator.solo.ncl\n\nlet orchestrator_config = import "../../values/orchestrator.solo.ncl" in\nlet control_center_config = import "../../values/control-center.solo.ncl" in\n\n{\n  version = "3.8",\n  services = {\n    orchestrator = {\n      image = "provisioning-orchestrator:latest",\n      container_name = "orchestrator",\n      ports = [\n        "%{std.to_string orchestrator_config.orchestrator.server.port}:9090",\n      ],\n      environment = {\n        ORCHESTRATOR_SERVER_HOST = orchestrator_config.orchestrator.server.host,\n        ORCHESTRATOR_SERVER_PORT = std.to_string orchestrator_config.orchestrator.server.port,\n        ORCHESTRATOR_STORAGE_BACKEND = orchestrator_config.orchestrator.storage.backend,\n      },\n      volumes = [\n        "./data/orchestrator:%{orchestrator_config.orchestrator.storage.path}",\n      ],\n      restart = "unless-stopped",\n    },\n    control-center = {\n      image = "provisioning-control-center:latest",\n      container_name = "control-center",\n      ports = [\n        "%{std.to_string control_center_config.control_center.server.port}:8080",\n      ],\n      environment = {\n        CONTROL_CENTER_SERVER_HOST = control_center_config.control_center.server.host,\n        CONTROL_CENTER_SERVER_PORT = std.to_string control_center_config.control_center.server.port,\n      },\n      restart = "unless-stopped",\n    },\n  },\n}\n```\n\n### Rendering Docker Compose\n\n```\n# Export Nickel template to YAML\nnickel export --format json templates/docker-compose/platform-stack.solo.yml.ncl | yq -P > docker-compose.solo.yml\n```\n\n## Kubernetes Templates\n\nNickel templates for Kubernetes manifests:\n\n```\n# templates/kubernetes/orchestrator-deployment.yaml.ncl\nlet config = import "../../values/orchestrator.solo.ncl" in\n\n{\n  apiVersion = "apps/v1",\n  kind = "Deployment",\n  metadata = {\n    name = "orchestrator",\n    labels = {\n      app = "orchestrator",\n    },\n  },\n  spec = {\n    replicas = 1,\n    selector = {\n      matchLabels = {\n        app = "orchestrator",\n      },\n    },\n    template = {\n      metadata = {\n        labels = {\n          app = "orchestrator",\n        },\n      },\n      spec = {\n        containers = [\n          {\n            name = "orchestrator",\n            image = "provisioning-orchestrator:latest",\n            ports = [\n              {\n                containerPort = 9090,\n              },\n            ],\n            env = [\n              {\n                name = "ORCHESTRATOR_SERVER_PORT",\n                value = std.to_string config.orchestrator.server.port,\n              },\n              {\n                name = "ORCHESTRATOR_STORAGE_BACKEND",\n                value = config.orchestrator.storage.backend,\n              },\n            ],\n            volumeMounts = [\n              {\n                name = "data",\n                mountPath = config.orchestrator.storage.path,\n              },\n            ],\n          },\n        ],\n        volumes = [\n          {\n            name = "data",\n            persistentVolumeClaim = {\n              claimName = "orchestrator-pvc",\n            },\n          },\n        ],\n      },\n    },\n  },\n}\n```\n\n## Rendering Templates\n\n### Render to JSON\n\n```\nnickel export --format json templates/orchestrator-config.ncl.j2 > config.json\n```\n\n### Render to YAML (via yq)\n\n```\nnickel export --format json templates/kubernetes/orchestrator-deployment.yaml.ncl | yq -P > deployment.yaml\n```\n\n### Render to TOML\n\n```\nnickel export --format toml templates/configs/orchestrator-config.toml.ncl > config.toml\n```\n\n## Template Variables\n\nVariables in templates come from:\n1. **Form values** (TypeDialog input)\n2. **Imported configs** (Nickel imports)\n3. **Constraint interpolation** (constraints.toml)\n\n## Best Practices\n\n1. **Use conditional blocks** - Only include fields if set\n2. **Import configs** - Reuse Nickel configs in templates\n3. **Type conversion** - Use `std.to_string` for numeric values\n4. **Comments** - Explain generated/auto-edited markers\n5. **Validation** - Use `nickel typecheck` to verify templates\n6. **Environment variables** - Prefer env over hardcoding\n\n## Template Testing\n\n```\n# Typecheck Jinja2 + Nickel template\nnickel typecheck templates/orchestrator-config.ncl.j2\n\n# Evaluate and view output\nnickel eval templates/orchestrator-config.ncl.j2\n\n# Export and validate output\nnickel export --format json templates/orchestrator-config.ncl.j2 | jq '.'\n```\n\n## Adding a New Template\n\n1. **Create template file** (`{service}-config.ncl.j2` or `{name}.yml.ncl`)\n2. **Define structure** (Nickel or Jinja2)\n3. **Import configs** (if Nickel)\n4. **Use variables** (from forms or imports)\n5. **Typecheck**: `nickel typecheck templates/{file}`\n6. **Test rendering**: `nickel export {format} templates/{file}`\n\n---\n\n**Version**: 1.0.0\n**Last Updated**: 2025-01-05
