@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 10, 22:30 UTC

master
go 1,327 lines 40.4 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddy
16
17 import (
18 "bytes"
19 "context"
20 "encoding/hex"
21 "encoding/json"
22 "errors"
23 "fmt"
24 "io"
25 "io/fs"
26 "log"
27 "net/http"
28 "os"
29 "path"
30 "path/filepath"
31 "runtime/debug"
32 "strconv"
33 "strings"
34 "sync"
35 "sync/atomic"
36 "time"
37
38 "github.com/caddyserver/certmagic"
39 "github.com/google/uuid"
40 "go.uber.org/zap"
41
42 "github.com/caddyserver/caddy/v2/internal/filesystems"
43 "github.com/caddyserver/caddy/v2/notify"
44 )
45
46 // Config is the top (or beginning) of the Caddy configuration structure.
47 // Caddy config is expressed natively as a JSON document. If you prefer
48 // not to work with JSON directly, there are [many config adapters](/docs/config-adapters)
49 // available that can convert various inputs into Caddy JSON.
50 //
51 // Many parts of this config are extensible through the use of Caddy modules.
52 // Fields which have a json.RawMessage type and which appear as dots (•••) in
53 // the online docs can be fulfilled by modules in a certain module
54 // namespace. The docs show which modules can be used in a given place.
55 //
56 // Whenever a module is used, its name must be given either inline as part of
57 // the module, or as the key to the module's value. The docs will make it clear
58 // which to use.
59 //
60 // Generally, all config settings are optional, as it is Caddy convention to
61 // have good, documented default values. If a parameter is required, the docs
62 // should say so.
63 //
64 // Go programs which are directly building a Config struct value should take
65 // care to populate the JSON-encodable fields of the struct (i.e. the fields
66 // with `json` struct tags) if employing the module lifecycle (e.g. Provision
67 // method calls).
68 type Config struct {
69 Admin *AdminConfig `json:"admin,omitempty"`
70 Logging *Logging `json:"logging,omitempty"`
71
72 // StorageRaw is a storage module that defines how/where Caddy
73 // stores assets (such as TLS certificates). The default storage
74 // module is `caddy.storage.file_system` (the local file system),
75 // and the default path
76 // [depends on the OS and environment](/docs/conventions#data-directory).
77 StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
78
79 // AppsRaw are the apps that Caddy will load and run. The
80 // app module name is the key, and the app's config is the
81 // associated value.
82 AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
83
84 apps map[string]App
85
86 // failedApps is a map of apps that failed to provision with their underlying error.
87 failedApps map[string]error
88 storage certmagic.Storage
89 eventEmitter eventEmitter
90
91 cancelFunc context.CancelCauseFunc
92
93 // fileSystems is a dict of fileSystems that will later be loaded from and added to.
94 fileSystems FileSystems
95 }
96
97 // App is a thing that Caddy runs.
98 type App interface {
99 Start() error
100 Stop() error
101 }
102
103 // Run runs the given config, replacing any existing config.
104 func Run(cfg *Config) error {
105 cfgJSON, err := json.Marshal(cfg)
106 if err != nil {
107 return err
108 }
109 return Load(cfgJSON, true)
110 }
111
112 // Load loads the given config JSON and runs it only
113 // if it is different from the current config or
114 // forceReload is true.
115 func Load(cfgJSON []byte, forceReload bool) error {
116 if err := notify.Reloading(); err != nil {
117 Log().Error("unable to notify service manager of reloading state", zap.Error(err))
118 }
119
120 // after reload, notify system of success or, if
121 // failure, update with status (error message)
122 var err error
123 defer func() {
124 if err != nil {
125 if notifyErr := notify.Error(err, 0); notifyErr != nil {
126 Log().Error("unable to notify to service manager of reload error",
127 zap.Error(notifyErr),
128 zap.String("reload_err", err.Error()))
129 }
130 }
131 if notifyErr := notify.Ready(); notifyErr != nil {
132 Log().Error("unable to notify to service manager of ready state", zap.Error(notifyErr))
133 }
134 }()
135
136 err = changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload)
137 if errors.Is(err, errSameConfig) {
138 err = nil // not really an error
139 }
140
141 return err
142 }
143
144 // changeConfig changes the current config (rawCfg) according to the
145 // method, traversed via the given path, and uses the given input as
146 // the new value (if applicable; i.e. "DELETE" doesn't have an input).
147 // If the resulting config is the same as the previous, no reload will
148 // occur unless forceReload is true. If the config is unchanged and not
149 // forcefully reloaded, then errConfigUnchanged is returned. This function
150 // is safe for concurrent use.
151 // The ifMatchHeader can optionally be given a string of the format:
152 //
153 // "<path> <hash>"
154 //
155 // where <path> is the absolute path in the config and <hash> is the expected hash of
156 // the config at that path. If the hash in the ifMatchHeader doesn't match
157 // the hash of the config, then an APIError with status 412 will be returned.
158 func changeConfig(method, path string, input []byte, ifMatchHeader string, forceReload bool) error {
159 switch method {
160 case http.MethodGet,
161 http.MethodHead,
162 http.MethodOptions,
163 http.MethodConnect,
164 http.MethodTrace:
165 return fmt.Errorf("method not allowed")
166 }
167
168 rawCfgMu.Lock()
169 defer rawCfgMu.Unlock()
170
171 if ifMatchHeader != "" {
172 // expect the first and last character to be quotes
173 if len(ifMatchHeader) < 2 || ifMatchHeader[0] != '"' || ifMatchHeader[len(ifMatchHeader)-1] != '"' {
174 return APIError{
175 HTTPStatus: http.StatusBadRequest,
176 Err: fmt.Errorf("malformed If-Match header; expect quoted string"),
177 }
178 }
179
180 // read out the parts
181 parts := strings.Fields(ifMatchHeader[1 : len(ifMatchHeader)-1])
182 if len(parts) != 2 {
183 return APIError{
184 HTTPStatus: http.StatusBadRequest,
185 Err: fmt.Errorf("malformed If-Match header; expect format \"<path> <hash>\""),
186 }
187 }
188
189 // get the current hash of the config
190 // at the given path
191 hash := etagHasher()
192 err := unsyncedConfigAccess(http.MethodGet, parts[0], nil, hash)
193 if err != nil {
194 return err
195 }
196
197 if hex.EncodeToString(hash.Sum(nil)) != parts[1] {
198 return APIError{
199 HTTPStatus: http.StatusPreconditionFailed,
200 Err: fmt.Errorf("If-Match header did not match current config hash"),
201 }
202 }
203 }
204
205 err := unsyncedConfigAccess(method, path, input, nil)
206 if err != nil {
207 return err
208 }
209
210 // the mutation is complete, so encode the entire config as JSON
211 newCfg, err := json.Marshal(rawCfg[rawConfigKey])
212 if err != nil {
213 return APIError{
214 HTTPStatus: http.StatusBadRequest,
215 Err: fmt.Errorf("encoding new config: %v", err),
216 }
217 }
218
219 // if nothing changed, no need to do a whole reload unless the client forces it
220 if !forceReload && bytes.Equal(rawCfgJSON, newCfg) {
221 Log().Info("config is unchanged")
222 return errSameConfig
223 }
224
225 // find any IDs in this config and index them
226 idx := make(map[string]string)
227 err = indexConfigObjects(rawCfg[rawConfigKey], "/"+rawConfigKey, idx)
228 if err != nil {
229 if len(rawCfgJSON) > 0 {
230 var oldCfg any
231 err2 := json.Unmarshal(rawCfgJSON, &oldCfg)
232 if err2 != nil {
233 err = fmt.Errorf("%v; additionally, restoring old config: %v", err, err2)
234 }
235 rawCfg[rawConfigKey] = oldCfg
236 } else {
237 rawCfg[rawConfigKey] = nil
238 }
239 return APIError{
240 HTTPStatus: http.StatusBadRequest,
241 Err: fmt.Errorf("indexing config: %v", err),
242 }
243 }
244
245 // load this new config; if it fails, we need to revert to
246 // our old representation of caddy's actual config
247 err = unsyncedDecodeAndRun(newCfg, true)
248 if err != nil {
249 if len(rawCfgJSON) > 0 {
250 // restore old config state to keep it consistent
251 // with what caddy is still running; we need to
252 // unmarshal it again because it's likely that
253 // pointers deep in our rawCfg map were modified
254 var oldCfg any
255 err2 := json.Unmarshal(rawCfgJSON, &oldCfg)
256 if err2 != nil {
257 err = fmt.Errorf("%v; additionally, restoring old config: %v", err, err2)
258 }
259 rawCfg[rawConfigKey] = oldCfg
260 } else {
261 rawCfg[rawConfigKey] = nil
262 }
263
264 return fmt.Errorf("loading new config: %v", err)
265 }
266
267 // success, so update our stored copy of the encoded
268 // config to keep it consistent with what caddy is now
269 // running (storing an encoded copy is not strictly
270 // necessary, but avoids an extra json.Marshal for
271 // each config change)
272 rawCfgJSON = newCfg
273 rawCfgIndex = idx
274
275 return nil
276 }
277
278 // readConfig traverses the current config to path
279 // and writes its JSON encoding to out.
280 func readConfig(path string, out io.Writer) error {
281 rawCfgMu.RLock()
282 defer rawCfgMu.RUnlock()
283 return unsyncedConfigAccess(http.MethodGet, path, nil, out)
284 }
285
286 // indexConfigObjects recursively searches ptr for object fields named
287 // "@id" and maps that ID value to the full configPath in the index.
288 // This function is NOT safe for concurrent access; obtain a write lock
289 // on currentCtxMu.
290 func indexConfigObjects(ptr any, configPath string, index map[string]string) error {
291 switch val := ptr.(type) {
292 case map[string]any:
293 for k, v := range val {
294 if k == idKey {
295 var idStr string
296 switch idVal := v.(type) {
297 case string:
298 idStr = idVal
299 case float64: // all JSON numbers decode as float64
300 idStr = fmt.Sprintf("%v", idVal)
301 default:
302 return fmt.Errorf("%s: %s field must be a string or number", configPath, idKey)
303 }
304 if existingPath, ok := index[idStr]; ok {
305 return fmt.Errorf("duplicate ID '%s' found at %s and %s", idStr, existingPath, configPath)
306 }
307 index[idStr] = configPath
308 continue
309 }
310 // traverse this object property recursively
311 err := indexConfigObjects(val[k], path.Join(configPath, k), index)
312 if err != nil {
313 return err
314 }
315 }
316 case []any:
317 // traverse each element of the array recursively
318 for i := range val {
319 err := indexConfigObjects(val[i], path.Join(configPath, strconv.Itoa(i)), index)
320 if err != nil {
321 return err
322 }
323 }
324 }
325
326 return nil
327 }
328
329 // unsyncedDecodeAndRun removes any meta fields (like @id tags)
330 // from cfgJSON, decodes the result into a *Config, and runs
331 // it as the new config, replacing any other current config.
332 // It does NOT update the raw config state, as this is a
333 // lower-level function; most callers will want to use Load
334 // instead. A write lock on rawCfgMu is required! If
335 // allowPersist is false, it will not be persisted to disk,
336 // even if it is configured to.
337 func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
338 // remove any @id fields from the JSON, which would cause
339 // loading to break since the field wouldn't be recognized
340 strippedCfgJSON := RemoveMetaFields(cfgJSON)
341
342 var newCfg *Config
343 err := StrictUnmarshalJSON(strippedCfgJSON, &newCfg)
344 if err != nil {
345 return err
346 }
347
348 // prevent recursive config loads; that is a user error, and
349 // although frequent config loads should be safe, we cannot
350 // guarantee that in the presence of third party plugins, nor
351 // do we want this error to go unnoticed (we assume it was a
352 // pulled config if we're not allowed to persist it)
353 if !allowPersist &&
354 newCfg != nil &&
355 newCfg.Admin != nil &&
356 newCfg.Admin.Config != nil &&
357 newCfg.Admin.Config.LoadRaw != nil &&
358 newCfg.Admin.Config.LoadDelay <= 0 {
359 return fmt.Errorf("recursive config loading detected: pulled configs cannot pull other configs without positive load_delay")
360 }
361
362 // run the new config and start all its apps
363 ctx, err := run(newCfg, true)
364 if err != nil {
365 return err
366 }
367
368 // swap old context (including its config) with the new one
369 currentCtxMu.Lock()
370 oldCtx := currentCtx
371 currentCtx = ctx
372 currentCtxMu.Unlock()
373
374 // Stop, Cleanup each old app
375 unsyncedStop(oldCtx)
376
377 // autosave a non-nil config, if not disabled
378 if allowPersist &&
379 newCfg != nil &&
380 (newCfg.Admin == nil ||
381 newCfg.Admin.Config == nil ||
382 newCfg.Admin.Config.Persist == nil ||
383 *newCfg.Admin.Config.Persist) {
384 dir := filepath.Dir(ConfigAutosavePath)
385 err := os.MkdirAll(dir, 0o700)
386 if err != nil {
387 Log().Error("unable to create folder for config autosave",
388 zap.String("dir", dir),
389 zap.Error(err))
390 } else {
391 err := os.WriteFile(ConfigAutosavePath, cfgJSON, 0o600)
392 if err == nil {
393 Log().Info("autosaved config (load with --resume flag)", zap.String("file", ConfigAutosavePath))
394 } else {
395 Log().Error("unable to autosave config",
396 zap.String("file", ConfigAutosavePath),
397 zap.Error(err))
398 }
399 }
400 }
401
402 return nil
403 }
404
405 // run runs newCfg and starts all its apps if
406 // start is true. If any errors happen, cleanup
407 // is performed if any modules were provisioned;
408 // apps that were started already will be stopped,
409 // so this function should not leak resources if
410 // an error is returned. However, if no error is
411 // returned and start == false, you should cancel
412 // the config if you are not going to start it,
413 // so that each provisioned module will be
414 // cleaned up.
415 //
416 // This is a low-level function; most callers
417 // will want to use Run instead, which also
418 // updates the config's raw state.
419 func run(newCfg *Config, start bool) (Context, error) {
420 ctx, err := provisionContext(newCfg, start)
421 if err != nil {
422 globalMetrics.configSuccess.Set(0)
423 return ctx, err
424 }
425
426 if !start {
427 return ctx, nil
428 }
429
430 defer func() {
431 // if newCfg fails to start completely, clean up the already provisioned modules
432 // partially copied from provisionContext
433 if err != nil {
434 globalMetrics.configSuccess.Set(0)
435 ctx.cfg.cancelFunc(fmt.Errorf("configuration start error: %w", err))
436
437 if currentCtx.cfg != nil {
438 certmagic.Default.Storage = currentCtx.cfg.storage
439 }
440 }
441 }()
442
443 // Start
444 err = func() error {
445 started := make([]string, 0, len(ctx.cfg.apps))
446 for name, a := range ctx.cfg.apps {
447 err := a.Start()
448 if err != nil {
449 // an app failed to start, so we need to stop
450 // all other apps that were already started
451 for _, otherAppName := range started {
452 err2 := ctx.cfg.apps[otherAppName].Stop()
453 if err2 != nil {
454 err = fmt.Errorf("%v; additionally, aborting app %s: %v",
455 err, otherAppName, err2)
456 }
457 }
458 return fmt.Errorf("%s app module: start: %v", name, err)
459 }
460 started = append(started, name)
461 }
462 return nil
463 }()
464 if err != nil {
465 return ctx, err
466 }
467 globalMetrics.configSuccess.Set(1)
468 globalMetrics.configSuccessTime.SetToCurrentTime()
469
470 // TODO: This event is experimental and subject to change.
471 ctx.emitEvent("started", nil)
472
473 // now that the user's config is running, finish setting up anything else,
474 // such as remote admin endpoint, config loader, etc.
475 err = finishSettingUp(ctx, ctx.cfg)
476 return ctx, err
477 }
478
479 // provisionContext creates a new context from the given configuration and provisions
480 // storage and apps.
481 // If `newCfg` is nil a new empty configuration will be created.
482 // If `replaceAdminServer` is true any currently active admin server will be replaced
483 // with a new admin server based on the provided configuration.
484 func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error) {
485 // because we will need to roll back any state
486 // modifications if this function errors, we
487 // keep a single error value and scope all
488 // sub-operations to their own functions to
489 // ensure this error value does not get
490 // overridden or missed when it should have
491 // been set by a short assignment
492 var err error
493
494 if newCfg == nil {
495 newCfg = new(Config)
496 }
497
498 // create a context within which to load
499 // modules - essentially our new config's
500 // execution environment; be sure that
501 // cleanup occurs when we return if there
502 // was an error; if no error, it will get
503 // cleaned up on next config cycle
504 ctx, cancelCause := NewContextWithCause(Context{Context: context.Background(), cfg: newCfg})
505 defer func() {
506 if err != nil {
507 globalMetrics.configSuccess.Set(0)
508 // if there were any errors during startup,
509 // we should cancel the new context we created
510 // since the associated config won't be used;
511 // this will cause all modules that were newly
512 // provisioned to clean themselves up
513 cancelCause(fmt.Errorf("configuration error: %w", err))
514
515 // also undo any other state changes we made
516 if currentCtx.cfg != nil {
517 certmagic.Default.Storage = currentCtx.cfg.storage
518 }
519 }
520 }()
521 newCfg.cancelFunc = cancelCause // clean up later
522
523 // set up logging before anything bad happens
524 if newCfg.Logging == nil {
525 newCfg.Logging = new(Logging)
526 }
527 err = newCfg.Logging.openLogs(ctx)
528 if err != nil {
529 return ctx, err
530 }
531
532 // create the new filesystem map
533 newCfg.fileSystems = &filesystems.FileSystemMap{}
534
535 // prepare the new config for use
536 newCfg.apps = make(map[string]App)
537 newCfg.failedApps = make(map[string]error)
538
539 // set up global storage and make it CertMagic's default storage, too
540 err = func() error {
541 if newCfg.StorageRaw != nil {
542 val, err := ctx.LoadModule(newCfg, "StorageRaw")
543 if err != nil {
544 return fmt.Errorf("loading storage module: %v", err)
545 }
546 stor, err := val.(StorageConverter).CertMagicStorage()
547 if err != nil {
548 return fmt.Errorf("creating storage value: %v", err)
549 }
550 newCfg.storage = stor
551 }
552
553 if newCfg.storage == nil {
554 newCfg.storage = DefaultStorage
555 }
556 certmagic.Default.Storage = newCfg.storage
557
558 return nil
559 }()
560 if err != nil {
561 return ctx, err
562 }
563
564 // start the admin endpoint (and stop any prior one)
565 if replaceAdminServer {
566 err = replaceLocalAdminServer(newCfg, ctx)
567 if err != nil {
568 return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
569 }
570 }
571
572 // Load and Provision each app and their submodules
573 err = func() error {
574 for appName := range newCfg.AppsRaw {
575 if _, err := ctx.App(appName); err != nil {
576 return err
577 }
578 }
579 return nil
580 }()
581 return ctx, err
582 }
583
584 // ProvisionContext creates a new context from the configuration and provisions storage
585 // and app modules.
586 // The function is intended for testing and advanced use cases only, typically `Run` should be
587 // use to ensure a fully functional caddy instance.
588 // EXPERIMENTAL: While this is public the interface and implementation details of this function may change.
589 func ProvisionContext(newCfg *Config) (Context, error) {
590 return provisionContext(newCfg, false)
591 }
592
593 // finishSettingUp should be run after all apps have successfully started.
594 func finishSettingUp(ctx Context, cfg *Config) error {
595 // establish this server's identity (only after apps are loaded
596 // so that cert management of this endpoint doesn't prevent user's
597 // servers from starting which likely also use HTTP/HTTPS ports;
598 // but before remote management which may depend on these creds)
599 err := manageIdentity(ctx, cfg)
600 if err != nil {
601 return fmt.Errorf("provisioning remote admin endpoint: %v", err)
602 }
603
604 // replace any remote admin endpoint
605 err = replaceRemoteAdminServer(ctx, cfg)
606 if err != nil {
607 return fmt.Errorf("provisioning remote admin endpoint: %v", err)
608 }
609
610 // if dynamic config is requested, set that up and run it
611 if cfg != nil && cfg.Admin != nil && cfg.Admin.Config != nil && cfg.Admin.Config.LoadRaw != nil {
612 val, err := ctx.LoadModule(cfg.Admin.Config, "LoadRaw")
613 if err != nil {
614 return fmt.Errorf("loading config loader module: %s", err)
615 }
616
617 logger := Log().Named("config_loader").With(
618 zap.String("module", val.(Module).CaddyModule().ID.Name()),
619 zap.Int("load_delay", int(cfg.Admin.Config.LoadDelay)))
620
621 runLoadedConfig := func(config []byte) error {
622 logger.Info("applying dynamically-loaded config")
623 err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, "", false)
624 if errors.Is(err, errSameConfig) {
625 return err
626 }
627 if err != nil {
628 logger.Error("failed to run dynamically-loaded config", zap.Error(err))
629 return err
630 }
631 logger.Info("successfully applied dynamically-loaded config")
632 return nil
633 }
634
635 if cfg.Admin.Config.LoadDelay > 0 {
636 go func() {
637 // the loop is here to iterate ONLY if there is an error, a no-op config load,
638 // or an unchanged config; in which case we simply wait the delay and try again
639 for {
640 timer := time.NewTimer(time.Duration(cfg.Admin.Config.LoadDelay))
641 select {
642 case <-timer.C:
643 loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
644 if err != nil {
645 logger.Error("failed loading dynamic config; will retry", zap.Error(err))
646 continue
647 }
648 if loadedConfig == nil {
649 logger.Info("dynamically-loaded config was nil; will retry")
650 continue
651 }
652 err = runLoadedConfig(loadedConfig)
653 if errors.Is(err, errSameConfig) {
654 logger.Info("dynamically-loaded config was unchanged; will retry")
655 continue
656 }
657 case <-ctx.Done():
658 if !timer.Stop() {
659 <-timer.C
660 }
661 logger.Info("stopping dynamic config loading")
662 }
663 break
664 }
665 }()
666 } else {
667 // if no LoadDelay is provided, will load config synchronously
668 loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
669 if err != nil {
670 return fmt.Errorf("loading dynamic config from %T: %v", val, err)
671 }
672 // do this in a goroutine so current config can finish being loaded; otherwise deadlock
673 go func() { _ = runLoadedConfig(loadedConfig) }()
674 }
675 }
676
677 return nil
678 }
679
680 // ConfigLoader is a type that can load a Caddy config. If
681 // the return value is non-nil, it must be valid Caddy JSON;
682 // if nil or with non-nil error, it is considered to be a
683 // no-op load and may be retried later.
684 type ConfigLoader interface {
685 LoadConfig(Context) ([]byte, error)
686 }
687
688 // Stop stops running the current configuration.
689 // It is the antithesis of Run(). This function
690 // will log any errors that occur during the
691 // stopping of individual apps and continue to
692 // stop the others. Stop should only be called
693 // if not replacing with a new config.
694 func Stop() error {
695 rawCfgMu.Lock()
696 defer rawCfgMu.Unlock()
697
698 currentCtxMu.Lock()
699 ctx := currentCtx
700 currentCtx = Context{}
701 currentCtxMu.Unlock()
702
703 unsyncedStop(ctx)
704
705 rawCfgJSON = nil
706 rawCfgIndex = nil
707 rawCfg[rawConfigKey] = nil
708
709 return nil
710 }
711
712 // unsyncedStop stops ctx from running, but has
713 // no locking around ctx. It is a no-op if ctx has a
714 // nil cfg. If any app returns an error when stopping,
715 // it is logged and the function continues stopping
716 // the next app. This function assumes all apps in
717 // ctx were successfully started first.
718 //
719 // A lock on rawCfgMu is required, even though this
720 // function does not access rawCfg, that lock
721 // synchronizes the stop/start of apps.
722 func unsyncedStop(ctx Context) {
723 if ctx.cfg == nil {
724 return
725 }
726
727 // TODO: This event is experimental and subject to change.
728 ctx.emitEvent("stopping", nil)
729
730 // stop each app
731 for name, a := range ctx.cfg.apps {
732 err := a.Stop()
733 if err != nil {
734 log.Printf("[ERROR] stop %s: %v", name, err)
735 }
736 }
737
738 // clean up all modules
739 ctx.cfg.cancelFunc(fmt.Errorf("stopping apps"))
740 }
741
742 // Validate loads, provisions, and validates
743 // cfg, but does not start running it.
744 func Validate(cfg *Config) error {
745 _, err := run(cfg, false)
746 if err == nil {
747 cfg.cancelFunc(fmt.Errorf("validation complete")) // call Cleanup on all modules
748 }
749 return err
750 }
751
752 // exitProcess exits the process as gracefully as possible,
753 // but it always exits, even if there are errors doing so.
754 // It stops all apps, cleans up external locks, removes any
755 // PID file, and shuts down admin endpoint(s) in a goroutine.
756 // Errors are logged along the way, and an appropriate exit
757 // code is emitted.
758 func exitProcess(ctx context.Context, logger *zap.Logger) {
759 // let the rest of the program know we're quitting; only do it once
760 if !exiting.CompareAndSwap(false, true) {
761 return
762 }
763
764 // give the OS or service/process manager our 2 weeks' notice: we quit
765 if err := notify.Stopping(); err != nil {
766 Log().Error("unable to notify service manager of stopping state", zap.Error(err))
767 }
768
769 if logger == nil {
770 logger = Log()
771 }
772 logger.Warn("exiting; byeee!! 👋")
773
774 exitCode := ExitCodeSuccess
775 lastContext := ActiveContext()
776
777 // stop all apps
778 if err := Stop(); err != nil {
779 logger.Error("failed to stop apps", zap.Error(err))
780 exitCode = ExitCodeFailedQuit
781 }
782
783 // clean up certmagic locks
784 certmagic.CleanUpOwnLocks(ctx, logger)
785
786 // remove pidfile
787 if pidfile != "" {
788 err := os.Remove(pidfile)
789 if err != nil {
790 logger.Error("cleaning up PID file:",
791 zap.String("pidfile", pidfile),
792 zap.Error(err))
793 exitCode = ExitCodeFailedQuit
794 }
795 }
796
797 // execute any process-exit callbacks
798 for _, exitFunc := range lastContext.exitFuncs {
799 exitFunc(ctx)
800 }
801 exitFuncsMu.Lock()
802 for _, exitFunc := range exitFuncs {
803 exitFunc(ctx)
804 }
805 exitFuncsMu.Unlock()
806
807 // shut down admin endpoint(s) in goroutines so that
808 // if this function was called from an admin handler,
809 // it has a chance to return gracefully
810 // use goroutine so that we can finish responding to API request
811 go func() {
812 defer func() {
813 logger = logger.With(zap.Int("exit_code", exitCode))
814 if exitCode == ExitCodeSuccess {
815 logger.Info("shutdown complete")
816 } else {
817 logger.Error("unclean shutdown")
818 }
819 os.Exit(exitCode)
820 }()
821
822 if remoteAdminServer != nil {
823 err := stopAdminServer(remoteAdminServer)
824 if err != nil {
825 exitCode = ExitCodeFailedQuit
826 logger.Error("failed to stop remote admin server gracefully", zap.Error(err))
827 }
828 }
829 if localAdminServer != nil {
830 err := stopAdminServer(localAdminServer)
831 if err != nil {
832 exitCode = ExitCodeFailedQuit
833 logger.Error("failed to stop local admin server gracefully", zap.Error(err))
834 }
835 }
836 }()
837 }
838
839 var exiting atomic.Bool
840
841 // Exiting returns true if the process is exiting.
842 // EXPERIMENTAL API: subject to change or removal.
843 func Exiting() bool { return exiting.Load() }
844
845 // OnExit registers a callback to invoke during process exit.
846 // This registration is PROCESS-GLOBAL, meaning that each
847 // function should only be registered once forever, NOT once
848 // per config load (etc).
849 //
850 // EXPERIMENTAL API: subject to change or removal.
851 func OnExit(f func(context.Context)) {
852 exitFuncsMu.Lock()
853 exitFuncs = append(exitFuncs, f)
854 exitFuncsMu.Unlock()
855 }
856
857 var (
858 exitFuncs []func(context.Context)
859 exitFuncsMu sync.Mutex
860 )
861
862 // Duration can be an integer or a string. An integer is
863 // interpreted as nanoseconds. If a string, it is a Go
864 // time.Duration value such as `300ms`, `1.5h`, or `2h45m`;
865 // valid units are `ns`, `us`/`µs`, `ms`, `s`, `m`, `h`, and `d`.
866 type Duration time.Duration
867
868 // UnmarshalJSON satisfies json.Unmarshaler.
869 func (d *Duration) UnmarshalJSON(b []byte) error {
870 if len(b) == 0 {
871 return io.EOF
872 }
873 var dur time.Duration
874 var err error
875 if b[0] == byte('"') && b[len(b)-1] == byte('"') {
876 dur, err = ParseDuration(strings.Trim(string(b), `"`))
877 } else {
878 err = json.Unmarshal(b, &dur)
879 }
880 *d = Duration(dur)
881 return err
882 }
883
884 // ParseDuration parses a duration string, adding
885 // support for the "d" unit meaning number of days,
886 // where a day is assumed to be 24h. The maximum
887 // input string length is 1024.
888 func ParseDuration(s string) (time.Duration, error) {
889 if len(s) > 1024 {
890 return 0, fmt.Errorf("parsing duration: input string too long")
891 }
892 var inNumber bool
893 var numStart int
894 for i := 0; i < len(s); i++ {
895 ch := s[i]
896 if ch == 'd' {
897 daysStr := s[numStart:i]
898 days, err := strconv.ParseFloat(daysStr, 64)
899 if err != nil {
900 return 0, err
901 }
902 hours := days * 24.0
903 hoursStr := strconv.FormatFloat(hours, 'f', -1, 64)
904 s = s[:numStart] + hoursStr + "h" + s[i+1:]
905 i--
906 continue
907 }
908 if !inNumber {
909 numStart = i
910 }
911 inNumber = (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == '+'
912 }
913 return time.ParseDuration(s)
914 }
915
916 // InstanceID returns the UUID for this instance, and generates one if it
917 // does not already exist. The UUID is stored in the local data directory,
918 // regardless of storage configuration, since each instance is intended to
919 // have its own unique ID.
920 func InstanceID() (uuid.UUID, error) {
921 appDataDir := AppDataDir()
922 uuidFilePath := filepath.Join(appDataDir, "instance.uuid")
923 uuidFileBytes, err := os.ReadFile(uuidFilePath)
924 if errors.Is(err, fs.ErrNotExist) {
925 uuid, err := uuid.NewRandom()
926 if err != nil {
927 return uuid, err
928 }
929 err = os.MkdirAll(appDataDir, 0o700)
930 if err != nil {
931 return uuid, err
932 }
933 err = os.WriteFile(uuidFilePath, []byte(uuid.String()), 0o600)
934 return uuid, err
935 } else if err != nil {
936 return [16]byte{}, err
937 }
938 return uuid.ParseBytes(uuidFileBytes)
939 }
940
941 // CustomVersion is an optional string that overrides Caddy's
942 // reported version. It can be helpful when downstream packagers
943 // need to manually set Caddy's version. If no other version
944 // information is available, the short form version (see
945 // Version()) will be set to CustomVersion, and the full version
946 // will include CustomVersion at the beginning.
947 //
948 // Set this variable during `go build` with `-ldflags`:
949 //
950 // -ldflags '-X github.com/caddyserver/caddy/v2.CustomVersion=v2.6.2'
951 //
952 // for example.
953 var CustomVersion string
954
955 // CustomBinaryName is an optional string that overrides the root
956 // command name from the default of "caddy". This is useful for
957 // downstream projects that embed Caddy but use a different binary
958 // name. Shell completions and help text will use this name instead
959 // of "caddy".
960 //
961 // Set this variable during `go build` with `-ldflags`:
962 //
963 // -ldflags '-X github.com/caddyserver/caddy/v2.CustomBinaryName=my_custom_caddy'
964 //
965 // for example.
966 var CustomBinaryName string
967
968 // CustomLongDescription is an optional string that overrides the
969 // long description of the root Cobra command. This is useful for
970 // downstream projects that embed Caddy but want different help
971 // output.
972 //
973 // Set this variable in an init() function of a package that is
974 // imported by your main:
975 //
976 // func init() {
977 // caddy.CustomLongDescription = "My custom server based on Caddy..."
978 // }
979 //
980 // for example.
981 var CustomLongDescription string
982
983 // Version returns the Caddy version in a simple/short form, and
984 // a full version string. The short form will not have spaces and
985 // is intended for User-Agent strings and similar, but may be
986 // omitting valuable information. Note that Caddy must be compiled
987 // in a special way to properly embed complete version information.
988 // First this function tries to get the version from the embedded
989 // build info provided by go.mod dependencies; then it tries to
990 // get info from embedded VCS information, which requires having
991 // built Caddy from a git repository. If no version is available,
992 // this function returns "(devel)" because Go uses that, but for
993 // the simple form we change it to "unknown". If still no version
994 // is available (e.g. no VCS repo), then it will use CustomVersion;
995 // CustomVersion is always prepended to the full version string.
996 //
997 // See relevant Go issues: https://github.com/golang/go/issues/29228
998 // and https://github.com/golang/go/issues/50603.
999 //
1000 // This function is experimental and subject to change or removal.
1001 func Version() (simple, full string) {
1002 // the currently-recommended way to build Caddy involves
1003 // building it as a dependency so we can extract version
1004 // information from go.mod tooling; once the upstream
1005 // Go issues are fixed, we should just be able to use
1006 // bi.Main... hopefully.
1007 var module *debug.Module
1008 bi, ok := debug.ReadBuildInfo()
1009 if !ok {
1010 if CustomVersion != "" {
1011 full = CustomVersion
1012 simple = CustomVersion
1013 return simple, full
1014 }
1015 full = "unknown"
1016 simple = "unknown"
1017 return simple, full
1018 }
1019 // find the Caddy module in the dependency list
1020 for _, dep := range bi.Deps {
1021 if dep.Path == ImportPath {
1022 module = dep
1023 break
1024 }
1025 }
1026 if module != nil {
1027 simple, full = module.Version, module.Version
1028 if module.Sum != "" {
1029 full += " " + module.Sum
1030 }
1031 if module.Replace != nil {
1032 full += " => " + module.Replace.Path
1033 if module.Replace.Version != "" {
1034 simple = module.Replace.Version + "_custom"
1035 full += "@" + module.Replace.Version
1036 }
1037 if module.Replace.Sum != "" {
1038 full += " " + module.Replace.Sum
1039 }
1040 }
1041 }
1042
1043 if full == "" {
1044 var vcsRevision string
1045 var vcsTime time.Time
1046 var vcsModified bool
1047 for _, setting := range bi.Settings {
1048 switch setting.Key {
1049 case "vcs.revision":
1050 vcsRevision = setting.Value
1051 case "vcs.time":
1052 vcsTime, _ = time.Parse(time.RFC3339, setting.Value)
1053 case "vcs.modified":
1054 vcsModified, _ = strconv.ParseBool(setting.Value)
1055 }
1056 }
1057
1058 if vcsRevision != "" {
1059 var modified string
1060 if vcsModified {
1061 modified = "+modified"
1062 }
1063 full = fmt.Sprintf("%s%s (%s)", vcsRevision, modified, vcsTime.Format(time.RFC822))
1064 simple = vcsRevision
1065
1066 // use short checksum for simple, if hex-only
1067 if _, err := hex.DecodeString(simple); err == nil {
1068 simple = simple[:8]
1069 }
1070
1071 // append date to simple since it can be convenient
1072 // to know the commit date as part of the version
1073 if !vcsTime.IsZero() {
1074 simple += "-" + vcsTime.Format("20060102")
1075 }
1076 }
1077 }
1078
1079 if full == "" {
1080 if CustomVersion != "" {
1081 full = CustomVersion
1082 } else {
1083 full = "unknown"
1084 }
1085 } else if CustomVersion != "" {
1086 full = CustomVersion + " " + full
1087 }
1088
1089 if simple == "" || simple == "(devel)" {
1090 if CustomVersion != "" {
1091 simple = CustomVersion
1092 } else {
1093 simple = "unknown"
1094 }
1095 }
1096
1097 return simple, full
1098 }
1099
1100 // Event represents something that has happened or is happening.
1101 // An Event value is not synchronized, so it should be copied if
1102 // being used in goroutines.
1103 //
1104 // EXPERIMENTAL: Events are subject to change.
1105 type Event struct {
1106 // If non-nil, the event has been aborted, meaning
1107 // propagation has stopped to other handlers and
1108 // the code should stop what it was doing. Emitters
1109 // may choose to use this as a signal to adjust their
1110 // code path appropriately.
1111 Aborted error
1112
1113 // The data associated with the event. Usually the
1114 // original emitter will be the only one to set or
1115 // change these values, but the field is exported
1116 // so handlers can have full access if needed.
1117 // However, this map is not synchronized, so
1118 // handlers must not use this map directly in new
1119 // goroutines; instead, copy the map to use it in a
1120 // goroutine. Data may be nil.
1121 Data map[string]any
1122
1123 id uuid.UUID
1124 ts time.Time
1125 name string
1126 origin Module
1127 }
1128
1129 // NewEvent creates a new event, but does not emit the event. To emit an
1130 // event, call Emit() on the current instance of the caddyevents app instead.
1131 //
1132 // EXPERIMENTAL: Subject to change.
1133 func NewEvent(ctx Context, name string, data map[string]any) (Event, error) {
1134 id, err := uuid.NewRandom()
1135 if err != nil {
1136 return Event{}, fmt.Errorf("generating new event ID: %v", err)
1137 }
1138 name = strings.ToLower(name)
1139 return Event{
1140 Data: data,
1141 id: id,
1142 ts: time.Now(),
1143 name: name,
1144 origin: ctx.Module(),
1145 }, nil
1146 }
1147
1148 func (e Event) ID() uuid.UUID { return e.id }
1149 func (e Event) Timestamp() time.Time { return e.ts }
1150 func (e Event) Name() string { return e.name }
1151 func (e Event) Origin() Module { return e.origin } // Returns the module that originated the event. May be nil, usually if caddy core emits the event.
1152
1153 // CloudEvent exports event e as a structure that, when
1154 // serialized as JSON, is compatible with the
1155 // CloudEvents spec.
1156 func (e Event) CloudEvent() CloudEvent {
1157 dataJSON, _ := json.Marshal(e.Data)
1158 var source string
1159 if e.Origin() == nil {
1160 source = "caddy"
1161 } else {
1162 source = string(e.Origin().CaddyModule().ID)
1163 }
1164 return CloudEvent{
1165 ID: e.id.String(),
1166 Source: source,
1167 SpecVersion: "1.0",
1168 Type: e.name,
1169 Time: e.ts,
1170 DataContentType: "application/json",
1171 Data: dataJSON,
1172 }
1173 }
1174
1175 // CloudEvent is a JSON-serializable structure that
1176 // is compatible with the CloudEvents specification.
1177 // See https://cloudevents.io.
1178 // EXPERIMENTAL: Subject to change.
1179 type CloudEvent struct {
1180 ID string `json:"id"`
1181 Source string `json:"source"`
1182 SpecVersion string `json:"specversion"`
1183 Type string `json:"type"`
1184 Time time.Time `json:"time"`
1185 DataContentType string `json:"datacontenttype,omitempty"`
1186 Data json.RawMessage `json:"data,omitempty"`
1187 }
1188
1189 // ErrEventAborted cancels an event.
1190 var ErrEventAborted = errors.New("event aborted")
1191
1192 // ActiveContext returns the currently-active context.
1193 // This function is experimental and might be changed
1194 // or removed in the future.
1195 func ActiveContext() Context {
1196 currentCtxMu.RLock()
1197 defer currentCtxMu.RUnlock()
1198 return currentCtx
1199 }
1200
1201 // CtxKey is a value type for use with context.WithValue.
1202 type CtxKey string
1203
1204 // This group of variables pertains to the current configuration.
1205 var (
1206 // currentCtx is the root context for the currently-running
1207 // configuration, which can be accessed through this value.
1208 // If the Config contained in this value is not nil, then
1209 // a config is currently active/running.
1210 currentCtx Context
1211 currentCtxMu sync.RWMutex
1212
1213 // rawCfg is the current, generic-decoded configuration;
1214 // we initialize it as a map with one field ("config")
1215 // to maintain parity with the API endpoint and to avoid
1216 // the special case of having to access/mutate the variable
1217 // directly without traversing into it.
1218 rawCfg = map[string]any{
1219 rawConfigKey: nil,
1220 }
1221
1222 // rawCfgJSON is the JSON-encoded form of rawCfg. Keeping
1223 // this around avoids an extra Marshal call during changes.
1224 rawCfgJSON []byte
1225
1226 // rawCfgIndex is the map of user-assigned ID to expanded
1227 // path, for converting /id/ paths to /config/ paths.
1228 rawCfgIndex map[string]string
1229
1230 // rawCfgMu protects all the rawCfg fields and also
1231 // essentially synchronizes config changes/reloads.
1232 rawCfgMu sync.RWMutex
1233 )
1234
1235 // lastConfigFile and lastConfigAdapter remember the source config
1236 // file and adapter used when Caddy was started via the CLI "run" command.
1237 // These are consulted by the SIGUSR1 handler to attempt reloading from
1238 // the same source. They are intentionally not set for other entrypoints
1239 // such as "caddy start" or subcommands like file-server.
1240 var (
1241 lastConfigMu sync.RWMutex
1242 lastConfigFile string
1243 lastConfigAdapter string
1244 )
1245
1246 // reloadFromSourceFunc is the type of stored callback
1247 // which is called when we receive a SIGUSR1 signal.
1248 type reloadFromSourceFunc func(file, adapter string) error
1249
1250 // reloadFromSourceCallback is the stored callback
1251 // which is called when we receive a SIGUSR1 signal.
1252 var reloadFromSourceCallback reloadFromSourceFunc
1253
1254 // errReloadFromSourceUnavailable is returned when no reload-from-source callback is set.
1255 var errReloadFromSourceUnavailable = errors.New("reload from source unavailable in this process") //nolint:unused
1256
1257 // SetLastConfig records the given source file and adapter as the
1258 // last-known external configuration source. Intended to be called
1259 // only when starting via "caddy run --config <file> --adapter <adapter>".
1260 func SetLastConfig(file, adapter string, fn reloadFromSourceFunc) {
1261 lastConfigMu.Lock()
1262 lastConfigFile = file
1263 lastConfigAdapter = adapter
1264 reloadFromSourceCallback = fn
1265 lastConfigMu.Unlock()
1266 }
1267
1268 // ClearLastConfigIfDifferent clears the recorded last-config if the provided
1269 // source file/adapter do not match the recorded last-config. If both srcFile
1270 // and srcAdapter are empty, the last-config is cleared.
1271 func ClearLastConfigIfDifferent(srcFile, srcAdapter string) {
1272 if (srcFile != "" || srcAdapter != "") && lastConfigMatches(srcFile, srcAdapter) {
1273 return
1274 }
1275 SetLastConfig("", "", nil)
1276 }
1277
1278 // getLastConfig returns the last-known config file and adapter.
1279 func getLastConfig() (file, adapter string, fn reloadFromSourceFunc) {
1280 lastConfigMu.RLock()
1281 f, a, cb := lastConfigFile, lastConfigAdapter, reloadFromSourceCallback
1282 lastConfigMu.RUnlock()
1283 return f, a, cb
1284 }
1285
1286 // lastConfigMatches returns true if the provided source file and/or adapter
1287 // matches the recorded last-config. Matching rules (in priority order):
1288 // 1. If srcAdapter is provided and differs from the recorded adapter, no match.
1289 // 2. If srcFile exactly equals the recorded file, match.
1290 // 3. If both sides can be made absolute and equal, match.
1291 // 4. If basenames are equal, match.
1292 func lastConfigMatches(srcFile, srcAdapter string) bool {
1293 lf, la, _ := getLastConfig()
1294
1295 // If adapter is provided, it must match.
1296 if srcAdapter != "" && srcAdapter != la {
1297 return false
1298 }
1299
1300 // Quick equality check.
1301 if srcFile == lf {
1302 return true
1303 }
1304
1305 // Try absolute path comparison.
1306 sAbs, sErr := filepath.Abs(srcFile)
1307 lAbs, lErr := filepath.Abs(lf)
1308 if sErr == nil && lErr == nil && sAbs == lAbs {
1309 return true
1310 }
1311
1312 // Final fallback: basename equality.
1313 if filepath.Base(srcFile) == filepath.Base(lf) {
1314 return true
1315 }
1316
1317 return false
1318 }
1319
1320 // errSameConfig is returned if the new config is the same
1321 // as the old one. This isn't usually an actual, actionable
1322 // error; it's mostly a sentinel value.
1323 var errSameConfig = errors.New("config is unchanged")
1324
1325 // ImportPath is the package import path for Caddy core.
1326 // This identifier may be removed in the future.
1327 const ImportPath = "github.com/caddyserver/caddy/v2"