Files
Charon/backend/internal/caddy/config.go

160 lines
3.7 KiB
Go

package caddy
import (
"fmt"
"strings"
"github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/models"
)
// GenerateConfig creates a Caddy JSON configuration from proxy hosts.
// This is the core transformation layer from our database model to Caddy config.
func GenerateConfig(hosts []models.ProxyHost, storageDir string, acmeEmail string) (*Config, error) {
// Define log file paths
// We assume storageDir is like ".../data/caddy/data", so we go up to ".../data/logs"
// Or we can just use a relative path if Caddy's working directory is set correctly.
// In Docker, WORKDIR is /app, and storageDir passed here is usually /app/data/caddy.
// Let's put logs in /app/data/logs/access.log
logFile := "/app/data/logs/access.log"
config := &Config{
Logging: &LoggingConfig{
Logs: map[string]*LogConfig{
"access": {
Level: "INFO",
Writer: &WriterConfig{
Output: "file",
Filename: logFile,
Roll: true,
RollSize: 10, // 10 MB
RollKeep: 5, // Keep 5 files
RollKeepDays: 7, // Keep for 7 days
},
Encoder: &EncoderConfig{
Format: "json",
},
Include: []string{"http.log.access.access_log"},
},
},
},
Apps: Apps{
HTTP: &HTTPApp{
Servers: map[string]*Server{},
},
},
Storage: Storage{
System: "file_system",
Root: storageDir,
},
}
if acmeEmail != "" {
config.Apps.TLS = &TLSApp{
Automation: &AutomationConfig{
Policies: []*AutomationPolicy{
{
IssuersRaw: []interface{}{
map[string]interface{}{
"module": "acme",
"email": acmeEmail,
},
map[string]interface{}{
"module": "zerossl",
"email": acmeEmail,
},
},
},
},
},
}
}
if len(hosts) == 0 {
return config, nil
}
// We already initialized srv0 above, so we just append routes to it
routes := make([]*Route, 0)
for _, host := range hosts {
if !host.Enabled {
continue
}
if host.DomainNames == "" {
return nil, fmt.Errorf("proxy host %s has empty domain names", host.UUID)
}
// Parse comma-separated domains
domains := strings.Split(host.DomainNames, ",")
for i := range domains {
domains[i] = strings.TrimSpace(domains[i])
}
// Build handlers for this host
handlers := make([]Handler, 0)
// Add HSTS header if enabled
if host.HSTSEnabled {
hstsValue := "max-age=31536000"
if host.HSTSSubdomains {
hstsValue += "; includeSubDomains"
}
handlers = append(handlers, HeaderHandler(map[string][]string{
"Strict-Transport-Security": {hstsValue},
}))
}
// Add exploit blocking if enabled
if host.BlockExploits {
handlers = append(handlers, BlockExploitsHandler())
}
// Handle custom locations first (more specific routes)
for _, loc := range host.Locations {
dial := fmt.Sprintf("%s:%d", loc.ForwardHost, loc.ForwardPort)
locRoute := &Route{
Match: []Match{
{
Host: domains,
Path: []string{loc.Path, loc.Path + "/*"},
},
},
Handle: []Handler{
ReverseProxyHandler(dial, host.WebsocketSupport),
},
Terminal: true,
}
routes = append(routes, locRoute)
}
// Main proxy handler
dial := fmt.Sprintf("%s:%d", host.ForwardHost, host.ForwardPort)
mainHandlers := append(handlers, ReverseProxyHandler(dial, host.WebsocketSupport))
route := &Route{
Match: []Match{
{Host: domains},
},
Handle: mainHandlers,
Terminal: true,
}
routes = append(routes, route)
}
config.Apps.HTTP.Servers["cpm_server"] = &Server{
Listen: []string{":80", ":443"},
Routes: routes,
AutoHTTPS: &AutoHTTPSConfig{
Disable: false,
DisableRedir: false,
},
Logs: &ServerLogs{
DefaultLoggerName: "access_log",
},
}
return config, nil
}