diff --git a/caddyconfig/configbuilder/configbuilder.go b/caddyconfig/configbuilder/configbuilder.go new file mode 100644 index 00000000000..d415a1418f6 --- /dev/null +++ b/caddyconfig/configbuilder/configbuilder.go @@ -0,0 +1,182 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configbuilder + +import ( + "fmt" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" +) + +// Builder provides methods for safely building a Caddy configuration +// by accumulating apps, admin config, logging, storage, etc. +// It prevents common errors like duplicate app names. +type Builder struct { + config *caddy.Config + warnings *[]caddyconfig.Warning + // apps stores structured app configs before marshaling + // Multiple blocks can contribute to the same app + apps map[string]any +} + +// New creates a new config builder with an empty configuration. +func New() *Builder { + warnings := []caddyconfig.Warning{} + return &Builder{ + config: &caddy.Config{ + AppsRaw: make(caddy.ModuleMap), + }, + warnings: &warnings, + apps: make(map[string]any), + } +} + +// Config returns the built configuration. +// This automatically finalizes any structured apps that haven't been marshaled yet. +func (b *Builder) Config() *caddy.Config { + b.finalize() + return b.config +} + +// Warnings returns the accumulated warnings. +func (b *Builder) Warnings() []caddyconfig.Warning { + return *b.warnings +} + +// AddWarning adds a warning to the builder. +func (b *Builder) AddWarning(message string) { + *b.warnings = append(*b.warnings, caddyconfig.Warning{ + Message: message, + }) +} + +// GetApp retrieves a structured app config by name. +// Returns a pointer to the app and true if found, nil and false otherwise. +// The returned value can be type-asserted to the specific app type. +// Blocks should use this to get an existing app before modifying it. +func (b *Builder) GetApp(name string) (any, bool) { + app, ok := b.apps[name] + return app, ok +} + +// GetTypedApp retrieves a structured app config by name with the correct type. +// Returns a pointer to the app and true if found, nil and false otherwise. +// This is a type-safe alternative to GetApp that uses generics. +// Example: httpApp, ok := builder.GetTypedApp[caddyhttp.App]("http") +func GetTypedApp[T any](b *Builder, name string) (*T, bool) { + app, ok := b.apps[name] + if !ok { + return nil, false + } + typedApp, ok := app.(*T) + return typedApp, ok +} + +// CreateApp stores a new structured app config by name. +// Returns an error if an app with this name already exists. +// Blocks that want to modify an existing app should use GetApp() first. +func (b *Builder) CreateApp(name string, app any) error { + if _, exists := b.apps[name]; exists { + return fmt.Errorf("app '%s' already exists", name) + } + b.apps[name] = app + return nil +} + +// finalize marshals all structured apps to JSON and adds them to the config. +// This is called automatically by Config(). +func (b *Builder) finalize() { + for name, app := range b.apps { + b.addApp(name, app) + } + // Clear apps after finalizing so we don't re-add them if Config() is called again + b.apps = make(map[string]any) +} + +// addApp is an internal method to add an app to the configuration by marshaling it to JSON. +// If an app with the same name already exists, a warning is added and the duplicate is ignored. +func (b *Builder) addApp(name string, val any) { + if b.config.AppsRaw == nil { + b.config.AppsRaw = make(caddy.ModuleMap) + } + + if _, exists := b.config.AppsRaw[name]; exists { + b.AddWarning(fmt.Sprintf("app '%s' already exists in configuration, ignoring duplicate declaration", name)) + return + } + + b.config.AppsRaw[name] = caddyconfig.JSON(val, b.warnings) +} + +// SetAdmin sets the admin configuration. +// If admin config already exists, it will be replaced. +func (b *Builder) SetAdmin(admin *caddy.AdminConfig) { + b.config.Admin = admin +} + +// GetAdmin returns the admin configuration, creating it if it doesn't exist. +func (b *Builder) GetAdmin() *caddy.AdminConfig { + if b.config.Admin == nil { + b.config.Admin = new(caddy.AdminConfig) + } + return b.config.Admin +} + +// SetStorage sets the storage configuration from a StorageConverter. +// If storage config already exists, it will be replaced. +func (b *Builder) SetStorage(storage caddy.StorageConverter) { + b.config.StorageRaw = caddyconfig.JSONModuleObject( + storage, + "module", + storage.(caddy.Module).CaddyModule().ID.Name(), + b.warnings, + ) +} + +// SetDefaultLogger sets the default logger configuration. +func (b *Builder) SetDefaultLogger(log *caddy.CustomLog) { + if b.config.Logging == nil { + b.config.Logging = &caddy.Logging{ + Logs: make(map[string]*caddy.CustomLog), + } + } + if b.config.Logging.Logs == nil { + b.config.Logging.Logs = make(map[string]*caddy.CustomLog) + } + b.config.Logging.Logs[caddy.DefaultLoggerName] = log +} + +// SetLogger sets a named logger configuration. +func (b *Builder) SetLogger(name string, log *caddy.CustomLog) { + if b.config.Logging == nil { + b.config.Logging = &caddy.Logging{ + Logs: make(map[string]*caddy.CustomLog), + } + } + if b.config.Logging.Logs == nil { + b.config.Logging.Logs = make(map[string]*caddy.CustomLog) + } + b.config.Logging.Logs[name] = log +} + +// AddRawApp adds a raw JSON app to the configuration. +// This is useful for adding apps that come from global options as pre-marshaled JSON. +func (b *Builder) AddRawApp(name string, rawJSON []byte) { + if b.config.AppsRaw == nil { + b.config.AppsRaw = make(caddy.ModuleMap) + } + b.config.AppsRaw[name] = rawJSON +} diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go index 1121776d98f..7f832206b5a 100644 --- a/caddyconfig/httpcaddyfile/addresses.go +++ b/caddyconfig/httpcaddyfile/addresses.go @@ -77,10 +77,10 @@ import ( // repetition may be undesirable, so call consolidateAddrMappings() to map // multiple addresses to the same lists of server blocks (a many:many mapping). // (Doing this is essentially a map-reduce technique.) -func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock, +func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []ServerBlock, options map[string]any, -) (map[string]map[string][]serverBlock, error) { - addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{} +) (map[string]map[string][]ServerBlock, error) { + addrToProtocolToServerBlocks := map[string]map[string][]ServerBlock{} type keyWithParsedKey struct { key caddyfile.Token @@ -94,7 +94,7 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks [] // key of a server block as its own, but without having to repeat its // contents in cases where multiple keys really can be served together addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{} - for j, key := range sblock.block.Keys { + for j, key := range sblock.Block.Keys { parsedKey, err := ParseAddress(key.Text) if err != nil { return nil, fmt.Errorf("parsing key: %v", err) @@ -154,7 +154,7 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks [] protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr] if !ok { - protocolToServerBlocks = map[string][]serverBlock{} + protocolToServerBlocks = map[string][]ServerBlock{} addrToProtocolToServerBlocks[addr] = protocolToServerBlocks } @@ -169,13 +169,13 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks [] parsedKeys[k] = keyWithParsedKey.parsedKey } - protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{ - block: caddyfile.ServerBlock{ + protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], ServerBlock{ + Block: caddyfile.ServerBlock{ Keys: keys, - Segments: sblock.block.Segments, + Segments: sblock.Block.Segments, }, - pile: sblock.pile, - parsedKeys: parsedKeys, + Pile: sblock.Pile, + ParsedKeys: parsedKeys, }) } } @@ -192,7 +192,7 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks [] // Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each // association from multiple addresses to multiple server blocks; i.e. each element of // the returned slice) becomes a server definition in the output JSON. -func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation { +func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]ServerBlock) []sbAddrAssociation { sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks)) addrs := make([]string, 0, len(addrToProtocolToServerBlocks)) @@ -267,7 +267,7 @@ func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[s // listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from // Caddy listener addresses and the protocols to serve them with to the parsed address for each server block. -func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address, +func (st *ServerType) listenersForServerBlockAddress(sblock ServerBlock, addr Address, options map[string]any, ) (map[string]map[string]struct{}, error) { switch addr.Scheme { @@ -307,8 +307,8 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad } // the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional - lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"])) - for _, cfgVal := range sblock.pile["bind"] { + lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.Pile["bind"])) + for _, cfgVal := range sblock.Pile["bind"] { if val, ok := cfgVal.Value.(addressesWithProtocols); ok { lnCfgVals = append(lnCfgVals, val) } diff --git a/caddyconfig/httpcaddyfile/blocktypes/blocktypes.go b/caddyconfig/httpcaddyfile/blocktypes/blocktypes.go new file mode 100644 index 00000000000..19b18906171 --- /dev/null +++ b/caddyconfig/httpcaddyfile/blocktypes/blocktypes.go @@ -0,0 +1,127 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package blocktypes + +import ( + "fmt" + "sync" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/configbuilder" +) + +// BlockTypeSetupFunc is a function that processes server blocks of a specific type +// and builds the configuration using the provided builder. It receives a config builder, +// the server blocks to process, and any global options. +type BlockTypeSetupFunc func(builder *configbuilder.Builder, blocks []caddyfile.ServerBlock, options map[string]any) ([]caddyconfig.Warning, error) + +// BlockTypeInfo contains metadata about a registered block type. +type BlockTypeInfo struct { + SetupFunc BlockTypeSetupFunc + Parent string // Parent block type (e.g., "http" for "http.server") +} + +var ( + registeredBlockTypes = make(map[string]*BlockTypeInfo) + blockTypesMu sync.RWMutex +) + +// RegisterBlockType registers a top-level block type handler with the given name. +// This allows the block type to be used in xcaddyfile configurations with [name] syntax. +// For example, RegisterBlockType("http", setupFunc) allows [http] blocks. +// +// Block type handlers are responsible for parsing their specific block types +// and mutating the provided caddy.Config accordingly. +func RegisterBlockType(name string, setupFunc BlockTypeSetupFunc) { + blockTypesMu.Lock() + defer blockTypesMu.Unlock() + + if _, exists := registeredBlockTypes[name]; exists { + panic(fmt.Sprintf("block type %s already registered", name)) + } + + registeredBlockTypes[name] = &BlockTypeInfo{ + SetupFunc: setupFunc, + Parent: "", // No parent for top-level block types + } +} + +// RegisterChildBlockType registers a hierarchical block type under a parent. +// For example, RegisterChildBlockType("http.server", "http", setupFunc) registers +// [http.server] as a child of [http]. +// +// Child blocks are processed after their parent and can access context set by the parent. +func RegisterChildBlockType(name, parent string, setupFunc BlockTypeSetupFunc) { + blockTypesMu.Lock() + defer blockTypesMu.Unlock() + + if _, exists := registeredBlockTypes[name]; exists { + panic(fmt.Sprintf("block type %s already registered", name)) + } + + registeredBlockTypes[name] = &BlockTypeInfo{ + SetupFunc: setupFunc, + Parent: parent, + } +} + +// GetBlockType retrieves a registered block type handler by name. +// Returns the handler function and true if found, nil and false otherwise. +func GetBlockType(name string) (BlockTypeSetupFunc, bool) { + blockTypesMu.RLock() + defer blockTypesMu.RUnlock() + + info, exists := registeredBlockTypes[name] + if !exists { + return nil, false + } + return info.SetupFunc, true +} + +// GetBlockTypeInfo retrieves full block type info including parent relationship. +func GetBlockTypeInfo(name string) (*BlockTypeInfo, bool) { + blockTypesMu.RLock() + defer blockTypesMu.RUnlock() + + info, exists := registeredBlockTypes[name] + return info, exists +} + +// GetChildBlockTypes returns all child block types for a given parent. +func GetChildBlockTypes(parent string) []string { + blockTypesMu.RLock() + defer blockTypesMu.RUnlock() + + var children []string + for name, info := range registeredBlockTypes { + if info.Parent == parent { + children = append(children, name) + } + } + return children +} + +// RegisteredBlockTypes returns a list of all registered block type names. +func RegisteredBlockTypes() []string { + blockTypesMu.RLock() + defer blockTypesMu.RUnlock() + + names := make([]string, 0, len(registeredBlockTypes)) + for name := range registeredBlockTypes { + names = append(names, name) + } + return names +} diff --git a/caddyconfig/httpcaddyfile/blocktypes/globalblock/globalblock.go b/caddyconfig/httpcaddyfile/blocktypes/globalblock/globalblock.go new file mode 100644 index 00000000000..5276466ebdf --- /dev/null +++ b/caddyconfig/httpcaddyfile/blocktypes/globalblock/globalblock.go @@ -0,0 +1,50 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package globalblock + +import ( + "fmt" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/configbuilder" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes" +) + +func init() { + blocktypes.RegisterBlockType("global", setup) +} + +// setup processes global configuration blocks which store global options +// (http_port, https_port, grace_period, etc.) in options for other block parsers to use. +func setup(builder *configbuilder.Builder, blocks []caddyfile.ServerBlock, options map[string]any) ([]caddyconfig.Warning, error) { + var warnings []caddyconfig.Warning + + // Process each global block + for _, block := range blocks { + // Global blocks should not have keys + if len(block.Keys) > 0 { + return warnings, fmt.Errorf("[global] blocks should not have keys") + } + + // Use httpcaddyfile's EvaluateGlobalOptions for all global options + if err := httpcaddyfile.EvaluateGlobalOptions(block.Segments, options); err != nil { + return warnings, err + } + } + + return warnings, nil +} diff --git a/caddyconfig/httpcaddyfile/blocktypes/httpserverblock/httpserverblock.go b/caddyconfig/httpcaddyfile/blocktypes/httpserverblock/httpserverblock.go new file mode 100644 index 00000000000..4e8bbff8a01 --- /dev/null +++ b/caddyconfig/httpcaddyfile/blocktypes/httpserverblock/httpserverblock.go @@ -0,0 +1,125 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpserverblock + +import ( + "fmt" + "reflect" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/configbuilder" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddypki" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + blocktypes.RegisterChildBlockType("http.server", "global", setup) +} + +// extractServerBlocks converts raw caddyfile blocks to httpcaddyfile serverBlock format +func extractServerBlocks(inputBlocks []caddyfile.ServerBlock, warnings []caddyconfig.Warning) ([]httpcaddyfile.ServerBlock, []caddyconfig.Warning, error) { + serverBlocks := make([]httpcaddyfile.ServerBlock, 0, len(inputBlocks)) + for _, sblock := range inputBlocks { + for j, k := range sblock.Keys { + if j == 0 && strings.HasPrefix(k.Text, "@") { + return nil, warnings, fmt.Errorf("%s:%d: cannot define a matcher outside of a site block: '%s'", k.File, k.Line, k.Text) + } + if httpcaddyfile.DirectiveIsRegistered(k.Text) { + return nil, warnings, fmt.Errorf("%s:%d: parsed '%s' as a site address, but it is a known directive; directives must appear in a site block", k.File, k.Line, k.Text) + } + } + serverBlocks = append(serverBlocks, httpcaddyfile.ServerBlock{ + Block: sblock, + Pile: make(map[string][]httpcaddyfile.ConfigValue), + }) + } + return serverBlocks, warnings, nil +} + +// setup processes [http.server] blocks using the httpcaddyfile adapter. +// This leverages all existing HTTP configuration logic. +// The [global] block should have been processed first to set up global options. +func setup(builder *configbuilder.Builder, blocks []caddyfile.ServerBlock, options map[string]any) ([]caddyconfig.Warning, error) { + var warnings []caddyconfig.Warning + + // Extract server blocks with validation + serverBlocks, warnings, err := extractServerBlocks(blocks, warnings) + if err != nil { + return warnings, err + } + + // Use httpcaddyfile.BuildServersAndPairings to build servers and pairings + servers, pairings, metrics, warnings, err := httpcaddyfile.BuildServersAndPairings(serverBlocks, options, warnings) + if err != nil { + return warnings, err + } + + // Collect server-specific custom logs for xcaddyfiletype to process later + // Use a unique key name to avoid conflicts with other options + serverLogs := httpcaddyfile.CollectServerLogs(pairings) + if len(serverLogs) > 0 { + options["__xcaddyfile_server_logs__"] = serverLogs + } + + // Construct the HTTP app from the servers + // Use options from [global] block if they were set + httpApp := &caddyhttp.App{ + HTTPPort: httpcaddyfile.TryInt(options["http_port"], &warnings), + HTTPSPort: httpcaddyfile.TryInt(options["https_port"], &warnings), + GracePeriod: httpcaddyfile.TryDuration(options["grace_period"], &warnings), + ShutdownDelay: httpcaddyfile.TryDuration(options["shutdown_delay"], &warnings), + Metrics: metrics, + Servers: servers, + } + + // Create the HTTP app (should be the first time, since [global] doesn't create it) + if err := builder.CreateApp("http", httpApp); err != nil { + return warnings, err + } + + // Build TLS app using the pairings + tlsApp, tlsWarnings, err := httpcaddyfile.BuildTLSApp(pairings, options, warnings) + if err != nil { + return append(warnings, tlsWarnings...), err + } + warnings = append(warnings, tlsWarnings...) + // Only add TLS app if it's not empty (has certificates or automation policies) + if tlsApp != nil && (!reflect.DeepEqual(tlsApp, &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)})) { + if err := builder.CreateApp("tls", tlsApp); err != nil { + return warnings, err + } + } + + // Build PKI app using the pairings + pkiApp, pkiWarnings, err := httpcaddyfile.BuildPKIApp(pairings, options, warnings) + if err != nil { + return append(warnings, pkiWarnings...), err + } + warnings = append(warnings, pkiWarnings...) + // Only add PKI app if it's not empty (has CAs) + if pkiApp != nil && (!reflect.DeepEqual(pkiApp, &caddypki.PKI{CAs: make(map[string]*caddypki.CA)})) { + if err := builder.CreateApp("pki", pkiApp); err != nil { + return warnings, err + } + } + + return warnings, nil +} diff --git a/caddyconfig/httpcaddyfile/builtins_test.go b/caddyconfig/httpcaddyfile/builtins_test.go index c23531f22e6..0a5cbe9d798 100644 --- a/caddyconfig/httpcaddyfile/builtins_test.go +++ b/caddyconfig/httpcaddyfile/builtins_test.go @@ -1,10 +1,13 @@ -package httpcaddyfile +package httpcaddyfile_test import ( "strings" "testing" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + _ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/globalblock" + _ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/httpserverblock" _ "github.com/caddyserver/caddy/v2/modules/logging" ) @@ -79,7 +82,7 @@ func TestLogDirectiveSyntax(t *testing.T) { } { adapter := caddyfile.Adapter{ - ServerType: ServerType{}, + ServerType: httpcaddyfile.ServerType{}, } out, _, err := adapter.Adapt([]byte(tc.input), nil) @@ -220,7 +223,7 @@ func TestRedirDirectiveSyntax(t *testing.T) { } { adapter := caddyfile.Adapter{ - ServerType: ServerType{}, + ServerType: httpcaddyfile.ServerType{}, } _, _, err := adapter.Adapt([]byte(tc.input), nil) @@ -283,7 +286,7 @@ func TestImportErrorLine(t *testing.T) { }, } { adapter := caddyfile.Adapter{ - ServerType: ServerType{}, + ServerType: httpcaddyfile.ServerType{}, } _, _, err := adapter.Adapt([]byte(tc.input), nil) @@ -356,7 +359,7 @@ func TestNestedImport(t *testing.T) { }, } { adapter := caddyfile.Adapter{ - ServerType: ServerType{}, + ServerType: httpcaddyfile.ServerType{}, } _, _, err := adapter.Adapt([]byte(tc.input), nil) diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index eac7f5dc2f7..36b24e27cf1 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -530,13 +530,13 @@ func sortRoutes(routes []ConfigValue) { }) } -// serverBlock pairs a Caddyfile server block with -// a "pile" of config values, keyed by class name, -// as well as its parsed keys for convenience. -type serverBlock struct { - block caddyfile.ServerBlock - pile map[string][]ConfigValue // config values obtained from directives - parsedKeys []Address +// ServerBlock pairs a Caddyfile server block with a "pile" of config values, +// keyed by class name, as well as its parsed keys for convenience. +// This is the unified type used by both httpcaddyfile and xcaddyfile. +type ServerBlock struct { + Block caddyfile.ServerBlock + Pile map[string][]ConfigValue // config values obtained from directives + ParsedKeys []Address } // hostsFromKeys returns a list of all the non-empty hostnames found in @@ -550,10 +550,10 @@ type serverBlock struct { // header of requests that come in for that key. // // The resulting slice is not sorted but will never have duplicates. -func (sb serverBlock) hostsFromKeys(loggerMode bool) []string { +func (sb ServerBlock) hostsFromKeys(loggerMode bool) []string { // ensure each entry in our list is unique hostMap := make(map[string]struct{}) - for _, addr := range sb.parsedKeys { + for _, addr := range sb.ParsedKeys { if addr.Host == "" { if !loggerMode { // server block contains a key like ":443", i.e. the host portion @@ -582,10 +582,10 @@ func (sb serverBlock) hostsFromKeys(loggerMode bool) []string { return sblockHosts } -func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string { +func (sb ServerBlock) hostsFromKeysNotHTTP(httpPort string) []string { // ensure each entry in our list is unique hostMap := make(map[string]struct{}) - for _, addr := range sb.parsedKeys { + for _, addr := range sb.ParsedKeys { if addr.Host == "" { continue } @@ -605,16 +605,16 @@ func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string { // hasHostCatchAllKey returns true if sb has a key that // omits a host portion, i.e. it "catches all" hosts. -func (sb serverBlock) hasHostCatchAllKey() bool { - return slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool { +func (sb ServerBlock) hasHostCatchAllKey() bool { + return slices.ContainsFunc(sb.ParsedKeys, func(addr Address) bool { return addr.Host == "" }) } // isAllHTTP returns true if all sb keys explicitly specify // the http:// scheme -func (sb serverBlock) isAllHTTP() bool { - return !slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool { +func (sb ServerBlock) isAllHTTP() bool { + return !slices.ContainsFunc(sb.ParsedKeys, func(addr Address) bool { return addr.Scheme != "http" }) } diff --git a/caddyconfig/httpcaddyfile/directives_test.go b/caddyconfig/httpcaddyfile/directives_test.go index 2b4d3e6c370..978b9f1276a 100644 --- a/caddyconfig/httpcaddyfile/directives_test.go +++ b/caddyconfig/httpcaddyfile/directives_test.go @@ -78,7 +78,7 @@ func TestHostsFromKeys(t *testing.T) { []string{"example.com:2015"}, }, } { - sb := serverBlock{parsedKeys: tc.keys} + sb := ServerBlock{ParsedKeys: tc.keys} // test in normal mode actual := sb.hostsFromKeys(false) diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 3dcd3ea5b62..0183f495d13 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -30,8 +30,9 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/configbuilder" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes" "github.com/caddyserver/caddy/v2/modules/caddyhttp" - "github.com/caddyserver/caddy/v2/modules/caddypki" "github.com/caddyserver/caddy/v2/modules/caddytls" ) @@ -53,71 +54,172 @@ type App struct { // ServerType can set up a config from an HTTP Caddyfile. type ServerType struct{} -// Setup makes a config from the tokens. +// Setup processes the server blocks from a Caddyfile and builds a complete Caddy configuration. +// It supports explicit [type] syntax with type registry for extensibility, with backwards +// compatibility defaults: first block with no keys is treated as [global], others as [http.server]. func (st ServerType) Setup( inputServerBlocks []caddyfile.ServerBlock, options map[string]any, ) (*caddy.Config, []caddyconfig.Warning, error) { var warnings []caddyconfig.Warning - gc := counter{new(int)} - state := make(map[string]any) - // load all the server blocks and associate them with a "pile" of config values - originalServerBlocks := make([]serverBlock, 0, len(inputServerBlocks)) - for _, sblock := range inputServerBlocks { - for j, k := range sblock.Keys { - if j == 0 && strings.HasPrefix(k.Text, "@") { - return nil, warnings, fmt.Errorf("%s:%d: cannot define a matcher outside of a site block: '%s'", k.File, k.Line, k.Text) + // Track block types in order of appearance + type blockGroup struct { + blockType string + blocks []caddyfile.ServerBlock + } + var orderedBlocks []blockGroup + seenTypes := make(map[string]int) // maps block type to index in orderedBlocks + + for i, sblock := range inputServerBlocks { + // Extract explicit block type if present + blockType, err := extractBlockType(&sblock) + if err != nil { + return nil, warnings, err + } + + // Apply caddyfile compatibility defaults + if blockType == "" { + // First block with no keys is treated as global config + if i == 0 && len(sblock.Keys) == 0 { + blockType = "global" + } else { + // Default to http.server for backwards compatibility with Caddyfile + blockType = "http.server" } - if _, ok := registeredDirectives[k.Text]; ok { - return nil, warnings, fmt.Errorf("%s:%d: parsed '%s' as a site address, but it is a known directive; directives must appear in a site block", k.File, k.Line, k.Text) + } + + // Add to the appropriate group, preserving order + if idx, seen := seenTypes[blockType]; seen { + // Append to existing group + orderedBlocks[idx].blocks = append(orderedBlocks[idx].blocks, sblock) + } else { + // Create new group + seenTypes[blockType] = len(orderedBlocks) + orderedBlocks = append(orderedBlocks, blockGroup{ + blockType: blockType, + blocks: []caddyfile.ServerBlock{sblock}, + }) + } + } + + // Create the config builder + builder := configbuilder.New() + + // Process block types in the order they appear, but ensure parent blocks + // are processed before their children + processed := make(map[string]bool) + + var processBlock func(blockType string) error + processBlock = func(blockType string) error { + if processed[blockType] { + return nil + } + + // Get block type info to check for parent + info, ok := blocktypes.GetBlockTypeInfo(blockType) + if !ok { + available := blocktypes.RegisteredBlockTypes() + return fmt.Errorf("block type '%s' is not registered; available types: %v", blockType, available) + } + + // Process parent first if it exists + if info.Parent != "" { + if err := processBlock(info.Parent); err != nil { + return err } } - originalServerBlocks = append(originalServerBlocks, serverBlock{ - block: sblock, - pile: make(map[string][]ConfigValue), - }) + + // Find and process this block type's blocks + for _, group := range orderedBlocks { + if group.blockType == blockType { + blockWarnings, err := info.SetupFunc(builder, group.blocks, options) + warnings = append(warnings, blockWarnings...) + processed[blockType] = true + if err != nil { + return fmt.Errorf("processing [%s] blocks: %w", blockType, err) + } + break + } + } + + return nil } - // apply any global options - var err error - originalServerBlocks, err = st.evaluateGlobalOptionsBlock(originalServerBlocks, options) - if err != nil { + // Process each block type + for _, group := range orderedBlocks { + if err := processBlock(group.blockType); err != nil { + return nil, warnings, err + } + } + + // Apply global options from the options map to the builder + // This mirrors what httpcaddyfile does at the end of Setup() + if err := applyGlobalOptions(builder, options, &warnings); err != nil { + return nil, warnings, err + } + + // Get the config - this finalizes structured apps + cfg := builder.Config() + + // Process logs after all apps are finalized so we can collect server-specific logs + if err := processLogs(cfg, options, &warnings); err != nil { return nil, warnings, err } + return cfg, warnings, nil +} + +// ServerBlockPairings represents the association between listen addresses +// and server blocks. This is needed for building TLS and PKI apps. +type ServerBlockPairings []sbAddrAssociation + +// BuildServersAndPairings processes server blocks and creates servers from pairings. +// This is the core reusable primitive for building servers that is used by both +// httpcaddyfile and xcaddyfile. It processes directives, creates pairings, +// and builds servers without constructing the full HTTP app or processing +// global options. It returns the servers map, pairings, hoisted metrics, +// warnings, and any error. +// +// The caller is responsible for extracting/loading server blocks and for +// constructing the HTTP app from the returned servers. +func BuildServersAndPairings(originalServerBlocks []ServerBlock, options map[string]any, warnings []caddyconfig.Warning) (map[string]*caddyhttp.Server, ServerBlockPairings, *caddyhttp.Metrics, []caddyconfig.Warning, error) { + st := ServerType{} + gc := counter{new(int)} + state := make(map[string]any) + // this will replace both static and user-defined placeholder shorthands // with actual identifiers used by Caddy replacer := NewShorthandReplacer() - originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer) + originalServerBlocks, err := st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer) if err != nil { - return nil, warnings, err + return nil, nil, nil, warnings, err } for _, sb := range originalServerBlocks { - for i := range sb.block.Segments { - replacer.ApplyToSegment(&sb.block.Segments[i]) + for i := range sb.Block.Segments { + replacer.ApplyToSegment(&sb.Block.Segments[i]) } - if len(sb.block.Keys) == 0 { - return nil, warnings, fmt.Errorf("server block without any key is global configuration, and if used, it must be first") + if len(sb.Block.Keys) == 0 { + return nil, nil, nil, warnings, fmt.Errorf("server block without any key is global configuration, and if used, it must be first") } // extract matcher definitions matcherDefs := make(map[string]caddy.ModuleMap) - for _, segment := range sb.block.Segments { + for _, segment := range sb.Block.Segments { if dir := segment.Directive(); strings.HasPrefix(dir, matcherPrefix) { - d := sb.block.DispenseDirective(dir) + d := sb.Block.DispenseDirective(dir) err := parseMatcherDefinitions(d, matcherDefs) if err != nil { - return nil, warnings, err + return nil, nil, nil, warnings, err } } } // evaluate each directive ("segment") in this block - for _, segment := range sb.block.Segments { + for _, segment := range sb.Block.Segments { dir := segment.Directive() if strings.HasPrefix(dir, matcherPrefix) { @@ -129,10 +231,10 @@ func (st ServerType) Setup( if !ok { tkn := segment[0] message := "%s:%d: unrecognized directive: %s" - if !sb.block.HasBraces { + if !sb.Block.HasBraces { message += "\nDid you mean to define a second site? If so, you must use curly braces around each site to separate their configurations." } - return nil, warnings, fmt.Errorf(message, tkn.File, tkn.Line, dir) + return nil, nil, nil, warnings, fmt.Errorf(message, tkn.File, tkn.Line, dir) } h := Helper{ @@ -140,21 +242,21 @@ func (st ServerType) Setup( options: options, warnings: &warnings, matcherDefs: matcherDefs, - parentBlock: sb.block, + parentBlock: sb.Block, groupCounter: gc, State: state, } results, err := dirFunc(h) if err != nil { - return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err) + return nil, nil, nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err) } dir = normalizeDirectiveName(dir) for _, result := range results { result.directive = dir - sb.pile[result.Class] = append(sb.pile[result.Class], result) + sb.Pile[result.Class] = append(sb.Pile[result.Class], result) } // specially handle named routes that were pulled out from @@ -164,7 +266,7 @@ func (st ServerType) Setup( if state[namedRouteKey] != nil { for name := range state[namedRouteKey].(map[string]struct{}) { result := ConfigValue{Class: namedRouteKey, Value: name} - sb.pile[result.Class] = append(sb.pile[result.Class], result) + sb.Pile[result.Class] = append(sb.Pile[result.Class], result) } state[namedRouteKey] = nil } @@ -174,7 +276,7 @@ func (st ServerType) Setup( // map sbmap, err := st.mapAddressToProtocolToServerBlocks(originalServerBlocks, options) if err != nil { - return nil, warnings, err + return nil, nil, nil, warnings, err } // reduce @@ -184,7 +286,7 @@ func (st ServerType) Setup( // blocks is basically a server definition servers, err := st.serversFromPairings(pairings, options, &warnings, gc) if err != nil { - return nil, warnings, err + return nil, nil, nil, warnings, err } // hoist the metrics config from per-server to global @@ -199,179 +301,14 @@ func (st ServerType) Setup( } } - // now that each server is configured, make the HTTP app - httpApp := caddyhttp.App{ - HTTPPort: tryInt(options["http_port"], &warnings), - HTTPSPort: tryInt(options["https_port"], &warnings), - GracePeriod: tryDuration(options["grace_period"], &warnings), - ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings), - Metrics: metrics, - Servers: servers, - } - - // then make the TLS app - tlsApp, warnings, err := st.buildTLSApp(pairings, options, warnings) - if err != nil { - return nil, warnings, err - } - - // then make the PKI app - pkiApp, warnings, err := st.buildPKIApp(pairings, options, warnings) - if err != nil { - return nil, warnings, err - } - - // extract any custom logs, and enforce configured levels - var customLogs []namedCustomLog - var hasDefaultLog bool - addCustomLog := func(ncl namedCustomLog) { - if ncl.name == "" { - return - } - if ncl.name == caddy.DefaultLoggerName { - hasDefaultLog = true - } - if _, ok := options["debug"]; ok && ncl.log != nil && ncl.log.Level == "" { - ncl.log.Level = zap.DebugLevel.CapitalString() - } - customLogs = append(customLogs, ncl) - } - - // Apply global log options, when set - if options["log"] != nil { - for _, logValue := range options["log"].([]ConfigValue) { - addCustomLog(logValue.Value.(namedCustomLog)) - } - } - - if !hasDefaultLog { - // if the default log was not customized, ensure we - // configure it with any applicable options - if _, ok := options["debug"]; ok { - customLogs = append(customLogs, namedCustomLog{ - name: caddy.DefaultLoggerName, - log: &caddy.CustomLog{ - BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()}, - }, - }) - } - } - - // Apply server-specific log options - for _, p := range pairings { - for _, sb := range p.serverBlocks { - for _, clVal := range sb.pile["custom_log"] { - addCustomLog(clVal.Value.(namedCustomLog)) - } - } - } - - // annnd the top-level config, then we're done! - cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)} - - // loop through the configured options, and if any of - // them are an httpcaddyfile App, then we insert them - // into the config as raw Caddy apps - for _, opt := range options { - if app, ok := opt.(App); ok { - cfg.AppsRaw[app.Name] = app.Value - } - } - - // insert the standard Caddy apps into the config - if len(httpApp.Servers) > 0 { - cfg.AppsRaw["http"] = caddyconfig.JSON(httpApp, &warnings) - } - if !reflect.DeepEqual(tlsApp, &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) { - cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings) - } - if !reflect.DeepEqual(pkiApp, &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}) { - cfg.AppsRaw["pki"] = caddyconfig.JSON(pkiApp, &warnings) - } - if filesystems, ok := options["filesystem"].(caddy.Module); ok { - cfg.AppsRaw["caddy.filesystems"] = caddyconfig.JSON( - filesystems, - &warnings) - } - - if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok { - cfg.StorageRaw = caddyconfig.JSONModuleObject(storageCvtr, - "module", - storageCvtr.(caddy.Module).CaddyModule().ID.Name(), - &warnings) - } - if adminConfig, ok := options["admin"].(*caddy.AdminConfig); ok && adminConfig != nil { - cfg.Admin = adminConfig - } - if pc, ok := options["persist_config"].(string); ok && pc == "off" { - if cfg.Admin == nil { - cfg.Admin = new(caddy.AdminConfig) - } - if cfg.Admin.Config == nil { - cfg.Admin.Config = new(caddy.ConfigSettings) - } - cfg.Admin.Config.Persist = new(bool) - } - - if len(customLogs) > 0 { - if cfg.Logging == nil { - cfg.Logging = &caddy.Logging{ - Logs: make(map[string]*caddy.CustomLog), - } - } - - // Add the default log first if defined, so that it doesn't - // accidentally get re-created below due to the Exclude logic - for _, ncl := range customLogs { - if ncl.name == caddy.DefaultLoggerName && ncl.log != nil { - cfg.Logging.Logs[caddy.DefaultLoggerName] = ncl.log - break - } - } - - // Add the rest of the custom logs - for _, ncl := range customLogs { - if ncl.log == nil || ncl.name == caddy.DefaultLoggerName { - continue - } - if ncl.name != "" { - cfg.Logging.Logs[ncl.name] = ncl.log - } - // most users seem to prefer not writing access logs - // to the default log when they are directed to a - // file or have any other special customization - if ncl.name != caddy.DefaultLoggerName && len(ncl.log.Include) > 0 { - defaultLog, ok := cfg.Logging.Logs[caddy.DefaultLoggerName] - if !ok { - defaultLog = new(caddy.CustomLog) - cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog - } - defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...) - - // avoid duplicates by sorting + compacting - sort.Strings(defaultLog.Exclude) - defaultLog.Exclude = slices.Compact(defaultLog.Exclude) - } - } - // we may have not actually added anything, so remove if empty - if len(cfg.Logging.Logs) == 0 { - cfg.Logging = nil - } - } - - return cfg, warnings, nil + return servers, pairings, metrics, warnings, nil } -// evaluateGlobalOptionsBlock evaluates the global options block, -// which is expected to be the first server block if it has zero -// keys. It returns the updated list of server blocks with the -// global options block removed, and updates options accordingly. -func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options map[string]any) ([]serverBlock, error) { - if len(serverBlocks) == 0 || len(serverBlocks[0].block.Keys) > 0 { - return serverBlocks, nil - } - - for _, segment := range serverBlocks[0].block.Segments { +// EvaluateGlobalOptions processes global option directives from the given segments +// and stores the parsed values in the options map. This is the core reusable logic +// for parsing global options that can be used by both httpcaddyfile and xcaddyfile. +func EvaluateGlobalOptions(segments []caddyfile.Segment, options map[string]any) error { + for _, segment := range segments { opt := segment.Directive() var val any var err error @@ -380,24 +317,24 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options optFunc, ok := registeredGlobalOptions[opt] if !ok { tkn := segment[0] - return nil, fmt.Errorf("%s:%d: unrecognized global option: %s", tkn.File, tkn.Line, opt) + return fmt.Errorf("%s:%d: unrecognized global option: %s", tkn.File, tkn.Line, opt) } val, err = optFunc(disp, options[opt]) if err != nil { - return nil, fmt.Errorf("parsing caddyfile tokens for '%s': %v", opt, err) + return fmt.Errorf("parsing caddyfile tokens for '%s': %v", opt, err) } // As a special case, fold multiple "servers" options together // in an array instead of overwriting a possible existing value if opt == "servers" { - existingOpts, ok := options[opt].([]serverOptions) + existingOpts, ok := options[opt].([]ServerOptions) if !ok { - existingOpts = []serverOptions{} + existingOpts = []ServerOptions{} } - serverOpts, ok := val.(serverOptions) + serverOpts, ok := val.(ServerOptions) if !ok { - return nil, fmt.Errorf("unexpected type from 'servers' global options: %T", val) + return fmt.Errorf("unexpected type from 'servers' global options: %T", val) } options[opt] = append(existingOpts, serverOpts) continue @@ -411,7 +348,7 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options } logOpts, ok := val.([]ConfigValue) if !ok { - return nil, fmt.Errorf("unexpected type from 'log' global options: %T", val) + return fmt.Errorf("unexpected type from 'log' global options: %T", val) } options[opt] = append(existingOpts, logOpts...) continue @@ -425,7 +362,7 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options } defaultBindOpts, ok := val.([]ConfigValue) if !ok { - return nil, fmt.Errorf("unexpected type from 'default_bind' global options: %T", val) + return fmt.Errorf("unexpected type from 'default_bind' global options: %T", val) } options[opt] = append(existingOpts, defaultBindOpts...) continue @@ -435,7 +372,7 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options } // If we got "servers" options, we'll sort them by their listener address - if serverOpts, ok := options["servers"].([]serverOptions); ok { + if serverOpts, ok := options["servers"].([]ServerOptions); ok { sort.Slice(serverOpts, func(i, j int) bool { return len(serverOpts[i].ListenerAddress) > len(serverOpts[j].ListenerAddress) }) @@ -444,24 +381,24 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options seen := make(map[string]bool) for _, entry := range serverOpts { if _, alreadySeen := seen[entry.ListenerAddress]; alreadySeen { - return nil, fmt.Errorf("cannot have 'servers' global options with duplicate listener addresses: %s", entry.ListenerAddress) + return fmt.Errorf("cannot have 'servers' global options with duplicate listener addresses: %s", entry.ListenerAddress) } seen[entry.ListenerAddress] = true } } - return serverBlocks[1:], nil + return nil } // extractNamedRoutes pulls out any named route server blocks // so they don't get parsed as sites, and stores them in options // for later. func (ServerType) extractNamedRoutes( - serverBlocks []serverBlock, + serverBlocks []ServerBlock, options map[string]any, warnings *[]caddyconfig.Warning, replacer ShorthandReplacer, -) ([]serverBlock, error) { +) ([]ServerBlock, error) { namedRoutes := map[string]*caddyhttp.Route{} gc := counter{new(int)} @@ -469,12 +406,12 @@ func (ServerType) extractNamedRoutes( // copy the server blocks so we can // splice out the named route ones - filtered := append([]serverBlock{}, serverBlocks...) + filtered := append([]ServerBlock{}, serverBlocks...) index := -1 for _, sb := range serverBlocks { index++ - if !sb.block.IsNamedRoute { + if !sb.Block.IsNamedRoute { continue } @@ -482,18 +419,18 @@ func (ServerType) extractNamedRoutes( filtered = append(filtered[:index], filtered[index+1:]...) index-- - if len(sb.block.Segments) == 0 { + if len(sb.Block.Segments) == 0 { continue } wholeSegment := caddyfile.Segment{} - for i := range sb.block.Segments { + for i := range sb.Block.Segments { // replace user-defined placeholder shorthands in extracted named routes - replacer.ApplyToSegment(&sb.block.Segments[i]) + replacer.ApplyToSegment(&sb.Block.Segments[i]) // zip up all the segments since ParseSegmentAsSubroute // was designed to take a directive+ - wholeSegment = append(wholeSegment, sb.block.Segments[i]...) + wholeSegment = append(wholeSegment, sb.Block.Segments[i]...) } h := Helper{ @@ -501,7 +438,7 @@ func (ServerType) extractNamedRoutes( options: options, warnings: warnings, matcherDefs: nil, - parentBlock: sb.block, + parentBlock: sb.Block, groupCounter: gc, State: state, } @@ -521,7 +458,7 @@ func (ServerType) extractNamedRoutes( route.HandlersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", subroute.CaddyModule().ID.Name(), h.warnings)} } - namedRoutes[sb.block.GetKeysText()[0]] = &route + namedRoutes[sb.Block.GetKeysText()[0]] = &route } options["named_routes"] = namedRoutes @@ -559,12 +496,12 @@ func (st *ServerType) serversFromPairings( // address), otherwise their routes will improperly be added // to the same server (see issue #4635) for j, sblock1 := range p.serverBlocks { - for _, key := range sblock1.block.GetKeysText() { + for _, key := range sblock1.Block.GetKeysText() { for k, sblock2 := range p.serverBlocks { if k == j { continue } - if slices.Contains(sblock2.block.GetKeysText(), key) { + if slices.Contains(sblock2.Block.GetKeysText(), key) { return nil, fmt.Errorf("ambiguous site definition: %s", key) } } @@ -640,7 +577,7 @@ func (st *ServerType) serversFromPairings( // See ParseAddress() where parsing should later reject paths // See https://github.com/caddyserver/caddy/pull/4728 for a full explanation for _, sblock := range p.serverBlocks { - for _, addr := range sblock.parsedKeys { + for _, addr := range sblock.ParsedKeys { if addr.Path != "" { caddy.Log().Named("caddyfile").Warn("Using a path in a site address is deprecated; please use the 'handle' directive instead", zap.String("address", addr.String())) } @@ -658,7 +595,7 @@ func (st *ServerType) serversFromPairings( var iLongestPath, jLongestPath string var iLongestHost, jLongestHost string var iWildcardHost, jWildcardHost bool - for _, addr := range p.serverBlocks[i].parsedKeys { + for _, addr := range p.serverBlocks[i].ParsedKeys { if strings.Contains(addr.Host, "*") || addr.Host == "" { iWildcardHost = true } @@ -669,7 +606,7 @@ func (st *ServerType) serversFromPairings( iLongestPath = addr.Path } } - for _, addr := range p.serverBlocks[j].parsedKeys { + for _, addr := range p.serverBlocks[j].ParsedKeys { if strings.Contains(addr.Host, "*") || addr.Host == "" { jWildcardHost = true } @@ -707,7 +644,7 @@ func (st *ServerType) serversFromPairings( // that all server blocks can populate it with data, even when not // coming with a log directive for _, sblock := range p.serverBlocks { - if len(sblock.pile["custom_log"]) != 0 { + if len(sblock.Pile["custom_log"]) != 0 { srv.Logs = new(caddyhttp.ServerLogConfig) break } @@ -716,10 +653,10 @@ func (st *ServerType) serversFromPairings( // add named routes to the server if 'invoke' was used inside of it configuredNamedRoutes := options["named_routes"].(map[string]*caddyhttp.Route) for _, sblock := range p.serverBlocks { - if len(sblock.pile[namedRouteKey]) == 0 { + if len(sblock.Pile[namedRouteKey]) == 0 { continue } - for _, value := range sblock.pile[namedRouteKey] { + for _, value := range sblock.Pile[namedRouteKey] { if srv.NamedRoutes == nil { srv.NamedRoutes = map[string]*caddyhttp.Route{} } @@ -735,7 +672,7 @@ func (st *ServerType) serversFromPairings( for _, sblock := range p.serverBlocks { matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock) if err != nil { - return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err) + return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.Block.Keys, err) } hosts := sblock.hostsFromKeys(false) @@ -749,14 +686,14 @@ func (st *ServerType) serversFromPairings( // collect hosts that are forced to be automated forceAutomatedNames := make(map[string]struct{}) - if _, ok := sblock.pile["tls.force_automate"]; ok { + if _, ok := sblock.Pile["tls.force_automate"]; ok { for _, host := range hosts { forceAutomatedNames[host] = struct{}{} } } // tls: connection policies - if cpVals, ok := sblock.pile["tls.connection_policy"]; ok { + if cpVals, ok := sblock.Pile["tls.connection_policy"]; ok { // tls connection policies for _, cpVal := range cpVals { cp := cpVal.Value.(*caddytls.ConnectionPolicy) @@ -799,7 +736,7 @@ func (st *ServerType) serversFromPairings( } } - for _, addr := range sblock.parsedKeys { + for _, addr := range sblock.ParsedKeys { // if server only uses HTTP port, auto-HTTPS will not apply if listenersUseAnyPortOtherThan(srv.Listen, httpPort) { // exclude any hosts that were defined explicitly with "http://" @@ -820,7 +757,7 @@ func (st *ServerType) serversFromPairings( // Second part of the condition is to allow creating TLS conn policy even though `auto_https` has been disabled // ensuring compatibility with behavior described in below link // https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761 - createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"] + createdTLSConnPolicies, ok := sblock.Pile["tls.connection_policy"] hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) || (addr.Host != "" && srv.AutoHTTPS != nil && !slices.Contains(srv.AutoHTTPS.Skip, addr.Host)) @@ -838,7 +775,7 @@ func (st *ServerType) serversFromPairings( } // Look for any config values that provide listener wrappers on the server block - for _, listenerConfig := range sblock.pile["listener_wrapper"] { + for _, listenerConfig := range sblock.Pile["listener_wrapper"] { listenerWrapper, ok := listenerConfig.Value.(caddy.ListenerWrapper) if !ok { return nil, fmt.Errorf("config for a listener wrapper did not provide a value that implements caddy.ListenerWrapper") @@ -852,7 +789,7 @@ func (st *ServerType) serversFromPairings( } // set up each handler directive, making sure to honor directive order - dirRoutes := sblock.pile["route"] + dirRoutes := sblock.Pile["route"] siteSubroute, err := buildSubroute(dirRoutes, groupCounter, true) if err != nil { return nil, err @@ -862,7 +799,7 @@ func (st *ServerType) serversFromPairings( srv.Routes = appendSubrouteToRouteList(srv.Routes, siteSubroute, matcherSetsEnc, p, warnings) // if error routes are defined, add those too - if errorSubrouteVals, ok := sblock.pile["error_route"]; ok { + if errorSubrouteVals, ok := sblock.Pile["error_route"]; ok { if srv.Errors == nil { srv.Errors = new(caddyhttp.HTTPErrorConfig) } @@ -884,7 +821,7 @@ func (st *ServerType) serversFromPairings( // add log associations // see https://github.com/caddyserver/caddy/issues/3310 sblockLogHosts := sblock.hostsFromKeys(true) - for _, cval := range sblock.pile["custom_log"] { + for _, cval := range sblock.Pile["custom_log"] { ncl := cval.Value.(namedCustomLog) // if `no_hostname` is set, then this logger will not @@ -922,7 +859,7 @@ func (st *ServerType) serversFromPairings( } } } - if srv.Logs != nil && len(sblock.pile["custom_log"]) == 0 { + if srv.Logs != nil && len(sblock.Pile["custom_log"]) == 0 { // server has access logs enabled, but this server block does not // enable access logs; therefore, all hosts of this server block // should not be access-logged @@ -985,7 +922,7 @@ func (st *ServerType) serversFromPairings( return servers, nil } -func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, options map[string]any) error { +func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []ServerBlock, options map[string]any) error { httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort) if hp, ok := options["http_port"].(int); ok { httpPort = strconv.Itoa(hp) @@ -1023,7 +960,7 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, } for _, sblock := range serverBlocks { - for _, addr := range sblock.parsedKeys { + for _, addr := range sblock.ParsedKeys { if addr.Scheme == "http" || addr.Port == httpPort { if err := checkAndSetHTTP(addr); err != nil { return err @@ -1442,7 +1379,7 @@ func matcherSetFromMatcherToken( return nil, false, nil } -func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.ModuleMap, error) { +func (st *ServerType) compileEncodedMatcherSets(sblock ServerBlock) ([]caddy.ModuleMap, error) { type hostPathPair struct { hostm caddyhttp.MatchHost pathm caddyhttp.MatchPath @@ -1452,7 +1389,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod var matcherPairs []*hostPathPair var catchAllHosts bool - for _, addr := range sblock.parsedKeys { + for _, addr := range sblock.ParsedKeys { // choose a matcher pair that should be shared by this // server block; if none exists yet, create one var chosenMatcherPair *hostPathPair @@ -1510,7 +1447,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod for _, ms := range matcherSets { msEncoded, err := encodeMatcherSet(ms) if err != nil { - return nil, fmt.Errorf("server block %v: %v", sblock.block.Keys, err) + return nil, fmt.Errorf("server block %v: %v", sblock.Block.Keys, err) } matcherSetsEnc = append(matcherSetsEnc, msEncoded) } @@ -1640,9 +1577,16 @@ func WasReplacedPlaceholderShorthand(token string) string { return "" } -// tryInt tries to convert val to an integer. If it fails, +// DirectiveIsRegistered returns true if the directive name is registered. +// This is exported for use by xcaddyfile. +func DirectiveIsRegistered(name string) bool { + _, ok := registeredDirectives[name] + return ok +} + +// TryInt tries to convert val to an integer. If it fails, // it downgrades the error to a warning and returns 0. -func tryInt(val any, warnings *[]caddyconfig.Warning) int { +func TryInt(val any, warnings *[]caddyconfig.Warning) int { intVal, ok := val.(int) if val != nil && !ok && warnings != nil { *warnings = append(*warnings, caddyconfig.Warning{Message: "not an integer type"}) @@ -1658,7 +1602,9 @@ func tryString(val any, warnings *[]caddyconfig.Warning) string { return stringVal } -func tryDuration(val any, warnings *[]caddyconfig.Warning) caddy.Duration { +// TryDuration converts val to a caddy.Duration, adding a warning if conversion fails. +// This is exported for use by xcaddyfile. +func TryDuration(val any, warnings *[]caddyconfig.Warning) caddy.Duration { durationVal, ok := val.(caddy.Duration) if val != nil && !ok && warnings != nil { *warnings = append(*warnings, caddyconfig.Warning{Message: "not a duration type"}) @@ -1741,6 +1687,37 @@ type namedCustomLog struct { noHostname bool } +// GetNamedCustomLogName extracts the name field from a namedCustomLog value. +// This is exported to allow xcaddyfile to work with namedCustomLog without exposing the type. +func GetNamedCustomLogName(v any) string { + if ncl, ok := v.(namedCustomLog); ok { + return ncl.name + } + return "" +} + +// GetNamedCustomLogLog extracts the log field from a namedCustomLog value. +// This is exported to allow xcaddyfile to work with namedCustomLog without exposing the type. +func GetNamedCustomLogLog(v any) *caddy.CustomLog { + if ncl, ok := v.(namedCustomLog); ok { + return ncl.log + } + return nil +} + +// CollectServerLogs extracts all server-specific custom log configurations from pairings. +// This is exported to allow xcaddyfile and other adapters to access server-specific logs +// that are defined within server blocks (via the log directive). +func CollectServerLogs(pairings ServerBlockPairings) []ConfigValue { + var logs []ConfigValue + for _, p := range pairings { + for _, sb := range p.serverBlocks { + logs = append(logs, sb.Pile["custom_log"]...) + } + } + return logs +} + // addressWithProtocols associates a listen address with // the protocols to serve it with type addressWithProtocols struct { @@ -1753,7 +1730,7 @@ type addressWithProtocols struct { // blocks that are served on those addresses. type sbAddrAssociation struct { addressesWithProtocols []addressWithProtocols - serverBlocks []serverBlock + serverBlocks []ServerBlock } const ( @@ -1761,5 +1738,182 @@ const ( namedRouteKey = "named_route" ) +// extractBlockType extracts the block type from a server block if it has +// explicit [type] syntax. Returns empty string if no explicit type is found. +func extractBlockType(sblock *caddyfile.ServerBlock) (blockType string, err error) { + if len(sblock.Keys) == 0 { + return "", nil + } + + firstKey := sblock.Keys[0].Text + + // Check if this uses explicit [type] syntax + if strings.HasPrefix(firstKey, "[") && strings.HasSuffix(firstKey, "]") { + // Extract the block type name + blockType = strings.TrimSuffix(strings.TrimPrefix(firstKey, "["), "]") + if blockType == "" { + return "", fmt.Errorf("%s:%d: empty block type declaration []", + sblock.Keys[0].File, sblock.Keys[0].Line) + } + + // Remove the [type] token from the keys + sblock.Keys = sblock.Keys[1:] + return blockType, nil + } + + // No explicit type + return "", nil +} + +// applyGlobalOptions applies values from the options map to the builder. +// This includes admin config, storage, logging, and other global settings. +func applyGlobalOptions(builder *configbuilder.Builder, options map[string]any, warnings *[]caddyconfig.Warning) error { + // Add any httpcaddyfile.App types from options + for _, opt := range options { + if app, ok := opt.(App); ok { + builder.AddRawApp(app.Name, app.Value) + } + } + + // Apply filesystem option + if filesystems, ok := options["filesystem"].(caddy.Module); ok { + builder.AddRawApp("caddy.filesystems", caddyconfig.JSON(filesystems, warnings)) + } + + // Apply storage option + if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok { + builder.SetStorage(storageCvtr) + } + + // Apply admin option + if adminConfig, ok := options["admin"].(*caddy.AdminConfig); ok && adminConfig != nil { + builder.SetAdmin(adminConfig) + } + + // Apply persist_config option + if pc, ok := options["persist_config"].(string); ok && pc == "off" { + admin := builder.GetAdmin() + if admin.Config == nil { + admin.Config = new(caddy.ConfigSettings) + } + falseBool := false + admin.Config.Persist = &falseBool + } + + return nil +} + +// processLogs collects all custom logs (global and server-specific) and applies them to the config. +// This must run after the config is finalized so we can access the HTTP app. +func processLogs(cfg *caddy.Config, options map[string]any, warnings *[]caddyconfig.Warning) error { + var customLogs []struct { + name string + log *caddy.CustomLog + } + var hasDefaultLog bool + + addCustomLog := func(name string, log *caddy.CustomLog) { + if name == "" { + return + } + if name == caddy.DefaultLoggerName { + hasDefaultLog = true + } + // Apply debug level if debug is on and no level is set + if _, ok := options["debug"]; ok && log != nil && log.Level == "" { + log.Level = zap.DebugLevel.CapitalString() + } + customLogs = append(customLogs, struct { + name string + log *caddy.CustomLog + }{name: name, log: log}) + } + + // Collect global log options from options["log"] + if options["log"] != nil { + for _, logValue := range options["log"].([]ConfigValue) { + nclValue := logValue.Value + name := GetNamedCustomLogName(nclValue) + log := GetNamedCustomLogLog(nclValue) + addCustomLog(name, log) + } + } + + // Collect server-specific log options from options["__xcaddyfile_server_logs__"] + if options["__xcaddyfile_server_logs__"] != nil { + for _, logValue := range options["__xcaddyfile_server_logs__"].([]ConfigValue) { + nclValue := logValue.Value + name := GetNamedCustomLogName(nclValue) + log := GetNamedCustomLogLog(nclValue) + addCustomLog(name, log) + } + } + + // If no default log was configured but debug is on, add one + if !hasDefaultLog { + if _, ok := options["debug"]; ok { + customLogs = append(customLogs, struct { + name string + log *caddy.CustomLog + }{ + name: caddy.DefaultLoggerName, + log: &caddy.CustomLog{ + BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()}, + }, + }) + } + } + + // Apply custom logs to the config + if len(customLogs) > 0 { + if cfg.Logging == nil { + cfg.Logging = &caddy.Logging{ + Logs: make(map[string]*caddy.CustomLog), + } + } + if cfg.Logging.Logs == nil { + cfg.Logging.Logs = make(map[string]*caddy.CustomLog) + } + + // Add the default log first if defined + for _, ncl := range customLogs { + if ncl.name == caddy.DefaultLoggerName && ncl.log != nil { + cfg.Logging.Logs[caddy.DefaultLoggerName] = ncl.log + break + } + } + + // Add the rest of the custom logs + for _, ncl := range customLogs { + if ncl.log == nil || ncl.name == caddy.DefaultLoggerName { + continue + } + if ncl.name != "" { + cfg.Logging.Logs[ncl.name] = ncl.log + } + // Most users prefer not writing access logs to the default log + // when they are directed to a file or have special customization + if ncl.name != caddy.DefaultLoggerName && len(ncl.log.Include) > 0 { + defaultLog, ok := cfg.Logging.Logs[caddy.DefaultLoggerName] + if !ok { + defaultLog = new(caddy.CustomLog) + cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog + } + defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...) + + // Avoid duplicates by sorting + compacting + sort.Strings(defaultLog.Exclude) + defaultLog.Exclude = slices.Compact(defaultLog.Exclude) + } + } + // we may have not actually added anything, so remove if empty + if len(cfg.Logging.Logs) == 0 { + cfg.Logging = nil + } + } + + return nil +} + // Interface guard var _ caddyfile.ServerType = (*ServerType)(nil) diff --git a/caddyconfig/httpcaddyfile/options_test.go b/caddyconfig/httpcaddyfile/options_test.go index bc9e8813404..1b6ac708e76 100644 --- a/caddyconfig/httpcaddyfile/options_test.go +++ b/caddyconfig/httpcaddyfile/options_test.go @@ -1,9 +1,12 @@ -package httpcaddyfile +package httpcaddyfile_test import ( "testing" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + _ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/globalblock" + _ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/httpserverblock" _ "github.com/caddyserver/caddy/v2/modules/logging" ) @@ -47,7 +50,7 @@ func TestGlobalLogOptionSyntax(t *testing.T) { } { adapter := caddyfile.Adapter{ - ServerType: ServerType{}, + ServerType: httpcaddyfile.ServerType{}, } out, _, err := adapter.Adapt([]byte(tc.input), nil) diff --git a/caddyconfig/httpcaddyfile/pkiapp.go b/caddyconfig/httpcaddyfile/pkiapp.go index 25b6c221cd3..e48a7474e10 100644 --- a/caddyconfig/httpcaddyfile/pkiapp.go +++ b/caddyconfig/httpcaddyfile/pkiapp.go @@ -171,6 +171,17 @@ func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) { return pki, nil } +// BuildPKIApp builds the PKI app from server block pairings and global options. +// This is exported for use by xcaddyfile and other adapters. +func BuildPKIApp( + pairings ServerBlockPairings, + options map[string]any, + warnings []caddyconfig.Warning, +) (*caddypki.PKI, []caddyconfig.Warning, error) { + st := ServerType{} + return st.buildPKIApp(pairings, options, warnings) +} + func (st ServerType) buildPKIApp( pairings []sbAddrAssociation, options map[string]any, @@ -211,7 +222,7 @@ func (st ServerType) buildPKIApp( for _, sblock := range p.serverBlocks { // find all the CAs that were defined and add them to the app config // i.e. from any "acme_server" directives - for _, caCfgValue := range sblock.pile["pki.ca"] { + for _, caCfgValue := range sblock.Pile["pki.ca"] { ca := caCfgValue.Value.(*caddypki.CA) if skipInstallTrust { ca.InstallTrust = &falseBool diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go index 9431f1aed58..3f733d3d70b 100644 --- a/caddyconfig/httpcaddyfile/serveroptions.go +++ b/caddyconfig/httpcaddyfile/serveroptions.go @@ -28,11 +28,11 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) -// serverOptions collects server config overrides parsed from Caddyfile global options -type serverOptions struct { +// ServerOptions collects server config overrides parsed from Caddyfile global options +type ServerOptions struct { // If set, will only apply these options to servers that contain a // listener address that matches exactly. If empty, will apply to all - // servers that were not already matched by another serverOptions. + // servers that were not already matched by another ServerOptions. ListenerAddress string // These will all map 1:1 to the caddyhttp.Server struct @@ -61,7 +61,7 @@ type serverOptions struct { func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { d.Next() // consume option name - serverOpts := serverOptions{} + serverOpts := ServerOptions{} if d.NextArg() { serverOpts.ListenerAddress = d.Val() if d.NextArg() { @@ -301,7 +301,7 @@ func applyServerOptions( options map[string]any, _ *[]caddyconfig.Warning, ) error { - serverOpts, ok := options["servers"].([]serverOptions) + serverOpts, ok := options["servers"].([]ServerOptions) if !ok { return nil } @@ -323,7 +323,7 @@ func applyServerOptions( for key, server := range servers { // find the options that apply to this server - optsIndex := slices.IndexFunc(serverOpts, func(s serverOptions) bool { + optsIndex := slices.IndexFunc(serverOpts, func(s ServerOptions) bool { return s.ListenerAddress == "" || slices.Contains(server.Listen, s.ListenerAddress) }) diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index 30948f84fff..9ed78c11c78 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -33,6 +33,17 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddytls" ) +// BuildTLSApp builds the TLS app from server block pairings and global options. +// This is exported for use by xcaddyfile and other adapters. +func BuildTLSApp( + pairings ServerBlockPairings, + options map[string]any, + warnings []caddyconfig.Warning, +) (*caddytls.TLS, []caddyconfig.Warning, error) { + st := ServerType{} + return st.buildTLSApp(pairings, options, warnings) +} + func (st ServerType) buildTLSApp( pairings []sbAddrAssociation, options map[string]any, @@ -57,14 +68,14 @@ func (st ServerType) buildTLSApp( if !slices.Contains(autoHTTPS, "off") { for _, pair := range pairings { for _, sb := range pair.serverBlocks { - for _, addr := range sb.parsedKeys { + for _, addr := range sb.ParsedKeys { if addr.Host != "" { continue } // this server block has a hostless key, now // go through and add all the hosts to the set - for _, otherAddr := range sb.parsedKeys { + for _, otherAddr := range sb.ParsedKeys { if otherAddr.Original == addr.Original { continue } @@ -104,7 +115,7 @@ func (st ServerType) buildTLSApp( continue } for _, sblock := range p.serverBlocks { - for _, addr := range sblock.parsedKeys { + for _, addr := range sblock.ParsedKeys { if strings.HasPrefix(addr.Host, "*.") { wildcardHosts = append(wildcardHosts, addr.Host[2:]) } @@ -147,28 +158,28 @@ func (st ServerType) buildTLSApp( } // on-demand tls - if _, ok := sblock.pile["tls.on_demand"]; ok { + if _, ok := sblock.Pile["tls.on_demand"]; ok { ap.OnDemand = true } // collect hosts that are forced to have certs automated for their specific name - if _, ok := sblock.pile["tls.force_automate"]; ok { + if _, ok := sblock.Pile["tls.force_automate"]; ok { for _, host := range sblockHosts { forcedAutomatedNames[host] = struct{}{} } } // reuse private keys tls - if _, ok := sblock.pile["tls.reuse_private_keys"]; ok { + if _, ok := sblock.Pile["tls.reuse_private_keys"]; ok { ap.ReusePrivateKeys = true } - if keyTypeVals, ok := sblock.pile["tls.key_type"]; ok { + if keyTypeVals, ok := sblock.Pile["tls.key_type"]; ok { ap.KeyType = keyTypeVals[0].Value.(string) } // certificate issuers - if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok { + if issuerVals, ok := sblock.Pile["tls.cert_issuer"]; ok { var issuers []certmagic.Issuer for _, issuerVal := range issuerVals { issuers = append(issuers, issuerVal.Value.(certmagic.Issuer)) @@ -193,14 +204,14 @@ func (st ServerType) buildTLSApp( } // certificate managers - if certManagerVals, ok := sblock.pile["tls.cert_manager"]; ok { + if certManagerVals, ok := sblock.Pile["tls.cert_manager"]; ok { for _, certManager := range certManagerVals { certGetterName := certManager.Value.(caddy.Module).CaddyModule().ID.Name() ap.ManagersRaw = append(ap.ManagersRaw, caddyconfig.JSONModuleObject(certManager.Value, "via", certGetterName, &warnings)) } } // custom bind host - for _, cfgVal := range sblock.pile["bind"] { + for _, cfgVal := range sblock.Pile["bind"] { for _, iss := range ap.Issuers { // if an issuer was already configured and it is NOT an ACME issuer, // skip, since we intend to adjust only ACME issuers; ensure we @@ -313,7 +324,7 @@ func (st ServerType) buildTLSApp( } // certificate loaders - if clVals, ok := sblock.pile["tls.cert_loader"]; ok { + if clVals, ok := sblock.Pile["tls.cert_loader"]; ok { for _, clVal := range clVals { certLoaders = append(certLoaders, clVal.Value.(caddytls.CertificateLoader)) } diff --git a/modules/standard/imports.go b/modules/standard/imports.go index 813c63d6c44..66b84aea14a 100644 --- a/modules/standard/imports.go +++ b/modules/standard/imports.go @@ -3,6 +3,8 @@ package standard import ( // standard Caddy modules _ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + _ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/globalblock" + _ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/httpserverblock" _ "github.com/caddyserver/caddy/v2/modules/caddyevents" _ "github.com/caddyserver/caddy/v2/modules/caddyevents/eventsconfig" _ "github.com/caddyserver/caddy/v2/modules/caddyfs"