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
| 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" |