# Caddy Configuration Manager for Windows # This script adds new service configurations to your Caddyfile param( [string]$Config, [string]$Subdomain, [string]$CaddyfilePath = "C:\caddy\Caddyfile", [bool]$ReloadCaddy = $true ) # Function to write JSON response function Write-JsonResponse { param($Status, $Message, $Data = $null) $response = @{ status = $Status message = $Message timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } if ($Data) { $response.data = $Data } return $response | ConvertTo-Json } # Function to extract CA names from Caddyfile function Get-CaddyfileCAs { param([string]$CaddyfilePath) try { Write-Host "DEBUG: Checking file path: $CaddyfilePath" if (-not (Test-Path $CaddyfilePath)) { Write-Host "DEBUG: File not found" return @() } $content = Get-Content $CaddyfilePath -Raw Write-Host "DEBUG: File content length: $($content.Length)" Write-Host "DEBUG: First 200 chars: $($content.Substring(0, [Math]::Min(200, $content.Length)))" $caNames = @() # Pattern 1: PKI block with CA definitions - pki { ca ca_name { name "Friendly Name" } } $pkiPattern = 'pki\s*\{[^}]*?ca\s+([^\s\{]+)\s*\{([^}]*?)\}' Write-Host "DEBUG: Searching for PKI pattern: $pkiPattern" $pkiMatches = [regex]::Matches($content, $pkiPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Singleline) Write-Host "DEBUG: PKI matches found: $($pkiMatches.Count)" foreach ($match in $pkiMatches) { $caId = $match.Groups[1].Value $caBlock = $match.Groups[2].Value Write-Host "DEBUG: Found CA ID: $caId" Write-Host "DEBUG: CA Block: $caBlock" # Try to extract the friendly name from within the CA block $nameMatch = [regex]::Match($caBlock, 'name\s+"([^"]+)"', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) if ($nameMatch.Success) { $friendlyName = $nameMatch.Groups[1].Value Write-Host "DEBUG: Found friendly name: $friendlyName" $caNames += "$caId ($friendlyName)" } else { Write-Host "DEBUG: No friendly name found, using ID only" $caNames += $caId } } # Pattern 2: tls { ca ca_name } $matches1 = [regex]::Matches($content, 'tls\s*\{\s*ca\s+([^\s\}]+)', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) Write-Host "DEBUG: TLS block matches: $($matches1.Count)" foreach ($match in $matches1) { $caNames += $match.Groups[1].Value } # Pattern 3: tls ca_name (direct) $matches2 = [regex]::Matches($content, 'tls\s+([^\s\{]+)', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) Write-Host "DEBUG: Direct TLS matches: $($matches2.Count)" foreach ($match in $matches2) { $caName = $match.Groups[1].Value if ($caName -ne "internal" -and $caName -ne "off") { $caNames += $caName } } Write-Host "DEBUG: Total CAs found: $($caNames.Count)" Write-Host "DEBUG: CA list: $($caNames -join ', ')" # Remove duplicates and return return $caNames | Sort-Object | Get-Unique } catch { Write-Host "DEBUG: Exception in Get-CaddyfileCAs: $($_.Exception.Message)" Write-Host "Error reading CAs from Caddyfile: $($_.Exception.Message)" return @() } } # Handle get-cas command if ($args[0] -eq "get-cas") { $CaddyfilePath = if ($args[1]) { $args[1] } else { "C:\caddy\Caddyfile" } Write-Host "DEBUG: get-cas command received" Write-Host "DEBUG: Caddyfile path: $CaddyfilePath" Write-Host "DEBUG: File exists: $(Test-Path $CaddyfilePath)" try { $cas = Get-CaddyfileCAs -CaddyfilePath $CaddyfilePath Write-Host "DEBUG: CAs found: $($cas -join ', ')" $response = @{ status = "success" message = "CAs retrieved successfully" data = @{ cas = $cas count = $cas.Count caddyfilePath = $CaddyfilePath } timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } Write-Output ($response | ConvertTo-Json) exit 0 } catch { Write-Host "DEBUG: Error occurred: $($_.Exception.Message)" $response = @{ status = "error" message = "Failed to retrieve CAs: $($_.Exception.Message)" timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } Write-Output ($response | ConvertTo-Json) exit 1 } } # Main configuration addition logic try { # Validate required parameters for config addition if (-not $Config -or -not $Subdomain) { Write-Output (Write-JsonResponse "error" "Config and Subdomain parameters are required") exit 1 } # Check if Caddyfile exists if (-not (Test-Path $CaddyfilePath)) { Write-Output (Write-JsonResponse "error" "Caddyfile not found at: $CaddyfilePath") exit 1 } # Read existing Caddyfile $existingConfig = Get-Content $CaddyfilePath -Raw -ErrorAction Stop # Check if subdomain already exists if ($existingConfig -match "$Subdomain\.sami\s*\{") { Write-Output (Write-JsonResponse "error" "Subdomain '$Subdomain.sami' already exists in Caddyfile") exit 1 } # Create backup $backupPath = "$CaddyfilePath.backup.$(Get-Date -Format 'yyyyMMdd-HHmmss')" Copy-Item $CaddyfilePath $backupPath -ErrorAction Stop Write-Host "Backup created: $backupPath" # Append new configuration $newContent = $existingConfig.TrimEnd() + "`n`n" + $Config.TrimEnd() + "`n" Set-Content -Path $CaddyfilePath -Value $newContent -NoNewline -ErrorAction Stop Write-Host "Configuration added successfully" # Reload Caddy if requested if ($ReloadCaddy) { Write-Host "Reloading Caddy..." # Try to reload Caddy $reloadResult = & caddy reload --config $CaddyfilePath 2>&1 if ($LASTEXITCODE -eq 0) { Write-Host "Caddy reloaded successfully" Write-Output (Write-JsonResponse "success" "Configuration added and Caddy reloaded successfully" @{ backup = $backupPath subdomain = "$Subdomain.sami" }) } else { Write-Host "Caddy reload failed: $reloadResult" # Restore backup if reload failed Copy-Item $backupPath $CaddyfilePath -Force Write-Output (Write-JsonResponse "error" "Caddy reload failed. Configuration rolled back. Error: $reloadResult") exit 1 } } else { Write-Output (Write-JsonResponse "success" "Configuration added successfully (Caddy not reloaded)" @{ backup = $backupPath subdomain = "$Subdomain.sami" }) } } catch { Write-Output (Write-JsonResponse "error" "Error: $($_.Exception.Message)") exit 1 }