# CoreDNS Corefile Generator # Generates and manages Corefile configuration for CoreDNS # ../utils/log.nu does not exist — dangling import removed (ADR-025 L2). # Generate Corefile from configuration export def generate-corefile [ config: record # CoreDNS configuration ] -> string { log debug $"Generating Corefile from config: ($config)" let local_config = $config.local? | default {} let zones = $local_config.zones? | default ["provisioning.local"] let port = $local_config.port? | default 5353 let zones_path = $local_config.zones_path? | default "~/.provisioning/coredns/zones" let upstream = $config.upstream? | default ["8.8.8.8", "1.1.1.1"] let enable_logging = $config.enable_logging? | default true let enable_metrics = $config.enable_metrics? | default true let metrics_port = $config.metrics_port? | default 9153 let dynamic_enabled = $config.dynamic_updates?.enabled? | default true let dynamic_endpoint = $config.dynamic_updates?.api_endpoint? | default "http://localhost:8080/dns" mut corefile_content = [] # Generate zone blocks for zone in $zones { let zone_block = generate-zone-block $zone $port $zones_path $enable_logging $dynamic_enabled $corefile_content = ($corefile_content | append $zone_block) } # Generate catch-all forward block let forward_block = generate-forward-block $port $upstream $enable_logging $corefile_content = ($corefile_content | append $forward_block) # Generate metrics block if enabled if $enable_metrics { let metrics_block = generate-metrics-block $metrics_port $corefile_content = ($corefile_content | append $metrics_block) } $corefile_content | str join "\n\n" } # Generate zone block for Corefile def generate-zone-block [ zone: string port: int zones_path: string enable_logging: bool dynamic_enabled: bool ] -> string { let expanded_path = $zones_path | path expand mut plugins = [] # File plugin $plugins = ($plugins | append $" file ($expanded_path)/($zone).zone") # Dynamic updates via HTTP (if enabled) if $dynamic_enabled { $plugins = ($plugins | append " # Dynamic DNS updates via orchestrator API") $plugins = ($plugins | append " # Updates handled through external API") } # Logging if $enable_logging { $plugins = ($plugins | append " log") } # Error handling $plugins = ($plugins | append " errors") # Cache $plugins = ($plugins | append " cache 30") let plugins_str = $plugins | str join "\n" $"($zone):($port) { ($plugins_str) }" } # Generate forward block for Corefile def generate-forward-block [ port: int upstream: list enable_logging: bool ] -> string { let upstream_str = $upstream | str join " " mut plugins = [] $plugins = ($plugins | append $" forward . ($upstream_str)") if $enable_logging { $plugins = ($plugins | append " log") } $plugins = ($plugins | append " errors") $plugins = ($plugins | append " cache 30") let plugins_str = $plugins | str join "\n" $".:($port) { ($plugins_str) }" } # Generate metrics block for Corefile def generate-metrics-block [ metrics_port: int ] -> string { $".:($metrics_port) { prometheus errors }" } # Generate zone file for domain export def generate-zone-file [ zone_name: string # Zone name (e.g., "provisioning.local") records: list # List of DNS records config: record # CoreDNS configuration ] -> string { log debug $"Generating zone file for ($zone_name)" let default_ttl = $config.default_ttl? | default 3600 let admin_email = $"admin.($zone_name)" let serial = date now | format date "%Y%m%d01" # YYYYMMDDnn format mut zone_content = [] # Origin and TTL $zone_content = ($zone_content | append $"$ORIGIN ($zone_name).") $zone_content = ($zone_content | append $"$TTL ($default_ttl)") $zone_content = ($zone_content | append "") # SOA record let soa_block = generate-soa-record $zone_name $admin_email $serial $zone_content = ($zone_content | append $soa_block) $zone_content = ($zone_content | append "") # NS record $zone_content = ($zone_content | append $"@ IN NS ns1.($zone_name).") $zone_content = ($zone_content | append "") # A record for nameserver $zone_content = ($zone_content | append "ns1 IN A 127.0.0.1") $zone_content = ($zone_content | append "") # User-defined records if ($records | is-not-empty) { $zone_content = ($zone_content | append "; Infrastructure records") for record in $records { let record_line = generate-record-line $record $zone_content = ($zone_content | append $record_line) } } $zone_content | str join "\n" } # Generate SOA record def generate-soa-record [ zone_name: string admin_email: string serial: string ] -> string { $"@ IN SOA ns1.($zone_name). ($admin_email). ( ($serial) ; Serial 3600 ; Refresh 1800 ; Retry 604800 ; Expire 86400 ) ; Minimum TTL" } # Generate DNS record line def generate-record-line [ record: record ] -> string { let name = $record.name let type = $record.type let value = $record.value let ttl = $record.ttl? | default "" let priority = $record.priority? | default "" let comment = $record.comment? | default "" mut parts = [] # Name (padded to 16 chars) $parts = ($parts | append ($name | fill -a left -w 16)) # TTL (optional) if ($ttl != "") { $parts = ($parts | append ($ttl | into string | fill -a left -w 8)) } else { $parts = ($parts | append ("" | fill -a left -w 8)) } # Class (always IN) $parts = ($parts | append "IN") # Type $parts = ($parts | append ($type | fill -a left -w 8)) # Priority (for MX and SRV) if ($priority != "") { $parts = ($parts | append ($priority | into string)) } # Value $parts = ($parts | append $value) let record_line = $parts | str join " " # Add comment if present if ($comment != "") { $"($record_line) ; ($comment)" } else { $record_line } } # Validate Corefile syntax export def validate-corefile [ corefile_path: string # Path to Corefile ] -> record { log debug $"Validating Corefile at ($corefile_path)" let expanded_path = $corefile_path | path expand if not ($expanded_path | path exists) { return { valid: false errors: ["Corefile not found"] warnings: [] } } mut errors = [] mut warnings = [] let content = open $expanded_path # Basic validation checks if ($content | str contains "file ") { # Check if zone files are referenced log debug "Zone files referenced in Corefile" } else { $warnings = ($warnings | append "No zone files referenced") } if ($content | str contains "forward ") { log debug "Forward plugin configured" } else { $warnings = ($warnings | append "No forward plugin configured") } # Check for balanced braces let open_braces = $content | str replace -a -r '[^{]' '' | str length let close_braces = $content | str replace -a -r '[^}]' '' | str length if $open_braces != $close_braces { $errors = ($errors | append "Unbalanced braces in Corefile") } { valid: ($errors | is-empty) errors: $errors warnings: $warnings } } # Add plugin to Corefile export def add-corefile-plugin [ corefile_path: string # Path to Corefile zone: string # Zone name plugin_config: string # Plugin configuration line ] -> bool { log debug $"Adding plugin to Corefile zone ($zone)" let expanded_path = $corefile_path | path expand if not ($expanded_path | path exists) { log error $"Corefile not found at ($expanded_path)" return false } let content = open $expanded_path # Find zone block and add plugin let zone_pattern = $"($zone):\\d+ \\{" if ($content | str contains -r $zone_pattern) { # Insert plugin before closing brace let updated = $content | str replace -r $"($zone_pattern)(.*?)\\}" $"$1$2 ($plugin_config)\n}" $updated | save -f $expanded_path log info $"Added plugin to ($zone) in Corefile" true } else { log error $"Zone ($zone) not found in Corefile" false } } # Write Corefile to disk export def write-corefile [ corefile_path: string # Path to write Corefile content: string # Corefile content ] -> bool { log debug $"Writing Corefile to ($corefile_path)" let expanded_path = $corefile_path | path expand let parent_dir = $expanded_path | path dirname # Create parent directory if needed if not ($parent_dir | path exists) { mkdir $parent_dir } let result = (do { $content | save -f $expanded_path } | complete) if $result.exit_code == 0 { log info $"Corefile written to [$expanded_path]" true } else { log error $"Failed to write Corefile to [$expanded_path]" false } } # Read Corefile from disk export def read-corefile [ corefile_path: string # Path to Corefile ] -> string { let expanded_path = $corefile_path | path expand if not ($expanded_path | path exists) { log error $"Corefile not found at ($expanded_path)" return "" } open $expanded_path } # Update Corefile with new configuration export def update-corefile [ config: record # CoreDNS configuration corefile_path: string # Path to Corefile ] -> bool { log info "Updating Corefile with new configuration" let new_content = generate-corefile $config write-corefile $corefile_path $new_content }