Bladeren bron

chore: remove temporary migration helper files

- Deleted check_toggles.py
- Deleted fix_all_compose.py
- Deleted fix_all_templates.py
- Deleted fix_compose_templates.py
- Deleted COMPOSE_TEMPLATES_FIX_SUMMARY.md

These files were used during the schema-to-template migration process and are no longer needed.
xcad 6 maanden geleden
bovenliggende
commit
c499424a58
5 gewijzigde bestanden met toevoegingen van 0 en 1228 verwijderingen
  1. 0 262
      COMPOSE_TEMPLATES_FIX_SUMMARY.md
  2. 0 107
      check_toggles.py
  3. 0 316
      fix_all_compose.py
  4. 0 287
      fix_all_templates.py
  5. 0 256
      fix_compose_templates.py

+ 0 - 262
COMPOSE_TEMPLATES_FIX_SUMMARY.md

@@ -1,262 +0,0 @@
-# Compose Templates Schema Fix Summary
-
-## Overview
-Successfully fixed **ALL 34 compose templates** by adding **109 missing schema variables** across 27 templates to achieve full v1.2 schema compliance.
-
-## Final Status
-✅ **All 34/34 templates validated successfully!**
-
----
-
-## Schema v1.2 Sections Implemented
-
-### GENERAL Section
-- `service_name`, `container_name`, `container_hostname`
-- `container_timezone`, `user_uid`, `user_gid`
-- `container_loglevel`, `restart_policy`
-
-### NETWORK Section
-- `network_mode`, `network_name`, `network_external`
-- `network_macvlan_ipv4_address`, `network_macvlan_parent_interface`
-- `network_macvlan_subnet`, `network_macvlan_gateway`
-
-### PORTS Section
-- `ports_http`, `ports_https`, `ports_ssh`
-- `ports_dns`, `ports_dhcp`, `ports_smtp`, `ports_snmp`
-
-### TRAEFIK Section (toggle: traefik_enabled)
-- `traefik_enabled`, `traefik_network`
-- `traefik_host`, `traefik_domain`
-
-### TRAEFIK_TLS Section (toggle: traefik_tls_enabled)
-- `traefik_tls_enabled`, `traefik_tls_certresolver`
-
-### VOLUME Section (toggle: volume_mode)
-- `volume_mode`, `volume_mount_path`
-- `volume_nfs_server`, `volume_nfs_path`, `volume_nfs_options`
-
-### RESOURCES Section
-- `resources_cpu_limit`, `resources_memory_limit`
-
-### SWARM Section (toggle: swarm_enabled)
-- `swarm_enabled`, `swarm_replicas`
-- `swarm_placement_mode`, `swarm_placement_host`
-
-### DATABASE Section (toggle: database_enabled)
-- `database_enabled`, `database_type`, `database_external`
-- `database_host`, `database_port`
-- `database_name`, `database_user`, `database_password`
-
-### EMAIL Section (toggle: email_enabled)
-- `email_enabled`, `email_host`, `email_port`
-- `email_username`, `email_password`
-- `email_from`, `email_encryption`
-
-### AUTHENTIK Section (toggle: authentik_enabled)
-- `authentik_enabled`, `authentik_url`
-- `authentik_slug`, `authentik_client_id`, `authentik_client_secret`
-
----
-
-## Templates Fixed (27/34)
-
-### adguardhome (3 variables added)
-- **[network]**: network_external
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### authentik (5 variables added)
-- **[database]**: database_external
-- **[email]**: email_enabled, email_encryption
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### bind9 (2 variables added)
-- **[network]**: network_external, network_mode
-
-### checkmk (2 variables added)
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### dockge (2 variables added)
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### gitea (3 variables added)
-- **[database]**: database_external
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### gitlab (5 variables added)
-- **[swarm]**: swarm_enabled, swarm_placement_mode, swarm_replicas
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### grafana (8 variables added)
-- **[authentik]**: authentik_enabled
-- **[database]**: database_external
-- **[swarm]**: swarm_enabled, swarm_placement_host, swarm_placement_mode, swarm_replicas
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### homepage (7 variables added)
-- **[swarm]**: swarm_enabled, swarm_placement_mode
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-- **[volume]**: volume_nfs_options, volume_nfs_path, volume_nfs_server
-
-### homer (8 variables added)
-- **[authentik]**: authentik_client_id, authentik_client_secret, authentik_enabled, authentik_slug, authentik_url
-- **[swarm]**: swarm_enabled
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### influxdb (2 variables added)
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### komodo (9 variables added)
-- **[network]**: network_external, network_mode
-- **[swarm]**: swarm_enabled, swarm_placement_host, swarm_placement_mode, swarm_replicas
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-- **[volume]**: volume_mode
-
-### loki (2 variables added)
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### n8n (4 variables added)
-- **[database]**: database_external
-- **[network]**: network_external
-- **[swarm]**: swarm_placement_mode
-- **[traefik_tls]**: traefik_tls_enabled
-
-### netbox (5 variables added)
-- **[database]**: database_external
-- **[email]**: email_enabled, email_encryption
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### nextcloud (5 variables added)
-- **[swarm]**: swarm_enabled, swarm_placement_host, swarm_placement_mode
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### nginx (2 variables added)
-- **[swarm]**: swarm_placement_host, swarm_placement_mode
-
-### openwebui (4 variables added)
-- **[authentik]**: authentik_slug, authentik_url
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### pangolin (9 variables added)
-- **[network]**: network_external, network_mode
-- **[swarm]**: swarm_enabled, swarm_placement_host, swarm_placement_mode, swarm_replicas
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-- **[volume]**: volume_mode
-
-### pihole (5 variables added)
-- **[network]**: network_external
-- **[swarm]**: swarm_enabled, swarm_placement_mode
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### portainer (5 variables added)
-- **[swarm]**: swarm_enabled, swarm_placement_mode, swarm_replicas
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### postgres (4 variables added)
-- **[network]**: network_external
-- **[swarm]**: swarm_enabled, swarm_placement_host, swarm_placement_mode
-
-### prometheus (2 variables added)
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### semaphoreui (3 variables added)
-- **[database]**: database_external
-- **[traefik]**: traefik_enabled
-- **[traefik_tls]**: traefik_tls_enabled
-
-### traefik (1 variable added)
-- **[swarm]**: swarm_enabled
-
-### twingate-connector (2 variables added)
-- **[swarm]**: swarm_enabled, swarm_placement_mode
-
----
-
-## Templates Already Complete (8/34)
-
-These templates had all schema variables already defined:
-
-1. **alloy** - Complete
-2. **gitlab-runner** - Complete
-3. **homeassistant** - Complete
-4. **mariadb** - Complete
-5. **passbolt** - Complete
-6. **renovate** - Complete
-7. **uptimekuma** - Complete
-8. **whoami** - Complete
-
----
-
-## Key Improvements
-
-### Toggle Variable Sections
-When a toggle variable (like `traefik_enabled`) is used in a compose.yaml.j2, the entire section with all related variables is now added:
-
-- **traefik_enabled** → All traefik variables added
-- **traefik_tls_enabled** → All traefik_tls variables added
-- **volume_mode** → All volume variables added
-- **swarm_enabled** → All swarm variables added
-- **database_enabled** → All database variables added
-- **email_enabled** → All email variables added
-- **authentik_enabled** → All authentik variables added
-
-### Consistency
-All 34 templates now follow the same schema v1.2 structure with proper:
-- Variable types (str, int, bool, enum)
-- Default values
-- Required flags
-- Descriptions
-- Options for enum types
-
-### Validation Ready
-All templates can now be validated against the schema without missing variable errors.
-
----
-
-## Statistics
-
-- **Total templates**: 34
-- **Templates fixed**: 27
-- **Templates already complete**: 7
-- **Total variables added**: 109
-- **Most common additions**: 
-  - traefik_enabled: 22 templates
-  - traefik_tls_enabled: 22 templates
-  - swarm_enabled: 11 templates
-  - network_external: 8 templates
-  - database_external: 6 templates
-
----
-
-## Validation Results
-
-**✅ All 34 templates pass schema validation without errors!**
-
-Each template was tested with:
-```bash
-python3 -m cli compose show <template_name>
-```
-
-All templates validated successfully with no "not defined in spec" errors.
-
----
-
-**Status**: ✅ All 34 compose templates are now complete with full schema v1.2 compliance!

+ 0 - 107
check_toggles.py

@@ -1,107 +0,0 @@
-#!/usr/bin/env python3
-"""
-Comprehensive check for missing toggle variables and their sections
-"""
-import re
-import yaml
-from pathlib import Path
-from collections import defaultdict
-
-def extract_variables(compose_content):
-    """Extract all {{ variable_name }} from compose file"""
-    pattern = r'\{\{\s*(\w+)\s*\}\}'
-    return set(re.findall(pattern, compose_content))
-
-def extract_conditionals(compose_content):
-    """Extract variables used in conditionals {% if var %}"""
-    pattern = r'\{%\s*if\s+(\w+)'
-    return set(re.findall(pattern, compose_content))
-
-def get_existing_variables(template_data):
-    """Get set of existing variables in template spec"""
-    if not template_data or 'spec' not in template_data:
-        return set()
-    
-    existing = set()
-    spec = template_data['spec']
-    
-    for section_name, section_data in spec.items():
-        if isinstance(section_data, dict) and 'vars' in section_data:
-            existing.update(section_data['vars'].keys())
-    
-    return existing
-
-def analyze_template(template_dir):
-    """Analyze template for missing variables"""
-    compose_path = template_dir / "compose.yaml.j2"
-    template_path = template_dir / "template.yaml"
-    
-    if not compose_path.exists() or not template_path.exists():
-        return None
-    
-    # Read files
-    with open(compose_path, 'r') as f:
-        compose_content = f.read()
-    
-    with open(template_path, 'r') as f:
-        template_data = yaml.safe_load(f)
-    
-    # Extract all referenced variables
-    used_vars = extract_variables(compose_content)
-    conditional_vars = extract_conditionals(compose_content)
-    all_used = used_vars | conditional_vars
-    
-    existing_vars = get_existing_variables(template_data)
-    missing_vars = all_used - existing_vars
-    
-    return {
-        'name': template_dir.name,
-        'used_vars': used_vars,
-        'conditional_vars': conditional_vars,
-        'all_used': all_used,
-        'existing_vars': existing_vars,
-        'missing_vars': missing_vars
-    }
-
-def main():
-    base_dir = Path("/Users/xcad/Projects/christianlempa/boilerplates/library/compose")
-    
-    toggle_vars = {
-        'traefik_enabled', 'traefik_tls_enabled', 'volume_mode', 
-        'swarm_enabled', 'database_enabled', 'email_enabled', 
-        'authentik_enabled', 'resources_enabled'
-    }
-    
-    print("="*80)
-    print("CHECKING FOR MISSING TOGGLE VARIABLES")
-    print("="*80)
-    
-    issues = []
-    
-    for template_dir in sorted(base_dir.iterdir()):
-        if template_dir.is_dir():
-            result = analyze_template(template_dir)
-            if result and result['missing_vars']:
-                # Check if any missing vars are toggle variables
-                missing_toggles = result['missing_vars'] & toggle_vars
-                if missing_toggles:
-                    issues.append({
-                        'template': result['name'],
-                        'missing_toggles': missing_toggles,
-                        'all_missing': result['missing_vars']
-                    })
-                    print(f"\n{result['name']}:")
-                    print(f"  Missing toggle vars: {', '.join(sorted(missing_toggles))}")
-                    if result['missing_vars'] - missing_toggles:
-                        print(f"  Other missing vars: {', '.join(sorted(result['missing_vars'] - missing_toggles))}")
-    
-    print(f"\n\n{'='*80}")
-    print(f"SUMMARY: {len(issues)} templates missing toggle variables")
-    print('='*80)
-    
-    if issues:
-        for issue in issues:
-            print(f"  - {issue['template']}: {len(issue['missing_toggles'])} toggle(s), {len(issue['all_missing'])} total missing")
-
-if __name__ == "__main__":
-    main()

+ 0 - 316
fix_all_compose.py

@@ -1,316 +0,0 @@
-#!/usr/bin/env python3
-"""
-Comprehensive script to add ALL missing schema variables to compose templates
-"""
-import re
-import yaml
-from pathlib import Path
-from collections import OrderedDict
-
-# Complete v1.2 schema
-SCHEMA_SECTIONS = OrderedDict([
-    ('general', {
-        'service_name': {'type': 'str', 'required': True, 'description': 'The name of the service'},
-        'container_name': {'type': 'str', 'required': True, 'description': 'The name of the container'},
-        'container_hostname': {'type': 'str', 'required': True, 'description': 'The hostname of the container'},
-        'container_timezone': {'type': 'str', 'default': 'UTC', 'description': 'The timezone for the container'},
-        'user_uid': {'type': 'int', 'default': 1000, 'description': 'The user ID to run the container as'},
-        'user_gid': {'type': 'int', 'default': 1000, 'description': 'The group ID to run the container as'},
-        'container_loglevel': {'type': 'str', 'default': 'INFO', 'description': 'The log level for the container'},
-        'restart_policy': {'type': 'enum', 'options': ['unless-stopped', 'always', 'on-failure', 'no'], 'default': 'unless-stopped', 'required': True, 'description': 'The restart policy for the container'},
-    }),
-    ('network', {
-        'network_mode': {'type': 'str', 'default': 'bridge', 'description': 'The network mode for the container'},
-        'network_name': {'type': 'str', 'default': '', 'description': 'The name of the network'},
-        'network_external': {'type': 'bool', 'default': False, 'description': 'Whether the network is external'},
-        'network_macvlan_ipv4_address': {'type': 'str', 'default': '', 'description': 'The IPv4 address for macvlan network'},
-        'network_macvlan_parent_interface': {'type': 'str', 'default': '', 'description': 'The parent interface for macvlan network'},
-        'network_macvlan_subnet': {'type': 'str', 'default': '', 'description': 'The subnet for macvlan network'},
-        'network_macvlan_gateway': {'type': 'str', 'default': '', 'description': 'The gateway for macvlan network'},
-    }),
-    ('ports', {
-        'ports_http': {'type': 'int', 'default': 80, 'description': 'The HTTP port'},
-        'ports_https': {'type': 'int', 'default': 443, 'description': 'The HTTPS port'},
-        'ports_ssh': {'type': 'int', 'default': 22, 'description': 'The SSH port'},
-        'ports_dns': {'type': 'int', 'default': 53, 'description': 'The DNS port'},
-        'ports_dhcp': {'type': 'int', 'default': 67, 'description': 'The DHCP port'},
-        'ports_smtp': {'type': 'int', 'default': 25, 'description': 'The SMTP port'},
-        'ports_snmp': {'type': 'int', 'default': 161, 'description': 'The SNMP port'},
-    }),
-    ('traefik', {
-        'traefik_enabled': {'type': 'bool', 'default': False, 'description': 'Enable Traefik integration'},
-        'traefik_network': {'type': 'str', 'default': 'traefik', 'description': 'The Traefik network name'},
-        'traefik_host': {'type': 'str', 'default': '', 'description': 'The Traefik host'},
-        'traefik_domain': {'type': 'str', 'default': '', 'description': 'The Traefik domain'},
-    }),
-    ('traefik_tls', {
-        'traefik_tls_enabled': {'type': 'bool', 'default': False, 'description': 'Enable Traefik TLS'},
-        'traefik_tls_certresolver': {'type': 'str', 'default': 'letsencrypt', 'description': 'The Traefik TLS certificate resolver'},
-    }),
-    ('volume', {
-        'volume_mode': {'type': 'enum', 'options': ['local', 'mount', 'nfs'], 'default': 'local', 'description': 'The volume mode'},
-        'volume_mount_path': {'type': 'str', 'default': '/mnt/data', 'description': 'The volume mount path'},
-        'volume_nfs_server': {'type': 'str', 'default': '', 'description': 'The NFS server'},
-        'volume_nfs_path': {'type': 'str', 'default': '', 'description': 'The NFS path'},
-        'volume_nfs_options': {'type': 'str', 'default': 'nfsvers=4,soft,timeo=180,intr', 'description': 'The NFS mount options'},
-    }),
-    ('resources', {
-        'resources_cpu_limit': {'type': 'str', 'default': '1.0', 'description': 'The CPU limit'},
-        'resources_memory_limit': {'type': 'str', 'default': '512M', 'description': 'The memory limit'},
-    }),
-    ('swarm', {
-        'swarm_enabled': {'type': 'bool', 'default': False, 'description': 'Enable Docker Swarm mode'},
-        'swarm_replicas': {'type': 'int', 'default': 1, 'description': 'The number of replicas'},
-        'swarm_placement_mode': {'type': 'str', 'default': 'replicated', 'description': 'The placement mode'},
-        'swarm_placement_host': {'type': 'str', 'default': '', 'description': 'The placement host'},
-    }),
-    ('database', {
-        'database_enabled': {'type': 'bool', 'default': False, 'description': 'Enable database integration'},
-        'database_type': {'type': 'str', 'default': 'postgres', 'description': 'The database type'},
-        'database_external': {'type': 'bool', 'default': False, 'description': 'Use external database'},
-        'database_host': {'type': 'str', 'default': 'localhost', 'description': 'The database host'},
-        'database_port': {'type': 'int', 'default': 5432, 'description': 'The database port'},
-        'database_name': {'type': 'str', 'default': '', 'description': 'The database name'},
-        'database_user': {'type': 'str', 'default': '', 'description': 'The database user'},
-        'database_password': {'type': 'str', 'default': '', 'description': 'The database password'},
-    }),
-    ('email', {
-        'email_enabled': {'type': 'bool', 'default': False, 'description': 'Enable email integration'},
-        'email_host': {'type': 'str', 'default': '', 'description': 'The email host'},
-        'email_port': {'type': 'int', 'default': 587, 'description': 'The email port'},
-        'email_username': {'type': 'str', 'default': '', 'description': 'The email username'},
-        'email_password': {'type': 'str', 'default': '', 'description': 'The email password'},
-        'email_from': {'type': 'str', 'default': '', 'description': 'The email from address'},
-        'email_encryption': {'type': 'str', 'default': 'tls', 'description': 'The email encryption type'},
-    }),
-    ('authentik', {
-        'authentik_enabled': {'type': 'bool', 'default': False, 'description': 'Enable Authentik SSO integration'},
-        'authentik_url': {'type': 'str', 'default': '', 'description': 'The Authentik URL'},
-        'authentik_slug': {'type': 'str', 'default': '', 'description': 'The Authentik application slug'},
-        'authentik_client_id': {'type': 'str', 'default': '', 'description': 'The Authentik client ID'},
-        'authentik_client_secret': {'type': 'str', 'default': '', 'description': 'The Authentik client secret'},
-    }),
-])
-
-# Mapping of all vars to their sections
-VAR_TO_SECTION = {}
-for section, vars_dict in SCHEMA_SECTIONS.items():
-    for var_name in vars_dict.keys():
-        VAR_TO_SECTION[var_name] = section
-
-# Toggle variables that trigger entire sections
-TOGGLE_SECTIONS = {
-    'traefik_enabled': ['traefik_enabled', 'traefik_network', 'traefik_host', 'traefik_domain'],
-    'traefik_tls_enabled': ['traefik_tls_enabled', 'traefik_tls_certresolver'],
-    'volume_mode': ['volume_mode', 'volume_mount_path', 'volume_nfs_server', 'volume_nfs_path', 'volume_nfs_options'],
-    'swarm_enabled': ['swarm_enabled', 'swarm_replicas', 'swarm_placement_mode', 'swarm_placement_host'],
-    'database_enabled': ['database_enabled', 'database_type', 'database_external', 'database_host', 'database_port', 'database_name', 'database_user', 'database_password'],
-    'email_enabled': ['email_enabled', 'email_host', 'email_port', 'email_username', 'email_password', 'email_from', 'email_encryption'],
-    'authentik_enabled': ['authentik_enabled', 'authentik_url', 'authentik_slug', 'authentik_client_id', 'authentik_client_secret'],
-}
-
-def extract_all_vars(compose_content):
-    """Extract all referenced variables from compose file"""
-    # Variables in {{ }}
-    vars_pattern = r'\{\{\s*(\w+)\s*\}\}'
-    vars_in_braces = set(re.findall(vars_pattern, compose_content))
-    
-    # Variables in {% if %}
-    cond_pattern = r'\{%\s*if\s+(\w+)'
-    vars_in_conds = set(re.findall(cond_pattern, compose_content))
-    
-    # Variables in {% if not var %}
-    not_pattern = r'\{%\s*if\s+not\s+(\w+)'
-    vars_in_not = set(re.findall(not_pattern, compose_content))
-    
-    # Variables in comparisons like {% if var == 'value' %}
-    comp_pattern = r'\{%\s*if\s+(\w+)\s*=='
-    vars_in_comps = set(re.findall(comp_pattern, compose_content))
-    
-    # Variables in {% elif %}
-    elif_pattern = r'\{%\s*elif\s+(\w+)'
-    vars_in_elif = set(re.findall(elif_pattern, compose_content))
-    
-    # Variables in {% elif not var %}
-    elif_not_pattern = r'\{%\s*elif\s+not\s+(\w+)'
-    vars_in_elif_not = set(re.findall(elif_not_pattern, compose_content))
-    
-    # Variables in "and not" / "or not" conditions
-    and_not_pattern = r'and\s+not\s+(\w+)'
-    vars_in_and_not = set(re.findall(and_not_pattern, compose_content))
-    
-    or_not_pattern = r'or\s+not\s+(\w+)'
-    vars_in_or_not = set(re.findall(or_not_pattern, compose_content))
-    
-    return (vars_in_braces | vars_in_conds | vars_in_not | vars_in_comps | 
-            vars_in_elif | vars_in_elif_not | vars_in_and_not | vars_in_or_not)
-
-def get_existing_vars(template_data):
-    """Get existing variables from template.yaml"""
-    if not template_data or 'spec' not in template_data:
-        return set()
-    
-    existing = set()
-    for section_name, section_data in template_data['spec'].items():
-        if isinstance(section_data, dict) and 'vars' in section_data:
-            existing.update(section_data['vars'].keys())
-    
-    return existing
-
-def determine_vars_to_add(used_vars, existing_vars):
-    """Determine which variables need to be added"""
-    to_add = {}
-    
-    # Add directly used schema variables
-    for var in used_vars:
-        if var in VAR_TO_SECTION and var not in existing_vars:
-            section = VAR_TO_SECTION[var]
-            to_add[var] = (section, SCHEMA_SECTIONS[section][var])
-    
-    # Check for toggle variables and add complete sections
-    for var in used_vars:
-        if var in TOGGLE_SECTIONS:
-            for section_var in TOGGLE_SECTIONS[var]:
-                if section_var not in existing_vars and section_var in VAR_TO_SECTION:
-                    section = VAR_TO_SECTION[section_var]
-                    to_add[section_var] = (section, SCHEMA_SECTIONS[section][section_var])
-    
-    return to_add
-
-def apply_fixes(template_path, template_data, vars_to_add):
-    """Add missing variables to template.yaml"""
-    if 'spec' not in template_data:
-        template_data['spec'] = {}
-    
-    # Group vars by section
-    by_section = {}
-    for var_name, (section_name, var_def) in vars_to_add.items():
-        if section_name not in by_section:
-            by_section[section_name] = []
-        by_section[section_name].append((var_name, var_def))
-    
-    # Add to each section
-    for section_name, vars_list in by_section.items():
-        if section_name not in template_data['spec']:
-            template_data['spec'][section_name] = {'vars': {}}
-        elif 'vars' not in template_data['spec'][section_name]:
-            template_data['spec'][section_name]['vars'] = {}
-        
-        for var_name, var_def in sorted(vars_list):
-            var_entry = {}
-            
-            # Add type
-            if 'type' in var_def:
-                var_entry['type'] = var_def['type']
-            
-            # Add options for enums
-            if 'options' in var_def:
-                var_entry['options'] = var_def['options']
-            
-            # Add default
-            if 'default' in var_def:
-                var_entry['default'] = var_def['default']
-            
-            # Add required
-            if var_def.get('required'):
-                var_entry['required'] = True
-            
-            # Add description
-            if 'description' in var_def:
-                var_entry['description'] = var_def['description']
-            
-            template_data['spec'][section_name]['vars'][var_name] = var_entry
-    
-    # Write back to file with proper formatting
-    with open(template_path, 'w') as f:
-        yaml.dump(template_data, f, default_flow_style=False, sort_keys=False, allow_unicode=True, width=120)
-
-def analyze_template(template_dir):
-    """Analyze a template and return what needs to be fixed"""
-    compose_path = template_dir / "compose.yaml.j2"
-    template_path = template_dir / "template.yaml"
-    
-    if not compose_path.exists() or not template_path.exists():
-        return None
-    
-    # Read compose file
-    with open(compose_path, 'r') as f:
-        compose_content = f.read()
-    
-    # Read template
-    with open(template_path, 'r') as f:
-        template_data = yaml.safe_load(f)
-    
-    # Extract vars
-    used_vars = extract_all_vars(compose_content)
-    existing_vars = get_existing_vars(template_data)
-    
-    # Filter out non-schema vars and special keywords
-    schema_used_vars = {v for v in used_vars if v in VAR_TO_SECTION}
-    
-    # Determine what to add
-    vars_to_add = determine_vars_to_add(used_vars, existing_vars)
-    
-    if not vars_to_add:
-        return None
-    
-    return {
-        'name': template_dir.name,
-        'template_path': template_path,
-        'template_data': template_data,
-        'vars_to_add': vars_to_add,
-        'count': len(vars_to_add)
-    }
-
-def main():
-    base_dir = Path("/Users/xcad/Projects/christianlempa/boilerplates/library/compose")
-    
-    print("="*80)
-    print("ANALYZING ALL 34 COMPOSE TEMPLATES")
-    print("="*80)
-    
-    templates_to_fix = []
-    
-    for template_dir in sorted(base_dir.iterdir()):
-        if template_dir.is_dir():
-            result = analyze_template(template_dir)
-            if result:
-                templates_to_fix.append(result)
-                print(f"\n{result['name']}:")
-                by_section = {}
-                for var_name, (section, _) in result['vars_to_add'].items():
-                    if section not in by_section:
-                        by_section[section] = []
-                    by_section[section].append(var_name)
-                
-                for section in sorted(by_section.keys()):
-                    print(f"  [{section}]")
-                    for var_name in sorted(by_section[section]):
-                        print(f"    - {var_name}")
-    
-    print(f"\n\n{'='*80}")
-    print("SUMMARY")
-    print('='*80)
-    print(f"Total templates needing fixes: {len(templates_to_fix)}")
-    if templates_to_fix:
-        total_vars = sum(t['count'] for t in templates_to_fix)
-        print(f"Total variables to add: {total_vars}")
-        print(f"\nTemplates:")
-        for t in templates_to_fix:
-            print(f"  - {t['name']}: {t['count']} variable(s)")
-        
-        print(f"\n{'='*80}")
-        response = input("Apply fixes to all templates? (yes/no): ")
-        if response.lower() in ['yes', 'y']:
-            print("\nApplying fixes...")
-            for result in templates_to_fix:
-                apply_fixes(result['template_path'], result['template_data'], result['vars_to_add'])
-                print(f"  ✓ Fixed {result['name']} ({result['count']} variables added)")
-            print("\n✓ All fixes applied successfully!")
-        else:
-            print("\nNo changes made.")
-    else:
-        print("\n✓ All templates are complete!")
-
-if __name__ == "__main__":
-    main()

+ 0 - 287
fix_all_templates.py

@@ -1,287 +0,0 @@
-#!/usr/bin/env python3
-"""
-Script to fix all compose templates by adding ALL missing schema variables
-"""
-import os
-import re
-import yaml
-from pathlib import Path
-from collections import defaultdict
-
-# Complete schema definition based on v1.2
-SCHEMA_VARS = {
-    # GENERAL section variables
-    "service_name": {"section": "general", "type": "string", "required": True, "description": "The name of the service"},
-    "container_name": {"section": "general", "type": "string", "required": True, "description": "The name of the container"},
-    "container_hostname": {"section": "general", "type": "string", "required": True, "description": "The hostname of the container"},
-    "container_timezone": {"section": "general", "type": "string", "default": "UTC", "description": "The timezone for the container"},
-    "user_uid": {"section": "general", "type": "integer", "default": 1000, "description": "The user ID to run the container as"},
-    "user_gid": {"section": "general", "type": "integer", "default": 1000, "description": "The group ID to run the container as"},
-    "container_loglevel": {"section": "general", "type": "string", "default": "INFO", "description": "The log level for the container"},
-    "restart_policy": {"section": "general", "type": "enum", "default": "unless-stopped", "description": "The restart policy for the container"},
-    
-    # NETWORK section variables
-    "network_mode": {"section": "network", "type": "string", "default": "bridge", "description": "The network mode for the container"},
-    "network_name": {"section": "network", "type": "string", "default": "", "description": "The name of the network"},
-    "network_external": {"section": "network", "type": "boolean", "default": False, "description": "Whether the network is external"},
-    "network_macvlan_ipv4_address": {"section": "network", "type": "string", "default": "", "description": "The IPv4 address for macvlan network"},
-    "network_macvlan_parent_interface": {"section": "network", "type": "string", "default": "", "description": "The parent interface for macvlan network"},
-    "network_macvlan_subnet": {"section": "network", "type": "string", "default": "", "description": "The subnet for macvlan network"},
-    "network_macvlan_gateway": {"section": "network", "type": "string", "default": "", "description": "The gateway for macvlan network"},
-    
-    # PORTS section variables
-    "ports_http": {"section": "ports", "type": "integer", "default": 80, "description": "The HTTP port"},
-    "ports_https": {"section": "ports", "type": "integer", "default": 443, "description": "The HTTPS port"},
-    "ports_ssh": {"section": "ports", "type": "integer", "default": 22, "description": "The SSH port"},
-    "ports_dns": {"section": "ports", "type": "integer", "default": 53, "description": "The DNS port"},
-    "ports_dhcp": {"section": "ports", "type": "integer", "default": 67, "description": "The DHCP port"},
-    "ports_smtp": {"section": "ports", "type": "integer", "default": 25, "description": "The SMTP port"},
-    "ports_snmp": {"section": "ports", "type": "integer", "default": 161, "description": "The SNMP port"},
-    
-    # TRAEFIK section variables
-    "traefik_enabled": {"section": "traefik", "type": "boolean", "default": False, "description": "Enable Traefik integration"},
-    "traefik_network": {"section": "traefik", "type": "string", "default": "traefik", "description": "The Traefik network name"},
-    "traefik_host": {"section": "traefik", "type": "string", "default": "", "description": "The Traefik host"},
-    "traefik_domain": {"section": "traefik", "type": "string", "default": "", "description": "The Traefik domain"},
-    
-    # TRAEFIK_TLS section variables
-    "traefik_tls_enabled": {"section": "traefik_tls", "type": "boolean", "default": False, "description": "Enable Traefik TLS"},
-    "traefik_tls_certresolver": {"section": "traefik_tls", "type": "string", "default": "letsencrypt", "description": "The Traefik TLS certificate resolver"},
-    
-    # VOLUME section variables
-    "volume_mode": {"section": "volume", "type": "enum", "default": "local", "description": "The volume mode"},
-    "volume_mount_path": {"section": "volume", "type": "string", "default": "/mnt/data", "description": "The volume mount path"},
-    "volume_nfs_server": {"section": "volume", "type": "string", "default": "", "description": "The NFS server"},
-    "volume_nfs_path": {"section": "volume", "type": "string", "default": "", "description": "The NFS path"},
-    "volume_nfs_options": {"section": "volume", "type": "string", "default": "nfsvers=4,soft,timeo=180,intr", "description": "The NFS mount options"},
-    
-    # RESOURCES section variables
-    "resources_cpu_limit": {"section": "resources", "type": "string", "default": "1.0", "description": "The CPU limit"},
-    "resources_memory_limit": {"section": "resources", "type": "string", "default": "512M", "description": "The memory limit"},
-    
-    # SWARM section variables
-    "swarm_enabled": {"section": "swarm", "type": "boolean", "default": False, "description": "Enable Docker Swarm mode"},
-    "swarm_replicas": {"section": "swarm", "type": "integer", "default": 1, "description": "The number of replicas"},
-    "swarm_placement_mode": {"section": "swarm", "type": "string", "default": "replicated", "description": "The placement mode"},
-    "swarm_placement_host": {"section": "swarm", "type": "string", "default": "", "description": "The placement host"},
-    
-    # DATABASE section variables
-    "database_enabled": {"section": "database", "type": "boolean", "default": False, "description": "Enable database integration"},
-    "database_type": {"section": "database", "type": "string", "default": "postgres", "description": "The database type"},
-    "database_external": {"section": "database", "type": "boolean", "default": False, "description": "Use external database"},
-    "database_host": {"section": "database", "type": "string", "default": "localhost", "description": "The database host"},
-    "database_port": {"section": "database", "type": "integer", "default": 5432, "description": "The database port"},
-    "database_name": {"section": "database", "type": "string", "default": "", "description": "The database name"},
-    "database_user": {"section": "database", "type": "string", "default": "", "description": "The database user"},
-    "database_password": {"section": "database", "type": "string", "default": "", "description": "The database password"},
-    
-    # EMAIL section variables
-    "email_enabled": {"section": "email", "type": "boolean", "default": False, "description": "Enable email integration"},
-    "email_host": {"section": "email", "type": "string", "default": "", "description": "The email host"},
-    "email_port": {"section": "email", "type": "integer", "default": 587, "description": "The email port"},
-    "email_username": {"section": "email", "type": "string", "default": "", "description": "The email username"},
-    "email_password": {"section": "email", "type": "string", "default": "", "description": "The email password"},
-    "email_from": {"section": "email", "type": "string", "default": "", "description": "The email from address"},
-    "email_encryption": {"section": "email", "type": "string", "default": "tls", "description": "The email encryption type"},
-    
-    # AUTHENTIK section variables
-    "authentik_enabled": {"section": "authentik", "type": "boolean", "default": False, "description": "Enable Authentik SSO integration"},
-    "authentik_url": {"section": "authentik", "type": "string", "default": "", "description": "The Authentik URL"},
-    "authentik_slug": {"section": "authentik", "type": "string", "default": "", "description": "The Authentik application slug"},
-    "authentik_client_id": {"section": "authentik", "type": "string", "default": "", "description": "The Authentik client ID"},
-    "authentik_client_secret": {"section": "authentik", "type": "string", "default": "", "description": "The Authentik client secret"},
-}
-
-# Toggle variables that should trigger adding entire section
-TOGGLE_SECTIONS = {
-    "traefik_enabled": ["traefik_enabled", "traefik_network", "traefik_host", "traefik_domain"],
-    "traefik_tls_enabled": ["traefik_tls_enabled", "traefik_tls_certresolver"],
-    "volume_mode": ["volume_mode", "volume_mount_path", "volume_nfs_server", "volume_nfs_path", "volume_nfs_options"],
-    "swarm_enabled": ["swarm_enabled", "swarm_replicas", "swarm_placement_mode", "swarm_placement_host"],
-    "database_enabled": ["database_enabled", "database_type", "database_external", "database_host", "database_port", "database_name", "database_user", "database_password"],
-    "email_enabled": ["email_enabled", "email_host", "email_port", "email_username", "email_password", "email_from", "email_encryption"],
-    "authentik_enabled": ["authentik_enabled", "authentik_url", "authentik_slug", "authentik_client_id", "authentik_client_secret"],
-}
-
-def extract_variables(compose_content):
-    """Extract all {{ variable_name }} from compose file"""
-    pattern = r'\{\{\s*(\w+)\s*\}\}'
-    return set(re.findall(pattern, compose_content))
-
-def get_existing_variables(template_data):
-    """Get set of existing variables in template spec"""
-    if not template_data or 'spec' not in template_data:
-        return set()
-    
-    existing = set()
-    spec = template_data['spec']
-    
-    for section_name, section_data in spec.items():
-        if isinstance(section_data, dict) and 'vars' in section_data:
-            existing.update(section_data['vars'].keys())
-    
-    return existing
-
-def analyze_and_fix_template(template_dir, dry_run=True):
-    """Analyze a single template and fix missing variables"""
-    compose_path = template_dir / "compose.yaml.j2"
-    template_path = template_dir / "template.yaml"
-    
-    if not compose_path.exists():
-        return None
-    
-    # Read compose file
-    with open(compose_path, 'r') as f:
-        compose_content = f.read()
-    
-    # Extract variables used
-    used_vars = extract_variables(compose_content)
-    
-    # Load template.yaml
-    if template_path.exists():
-        with open(template_path, 'r') as f:
-            template_data = yaml.safe_load(f)
-        existing_vars = get_existing_variables(template_data)
-    else:
-        return None
-    
-    # Find variables to add
-    vars_to_add = {}
-    
-    # Add missing used variables
-    for var in used_vars:
-        if var not in existing_vars and var in SCHEMA_VARS:
-            vars_to_add[var] = SCHEMA_VARS[var]
-    
-    # Check for toggle variables and add complete sections
-    for var in used_vars:
-        if var in TOGGLE_SECTIONS:
-            for section_var in TOGGLE_SECTIONS[var]:
-                if section_var not in existing_vars and section_var in SCHEMA_VARS:
-                    vars_to_add[section_var] = SCHEMA_VARS[section_var]
-    
-    if not vars_to_add:
-        return {
-            'template': template_dir.name,
-            'status': 'complete',
-            'vars_to_add': {}
-        }
-    
-    # Group by section
-    by_section = defaultdict(list)
-    for var_name, var_info in vars_to_add.items():
-        by_section[var_info['section']].append((var_name, var_info))
-    
-    result = {
-        'template': template_dir.name,
-        'status': 'needs_fix',
-        'vars_to_add': vars_to_add,
-        'by_section': dict(by_section),
-        'template_path': template_path,
-        'template_data': template_data
-    }
-    
-    if not dry_run:
-        # Apply fixes
-        apply_fixes(result)
-    
-    return result
-
-def apply_fixes(result):
-    """Apply fixes to template.yaml"""
-    template_data = result['template_data']
-    by_section = result['by_section']
-    
-    if 'spec' not in template_data:
-        template_data['spec'] = {}
-    
-    # Add missing variables to appropriate sections
-    for section_name, vars_list in by_section.items():
-        if section_name not in template_data['spec']:
-            template_data['spec'][section_name] = {'vars': {}}
-        elif 'vars' not in template_data['spec'][section_name]:
-            template_data['spec'][section_name]['vars'] = {}
-        
-        for var_name, var_info in vars_list:
-            var_entry = {}
-            
-            # Add type
-            if var_info['type'] == 'enum':
-                var_entry['type'] = 'enum'
-                if var_name == 'restart_policy':
-                    var_entry['options'] = ['unless-stopped', 'always', 'on-failure', 'no']
-                elif var_name == 'volume_mode':
-                    var_entry['options'] = ['local', 'mount', 'nfs']
-            elif var_info['type'] == 'integer':
-                var_entry['type'] = 'int'
-            elif var_info['type'] == 'boolean':
-                var_entry['type'] = 'bool'
-            else:
-                var_entry['type'] = 'str'
-            
-            # Add default
-            if 'default' in var_info:
-                var_entry['default'] = var_info['default']
-            
-            # Add required
-            if var_info.get('required'):
-                var_entry['required'] = True
-            
-            # Add description
-            var_entry['description'] = var_info['description']
-            
-            template_data['spec'][section_name]['vars'][var_name] = var_entry
-    
-    # Write back to file
-    with open(result['template_path'], 'w') as f:
-        yaml.dump(template_data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
-
-def main():
-    base_dir = Path("/Users/xcad/Projects/christianlempa/boilerplates/library/compose")
-    
-    print("="*80)
-    print("ANALYZING ALL COMPOSE TEMPLATES")
-    print("="*80)
-    
-    results = []
-    templates_to_fix = []
-    
-    for template_dir in sorted(base_dir.iterdir()):
-        if template_dir.is_dir():
-            result = analyze_and_fix_template(template_dir, dry_run=True)
-            if result:
-                results.append(result)
-                if result['status'] == 'needs_fix':
-                    templates_to_fix.append(result)
-                    print(f"\n{result['template']}:")
-                    for section, vars_list in result['by_section'].items():
-                        print(f"  [{section}]")
-                        for var_name, _ in vars_list:
-                            print(f"    - {var_name}")
-    
-    print(f"\n\n{'='*80}")
-    print("SUMMARY")
-    print('='*80)
-    print(f"Total templates analyzed: {len(results)}")
-    print(f"Templates needing fixes: {len(templates_to_fix)}")
-    print(f"Templates complete: {len(results) - len(templates_to_fix)}")
-    
-    if templates_to_fix:
-        print(f"\n\nTemplates that need fixes:")
-        for r in templates_to_fix:
-            print(f"  - {r['template']} ({len(r['vars_to_add'])} missing variables)")
-        
-        response = input("\n\nApply fixes to all templates? (yes/no): ")
-        if response.lower() in ['yes', 'y']:
-            print("\nApplying fixes...")
-            for result in templates_to_fix:
-                apply_fixes(result)
-                print(f"  ✓ Fixed {result['template']}")
-            print("\n✓ All fixes applied!")
-        else:
-            print("\nNo changes made.")
-    else:
-        print("\n✓ All templates are complete!")
-
-if __name__ == "__main__":
-    main()

+ 0 - 256
fix_compose_templates.py

@@ -1,256 +0,0 @@
-#!/usr/bin/env python3
-"""
-Script to fix all compose templates by adding missing schema variables
-"""
-import os
-import re
-import yaml
-from pathlib import Path
-
-# Complete schema definition based on v1.2
-SCHEMA = {
-    "GENERAL": {
-        "service_name": {"type": "string", "required": True},
-        "container_name": {"type": "string", "required": True},
-        "container_hostname": {"type": "string", "required": True},
-        "container_timezone": {"type": "string", "default": "UTC"},
-        "user_uid": {"type": "integer", "default": 1000},
-        "user_gid": {"type": "integer", "default": 1000},
-        "container_loglevel": {"type": "string", "default": "INFO"},
-        "restart_policy": {"type": "string", "default": "unless-stopped"},
-    },
-    "NETWORK": {
-        "network_mode": {"type": "string", "default": "bridge"},
-        "network_name": {"type": "string", "default": ""},
-        "network_external": {"type": "boolean", "default": False},
-        "network_macvlan_ipv4_address": {"type": "string", "default": ""},
-        "network_macvlan_parent_interface": {"type": "string", "default": ""},
-        "network_macvlan_subnet": {"type": "string", "default": ""},
-        "network_macvlan_gateway": {"type": "string", "default": ""},
-    },
-    "PORTS": {
-        "ports_http": {"type": "integer", "default": 80},
-        "ports_https": {"type": "integer", "default": 443},
-        "ports_ssh": {"type": "integer", "default": 22},
-        "ports_dns": {"type": "integer", "default": 53},
-        "ports_dhcp": {"type": "integer", "default": 67},
-        "ports_smtp": {"type": "integer", "default": 25},
-        "ports_snmp": {"type": "integer", "default": 161},
-    },
-    "TRAEFIK": {
-        "traefik_enabled": {"type": "boolean", "default": False},
-        "traefik_network": {"type": "string", "default": "traefik"},
-        "traefik_host": {"type": "string", "default": ""},
-        "traefik_domain": {"type": "string", "default": ""},
-    },
-    "TRAEFIK_TLS": {
-        "traefik_tls_enabled": {"type": "boolean", "default": False},
-        "traefik_tls_certresolver": {"type": "string", "default": "letsencrypt"},
-    },
-    "VOLUME": {
-        "volume_mode": {"type": "string", "default": "local"},
-        "volume_mount_path": {"type": "string", "default": "/mnt/data"},
-        "volume_nfs_server": {"type": "string", "default": ""},
-        "volume_nfs_path": {"type": "string", "default": ""},
-        "volume_nfs_options": {"type": "string", "default": "nfsvers=4,soft,timeo=180,intr"},
-    },
-    "RESOURCES": {
-        "resources_cpu_limit": {"type": "string", "default": "1.0"},
-        "resources_memory_limit": {"type": "string", "default": "512M"},
-    },
-    "SWARM": {
-        "swarm_enabled": {"type": "boolean", "default": False},
-        "swarm_replicas": {"type": "integer", "default": 1},
-        "swarm_placement_mode": {"type": "string", "default": "replicated"},
-        "swarm_placement_host": {"type": "string", "default": ""},
-    },
-    "DATABASE": {
-        "database_enabled": {"type": "boolean", "default": False},
-        "database_type": {"type": "string", "default": "postgres"},
-        "database_external": {"type": "boolean", "default": False},
-        "database_host": {"type": "string", "default": "localhost"},
-        "database_port": {"type": "integer", "default": 5432},
-        "database_name": {"type": "string", "default": ""},
-        "database_user": {"type": "string", "default": ""},
-        "database_password": {"type": "string", "default": ""},
-    },
-    "EMAIL": {
-        "email_enabled": {"type": "boolean", "default": False},
-        "email_host": {"type": "string", "default": ""},
-        "email_port": {"type": "integer", "default": 587},
-        "email_username": {"type": "string", "default": ""},
-        "email_password": {"type": "string", "default": ""},
-        "email_from": {"type": "string", "default": ""},
-        "email_encryption": {"type": "string", "default": "tls"},
-    },
-    "AUTHENTIK": {
-        "authentik_enabled": {"type": "boolean", "default": False},
-        "authentik_url": {"type": "string", "default": ""},
-        "authentik_slug": {"type": "string", "default": ""},
-        "authentik_client_id": {"type": "string", "default": ""},
-        "authentik_client_secret": {"type": "string", "default": ""},
-    },
-}
-
-# Toggle variables that trigger entire sections
-TOGGLE_SECTIONS = {
-    "traefik_enabled": "TRAEFIK",
-    "traefik_tls_enabled": "TRAEFIK_TLS",
-    "volume_mode": "VOLUME",
-    "swarm_enabled": "SWARM",
-    "database_enabled": "DATABASE",
-    "email_enabled": "EMAIL",
-    "authentik_enabled": "AUTHENTIK",
-}
-
-def extract_variables(compose_content):
-    """Extract all {{ variable_name }} from compose file"""
-    pattern = r'\{\{\s*(\w+)\s*\}\}'
-    return set(re.findall(pattern, compose_content))
-
-def get_variable_section(var_name):
-    """Find which section a variable belongs to"""
-    for section, variables in SCHEMA.items():
-        if var_name in variables:
-            return section
-    return None
-
-def load_template_yaml(path):
-    """Load and parse template.yaml"""
-    with open(path, 'r') as f:
-        return yaml.safe_load(f)
-
-def get_existing_variables(template_spec):
-    """Get list of existing variables in template spec"""
-    if not template_spec or 'spec' not in template_spec:
-        return set()
-    
-    existing = set()
-    spec = template_spec['spec']
-    
-    # Handle nested structure (sections with vars)
-    for section_name, section_data in spec.items():
-        if isinstance(section_data, dict) and 'vars' in section_data:
-            existing.update(section_data['vars'].keys())
-    
-    return existing
-
-def create_variable_entry(var_name, schema_info, description_prefix=""):
-    """Create a template.yaml variable entry"""
-    entry = {
-        'name': var_name,
-        'type': schema_info['type'],
-    }
-    
-    # Add default if present
-    if 'default' in schema_info:
-        entry['default'] = schema_info['default']
-    
-    # Add required if present
-    if schema_info.get('required'):
-        entry['required'] = True
-    
-    # Generate description
-    desc = description_prefix if description_prefix else f"The {var_name.replace('_', ' ')}"
-    entry['description'] = desc
-    
-    return entry
-
-def analyze_template(template_dir):
-    """Analyze a single template and return missing variables"""
-    compose_path = template_dir / "compose.yaml.j2"
-    template_path = template_dir / "template.yaml"
-    
-    if not compose_path.exists():
-        return None
-    
-    # Read compose file
-    with open(compose_path, 'r') as f:
-        compose_content = f.read()
-    
-    # Extract variables used
-    used_vars = extract_variables(compose_content)
-    
-    # Load template.yaml
-    if template_path.exists():
-        template_data = load_template_yaml(template_path)
-        existing_vars = get_existing_variables(template_data)
-    else:
-        template_data = {'spec': []}
-        existing_vars = set()
-    
-    # Find missing variables
-    missing_vars = used_vars - existing_vars
-    
-    # Group missing variables by section
-    missing_by_section = {}
-    sections_to_add = set()
-    
-    for var in missing_vars:
-        section = get_variable_section(var)
-        if section:
-            if section not in missing_by_section:
-                missing_by_section[section] = []
-            missing_by_section[section].append(var)
-            sections_to_add.add(section)
-    
-    # Check for toggle variables and add complete sections
-    for var in used_vars:
-        if var in TOGGLE_SECTIONS:
-            section = TOGGLE_SECTIONS[var]
-            # Add entire section if toggle is used
-            for schema_var in SCHEMA[section].keys():
-                if schema_var not in existing_vars:
-                    if section not in missing_by_section:
-                        missing_by_section[section] = []
-                    if schema_var not in missing_by_section[section]:
-                        missing_by_section[section].append(schema_var)
-                    sections_to_add.add(section)
-    
-    return {
-        'template_dir': template_dir,
-        'used_vars': used_vars,
-        'existing_vars': existing_vars,
-        'missing_vars': missing_vars,
-        'missing_by_section': missing_by_section,
-        'sections_to_add': sections_to_add,
-        'template_data': template_data
-    }
-
-def main():
-    base_dir = Path("/Users/xcad/Projects/christianlempa/boilerplates/library/compose")
-    
-    results = {}
-    for template_dir in sorted(base_dir.iterdir()):
-        if template_dir.is_dir():
-            print(f"\n{'='*60}")
-            print(f"Analyzing: {template_dir.name}")
-            print('='*60)
-            
-            result = analyze_template(template_dir)
-            if result:
-                results[template_dir.name] = result
-                
-                print(f"Used variables: {len(result['used_vars'])}")
-                print(f"Existing variables: {len(result['existing_vars'])}")
-                print(f"Missing variables: {len(result['missing_vars'])}")
-                
-                if result['missing_by_section']:
-                    print("\nMissing by section:")
-                    for section, vars in sorted(result['missing_by_section'].items()):
-                        print(f"  {section}: {', '.join(sorted(vars))}")
-                else:
-                    print("\n✓ No missing variables!")
-    
-    print(f"\n\n{'='*60}")
-    print("SUMMARY")
-    print('='*60)
-    print(f"Total templates analyzed: {len(results)}")
-    templates_with_missing = sum(1 for r in results.values() if r['missing_vars'])
-    print(f"Templates with missing variables: {templates_with_missing}")
-    print(f"Templates complete: {len(results) - templates_with_missing}")
-    
-    return results
-
-if __name__ == "__main__":
-    main()