[
  {
    "path": ".gitignore",
    "content": "_book/\n.*\n!.gitignore\nreferences\nagent/\n"
  },
  {
    "path": "Caddyfile",
    "content": "http://127.0.0.1:22020\n\nroot * src\n\nfile_server\ntemplates\nencode gzip\n\ntry_files {path}.html {path}\n\nredir   /docs/json      /docs/json/\nredir   /docs/modules   /docs/modules/\nrewrite /docs/json/*    /docs/json/index.html\nrewrite /docs/modules/* /docs/modules/index.html\nrewrite /docs/*         /docs/index.html\n\nrewrite /api/modules    /api/modules/index.json\nrewrite /api/modules/*  {path}.json\nrewrite /api/docs/config/ /api/docs/config.json\n\n# redirect to /docs/\nredir   /               /docs/\n"
  },
  {
    "path": "readme.md",
    "content": "Caddy v2 中文文档\n=================\n\n这是Caddy v2中文文档的网站, [https://caddy2.dengxiaolong.com/](https://caddy2.dengxiaolong.com/docs/).\n\n\n## 要求\n\n- 安装Caddy 2 (在PATH可直接运行`caddy`)\n\n\n## 快速开始\n\n1. `git clone https://github.com/phpple/caddy2-cn-doc/`\n2. `cd caddy2-cn-doc`\n3. `caddy run`\n\n第一次，系统可能会提示你输入密码。这样Caddy就可以通过本地HTTPS为站点提供服务。如果无法绑定低端口，请更改[`Caddyfile`](Caddyfile)顶部的地址，例如`localhost:2015`。\n\n然后你可以通过浏览器访问[http://127.0.0.1:22020/](http://127.0.0.1:22020/) (或者你配置的其他地址)。\n"
  },
  {
    "path": "src/api/docs/config.json",
    "content": "{\n  \"status_code\": 200,\n  \"result\": {\n    \"structure\": {\n      \"type\": \"struct\",\n      \"type_name\": \"github.com/caddyserver/caddy/v2.Config\",\n      \"struct_fields\": [\n        {\n          \"key\": \"admin\",\n          \"value\": {\n            \"type\": \"struct\",\n            \"type_name\": \"github.com/caddyserver/caddy/v2.AdminConfig\",\n            \"struct_fields\": [\n              {\n                \"key\": \"disabled\",\n                \"value\": {\n                  \"type\": \"bool\",\n                  \"doc\": \"If true, the admin endpoint will be completely disabled.\\nNote that this makes any runtime changes to the config\\nimpossible, since the interface to do so is through the\\nadmin endpoint.\"\n                },\n                \"doc\": \"If true, the admin endpoint will be completely disabled.\\nNote that this makes any runtime changes to the config\\nimpossible, since the interface to do so is through the\\nadmin endpoint.\"\n              },\n              {\n                \"key\": \"listen\",\n                \"value\": {\n                  \"type\": \"string\",\n                  \"doc\": \"The address to which the admin endpoint's listener should\\nbind itself. Can be any single network address that can be\\nparsed by Caddy. Default: localhost:2019\"\n                },\n                \"doc\": \"The address to which the admin endpoint's listener should\\nbind itself. Can be any single network address that can be\\nparsed by Caddy. Default: localhost:2019\"\n              },\n              {\n                \"key\": \"enforce_origin\",\n                \"value\": {\n                  \"type\": \"bool\",\n                  \"doc\": \"If true, CORS headers will be emitted, and requests to the\\nAPI will be rejected if their `Host` and `Origin` headers\\ndo not match the expected value(s). Use `origins` to\\ncustomize which origins/hosts are allowed. If `origins` is\\nnot set, the listen address is the only value allowed by\\ndefault. Enforced only on local (plaintext) endpoint.\"\n                },\n                \"doc\": \"If true, CORS headers will be emitted, and requests to the\\nAPI will be rejected if their `Host` and `Origin` headers\\ndo not match the expected value(s). Use `origins` to\\ncustomize which origins/hosts are allowed. If `origins` is\\nnot set, the listen address is the only value allowed by\\ndefault. Enforced only on local (plaintext) endpoint.\"\n              },\n              {\n                \"key\": \"origins\",\n                \"value\": {\n                  \"type\": \"array\",\n                  \"elems\": {\n                    \"type\": \"string\",\n                    \"doc\": \"The list of allowed origins/hosts for API requests. Only needed\\nif accessing the admin endpoint from a host different from the\\nsocket's network interface or if `enforce_origin` is true. If not\\nset, the listener address will be the default value. If set but\\nempty, no origins will be allowed. Enforced only on local\\n(plaintext) endpoint.\"\n                  }\n                },\n                \"doc\": \"The list of allowed origins/hosts for API requests. Only needed\\nif accessing the admin endpoint from a host different from the\\nsocket's network interface or if `enforce_origin` is true. If not\\nset, the listener address will be the default value. If set but\\nempty, no origins will be allowed. Enforced only on local\\n(plaintext) endpoint.\"\n              },\n              {\n                \"key\": \"config\",\n                \"value\": {\n                  \"type\": \"struct\",\n                  \"type_name\": \"github.com/caddyserver/caddy/v2.ConfigSettings\",\n                  \"struct_fields\": [\n                    {\n                      \"key\": \"persist\",\n                      \"value\": {\n                        \"type\": \"bool\",\n                        \"doc\": \"Whether to keep a copy of the active config on disk. Default is true.\\nNote that \\\"pulled\\\" dynamic configs (using the neighboring \\\"load\\\" module)\\nare not persisted; only configs that are pushed to Caddy get persisted.\"\n                      },\n                      \"doc\": \"Whether to keep a copy of the active config on disk. Default is true.\\nNote that \\\"pulled\\\" dynamic configs (using the neighboring \\\"load\\\" module)\\nare not persisted; only configs that are pushed to Caddy get persisted.\"\n                    },\n                    {\n                      \"key\": \"load\",\n                      \"value\": {\n                        \"type\": \"module\",\n                        \"doc\": \"Loads a configuration to use. This is helpful if your configs are\\nmanaged elsewhere, and you want Caddy to pull its config dynamically\\nwhen it starts. The pulled config completely replaces the current\\none, just like any other config load. It is an error if a pulled\\nconfig is configured to pull another config.\\n\\nEXPERIMENTAL: Subject to change.\",\n                        \"module_namespace\": \"caddy.config_loaders\",\n                        \"module_inline_key\": \"module\"\n                      },\n                      \"doc\": \"Loads a configuration to use. This is helpful if your configs are\\nmanaged elsewhere, and you want Caddy to pull its config dynamically\\nwhen it starts. The pulled config completely replaces the current\\none, just like any other config load. It is an error if a pulled\\nconfig is configured to pull another config.\\n\\nEXPERIMENTAL: Subject to change.\"\n                    }\n                  ],\n                  \"doc\": \"Options pertaining to configuration management.\\n\\n\\nConfigSettings configures the management of configuration.\"\n                },\n                \"doc\": \"Options pertaining to configuration management.\\n\\n\\nConfigSettings configures the management of configuration.\"\n              },\n              {\n                \"key\": \"identity\",\n                \"value\": {\n                  \"type\": \"struct\",\n                  \"type_name\": \"github.com/caddyserver/caddy/v2.IdentityConfig\",\n                  \"struct_fields\": [\n                    {\n                      \"key\": \"identifiers\",\n                      \"value\": {\n                        \"type\": \"array\",\n                        \"elems\": {\n                          \"type\": \"string\",\n                          \"doc\": \"List of names or IP addresses which refer to this server.\\nCertificates will be obtained for these identifiers so\\nsecure TLS connections can be made using them.\"\n                        }\n                      },\n                      \"doc\": \"List of names or IP addresses which refer to this server.\\nCertificates will be obtained for these identifiers so\\nsecure TLS connections can be made using them.\"\n                    },\n                    {\n                      \"key\": \"issuers\",\n                      \"value\": {\n                        \"type\": \"array\",\n                        \"elems\": {\n                          \"type\": \"module\",\n                          \"doc\": \"Issuers that can provide this admin endpoint its identity\\ncertificate(s). Default: ACME issuers configured for\\nZeroSSL and Let's Encrypt. Be sure to change this if you\\nrequire credentials for private identifiers.\",\n                          \"module_namespace\": \"tls.issuance\",\n                          \"module_inline_key\": \"module\"\n                        }\n                      },\n                      \"doc\": \"Issuers that can provide this admin endpoint its identity\\ncertificate(s). Default: ACME issuers configured for\\nZeroSSL and Let's Encrypt. Be sure to change this if you\\nrequire credentials for private identifiers.\"\n                    }\n                  ],\n                  \"doc\": \"Options that establish this server's identity. Identity refers to\\ncredentials which can be used to uniquely identify and authenticate\\nthis server instance. This is required if remote administration is\\nenabled (but does not require remote administration to be enabled).\\nDefault: no identity management.\\n\\n\\nIdentityConfig configures management of this server's identity. An identity\\nconsists of credentials that uniquely verify this instance; for example,\\nTLS certificates (public + private key pairs).\"\n                },\n                \"doc\": \"Options that establish this server's identity. Identity refers to\\ncredentials which can be used to uniquely identify and authenticate\\nthis server instance. This is required if remote administration is\\nenabled (but does not require remote administration to be enabled).\\nDefault: no identity management.\\n\\n\\nIdentityConfig configures management of this server's identity. An identity\\nconsists of credentials that uniquely verify this instance; for example,\\nTLS certificates (public + private key pairs).\"\n              },\n              {\n                \"key\": \"remote\",\n                \"value\": {\n                  \"type\": \"struct\",\n                  \"type_name\": \"github.com/caddyserver/caddy/v2.RemoteAdmin\",\n                  \"struct_fields\": [\n                    {\n                      \"key\": \"listen\",\n                      \"value\": {\n                        \"type\": \"string\",\n                        \"doc\": \"The address on which to start the secure listener.\\nDefault: :2021\"\n                      },\n                      \"doc\": \"The address on which to start the secure listener.\\nDefault: :2021\"\n                    },\n                    {\n                      \"key\": \"access_control\",\n                      \"value\": {\n                        \"type\": \"array\",\n                        \"elems\": {\n                          \"type\": \"struct\",\n                          \"type_name\": \"github.com/caddyserver/caddy/v2.AdminAccess\",\n                          \"struct_fields\": [\n                            {\n                              \"key\": \"public_keys\",\n                              \"value\": {\n                                \"type\": \"array\",\n                                \"elems\": {\n                                  \"type\": \"string\",\n                                  \"doc\": \"Base64-encoded DER certificates containing public keys to accept.\\n(The contents of PEM certificate blocks are base64-encoded DER.)\\nAny of these public keys can appear in any part of a verified chain.\"\n                                }\n                              },\n                              \"doc\": \"Base64-encoded DER certificates containing public keys to accept.\\n(The contents of PEM certificate blocks are base64-encoded DER.)\\nAny of these public keys can appear in any part of a verified chain.\"\n                            },\n                            {\n                              \"key\": \"permissions\",\n                              \"value\": {\n                                \"type\": \"array\",\n                                \"elems\": {\n                                  \"type\": \"struct\",\n                                  \"type_name\": \"github.com/caddyserver/caddy/v2.AdminPermissions\",\n                                  \"struct_fields\": [\n                                    {\n                                      \"key\": \"paths\",\n                                      \"value\": {\n                                        \"type\": \"array\",\n                                        \"elems\": {\n                                          \"type\": \"string\",\n                                          \"doc\": \"The API paths allowed. Paths are simple prefix matches.\\nAny subpath of the specified paths will be allowed.\"\n                                        }\n                                      },\n                                      \"doc\": \"The API paths allowed. Paths are simple prefix matches.\\nAny subpath of the specified paths will be allowed.\"\n                                    },\n                                    {\n                                      \"key\": \"methods\",\n                                      \"value\": {\n                                        \"type\": \"array\",\n                                        \"elems\": {\n                                          \"type\": \"string\",\n                                          \"doc\": \"The HTTP methods allowed for the given paths.\"\n                                        }\n                                      },\n                                      \"doc\": \"The HTTP methods allowed for the given paths.\"\n                                    }\n                                  ],\n                                  \"doc\": \"Limits what the associated identities are allowed to do.\\nIf unspecified, all permissions are granted.\\n\\n\\nAdminPermissions specifies what kinds of requests are allowed\\nto be made to the admin endpoint.\"\n                                }\n                              },\n                              \"doc\": \"Limits what the associated identities are allowed to do.\\nIf unspecified, all permissions are granted.\\n\\n\\nAdminPermissions specifies what kinds of requests are allowed\\nto be made to the admin endpoint.\"\n                            }\n                          ],\n                          \"doc\": \"List of access controls for this secure admin endpoint.\\nThis configures TLS mutual authentication (i.e. authorized\\nclient certificates), but also application-layer permissions\\nlike which paths and methods each identity is authorized for.\\n\\n\\nAdminAccess specifies what permissions an identity or group\\nof identities are granted.\"\n                        }\n                      },\n                      \"doc\": \"List of access controls for this secure admin endpoint.\\nThis configures TLS mutual authentication (i.e. authorized\\nclient certificates), but also application-layer permissions\\nlike which paths and methods each identity is authorized for.\\n\\n\\nAdminAccess specifies what permissions an identity or group\\nof identities are granted.\"\n                    }\n                  ],\n                  \"doc\": \"Options pertaining to remote administration. By default, remote\\nadministration is disabled. If enabled, identity management must\\nalso be configured, as that is how the endpoint is secured.\\nSee the neighboring \\\"identity\\\" object.\\n\\nEXPERIMENTAL: This feature is subject to change.\\n\\n\\nRemoteAdmin enables and configures remote administration. If enabled,\\na secure listener enforcing mutual TLS authentication will be started\\non a different port from the standard plaintext admin server.\\n\\nThis endpoint is secured using identity management, which must be\\nconfigured separately (because identity management does not depend\\non remote administration). See the admin/identity config struct.\\n\\nEXPERIMENTAL: Subject to change.\"\n                },\n                \"doc\": \"Options pertaining to remote administration. By default, remote\\nadministration is disabled. If enabled, identity management must\\nalso be configured, as that is how the endpoint is secured.\\nSee the neighboring \\\"identity\\\" object.\\n\\nEXPERIMENTAL: This feature is subject to change.\\n\\n\\nRemoteAdmin enables and configures remote administration. If enabled,\\na secure listener enforcing mutual TLS authentication will be started\\non a different port from the standard plaintext admin server.\\n\\nThis endpoint is secured using identity management, which must be\\nconfigured separately (because identity management does not depend\\non remote administration). See the admin/identity config struct.\\n\\nEXPERIMENTAL: Subject to change.\"\n              }\n            ],\n            \"doc\": \"AdminConfig configures Caddy's API endpoint, which is used\\nto manage Caddy while it is running.\\n\"\n          },\n          \"doc\": \"AdminConfig configures Caddy's API endpoint, which is used\\nto manage Caddy while it is running.\\n\"\n        },\n        {\n          \"key\": \"logging\",\n          \"value\": {\n            \"type\": \"struct\",\n            \"type_name\": \"github.com/caddyserver/caddy/v2.Logging\",\n            \"struct_fields\": [\n              {\n                \"key\": \"sink\",\n                \"value\": {\n                  \"type\": \"struct\",\n                  \"type_name\": \"github.com/caddyserver/caddy/v2.StandardLibLog\",\n                  \"struct_fields\": [\n                    {\n                      \"key\": \"writer\",\n                      \"value\": {\n                        \"type\": \"module\",\n                        \"doc\": \"The module that writes out log entries for the sink.\",\n                        \"module_namespace\": \"caddy.logging.writers\",\n                        \"module_inline_key\": \"output\"\n                      },\n                      \"doc\": \"The module that writes out log entries for the sink.\"\n                    }\n                  ],\n                  \"doc\": \"Sink is the destination for all unstructured logs emitted\\nfrom Go's standard library logger. These logs are common\\nin dependencies that are not designed specifically for use\\nin Caddy. Because it is global and unstructured, the sink\\nlacks most advanced features and customizations.\\n\\n\\nStandardLibLog configures the default Go standard library\\nglobal logger in the log package. This is necessary because\\nmodule dependencies which are not built specifically for\\nCaddy will use the standard logger. This is also known as\\nthe \\\"sink\\\" logger.\"\n                },\n                \"doc\": \"Sink is the destination for all unstructured logs emitted\\nfrom Go's standard library logger. These logs are common\\nin dependencies that are not designed specifically for use\\nin Caddy. Because it is global and unstructured, the sink\\nlacks most advanced features and customizations.\\n\\n\\nStandardLibLog configures the default Go standard library\\nglobal logger in the log package. This is necessary because\\nmodule dependencies which are not built specifically for\\nCaddy will use the standard logger. This is also known as\\nthe \\\"sink\\\" logger.\"\n              },\n              {\n                \"key\": \"logs\",\n                \"value\": {\n                  \"type\": \"map\",\n                  \"map_keys\": {\n                    \"type\": \"string\"\n                  },\n                  \"elems\": {\n                    \"type\": \"struct\",\n                    \"type_name\": \"github.com/caddyserver/caddy/v2.CustomLog\",\n                    \"struct_fields\": [\n                      {\n                        \"key\": \"writer\",\n                        \"value\": {\n                          \"type\": \"module\",\n                          \"doc\": \"The writer defines where log entries are emitted.\",\n                          \"module_namespace\": \"caddy.logging.writers\",\n                          \"module_inline_key\": \"output\"\n                        },\n                        \"doc\": \"The writer defines where log entries are emitted.\"\n                      },\n                      {\n                        \"key\": \"encoder\",\n                        \"value\": {\n                          \"type\": \"module\",\n                          \"doc\": \"The encoder is how the log entries are formatted or encoded.\",\n                          \"module_namespace\": \"caddy.logging.encoders\",\n                          \"module_inline_key\": \"format\"\n                        },\n                        \"doc\": \"The encoder is how the log entries are formatted or encoded.\"\n                      },\n                      {\n                        \"key\": \"level\",\n                        \"value\": {\n                          \"type\": \"string\",\n                          \"doc\": \"Level is the minimum level to emit, and is inclusive.\\nPossible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL\"\n                        },\n                        \"doc\": \"Level is the minimum level to emit, and is inclusive.\\nPossible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL\"\n                      },\n                      {\n                        \"key\": \"sampling\",\n                        \"value\": {\n                          \"type\": \"struct\",\n                          \"type_name\": \"github.com/caddyserver/caddy/v2.LogSampling\",\n                          \"struct_fields\": [\n                            {\n                              \"key\": \"interval\",\n                              \"value\": {\n                                \"type\": \"int\",\n                                \"type_name\": \"time.Duration\",\n                                \"doc\": \"The window over which to conduct sampling.\\n\\n\\nA Duration represents the elapsed time between two instants\\nas an int64 nanosecond count. The representation limits the\\nlargest representable duration to approximately 290 years.\"\n                              },\n                              \"doc\": \"The window over which to conduct sampling.\\n\\n\\nA Duration represents the elapsed time between two instants\\nas an int64 nanosecond count. The representation limits the\\nlargest representable duration to approximately 290 years.\"\n                            },\n                            {\n                              \"key\": \"first\",\n                              \"value\": {\n                                \"type\": \"int\",\n                                \"doc\": \"Log this many entries within a given level and\\nmessage for each interval.\"\n                              },\n                              \"doc\": \"Log this many entries within a given level and\\nmessage for each interval.\"\n                            },\n                            {\n                              \"key\": \"thereafter\",\n                              \"value\": {\n                                \"type\": \"int\",\n                                \"doc\": \"If more entries with the same level and message\\nare seen during the same interval, keep one in\\nthis many entries until the end of the interval.\"\n                              },\n                              \"doc\": \"If more entries with the same level and message\\nare seen during the same interval, keep one in\\nthis many entries until the end of the interval.\"\n                            }\n                          ],\n                          \"doc\": \"Sampling configures log entry sampling. If enabled,\\nonly some log entries will be emitted. This is useful\\nfor improving performance on extremely high-pressure\\nservers.\\n\\n\\nLogSampling configures log entry sampling.\"\n                        },\n                        \"doc\": \"Sampling configures log entry sampling. If enabled,\\nonly some log entries will be emitted. This is useful\\nfor improving performance on extremely high-pressure\\nservers.\\n\\n\\nLogSampling configures log entry sampling.\"\n                      },\n                      {\n                        \"key\": \"include\",\n                        \"value\": {\n                          \"type\": \"array\",\n                          \"elems\": {\n                            \"type\": \"string\",\n                            \"doc\": \"Include defines the names of loggers to emit in this\\nlog. For example, to include only logs emitted by the\\nadmin API, you would include \\\"admin.api\\\".\"\n                          }\n                        },\n                        \"doc\": \"Include defines the names of loggers to emit in this\\nlog. For example, to include only logs emitted by the\\nadmin API, you would include \\\"admin.api\\\".\"\n                      },\n                      {\n                        \"key\": \"exclude\",\n                        \"value\": {\n                          \"type\": \"array\",\n                          \"elems\": {\n                            \"type\": \"string\",\n                            \"doc\": \"Exclude defines the names of loggers that should be\\nskipped by this log. For example, to exclude only\\nHTTP access logs, you would exclude \\\"http.log.access\\\".\"\n                          }\n                        },\n                        \"doc\": \"Exclude defines the names of loggers that should be\\nskipped by this log. For example, to exclude only\\nHTTP access logs, you would exclude \\\"http.log.access\\\".\"\n                      }\n                    ],\n                    \"doc\": \"Logs are your logs, keyed by an arbitrary name of your\\nchoosing. The default log can be customized by defining\\na log called \\\"default\\\". You can further define other logs\\nand filter what kinds of entries they accept.\\n\\n\\nCustomLog represents a custom logger configuration.\\n\\nBy default, a log will emit all log entries. Some entries\\nwill be skipped if sampling is enabled. Further, the Include\\nand Exclude parameters define which loggers (by name) are\\nallowed or rejected from emitting in this log. If both Include\\nand Exclude are populated, their values must be mutually\\nexclusive, and longer namespaces have priority. If neither\\nare populated, all logs are emitted.\"\n                  }\n                },\n                \"doc\": \"Logs are your logs, keyed by an arbitrary name of your\\nchoosing. The default log can be customized by defining\\na log called \\\"default\\\". You can further define other logs\\nand filter what kinds of entries they accept.\\n\\n\\nCustomLog represents a custom logger configuration.\\n\\nBy default, a log will emit all log entries. Some entries\\nwill be skipped if sampling is enabled. Further, the Include\\nand Exclude parameters define which loggers (by name) are\\nallowed or rejected from emitting in this log. If both Include\\nand Exclude are populated, their values must be mutually\\nexclusive, and longer namespaces have priority. If neither\\nare populated, all logs are emitted.\"\n              }\n            ],\n            \"doc\": \"Logging facilitates logging within Caddy. The default log is\\ncalled \\\"default\\\" and you can customize it. You can also define\\nadditional logs.\\n\\nBy default, all logs at INFO level and higher are written to\\nstandard error (\\\"stderr\\\" writer) in a human-readable format\\n(\\\"console\\\" encoder if stdout is an interactive terminal, \\\"json\\\"\\nencoder otherwise).\\n\\nAll defined logs accept all log entries by default, but you\\ncan filter by level and module/logger names. A logger's name\\nis the same as the module's name, but a module may append to\\nlogger names for more specificity. For example, you can\\nfilter logs emitted only by HTTP handlers using the name\\n\\\"http.handlers\\\", because all HTTP handler module names have\\nthat prefix.\\n\\nCaddy logs (except the sink) are zero-allocation, so they are\\nvery high-performing in terms of memory and CPU time. Enabling\\nsampling can further increase throughput on extremely high-load\\nservers.\\n\"\n          },\n          \"doc\": \"Logging facilitates logging within Caddy. The default log is\\ncalled \\\"default\\\" and you can customize it. You can also define\\nadditional logs.\\n\\nBy default, all logs at INFO level and higher are written to\\nstandard error (\\\"stderr\\\" writer) in a human-readable format\\n(\\\"console\\\" encoder if stdout is an interactive terminal, \\\"json\\\"\\nencoder otherwise).\\n\\nAll defined logs accept all log entries by default, but you\\ncan filter by level and module/logger names. A logger's name\\nis the same as the module's name, but a module may append to\\nlogger names for more specificity. For example, you can\\nfilter logs emitted only by HTTP handlers using the name\\n\\\"http.handlers\\\", because all HTTP handler module names have\\nthat prefix.\\n\\nCaddy logs (except the sink) are zero-allocation, so they are\\nvery high-performing in terms of memory and CPU time. Enabling\\nsampling can further increase throughput on extremely high-load\\nservers.\\n\"\n        },\n        {\n          \"key\": \"storage\",\n          \"value\": {\n            \"type\": \"module\",\n            \"doc\": \"StorageRaw is a storage module that defines how/where Caddy\\nstores assets (such as TLS certificates). The default storage\\nmodule is `caddy.storage.file_system` (the local file system),\\nand the default path\\n[depends on the OS and environment](/docs/conventions#data-directory).\",\n            \"module_namespace\": \"caddy.storage\",\n            \"module_inline_key\": \"module\"\n          },\n          \"doc\": \"StorageRaw is a storage module that defines how/where Caddy\\nstores assets (such as TLS certificates). The default storage\\nmodule is `caddy.storage.file_system` (the local file system),\\nand the default path\\n[depends on the OS and environment](/docs/conventions#data-directory).\"\n        },\n        {\n          \"key\": \"apps\",\n          \"value\": {\n            \"type\": \"module_map\",\n            \"type_name\": \"github.com/caddyserver/caddy/v2.ModuleMap\",\n            \"doc\": \"AppsRaw are the apps that Caddy will load and run. The\\napp module name is the key, and the app's config is the\\nassociated value.\\n\\n\\nModuleMap is a map that can contain multiple modules,\\nwhere the map key is the module's name. (The namespace\\nis usually read from an associated field's struct tag.)\\nBecause the module's name is given as the key in a\\nmodule map, the name does not have to be given in the\\njson.RawMessage.\",\n            \"module_namespace\": \"\"\n          },\n          \"doc\": \"AppsRaw are the apps that Caddy will load and run. The\\napp module name is the key, and the app's config is the\\nassociated value.\\n\\n\\nModuleMap is a map that can contain multiple modules,\\nwhere the map key is the module's name. (The namespace\\nis usually read from an associated field's struct tag.)\\nBecause the module's name is given as the key in a\\nmodule map, the name does not have to be given in the\\njson.RawMessage.\"\n        }\n      ],\n      \"doc\": \"Config is the top (or beginning) of the Caddy configuration structure.\\nCaddy config is expressed natively as a JSON document. If you prefer\\nnot to work with JSON directly, there are [many config adapters](/docs/config-adapters)\\navailable that can convert various inputs into Caddy JSON.\\n\\nMany parts of this config are extensible through the use of Caddy modules.\\nFields which have a json.RawMessage type and which appear as dots (•••) in\\nthe online docs can be fulfilled by modules in a certain module\\nnamespace. The docs show which modules can be used in a given place.\\n\\nWhenever a module is used, its name must be given either inline as part of\\nthe module, or as the key to the module's value. The docs will make it clear\\nwhich to use.\\n\\nGenerally, all config settings are optional, as it is Caddy convention to\\nhave good, documented default values. If a parameter is required, the docs\\nshould say so.\\n\\nGo programs which are directly building a Config struct value should take\\ncare to populate the JSON-encodable fields of the struct (i.e. the fields\\nwith `json` struct tags) if employing the module lifecycle (e.g. Provision\\nmethod calls).\\n\"\n    },\n    \"namespaces\": {\n      \"\": [\n        {\n          \"name\": \"exec\",\n          \"docs\": \"exec is top level module that runs shell commands.\",\n          \"package\": \"github.com/abiosoft/caddy-exec\",\n          \"repo\": \"https://github.com/abiosoft/caddy-exec\"\n        },\n        {\n          \"name\": \"supervisor\",\n          \"package\": \"github.com/baldinof/caddy-supervisor\",\n          \"repo\": \"https://github.com/baldinof/caddy-supervisor\"\n        },\n        {\n          \"name\": \"http\",\n          \"docs\": \"http is a robust, production-ready HTTP server.\\n\\nHTTPS is enabled by default if host matchers with qualifying names are used\\nin any of routes; certificates are automatically provisioned and renewed.\\nAdditionally, automatic HTTPS will also enable HTTPS for servers that listen\\nonly on the HTTPS port but which do not have any TLS connection policies\\ndefined by adding a good, default TLS connection policy.\\n\\nIn HTTP routes, additional placeholders are available (replace any `*`):\\n\\nPlaceholder | Description\\n------------|---------------\\n`{http.request.body}` | The request body (⚠️ inefficient; use only for debugging)\\n`{http.request.cookie.*}` | HTTP request cookie\\n`{http.request.duration}` | Time up to now spent handling the request (after decoding headers from client)\\n`{http.request.header.*}` | Specific request header field\\n`{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo\\n`{http.request.host}` | The host part of the request's Host header\\n`{http.request.hostport}` | The host and port from the request's Host header\\n`{http.request.method}` | The request method\\n`{http.request.orig_method}` | The request's original method\\n`{http.request.orig_uri.path.dir}` | The request's original directory\\n`{http.request.orig_uri.path.file}` | The request's original filename\\n`{http.request.orig_uri.path}` | The request's original path\\n`{http.request.orig_uri.query}` | The request's original query string (without `?`)\\n`{http.request.orig_uri}` | The request's original URI\\n`{http.request.port}` | The port part of the request's Host header\\n`{http.request.proto}` | The protocol of the request\\n`{http.request.remote.host}` | The host part of the remote client's address\\n`{http.request.remote.port}` | The port part of the remote client's address\\n`{http.request.remote}` | The address of the remote client\\n`{http.request.scheme}` | The request scheme\\n`{http.request.tls.version}` | The TLS version name\\n`{http.request.tls.cipher_suite}` | The TLS cipher suite\\n`{http.request.tls.resumed}` | The TLS connection resumed a previous connection\\n`{http.request.tls.proto}` | The negotiated next protocol\\n`{http.request.tls.proto_mutual}` | The negotiated next protocol was advertised by the server\\n`{http.request.tls.server_name}` | The server name requested by the client, if any\\n`{http.request.tls.client.fingerprint}` | The SHA256 checksum of the client certificate\\n`{http.request.tls.client.public_key}` | The public key of the client certificate.\\n`{http.request.tls.client.public_key_sha256}` | The SHA256 checksum of the client's public key.\\n`{http.request.tls.client.certificate_pem}` | The PEM-encoded value of the certificate.\\n`{http.request.tls.client.certificate_der_base64}` | The base64-encoded value of the certificate.\\n`{http.request.tls.client.issuer}` | The issuer DN of the client certificate\\n`{http.request.tls.client.serial}` | The serial number of the client certificate\\n`{http.request.tls.client.subject}` | The subject DN of the client certificate\\n`{http.request.tls.client.san.dns_names.*}` | SAN DNS names(index optional)\\n`{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional)\\n`{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional)\\n`{http.request.tls.client.san.uris.*}` | SAN URIs (index optional)\\n`{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left)\\n`{http.request.uri.path.dir}` | The directory, excluding leaf filename\\n`{http.request.uri.path.file}` | The filename of the path, excluding directory\\n`{http.request.uri.path}` | The path component of the request URI\\n`{http.request.uri.query.*}` | Individual query string value\\n`{http.request.uri.query}` | The query string (without `?`)\\n`{http.request.uri}` | The full request URI\\n`{http.response.header.*}` | Specific response header field\\n`{http.vars.*}` | Custom variables in the HTTP handler chain\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"pki\",\n          \"docs\": \"pki provides Public Key Infrastructure facilities for Caddy.\\n\\nThis app can define certificate authorities (CAs) which are capable\\nof signing certificates. Other modules can be configured to use\\nthe CAs defined by this app for issuing certificates or getting\\nkey information needed for establishing trust.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/caddypki\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"tls\",\n          \"docs\": \"tls provides TLS facilities including certificate\\nloading and management, client auth, and more.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"security\",\n          \"docs\": \"security implements security manager.\",\n          \"package\": \"github.com/greenpau/caddy-security\",\n          \"repo\": \"https://github.com/greenpau/caddy-security\"\n        },\n        {\n          \"name\": \"crowdsec\",\n          \"docs\": \"crowdsec is a Caddy App that functions as a CrowdSec bouncer. It acts\\nas a CrowdSec API client as well as a local cache for CrowdSec decisions,\\nwhich can be used by the HTTP handler and Layer4 matcher to decide if\\na request or connection is allowed or not.\",\n          \"package\": \"github.com/hslatman/caddy-crowdsec-bouncer/crowdsec\",\n          \"repo\": \"https://github.com/hslatman/caddy-crowdsec-bouncer\"\n        },\n        {\n          \"name\": \"dynamic_dns\",\n          \"docs\": \"dynamic_dns is a Caddy app that keeps your DNS records updated with the public\\nIP address of your instance. It updates A and AAAA records.\",\n          \"package\": \"github.com/mholt/caddy-dynamicdns\",\n          \"repo\": \"https://github.com/mholt/caddy-dynamicdns\"\n        },\n        {\n          \"name\": \"layer4\",\n          \"docs\": \"layer4 is a Caddy app that operates closest to layer 4 of the OSI model.\",\n          \"package\": \"github.com/mholt/caddy-l4/layer4\",\n          \"repo\": \"https://github.com/mholt/caddy-l4\"\n        }\n      ],\n      \"caddy.config_loaders\": [\n        {\n          \"name\": \"http\",\n          \"docs\": \"http can load Caddy configs over HTTP(S). It can adapt the config\\nbased on the Content-Type header of the HTTP response.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/caddyconfig\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        }\n      ],\n      \"caddy.logging.encoders\": [\n        {\n          \"name\": \"console\",\n          \"docs\": \"console encodes log entries that are mostly human-readable.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"filter\",\n          \"docs\": \"filter can filter (manipulate) fields on\\nlog entries before they are actually encoded by\\nan underlying encoder.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"json\",\n          \"docs\": \"json encodes entries as JSON.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"logfmt\",\n          \"docs\": \"logfmt encodes log entries as logfmt:\\nhttps://www.brandur.org/logfmt\\n\\nNote that logfmt does not encode nested structures\\nproperly, so it is not a good fit for most logs.\\n\\n⚠️ DEPRECATED. Do not use. It will eventually be removed\\nfrom the standard Caddy modules. For more information,\\nsee https://github.com/caddyserver/caddy/issues/3575.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"single_field\",\n          \"docs\": \"single_field writes a log entry that consists entirely\\nof a single string field in the log entry. This is useful\\nfor custom, self-encoded log entries that consist of a\\nsingle field in the structured log entry.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"formatted\",\n          \"docs\": \"formatted allows the user to provide custom template for log prints. The\\nencoder builds atop the json encoder, thus it follows its message structure. The placeholders\\nare namespaced by the name of the app logging the message.\",\n          \"package\": \"github.com/caddyserver/format-encoder\",\n          \"repo\": \"https://github.com/caddyserver/format-encoder\"\n        }\n      ],\n      \"caddy.logging.writers\": [\n        {\n          \"name\": \"discard\",\n          \"docs\": \"discard discards all writes.\",\n          \"package\": \"github.com/caddyserver/caddy/v2\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"stderr\",\n          \"docs\": \"stderr writes logs to standard error.\",\n          \"package\": \"github.com/caddyserver/caddy/v2\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"stdout\",\n          \"docs\": \"stdout writes logs to standard out.\",\n          \"package\": \"github.com/caddyserver/caddy/v2\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"file\",\n          \"docs\": \"file can write logs to files. By default, log files\\nare rotated (\\\"rolled\\\") when they get large, and old log\\nfiles get deleted, to ensure that the process does not\\nexhaust disk space.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"net\",\n          \"docs\": \"net implements a log writer that outputs to a network socket. If\\nthe socket goes down, it will dump logs to stderr while it attempts to\\nreconnect.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        }\n      ],\n      \"caddy.storage\": [\n        {\n          \"name\": \"file_system\",\n          \"docs\": \"file_system is a certmagic.Storage wrapper for certmagic.FileStorage.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/filestorage\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"redis\",\n          \"docs\": \"redis contain Redis client, and plugin option\",\n          \"package\": \"github.com/gamalan/caddy-tlsredis\",\n          \"repo\": \"https://github.com/gamalan/caddy-tlsredis\"\n        },\n        {\n          \"name\": \"consul\",\n          \"docs\": \"consul allows to store certificates and other TLS resources\\nin a shared cluster environment using Consul's key/value-store.\\nIt uses distributed locks to ensure consistency.\",\n          \"package\": \"github.com/pteich/caddy-tlsconsul\",\n          \"repo\": \"https://github.com/pteich/caddy-tlsconsul\"\n        },\n        {\n          \"name\": \"consul\",\n          \"docs\": \"consul holds all parameters for the Consul connection\",\n          \"package\": \"github.com/pteich/caddy-tlsconsul\",\n          \"repo\": \"https://github.com/pteich/caddy-tlsconsul\"\n        },\n        {\n          \"name\": \"dynamodb\",\n          \"docs\": \"dynamodb implements certmagic.Storage to facilitate\\nstorage of certificates in DynamoDB for a clustered environment.\\nAlso implements certmagic.Locker to facilitate locking\\nand unlocking of cert data during storage\",\n          \"package\": \"github.com/silinternational/certmagic-storage-dynamodb/v2\",\n          \"repo\": \"https://github.com/silinternational/certmagic-storage-dynamodb\"\n        },\n        {\n          \"name\": \"s3\",\n          \"package\": \"github.com/ss098/certmagic-s3\",\n          \"repo\": \"https://github.com/ss098/certmagic-s3\"\n        },\n        {\n          \"name\": \"s3\",\n          \"package\": \"github.com/techknowlogick/certmagic-s3\",\n          \"repo\": \"https://github.com/techknowlogick/certmagic-s3\"\n        }\n      ],\n      \"tls.issuance\": [\n        {\n          \"name\": \"acme\",\n          \"docs\": \"acme manages certificates using the ACME protocol (RFC 8555).\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"internal\",\n          \"docs\": \"internal is a certificate issuer that generates\\ncertificates internally using a locally-configured\\nCA which can be customized using the `pki` app.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        },\n        {\n          \"name\": \"zerossl\",\n          \"docs\": \"zerossl makes an ACME manager\\nfor managing certificates using ACME.\",\n          \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n          \"repo\": \"https://github.com/caddyserver/caddy\"\n        }\n      ]\n    },\n    \"breadcrumb\": {\n      \"\": {\n        \"type\": \"struct\",\n        \"type_name\": \"github.com/caddyserver/caddy/v2.Config\",\n        \"struct_fields\": [\n          {\n            \"key\": \"admin\",\n            \"value\": {\n              \"type\": \"struct\",\n              \"type_name\": \"github.com/caddyserver/caddy/v2.AdminConfig\",\n              \"struct_fields\": [\n                {\n                  \"key\": \"disabled\",\n                  \"value\": {\n                    \"type\": \"bool\",\n                    \"doc\": \"If true, the admin endpoint will be completely disabled.\\nNote that this makes any runtime changes to the config\\nimpossible, since the interface to do so is through the\\nadmin endpoint.\"\n                  },\n                  \"doc\": \"If true, the admin endpoint will be completely disabled.\\nNote that this makes any runtime changes to the config\\nimpossible, since the interface to do so is through the\\nadmin endpoint.\"\n                },\n                {\n                  \"key\": \"listen\",\n                  \"value\": {\n                    \"type\": \"string\",\n                    \"doc\": \"The address to which the admin endpoint's listener should\\nbind itself. Can be any single network address that can be\\nparsed by Caddy. Default: localhost:2019\"\n                  },\n                  \"doc\": \"The address to which the admin endpoint's listener should\\nbind itself. Can be any single network address that can be\\nparsed by Caddy. Default: localhost:2019\"\n                },\n                {\n                  \"key\": \"enforce_origin\",\n                  \"value\": {\n                    \"type\": \"bool\",\n                    \"doc\": \"If true, CORS headers will be emitted, and requests to the\\nAPI will be rejected if their `Host` and `Origin` headers\\ndo not match the expected value(s). Use `origins` to\\ncustomize which origins/hosts are allowed. If `origins` is\\nnot set, the listen address is the only value allowed by\\ndefault. Enforced only on local (plaintext) endpoint.\"\n                  },\n                  \"doc\": \"If true, CORS headers will be emitted, and requests to the\\nAPI will be rejected if their `Host` and `Origin` headers\\ndo not match the expected value(s). Use `origins` to\\ncustomize which origins/hosts are allowed. If `origins` is\\nnot set, the listen address is the only value allowed by\\ndefault. Enforced only on local (plaintext) endpoint.\"\n                },\n                {\n                  \"key\": \"origins\",\n                  \"value\": {\n                    \"type\": \"array\",\n                    \"elems\": {\n                      \"type\": \"string\",\n                      \"doc\": \"The list of allowed origins/hosts for API requests. Only needed\\nif accessing the admin endpoint from a host different from the\\nsocket's network interface or if `enforce_origin` is true. If not\\nset, the listener address will be the default value. If set but\\nempty, no origins will be allowed. Enforced only on local\\n(plaintext) endpoint.\"\n                    }\n                  },\n                  \"doc\": \"The list of allowed origins/hosts for API requests. Only needed\\nif accessing the admin endpoint from a host different from the\\nsocket's network interface or if `enforce_origin` is true. If not\\nset, the listener address will be the default value. If set but\\nempty, no origins will be allowed. Enforced only on local\\n(plaintext) endpoint.\"\n                },\n                {\n                  \"key\": \"config\",\n                  \"value\": {\n                    \"type\": \"struct\",\n                    \"type_name\": \"github.com/caddyserver/caddy/v2.ConfigSettings\",\n                    \"struct_fields\": [\n                      {\n                        \"key\": \"persist\",\n                        \"value\": {\n                          \"type\": \"bool\",\n                          \"doc\": \"Whether to keep a copy of the active config on disk. Default is true.\\nNote that \\\"pulled\\\" dynamic configs (using the neighboring \\\"load\\\" module)\\nare not persisted; only configs that are pushed to Caddy get persisted.\"\n                        },\n                        \"doc\": \"Whether to keep a copy of the active config on disk. Default is true.\\nNote that \\\"pulled\\\" dynamic configs (using the neighboring \\\"load\\\" module)\\nare not persisted; only configs that are pushed to Caddy get persisted.\"\n                      },\n                      {\n                        \"key\": \"load\",\n                        \"value\": {\n                          \"type\": \"module\",\n                          \"doc\": \"Loads a configuration to use. This is helpful if your configs are\\nmanaged elsewhere, and you want Caddy to pull its config dynamically\\nwhen it starts. The pulled config completely replaces the current\\none, just like any other config load. It is an error if a pulled\\nconfig is configured to pull another config.\\n\\nEXPERIMENTAL: Subject to change.\",\n                          \"module_namespace\": \"caddy.config_loaders\",\n                          \"module_inline_key\": \"module\"\n                        },\n                        \"doc\": \"Loads a configuration to use. This is helpful if your configs are\\nmanaged elsewhere, and you want Caddy to pull its config dynamically\\nwhen it starts. The pulled config completely replaces the current\\none, just like any other config load. It is an error if a pulled\\nconfig is configured to pull another config.\\n\\nEXPERIMENTAL: Subject to change.\"\n                      }\n                    ],\n                    \"doc\": \"Options pertaining to configuration management.\\n\\n\\nConfigSettings configures the management of configuration.\"\n                  },\n                  \"doc\": \"Options pertaining to configuration management.\\n\\n\\nConfigSettings configures the management of configuration.\"\n                },\n                {\n                  \"key\": \"identity\",\n                  \"value\": {\n                    \"type\": \"struct\",\n                    \"type_name\": \"github.com/caddyserver/caddy/v2.IdentityConfig\",\n                    \"struct_fields\": [\n                      {\n                        \"key\": \"identifiers\",\n                        \"value\": {\n                          \"type\": \"array\",\n                          \"elems\": {\n                            \"type\": \"string\",\n                            \"doc\": \"List of names or IP addresses which refer to this server.\\nCertificates will be obtained for these identifiers so\\nsecure TLS connections can be made using them.\"\n                          }\n                        },\n                        \"doc\": \"List of names or IP addresses which refer to this server.\\nCertificates will be obtained for these identifiers so\\nsecure TLS connections can be made using them.\"\n                      },\n                      {\n                        \"key\": \"issuers\",\n                        \"value\": {\n                          \"type\": \"array\",\n                          \"elems\": {\n                            \"type\": \"module\",\n                            \"doc\": \"Issuers that can provide this admin endpoint its identity\\ncertificate(s). Default: ACME issuers configured for\\nZeroSSL and Let's Encrypt. Be sure to change this if you\\nrequire credentials for private identifiers.\",\n                            \"module_namespace\": \"tls.issuance\",\n                            \"module_inline_key\": \"module\"\n                          }\n                        },\n                        \"doc\": \"Issuers that can provide this admin endpoint its identity\\ncertificate(s). Default: ACME issuers configured for\\nZeroSSL and Let's Encrypt. Be sure to change this if you\\nrequire credentials for private identifiers.\"\n                      }\n                    ],\n                    \"doc\": \"Options that establish this server's identity. Identity refers to\\ncredentials which can be used to uniquely identify and authenticate\\nthis server instance. This is required if remote administration is\\nenabled (but does not require remote administration to be enabled).\\nDefault: no identity management.\\n\\n\\nIdentityConfig configures management of this server's identity. An identity\\nconsists of credentials that uniquely verify this instance; for example,\\nTLS certificates (public + private key pairs).\"\n                  },\n                  \"doc\": \"Options that establish this server's identity. Identity refers to\\ncredentials which can be used to uniquely identify and authenticate\\nthis server instance. This is required if remote administration is\\nenabled (but does not require remote administration to be enabled).\\nDefault: no identity management.\\n\\n\\nIdentityConfig configures management of this server's identity. An identity\\nconsists of credentials that uniquely verify this instance; for example,\\nTLS certificates (public + private key pairs).\"\n                },\n                {\n                  \"key\": \"remote\",\n                  \"value\": {\n                    \"type\": \"struct\",\n                    \"type_name\": \"github.com/caddyserver/caddy/v2.RemoteAdmin\",\n                    \"struct_fields\": [\n                      {\n                        \"key\": \"listen\",\n                        \"value\": {\n                          \"type\": \"string\",\n                          \"doc\": \"The address on which to start the secure listener.\\nDefault: :2021\"\n                        },\n                        \"doc\": \"The address on which to start the secure listener.\\nDefault: :2021\"\n                      },\n                      {\n                        \"key\": \"access_control\",\n                        \"value\": {\n                          \"type\": \"array\",\n                          \"elems\": {\n                            \"type\": \"struct\",\n                            \"type_name\": \"github.com/caddyserver/caddy/v2.AdminAccess\",\n                            \"struct_fields\": [\n                              {\n                                \"key\": \"public_keys\",\n                                \"value\": {\n                                  \"type\": \"array\",\n                                  \"elems\": {\n                                    \"type\": \"string\",\n                                    \"doc\": \"Base64-encoded DER certificates containing public keys to accept.\\n(The contents of PEM certificate blocks are base64-encoded DER.)\\nAny of these public keys can appear in any part of a verified chain.\"\n                                  }\n                                },\n                                \"doc\": \"Base64-encoded DER certificates containing public keys to accept.\\n(The contents of PEM certificate blocks are base64-encoded DER.)\\nAny of these public keys can appear in any part of a verified chain.\"\n                              },\n                              {\n                                \"key\": \"permissions\",\n                                \"value\": {\n                                  \"type\": \"array\",\n                                  \"elems\": {\n                                    \"type\": \"struct\",\n                                    \"type_name\": \"github.com/caddyserver/caddy/v2.AdminPermissions\",\n                                    \"struct_fields\": [\n                                      {\n                                        \"key\": \"paths\",\n                                        \"value\": {\n                                          \"type\": \"array\",\n                                          \"elems\": {\n                                            \"type\": \"string\",\n                                            \"doc\": \"The API paths allowed. Paths are simple prefix matches.\\nAny subpath of the specified paths will be allowed.\"\n                                          }\n                                        },\n                                        \"doc\": \"The API paths allowed. Paths are simple prefix matches.\\nAny subpath of the specified paths will be allowed.\"\n                                      },\n                                      {\n                                        \"key\": \"methods\",\n                                        \"value\": {\n                                          \"type\": \"array\",\n                                          \"elems\": {\n                                            \"type\": \"string\",\n                                            \"doc\": \"The HTTP methods allowed for the given paths.\"\n                                          }\n                                        },\n                                        \"doc\": \"The HTTP methods allowed for the given paths.\"\n                                      }\n                                    ],\n                                    \"doc\": \"Limits what the associated identities are allowed to do.\\nIf unspecified, all permissions are granted.\\n\\n\\nAdminPermissions specifies what kinds of requests are allowed\\nto be made to the admin endpoint.\"\n                                  }\n                                },\n                                \"doc\": \"Limits what the associated identities are allowed to do.\\nIf unspecified, all permissions are granted.\\n\\n\\nAdminPermissions specifies what kinds of requests are allowed\\nto be made to the admin endpoint.\"\n                              }\n                            ],\n                            \"doc\": \"List of access controls for this secure admin endpoint.\\nThis configures TLS mutual authentication (i.e. authorized\\nclient certificates), but also application-layer permissions\\nlike which paths and methods each identity is authorized for.\\n\\n\\nAdminAccess specifies what permissions an identity or group\\nof identities are granted.\"\n                          }\n                        },\n                        \"doc\": \"List of access controls for this secure admin endpoint.\\nThis configures TLS mutual authentication (i.e. authorized\\nclient certificates), but also application-layer permissions\\nlike which paths and methods each identity is authorized for.\\n\\n\\nAdminAccess specifies what permissions an identity or group\\nof identities are granted.\"\n                      }\n                    ],\n                    \"doc\": \"Options pertaining to remote administration. By default, remote\\nadministration is disabled. If enabled, identity management must\\nalso be configured, as that is how the endpoint is secured.\\nSee the neighboring \\\"identity\\\" object.\\n\\nEXPERIMENTAL: This feature is subject to change.\\n\\n\\nRemoteAdmin enables and configures remote administration. If enabled,\\na secure listener enforcing mutual TLS authentication will be started\\non a different port from the standard plaintext admin server.\\n\\nThis endpoint is secured using identity management, which must be\\nconfigured separately (because identity management does not depend\\non remote administration). See the admin/identity config struct.\\n\\nEXPERIMENTAL: Subject to change.\"\n                  },\n                  \"doc\": \"Options pertaining to remote administration. By default, remote\\nadministration is disabled. If enabled, identity management must\\nalso be configured, as that is how the endpoint is secured.\\nSee the neighboring \\\"identity\\\" object.\\n\\nEXPERIMENTAL: This feature is subject to change.\\n\\n\\nRemoteAdmin enables and configures remote administration. If enabled,\\na secure listener enforcing mutual TLS authentication will be started\\non a different port from the standard plaintext admin server.\\n\\nThis endpoint is secured using identity management, which must be\\nconfigured separately (because identity management does not depend\\non remote administration). See the admin/identity config struct.\\n\\nEXPERIMENTAL: Subject to change.\"\n                }\n              ],\n              \"doc\": \"AdminConfig configures Caddy's API endpoint, which is used\\nto manage Caddy while it is running.\\n\"\n            },\n            \"doc\": \"AdminConfig configures Caddy's API endpoint, which is used\\nto manage Caddy while it is running.\\n\"\n          },\n          {\n            \"key\": \"logging\",\n            \"value\": {\n              \"type\": \"struct\",\n              \"type_name\": \"github.com/caddyserver/caddy/v2.Logging\",\n              \"struct_fields\": [\n                {\n                  \"key\": \"sink\",\n                  \"value\": {\n                    \"type\": \"struct\",\n                    \"type_name\": \"github.com/caddyserver/caddy/v2.StandardLibLog\",\n                    \"struct_fields\": [\n                      {\n                        \"key\": \"writer\",\n                        \"value\": {\n                          \"type\": \"module\",\n                          \"doc\": \"The module that writes out log entries for the sink.\",\n                          \"module_namespace\": \"caddy.logging.writers\",\n                          \"module_inline_key\": \"output\"\n                        },\n                        \"doc\": \"The module that writes out log entries for the sink.\"\n                      }\n                    ],\n                    \"doc\": \"Sink is the destination for all unstructured logs emitted\\nfrom Go's standard library logger. These logs are common\\nin dependencies that are not designed specifically for use\\nin Caddy. Because it is global and unstructured, the sink\\nlacks most advanced features and customizations.\\n\\n\\nStandardLibLog configures the default Go standard library\\nglobal logger in the log package. This is necessary because\\nmodule dependencies which are not built specifically for\\nCaddy will use the standard logger. This is also known as\\nthe \\\"sink\\\" logger.\"\n                  },\n                  \"doc\": \"Sink is the destination for all unstructured logs emitted\\nfrom Go's standard library logger. These logs are common\\nin dependencies that are not designed specifically for use\\nin Caddy. Because it is global and unstructured, the sink\\nlacks most advanced features and customizations.\\n\\n\\nStandardLibLog configures the default Go standard library\\nglobal logger in the log package. This is necessary because\\nmodule dependencies which are not built specifically for\\nCaddy will use the standard logger. This is also known as\\nthe \\\"sink\\\" logger.\"\n                },\n                {\n                  \"key\": \"logs\",\n                  \"value\": {\n                    \"type\": \"map\",\n                    \"map_keys\": {\n                      \"type\": \"string\"\n                    },\n                    \"elems\": {\n                      \"type\": \"struct\",\n                      \"type_name\": \"github.com/caddyserver/caddy/v2.CustomLog\",\n                      \"struct_fields\": [\n                        {\n                          \"key\": \"writer\",\n                          \"value\": {\n                            \"type\": \"module\",\n                            \"doc\": \"The writer defines where log entries are emitted.\",\n                            \"module_namespace\": \"caddy.logging.writers\",\n                            \"module_inline_key\": \"output\"\n                          },\n                          \"doc\": \"The writer defines where log entries are emitted.\"\n                        },\n                        {\n                          \"key\": \"encoder\",\n                          \"value\": {\n                            \"type\": \"module\",\n                            \"doc\": \"The encoder is how the log entries are formatted or encoded.\",\n                            \"module_namespace\": \"caddy.logging.encoders\",\n                            \"module_inline_key\": \"format\"\n                          },\n                          \"doc\": \"The encoder is how the log entries are formatted or encoded.\"\n                        },\n                        {\n                          \"key\": \"level\",\n                          \"value\": {\n                            \"type\": \"string\",\n                            \"doc\": \"Level is the minimum level to emit, and is inclusive.\\nPossible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL\"\n                          },\n                          \"doc\": \"Level is the minimum level to emit, and is inclusive.\\nPossible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL\"\n                        },\n                        {\n                          \"key\": \"sampling\",\n                          \"value\": {\n                            \"type\": \"struct\",\n                            \"type_name\": \"github.com/caddyserver/caddy/v2.LogSampling\",\n                            \"struct_fields\": [\n                              {\n                                \"key\": \"interval\",\n                                \"value\": {\n                                  \"type\": \"int\",\n                                  \"type_name\": \"time.Duration\",\n                                  \"doc\": \"The window over which to conduct sampling.\\n\\n\\nA Duration represents the elapsed time between two instants\\nas an int64 nanosecond count. The representation limits the\\nlargest representable duration to approximately 290 years.\"\n                                },\n                                \"doc\": \"The window over which to conduct sampling.\\n\\n\\nA Duration represents the elapsed time between two instants\\nas an int64 nanosecond count. The representation limits the\\nlargest representable duration to approximately 290 years.\"\n                              },\n                              {\n                                \"key\": \"first\",\n                                \"value\": {\n                                  \"type\": \"int\",\n                                  \"doc\": \"Log this many entries within a given level and\\nmessage for each interval.\"\n                                },\n                                \"doc\": \"Log this many entries within a given level and\\nmessage for each interval.\"\n                              },\n                              {\n                                \"key\": \"thereafter\",\n                                \"value\": {\n                                  \"type\": \"int\",\n                                  \"doc\": \"If more entries with the same level and message\\nare seen during the same interval, keep one in\\nthis many entries until the end of the interval.\"\n                                },\n                                \"doc\": \"If more entries with the same level and message\\nare seen during the same interval, keep one in\\nthis many entries until the end of the interval.\"\n                              }\n                            ],\n                            \"doc\": \"Sampling configures log entry sampling. If enabled,\\nonly some log entries will be emitted. This is useful\\nfor improving performance on extremely high-pressure\\nservers.\\n\\n\\nLogSampling configures log entry sampling.\"\n                          },\n                          \"doc\": \"Sampling configures log entry sampling. If enabled,\\nonly some log entries will be emitted. This is useful\\nfor improving performance on extremely high-pressure\\nservers.\\n\\n\\nLogSampling configures log entry sampling.\"\n                        },\n                        {\n                          \"key\": \"include\",\n                          \"value\": {\n                            \"type\": \"array\",\n                            \"elems\": {\n                              \"type\": \"string\",\n                              \"doc\": \"Include defines the names of loggers to emit in this\\nlog. For example, to include only logs emitted by the\\nadmin API, you would include \\\"admin.api\\\".\"\n                            }\n                          },\n                          \"doc\": \"Include defines the names of loggers to emit in this\\nlog. For example, to include only logs emitted by the\\nadmin API, you would include \\\"admin.api\\\".\"\n                        },\n                        {\n                          \"key\": \"exclude\",\n                          \"value\": {\n                            \"type\": \"array\",\n                            \"elems\": {\n                              \"type\": \"string\",\n                              \"doc\": \"Exclude defines the names of loggers that should be\\nskipped by this log. For example, to exclude only\\nHTTP access logs, you would exclude \\\"http.log.access\\\".\"\n                            }\n                          },\n                          \"doc\": \"Exclude defines the names of loggers that should be\\nskipped by this log. For example, to exclude only\\nHTTP access logs, you would exclude \\\"http.log.access\\\".\"\n                        }\n                      ],\n                      \"doc\": \"Logs are your logs, keyed by an arbitrary name of your\\nchoosing. The default log can be customized by defining\\na log called \\\"default\\\". You can further define other logs\\nand filter what kinds of entries they accept.\\n\\n\\nCustomLog represents a custom logger configuration.\\n\\nBy default, a log will emit all log entries. Some entries\\nwill be skipped if sampling is enabled. Further, the Include\\nand Exclude parameters define which loggers (by name) are\\nallowed or rejected from emitting in this log. If both Include\\nand Exclude are populated, their values must be mutually\\nexclusive, and longer namespaces have priority. If neither\\nare populated, all logs are emitted.\"\n                    }\n                  },\n                  \"doc\": \"Logs are your logs, keyed by an arbitrary name of your\\nchoosing. The default log can be customized by defining\\na log called \\\"default\\\". You can further define other logs\\nand filter what kinds of entries they accept.\\n\\n\\nCustomLog represents a custom logger configuration.\\n\\nBy default, a log will emit all log entries. Some entries\\nwill be skipped if sampling is enabled. Further, the Include\\nand Exclude parameters define which loggers (by name) are\\nallowed or rejected from emitting in this log. If both Include\\nand Exclude are populated, their values must be mutually\\nexclusive, and longer namespaces have priority. If neither\\nare populated, all logs are emitted.\"\n                }\n              ],\n              \"doc\": \"Logging facilitates logging within Caddy. The default log is\\ncalled \\\"default\\\" and you can customize it. You can also define\\nadditional logs.\\n\\nBy default, all logs at INFO level and higher are written to\\nstandard error (\\\"stderr\\\" writer) in a human-readable format\\n(\\\"console\\\" encoder if stdout is an interactive terminal, \\\"json\\\"\\nencoder otherwise).\\n\\nAll defined logs accept all log entries by default, but you\\ncan filter by level and module/logger names. A logger's name\\nis the same as the module's name, but a module may append to\\nlogger names for more specificity. For example, you can\\nfilter logs emitted only by HTTP handlers using the name\\n\\\"http.handlers\\\", because all HTTP handler module names have\\nthat prefix.\\n\\nCaddy logs (except the sink) are zero-allocation, so they are\\nvery high-performing in terms of memory and CPU time. Enabling\\nsampling can further increase throughput on extremely high-load\\nservers.\\n\"\n            },\n            \"doc\": \"Logging facilitates logging within Caddy. The default log is\\ncalled \\\"default\\\" and you can customize it. You can also define\\nadditional logs.\\n\\nBy default, all logs at INFO level and higher are written to\\nstandard error (\\\"stderr\\\" writer) in a human-readable format\\n(\\\"console\\\" encoder if stdout is an interactive terminal, \\\"json\\\"\\nencoder otherwise).\\n\\nAll defined logs accept all log entries by default, but you\\ncan filter by level and module/logger names. A logger's name\\nis the same as the module's name, but a module may append to\\nlogger names for more specificity. For example, you can\\nfilter logs emitted only by HTTP handlers using the name\\n\\\"http.handlers\\\", because all HTTP handler module names have\\nthat prefix.\\n\\nCaddy logs (except the sink) are zero-allocation, so they are\\nvery high-performing in terms of memory and CPU time. Enabling\\nsampling can further increase throughput on extremely high-load\\nservers.\\n\"\n          },\n          {\n            \"key\": \"storage\",\n            \"value\": {\n              \"type\": \"module\",\n              \"doc\": \"StorageRaw is a storage module that defines how/where Caddy\\nstores assets (such as TLS certificates). The default storage\\nmodule is `caddy.storage.file_system` (the local file system),\\nand the default path\\n[depends on the OS and environment](/docs/conventions#data-directory).\",\n              \"module_namespace\": \"caddy.storage\",\n              \"module_inline_key\": \"module\"\n            },\n            \"doc\": \"StorageRaw is a storage module that defines how/where Caddy\\nstores assets (such as TLS certificates). The default storage\\nmodule is `caddy.storage.file_system` (the local file system),\\nand the default path\\n[depends on the OS and environment](/docs/conventions#data-directory).\"\n          },\n          {\n            \"key\": \"apps\",\n            \"value\": {\n              \"type\": \"module_map\",\n              \"type_name\": \"github.com/caddyserver/caddy/v2.ModuleMap\",\n              \"doc\": \"AppsRaw are the apps that Caddy will load and run. The\\napp module name is the key, and the app's config is the\\nassociated value.\\n\\n\\nModuleMap is a map that can contain multiple modules,\\nwhere the map key is the module's name. (The namespace\\nis usually read from an associated field's struct tag.)\\nBecause the module's name is given as the key in a\\nmodule map, the name does not have to be given in the\\njson.RawMessage.\",\n              \"module_namespace\": \"\"\n            },\n            \"doc\": \"AppsRaw are the apps that Caddy will load and run. The\\napp module name is the key, and the app's config is the\\nassociated value.\\n\\n\\nModuleMap is a map that can contain multiple modules,\\nwhere the map key is the module's name. (The namespace\\nis usually read from an associated field's struct tag.)\\nBecause the module's name is given as the key in a\\nmodule map, the name does not have to be given in the\\njson.RawMessage.\"\n          }\n        ],\n        \"doc\": \"Config is the top (or beginning) of the Caddy configuration structure.\\nCaddy config is expressed natively as a JSON document. If you prefer\\nnot to work with JSON directly, there are [many config adapters](/docs/config-adapters)\\navailable that can convert various inputs into Caddy JSON.\\n\\nMany parts of this config are extensible through the use of Caddy modules.\\nFields which have a json.RawMessage type and which appear as dots (•••) in\\nthe online docs can be fulfilled by modules in a certain module\\nnamespace. The docs show which modules can be used in a given place.\\n\\nWhenever a module is used, its name must be given either inline as part of\\nthe module, or as the key to the module's value. The docs will make it clear\\nwhich to use.\\n\\nGenerally, all config settings are optional, as it is Caddy convention to\\nhave good, documented default values. If a parameter is required, the docs\\nshould say so.\\n\\nGo programs which are directly building a Config struct value should take\\ncare to populate the JSON-encodable fields of the struct (i.e. the fields\\nwith `json` struct tags) if employing the module lifecycle (e.g. Provision\\nmethod calls).\\n\"\n      }\n    },\n    \"repo\": \"https://github.com/caddyserver/caddy\"\n  }\n}"
  },
  {
    "path": "src/api/modules/index.json",
    "content": "{\n  \"status_code\": 200,\n  \"result\": {\n    \"admin.api.load\": [\n      {\n        \"name\": \"admin.api.load\",\n        \"docs\": \"admin.api.load is a module that provides the /load endpoint\\nfor the Caddy admin API. The only reason it's not baked\\ninto the caddy package directly is because of the import\\nof the caddyconfig package for its GetAdapter function.\\nIf the caddy package depends on the caddyconfig package,\\nthen the caddyconfig package will not be able to import\\nthe caddy package, and it can more easily cause backward\\nedges in the dependency tree (i.e. import cycle).\\nFortunately, the admin API has first-class support for\\nadding endpoints from modules.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/caddyconfig\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"admin.api.metrics\": [\n      {\n        \"name\": \"admin.api.metrics\",\n        \"docs\": \"admin.api.metrics is a module that serves a metrics endpoint so that any gathered\\nmetrics can be exposed for scraping. This module is not configurable, and\\nis permanently mounted to the admin API endpoint at \\\"/metrics\\\".\\nSee the Metrics module for a configurable endpoint that is usable if the\\nAdmin API is disabled.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/metrics\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"admin.api.reverse_proxy\": [\n      {\n        \"name\": \"admin.api.reverse_proxy\",\n        \"docs\": \"admin.api.reverse_proxy is a module that provides the\\n/reverse_proxy/upstreams endpoint for the Caddy admin\\nAPI. This allows for checking the health of configured\\nreverse proxy upstreams in the pool.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.config_loaders.http\": [\n      {\n        \"name\": \"caddy.config_loaders.http\",\n        \"docs\": \"caddy.config_loaders.http can load Caddy configs over HTTP(S). It can adapt the config\\nbased on the Content-Type header of the HTTP response.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/caddyconfig\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.listeners.proxy_protocol\": [\n      {\n        \"name\": \"caddy.listeners.proxy_protocol\",\n        \"package\": \"github.com/mastercactapus/caddy2-proxyprotocol\",\n        \"repo\": \"https://github.com/mastercactapus/caddy2-proxyprotocol\"\n      }\n    ],\n    \"caddy.listeners.tls\": [\n      {\n        \"name\": \"caddy.listeners.tls\",\n        \"docs\": \"caddy.listeners.tls is a no-op listener wrapper that marks\\nwhere the TLS listener should be in a chain of listener wrappers.\\nIt should only be used if another listener wrapper must be placed\\nin front of the TLS handshake.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.encoders.console\": [\n      {\n        \"name\": \"caddy.logging.encoders.console\",\n        \"docs\": \"caddy.logging.encoders.console encodes log entries that are mostly human-readable.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.encoders.filter\": [\n      {\n        \"name\": \"caddy.logging.encoders.filter\",\n        \"docs\": \"caddy.logging.encoders.filter can filter (manipulate) fields on\\nlog entries before they are actually encoded by\\nan underlying encoder.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.encoders.filter.basic_auth_user\": [\n      {\n        \"name\": \"caddy.logging.encoders.filter.basic_auth_user\",\n        \"docs\": \"caddy.logging.encoders.filter.basic_auth_user is a Caddy log field filter that replaces the a base64 encoded authorization\\nheader with just the user name.\",\n        \"package\": \"github.com/ueffel/caddy-basic-auth-filter\",\n        \"repo\": \"https://github.com/ueffel/caddy-basic-auth-filter\"\n      }\n    ],\n    \"caddy.logging.encoders.filter.delete\": [\n      {\n        \"name\": \"caddy.logging.encoders.filter.delete\",\n        \"docs\": \"caddy.logging.encoders.filter.delete is a Caddy log field filter that\\ndeletes the field.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.encoders.filter.ip_mask\": [\n      {\n        \"name\": \"caddy.logging.encoders.filter.ip_mask\",\n        \"docs\": \"caddy.logging.encoders.filter.ip_mask is a Caddy log field filter that\\nmasks IP addresses.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.encoders.filter.replace\": [\n      {\n        \"name\": \"caddy.logging.encoders.filter.replace\",\n        \"docs\": \"caddy.logging.encoders.filter.replace is a Caddy log field filter that\\nreplaces the field with the indicated string.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.encoders.filter.tls_cipher\": [\n      {\n        \"name\": \"caddy.logging.encoders.filter.tls_cipher\",\n        \"docs\": \"caddy.logging.encoders.filter.tls_cipher is Caddy log field filter that replaces the numeric TLS cipher_suite value with\\nthe string representation.\",\n        \"package\": \"github.com/ueffel/caddy-tls-format\",\n        \"repo\": \"https://github.com/ueffel/caddy-tls-format\"\n      }\n    ],\n    \"caddy.logging.encoders.filter.tls_version\": [\n      {\n        \"name\": \"caddy.logging.encoders.filter.tls_version\",\n        \"docs\": \"caddy.logging.encoders.filter.tls_version is a Caddy log field filter that replaces the numeric TLS version with the\\nstring version and optionally adds a prefix.\",\n        \"package\": \"github.com/ueffel/caddy-tls-format\",\n        \"repo\": \"https://github.com/ueffel/caddy-tls-format\"\n      }\n    ],\n    \"caddy.logging.encoders.formatted\": [\n      {\n        \"name\": \"caddy.logging.encoders.formatted\",\n        \"docs\": \"caddy.logging.encoders.formatted allows the user to provide custom template for log prints. The\\nencoder builds atop the json encoder, thus it follows its message structure. The placeholders\\nare namespaced by the name of the app logging the message.\",\n        \"package\": \"github.com/caddyserver/format-encoder\",\n        \"repo\": \"https://github.com/caddyserver/format-encoder\"\n      }\n    ],\n    \"caddy.logging.encoders.json\": [\n      {\n        \"name\": \"caddy.logging.encoders.json\",\n        \"docs\": \"caddy.logging.encoders.json encodes entries as JSON.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.encoders.logfmt\": [\n      {\n        \"name\": \"caddy.logging.encoders.logfmt\",\n        \"docs\": \"caddy.logging.encoders.logfmt encodes log entries as logfmt:\\nhttps://www.brandur.org/logfmt\\n\\nNote that logfmt does not encode nested structures\\nproperly, so it is not a good fit for most logs.\\n\\n⚠️ DEPRECATED. Do not use. It will eventually be removed\\nfrom the standard Caddy modules. For more information,\\nsee https://github.com/caddyserver/caddy/issues/3575.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.encoders.single_field\": [\n      {\n        \"name\": \"caddy.logging.encoders.single_field\",\n        \"docs\": \"caddy.logging.encoders.single_field writes a log entry that consists entirely\\nof a single string field in the log entry. This is useful\\nfor custom, self-encoded log entries that consist of a\\nsingle field in the structured log entry.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.writers.discard\": [\n      {\n        \"name\": \"caddy.logging.writers.discard\",\n        \"docs\": \"caddy.logging.writers.discard discards all writes.\",\n        \"package\": \"github.com/caddyserver/caddy/v2\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.writers.file\": [\n      {\n        \"name\": \"caddy.logging.writers.file\",\n        \"docs\": \"caddy.logging.writers.file can write logs to files. By default, log files\\nare rotated (\\\"rolled\\\") when they get large, and old log\\nfiles get deleted, to ensure that the process does not\\nexhaust disk space.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.writers.net\": [\n      {\n        \"name\": \"caddy.logging.writers.net\",\n        \"docs\": \"caddy.logging.writers.net implements a log writer that outputs to a network socket. If\\nthe socket goes down, it will dump logs to stderr while it attempts to\\nreconnect.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/logging\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.writers.stderr\": [\n      {\n        \"name\": \"caddy.logging.writers.stderr\",\n        \"docs\": \"caddy.logging.writers.stderr writes logs to standard error.\",\n        \"package\": \"github.com/caddyserver/caddy/v2\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.logging.writers.stdout\": [\n      {\n        \"name\": \"caddy.logging.writers.stdout\",\n        \"docs\": \"caddy.logging.writers.stdout writes logs to standard out.\",\n        \"package\": \"github.com/caddyserver/caddy/v2\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.storage.consul\": [\n      {\n        \"name\": \"caddy.storage.consul\",\n        \"docs\": \"caddy.storage.consul allows to store certificates and other TLS resources\\nin a shared cluster environment using Consul's key/value-store.\\nIt uses distributed locks to ensure consistency.\",\n        \"package\": \"github.com/pteich/caddy-tlsconsul\",\n        \"repo\": \"https://github.com/pteich/caddy-tlsconsul\"\n      },\n      {\n        \"name\": \"caddy.storage.consul\",\n        \"docs\": \"caddy.storage.consul holds all parameters for the Consul connection\",\n        \"package\": \"github.com/pteich/caddy-tlsconsul\",\n        \"repo\": \"https://github.com/pteich/caddy-tlsconsul\"\n      }\n    ],\n    \"caddy.storage.dynamodb\": [\n      {\n        \"name\": \"caddy.storage.dynamodb\",\n        \"docs\": \"caddy.storage.dynamodb implements certmagic.Storage to facilitate\\nstorage of certificates in DynamoDB for a clustered environment.\\nAlso implements certmagic.Locker to facilitate locking\\nand unlocking of cert data during storage\",\n        \"package\": \"github.com/silinternational/certmagic-storage-dynamodb/v2\",\n        \"repo\": \"https://github.com/silinternational/certmagic-storage-dynamodb\"\n      }\n    ],\n    \"caddy.storage.file_system\": [\n      {\n        \"name\": \"caddy.storage.file_system\",\n        \"docs\": \"caddy.storage.file_system is a certmagic.Storage wrapper for certmagic.FileStorage.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/filestorage\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"caddy.storage.redis\": [\n      {\n        \"name\": \"caddy.storage.redis\",\n        \"docs\": \"caddy.storage.redis contain Redis client, and plugin option\",\n        \"package\": \"github.com/gamalan/caddy-tlsredis\",\n        \"repo\": \"https://github.com/gamalan/caddy-tlsredis\"\n      }\n    ],\n    \"caddy.storage.s3\": [\n      {\n        \"name\": \"caddy.storage.s3\",\n        \"package\": \"github.com/ss098/certmagic-s3\",\n        \"repo\": \"https://github.com/ss098/certmagic-s3\"\n      },\n      {\n        \"name\": \"caddy.storage.s3\",\n        \"package\": \"github.com/techknowlogick/certmagic-s3\",\n        \"repo\": \"https://github.com/techknowlogick/certmagic-s3\"\n      }\n    ],\n    \"crowdsec\": [\n      {\n        \"name\": \"crowdsec\",\n        \"docs\": \"crowdsec is a Caddy App that functions as a CrowdSec bouncer. It acts\\nas a CrowdSec API client as well as a local cache for CrowdSec decisions,\\nwhich can be used by the HTTP handler and Layer4 matcher to decide if\\na request or connection is allowed or not.\",\n        \"package\": \"github.com/hslatman/caddy-crowdsec-bouncer/crowdsec\",\n        \"repo\": \"https://github.com/hslatman/caddy-crowdsec-bouncer\"\n      }\n    ],\n    \"dns.providers.alidns\": [\n      {\n        \"name\": \"dns.providers.alidns\",\n        \"docs\": \"dns.providers.alidns wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/alidns\",\n        \"repo\": \"https://github.com/caddy-dns/alidns\"\n      }\n    ],\n    \"dns.providers.azure\": [\n      {\n        \"name\": \"dns.providers.azure\",\n        \"docs\": \"dns.providers.azure wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/azure\",\n        \"repo\": \"https://github.com/caddy-dns/azure\"\n      }\n    ],\n    \"dns.providers.cloudflare\": [\n      {\n        \"name\": \"dns.providers.cloudflare\",\n        \"docs\": \"dns.providers.cloudflare wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/cloudflare\",\n        \"repo\": \"https://github.com/caddy-dns/cloudflare\"\n      }\n    ],\n    \"dns.providers.digitalocean\": [\n      {\n        \"name\": \"dns.providers.digitalocean\",\n        \"docs\": \"dns.providers.digitalocean wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/digitalocean\",\n        \"repo\": \"https://github.com/caddy-dns/digitalocean\"\n      }\n    ],\n    \"dns.providers.dnspod\": [\n      {\n        \"name\": \"dns.providers.dnspod\",\n        \"docs\": \"dns.providers.dnspod wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/dnspod\",\n        \"repo\": \"https://github.com/caddy-dns/dnspod\"\n      }\n    ],\n    \"dns.providers.duckdns\": [\n      {\n        \"name\": \"dns.providers.duckdns\",\n        \"docs\": \"dns.providers.duckdns wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/duckdns\",\n        \"repo\": \"https://github.com/caddy-dns/duckdns\"\n      }\n    ],\n    \"dns.providers.gandi\": [\n      {\n        \"name\": \"dns.providers.gandi\",\n        \"docs\": \"dns.providers.gandi wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/gandi\",\n        \"repo\": \"https://github.com/caddy-dns/gandi\"\n      }\n    ],\n    \"dns.providers.godaddy\": [\n      {\n        \"name\": \"dns.providers.godaddy\",\n        \"docs\": \"dns.providers.godaddy wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/godaddy\",\n        \"repo\": \"https://github.com/caddy-dns/godaddy\"\n      }\n    ],\n    \"dns.providers.googleclouddns\": [\n      {\n        \"name\": \"dns.providers.googleclouddns\",\n        \"docs\": \"dns.providers.googleclouddns lets Caddy read and manipulate DNS records hosted by this DNS provider.\",\n        \"package\": \"github.com/caddy-dns/googleclouddns\",\n        \"repo\": \"https://github.com/caddy-dns/googleclouddns\"\n      }\n    ],\n    \"dns.providers.hetzner\": [\n      {\n        \"name\": \"dns.providers.hetzner\",\n        \"docs\": \"dns.providers.hetzner wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/hetzner\",\n        \"repo\": \"https://github.com/caddy-dns/hetzner\"\n      }\n    ],\n    \"dns.providers.lego_deprecated\": [\n      {\n        \"name\": \"dns.providers.lego_deprecated\",\n        \"docs\": \"dns.providers.lego_deprecated is a shim module that allows any and all of the\\nDNS providers in go-acme/lego to be used with Caddy. They must\\nbe configured via environment variables, they do not support\\ncancellation in the case of frequent config changes.\\n\\nEven though this module is in the dns.providers namespace, it\\nis only a special case for solving ACME challenges, intended to\\nreplace the modules that used to be in the now-defunct tls.dns\\nnamespace. Using it in other places of the Caddy config will\\nresult in errors.\\n\\nThis module will eventually go away in favor of the modules that\\nmake use of the libdns APIs: https://github.com/libdns\",\n        \"package\": \"github.com/caddy-dns/lego-deprecated\",\n        \"repo\": \"https://github.com/caddy-dns/lego-deprecated\"\n      }\n    ],\n    \"dns.providers.netcup\": [\n      {\n        \"name\": \"dns.providers.netcup\",\n        \"docs\": \"dns.providers.netcup lets Caddy read and manipulate DNS records hosted by this DNS provider.\",\n        \"package\": \"github.com/caddy-dns/netcup\",\n        \"repo\": \"https://github.com/caddy-dns/netcup\"\n      }\n    ],\n    \"dns.providers.openstack-designate\": [\n      {\n        \"name\": \"dns.providers.openstack-designate\",\n        \"docs\": \"dns.providers.openstack-designate wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/openstack-designate\",\n        \"repo\": \"https://github.com/caddy-dns/openstack-designate\"\n      }\n    ],\n    \"dns.providers.route53\": [\n      {\n        \"name\": \"dns.providers.route53\",\n        \"docs\": \"dns.providers.route53 wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/route53\",\n        \"repo\": \"https://github.com/caddy-dns/route53\"\n      }\n    ],\n    \"dns.providers.vultr\": [\n      {\n        \"name\": \"dns.providers.vultr\",\n        \"docs\": \"dns.providers.vultr wraps the provider implementation as a Caddy module.\",\n        \"package\": \"github.com/caddy-dns/vultr\",\n        \"repo\": \"https://github.com/caddy-dns/vultr\"\n      }\n    ],\n    \"dynamic_dns\": [\n      {\n        \"name\": \"dynamic_dns\",\n        \"docs\": \"dynamic_dns is a Caddy app that keeps your DNS records updated with the public\\nIP address of your instance. It updates A and AAAA records.\",\n        \"package\": \"github.com/mholt/caddy-dynamicdns\",\n        \"repo\": \"https://github.com/mholt/caddy-dynamicdns\"\n      }\n    ],\n    \"dynamic_dns.ip_sources.simple_http\": [\n      {\n        \"name\": \"dynamic_dns.ip_sources.simple_http\",\n        \"docs\": \"dynamic_dns.ip_sources.simple_http is an IP source that looks up the public IP addresses by\\nmaking HTTP(S) requests to the specified endpoints; it will try each\\nendpoint with IPv4 and IPv6 until at least one returns a valid value.\\nIt is OK if an endpoint doesn't support both IP versions; returning\\na single valid IP address is sufficient.\\n\\nThe endpoints must return HTTP status 200 and the response body must\\ncontain only the IP address in plain text.\",\n        \"package\": \"github.com/mholt/caddy-dynamicdns\",\n        \"repo\": \"https://github.com/mholt/caddy-dynamicdns\"\n      }\n    ],\n    \"dynamic_dns.ip_sources.upnp\": [\n      {\n        \"name\": \"dynamic_dns.ip_sources.upnp\",\n        \"docs\": \"dynamic_dns.ip_sources.upnp gets the IP address from UPnP device.\",\n        \"package\": \"github.com/mholt/caddy-dynamicdns\",\n        \"repo\": \"https://github.com/mholt/caddy-dynamicdns\"\n      }\n    ],\n    \"exec\": [\n      {\n        \"name\": \"exec\",\n        \"docs\": \"exec is top level module that runs shell commands.\",\n        \"package\": \"github.com/abiosoft/caddy-exec\",\n        \"repo\": \"https://github.com/abiosoft/caddy-exec\"\n      }\n    ],\n    \"http\": [\n      {\n        \"name\": \"http\",\n        \"docs\": \"http is a robust, production-ready HTTP server.\\n\\nHTTPS is enabled by default if host matchers with qualifying names are used\\nin any of routes; certificates are automatically provisioned and renewed.\\nAdditionally, automatic HTTPS will also enable HTTPS for servers that listen\\nonly on the HTTPS port but which do not have any TLS connection policies\\ndefined by adding a good, default TLS connection policy.\\n\\nIn HTTP routes, additional placeholders are available (replace any `*`):\\n\\nPlaceholder | Description\\n------------|---------------\\n`{http.request.body}` | The request body (⚠️ inefficient; use only for debugging)\\n`{http.request.cookie.*}` | HTTP request cookie\\n`{http.request.duration}` | Time up to now spent handling the request (after decoding headers from client)\\n`{http.request.header.*}` | Specific request header field\\n`{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo\\n`{http.request.host}` | The host part of the request's Host header\\n`{http.request.hostport}` | The host and port from the request's Host header\\n`{http.request.method}` | The request method\\n`{http.request.orig_method}` | The request's original method\\n`{http.request.orig_uri.path.dir}` | The request's original directory\\n`{http.request.orig_uri.path.file}` | The request's original filename\\n`{http.request.orig_uri.path}` | The request's original path\\n`{http.request.orig_uri.query}` | The request's original query string (without `?`)\\n`{http.request.orig_uri}` | The request's original URI\\n`{http.request.port}` | The port part of the request's Host header\\n`{http.request.proto}` | The protocol of the request\\n`{http.request.remote.host}` | The host part of the remote client's address\\n`{http.request.remote.port}` | The port part of the remote client's address\\n`{http.request.remote}` | The address of the remote client\\n`{http.request.scheme}` | The request scheme\\n`{http.request.tls.version}` | The TLS version name\\n`{http.request.tls.cipher_suite}` | The TLS cipher suite\\n`{http.request.tls.resumed}` | The TLS connection resumed a previous connection\\n`{http.request.tls.proto}` | The negotiated next protocol\\n`{http.request.tls.proto_mutual}` | The negotiated next protocol was advertised by the server\\n`{http.request.tls.server_name}` | The server name requested by the client, if any\\n`{http.request.tls.client.fingerprint}` | The SHA256 checksum of the client certificate\\n`{http.request.tls.client.public_key}` | The public key of the client certificate.\\n`{http.request.tls.client.public_key_sha256}` | The SHA256 checksum of the client's public key.\\n`{http.request.tls.client.certificate_pem}` | The PEM-encoded value of the certificate.\\n`{http.request.tls.client.certificate_der_base64}` | The base64-encoded value of the certificate.\\n`{http.request.tls.client.issuer}` | The issuer DN of the client certificate\\n`{http.request.tls.client.serial}` | The serial number of the client certificate\\n`{http.request.tls.client.subject}` | The subject DN of the client certificate\\n`{http.request.tls.client.san.dns_names.*}` | SAN DNS names(index optional)\\n`{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional)\\n`{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional)\\n`{http.request.tls.client.san.uris.*}` | SAN URIs (index optional)\\n`{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left)\\n`{http.request.uri.path.dir}` | The directory, excluding leaf filename\\n`{http.request.uri.path.file}` | The filename of the path, excluding directory\\n`{http.request.uri.path}` | The path component of the request URI\\n`{http.request.uri.query.*}` | Individual query string value\\n`{http.request.uri.query}` | The query string (without `?`)\\n`{http.request.uri}` | The full request URI\\n`{http.response.header.*}` | Specific response header field\\n`{http.vars.*}` | Custom variables in the HTTP handler chain\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.authentication.hashes.bcrypt\": [\n      {\n        \"name\": \"http.authentication.hashes.bcrypt\",\n        \"docs\": \"http.authentication.hashes.bcrypt implements the bcrypt hash.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.authentication.hashes.scrypt\": [\n      {\n        \"name\": \"http.authentication.hashes.scrypt\",\n        \"docs\": \"http.authentication.hashes.scrypt implements the scrypt KDF as a hash.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.authentication.providers.authorize\": [\n      {\n        \"name\": \"http.authentication.providers.authorize\",\n        \"docs\": \"http.authentication.providers.authorize authorizes access to endpoints based on\\nthe presense and content of JWT token.\",\n        \"package\": \"github.com/greenpau/caddy-authorize\",\n        \"repo\": \"https://github.com/greenpau/caddy-authorize\"\n      }\n    ],\n    \"http.authentication.providers.authorizer\": [\n      {\n        \"name\": \"http.authentication.providers.authorizer\",\n        \"docs\": \"http.authentication.providers.authorizer authorizes access to endpoints based on\\nthe presense and content of JWT token.\",\n        \"package\": \"github.com/greenpau/caddy-security\",\n        \"repo\": \"https://github.com/greenpau/caddy-security\"\n      }\n    ],\n    \"http.authentication.providers.forms\": [\n      {\n        \"name\": \"http.authentication.providers.forms\",\n        \"docs\": \"http.authentication.providers.forms authorizes access to endpoints based on\\nthe credentials provided in a request.\",\n        \"package\": \"github.com/greenpau/caddy-auth-forms\",\n        \"repo\": \"https://github.com/greenpau/caddy-auth-forms\"\n      }\n    ],\n    \"http.authentication.providers.http_basic\": [\n      {\n        \"name\": \"http.authentication.providers.http_basic\",\n        \"docs\": \"http.authentication.providers.http_basic facilitates HTTP basic authentication.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.authentication.providers.jwt\": [\n      {\n        \"name\": \"http.authentication.providers.jwt\",\n        \"docs\": \"http.authentication.providers.jwt facilitates JWT (JSON Web Token) authentication.\",\n        \"package\": \"github.com/ggicci/caddy-jwt\",\n        \"repo\": \"https://github.com/ggicci/caddy-jwt\"\n      },\n      {\n        \"name\": \"http.authentication.providers.jwt\",\n        \"docs\": \"http.authentication.providers.jwt authorizes access to endpoints based on\\nthe presense and content of JWT token.\",\n        \"package\": \"github.com/greenpau/caddy-auth-jwt\",\n        \"repo\": \"https://github.com/greenpau/caddy-auth-jwt\"\n      }\n    ],\n    \"http.authentication.providers.saml\": [\n      {\n        \"name\": \"http.authentication.providers.saml\",\n        \"docs\": \"http.authentication.providers.saml authenticates requests the SAML Response to the SP Assertion\\nConsumer Service using the HTTP-POST Binding.\",\n        \"package\": \"github.com/greenpau/caddy-auth-saml\",\n        \"repo\": \"https://github.com/greenpau/caddy-auth-saml\"\n      }\n    ],\n    \"http.encoders.br\": [\n      {\n        \"name\": \"http.encoders.br\",\n        \"docs\": \"http.encoders.br can create brotli encoders.\",\n        \"package\": \"github.com/ueffel/caddy-brotli\",\n        \"repo\": \"https://github.com/ueffel/caddy-brotli\"\n      }\n    ],\n    \"http.encoders.gzip\": [\n      {\n        \"name\": \"http.encoders.gzip\",\n        \"docs\": \"http.encoders.gzip can create gzip encoders.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/gzip\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.encoders.zstd\": [\n      {\n        \"name\": \"http.encoders.zstd\",\n        \"docs\": \"http.encoders.zstd can create Zstandard encoders.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/zstd\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.acme_server\": [\n      {\n        \"name\": \"http.handlers.acme_server\",\n        \"docs\": \"http.handlers.acme_server is an ACME server handler.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddypki/acmeserver\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.authelia\": [\n      {\n        \"name\": \"http.handlers.authelia\",\n        \"docs\": \"http.handlers.authelia implements a plugin for securing routes with authentication\",\n        \"package\": \"github.com/HeavenVolkoff/caddy-authelia/plugin\",\n        \"repo\": \"https://github.com/HeavenVolkoff/caddy-authelia\"\n      }\n    ],\n    \"http.handlers.authentication\": [\n      {\n        \"name\": \"http.handlers.authentication\",\n        \"docs\": \"http.handlers.authentication is a middleware which provides user authentication.\\nRejects requests with HTTP 401 if the request is not authenticated.\\n\\nAfter a successful authentication, the placeholder\\n`{http.auth.user.id}` will be set to the username, and also\\n`{http.auth.user.*}` placeholders may be set for any authentication\\nmodules that provide user metadata.\\n\\nIts API is still experimental and may be subject to change.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.authenticator\": [\n      {\n        \"name\": \"http.handlers.authenticator\",\n        \"docs\": \"http.handlers.authenticator implements Form-Based, Basic, Local, LDAP,\\nOpenID Connect, OAuth 2.0, SAML Authentication.\",\n        \"package\": \"github.com/greenpau/caddy-security\",\n        \"repo\": \"https://github.com/greenpau/caddy-security\"\n      }\n    ],\n    \"http.handlers.authp\": [\n      {\n        \"name\": \"http.handlers.authp\",\n        \"docs\": \"http.handlers.authp implements Form-Based, Basic, Local, LDAP,\\nOpenID Connect, OAuth 2.0, SAML Authentication.\",\n        \"package\": \"github.com/greenpau/caddy-auth-portal\",\n        \"repo\": \"https://github.com/greenpau/caddy-auth-portal\"\n      }\n    ],\n    \"http.handlers.authz\": [\n      {\n        \"name\": \"http.handlers.authz\",\n        \"package\": \"github.com/casbin/caddy-authz/v2\",\n        \"repo\": \"https://github.com/casbin/caddy-authz\"\n      }\n    ],\n    \"http.handlers.cgi\": [\n      {\n        \"name\": \"http.handlers.cgi\",\n        \"package\": \"github.com/aksdb/caddy-cgi/v2\",\n        \"repo\": \"https://github.com/aksdb/caddy-cgi\"\n      }\n    ],\n    \"http.handlers.crowdsec\": [\n      {\n        \"name\": \"http.handlers.crowdsec\",\n        \"docs\": \"http.handlers.crowdsec matches request IPs to CrowdSec decisions to (dis)allow access\",\n        \"package\": \"github.com/hslatman/caddy-crowdsec-bouncer/http\",\n        \"repo\": \"https://github.com/hslatman/caddy-crowdsec-bouncer\"\n      }\n    ],\n    \"http.handlers.ct\": [\n      {\n        \"name\": \"http.handlers.ct\",\n        \"docs\": \"http.handlers.ct allows to transpile YAML based configuration into a JSON ignition to be used with Flatcar or Fedora CoreOS.\",\n        \"package\": \"github.com/cubic3d/caddy-ct\",\n        \"repo\": \"https://github.com/cubic3d/caddy-ct\"\n      }\n    ],\n    \"http.handlers.encode\": [\n      {\n        \"name\": \"http.handlers.encode\",\n        \"docs\": \"http.handlers.encode is a middleware which can encode responses.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.error\": [\n      {\n        \"name\": \"http.handlers.error\",\n        \"docs\": \"http.handlers.error implements a simple handler that returns an error.\\nThis handler returns an error value, but does not write a response.\\nThis is useful when you want the server to act as if an error\\noccurred; for example, to invoke your custom error handling logic.\\n\\nSince this handler does not write a response, the error information\\nis for use by the server to know how to handle the error.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.exec\": [\n      {\n        \"name\": \"http.handlers.exec\",\n        \"docs\": \"http.handlers.exec implements an HTTP handler that runs shell command.\",\n        \"package\": \"github.com/abiosoft/caddy-exec\",\n        \"repo\": \"https://github.com/abiosoft/caddy-exec\"\n      }\n    ],\n    \"http.handlers.file_server\": [\n      {\n        \"name\": \"http.handlers.file_server\",\n        \"docs\": \"http.handlers.file_server implements a static file server responder for Caddy.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.filter\": [\n      {\n        \"name\": \"http.handlers.filter\",\n        \"docs\": \"http.handlers.filter implements an HTTP handler that writes the\\nvisitor's IP address to a file or stream.\",\n        \"package\": \"github.com/sjtug/caddy2-filter\",\n        \"repo\": \"https://github.com/sjtug/caddy2-filter\"\n      }\n    ],\n    \"http.handlers.geofence\": [\n      {\n        \"name\": \"http.handlers.geofence\",\n        \"docs\": \"http.handlers.geofence implements IP geofencing functionality. https://github.com/circa10a/caddy-geofence\",\n        \"package\": \"github.com/circa10a/caddy-geofence\",\n        \"repo\": \"https://github.com/circa10a/caddy-geofence\"\n      }\n    ],\n    \"http.handlers.git\": [\n      {\n        \"name\": \"http.handlers.git\",\n        \"docs\": \"http.handlers.git implements git repository manager.\",\n        \"package\": \"github.com/greenpau/caddy-git\",\n        \"repo\": \"https://github.com/greenpau/caddy-git\"\n      }\n    ],\n    \"http.handlers.gopkg\": [\n      {\n        \"name\": \"http.handlers.gopkg\",\n        \"docs\": \"http.handlers.gopkg implements vanity go package import paths.\\n\\nVanity go package import paths give a cleaner appearance to go projects by separating the source code location from\\nthe import path. It also gives flexibility to developers by allowing them to change a project's source code hosting\\nplatform without requiring the project to be renamed. Finally, it allows projects hosted on various platforms to be\\ngrouped under a common import path.\",\n        \"package\": \"magnax.ca/caddy/gopkg\",\n        \"repo\": \"https://github.com/MagnaXSoftware/gopkg\"\n      },\n      {\n        \"name\": \"http.handlers.gopkg\",\n        \"docs\": \"http.handlers.gopkg represents the GoPkg Caddy module.\",\n        \"package\": \"magnax.ca/caddy/gopkg\",\n        \"repo\": \"https://github.com/MagnaXSoftware/gopkg\"\n      }\n    ],\n    \"http.handlers.headers\": [\n      {\n        \"name\": \"http.handlers.headers\",\n        \"docs\": \"http.handlers.headers is a middleware which modifies request and response headers.\\n\\nChanges to headers are applied immediately, except for the response\\nheaders when Deferred is true or when Required is set. In those cases,\\nthe changes are applied when the headers are written to the response.\\nNote that deferred changes do not take effect if an error occurs later\\nin the middleware chain.\\n\\nProperties in this module accept placeholders.\\n\\nResponse header operations can be conditioned upon response status code\\nand/or other header values.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.hmac\": [\n      {\n        \"name\": \"http.handlers.hmac\",\n        \"docs\": \"http.handlers.hmac implements an HTTP handler that\\nvalidates request body with hmac.\",\n        \"package\": \"github.com/abiosoft/caddy-hmac\",\n        \"repo\": \"https://github.com/abiosoft/caddy-hmac\"\n      }\n    ],\n    \"http.handlers.image_filter\": [\n      {\n        \"name\": \"http.handlers.image_filter\",\n        \"docs\": \"http.handlers.image_filter is a caddy module that can apply image filters to images from the filesystem at\\nruntime. It should be used together with a cache module, so filters don't have to be applied\\nrepeatedly because it's an expensive operation.\",\n        \"package\": \"github.com/ueffel/caddy-imagefilter\",\n        \"repo\": \"https://github.com/ueffel/caddy-imagefilter\"\n      }\n    ],\n    \"http.handlers.json_parse\": [\n      {\n        \"name\": \"http.handlers.json_parse\",\n        \"docs\": \"http.handlers.json_parse implements an HTTP handler that parses\\njson body as placeholders.\",\n        \"package\": \"github.com/abiosoft/caddy-json-parse\",\n        \"repo\": \"https://github.com/abiosoft/caddy-json-parse\"\n      }\n    ],\n    \"http.handlers.map\": [\n      {\n        \"name\": \"http.handlers.map\",\n        \"docs\": \"http.handlers.map implements a middleware that maps inputs to outputs. Specifically, it\\ncompares a source value against the map inputs, and for one that matches, it\\napplies the output values to each destination. Destinations become placeholder\\nnames.\\n\\nMapped placeholders are not evaluated until they are used, so even for very\\nlarge mappings, this handler is quite efficient.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/map\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.mercure\": [\n      {\n        \"name\": \"http.handlers.mercure\",\n        \"docs\": \"http.handlers.mercure implements a Mercure hub as a Caddy module. Mercure is a protocol allowing to push data updates to web browsers and other HTTP clients in a convenient, fast, reliable and battery-efficient way.\",\n        \"package\": \"github.com/dunglas/mercure/caddy\",\n        \"repo\": \"https://github.com/dunglas/mercure\"\n      }\n    ],\n    \"http.handlers.metrics\": [\n      {\n        \"name\": \"http.handlers.metrics\",\n        \"docs\": \"http.handlers.metrics is a module that serves a /metrics endpoint so that any gathered\\nmetrics can be exposed for scraping. This module is configurable by end-users\\nunlike AdminMetrics.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/metrics\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.openapi\": [\n      {\n        \"name\": \"http.handlers.openapi\",\n        \"package\": \"github.com/chukmunnlee/caddy-openapi\",\n        \"repo\": \"https://github.com/chukmunnlee/caddy-openapi\"\n      }\n    ],\n    \"http.handlers.openapi_validator\": [\n      {\n        \"name\": \"http.handlers.openapi_validator\",\n        \"docs\": \"http.handlers.openapi_validator is used to validate OpenAPI requests and responses against an OpenAPI specification\",\n        \"package\": \"github.com/hslatman/caddy-openapi-validator\",\n        \"repo\": \"https://github.com/hslatman/caddy-openapi-validator\"\n      }\n    ],\n    \"http.handlers.pirsch\": [\n      {\n        \"name\": \"http.handlers.pirsch\",\n        \"package\": \"github.com/muety/caddy-pirsch-plugin\",\n        \"repo\": \"https://github.com/muety/caddy-pirsch-plugin\"\n      }\n    ],\n    \"http.handlers.prometheus\": [\n      {\n        \"name\": \"http.handlers.prometheus\",\n        \"docs\": \"http.handlers.prometheus -\",\n        \"package\": \"github.com/hairyhenderson/caddyprom\",\n        \"repo\": \"https://github.com/hairyhenderson/caddyprom\"\n      }\n    ],\n    \"http.handlers.push\": [\n      {\n        \"name\": \"http.handlers.push\",\n        \"docs\": \"http.handlers.push is a middleware for manipulating the request body.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/push\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.quantity_limiter\": [\n      {\n        \"name\": \"http.handlers.quantity_limiter\",\n        \"docs\": \"http.handlers.quantity_limiter limits the number of successful requests for a token and allows the counter to be reset.\",\n        \"package\": \"github.com/cubic3d/caddy-quantity-limiter\",\n        \"repo\": \"https://github.com/cubic3d/caddy-quantity-limiter\"\n      }\n    ],\n    \"http.handlers.rate_limit\": [\n      {\n        \"name\": \"http.handlers.rate_limit\",\n        \"docs\": \"http.handlers.rate_limit implements a handler for rate-limiting.\\n\\nIf a client exceeds the rate limit, an HTTP error with status `\\u003creject_status\\u003e` will\\nbe returned. This error can be handled using the conventional error handlers.\\nSee [handle_errors](https://caddyserver.com/docs/caddyfile/directives/handle_errors)\\nfor how to set up error handlers.\",\n        \"package\": \"github.com/RussellLuo/caddy-ext/ratelimit\",\n        \"repo\": \"https://github.com/RussellLuo/caddy-ext\"\n      },\n      {\n        \"name\": \"http.handlers.rate_limit\",\n        \"docs\": \"http.handlers.rate_limit implements rate limiting functionality.\\n\\nIf a rate limit is exceeded, an HTTP error with status 429 will be\\nreturned. This error can be handled using the conventional error\\nhandling routes in your config. An additional placeholder is made\\navailable, called `{http.rate_limit.exceeded.name}`, which you can\\nuse for logging or handling; it contains the name of the rate limit\\nzone which limit was exceeded.\",\n        \"package\": \"github.com/mholt/caddy-ratelimit\",\n        \"repo\": \"https://github.com/mholt/caddy-ratelimit\"\n      }\n    ],\n    \"http.handlers.realip\": [\n      {\n        \"name\": \"http.handlers.realip\",\n        \"package\": \"github.com/kirsch33/realip\",\n        \"repo\": \"https://github.com/kirsch33/realip\"\n      }\n    ],\n    \"http.handlers.replace_response\": [\n      {\n        \"name\": \"http.handlers.replace_response\",\n        \"docs\": \"http.handlers.replace_response manipulates response bodies by performing\\nsubstring or regex replacements.\",\n        \"package\": \"github.com/caddyserver/replace-response\",\n        \"repo\": \"https://github.com/caddyserver/replace-response\"\n      }\n    ],\n    \"http.handlers.request_body\": [\n      {\n        \"name\": \"http.handlers.request_body\",\n        \"docs\": \"http.handlers.request_body is a middleware for manipulating the request body.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/requestbody\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.request_debug\": [\n      {\n        \"name\": \"http.handlers.request_debug\",\n        \"docs\": \"http.handlers.request_debug is a middleware which displays the content of the request it\\nhandles. It helps troubleshooting web requests by exposing headers\\n(e.g. cookies), URL parameters, etc.\",\n        \"package\": \"github.com/greenpau/caddy-request-debug\",\n        \"repo\": \"https://github.com/greenpau/caddy-request-debug\"\n      }\n    ],\n    \"http.handlers.request_id\": [\n      {\n        \"name\": \"http.handlers.request_id\",\n        \"docs\": \"http.handlers.request_id implements an HTTP handler that writes a\\nunique request ID to response headers.\",\n        \"package\": \"github.com/lolPants/caddy-requestid\",\n        \"repo\": \"https://github.com/lolPants/caddy-requestid\"\n      }\n    ],\n    \"http.handlers.reverse_proxy\": [\n      {\n        \"name\": \"http.handlers.reverse_proxy\",\n        \"docs\": \"http.handlers.reverse_proxy implements a highly configurable and production-ready reverse proxy.\\n\\nUpon proxying, this module sets the following placeholders (which can be used\\nboth within and after this handler; for example, in response headers):\\n\\nPlaceholder | Description\\n------------|-------------\\n`{http.reverse_proxy.upstream.address}` | The full address to the upstream as given in the config\\n`{http.reverse_proxy.upstream.hostport}` | The host:port of the upstream\\n`{http.reverse_proxy.upstream.host}` | The host of the upstream\\n`{http.reverse_proxy.upstream.port}` | The port of the upstream\\n`{http.reverse_proxy.upstream.requests}` | The approximate current number of requests to the upstream\\n`{http.reverse_proxy.upstream.max_requests}` | The maximum approximate number of requests allowed to the upstream\\n`{http.reverse_proxy.upstream.fails}` | The number of recent failed requests to the upstream\\n`{http.reverse_proxy.upstream.latency}` | How long it took the proxy upstream to write the response header.\\n`{http.reverse_proxy.upstream.duration}` | Time spent proxying to the upstream, including writing response body to client.\\n`{http.reverse_proxy.duration}` | Total time spent proxying, including selecting an upstream, retries, and writing response.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.rewrite\": [\n      {\n        \"name\": \"http.handlers.rewrite\",\n        \"docs\": \"http.handlers.rewrite is a middleware which can rewrite HTTP requests.\\n\\nThe Method and URI properties are \\\"setters\\\": the request URI\\nwill be set to the given values. Other properties are \\\"modifiers\\\":\\nthey modify existing files but do not explicitly specify what the\\nresult will be. It is atypical to combine the use of setters and\\nmodifiers in a single rewrite.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.s3proxy\": [\n      {\n        \"name\": \"http.handlers.s3proxy\",\n        \"docs\": \"http.handlers.s3proxy implements a proxy to return, set, delete or browse objects from S3\",\n        \"package\": \"github.com/lindenlab/caddy-s3-proxy\",\n        \"repo\": \"https://github.com/lindenlab/caddy-s3-proxy\"\n      }\n    ],\n    \"http.handlers.static_response\": [\n      {\n        \"name\": \"http.handlers.static_response\",\n        \"docs\": \"http.handlers.static_response implements a simple responder for static responses.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.subroute\": [\n      {\n        \"name\": \"http.handlers.subroute\",\n        \"docs\": \"http.handlers.subroute implements a handler that compiles and executes routes.\\nThis is useful for a batch of routes that all inherit the same\\nmatchers, or for multiple routes that should be treated as a\\nsingle route.\\n\\nYou can also use subroutes to handle errors from its handlers.\\nFirst the primary routes will be executed, and if they return an\\nerror, the errors routes will be executed; in that case, an error\\nis only returned to the entry point at the server if there is an\\nadditional error returned from the errors routes.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.teapot\": [\n      {\n        \"name\": \"http.handlers.teapot\",\n        \"docs\": \"http.handlers.teapot implements a static \\\"418 I'm a teapot\\\" response to all requests on the route\",\n        \"package\": \"github.com/hairyhenderson/caddy-teapot-module\",\n        \"repo\": \"https://github.com/hairyhenderson/caddy-teapot-module\"\n      }\n    ],\n    \"http.handlers.templates\": [\n      {\n        \"name\": \"http.handlers.templates\",\n        \"docs\": \"http.handlers.templates is a middleware which executes response bodies as Go templates.\\nThe syntax is documented in the Go standard library's\\n[text/template package](https://golang.org/pkg/text/template/).\\n\\n⚠️ Template functions/actions are still experimental, so they are subject to change.\\n\\n[All Sprig functions](https://masterminds.github.io/sprig/) are supported.\\n\\nIn addition to the standard functions and the Sprig library, Caddy adds\\nextra functions and data that are available to a template:\\n\\n##### `.Args`\\n\\nAccess arguments passed to this page/context, for example as the result of a `include`.\\n\\n```\\n{{.Args 0}} // first argument\\n```\\n\\n##### `.Cookie`\\n\\nGets the value of a cookie by name.\\n\\n```\\n{{.Cookie \\\"cookiename\\\"}}\\n```\\n\\n##### `env`\\n\\nGets an environment variable.\\n\\n```\\n{{env \\\"VAR_NAME\\\"}}\\n```\\n\\n##### `placeholder`\\n\\nGets an [placeholder variable](/docs/conventions#placeholders).\\nThe braces (`{}`) have to be omitted.\\n\\n```\\n{{placeholder \\\"http.request.uri.path\\\"}}\\n{{placeholder \\\"http.error.status_code\\\"}}\\n```\\n\\n##### `.Host`\\n\\nReturns the hostname portion (no port) of the Host header of the HTTP request.\\n\\n```\\n{{.Host}}\\n```\\n\\n##### `httpInclude`\\n\\nIncludes the contents of another file by making a virtual HTTP request (also known as a sub-request). The URI path must exist on the same virtual server because the request does not use sockets; instead, the request is crafted in memory and the handler is invoked directly for increased efficiency.\\n\\n```\\n{{httpInclude \\\"/foo/bar?q=val\\\"}}\\n```\\n\\n##### `include`\\n\\nIncludes the contents of another file. Optionally can pass key-value pairs as arguments to be accessed by the included file.\\n\\n```\\n{{include \\\"path/to/file.html\\\"}}  // no arguments\\n{{include \\\"path/to/file.html\\\" \\\"arg1\\\" 2 \\\"value 3\\\"}}  // with arguments\\n```\\n\\n##### `listFiles`\\n\\nReturns a list of the files in the given directory, which is relative to the template context's file root.\\n\\n```\\n{{listFiles \\\"/mydir\\\"}}\\n```\\n\\n##### `markdown`\\n\\nRenders the given Markdown text as HTML.\\n\\n```\\n{{markdown \\\"My _markdown_ text\\\"}}\\n```\\n\\n##### `.RemoteIP`\\n\\nReturns the client's IP address.\\n\\n```\\n{{.RemoteIP}}\\n```\\n\\n##### `.Req`\\n\\nAccesses the current HTTP request, which has various fields, including:\\n\\n   - `.Method` - the method\\n   - `.URL` - the URL, which in turn has component fields (Scheme, Host, Path, etc.)\\n   - `.Header` - the header fields\\n   - `.Host` - the Host or :authority header of the request\\n\\n```\\n{{.Req.Header.Get \\\"User-Agent\\\"}}\\n```\\n\\n##### `.RespHeader.Add`\\n\\nAdds a header field to the HTTP response.\\n\\n```\\n{{.RespHeader.Add \\\"Field-Name\\\" \\\"val\\\"}}\\n```\\n\\n##### `.RespHeader.Del`\\n\\nDeletes a header field on the HTTP response.\\n\\n```\\n{{.RespHeader.Del \\\"Field-Name\\\"}}\\n```\\n\\n##### `.RespHeader.Set`\\n\\nSets a header field on the HTTP response, replacing any existing value.\\n\\n```\\n{{.RespHeader.Set \\\"Field-Name\\\" \\\"val\\\"}}\\n```\\n\\n##### `splitFrontMatter`\\n\\nSplits front matter out from the body. Front matter is metadata that appears at the very beginning of a file or string. Front matter can be in YAML, TOML, or JSON formats:\\n\\n**TOML** front matter starts and ends with `+++`:\\n\\n```\\n+++\\ntemplate = \\\"blog\\\"\\ntitle = \\\"Blog Homepage\\\"\\nsitename = \\\"A Caddy site\\\"\\n+++\\n```\\n\\n**YAML** is surrounded by `---`:\\n\\n```\\n---\\ntemplate: blog\\ntitle: Blog Homepage\\nsitename: A Caddy site\\n---\\n```\\n\\n**JSON** is simply `{` and `}`:\\n\\n```\\n{\\n\\t\\\"template\\\": \\\"blog\\\",\\n\\t\\\"title\\\": \\\"Blog Homepage\\\",\\n\\t\\\"sitename\\\": \\\"A Caddy site\\\"\\n}\\n```\\n\\nThe resulting front matter will be made available like so:\\n\\n- `.Meta` to access the metadata fields, for example: `{{$parsed.Meta.title}}`\\n- `.Body` to access the body after the front matter, for example: `{{markdown $parsed.Body}}`\\n\\n##### `stripHTML`\\n\\nRemoves HTML from a string.\\n\\n```\\n{{stripHTML \\\"Shows \\u003cb\\u003eonly\\u003c/b\\u003e text content\\\"}}\\n```\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/templates\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.trace\": [\n      {\n        \"name\": \"http.handlers.trace\",\n        \"docs\": \"http.handlers.trace is a middleware which displays the content of the request it\\nhandles. It helps troubleshooting web requests by exposing headers\\n(e.g. cookies), URL parameters, etc.\",\n        \"package\": \"github.com/greenpau/caddy-trace\",\n        \"repo\": \"https://github.com/greenpau/caddy-trace\"\n      }\n    ],\n    \"http.handlers.vars\": [\n      {\n        \"name\": \"http.handlers.vars\",\n        \"docs\": \"http.handlers.vars is an HTTP middleware which sets variables to\\nhave values that can be used in the HTTP request handler\\nchain. The primary way to access variables is with placeholders,\\nwhich have the form: `{http.vars.variable_name}`, or with\\nthe `vars` and `vars_regexp` request matchers.\\n\\nThe key is the variable name, and the value is the value of the\\nvariable. Both the name and value may use or contain placeholders.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.handlers.vulcain\": [\n      {\n        \"name\": \"http.handlers.vulcain\",\n        \"package\": \"github.com/dunglas/vulcain/caddy\",\n        \"repo\": \"https://github.com/dunglas/vulcain\"\n      }\n    ],\n    \"http.handlers.webdav\": [\n      {\n        \"name\": \"http.handlers.webdav\",\n        \"docs\": \"http.handlers.webdav implements an HTTP handler for responding to WebDAV clients.\",\n        \"package\": \"github.com/mholt/caddy-webdav\",\n        \"repo\": \"https://github.com/mholt/caddy-webdav\"\n      }\n    ],\n    \"http.handlers.webhook\": [\n      {\n        \"name\": \"http.handlers.webhook\",\n        \"docs\": \"http.handlers.webhook is the module configuration.\",\n        \"package\": \"github.com/WingLim/caddy-webhook\",\n        \"repo\": \"https://github.com/WingLim/caddy-webhook\"\n      }\n    ],\n    \"http.matchers.exec_noop\": [\n      {\n        \"name\": \"http.matchers.exec_noop\",\n        \"docs\": \"http.matchers.exec_noop is a matcher that blocks all requests.\\nIt's primary purpose is to ensure the command is not\\nexecuted when no route/matcher is specified.\\nLimitation of Caddyfile config. JSON/API config do not need this.\",\n        \"package\": \"github.com/abiosoft/caddy-exec\",\n        \"repo\": \"https://github.com/abiosoft/caddy-exec\"\n      }\n    ],\n    \"http.matchers.execnopmatch\": [\n      {\n        \"name\": \"http.matchers.execnopmatch\",\n        \"docs\": \"http.matchers.execnopmatch is a matcher that blocks all request.\\nIt's primary purpose is to ensure the command is not\\nexecuted when no route/matcher is specified.\\nLimitation of Caddyfile config. JSON/API config do not need this.\",\n        \"package\": \"github.com/abiosoft/caddy-exec\",\n        \"repo\": \"https://github.com/abiosoft/caddy-exec\"\n      }\n    ],\n    \"http.matchers.expression\": [\n      {\n        \"name\": \"http.matchers.expression\",\n        \"docs\": \"http.matchers.expression matches requests by evaluating a\\n[CEL](https://github.com/google/cel-spec) expression.\\nThis enables complex logic to be expressed using a comfortable,\\nfamiliar syntax. Please refer to\\n[the standard definitions of CEL functions and operators](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions).\\n\\nThis matcher's JSON interface is actually a string, not a struct.\\nThe generated docs are not correct because this type has custom\\nmarshaling logic.\\n\\nCOMPATIBILITY NOTE: This module is still experimental and is not\\nsubject to Caddy's compatibility guarantee.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.file\": [\n      {\n        \"name\": \"http.matchers.file\",\n        \"docs\": \"http.matchers.file is an HTTP request matcher that can match\\nrequests based upon file existence.\\n\\nUpon matching, three new placeholders will be made\\navailable:\\n\\n- `{http.matchers.file.relative}` The root-relative\\npath of the file. This is often useful when rewriting\\nrequests.\\n- `{http.matchers.file.absolute}` The absolute path\\nof the matched file.\\n- `{http.matchers.file.type}` Set to \\\"directory\\\" if\\nthe matched file is a directory, \\\"file\\\" otherwise.\\n- `{http.matchers.file.remainder}` Set to the remainder\\nof the path if the path was split by `split_path`.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.header\": [\n      {\n        \"name\": \"http.matchers.header\",\n        \"docs\": \"http.matchers.header matches requests by header fields. The key is the field\\nname and the array is the list of field values. It performs fast,\\nexact string comparisons of the field values. Fast prefix, suffix,\\nand substring matches can also be done by suffixing, prefixing, or\\nsurrounding the value with the wildcard `*` character, respectively.\\nIf a list is null, the header must not exist. If the list is empty,\\nthe field must simply exist, regardless of its value.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.header_regexp\": [\n      {\n        \"name\": \"http.matchers.header_regexp\",\n        \"docs\": \"http.matchers.header_regexp matches requests by a regular expression on header fields.\\n\\nUpon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`\\nwhere `name` is the regular expression's name, and `capture_group` is either\\nthe named or positional capture group from the expression itself. If no name\\nis given, then the placeholder omits the name: `{http.regexp.capture_group}`\\n(potentially leading to collisions).\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.host\": [\n      {\n        \"name\": \"http.matchers.host\",\n        \"docs\": \"http.matchers.host matches requests by the Host value (case-insensitive).\\n\\nWhen used in a top-level HTTP route,\\n[qualifying domain names](/docs/automatic-https#hostname-requirements)\\nmay trigger [automatic HTTPS](/docs/automatic-https), which automatically\\nprovisions and renews certificates for you. Before doing this, you\\nshould ensure that DNS records for these domains are properly configured,\\nespecially A/AAAA pointed at your server.\\n\\nAutomatic HTTPS can be\\n[customized or disabled](/docs/modules/http#servers/automatic_https).\\n\\nWildcards (`*`) may be used to represent exactly one label of the\\nhostname, in accordance with RFC 1034 (because host matchers are also\\nused for automatic HTTPS which influences TLS certificates). Thus,\\na host of `*` matches hosts like `localhost` or `internal` but not\\n`example.com`. To catch all hosts, omit the host matcher entirely.\\n\\nThe wildcard can be useful for matching all subdomains, for example:\\n`*.example.com` matches `foo.example.com` but not `foo.bar.example.com`.\\n\\nDuplicate entries will return an error.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.maxmind_geolocation\": [\n      {\n        \"name\": \"http.matchers.maxmind_geolocation\",\n        \"docs\": \"Allows to filter requests based on source IP country.\",\n        \"package\": \"github.com/porech/caddy-maxmind-geolocation\",\n        \"repo\": \"https://github.com/porech/caddy-maxmind-geolocation\"\n      }\n    ],\n    \"http.matchers.method\": [\n      {\n        \"name\": \"http.matchers.method\",\n        \"docs\": \"http.matchers.method matches requests by the method.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.not\": [\n      {\n        \"name\": \"http.matchers.not\",\n        \"docs\": \"http.matchers.not matches requests by negating the results of its matcher\\nsets. A single \\\"not\\\" matcher takes one or more matcher sets. Each\\nmatcher set is OR'ed; in other words, if any matcher set returns\\ntrue, the final result of the \\\"not\\\" matcher is false. Individual\\nmatchers within a set work the same (i.e. different matchers in\\nthe same set are AND'ed).\\n\\nNOTE: The generated docs which describe the structure of this\\nmodule are wrong because of how this type unmarshals JSON in a\\ncustom way. The correct structure is:\\n\\n```json\\n[\\n\\t{},\\n\\t{}\\n]\\n```\\n\\nwhere each of the array elements is a matcher set, i.e. an\\nobject keyed by matcher name.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.path\": [\n      {\n        \"name\": \"http.matchers.path\",\n        \"docs\": \"http.matchers.path matches requests by the URI's path (case-insensitive). Path\\nmatches are exact, but wildcards may be used:\\n\\n- At the end, for a prefix match (`/prefix/*`)\\n- At the beginning, for a suffix match (`*.suffix`)\\n- On both sides, for a substring match (`*/contains/*`)\\n- In the middle, for a globular match (`/accounts/*/info`)\\n\\nThis matcher is fast, so it does not support regular expressions or\\ncapture groups. For slower but more powerful matching, use the\\npath_regexp matcher.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.path_regexp\": [\n      {\n        \"name\": \"http.matchers.path_regexp\",\n        \"docs\": \"http.matchers.path_regexp matches requests by a regular expression on the URI's path.\\n\\nUpon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`\\nwhere `name` is the regular expression's name, and `capture_group` is either\\nthe named or positional capture group from the expression itself. If no name\\nis given, then the placeholder omits the name: `{http.regexp.capture_group}`\\n(potentially leading to collisions).\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.protocol\": [\n      {\n        \"name\": \"http.matchers.protocol\",\n        \"docs\": \"http.matchers.protocol matches requests by protocol. Recognized values are\\n\\\"http\\\", \\\"https\\\", and \\\"grpc\\\".\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.query\": [\n      {\n        \"name\": \"http.matchers.query\",\n        \"docs\": \"http.matchers.query matches requests by the URI's query string. It takes a JSON object\\nkeyed by the query keys, with an array of string values to match for that key.\\nQuery key matches are exact, but wildcards may be used for value matches. Both\\nkeys and values may be placeholders.\\nAn example of the structure to match `?key=value\\u0026topic=api\\u0026query=something` is:\\n\\n```json\\n{\\n\\t\\\"key\\\": [\\\"value\\\"],\\n\\t\\\"topic\\\": [\\\"api\\\"],\\n\\t\\\"query\\\": [\\\"*\\\"]\\n}\\n```\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.remote_host\": [\n      {\n        \"name\": \"http.matchers.remote_host\",\n        \"docs\": \"http.matchers.remote_host matches based on the remote IP of the\\nconnection. A host name can be specified, whose A and AAAA\\nDNS records will be resolved to a corresponding IP for matching.\\n\\nNote that IPs can sometimes be spoofed, so do not rely\\non this as a replacement for actual authentication.\",\n        \"package\": \"github.com/muety/caddy-remote-host\",\n        \"repo\": \"https://github.com/muety/caddy-remote-host\"\n      }\n    ],\n    \"http.matchers.remote_ip\": [\n      {\n        \"name\": \"http.matchers.remote_ip\",\n        \"docs\": \"http.matchers.remote_ip matches requests by client IP (or CIDR range).\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.vars\": [\n      {\n        \"name\": \"http.matchers.vars\",\n        \"docs\": \"http.matchers.vars is an HTTP request matcher which can match\\nrequests based on variables in the context.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.matchers.vars_regexp\": [\n      {\n        \"name\": \"http.matchers.vars_regexp\",\n        \"docs\": \"http.matchers.vars_regexp matches the value of the context variables by a given regular expression.\\n\\nUpon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`\\nwhere `name` is the regular expression's name, and `capture_group` is either\\nthe named or positional capture group from the expression itself. If no name\\nis given, then the placeholder omits the name: `{http.regexp.capture_group}`\\n(potentially leading to collisions).\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.precompressed.br\": [\n      {\n        \"name\": \"http.precompressed.br\",\n        \"docs\": \"http.precompressed.br provides the file extension for files precompressed with brotli encoding.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/brotli\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.precompressed.gzip\": [\n      {\n        \"name\": \"http.precompressed.gzip\",\n        \"docs\": \"http.precompressed.gzip provides the file extension for files precompressed with gzip encoding.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/gzip\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.precompressed.zstd\": [\n      {\n        \"name\": \"http.precompressed.zstd\",\n        \"docs\": \"http.precompressed.zstd provides the file extension for files precompressed with zstandard encoding.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/zstd\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.cookie\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.cookie\",\n        \"docs\": \"http.reverse_proxy.selection_policies.cookie is a policy that selects\\na host based on a given cookie name.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.first\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.first\",\n        \"docs\": \"http.reverse_proxy.selection_policies.first is a policy that selects\\nthe first available host.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.header\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.header\",\n        \"docs\": \"http.reverse_proxy.selection_policies.header is a policy that selects\\na host based on a given request header.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.ip_hash\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.ip_hash\",\n        \"docs\": \"http.reverse_proxy.selection_policies.ip_hash is a policy that selects a host\\nbased on hashing the remote IP of the request.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.least_conn\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.least_conn\",\n        \"docs\": \"http.reverse_proxy.selection_policies.least_conn is a policy that selects the\\nhost with the least active requests. If multiple\\nhosts have the same fewest number, one is chosen\\nrandomly. The term \\\"conn\\\" or \\\"connection\\\" is used\\nin this policy name due to its similar meaning in\\nother software, but our load balancer actually\\ncounts active requests rather than connections,\\nsince these days requests are multiplexed onto\\nshared connections.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.random\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.random\",\n        \"docs\": \"http.reverse_proxy.selection_policies.random is a policy that selects\\nan available host at random.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.random_choose\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.random_choose\",\n        \"docs\": \"http.reverse_proxy.selection_policies.random_choose is a policy that selects\\ntwo or more available hosts at random, then\\nchooses the one with the least load.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.round_robin\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.round_robin\",\n        \"docs\": \"http.reverse_proxy.selection_policies.round_robin is a policy that selects\\na host based on round-robin ordering.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.selection_policies.uri_hash\": [\n      {\n        \"name\": \"http.reverse_proxy.selection_policies.uri_hash\",\n        \"docs\": \"http.reverse_proxy.selection_policies.uri_hash is a policy that selects a\\nhost by hashing the request URI.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.transport.fastcgi\": [\n      {\n        \"name\": \"http.reverse_proxy.transport.fastcgi\",\n        \"docs\": \"http.reverse_proxy.transport.fastcgi facilitates FastCGI communication.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/fastcgi\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.transport.http\": [\n      {\n        \"name\": \"http.reverse_proxy.transport.http\",\n        \"docs\": \"http.reverse_proxy.transport.http is essentially a configuration wrapper for http.Transport.\\nIt defines a JSON structure useful when configuring the HTTP transport\\nfor Caddy's reverse proxy. It builds its http.Transport at Provision.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"http.reverse_proxy.transport.http_ntlm\": [\n      {\n        \"name\": \"http.reverse_proxy.transport.http_ntlm\",\n        \"docs\": \"http.reverse_proxy.transport.http_ntlm proxies HTTP with NTLM authentication.\\nIt basically wraps HTTPTransport so that it is compatible with\\nNTLM's HTTP-hostile requirements. Specifically, it will use\\nHTTPTransport's single, default *http.Transport for all requests\\n(unless the client's connection is already mapped to a different\\ntransport) until a request comes in with an Authorization header\\nthat has \\\"NTLM\\\" or \\\"Negotiate\\\"; when that happens, NTLMTransport\\nmaps the client's connection (by its address, req.RemoteAddr)\\nto a new transport that is used only by that downstream conn.\\nWhen the upstream connection is closed, the mapping is deleted.\\nThis preserves NTLM authentication contexts by ensuring that\\nclient connections use the same upstream connection. It does\\nhurt performance a bit, but that's NTLM for you.\\n\\nThis transport also forces HTTP/1.1 and Keep-Alives in order\\nfor NTLM to succeed.\\n\\nIt is basically the same thing as\\n[nginx's paid ntlm directive](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#ntlm)\\n(but is free in Caddy!).\",\n        \"package\": \"github.com/caddyserver/ntlm-transport\",\n        \"repo\": \"https://github.com/caddyserver/ntlm-transport\"\n      }\n    ],\n    \"layer4\": [\n      {\n        \"name\": \"layer4\",\n        \"docs\": \"layer4 is a Caddy app that operates closest to layer 4 of the OSI model.\",\n        \"package\": \"github.com/mholt/caddy-l4/layer4\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.handlers.echo\": [\n      {\n        \"name\": \"layer4.handlers.echo\",\n        \"docs\": \"layer4.handlers.echo is a simple handler that writes what it reads.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4echo\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.handlers.proxy\": [\n      {\n        \"name\": \"layer4.handlers.proxy\",\n        \"docs\": \"layer4.handlers.proxy is a handler that can proxy connections.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxy\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.handlers.proxy_protocol\": [\n      {\n        \"name\": \"layer4.handlers.proxy_protocol\",\n        \"docs\": \"layer4.handlers.proxy_protocol is a connection handler that accepts the PROXY protocol.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxyprotocol\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.handlers.tee\": [\n      {\n        \"name\": \"layer4.handlers.tee\",\n        \"docs\": \"layer4.handlers.tee is a layer4 handler that replicates a connection so\\nthat a branch of handlers can concurrently handle it. Reads\\nhappen in lock-step with all concurrent branches so as to\\navoid buffering: if one of the branches (including the main\\nhandler chain) stops reading from the connection, it will\\nblock all branches.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4tee\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.handlers.throttle\": [\n      {\n        \"name\": \"layer4.handlers.throttle\",\n        \"docs\": \"layer4.handlers.throttle throttles connections using leaky bucket rate limiting.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4throttle\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.handlers.tls\": [\n      {\n        \"name\": \"layer4.handlers.tls\",\n        \"docs\": \"layer4.handlers.tls is a connection handler that terminates TLS.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4tls\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.matchers.crowdsec\": [\n      {\n        \"name\": \"layer4.matchers.crowdsec\",\n        \"docs\": \"layer4.matchers.crowdsec matches IPs to CrowdSec decisions to (dis)allow access\",\n        \"package\": \"github.com/hslatman/caddy-crowdsec-bouncer/layer4\",\n        \"repo\": \"https://github.com/hslatman/caddy-crowdsec-bouncer\"\n      }\n    ],\n    \"layer4.matchers.http\": [\n      {\n        \"name\": \"layer4.matchers.http\",\n        \"docs\": \"layer4.matchers.http is able to match HTTP connections. The auto-generated\\ndocumentation for this type is wrong; instead of an object, it\\nis an array of matcher set objects.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4http\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.matchers.ip\": [\n      {\n        \"name\": \"layer4.matchers.ip\",\n        \"docs\": \"layer4.matchers.ip matches requests by remote IP (or CIDR range).\",\n        \"package\": \"github.com/mholt/caddy-l4/layer4\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.matchers.proxy_protocol\": [\n      {\n        \"name\": \"layer4.matchers.proxy_protocol\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxy\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.matchers.ssh\": [\n      {\n        \"name\": \"layer4.matchers.ssh\",\n        \"docs\": \"layer4.matchers.ssh is able to match SSH connections.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4ssh\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.matchers.tls\": [\n      {\n        \"name\": \"layer4.matchers.tls\",\n        \"docs\": \"layer4.matchers.tls is able to match TLS connections. Its structure\\nis different from the auto-generated documentation. This\\nvalue should be a map of matcher names to their values.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4tls\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.matchers.xmpp\": [\n      {\n        \"name\": \"layer4.matchers.xmpp\",\n        \"docs\": \"layer4.matchers.xmpp is able to match XMPP connections.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4xmpp\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.proxy.selection_policies.first\": [\n      {\n        \"name\": \"layer4.proxy.selection_policies.first\",\n        \"docs\": \"layer4.proxy.selection_policies.first is a policy that selects\\nthe first available host.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxy\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.proxy.selection_policies.ip_hash\": [\n      {\n        \"name\": \"layer4.proxy.selection_policies.ip_hash\",\n        \"docs\": \"layer4.proxy.selection_policies.ip_hash is a policy that selects a host\\nbased on hashing the remote IP of the connection.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxy\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.proxy.selection_policies.least_conn\": [\n      {\n        \"name\": \"layer4.proxy.selection_policies.least_conn\",\n        \"docs\": \"layer4.proxy.selection_policies.least_conn is a policy that selects the upstream\\nwith the least active connections. If multiple upstreams\\nhave the same fewest number, one is chosen randomly.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxy\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.proxy.selection_policies.random\": [\n      {\n        \"name\": \"layer4.proxy.selection_policies.random\",\n        \"docs\": \"layer4.proxy.selection_policies.random is a policy that selects\\nan available host at random.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxy\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.proxy.selection_policies.random_choose\": [\n      {\n        \"name\": \"layer4.proxy.selection_policies.random_choose\",\n        \"docs\": \"layer4.proxy.selection_policies.random_choose is a policy that selects\\ntwo or more available hosts at random, then\\nchooses the one with the least load.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxy\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"layer4.proxy.selection_policies.round_robin\": [\n      {\n        \"name\": \"layer4.proxy.selection_policies.round_robin\",\n        \"docs\": \"layer4.proxy.selection_policies.round_robin is a policy that selects\\na host based on round-robin ordering.\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4proxy\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"pki\": [\n      {\n        \"name\": \"pki\",\n        \"docs\": \"pki provides Public Key Infrastructure facilities for Caddy.\\n\\nThis app can define certificate authorities (CAs) which are capable\\nof signing certificates. Other modules can be configured to use\\nthe CAs defined by this app for issuing certificates or getting\\nkey information needed for establishing trust.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddypki\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"security\": [\n      {\n        \"name\": \"security\",\n        \"docs\": \"security implements security manager.\",\n        \"package\": \"github.com/greenpau/caddy-security\",\n        \"repo\": \"https://github.com/greenpau/caddy-security\"\n      }\n    ],\n    \"supervisor\": [\n      {\n        \"name\": \"supervisor\",\n        \"package\": \"github.com/baldinof/caddy-supervisor\",\n        \"repo\": \"https://github.com/baldinof/caddy-supervisor\"\n      }\n    ],\n    \"tls\": [\n      {\n        \"name\": \"tls\",\n        \"docs\": \"tls provides TLS facilities including certificate\\nloading and management, client auth, and more.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.certificates.automate\": [\n      {\n        \"name\": \"tls.certificates.automate\",\n        \"docs\": \"tls.certificates.automate will automatically manage certificates for the names in the\\nlist, including obtaining and renewing certificates. Automated certificates\\nare managed according to their matching automation policy, configured\\nelsewhere in this app.\\n\\nTechnically, this is a no-op certificate loader module that is treated as\\na special case: it uses this app's automation features to load certificates\\nfor the list of hostnames, rather than loading certificates manually.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.certificates.load_files\": [\n      {\n        \"name\": \"tls.certificates.load_files\",\n        \"docs\": \"tls.certificates.load_files loads certificates and their associated keys from disk.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.certificates.load_folders\": [\n      {\n        \"name\": \"tls.certificates.load_folders\",\n        \"docs\": \"tls.certificates.load_folders loads certificates and their associated keys from disk\\nby recursively walking the specified directories, looking for PEM\\nfiles which contain both a certificate and a key.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.certificates.load_pem\": [\n      {\n        \"name\": \"tls.certificates.load_pem\",\n        \"docs\": \"tls.certificates.load_pem loads certificates and their associated keys by\\ndecoding their PEM blocks directly. This has the advantage\\nof not needing to store them on disk at all.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.certificates.load_storage\": [\n      {\n        \"name\": \"tls.certificates.load_storage\",\n        \"docs\": \"tls.certificates.load_storage loads certificates and their associated keys\\nfrom the globally configured storage module.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.handshake_match.alpn\": [\n      {\n        \"name\": \"tls.handshake_match.alpn\",\n        \"package\": \"github.com/mholt/caddy-l4/modules/l4tls\",\n        \"repo\": \"https://github.com/mholt/caddy-l4\"\n      }\n    ],\n    \"tls.handshake_match.remote_ip\": [\n      {\n        \"name\": \"tls.handshake_match.remote_ip\",\n        \"docs\": \"tls.handshake_match.remote_ip matches based on the remote IP of the\\nconnection. Specific IPs or CIDR ranges can be specified.\\n\\nNote that IPs can sometimes be spoofed, so do not rely\\non this as a replacement for actual authentication.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.handshake_match.sni\": [\n      {\n        \"name\": \"tls.handshake_match.sni\",\n        \"docs\": \"tls.handshake_match.sni matches based on SNI. Names in\\nthis list may use left-most-label wildcards,\\nsimilar to wildcard certificates.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.issuance.acme\": [\n      {\n        \"name\": \"tls.issuance.acme\",\n        \"docs\": \"tls.issuance.acme manages certificates using the ACME protocol (RFC 8555).\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.issuance.internal\": [\n      {\n        \"name\": \"tls.issuance.internal\",\n        \"docs\": \"tls.issuance.internal is a certificate issuer that generates\\ncertificates internally using a locally-configured\\nCA which can be customized using the `pki` app.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.issuance.zerossl\": [\n      {\n        \"name\": \"tls.issuance.zerossl\",\n        \"docs\": \"tls.issuance.zerossl makes an ACME manager\\nfor managing certificates using ACME.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.stek.distributed\": [\n      {\n        \"name\": \"tls.stek.distributed\",\n        \"docs\": \"tls.stek.distributed implements a distributed STEK provider. This\\nmodule will obtain STEKs from a storage module instead\\nof generating STEKs internally. This allows STEKs to be\\ncoordinated, improving TLS session resumption in a cluster.\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls/distributedstek\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ],\n    \"tls.stek.standard\": [\n      {\n        \"name\": \"tls.stek.standard\",\n        \"package\": \"github.com/caddyserver/caddy/v2/modules/caddytls/standardstek\",\n        \"repo\": \"https://github.com/caddyserver/caddy\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/docs/index.html",
    "content": "{{$pathParts := splitList \"/\" .OriginalReq.URL.Path}}\n{{$markdownFilename := default \"index\" (slice $pathParts 2 | join \"/\")}}\n{{$markdownFilePath := printf \"/docs/markdown/%s.md\" $markdownFilename}}\n{{if not (fileExists $markdownFilePath)}}{{httpError 404}}{{end}}\n{{$markdownFile := (include $markdownFilePath | splitFrontMatter)}}\n{{$title := default $markdownFilename $markdownFile.Meta.title}}\n<!DOCTYPE html>\n<html>\n<head>\n    <title>{{$title}} &mdash; Caddy v2中文文档</title>\n    {{include \"/includes/docs/head.html\"}}\n    <meta property=\"og:title\" content=\"{{$title}} - Caddy v2中文文档\">\n    <meta name=\"twitter:title\" value=\"{{$title}} - Caddy v2中文文档\">\n</head>\n<body>\n{{include \"/includes/docs/header.html\"}}\n<main>\n    {{include \"/includes/docs/nav.html\"}}\n    <div class=\"article-container\">\n        <div class=\"paper\" id=\"paper1\"></div>\n        <div class=\"paper\" id=\"paper2\"></div>\n        <div class=\"paper paper3\">\n            <article>{{markdown $markdownFile.Body}}</article>\n\n            {{include \"/includes/donate.html\"}}\n        </div>\n    </div>\n    <div class=\"sidebar\"></div>\n</main>\n{{include \"/includes/footer.html\"}}\n\n</body>\n</html>"
  },
  {
    "path": "src/docs/json/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>JSON配置结构 - Caddy V2中文文档</title>\n\t\t{{include \"/includes/docs/head.html\"}}\n\t\t<link rel=\"stylesheet\" href=\"/resources/css/docs-json.css\">\n\t\t<script src=\"/resources/js/marked-0.8.0.min.js\"></script>\n\t\t<script src=\"/resources/js/docs-api.js\"></script>\n\t\t<script src=\"/resources/js/json-docs.js\"></script>\n\t</head>\n\t<body>\n\t\t{{include \"/includes/docs/header.html\"}}\n\t\t<main>\n\t\t\t{{include \"/includes/docs/nav.html\"}}\n\t\t\t<div class=\"article-container\">\n\t\t\t\t<div class=\"paper\" id=\"paper1\"></div>\n\t\t\t\t<div class=\"paper\" id=\"paper2\"></div>\n\t\t\t\t<div class=\"paper paper3\">\n\t\t\t\t\t<article id=\"json-docs-container\">\n\t\t\t\t\t\t<div class=\"breadcrumbs\">\n\t\t\t\t\t\t\t<!--Populated by JS-->\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{{include \"/includes/docs/renderbox.html\"}}\n\t\t\t\t\t\t{{include \"/includes/docs/details.html\"}}\n\t\t\t\t\t</article>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"sidebar\"></div>\n\t\t</main>\n\n\t\t{{include \"/includes/docs/hovercard.html\"}}\n\n\t\t{{include \"/includes/footer.html\"}}\n\t</body>\n</html>"
  },
  {
    "path": "src/docs/markdown/api-tutorial.md",
    "content": "---\ntitle: \"API教程\"\n---\n\n# API教程\n\n本教程将向你展示如何使用Caddy的[管理API](/docs/api)，这使得以可编程方式实现自动化成为可能。\n\n**目标：**\n- 🔲 运行守护程序\n- 🔲 给 Caddy 一个配置\n- 🔲 测试配置\n- 🔲 替换活动配置\n- 🔲 遍历配置\n- 🔲 使用`@id`标签\n\n**必备：**\n- 基本的终端/命令行技能\n- 基本的JSON经验\n- PATH支持`caddy`和`curl`\n\n---\n\n要启动 Caddy 守护程序，请使用`run`子命令：\n\n<pre><code class=\"cmd bash\">caddy run</code></pre>\n\n<aside class=\"complete\">运行守护程序</aside>\n\n这永远阻塞，但它在做什么？此刻……什么都没有。默认情况下，Caddy的配置（“config”）为空白。我们可以使用另一个终端中的[管理API](/docs/api)来验证这一点：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/config/</code></pre>\n\n我们可以通过给它一个配置来使Caddy变得有用。一种方法是向 [/load](/docs/api#post-load)端点发出 POST 请求。就像任何 HTTP 请求一样，有很多方法可以做到这一点，但在本教程中，我们将使用`curl`。\n\n## 你的第一个配置\n\n为了能发起请求，我们需要进行配置。Caddy的配置只是一个[JSON文档](/docs/json/) （或[任何能转换为JSON](/docs/config-adapters)的文件）。\n\n<aside class=\"tip\">\n    不需要配置文件。配置API始终可以在没有文件的情况下使用，这非常利于实现自动化。本教程则使用文件，因为它更方便手动编辑。\t\n</aside>\n\n将下面的内容保存到JSON文件：\n\n```json\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"example\": {\n\t\t\t\t\t\"listen\": [\":2015\"],\n\t\t\t\t\t\"routes\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"handle\": [{\n\t\t\t\t\t\t\t\t\"handler\": \"static_response\",\n\t\t\t\t\t\t\t\t\"body\": \"Hello, world!\"\n\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n然后上传：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/load \\\n\t-H \"Content-Type: application/json\" \\\n\t-d @caddy.json\n</code></pre>\n\n<aside class=\"tip\">\n    确保不要忘记文件名前面的`@`；这告诉`curl`你正在发送一个文件。\n</aside>\n\n<aside class=\"complete\">给Caddy一个配置</aside>\n\n我们可以验证 Caddy 是否通过另一个 GET 请求应用了我们的新配置：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/config/</code></pre>\n\n通过在浏览器中访问[localhost:2015](http://localhost:2015)或使用`curl`命令来测试它是否有效：\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl localhost:2015</span>\nHello, world!</code></pre>\n\n<aside class=\"complete\">测试配置</aside>\n\n如果你看到_Hello, world!_，就恭喜啦——它已经工作了！确保你的配置按预期工作始终是一个好主意，尤其是在部署到生产环境之前。\n\n```json\n{\n\t\"handler\": \"static_response\",\n\t\"body\": \"I can do hard things.\"\n}\n```\n\n保存配置文件，然后通过再次运行相同的POST请求来更新Caddy的活动配置：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/load \\\n\t-H \"Content-Type: application/json\" \\\n\t-d @caddy.json\n</code></pre>\n\n<aside class=\"complete\">替换活动配置</aside>\n\n为了更好地衡量，请验证配置是否已更新：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/config/</code></pre>\n\n通过在浏览器中刷新页面（或再次运行`curl`）来测试它，你将看到一条鼓舞人心的消息！\n\n## 遍历配置\n\n让我们使用 Caddy API 的强大功能来进行更改，而不需要修改我们的配置文件，而不是上传整个配置文件。\n\n通过像我们上面所做的那样替换整个配置来对生产服务器进行少量更改可能是危险的；这就像拥有对文件系统的root访问权限。Caddy的API允许你限制更改的范围，以确保配置的其他部分不会被意外更改。\n使用请求URI的路径，我们可以遍历配置结构并仅更新消息字符串（如果被遮挡，请确保向右滚动）：\n\n<aside class=\"tip\">\n通过像我们上面所做的那样替换整个配置来对生产服务器进行少量更改可能是危险的；这就像拥有对文件系统的root访问权限。Caddy的API允许你限制更改的范围，以确保配置的其他部分不会被意外更改。\n</aside>\n\n使用请求 URI 的路径，我们可以遍历配置结构并仅更新消息字符串（如果被遮挡，请确保向右滚动）：\n\n<pre><code class=\"cmd bash\">curl \\\n\tlocalhost:2019/config/apps/http/servers/example/routes/0/handle/0/body \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '\"Work smarter, not harder.\"'\n</code></pre>\n\n<aside class=\"tip\">\n\t每次你使用 API 更改配置时，Caddy 都会保留一份新配置的副本，便于你可以通过<a href=\"/docs/command-line#caddy-run\"><b>--resume</b>恢复</a>!\n</aside>\n\n你可以验证它是否适用于类似的GET请求，例如：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/config/apps/http/servers/example/routes</code></pre>\n\n你应该看到：\n\n```json\n[{\"handle\":[{\"body\":\"Work smarter, not harder.\",\"handler\":\"static_response\"}]}]\n```\n\n<aside class=\"tip\">\n    你可以使用<a href=\"https://stedolan.github.io/jq/\">jq command</a>命令来美化 JSON 输出：<code>curl ... | jq</code>\n</aside>\n\n<aside class=\"complete\">遍历配置</aside>\n\n**重要提示：**\n显而易见，一旦你使用API进行配置，原始配置文件并不会被修改，这样你的配置文件就过时了。有几种方法可以处理这个问题：\n\n- 使用`--resume`作为[caddy run](/docs/command-line#caddy-run)命令的最后一个参数。\n- 不要混合使用配置文件和API两种方式进行更改; 始终使用同一种方式。\n- 使用GET请求[导出Caddy的新配置](/docs/api#get-configpath) (不如前两个选项推荐)。\n\n## 在JSON中使用`@id`\n\n配置遍历当然有用，但是路径有点长，你不觉得吗？\n\n我们可以给我们的处理对象一个[`@id`标签](/docs/api#using-id-in-json)，使它更容易访问：\n\n<pre><code class=\"cmd bash\">curl \\\n\tlocalhost:2019/config/apps/http/servers/example/routes/0/handle/0/@id \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '\"msg\"'\n</code></pre>\n\n这给我们的处理对象添加了一个属性：\"@id\": \"msg\"，所以它现在看起来像这样：\n\n```json\n{\n\t\"@id\": \"msg\",\n\t\"body\": \"Work smarter, not harder.\",\n\t\"handler\": \"static_response\"\n}\n```\n\n<aside class=\"tip\">\n    <b>@id</b>标签可以放在任何对象中，并且可以有任何原始值（通常是字符串）。<a href=\"/docs/api#using-id-in-json\">了解更多</a>\n</aside>\n\n然后我们可以直接访问它：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/id/msg</code></pre>\n\n现在我们也通过更短的路径更改消息：\n\n<pre><code class=\"cmd bash\">curl \\\n\tlocalhost:2019/id/msg/body \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '\"Some shortcuts are good.\"'\n</code></pre>\n\n并再次检查：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/id/msg/body</code></pre>\n\n<aside class=\"complete\">使用<code>@id</code>标签</aside>\n"
  },
  {
    "path": "src/docs/markdown/api.md",
    "content": "---\ntitle: \"API\"\n---\n\n# API\n\nCaddy是通过管理端点进行配置的，该端点可以使用[REST](https://en.wikipedia.org/wiki/Representational_state_transfer)API通过HTTP访问。你可以在Caddy配置中[配置此端点](/docs/json/admin/)。\n\n**默认地址：`localhost:2019`**\n\n默认地址可以通过设置`CADDY_ADMIN`环境变量来更改。某些安装方法可能会将其设置为其他值。Caddy配置中的地址始终优先于默认设置。\n\n<aside class=\"tip\">\n\t如果你在服务器上运行不受信任的代码（哎呀😬)，请确保通过隔离进程、修补易受攻击的程序以及将端点配置为绑定到许可的unix套接字来保护你管理的端点。\n</aside>\n\n最新的配置将在任何更改后保存到磁盘（除非[禁用](/docs/json/admin/config/))在重启后恢复上一个工作配置，这可以保证在电源循环或类似情况下配置的持久性。\n\n要开始使用 API，请尝试我们的[API教程](/docs/api-tutorial)或者，如果你只有一分钟时间，请尝试我们的[API快速入门指南](/docs/quick-starts/api)。\n\n---\n\n- **[POST /load](#post-load)**\n  设置或替换当前配置\n\n- **[POST /stop](#post-stop)**\n  停止当前配置并退出进程\n\n- **[GET /config/[path]](#get-configpath)**\n  导出指定路径的配置\n\n- **[POST /config/[path]](#post-configpath)**\n  设置或替换对象；追加到数组\n\n- **[PUT /config/[path]](#put-configpath)**\n  创建新对象；插入数组\n\n- **[PATCH /config/[path]](#patch-configpath)**\n  替换现有对象或数组元素\n\n- **[DELETE /config/[path]](#delete-configpath)**\n  删除指定路径的值\n\n- **[在JSON中使用`@id`](#using-id-in-json)**\n  轻松遍历配置结构\n\n- **[并发配置修改](#concurrent-config-changes)**\n  在对配置进行非同步修改时避免冲突。\n\n- **[POST /adapt](#post-adapt)**\n  将配置适配为JSON格式，而不实际运行它\n\n- **[GET /pki/ca/&lt;id&gt;](#get-pkicaltidgt)**\n  返回有关特定[PKI应用](/docs/json/apps/pki/)的CA的信息\n\n- **[GET /pki/ca/&lt;id&gt;/certificates](#get-pkicaltidgtcertificates)**\n  返回特定[PKI应用](/docs/json/apps/pki/) CA的证书链\n\n- **[GET /reverse_proxy/upstreams](#get-reverse-proxyupstreams)**\n  返回配置的代理上游的当前状态\n\n\n## POST /load\n\n设置 Caddy 的配置，覆盖任何以前的配置。它会一直阻塞，直到重新加载完成或失败。配置更改是轻量级、高效的，并且会导致零停机。如果新配置因任何原因失败，则旧配置将回滚到原位而不会停机。\n\n该端点使用配置适配器支持不同的配置格式。请求的`Content-Type`标头指示请求正文中使用的配置格式。通常这个值应该是`application/json`，代表Caddy的原生配置格式。对于其他配置格式，请指定适当的`Content-Type`，正斜杠`/`之后的值是要使用的配置适配器的名称。例如，在提交Caddyfile时，使用类似于`text/caddyfile`的值；或者对于JSON 5，使用类似于`application/json5`的值; 等等。\n\n如果新配置与当前配置相同，则不会发生重新加载。要强制重新加载，请在请求标头中设置`Cache-Control: must-revalidate`。\n\n### 示例\n\n设置新的活动配置：\n\n<pre><code class=\"cmd bash\">curl \"http://localhost:2019/load\" \\\n\t-H \"Content-Type: application/json\" \\\n\t-d @caddy.json</code></pre>\n\n注意：`curl`的`-d`标志会删除换行符，因此如果你的配置格式对换行符敏感（例如 Caddyfile），请改用`--data-binary`：\n\n<pre><code class=\"cmd bash\">curl \"http://localhost:2019/load\" \\\n\t-H \"Content-Type: text/caddyfile\" \\\n\t--data-binary @Caddyfile</code></pre>\n\n\n## POST /stop\n\n优雅地关闭服务器并退出进程。要仅停止正在运行的配置而不退出进程，请使用[DELETE /config/](#delete-configpath)。\n\n### 示例\n\n停止进程：\n\n<pre><code class=\"cmd bash\">curl -X POST \"http://localhost:2019/stop\"</code></pre>\n\n\n## GET /config/[path]\n\n在命名路径中导出 Caddy 的当前配置。返回 JSON 正文。\n\n### 示例\n\n导出整个配置并漂亮地打印它：\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl \"http://localhost:2019/config/\" | jq</span>\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"myserver\": {\n\t\t\t\t\t\"listen\": [\n\t\t\t\t\t\t\":443\"\n\t\t\t\t\t],\n\t\t\t\t\t\"routes\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"match\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"example.com\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"handle\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"handler\": \"file_server\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}</code></pre>\n\n仅导出侦听器地址：\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl \"http://localhost:2019/config/apps/http/servers/myserver/listen\"</span>\n[\":443\"]</code></pre>\n\n\n\n## POST /config/[path]\n\n将 Caddy 的配置更改为请求的 JSON 正文的命名路径。如果目标值是一个数组，则追加 POST；如果是一个对象，则会进行创建或替换。\n\n作为一种特殊情况，如果满足以下条件，可以将许多项目添加到数组中：\n\n1. 路径结束于`/...`\n2. `/...`之前的路径元素指的是一个数组\n3. 有效载荷是一个数组\n\n在这种情况下，有效载荷数组中的元素将被扩展，并且每个元素都将附加到目标数组中。在 Go 术语中，这将具有与以下相同的效果：\n\n```go\nbaseSlice = append(baseSlice, newElems...)\n```\n\n### 示例\n\n添加监听地址：\n\n<pre><code class=\"cmd bash\">curl \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '\":8080\"' \\\n\t\"http://localhost:2019/config/apps/http/servers/myserver/listen\"</code></pre>\n\n添加多个监听地址：\n\n<pre><code class=\"cmd bash\">curl \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '[\":8080\", \":5133\"]' \\\n\t\"http://localhost:2019/config/apps/http/servers/myserver/listen/...\"</code></pre>\n\n## PUT /config/[path]\n\n将 Caddy 的配置更改为请求的 JSON 正文的命名路径。如果目标值是数组中的位置（索引），则 PUT 插入；如果是一个对象，它会严格创建一个新值。\n\n### 示例\n\n在第一个槽中添加监听地址：\n\n<pre><code class=\"cmd bash\">curl -X PUT \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '\":8080\"' \\\n\t\"http://localhost:2019/config/apps/http/servers/myserver/listen/0\"</code></pre>\n\n\n## PATCH /config/[path]\n\n将 Caddy 的配置更改为请求的 JSON 正文的命名路径。PATCH 严格替换现有值或数组元素。\n\n### 示例\n\n替换监听地址：\n\n<pre><code class=\"cmd bash\">curl -X PATCH \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '[\":8081\", \":8082\"]' \\\n\t\"http://localhost:2019/config/apps/http/servers/myserver/listen\"</code></pre>\n\n\n\n## DELETE /config/[path]\n\n在命名路径中删除 Caddy 的配置。DELETE 删除目标值。\n\n### 示例\n\n要卸载整个当前配置但保持进程运行：\n\n<pre><code class=\"cmd bash\">curl -X DELETE \"http://localhost:2019/config/\"</code></pre>\n\n只停止一个 HTTP 服务器：\n\n<pre><code class=\"cmd bash\">curl -X DELETE \"http://localhost:2019/config/apps/http/servers/myserver\"</code></pre>\n\n\n<h2 id=\"using-id-in-json\">在JSON中使用`@id`</h2>\n\n你可以在 JSON 文档中嵌入 ID，以便更轻松地直接访问 JSON 的这些部分。\n\n只需添加一个称为`\"@id\"`对象的字段并为其指定一个唯一名称。例如，如果你有一个想要经常访问的反向代理处理程序：\n\n<pre><code class=\"cmd json\">{\n\t\"@id\": \"my_proxy\",\n\t\"handler\": \"reverse_proxy\"\n}</code></pre>\n\n要使用它，只需以与`/config/`相应端点相同的方式向`/id/`API端点发出请求，但无需完整路径。ID 将请求直接带入你的配置范围。\n\n例如，要在没有 ID 的情况下访问反向代理的上游，路径将类似于：\n\n<pre><code class=\"cmd\">/config/apps/http/servers/myserver/routes/1/handle/0/upstreams</code></pre>\n\n但是有了ID，路径就变成了：\n\n<pre><code class=\"cmd\">/id/my_proxy/upstreams</code></pre>\n\n这更容易记忆和手写。\n\n<h2 id=\"concurrent-config-changes\">并发配置更改</h2>\n\n<aside class=\"tip\">\n本节适用于所有`/config/`端点。它是实验性的，可能会发生变化。\n</aside>\n\nCaddy的配置API为单个请求提供[ACID保证](https://en.wikipedia.org/wiki/ACID)，但涉及多个请求的更改如果没有适当同步，可能会导致冲突或数据丢失。\n\n例如，两个客户端可能同时使用`GET /config/foo`，在该范围内进行编辑（配置路径），然后同时调用`POST|PUT|PATCH|DELETE /config/foo/...`来应用更改，导致冲突：要么一个会覆盖另一个，要么第二个可能会将配置留在意外状态，因为它是针对不同版本的配置应用的，而不是针对准备好的版本。这是因为更改彼此不知道。\n\nCaddy的API不支持跨多个请求的事务，并且HTTP是一种无状态协议。但是，您可以使用`Etag`标头和`If-Match`标头来检测和防止所有更改的冲突，作为一种乐观并发控制。如果有任何可能同时使用Caddy的`/config/...`端点而没有同步，则对`GET /config/...`请求的所有响应都有一个名为`Etag`的HTTP尾部，其中包含该范围内内容的路径和哈希（例如`Etag: \"/config/apps/http/servers 65760b8e\"`）。只需在具有更改性质的请求上设置`If-Match`标头，以前一个`GET`请求的Etag尾部的值为依据。\n\n这个基本算法如下：\n\n1. 对配置的任何范围`S`执行`GET`请求。保存响应的`Etag`尾部。\n2. 对返回的配置进行所需的更改。\n3. 在范围`S`内执行`POST|PUT|PATCH|DELETE`请求，将`If-Match`标头设置为最近的`Etag`值。\n4. 如果响应是HTTP 412（前提条件失败），则从步骤1重新开始，或者在尝试次数过多后放弃。\n\n该算法可以安全地允许对Caddy的配置进行多个重叠更改，而无需显式同步。它的设计使得对配置不同部分的同时更改不需要重试：只有重叠到配置相同范围的更改才可能导致冲突，因此需要重试。\n\n## POST /adapt\n\n将配置适配为Caddy JSON格式，而不加载或运行它。如果成功，生成的JSON文档将在响应正文中返回。\n\nContent-Type标头用于指定配置格式，方式与[/load](#post-load)相同。例如，要适配Caddyfile，请设置`Content-Type: text/caddyfile`。\n\n只要关联的[配置适配器](/docs/config-adapters)已插入到您的Caddy构建中，此端点将适应任何配置格式。\n\n### 示例\n\n将Caddyfile适配为JSON：\n\n```bash\ncurl \"http://localhost:2019/adapt\" \\\n\t-H \"Content-Type: text/caddyfile\" \\\n\t--data-binary @Caddyfile\n```\n\n\n## GET /pki/ca/&lt;id&gt;\n<a name=\"get-pkicaltidgt\"></a>\n\n通过其ID返回有关特定[PKI应用](/docs/json/apps/pki/) CA的信息。如果请求的CA ID是默认值（`local`），则如果尚未创建CA，则将会创建该CA。如果其他CA ID尚未创建，则将返回错误。\n\n```bash\ncurl \"http://localhost:2019/pki/ca/local\" | jq\n{\n\t\"id\": \"local\",\n\t\"name\": \"Caddy Local Authority\",\n\t\"root_common_name\": \"Caddy Local Authority - 2022 ECC Root\",\n\t\"intermediate_common_name\": \"Caddy Local Authority - ECC Intermediate\",\n\t\"root_certificate\": \"-----BEGIN CERTIFICATE-----\\nMIIB ... gRw==\\n-----END CERTIFICATE-----\\n\",\n\t\"intermediate_certificate\": \"-----BEGIN CERTIFICATE-----\\nMIIB ... FzQ==\\n-----END CERTIFICATE-----\\n\"\n}\n```\n\n\n## GET /pki/ca/&lt;id&gt;/certificates\n<a name=\"get-pkicaltidgtcertificates\"></a>\n\n通过其ID返回特定[PKI应用](/docs/json/apps/pki/) CA的证书链。如果请求的CA ID是默认值（`local`），则如果尚未创建CA，则将会创建该CA。如果其他CA ID尚未创建，则将返回错误。\n\n此端点由[`caddy trust`](/docs/command-line#caddy-trust)命令在内部使用，以允许将CA的根证书安装到系统的信任存储中。\n\n```bash\ncurl \"http://localhost:2019/pki/ca/local/certificates\"\n-----BEGIN CERTIFICATE-----\nMIIByDCCAW2gAwIBAgIQViS12trTXBS/nyxy7Zg9JDAKBggqhkjOPQQDAjAwMS4w\n...\nBy75JkP6C14OfU733oElfDUMa5ctbMY53rWFzQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIBpDCCAUmgAwIBAgIQTS5a+3LUKNxC6qN3ZDR8bDAKBggqhkjOPQQDAjAwMS4w\n...\n9M9t0FwCIQCAlUr4ZlFzHE/3K6dARYKusR1ck4A3MtucSSyar6lgRw==\n-----END CERTIFICATE-----\n```\n\n## GET /reverse_proxy/upstreams\n<a name=\"get-reverse-proxyupstreams\"></a>\n\n将配置的反向代理上游（后端）的当前状态作为 JSON 文档返回。\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl \"http://localhost:2019/reverse_proxy/upstreams\" | jq</span>\n[\n\t{\"address\": \"10.0.1.1:80\", \"num_requests\": 4, \"fails\": 2},\n\t{\"address\": \"10.0.1.2:80\", \"num_requests\": 5, \"fails\": 4},\n\t{\"address\": \"10.0.1.3:80\", \"num_requests\": 3, \"fails\": 3}\n]</code></pre>\n\nJSON 数组中的每个条目都是存储在全局上游池中的已配置[upstream](/docs/json/apps/http/servers/routes/handle/reverse_proxy/upstreams/)。\n\n- **address** 上游的拨号地址。对于SRV上游，这是`lookup_srv`的DNS名称。\n- **healthy** 反映了Caddy是否认为上游是健康的。请注意，“健康”是与“可用性”不同的概念。不健康的后端将始终不可用，但健康的后端可能不可用。无论特定的反向代理处理程序配置如何，运行状况都是一个全局特性，而可用性由特定的反向代理处理程序的配置决定。例如，如果处理程序被配置为一次只允许N个请求并且它当前有N个活动请求，那么健康的后端将不可用。“健康”属性不反映可用性。\n- **num_requests** 是上游当前正在处理的活动请求的数量。\n- **fails** 当前记录的失败请求数，由被动运行状况检查配置。\n\n如果你的目标是确定后端的可用性，则需要根据你正在使用的处理程序配置交叉检查上游的相关属性。例如，如果你为代理启用了[被动健康检查](/docs/json/apps/http/servers/routes/handle/reverse_proxy/health_checks/passive/)，那么你还需要考虑`fails`和`num_requests`值来确定上游是否可用：检查`fails`数量是否小于为你的代理配置的最大故障数量代理（即[`max_fails`](/docs/json/apps/http/servers/routes/handle/reverse_proxy/health_checks/passive/max_fails)），并且`num_requests`小于或等于你配置的每个上游的最大请求量（即对于整个代理的[`unhealthy_request_count`](/docs/json/apps/http/servers/routes/handle/reverse_proxy/health_checks/passive/unhealthy_request_count)，或对于单个上游的[`max_requests`](/docs/json/apps/http/servers/routes/handle/reverse_proxy/upstreams/max_requests)）。\n"
  },
  {
    "path": "src/docs/markdown/architecture.md",
    "content": "---\ntitle: 架构\n---\n\n架构\n============\n\nCaddy 是一个单一的、自包含的、静态的二进制文件，外部依赖项为零，因为它是用 Go 编写的。这些价值观构成了项目愿景的重要组成部分，因为它们简化了部署并减少了生产环境中繁琐的故障排除。\n\n\n如果没有动态链接，那么如何扩展呢？Caddy 采用了一种新颖的插件架构，其功能远远超出任何其他 Web 服务器，即使是那些具有外部（动态链接）依赖项的服务器。\n\n我们“更少的活动部件”的理念最终会导致更可靠、更易于管理、更便宜的站点——尤其是在规模上。这份半技术文档描述了我们如何通过软件工程实现这一目标。\n\n\n## 概述\n\nCCaddy 由命令、核心库和模块组成。\n\n**命令**提供了你更熟悉的[命令行界面](/docs/command-line)。这是你从操作系统启动进程的方式。这里的代码和逻辑数量相当少，并且仅具有以用户所需方式引导核心所需的内容。我们有意避免使用标记和环境变量进行配置，除非它们与引导配置有关。\n\n\n<aside class=\"tip\">\n\t模块可以向命令行界面添加子命令。例如，<a href=\"/docs/command-line#caddy-file-server\"><code>caddy file-server</code></a>命令就是从这里来的。这些添加的命令可以使用任何标志或环境变量，尽管核心Caddy命令会尽量减少它们的使用。\n</aside>\n\n**[核心库](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc)**，或者叫 Caddy的“核心”，主要作用是管理配置。它可以[`Run()`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Run) 一个新配置或[`Stop()`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Stop) 正在运行的配置。它还为要使用的模块提供各种实用程序、类型和值。\n\n**模块**做其他所有事情。许多模块内置在 Caddy 中，称为 _标准模块_。这是是对大多数用户而言最有用的。\n\n<aside class=\"tip\">\n\t有时术语“模块”、“插件”和“扩展”可以互换使用，通常这是可以的。从技术上讲，所有模块都是插件，但并非所有插件都是模块。模块是一种特定类型的插件，可以扩展Caddy的<a href=\"/docs/json/\">配置结构</a>。\n</aside>\n\n\n\n\n## Caddy核心\n\nCaddy的核心只是加载一个初始配置（\"config\"），或者，在没有配置的情况下，随后打开一个套接字以接受新配置。\n\n\n[Caddy配置](/docs/json/) 就是一个JSON文档，在其顶层包含一些字段：\n\n```json\n{\n\t\"admin\": {},\n\t\"logging\": {},\n\t\"apps\": {•••},\n\t...\n}\n```\n\nCaddy 的核心知道如何在本地使用其中一些字段：\n\n- [`admin`](/docs/json/admin/) 通过它能设置[管理API](/docs/api)并管理流程\n- [`logging`](/docs/json/logging/) 通过它可以[记录logs](/docs/logging)\n\n但是其他顶级字段（[`apps`](/docs/json/apps/)）对于 Caddy 的核心是不透明的。事实上，Caddy 所知道的如何处理`apps`中的字节，是将它们反序列化为一个接口类型，它可以调用两个方法：\n\n\n1. `Start()`\n2. `Stop()`\n\n……就是这样，它在每个应用的配置载入时调用`Start()`，且在它们的配置被卸载时调用`Stop()`。\n\n\n启动应用的模块时，它会初始化应用模块的生命周期。\n\n\n<aside class=\"tip\">\n\t如果你是构建 Caddy 模块的程序员，你可以在我们的<a href=\"/docs/extending-caddy\">扩展指南</a>找到类似的信息，但更关注代码。\n</aside>\n\n\n## 模块生命周期\n\n有两种模块：_主机模块_ 和 _访客模块_。\n\n\n**主机模块**（或“父”模块）是加载其他模块的模块。\n\n**访客模块**（或“子”模块）是那些被加载的模块。所有模块都是访客模块——甚至是应用程序模块。\n\n模块被加载，被配置和验证，被使用，然后被清理，按以下顺序：\n\n1. 被载入\n2. 被配置和验证\n3. 被使用\n4. 被清理\n\n当首先加载配置时，Caddy 通过初始化所有已配置的应用模块来启动模块生命周期。之后，它被每个应用程序模块带着它走完剩下的路。\n\n\n### 加载阶段\n\n加载模块涉及将其 JSON 字节反序列化为内存中的类型值。就是……基本上就是这，它只是将 JSON 解码为一个值。\n\n\n### 供应阶段\n\n这个阶段是大部分设置工作的地方。所有模块在加载后都有机会自行配置。\n\n由于 JSON 编码中的任何属性都已经被解码，因此这里只需要进行额外的设置。配置期间最常见的任务是设置访客模块。换句话说，供应主机模块也会导致供应其客户模块，一直向下。\n\n您可以通过在我们的文档[遍历 Caddy 的 JSON 结构](/docs/json/)来了解这一点。你看到`{•••}` 的任何地方都是可以使用访客模块的地方；当你单击其中一个时，你可以一直向下探索，直到没有更多的访客模块。\n\n其他常见的配置任务是设置将在模块生命周期内使用的内部值，或标准化输入。例如，[http.matchers.remote_ip](/docs/modules/http.matchers.remote_ip) 模块使用配置阶段从它从 JSON 接收的字符串输入中解析 CIDR 值。这样，它就不必在每个 HTTP 请求期间都这样做，因此效率更高。\n\n验证也可以在供应阶段进行。如果模块的结果配置无效，则会在此处返回错误，从而中止整个配置加载过程。\n\n### 使用阶段\n\n一旦客户模块被配置和验证，它就可以被它的主机模块使用。这究竟意味着什么取决于每个主机模块。\n\n每个模块都有一个 ID，它由一个命名空间和该命名空间中的名称组成。例如，[`http.handlers.reverse_proxy`](/docs/modules/http.handlers.reverse_proxy) 是一个 HTTP 处理程序，因为它在`http.handlers` 命名空间中，并且它的名称是`reverse_proxy`。命名空间中的所有模块都`http.handlers` 满足主机模块已知的相同接口。因此，`http` 应用程序知道如何加载和使用这些类型的模块。\n\n### 清理阶段\n\n当需要停止配置时，所有模块都会被卸载。如果一个模块分配了任何应该被释放的资源，它就有机会在清理阶段这样做。\n\n## 插入\n\n一个模块——或任何 Caddy 插件——通过`import`为模块的包添加一个“插入”到 Caddy。通过导入包，[模块将自己注册](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#RegisterModule) 到 Caddy 核心，因此当 Caddy 进程启动时，它通过名称知道每个模块。它甚至可以在模块值和名称之间关联，反之亦然。\n\n<aside class=\"tip\">\n\t无需修改 Caddy 代码库即可添加插件。<a href=\"https://github.com/caddyserver/caddy/#with-version-information-andor-plugins\">自述</a>文件中有执行此 操作的说明！\n</aside>\n\n\n## 管理配置\n\n由于服务器需要的高并发性和数千个参数，更改正在运行的服务器的活动配置（通常称为“重新加载”）可能会很棘手。Caddy 使用具有许多优点的设计优雅地解决了这个问题：\n\n- 不中断运行服务\n- 可以进行粒度配置更改\n- 只需要一把锁（在后台）\n- 所有的重载都是原子的、一致的、隔离的，并且大多是持久的（“ACID”）\n- 最小的全局状态\n\n您可以[在此处观看有关 Caddy 2 设计的视频](https://www.youtube.com/watch?v=EhJO8giOqQs)。\n\n配置重新加载通过配置新模块来工作，如果全部成功，则清理旧模块。在短时间内，两个配置同时运行。\n\n每个配置都与一个包含所有模块状态的[上下文](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Context)相关联，因此大多数状态永远不会逃脱配置的范围。这对于正确性、性能和简单性来说是个好消息！\n\n然而，有时真正的全局状态是必要的。例如，反向代理可能会跟踪其上游的健康状况；由于全局每个上游只有一个，因此如果每次进行小的配置更改时都忘记它们，那将是很糟糕的。幸运的是，Caddy[提供了](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#UsagePool)类似于语言运行时的垃圾收集器的工具来保持全局状态的整洁。\n\n一种明显的在线配置更新方法是同步对每个配置参数的访问，即使在热路径中也是如此。这在性能和复杂性方面非常糟糕——&mdash;尤其&mdash;是在规模上——因此 Caddy 不使用这种方法。\n\n相反，配置被视为不可变的原子单元：要么整个东西被替换，要么什么都没有改变。[管理API端点](/docs/api)&mdash;&mdash;允许通过遍历结构进行精细更改——仅改变配置的内存表示，从中生成并加载一个全新的配置文档。这种方法在简单性、性能和一致性方面具有巨大的优势。由于只有一把锁，Caddy 很容易处理快速重新加载。\n\n"
  },
  {
    "path": "src/docs/markdown/automatic-https.md",
    "content": "---\ntitle: \"自动HTTPS\"\n---\n\n# 自动HTTPS\n\n**Caddy是第一个也是唯一一个_默认_自动使用HTTPS的Web服务器。**\n\n自动HTTPS为你的所有站点提供TLS证书并保持更新。它还为你将HTTP重定向到HTTPS！Caddy使用安全且现代的默认设置——无需停机、额外配置或单独的工具。\n\n<aside class=\"tip\">Caddy创新自动HTTPS技术；我们从2015年第一天就开始这样做了。Caddy的HTTPS自动化逻辑是世界上最成熟和最强大的。</aside>\n\n这是一个28秒的视频，展示了它的工作原理：\n\n<iframe width=\"100%\" height=\"480\" src=\"https://www.youtube-nocookie.com/embed/nk4EWHvvZtI?rel=0\" frameborder=\"0\" allowfullscreen=\"\"></iframe>\n\n\n**菜单：**\n\n- [概览(overview)](#overview)\n- [激活(Activation)](#activation)\n- [效果(Effects)](#effects)\n- [主机名要求(Hostname requirements)](#hostname-requirements)\n- [本地(Local)HTTPS](#local-https)\n- [测试(Testing)](#testing)\n- [ACME质询(Challenges)](#acme-challenges)\n- [按需(On-Demand)TLS](#on-demand-tls)\n- [错误(Errors)](#errors)\n- [存储(Storage)](#storage)\n- [通配符证书(Wildcard certificates)](#wildcard-certificates)\n\n\n\n## 概览(Overview)\n\n**默认情况下，Caddy通过HTTPS为所有站点提供服务。**\n\n- Caddy 使用本地自动信任的自签名证书（如果允许）通过 HTTPS 提供 IP 地址和本地/内部主机名。\n    - 例子：`localhost`、`127.0.0.1`\n- Caddy使用来自公开的ACME CA的证书，通过HTTPS提供公共DNS名称，例如[Let's Encrypt](https://letsencrypt.org)或者[ZeroSSL](https://zerossl.com).\n    - 例子：`example.com`、`sub.example.com`、`*.example.com`\n\nCaddy 会自动更新所有托管证书并将 HTTP（默认端口 80）重定向到 HTTPS（默认端口 443）。\n\n**对于本地 HTTPS：**\n\n- Caddy 可能会提示输入密码以将其唯一的根证书安装到你的信任库中。每个根只发生一次；你可以随时删除它。\n- 任何在不信任Caddy根目录的情况下访问该站点的客户端都会显示安全错误。\n\n**对于公共域名：**\n\n<aside class=\"tip\">\n    这些是任何基本生产网站的常见要求，而不仅仅是Caddy。主要区别是在运行 Caddy<b>之前</b>正确设置 DNS 记录，以便它可以提供证书。\n</aside>\n\n- 如果你的域的A/AAAA记录指向你的服务器，\n- 端口80和443对外开放，\n- Caddy 可以绑定到那些端口（ _或者_ 这些端口被转发到 Caddy），\n- 你的[data目录](/docs/conventions#data-directory)是可写且持久的，\n- 并且你的域名出现在配置中的相关位置，\n\n然后网站将自动通过 HTTPS 提供服务。你无需为此做任何其他事情。它就是管用！\n\n由于HTTPS使用共享的公共基础架构，因此作为服务器管理员，你应该了解此页面上的其余信息，以便避免不必要的问题，在问题发生时进行故障排除，并正确配置高级部署。\n\n\n## 激活(Activation)\n\n当Caddy知道它所服务的域名（即主机名）或IP地址时，它会隐式激活自动HTTPS。有多种方法可以告诉Caddy你的域名或IP，具体取决于你运行或配置Caddy的方式：\n\n- [Caddyfile](/docs/caddyfile)的[站点地址](/docs/caddyfile/concepts#addresses)\n- [路由](/docs/modules/http#servers/routes)中的[域名匹配器](/docs/json/apps/http/servers/routes/match/host/)\n- 命令行标志，如[--domain](/docs/command-line#caddy-file-server)或[--from](/docs/command-line#caddy-reverse-proxy)\n- [自动化](/docs/json/apps/tls/certificates/automate/)证书加载器\n\n以下任何一项都将阻止全部或部分激活自动HTTPS：\n\n- [明确禁用它](/docs/json/apps/http/servers/automatic_https/)\n- 在配置中不提供任何域名或IP\n- 仅监听HTTP端口\n- 手动加载证书（除非[这个配置属性](/docs/json/apps/http/servers/automatic_https/ignore_loaded_certificates/)为`true`）\n\n## 效果(Effects)\n\n激活自动HTTPS后，会发生以下情况：\n\n- [所有域名](#hostname-requirements)的证书会被获取和更新\n- 默认端口（如果有）会被更改为[HTTPS端口](/docs/modules/http#https_port)`443`\n- HTTP（使用[HTTP端口](/docs/modules/http#http_port)`80`）会被重定向到HTTPS\n\n自动HTTPS永远不会覆盖显式配置。\n\n如有必要，你可以[自定义或禁用HTTPS](/docs/json/apps/http/servers/automatic_https/)；例如，你可以跳过某些域名或禁用重定向（对于 Caddyfile，请使用[全局选项](/docs/caddyfile/options)执行该操作）。\n\n\n## 主机名要求(Hostname requirements)\n\n如果所有主机名（域名）符合以下条件，则它们都有资格获得完全托管的证书：\n\n- 非空\n- 仅由字母数字、连字符、点和通配符（`*`）组成\n- 不要以点开头或结尾（[RFC 1034](https://tools.ietf.org/html/rfc1034#section-3.5)）\n\n此外，如果主机名符合以下条件，则它们有资格获得公开信任的证书：\n\n- 不是本地主机（包括`.localhost`和`.local`TLD）\n- 不是IP地址\n- 只有一个通配符`*`作为最左边的标签\n\n\n## 本地(Local) HTTPS\n\n为了通过 HTTPS 为非公共站点提供服务，Caddy 生成自己的证书颁发机构 (CA) 并使用它来签署证书。信任链由根证书和中间证书组成。叶证书由中间人签名。它们存储在[Caddy的数据目录](/docs/conventions#data-directory)中的`pki/authorities/local`。\n\nCaddy 的本地CA由[Smallstep库](https://smallstep.com/certificates/)提供支持。\n\n本地HTTPS不使用ACME，也不执行任何DNS验证。它仅在本地机器上工作，并且仅在安装了CA的根证书的地方才受信任。\n\n### CA根(Root)\n\n根的私钥是使用加密安全的伪随机源唯一生成的，并以有限的权限保存到存储中。它仅被加载到内存中以执行签名任务，之后它留下了垃圾收集的范围。\n\n虽然可以将Caddy配置为直接使用root签名（以支持不兼容的客户端），但默认情况此功能被禁用，且root密钥仅用于签署中间体。\n\n第一次使用根密钥时，Caddy将尝试将其安装到系统的本地信任库中。如果它没有权限这样做，它会提示输入密码。如果不需要，可以在配置中禁用此行为。\n\n<aside class=\"tip\">\n只要你的计算机没有受到威胁并且你的唯一根密钥没有泄露，那么在你自己的计算机上信任 Caddy 的根证书是安全的。\n</aside>\n\n安装 Caddy 的根 CA 后，你将在本地信任存储中看到它作为“Caddy Local Authority”（除非你配置了不同的名称）。如果你愿意，你可以随时卸载它（该[`caddy untrust`](/docs/command-line#caddy-untrust)命令使这很容易）。\n\n请注意，自动将证书安装到本地信任存储中只是为了方便，不能保证能正常工作，尤其是在使用容器或Caddy作为非特权系统服务运行的情况下。最后，如果你依赖内部PKI，系统管理员有责任确保将Caddy的根CA正确添加到必要的信任存储中（这超出了Web服务器的范围）。\n\n### CA中间体(Intermediates)\n\n中间证书和密钥也将被生成，用于签署叶(leaf)（单个站点）证书。\n\n与根证书不同，中间证书的生命周期要短得多，并且会根据需要自动更新。\n\n\n## 测试(Testing)\n\n要测试或试验你的 Caddy 配置，请确保[将ACME端点更改](/docs/modules/tls.issuance.acme#ca)为暂存或开发URL，否则你可能会达到速率限制，这可能会阻止你访问HTTPS长达一周，具体取决于你遇到的速率限制。\n\nCaddy的默认CA 之一是[Let's Encrypt](https://letsencrypt.org/)，它有一个[暂存端点](https://letsencrypt.org/docs/staging-environment/)不被相同的[速率限制](https://letsencrypt.org/docs/rate-limits/)影响：\n\n```\nhttps://acme-staging-v02.api.letsencrypt.org/directory\n```\n\n## ACME质询(challenges)\n\n获取公众信任的 TLS 证书需要来自公众信任的第三方机构的验证。如今，此验证过程通过[ACME协议](https://tools.ietf.org/html/rfc8555)自动执行，并且可以执行以下三种方式之一（“质询类型”），如下所述。\n\n前两种质询类型默认启用。如果启用了多种质询，Caddy会随机选择一种，以避免依赖特定质询带来的意外。随着时间的推移，它会了解哪种质询类型最成功，并会开始倾向于选取它，但如果有必要，它还是会重新选取其他可用的质询类型。\n\n### HTTP质询(challenge)\n\nHTTP质询对候选主机名的A/AAAA记录执行权威DNS查找，然后使用HTTP通过端口80请求临时加密资源。如果CA看到预期的资源，则会颁发证书。\n\n此质询要求端口80可从外部访问。如果Caddy无法监听80端口，则必须将来自80端口的数据包转发到Caddy的[HTTP端口](/docs/json/apps/http/http_port/)。\n\n默认情况下启用此质询，不需要显式配置。\n\n\n### TLS-ALPN质询(challenge)\n\nTLS-ALPN 质询对候选主机名的A/AAAA记录执行权威DNS查找，然后使用包含特殊ServerName和ALPN值的TLS握手通过端口443请求临时加密资源。如果CA看到预期的资源，则会颁发证书。\n\n此种质询要求端口443可从外部访问。如果Caddy无法监听443端口，则必须将来自443端口的数据包转发到Caddy的[HTTPS端口](/docs/json/apps/http/https_port/)。\n\n默认情况下启用此质询，不需要显式配置。\n\n\n### DNS质询(challenge)\n\nDNS质询对候选主机名的TX 记录执行权威DNS查找，并查找具有特定值的特殊TXT记录。如果CA看到预期值，则颁发证书。\n\n此质询不需要任何开放端口，并且请求证书的服务器不需要可从外部访问。但是，DNS 质询需要配置。Caddy 需要知道访问你域的 DNS 提供商的凭据，以便它可以设置（和清除）特殊的 TXT 记录。如果启用 DNS 质询，则默认禁用其他质询。\n\nDNS提供商支持是一项社区工作。[在我们的wiki上了解如何为你的提供商启用DNS质询](https://caddy.community/t/how-to-use-dns-provider-modules-in-caddy-2/8148)。\n\n\n## 按需(On-Demand) TLS\n\nCaddy开创了一种我们称为**按需(On-Demand) TLS**的新技术，它在需要它的第一次TLS握手期间动态获取新证书，而不是在配置加载时。至关重要的是，这不需要提前在配置中指定域名。\n\n许多企业依靠这一独特的功能以更低的成本扩展他们的 TLS 部署，并且在为数万个站点提供服务时不会遇到运营难题。\n\n按需 TLS 在以下情况下很有用：\n\n- 当你启动或重新加载服务器时，你并不知道所有域名，\n- 域名可能未立即正确配置（尚未设置 DNS 记录），\n- 你无法控制域名（例如，它们是客户的域名）。\n\n启用按需 TLS 后，你无需在配置中指定域名即可为其获取证书。相反，当收到 Caddy 尚未获得证书的服务器名称 (SNI) 的 TLS 握手时，会在 Caddy 获得用于完成握手的证书时保持握手。延迟通常只有几秒钟，只有最初的握手很慢。所有未来的握手都很快，因为证书被缓存和重用，并且更新发生在后台。未来的握手可能会触发对证书的维护以使其保持更新，但如果证书尚未过期，则此维护将在后台进行。\n\n### 使用按需(Using On-Demand) TLS\n\n**在生产环境中，必须同时启用和限制按需TLS。在不限制的情况下启用会使你的服务器受到攻击。**\n\n如果使用 JSON 配置，则在[TLS自动化策略](/docs/json/apps/tls/automation/policies/)中启用按需TLS，如果使用Caddyfile，则在带有[带有`tls`指令的站点块](/docs/caddyfile/directives/tls)中启用它。\n\n为防止滥用此功能，你必须配置限制。这是在[`automation`JSON配置对象](/docs/json/apps/tls/automation/on_demand/)或Caddyfile的[`on_demand_tls`全局选项](/docs/caddyfile/options#on-demand-tls)中完成的。限制是“全局的”，不能按站点或按域进行配置。主要限制是“询问”端点，Caddy 将向其发送 HTTP 请求以询问它是否有权在握手中获取和管理域的证书。这意味着你将需要一些内部后端，例如，可以查询数据库的帐户表并查看客户是否已使用该域名注册。\n\n你还可以将速率限制配置为限制，但仅速率限制不足以提供保护。\n\n请注意你的 CA 能够以多快的速度颁发证书。如果花费的时间超过几秒钟，这将对用户体验产生负面影响（仅适用于第一个客户端）。\n\n由于它的延迟性质和滥用的可能性（如果没有通过适当的配置来缓解），我们建议仅在你的实际用例在上面描述时才启用按需 TLS。\n\n[有关有效使用按需TLS的更多信息，请参阅我们的wiki文章](https://caddy.community/t/serving-tens-of-thousands-of-domains-over-https-with-caddy/11179)。\n\n## 错误(Errors)\n\n如果证书管理出现错误，Caddy会尽力继续。\n\n默认情况下，证书管理在后台执行。这意味着它不会阻止启动或减慢你的网站。但是，这也意味着服务器将在所有证书可用之前运行。在后台运行允许Caddy在很长一段时间内以指数退避重试。\n\n如果在获取或更新证书时出错，会发生以下情况：\n\n1. Caddy在短暂暂停后重试一次，以防万一这是偶然事件\n2. Caddy短暂停顿，然后切换到下一个启用的质询类型\n3. 在尝试了所有启用的质询类型后，[它会尝试下一个配置的颁发者](#issuer-fallback)\n    - Let's Encrypt\n    - ZeroSSL\n4. 在尝试了所有发行人之后，它会以指数方式回退\n    - 尝试之间最多间隔 1 天\n    - 长达30天\n\n在使用Let's Encrypt重试期间，Caddy 切换到他们的[暂存环境](https://letsencrypt.org/docs/staging-environment/)以避免速率限制问题。这不是一个完美的策略，但总的来说它很有帮助。\n\nACME质询至少需要几秒钟才能完成，内部速率限制有助于减少意外滥用。除了你或CA配置的内容之外，Caddy还使用内部速率限制，这样你就可以给Caddy一个包含一百万个域名的配置(platter)，它会逐渐（但尽可能快地）获得所有域名的证书。Caddy的内部速率限制目前是每个ACME帐户每10秒尝试10次。\n\n为避免资源泄漏，Caddy会在配置更改时中止正在进行的任务（包括ACME事务）。虽然Caddy能够处理频繁的配置重新加载，但请注意诸如此类的操作注意事项，并考虑批量配置更改以减少重新加载并让Caddy有机会在后台实际完成获取证书。\n\n### 发行人后备(Issuer fallback)\n\nCaddy 是第一个（也是迄今为止唯一一个）在无法成功获得证书时支持完全冗余、自动故障转移到其他CA的服务器。\n\n默认情况下，Caddy启用两个与ACME兼容的CA：[**Let's Encrypt**](https://letsencrypt.org)和[**ZeroSSL**](https://zerossl.com)。如果Caddy无法从Let's Encrypt获得证书，它将尝试使用ZeroSSL；如果两者都失败，它将退避并稍后重试。在你的配置中，你可以自定义Caddy使用哪些颁发者来获取证书，可以是通用的，也可以是特定名称的。\n\n## 存储(Storage)\n\nCaddy 将在其[配置的存储设施](/docs/json/storage/)中存储公共证书、私钥和其他资产（或默认存储设施，如果未配置 - 请参阅链接了解详细信息）。\n\n**使用默认配置需要了解的主要内容是该`$HOME`文件夹必须是可写且持久的。`--environ`的作用是帮助你排除故障，如果指定了标志，Caddy会在启动时打印该环境变量。\n\n任何配置为使用相同存储的Caddy实例都将自动共享这些资源并作为集群协调证书管理。\n\n在尝试任何 ACME 事务之前，Caddy 将测试配置的存储以确保它是可写的并且有足够的容量。这有助于减少不必要的锁争用。\n\n## 通配符证书(Wildcard certificates)\n\n当 Caddy 配置为使用合格的通配符名称为站点提供服务时，它可以获取和管理通配符证书。如果只有最左边的域标签是通配符，则站点名称有资格使用通配符。例如，`*.example.com`符合条件，但这些不符合条件：`sub.*.example.com`、`foo*.example.com`、`*bar.example.com`和`*.*.example.com`。\n\n如果使用 Caddyfile，Caddy 会根据证书主题名称获取站点名称。换句话说，定义为的站点`sub.example.com`将导致 Caddy管理`sub.example.com`的证书, ，定义为的站点`*.example.com`将导致Caddy管理`*.example.com`的通配符证书。你可以在我们的[常用Caddyfile模式](/docs/caddyfile/patterns#wildcard-certificates)页面上看到这一点。如果你需要不同的行为，JSON 配置可以让你更精确地控制证书主题和站点名称（“主机匹配器”）。\n\n通配符证书代表了广泛的权限，并且仅应在你拥有如此多的子域以致为它们管理单个证书会使 PKI 紧张或导致你达到 CA 强制的速率限制时使用。\n\n**注意：** [Let's Encrypt](https://letsencrypt.org/docs/challenge-types/)需要使用[DNS质询](#dns-challenge)才能获得通配符证书。\n\n## 加密的ClientHello（ECH）\n\n通常在TLS握手时，ClientHello（包含SNI，即目标域名）会以明文传输。ECH通过“外层”ClientHello包裹真实的“内层”ClientHello，从而保护真实域名不在链路上明文暴露。\n\nCaddy可以自动生成、发布并提供ECH配置。要让ECH真正产生隐私收益，还需要DNS发布、客户端支持与安全DNS查询（如DoH/DoT）等条件共同满足。\n\n### 部署注意事项\n\nECH是一个系统性能力，部署时应重点关注以下几点：\n\n- DNS中需要有对应域名记录，Caddy才会为其发布HTTPS类型记录中的ECH配置。\n- 客户端即使看到`encrypted_client_hello`扩展，也不代表已成功使用ECH，排障时应结合SNI值判断。\n- ECH密钥需要轮换，并在一段时间内兼容旧配置，避免客户端缓存导致连接失败。\n- 建议统一使用一个public name来增大匿名集，但也要评估集中化带来的运维与信任边界问题。\n- 若要保护子域名隐私，应同时评估通配符证书、DNSSEC配置与客户端能力。\n\n#### 发布（Publication）\n\nCaddy仅会为已有DNS记录的域名发布HTTPS类型记录中的ECH配置；若只有通配符记录，请确保配置中也包含对应通配符域名。\n\n#### ECH GREASE\n\n现代浏览器常会发送带`encrypted_client_hello`扩展的握手以增加不可区分性。排障时请不要仅凭扩展存在与否判断是否启用ECH。\n\n#### 密钥轮换（Key rotation）\n\nECH密钥应定期轮换，并在一段时间内继续兼容旧配置，以覆盖DNS缓存传播周期，减少握手失败或明文回退风险。\n\n#### Public name\n\nouter SNI中的public name必须由你的服务器可权威响应，且建议使用统一、通用的名称，以便在配置更新时为客户端提供带内更新路径。\n\n#### 匿名集（Anonymity set）\n\n匿名集越大，旁路观察者越难推断真实目标。实践中建议同一组织尽量收敛到较少public name。\n\n#### 集中化（Centralization）\n\nECH通常与DoH/DoT联动，可能在提升隐私的同时带来流量与依赖集中化。部署时应结合自身威胁模型权衡。\n\n#### 子域名隐私（Subdomain privacy）\n\n如需降低子域名泄露风险，可结合通配符证书、合理DNSSEC策略与ECH一并部署；同时避免在域名中放置敏感信息。\n\n### 启用ECH\n\nECH依赖将配置发布到DNS，因此你需要使用包含对应DNS提供商模块（`caddy-dns`）的Caddy构建。\n\n在Caddyfile中，可在全局选项中配置DNS提供商与ECH public name：\n\n```caddy\n{\n\tdns <provider config...>\n\tech example.com\n}\n```\n\n若使用JSON，可在`tls`应用中配置：\n\n```json\n\"encrypted_client_hello\": {\n\t\"configs\": [\n\t\t{\n\t\t\t\"public_name\": \"example.com\"\n\t\t}\n\t]\n},\n\"dns\": {\n\t\"name\": \"<provider name>\"\n}\n```\n\n### 验证ECH\n\n可通过抓包（如Wireshark）确认SNI字段是否显示public name而非真实站点域名；这通常是验证ECH是否生效的关键指标。\n\n部署检查建议：\n\n- 启动后确认日志中出现ECH配置发布成功信息。\n- 确认浏览器已启用ECH，并在必要时启用DoH/DoT。\n- 清理DNS缓存并建立新连接后再抓包验证，避免复用旧连接影响判断。\n\n### 存储中的ECH\n\nECH配置会存放在[数据目录](/docs/conventions#data-directory)的存储模块中（默认文件系统），位于`ech/configs`路径下。元数据文件用于记录发布时间，避免每次重载都重复向DNS提供商发布。必要时可删除元数据触发重建，但应注意这也可能影响轮换时间点。\n"
  },
  {
    "path": "src/docs/markdown/build.md",
    "content": "---\ntitle: 从源码构建\n---\n\n# 从源码构建\n\n如果你需要自定义构建（例如带插件），构建Caddy有多种方式：\n- [Git](#git)：从Git仓库构建\n- [`xcaddy`](#xcaddy)：使用`xcaddy`构建\n- [Docker](#docker)：构建自定义Docker镜像\n\n要求：\n\n- [Go](https://golang.org/doc/install) 1.20或更高版本\n\n[Package Support Files](#package-support-files-for-custom-builds-for-debianubunturaspbian)一节包含给这类用户的说明：已在Debian衍生系统上通过APT安装Caddy，但仍需要自定义构建可执行文件用于生产运维。\n\n\n\n## Git\n\n要求：\n\n- 已安装Go（见上文）\n\n克隆仓库：\n\n<pre><code class=\"cmd bash\">git clone \"https://github.com/caddyserver/caddy.git\"</code></pre>\n\n如果你没有git，也可以[从GitHub](https://github.com/caddyserver/caddy)下载源码压缩包。每个[发布版本](https://github.com/caddyserver/caddy/releases)也提供源码快照。\n\n构建：\n\n<pre><code class=\"cmd\"><span class=\"bash\">cd caddy/cmd/caddy/</span>\n<span class=\"bash\">go build</span></code></pre>\n\n\n<aside class=\"tip\">\n\n由于[Go中的一个bug](https://github.com/golang/go/issues/29228)，以上基础步骤不会嵌入版本信息。若你需要版本号（`caddy version`），应将Caddy作为依赖而非主模块来编译。具体说明在Caddy的[main.go](https://github.com/caddyserver/caddy/blob/master/cmd/caddy/main.go)文件中。或者可使用[`xcaddy`](#xcaddy)自动处理。\n\n</aside>\n\nGo程序很容易交叉编译到其他平台。只需设置不同的`GOOS`、`GOARCH`和/或`GOARM`环境变量。（详见[go文档](https://golang.org/doc/install/source#environment)）\n\n例如，当你当前不在Windows上时，编译Windows版Caddy：\n\n<pre><code class=\"cmd bash\">GOOS=windows go build</code></pre>\n\n类似地，当你当前不在Linux或ARMv6上时，编译Linux ARMv6版：\n\n<pre><code class=\"cmd bash\">GOOS=linux GOARCH=arm GOARM=6 go build</code></pre>\n\n\n\n## xcaddy\n\n[`xcaddy`命令](https://github.com/caddyserver/xcaddy)是构建带版本信息和/或插件Caddy的最简单方式。\n\n要求：\n\n- 已安装Go（见上文）\n- 确保[`xcaddy`](https://github.com/caddyserver/xcaddy/releases)在你的`PATH`中\n\n你**不需要**下载Caddy源码（它会自动完成）。\n\n然后，构建Caddy（包含版本信息）只需：\n\n<pre><code class=\"cmd bash\">xcaddy build</code></pre>\n\n若要携带插件构建，使用`--with`：\n\n<pre><code class=\"cmd bash\">xcaddy build \\\n    --with github.com/caddyserver/nginx-adapter\n\t--with github.com/caddyserver/ntlm-transport@v0.1.1</code></pre>\n\n如你所见，可以通过`@`语法指定插件版本。版本可以是tag名、commit SHA或分支。\n\n使用`xcaddy`的跨平台编译方式与`go`命令一致。例如，为macOS交叉编译：\n\n<pre><code class=\"cmd bash\">GOOS=darwin xcaddy build</code></pre>\n\n\n\n## Docker\n\n你可以使用`:builder`镜像，快速构建带自定义模块的新Caddy二进制：\n\n```Dockerfile\nFROM caddy:<version>-builder AS builder\n\nRUN --mount=type=cache,target=/go/pkg/mod \\\n    --mount=type=cache,target=/root/.cache/go-build \\\n    xcaddy build \\\n    --with github.com/caddyserver/nginx-adapter \\\n    --with github.com/hairyhenderson/caddy-teapot-module@v0.0.3-0\n\nFROM caddy:<version>\n\nCOPY --from=builder /usr/bin/caddy /usr/bin/caddy\n```\n\n请将`<version>`替换为当前最新Caddy版本作为起点。\n\n注意第二个`FROM`指令：它只是把新构建的二进制覆盖到常规`caddy`镜像上，因此产物镜像会小很多。\n\n构建阶段用`xcaddy`构建并注入指定模块，过程与[上文](#xcaddy)一致。`--mount=type=cache,target=/go/pkg/mod`和`--mount=type=cache,target=/root/.cache/go-build`分别用于缓存Go模块依赖和构建产物，可显著加速后续构建。该参数是[Docker特性](https://docs.docker.com/build/cache/optimize/#use-cache-mounts)，不是`xcaddy`特性。\n\n若使用Docker Compose，请参见我们推荐的[`compose.yml`](/docs/running#docker-compose)及使用说明。\n\n\n\n## Package support files for custom builds for Debian/Ubuntu/Raspbian\n\n该流程旨在：运行自定义`caddy`二进制的同时，保留`caddy`软件包中的支持文件。\n\n该流程让用户可以继续利用官方包中的默认配置、systemd服务文件和bash补全。\n\n要求：\n- 按[这些说明](/docs/install#debian-ubuntu-raspbian)安装`caddy`软件包\n- 构建你的自定义`caddy`二进制（见上文），或从[下载页](/download)下载自定义构建\n- 你的自定义`caddy`二进制应位于当前目录\n\n流程：\n<pre><code class=\"cmd\"><span class=\"bash\">sudo dpkg-divert --divert /usr/bin/caddy.default --rename /usr/bin/caddy</span>\n<span class=\"bash\">sudo mv ./caddy /usr/bin/caddy.custom</span>\n<span class=\"bash\">sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.default 10</span>\n<span class=\"bash\">sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.custom 50</span>\n<span class=\"bash\">sudo systemctl restart caddy</span>\n</code></pre>\n\n说明：\n\n- `dpkg-divert`会把`/usr/bin/caddy`二进制移动到`/usr/bin/caddy.default`，并设置diversion，以防后续软件包再向该路径安装文件。\n\n- `update-alternatives`会创建到`/usr/bin/caddy`的符号链接，指向你期望的caddy二进制。\n\n- `systemctl restart caddy`会停止默认版本Caddy服务并启动自定义版本。\n\n你可以执行下面命令并按屏幕提示，在默认/自定义`caddy`二进制之间切换；完成后重启Caddy服务。\n\n<pre><code class=\"cmd bash\">update-alternatives --config caddy</code></pre>\n\n完成上述配置后，你可运行[`caddy upgrade`](/docs/command-line#caddy-upgrade)来升级Caddy。它会尝试按你当前构建中的同款插件组合，下载带最新Caddy版本的新构建，然后替换当前二进制。\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/concepts.md",
    "content": "---\ntitle: Caddyfile概念\n---\n\n# Caddyfile概念\n\n本文档将帮助你详细了解HTTP Caddyfile。\n\n1. [结构](#structure)\n2. [地址](#addresses)\n3. [匹配器](#matchers)\n4. [占位符](#placeholders)\n5. [片段](#snippets)\n6. [注释](#comments)\n7. [环境变量](#environment-variables)\n8. [全局选项](#global-options)\n\n\n<h2 id=\"structure\">结构</h2>\n\n下面的图片可以直观地描述Caddyfile的结构：\n\n![Caddyfile的结构](/resources/images/caddyfile-visual.png)\n\n关键点：\n\n- 可选的**全局选项块**可以放在文件的头部\n- 否则, Caddyfile的首行**总是**要提供服务的网站地址。\n- 所有指令和匹配器都**必须**放在站点块中。跨站点块没有全局范围或继承。\n- 如果**只有一个站点块**，则其花括号`{ }`是可选的。\n\n一个Caddyfile至少包含一个或多个站点块，这些块总是以站点的一个或多个[地址](#addresses)开始。出现在地址之前的任何指令都会使扰乱解析器。\n\n### 块\n\n打开和关闭一个**块**是用花括号完成的：\n\n```\n... {\n\t...\n}\n```\n\n- 打开的花括号`{`必须位于行尾。\n- 大括号`}`必须独占一行。\n\n当只有一个站点块时，花括号（和缩进）是可选的。这是为了方便快速定义单个站点，例如：\n\n```caddy\nlocalhost\n\nreverse_proxy /api/* localhost:9001\nfile_server\n```\n\n相当于：\n\n```caddy\nlocalhost {\n\treverse_proxy /api/* localhost:9001\n\tfile_server\n}\n```\n\n当你只有一个站点块时；这是一个偏好问题。\n\n要使用相同的Caddyfile配置多个站点，你必须在每个站点周围使用花括号来分隔它们的配置：\n\n```caddy\nexample1.com {\n\troot * /www/example.com\n\tfile_server\n}\n\nexample2.com {\n\treverse_proxy localhost:9000\n}\n```\n\n如果一个请求匹配多个站点块，则选择具有最具体匹配地址的站点块。请求不会级联到其他站点块。\n\n### 指令\n\n[**指令**](/docs/caddyfile/directives)是自定义网站服务方式的关键字。例如，完整的文件服务器配置可能如下所示：\n\n```caddy\nlocalhost\n\nfile_server\n```\n\n或反向代理：\n\n```caddy\nlocalhost\n\nreverse_proxy localhost:9000\n```\n\n在这些示例中，[`file_server`](/docs/caddyfile/directives/file_server)和[`reverse_proxy`](/docs/caddyfile/directives/reverse_proxy)是指令。指令是站点块中一行的第一个单词。\n\n在第二个示例中，`localhost:9000`是一个**参数**，因为它出现在指令之后的同一行。\n\n请注意，当调整 Caddyfile 时，指令会根据特定的默认[指令顺序](/docs/caddyfile/directives#directive-order)进行排序。\n\n**子指令**可以出现在指令块中：\n\n```caddy\nlocalhost\n\nreverse_proxy localhost:9000 localhost:9001 {\n\tlb_policy first\n}\n```\n\n这里，`lb_policy`是[`reverse_proxy`](/docs/caddyfile/directives/reverse_proxy)的子指令（它用于设置后端之间使用的负载平衡策略）。\n\n\n### 标记和引号\n\nCaddyfile在被解析之前被词法解析成标记。空格在Caddyfile中很重要，因为标记就是由它进行分隔。\n\n通常，指令需要一定数量的参数。如果单个参数有一个带有空格的值，它会被作为两个单独的标记进行词法分析：\n\n```caddy-d\ndirective abc def\n```\n\n这可能会出现问题并返回错误或意外行为。\n\n如果`abc def`应该是单个参数的值，则需要使用引号：\n\n```caddy-d\ndirective \"abc def\"\n```\n\n如果你也需要在带引号的标记中使用引号，则可以转义引号：\n\n```caddy-d\ndirective \"\\\"abc def\\\"\"\n```\n\n在带引号的标记内，所有其他字符都按字面意思处理，包括空格、制表符和换行符。\n\n你还可以使用反引号`来引用标记：\n\n```caddy-d\ndirective `\"foo bar\"`\n```\n\n当标记包含引号文字时，反引号字符串很方便，例如JSON文本。\n\n\n<h2 id=\"addresses\">地址</h2>\n\n地址总是出现在站点块的顶部，并且通常出现在Caddyfile中的第一行。\n\n这些是有效地址的示例：\n\n- `localhost`\n- `example.com`\n- `:443`\n- `http://example.com`\n- `localhost:8080`\n- `127.0.0.1`\n- `[::1]:2015`\n- `example.com/foo/*`\n- `*.example.com`\n- `http://`\n\n<aside class=\"tip\">\n    如果你的站点地址包含主机名或 IP 地址，则会启用<a href=\"/docs/automatic-https\">自动HTTPS</a>。然而，这种行为纯粹是隐含的，因此它永远不会覆盖任何显式配置。例如，如果站点的地址是<code>http://example.com</code>，则不会激活自动HTTPS，因为该方案是明确的<code>http://</code>。\t\n</aside>\n\n从地址中，Caddy可以潜在地推断出你站点的方案、主机、端口和路径。\n\n如果你指定主机名，则只会处理具有匹配Host标头的请求。换句话说，如果站点地址是`localhost`，那么Caddy将不会匹配到`127.0.0.1`的请求。\n\n可以使用通配符(`*`)，但仅代表主机名的一个标签。例如，`*.example.com`匹配`foo.example.com`，但不匹配`foo.bar.example.com`，`*`匹配`localhost`，但不匹配`example.com`。要捕获所有主机，请省略地址的主机部分。\n\n如果多个站点共享相同的定义，你可以将所有站点一起列出：\n\n```caddy\nlocalhost:8080, example.com, www.example.com\n```\n\n或者\n\n```caddy\nlocalhost:8080,\nexample.com,\nwww.example.com\n```\n\n请注意逗号如何表示地址的延续。\n\n地址必须是唯一的；你不能多次指定同一个地址。\n\n\n\n<h2 id=\"matchers\">匹配器</h2>\n\n默认情况下，注入HTTP处理程序的指令适用于所有请求（除非另有说明）。\n\n请求匹配器可用于按给定标准对请求进行分类。这个概念源于[底层的JSON结构](/docs/json/apps/http/servers/routes/match/)，知道如何在Caddyfile中使用它们很重要。使用匹配器，你可以准确指定某个指令适用于哪些请求。\n\n对于支持匹配器的指令，指令后的第一个参数是**匹配器标记**。这里有些例子：\n\n```caddy-d\nroot *           /var/www  # matcher token: *\nroot /index.html /var/www  # matcher token: /index.html\nroot @post       /var/www  # matcher token: @post\n```\n\n完全省略匹配器标记，则可以匹配所有的请求；例如，如果下一个参数看起来不像路径匹配器，则不需要给出`*`。\n\n**[阅读有关请求匹配器的页面](/docs/caddyfile/matchers)，了解更多信息。**\n\n\n\n\n<h2 id=\"placeholders\">占位符</h2>\n\n你可以在Caddyfile中使用任何[Caddy占位符](/docs/conventions#placeholders)，但为方便起见，你还可以使用一些等效的速记符：\n\n| 简写                                    | 替换                                                 |\n|---------------------------------------|----------------------------------------------------|\n| `{dir}`                               | `{http.request.uri.path.dir}`                      |\n| `{file}`                              | `{http.request.uri.path.file}`                     |\n| `{header.*}`                          | `{http.request.header.*}`                          |\n| `{host}`                              | `{http.request.host}`                              |\n| `{labels.*}`                          | `{http.request.host.labels.*}`                     |\n| `{hostport}`                          | `{http.request.hostport}`                          |\n| `{port}`                              | `{http.request.port}`                              |\n| `{method}`                            | `{http.request.method}`                            |\n| `{path}`                              | `{http.request.uri.path}`                          |\n| `{path.*}`                            | `{http.request.uri.path.*}`                        |\n| `{query}`                             | `{http.request.uri.query}`                         |\n| `{query.*}`                           | `{http.request.uri.query.*}`                       |\n| `{re.*.*}`                            | `{http.regexp.*.*}`                                |\n| `{remote}`                            | `{http.request.remote}`                            |\n| `{remote_host}`                       | `{http.request.remote.host}`                       |\n| `{remote_port}`                       | `{http.request.remote.port}`                       |\n| `{scheme}`                            | `{http.request.scheme}`                            |\n| `{uri}`                               | `{http.request.uri}`                               |\n| `{tls_cipher}`                        | `{http.request.tls.cipher_suite}`                  |\n| `{tls_version}`                       | `{http.request.tls.version}`                       |\n| `{tls_client_fingerprint}`            | `{http.request.tls.client.fingerprint}`            |\n| `{tls_client_issuer}`                 | `{http.request.tls.client.issuer}`                 |\n| `{tls_client_serial}`                 | `{http.request.tls.client.serial}`                 |\n| `{tls_client_subject}`                | `{http.request.tls.client.subject}`                |\n| `{tls_client_certificate_pem}`        | `{http.request.tls.client.certificate_pem}`        |\n| `{tls_client_certificate_der_base64}` | `{http.request.tls.client.certificate_der_base64}` |\n| `{upstream_hostport}`                 | `{http.reverse_proxy.upstream.hostport}`           |\n\n\n\n<h2 id=\"snippets\">片段</h2>\n\n你可以定义称为片段的特殊块，方法是给它们一个用括号括起来的名称：\n\n```caddy\n(redirect) {\n\t@http {\n\t\tprotocol http\n\t}\n\tredir @http https://{host}{uri}\n}\n```\n\n然后你可以在任何你需要的地方重复使用它：\n\n```caddy-d\nimport redirect\n```\n\n[`import`](/docs/caddyfile/directives/import) 指令还可用于在其位置包含其他文件。作为一种特殊情况，它几乎可以出现在Caddyfile中的任何位置。\n\n你可以将参数传递给导入的配置并像这样使用它们：\n\n```caddy\n(snippet) {\n  respond \"Yahaha! You found {args.0}!\"\n}\n\na.example.com {\n\timport snippet \"Example A\"\n}\n\nb.example.com {\n\timport snippet \"Example B\"\n}\n```\n\n\n<h2 id=\"comments\">注释</h2>\n\n注释从行首的`#`开始并一直持续到行尾：\n\n```caddy-d\n# Comments can start a line\ndirective  # or go at the end\n```\n\n哈希字符`#`不能出现在标记的中间（即它必须以空格开头或出现在行首）。这允许在URI或其他值中使用它而不需要引号。\n\n<h2 id=\"environment-variables\">环境变量</h2>\n\n如果你的配置依赖于环境变量，你可以在Caddyfile中使用它们：\n\n```caddy\n{$SITE_ADDRESS}\n```\n这种形式的环境变量在解析开始之前被替换，因此它们可以扩展为空值、部分标记、完整标记，甚至是多个标记和行。\n\n当未找到环境变量时，可以指定默认值，方法是使用`:`变量名和默认值之间的分隔符：\n\n```caddy\n{$DOMAIN:localhost}\n```\n\n如果你想将环境变量的替换推迟到运行时，你可以使用[标准`{env.*}`占位符](/docs/conventions#placeholders)。\n\n\n<h2 id=\"global-options\">全局选项</h2>\n\nCaddyfile可以选择以没有键的特殊块开始，称为[全局选项块](/docs/caddyfile/options)：\n\n```caddy\n{\n\t...\n}\n```\n\n如果存在，它必须是配置中的第一个块。\n\n它用于设置全局适用的选项，或不适用于任何特定站点。在里面，只能设置全局选项；你不能在其中使用常规站点指令。\n\n点击[了解](/docs/caddyfile/options)有关全局选项块的更多信息。"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/abort.md",
    "content": "---\ntitle: abort (Caddyfile指令)\n---\n\n# abort\n\n通过立即中止HTTP处理链和关闭连接，阻止对客户端的任何响应。同一连接上的任何并发的、活动的HTTP流都会被中断。\n\n\n## 语法\n\n```caddy-d\nabort [<matcher>]\n```\n\n## 示例\n\n当使用通配符证书时，强行关闭收到的未定义域名的连接。\n\n```caddy\n*.example.com {\n    @foo host foo.example.com\n    handle @foo {\n        respond \"This is foo!\" 200\n    }\n\n    # 未定义的域名会落到这里，但我们不想接收它们的请求\n    handle {\n        中止\n    }\n}\n```"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/acme_server.md",
    "content": "---\ntitle: acme_server (Caddyfile指令)\n---\n\n# acme_server\n\n一个嵌入式[ACME协议](https://tools.ietf.org/html/rfc8555)服务器处理程序。这允许一个Caddy实例为任何其他兼容ACME的软件（包括其他Caddy实例）签发证书。\n\n启用后，匹配路径`/acme/*`的请求将由ACME服务器处理。\n\n\n## 客户端配置\n\n使用ACME服务器的默认值时，ACME客户端只需配置为使用`https://localhost/acme/local/directory`作为它们的ACME端点。（`local`是Caddy默认CA的ID。）\n\n\n## 语法\n\n```caddy-d\nacme_server [<matcher>] {\n\tca         <id>\n\tlifetime   <duration>\n\tresolvers  <resolvers...>\n\tchallenges <challenges...>\n\tallow_wildcard_names\n\tallow {\n\t\tdomains <domains...>\n\t\tip_ranges <addresses...>\n\t}\n\tdeny {\n\t\tdomains <domains...>\n\t\tip_ranges <addresses...>\n\t}\n}\n```\n\n- **ca** 指定用于签署证书的证书颁发机构ID。默认值是`local`，也就是Caddy的默认CA，用于本地使用的自签名证书，这在开发环境中最常见。若用于更广泛的场景，建议指定不同的CA以避免混淆。如果给定ID的CA尚不存在，它将被创建。参见[PKI应用全局选项](/docs/caddyfile/options#pki-options)来配置其他CA。\n\n- **lifetime**（默认：`12h`）是一个[持续时间](/docs/conventions#durations)，指定已签发证书的有效期。该值必须小于用于签名的[中间证书](/docs/caddyfile/options#intermediate-lifetime)的有效期。除非绝对必要，否则不建议更改。\n\n- **resolvers** 是用于查找TXT记录以完成ACME DNS质询的DNS解析器地址。接受[网络地址](/docs/conventions#network-addresses)，未指定时默认使用UDP和53端口。如果主机是IP地址，将直接拨号到该地址解析上游服务器。如果主机不是IP地址，则使用Go标准库的[名称解析约定](https://golang.org/pkg/net/#hdr-Name_Resolution)解析地址。如果指定了多个解析器，将随机选择一个。\n\n- **challenges** 设置启用的质询类型。如果未设置，或该指令未带值使用，则启用所有质询类型。可接受的值为：`http-01`、`tls-alpn-01`、`dns-01`。\n\n- **allow_wildcard_names** 启用签发带有通配符SAN（Subject Alternative Name）的证书。\n\n- **allow**、**deny** 配置`acme_server`的运行策略。策略评估遵循Step-CA [这里](https://smallstep.com/docs/step-ca/policies/#policy-evaluation)描述的规则。\n\n- **domains** 根据策略评估规则设置允许或拒绝的主体域名。\n\n- **ip_ranges** 根据策略评估规则设置允许或拒绝的主体IP范围。\n\n## 示例\n\n在域名`acme.example.com`上提供ID为`home`的ACME服务器，通过[`pki`全局选项](/docs/caddyfile/options#pki-options)自定义CA，并使用`internal`签发者签发自己的证书：\n\n```caddy\n{\n\tpki {\n\t\tca home {\n\t\t\tname \"My Home CA\"\n\t\t}\n\t}\n}\n\nacme.example.com {\n\ttls {\n\t\tissuer internal {\n\t\t\tca home\n\t\t}\n\t}\n\tacme_server {\n\t\tca home\n\t}\n}\n```\n\n如果你有另一台Caddy服务器，它可以使用上面的ACME服务器来签发自己的证书：\n\n```caddy\n{\n\tacme_ca https://acme.example.com/acme/home/directory\n\tacme_ca_root /path/to/home_ca_root.crt\n}\n\nexample.com {\n\trespond \"Hello, world!\"\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/basic_auth.md",
    "content": "---\ntitle: basic_auth (Caddyfile指令)\n---\n\n# basic_auth\n\n启用 HTTP 基本身份验证，可用于使用用户名和散列密码保护目录和文件。\n\n**请注意，基本身份验证在普通 HTTP 上并不安全。** 在决定使用 HTTP 基本身份验证保护什么时，请谨慎使用。\n\n当用户请求受保护的资源时，如果尚未提供用户名和密码，浏览器会提示用户输入。如果 `Authorization` 标头中存在正确的凭据，服务器会授予访问权限；如果标头缺失或凭据不正确，服务器会响应 HTTP 401 Unauthorized。\n\nCaddy 配置不接受明文密码；在将密码放入配置之前，必须先对其进行哈希处理。[`caddy hash-password`](/docs/command-line#caddy-hash-password) 命令可以帮助完成这一步。\n\n认证成功后，`{http.auth.user.id}` 占位符将可用，其中包含已认证的用户名。\n\n在 v2.8.0 之前，此指令名为 `basicauth`；后来为与其他指令保持一致而重命名为 `basic_auth`。\n\n\n## 语法\n\n```caddy-d\nbasic_auth [<matcher>] [<hash_algorithm> [<realm>]] {\n\t<username> <hashed_password>\n\t...\n}\n```\n\n- **&lt;hash_algorithm&gt;** 指定此配置中用于哈希的密码哈希算法（或密钥派生函数）。可用选项包括 `argon2id`，默认值是 `bcrypt`。\n\n- **&lt;realm&gt;** 是自定义 realm 名称。\n\n- **&lt;username&gt;** 是用户名或用户 ID。\n\n- **&lt;hashed_password&gt;** 是密码哈希。\n\n\n## 示例\n\n要求对 `example.com` 的所有请求进行认证：\n\n```caddy\nexample.com {\n\tbasic_auth {\n\t\t# 用户名 \"Bob\"，密码 \"hiccup\"\n\t\tBob $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG\n\t}\n\trespond \"Welcome, {http.auth.user.id}\" 200\n}\n```\n\n保护 `/secret/` 中的文件，使只有 `Bob` 可以访问（其他路径仍可被任何人访问）：\n\n```caddy\nexample.com {\n\troot /srv\n\n\tbasic_auth /secret/* {\n\t\t# 用户名 \"Bob\"，密码 \"hiccup\"\n\t\tBob $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG\n\t}\n\n\tfile_server\n}\n```\n\n`argon2id` 示例：\n\n```caddy\nexample.com {\n\troot /srv\n\n\tbasic_auth /secret/* argon2id {\n\t\t# 用户名 \"Bob\"，密码 \"hiccup\"\n\t\tBob $argon2id$v=19$m=47104,t=1,p=1$zJPvVe48N64JUa9MFlVhiw$b5Tznu0PxnA4TciY6qYe2BFPxncF1ePQaeNukHhH1cU\n\t}\n\n\tfile_server\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/bind.md",
    "content": "---\ntitle: bind (Caddyfile指令)\n---\n\n# bind\n\n指定服务器的套接字绑定的接口。通常情况下，监听器会绑定到空（通配符）接口。然而，你可以强制监听器绑定到另一个主机名或IP。(这个指令只接受一个主机，而不能是端口）。\n\n通常，监听器会绑定到空（通配符）接口。不过，你可以强制监听器改为绑定到其他主机名或IP。此指令只接受主机，不接受端口。端口由[站点地址](/docs/caddyfile/concepts#addresses)决定（默认为`443`）。\n\n请注意，站点绑定方式不一致可能会导致意外后果。例如，如果同一端口上的两个站点都解析到`127.0.0.1`，而只有其中一个站点配置了`bind 127.0.0.1`，那么只有一个站点可访问，因为另一个站点会在没有指定具体主机的情况下绑定到该端口；操作系统会选择更具体匹配的套接字。（虚拟主机不会在不同监听器之间共享。）\n\n`bind`接受[网络地址](/docs/conventions#network-addresses)，但不能包含端口。\n\n\n## 语法\n\n```caddy-d\nbind <hosts...>\n```\n\n- **&lt;hosts...&gt;** 是要绑定监听器的主机接口列表。\n\n\n## 示例\n\n要使套接字只能从当前机器访问，请绑定到回环接口（localhost）：\n\n```caddy\nexample.com {\n\tbind 127.0.0.1\n}\n```\n\n要包含IPv6：\n\n```caddy\nexample.com {\n\tbind 127.0.0.1 [::1]\n}\n```\n\n要绑定到`10.0.0.1:8080`：\n\n```caddy\nexample.com:8080 {\n\tbind 10.0.0.1\n}\n```\n\n要绑定到`/run/caddy`处的Unix域套接字：\n\n```caddy\nexample.com {\n\tbind unix//run/caddy\n}\n```\n\n要将文件权限改为所有用户可写（[默认](/docs/conventions#network-addresses)为`0200`，即只有所有者可写）：\n\n```caddy\nexample.com {\n\tbind unix//run/caddy|0222\n}\n```\n\n要把一个域名绑定到两个不同接口，并返回不同响应：\n\n```caddy\nexample.com {\n\tbind 10.0.0.1\n\trespond \"One\"\n}\n\nexample.com {\n\tbind 10.0.0.2\n\trespond \"Two\"\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/encode.md",
    "content": "---\ntitle: encode (Caddyfile指令)\n---\n\n<script>\nready(function() {\n// We'll add links to all the subdirectives if a matching anchor tag is found on the page.\naddLinksToSubdirectives();\n\n// Response matchers\n$$_('pre.chroma .k').forEach(item => {\nif (item.innerText.includes('status')) {\nitem.innerHTML = '<a href=\"/docs/caddyfile/response-matchers#status\" style=\"color: inherit;\" title=\"Response matcher\">status</a>';\n}\n});\n\n$$_('pre.chroma .k').forEach(item => {\nif (item.innerText.includes('header')) {\nitem.innerHTML = '<a href=\"/docs/caddyfile/response-matchers#header\" style=\"color: inherit;\" title=\"Response matcher\">header</a>';\n}\n});\n});\n</script>\n\n# encode\n\n使用指定的编码对响应进行编码。其典型用途就是压缩。\n\n## 语法\n\n```caddy-d\nencode [<matcher>] [<formats...>] {\n\t# 编码格式\n\tgzip [<level>]\n\tzstd [<level>]\n\n\tminimum_length <length>\n\n\tmatch <inline_response_matcher>\n}\n```\n\n- **&lt;formats...&gt;** 是要启用的编码格式列表。如果启用了多种编码，则会根据请求的`Accept-Encoding`头选择编码；如果客户端没有强偏好（q-factor），则使用第一个受支持的编码。如果省略，默认启用`zstd`（优先）和`gzip`。\n\n- **gzip** <span id=\"gzip\"/> 启用Gzip压缩，可选择指定级别。\n\n- **zstd** <span id=\"zstd\"/> 启用Zstandard压缩，可选择指定级别（可能的值为default、fastest、better、best）。默认压缩级别大致等同于Zstandard的默认模式（级别3）。\n\n- **minimum_length** <span id=\"minimum_length\"/> 是响应应达到多少字节才进行编码的最小值（默认：512）。\n\n- **match** <span id=\"match\"/> 是一个[响应匹配器](/docs/caddyfile/response-matchers)。只有匹配的响应会被编码。默认如下：\n\n  ```caddy-d\n  match {\n    header Content-Type application/atom+xml*\n    header Content-Type application/eot*\n    header Content-Type application/font*\n    header Content-Type application/geo+json*\n    header Content-Type application/graphql+json*\n    header Content-Type application/javascript*\n    header Content-Type application/json*\n    header Content-Type application/ld+json*\n    header Content-Type application/manifest+json*\n    header Content-Type application/opentype*\n    header Content-Type application/otf*\n    header Content-Type application/rss+xml*\n    header Content-Type application/truetype*\n    header Content-Type application/ttf*\n    header Content-Type application/vnd.api+json*\n    header Content-Type application/vnd.ms-fontobject*\n    header Content-Type application/wasm*\n    header Content-Type application/x-httpd-cgi*\n    header Content-Type application/x-javascript*\n    header Content-Type application/x-opentype*\n    header Content-Type application/x-otf*\n    header Content-Type application/x-perl*\n    header Content-Type application/x-protobuf*\n    header Content-Type application/x-ttf*\n    header Content-Type application/xhtml+xml*\n    header Content-Type application/xml*\n    header Content-Type font/*\n    header Content-Type image/svg+xml*\n    header Content-Type image/vnd.microsoft.icon*\n    header Content-Type image/x-icon*\n    header Content-Type multipart/bag*\n    header Content-Type multipart/mixed*\n    header Content-Type text/*\n  }\n  ```\n\n\n## 示例\n\n启用Gzip压缩：\n\n```caddy-d\nencode gzip\n```\n\n启用Zstandard和Gzip压缩（Zstandard隐式优先，因为它排在前面）：\n\n```caddy-d\nencode zstd gzip\n```\n\n由于这是默认值，前一个配置与下面的配置完全等价：\n\n```caddy-d\nencode\n```\n\n完整站点示例，压缩由[`file_server`](file_server)提供的静态文件：\n\n```caddy\nexample.com {\n\troot /srv\n\tencode\n\tfile_server\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/error.md",
    "content": "---\ntitle: error (Caddyfile 指令)\n---\n\n# error\n\n触发HTTP处理链中的一个错误，有一个可选的消息和推荐的HTTP状态码。\n\n这个处理程序并不会输出响应。相反，它应该与[`handle_errors`](handle_errors)指令搭配使用，以调用你的自定义错误处理逻辑。\n\n\n## 语法\n\n``caddy-d\nerror [<matcher>] <status>|<message> [<status>] {\nmessage <text>\n}\n```\n\n- **&lt;status&gt;**是要输出的HTTP状态代码。默认是`500`。\n- **&lt;message&gt;**是错误信息。默认是没有错误信息。\n- **message**是提供错误信息的另一种方式；多行的时候使用起来非常方便。\n\n澄清一下，第一个非匹配器参数可以是一个3位数的状态代码，或者一个错误信息字符串。如果是一个错误信息，下一个参数可以是状态代码。\n\n## 示例\n\n在某些请求路径上触发一个错误, 并使用[`handle_errors`](handle_errors)来输出一个响应。\n\n``caddy\nexample.com {\n\troot * /srv\n\n\t# 对某些路径触发错误\n    error /private* \"Unauthorized\" 403\n\terror /hidden* \"Not found\" 404\n\n    # 通过提供一个HTML页面来处理这个错误 \n    handle_errors {\n        rewrite * /{err.status_code}.html\n\t\tfile_server\n    }\n\n\tfile_server\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/file_server.md",
    "content": "---\ntitle: file_server(Caddyfile指令)\n---\n\n<script>\nready(function() {\n\t// 修正行内browse参数的高亮，并链接到browse小节。\n\tfor (let item of $$_('pre.chroma .s')) {\n\t\tif (item.innerText.includes('browse')) {\n\t\t\tconst span = document.createElement('span');\n\t\t\tspan.className = 'k';\n\t\t\titem.parentNode.insertBefore(span, item);\n\t\t\tspan.appendChild(item);\n\t\t\tspan.innerHTML = '<a href=\"#browse\" style=\"color: inherit;\" title=\"browse\">browse</a>';\n\t\t\tbreak;\n\t\t}\n\t}\n\n\taddLinksToSubdirectives();\n});\n</script>\n\n# file_server\n\n一个静态文件服务器，支持真实和虚拟文件系统。它通过将请求的URI路径附加到[站点的根路径](/docs/caddyfile/directives/root)来形成文件路径。\n\n默认情况下，它会强制执行规范URI；这意味着对于不以尾部斜线结尾的目录请求，会发出HTTP重定向来添加尾部斜线；对于带有尾部斜线的文件请求，会发出HTTP重定向来移除尾部斜线。不过，如果内部重写修改了路径的最后一个元素（文件名），则不会发出重定向。\n\n最常见的是，`file_server`指令会与[`root`](root)指令配对使用，为整个站点设置文件根目录。此指令也有一个`root`子指令（见下文），可仅为这个处理器设置根目录（不推荐）。注意，站点根目录不提供沙盒保证：文件服务器确实会防止路径组件中的目录遍历，但根目录中的符号链接仍可能允许访问根目录之外的内容。\n\n发生错误时（例如文件未找到`404`、权限被拒绝`403`），会调用错误路由。使用[`handle_errors`](handle_errors)指令可以定义错误路由并显示自定义错误页面。\n\n使用`browse`时，默认输出由HTML模板生成。客户端也可以分别通过`Accept: application/json`或`Accept: text/plain`请求JSON或纯文本形式的目录列表。JSON输出适合脚本使用，纯文本输出适合人在终端中查看。\n\n\n## 语法\n\n```caddy-d\nfile_server [<matcher>] [browse] {\n\tfs            <backend...>\n\troot          <path>\n\thide          <files...>\n\tindex         <filenames...>\n\tbrowse        [<template_file>] {\n\t\treveal_symlinks\n\t\tsort <sort_field> [<direction>]\n\t\tfile_limit <number>\n\t}\n\tprecompressed [<formats...>]\n\tstatus        <status>\n\tdisable_canonical_uris\n\tpass_thru\n}\n```\n\n- **fs** <span id=\"fs\"/> 指定要使用的备用（也许是虚拟的）文件系统。可以使用`caddy.fs`命名空间中的任何Caddy模块。任何根路径/前缀仍会应用到备用文件系统模块。默认情况下使用本地磁盘。\n\n\t[`xcaddy`](/docs/build#xcaddy) v0.4.0引入了[`--embed`标志](https://github.com/caddyserver/xcaddy#custom-builds)，可将文件系统树嵌入到自定义Caddy构建中，并注册名为`embedded`的`fs`模块，让你的静态站点可以作为Caddy可执行文件分发。\n\n- **root** <span id=\"root\"/> 设置站点根路径。它类似于[`root`](root)指令，但只应用于这个文件服务器实例，并会覆盖任何可能已经定义的其他站点根。默认值：`{http.vars.root}`或当前工作目录。注意：这个子指令只改变此处理器的根目录。对于其他指令（例如[`try_files`](try_files)或[`templates`](templates)），如果需要知道相同的站点根目录，应使用[`root`](root)指令。\n\n- **hide** <span id=\"hide\"/> 是要隐藏的文件或文件夹列表；如果请求这些文件，文件服务器会假装它们不存在。它接受占位符和glob模式。注意，这些是 _文件系统_ 路径，**不是**请求路径。换句话说，相对路径以当前工作目录为基准，而不是站点根目录；并且所有路径在比较前都会尽可能转换为绝对形式。指定不带路径分隔符的文件名或模式时，会隐藏任何位置中所有名称匹配的文件；否则，会先尝试路径前缀匹配，然后再尝试glob匹配。由于这是Caddyfile配置，默认会加入活动配置文件。隐藏比较区分大小写；在大小写不敏感的文件系统上，大小写不同的请求路径仍可能解析到同一个磁盘路径，因此不应把`hide`视为保护敏感路径的安全边界。\n\n- **index** <span id=\"index\"/> 是要查找作为索引文件的文件名列表。默认值：`index.html index.txt`\n\n- **browse** <span id=\"browse\"/> 对没有索引文件的目录请求启用文件列表。\n\n\t- **<template_file>** <span id=\"template_file\"/> 是可选的自定义模板文件，用于目录列表。默认模板可通过`caddy file-server export-template`命令导出，该命令会将默认模板打印到标准输出。嵌入模板也可以在[源代码中找到](https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/fileserver/browse.html)。目录列表模板也可以使用[标准templates模块](/docs/modules/http.handlers.templates#docs)中的动作。\n\n\t- **reveal_symlinks** <span id=\"reveal_symlinks\"/> 在目录列表中显示符号链接的目标。默认情况下，符号链接目标会被隐藏，只显示链接文件本身。\n\n\t- **sort** <span id=\"sort\"/> 更改目录列表的默认排序。第一个参数是排序字段/列：`name`、`namedirfirst`、`size`或`time`。第二个参数是可选方向：`asc`或`desc`。例如，`sort name desc`会按名称降序排序。\n\n\t- **file_limit** <span id=\"file_limit\"/> 设置目录列表中最多显示的文件数。默认值：`10000`。如果文件数量超过此限制，只会显示前N个文件，其中N是指定的限制值。\n\n- **precompressed** <span id=\"precompressed\"/> 是用于搜索预压缩sidecar文件的编码格式列表。参数是一个有序的编码格式列表，用于搜索预压缩的[sidecar文件](https://en.wikipedia.org/wiki/Sidecar_file)。支持的格式有`gzip`（`.gz`）、`zstd`（`.zst`）和`br`（`.br`）。如果省略格式，则默认使用`br zstd gzip`（按此顺序）。\n\n\t所有文件查找都会先查找未压缩文件是否存在。一旦找到，Caddy会查找每种启用格式对应文件扩展名的sidecar文件。如果找到预压缩的sidecar文件，Caddy会用预压缩文件响应，并适当地设置`Content-Encoding`响应头。否则，Caddy会正常响应未压缩文件。如果启用了[`encode`](encode)指令，那么在没有预压缩文件时，它可能会即时压缩响应。\n\n- **status** <span id=\"status\"/> 是写入响应时使用的可选状态码覆盖。用[自定义错误页面](handle_errors)响应请求时特别有用。可以是3位状态码，例如：`404`。支持占位符。默认情况下，写入的状态码通常是`200`，部分内容则是`206`。\n\n- **disable_canonical_uris** <span id=\"disable_canonical_uris\"/> 禁用默认重定向行为（如果请求路径是目录则添加尾部斜线，如果请求路径是文件则移除尾部斜线）。注意，默认情况下，如果请求路径的最后一个元素（文件名）经历了内部重写，则不会执行规范化，以避免用隐式行为破坏显式重写。\n\n- **pass_thru** <span id=\"pass_thru\"/> 启用pass-thru模式：如果找不到请求的文件，则继续执行路由中的下一个HTTP处理器，而不是触发`404`错误（调用[`handle_errors`](handle_errors)路由）。实际上，这通常只在后面还有其他处理器指令的[`route`](route)块中有用，因为此指令实际上会[排序在最后](/docs/caddyfile/directives#directive-order)。\n\n\n## 示例\n\n从当前目录提供静态文件：\n\n```caddy-d\nfile_server\n```\n\n启用文件列表：\n\n```caddy-d\nfile_server browse\n```\n\n只提供`/static`文件夹中的静态文件：\n\n```caddy-d\nfile_server /static/*\n```\n\n`file_server`指令通常与[`root`指令](root)配对，以设置提供文件的根路径：\n\n```caddy\nexample.com {\n\troot /srv\n\tfile_server\n}\n```\n\n<aside class=\"tip\">\n\n如果你将Caddy作为systemd服务运行，从`/home`读取文件将无法工作，因为`caddy`用户没有`/home`目录的“可执行”权限（遍历目录所必需）。建议你将文件放在`/srv`或`/var/www/html`中。\n\n</aside>\n\n\n隐藏所有`.git`文件夹及其内容：\n\n```caddy-d\nfile_server {\n\thide .git\n}\n```\n\n如果客户端支持（`Accept-Encoding`头），则在请求文件旁边检查预压缩文件是否存在。因此，如果请求`/path/to/file`，它会按顺序检查`/path/to/file.br`、`/path/to/file.zst`和`/path/to/file.gz`，并提供第一个可用文件，同时设置对应的`Content-Encoding`：\n\n```caddy-d\nfile_server {\n\tprecompressed\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/forward_auth.md",
    "content": "---\ntitle: forward_auth (Caddyfile指令)\n---\n\n<script>\nready(function() {\n// Fix > in code blocks\n$$_('pre.chroma .k').forEach(item => {\nif (item.innerText.includes('>')) {\n// Skip if ends with >\nif (item.innerText.trim().endsWith('>')) return;\n// Replace > with <span class=\"p\">&gt;</span>\nitem.innerHTML = item.innerHTML.replace(/&gt;/g, '<span class=\"p\">&gt;</span>');\n}\n});\n\n// Fix uri subdirective, gets parsed as matcher arg because of \"uri\" directive\n$$_('.k').forEach(item => {\nif (item.innerText.includes('uri') && item.nextElementSibling && item.nextElementSibling.classList.contains('nd')) {\nconst next = item.nextElementSibling;\nnext.classList.remove('nd');\nnext.classList.add('s');\nnext.textContent = next.textContent;\n}\n});\n});\n</script>\n\n# forward_auth\n\n一个专用的(opinionated)指令，它会把请求复制给认证网关，认证网关可以决定是否应该继续处理，或者需要跳转到一个登录页面。\n\n- [语法](#syntax)\n- [扩展形式](#expanded-form)\n- [示例](#examples)\n  - [Authelia](#authelia)\n  - [Tailscale](#tailscale)\n\nCaddy的[`reverse_proxy`](/docs/caddyfile/directives/reverse_proxy)能够向外部服务执行“预检查请求”，但此指令专门针对认证用例定制。它实际上只是使用一个更长、更常见配置（如下）的便捷方式。\n\n此指令会把`uri`重写后，向配置的上游发出`GET`请求：\n- 如果上游响应`2xx`状态码，则允许访问，`copy_headers`中的头字段会被复制到原始请求，并继续处理。\n- 否则，如果上游响应任何其他状态码，则上游响应会被复制回客户端。这个响应通常应包含重定向到认证网关登录页面的内容。\n\n如果这并不完全是你想要的行为，可以以下面的[扩展形式](#expanded-form)为基础，按需自定义。\n\n支持[`reverse_proxy`](/docs/caddyfile/directives/reverse_proxy)的所有子指令，并将它们传递给底层的`reverse_proxy`处理器。\n\n\n## 语法\n\n```caddy-d\nforward_auth [<matcher>] [<upstreams...>] {\n\turi          <to>\n\tcopy_headers <fields...> {\n\t\t<fields...>\n\t}\n}\n```\n\n- **&lt;upstreams...&gt;** 是要向其发送认证请求的上游（后端）列表。\n\n- **uri** 是在发送给上游的请求上设置的URI（路径和查询）。这通常是认证网关的验证端点。\n\n- **copy_headers** 是在请求获得成功状态码时，要从响应复制到原始请求的HTTP头字段列表。\n\n  可通过`>`后接新名称来重命名字段，例如`Before>After`。\n\n  如果你更喜欢可读性，也可以使用块来逐行列出所有字段。\n\n由于此指令是反向代理上带有预设行为的包装器，你可以使用[`reverse_proxy`](/docs/caddyfile/directives/reverse_proxy#syntax)的任何子指令来自定义它。\n\n\n## 扩展形式\n\n`forward_auth`指令等同于以下配置。像[Authelia](https://www.authelia.com/)这样的认证网关适合这个预设。如果你的网关不适合，可以借用下面的配置并按需自定义，而不是使用`forward_auth`快捷方式。\n\n```caddy-d\nreverse_proxy <upstreams...> {\n\t# 始终使用 GET，避免消耗传入请求的\n\t# 请求体\n\tmethod GET\n\n\t# 将 URI 改为认证网关的\n\t# 验证端点\n\trewrite <to>\n\n\t# 转发原始方法和 URI，\n\t# 因为它们会在上面被重写；这\n\t# 是对 reverse_proxy 已设置的其他\n\t# X-Forwarded-* 头的补充\n\theader_up X-Forwarded-Method {method}\n\theader_up X-Forwarded-Uri {uri}\n\n\t# 成功响应时，复制响应头\n\t@good status 2xx\n\thandle_response @good {\n\t\t# 例如，对每个 copy_headers 字段……\n\t\trequest_header Remote-User {rp.header.Remote-User}\n\t\trequest_header Remote-Email {rp.header.Remote-Email}\n\t}\n}\n```\n\n\n## 示例\n\n\n### Authelia\n\n在通过反向代理提供你的应用之前，将认证委托给[Authelia](https://www.authelia.com/)：\n\n```caddy\n# 提供认证网关本身\nauth.example.com {\n\treverse_proxy authelia:9091\n}\n\n# 提供你的应用\napp1.example.com {\n\tforward_auth authelia:9091 {\n\t\turi /api/authz/forward-auth\n\t\tcopy_headers Remote-User Remote-Groups Remote-Name Remote-Email\n\t}\n\n\treverse_proxy app1:8080\n}\n```\n\n更多信息，请参阅[Authelia文档](https://www.authelia.com/integration/proxies/caddy/)中与Caddy集成的说明。\n\n\n### Tailscale\n\n将认证委托给[Tailscale](https://tailscale.com/)（目前名为[`nginx-auth`](https://tailscale.com/blog/tailscale-auth-nginx/)，但它仍可与Caddy一起使用），并使用`copy_headers`的替代语法来*重命名*复制的头（注意每个头中的`>`）：\n\n```caddy-d\nforward_auth unix//run/tailscale.nginx-auth.sock {\n\turi /auth\n\theader_up Remote-Addr {remote_host}\n\theader_up Remote-Port {remote_port}\n\theader_up Original-URI {uri}\n\tcopy_headers {\n\t\tTailscale-User>X-Webauth-User\n\t\tTailscale-Name>X-Webauth-Name\n\t\tTailscale-Login>X-Webauth-Login\n\t\tTailscale-Tailnet>X-Webauth-Tailnet\n\t\tTailscale-Profile-Picture>X-Webauth-Profile-Picture\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/fs.md",
    "content": "---\ntitle: fs (Caddyfile指令)\n---\n\n# fs\n\n设置执行文件 I/O 时应使用哪个文件系统。\n\n这可以让你连接到云端运行的远程文件系统、具有类文件接口的数据库，甚至读取嵌入到 Caddy 二进制文件中的文件。\n\n首先，你必须使用 [`filesystem` 全局选项](/docs/caddyfile/options#filesystem)声明一个文件系统名称，然后可以使用此指令指定要使用哪个文件系统。\n\n此指令通常会与 [`file_server` 指令](file_server)一起使用以提供静态文件，或与 [`try_files` 指令](try_files)一起使用以根据文件是否存在执行重写。通常也会与 [`root` 指令](root)一起使用，以设置文件系统内的根路径。\n\n\n## 语法\n\n```caddy-d\nfs [<matcher>] <filesystem>\n```\n\n## 示例\n\n使用名为 `foo` 的文件系统，并假设有一个名为 `custom`、可能需要认证的模块：\n\n```caddy\n{\n\tfilesystem foo custom {\n\t\tapi_key abc123\n\t}\n}\n\nexample.com {\n\tfs foo\n\troot /srv\n\tfile_server\n}\n```\n\n仅从 `foo` 文件系统提供图片，其余内容仍使用默认文件系统：\n\n```caddy\nexample.com {\n\tfs /images* foo\n\troot /srv\n\tfile_server\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/handle.md",
    "content": "---\ntitle: handle (Caddyfile指令)\n---\n\n# handle\n\n评估一组指令，这些指令与同级嵌套的其他`handle`块相互排斥。\n\n换句话说，当多个`handle`指令连续出现时，只会评估第一个_匹配_的`handle`块。没有匹配器的`handle`会作为_后备_路由。\n\n`handle`指令会根据其匹配器，按照[指令排序算法](/docs/caddyfile/directives#sorting-algorithm)排序。[`handle_path`](handle_path)指令是一个特殊情况，它与带路径匹配器的`handle`具有相同排序优先级。\n\n如果需要，handle块可以嵌套。只有HTTP处理器指令可以在handle块内使用。\n\n## 语法\n\n```caddy-d\nhandle [<matcher>] {\n\t<directives...>\n}\n```\n\n- **<directives...>** 是HTTP处理器指令或指令块的列表，每行一个，写法与在handle块外部使用时相同。\n\n\n\n## 类似指令\n\n还有其他一些指令也可以包装HTTP处理器指令，但每个指令都有自己的用途，取决于你想表达的行为：\n\n- [`handle_path`](handle_path)的作用与`handle`相同，但它会在运行处理器之前从请求中剥离一个前缀。\n\n- [`handle_errors`](handle_errors)类似于`handle`，但只会在Caddy处理请求时遇到错误时调用。\n\n- [`route`](route)像`handle`一样包装其他指令，但有两个区别：\n  1. route块之间不互斥；\n  2. route内的指令不会被[重新排序](/docs/caddyfile/directives#directive-order)，在需要时能给你更多控制。\n\n\n\n## 示例\n\n用静态文件服务器处理`/foo/`中的请求，并用反向代理处理其他请求：\n\n```caddy\nexample.com {\n\thandle /foo/* {\n\t\tfile_server\n\t}\n\n\thandle {\n\t\treverse_proxy 127.0.0.1:8080\n\t}\n}\n```\n\n你可以在同一个站点中混合使用`handle`和[`handle_path`](handle_path)，它们之间仍然是互斥的：\n\n```caddy\nexample.com {\n\thandle_path /foo/* {\n\t\t# 路径中的 \"/foo\" 前缀会被剥离\n\t}\n\n\thandle /bar/* {\n\t\t# 路径仍保留 \"/bar\"\n\t}\n}\n```\n\n你可以嵌套`handle`块来创建更复杂的路由逻辑：\n\n```caddy\nexample.com {\n\thandle /foo* {\n\t\thandle /foo/bar* {\n\t\t\t# 此块只匹配 /foo/bar 下的路径\n\t\t}\n\n\t\thandle {\n\t\t\t# 此块匹配 /foo/ 下的其他所有内容\n\t\t}\n\t}\n\n\thandle {\n\t\t# 此块匹配其他所有内容（作为后备）\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/handle_errors.md",
    "content": "---\ntitle: handle_errors (Caddyfile指令)\n---\n\n# handle_errors\n\n设置错误处理程序。\n\n当正常的HTTP请求处理程序返回错误时，正常处理会停止，错误处理程序会被调用。错误处理程序形成一条路由，就像普通路由一样，它们可以做普通路由能做的任何事情。这让你在处理HTTP请求期间发生的错误时有很大的控制力和灵活性。例如，你可以提供静态错误页面、模板化错误页面，或者反向代理到另一个后端来处理错误。\n\n该指令可以使用不同状态码重复多次，以便用不同方式处理不同错误。如果未指定状态码，则匹配任何错误；如果其他错误处理程序都不匹配，它会作为后备处理程序。\n\n请求的上下文会带入错误路由，因此在请求上下文中设置的任何值，例如[站点根目录](root)或[vars](vars)，也会在错误处理程序中保留。此外，处理错误时还可以使用[新的占位符](#placeholders)。\n\n请注意，某些指令（例如[`reverse_proxy`](reverse_proxy)）可能会写入被归类为错误的HTTP状态响应，但这_不会_触发错误路由。\n\n你可以使用[`error`](error)指令，根据自己的路由决策显式触发错误。\n\n\n## 语法\n\n```caddy-d\nhandle_errors [<status_codes...>] {\n\t<directives...>\n}\n```\n\n- **<status_codes...>** 是一个或多个HTTP状态码，用于匹配正在处理的错误。状态码可以是3位数字，也可以是特殊的`4xx`或`5xx`，分别匹配400-499或500-599范围内的所有状态码。如果未指定状态码，则匹配任何错误；如果其他错误处理程序都不匹配，它会作为后备处理程序。\n\n- **<directives...>** 是HTTP处理器[指令](/docs/caddyfile/directives)和[匹配器](/docs/caddyfile/matchers)的列表，每行一个。\n\n\n## 占位符\n\n处理错误时可以使用以下占位符。它们是完整占位符的[Caddyfile简写](/docs/caddyfile/concepts#placeholders)，完整占位符可在[HTTP服务器错误路由的JSON文档](/docs/json/apps/http/servers/errors/#routes)中找到。\n\n| 占位符 | 描述 |\n|---|---|\n| `{err.status_code}` | 推荐的HTTP状态码 |\n| `{err.status_text}` | 与推荐状态码关联的状态文本 |\n| `{err.message}` | 错误消息 |\n| `{err.trace}` | 错误来源 |\n| `{err.id}` | 本次错误发生的标识符 |\n\n\n## 示例\n\n基于状态码的自定义错误页面（即`404`错误对应名为`404.html`的页面）。注意，当[`file_server`](file_server)在`handle_errors`中运行时，会保留错误的HTTP状态码（假设你事先在站点中设置了[站点根目录](root)）：\n\n```caddy-d\nhandle_errors {\n\trewrite /{err.status_code}.html\n\tfile_server\n}\n```\n\n使用[`templates`](templates)写入自定义错误消息的单一错误页面：\n\n```caddy-d\nhandle_errors {\n\trewrite /error.html\n\ttemplates\n\tfile_server\n}\n```\n\n如果只想为部分错误代码提供自定义错误页面，可以先用[`file`](/docs/caddyfile/matchers#file)匹配器检查自定义错误文件是否存在：\n\n```caddy-d\nhandle_errors {\n\t@custom_err file /err-{err.status_code}.html /err.html\n\thandle @custom_err {\n\t\trewrite {file_match.relative}\n\t\tfile_server\n\t}\n\trespond \"{err.status_code} {err.status_text}\"\n}\n```\n\n反向代理到一台非常擅长处理HTTP错误并改善你心情的专业服务器😸：\n\n```caddy-d\nhandle_errors {\n\trewrite /{err.status_code}\n\treverse_proxy https://http.cat {\n\t\treplace_status {err.status_code}\n\t}\n}\n```\n\n只需使用[`respond`](respond)即可返回错误代码和名称\n\n```caddy-d\nhandle_errors {\n\trespond \"{err.status_code} {err.status_text}\"\n}\n```\n\n要用不同方式处理特定错误代码：\n\n```caddy-d\nhandle_errors 404 410 {\n\trespond \"It's a 404 or 410 error!\"\n}\n\nhandle_errors 5xx {\n\trespond \"It's a 5xx error.\"\n}\n\nhandle_errors {\n\trespond \"It's another error\"\n}\n```\n\n上面的行为与下面相同；下面使用[`expression`](/docs/caddyfile/matchers#expression)匹配器匹配状态码，并使用[`handle`](handle)实现互斥：\n\n```caddy-d\nhandle_errors {\n\t@404-410 `{err.status_code} in [404, 410]`\n\thandle @404-410 {\n\t\trespond \"It's a 404 or 410 error!\"\n\t}\n\n\t@5xx `{err.status_code} >= 500 && {err.status_code} < 600`\n\thandle @5xx {\n\t\trespond \"It's a 5xx error.\"\n\t}\n\n\thandle {\n\t\trespond \"It's another error\"\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/handle_path.md",
    "content": "---\ntitle: handle_path (Caddyfile指令)\n---\n\n<script>\nready(function() {\n// Add a link to [<path_matcher>] as a special case for this directive.\n// The matcher text includes <> characters which are parsed as HTML,\n// so we must use text() to change the link text.\n$$_('pre.chroma .s').forEach(item => {\nif (item.innerText.includes('<path_matcher>')) {\nlet text = item.innerText.replace(/</g, '&lt;').replace(/>/g, '&gt;');\nitem.innerHTML = `<a href=\"/docs/caddyfile/matchers#path-matchers\" style=\"color: inherit;\" title=\"Matcher token\">${text}</a>`;\nitem.classList.remove('s');\nitem.classList.add('nd');\n}\n});\n});\n</script>\n\n# handle_path\n\n与[`handle`指令](/docs/caddyfile/directives/handle)相同，但会隐式剥离匹配到的路径前缀。\n\n处理匹配某个路径的请求（同时从请求URI中剥离该路径）是一种足够常见的用例，因此提供了这个专门的便捷指令。\n\n\n## 语法\n\n```caddy-d\nhandle_path <path_matcher> {\n\t<directives...>\n}\n```\n\n- **<directives...>** 是HTTP处理器指令或指令块的列表，每行一个，写法与在`handle_path`块外部使用时相同。\n\n只接受且必须提供一个[路径匹配器](/docs/caddyfile/matchers#path-matchers)；不能在`handle_path`中使用命名匹配器。\n\n## 示例\n\n这个配置：\n\n```caddy-d\nhandle_path /prefix/* {\n\t...\n}\n```\n\n👆 实际上等同于下面这个配置 👇，但 👆 的`handle_path`形式稍微更简洁\n\n```caddy-d\nhandle /prefix/* {\n\turi strip_prefix /prefix\n\t...\n}\n```\n\n一个完整的Caddyfile示例，其中`handle_path`和`handle`相互排斥；但请注意[子文件夹问题 <img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://caddy.community/t/the-subfolder-problem-or-why-cant-i-reverse-proxy-my-app-into-a-subfolder/8575)\n\n```caddy\nexample.com {\n\t# 提供 API，并剥离 /api 前缀\n\thandle_path /api/* {\n\t\treverse_proxy localhost:9000\n\t}\n\n\t# 提供静态站点\n\thandle {\n\t\troot /srv\n\t\tfile_server\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/header.md",
    "content": "---\ntitle: header (Caddyfile directive)\n---\n\n# header\n\n操作 HTTP 响应标头字段。它可以设置、添加和删除标头值，或使用正则表达式执行替换。\n\n默认情况下，除非删除任何标头（`-`前缀）或设置默认值（`?`前缀），否则立即执行标头操作。在这些情况下，标头操作会自动推迟，直到将它们写入客户端为止。\n\n要操作 HTTP 请求标头，您可以使用[`request_header`](request_header)指令。\n\n\n## 语法\n\n```caddy-d\nheader [<matcher>] [[+|-|?|>]<field> [<value>|<find>] [<replace>]] {\n\t# Add\n\t+<field> <value>\n\n\t# Set\n\t<field> <value>\n\n\t# Set with defer\n\t><field> <value>\n\n\t# Delete\n\t-<field>\n\n\t# Replace\n\t<field> <find> <replace>\n\n\t# Replace with defer\n\t><field> <find> <replace>\n\n\t# Default\n\t?<field> <value>\n\n\t[defer]\n\n\tmatch <inline_response_matcher>\n}\n```\n\n- **&lt;field&gt;** 是头字段的名称。\n\n  如果没有前缀，该字段将被设置（覆盖）。\n\n  以`+`为前缀来添加该字段，而不是覆盖（设置）该字段（如果该字段已存在）；标头字段可以在响应中出现多次。\n\n  前缀为`-`即可删除该字段。该字段可以使用前缀或后缀`*`通配符来删除所有匹配的字段。\n\n  以`?`为前缀来设置该字段的默认值。仅当该字段尚不存在时才会写入。\n\n  以`>`为前缀设置该字段，并启用`defer`作为快捷方式。\n\n- **&lt;value&gt;** 是添加或设置字段时的标头字段值。\n\n- **&lt;find&gt;** 是要搜索的正则表达式。占位符可用于动态输入搜索模式。使用的正则表达式语言是 RE2，包含在 Go 中。请参阅[RE2 语法参考](https://github.com/google/re2/wiki/Syntax)和[Go regexp 语法概述](https://pkg.go.dev/regexp/syntax)。\n\n- **&lt;replace&gt;** 为重置值；如果执行搜索和替换，则需要。使用`$1`或`$2`等从搜索模式引用捕获组。如果替换值为`\"\"`，则将从该值中删除匹配的文本。详情请见[Go documentation](https://golang.org/pkg/regexp/#Regexp.Expand)。\n\n- **defer** 推迟标头操作的执行，直到响应发送到客户端。该选项在以下条件下自动启用：\n\t- 使用`-`删除任何标头字段时。\n\t- 使用`?`设置默认值时。\n\t- 在设置或替换操作中使用`>`前缀时。\n\t- 当存在一个或多个`match`条件时。\n\n- **match**<span id=\"match\"/>是内联[响应匹配器](/docs/caddyfile/response-matchers)。标头操作仅适用于满足指定条件的响应。\n\n对于多个标头操作，您可以打开一个块并以相同的方式为每行指定一个操作。\n\n当使用`?`前缀设置默认标头值时，如果它位于具有多个标头操作的`header`块中，它会自动分离到自己的`header`处理程序中。[在底层](/docs/modules/http.handlers.headers#response/require)，使用`?`配置一个[响应匹配器](/docs/caddyfile/response-matchers)，它应用于指令的整个处理程序，它仅应用标头操作（如`defer`），但前提是该字段尚未设置。\n\n\n## 示例\n\n在所有响应上设置自定义标头字段：\n\n```caddy-d\nheader Custom-Header \"My value\"\n```\n\n删除“隐藏”标头字段：\n\n```caddy-d\nheader -Hidden\n```\n\n在任何 Location 标头中将`http://`替换为`https://`：\n\n```caddy-d\nheader Location http:// https://\n```\n\n在所有页面上设置安全和隐私标题：（**WARNING:**仅在您了解其含义时使用！）\n\n```caddy-d\nheader {\n\t# disable FLoC tracking\n\tPermissions-Policy interest-cohort=()\n\n\t# enable HSTS\n\tStrict-Transport-Security max-age=31536000;\n\n\t# disable clients from sniffing the media type\n\tX-Content-Type-Options nosniff\n\n\t# clickjacking protection\n\tX-Frame-Options DENY\n}\n```\n\n旨在互斥的多个标头指令：\n\n```caddy-d\nroute {\n\theader           Cache-Control max-age=3600\n\theader /static/* Cache-Control max-age=31536000\n}\n```\n\n如果上游未定义缓存过期时间，则设置默认缓存过期时间：\n\n```caddy-d\nheader ?Cache-Control \"max-age=3600\"\nreverse_proxy upstream:443\n```\n\n将所有对 GET 请求的成功响应标记为可缓存长达一个小时：\n\n```caddy-d\n@GET method GET\nheader @GET Cache-Control \"max-age=3600\" {\n\tmatch status 2xx\n}\nreverse_proxy upstream:443\n```\n\n防止在上游服务器发生异常时缓存错误响应：\n\n```caddy-d\nheader {\n\t-Cache-Control\n\t-CDN-Cache-Control\n\tmatch status 500\n}\nreverse_proxy upstream:443\n```\n\n如果上游服务器支持客户端提示，则将浅色模式响应标记为可与深色模式响应单独缓存：\n```caddy-d\nheader {\n\tCache-Control \"max-age=3600\"\n\tVary \"Sec-CH-Prefers-Color-Scheme\"\n\tmatch {\n\t\theader Accept-CH \"*Sec-CH-Prefers-Color-Scheme*\"\n\t\theader Critical-CH \"Sec-CH-Prefers-Color-Scheme\"\n\t}\n}\nreverse_proxy upstream:443\n```\n\n通过用特定域替换通配符值来防止过度宽松的 CORS 标头：\n```caddy-d\nheader >Access-Control-Allow-Origin \"\\*\" \"allowed-partner.com\"\nreverse_proxy upstream:443\n```\n**Note**：在替换操作中，`<find>`值被解释为正则表达式。要匹配`*`字符，必须使用反斜杠对其进行转义，如上例所示。\n\n或者，您可以使用[响应匹配器](/docs/caddyfile/response-matchers)逐字匹配标头值：\n```caddy-d\nheader Access-Control-Allow-Origin \"allowed-partner.com\" {\n\tmatch header Access-Control-Allow-Origin *\n}\nreverse_proxy upstream:443\n```\n\n覆盖代理上游为以`/no-cache`开头的路径设置的缓存过期时间；启用`defer`是必要的，以确保在代理写入其标头之后设置标头：\n\n```caddy-d\nheader /no-cache* >Cache-Control no-cache\nreverse_proxy upstream:443\n```\n\n执行`Set-Cookie`标头的延迟更新以添加`SameSite=None`；正则表达式捕获用于获取现有值，`$1`在开头重新插入它并附加附加选项：\n\n```caddy-d\nheader >Set-Cookie (.*) \"$1; SameSite=None;\"\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/import.md",
    "content": "---\ntitle: import (Caddyfile directive)\n---\n\n# import\n\n包含[snippet](/docs/caddyfile/concepts#snippets)或文件，将此指令替换为代码片段或文件的内容。\n\n该指令是一种特殊情况：它在解析结构之前进行评估，并且可以出现在 Caddyfile 中的任何位置。\n\n## 语法\n\n```caddy-d\nimport <pattern> [<args...>] [{block}]\n```\n\n- **&lt;pattern&gt;** 是要包含的文件名、glob 模式或[snippet](/docs/caddyfile/concepts#snippets)的名称。它的内容将替换该行，就像该文件的内容一开始就出现在此处一样。\n\n  如果找不到特定文件，则为错误，但空 glob 模式不是错误。\n\n  如果导入特定文件，如果文件为空，将会发出警告。\n\n  如果模式是文件名或 glob，则它始终与`import`出现的文件相关。\n\n  如果使用 glob 模式`*`作为最终路径段，则隐藏文件（即以`.`开头的文件）将被忽略。要导入隐藏文件，请使用`.*`作为最后一段。\n- **&lt;args...&gt;** 是传递给导入令牌的可选参数列表。此占位符是一种特殊情况，在 Caddyfile 解析时而不是在运行时进行评估。它们可以以多种形式使用，类似于[Go 的切片语法](https://gobyexample.com/slices)：\n  -`{args[n]}`，其中`n`是参数的从 0 开始的位置索引\n  -`{args[:]}`插入所有参数\n  -`{args[:m]}`插入`m`之前的参数\n  -`{args[n:]}`插入以`n`开头的参数\n  -`{args[n:m]}`插入`n`和`m`之间的参数\n\n  对于插入许多令牌的表单，占位符**must**是[词法单元](/docs/caddyfile/concepts#tokens-and-quotes)本身，它不能是另一个令牌的一部分。换句话说，它周围必须有空格，并且不能用引号引起来。\n\n  请注意，在 v2.7.0 之前，语法为`{args.N}`，但这种形式已被弃用，取而代之的是上面更灵活的语法。\n\n⚠️<i>实验版</i><span style='white-space: pre;'>|</span><span>v2.9.x+</span>\n- **{block}** 是传递给导入令牌的可选块。此占位符是一种特殊情况，在 Caddyfile 解析时（而不是运行时）递归求值。它们可以以两种形式使用：\n  -`{block}`，其中整个提供的块的内容将替换占位符\n  -`{blocks.key}`，其中`key`是所提供块中参数的第一个标记\n\n\n## 示例\n\n导入相邻启用站点的文件夹中的所有文件（隐藏文件除外）：\n\n```caddy-d\nimport sites-enabled/*\n```\n\n导入使用导入参数设置 CORS 标头的代码片段：\n\n```caddy\n(cors) {\n\t@origin header Origin {args[0]}\n\theader @origin Access-Control-Allow-Origin \"{args[0]}\"\n\theader @origin Access-Control-Allow-Methods \"OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE\"\n}\n\nexample.com {\n\timport cors example.com\n}\n```\n\n导入一个片段，它将代理上游列表作为参数：\n\n```caddy\n(https-proxy) {\n\treverse_proxy {args[:]} {\n\t\ttransport http {\n\t\t\ttls\n\t\t}\n\t}\n}\n\nexample.com {\n\timport https-proxy 10.0.0.1 10.0.0.2 10.0.0.3\n}\n```\n\n导入一个片段，该片段创建一个代理，并将前缀重写规则作为第一个参数：\n\n```caddy\n(proxy-rewrite) {\n\trewrite {args[0]}{uri}\n\treverse_proxy {args[1:]}\n}\n\nexample.com {\n\timport proxy-rewrite /api 10.0.0.1 10.0.0.2 10.0.0.3\n}\n```\n\n\n⚠️<i>实验版</i><span style='white-space: pre;'>|</span><span>v2.9.x+</span>\n\n导入一个片段，该片段以可配置的“hello world”消息和内容类型进行响应：\n\n```caddy\n(hello-world) {\n\theader {\n\t\tCache-Control max-age=3600\n\t\tX-Foo bar\n\t\t{blocks.content_type}\n\t}\n\trespond /hello-world 200 {\n\t\t{blocks.body}\n\t}\n}\n\nexample.com {\n\timport hello-world {\n\t\tcontent_type {\n\t\t\tContent-Type text/html\n\t\t}\n\t\tbody {\n\t\t\tbody \"<h1>hello world</h1>\"\n\t\t}\n\t}\n}\n```\n\n导入为反向代理提供可扩展选项的代码片段：\n\n```caddy\n(extendable-proxy) {\n\treverse_proxy {\n\t\t{blocks.proxy_target}\n\t\t{blocks.proxy_options}\n\t}\n}\n\nexample.com {\n\timport extendable-proxy {\n\t\tproxy_target {\n\t\t\tto 10.0.0.1\n\t\t}\n\t\tproxy_options {\n\t\t\ttransport http {\n\t\t\t\ttls\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n导入一个提供任何指令集的代码片段，但带有预加载的中间件：\n\n```caddy\n(instrumented-route) {\n\theader {\n\t\tAlt-Svc `h3=\"0.0.0.0:443\"; ma=2592000`\n\t}\n\ttracing {\n\t\tspan args[0]\n\t}\n\t{block}\n}\n\nexample.com {\n\timport instrumented-route example-com {\n\t\trespond \"OK\"\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/intercept.md",
    "content": "---\ntitle: intercept (Caddyfile指令)\n---\n\n<script>\nready(function() {\n\t// Fix response matchers to render with the right color,\n\t// and link to response matchers section\n\t$$_('pre.chroma .k').forEach(item => {\n\t\tif (item.innerText.includes('@')) {\n\t\t\tlet text = item.innerText.replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\t\t\tlet url = '#' + item.innerText.replace(/_/g, \"-\");\n\t\t\titem.classList.add('nd');\n\t\t\titem.classList.remove('k');\n\t\t\titem.innerHTML = `<a href=\"#response-matcher\" style=\"color: inherit;\" title=\"Response matcher\">${text}</a>`;\n\t\t}\n\t});\n\n\t// Response matchers\n\tconst nameMatchers = Array.from($$_('pre.chroma .nd')).filter(item => item.innerText.includes('@name'));\n\tif (nameMatchers.length > 0) {\n\t\tconst first = nameMatchers[0];\n\t\tconst span = document.createElement('span');\n\t\tspan.className = 'nd';\n\t\tfirst.parentNode.insertBefore(span, first);\n\t\tspan.appendChild(first);\n\t\tspan.innerHTML = '<a href=\"/docs/caddyfile/response-matchers\" style=\"color: inherit;\">@name</a>';\n\t}\n\n\t$$_('pre.chroma .k').forEach(item => {\n\t\tif (item.innerText === 'status') {\n\t\t\titem.innerHTML = '<a href=\"/docs/caddyfile/response-matchers#status\" style=\"color: inherit;\">status</a>';\n\t\t}\n\t});\n\n\tconst headerElements = $$_('pre.chroma .k');\n\tfor (let item of headerElements) {\n\t\tif (item.innerText.includes('header')) {\n\t\t\titem.innerHTML = '<a href=\"/docs/caddyfile/response-matchers#header\" style=\"color: inherit;\">header</a>';\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// We'll add links to all the subdirectives if a matching anchor tag is found on the page.\n\taddLinksToSubdirectives();\n});\n</script>\n\n# intercept\n\n这是从 [`reverse_proxy` 指令](reverse_proxy)的[响应拦截](reverse_proxy#intercepting-responses)功能抽象出来的通用形式。它可以与任何会产生响应的处理器一起使用，包括来自插件的处理器，例如 [FrankenPHP](https://frankenphp.dev/) 的 `php_server`。\n\n此指令允许你[匹配响应](/docs/caddyfile/response-matchers)，并调用第一个匹配的 `handle_response` 路由或 `replace_status`。调用时，原始响应体会被暂缓写出，从而让该路由有机会写出不同的响应体、新的状态码，或执行必要的响应头操作。如果该路由**没有**写出新的响应体，则会改为写出原始响应体。\n\n\n## 语法\n\n```caddy-d\nintercept [<matcher>] {\n\t@name {\n\t\tstatus <code...>\n\t\theader <field> [<value>]\n\t}\n\n\treplace_status [<response_matcher>] <code>\n\n\thandle_response [<response_matcher>] {\n\t\t<directives...>\n\t}\n}\n```\n\n- **@name** 是一个命名的[响应匹配器](/docs/caddyfile/response-matchers)块。只要每个响应匹配器都有唯一名称，就可以定义多个匹配器。响应可以按状态码以及响应头是否存在或其值进行匹配。\n\n- **replace_status** <span id=\"replace_status\"/> 会在给定匹配器匹配响应时，直接更改响应状态码。\n\n- **handle_response** <span id=\"handle_response\"/> 定义原始响应被给定响应匹配器匹配时要执行的路由。如果省略匹配器，则拦截所有响应。定义多个 `handle_response` 块时，将应用第一个匹配的块。在该块内部，可以使用所有其他[指令](/docs/caddyfile/directives)。\n\n在 `handle_response` 路由中，可以使用以下占位符从原始响应中提取信息：\n\n- `{resp.status_code}` 原始响应的状态码。\n\n- `{resp.header.*}` 原始响应的响应头。\n\n\n## 示例\n\n使用 [FrankenPHP](https://frankenphp.dev/) 的 `php_server` 时，可以用 `intercept` 实现 `X-Accel-Redirect` 支持，按 PHP 应用的请求提供静态文件：\n\n```caddy\nlocalhost {\n\troot /srv\n\n\tintercept {\n\t\t@accel header X-Accel-Redirect *\n\t\thandle_response @accel {\n\t\t\troot /path/to/private/files\n\t\t\trewrite {resp.header.X-Accel-Redirect}\n\t\t\tmethod GET\n\t\t\tfile_server\n\t\t}\n\t}\n\n\tphp_server\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/invoke.md",
    "content": "---\ntitle: invoke (Caddyfile指令)\n---\n\n# invoke\n\n<i>⚠️ 实验性</i>\n\n调用一个[命名路由](/docs/caddyfile/concepts#named-routes)。\n\n当与拥有自身内存状态的 HTTP 处理器指令配合使用时，或者这些处理器在加载时创建成本较高时，此指令很有用。如果你有数百个甚至更多站点，调用命名路由可以帮助减少内存使用。\n\n<aside class=\"tip\">\n\n与 [`import`](/docs/caddyfile/directives/import) 不同，`invoke` 不支持参数，但你可以使用 [`vars`](/docs/caddyfile/directives/vars) 定义可在命名路由中使用的变量。\n\n</aside>\n\n## 语法\n\n```caddy-d\ninvoke [<matcher>] <route-name>\n```\n\n- **&lt;route-name&gt;** 是要调用的、此前已定义的路由名称。如果找不到该路由，则会触发错误。\n\n\n## 示例\n\n定义一个带有 [`reverse_proxy`](/docs/caddyfile/directives/reverse_proxy) 的[命名路由](/docs/caddyfile/concepts#named-routes)，可在多个站点中复用，并为每个站点复用相同的内存中负载均衡状态。\n\n```caddy\n&(app-proxy) {\n\treverse_proxy app-01:8080 app-02:8080 app-03:8080 {\n\t\tlb_policy least_conn\n\t\thealth_uri /healthz\n\t\thealth_interval 5s\n\t}\n}\n\n# 根域名允许通过 /app 子路径访问应用，\n# 其他路径访问主站点。\nexample.com {\n\thandle_path /app* {\n\t\tinvoke app-proxy\n\t}\n\n\thandle {\n\t\troot /srv\n\t\tfile_server\n\t}\n}\n\n# 该应用也可通过子域名访问。\napp.example.com {\n\tinvoke app-proxy\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/log.md",
    "content": "---\ntitle: log (Caddyfile directive)\n---\n\n<script>\nready(function() {\n\t// Fix > in code blocks\n\t$$_('pre.chroma .k').forEach(item => {\n\t\tif (item.innerText.includes('>')) {\n\t\t\t// Skip if ends with >\n\t\t\tif (item.textContent.trim().endsWith('>')) return;\n\t\t\t// Replace > with <span class=\"p\">&gt;</span>\n\t\t\titem.innerHTML = item.innerHTML.replace(/&gt;/g, '<span class=\"p\">&gt;</span>');\n\t\t}\n\t});\n\n\t// We'll add links to all the subdirectives if a matching anchor tag is found on the page.\n\taddLinksToSubdirectives();\n});\n</script>\n\n# log\n\n启用和配置 HTTP 请求日志记录（也称为访问日志）。\n\n<aside class=\"tip\">\n\n要配置 Caddy 的运行时日志，请参阅[`log` 全局选项](/docs/caddyfile/options#log)。\n\n</aside>\n\n\n`log`指令适用于它出现的站点块的主机名，除非被`hostnames`子指令覆盖。\n\n配置后，默认情况下将记录对该站点的所有请求。要有条件地跳过日志记录中的某些请求，请使用[`log_skip` 指令](log_skip)。\n\n要将自定义字段添加到日志条目，请使用[`log_append` 指令](log_append)。\n\n\n- [语法](#syntax)\n- [输出模块](#output-modules)\n  - [stderr](#stderr)\n  - [stdout](#stdout)\n  - [discard](#discard)\n  - [file](#file)\n  - [net](#net)\n- [格式模块](#format-modules)\n  - [console](#console)\n  - [json](#json)\n  - [filter](#filter)\n    - [delete](#delete)\n\t- [rename](#rename)\n\t- [replace](#replace)\n\t- [ip_mask](#ip-mask)\n\t- [query](#query)\n\t- [cookie](#cookie)\n\t- [regexp](#regexp)\n\t- [hash](#hash)\n  - [append](#append)\n- [示例](#examples)\n\n默认情况下，具有潜在敏感信息的标头（`Cookie`、`Set-Cookie`、`Authorization`和`Proxy-Authorization`）将在访问日志中记录为`REDACTED`。可以使用[`log_credentials`](/docs/caddyfile/options#log-credentials)全局服务器选项禁用此行为。\n\n\n## 语法\n\n```caddy-d\nlog [<logger_name>] {\n\thostnames <hostnames...>\n\tno_hostname\n\toutput <writer_module> ...\n\tformat <encoder_module> ...\n\tlevel  <level>\n\tsampling {\n\t\tinterval   <duration>\n\t\tfirst      <number>\n\t\tthereafter <number>\n\t}\n}\n```\n\n- **logger_name**<span id=\"logger_name\"/>是此站点记录器名称的可选覆盖。\n\n  默认情况下，记录器名称是自动生成的，例如`log0`、`log1`等，具体取决于 Caddyfile 中站点的顺序。仅当您希望从全局选项中定义的另一个记录器可靠地引用此记录器的输出时，这才有用。见下面的[示例](#multiple-outputs)。\n\n- **hostnames**<span id=\"hostnames\"/>是此记录器适用的主机名的可选覆盖。\n\n  默认情况下，记录器适用于它出现的站点块的主机名，即站点地址。如果您希望在[wildcard site block](/docs/caddyfile/patterns#wildcard-certificates)中的每个子域定义不同的记录器，这非常有用。见下面的[示例](#wildcard-logs)。\n\n- **no_hostname**<span id=\"no_hostname\"/>阻止记录器与任何站点块的主机名关联。默认情况下，记录器与`log`指令出现的[site address](/docs/caddyfile/concepts#addresses)关联。\n\n  当您想要根据某些条件（例如请求路径或方法）使用[`log_name` 指令](/docs/caddyfile/directives/log_name)将请求记录到不同的文件时，这非常有用。\n\n- **output**<span id=\"output\"/>配置日志写入位置。见下面的[`output` 模块](#output-modules)。\n\n  默认：`stderr`。\n\n- **format**<span id=\"format\"/>描述如何对日志进行编码或格式化。见下面的[`format` 模块](#format-modules)。\n\n  默认：如果检测到`stderr`是终端，则为`console`，否则为`json`。\n\n- **level**<span id=\"level\"/>是登录的最低入门级别。默认：`INFO`。\n\n  请注意，访问日志目前仅发出`INFO`和`ERROR`级别的日志。\n\n- **sampling**<span id=\"sampling\"/>配置日志采样以减少日志量。如果指定了采样，则启用采样，并且以下默认值生效。省略此项将禁用采样。\n\n  - **interval**是进行采样的[持续时间窗口](/docs/conventions#durations)。默认：`1s`（禁用）。\n\n  - **first** 是在给定级别和每个时间间隔的消息中保留多少日志。默认：`100`。\n\n  - **thereafter**是在第一个保留日志之后的每个间隔中要跳过的日志数。默认：`100`。\n\n  例如，对于`interval 1s`、`first 5`和`thereafter 10`，在每 10 秒间隔内，将保留前 5 个日志条目，然后将允许在该秒内通过具有相同级别和消息的每 10 个日志条目。\n\n\n### 输出模块\n\n**output** 子指令允许您自定义日志写入位置。\n\n#### 标准错误\n\n标准错误（控制台，是默认值）。\n\n```caddy-d\noutput stderr\n```\n\n#### 标准输出\n\n标准输出（控制台）。\n\n```caddy-d\noutput stdout\n```\n\n#### 丢弃\n\n无输出。\n\n```caddy-d\noutput discard\n```\n\n#### 文件\n\n一个文件。默认情况下，日志文件根据大小进行轮换（“滚动”），以防止磁盘空间耗尽。\n\n日志滚动由[timberjack <img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://github.com/DeRuina/timberjack)提供\n\n<aside class=\"tip\">\n\n**A note about reloading log file options:** 需要重新启动服务器才能将配置更改应用于给定的输出文件。\n除非您添加新的日志文件名，否则这些更改不会在服务器重新加载时应用。\n\n</aside>\n\n```caddy-d\noutput file <filename> {\n\tmode          <mode>\n\troll_disabled\n\troll_size     <size>\n\troll_interval <duration>\n\troll_minutes  <minutes...>\n\troll_at\t      <times...>\n\troll_uncompressed\n\troll_local_time\n\troll_keep     <num>\n\troll_keep_for <days>\n\tbackup_time_format <format>\n}\n```\n\n- **&lt;filename&gt;** 是日志文件的路径。\n\n  滚动时，文件将使用模板`<name>-<timestamp>-<reason>.log`重命名。时间戳根据[`backup_time_format`](#backup_time_format)选项进行格式化。原因是`size`或`time`，具体取决于哪个触发了轮换。如果文件被压缩，`.gz`会附加到文件名中。\n\n   例如，如果文件名为`access.log`，则滚动文件如果因大小而滚动，则可能命名为`access-2026-01-30T22-15-42.123-size.log`；如果因时间滚动，则滚动文件可能命名为`access-2025-01-30T00-00-00.000-time.log`。\n\n- **mode**<span id=\"mode\"/>是用于日志文件的 Unix 文件模式/权限。该模式由 1 到 4 个八进制数字组成（与 Unix[chmod <img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://en.wikipedia.org/wiki/Chmod)命令接受的数字格式相同，除了全零模式被解释为默认模式`600`）。\n\n  例如：`0600`会将模式设置为`rw-,---,---`（日志文件所有者有读/写权限，其他任何人都没有权限）；`0640`将模式设置为`rw-,r--,---`（对文件所有者具有读/写权限，对组仅具有读权限）；`644`将模式设置为`rw-,r--,r--`向日志文件所有者提供读/写权限，但仅向群组所有者和其他用户提供读权限。\n\n- **roll_disabled**<span id=\"roll_disabled\"/>禁用日志滚动。这可能会导致磁盘空间耗尽，因此仅当您的日志文件以其他方式维护时才使用此选项。\n\n- **roll_size**<span id=\"roll_size\"/>是滚动日志文件的大小。当前的实现支持兆字节分辨率；小数值向上舍入到下一个整数兆字节。例如，`1.1MiB`向上舍入为`2MiB`。\n\n  该功能始终处于启用状态。如果写入日志导致文件超过指定大小，则日志将立即轮转。备份文件名将包含`size`作为原因。\n\n  默认：`100MiB`\n\n- **roll_interval**<span id=\"roll_interval\"/>是日志轮转之间的最大持续时间。该值为[持续时间字符串](/docs/conventions#durations)，之后滚动日志文件。\n\n  启用后，自上次轮换后经过此持续时间后，文件将在下次写入日志时轮换。备份文件名将包含`time`作为原因。\n\n  请注意，如果设置为`24h`，则不一定在午夜滚动，而是在自上次旋转以来的 24 小时标记处滚动。如果由于尺寸而发生滚动，则下一次旋转的时间将比上一次旋转的时间有所偏移。您可以使用`roll_at`或`roll_minutes`选项在特定时间进行滚动。\n\n  默认值：禁用\n\n- **roll_minutes**<span id=\"roll_minutes\"/>是滚动日志文件的分钟值 (0-59) 列表。例如，`10 40`每 30 分钟滚动一次日志文件，`xx:10`和`xx:40`每小时滚动一次日志文件。旋转与时钟分钟（秒 0）对齐。\n\n  启用此功能会生成一个 goroutine 计时器，该计时器会在指定的分钟值处触发日志轮换（即引入少量后台处理）。除了`roll_interval`和`roll_size`之外，还可以进行此操作。备份文件名将包含`time`作为原因。\n\n  默认值：禁用\n\n- **roll_at**<span id=\"roll_at\"/>是滚动日志文件的时间值列表（采用 24 小时格式）。例如，`00:00 12:00`每天会在午夜和中午滚动日志文件两次。旋转与时钟分钟（秒 0）对齐。\n\n  启用此功能会生成一个 goroutine 计时器，该计时器会在指定时间触发日志轮换（即引入少量后台处理）。除了`roll_interval`和`roll_size`之外，还可以进行此操作。备份文件名将包含`time`作为原因。\n\n  默认值：禁用\n\n- **roll_uncompressed**<span id=\"roll_uncompressed\"/>关闭 gzip 日志压缩。\n\n  默认：`gzip`压缩已启用。\n\n- **roll_local_time**<span id=\"roll_local_time\"/>设置滚动以在文件名中使用本地时间戳。 \n  默认：使用 UTC 时间。\n\n- **roll_keep**<span id=\"roll_keep\"/>是在删除最旧的日志文件之前保留多少个日志文件。创建新日志文件时触发。\n\n  默认：`10`\n\n- **roll_keep_for**<span id=\"roll_keep_for\"/>是将滚动文件保留为[持续时间字符串](/docs/conventions#durations)的时间。创建新日志文件时触发。\n  当前的实现支持日分辨率；小数值向上舍入到下一个整天。例如，`36h`（1.5 天）向上舍入为`48h`（2 天）。\n  \n  默认：`2160h`（90 天）\n\n- **backup_time_format**<span id=\"backup_time_format\"/>是备份文件名中使用的时间格式。必须是有效的时间布局字符串；详情请参见[Go documentation](https://pkg.go.dev/time#pkg-constants)。\n\n  默认：`2006-01-02T15-04-05`\n\n\n#### 网\n\n一个网络套接字。如果套接字出现故障，它会在尝试重新连接时将日志转储到 stderr。\n\n```caddy-d\noutput net <address> {\n\tdial_timeout <duration>\n\tsoft_start\n}\n```\n\n- **&lt;address&gt;** 是要写入日志的[address](/docs/conventions#network-addresses)。\n\n- **dial_timeout**<span id=\"dial_timeout\"/>是等待成功连接到日志套接字的时间。如果套接字出现故障，日志发送可能会被阻止长达这么长时间。\n\n- **soft_start**<span id=\"soft_start\"/>在连接到套接字时将忽略错误，即使远程日志服务关闭，也允许您加载配置。日志将被发送到 stderr。\n\n\n### 格式化模块\n\n**format** 子指令允许您自定义日志的编码（格式化）方式。它出现在`log`块内。\n\n<aside class=\"tip\">\n\n**A note about Common Log Format (CLF):** CLF 与现代结构化日志发生冲突。要将您的访问日志转换为已弃用的通用日志格式，请使用[`transform-encoder` 插件 <img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://github.com/caddyserver/transform-encoder)。\n\n</aside>\n\n\n除了每个单独编码器的语法之外，还可以在大多数编码器上设置这些通用属性：\n\n```caddy-d\nformat <encoder_module> {\n\tmessage_key     <key>\n\tlevel_key       <key>\n\ttime_key        <key>\n\tname_key        <key>\n\tcaller_key      <key>\n\tstacktrace_key  <key>\n\tline_ending     <char>\n\ttime_format     <format>\n\ttime_local\n\tduration_format <format>\n\tlevel_format    <format>\n}\n```\n\n- **message_key**<span id=\"message_key\"/>日志条目的消息字段的键。默认：`msg`\n\n- **level_key**<span id=\"level_key\"/>日志条目的级别字段的键。默认：`level`\n\n- **time_key**<span id=\"time_key\"/>日志条目的时间字段的键。默认：`ts`\n- **name_key**<span id=\"name_key\"/>日志条目的名称字段的键。默认：`name`\n\n- **caller_key**<span id=\"caller_key\"/>日志条目的呼叫者字段的键。\n\n- **stacktrace_key**<span id=\"stacktrace_key\"/>日志条目的 stacktrace 字段的键。\n\n- **line_ending**<span id=\"line_ending\"/>要使用的行结尾。\n\n- **time_format**<span id=\"time_format\"/>时间戳的格式。\n  默认：如果格式默认为`console`则为`wall_milli`，否则为`unix_seconds_float`。\n  \n  可能是以下之一：\n  -`unix_seconds_float`自 Unix 纪元以来的浮点数秒数。\n  -`unix_milli_float`自 Unix 纪元以来的浮点数毫秒数。\n  -`unix_nano`自 Unix 纪元以来的纳秒整数。\n  -`iso8601`示例：`2006-01-02T15:04:05.000Z0700`\n  -`rfc3339`示例：`2006-01-02T15:04:05Z07:00`\n  -`rfc3339_nano`示例：`2006-01-02T15:04:05.999999999Z07:00`\n  -`wall`示例：`2006/01/02 15:04:05`\n  -`wall_milli`示例：`2006/01/02 15:04:05.000`\n  -`wall_nano`示例：`2006/01/02 15:04:05.000000000`\n  -`common_log`示例：`02/Jan/2006:15:04:05 -0700`\n  - 或者，任何兼容的时间布局字符串；详情请参见[Go documentation](https://pkg.go.dev/time#pkg-constants)。\n  \n  请注意，格式字符串的部分是布局的特殊常量；所以`2006`是年份，`01`是月份，`Jan`是字符串形式的月份，`02`是日期。不要在格式字符串中使用实际的当前日期数字。\n\n- **time_local**<span id=\"time_local\"/>使用本地系统时间而不是默认的 UTC 时间进行日志记录。\n\n- **duration_format**<span id=\"duration_format\"/>持续时间的格式。\n\n  默认：`seconds`。\n  \n  可能是以下之一：\n  -`s`、`second`或`seconds`浮点数经过的秒数。\n  -`ms`、`milli`或`millis`浮点数经过的毫秒数。\n  -`ns`、`nano`或`nanos`已过去的纳秒整数。\n  -`string`使用 Go 内置的字符串格式，例如`1m32.05s`或`6.31ms`。\n\n- **level_format**<span id=\"level_format\"/>级别的格式。\n\n  默认：如果格式默认为`console`则为`color`，否则为`lower`。\n  \n  可能是以下之一：\n  -`lower`小写。\n  -`upper`大写。\n  -`color`大写，采用 ANSI 颜色。\n  \n\n#### 安慰\n\n控制台编码器格式化日志条目以供人类可读，同时保留一些结构。\n\n```caddy-d\nformat console\n```\n\n#### json\n\n将每个日志条目格式化为 JSON 对象。\n\n```caddy-d\nformat json\n```\n\n\n#### 筛选\n\n允许按字段过滤。\n\n```caddy-d\nformat filter {\n\tfields {\n\t\t<field> <filter> ...\n\t}\n\t<field> <filter> ...\n\twrap <encode_module> ...\n}\n```\n\n可以通过用`>`表示嵌套层来引用嵌套字段。换句话说，对于像`{\"a\":{\"b\":0}}`这样的对象，内部字段可以被引用为`a>b`。\n\n以下字段是日志的基础字段，无法过滤，因为它们是由底层日志库作为特殊情况添加的：`ts`、`level`、`logger`和`msg`。\n\n指定`wrap`是可选的；如果省略，则根据当前输出模块是[`stderr`](#stderr)还是[`stdout`](#stdout)来选择默认值，并且是交互式终端，在这种情况下选择[`console`](#console)，否则选择[`json`](#json)。\n\n作为快捷方式，可以省略`fields`块，并且可以直接在`filter`块内指定过滤器。\n\n\n这些是可用的过滤器：\n\n##### 删除\n\n标记要跳过编码的字段。\n\n```caddy-d\n<field> delete\n```\n\n\n##### 重命名\n\n重命名日志字段的键。\n\n```caddy-d\n<field> rename <key>\n```\n\n\n##### 代替\n\n标记要在编码时用提供的字符串替换的字段。\n\n```caddy-d\n<field> replace <replacement>\n```\n\n\n##### IP掩码\n\n使用 CIDR 掩码（即 IP 中要保留的位数，从左侧开始）屏蔽字段中的 IP 地址。如果该字段是字符串数组（例如 HTTP 标头），则数组中的每个值都会被屏蔽。该值可以是逗号分隔的 IP 地址字符串。\n\nIPv4 和 IPv6 地址有单独的配置，因为它们具有不同的总位数。\n\n最常见的是，要过滤的字段是：\n-`request>remote_ip`为直连客户端\n- 配置[`trusted_proxies`](/docs/caddyfile/options#trusted-proxies)时解析的“真实客户端”为`request>client_ip`\n-`request>headers>X-Forwarded-For`（如果位于反向代理后面）\n\n```caddy-d\n<field> ip_mask [<ipv4> [<ipv6>]] {\n\tipv4 <cidr>\n\tipv6 <cidr>\n}\n```\n\n\n##### 询问\n\n将字段标记为执行一项或多项操作，以操作 URL 字段的查询部分。最常见的是，要过滤的字段是`request>uri`。\n\n```caddy-d\n<field> query {\n\tdelete  <key>\n\treplace <key> <replacement>\n\thash    <key>\n}\n```\n\n可用的操作有：\n\n- **delete** 从查询中删除给定的键。\n\n- **replace** 用 **replacement** 替换给定查询键的值。用于插入密文占位符；您将看到查询键位于 URL 中，但值是隐藏的。\n\n- **hash** 将给定查询键的值替换为该值的 SHA-256 哈希值的前 4 个字节（小写十六进制）。如果值敏感，则有助于模糊该值，同时能够注意到每个请求是否具有不同的值。\n\n\n##### 曲奇饼\n\n将字段标记为执行一个或多个操作，以操作`Cookie`HTTP 标头的值。最常见的是，要过滤的字段是`request>headers>Cookie`。\n\n```caddy-d\n<field> cookie {\n\tdelete  <name>\n\treplace <name> <replacement>\n\thash    <name>\n}\n```\n\n可用的操作有：\n\n- **delete** 从标头中按名称删除给定的 cookie。\n\n- **replace** 用 **replacement** 替换给定 cookie 的值。用于插入密文占位符；您会看到 cookie 位于标头中，但值是隐藏的。\n\n- **hash** 将给定 cookie 的值替换为该值的 SHA-256 哈希值的前 4 个字节（小写十六进制）。如果值敏感，则有助于模糊该值，同时能够注意到每个请求是否具有不同的值。\n\n如果为同一个 cookie 名称定义了多个操作，则仅应用第一个操作。\n\n\n##### 正则表达式\n\n将字段标记为在编码时应用正则表达式替换。如果该字段是字符串数组（例如 HTTP 标头），则数组中的每个值都会应用替换。\n\n```caddy-d\n<field> regexp <pattern> <replacement>\n```\n\n使用的正则表达式语言是 RE2，包含在 Go 中。请参阅[RE2 语法参考](https://github.com/google/re2/wiki/Syntax)和[Go regexp 语法概述](https://pkg.go.dev/regexp/syntax)。\n\n在替换字符串中，可以使用`${group}`引用捕获组，其中`group`是表达式中捕获组的名称或编号。捕获组`0`是完整的正则表达式匹配，`1`是第一个捕获组，`2`是第二个捕获组，依此类推。\n\n\n##### 散列\n\n标记要替换为编码时值的 SHA-256 哈希值的前 4 个字节（8 个十六进制字符）的字段。如果字段是字符串数组（例如 HTTP 标头），则数组中的每个值都会进行哈希处理。\n\n如果值敏感，则有助于模糊该值，同时能够注意到每个请求是否具有不同的值。\n\n```caddy-d\n<field> hash\n```\n\n#### 附加\n\n将字段附加到所有日志条目。\n\n```caddy-d\nformat append {\n\tfields {\n\t\t<field> <value>\n\t}\n\t<field> <value>\n\twrap <encode_module> ...\n}\n```\n\n它对于添加有关生成日志条目的 Caddy 实例的信息最有用，可能是通过环境变量。字段值可以是全局占位符（例如`{env.*}`），但不是每个请求占位符，因为日志是在 HTTP 请求上下文之外写入的。\n\n指定`wrap`是可选的；如果省略，则根据当前输出模块是[`stderr`](#stderr)还是[`stdout`](#stdout)来选择默认值，并且是交互式终端，在这种情况下选择[`console`](#console)，否则选择[`json`](#json)。\n\n可以省略`fields`块，并且可以直接在`append`块内指定字段。\n\n\n\n## 示例\n\n启用对默认记录器的访问日志记录。\n\n换句话说，默认情况下记录到`stderr`，但是可以通过使用[`log` 全局选项](/docs/caddyfile/options#log)重新配置`default`记录器来更改：\n\n```caddy\nexample.com {\n\tlog\n}\n```\n\n\n将日志写入文件（使用日志滚动，默认启用）：\n\n```caddy\nexample.com {\n\tlog {\n\t\toutput file /var/log/access.log\n\t}\n}\n```\n\n\n自定义日志滚动，每天午夜或日志文件达到 1 GB 时滚动（以先到者为准），并保留 5 个滚动文件或 30 天的日志：\n\n```caddy\nexample.com {\n\tlog {\n\t\toutput file /var/log/access.log {\n\t\t\troll_at 00:00\n\t\t\troll_size 1gb\n\t\t\troll_keep 5\n\t\t\troll_keep_for 720h\n\t\t}\n\t}\n}\n```\n\n\n从日志中删除`User-Agent`请求标头：\n\n```caddy\nexample.com {\n\tlog {\n\t\tformat filter {\n\t\t\trequest>headers>User-Agent delete\n\t\t}\n\t}\n}\n```\n\n\n编辑多个敏感 cookie。 （请注意，默认情况下，某些敏感标头会使用空值进行记录；请参阅[`log_credentials` 全局选项](/docs/caddyfile/options#log-credentials)以启用记录`Cookie`标头值）：\n\n```caddy\nexample.com {\n\tlog {\n\t\tformat filter {\n\t\t\trequest>headers>Cookie cookie {\n\t\t\t\treplace session REDACTED\n\t\t\t\tdelete secret\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n\n从请求中屏蔽远程地址，保留 IPv4 地址的前 16 位（即 255.255.0.0），以及 IPv6 地址的前 32 位。\n\n请注意，从 Caddy v2.7 开始，`remote_ip`和`client_ip`都会被记录，其中`client_ip`是配置[`trusted_proxies`](/docs/caddyfile/options#trusted-proxies)时的“真实 IP”：\n\n```caddy\nexample.com {\n\tlog {\n\t\tformat filter {\n\t\t\trequest>remote_ip ip_mask 16 32\n\t\t\trequest>client_ip ip_mask 16 32\n\t\t}\n\t}\n}\n```\n\n\n要将环境变量中的服务器 ID 附加到所有日志条目，并将其与`filter`链接以删除标头：\n\n```caddy\nexample.com {\n\tlog {\n\t\tformat append {\n\t\t\tserver_id {env.SERVER_ID}\n\t\t\twrap filter {\n\t\t\t\trequest>headers>Cookie delete\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n\n<span id=\"wildcard-logs\" />通过为每个记录器覆盖`hostnames`为[wildcard site block](/docs/caddyfile/patterns#wildcard-certificates)中的每个子域写入单独的日志文件。这里使用[snippet](/docs/caddyfile/concepts#snippets)来避免重复：\n\n```caddy\n(subdomain-log) {\n\tlog {\n\t\thostnames {args[0]}\n\t\toutput file /var/log/{args[0]}.log\n\t}\n}\n\n*.example.com {\n\timport subdomain-log foo.example.com\n\t@foo host foo.example.com\n\thandle @foo {\n\t\trespond \"foo\"\n\t}\n\n\timport subdomain-log bar.example.com\n\t@bar host bar.example.com\n\thandle @bar {\n\t\trespond \"bar\"\n\t}\n}\n```\n\n<span id=\"multiple-outputs\" />将特定子域的访问日志写入两个不同的文件，具有不同的格式（一个为[`transform-encoder` 插件 <img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://github.com/caddyserver/transform-encoder)，另一个为[`json`](#json)）。 \n\n这是通过在站点块中将记录器名称覆盖为`foo`来实现的，然后使用`include http.log.access.foo`将该记录器生成的访问日志包含在全局选项中的两个记录器中：\n\n```caddy\n{\n\tlog access-formatted {\n\t\tinclude http.log.access.foo\n\t\toutput file /var/log/access-foo.log\n\t\tformat transform \"{common_log}\"\n\t}\n\n\tlog access-json {\n\t\tinclude http.log.access.foo\n\t\toutput file /var/log/access-foo.json\n\t\tformat json\n\t}\n}\n\nfoo.example.com {\n\tlog foo\n}\n```\n\n<span id=\"sampling-example\" />通过采样减少日志量，例如保留每秒前 5 个请求，然后每 10 个请求保留 1 个：\n\n```caddy\nexample.com {\n\tlog {\n\t\tsampling {\n\t\t\tinterval   1s\n\t\t\tfirst      5\n\t\t\tthereafter 10\n\t\t}\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/log_append.md",
    "content": "---\ntitle: log_append (Caddyfile指令)\n---\n\n# log_append\n\n为当前请求的访问日志追加一个字段。\n\n此指令应与 [`log` 指令](log)一起使用，因为首先需要 `log` 启用访问日志。\n\n值可以是静态字符串，也可以是一个[占位符](/docs/caddyfile/concepts#placeholders)，该占位符会在请求时被替换为对应值。\n\n\n## 语法\n\n```caddy-d\nlog_append [<matcher>] [<]<key> <value>\n```\n\n默认情况下，日志字段会在中间件链返回时添加（即“late”模式），也就是在后续所有处理器完成之后添加（例如在 [`reverse_proxy`](reverse_proxy)、[`respond`](respond) 或 [`file_server`](file_server) 等会写出响应的处理器之后），因此它会捕获请求和响应的最终状态。\n\n如果使用 `<` 作为键名前缀，则该字段会被标记为“early”模式，意味着日志字段会在调用链中的下一个处理器之前添加到日志中，因此可以在请求被后续处理器修改之前读取它。\n\n仅用于调试目的（不要在生产环境中使用）：当值为以下占位符之一时，此处理器有特殊处理：`{http.request.body}`、`{http.request.body_base64}`、`{http.response.body}` 或 `{http.response.body_base64}`。如果使用请求体占位符，则会隐式启用“early”模式，并缓冲请求体。如果使用响应体占位符，则会启用响应缓冲以捕获响应体，并在写出响应时以“late”模式将该字段添加到日志中。\n\n\n## 示例\n\n在日志中显示请求所服务的站点区域，是 `static` 还是 `dynamic`：\n\n```caddy\nexample.com {\n\tlog\n\n\thandle /static* {\n\t\tlog_append area \"static\"\n\t\trespond \"Static response!\"\n\t}\n\n\thandle {\n\t\tlog_append area \"dynamic\"\n\t\treverse_proxy localhost:9000\n\t}\n}\n```\n\n在日志中显示实际使用的反向代理上游（`node1`、`node2` 或 `node3`），以及代理到上游所花费的毫秒数和上游写出响应头所需时间：\n\n```caddy\nexample.com {\n\tlog\n\n\thandle {\n\t\treverse_proxy node1:80 node2:80 node3:80 {\n\t\t\tlb_policy random_choose 2\n\t\t}\n\t\tlog_append upstream_host {rp.upstream.host}\n\t\tlog_append upstream_duration_ms {rp.upstream.duration_ms}\n\t\tlog_append upstream_latency_ms {rp.upstream.latency_ms}\n\t}\n}\n```\n\n可以通过给键名加上 `<` 前缀，让字段以“early”模式添加到日志中。这允许你在请求被后续处理器修改之前捕获请求状态。例如，记录重写之前的原始请求路径（虽然这是一个刻意构造的例子，因为原始请求路径本来就会被记录，但它有助于说明这一点）：\n\n```caddy\nexample.com {\n\tlog\n\tlog_append <original_path {http.request.uri.path}\n\trewrite /new-base{uri}\n\treverse_proxy localhost:9000\n}\n```\n\n为了调试，将请求体和响应体添加到日志中（不要在生产环境中使用，因为这会损害性能并让日志非常嘈杂）。如果你预期请求体或响应体是包含不可打印字符的二进制数据，可以改用这些占位符的 base64 版本（例如 `{http.request.body_base64}` 和 `{http.response.body_base64}`），这样更便于复制和检查：\n\n```caddy\nexample.com {\n\tlog\n\tlog_append req_body {http.request.body}\n\tlog_append resp_body {http.response.body}\n\n\treverse_proxy localhost:9000\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/log_name.md",
    "content": "---\ntitle: log_name (Caddyfile指令)\n---\n\n# log_name\n\n在使用 [`log` 指令](log)写入访问日志时，覆盖当前请求要使用的日志器名称。\n\n当你想根据请求路径或方法等条件，将请求记录到不同文件时，此指令很有用。\n\n可以指定多个日志器名称，这样请求日志会被推送到多个匹配的日志器。\n\n它通常与 `log` 指令的 [`no_hostname`](log#no_hostname) 选项配合使用。该选项会防止日志器关联到站点块的任何主机名，因此只有设置了 `log_name` 的请求才会将日志推送到该日志器。\n\n\n## 语法\n\n```caddy-d\nlog_name [<matcher>] <names...>\n```\n\n\n## 示例\n\n你可能希望将请求记录到不同文件中，例如把健康检查日志与主要访问日志分开。\n\n在 `log` 中使用 `no_hostname` 可以防止日志器关联到站点块的任何主机名（此处为 `localhost`），因此只有设置了对应 `log_name` 的请求才会写入该日志器。\n\n```caddy\nlocalhost {\n\tlog {\n\t\toutput file ./caddy.access.log\n\t}\n\n\tlog health_check_log {\n\t\toutput file ./caddy.access.health.log\n\t\tno_hostname\n\t}\n\n\thandle /healthz* {\n\t\tlog_name health_check_log\n\t\trespond \"Healthy\"\n\t}\n\n\thandle {\n\t\trespond \"Hello World\"\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/log_skip.md",
    "content": "---\ntitle: log_skip (Caddyfile指令)\n---\n\n# log_skip\n\n对匹配的请求跳过访问日志记录。\n\n此指令应与 [`log` 指令](log)一起使用，用于跳过那些与你需求无关的请求日志。\n\n在 v2.8.0 之前，此指令名为 `skip_log`；后来为与其他指令保持一致而重命名为 `log_skip`。\n\n\n## 语法\n\n```caddy-d\nlog_skip [<matcher>]\n```\n\n\n## 示例\n\n对存储在子路径中的静态文件跳过访问日志记录：\n\n```caddy\nexample.com {\n\troot /srv\n\n\tlog\n\tlog_skip /static*\n\n\tfile_server\n}\n```\n\n\n对匹配某个模式的请求跳过访问日志；在此例中，是具有特定扩展名的文件：\n\n```caddy-d\n@skip path_regexp \\.(js|css|png|jpe?g|gif|ico|woff|otf|ttf|eot|svg|txt|pdf|docx?|xlsx?)$\nlog_skip @skip\n```\n\n\n如果它位于一个已经处于匹配器中的路由内，则不需要再提供匹配器。例如，为特定子路径提供文件服务的 handle：\n\n```caddy-d\nhandle_path /static* {\n\troot /srv/static\n\tlog_skip\n\tfile_server\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/map.md",
    "content": "---\ntitle: map (Caddyfile指令)\n---\n\n# map\n\n设置根据输入值进行切换的自定义占位符的值。\n\n它将源值与映射的输入端进行比较，如果匹配，则将输出值应用到每个目标。目标则替换成对应的占位符。也可以为每个目标指定默认的输出值。\n\n映射的占位符在使用前不会被评估，所以即使是非常大的映射，这个指令也是相当有效的。\n\n## Syntax\n\n```caddy-d\nmap [<matcher>] <source> <destinations...> {\n\t[~]<input> <outputs...>\n\tdefault    <defaults...>\n}\n```\n\n- **&lt;source&gt;**  是要切换的输入值。通常是一个占位符。\n\n- **&lt;destinations...&gt;** 是要创建的容纳输出值的占位符。\n\n- **&lt;input&gt;** 是要匹配的输入值。如果前缀带了`~`，它讲被作为正则表达式处理。\n\n- **&lt;outputs...&gt;** 是一个或多个要存储在相关占位符中的输出值。 第一个输出写到第一个目标，第二个输出写到第二个目标，以此类推。\n\n  作为一种特殊情况，Caddyfile分析器将字面连字符（`-`）的输出视为null/nil值。如果你想在给定输入的情况下，对该特定输出使用默认值，但又想对其他输出使用非默认值，这很有用。\n\n  如果可能的话，输出将被转换类型；`true`和`false`将被转换为布尔类型，数字值将被相应地转换为整数或浮点数。为了避免这种转换，你可以用[引号](/docs/caddyfile/concepts#tokens-and-quotes)来包裹输出，它们将保持为字符串。\n\n  每个映射的输出数量不得超过目标的数量；但是，为了方便起见，输出的数量可以少于目标的数量，任何缺失的输出将被隐含地填入。\n\n  如果使用正则表达式作为输入，那么捕获组可以用`${group}`来引用，其中`group`是表达式中捕获组的名称或编号。捕获组`0`是完整的重构表达式匹配，`1`是第一个捕获组，`2`是第二个捕获组，以此类推。\n\n- **&lt;default&gt;** 指定了在没有匹配输入的情况下要存储的输出值。\n\n\n\n\n## 示例\n\n下面的例子演示了这个指令的大部分内容。\n\n```caddy-d\nmap {host}             {my_placeholder}  {magic_number} {\n\texample.com        \"some value\"      3\n\tfoo.example.com    \"another value\"\n\t(.*)\\.example.com  \"${1} subdomain\"  5\n\n\t~.*\\.net$          -                 7\n\t~.*\\.xyz$          -                 15\n\n\tdefault            \"unknown domain\"  42\n}\n```\n\n这条指令切换到`{host}`的值，也就是请求的域名。\n\n- 如果请求的是`example.com`，则将`{my_placeholder}`设`some value`，将`{magic_number}`设为`3`。\n- 否则，如果请求的是`foo.example.com`，将`{my_placeholder}`设置为`another value`，并让`{magic_number}`默认为`42`。\n- 否则，如果请求是针对`example.com`的任何一个子域，则将`{my_placeholder}`设置为包含正则表达式捕获的第一个组的字符串，即整个子域，并将`{magic_number}`设置为`5`。\n- 否则，如果请求是针对任何以`.net`或`.xyz`结尾的主机，只需将`{magic_number}`分别设置为`7`或`15`。不设置`{my_placeholder}`。\n- 否则（对于所有其他主机），将适用默认值。`{my_placeholder}`将被设置为`unknown domain`，`{magic_number}`将被设置为`42`。\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/method.md",
    "content": "---\ntitle: method (Caddyfile指令)\n---\n\n# method\n\n改变请求中的HTTP方法。\n\n\n## 语法\n\n```caddy-d\nmethod [<matcher>] <method>.\n```\n\n- **&lt;method&gt;** 是要改变请求的HTTP方法。\n\n\n## 示例\n\n将`/api`下的所有请求的方法改为`POST`。\n\n```caddy-d\nmethod /api* POST\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/metrics.md",
    "content": "---\ntitle: metrics (Caddyfile指令)\n---\n\n# metrics\n\n配置一个Prometheus度量标准展示端点，这样采集的指标可以被暴露出来以供抓取。\n\n注意，[admin API](/docs/api)上也有一个`/metrics`端点。 但它不能进行配置，而且当管理API被禁用时也不能使用。\n\n这个端点将以[Prometheus数据传输格式](https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format)返回指标。如果经过协商，也可以使用[OpenMetrics数据传输格式](https://pkg.go.dev/github.com/prometheus/client_golang@v1.9.0/prometheus/promhttp#HandlerOpts)\n(`application/openmetrics-text`)。\n\n另见[用Prometheus度量标准监控Caddy](/docs/metrics)。\n\n## 语法\n\n```caddy-d\nmetrics [<matcher>] {\n\tdisable_openmetrics\n}\n```\n\n- **disable_openmetrics** 禁用OpenMetrics格式。这个指令通常没有必要，除非需要解决解析错误。\n\n## 示例\n\n在默认的`/metrics`路径下显示指标。\n\n```caddy-d\n  metrics /metrics\n```\n\n在另一个路径上显示指标。\n\n```caddy-d\nmetrics /foo/bar/baz\n```\n\n在一个单独的子域中提供指标。\n\n```caddy\nmetrics.example.com {\n\tmetrics\n}\n```\n\n停用OpenMetrics格式。\n\n```caddy-d\nmetrics /metrics {\n\tdisable_openmetrics\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/php_fastcgi.md",
    "content": "---\ntitle: php_fastcgi (Caddyfile directive)\n---\n\n<script>\nready(function() {\n\t// We'll add links to all the subdirectives if a matching anchor tag is found on the page.\n\taddLinksToSubdirectives();\n});\n</script>\n\n# php_fastcgi\n\n一个固执己见的指令，将请求代理到 PHP FastCGI 服务器，例如 php-fpm。\n\n- [语法](#syntax)\n- [展开形式](#expanded-form)\n  - [解释](#explanation)\n- [示例](#examples)\n\nCaddy 的[`reverse_proxy`](reverse_proxy)能够为任何 FastCGI 应用程序提供服务，但该指令是专门为 PHP 应用程序量身定制的。该指令是一个方便的快捷方式，取代了[更长的配置](#expanded-form)。\n\n它期望站点根目录中的任何`index.php`都充当路由器。如果不希望这样，可以重新配置[`try_files` 子指令](#try_files)来修改默认的重写行为，或者以[展开形式](#expanded-form)为基础并根据您的需要进行自定义。\n\n除了下面列出的子指令之外，该指令还支持[`reverse_proxy`](reverse_proxy#syntax)的所有子指令。例如，您可以启用负载均衡和运行状况检查。\n\n**Most modern PHP apps work fine without extra subdirectives or customization.** 子指令通常仅在某些边缘情况或旧版 PHP 应用程序中使用。\n\n## 语法\n\n```caddy-d\nphp_fastcgi [<matcher>] <php-fpm_gateways...> {\n\troot <path>\n\tsplit <substrings...>\n\tindex <filename>|off\n\ttry_files <files...>\n\tenv [<key> <value>]\n\tresolve_root_symlink\n\tcapture_stderr\n\tdial_timeout  <duration>\n\tread_timeout  <duration>\n\twrite_timeout <duration>\n\n\t<any other reverse_proxy subdirectives...>\n}\n```\n\n- **<php-fpm_gateways...>** 是 FastCGI 服务器的[addresses](/docs/conventions#network-addresses)。通常是 TCP 套接字或 unix 套接字文件。\n\n- **root**<span id=\"root\"/>将根文件夹设置为站点。建议始终将[`root` 指令](root)与`php_fastcgi`结合使用，但是当您的 PHP-FPM 上游使用与 Caddy 不同的根目录时，覆盖此设置可能会很有用（请参阅[示例](#docker)）。如果使用，则默认为[`root` 指令](root)的值，否则默认为 Caddy 的当前工作目录。\n\n- **split**<span id=\"split\"/>设置将 URI 分成两部分的子字符串。第一个匹配的子字符串将用于从路径中分割“路径信息”。第一部分以匹配的子字符串为后缀，并将被假定为实际资源（CGI 脚本）名称。第二部分将设置为 PATH_INFO 供 CGI 脚本使用。默认：`.php`\n\n- **index**<span id=\"index\"/>指定视为目录索引文件的文件名。这会影响[展开形式](#expanded-form)中的文件匹配器。默认：`index.php`。可以设置为`off`，以在找不到匹配文件时禁用重写回退到`index.php`。\n\n- **try_files**<span id=\"try_files\"/>指定默认尝试文件重写的覆盖。详情请见[`try_files` 指令](try_files)。默认：`{path} {path}/index.php index.php`。\n\n- **env**<span id=\"env\"/>将额外的环境变量设置为给定值。可以为多个环境变量指定多次。默认情况下，所有相关的 FastCGI 环境变量都已设置（包括 HTTP 标头），但您可以根据需要添加或覆盖变量。 \n\n- **resolve_root_symlink**<span id=\"resolve_root_symlink\"/>当[`root`](#root)目录是符号链接（symlink）时，这可以将其解析为其实际值。有时，通过简单地交换符号链接以指向另一个目录中的新版本，这可以用作部署策略。默认情况下禁用以避免重复的系统调用。\n\n- **capture_stderr**<span id=\"capture_stderr\"/>可以捕获并记录上游 fastcgi 服务器在`stderr`上发送的任何消息。默认情况下，日志记录在`WARN`级别完成。如果响应具有`4xx`或`5xx`状态，则将使用`ERROR`级别。默认情况下，`stderr`被忽略。\n\n- **dial_timeout**<span id=\"dial_timeout\"/>是[持续时间值](/docs/conventions#durations)，设置连接到上游套接字时等待多长时间。默认：`3s`。\n\n- **read_timeout**<span id=\"read_timeout\"/>是[持续时间值](/docs/conventions#durations)，设置从 FastCGI 上游读取时等待的时间。默认：无超时。\n\n- **write_timeout**<span id=\"write_timeout\"/>是[持续时间值](/docs/conventions#durations)，设置发送到 FastCGI 上游时等待多长时间。默认：无超时。\n\n\n由于该指令是反向代理上的固执己见的包装器，因此您可以使用[`reverse_proxy`](reverse_proxy#syntax)的任何子指令来自定义它。\n\n\n## 展开形式\n\n`php_fastcgi`指令（无子指令）与以下配置相同。大多数现代 PHP 应用程序都可以很好地使用此预设。如果您没有，请随意借鉴此内容并根据需要进行自定义，而不是使用`php_fastcgi`快捷方式。\n\n```caddy-d\nroute {\n\t# Add trailing slash for directory requests\n\t# This redirection is automatically disabled if \"{http.request.uri.path}/index.php\"\n\t# doesn't appear in the try_files list\n\t@canonicalPath {\n\t\tfile {path}/index.php\n\t\tnot path */\n\t}\n\tredir @canonicalPath {http.request.orig_uri.path}/ 308\n\n\t# If the requested file does not exist, try index files and assume index.php always exists\n\t@indexFiles file {\n\t\ttry_files {path} {path}/index.php index.php\n\t\ttry_policy first_exist_fallback\n\t\tsplit_path .php\n\t}\n\trewrite @indexFiles {file_match.relative}\n\n\t# Proxy PHP files to the FastCGI responder\n\t@phpFiles path *.php\n\treverse_proxy @phpFiles <php-fpm_gateway> {\n\t\ttransport fastcgi {\n\t\t\tsplit .php\n\t\t}\n\t}\n}\n```\n\n### 解释\n\n- 第一部分涉及规范化请求路径。目标是确保针对磁盘上目录的请求实际上将尾部斜杠`/`添加到请求路径中，以便只有单个 URL 对于对该目录的请求有效。\n\n  仅当`try_files`子指令包含`{path}/index.php`（默认值）时，才会发生此规范化。\n\n  这是通过使用请求匹配器来执行的，该请求匹配器仅匹配不以斜杠结尾的请求，并且映射到磁盘上包含`index.php`文件的目录，如果匹配，则执行附加尾部斜杠的 HTTP 308 重定向。例如，如果磁盘上存在`/foo/index.php`，它会将路径`/foo`的请求重定向到`/foo/`（附加`/`，以规范化目录路径）。\n\n- 下一节将讨论根据磁盘上是否存在匹配文件来执行路径重写。这还有一个副作用，就是记住`.php`之后的路径部分（如果请求路径中有`.php`）。这对于 Caddy 正确设置 FastCGI 环境变量非常重要。\n\n  - 首先，它检查`{path}`是否是磁盘上存在的文件。如果是这样，它将重写到该路径。这本质上使其余部分短路，并确保对磁盘上_确实存在_的文件的请求不会被重写（请参阅下面的后续步骤）。因此，如果您的磁盘上有一个`/js/app.js`文件，那么对该路径的请求将保持不变。\n\n  - 其次，它检查`{path}/index.php`是否是磁盘上存在的文件。如果是这样，它将重写到该路径。对于像`/foo/`这样的目录的请求，它会查找`/foo//index.php`（它被规范化为`/foo/index.php`），并将请求重写到该路径（如果存在）。如果您在 Webroot 的子目录中运行另一个 PHP 应用程序，此行为有时会很有用。\n\n  - 最后，它总是会重写为`index.php`（它几乎总是存在于现代 PHP 应用程序中）。这允许您的 PHP 应用程序通过使用`index.php`脚本作为其入口点来处理对_不_映射到磁盘上的文件的路径的任何请求。\n\n- 最后，最后一部分是实际代理对 PHP FastCGI（或 PHP-FPM）服务的请求以实际运行 PHP 代码。请求匹配器只会匹配以`.php`结尾的请求，因此，任何不是 PHP 脚本且存在于磁盘上的文件都不会被该指令处理，并且会失败。\n\n`php_fastcgi`指令本身通常是不够的。它几乎应该总是与[`root` 指令](root)配对来设置文件在磁盘上的位置（对于现代 PHP 应用程序，这可能是`/var/www/html/public`，其中`public`目录包含您的`index.php`），并与[`file_server` 指令](file_server)一起提供您的静态文件（您的 JS、CSS、图像等），这些文件不会由该指令处理并失败。\n\n\n\n## 示例\n\n将所有 PHP 请求代理到在`127.0.0.1:9000`监听的 FastCGI 响应程序：\n\n```caddy-d\nphp_fastcgi 127.0.0.1:9000\n```\n\n相同，但仅适用于`/blog/`下的请求：\n\n```caddy-d\nphp_fastcgi /blog/* localhost:9000\n```\n\n当使用 PHP-FPM 通过 unix 套接字监听时：\n\n```caddy-d\nphp_fastcgi unix//run/php/php8.2-fpm.sock\n```\n\n[`root` 指令](root)几乎总是用于指定包含 PHP 脚本的目录，而[`file_server` 指令](file_server)则用于提供静态文件：\n\n```caddy\nexample.com {\n\troot /var/www/html/public\n\tphp_fastcgi 127.0.0.1:9000\n\tfile_server\n}\n```\n\n<span id=\"docker\"/>当使用 Caddy 提供多个 PHP 应用程序时，每个应用程序的 Webroot 必须不同，以便 Caddy 可以单独读取和提供静态文件并检测 PHP 文件是否存在。\n\n如果您使用 Docker，通常您的 PHP-FPM 容器会将文件安装在同一根目录下。在这种情况下，解决方案是将文件挂载到 Caddy 容器的不同目录中，然后使用[`root` subdirective](#root)设置每个容器的根目录：\n\n```caddy\napp1.example.com {\n\troot /srv/app1/public\n\tphp_fastcgi app1:9000 {\n\t\troot /var/www/html/public\n\t}\n\tfile_server\n}\n\napp2.example.com {\n\troot /srv/app2/public\n\tphp_fastcgi app2:9000 {\n\t\troot /var/www/html/public\n\t}\n\tfile_server\n}\n```\n\n对于不使用`index.php`作为入口点的 PHP 站点，您可以回退到发出`404`错误。可以使用[`handle_errors` 指令](handle_errors)捕获并处理该错误：\n\n```caddy\nexample.com {\n\tphp_fastcgi localhost:9000 {\n\t\ttry_files {path} {path}/index.php =404\n\t}\n\n\thandle_errors {\n\t\trespond \"{err.status_code} {err.status_text}\"\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/push.md",
    "content": "---\ntitle: push (Caddyfile指令)\n---\n\n# push\n\n配置服务器使用HTTP/2服务器预先向客户端推送资源。\n\n通过指定响应的Link头，可以为服务器推送资源提供链接。这条指令将自动推送由上游Link头描述的资源，格式如下。\n\n- `<resource>; as=script`\n- `<resource>; as=script,<resource>; as=style`\n- `<resource>; nopush`\n- `<resource>;<resource2>;...`\n\n其中`<resource>`以正斜线`/`开头（也就是说，是一个具有相同主机的URI路径）。只有同主机的资源菜可以被推送。如果有属于外部资源的路径，或者标识了`nopush`的属性，它将不会被推送。\n\n默认情况下，推送请求将包括一些被认为可以从原始请求中安全复制的头信息。\n\n- Accept-Encoding\n- Accept-Language\n- Accept\n- Cache-Control\n- User-Agent\n\n如果没有这些头信息，许多请求都会失败；这些header头是不需要手动配置的。\n\n推送请求在内部被虚拟化，所以它们是非常轻量的。\n\n\n## 语法\n\n```caddy-d\npush [<matcher>] [<resource>] {\n\t[GET|HEAD] <resource>\n\theaders {\n\t\t[+]<field> [<value|regexp> [<replacement>]]\n\t\t-<field>\n\t}\n}\n```\n\n- **&lt;resource&gt;** 是要推送的目标URI路径。如果在区块内使用，可以选择在前面加上方法（GET或POST；默认是GET）。\n- **&lt;headers&gt;** 使用与[`header`指令](/docs/caddyfile/directives/header)相同的语法来操作推送请求的头信息。有些头信息是默认带入的，不需要明确配置（见上文）。\n\n\n## 示例\n\n在响应中推送任何由`Link`头信息描述的资源。\n\n```caddy-d\npush\n```\n\n和上面一样，但也向所有请求推送`/resources/style.css`。\n\n```caddy-d\npush * /resources/style.css\n```\n\n只有在客户端请求`/foo.html`时才推送`/foo.jpg`。\n\n```caddy-d\npush /foo.html /foo.jpg\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/redir.md",
    "content": "---\ntitle: redir (Caddyfile指令)\n---\n\n# redir\n\n向客户发出一个HTTP重定向。\n\n这条指令意味着一个匹配的请求将被拒绝。它在处理程序链中被安排在很早的位置（在[`rewrite`](/docs/caddyfile/directives/rewrite)之前）。\n\n## 语法\n\n```caddy-d\nredir [<matcher>] <to> [<code>]\n```\n\n- **&lt;to&gt;** 是目标位置。成为响应的Location的header头。\n- **&lt;code&gt;** 是重定向要使用的HTTP状态码。可以是。\n\t- 3xx范围内的一个正整数，或401\n\t- `temporary` 用于临时重定向（默认为302）\n\t- `permanent` 用于永久重定向（301）\n\t- `html` 使用一个HTML文档来执行重定向（对重定向浏览器很有用，但对重定向API客户端没有用）\n\t- 一个带有状态代码值的占位符\n\n\n\n## 示例\n\n将所有请求重定向到`https://example.com`。\n\n```caddy-d\nredir https://example.com\n```\n\n和上面一样，但保留URI不变：\n\n```caddy-d\nredir https://example.com{uri}\n```\n\n相同，但是是永久重定向：\n\n```caddy-d\nredir https://example.com{uri} permanent\n```\n\n将旧的`/about-us`页面重定向到新的`/about`页面。\n\n```caddy-d\nredir /about-us /about\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/request_body.md",
    "content": "---\ntitle: request_body (Caddyfile指令)\n---\n\n# request_body\n\n操作或设置对传入请求正文的限制。\n\n## 语法\n\n```caddy-d\nrequest_body [<matcher>] {\n\tmax_size <value>\n\tset <body_content>\n}\n```\n\n- **max_size** 是允许的请求体最大字节数。它接受[go-humanize](https://pkg.go.dev/github.com/dustin/go-humanize#pkg-constants)支持的所有大小值。读取超过限定的字节数将返回HTTP状态`413`的错误。\n\n⚠️ <i>实验性</i> <span style='white-space: pre;'> | </span> <span>v2.10.0+</span>\n- **set** 允许将请求体设置为特定内容。内容可以包含占位符，以便动态插入数据。\n\n## 示例\n\n将请求体大小限制为10兆字节：\n\n```caddy\nexample.com {\n\trequest_body {\n\t\tmax_size 10MB\n\t}\n\treverse_proxy localhost:8080\n}\n```\n\n使用包含SQL查询的JSON结构设置请求体：\n\n```caddy\nexample.com {\n\thandle /jazz {\n\t\trequest_body {\n\t\t\tset `\\{\"statementText\":\"SELECT name, genre, debut_year FROM artists WHERE genre = 'Jazz'\"}`\n\t\t}\n\n\t\treverse_proxy localhost:8080 {\n\t\t\theader_up Content-Type application/json\n\t\t\tmethod POST\n\t\t\trewrite /execute-sql\n\t\t}\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/request_header.md",
    "content": "---\ntitle: request_header (Caddyfile指令)\n---\n\n# request_header\n\n处理请求中的HTTP标头字段。它可以设置、添加和删除标头值，或使用正则表达式执行替换。\n\n## Syntax\n\n```caddy-d\nrequest_header [<matcher>] [[+|-]<field> [<value>|<find>] [<replace>]]\n```\n\n- **&lt;field&gt;** 是标头字段的名称。默认情况下，将覆盖任何现有的同名字段。\n  - 带前缀`+`的字段将会被添加而不是被替换；\n  - 带前缀`-`的字段则会被删除。\n- **&lt;value&gt;** 是标头字段值，被添加或设置。\n- **&lt;find&gt;** 是要搜索的子字符串或正则表达式。\n- **&lt;replace&gt;** 是替换值；如果执行搜索和替换，则需要。\n\n\n## 示例\n\n从请求中删除Referer标头：\n\n```caddy-d\nrequest_header -Referer\n```\n\n删除请求中所有包含下划线的header头：\n\n```caddy-d\nrequest_header -*_*\n```"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/respond.md",
    "content": "---\ntitle: respond (Caddyfile指令)\n---\n\n# respond\n\n写一个硬编码/静态响应给客户端。\n\n如果响应体非空，此指令会在尚未设置`Content-Type`头时设置它。默认值是`text/plain; utf-8`；如果响应体是有效的JSON对象或数组，则设置为`application/json`。对于所有其他内容类型，请使用[`header`指令](/docs/caddyfile/directives/header)显式设置合适的Content-Type。\n\n\n## 语法\n\n```caddy-d\nrespond [<matcher>] <status>|<body> [<status>] {\n\tbody <text>\n\tclose\n}\n```\n\n- **&lt;status&gt;** 是要写入的HTTP状态码。\n\n  如果是`103`（Early Hints），响应会在没有响应体的情况下写出，并且处理器链会继续。（HTTP `1xx`响应是信息性响应，不是最终响应。）\n\n  默认：`200`\n\n- **&lt;body&gt;** 是要写入的响应体。\n\n- **body** 是提供响应体的另一种方式；如果是多行内容会很方便。\n\n- **close** 会在写完响应后关闭客户端与服务器的连接。\n\n澄清一下，第一个非匹配器参数可以是3位状态码，也可以是响应体字符串。如果它是响应体，下一个参数可以是状态码。\n\n<aside class=\"tip\">\n\n用错误状态码响应不同于在处理器链中返回错误，后者会在内部调用错误处理程序。\n\n</aside>\n\n\n## 示例\n\n对所有健康检查写入空响应体的200状态，并对所有其他请求写入简单响应体：\n\n```caddy\nexample.com {\n\trespond /health-check 200\n\trespond \"Hello, world!\"\n}\n```\n\n写入错误响应并关闭连接：\n\n<aside class=\"tip\">\n\n你可能更想使用[`error`指令](error)，它会触发一个可由[`handle_errors`指令](handle_errors)处理的错误。\n\n</aside>\n\n```caddy\nexample.com {\n\trespond /secret/* \"Access denied\" 403 {\n\t\tclose\n\t}\n}\n```\n\n使用[heredoc语法](/docs/caddyfile/concepts#heredocs)控制空白并写入HTML响应，同时设置`Content-Type`头以匹配响应体：\n\n```caddy\nexample.com {\n\theader Content-Type text/html\n\trespond <<HTML\n\t\t<html>\n\t\t\t<head><title>Foo</title></head>\n\t\t\t<body>Foo</body>\n\t\t</html>\n\t\tHTML 200\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/reverse_proxy.md",
    "content": "---\ntitle: reverse_proxy (Caddyfile directive)\n---\n\n<script>\nready(function() {\n\t// Fix response matchers to render with the right color,\n\t// and link to response matchers section\n\t$$_('pre.chroma .k').forEach(item => {\n\t\tif (item.innerText.includes('@')) {\n\t\t\tlet text = item.innerText.replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\t\t\tlet url = '#' + item.innerText.replace(/_/g, \"-\");\n\t\t\titem.classList.add('nd');\n\t\t\titem.classList.remove('k');\n\t\t\titem.innerHTML = `<a href=\"/docs/caddyfile/response-matchers\" style=\"color: inherit;\" title=\"Response matcher\">${text}</a>`;\n\t\t}\n\t});\n\n\t// Fix matcher placeholder\n\tconst nameMatchers = $$_('pre.chroma .nd');\n\tfor (let item of nameMatchers) {\n\t\tif (item.innerText.includes('@name')) {\n\t\t\titem.innerHTML = '<a href=\"/docs/caddyfile/response-matchers\" style=\"color: inherit;\" title=\"Response matcher\">@name</a>';\n\t\t\tbreak;\n\t\t}\n\t}\n\t\n\tconst replaceStatusElements = $$_('pre.chroma .k');\n\tfor (let item of replaceStatusElements) {\n\t\tif (item.innerText.includes('replace_status') && item.nextElementSibling) {\n\t\t\tconst next = item.nextElementSibling;\n\t\t\tconst span = document.createElement('span');\n\t\t\tspan.className = 'nd';\n\t\t\tnext.parentNode.insertBefore(span, next);\n\t\t\tspan.appendChild(next);\n\t\t\tspan.innerHTML = '<a href=\"/docs/caddyfile/response-matchers\" style=\"color: inherit;\" title=\"Response matcher\">[&lt;matcher&gt;]</a>';\n\t\t\tbreak;\n\t\t}\n\t}\n\t\n\tconst handleResponseElements = $$_('pre.chroma .k');\n\tfor (let item of handleResponseElements) {\n\t\tif (item.innerText.includes('handle_response') && item.nextElementSibling) {\n\t\t\tconst next = item.nextElementSibling;\n\t\t\tconst span = document.createElement('span');\n\t\t\tspan.className = 'nd';\n\t\t\tnext.parentNode.insertBefore(span, next);\n\t\t\tspan.appendChild(next);\n\t\t\tspan.innerHTML = '<a href=\"/docs/caddyfile/response-matchers\" style=\"color: inherit;\" title=\"Response matcher\">[&lt;matcher&gt;]</a>';\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// We'll add links to all the subdirectives if a matching anchor tag is found on the page.\n\taddLinksToSubdirectives();\n});\n</script>\n\n# reverse_proxy\n\n通过可配置的传输、负载均衡、运行状况检查、请求操作和缓冲选项将请求代理到一个或多个后端。\n\n- [语法](#syntax)\n- [上游](#upstreams)\n  - [上游地址](#upstream-addresses)\n  - [动态上游](#dynamic-upstreams)\n    - [SRV](#srv)\n    - [A/AAAA](#aaaaa)\n\t- [Multi](#multi)\n- [负载均衡](#load-balancing)\n  - [主动健康检查](#active-health-checks)\n  - [被动健康检查](#passive-health-checks)\n  - [事件](#events)\n- [流式传输](#streaming)\n- [标头](#headers)\n- [重写](#rewrites)\n- [传输](#transports)\n  - [`http` 传输](#the-http-transport)\n  - [`fastcgi` 传输](#the-fastcgi-transport)\n- [拦截响应](#intercepting-responses)\n- [示例](#examples)\n\n\n\n## 语法\n\n```caddy-d\nreverse_proxy [<matcher>] [<upstreams...>] {\n\t# backends\n\tto      <upstreams...>\n\tdynamic <module> ...\n\n\t# load balancing\n\tlb_policy       <name> [<options...>]\n\tlb_retries      <retries>\n\tlb_try_duration <duration>\n\tlb_try_interval <interval>\n\tlb_retry_match  <request-matcher>\n\n\t# active health checking\n\thealth_uri          <uri>\n\thealth_upstream     <ip:port>\n\thealth_port         <port>\n\thealth_interval     <interval>\n\thealth_passes       <num>\n\thealth_fails\t    <num>\n\thealth_timeout      <duration>\n\thealth_method       <method>\n\thealth_status       <status>\n\thealth_request_body <body>\n\thealth_body         <regexp>\n\thealth_follow_redirects\n\thealth_headers {\n\t\t<field> [<values...>]\n\t}\n\n\t# passive health checking\n\tfail_duration     <duration>\n\tmax_fails         <num>\n\tunhealthy_status  <status>\n\tunhealthy_latency <duration>\n\tunhealthy_request_count <num>\n\n\t# streaming\n\tflush_interval     <duration>\n\trequest_buffers    <size>\n\tresponse_buffers   <size>\n\tstream_timeout     <duration>\n\tstream_close_delay <duration>\n\n\t# request/header manipulation\n\ttrusted_proxies [private_ranges] <ranges...>\n\theader_up   [+|-]<field> [<value|regexp> [<replacement>]]\n\theader_down [+|-]<field> [<value|regexp> [<replacement>]]\n\tmethod <method>\n\trewrite <to>\n\n\t# round trip\n\ttransport <name> {\n\t\t...\n\t}\n\n\t# optionally intercept responses from upstream\n\t@name {\n\t\tstatus <code...>\n\t\theader <field> [<value>]\n\t}\n\treplace_status [<matcher>] <status_code>\n\thandle_response [<matcher>] {\n\t\t<directives...>\n\n\t\t# special directives only available in handle_response\n\t\tcopy_response [<matcher>] [<status>] {\n\t\t\tstatus <status>\n\t\t}\n\t\tcopy_response_headers [<matcher>] {\n\t\t\tinclude <fields...>\n\t\t\texclude <fields...>\n\t\t}\n\t}\n}\n```\n\n\n\n## 上游\n\n- **&lt;upstreams...&gt;** 是要代理的上游（后端）列表。\n- **to**<span id=\"to\"/>是指定上游列表的另一种方法，每行一个（或多个）。\n- **dynamic**<span id=\"dynamic\"/>配置_动态上游_ 模块。这允许为每个请求动态获取上游列表。有关标准动态上游模块的说明，请参阅下面的[动态上游](#dynamic-upstreams)。动态上游在每次代理循环迭代时都会被检索（即，如果启用了负载均衡重试，每个请求可能会多次检索），并且会优先于静态上游。如果发生错误，代理将回退到使用任何静态配置的上游。\n\n\n### 上游地址\n\n静态上游地址可以采用仅包含方案和主机/端口的 URL 形式，或传统的[Caddy network address](/docs/conventions#network-addresses)。有效示例：\n\n- `localhost:4000`\n- `127.0.0.1:4000`\n- `[::1]:4000`\n- `http://localhost:4000`\n- `https://example.com`\n- `h2c://127.0.0.1`\n- `example.com`\n- `unix//var/php.sock`\n- `unix+h2c//var/grpc.sock`\n- `localhost:8001-8006`\n- `[fe80::ea9f:80ff:fe46:cbfd%eth0]:443`\n\n默认情况下，通过纯文本 HTTP 与上游建立连接。使用 URL 形式时，可以使用方案来设置一些[`transport`](#transports)默认值作为简写。\n- 使用`https://`作为方案将使用[`http` 传输](#the-http-transport)并启用[`tls`](#tls)。\n\n  此外，您可能需要覆盖`Host`标头，使其与 TLS SNI 值匹配，服务器使用该值进行路由和证书选择。请参阅下面的[HTTPS](#https)部分了解更多详细信息。\n\n- 使用`h2c://`作为方案将使用[`http` 传输](#the-http-transport)，并将[HTTP 版本](#versions)设置为允许明文 HTTP/2 连接。\n\n- 使用`http://`作为方案与省略该方案相同，因为 HTTP 已经是默认值。包含此语法是为了与其他方案快捷方式对称。\n\n方案不能混合，因为它们修改公共传输​​配置（启用 TLS 的传输不能同时承载 HTTPS 和纯文本 HTTP）。任何显式传输配置都不会被覆盖，并且省略方案或使用其他端口将不会假定特定的传输。\n\n当将 IPv6 与区域（例如，具有特定网络接口的链接本地地址）一起使用时，方案 **cannot** 用作快捷方式，因为`%`将导致 URL 解析错误；相反，显式配置传输。\n\n当使用[network address](/docs/conventions#network-addresses)形式时，网络类型被指定为上游地址的前缀。这不能与 URL 方案结合使用。作为特殊情况，支持`unix+h2c/`作为`unix/`网络的快捷方式，并具有与`h2c://`方案相同的效果。支持端口范围作为快捷方式，可扩展到具有同一主机的多个上游。\n\n上游地址**cannot**包含路径或查询字符串，因为这意味着在代理时同时重写请求，而这种行为未定义或支持。如果需要，您可以使用[`rewrite`](/docs/caddyfile/directives/rewrite)指令。\n\n如果地址不是 URL（即没有方案），则可以使用[placeholders](/docs/caddyfile/concepts#placeholders)，但这会使上游“动态静态”，这意味着在健康检查和负载均衡方面，可能有许多不同的后端充当单个静态上游。如果可能的话，我们建议使用[动态上游](#dynamic-upstreams)模块。使用占位符时，必须包含端口（通过占位符替换或作为地址的静态后缀）。\n\n\n### 动态上游\n\nCaddy 的反向代理标配了一些动态上游模块。请注意，使用动态上游会对负载均衡和运行状况检查产生影响，具体取决于特定的策略配置：动态上游不会运行主动运行状况检查；如果上游列表相对稳定且一致（尤其是循环），则最好提供负载均衡和被动健康检查。理想情况下，动态上游模块仅返回健康、可用的后端。\n\n\n#### SRV\n\n从 SRV DNS 记录检索上游。\n\n```caddy-d\n\tdynamic srv [<full_name>] {\n\t\tservice   <service>\n\t\tproto     <proto>\n\t\tname      <name>\n\t\trefresh   <interval>\n\t\tresolvers <ip...>\n\t\tdial_timeout        <duration>\n\t\tdial_fallback_delay <duration>\n\t}\n```\n\n- **&lt;full_name&gt;** 是要查找的记录的完整域名（即`_service._proto.name`）。\n- **service**是全称的服务组件。\n- **proto** 是全名的协议组件。`tcp`或`udp`。\n- **name** 是名称组成部分。或者，如果`service`和`proto`为空，则查询完整域名。\n- **refresh** 是刷新缓存结果的频率。默认：`1m`\n- **resolvers** 是覆盖系统解析器的 DNS 解析器列表。\n- **dial_timeout** 是拨打查询的超时时间。\n- **dial_fallback_delay** 是生成 RFC 6555 快速回退连接之前等待的时间。默认：`300ms`\n\n\n\n#### A/AAAA\n\n从 A/AAAA DNS 记录检索上游。\n\n```caddy-d\n\tdynamic a [<name> <port>] {\n\t\tname      <name>\n\t\tport      <port>\n\t\trefresh   <interval>\n\t\tresolvers <ip...>\n\t\tdial_timeout        <duration>\n\t\tdial_fallback_delay <duration>\n\t\tversions ipv4|ipv6\n\t}\n```\n\n- **name** 是要查询的域名。\n- **port** 是用于后端的端口。\n- **refresh** 是刷新缓存结果的频率。默认：`1m`\n- **resolvers** 是覆盖系统解析器的 DNS 解析器列表。\n- **dial_timeout** 是拨打查询的超时时间。\n- **dial_fallback_delay** 是生成 RFC 6555 快速回退连接之前等待的时间。默认：`300ms`\n- **versions** 是要解析的 IP 版本列表。默认：`ipv4 ipv6`，分别对应A记录和AAAA记录。\n\n\n#### 多\n\n附加多个动态上游模块的结果。如果您需要上游的冗余源，例如：由辅助 SRV 集群备份的主 SRV 集群，则非常有用。\n\n```caddy-d\n\tdynamic multi {\n\t\t<source> [...]\n\t}\n```\n\n- **&lt;source&gt;** 是动态上游模块的名称，后面是其配置。可以指定多个。\n\n\n\n\n## 负载均衡\n\n负载均衡通常用于在多个上游之间分配流量。通过启用重试，它还可以与一个或多个上游一起使用，以保留请求，直到可以选择健康的上游（例如，在重新启动或重新部署上游时等待并减少错误）。\n\n默认情况下，使用`random`策略启用此功能。默认情况下禁用重试。\n\n- **lb_policy**<span id=\"lb_policy\"/>是负载均衡策略的名称以及任何选项。默认：`random`。\n\n  对于涉及哈希的策略，[highest-random-weight (HRW)](https://en.wikipedia.org/wiki/Rendezvous_hashing)算法用于确保具有相同哈希键的客户端或请求映射到相同的上游，即使上游列表发生变化。\n\n  某些策略支持回退作为选项（如果注明），在这种情况下，它们采用[block](/docs/caddyfile/concepts#blocks)和`fallback <policy>`，后者采用另一个负载均衡策略。对于这些策略，默认后备是`random`。配置回退允许在主策略未选择策略时使用辅助策略，从而实现强大的组合。如果需要，后备可以嵌套多次。\n  \n  例如，`header`可以用作主连接，以允许开发人员选择特定的上游，并为所有其他连接提供`first`的回退，以实现主/辅助故障转移。\n  ```caddy-d\n  lb_policy header X-Upstream {\n  \tfallback first\n  }\n  ```\n\n\t-`random`随机选择上游\n\n\t-`random_choose <n>`随机选择两个或多个上游，然后选择负载最小的一个（`n`通常为 2）\n\n\t-`first`按照配置中定义的顺序选择第一个可用的上游，允许主/辅助故障转移；请记住同时启用健康检查，否则不会发生故障转移\n\n\t-`round_robin`依次迭代每个上游\n\n\t-`weighted_round_robin <weights...>`依次迭代每个上游，尊重提供的权重。权重参数的数量应与配置的上游数量相匹配。权重应该是非负整数。例如，有两个上游且权重为`5 1`，第一个上游将连续被选择 5 次，然后第二个上游被选择一次，然后重复循环。如果使用零作为权重，这将禁止为新请求选择上游。\n\n\t-`least_conn`选择当前请求数最少的上游；如果超过一台主机的请求数最少，则随机选择其中一台主机\n\n\t-`ip_hash`将远程 IP（直接对等点）映射到粘性上游\n\n\t-`client_ip_hash`将客户端 IP 映射到粘性上游；最好与[`servers > trusted_proxies` 全局选项](/docs/caddyfile/options#trusted-proxies)搭配使用，[`servers > trusted_proxies` 全局选项](/docs/caddyfile/options#trusted-proxies)可以实现真实客户端 IP 解析，否则其行为与`ip_hash`相同\n\n\t-`uri_hash`将请求 URI（路径和查询）映射到粘性上游\n\n\t-`query [key]`通过对查询值进行哈希处理，将请求查询映射到粘性上游；如果指定的键不存在，将使用回退策略选择上游（默认为`random`）\n\n\t-`header [field]`通过哈希标头值将请求标头映射到粘性上游；如果指定的头字段不存在，则将使用回退策略选择上游（默认为`random`）\n\n\t- 来自客户端的第一个请求的`cookie [<name> [<secret>]]`（当没有 cookie 时），将使用后备策略选择上游（默认为`random`），并将`Set-Cookie`标头添加到响应中（如果未指定，默认 cookie 名称为`lb`）。 cookie 值是所选上游的上游拨号地址，使用 HMAC-SHA256 进行哈希处理（使用`<secret>`作为共享密钥，如果未指定则为空字符串）。\n\t\n\t  在存在 cookie 的后续请求中，cookie 值将映射到相同的上游（如果可用）；如果不可用或未找到，则使用回退策略选择新的上游，并将 cookie 添加到响应中。\n\n\t  如果您希望使用特定上游进行调试，您可以使用密钥对上游地址进行散列，并在 HTTP 客户端（浏览器或其他）中设置 cookie。例如，使用 PHP，您可以运行以下命令来计算 cookie 值，其中`10.1.0.10:8080`是您的上游之一的地址，`secret`是您配置的密钥。\n\t  ```php\n\t  echo hash_hmac('sha256', '10.1.0.10:8080', 'secret');\n\t  // cdd96966817dd14a99f47ee17451464f29998da170814a16b483e4c1ff4c48cf\n\t  ```\n\t\n\t  您可以通过 Javascript 控制台在浏览器中设置 cookie，例如设置名为`lb`的 cookie：\n\t  ```js\n\t  document.cookie = \"lb=cdd96966817dd14a99f47ee17451464f29998da170814a16b483e4c1ff4c48cf\";\n\t  ```\n\n- **lb_retries**<span id=\"lb_retries\"/>是在下一个可用主机关闭时为每个请求重试选择可用后端的次数。默认情况下，重试被禁用（零）。\n\n  如果还配置了[`lb_try_duration`](#lb_try_duration)，则在达到持续时间后重试可能会提前停止。换句话说，重试持续时间优先于重试计数。\n\n- **lb_try_duration**<span id=\"lb_try_duration\"/>是一个[持续时间值](/docs/conventions#durations)，它定义了在下一个可用主机关闭时为每个请求尝试选择可用后端的时间。默认情况下，重试被禁用（零持续时间）。\n\n  当负载均衡器尝试查找可用的上游主机时，客户端将等待这么长时间。合理的起点可能是`5s`，因为 HTTP 传输的默认拨号超时是`3s`，因此如果无法到达第一个选定的上游，则应允许至少重试一次；但请随意尝试，为您的用例找到合适的平衡点。\n\n- **lb_try_interval**<span id=\"lb_try_interval\"/>是[持续时间值](/docs/conventions#durations)，定义从池中选择下一个主机之间等待的时间。默认是`250ms`。仅当对上游主机的请求失败时才相关。请注意，如果所有后端都关闭并且延迟非常低，则将其设置为`0`并使用非零`lb_try_duration`可能会导致 CPU 旋转。\n\n- **lb_retry_match**<span id=\"lb_retry_match\"/>限制允许重试的请求。请求必须符合此条件，以便在与上游的连接成功但后续往返失败时重试。如果与上游的连接失败，则始终允许重试。默认情况下，只重试`GET`请求。\n\n  此选项的语法与[命名请求匹配器](/docs/caddyfile/matchers#named-matchers)相同，但没有`@name`。如果您只需要一个匹配器，您可以将其配置在同一行上。对于多个匹配器，需要一个块。\n\n\n\n### 主动健康检查\n\n主动运行状况检查在后台通过计时器执行运行状况检查。要启用此功能，需要`health_uri`或`health_port`。\n\n- **health_uri**<span id=\"health_uri\"/>是主动健康检查的 URI 路径（和可选查询）。\n\n- **health_upstream**<span id=\"health_upstream\"/>是用于主动健康检查的 ip:port（如果与上游不同）。这应该与`health_header`和`{http.reverse_proxy.active.target_upstream}`一起使用。\n\n- **health_port**<span id=\"health_port\"/>是用于主动运行状况检查的端口（如果与上游端口不同）。如果使用`health_upstream`则忽略。\n\n- **health_interval**<span id=\"health_interval\"/>是[持续时间值](/docs/conventions#durations)，定义执行主动健康检查的频率。默认：`30s`。\n\n- **health_passes**<span id=\"health_passes\"/>是在将后端再次标记为健康之前所需的连续健康检查次数。默认：`1`。\n\n- **health_fails**<span id=\"health_fails\"/>是将后端标记为不健康之前所需的连续健康检查次数。默认：`1`。\n\n- **health_timeout**<span id=\"health_timeout\"/>是一个[持续时间值](/docs/conventions#durations)，定义在将后端标记为关闭之前等待回复的时间。默认：`5s`。\n\n- **health_method**<span id=\"health_method\"/>是用于主动健康检查的 HTTP 方法。默认：`GET`。\n\n- **health_status**<span id=\"health_status\"/>是健康后端期望的 HTTP 状态代码。可以是 3 位状态代码，或以`xx`结尾的状态代码类。例如：`200`（默认）或`2xx`。\n\n- **health_request_body**<span id=\"health_request_body\"/>是一个字符串，表示与主动健康检查一起发送的请求正文。\n\n- **health_body**<span id=\"health_body\"/>是匹配主动健康检查的响应正文的子字符串或正则表达式。如果后端没有返回匹配的body，则会被标记为down。\n\n- **health_follow_redirects**<span id=\"health_follow_redirects\"/>将导致健康检查遵循上游提供的重定向。默认情况下，重定向响应将导致运行状况检查失败。\n\n- **health_headers**<span id=\"health_headers\"/>允许指定要在活动健康检查请求上设置的标头。如果您需要更改`Host`标头，或者需要向后端提供一些身份验证作为健康检查的一部分，这非常有用。\n\n\n\n### 被动健康检查\n\n被动健康检查与实际代理请求一致发生。要启用此功能，需要`fail_duration`。\n\n- **fail_duration**<span id=\"fail_duration\"/>是一个[持续时间值](/docs/conventions#durations)，定义了记住失败请求的时间。持续时间 >`0`启用被动健康检查；默认为`0`（关闭）。一个合理的起点可能是`30s`，以便在将不健康的上游恢复在线时平衡错误率和响应能力；但请随意尝试，为您的用例找到合适的平衡点。\n\n- **max_fails**<span id=\"max_fails\"/>是在考虑后端关闭之前`fail_duration`内所需的最大失败请求数；必须 >=`1`；默认是`1`。\n\n- **unhealthy_status** 如果响应返回时带有这些状态代码之一，则<span id=\"unhealthy_status\"/>将请求视为失败。可以是 3 位状态代码或以`xx`结尾的状态代码类，例如：`404`或`5xx`。\n\n- **unhealthy_latency**<span id=\"unhealthy_latency\"/>是一个[持续时间值](/docs/conventions#durations)，如果需要这么长时间才能获得响应，则将请求视为失败。\n\n- **unhealthy_request_count**<span id=\"unhealthy_request_count\"/>是在将后端标记为关闭之前允许的并发请求数。换句话说，如果某个特定后端当前正在处理这么多请求，则它被视为“过载”，并且将优先选择其他后端。\n\n  这应该是一个相当大的数字；配置此意味着代理将有`unhealthy_request_count × upstreams_count`并发请求总数的限制，并且该点之后的任何请求都将由于没有可用的上游而导致错误。\n\n\n## 活动\n\n当上游从健康状态转变为不健康状态（反之亦然）时，会发出[an event](/docs/caddyfile/options#event-options)。这些事件可用于触发其他操作，例如发送通知或记录消息。活动详情如下：\n\n- 当上游之前不健康但被标记为健康时，会发出`healthy`\n- 当上游之前健康但被标记为不健康时，会发出`unhealthy`\n\n在这两种情况下，`host`作为元数据包含在事件中，以标识更改状态的上游。例如，它可以用作`{event.data.host}`和`exec`事件处理程序的占位符。\n\n\n\n## 流媒体\n\n默认情况下，代理会部分缓冲响应以提高线路效率。\n\n该代理还支持 WebSocket 连接，执行 HTTP 升级请求，然后将连接转换为双向隧道。\n\n<aside class=\"tip\">\n\n默认情况下，重新加载配置时，WebSocket 连接将被强制关闭（并向客户端和上游发送关闭控制消息）。每个请求都保存对配置的引用，因此必须关闭旧连接以控制内存使用情况。可以使用[`stream_timeout`](#stream_timeout)和[`stream_close_delay`](#stream_close_delay)选项自定义此关闭行为。\n\n</aside>\n\n- **flush_interval**<span id=\"flush_interval\"/>是一个[持续时间值](/docs/conventions#durations)，用于调整 Caddy 刷新客户端响应缓冲区的频率。默认情况下，不进行定期刷新。负值（通常为 -1）表示“低延迟模式”，该模式完全禁用响应缓冲并在每次写入客户端后立即刷新，并且即使客户端提前断开连接也不会取消对后端的请求。如果响应满足以下条件之一，则忽略此选项，并且响应会立即刷新到客户端：\n\t- `Content-Type: text/event-stream`\n\t-`Content-Length`未知\n\t- 代理双方HTTP/2，`Content-Length`未知，`Accept-Encoding`要么未设置，要么是“身份”\n\n- **request_buffers**<span id=\"request_buffers\"/>将导致代理在向上游发送请求正文之前将最多`<size>`数量的字节读取到缓冲区中。这是非常低效的，只有在上游需要立即读取请求主体时才应该这样做（这是上游应用程序应该修复的问题）。这接受[go-humanize](https://github.com/dustin/go-humanize/blob/master/bytes.go)支持的所有大小格式。\n\n- **response_buffers**<span id=\"response_buffers\"/>将导致代理从响应正文中读取最多`<size>`数量的字节，然后将其读入缓冲区，然后再返回给客户端。出于性能原因，应尽可能避免这种情况，但如果后端具有更严格的内存限制，则这可能很有用。这接受[go-humanize](https://github.com/dustin/go-humanize/blob/master/bytes.go)支持的所有大小格式。\n\n- **stream_timeout**<span id=\"stream_timeout\"/>是[持续时间值](/docs/conventions#durations)，之后 WebSocket 等流式请求将在超时结束时被强制关闭。如果连接保持打开时间过长，这实际上会取消连接。一个合理的起点可能是`24h`来剔除超过一天的连接。默认：无超时。\n\n- **stream_close_delay**<span id=\"stream_close_delay\"/>是一个[持续时间值](/docs/conventions#durations)，它会延迟配置卸载时强制关闭 WebSocket 等流式请求；相反，流将保持打开状态，直到延迟完成。换句话说，启用此功能可以防止流在重新加载 Caddy 的配置时立即关闭。启用此功能可能是一个好主意，可以避免大量重新连接因之前的配置关闭而关闭连接的客户端。一个合理的起点可能是像`5m`这样的东西，允许用户在配置重新加载后有 5 分钟的时间自然离开页面。默认：无延迟。\n\n\n\n## 标头\n\n代理可以**manipulate headers**：\n\n- **header_up**<span id=\"header_up\"/>在上游到后端的请求标头中设置、添加（使用`+`前缀）、删除（使用`-`前缀）或执行替换（通过使用两个参数，搜索和替换）。\n\n- **header_down**<span id=\"header_down\"/>在来自后端下游的响应标头中设置、添加（使用`+`前缀）、删除（使用`-`前缀）或执行替换（通过使用两个参数，搜索和替换）。\n\n例如，要设置请求标头，覆盖任何现有值：\n\n```caddy-d\nheader_up Some-Header \"the value\"\n```\n\n添加响应头；请注意，标头字段可以有多个值：\n\n```caddy-d\nheader_down +Some-Header \"first value\"\nheader_down +Some-Header \"second value\"\n```\n\n要删除请求标头，防止其到达后端：\n\n```caddy-d\nheader_up -Some-Header\n```\n\n要删除所有匹配的请求标头，请使用后缀匹配：\n\n```caddy-d\nheader_up -Some-*\n```\n\n要删除_all_请求标头，以便能够单独添加您想要的标头（不推荐）：\n\n```caddy-d\nheader_up -*\n```\n\n要对请求标头执行正则表达式替换：\n\n```caddy-d\nheader_up Some-Header \"^prefix-([A-Za-z0-9]*)$\" \"replaced-$1-suffix\"\n```\n\n使用的正则表达式语言是 RE2，包含在 Go 中。请参阅[RE2 语法参考](https://github.com/google/re2/wiki/Syntax)和[Go regexp 语法概述](https://pkg.go.dev/regexp/syntax)。替换字符串是[展开](https://pkg.go.dev/regexp#Regexp.Expand)，允许使用捕获的值，例如`$1`是第一个捕获组。\n\n\n### 默认值\n\n默认情况下，Caddy 不加修改地将传入标头（包括`Host`）传递到后端，但有以下三个例外：\n\n- 它设置或扩充[`X-Forwarded-For`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)标头字段。\n- 它设置[`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto)标头字段。\n- 它设置[`X-Forwarded-Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host)标头字段。\n\n<span id=\"trusted_proxies\"/>对于这些`X-Forwarded-*`标头，默认情况下，代理将忽略传入请求中的值，以防止欺骗。\n\n如果 Caddy 不是您的客户端连接到的第一个服务器（例如，当 CDN 位于 Caddy 前面时），您可以使用 IP 范围 (CIDR) 列表配置`trusted_proxies`，相信传入请求已为这些标头发送了正确的值。\n\n强烈建议您通过[`servers > trusted_proxies` 全局选项](/docs/caddyfile/options#trusted-proxies)而不是在代理中进行配置，以便这适用于服务器中的所有代理处理程序，并且这样做的好处是启用客户端 IP 解析。\n\n<aside class=\"tip\">\n\n如果您在 Caddy 之前使用 Cloudflare，请注意您可能容易受到`X-Forwarded-For`标头的欺骗。我们[Authelia](https://www.authelia.com)的朋友已经记录了[解决方法](https://www.authelia.com/integration/proxies/forwarded-headers/)来配置 Cloudflare 以忽略此标头的传入值。\n\n</aside>\n\n此外，当使用[`http` 传输](#the-http-transport)时，如果客户端请求中缺少`Accept-Encoding: gzip`标头，则会设置该标头。这允许上游在可能的情况下提供压缩内容。可以使用传输上的[`compression off`](#compression)禁用此行为。\n\n\n### HTTPS\n\n由于（大多数）标头在被代理时保留其原始值，因此在代理到 HTTPS 时，通常需要使用配置的上游地址覆盖`Host`标头，以便`Host`标头与 TLS ServerName 值匹配：\n\n```caddy-d\nreverse_proxy https://example.com {\n\theader_up Host {upstream_hostport}\n}\n```\n\n从 Caddy v2.11.0 开始，这是自动完成的，因此在代理到 HTTPS 时不再需要显式覆盖`Host`标头。如果您希望退出此行为，您可以将`Host`标头设置为其原始值（但这很少有意义）：\n\n```caddy-d\nreverse_proxy https://example.com {\n\theader_up Host {hostport}\n}\n```\n\n`X-Forwarded-Host`标头仍然传递[默认情况下](#defaults)，因此如果上游需要知道原始`Host`标头值，则仍可以使用该标头。\n\n这同样适用于在 caddy 中终止 TLS 并通过 HTTP 进行代理（无论是端口还是 unix 套接字）。事实上，当 caddy 是`reverse_proxy`的目标时，它本身必须接收正确的 Host。在 unix 套接字情况下，`upstream_hostport`将是套接字路径，并且必须显式设置主机。\n\n\n\n## 重写\n\n默认情况下，Caddy 使用与传入请求相同的 HTTP 方法和 URI 执行上游请求，除非在到达`reverse_proxy`之前在中间件链中执行了重写。\n\n在代理之前，请求被克隆；这确保了处理程序期间对请求所做的任何修改都不会泄漏到其他处理程序。这在需要在代理之后继续处理的情况下非常有用。\n\n除了[标头操作](#headers)之外，请求的方法和 URI 在发送到上游之前还可以更改：\n\n- **method**<span id=\"method\"/>更改克隆请求的 HTTP 方法。如果该方法更改为`GET`或`HEAD`，则传入请求的正文将不会由此处理程序发送到上游。如果您希望允许不同的处理程序使用请求正文，这非常有用。\n- **rewrite**<span id=\"rewrite\"/>更改克隆请求的 URI（路径和查询）。这与[`rewrite` 指令](/docs/caddyfile/directives/rewrite)类似，只是它不会将重写保留到此处理程序的范围之外。\n\n这些重写通常对于“预检查请求”之类的模式很有用，其中请求被发送到另一台服务器以帮助决定如何继续处理当前请求。\n\n例如，请求可以发送到身份验证网关，以确定该请求是否来自经过身份验证的用户（例如，该请求具有会话 cookie）并应该继续，或者应该重定向到登录页面。对于这种模式，Caddy 提供了一个快捷指令[`forward_auth`](/docs/caddyfile/directives/forward_auth)来跳过大部分配置样板。\n\n\n\n\n## 交通\n\nCaddy 的代理 **transport** 是可插拔的：\n\n- **transport**<span id=\"transport\"/>定义了如何与后端通信。默认为`http`。\n\n\n### `http`运输\n\n```caddy-d\ntransport http {\n\tread_buffer             <size>\n\twrite_buffer            <size>\n\tmax_response_header     <size>\n\tproxy_protocol          v1|v2\n\tdial_timeout            <duration>\n\tdial_fallback_delay     <duration>\n\tresponse_header_timeout <duration>\n\texpect_continue_timeout <duration>\n\tresolvers <ip...>\n\ttls\n\ttls_client_auth <automate_name> | <cert_file> <key_file>\n\ttls_insecure_skip_verify\n\ttls_curves <curves...>\n\ttls_timeout <duration>\n\ttls_trust_pool <module>\n\ttls_server_name <server_name>\n\ttls_renegotiation <level>\n\ttls_except_ports <ports...>\n\tkeepalive [off|<duration>]\n\tkeepalive_interval <interval>\n\tkeepalive_idle_conns <max_count>\n\tkeepalive_idle_conns_per_host <count>\n\tversions <versions...>\n\tcompression off\n\tmax_conns_per_host <count>\n\tnetwork_proxy <module>\n}\n```\n\n- **read_buffer**<span id=\"read_buffer\"/>是读取缓冲区的大小（以字节为单位）。它接受[go-humanize](https://github.com/dustin/go-humanize/blob/master/bytes.go)支持的所有格式。默认：`4KiB`。\n\n- **write_buffer**<span id=\"write_buffer\"/>是写入缓冲区的大小（以字节为单位）。它接受[go-humanize](https://github.com/dustin/go-humanize/blob/master/bytes.go)支持的所有格式。默认：`4KiB`。\n\n- **max_response_header**<span id=\"max_response_header\"/>是从响应标头读取的最大字节数。它接受[go-humanize](https://github.com/dustin/go-humanize/blob/master/bytes.go)支持的所有格式。默认：`10MiB`。\n\n- **proxy_protocol**<span id=\"proxy_protocol\"/>在与上游的连接上启用[PROXY 协议](https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt)（由 HAProxy 普及），并在前面添加真实的客户端 IP 数据。如果 Caddy 位于另一个代理后面，则最好与[`servers > trusted_proxies` 全局选项](/docs/caddyfile/options#trusted-proxies)配对。支持`v1`和`v2`版本。仅当您知道上游服务器能够解析 PROXY 协议时才应使用此选项。默认情况下，此功能处于禁用状态。\n\n- **dial_timeout**<span id=\"dial_timeout\"/>是连接到上游套接字时等待的最大[持续时间](/docs/conventions#durations)。默认：`3s`。\n\n- **dial_fallback_delay**<span id=\"dial_fallback_delay\"/>是在生成 RFC 6555 快速回退连接之前等待的最大[持续时间](/docs/conventions#durations)。负值会禁用此功能。默认：`300ms`。\n\n- **response_header_timeout**<span id=\"response_header_timeout\"/>是等待从上游读取响应标头的最大[持续时间](/docs/conventions#durations)。默认：无超时。\n\n- **expect_continue_timeout**<span id=\"expect_continue_timeout\"/>是如果请求具有`Expect: 100-continue`标头，则在完全写入请求标头后等待上游第一个响应标头的最大[持续时间](/docs/conventions#durations)。默认：无超时。\n\n- **read_timeout**<span id=\"read_timeout\"/>是等待后端下次读取的最大[持续时间](/docs/conventions#durations)。默认：无超时。\n\n- **write_timeout**<span id=\"write_timeout\"/>是等待下一次写入后端的最大[持续时间](/docs/conventions#durations)。默认：无超时。\n\n- **resolvers**<span id=\"resolvers\"/>是一个 DNS 解析器列表，用于覆盖系统解析器。\n\n- **tls**<span id=\"tls\"/>在后端使用 HTTPS。如果您使用`https://`方案指定后端，或者配置了以下任何`tls_*`选项，则会自动启用此功能。\n\n- **tls_client_auth**<span id=\"tls_client_auth\"/>通过以下两种方式之一启用 TLS 客户端身份验证：(1) 通过指定 Caddy 应获取证书并保持更新的域名，或 (2) 通过指定要向后端提供 TLS 客户端身份验证的证书和密钥文件。\n\n- **tls_insecure_skip_verify**<span id=\"tls_insecure_skip_verify\"/>关闭 TLS 握手验证，使连接不安全并且容易受到中间人攻击。 _请勿在生产中使用。_\n\n- **tls_curves**<span id=\"tls_curves\"/>是支持上游连接的椭圆曲线列表。 Caddy 的默认设置既现代又安全，因此您只需在有特定要求时才需要配置此设置。\n\n- **tls_timeout**<span id=\"tls_timeout\"/>是等待 TLS 握手完成的最大[持续时间](/docs/conventions#durations)。默认：无超时。\n\n- **tls_trust_pool**<span id=\"tls_trust_pool\"/>配置受信任的证书颁发机构的来源，类似于`tls`指令文档中描述的[`trust_pool` 子指令](/docs/caddyfile/directives/tls#trust_pool)。标准 Caddy 安装中可用的信任池源列表可在[此处](/docs/caddyfile/directives/tls#trust-pool-providers)中找到。\n\n- **tls_server_name**<span id=\"tls_server_name\"/>设置验证 TLS 握手中收到的证书时使用的服务器名称。默认情况下，这将使用上游地址的主机部分。\n\n  仅当您的上游地址与上游可能使用的证书不匹配时，您才需要覆盖此设置。例如，如果上游地址是 IP 地址，那么您需要将其配置为上游服务器提供服务的主机名。\n\n  可以使用请求占位符，在这种情况下，每个请求都会使用 HTTP 传输配置的克隆，这可能会导致性能损失。\n\n- **tls_renegotiation**<span id=\"tls_renegotiation\"/>设置 TLS 重新协商级别。 TLS 重新协商是在第一次握手之后执行后续握手的行为。该级别可能是以下之一：\n  -`never`（默认）禁用重新协商。\n  -`once`允许远程服务器每次连接请求重新协商一次。\n  -`freely`允许远程服务器重复请求重新协商。\n\n- **tls_except_ports**<span id=\"tls_except_ports\"/>当启用 TLS 时，如果上游目标使用给定端口之一，则将为这些连接禁用 TLS。这在配置动态上游时可能很有用，其中一些上游期望 HTTP，而另一些则期望 HTTPS 请求。\n\n- **keepalive**<span id=\"keepalive\"/>是`off`或[持续时间值](/docs/conventions#durations)，指定保持连接打开的时间（超时）。默认：`2m`。\n\n  ⚠️ 如果 keepalive 持续时间超过上游服务器的 keepalive 超时，则对 HTTP/1.1 上游的请求可能会因“对等方重置连接”错误而失败。幂等请求将由 Go 的 HTTP 传输重试，但在其他情况下 Caddy 将以状态代码 502 进行响应。\n\n- **keepalive_interval**<span id=\"keepalive_interval\"/>是活跃探针之间的[持续时间](/docs/conventions#durations)。默认：`30s`。\n\n- **keepalive_idle_conns**<span id=\"keepalive_idle_conns\"/>定义保持活动的最大连接数。默认：无限制。\n\n- **keepalive_idle_conns_per_host**<span id=\"keepalive_idle_conns_per_host\"/>如果非零，则控制保持每个主机的最大空闲（保持活动）连接。默认：`32`。\n\n- **versions**<span id=\"versions\"/>允许自定义支持哪些版本的 HTTP。\n  \n  有效选项有：`1.1`、`2`、`h2c`、`3`。 \n\n  默认：`1.1 2`，或者如果[上游的 scheme](#upstream-addresses)是`h2c://`，则默认为`h2c 2`。\n\n  `h2c`支持与上游的明文 HTTP/2 连接。这是一个非标准功能，不使用 Go 的默认 HTTP 传输，因此它排除了其他功能。\n\n  `3`启用与上游的 HTTP/3 连接。 ⚠️ 这是一项实验性功能，可能会发生变化。\n\n- **compression**<span id=\"compression\"/>可用于通过将其设置为`off`来禁用对后端的压缩。\n\n- **max_conns_per_host**<span id=\"max_conns_per_host\"/>可选择限制每个主机的连接总数，包括处于拨号、活动和空闲状态的连接。默认：无限制。\n\n- **network_proxy**<span id=\"network_proxy\"/>指定用于向上游服务器发出请求的网络代理模块的名称。如果未明确配置，Caddy 会按照[Go 标准库](https://pkg.go.dev/golang.org/x/net/http/httpproxy#FromEnvironment)尊重通过环境变量配置的代理，即`HTTP_PROXY`、`HTTPS_PROXY`和`NO_PROXY`。当为此参数提供值时，请求将按以下顺序流经反向代理：客户端（用户）→`reverse_proxy`→`network_proxy`→ 上游。内置模块有：\n\t-`none`，用于忽略`HTTP_PROXY`、`HTTPS_PROXY`、`NO_PROXY`的环境设置。\n\t-`url <url>`，用于指定覆盖环境配置的单个 URL。\n\n### `fastcgi`运输\n\n```caddy-d\ntransport fastcgi {\n\troot  <path>\n\tsplit <at>\n\tenv   <key> <value>\n\tresolve_root_symlink\n\tdial_timeout  <duration>\n\tread_timeout  <duration>\n\twrite_timeout <duration>\n\tcapture_stderr\n}\n```\n\n- **root**<span id=\"root\"/>是网站的根目录。默认：`{http.vars.root}`或当前工作目录。\n\n- **split**<span id=\"split\"/>是分割路径以获取 URI 末尾的 PATH_INFO 的位置​​。\n\n- **env**<span id=\"env\"/>将额外的环境变量设置为给定值。可以为多个环境变量指定多次。\n\n- **resolve_root_symlink**<span id=\"resolve_root_symlink\"/>通过评估符号链接（如果存在）来将`root`目录解析为其实际值。\n\n- **dial_timeout**<span id=\"dial_timeout\"/>是连接到上游套接字时等待的时间。接受[持续时间值](/docs/conventions#durations)。默认：`3s`。\n\n- **read_timeout**<span id=\"read_timeout\"/>是从 FastCGI 服务器读取时等待的时间。接受[持续时间值](/docs/conventions#durations)。默认：无超时。\n\n- **write_timeout**<span id=\"write_timeout\"/>是发送到FastCGI 服务器时等待的时间。接受[持续时间值](/docs/conventions#durations)。默认：无超时。\n\n- **capture_stderr**<span id=\"capture_stderr\"/>可以捕获并记录上游 fastcgi 服务器在`stderr`上发送的任何消息。默认情况下，日志记录在`WARN`级别完成。如果响应具有`4xx`或`5xx`状态，则将使用`ERROR`级别。默认情况下，`stderr`被忽略。\n\n<aside class=\"tip\">\n\n如果您尝试为现代 PHP 应用程序提供服务，您可能正在寻找[`php_fastcgi` 指令](/docs/caddyfile/directives/php_fastcgi)，它是使用`fastcgi`指令的代理的快捷方式，并进行了必要的重写以使用`index.php`作为路由入口点。\n\n</aside>\n\n\n\n## 拦截响应\n\n反向代理可以配置为拦截来自后端的响应。为了实现这一点，可以定义[响应匹配器](/docs/caddyfile/response-matchers)（类似于请求匹配器的语法），并且将调用第一个匹配的`handle_response`路由。\n\n当调用响应处理程序时，来自后端的响应不会写入客户端，而是会执行配置的`handle_response`路由，并由该路由来写入响应。如果路由确实_不_写入响应，则请求处理将继续使用[排序在其后](/docs/caddyfile/directives#directive-order)此`reverse_proxy`的任何处理程序。\n\n- **@name** 是[响应匹配器](/docs/caddyfile/response-matchers)的名称。只要每个响应匹配器都有唯一的名称，就可以定义多个匹配器。响应可以根据状态代码和响应标头的存在或值进行匹配。\n\n- **replace_status**<span id=\"replace_status\"/>在与给定匹配器匹配时仅更改响应的状态代码。\n\n- **handle_response**<span id=\"handle_response\"/>定义当与给定匹配器匹配时执行的路由（或者，如果省略匹配器，则所有响应）。将应用第一个匹配块。在`handle_response`块内，可以使用任何其他[directives](/docs/caddyfile/directives)。\n\n此外，在`handle_response`内部，可以使用两个特殊的处理程序指令：\n\n- **copy_response**<span id=\"copy_response\"/>将从后端收到的响应正文复制回客户端。执行此操作时，可以选择允许更改响应的状态代码。该指令是[排序在 `respond` 之前](/docs/caddyfile/directives#directive-order)。\n\n- **copy_response_headers**<span id=\"copy_response_headers\"/>将响应标头从后端复制到客户端，可选地包含 _或_ 排除标头字段列表（不能同时指定`include`和`exclude`）。该指令是[排序在 `header` 之后](/docs/caddyfile/directives#directive-order)。\n\n`handle_response`路由中将提供三个占位符：\n\n-`{rp.status_code}`后端响应的状态码。\n\n-`{rp.status_text}`后端响应的状态文本。\n\n-`{rp.header.*}`后端响应的标头。\n\n虽然反向代理响应处理程序可以将从代理接收到的新响应复制回客户端，但它无法将该新响应传递给后续反向代理。每次使用`reverse_proxy`都会收到原始请求（或使用不同模块修改的）的正文。\n\n\n\n\n## 示例\n\n将所有请求反向代理到本地后端：\n\n```caddy\nexample.com {\n\treverse_proxy localhost:9005\n}\n```\n\n\n[Load-balance](#load-balancing)所有请求[between 3 backends](#upstreams):\n\n```caddy\nexample.com {\n\treverse_proxy node1:80 node2:80 node3:80\n}\n```\n\n\n相同，但仅限`/api`内的请求，并使用[`cookie` policy](#lb_policy)进行粘性：\n\n```caddy\nexample.com {\n\treverse_proxy /api/* node1:80 node2:80 node3:80 {\n\t\tlb_policy cookie api_sticky\n\t}\n}\n```\n\n\n使用[active health checks](#active-health-checks)来确定哪些后端是健康的，并在连接失败时启用[retries](#lb_try_duration)，保留请求直到找到健康的后端：\n\n```caddy\nexample.com {\n\treverse_proxy node1:80 node2:80 node3:80 {\n\t\thealth_uri /healthz\n\t\tlb_try_duration 5s\n\t}\n}\n```\n\n\n配置一些[传输选项](#transports)：\n\n```caddy\nexample.com {\n\treverse_proxy localhost:8080 {\n\t\ttransport http {\n\t\t\tdial_timeout 2s\n\t\t\tresponse_header_timeout 30s\n\t\t}\n\t}\n}\n```\n\n\n反向代理[HTTPS upstream](#https)（从 v2.11.0 开始，Caddy 会自动设置`Host`标头以匹配上游主机，因此不再需要手动执行此操作）：\n\n```caddy\nexample.com {\n\treverse_proxy https://example.com\n}\n```\n\n\n反向代理到 HTTPS 上游，但[⚠️ disable TLS verification](#tls_insecure_skip_verify)。不建议这样做，因为它会禁用 HTTPS 提供的所有安全检查；如果可能的话，首选在专用网络中通过 HTTP 进行代理，因为它避免了错误的安全感：\n\n```caddy\nexample.com {\n\treverse_proxy 10.0.0.1:443 {\n\t\ttransport http {\n\t\t\ttls_insecure_skip_verify\n\t\t}\n\t}\n}\n```\n\n\n相反，您可以通过显式[trusting the upstream's certificate](#tls_trust_pool)与上游建立信任，并（可选）设置 TLS-SNI 以匹配上游证书中的主机名：\n\n```caddy\nexample.com {\n\treverse_proxy 10.0.0.1:443 {\n\t\ttransport http {\n\t\t\ttls_trust_pool file /path/to/cert.pem\n\t\t\ttls_server_name app.example.com\n\t\t}\n\t}\n}\n```\n\n\n\n代理前的[去除路径前缀](handle_path)；但要注意[子文件夹问题 <img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://caddy.community/t/the-subfolder-problem-or-why-cant-i-reverse-proxy-my-app-into-a-subfolder/8575)：\n\n```caddy\nexample.com {\n\thandle_path /prefix/* {\n\t\treverse_proxy localhost:9000\n\t}\n}\n```\n\n\n在代理之前使用[`rewrite`](/docs/caddyfile/directives/rewrite)替换路径前缀：\n\n```caddy\nexample.com {\n\thandle_path /old-prefix/* {\n\t\trewrite /new-prefix{path}\n\t\treverse_proxy localhost:9000\n\t}\n}\n```\n\n\n`X-Accel-Redirect`支持，即按请求提供静态文件，通过[intercepting the response](#intercepting-responses)：\n\n```caddy\nexample.com {\n\treverse_proxy localhost:8080 {\n\t\t@accel header X-Accel-Redirect *\n\t\thandle_response @accel {\n\t\t\troot /path/to/private/files\n\t\t\trewrite {rp.header.X-Accel-Redirect}\n\t\t\tmethod GET\n\t\t\tfile_server\n\t\t}\n\t}\n}\n```\n\n\n来自上游的错误的自定义错误页面，由[intercepting error responses](#intercepting-responses)通过状态代码：\n\n```caddy\nexample.com {\n\treverse_proxy localhost:8080 {\n\t\t@error status 500 503\n\t\thandle_response @error {\n\t\t\troot /path/to/error/pages\n\t\t\trewrite /{rp.status_code}.html\n\t\t\tfile_server\n\t\t}\n\t}\n}\n```\n\n\n从[`A`/`AAAA` record](#aaaaa)DNS 查询中获取后端[dynamically](#dynamic-upstreams)：\n\n```caddy\nexample.com {\n\treverse_proxy {\n\t\tdynamic a example.com 9000\n\t}\n}\n```\n\n\n从[`SRV` record](#srv)DNS 查询中获取后端[dynamically](#dynamic-upstreams)：\n\n```caddy\nexample.com {\n\treverse_proxy {\n\t\tdynamic srv _api._tcp.example.com\n\t}\n}\n```\n\n\n在创建中间服务以进行更彻底的健康检查时，使用[active health checks](#active-health-checks)和`health_upstream`会很有帮助。  然后，`{http.reverse_proxy.active.target_upstream}`可以用作标头，向健康检查服务提供原始上游。\n\n```caddy\nexample.com {\n\treverse_proxy node1:80 node2:80 node3:80 {\n\t\thealth_uri /health\n\t\thealth_upstream 127.0.0.1:53336\n\t\thealth_headers {\n\t\t\tFull-Upstream {http.reverse_proxy.active.target_upstream}\n\t\t}\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/rewrite.md",
    "content": "---\ntitle: rewrite (Caddyfile指令)\n---\n\n# rewrite\n\n对请求进行内部重写。重写会改变请求URI的部分或全部内容。请注意，URI不包括方案或授权（主机和端口），而且客户通常不发送片段。因此，这个指令主要用于路径和查询字符串的处理。\n\n重写会改变请求URI的部分或全部内容。请注意，URI不包含scheme或authority（主机和端口），而且客户端通常不会发送fragment。因此，此指令主要用于**路径**和**查询**字符串操作。\n\n`rewrite`指令表示接受该请求，但要进行修改。\n\n它与同一块中的其他`rewrite`指令相互排斥，因此可以安全地定义原本会互相级联的重写，因为只会执行第一个匹配的重写。\n\n在`rewrite`之前匹配请求的[请求匹配器](/docs/caddyfile/matchers)，在`rewrite`之后可能不再匹配同一个请求。如果想让你的`rewrite`与其他处理器共享一条路由，请使用[`route`](route)或[`handle`](handle)指令。\n\n\n## 语法\n\n```caddy-d\nrewrite [<matcher>] <to>\n```\n\n- **&lt;to&gt;** 是要将请求重写到的URI。只有重写中指定的URI组成部分（路径或查询字符串）会被操作。URI路径是`?`之前的任意子串。如果省略`?`，则整个标记都被视为路径。\n\n在v2.8.0之前，如果`<to>`参数以`/`开头，解析器可能会把它误认为[匹配器标记](/docs/caddyfile/matchers#syntax)，因此必须指定通配符匹配器标记（`*`）。\n\n\n## 类似指令\n\n还有一些指令也会执行重写，但表达不同的意图，或在不完全替换URI的情况下进行重写：\n\n- [`uri`](uri)操作URI（剥离前缀、后缀或替换子串）。\n\n- [`try_files`](try_files)根据文件是否存在来重写请求。\n\n\n\n## 示例\n\n将所有请求重写到`index.html`，保留任何查询字符串不变：\n\n```caddy\nexample.com {\n\trewrite /index.html\n}\n```\n\n<aside class=\"tip\">\n\n请注意，在v2.8.0之前，这里需要一个[通配符匹配器](/docs/caddyfile/matchers#wildcard-matchers)，因为第一个参数会与[路径匹配器](/docs/caddyfile/matchers#path-matchers)混淆，例如`rewrite * /foo`，但现在可以简化为`rewrite /foo`。\n\n</aside>\n\n为所有请求加上`/api`前缀，保留URI的其余部分，然后反向代理到应用：\n\n```caddy\napi.example.com {\n\trewrite /api{uri}\n\treverse_proxy localhost:8080\n}\n```\n\n将API请求的查询字符串替换为`a=b`，路径保持不变：\n\n```caddy\nexample.com {\n\trewrite ?a=b\n}\n```\n\n仅对`/api/`的请求保留现有查询字符串，并添加一个键值对：\n\n```caddy\nexample.com {\n\trewrite /api/* ?{query}&a=b\n}\n```\n\n同时改变路径和查询字符串，保留原始查询字符串，并将原始路径作为`p`参数添加：\n\n```caddy\nexample.com {\n\trewrite /index.php?{query}&p={path}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/root.md",
    "content": "---\ntitle: root (Caddyfile指令)\n---\n\n# root\n\n设置网站的根路径，由各种匹配器和访问文件系统的指令使用。如果不设置，默认的网站根目录是当前工作目录。\n\n具体来说，这个指令设置了`{http.vars.root}`占位符。它与同一区块中的其他`root'指令是相互排斥的，所以用相交的匹配器定义多个根是安全的：它们不会级联和相互覆盖。\n\n该指令不会自动启用静态文件的服务，所以它通常与[`file_server`指令](/docs/caddyfile/directives/file_server)或[`php_fastcgi`指令](/docs/caddyfile/directives/php_fastcgi)一起使用。\n\n\n## 语法\n\n```caddy-d\nroot [<matcher>] <path>\n```\n\n- **&lt;path&gt;** 是用于网站根目录的路径。\n\n注意`<path>`参数如果以`/`开头，可能会被解析器混淆为[匹配器标记]（/docs/caddyfile/matchers#syntax）。为了消除混淆，可以指定一个通配符匹配器标记（`*`）。见下面的例子。\n\n## 示例\n\n为所有请求设置网站根目录为`/home/user/public_html`。\n\n(注意，这里需要一个[通配符匹配器](/docs/caddyfile/matchers#wildcard-matchers)，因为第一个参数与[路径匹配器](/docs/caddyfile/matchers#path-matchers)不明确。)\n\n```caddy-d\nroot * /home/user/public_html\n```\n\n为所有请求设置网站根目录为`public_html`（相对于当前工作目录）。\n\n(这里不需要匹配器标记，因为我们的网站根目录是一个相对路径，所以它不是以正斜杠开始的，因此不会产生歧义。)\n\n```caddy-d\nroot public_html\n```\n\n只为`/foo/*`中的请求改变网站根。\n\n```caddy-d\nroot /foo/* /home/user/public_html/foo\n```\n\nroot \"指令通常与[`file_server`](/docs/caddyfile/directives/file_server)配对，为静态文件提供服务，或者与[`php_fastcgi`](/docs/caddyfile/directives/php_fastcgi)配对，为PHP网站提供服务。\n\n```caddy-d\nroot * /home/user/public_html\nfile_server\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/route.md",
    "content": "---\ntitle: route (Caddyfile指令)\n---\n\n# route\n\n将一组指令按字面意思作为一个单元进行评估。\n\n路由块中包含的指令不会在内部被重新排序。只有HTTP处理指令（将处理程序或中间件添加到链中的指令）可以在路由块中使用。\n\n这个指令是一个特例，它的子指令也是常规指令。\n\n\n## 语法\n\n```caddy-d\nroute [<matcher>] {\n\t<directives...>\n}\n```\n\n- **<directives...>** 是一个指令或指令块的列表，每行一个，就像在路由块外面一样；只是这些指令不会被重新排序。只有HTTP处理指令可以被使用。\n\n\n\n## 实用性\n\n`路由`指令在某些高级用例或边缘用例中很有帮助，可以对HTTP处理程序链的部分进行绝对控制。\n\n因为HTTP中间件的评估顺序很重要，Caddyfile通常会在解析后重新排列指令，以使Caddyfile更容易使用；你不必担心你输入东西的顺序。\n\n虽然内置的顺序与大多数网站兼容，但有时你需要对顺序进行手动控制，可以是整个网站，也可以只是其中的一部分。这就是 \"路由 \"指令的用处。\n\n为了说明这一点，考虑两个终止处理程序的情况。`redir`和`file_server`。这两个处理程序都向客户端写入响应，并且不调用链中的下一个处理程序，所以对于某个请求，只有其中一个会被执行。哪个先执行？通常情况下，`redir`在`file_server`之前执行，因为通常你只想在特定情况下发出重定向，在一般情况下提供文件。\n\n然而，在有些情况下，第二个指令（`redir`）比第二个指令（`file_server`）有更具体的匹配器。换句话说，你想在一般情况下重定向，而只服务于一个特定的文件。\n\n所以你可以尝试这样的Caddyfile（但这不会像预期的那样工作！）。\n\n```caddy\nexample.com\n\nfile_server /specific.html\nredir https://anothersite.com{uri}\n```\n\n问题是，在内部，`redir`排在`file_server`之前，但在这种情况下，`redir`的匹配器是`file_server`的匹配器的超集（`*`是`/specific.html`的超集）。\n\n幸运的是，解决方案很简单：只要把这两个指令包在一个`路由`块中。\n\n```caddy\nexample.com\n\nroute {\n\tfile_server /specific.html\n\tredir https://anothersite.com{uri}\n}\n```\n\n<aside class=\"tip\">\n另一种方法是使两个匹配器相互排斥，但如果有一个或两个以上的条件，这很快会变得复杂。使用`route`指令，两个处理程序的互斥性是隐含的，因为它们都是终端处理程序。\n</aside>\n\n而现在`file_server`将在`redir`之前被链入，因为这个顺序是按字面意思来的。\n\n## 类似的指令\n\n还有其他一些指令可以包装HTTP处理程序指令，但每个指令都有其用途，取决于你想表达的行为。\n\n- [`handle`](handle)像`route`那样包装其他指令，但有两个区别。\n  - 1）handle块是相互排斥的\n  - 2）通常handle中的指令是[重新排序的](/docs/caddyfile/directives#directive-order)。\n- [`handle_path`](handle_path)的作用与`handle`相同，但它在运行其处理程序之前从请求中剥离了一个前缀。\n- [`handle_errors`](handle_errors)和`handle`一样，但只有当Caddy在处理请求时遇到错误才会调用。\n\n## 示例\n\n在代理所有API请求到后端之前，从请求路径中去除`/api`前缀。\n\n```caddy-d\nroute /api/* {\n\turi strip_prefix /api\n\treverse_proxy localhost:9000\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/templates.md",
    "content": "---\ntitle: templates (Caddyfile指令)\n---\n\n# templates\n\n将响应体作为[template](/docs/modules/http.handlers.templates)文件执行。模板提供了制作简单动态页面的功能基元。功能包括HTTP子请求、HTML文件包含、Markdown渲染、JSON解析、基本数据结构、随机性、时间等。\n\n\n## 语法\n\n```caddy-d\ntemplates [<matcher>] {\n\tmime    <types...>\n\tbetween <open_delim> <close_delim>\n\troot    <path>\n\textensions {\n\t\t<name> {\n\t\t\t...\n\t\t}\n\t}\n}\n```\n\n- **mime** 是模板中间件会处理的MIME类型；任何不具备合格`Content-Type`的响应都不会被作为模板求值。\n\n  默认：`text/html text/plain`。\n\n- **between** 是模板动作的开始和结束定界符。如果它们与文档其他部分冲突，你可以更改它们。\n\n  默认：`{{printf \"{{ }}\"}}`。\n\n- **root** 是使用访问文件系统的函数时的站点根目录。\n\n  默认为[`root`](root)指令设置的站点根目录；如果未设置，则为当前工作目录。\n\n- **extensions** 允许你注册由`http.handlers.templates.functions.*`命名空间中的模块提供的自定义模板函数。\n\n  块内的每个子指令都对应一个模块名称。这些模块可以向模板函数映射添加自定义函数，通常用于实现可复用组件。此功能主要面向插件。\n\n内置模板函数的文档可在[templates模块](/docs/modules/http.handlers.templates#docs)中找到。\n\n\n\n## 示例\n\n关于使用模板提供markdown服务的完整站点示例，请查看[这个网站](https://github.com/caddyserver/website)的源代码！具体来说，请查看[`Caddyfile`](https://github.com/caddyserver/website/blob/master/Caddyfile)和[`src/docs/index.html`](https://github.com/caddyserver/website/blob/master/src/docs/index.html)。\n\n为静态站点启用模板：\n\n```caddy\nexample.com {\n\troot /srv\n\ttemplates\n\tfile_server\n}\n```\n\n要使用模板提供简单的静态响应，请确保设置`Content-Type`：\n\n```caddy\nexample.com {\n\theader Content-Type text/plain\n\ttemplates\n\trespond `Current year is: {{printf \"{{\"}}now | date \"2006\"{{printf \"}}\"}}`\n}\n```\n\n使用模板扩展（插件）：\n\n```caddy\nexample.com {\n\troot /srv\n\ttemplates {\n\t\textensions {\n\t\t\t# 需要 caddy-hitcounter 插件：\n\t\t\t# https://github.com/mholt/caddy-hitcounter\n\t\t\thitCounter {\n\t\t\t\tstyle bright_green\n\t\t\t\tpad_digits 6\n\t\t\t}\n\t\t}\n\t}\n\tfile_server\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/tls.md",
    "content": "---\ntitle: tls (Caddyfile指令)\n---\n\n<script>\nready(function() {\n\t// We'll add links to all the subdirectives if a matching anchor tag is found on the page.\n\taddLinksToSubdirectives();\n});\n</script>\n\n# tls\n\n为网站配置TLS。\n\n**Caddy的默认TLS设置是安全的。只有在你有充分的理由并了解其影响的情况下才能改变这些设置。**这个指令最常见的用途是指定一个ACME账户的电子邮件地址、改变ACME CA端点，或者提供你自己的证书。\n\n兼容性说明：由于其作为安全协议的敏感性质，在新的次要版本或补丁版本中可能会对TLS默认值进行有意的调整。旧的或坏的TLS版本、密码、功能等可能在任何时候被删除。如果你的部署对变化非常敏感，你应该明确指定那些必须保持不变的值，并对升级保持警惕。在几乎所有情况下，我们建议使用默认设置。\n\n\n## 语法\n\n```caddy-d\ntls [internal|force_automate|<email>] | [<cert_file> <key_file>] {\n\tprotocols <min> [<max>]\n\tciphers   <cipher_suites...>\n\tcurves    <groups...>\n\talpn      <values...>\n\tload      <paths...>\n\tca        <ca_dir_url>\n\tca_root   <pem_file>\n\tkey_type  ed25519|p256|p384|rsa2048|rsa4096\n\tdns       <provider_name> [<params...>]\n\tpropagation_timeout <duration>\n\tpropagation_delay   <duration>\n\tdns_ttl             <duration>\n\tdns_challenge_override_domain <domain>\n\tresolvers <dns_servers...>\n\teab       <key_id> <mac_key>\n\ton_demand\n\treuse_private_keys\n\tclient_auth {\n\t\tmode                   [request|require|verify_if_given|require_and_verify]\n\t\ttrust_pool             <module>\n\t\tverifier               <module>\n\t}\n\tissuer          <issuer_name>  [<params...>]\n\tget_certificate <manager_name> [<params...>]\n\tinsecure_secrets_log <log_file>\n\trenewal_window_ratio <ratio>\n\tforce_automate\n}\n```\n\n- **internal**意味着使用Caddy内部的、本地信任的CA来为这个网站制作证书。要进一步配置[`internal`](#internal)发行者，请使用[`issuer`](#issuer)子指令。\n\n- **force_automate**强制Caddy为站点自动管理证书，即使已经有其他托管证书适用。\n\n- **&lt;email&gt;**是用于管理网站证书的ACME账户电子邮件地址。你也可以用[`email`全局选项](/docs/caddyfile/options#email)一次性为所有站点配置。\n\n<aside class=\"tip\">\n\n请记住，Let's Encrypt可能会向你发送证书即将过期的邮件，但这可能有误导性，因为Caddy在续期时可能选择了其他签发者（例如ZeroSSL）。请检查日志或证书本身（例如在浏览器中）确认实际使用的签发者以及证书是否仍在有效期内；如果是，可以安全地忽略Let's Encrypt的邮件。\n\n</aside>\n\n- **&lt;cert_file&gt;**和**&lt;key_file&gt;**是证书和私钥PEM文件的路径。只指定一个是无效的。\n\n- **protocols** <span id=\"protocols\"/>指定最小和最大协议版本。除非你知道自己在做什么，否则不要修改。通常不需要配置它，因为Caddy会始终使用现代默认值。\n\n  默认最小值：`tls1.2`，默认最大值：`tls1.3`。\n\n- **ciphers** <span id=\"ciphers\"/>指定按偏好降序排列的密码套件名称列表。除非你知道自己在做什么，否则不要修改。请注意，TLS 1.3的密码套件不可自定义；并且并非所有TLS 1.2密码都默认启用。支持的名称如下（按Go标准库偏好顺序）：\n\t- `TLS_AES_128_GCM_SHA256`\n\t- `TLS_CHACHA20_POLY1305_SHA256`\n\t- `TLS_AES_256_GCM_SHA384`\n\t- `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`\n\t- `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`\n\t- `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`\n\t- `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`\n\t- `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256`\n\t- `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`\n\t- `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`\n\t- `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`\n\t- `TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`\n\t- `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`\n\t- `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`\n\n- **curves** <span id=\"curves\"/>指定要支持的EC组列表。建议不要修改默认值。支持的值是：\n\t- `x25519mlkem768`（PQC）\n\t- `x25519`\n\t- `secp256r1`\n\t- `secp384r1`\n\t- `secp521r1`\n\n- **alpn** <span id=\"alpn\"/>是在TLS握手的[ALPN扩展<img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://developer.mozilla.org/en-US/docs/Glossary/ALPN)中公布的值列表。\n\n- **load** <span id=\"load\"/>指定一个文件夹列表，从该文件夹中加载证书+密钥捆绑的PEM文件。\n\n- **ca** <span id=\"ca\"/>改变ACME CA端点。这最常用于测试时设置[Let's Encrypt暂存端点<img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://letsencrypt.org/docs/staging-environment/)，或内部ACME服务器。（要为整个Caddyfile改变这个值，请使用`acme_ca`[全局选项](/docs/caddyfile/options)代替。）\n\n- **ca_root** <span id=\"ca_root\"/>指定一个PEM文件，其中包含ACME CA端点的可信根证书，如果不在系统信任存储中。\n\n- **key_type** <span id=\"key_type\"/>是生成CSR时要使用的密钥类型。只有在你有特定要求时才设置这个。\n\n- **dns** <span id=\"dns\"/>使用指定的提供者插件启用[DNS挑战](/docs/automatic-https#dns-challenge)，该插件必须来自[`caddy-dns` <img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://github.com/caddy-dns)仓库之一。每个提供者插件可能在名称后有自己的语法，详情请参考各自文档。维护每个DNS提供商的支持是一项社区工作。[了解如何在我们的维基上为你的提供商启用DNS挑战。](https://caddy.community/t/how-to-use-dns-provider-modules-in-caddy-2/8148)\n\n- **propagation_timeout** <span id=\"propagation_timeout\"/>是一个[持续时间值](/docs/conventions#durations)，设置使用DNS挑战时等待DNS TXT记录出现的最长时间。设置为`-1`可禁用传播检查。默认2分钟。\n\n- **propagation_delay** <span id=\"propagation_delay\"/>是一个[持续时间值](/docs/conventions#durations)，设置使用DNS挑战时，在开始DNS TXT记录传播检查前等待多长时间。默认`0`（不等待）。\n\n- **dns_ttl** <span id=\"dns_ttl\"/>是一个[持续时间值](/docs/conventions#durations)，设置DNS挑战使用的`TXT`记录TTL。很少需要设置。\n\n- **dns_challenge_override_domain** <span id=\"dns_challenge_override_domain\"/>覆盖用于DNS挑战的域名。这用于将挑战委托给另一个域名。\n\n  如果你的主域名DNS提供商没有可用的[DNS插件<img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://github.com/caddy-dns)，你可能会用到它。你可以在主域名添加`_acme-challenge`子域的`CNAME`记录，指向一个你 _确实_ 有插件的辅助域名。此选项 _不_ 要求插件提供特殊支持。\n\n  当ACME签发者尝试为主域名完成DNS挑战时，会跟随`CNAME`到辅助域名查找`TXT`记录。\n\n  **注意：**这里的值应使用CNAME记录中的完整规范名称；不会自动添加`_acme-challenge`子域。\n\n- **resolvers** <span id=\"resolvers\"/>自定义执行DNS挑战时使用的DNS解析器；这些解析器优先于系统解析器或任何默认解析器。如果在这里设置，解析器将传播到所有配置的证书签发者。\n\n  这通常是一个IP地址列表。例如，使用[Google Public DNS<img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://developers.google.com/speed/public-dns)：\n\n  ```caddy-d\n  resolvers 8.8.8.8 8.8.4.4\n  ```\n\n- **eab** <span id=\"eab\"/>为这个站点配置ACME外部账户绑定（EAB），使用你的CA提供的密钥ID和MAC密钥。\n\n- **on_demand** <span id=\"on_demand\"/>为网站块地址中给出的主机名启用[按需TLS](/docs/automatic-https#on-demand-tls)。**安全警告：**在生产中这样做是不安全的，除非你也配置了[`on_demand_tls`全局选项](/docs/caddyfile/options#on-demand-tls)以减少滥用。\n\n- **reuse_private_keys** <span id=\"reuse_private_keys\"/>启用证书续期时复用私钥。默认情况下，每张新证书都会创建新密钥，以缓解固定密钥并降低密钥泄露影响范围。密钥固定违背行业最佳实践。除非你有明确理由，否则不建议使用此选项；它将来可能被移除。\n\n- **client_auth** <span id=\"client_auth\"/>启用并配置TLS客户端认证：\n    - **mode** <span id=\"mode\"/>是验证客户端的模式。允许的值是：\n\n      | 模式 | 说明 |\n      |--------------------|------------------------------------------------------------------------------------------|\n      | request | 要求客户提供证书，但即使没有证书也允许；不对其进行验证。|\n      | require | 要求客户出示证书，但不进行验证。|\n      | verify_if_given | 要求客户提供证书，即使没有也允许，但如果有则验证。|\n      | require_and_verify | 要求客户出示经过验证的有效证书。|\n\n  默认值：如果提供`trust_pool`模块则为`require_and_verify`；否则为`require`。\n\n    - **trust_pool** <span id=\"trust_pool\"/>配置证书颁发机构（CA）的来源，用于提供验证客户端证书的证书池。\n\n  提供受信证书池的证书颁发机构及其配置取决于所配置的信任池模块。Caddy中可用的标准模块[列在下方](#trust-pool-providers)。完整模块列表（包括第三方模块）见[`trust_pool`JSON文档](/docs/json/apps/http/servers/tls_connection_policies/client_authentication/#trust_pool)。\n\n    - **verifier** <span id=\"verifier\"/>启用自定义客户端证书验证器模块。这些模块可以执行自定义客户端认证检查，例如确保证书未被吊销。\n\n- **issuer** <span id=\"issuer\"/>配置一个自定义的证书颁发者，或一个获得证书的来源。使用哪一个签发者以及这一段后面的选项取决于可用的签发者模块（标准签发者见下文；插件可以添加其他的）。其他一些子指令，如`ca`和`dns`实际上是配置`acme`签发者的快捷方式（这个子指令是后来添加的），所以指定这个指令和其他一些指令是混乱的，因此禁止使用。这个子指令可以被多次指定，以配置多个冗余的签发者；如果一个签发失败，将尝试下一个签发者。\n\n- **get_certificate** <span id=\"get_certificate\"/>可以在握手时从一个[管理模块](#certificate-managers)获取证书。\n\n- **insecure_secrets_log** <span id=\"insecure_secrets_log\"/>启用将TLS密钥记录到文件。这也称为`SSLKEYLOGFILE`，使用NSS密钥日志格式，可由Wireshark或其他工具解析。⚠️**安全警告：**这是不安全的，因为它允许其他程序或工具解密TLS连接，从而完全破坏安全性。不过，此能力可用于调试和排查问题。\n\n- **renewal_window_ratio** <span id=\"renewal_window_ratio\"/>是0到1之间的比率，用于决定证书剩余多少生命周期时Caddy开始尝试续期。例如，如果证书生命周期为90天，而此比率为`0.3333`（默认值），则Caddy会在证书剩余30天或更少时持续尝试续期。也可以通过[`renewal_window_ratio`全局选项](/docs/caddyfile/options#renewal_window_ratio)设置。\n\n  你通常很少需要修改它，但如果CA签发时间很长，稍晚续期可能有用。\n\n  请记住，这只是一个建议，因为ACME签发者可能实现[ARI扩展](https://datatracker.ietf.org/doc/rfc9773/)。ARI会指定ACME客户端（这里是Caddy）应尝试续期的窗口，而该窗口可能与此比率不一致。\n\n- **force_automate**与在行内指定相同（见上文）。\n\n### 信任池提供者\n\n以下是可以在`trust_pool`子指令中使用的标准信任池提供者：\n\n#### inline\n\n`inline`模块会直接解析Caddyfile中列出的base64 DER编码受信根证书。`trust_der`指令可以重复多次。\n\n```caddy-d\ntrust_pool inline {\n\ttrust_der      <base64_der>\n}\n```\n\n- **trust_der** <span id=\"trust_der\"/>是用于验证客户端证书的base64 DER编码CA证书。\n\n#### file\n\n`file`模块从磁盘上的PEM文件读取受信根证书。`pem_file`指令可以在同一行接受多个文件路径，也可以重复多次。\n\n```caddy-d\n... file [<pem_file>...] {\n\tpem_file <pem_file>...\n}\n```\n\n- **pem_file** <span id=\"pem_file\"/>是用于验证客户端证书的PEM CA证书文件路径。\n\n#### pki_root\n\n`pki_root`模块从[PKI应用](/docs/caddyfile/options#pki-options)中定义的证书颁发机构获取并信任 _根_ 证书。`authority`指令可以同时接受多个机构，也可以重复多次。\n\n```caddy-d\n... pki_root [<ca_name>...] {\n\tauthority <ca_name>...\n}\n```\n\n- **authority** <span id=\"authority\"/>是PKI应用中配置的证书颁发机构名称。\n\n#### pki_intermediate\n\n`pki_intermediate`模块从[PKI应用](/docs/caddyfile/options#pki-options)中定义的证书颁发机构获取并信任 _中间_ 证书。`authority`指令可以同时接受多个机构，也可以重复多次。\n\n```caddy-d\n... pki_intermediate [<ca_name>...] {\n\tauthority <ca_name>...\n}\n```\n\n- **authority** <span id=\"authority\"/>是PKI应用中配置的证书颁发机构名称。\n\n#### storage\n\n`storage`模块从Caddy[存储](/docs/caddyfile/options#storage)中提取受信证书根。`keys`指令可以同时接受多个存储键，也可以重复多次。\n\n```caddy-d\n... storage [<storage_keys>...] {\n\tstorage <storage_module>\n\tkeys    <storage_keys>...\n}\n```\n\n- **storage** <span id=\"storage\"/>是要使用的可选存储模块。如果未指定，将使用默认存储模块。如果指定，只能指定一次。\n\n- **keys** <span id=\"keys\"/>是存放证书PEM文件的存储键列表。该指令可以在同一行接受多个值，也可以重复多次。\n\n#### http\n\n`http`模块从HTTP端点获取受信证书。`endpoints`指令可以同时接受多个端点，也可以重复多次。\n\n```caddy-d\n... http [<endpoints...>] {\n\tendpoints   <endpoints...>\n\ttls         <tls_config>\n}\n```\n\n- **endpoints** <span id=\"endpoints\"/>是获取证书的HTTP端点列表。该指令可以在同一行接受多个值，也可以重复多次。\n\n- **tls** <span id=\"tls\"/>是连接HTTP端点时要使用的可选TLS配置。片段解析方式见[下一节](#tls-1)。\n\n##### TLS\n\n```caddy-d\n... {\n\tca                    <ca_module>\n\tinsecure_skip_verify\n\thandshake_timeout     <duration>\n\tserver_name           <name>\n\trenegotiation         <never|once|freely>\n}\n```\n\n- **ca** <span id=\"ca\"/>是用于定义信任池提供者的可选指令。配置行为与[`trust_pool`](#trust_pool)相同。如果指定，只能指定一次。\n\n- **insecure_skip_verify** <span id=\"insecure_skip_verify\"/>关闭TLS握手验证，使连接不安全并容易受到中间人攻击。_不要在生产环境中使用。_验证会针对系统信任的证书颁发机构，或由[`ca`](#ca)指令决定的机构执行。\n\n- **handshake_timeout** <span id=\"handshake_timeout\"/>是等待TLS握手完成的最大[持续时间](/docs/conventions#durations)。默认：无超时。\n\n- **server_name** <span id=\"server_name\"/>设置验证TLS握手中收到的证书时使用的服务器名称。默认使用上游地址的主机部分。\n\n- **renegotiation** <span id=\"renegotiation\"/>设置TLS重新协商级别。TLS重新协商是指在第一次握手之后执行后续握手。级别可以是：\n  - `never`（默认）禁用重新协商。\n  - `once`允许远程服务器每个连接请求一次重新协商。\n  - `freely`允许远程服务器反复请求重新协商。\n\n### 验证器\n\n如果配置了`trust_pool`，客户端证书验证器模块会在验证证书由受信证书颁发机构签发之后执行。标准Caddy当前提供的验证器是`leaf`。\n\n#### Leaf\n\n`leaf`验证器检查客户端证书是否属于一组定义好的允许证书。证书集合通过[加载器](https://caddyserver.com/docs/modules/tls.client_auth.verifier.leaf#leaf_certs_loaders)模块加载。\n\n##### 加载器\n\n标准Caddy发行版捆绑4个加载器，其中3个可在Caddyfile中使用。\n\n###### File\n\n`file`加载器从指定PEM文件中加载证书集合。\n\n```caddy-d\n... file <pem_files...>\n```\n\n###### Folder\n\n`folder`加载器会递归遍历指定目录，查找要作为已接受客户端证书加载的PEM文件。\n\n```caddy-d\n... folder <folders...>\n```\n\n###### PEM\n\n`pem`加载器接受以内联方式写在Caddyfile中的PEM格式证书。\n\n```caddy-d\n... pem <pem_strings...>\n```\n\n### 发行人\n\n以下签发者随`tls`指令标准提供：\n\n#### acme\n\n使用ACME协议获取证书。请注意，`acme`是默认签发者（使用Let's Encrypt），所以通常不需要显式配置。\n\n```caddy-d\n... acme [<directory_url>] {\n\tdir      <directory_url>\n\ttest_dir <test_directory_url>\n\temail    <email>\n\ttimeout  <duration>\n\tdisable_http_challenge\n\tdisable_tlsalpn_challenge\n\talt_http_port    <port>\n\talt_tlsalpn_port <port>\n\teab <key_id> <mac_key>\n\ttrusted_roots <pem_files...>\n\tdns [<provider_name> [<options>]]\n\tpropagation_timeout <duration>\n\tpropagation_delay   <duration>\n\tdns_ttl             <duration>\n\tdns_challenge_override_domain <domain>\n\tresolvers <dns_servers...>\n\tpreferred_chains [smallest] {\n\t\troot_common_name <common_names...>\n\t\tany_common_name  <common_names...>\n\t}\n\tprofile <name>\n}\n```\n\n- **dir** <span id=\"dir\"/>是ACME CA目录的URL。\n\n  默认值：`https://acme-v02.api.letsencrypt.org/directory`\n\n- **test_dir** <span id=\"test_dir\"/>是一个可选的后备目录，在重试挑战时使用；如果所有挑战失败，在重试时将使用该端点；如果CA有暂存端点且你想避免其生产端点的速率限制，则非常有用。\n\n  默认值：`https://acme-staging-v02.api.letsencrypt.org/directory`\n\n- **email** <span id=\"email\"/>是ACME帐户的联系电子邮件地址。\n\n- **timeout** <span id=\"timeout\"/>是一个[持续时间值](/docs/conventions#durations)，设置ACME操作超时前要等待多长时间。\n\n- **disable_http_challenge** <span id=\"disable_http_challenge\"/>将禁用HTTP挑战。\n\n- **disable_tlsalpn_challenge** <span id=\"disable_tlsalpn_challenge\"/>将禁用TLS-ALPN挑战。\n\n- **alt_http_port** <span id=\"alt_http_port\"/>是提供HTTP挑战的备用端口；它必须发生在80端口，所以你必须将数据包转发到这个备用端口。\n\n- **alt_tlsalpn_port** <span id=\"alt_tlsalpn_port\"/>是一个备用端口，用于提供TLS-ALPN挑战；它必须发生在443端口，所以你必须将数据包转发到这个备用端口。\n\n- **eab** <span id=\"eab\"/>指定外部账户绑定，某些ACME CA可能需要它。\n\n- **trusted_roots** <span id=\"trusted_roots\"/>是一个或多个根证书（作为PEM文件名），当连接到ACME CA服务器时要信任。\n\n- **dns** <span id=\"dns\"/>配置DNS挑战。除非[`dns`全局选项](/docs/caddyfile/options#dns)指定了全局适用的DNS提供者模块，否则必须在这里配置提供者。\n\n- **propagation_timeout** <span id=\"propagation_timeout\"/>是一个[持续时间值](/docs/conventions#durations)，设置使用DNS挑战时等待DNS TXT记录出现的最长时间。设置为`-1`可禁用传播检查。默认2分钟。\n\n- **propagation_delay** <span id=\"propagation_delay\"/>是一个[持续时间值](/docs/conventions#durations)，设置使用DNS挑战时，在开始DNS TXT记录传播检查前等待多长时间。默认0（不等待）。\n\n- **dns_ttl** <span id=\"dns_ttl\"/>是一个[持续时间值](/docs/conventions#durations)，设置DNS挑战使用的`TXT`记录TTL。很少需要设置。\n\n- **dns_challenge_override_domain** <span id=\"dns_challenge_override_domain\"/>覆盖用于DNS挑战的域名。这用于将挑战委托给另一个域名。\n\n  如果你的主域名DNS提供商没有可用的[DNS插件<img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://github.com/caddy-dns)，你可能会用到它。你可以在主域名添加`_acme-challenge`子域的`CNAME`记录，指向一个你 _确实_ 有插件的辅助域名。此选项 _不_ 要求插件提供特殊支持。\n\n  当ACME签发者尝试为主域名完成DNS挑战时，会跟随`CNAME`到辅助域名查找`TXT`记录。\n\n  **注意：**这里的值应使用CNAME记录中的完整规范名称；不会自动添加`_acme-challenge`子域。\n\n- **resolvers** <span id=\"resolvers\"/>自定义执行DNS挑战时使用的DNS解析器；这些解析器优先于系统解析器或任何默认解析器。如果在这里设置，解析器将传播到所有配置的证书签发者。\n\n  这通常是一个IP地址列表。例如，使用[Google Public DNS<img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://developers.google.com/speed/public-dns)：\n\n  ```caddy-d\n  resolvers 8.8.8.8 8.8.4.4\n  ```\n\n- **preferred_chains** <span id=\"preferred_chains\"/>指定Caddy应该优先使用哪些证书链；如果你的CA提供多个证书链，这会很有用。使用以下选项之一：\n\t- **smallest** <span id=\"smallest\"/>将告诉Caddy倾向于使用字节数最少的链。\n\n\t- **root_common_name** <span id=\"root_common_name\"/>是一个或多个通用名称的列表；Caddy将选择第一个根部与指定通用名称中至少一个相匹配的链。\n\n\t- **any_common_name** <span id=\"any_common_name\"/>是一个或多个通用名称的列表；Caddy将选择第一个具有与指定通用名称中至少一个相匹配的发行者的链。\n\n- **profile**是订购证书时要应用的[ACME配置文件](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/)名称。如果指定了它，所有配置的（无论隐式还是显式）CA都必须支持该配置文件。可用配置文件请参考你的CA文档；某些CA可能不支持配置文件。实验性：ACME配置文件规范仍处于草案状态，因此此功能可能发生变化或被移除。\n\n#### zerossl\n\n使用[ZeroSSL专有证书签发API](https://zerossl.com/documentation/api/)获取证书。需要API密钥，并且根据你的套餐可能需要付费。请注意，这与[ZeroSSL的ACME端点](https://zerossl.com/documentation/acme/)不同。若要使用ZeroSSL的ACME端点，请使用上面描述的`acme`签发者，并配置ZeroSSL的ACME目录端点。\n\n```caddy-d\n... zerossl <api_key> {\n\tvalidity_days <days>\n\talt_http_port <port>\n\tdns <provider_name> ...\n\tpropagation_delay <duration>\n\tpropagation_timeout <duration>\n\tresolvers <list...>\n\tdns_ttl <duration>\n}\n```\n\n- **validity_days** <span id=\"validity_days\"/>定义证书生命周期。只接受某些值；详情见[ZeroSSL文档](https://zerossl.com/documentation/api/create-certificate/)。\n<!--\n  Default: `https://acme-v02.api.letsencrypt.org/directory`\n -->\n- **alt_http_port** <span id=\"zerossl_alt_http_port\"/>是完成ZeroSSL HTTP验证时要使用的端口，如果不是80端口。\n- **dns** <span id=\"zerossl_dns\"/>使用命名DNS提供者及给定配置启用CNAME验证方法，以便自动创建记录。DNS提供者插件必须从[`caddy-dns` <img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://github.com/caddy-dns)仓库安装。每个提供者插件可能在名称后有自己的语法，详情请参考各自文档。维护每个DNS提供商的支持是一项社区工作。\n- **propagation_delay** <span id=\"zerossl_propagation_delay\"/>是在检查CNAME记录传播前等待的时间。\n- **propagation_timeout** <span id=\"zerossl_propagation_timeout\"/>是放弃前等待CNAME记录传播的时间。\n- **resolvers** <span id=\"zerossl_resolvers\"/>定义检查CNAME记录传播时使用的自定义DNS解析器。\n- **dns_ttl** <span id=\"zerossl_dns_ttl\"/>配置作为验证过程一部分创建的CNAME记录TTL。\n\n\n#### internal\n\n从一个内部证书颁发机构获取证书。\n\n```caddy-d\n... internal {\n\tca       <name>\n\tlifetime <duration>\n\tsign_with_root\n}\n```\n\n- **ca** <span id=\"ca\"/>是要使用的内部CA名称。默认值：`local`。参见[PKI应用全局选项](/docs/caddyfile/options#pki-options)来配置`local`CA，或创建其他CA。\n\n  默认情况下，根CA证书生命周期为`3600d`（10年），中间证书生命周期为`7d`（7天）。\n\n  Caddy会尝试将根CA证书安装到系统信任存储，但当Caddy以非特权用户运行或在Docker容器中运行时可能失败。在这种情况下，需要手动安装根CA证书，可以使用[`caddy trust`](/docs/command-line#caddy-trust)命令，或[从容器中复制出来](/docs/running#usage)。\n\n- **lifetime** <span id=\"lifetime\"/>是一个[持续时间值](/docs/conventions#durations)，设置内部签发的叶子证书有效期。默认值：`12h`。除非绝对必要，否则不建议修改。它必须短于中间证书生命周期。\n\n- **sign_with_root** <span id=\"sign_with_root\"/>强制由根证书而不是中间证书签发。不建议这样做，只应在设备/客户端不能正确验证证书链时使用（非常罕见）。\n\n\n\n### 证书管理器\n\n证书管理器模块与签发者模块不同：使用管理器模块意味着外部工具或服务负责保持证书续期，而签发者模块意味着Caddy自身管理证书。（签发者模块以证书签名请求（CSR）作为输入，而证书管理器模块以TLS ClientHello作为输入。）\n\n以下管理器模块随`tls`指令标准提供：\n\n#### tailscale\n\n从本地运行的[Tailscale<img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://tailscale.com)实例获取证书。[你的Tailscale账户必须启用HTTPS](https://tailscale.com/kb/1153/enabling-https/)（或你的开源[Headscale服务器<img src=\"/old/resources/images/external-link.svg\" class=\"external-link\">](https://github.com/juanfont/headscale)）；并且Caddy进程必须以root身份运行，或者你必须配置`tailscaled`授予Caddy用户[获取证书的权限](https://github.com/caddyserver/caddy/pull/4541#issuecomment-1021568348)。\n\n_**注意：这通常是不必要的！** Caddy会自动对所有`*.ts.net`域名使用Tailscale，不需要任何额外配置。_\n\n```caddy-d\nget_certificate tailscale  # often unnecessary!\n```\n\n\n#### http\n\n通过发出HTTP(S)请求来获取证书。响应必须有`200`状态码，正文必须包含PEM链，包括完整证书（含中间证书）以及私钥。\n\n```caddy-d\nget_certificate http <url>\n```\n\n- **url** <span id=\"url\"/>是要请求的完整URL。出于性能原因，强烈建议它是本地端点。该URL会附加以下查询字符串参数：\n\n  - `server_name`：SNI值\n  - `signature_schemes`：签名算法十六进制ID的逗号分隔列表\n  - `cipher_suites`：密码套件十六进制ID的逗号分隔列表\n  - `local_ip`：客户端发起请求时连接到的IP地址\n\n\n## 示例\n\n使用自定义证书和密钥。证书应具有与站点地址匹配的[SAN](https://en.wikipedia.org/wiki/Subject_Alternative_Name)：\n\n```caddy\nexample.com {\n\ttls cert.pem key.pem\n}\n```\n\n为当前网站块上的所有主机使用本地信任的证书，而不是通过ACME / Let's Encrypt的公共证书（在开发环境中很有用）：\n\n```caddy\nexample.com {\n\ttls internal\n}\n```\n\n使用本地信任的证书，但使用[按需](/docs/automatic-https#on-demand-tls)管理而不是后台管理。这允许你将任意域名指向Caddy实例，并自动为其配置证书。如果你的Caddy实例可公开访问，**不应**使用它，因为攻击者可能利用它耗尽服务器资源：\n\n```caddy\nhttps:// {\n\ttls internal {\n\t\ton_demand\n\t}\n}\n```\n\n对内部CA使用自定义选项（不能使用`tls internal`的快捷方式）：\n\n```caddy\nexample.com {\n\ttls {\n\t\tissuer internal {\n\t\t\tca foo\n\t\t}\n\t}\n}\n```\n\n为你的ACME账户指定一个电子邮件地址（但如果所有网站只使用一个电子邮件，我们建议用`email`[全局选项](/docs/caddyfile/options)代替）：\n\n```caddy\nexample.com {\n\ttls your@email.com\n}\n```\n\n为Cloudflare上管理的域名启用DNS挑战，在环境变量中使用账户凭证。这会解锁通配符证书支持，而通配符证书需要DNS验证：\n\n```caddy\n*.example.com {\n\ttls {\n\t\tdns cloudflare {env.CLOUDFLARE_API_TOKEN}\n\t}\n}\n```\n\n通过HTTP获取证书链，而不是让Caddy管理它。请注意，[`get_certificate`](#certificate-managers)意味着启用[`on_demand`](#on_demand)，即使用模块获取证书而不是触发ACME签发：\n\n```caddy\nhttps:// {\n\ttls {\n\t\tget_certificate http http://localhost:9007/certs\n\t}\n}\n```\n\n启用TLS客户端认证，并要求客户端提供有效证书，通过[`trust_pool`](#trust_pool)的`file`提供者对所有提供的CA进行验证：\n\n```caddy\nexample.com {\n\ttls {\n\t\tclient_auth {\n\t\t\ttrust_pool file ../caddy.ca.cer ../root.ca.cer\n\t\t}\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/tracing.md",
    "content": "---\ntitle: tracing (Caddyfile指令)\n---\n\n# tracing\n\n它提供了与OpenTelemetry追踪设施的整合。\n\n当启用时，它将传播一个现有的跟踪上下文或初始化一个新的。\n\n它基于[github.com/open-telemetry/opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go)。\n\n它使用[gRPC](https://github.com/grpc/)作为输出协议，使用W3C[tracecontext](https://www.w3.org/TR/trace-context/)和[baggage](https://www.w3.org/TR/baggage/)作为传播器。\n\n## 语法\n\n```caddy-d\ntracing {\n\t[span <span_name>]\n}\n```\n\n- **&lt;span_name&gt;** - 是一个span的名称。请参阅 span 命名[指南](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/specification/trace/api.md)。\n\n## 配置\n\n### 环境变量\n\n可以使用[OpenTelemetry]定义的环境变量对其进行配置。\nOpenTelemetry环境变量规范](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md)。\n\n关于导出器的配置细节，请\n见[spec](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/specification/protocol/exporter.md)。\n\n比如说：\n\n```bash\nexport OTEL_EXPORTER_OTLP_HEADERS=\"myAuthHeader=myToken,anotherHeader=value\"\nexport OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://my-otlp-endpoint:55680\n```\n\n## 示例\n\n下面是一个**Caddyfile**的例子：\n\n```\nhandle /myHandler {\n\ttracing {\n\t\tspan my-span\n\t}\n\treverse_proxy 127.0.0.1:8081\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/try_files.md",
    "content": "---\ntitle: try_files (Caddyfile指令)\n---\n\n# try_files\n\n将请求URI路径重写为站点根目录中存在的第一个列出的文件。如果没有文件匹配，则不执行重写。\n\n\n## 语法\n\n```caddy-d\ntry_files <files...> {\n\tpolicy first_exist|first_exist_fallback|smallest_size|largest_size|most_recently_modified\n}\n```\n\n- **<files...>** 是要尝试的文件列表。URI路径会被重写为第一个存在的文件。\n\n  要匹配目录，请在路径后追加正斜杠`/`。所有文件路径都相对于站点[root](root)，并且会展开[glob模式](https://pkg.go.dev/path/filepath#Match)。\n\n  每个参数也可以包含查询字符串；如果匹配到该特定文件，查询字符串也会被更改。\n\n  如果`try_policy`是`first_exist`（默认值），则列表中的最后一项可以是以`=`为前缀的数字（例如`=404`），作为后备，它会发出带有该代码的错误；该错误可以用[`handle_errors`](handle_errors)捕获和处理。\n\n- **policy** 是从文件列表中选择文件的策略。\n\n  默认：`first_exist`\n\n\n\n## 扩展形式\n\n`try_files`指令基本上是下面配置的快捷方式：\n\n```caddy-d\n@try_files file <files...>\nrewrite @try_files {file_match.relative}\n```\n\n请注意，此指令不接受匹配器标记。如果需要更复杂的匹配逻辑，请以上面的扩展形式为基础。\n\n更多细节请参见[`file`匹配器](/docs/caddyfile/matchers#file)。\n\n\n\n## 示例\n\n如果请求没有匹配任何静态文件，则重写到你的PHP index/router入口：\n\n```caddy-d\ntry_files {path} /index.php\n```\n\n同样如此，但会把原始路径加入查询字符串（某些旧版PHP应用需要）：\n\n```caddy-d\ntry_files {path} /index.php?{query}&p={path}\n```\n\n同样如此，但也匹配目录：\n\n```caddy-d\ntry_files {path} {path}/ /index.php?{query}&p={path}\n```\n\n如果文件或目录存在，则尝试重写到它；否则发出404错误（可用[`handle_errors`](handle_errors)捕获和处理）：\n\n```caddy-d\ntry_files {path} {path}/ =404\n```\n\n选择最近部署的静态文件版本（例如请求`index.html`时提供`index.be331df.html`）：\n\n```caddy-d\ntry_files {file.base}.*.{file.ext} {\n\tpolicy most_recently_modified\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/uri.md",
    "content": "---\ntitle: uri (Caddyfile指令)\n---\n\n# uri\n\n操作请求的URI。它可以剥离路径前缀/后缀，或替换整个URI中的子串。\n\n此指令不同于[`rewrite`](rewrite)：`uri`是对URI做差异化修改，而不是像`rewrite`那样将其重置为完全不同的内容。虽然`rewrite`会作为内部重定向被特殊处理，但`uri`只是另一个中间件。\n\n\n## 语法\n\n支持多种不同的操作：\n\n```caddy-d\nuri [<matcher>] strip_prefix <target>\nuri [<matcher>] strip_suffix <target>\nuri [<matcher>] replace      <target> <replacement> [<limit>]\nuri [<matcher>] path_regexp  <target> <replacement>\nuri [<matcher>] query        [-|+]<param> [<value>]\nuri [<matcher>] query {\n\t<param> [<value>] [<replacement>]\n\t...\n}\n```\n\n第一个（非匹配器）参数指定操作：\n\n- **strip_prefix** 从路径中剥离前缀。\n\n- **strip_suffix** 从路径中剥离后缀。\n\n- **replace** 在整个URI上执行子串替换。\n\n- **&lt;target&gt;** 是前缀、后缀或搜索字符串/正则表达式。如果是前缀，可以省略前导正斜杠，因为路径总是以正斜杠开头。\n\n- **&lt;replacement&gt;** 是替换字符串。支持使用`$name`或`${name}`语法的捕获组，或使用数字索引，例如`$1`。详见[Go文档](https://golang.org/pkg/regexp/#Regexp.Expand)。如果替换值是`\"\"`，则从值中删除匹配文本。\n\n- **&lt;limit&gt;** 是最大替换次数的可选限制。\n\n- **path_regexp** 在URI的路径部分执行正则表达式替换。\n\n- **&lt;target&gt;** 是前缀、后缀或搜索字符串/正则表达式。如果是前缀，可以省略前导正斜杠，因为路径总是以正斜杠开头。\n\n- **&lt;replacement&gt;** 是替换字符串。支持使用`$name`或`${name}`语法的捕获组，或使用数字索引，例如`$1`。详见[Go文档](https://golang.org/pkg/regexp/#Regexp.Expand)。如果替换值是`\"\"`，则从值中删除匹配文本。\n\n- **query** 对URI查询执行操作，其模式取决于参数名前缀或参数数量。可以使用块一次指定多个操作，这些操作会分组并按此顺序执行：重命名 🡒 设置 🡒 追加 🡒 替换 🡒 删除。\n\n- 没有前缀时，会在查询中用给定值设置该参数。\n\n  例如，`uri query foo bar`会将`foo`参数的值设置为`bar`。\n\n- 使用`-`前缀会从查询中移除该参数。\n\n  例如，`uri query -foo`会删除`foo`参数。\n\n- 使用`+`前缀会向查询追加一个带有给定值的参数。这_不会_覆盖同名已有参数（省略`+`则会覆盖）。\n\n  例如，`uri query +foo bar`会向查询追加`foo=bar`。\n\n- 参数中以`>`作为中缀时，会把该参数重命名为`>`后的值。\n\n  例如，`uri query foo>bar`会将`foo`参数重命名为`bar`。\n\n- 使用三个参数时，会执行查询值正则表达式替换，其中第一个参数是查询参数名，第二个是搜索值，第三个是替换值。第一个参数（参数名）可以是`*`，表示对所有查询参数执行替换。\n\n  支持使用`$name`或`${name}`语法的捕获组，或使用数字索引，例如`$1`。详见[Go文档](https://golang.org/pkg/regexp/#Regexp.Expand)。如果替换值是`\"\"`，则从值中删除匹配文本。\n\n  例如，`uri query foo ^(ba)r $1z`会替换`foo`参数的值；当该值以`bar`开头时，结果会变为`baz`。\n\nURI变更发生在URI的规范化或未转义形式上。不过，转义序列可以用于前缀或后缀模式，以便只匹配请求路径中这些位置上的字面转义。例如，`uri strip_prefix /a/b`会把`/a/b/c`和`/a%2Fb/c`都重写为`/c`；而`uri strip_prefix /a%2Fb`会把`/a%2Fb/c`重写为`/c`，但不会匹配`/a/b/c`。\n\nURI路径会在修改前清理目录遍历点。此外，多个斜杠（例如`//`）会被合并，除非`<target>`也包含多个斜杠。\n\n## 类似指令\n\n其他一些指令也可以操作请求URI。\n\n- [`rewrite`](rewrite)将整个路径和查询改为新值，而不是部分改变该值。\n\n- [`handle_path`](handle_path)与[`handle`](handle)相同，但会在运行其处理器之前从请求中剥离一个前缀。在许多情况下，可以用它替代`uri strip_prefix`，以减少一行配置。\n\n\n## 示例\n\n从所有请求路径开头剥离`/api`：\n\n```caddy-d\nuri strip_prefix /api\n```\n\n从所有请求路径末尾剥离`.php`：\n\n```caddy-d\nuri strip_suffix .php\n```\n\n在任何请求URI中将 \"/docs/\" 替换为 \"/v1/docs/\"：\n\n```caddy-d\nuri replace /docs/ /v1/docs/\n```\n\n将请求路径中所有重复斜杠（但不包括请求查询）折叠为单个斜杠：\n\n```caddy-d\nuri path_regexp /{2,} /\n```\n\n将`foo`查询参数的值设置为`bar`：\n\n```caddy-d\nuri query foo bar\n```\n\n从查询中移除`foo`参数：\n\n```caddy-d\nuri query -foo\n```\n\n将`foo`查询参数重命名为`bar`：\n\n```caddy-d\nuri query foo>bar\n```\n\n向查询追加`bar`参数：\n\n```caddy-d\nuri query +foo bar\n```\n\n将`foo`查询参数中以`bar`开头的值替换为`baz`：\n\n```caddy-d\nuri query foo ^(ba)r $1z\n```\n\n一次执行多个查询操作：\n\n```caddy-d\nuri query {\n\t+foo bar\n\t-baz\n\tqux test\n\trenamethis>renamed\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives/vars.md",
    "content": "---\ntitle: vars (Caddyfile指令)\n---\n\n# vars\n\n将一个或多个变量设置为一个特定的值，以便在以后的请求处理链中使用。\n\n访问变量的主要方式是使用占位符，其形式为`{vars.variable_name}`，或者使用[`vars`](/docs/caddyfile/matchers#vars)和[`vars_regexp`](/docs/caddyfile/matchers#vars_regexp) 请求匹配器。\n\n## 语法\n\n```caddy-d\nvars [<matcher>] [<name> <value>] {\n    <name> <value>\n    ...\n}\n```\n\n- **&lt;name&gt;**是要设置的变量名称。\n\n- **&lt;value&gt;**是该变量的值。\n\n  如果可能的话，该值将进行类型转换；`true`和`false`将被转换为布尔类型，数字值将被相应地转换为整数或浮点数。为了避免这种转换，你可以用[引号](/docs/caddyfile/concepts#tokens-and-quotes)来包裹输出，它们将保持为字符串。\n\n## 示例\n\n设置一个单一的变量，该值是基于请求路径的条件，然后用该值进行响应：\n\n```caddy-d\nvars /foo* isFoo \"yep\"\nvars isFoo \"nope\"\n\nrespond {vars.isFoo}\n```\n\n要设置多个变量，每个变量都转换为适当的标量类型：\n\n```caddy-d\nvars {\n\t# boolean\n\tabc true\n\n\t# integer\n\tdef 1\n\n\t# float\n\tghi 2.3\n\n\t# string\n\tjkl \"example\"\n}\n```"
  },
  {
    "path": "src/docs/markdown/caddyfile/directives.md",
    "content": "---\ntitle: Caddyfile指令\n---\n\n<style>\n#directive-table table {\n\tmargin: 0 auto;\n\toverflow: hidden;\n}\n\n#directive-table tr:hover {\n\tbackground: rgba(0, 0, 0, 10%);\n}\n\n#directive-table tr td:first-child {\n\tposition: relative;\n}\n\n#directive-table a:before {\n\tcontent: '';\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tbottom: 0;\n\tdisplay: block;\n\twidth: 100vw;\n}\n</style>\n\n# Caddyfile指令\n\n指令是出现在站点[块](/docs/caddyfile/concepts#blocks)中的功能关键字。有时，它们会打开自己的块，其中可以包含 _子指令_；但除非另有说明，指令**不能**在其他指令中使用。例如，不能在`file_server`块中使用`basic_auth`，因为`file_server`不知道如何进行身份验证。不过，你 _可以_ 在`handle`和`route`这样的特殊指令块中使用一些指令，因为它们专门用于组合HTTP处理器指令。\n\n- [语法](#语法)\n- [指令顺序](#指令顺序)\n- [排序算法](#排序算法)\n\n以下指令是Caddy的标准配置，可在HTTP Caddyfile中使用：\n\n<div id=\"directive-table\">\n\n指令 | 说明\n----------|------------\n**[abort](/docs/caddyfile/directives/abort)** | 中止HTTP请求\n**[acme_server](/docs/caddyfile/directives/acme_server)** | 嵌入式ACME服务器\n**[basic_auth](/docs/caddyfile/directives/basic_auth)** | 强制执行HTTP基本身份验证\n**[bind](/docs/caddyfile/directives/bind)** | 自定义服务器的套接字地址\n**[encode](/docs/caddyfile/directives/encode)** | 编码（通常是压缩）响应\n**[error](/docs/caddyfile/directives/error)** | 触发错误\n**[file_server](/docs/caddyfile/directives/file_server)** | 从磁盘提供文件\n**[forward_auth](/docs/caddyfile/directives/forward_auth)** | 将身份验证委托给外部服务\n**[fs](/docs/caddyfile/directives/fs)** | 设置文件I/O使用的文件系统\n**[handle](/docs/caddyfile/directives/handle)** | 一组互斥的指令\n**[handle_errors](/docs/caddyfile/directives/handle_errors)** | 定义路由的错误处理器\n**[handle_path](/docs/caddyfile/directives/handle_path)** | 像处理器，但去掉路径前缀\n**[header](/docs/caddyfile/directives/header)** | 设置或删除响应标头\n**[import](/docs/caddyfile/directives/import)** | 包括片段或文件\n**[intercept](/docs/caddyfile/directives/intercept)** | 拦截其他处理器写出的响应\n**[invoke](/docs/caddyfile/directives/invoke)** | 调用命名路由\n**[log](/docs/caddyfile/directives/log)** | 启用访问/请求日志记录\n**[log_append](/docs/caddyfile/directives/log_append)** | 向访问日志追加字段\n**[log_skip](/docs/caddyfile/directives/log_skip)** | 对匹配的请求跳过访问日志\n**[log_name](/docs/caddyfile/directives/log_name)** | 覆盖要写入的日志器名称\n**[map](/docs/caddyfile/directives/map)** | 将输入值映射到一个或多个输出\n**[method](/docs/caddyfile/directives/method)** | 在内部更改HTTP方法\n**[metrics](/docs/caddyfile/directives/metrics)** | 配置Prometheus指标展示端点\n**[php_fastcgi](/docs/caddyfile/directives/php_fastcgi)** | 通过FastCGI服务PHP站点\n**[push](/docs/caddyfile/directives/push)** | 使用HTTP/2服务器推送将内容推送到客户端\n**[redir](/docs/caddyfile/directives/redir)** | 向客户端发出HTTP重定向\n**[request_body](/docs/caddyfile/directives/request_body)** | 操作请求包\n**[request_header](/docs/caddyfile/directives/request_header)** | 操作请求头\n**[respond](/docs/caddyfile/directives/respond)** | 向客户端写入硬编码响应\n**[reverse_proxy](/docs/caddyfile/directives/reverse_proxy)** | 强大且可扩展的反向代理\n**[rewrite](/docs/caddyfile/directives/rewrite)** | 在内部重写请求\n**[root](/docs/caddyfile/directives/root)** | 设置站点根目录的路径\n**[route](/docs/caddyfile/directives/route)** | 将一组指令从字面上视为单个单元\n**[templates](/docs/caddyfile/directives/templates)** | 对响应执行模板\n**[tls](/docs/caddyfile/directives/tls)** | 自定义TLS设置\n**[tracing](/docs/caddyfile/directives/tracing)** | 集成OpenTelemetry的tracing\n**[try_files](/docs/caddyfile/directives/try_files)** | 取决于文件的存在的重写\n**[uri](/docs/caddyfile/directives/uri)** | 操作URL\n**[vars](/docs/caddyfile/directives/vars)** | 设置任意变量\n\n</div>\n\n## 语法\n\n每个指令的语法如下所示：\n\n```caddy-d\ndirective [<matcher>] <args...> {\n\tsubdirective [<args...>]\n}\n```\n\n`<>`表示要由实际值替换的标记。\n\n`[]`表示可选参数。\n\n`...`表示延续，即一个或多个参数，或者多行。\n\n除非另有说明，否则子指令始终是可选的，即使它们没有出现在`[]`.\n\n\n### 匹配器\n\n大多数——但不是全部——指令接受[匹配器标记](/docs/caddyfile/matchers#syntax)，它可以让你过滤请求。匹配器标记通常是可选的。如果你在指令的语法中看到这一点：\n\n```caddy-d\n[<matcher>]\n```\n\n然后该指令接受一个匹配器令牌，让你过滤该指令适用于哪些请求。\n\n由于匹配器标记的工作方式相同，因此不会在每一页上都描述匹配器令牌的各种可能性，以减少重复。如果想了解详情，请统一参考[匹配器文档](/docs/caddyfile/matchers)。\n\n\n## 指令顺序\n\n许多指令操纵HTTP处理程序链。这些指令的默认顺序已经被硬编码到Caddy中，评估其顺序是很重要的事情：\n\n```caddy-d\ntracing\n\nmap\nvars\nfs\nroot\nlog_append\nlog_skip\nlog_name\n\nheader\ncopy_response_headers # 仅在 reverse_proxy 的 handle_response 块中可用\nrequest_body\n\nredir\n\n# 传入请求操作\nmethod\nrewrite\nuri\ntry_files\n\n# 中间件处理器；其中一些会包装响应\nbasic_auth\nforward_auth\nrequest_header\nencode\npush\nintercept\ntemplates\n\n# 特殊路由和分派指令\ninvoke\nhandle\nhandle_path\nroute\n\n# 通常会响应请求的处理器\nabort\nerror\ncopy_response # 仅在 reverse_proxy 的 handle_response 块中可用\nrespond\nmetrics\nreverse_proxy\nphp_fastcgi\nfile_server\nacme_server\n```\n\n你可以使用[`order`全局选项](/docs/caddyfile/options)或[`route`指令](/docs/caddyfile/directives/route)覆盖/自定义该排序。\n\n\n\n## 排序算法\n\n为了便于使用，Caddyfile适配器会按照以下规则对指令排序：\n\n- 不同名称的指令会根据它们在[默认顺序](#指令顺序)中的位置排序。默认顺序可以通过[`order`全局选项](/docs/caddyfile/options)覆盖。插件提供的指令 _没有_ 顺序，因此应使用[`order`](/docs/caddyfile/options)全局选项或[`route`](/docs/caddyfile/directives/route)指令来设置顺序。\n\n- 同名指令会根据它们的[匹配器](/docs/caddyfile/matchers#syntax)排序。\n\n  - 最高优先级是只带有单个[路径匹配器](/docs/caddyfile/matchers#path-matchers)的指令。\n\n    路径匹配器会按具体程度排序，从最具体到最不具体。\n\n\t一般来说，这是按路径匹配器长度排序。有一个例外：如果路径以`*`结尾，且两个匹配器的路径除此之外相同，则没有`*`的匹配器会被认为更具体并排在前面。\n\n    例如：\n    - `/foobar`比`/foo`更具体\n    - `/foo`比`/foo*`更具体\n    - `/foo/*`比`/foo*`更具体\n\n  - 带有任何其他匹配器的指令排在后面，按它们在Caddyfile中出现的顺序排序。\n\n    这包括带有多个值的路径匹配器，以及[命名匹配器](/docs/caddyfile/matchers#named-matchers)。\n\n  - 没有匹配器的指令（即匹配所有请求）排在最后。\n\n- [`vars`](/docs/caddyfile/directives/vars)指令按匹配器排序的顺序相反，因为它会设置可能相互覆盖的值，所以最具体的匹配器应最后评估。\n\n- [`route`](/docs/caddyfile/directives/route)指令的内容会忽略上述所有规则，并保留指令在其中出现的顺序。\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/matchers.md",
    "content": "---\ntitle: 请求匹配器\n---\n\n# 请求匹配器\n\n**请求匹配器** 可用于按特定标准过滤（或分类）请求。\n\n### 菜单\n\n- [语法](#syntax)\n    - [例子](#examples)\n    - [通配符匹配器](#wildcard-matchers)\n    - [路径匹配器](#path-matchers)\n    - [命名匹配器](#named-matchers)\n- [标准匹配器](#standard-matchers)\n    - [expression](#expression)\n    - [file](#file)\n    - [header](#header)\n    - [header_regexp](#header-regexp)\n    - [host](#host)\n    - [method](#method)\n    - [not](#not)\n    - [path](#path)\n    - [path_regexp](#path-regexp)\n    - [protocol](#protocol)\n    - [query](#query)\n    - [remote_ip](#remote-ip)\n\n\n<h2 id=\"syntax\">语法</h2>\n\n在Caddyfile中，紧跟在指令后面的**匹配器标记**可以限制该指令的范围。匹配器标记可以是以下形式之一：\n\n1. **`*`** 匹配所有请求（通配符；默认）。\n2. **`/path`** 以正斜杠开头以匹配请求路径。\n3. **`@name`** 指定一个命名匹配器。\n\n匹配器标记[通常是可选](/docs/caddyfile/directives#matchers)的。如果省略匹配器标记，则它与通配符匹配器（`*`）相同。\n\n\n<h3 id=\"examples\">例子</h3>\n\n该指令适用于[所有](#wildcard-matchers)HTTP 请求：\n\n```caddy-d\nreverse_proxy localhost:9000\n```\n\n这是一样的：\n\n```caddy-d\nreverse_proxy * localhost:9000\n```\n\n但此指令仅适用于以`/api/`开头的[路径](#path-matchers)：\n\n```caddy-d\nreverse_proxy /api/* localhost:9000\n```\n\n要匹配路径以外的任何内容，请定义一个[命名匹配器](#named-matchers)并使用`@name`引用它：\n\n```caddy-d\n@postfoo {\n\tmethod POST\n\tpath /foo/*\n}\nreverse_proxy @postfoo localhost:9000\n```\n\n\n<h3 id=\"wildcard-matchers\">通配符匹配器</h3>\n\n通配符（或“catch-all”）匹配器`*`匹配所有请求，并且仅在需要匹配器标记时才需要。例如，如果你要给出指令的第一个参数也恰好是路径，那么它看起来就像一个路径匹配器！因此，你可以使用通配符匹配器来消除歧义，例如：\n\n```caddy-d\nroot * /home/www/mysite\n```\n\n否则，这个匹配器不经常使用。尽可能省略它很方便；只是一个偏好问题。\n\n<h3 id=\"path-matchers\">路径匹配器</h3>\n\n因为按路径匹配非常普遍，所以可以内联单个路径匹配器，如下所示：\n\n```caddy-d\nredir /old.html /new.html\n```\n\n路径匹配器标记必须以正斜杠`/`开头。\n\n**[路径匹配](/docs/caddyfile/matchers#path) 默认为精确匹配；**你必须附加`*`以进行快速前缀匹配。请注意，`/foo*`将匹配`/foo`、`/foo/`和`/foobar`；你可能实际是想要`/foo/*`。\n\n<h3 id=\"named-matchers\">命名匹配器</h3>\n\n所有不是路径或通配符匹配器的匹配器都必须命名为匹配器。这是一个在任何特定指令之外定义的匹配器，并且可以重用。\n\n定义具有唯一名称的匹配器为你提供了更大的灵活性，允许你将[任何可用的匹配器](#standard-matchers)组合成一个集合：\n\n```caddy-d\n@name {\n\t...\n}\n```\n\n或者，如果集合中只有一个匹配器：\n\n```caddy-d\n@name ...\n```\n\n然后你可以像这样使用匹配器：`@name`\n\n例如：\n\n```caddy-d\n@websockets {\n\theader Connection *Upgrade*\n\theader Upgrade    websocket\n}\nreverse_proxy @websockets localhost:6001\n```\n\n这仅代理具有名为“Connection”的标头字段（包含“Upgrade”）和另一个名为“Upgrade”且值为“websocket”的字段的请求。\n\n如果匹配器集仅包含一个匹配器，则单行语法也适用：\n\n```caddy-d\n@post method POST\nreverse_proxy @post localhost:6001\n```\n\n与指令一样，命名匹配器定义必须放在使用它们的站点块内。\n\n一个命名的匹配器定义构成一个_匹配器集_。集合中的匹配器是“与”的关系，也就是说，必须所有的都被匹配。例如，如果集合中有`aheader`和`path`匹配器，则两者都必须匹配。\n\n相同类型的国歌匹配器可以被(AND/OR)组合起来（例如，同一个集合中的多个`path`匹配器），如下面的相应部分所述。\n\n\n\n<h2 id=\"standard-matchers\">标准匹配器</h2>\n\n完整的匹配器文档可以[在每个匹配器模块的文档中](/docs/json/apps/http/servers/routes/match/)找到。\n\n\n\n### expression\n\n⚠️ _此模块仍处于试验阶段，因此可能会发生重大变化。_\n\n```caddy-d\nexpression <cel...>\n```\n\n通过任何[CEL(通用表达式语言)](https://github.com/google/cel-spec) 表达式返回`true`或者`false`。\n\n作为一种特殊情况，可以在这些CEL表达式中使用Caddy[占位符](/docs/conventions#placeholders) (或[Caddyfile缩写](/docs/caddyfile/concepts#placeholders))，因为它们在被CEL环境解释之前经过预处理并转换为常规CEL函数调用。\n\n#### 示例：\n\n匹配以`P`开头的请求方法，例如`PUT`或`POST`。\n\n```caddy-d\nexpression {method}.startsWith(\"P\")\n```\n\n处理返回`404`错误状态代码的请求，且与[`handle_errors`指令](/docs/caddyfile/directives/handle_errors)结合起来处理。\n\n```caddy-d\nexpression {http.error.status_code} == 404\n```\n\n---\n### 文件\n\n```caddy-d\nfile {\n\troot       <paths>\n\ttry_files  <files...>\n\ttry_policy first_exist|smallest_size|largest_size|most_recent_modified\n\tsplit_path <delims...>\n}\n```\n\n通过文件进行匹配。\n\n- `root`定义在其中查找文件的目录。默认是当前工作目录，或者`root`[变量](/docs/modules/http.handlers.vars) (`{http.vars.root}`)对应的位置 (可以通过[`root`指令](/docs/caddyfile/directives/root)设置)。\n- `try_files`检查其列表中与重试策略(try_policy)匹配的文件。如果`try_policy`是`first_exist`，那么列表中的最后一项可能是一个以`=`(比如`=404`)开头的数字，作为后备，将触发以这个数字作为错误码的回调; 该错误也可以使用[`handle_errors`](/docs/caddyfile/directives/handle_errors)捕获和处理错误。\n- `try_policy`指定如何选择文件。默认为`first_exist`.\n    - `first_exist`检查文件是否存在。选择存在的第一个文件。\n    - `smallest_size`选择大小最小的文件。\n    - `largest_size`选择最大的文件。\n    - `most_recent_modified`选择最近修改的文件。\n- `split_path`将导致路径在每个要尝试的文件路径中找到的列表中的第一个分隔符处拆分。对于每个拆分值，拆分的左侧（包括分隔符本身）将是尝试的文件路径。例如，`/remote.php/dav/`使用`.php`作为分隔符，将尝试文件`/remote.php`。每个分隔符必须出现在 URI 路径组件的末尾，才能用作拆分分隔符。这是一个小众设置，主要用于为 PHP 站点提供服务。\n\n因为`try_files`策略`first_exist`如此普遍，所以有一条捷径：\n\n```caddy-d\nfile <files...>\n```\n\n一个空`file` 匹配器（后面没有列出任何文件），将通过URI从相对于[站点根目录](/docs/caddyfile/directives/root)的位置逐字比对查找文件是否存在。\n\n由于基于磁盘上文件的存在进行重写非常普遍，因此还有一个[`try_files`指令](/docs/caddyfile/directives/try_files)，它是`file`匹配器和[`rewrite`处理器](/docs/caddyfile/directives/rewrite)的快捷方式。\n\n匹配后，将可以使用两个新的占位符：\n\n- `{http.matchers.file.relative}`文件的根相对路径。这在重写请求时通常很有用。\n- `{http.matchers.file.absolute}`匹配文件的绝对路径。\n\n#### 示例:\n\n匹配路径是存在文件的请求。\n\n```caddy-d\nfile\n```\n\n匹配请求，其中后跟的路径`.html`是存在的文件，或者如果不存在，则路径是存在的文件。\n\n```caddy-d\nfile {\n\ttry_files {path}.html {path} \n}\n```\n\n与上面相同，除了使用单行快捷方式，如果找不到文件，则回退到发出404错误。\n\n```caddy-d\nfile {path}.html {path} =404\n```\n\n---\n### header\n\n```caddy-d\nheader <field> [<value>]\n```\n\n通过请求头字段进行匹配。\n\n- `<field>`是要检查的 HTTP 标头字段的名称。\n    - 如果以`!`为前缀，则该字段必须不存在才能匹配 (省略`value`参数).\n- `<value>`是字段必须匹配的值。\n    - 如果前缀是`*`，则执行快速后缀匹配。\n    - 如果后缀为`*`，则执行快速前缀匹配。\n    - 如果用`*`括起来，它将执行快速子字符串匹配。\n    - 否则，它是快速精确匹配。\n\n统一集合的不同header字段是“和”的关系。每个字段的多个值之间是“或”的关系。\n\n#### 示例：\n\n匹配请求`Connection`标头字段包含`Upgrade`的请求：\n\n```caddy-d\nheader Connection *Upgrade*\n```\n\n匹配`Foo`标头字段包含`bar`或者`baz`的请求：\n\n```caddy-d\n@foo {\n\theader Foo bar\n\theader Foo baz\n}\n```\n\n匹配根本没有`Foo`标头字段的请求：\n\n```caddy-d\n@not_foo {\n\theader !Foo\n}\n```\n\n\n---\n### header_regexp\n\n```caddy-d\nheader_regexp [<name>] <field> <regexp>\n```\n\n和`header`类似，但是支持正则表达式。 捕获组可以通过[占位符](/docs/caddyfile/concepts#placeholders)访问，如`{re.name.capture_group}`，其中`name`是正则表达式的名称（可选，单推荐），且`capture_group`是表达式中捕获组的名称或编号。捕获组`0`是完整的正则表达式匹配，`1`是第一个捕获组，`2`是第二个捕获组，依此类推。\n\n使用的正则表达式语言是GO语言中内置的`RE2`。具体请查阅[RE2语法参考](https://github.com/google/re2/wiki/Syntax)和[Go正则语法概述](https://pkg.go.dev/regexp/syntax)。\n\n每个标头字段仅支持一个正则表达式。多个不同的字段是“与”的关系。\n\n#### 示例：\n\n将 Cookie 标头包含`login_`后跟十六进制字符串的请求，包含了一个可以通过`{re.login.1}`访问的捕获组。\n\n```caddy-d\nheader_regexp login Cookie login_([a-f0-9]+)\n```\n\n---\n### host\n\n```caddy-d\nhost <hosts...>\n```\n\n通过请求的`Host`标头字段匹配请求。在 Caddyfile 中使用它并不常见，因为大多数站点块已经在站点地址中明确主机。此匹配器主要用于未定义特定主机名的站点块。\n\n多个`host`匹配器之间是“或”的关系。\n\n#### 示例：\n\n```caddy-d\nhost sub.example.com\n```\n\n\n\n---\n### method\n\n```caddy-d\nmethod <verbs...>\n```\n\n通过HTTP请求的方法（动词）匹配请求。动词应该是大写的，比如`POST`。可以匹配一种或多种方法。\n\n多个`method`匹配之间是“或”的关系。\n\n#### 示例：\n\n匹配请求方法为`GET`的请求。\n\n```caddy-d\nmethod GET\n```\n\n匹配请求方法为`PUT`或者`DELETE`的请求。\n\n```caddy-d\nmethod PUT DELETE\n```\n\n\n---\n### not\n\n```caddy-d\nnot <any other matcher>\n```\n\n或者，要同时否定多个匹配器，请打开一个块：\n\n```caddy-d\nnot {\n\t<any other matchers...>\n}\n```\n\n包含起来的匹配器的结果将被否定。\n\n#### 示例：\n\n匹配路径不以`/css/`或`/js/`开头的请求。\n\n```caddy-d\nnot path /css/* /js/*\n```\n\n匹配两者关系为`NEIGHER`的请求：\n- 路径的前缀是`/api/`的，或非（NOR）\n- 请求方法为`POST`\n\n也就是说，必须__没有任何符合这些条件__(none of these)的请求才能被匹配：\n\n```caddy-d\nnot path /api/*\nnot method POST\n```\n\n匹配两者关系为`WITHOUT BOTH`的请求：\n- 路径的前缀是`/api/`，和(AND)\n- 请求方法为`POST`\n\n也就是说，必须__都不或者任何一个不__(neither or either)才能匹配：\n\n```caddy-d\nnot {\n\tpath /api/*\n\tmethod POST\n}\n```\n\n---\n### path\n\n```caddy-d\npath <paths...>\n```\n\n通过请求路径进行匹配，表示请求URI的路径部分。路径匹配是精确的，但也可以使用通配符`*`：\n\n- 放在最后，进行前缀匹配 (`/prefix/*`)\n- 放在开始，进行后缀匹配 (`*.suffix`)\n- 在两边，进行子字符串匹配 (`*/contains/*`)\n- 在中间，进行球状匹配 (`/accounts/*/info`)\n\n请求路径在匹配前经过URL解码、小写（不区分大小写）和清理（折叠双斜杠和目录遍历点）。例如`/foo*`也会匹配`/FOO`、`//foo`和`/%2F/foo`。\n\n多个`path`匹配之间是“或”的关系。\n\n---\n### path_regexp\n\n```caddy-d\npath_regexp [<name>] <regexp>\n```\n\n和`path`类似，但是支持正则表达式。捕获组可以通过[占位符](/docs/caddyfile/concepts#placeholders)访问，如`{re.name.capture_group}`，其中`name`是正则表达式的名称（可选，单推荐），且`capture_group`是表达式中捕获组的名称或编号。捕获组`0`是完整的正则表达式匹配，`1`是第一个捕获组，`2`是第二个捕获组，依此类推。\n\n请求路径在匹配前经过URL解码、小写（不区分大小写）和清理（折叠双斜杠和目录遍历点）。例如`/foo*`也会匹配`/FOO`、`//foo`和`/%2F/foo`。\n\n使用的正则表达式语言是GO语言中内置的`RE2`。具体请查阅[RE2语法参考](https://github.com/google/re2/wiki/Syntax)和[Go正则语法概述](https://pkg.go.dev/regexp/syntax)。\n\n每个命名匹配器只能有一个`path_regexp`匹配器。\n\n#### 示例：\n\n匹配路径以6个字符的十六进制字符串结尾的请求，后跟`.css`或`.js`作为文件扩展名，捕获组可以分别用`{re.static.1}`和`{re.static.2}`访问包含在`( )`中间的每个部分。\n\n```caddy-d\npath_regexp static \\.([a-f0-9]{6})\\.(css|js)$\n```\n\n---\n### protocol\n\n```caddy-d\nprotocol http|https|grpc\n```\n\n通过请求协议进行匹配。\n\n每个命名匹配器只能有一个`protocol`。\n\n\n---\n### query\n\n```caddy-d\nquery <key>=<val>...\n```\n\n通过查询字符串参数匹配请求。应该是`key=value`的键值对。键完全匹配，区分大小写。值可以包含占位符。值完全匹配，但也支持`*`匹配任何值。\n\n每个命名匹配器可以有多个`query`匹配器，具有相同键的对之间是“或”的关系。\n\n\n#### 示例：\n\n匹配带有`sort`查询参数且值为`asc`的请求。\n\n```caddy-d\nquery sort=asc\n```\n\n---\n### remote_ip\n\n```caddy-d\nremote_ip [forwarded] <ranges...>\n```\n\n通过远程（客户端）IP地址。接受确切的`IP`或`CIDR` 范围。如果第一个参数是`forwarded`，则`X-Forwarded-For`请求标头中的第一个IP（如果存在）将被首选作为参考IP，而不是默认的直接对等点(immediate peer)的IP。\n\n多个`remote_ip`匹配器是“或”的关系。\n\n#### 示例：\n\n匹配来自私有 IPv4 地址的请求。\n\n```caddy-d\nremote_ip 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8\n```"
  },
  {
    "path": "src/docs/markdown/caddyfile/options.md",
    "content": "---\ntitle: 全局选项\n---\n\n<script>\n$(function() {\n\t// We'll add links on the options in the code block at the top\n\t// to their associated anchor tags.\n\tlet headers = $('article h5').map((i, el) => el.id.replace(/-/g, \"_\")).toArray();\n\t$('pre.chroma .k')\n\t\t.filter((k, item) => headers.includes(item.innerText))\n\t\t.map(function(k, item) {\n\t\t\tlet text = item.innerText.replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\t\t\tlet url = '#' + item.innerText.replace(/_/g, \"-\");\n\t\t\t$(item).html('<a href=\"' + url + '\" style=\"color: inherit;\" title=\"' + text + '\">' + text + '</a>');\n\t\t});\n\t$('pre.chroma .k:contains(\"servers\")')\n\t\t.map(function(k, item) {\n\t\t\tlet text = item.innerText.replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\t\t\t$(item).html('<a href=\"#server-options\" style=\"color: inherit;\" title=\"Server Options\">' + text + '</a>');\n\t\t});\n});\n</script>\n\n# 全局选项\n\nCaddyfile可以让你指定全局应用的选项。一些选项充当默认值，而其他选项自定义Caddyfile[适配器](/docs/config-adapters)的行为。\n\nCaddyfile的最顶部可以是**全局选项块**。这是一个没有键的块：\n\n```caddy\n{\n\t...\n}\n```\n\n最多只能有一个，而且必须是Caddyfile的第一个块。\n\n可能的选项是：\n\n```caddy\n{\n\t# General Options\n\tdebug\n\thttp_port  <port>\n\thttps_port <port>\n\torder <dir1> first|last|[before|after <dir2>]\n\tstorage <module_name> {\n\t\t<options...>\n\t}\n\tstorage_clean_interval <duration>\n\tadmin   off|<addr> {\n\t\torigins <origins...>\n\t\tenforce_origin\n\t}\n\tlog [name] {\n\t\toutput  <writer_module> ...\n\t\tformat  <encoder_module> ...\n\t\tlevel   <level>\n\t\tinclude <namespaces...>\n\t\texclude <namespaces...>\n\t}\n\tgrace_period <duration>\n\n\t# TLS Options\n\tauto_https off|disable_redirects|ignore_loaded_certs\n\temail <yours>\n\tdefault_sni <name>\n\tlocal_certs\n\tskip_install_trust\n\tacme_ca <directory_url>\n\tacme_ca_root <pem_file>\n\tacme_eab <key_id> <mac_key>\n\tacme_dns <provider> ...\n\ton_demand_tls {\n\t\task      <endpoint>\n\t\tinterval <duration>\n\t\tburst    <n>\n\t}\n\tkey_type ed25519|p256|p384|rsa2048|rsa4096\n\tcert_issuer <name> ...\n\tocsp_stapling off\n\tpreferred_chains [smallest] {\n\t\troot_common_name <common_names...>\n\t\tany_common_name  <common_names...>\n\t}\n\n\t# Server Options\n\tservers [<listener_address>] {\n\t\tlistener_wrappers {\n\t\t\t<listener_wrappers...>\n\t\t}\n\t\ttimeouts {\n\t\t\tread_body   <duration>\n\t\t\tread_header <duration>\n\t\t\twrite       <duration>\n\t\t\tidle        <duration>\n\t\t}\n\t\tmax_header_size <size>\n\t\tprotocol {\n\t\t\tallow_h2c\n\t\t\texperimental_http3\n\t\t\tstrict_sni_host\n\t\t}\n\t}\n}\n```\n\n\n## 常规选项\n\n##### `debug`\n\n启用调试模式，将默认日至器的日志级别设置为`DEBUG`。这将展示在排除故障时展示更多有用的细节（且在生产模式下将非常详细）。请你在[社区](https://caddy.community)寻求帮助之前先启用此功能。\n\n\n##### `http_port`\n服务器用于HTTP的端口。仅限内部使用; 不会更改客户端的HTTP端口。默认：`80`\n\n\n##### `https_port`\n\n服务器用于HTTPS的端口。仅限内部使用; 不会更改客户端的HTTPS端口。默认：`443`\n\n\n##### `order`\n对HTTP处理指令进行排序。由于HTTP处理器按照顺序链执行，因此处理程序必须以正确的顺序执行。标准指令有[预定义顺序](/docs/caddyfile/directives#directive-order)，但如果使用第三方 HTTP 处理程序模块，则需要通过使用此选项或将指令放在\n\n例如，要使用[`replace-response`插件](https://github.com/caddyserver/replace-response)，而且你希望它在`encode`之后才被执行，以便它可以在响应编码之前进行。（因为响应流向上处理程序链，而不是向下）：\n\n```caddy-d\norder replace after encode\n```\n\n\n##### `storage`\n\n配置Caddy的存储机制。默认值为 [`file_system`](/docs/json/storage/file_system/)。还有许多其他可用的存储模块作为插件提供。\n\n例如，要更改文件系统的存储位置：\n\n```caddy-d\nstorage file_system /path/to/custom/location\n```\n\n在跨多个Caddy实例同步Caddy的存储时，通常需要自定义存储模块，以确保它们都使用相同的证书和密钥。有关更多详细信息，请参阅[有关存储的自动HTTPS]部分(/docs/automatic-https#storage)。\n\n\n##### `storage_clean_interval`\n\n多久扫描一次存储单元以查找旧资源或过期资源并将其移除。这些扫描会在存储模块上进行大量读取（和列表操作），因此对于大型部署需要设置成更长的时间间隔。该值是一个持续时间值。默认值：24小时。\n\n该过程首次启动时，总是会清理存储。然后，如果上次清理在该间隔的一半时间内完成，则在上次清理开始后的这段时间内将开始新的清洁（否则将跳过下次启动）。\n\n##### `admin`\n\n自定义[管理API端点](/docs/api)。如果为`off`，则管理端点将被禁用。如果禁用，则在不停止和启动服务器的情况下将无法更改配置。\n\n- **origins** 配置允许连接到端点的远程/源列表。\n\n- **enforce_origin** 启用Origin标头的强制执行。（这与通常强制执行起源不同，后者总是执行。）\n\n\n##### `log`\n\n自定义命名日志器。可以传递名称以指示要为其自定义行为的特定日志器。如果未指定名称，则修改默认日志器的行为。可以多次指定此选项以配置不同的日志器。你可以在[日志记录文档](/docs/logging)中阅读有关默认日志器和其他日志记录行为的更多信息。\n\n- **output** 配置写入日志的位置。有关更多信息，请参阅[log指令](/docs/caddyfile/directives/log#output-modules)文档，该文档具有相同的结构。\n- **format** 描述如何编码或格式化日志。有关更多信息，请参阅[log指令](/docs/caddyfile/directives/log#format-modules)文档，该文档具有相同的结构。\n- **level** 是要记录的最低入口级别。默认：`INFO`\n- **include** 标识此日志配置中包含的记录器。有关更多信息，请参阅[JSON文档](/docs/json/logging/logs/include/)。\n- **exclude** 标识从该日志配置中排除的记录器。有关更多信息，请参阅[JSON文档](/docs/json/logging/logs/exclude/)。\n\n\n##### `grace_period`\n\n定义在配置重新加载期间关闭HTTP服务器的宽限期。如果客户端没有在宽限期内完成他们的请求，服务器将被强制终止以允许重新加载完成并释放资源。默认情况下，没有设置宽限期。\n\n## TLS选项\n\n##### `auto_https`\n\n配置自动HTTPS。它可以完全禁用(`off`)，仅禁用HTTP到HTTPS的重定向(`disable_redirects`)，或者配置为自动生成证书，即使是出现在手动加载的证书上的名称(`ignore_loaded_certs`)。有关更多详细信息，请参阅[自动HTTPS](/docs/automatic-https) 页面。\n\n\n##### `email`\n\n你的电子邮件地址。主要在使用 CA 创建 ACME 帐户时使用，强烈建议在证书出现问题时使用。\n\n##### `default_sni`\n\n当客户端在其`ClientHello`中不使用SNI时，设置默认TLS服务器名称(TLS ServerName)。\n\n##### `local_certs`\n\n导致所有证书默认在内部颁发，而不是通过（公共）ACME CA，例如Let's Encrypt。这在开发环境中很有用。\n\n##### `skip_install_trust`\n\n跳过将本地CA的根安装到系统信任库以及Java和Mozilla Firefox信任库的尝试。\n\n##### `acme_ca`\n\n指定ACME CA目录的URL。强烈建议将此设置为Let's Encrypt的[暂存端点](https://letsencrypt.org/docs/staging-environment/)以进行测试或开发。默认值：ZeroSSL和Let's Encrypt 的生产端点。\n\n##### `acme_ca_root`\n\n指定一个PEM文件，其中包含ACME CA端点的受信任根证书（如果不在系统信任库中）。\n\n##### `acme_eab`\n\n指定用于所有 ACME 交易的外部帐户绑定(External Account Binding)。\n\n##### `acme_dns`\n\n配置用于所有ACME事务的ACME DNS质询提供者。在该标识后设置的提供者名字指定了提供者，就像在[`tls`指令的`acme`问题](/docs/caddyfile/directives/tls#acme)中指定的一样。\n\n##### `on_demand_tls`\n\n在启用的地方配置[按需TLS](/docs/automatic-https#on-demand-tls)，但不启用它（要启用它，请使用[按需`tls`子指令](/docs/caddyfile/directives/tls#syntax)）。强烈建议只在生产环境中使用，以防止滥用。\n\n- **ask** 将导致Caddy使用包含域名值的查询字符串`?domain=`向给定URL发出HTTP请求。如果端点返回 200 OK，Caddy 将被授权获取该名称的证书。\n- **interval**和**burst** 允许在 `<duration>`间隔进行`<n>`次证书操作。\n\n##### `key_type`\n\n指定为 TLS 证书生成的密钥类型；仅当你有特定需要对其进行自定义时才更改此设置。可能的值为：`ed25519`、`p256`、`p384`、`rsa2048`、`rsa4096`。\n\n##### `cert_issuer`\n\n定义TLS证书的颁发者（或来源）。发行者名称后面的标记设置发行者，就像在[tls指令](/docs/caddyfile/directives/tls#issuer)中指定的一样。如果你希望配置多个发行者来尝试，可以重复。它们将按照定义的顺序进行尝试。\n\n##### `ocsp_stapling`\n\n可以设置`off`为禁用OCSP装订。在由于防火墙而无法访问响应者的环境中很有用。\n\n##### `preferred_chains`\n\n如果你的CA提供多个证书链，你可以使用此选项来指定Caddy应该首选哪个链。设置以下选项之一：\n\n- **smallest** 将告诉Caddy首选字节数最少的链。\n- **root_common_name** 是一个或多个常用名称的列表；Caddy将选择第一个具有与至少一个指定的通用名称匹配的根的链。\n- **any_common_name** 是一个或多个常用名称的列表；Caddy将选择第一个具有与至少一个指定的通用名称匹配的发行者的链。\n\n请注意，如果没有任何[覆盖发行人级别的配置](/docs/caddyfile/directives/tls#acme)，`preferred_chains`将被作为全局选项影响所有发行人。\n\n## 服务器选项\n\n使用可能跨越多个站点的设置自定义[HTTP服务器](/docs/json/apps/http/servers/)，因此无法在站点块中正确配置。这些选项会影响侦听器/套接字或 HTTP 层下的其他行为。\n\n可以多次指定，使用不同的`listener_address`值，为每个服务器配置不同的选项。例如，`servers :443`将仅适用于绑定到侦听器地址的服务器`:443`。省略侦听器地址会将选项应用于任何剩余的服务器。\n\n<aside class=\"tip\">\n\t使用该<a href=\"/docs/command-line#caddy-adapt\"><code>caddy适配器</code></a>命令在 Caddyfile 中查找服务器的侦听地址。\n</aside>\n\n例如，要为port`:80`和`:443`指定不同的服务器配置，你可以指定两个`servers`块：\n\n```caddy\n{\n\tservers :443 {\n\t\tprotocol {\n\t\t\texperimental_http3\n\t\t}\n\t}\n\n\tservers :80 {\n\t\tprotocol {\n\t\t\tallow_h2c\n\t\t}\n\t}\n}\n```\n\n##### `listener_wrappers`\n\n允许配置[监听包装器](/docs/json/apps/http/servers/listener_wrappers/)，它可以修改基本侦听器的行为。它们按给定的顺序应用。\n\n有一个特殊的无操作[`tls`](/docs/json/apps/http/servers/listener_wrappers/tls/)侦听器包装器作为标准模块提供，它标记应在侦听器包装器链中处理TLS的位置。仅当必须在TLS握手之前放置另一个侦听器包装器时才应使用它。\n\n例如，假设你安装了[`proxy_protocol`](/docs/json/apps/http/servers/listener_wrappers/proxy_protocol/)插件：\n\n```caddy-d\nlistener_wrappers {\n\tproxy_protocol {\n\t\ttimeout 2s\n\t\tallow 192.168.86.1/24 192.168.86.1/24\n\t}\n\ttls\n}\n```\n\n\n##### `timeouts`\n\n- **read_body** 是一个[持续时间值](/docs/conventions#durations)，它设置允许从客户端上传读取多长时间。将此设置为一个较短的非零值可以减轻慢速攻击，但也可能影响合法慢速客户端。默认为无超时。\n\n- **read_header** 是一个[持续时间值](/docs/conventions#durations)，它设置允许从客户端的请求标头读取多长时间。默认为无超时。\n\n- **write** 是一个[持续时间值](/docs/conventions#durations)，用于设置允许写入客户端的时间长度。请注意，在提供大文件时将其设置为较小的值可能会对合法缓慢的客户端产生负面影响。默认为无超时。\n- \n- **idle** 是一个[持续时间值](/docs/conventions#durations)，用于设置启用 keep-alives 时等待下一个请求的最长时间。默认为 5 分钟，以帮助避免资源耗尽。\n\n##### `max_header_size`\n\n从客户端的 HTTP 请求标头中解析的最大大小。它接受[go-humanize](https://github.com/dustin/go-humanize/blob/master/bytes.go)支持的所有格式。\n\n##### `protocol`([DEPRECATED since 2022-08](https://github.com/caddyserver/caddy/blob/6d9a83376b5e19b3c0368541ee46044ab284038b/caddyconfig/httpcaddyfile/serveroptions.go#L241))\n\n- **allow_h2c** 启用 H2C（“明文 HTTP/2”或“H2 over TCP”）支持，如果客户端支持，它将通过明文 TCP 连接提供 HTTP/2。由于 Go 标准库没有实现这一点，因此使用 H2C 与该服务器的大多数其他选项不兼容。不要仅仅为了实现最大的客户端兼容性而启用它。在实践中，很少有客户端实现 H2C，甚至更少需要它。此设置仅适用于未加密的 HTTP 侦听器。⚠️实验功能；可能会更改或删除。\n\n- **experimental_http3** 启用实验性草案 HTTP/3 支持。请注意，HTTP/3 不是一个完整的规范，客户端支持非常有限。此选项将在未来消失。此选项不受兼容性承诺的约束。\n\n- **strict_sni_host** 要求请求的`Host`标头与客户端的TLS ClientHello发送的ServerName的值匹配；使用TLS客户端身份验证时，通常是必要的保护措施。\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/patterns.md",
    "content": "---\ntitle: 常见Caddyfile模式\n---\n\n# 常见Caddyfile模式\n\n此页面演示了一些用于常见用例的完整和最小的Caddyfile配置。这些可能是你自己的Caddyfile文档的有用起点。\n\n这些不是即插即用的解决方案；你将不得不自定义你的域名、端口/套接字、目录路径等。它们旨在说明一些最常见的配置模式。\n\n#### Menu\n\n- [静态文件服务器](#静态文件服务器)\n- [反向代理](#反向代理)\n- [PHP](#PHP)\n- [重定向到`www.`子域名](#重定向到`www.`子域名)\n- [尾随斜杠](#尾随斜杠)\n- [通配符](#通配符)\n- [单页面应用(SPAs)](#单页面应用(SPAs))\n\n\n## 静态文件服务器\n\n```caddy\nexample.com\n\nroot * /var/www\nfile_server\n```\n\n像往常一样，第一行是站点地址。该[`root`](/docs/caddyfile/directives/root)指令指定站点根目录的路径（`*`匹配所有请求的方法，以便与[路径匹配器](/docs/caddyfile/matchers#path-matchers)消除歧义）；如果站点不是当前工作目录，则更改站点的路径。最后，我们启用静态文件服务器。\n\n\n## 反向代理\n\n代理所有的请求：\n\n```caddy\nexample.com\n\nreverse_proxy localhost:5000\n```\n\n只代理以`/api/`开头的请求，并为其他所有内容提供静态文件：\n\n```caddy\nexample.com\n\nroot * /var/www\nreverse_proxy /api/* localhost:5000\nfile_server\n```\n\n\n## PHP\n\n### PHP-FPM\n\n在运行PHP FastCGI服务时，类似下面的配置适用于大多数现代PHP应用：\n\n```caddy\nexample.com {\n\troot /srv/public\n\tencode\n\tphp_fastcgi localhost:9000\n\tfile_server\n}\n```\n\n请按实际情况调整站点根目录；此示例假设应用的Web根目录位于`public`目录中。磁盘上存在的文件将由[`file_server`](/docs/caddyfile/directives/file_server)直接返回，其他请求会路由到`index.php`由PHP应用处理。\n\n有时你也可以用unix socket连接PHP-FPM：\n\n```caddy-d\nphp_fastcgi unix//run/php/php8.2-fpm.sock\n```\n\n[`php_fastcgi`指令](/docs/caddyfile/directives/php_fastcgi)本质上是[多段配置](/docs/caddyfile/directives/php_fastcgi#expanded-form)的快捷写法。\n\n### FrankenPHP\n\n你也可以使用[FrankenPHP](https://frankenphp.dev/)，它是一个通过CGO直接调用PHP的Caddy发行版。相比PHP-FPM，性能可高出数倍，启用worker模式时提升通常更明显。\n\n```caddy\n{\n    frankenphp\n    order php_server before file_server\n}\n\nexample.com {\n\troot /srv/public\n    encode zstd br gzip\n    php_server\n}\n```\n\n在运行PHP FastCGI服务的情况下，类似这样的内容适用于大多数现代PHP应用程序：\n\n```caddy\nexample.com\n\nroot * /var/www\nphp_fastcgi /blog/* localhost:9000\nfile_server\n```\n\n请对应地调整站点根目录和路径匹配器；此示例假定PHP仅位于`/blog/`子目录中——所有其他请求将作为静态文件提供。\n\n该[`php_fastcgi`指令](/docs/caddyfile/directives/php_fastcgi)实际上只是[几个配置](/docs/caddyfile/directives/php_fastcgi#expanded-form)的快捷方式。\n\n\n## 重定向到`www.`子域名\n\nT使用HTTP重定向，给域名**添加**`www.`子域。\n\n```caddy\nexample.com {\n\tredir https://www.example.com{uri}\n}\n\nwww.example.com {\n}\n```\n\n要**删除**它：\n\n```caddy\nwww.example.com {\n\tredir https://example.com{uri}\n}\n\nexample.com {\n}\n```\n\n\n## 尾随斜杠\n\n你通常不需要自己进行配置；该[`file_server`指令](/docs/caddyfile/directives/file_server)将通过 HTTP 重定向自动添加或删除请求中的尾部斜杠，具体取决于请求的资源是目录还是文件。\n\n但是，如果需要，你仍然可以在配置中强制使用斜杠。有两种方法可以做到：内部或外部。\n\n### 内部执法\n\n这使用[`rewrite`](/docs/caddyfile/directives/rewrite)指令。Caddy将在内部重写URI以添加或删除尾部斜杠：\n\n```caddy\nexample.com\n\nrewrite /add     /add/\nrewrite /remove/ /remove\n```\n\n使用重写，带有和不带有斜杠的请求将是相同的。\n\n\n### 外部执行\n\n这使用[`redir`](/docs/caddyfile/directives/redir)指令。Caddy 将要求浏览器更改 URI 以添加或删除尾部斜杠：\n\n```caddy\nexample.com\n\nredir /add     /add/\nredir /remove/ /remove\n```\n\n使用重定向，客户端将不得不重新发出请求，为资源强制执行一个可接受的URI。\n\n## 通配符证书\n\n如果你需要使用相同的通配符证书为多个子域提供服务，处理它们的最佳方法是使用如下所示的Caddyfile，利用[`handle`](/docs/caddyfile/directives/handle)指令和[`host`][`host`](/docs/caddyfile/matchers#host)匹配器：\n\n```caddy\n*.example.com {\n\ttls {\n\t\tdns <provider_name> [<params...>]\n\t}\n\n\t@foo host foo.example.com\n\thandle @foo {\n\t\trespond \"Foo!\"\n\t}\n\n\t@bar host bar.example.com\n\thandle @bar {\n\t\trespond \"Bar!\"\n\t}\n\n\t# Fallback for otherwise unhandled domains\n\thandle {\n\t\tabort\n\t}\n}\n```\n\n请注意，你必须启用[ACME DNS 质询](/docs/automatic-https#dns-challenge)才能让 Caddy 自动管理通配符证书。\n\n\n## 单页面应用(SPAs)\n\n当一个网页进行自己的路由时，服务器可能会收到大量的请求，这些请求并不存在于服务器端，但只要提供单一的索引文件，这些请求就可以在客户端呈现。这样架构的Web应用程序被称为SPA，或单页应用程序。\n\n其主要思想是让服务器“尝试文件”来查看请求的文件在服务器端是否存在，如果不存在，则返回到一个索引文件，客户端在其中进行路由(通常使用客户端JavaScript):`try_files {path} /index.html`\n\n最基本的SPA配置通常是这样的：\n\n```caddy\nexample.com\n\nroot * /path/to/site\ntry_files {path} /index.html\nfile_server\n```\n\n如果你的SPA与API或其他仅在服务器端使用的端点相结合，你会想要使用`handle`块来专门处理它们：\n\n```caddy\nexample.com\n\nhandle /api/* {\n\treverse_proxy backend:8000\n}\n\nhandle {\n\troot * /path/to/site\n\ttry_files {path} /index.html\n\tfile_server\n}\n```\n\n## Caddy代理到另一个Caddy\n\n如果你有一个公网可访问的Caddy实例（称为“front”），以及一个位于私网、承载真实应用的Caddy实例（称为“back”），可以用[`reverse_proxy`指令](/docs/caddyfile/directives/reverse_proxy)进行转发。\n\n前置实例：\n\n```caddy\nfoo.example.com, bar.example.com {\n\treverse_proxy 10.0.0.1:80\n}\n```\n\n后置实例：\n\n```caddy\n{\n\tservers {\n\t\ttrusted_proxies static private_ranges\n\t}\n}\n\nhttp://foo.example.com {\n\treverse_proxy foo-app:8080\n}\n\nhttp://bar.example.com {\n\treverse_proxy bar-app:9000\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/response-matchers.md",
    "content": "---\ntitle: 响应匹配器（Caddyfile）\n---\n\n<script>\nready(function() {\n\t// Response matchers\n\t$$_('pre.chroma .nd').forEach(item => {\n\t\tif (item.innerText.includes('@')) {\n\t\t\tlet text = item.innerText.replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\t\t\tlet url = '#' + item.innerText.replace(/_/g, \"-\");\n\t\t\titem.classList.add('nd');\n\t\t\titem.classList.remove('k');\n\t\t\titem.innerHTML = `<a href=\"#syntax\" style=\"color: inherit;\">${text}</a>`;\n\t\t}\n\t});\n\t\n\t$$_('pre.chroma .k').forEach(item => {\n\t\tif (item.innerText.includes('status')) {\n\t\t\titem.innerHTML = '<a href=\"#status\" style=\"color: inherit;\">status</a>';\n\t\t}\n\t});\n\t\n\t$$_('pre.chroma .k').forEach(item => {\n\t\tif (item.innerText.includes('header')) {\n\t\t\titem.innerHTML = '<a href=\"#header\" style=\"color: inherit;\">header</a>';\n\t\t}\n\t});\n\n\t// We'll add links to all the subdirectives if a matching anchor tag is found on the page.\n\taddLinksToSubdirectives();\n});\n</script>\n\n# 响应匹配器\n\n**响应匹配器**可用于按特定条件过滤（或分类）响应。\n\n它通常只会作为某些指令内部的配置出现，用来在向客户端写出响应时进行判断。\n\n- [语法](#syntax)\n- [匹配器](#matchers)\n\t- [status](#status)\n\t- [header](#header)\n\n## 语法\n\n如果某个指令支持响应匹配器，语法文档通常写作`[<response_matcher>]`或`[<inline_response_matcher>]`。\n\n- **<response_matcher>**可以是已声明的命名响应匹配器名称。例如：`@name`。\n- **<inline_response_matcher>**可以直接写响应条件本身，无需提前声明。例如：`status 200`。\n\n### 命名形式\n\n```caddy-d\n@name {\n\tstatus <code...>\n\theader <field> [<value>]\n}\n```\n如果指令只关心响应的一个维度，也可以把名称和条件写在同一行：\n\n```caddy-d\n@name status <code...>\n```\n\n### 内联形式\n\n```caddy-d\n... {\n\tstatus <code...>\n\theader <field> [<value>]\n}\n```\n```caddy-d\n... status <code...>\n```\n```caddy-d\n... header <field> [<value>]\n```\n\n## 匹配器\n\n### status\n\n```caddy-d\nstatus <code...>\n```\n\n按HTTP状态码匹配。\n\n- **&lt;code...&gt;**是HTTP状态码列表。特殊写法如`2xx`和`3xx`分别匹配`200`-`299`与`300`-`399`范围内全部状态码。\n\n#### 示例：\n\n```caddy-d\n@success status 2xx\n```\n\n### header\n\n```caddy-d\nheader <field> [<value>]\n```\n\n按响应头字段匹配。\n\n- `<field>`是要检查的HTTP头字段名。\n\t- 若以`!`开头，表示该字段必须不存在才能匹配（此时省略value参数）。\n- `<value>`是字段必须匹配的值。\n\t- 若以`*`开头，表示快速后缀匹配（出现在末尾）。\n\t- 若以`*`结尾，表示快速前缀匹配（出现在开头）。\n\t- 若前后都包裹`*`，表示快速子串匹配（出现在任意位置）。\n\t- 否则为快速精确匹配。\n\n同一个集合中，不同字段之间是AND关系；同一字段的多个值是OR关系。\n\n注意：同一个响应头字段可能重复出现且值不同。后端应用必须把响应头字段值视为数组而非单值；遇到这种情况时，Caddy不会解释其语义。\n\n#### 示例：\n\n匹配响应头`Foo`中包含`bar`的响应：\n\n```caddy-d\n@upgrade header Foo *bar*\n```\n\n匹配响应头`Foo`值为`bar`或`baz`的响应：\n\n```caddy-d\n@foo {\n\theader Foo bar\n\theader Foo baz\n}\n```\n\n匹配完全不包含`Foo`响应头字段的响应：\n\n```caddy-d\n@not_foo header !Foo\n```\n\n"
  },
  {
    "path": "src/docs/markdown/caddyfile/spec.md",
    "content": "---\ntitle: Caddyfile Spec\n---\n\nTODO: this page is unfinished\n\n<style>\n\t\n</style>\n\n# Caddyfile Specification\n\nThis page describes the syntax of the Caddyfile. If it is your first time writing a Caddyfile, try the <a href=\"/v1/tutorial/caddyfile\">Caddyfile primer</a> tutorial instead. This page is not beginner-friendly; it is technical and kind of boring.\n\nAlthough this article is verbose, the Caddyfile is designed to be easily readable and writable by humans. You will find that it is easy to remember, not cumbersome, and flows off the fingers.\n\nThe term \"Caddyfile\" often refers to a file, but more generally means a blob of Caddy configuration text. A Caddyfile can be used to configure any Caddy server type: HTTP, DNS, etc. The basic structure and syntax of the Caddyfile is the same for all server types, but semantics change. Because of this variability, this document treats the Caddyfile only as the generic configuration syntax as it applies to all server types. Caddyfile documentation for specific types may be found within their respective docs. For instance, the HTTP server <a href=\"/v1/docs/http-caddyfile\">documents the semantics of its Caddyfile</a>.\n\n#### Topics\n\n1. [File format & encoding](#)\n2. [Lexical syntax](#)\n3. [Structure](#)\n4. [Labels](#)\n5. [Directives](#)\n6. [Environment variables](#)\n7. [Import](#)\n8. [Reusable snippets](#)\n9. [Examples](#)\n\n\n## File Format &amp; Encoding\n\nThe Caddyfile is plain Unicode text encoded with UTF-8. Each code point is distinct; specifically, lowercase and uppercase characters are different. A leading byte order mark (0xFEFF), if present, will be ignored.\n\n\n\n## Lexical Syntax\n\nA <b>token</b> is a sequence of whitespace-delimited characters in the Caddyfile. A token that starts with quotes <code>\"</code> is read literally (including whitespace) until the next instance of quotes <code>\"</code> that is not escaped. Quote literals may be escaped with a backslash like so: <code>\\\"</code>. Only quotes are escapable. <!-- Those stupid --> &ldquo;Smart quotes&rdquo; are not valid as quotes.\n\n<b>Lines</b> are delimited with the <code>\\n</code> (newline) character only. Carriage return <code>\\r</code> is discarded unless quoted. Blank, unquoted lines are allowed and ignored.\n\n<b>Comments</b> are discarded by the lexer. Comments begin with an unquoted hash <code>#</code> and continue to the end of the line. Comments may start a line or appear in the middle of a line as part of an unquoted token. For the purposes of this document, commented and blank lines are no longer considered.\n\nTokens are then evaluated by the parser for structure.\n\n## Structure\n\nA Caddyfile has no global scope or inheritence between separate blocks. The most global unit of the Caddyfile is an <b>entry</b>. An entry consists of a list of labels and a definition associated with those labels. A <b>label</b> is a string identifier, and a <b>definition</b> is a body (one or more lines) of tokens grouped together in a <i>block</i>:\n\n<code class=\"block\"><span class=\"cf-label-bg\">list of labels</span>\n<span class=\"cf-block-bg\">definition (block)</span></code>\n\nA Caddyfile with <i>only one</i> entry may consist simply of the label line(s) followed by the definition on the next line(s), as shown above. However, a Caddyfile with <i>more than one</i> entry <b>must</b> enclose each definition in curly braces <code>{ }</code>. The opening curly brace <code>{</code> must be at the end of the label line, and the closing curly brace <code>}</code> must be the only token on its line:\n\n<code class=\"block\"><span class=\"cf-label-bg\">list of labels</span> <span class=\"cf-bigbrace\">{</span>\n<span class=\"cf-block-bg indent\">definition (block)</span>\n<span class=\"cf-bigbrace\">}</span>\n<span class=\"cf-label-bg\">list of labels</span> <span class=\"cf-bigbrace\">{</span>\n<span class=\"cf-block-bg indent\">definition (block)</span>\n<span class=\"cf-bigbrace\">}</span></code>\n<p>\nConsistent tab indentation is encouraged within blocks enclosed by curly braces.\n</p>\n<p>\n<b>The first line of a Caddyfile is always a label line.</b> Comment lines, empty lines, and <a href=\"/v1/docs/import\">import</a> lines are the exceptions.\n</p>\n\n<h3 id=\"labels\">Labels</h3>\n<p>\nLabels are the only tokens that appear outside of blocks (with one exception being the <a href=\"/v1/docs/import\">import</a> directive). A label line may have just one label:\n</p>\n<code class=\"block\"><span class=\"cf-addr\">label</span></code>\n<p>\nor several labels, separated by spaces:\n</p>\n<code class=\"block\"><span class=\"cf-addr\">label1 label2</span> ...</code>\n<p>\nIf many labels are to head a block, the labels may be suffixed with a comma. A comma-suffixed label may be followed by a newline, in which case the next line will be considered part of the same line:\n</p>\n<code class=\"block\"><span class=\"cf-addr\">label1</span>,\n<span class=\"cf-addr\">label2</span></code>\n<p>\nMixing of these patterns is valid (but discouraged), as long as the last label of the line has a comma if the next line is to continue the list of labels:\n</p>\n<code class=\"block\"><span class=\"cf-addr\">label1 label2</span>,\n<span class=\"cf-addr\">label3</span>, <span class=\"cf-addr\">label4</span>,\n<span class=\"cf-addr\">label5</span></code>\n<p>\nA definition with multiple labels is replicated across each label as if they had been defined separately but with the same definition.\n</p>\n\n<h3 id=\"directives\">Directives</h3>\n<p>\nThe body of the definition follows label lines. The first token of each line in a definition body is a <b>directive</b>. Every token <i>after</i> the directive on the same line is an <b>argument</b>. Arguments are optional:\n</p>\n<code class=\"block\"><span class=\"cf-dir\">directive1</span>\n<span class=\"cf-dir\">directive2</span> <span class=\"cf-arg\">arg1 arg2</span>\n<span class=\"cf-dir\">directive3</span> <span class=\"cf-arg\">arg3</span></code>\n<p>\nCommas are not acceptable delimiters for arguments; they will be treated as part of the argument value. Arguments are delimited solely by same-line whitespace.\n</p>\n<p>\nDirectives may span multiple lines by opening a block. Blocks are enclosed by curly braces <code>{ }</code>. The opening curly brace <code>{</code> must be at the end of the directive's first line, and the closing curly brace <code>}</code> must be the only token on its line:\n</p>\n<code class=\"block\"><span class=\"cf-dir\">directive</span> {\n...\n}</code>\n<p>\nWithin a directive block, the first token of each line may be considered a <b>subdirective</b> or <b>property</b>, depending on how it is used (other terms may be applied). And as before, they can have arguments:\n</p>\n<code class=\"block\"><span class=\"cf-dir\">directive</span> <span class=\"cf-arg\">arg1</span> {\n<span class=\"cf-subdir\">subdir</span> arg2 arg3\n...\n}</code>\n<p>\nSubdirectives cannot open new blocks. In other words, nested directive blocks are not supported. If a directive block is empty, the curly braces should be omitted entirely.\n</p>\n\n<h3 id=\"env\">Environment Variables</h3>\n<p>\nAny token (label, directive, argument, etc.) may contain or consist solely of an environment variable, which takes the Unix form or Windows form, enclosed in curly braces <code>{ }</code> without extra whitespace:\n</p>\n<code class=\"block\"><span class=\"cf-addr\">label_{$ENV_VAR_1}</span>\n<span class=\"cf-dir\">directive</span> <span class=\"cf-arg\">{%ENV_VAR_2%}</span></code>\n<p>\nEither form works on any OS. A single environment variable does not expand out into multiple tokens, arguments, or values.\n</p>\n\n<h3 id=\"import\">Import</h3>\n<p>\nThe <a href=\"/v1/docs/import\">import</a> directive is a special case, because it can appear outside a definition block. The consequence of this is that no label can take on the value of \"import\".\n</p>\n<p>\nWhere an import line is, that line will be replaced with the contents of the imported file, unmodified. See the <a href=\"/v1/docs/import\">import docs</a> for more information.\n</p>\n\n<h3 id=\"snippets\">Reusable Snippets</h3>\n<p>\nYou can define snippets to be reused later in your Caddyfile by defining a block with a single-token label surrounded by parentheses:\n</p>\n<code class=\"block\"><span class=\"cf-addr\">(mysnippet)</span> {\n...\n}</code>\n<p>\nThen you can invoke the snippet with the <code>import</code> directive:\n</p>\n<p>\n<code class=\"block\"><span class=\"cf-dir\">import</span> <span class=\"cf-arg\">mysnippet</span></code>\n\n<h3 id=\"examples\">Examples</h3>\n<p>\nA very simple Caddyfile with only one entry:\n<code class=\"block\"><span class=\"cf-addr\">label1</span>\n\n<span class=\"cf-dir\">directive1</span> <span class=\"cf-arg\">argument1</span>\n<span class=\"cf-dir\">directive2</span></code>\n</p>\n\n<p>\nAppending the prior example with another entry introduces the need for curly braces:\n<code class=\"block\"><span class=\"cf-addr\">label1</span> {\n<span class=\"cf-dir\">directive1</span> <span class=\"cf-arg\">arg1</span>\n<span class=\"cf-dir\">directive2</span>\n}\n<span class=\"cf-addr\">label2</span>, <span class=\"cf-addr\">label3</span> {\n<span class=\"cf-dir\">directive3</span> <span class=\"cf-arg\">arg2</span>\n<span class=\"cf-dir\">directive4</span> <span class=\"cf-arg\">arg3</span> <span class=\"cf-arg\">arg4</span>\n}\n</code>\n</p>\n\n<p>\nSome people prefer to always use braces even if there's just one entry; this is fine, but unnecessary:\n<code class=\"block\"><span class=\"cf-addr\">label1</span> {\n<span class=\"cf-dir\">directive1</span> <span class=\"cf-arg\">arg1</span>\n<span class=\"cf-dir\">directive2</span>\n}</code>\n</p>\n\n<p>\nExample in which a directive opens a block:\n<code class=\"block\"><span class=\"cf-addr\">label1</span>\n\n<span class=\"cf-dir\">directive</span> <span class=\"cf-arg\">arg1</span> {\n<span class=\"cf-subdir\">subdir</span> arg2 arg3\n}\n<span class=\"cf-dir\">directive</span> <span class=\"cf-arg\">arg4</span></code>\n</p>\n\n<p>\nSimilarly, but in an indented definition body, and with a comment:\n<code class=\"block\"><span class=\"cf-addr\">label1</span> {\n<span class=\"cf-dir\">directive1</span> <span class=\"cf-arg\">arg1</span>\n<span class=\"cf-dir\">directive2</span> <span class=\"cf-arg\">arg2</span> {\n<span class=\"cf-subdir\">subdir1</span> arg3 arg4\n<span class=\"cf-subdir\">subdir2</span>\n<span class=\"cf-comment\"># nested blocks not supported</span>\n}\n<span class=\"cf-dir\">directive3</span>\n}</code>\n</p>\n"
  },
  {
    "path": "src/docs/markdown/caddyfile-tutorial.md",
    "content": "---\ntitle: Caddyfile教程\n---\n\n# Caddyfile教程\n\n本教程将教你[HTTP Caddyfile](/docs/caddyfile)的基础知识，以便你可以快速轻松地生成美观、功能强大的站点配置。\n\n**目标：**\n- 🔲 第一个站点\n- 🔲 静态文件服务器\n- 🔲 模板\n- 🔲 压缩\n- 🔲 多个站点\n- 🔲 匹配器\n- 🔲 环境变量\n- 🔲 注释\n\n**先决条件：**\n- 基本的终端/命令行技能\n- 基本的文本编辑器技能\n- PATH变量包含`caddy`\n\n---\n\n新建一个名为`Caddyfile`（无扩展名）的文本文件。\n\n首先应该输入的是你的网站[地址](/docs/caddyfile/concepts#addresses)。\n\n```caddy\nlocalhost\n```\n\n<aside class=\"tip\">\n    如果HTTP和HTTPS端口（分别为80和443）是你操作系统上的特权端口，你将需要以提升的特权运行或使用更高的端口。要使用更高的端口，只需将其更改为类似<code>localhost:2015</code>的地址，并使用Caddyfile的<a href=\"/docs/caddyfile/options\">http_port</a>选项更改HTTP端口。\t\n</aside>\n\n然后按回车键并输入你想要它执行的操作。对于本教程，使你的Caddyfile如下所示：\n\n```caddy\nlocalhost\n\nrespond \"Hello, world!\"\n```\n\n保存并运行Caddy（因为这是一个培训教程，我们将使用该`--watch`标志，以便自动应用对Caddyfile的更改）：\n\n<pre><code class=\"cmd bash\">caddy run --watch</code></pre>\n\n<aside class=\"tip\">\n    如果你遇到权限错误，请尝试在你的地址中使用更高的端口（如<code>localhost:2015</code>）并<a href=\"/docs/caddyfile/options\">更改HTTP端口</a>，或者提升权限后再次运行。\n</aside>\n\n第一次，系统会要求你输入密码。这样Caddy就可以通过HTTPS为你的网站提供服务。\n\n<aside class=\"tip\">\n只要主机或IP是站点地址的一部分，Caddy默认通过HTTPS为所有站点提供服务。<a href=\"/docs/automatic-https\">自动HTTPS</a>可以通过显式地为地址添加前缀<code>http://</code>予以禁用。\n</aside>\n\n<aside class=\"complete\">第一个站点</aside>\n\n在你的浏览器中使用HTTPS浏览[https://localhost](https://localhost)，检查你的网站服务器是否正常运行。\n\n<aside class=\"tip\">\n    如果你第一次遇到证书错误，你可能需要重新启动浏览器。\n</aside>\n\n这并不是特别令人兴奋，所以让我们将静态响应更改为启用目录列表的[文件服务器](/docs/caddyfile/directives/file_server) ：\n\n```caddy\nlocalhost\n\nfile_server browse\n```\n\n保存你的 Caddyfile，然后刷新你的浏览器页面。如果当前目录中有索引文件，你应该会看到文件列表或HTML页面。\n\n<aside class=\"complete\">静态文件服务器</aside>\n\n## 添加功能\n\n利用文件服务器还可以做一些更有意思事情：基于模板页面展示。创建一个新文件并粘贴如下内容：\n\n```html\n<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>Caddy tutorial</title>\n\t</head>\n\t<body>\n\t\tPage loaded at: {{`{{`}}now | date \"Mon Jan 2 15:04:05 MST 2006\"{{`}}`}}\n\t</body>\n</html>\n```\n\n将其在当前目录保存为`caddy.html`，然后在浏览器中访问：[https://localhost/caddy.html](https://localhost/caddy.html)\n\n输出内容如下：\n\n```\nPage loaded at: {{`{{`}}now | date \"Mon Jan 2 15:04:05 MST 2006\"{{`}}`}}\n```\n\n等一下，我们不是应该能看到今天的日期吗？为什么它不起作用呢？这是因为：服务器尚未配置模板！小事一桩，只需在Caddyfile中添加一行即可：\n\n```caddy\nlocalhost\n\ntemplates\nfile_server browse\n```\n\n保存它，然后刷新浏览器页面，你将会看到：\n\n```\nPage loaded at: {{now | date \"Mon Jan 2 15:04:05 MST 2006\"}}\n```\n\n使用Caddy的[模板模块](/docs/modules/http.handlers.templates)，你可以对静态文件做很多有用的事情，例如包含其他HTML文件、制作子请求、设置响应头、处理数据结构等等！\n\n<aside class=\"complete\">模板</aside>\n\n使用快速且现代的压缩算法压缩响应是一种很好的做法。使用 [`encode`](/docs/caddyfile/directives/encode)可以启用Gzip和Zstandard支持：\n\n```caddy\nlocalhost\n\nencode zstd gzip\ntemplates\nfile_server browse\n```\n\n<aside class=\"tip\">浏览器还不支持Zstandard编码。希望很快能支持了！</aside>\n\n<aside class=\"complete\">压缩</aside>\n\n这是启动和运行半高级、可应用于生产的站点的基本过程！\n\n当你准备好开启[自动HTTPS](/docs/automatic-https)时，只需将你的网站地址（`localhost`在我们的教程中）替换为你的域名即可。有关更多信息，请参阅我们的[HTTPS快速入门指南](/docs/quick-starts/https)。\n\n## 多个站点\n\n使用我们当前的Caddyfile，我们只能有一个站点定义！只有第一行可以是站点的地址，然后文件的所有其余部分都必须是该站点的指令。\n\n但是制作起来很容易，所以我们可以添加更多网站！\n\n到目前为止，我们的Caddyfile内容如下：\n\n```caddy\nlocalhost\n\nencode zstd gzip\ntemplates\nfile_server browse\n```\n\n相当于这个：\n\n```caddy\nlocalhost {\n\tencode zstd gzip\n\ttemplates\n\tfile_server browse\n}\n```\n\n这样就能添加两个甚至更多的站点了。\n\n通过将我们的站点块包裹在花括号`{ }`中，我们可以在同一个Caddyfile中定义多个不同的站点。\n\n例如：\n\n```caddy\n:8080 {\n\trespond \"I am 8080\"\n}\n\n:8081 {\n\trespond \"I am 8081\"\n}\n```\n\n当用花括号包裹站点块时，只有[地址](/docs/caddyfile/concepts#addresses)出现在花括号外，[指令](/docs/caddyfile/directives)都出现在花括号内。\n\n对于共享相同配置的多个站点，你可以添加更多地址，例如：\n\n```caddy\n:8080, :8081 {\n\t...\n}\n```\n\n然后，你可以根据需要定义任意数量的不同站点，只要每个地址都是唯一的。\n\n<aside class=\"complete\">多个站点</aside>\n\n\n## 匹配器\n\n我们可能只想将某些指令应用于某些请求。例如，假设我们想要同时拥有一个文件服务器和一个反向代理，但我们显然不能在每个请求上都这样做！文件服务器将写入静态文件，或者反向代理将请求代理到后端。\n\n这个配置不会像我们想要的那样工作：\n\n```caddy\nlocalhost\n\nfile_server\nreverse_proxy 127.0.0.1:9005\n```\n\n在实践中，我们可能只想对 API 请求使用反向代理，即基本路径为`/api/`。通过添加[匹配器标记](/docs/caddyfile/matchers#syntax)很容易做到这一点：\n\n```caddy\nlocalhost\n\nfile_server\nreverse_proxy /api/* 127.0.0.1:9005\n```\n\n这里，现在反向代理只会处理所有以`/api/`开始的请求。\n\n我们刚刚添加的`/api/*`标记就被称为**匹配器标记**，你可以说它是一个匹配器标记，因为它以正斜杠开头，并且出现在指令之后（但你始终可以在[指令文档](/docs/caddyfile/directives)中查找它以确定）。\n\n匹配器真的很强大。你可以命名匹配器并使用它们`@name`来匹配不仅仅是请求路径！在继续之前花点时间了解更多关于[匹配器](/docs/caddyfile/matchers)的信息！\n\n<aside class=\"complete\">匹配器</aside>\n\n## 环境变量\n\nCaddyfile适配器允许在解析Caddyfile之前替换[环境变量](/docs/caddyfile/concepts#environment-variables)。\n\n首先，设置一个环境变量（在运行Caddy的同一shell中）：\n\n<pre><code class=\"cmd bash\">export SITE_ADDRESS=localhost:9055</code></pre>\n\n然后你可以在Caddyfile中这样使用它：\n\n```caddy\n{$SITE_ADDRESS}\n\nfile_server\n```\n\n在解析Caddyfile之前，它将被扩展为：\n\n```caddy\nlocalhost:9055\n\nfile_server\n```\n\n你可以在Caddyfile中的任何位置使用环境变量，使用数量也没有限制。\n\n<aside class=\"complete\">环境变量</aside>\n\n\n## 注释\n\n最后一件你会发现最有帮助的事情：如果你想在你的Caddyfile中添加注释或注释任何东西，你可以将`#`放在行首：\n\n```caddy\n# this starts a comment\n```\n\n<aside class=\"complete\">注释</aside>\n\n## 进一步阅读\n\n- [常见模式](/docs/caddyfile/patterns)\n- [Caddyfile概念](/docs/caddyfile/concepts)\n- [指令](/docs/caddyfile/directives)"
  },
  {
    "path": "src/docs/markdown/caddyfile.md",
    "content": "---\ntitle: Caddyfile\n---\n\n# Caddyfile\n\n**Caddyfile**是一种方便用户使用的Caddy配置格式。这是大多数人最喜欢使用Caddy的方式，因为它易于编写、易于理解，且能满足绝大部分的使用场景。\n\n它看起来像这样\n\n```caddy\nexample.com\n\nroot * /var/www/wordpress\nphp_fastcgi unix//run/php/php-version-fpm.sock\nfile_server\n```\n\n（这是一个真正的、生产就绪的Caddyfile，它通过完全托管的HTTPS为WordPress提供服务。）\n\n基本思路是，先填写网站的地址，然后填写网站需要具备的特性或功能。查看更多[常见模式](/docs/caddyfile/patterns)。\n\n## 菜单\n\n- #### [快速入门指南](/docs/quick-starts/caddyfile)\n- #### [完整的Caddyfile教程](/docs/caddyfile-tutorial)\n- #### [Caddyfile概念](/docs/caddyfile/concepts)\n- #### [指令](/docs/caddyfile/directives)\n- #### [请求匹配器](/docs/caddyfile/matchers)\n- #### [全局选项](/docs/caddyfile/options)\n- #### [常见模式](/docs/caddyfile/patterns)\n<!-- - #### [Caddyfile规范](/docs/caddyfile/spec) TODO: Finish this -->\n\n\n## 备注\n\nCaddyfile只是Caddy的[配置适配器](/docs/config-adapters)。在手工制作配置时通常首选它，但它不如Caddy的[原生JSON结构](/docs/json/)那样富有表现力、灵活或可编程。如果你正在尝试将你的的Caddy配置/部署实现自动化，你可能希望将JSON结合Caddy的[API](/docs/api)一起使用。（实际上，也可以将Caddyfile与API一起使用，只是在有限的范围内。）\n"
  },
  {
    "path": "src/docs/markdown/command-line.md",
    "content": "# 命令行\n\nCaddy有一个标准的类unix命令行接口，基本用法如下：\n\n```bash\ncaddy <command> [<args...>]\n```\n\n- `<>`代表要被你输入替换参数。\n- `[]`代表可选的参数。\n- `...`表示延续，即一个或多个参数。\n\n__快速开始：`caddy help`__\n\n- [caddy adapt](#caddy-adapt) 将配置文档适配为原生JSON\n- [caddy build-info](#caddy-build-info) 打印构建信息\n- [caddy environ](#caddy-environ) 打印环境\n- [caddy file-server](#caddy-file-server) 一个简单但可用于生产的文件服务器\n- [caddy fmt](#caddy-fmt) 格式化一个 Caddyfile\n- [caddy hash-password](#caddy-hash-password) 散列密码并输出 base64\n- [caddy help](#caddy-help) 查看 caddy 命令的帮助\n- [caddy list-modules](#caddy-list-modules) 列出已安装的 Caddy 模块\n- [caddy reload](#caddy-reload) 更改正在运行的 Caddy 进程的配置\n- [caddy reverse-proxy](#caddy-reverse-proxy) 一个简单但可用于生产的 HTTP(S) 反向代理\n- [caddy run](#caddy-run) 在前台启动 Caddy 进程\n- [caddy start](#caddy-start) 在后台启动 Caddy 进程\n- [caddy stop](#caddy-stop) 停止正在运行的 Caddy 进程\n- [caddy trust](#caddy-trust) 将证书安装到本地信任存储中\n- [caddy untrust](#caddy-untrust) 不信任来自本地信任存储的证书\n- [caddy upgrade](#caddy-upgrade) 将 Caddy 升级到最新版本\n- [caddy add-package](#caddy-add-package) 将 Caddy 升级到最新版本，添加了额外的插件\n- [caddy remove-package](#caddy-remove-package) 将 Caddy 升级到最新版本，删除了一些插件\n- [caddy validate](#caddy-validate) 测试配置文件是否有效\n- [caddy version](#caddy-version) 打印版本\n\n## 子命令\n\n### caddy adapt\n\n```bash\ncaddy adapt\n  [--config <path>]\n  [--adapter <name>]\n  [--pretty]\n  [--validate]\n```\n将配置适配成Caddy的原生JSON配置结构，并通过stdout输出，如果有任何stderr的警告，则直接退出。\n\n`--config`是配置文件的路径。如果省略，则假定当前目录存在`Caddyfile`文件；否则，必须指定该选项。\n\n`--adapter`指定要使用的配置适配器；默认为`caddyfile`。\n\n`--pretty`将使用缩进格式化输出以提高可读性。\n\n`--validate`将加载并提供适配了的配置以检查有效性（但它实际上不会开始运行配置）。\n\n请注意，成功适配了的配置仍可能无法通过验证。例如，使用这个Caddyfile：\n\n```caddy\nlocalhost\n\ntls cert_notexist.pem key_notexist.pem\n```\n尝试适配它：\n```bash\ncaddy adapt --config Caddyfile\n```\n它成功了且没有错误，然后运行下面的命令：\n\n```bash\ncaddy adapt --config Caddyfile --validate\nadapt: validation: loading app modules: module name 'tls': provision tls: loading certificates: open cert_notexist.pem: no such file or directory\n```\n\n即使Caddyfile可以毫无错误地适配JSON，但实际的证书和/或密钥文件不存在，因此验证失败，因为该错误出现在配置阶段。因此，验证(`--validate`)是比适配更强的错误检查。\n\n#### 例子\n要使Caddyfile适配JSON，可以轻松地手动读取和调整：\n\n```bash\ncaddy adapt--config /path/to/Caddyfile --pretty\n```\n\n### caddy build-info\n\n```bash\ncaddy build-info\n```\n\n打印`Go`提供的关于构建的信息（主模块路径、包版本、模块替换）。\n\n### caddy environ\n```bash\ncaddy environ\n```\n\n打印 caddy 看到的环境，然后退出。在调试 init 系统或进程管理器单元（如 systemd）时很有用。\n\n### caddy file-server\n\n```bash\ncaddy file-server\n  [--root <path>]\n  [--listen <addr>]\n  [--domain <example.com>]\n  [--browse]\n  [--templates]\n  [--access-log]\n```\n启动一个简单但可用于生产的静态文件服务器。\n\n`--root`指定根文件路径。默认为当前工作目录。\n\n`--listen`接受一个监听地址。默认为:80，除非--domain使用，否则:443将是默认值。\n\n`--domain`将仅通过该主机名提供文件，并且 Caddy 将尝试通过 HTTPS 提供文件，因此如果它是公共域名，请确保首先正确配置任何公共 DNS。默认端口将更改为 443。\n\n`--browse`如果请求没有索引文件的目录，将启用目录列表。\n\n`--templates`将启用模板渲染。\n\n`--access`-log启用请求/访问日志。\n\n此命令禁用管理 API，从而更容易在本地开发机器上运行多个实例。\n\n### caddy fmt\n\n```bash\ncaddy fmt [--overwrite] [<path>]\n```\n格式化或美化 Caddyfile，然后退出。除非使用，否则结果将打印到标准输出--overwrite。\n\n<path>指定 Caddyfile 的路径。如果-，则从标准输入读取输入。如果省略，则假定为当前目录中名为 Caddyfile 的文件。\n\n`--overwrite`导致结果写入输入文件而不是打印到终端。如果输入不是常规文件，则此标志无效。\n\n### caddy hash-password\n\n```bash\ncaddy hash-password\n  [--plaintext <password>]\n  [--algorithm <name>]\n  [--salt <string>]\n```\n散列密码并以 base64 编码将输出写入标准输出，然后退出。\n\n`--plaintext`是密码的明文形式。如果省略，将采用交互模式，并提示用户手动输入密码。\n\n`--algorithm`可能是 bcrypt 或 scrypt。默认为 bcrypt。\n\n`--salt`仅在算法需要外部盐（如 scrypt）时使用。\n\n### caddy help\n\n```bash\ncaddy help[<command>]\n```\n打印 CLI 帮助文本，可选择用于特定子命令，然后退出。\n\n### caddy list-modules\n\n```bash\ncaddy list-modules\n  [--packages]\n  [--versions]\n  [--skip-standard]\n```\n打印已安装的 Caddy 模块，可选择包含来自其关联 Go 模块的包和/或版本信息，然后退出。\n\n在某些脚本化的情况下，打印所有标准模块可能是多余的，因此你可以使用--skip-standard从输出中省略那些。\n\n注意：由于Go 中的一个错误，版本信息仅在 Caddy 构建为依赖项而不是主模块时可用。使用xcaddy使这更容易。\n\n### caddy reload\n\n```bash\ncaddy reload\n  [--config <path>]\n  [--adapter <name>]\n  [--address <interface>]\n  [--force]\n```\n为正在运行的 Caddy 实例提供新配置。这与将文档发布到/load 端点具有相同的效果，但是此命令对于围绕配置文件的简单工作流很方便。与stop、start和run命令相比，这个单一命令是更改/重新加载运行配置的正确语义方式。\n\n由于此命令使用 API，因此不得禁用管理端点。\n\n`--config`是要应用的配置文件。如果-，则从标准输入读取配置。如果未指定，它将尝试Caddyfile在当前工作目录中调用的文件，如果存在，它将使用caddyfile配置适配器对其进行调整；否则，如果没有要加载的配置文件，则会出错。\n\n`--adapter`指定要使用的配置适配器（如果有）。\n\n`--address`如果管理端点没有监听默认地址并且它与提供的配置文件中的地址不同，则需要使用。请注意，此时仅支持 TCP 地址。\n\n`--force`即使指定的配置与 Caddy 已经在运行的配置相同，也会导致重新加载。强制 Caddy 重新配置其模块可能很有用，这可能会产生副作用，例如：重新加载手动加载的 TLS 证书。\n\n### caddy reverse-proxy\n\n```bash\ncaddy reverse-proxy\n  [--from <addr>]\n`--to` <addr>\n  [--change-host-header]\n```\n启动一个简单但可用于生产的 HTTP(S) 反向代理。\n\n`--from`是代理的地址。\n\n`--to`是要代理的地址。\n\n`--change`-host-header将导致 Caddy 将 Host 标头从传入值更改为上游的地址。\n\n`--from`和参数都--to可以是 URL，因为方案和域名将从提供的 URL 中推断出来（路径和查询字符串被忽略）。或者它们可以是简单的网络地址而不是完整的 URL。\n\n此命令禁用管理 API，因此更容易在本地开发机器上运行多个实例。\n\n### caddy run\n\n```bash\ncaddy run\n  [--config <path>]\n  [--adapter <name>]\n  [--pidfile <file>]\n  [--environ]\n  [--envfile <file>]\n  [--resume]\n  [--watch]\n```\n\n使用“守护进程”模式无限期地运行Caddy。\n\n`--config`指定要立即加载和使用的初始配置文件。如果指定为`-`，则从标准输入读取配置。如果未指定配置，Caddy将以空白配置运行，并使用管理API端点的默认设置，可用于为其提供新配置。作为一种特殊情况，如果当前工作目录有一个名为“Caddyfile”的文件并且配置了`caddyfile`适配器（默认），那么即使没有任何命令行标志，该文件也将被加载并用于配置Caddy。\n\n`--adapter`是加载初始配置时使用的配置适配器的名称（如果有）。如果`--config`指定的文件名以“Caddyfile”开头，则已经假定使用`caddyfile`作为适配器，则不需要此标志。否则，如果提供的配置文件不是Caddy的原生JSON格式，则需要此标志。任何警告都将打印到日志中，但请注意，任何没有错误的适配都会立即被使用，即使有警告也是如此。如果要先查看适配结果，请使用[`caddy adapt](#caddy-adapt)子命令。\n\n`--pidfile`将PID写入指定文件。\n\n`--environ`在开始之前打印出环境。这与[`caddy environ`](#caddy-environ)命令相同，但打印后不退出。\n\n`--envfile`从指定文件加载环境变量。\n\n`--resume`使用自动保存的最后加载的配置，覆盖`--config`标志（如果存在）。使用此标志可通过机器重新启动或进程重新启动来保证配置的持久性。它在以[API](api)为中心的部署中最有用。\n\n`--watch`将监视配置文件并在更改后自动重新加载它。\n⚠️此功能仅供本地开发环境使用！\n\n> 在生产中运行时不要停止服务器来更改配置！这将导致停机。（这应该很明显，但你会惊讶于我们收到了多少关于它的投诉。）请改用[`caddy reload`](#caddy-reload)命令。\n\n### caddy start\n\n```bash\ncaddy start\n  [--config <path>]\n  [--adapter <name>]\n  [--envfile <file>]\n  [--pidfile <file>]\n  [--watch]\n```\n与`caddy run`相同，但该命令在后台运行。此命令仅在后台进程运行成功（或运行失败）之前阻塞，然后返回。\n\n注意：该标志`--config`不支持通过`-`选项从标准输入读取配置。\n\n不鼓励在系统服务或Windows上使用此命令。在Windows上，子进程将保持连接到终端，因此关闭窗口将强制停止Caddy，这并不明显。考虑改为将Caddy[作为服务](/docs/running)运行。\n\n启动后，你可以使用[`caddy stop`](#caddy-stop)或者[`/ stop`](api#post-stop)API端点退出后台进程。\n\n### caddy stop\n\n```bash\ncaddy stop[--address <interface>]\n```\n\n> 停止（和重新启动）服务器与配置更改正交。__不要使用stop命令更改生产中的配置，除非你想要停机__。请改用[caddy reload](#caddy-reload)命令。\n\n优雅地停止正在运行的Caddy进程（而不是直接停止进程）并使其退出。它使用管理API的[`POST /stop`](api#post-stop)端点来执行平滑关闭。\n\n`--address` 如果正在运行的实例的管理API不在默认端口上，则可以使用；也可以在此处指定备用地址。\n\n如果要停止当前配置但不想退出进程，请使用`caddy reload`空白配置或[`DELETE /config/`](api#delete-configpath)端点。\n\n### caddy trust\n\n```bash\ncaddy trust\n```\n将Caddy的默认内部CA（名为“local”）的根证书安装到本地信任库中；仅用于开发环境。如果没有足够的权限，可能会提示输入密码。\n\n这个命令通常是不必要的。因为Caddy将在第一次需要时自动将其根证书安装到本地信任存储中，所以此命令仅在你需要在具有提升的权限时预安装证书时有用，例如在自动化环境中的系统配置期间。\n\n### caddy untrust\n\n```bash\ncaddy untrust\n  [--ca <id>]\n  [--cert <path>]\n```\n不信任来自本地信任存储的根证书。仅用于开发环境。可以分别指定`--ca`或`--cert`标志，但不能同时指定两者。如果两者均未指定，则为Caddy的默认CA(`local`)。\n\n`--ca`指定不信任的Caddy CA的ID。默认的CA的ID是`local`。\n\n`--cert`指定要卸载的PEM编码证书文件的路径。\n\n### caddy upgrade\n\n```bash\ncaddy upgrade\n  [--keep-backup]\n```\n将当前的Caddy二进制文件替换为[我们下载页面](https://caddyserver.com/download)中安装了相同模块的最新版本，包括在Caddy网站上注册的所有第三方插件。\n\n升级不会中断正在运行的服务器；目前，该命令仅替换磁盘上的二进制文件。如果我们能找到更好的方法，这种升级模式可能会被改变。\n\n这种升级过程是容错的；当前二进制文件首先会被备份（在当前二进制文件复制出来一份到同目录）并在出现任何问题时自动恢复。如果你希望在升级过程完成后保留备份，你可以使用`--keep-backup`选项。\n\n该命令执行时如果你的用户无权写入可执行文件，则此需要提升权限。\n\n### caddy add-package\n\n```bash\ncaddy add-package <packages...>\n  [--keep-backup]\n```\n与`caddy upgrade`类似，将当前Caddy二进制文件替换为安装了相同模块的最新版本，另外会安装通过参数指定的包。从我们的[下载页面](https://caddyserver.com/download)找到你可以安装的软件包列表。每个参数都应该是完整的包名。\n\n例如：\n```bash\ncaddy add-package github.com/caddy-dns/cloudflare\n```\n\n### caddy remove-package\n\n```bash\ncaddy remove-package <packages...>\n  [--keep-backup]\n```\n与`caddy upgrade`命令类似，将当前Caddy二进制文件替换为安装了相同模块的最新版本，且会将通过参数列出的包删除掉。运行`caddy list-modules --packages`可以查看当前二进制文件中包含的非标准模块的包名列表。\n\n### caddy validate\n\n```bash\ncaddy validate\n  [--config <path>]\n  [--adapter <name>]\n```\n验证配置文件，然后退出。此命令将反序列化配置，然后加载和配置其所有模块，就好像启动配置一样（但实际上并未启动配置）。这会把加载或配置阶段出现的配置错误暴露出来，是比仅将配置序列化为JSON更强大的错误检查方式。\n\n`--config`是要验证的配置文件。如果指定该选项为`-`，则从标准输入stdin读取配置。默认使用当前目录下的`Caddyfile`，前提是这个文件存在。\n\n如果配置文件不是Caddy的原生JSON格式，`--adapter`是要使用的配置适配器的名称。如果配置文件以`Caddyfile`开头，则默认使用`caddyfile`这个适配器。\n\n### caddy version\n\n```bash\ncaddy version\n```\n打印版本并退出。"
  },
  {
    "path": "src/docs/markdown/config-adapters.md",
    "content": "---\ntitle: 配置适配器\n---\n\n# 配置适配器\n\nCaddy 的原生配置语言是[JSON](https://www.json.org/json-en.html)，但手动编写 JSON 可能很乏味且容易出错。这就是Caddy支持通过**配置适配器**配置其他语言的原因。这些适配器是可以将你喜欢的格式输出成[Caddy JSON](/docs/json/)格式的Caddy插件。\n\n例如，配置适配器可以[将NGINX配置转化成Caddy JSON](https://github.com/caddyserver/nginx-adapter)。\n\n## 已知的配置适配器\n\n以下配置适配器当前可用（一些是第三方项目）：\n\n- [**caddyfile**](/docs/caddyfile) (标准)\n- [**nginx**](https://github.com/caddyserver/nginx-adapter)\n- [**jsonc**](https://github.com/caddyserver/jsonc-adapter)\n- [**json5**](https://github.com/caddyserver/json5-adapter)\n- [**yaml**](https://github.com/abiosoft/caddy-yaml)\n- [**cue**](https://github.com/caddyserver/cue-adapter)\n- [**toml**](https://github.com/awoodbeck/caddy-toml-adapter)\n- [**hcl**](https://github.com/francislavoie/caddy-hcl)\n\n（此列表是已知适配器的临时位置，直到我们的新网站完成。）\n\n## 使用配置适配器\n\n你可以通过在命令行上使用大多数接受配置的子命令上的`--adapter` 标志来指定它来使用配置适配器：\n\n<pre><code class=\"cmd bash\">caddy run --config caddy.yaml --adapter yaml</code></pre>\n\n或者通过[`/load`端点](/docs/api#post-load)的APi：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/load \\\n\t-X POST \\\n\t-H \"Content-Type: application/yaml\" \\\n\t--data-binary @caddy.yaml</code></pre>\n\n如果你只想获取输出JSON而不运行它，可以使用以下[`caddy adapt`](/docs/command-line#caddy-adapt)命令：\n\n<pre><code class=\"cmd bash\">caddy adapt --config caddy.yaml --adapter yaml</code></pre>\n\n## 注意事项\n\n并非所有配置语言都与 Caddy 100% 兼容；某些功能或行为根本无法很好地转换或尚未编程到适配器或 Caddy 本身。\n\n一些适配器会进行1-1转换，例如YAML->JSON或TOML->JSON。其他是专门为Caddy设计的，例如Caddyfile。通常，这些适配器将始终有效。\n\n然而，并不是所有的适配器都能一直工作。配置适配器尽最大努力将你的输入转换为具有最高保真度和正确性的Caddy JSON。因为不能保证这个转换过程始终是完整和正确的，所以我们不称他们为“转换器”或“翻译者”。它们是“适配器”，因为它们至少会给你一个很好的起点来完成你的最终JSON配置。\n\n配置适配器可以输出生成的JSON、警告和错误。如果没有发生错误，则结果为JSON。当输入有问题（例如，语法错误）时会发生错误。当适应出现问题但不一定是致命的（例如，不支持的功能）时，会发出警告。如果使用带有警告的配置，建议小心。"
  },
  {
    "path": "src/docs/markdown/conventions.md",
    "content": "---\ntitle: 约定\n---\n\n# 约定\n\nCaddy生态系统遵循一些约定，以使整个平台的事情保持一致和直观。\n\n\n## 网络地址\n\n指定要拨号或绑定的网络地址时，Caddy接受以下格式的字符串：\n\n```\nnetwork/address\n```\n\n网络部分是可选的，是[Go的`net`包](https://golang.org/pkg/net/)可以识别的任何内容。默认网络是`tcp`。如果指定了网络，则必须用单个正斜杠`/`分隔网络和地址部分。\n\n地址部分可以是以下任何一种形式：\n\n- `host`\n- `host:port`\n- `:port`\n- `/path/to/unix/socket`\n\n主机可以是任何主机名、可解析的域名或IP地址。\n\n端口可以是单个值 ( `:8080`)或包含范围(`:8080-8085`))。端口范围将乘以单个地址。并非所有配置字段都接受端口范围。特殊端口`:0`是指任何可用的端口。\n\n仅当使用 unix* 网络类型时，unix 套接字路径才可接受。分隔网络和地址的正斜杠不被视为路径的一部分。\n\n有效示例：\n\n```\n:8080\n127.0.0.1:8080\nlocalhost:8080\nlocalhost:8080-8085\ntcp/localhost:8080\ntcp/localhost:8080-8085\nudp/localhost:9005\nunix//path/to/socket\n```\n\n<aside class=\"tip\">\n\tCaddy网络地址不是URL。URL将<a href=\"https://en.wikipedia.org/wiki/OSI_model#Layer_architecture\">OSI模型</a>的较低层和较高层耦合在一起，但Caddy经常使用独立于特定应用程序的网络地址，因此将它们组合起来会有问题。在Caddy中，网络地址就是指L3-L5可以拨号或绑定的资源，但是URL极大地结合了L3-L7。网络地址要求主机+端口和路径互斥，但URL不需要。网络地址有时支持端口范围，但URL不支持。\n</aside>\n\n\n## 占位符\n\nCaddy 的配置支持使用 _占位符_ （变量）。使用占位符是一种将动态值注入静态配置的简单方法。\n\n<aside class=\"tip\">\n\t占位符与其他软件中的变量类似。例如，<a href=\"https://nginx.org/en/docs/varindex.html\">nginx有</a>像`$uri`和`$document_root`这样的变量。\n</aside>\n\n占位符的两边都用花括号括起来`{ }`，里面包含变量名，例如：`{foo.bar}`。占位符大括号可以转义，如`\\{like so\\}`。变量名通常用点命名，以避免模块之间的冲突。\n\n哪些占位符可用取决于上下文。并非所有占位符在配置的所有部分都可用。例如，[HTTP应用程序设置的占位符](/docs/json/apps/http/)仅在与处理 HTTP 请求相关的配置区域中可用。\n\n以下占位符始终可用：\n\n占位符 | 描述\n------------|-------------\n`{env.*}` | 环境变量（例如`{env.HOME}`）\n`{system.hostname}` | 系统的本地主机名\n`{system.slash}` | 系统的文件路径分隔符\n`{system.os}` | 系统的操作系统\n`{system.arch}` | 系统架构\n`{time.now}` | Go时间结构的当前时间\n`{time.now.unix}` | 当前时间，以秒为单位的unix时间戳\n`{time.now.unix_ms}` | 当前时间，以毫秒为单位的unix时间戳\n`{time.now.common_log}` | 通用日志格式的当前时间\n`{time.now.year}` | YYYY格式的当前年份\n\n并非所有配置字段都支持占位符，但大多数都支持你期望的位置。\n\n\n## 文件位置\n\n本节包含有关在何处查找各种文件的信息。此处描述的文件和目录路径充其量是默认值；有些可以被覆盖。\n\n### 你的配置文件\n\n没有一个单一的、传统的地方可以放置你的配置文件。将它们放在对你最有意义的地方。\n\n<aside class=\"tip\">\n\t唯一的例外可能是当前工作目录中名为“Caddyfile”的文件，如果未指定其他配置文件，则为方便起见，caddy 命令会尝试该文件。\n</aside>\n\n带有默认配置文件的发行版应该记录这个配置文件的位置，即使它对包/发行版维护者来说是显而易见的。\n\n### 数据目录\n\nCaddy 将 TLS 证书和其他重要资产存储在数据目录中，该目录由[配置的存储模块](/docs/json/storage/)支持（默认：本地文件系统）。\n\n如果`XDG_DATA_HOME`设置了环境变量，则为`$XDG_DATA_HOME/caddy`。\n\n否则，它的路径因平台而异，遵守操作系统约定：\n\n操作系统 | 数据目录路径\n---|---------------------\n**Linux, BSD** | `$HOME/.local/share/caddy`\n**Windows** | `%AppData%\\Caddy`\n**macOS** | `$HOME/Library/Application Support/Caddy`\n**Plan 9** | `$HOME/lib/caddy`\n**Android** | `$HOME/caddy`（或`/sdcard/caddy`）\n\n所有其他操作系统都使用 Linux/BSD 目录路径。\n\n**数据目录不能被视为缓存。** 它的内容**不是**短暂的，也不是仅仅为了表演。Caddy 将 TLS 证书、私钥、OCSP 订书钉和其他必要信息存储到数据目录中。在不了解其含义的情况下，不应将其清除。\n\n至关重要的是，这个目录是持久的并且可由Caddy写入。\n\n\n### 配置目录\n\n这是 Caddy 可以将某些配置存储到磁盘的地方。最值得注意的是，它将最后一个活动配置（默认情况下）保存到此文件夹，以便以后使用[`caddy run --resume`](/docs/command-line#caddy-run)。\n\n<aside class=\"tip\">\n\t配置目录不是你需要存储<a href=\"#your-config-files\">配置文件</a>的地方。（不过，你可以这样做。）\n</aside>\n\n如果`XDG_CONFIG_HOME`设置了环境变量，则为`$XDG_CONFIG_HOME/caddy`。\n\n否则，它的路径因平台而异，遵守操作系统约定：\n\n\n操作系统 | 配置目录路径\n---|---------------------\n**Linux, BSD** | `$HOME/.config/caddy`\n**Windows** | `%AppData%\\Caddy`\n**macOS** | `$HOME/Library/Application Support/Caddy`\n**Plan 9** | `$HOME/lib/caddy`\n\n所有其他操作系统都使用 Linux/BSD 目录路径。\n\n至关重要的是，这个目录是持久的并且可由Caddy写入。\n\n\n## 持续时间\n\n持续时间字符串通常在Caddy的配置中使用。它们采用与[Go的`time.ParseDuration`语法](https://golang.org/pkg/time/#ParseDuration)，除此之外，你还能`d`表示一天（为简单起见，我们假定1天=24小时)。有效单位是：\n\n- `ns` (纳秒)\n- `us`/`µs` (微秒)\n- `ms` (毫秒)\n- `s` (秒)\n- `m` (分)\n- `h` (小时)\n- `d` (天)\n\n例子：\n\n- `250ms`\n- `5s`\n- `1.5h`\n- `2h45m`\n- `90d`\n\n在[JSON配置](/docs/json/)中，持续时间值也可以是表示纳秒的整数。\n"
  },
  {
    "path": "src/docs/markdown/examples.md",
    "content": "示例\n========\n\n这个页面后续可能会建设成一个由社区共同维护的示例集合。当前请先参考我们社区论坛Wiki中的示例：\n\n- [论坛Wiki示例](https://caddy.community/c/wiki/13)\n- [按关键字搜索Wiki分类](https://caddy.community/search?q=%23wiki%20)\n\n"
  },
  {
    "path": "src/docs/markdown/extending-caddy/caddyfile.md",
    "content": "---\ntitle: Caddyfile支持\n---\n\n# Caddyfile支持\n\nCaddy模块在[注册](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#RegisterModule)时，凭借其命名空间自动添加到[本地JSON配置](/docs/json/)中，使其既可使用又有记录。这使得对Caddyfile的支持纯粹是可选的，但它经常被那些喜欢Caddyfile的用户要求。\n\n## Unmarshaler\n\n要为你的模块添加Caddyfile支持，只需实现[`caddyfile.Unmarshaler`]（https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/caddyfile?tab=doc#Unmarshaler）接口。你可以通过解析标记的方式来选择你的模块的Caddyfile语法。\n\nunmarshaler的工作是简单地设置你的模块类型，例如，通过填充它的字段，使用传递给它的[`caddyfile.Dispenser`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/caddyfile?tab=doc#Dispenser)。例如，一个名为`Gizmo`的模块类型可能有这样的方法。\n\n```go\n// UnmarshalCaddyfile实现了caddyfile.Unmarshaler。语法：\n//\n//     gizmo <name> [<option>]\n//\nfunc (g *Gizmo) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {\n\tfor d.Next() {\n\t\tif !d.Args(&g.Name) {\n\t\t\t// 没有足够的参数\n\t\t\treturn d.ArgErr()\n\t\t}\n\t\tif d.NextArg() {\n\t\t\t// 可选的参数\n\t\t\tg.Option = d.Val()\n\t\t}\n\t\tif d.NextArg() {\n\t\t\t// 太多参数\n\t\t\treturn d.ArgErr()\n\t\t}\n\t}\n\treturn nil\n}\n```\n\n在方法的godoc注释中记录语法是一个好主意。参见[`caddyfile`包的godoc](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/caddyfile?tab=doc)，了解更多关于解析Caddyfile的信息。\n\n对于unmarshaler来说，接受其指令的多次出现也很重要（很少见，但在某些情况下可能发生）。由于第一个标记通常是模块的名称或指令（并且通常可以被unmarshaler跳过），这通常意味着将你的解析逻辑包裹在一个`for d.Next() { ... }`循环。\n\n请确保检查是否有遗漏或多余的参数。\n\n你还应该添加一个[接口防护](/docs/extending-caddy#interface-guards)，以确保接口得到正确满足。\n\n```go\nvar _ caddyfile.Unmarshaler = (*Gizmo)(nil)\n```\n\n### 块\n\n为了接受超过一行所能容纳的配置，你可能希望允许一个带有子指令的块。这可以用`d.NextBlock()`来完成, 并进行迭代, 直到返回到原来的嵌套级别。\n\n```go\nfor nesting := d.Nesting(); d.NextBlock(nesting); {\n\tswitch d.Val() {\n\t\tcase \"sub_directive_1\":\n\t\t\t// ...\n\t\tcase \"sub_directive_2\":\n\t\t\t// ...\n\t}\n}\n```\n\n\n只要循环的每一次迭代都会消耗整个片段（行或块），那么这就是一种处理块的优雅方式。\n\n## HTTP指令\n\nHTTP Caddyfile是Caddy的默认Caddyfile适配器语法（或 \"服务器类型\"）。它是可扩展的，意味着你可以为你的模块[注册](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile?tab=doc#RegisterDirective)自己的 \"顶级 \"指令：\n\n```go\nfunc init() {\n\thttpcaddyfile.RegisterDirective(\"gizmo\", parseCaddyfile)\n}\n```\n\n如果你的指令只返回一个HTTP处理程序（这很常见），你可能会发现[`RegisterHandlerDirective`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile?tab=doc#RegisterHandlerDirective)更容易：\n\n```go\nfunc init() {\n\thttpcaddyfile.RegisterHandlerDirective(\"gizmo\", parseCaddyfileHandler)\n}\n```\n\n基本的想法是，你与指令相关联的[解析函数](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile?tab=doc#UnmarshalFunc)会返回一个或多个[`ConfigValue`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile?tab=doc#ConfigValue)值。(或者，如果使用 `RegisterHandlerDirective`，它只是直接返回填充的 `caddyhttp.MiddlewareHandler` 值) 。每个配置值都与一个[\"类\"](#classes)相关联，这有助于HTTP Caddyfile适配器知道它可以用于最终JSON配置的哪一部分。所有的配置值都会被转储到一个堆中，适配器在构建最终的JSON配置时，会从中抽取。\n\n这种设计允许你的指令为任何公认的类返回任何配置值，这意味着它可以影响HTTP Caddyfile适配器有指定类的配置的任何部分。\n\n如果你已经实现了`UnmarshalCaddyfile()`方法，那么你的解析函数可以像这样简单：\n\n```go\n// parseCaddyfileHandler将h中的令牌解读为一个新的中间件处理值。\nfunc parseCaddyfileHandler(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {\n\tvar g Gizmo\n\terr := g.UnmarshalCaddyfile(h.Dispenser)\n\treturn g, err\n}\n```\n\n关于如何使用`httpcaddyfile.Helper`类型的更多信息，请参阅[`httpcaddyfile`包godoc](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile?tab=doc)。\n\n### 处理顺序\n\n所有返回HTTP中间件/处理程序值的指令都需要以正确的顺序进行评估。例如，设置网站根目录的处理程序必须在访问根目录的处理程序之前，这样它就能知道目录路径是什么。\n\nHTTP Caddyfile [对标准指令有一个硬编码的顺序](/docs/caddyfile/directives#directive-order)。这确保了用户不需要知道他们的网络服务器最常见的功能的实现细节，并使他们更容易写出正确的配置。鉴于Caddyfile的可扩展性，一个单一的、硬编码的列表也可以防止非确定性。\n\n**当你注册一个新的处理指令时，它必须在使用前被添加到该列表中（在`路由`块之外）。** 这在配置中使用两种方法之一。\n\n- [`order`全局选项](/docs/caddyfile/options)只为该配置修改标准顺序。例如：`order mydir before respond`将插入一个新的指令`mydir`，在`respond`处理程序之前被评估。然后该指令就可以正常使用了。\n- 或者，在[`路由`块](/docs/caddyfile/directives/route)中使用该指令。因为路由块中的指令不会被重新排序，在路由块中使用的指令不需要出现在列表中。\n\n请为你的用户记录你的指令在列表中的正确位置，以便他们能够正确地使用它。\n\n\n### 类\n\n这个表格描述了HTTP Caddyfile适配器所识别的每一个带有导出类型的类。\n\n类的名称 | 预期的类型 | 说明\n---------- | ------------- | -----------\nbind | `[]string` | 服务器监听器绑定地址\ntls.connection_policy | `*caddytls.ConnectionPolicy` | TLS连接策略\nroute | `caddyhttp.Route` | HTTP处理程序路线\nerror_route | `*caddyhttp.Subroute` | HTTP错误处理路由\ntls.cert_issuer | `certmagic.Issuer` | TLS证书发放者\ntls.cert_loader | `caddytls.CertificateLoader` | TLS证书加载器\n\n\n## 服务类型\n\n从结构上看，Caddyfile是一种简单的格式，因此可以有不同类型的Caddyfile格式（有时称为\"服务类型\"）来满足不同的需要。\n\n默认的Caddyfile格式是HTTP Caddyfile，你可能对它很熟悉。这种格式主要配置[`http`应用程序](/docs/modules/http)，同时只可能在Caddy配置结构的其他部分洒下一些配置（例如，`tls`应用程序加载和自动化证书）。\n\n要配置HTTP以外的应用程序，你可能想实现你自己的配置适配器，使用[你自己的服务器类型](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/caddyfile?tab=doc#Adapter)。Caddyfile适配器实际上将为你解析输入，并给你服务器块和选项的列表，由你的适配器来理解该结构并将其转化为JSON配置。"
  },
  {
    "path": "src/docs/markdown/extending-caddy/config-adapters.md",
    "content": "---\ntitle: 编写配置适配器\n---\n\n# 编写配置适配器\n\n由于各种原因，你可能希望使用一种不是[JSON](/docs/json/)的格式来配置Caddy。Caddy通过[配置适配器](/docs/config-adapters)对此有一流的支持。\n\n如果你喜欢的语言/语法/格式还不存在，你可以写一个!\n\n## 模板\n\n这里有一个模板，你可以开始使用：\n\n```go\npackage myadapter\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/caddyserver/caddy/v2/caddyconfig\"\n)\n\nfunc init() {\n\tcaddyconfig.RegisterAdapter(\"adapter_name\", MyAdapter{})\n}\n\n// MyAdapter adapts ____ to Caddy JSON.\ntype MyAdapter struct{\n}\n\n// Adapt adapts the body to Caddy JSON.\nfunc (a MyAdapter) Adapt(body []byte, options map[string]interface{}) ([]byte, []caddyconfig.Warning, error) {\n\t// TODO: parse body and convert it to JSON\n\treturn nil, nil, fmt.Errorf(\"not implemented\")\n}\n```\n\n- 参见 godoc for [`RegisterAdapter()`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig?tab=doc#RegisterAdapter)\n- 参见godoc for ['Adapter'](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig?tab=doc#Adapter) 接口\n\n返回的JSON不应该***缩进；它应该始终是紧凑的。如果调用者愿意，他们可以随时对其进行美化。\n\n请注意，虽然配置适配器是Caddy的_插件，但它们不是Caddy的_模块，因为它们没有集成到配置的某个部分（但为了方便，它们会显示在`list-modules`中）。因此，它们没有`Provision()`或`Validate()`方法，也不遵循模块生命周期的其他部分。它们只需要实现`Adapter'接口并注册为适配器。\n\n当填充配置中`json.RawMessage`类型的字段（即模块字段）时，使用`JSON()`和`JSONModuleObject()`函数。\n\n- [`caddyconfig.JSON()`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig?tab=doc#JSON)是用来处理没有嵌入模块名称的模块值。(通常用于ModuleMap字段，其中模块名称是地图的关键。)\n- [`caddyconfig.JSONModuleObject()`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig?tab=doc#JSONModuleObject)用于处理模块值，并在对象中加入模块名称。(其他地方几乎都在使用。)\n\n\n## Caddyfile服务器类型\n\n也可以实现一个自定义的Caddyfile格式。Caddyfile适配器是一个单一的适配器实现，其默认的 \"服务器类型\"是HTTP，但它在注册时支持替代的 \"服务器类型\"。例如，HTTP Caddyfile是这样注册的。\n\n```go\nfunc init() {\n\tcaddyconfig.RegisterAdapter(\"caddyfile\",  caddyfile.Adapter{ServerType: ServerType{}})\n}\n```\n\n你将实现[`caddyfile.ServerType`接口](https://pkg.go.dev/github.com/caddyserver/caddy/v2/caddyconfig/caddyfile?tab=doc#ServerType)并相应地注册你自己的适配器。\n"
  },
  {
    "path": "src/docs/markdown/extending-caddy/namespaces.md",
    "content": "---\ntitle: \"模块命名空间\"\n---\n\n# 模块命名空间\n\nCaddy的客户端模块以`interface{}`或`any`类型进行通用加载。为了使主机模块能够使用它们，加载的客户端模块通常首先进行已知类型的类型断言。本页面描述了所有标准模块的模块命名空间到Go类型的映射。\n\n非标准模块命名空间的文档可以在定义它们的主机模块的文档中找到。\n\n<aside class=\"tip\">\n\t读取此表的一种方式是，\"如果您的模块位于&lt;namespace&gt;，则它应该编译为&lt;type&gt;。\"\n</aside>\n\n命名空间 | 预期类型                                                                                                                                      | 描述 | 备注\n--------- |-------------------------------------------------------------------------------------------------------------------------------------------| ----------- | ----------\n|         | [`caddy.App`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#App)                                                             | Caddy应用程序\ncaddy.config_loaders | [`caddy.ConfigLoader`](https://pkg.go.dev/github.com/caddyserver/caddy/v2#ConfigLoader)                                                   | 加载配置 | <i>⚠️ 实验性</i>\ncaddy.fs  | [`fs.FS`](https://pkg.go.dev/io/fs#FS)                                                                                                    | 虚拟文件系统 |  <i>⚠️ 实验性</i>\ncaddy.listeners | [`caddy.ListenerWrapper`](https://pkg.go.dev/github.com/caddyserver/caddy/v2#ListenerWrapper)                                             | 包装网络监听器\ncaddy.logging.encoders | [`zapcore.Encoder`](https://pkg.go.dev/go.uber.org/zap/zapcore#Encoder)                                                                   | 日志条目编码器\ncaddy.logging.encoders.filter | [`logging.LogFieldFilter`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/logging?tab=doc#LogFieldFilter)                     | 日志字段过滤器\ncaddy.logging.writers | [`caddy.WriterOpener`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#WriterOpener)                                           | 日志编写器\ncaddy.storage | [`caddy.StorageConverter`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#StorageConverter)                                   | 存储后端\ndns.providers | [`certmagic.ACMEDNSProvider`](https://pkg.go.dev/github.com/caddyserver/certmagic#ACMEDNSProvider)                                        | DNS挑战求解器\nevents.handlers | [`caddyevents.Handler`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyevents#Handler)                                   | 事件处理程序 | <i>⚠️ 实验性</i>\nhttp.authentication.hashes | [`caddyauth.Comparer`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth?tab=doc#Comparer)                   | 密码哈希比较器\nhttp.authentication.providers | [`caddyauth.Authenticator`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth?tab=doc#Authenticator)         | HTTP身份验证提供程序\nhttp.encoders | [`encode.Encoder`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp/encode#Encoder)                                   | 通常是压缩\nhttp.handlers | [`caddyhttp.MiddlewareHandler`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp#MiddlewareHandler)                   | HTTP处理程序\nhttp.ip_sources | [`caddyhttp.IPRangeSource`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp#IPRangeSource)                           | 可信代理的IP范围\nhttp.matchers | [`caddyhttp.RequestMatcher`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp?tab=doc#RequestMatcher)                 | HTTP请求匹配器\nhttp.precompressed | [`encode.Precompressed`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp/encode#Precompressed)                       | 支持的预压缩映射\nhttp.reverse_proxy.circuit_breakers | [`reverseproxy.CircuitBreaker`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy?tab=doc#CircuitBreaker) | 反向代理断路器\nhttp.reverse_proxy.selection_policies | [`reverseproxy.Selector`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy?tab=doc#Selector)             | 负载均衡选择策略\nhttp.reverse_proxy.transport | [`http.RoundTripper`](https://pkg.go.dev/net/http?tab=doc#RoundTripper)                                                                   | HTTP反向代理传输\nhttp.reverse_proxy.upstreams | [`reverseproxy.UpstreamSource`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy?tab=doc#UpstreamSource) | 动态上游源 | <i>⚠️ 实验性</i>\ntls.certificates | [`caddytls.CertificateLoader`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddytls?tab=doc#CertificateLoader)             | TLS证书源\ntls.client_auth | [`caddytls.ClientCertificateVerifier`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddytls#ClientCertificateVerifier)     | 验证客户端证书\ntls.handshake_match | [`caddytls.ConnectionMatcher`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddytls?tab=doc#ConnectionMatcher)             | TLS连接匹配器\ntls.issuance | [`certmagic.Issuer`](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Issuer)                                                  | TLS证书签发\ntls.get_certificate | [`certmagic.Manager`](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Manager)                                                | TLS证书管理器 | <i>⚠️ 实验性</i>\ntls.stek | [`caddytls.STEKProvider`](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddytls?tab=doc#STEKProvider)                       | TLS会话票据密钥源\n\n标记为\"实验性\"的命名空间可能会发生更改。（请和他们一起开发，以便我们可以最终确定他们的接口！）\n"
  },
  {
    "path": "src/docs/markdown/extending-caddy/placeholders.md",
    "content": "---\ntitle: \"占位符支持\"\n---\n\n# 占位符\n\n在Caddy中，占位符由各个插件按需处理；它们不会在所有地方自动生效。\n\n这意味着如果你希望自己的插件支持占位符，就必须显式实现这项支持。\n\n如果你还不熟悉占位符，请先阅读[这里](/docs/conventions#placeholders)！\n\n## 占位符概览\n\n[占位符](/docs/conventions#placeholders)是形如`{foo.bar}`的字符串，用作动态配置值，并在运行时求值。\n\nCaddyfile中的[环境变量替换](/docs/caddyfile/concepts#environment-variables)以美元符号开头，例如`{$FOO}`，它在Caddyfile解析时就会被替换，不需要插件处理。它们虽然也使用`{}`语法，但**不是**占位符。\n\n因此，必须理解`{env.HOST}`（一个[全局占位符](/docs/conventions#placeholders)）与`{$HOST}`（Caddyfile环境变量替换）是本质不同的。\n\n例如如下Caddyfile：\n```caddy\n:8080 {\n\trespond {$HOST} 200\n}\n\n:8081 {\n\trespond {env.HOST} 200\n}\n```\n\n当你执行`HOST=example caddy adapt`把Caddyfile转成JSON时，会得到：\n\n```json\n{\n  \"apps\": {\n    \"http\": {\n      \"servers\": {\n        \"srv0\": {\n          \"listen\": [\":8080\"],\n          \"routes\": [\n            {\n              \"handle\": [\n                {\n                  \"body\": \"example\",\n                  \"handler\": \"static_response\",\n                  \"status_code\": 200\n                }\n              ]\n            }\n          ]\n        },\n        \"srv1\": {\n          \"listen\": [\":8081\"],\n          \"routes\": [\n            {\n              \"handle\": [\n                {\n                  \"body\": \"{env.HOST}\",\n                  \"handler\": \"static_response\",\n                  \"status_code\": 200\n                }\n              ]\n            }\n          ]\n        }\n      }\n    }\n  }\n}\n```\n\n重点看`srv0`和`srv1`里的`\"body\"`字段：\n\n- `srv0`使用的是`{$HOST}`（Caddyfile环境变量替换），因此在生成JSON时已经变成`example`。\n- `srv1`使用的是`{env.HOST}`（全局占位符），因此在adapt到JSON时会保持原样。\n\n这也意味着：直接写JSON配置（不使用Caddyfile）的用户不能使用`{$ENV}`语法。因此，插件作者应在配置`Provision`阶段实现占位符替换支持，下面会说明。\n\n## 实现占位符支持\n\n你不应在[`UnmarshalCaddyfile()`](/docs/extending-caddy/caddyfile)中处理占位符。占位符应在更晚阶段替换：要么在[`Provision()`](/docs/extending-caddy#provisioning)阶段，要么在模块执行阶段（例如HTTP处理器的`ServeHTTP()`、匹配器的`Match()`等），并使用`caddy.Replacer`。\n\n### 示例\n\n下面示例使用新建的replacer来处理占位符。它能访问[全局占位符](/docs/conventions#placeholders)（如`{env.HOST}`），但不能访问HTTP占位符（如`{http.request.uri}`），因为provision发生在配置加载阶段，而不是请求阶段。\n\n```go\nfunc (g *Gizmo) Provision(ctx caddy.Context) error {\n\trepl := caddy.NewReplacer()\n\tg.Name = repl.ReplaceAll(g.Name,\"\")\n\treturn nil\n}\n```\n\n下面示例在`ServeHTTP`期间从请求上下文`r.Context()`获取replacer。这个replacer可同时访问全局占位符和每个请求的HTTP占位符（如`{http.request.uri}`）。\n\n```go\nfunc (g *Gizmo) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {\n\trepl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)\n\t_, err := w.Write([]byte(repl.ReplaceAll(g.Name,\"\")))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn next.ServeHTTP(w, r)\n}\n```\n\n"
  },
  {
    "path": "src/docs/markdown/extending-caddy.md",
    "content": "---\ntitle: 扩展Caddy\n---\n\n# 扩展Caddy\n\nCaddy很容易扩展，因为它是模块化结构。大多数类型的Caddy扩展（或插件）被称为_modules_，如果它们扩展或插入Caddy的配置结构。为了清楚起见，Caddy的模块与[Go模块](https://github.com/golang/go/wiki/Modules)不同（但它们也是Go模块）。\n\n**先决条件：**\n- 对[Caddy的架构](/docs/architecture)的基本了解\n- 熟练掌握Go语言\n- [`go`](https://golang.org/doc/install)\n- [`xcaddy`](https://github.com/caddyserver/xcaddy)\n\n\n## 快速入门\n\nCaddy模块是任意一种命名类型，其包被导入时将自己注册为Caddy模块。最重要的是，一个模块总是实现[caddy.Module](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Module)接口，提供它的名字和构造函数。\n\n在一个新的Go模块中，将以下模板粘贴到Go文件中，并定制你的包名、类型名和Caddy模块ID。\n\n```go\npackage mymodule\n\nimport \"github.com/caddyserver/caddy/v2\"\n\nfunc init() {\n\tcaddy.RegisterModule(Gizmo{})\n}\n\n// Gizmo只是一个例子；可以是你自己的类型\ntype Gizmo struct {\n}\n\n// 通过CaddyModule方法返回Caddy模块的信息\nfunc (Gizmo) CaddyModule() caddy.ModuleInfo {\n\treturn caddy.ModuleInfo{\n\t\tID:  \"foo.gizmo\",\n\t\tNew: func() caddy.Module { return new(Gizmo) },\n\t}\n}\n```\n\n然后从你的项目目录中运行这个命令，则在列表中应该能看到你的模块：\n\n```bash\nxcaddy list-modules\n...\nfoo.gizmo\n...\n```\n\n<aside class=\"tip\">\n[`xcaddy`命令](https://github.com/caddyserver/xcaddy)是每个模块开发者工作流程中的一个重要部分。它用你的插件编译Caddy，然后用给定的参数运行它。它每次都会丢弃临时二进制文件（类似于`go run`）。\n</aside>\n\n\n恭喜你，你的模块在Caddy注册了，可以在[Caddy的配置文件](/docs/json/)中的任意位置使用具有相同命名空间的模块。\n\n基于这套机制，`xcaddy`只是制作了一个依赖Caddy和你的插件的新的Go模块（用适当的`replace`来使用你的本地开发版本），然后添加一个导入，确保它被编译到。\n\n```go\nimport _ \"github.com/example/mymodule\"\n```\n\n## 模块基础知识\n\nCaddy模块：\n\n1. 实现`caddy.Module`接口，提供一个ID和构造函数。\n2. 在适当的命名空间里有一个独特的名字\n3. 通常实现一些对该命名空间的主模块有意义的接口。\n\n**主模块**（或母模块）是用于加载/初始化其他模块的模块。它们通常为访客模块定义命名空间。\n\n**访客模块**（或_子模块_）是被加载或初始化的模块。所有模块都是访客模块。\n\n\n## 模块ID\n\n每个Caddy模块都有一个唯一的ID，由命名空间和名称组成。\n\n- 一个完整的ID看起来像`foo.bar.module_name`。\n- 命名空间是`foo.bar`。\n- 名称是`module_name`，在其命名空间中必须是唯一的。\n\n模块ID必须使用`snake_case`惯例。\n\n### 命名空间\n\n命名空间就像类一样，也就是说，一个命名空间定义了一些功能，这些功能在它的所有模块中是通用的。例如，我们可以预期在`http.handlers`命名空间中的所有模块都是HTTP处理程序。因此，宿主模块可以将该命名空间中的客体模块从 \"interface{}\"类型转为更具体、更有用的类型，如 \"caddyhttp.MiddlewareHandler\"。\n\n客体模块必须有正确的命名空间，才能被宿主模块识别，因为宿主模块会要求Caddy在某一命名空间内提供宿主模块所需的功能。例如，如果你要写一个叫`gizmo'的HTTP处理程序模块，你的模块的名字将是`http.handlers.gizmo'，因为`http'应用程序会在`http.handlers'命名空间中寻找处理程序。\n\n换句话说，Caddy模块被期望实现[某些接口]（/docs/extending-caddy/namespaces），这取决于它们的模块名称空间。有了这个约定，模块开发者可以说一些直观的东西，比如：\"`http.handlers`命名空间中的所有模块都是HTTP处理程序。\" 更为技术性的是，这通常意味着：\"`http.handlers`命名空间中的所有模块都实现了`caddyhttp.MiddlewareHandler`接口\"。因为这个方法集是已知的，所以更具体的类型可以被断言和使用。\n\n**[查看所有标准Caddy命名空间与它们的Go类型的映射表。]（/docs/extending-caddy/namespaces）**\n\n`caddy'和`admin'命名空间是保留的，不能作为应用程序的名称。\n\n要编写插入第三方主机模块的模块，请查阅这些模块的命名空间文档。\n\n###名称\n\n命名空间中的名字很重要，对用户来说非常明显，但并不特别重要，只要它是唯一的、简洁的，并且对它的作用有意义。\n\n## 应用程序模块\n\n应用程序是具有空的命名空间的模块，它习惯上成为自己的顶层命名空间。应用程序模块实现了[caddy.App](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#App)接口。\n\n这些模块出现在Caddy配置的顶层的[`\"apps\"`](/docs/json/#apps)属性中。\n\n```json\n{\n\t\"apps\": {}\n}\n```\n\n例如[apps](/docs/json/apps/)是`http`和`tls`。他们的是空命名空间。\n\n为这些应用程序编写的访客模块应该在一个由应用程序名称衍生的命名空间中。例如，HTTP处理程序使用`http.handlers`命名空间，TLS证书加载器使用`tls.certificates`命名空间。\n\n## 模块实现\n\n一个模块几乎可以是任何类型，但结构体是最常见的，因为它们可以保存用户配置。\n\n\n### 配置\n\n大多数模块需要一些配置。只要你的类型与JSON兼容，Caddy会自动处理这个问题。因此，如果一个模块是一个结构类型，它将需要在其字段上使用结构标签，根据Caddy的惯例，应该使用`snake_casing`。\n\n```go\ntype Gizmo struct {\n\tMyField string `json:\"my_field,omitempty\"`\n\tNumber  int    `json:\"number,omitempty\"`\n}\n```\n\n以这种方式使用结构标签将确保配置属性在所有Caddy中的命名是一致的。\n\n当一个模块被初始化时，它的配置已经填好了。也可以在模块初始化后执行额外的[provisioning](#provisioning)和[validation](#validating)步骤。\n\n\n### 模块生命周期\n\n一个模块的生命从它被主机模块加载时开始。会发生以下情况。\n\n1. [`New()`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#ModuleInfo.New)被调用，以获得一个模块的值的实例。\n2. 模块的配置被解密到该实例中。\n3. 如果该模块是一个[caddy.Provisioner](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Provisioner)，则调用`Provision()`方法。\n4. 如果该模块是[caddy.Validator](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Validator)，则调用`Validate()`方法。\n5. 在这一点上，宿主模块被赋予加载的客体模块作为`interface{}`值，所以宿主模块通常会对客体模块进行类型确认，使其成为更有用的类型。检查宿主模块的文档，了解其命名空间对客体模块的要求，例如，需要实现哪些方法。\n6. 当一个模块不再需要时，如果它是一个[caddy.CleanerUpper](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#CleanerUpper)，就会调用`Cleanup()`方法。\n\n请注意，你的模块的多个加载实例可能会在某一特定时间重叠! 在配置改变期间，新的模块会在旧的模块停止之前启动。一定要小心使用全局状态。使用[caddy.UsagePool](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#UsagePool)类型来帮助管理跨模块加载的全局状态。如果你的模块在套接字上监听，使用`caddy.Listen*()`来获得一个支持重叠使用的套接字。\n\n### 额外配置(provisioning)\n\n一个模块的配置将被自动解密到其值中。这意味着，例如，结构字段将为你填好。\n\n但是，如果你的模块需要额外的配置步骤，你可以实现（可选）[caddy.Provisioner]（https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Provisioner）接口。\n\n```go\n// Provision sets up the module.\nfunc (g *Gizmo) Provision(ctx caddy.Context) error {\n\t// TODO: set up the module\n\treturn nil\n}\n```\n\n这通常是宿主模块将加载他们的客人/子模块的地方，但它可以用于几乎任何东西。模块的配置是以任意的顺序进行的。\n\n一个模块可以通过调用`ctx.App()`访问其他应用程序，但模块不能有循环依赖关系。换句话说，如果`http`应用加载的模块依赖于`tls`应用，那么`tls`应用加载的模块就不能依赖于`http`应用。(与 Go 中禁止导入循环的规则非常相似）。\n\n此外，你应该避免在`Provision`中执行昂贵的操作，因为即使配置只是被验证，也要进行配置。当处于供应阶段时，不要期望模块会被实际使用。\n\n#### 日志\n\n如果你的模块需要记录日志，不要使用Go标准库中的`log.Print*()`。换句话说，**不要使用Go的全局日志器**。Caddy使用高性能、高度灵活、结构化的日志[zap]（https://github.com/uber-go/zap）。\n\n要发射日志，在你模块的Provision方法中获得一个日志器：\n\n```go\nfunc (g *Gizmo) Provision(ctx caddy.Context) error {\n\tg.logger = ctx.Logger(g) // g.logger is a *zap.Logger\n}\n```\n\n然后你可以使用`g.logger`发送结构化的、分层的日志。详见[zap的go文档](https://pkg.go.dev/go.uber.org/zap?tab=doc#Logger)。\n\n### 验证\n\n想验证其配置的模块可以通过满足（可选）[`caddy.Validator`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Validator)接口来进行验证。\n\n```go\n// Validate验证模块是否有可用的配置。\nfunc (g Gizmo) Validate() error {\n\t// TODO: 验证模块的设置。\n\treturn nil\n}\n```\n\nValidate应该是一个只读的函数。它在`Provision()`方法之后运行。\n\n\n### 接口守护\n\nCaddy模块的行为是隐性的，因为Go接口是隐性满足的。只需在你的模块类型中添加正确的方法，就可以使你的模块正确与否。因此，打错字或弄错方法签名会导致意外的（缺乏）行为。\n\n幸运的是，有一个简单的、无开销的、编译时的检查，你可以添加到你的代码中，以确保你已经添加了正确的方法。这些被称为接口防护。\n\n```go\nvar _ InterfaceName = (*YourType)(nil)\n```\n\n将`InterfaceName`替换为你打算满足的接口，将`YourType`替换为你的模块的类型名称。\n\n例如，一个HTTP处理程序，如静态文件服务器，可能满足多个接口：\n\n```go\n// Interface guards\nvar (\n\t_ caddy.Provisioner           = (*FileServer)(nil)\n\t_ caddyhttp.MiddlewareHandler = (*FileServer)(nil)\n)\n```\n\n这样，如果`*FileServer`不满足这些接口，程序就无法编译。\n\n没有接口防护，混乱的 bug 就会溜进来。例如，如果你的模块必须在使用前提供自己，但你的`Provision()`方法有一个错误（例如拼写错误或签名错误），提供将永远不会发生，导致挠头。接口防护是非常简单的，可以防止这种情况。它们通常放在文件的底部。\n\n## 主机模块\n\n当一个模块加载它自己的客户模块时，它就成为一个主机模块。如果一个模块的功能可以用不同的方式实现，这就很有用。\n\n一个主机模块几乎总是一个结构。通常情况下，支持客体模块需要两个结构域：一个用于保存其原始JSON，另一个用于保存其解码值。\n\n```go\ntype Gizmo struct {\n\tGadgetRaw json.RawMessage `json:\"gadget,omitempty\" caddy:\"namespace=foo.gizmo.gadgets inline_key=gadgeter\"`\n\n\tGadget Gadgeter `json:\"-\"`\n}\n```\n\n第一个字段（本例中的`GadgetRaw`）是可以找到客人模块的原始的、未被提供的JSON形式的地方。\n\n第二个字段(`Gadget`)是最终配置的值将被存储的地方。由于第二个字段不是面向用户的，我们用结构标签将其从JSON中排除。(如果其他软件包不需要它，你也可以取消导出，这样就不需要结构标签了。)\n\n### Caddy结构标签\n\n原始模块字段上的`caddy`结构标签帮助Caddy知道要加载的模块的名称空间和名称（包括完整的ID）。它也用于生成文档。\n\n该结构标签有一个非常简单的格式。`key1=val1 key2=val2 ...`。\n\n对于模块字段，结构标签将看起来像：\n\n```go\n`caddy:\"namespace=foo.bar inline_key=baz\"`\n```\n\n`namespace=`部分是必须的。它定义了寻找模块的命名空间。\n\n`inline_key=`部分只在模块名称与模块本身并列时使用；这意味着值是一个对象，其中一个键是_inline key_，其值是模块的名称。如果省略，那么字段类型必须是[`caddy.ModuleMap`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#ModuleMap)或`[]caddy.ModuleMap`，其中映射键是模块名称。\n\n\n###加载客户模块\n\n要加载一个客户模块，在提供阶段调用[`ctx.LoadModule()`](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#Context.LoadModule)。\n\n```go\n// Provision sets up g and loads its gadget.\nfunc (g *Gizmo) Provision(ctx caddy.Context) error {\n\tif g.GadgetRaw != nil {\n\t\tval, err := ctx.LoadModule(g, \"GadgetRaw\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"loading gadget module: %v\", err)\n\t\t}\n\t\tg.Gadget = val.(Gadgeter)\n\t}\n\treturn nil\n}\n```\n\n注意，`LoadModule()`调用需要一个指向结构的指针和一个字符串的字段名。很奇怪，对吗？为什么不直接传递结构字段呢？这是因为根据配置的布局，有几种不同的方式来加载模块。这个方法签名允许Caddy使用反射来找出加载模块的最佳方式，最重要的是，读取其结构标签。\n\n如果客体模块必须由用户明确设置，那么在尝试加载模块之前，如果Raw字段为nil或空，你应该返回一个错误。\n\n注意加载的模块是如何进行类型确认的。`g.Gadget = val.(Gadgeter)` - 这是因为返回的`val`是一个`interface{}`类型，不是很有用。然而，我们期望在声明的命名空间中的所有模块（在我们的例子中来自结构标签的`foo.gizmo.gadgets`）实现`Gadgeter`接口，所以这个类型断言是安全的，然后我们可以使用它\n\n如果你的主机模块定义了一个新的命名空间，一定要为开发者记录该命名空间和它的Go类型[就像我们在这里做的]（/docs/extending-caddy/namespaces）。\n\n## 完整的例子\n\n让我们假设我们想写一个HTTP处理模块。这将是一个为演示目的而设计的中间件，在每个HTTP请求中把访问者的IP地址打印成一个流。\n\n我们还希望它可以通过Caddyfile进行配置，因为大多数人喜欢在非自动情况下使用Caddyfile。我们通过注册一个Caddyfile处理程序指令来做到这一点，这是一种可以向HTTP路由添加处理程序的指令。我们还实现了`caddyfile.Unmarshaler`接口。通过添加这几行代码，这个模块就可以用Caddyfile进行配置了! 例如：`visitor_ip stdout`。\n\n以下是这样一个模块的代码，并附有解释说明：\n\n```go\npackage visitorip\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/caddyserver/caddy/v2\"\n\t\"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile\"\n\t\"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile\"\n\t\"github.com/caddyserver/caddy/v2/modules/caddyhttp\"\n)\n\nfunc init() {\n\tcaddy.RegisterModule(Middleware{})\n\thttpcaddyfile.RegisterHandlerDirective(\"visitor_ip\", parseCaddyfile)\n}\n\n// 中间件实现了一个HTTP处理程序，将访问者的IP地址写入\n// 访客的IP地址写到文件或流中。\ntype Middleware struct {\n\t// 要写入的文件或流。可以是 \"stdout\"或 \"stderr\"。\n\tOutput string `json:\"output,omitempty\"`\n\n\tw io.Writer\n}\n\n// CaddyModule返回Caddy模块的信息。\nfunc (Middleware) CaddyModule() caddy.ModuleInfo {\n\treturn caddy.ModuleInfo{\n\t\tID:  \"http.handlers.visitor_ip\",\n\t\tNew: func() caddy.Module { return new(Middleware) },\n\t}\n}\n\n// Provision实现了caddy.Provisioner。\nfunc (m *Middleware) Provision(ctx caddy.Context) error {\n\tswitch m.Output {\n\tcase \"stdout\":\n\t\tm.w = os.Stdout\n\tcase \"stderr\":\n\t\tm.w = os.Stderr\n\tdefault:\n\t\treturn fmt.Errorf(\"an output stream is required\")\n\t}\n\treturn nil\n}\n\n// Validate实现了caddy.Validator。\nfunc (m *Middleware) Validate() error {\n\tif m.w == nil {\n\t\treturn fmt.Errorf(\"no writer\")\n\t}\n\treturn nil\n}\n\n// ServeHTTP 实现了 caddyhttp.MiddlewareHandler。\nfunc (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {\n\tm.w.Write([]byte(r.RemoteAddr))\n\treturn next.ServeHTTP(w, r)\n}\n\n// UnmarshalCaddyfile实现了caddyfile.Unmarshaler。\nfunc (m *Middleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {\n\tfor d.Next() {\n\t\tif !d.Args(&m.Output) {\n\t\t\treturn d.ArgErr()\n\t\t}\n\t}\n\treturn nil\n}\n\n// parseCaddyfile从h中解读令牌到一个新的中间件。\nfunc parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {\n\tvar m Middleware\n\terr := m.UnmarshalCaddyfile(h.Dispenser)\n\treturn m, err\n}\n\n// Interface guards\nvar (\n\t_ caddy.Provisioner           = (*Middleware)(nil)\n\t_ caddy.Validator             = (*Middleware)(nil)\n\t_ caddyhttp.MiddlewareHandler = (*Middleware)(nil)\n\t_ caddyfile.Unmarshaler       = (*Middleware)(nil)\n)\n```"
  },
  {
    "path": "src/docs/markdown/faq.md",
    "content": "常见问题\n===\n\n这个页面目前仍在建设中（就像90年代网站常见的“under construction”页面）。请稍后再来查看！\n\n"
  },
  {
    "path": "src/docs/markdown/getting-started.md",
    "content": "---\ntitle: 入门指南\n---\n\n# 入门指南\n\n欢迎使用Caddy！本教程会带你了解使用Caddy的基础，并在较高层次上建立整体认知。\n\n**目标：**\n- 🔲 运行守护进程\n- 🔲 体验API\n- 🔲 给Caddy加载配置\n- 🔲 验证配置\n- 🔲 编写Caddyfile\n- 🔲 使用配置适配器\n- 🔲 用初始配置启动\n- 🔲 对比JSON与Caddyfile\n- 🔲 对比API与配置文件\n- 🔲 后台运行\n- 🔲 配置无停机重载\n\n**前提条件：**\n- 基本的终端/命令行技能\n- 基本的文本编辑器技能\n- `caddy`和`curl`已在你的PATH中\n\n---\n\n**如果你是通过包管理器[安装Caddy](/docs/install)的，Caddy可能已经以服务形式在运行。若是如此，请先停止该服务，再继续本教程。**\n\n先运行它：\n\n<pre><code class=\"cmd bash\">caddy</code></pre>\n\n没有子命令时，`caddy`只会显示帮助信息。忘记命令用法时，这个入口很有用。\n\n要以前台守护进程方式启动Caddy，请使用`run`子命令：\n\n<pre><code class=\"cmd bash\">caddy run</code></pre>\n\n<aside class=\"complete\">运行守护进程</aside>\n\n这个命令会一直阻塞，但它现在在做什么？此刻……什么都没做。默认情况下，Caddy配置（\"config\"）是空的。我们可以在另一个终端通过[管理API](/docs/api)验证：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/config/</code></pre>\n\n<aside class=\"tip\">\n\n这**不是**你的网站：`localhost:2019`这个管理端点用于控制Caddy，且默认只允许本机访问。\n\n</aside>\n\n\n<aside class=\"complete\">体验API</aside>\n\n要让Caddy真正工作起来，需要给它配置。方式有很多；下一节我们先用`curl`向[/load](/docs/api#post-load)端点发一个POST请求。\n\n\n\n## 你的第一个配置\n\n为准备这个请求，我们先写一份配置。Caddy配置的核心就是一个[JSON文档](/docs/json/)。\n\n把下面内容保存到JSON文件（例如`caddy.json`）：\n\n```json\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"example\": {\n\t\t\t\t\t\"listen\": [\":2015\"],\n\t\t\t\t\t\"routes\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"handle\": [{\n\t\t\t\t\t\t\t\t\"handler\": \"static_response\",\n\t\t\t\t\t\t\t\t\"body\": \"Hello, world!\"\n\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n<aside class=\"tip\">\n\n并不是必须用配置文件；这里只是为了教程演示。Caddy的[管理API](/docs/api)本就是给其他程序或脚本调用设计的。\n\n</aside>\n\n\n然后上传它：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/load \\\n\t-H \"Content-Type: application/json\" \\\n\t-d @caddy.json\n</code></pre>\n\n<aside class=\"complete\">给Caddy加载配置</aside>\n\n再发一个GET请求，确认Caddy已应用新配置：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/config/</code></pre>\n\n在浏览器访问[localhost:2015](http://localhost:2015)，或用`curl`测试：\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl localhost:2015</span>\nHello, world!</code></pre>\n\n如果你看到了_Hello, world!_，说明成功了。始终确认配置按预期工作是个好习惯，尤其是在部署到生产前。\n\n<aside class=\"complete\">验证配置</aside>\n\n\n## 你的第一个Caddyfile\n\n仅仅输出Hello World，这样做显得工作量有点大。\n\n配置Caddy的另一种方式是使用[**Caddyfile**](/docs/caddyfile)。上面那份JSON配置，可以简洁写成：\n\n```caddy\n:2015\n\nrespond \"Hello, world!\"\n```\n\n\n把它保存为当前目录下名为`Caddyfile`（无扩展名）的文件。\n\n<aside class=\"complete\">编写Caddyfile</aside>\n\n如果Caddy还在运行，先停止它（<kbd>Ctrl</kbd>+<kbd>C</kbd>），然后执行：\n\n<pre><code class=\"cmd bash\">caddy adapt</code></pre>\n\n如果你的Caddyfile在其他位置，或者文件名不是`Caddyfile`：\n\n<pre><code class=\"cmd bash\">caddy adapt --config /path/to/Caddyfile</code></pre>\n\n你会看到JSON输出！这里发生了什么？\n\n我们刚刚使用了[_配置适配器_](/docs/config-adapters)把Caddyfile转换成了Caddy原生JSON结构。\n\n<aside class=\"complete\">使用配置适配器</aside>\n\n虽然我们可以拿到输出后再发一次API请求，但这些步骤可省略，因为`caddy`命令本身就能帮你完成。若当前目录有名为Caddyfile的文件，且未指定其他配置，Caddy会自动加载它、完成适配并立即运行。\n\n现在当前目录已经有Caddyfile了，再执行一次`caddy run`：\n\n<pre><code class=\"cmd bash\">caddy run</code></pre>\n\n如果你的Caddyfile在其他位置：\n\n<pre><code class=\"cmd bash\">caddy run --config /path/to/Caddyfile</code></pre>\n\n（若文件名不是以“Caddyfile”开头，还需显式指定`--adapter caddyfile`。）\n\n现在再访问你的网站，会看到它已经工作了！\n\n<aside class=\"complete\">用初始配置启动</aside>\n\n如你所见，用初始配置启动Caddy有多种方式：\n\n- 当前目录中名为Caddyfile的文件\n- `--config`参数（可搭配`--adapter`）\n- `--resume`参数（前提是此前已加载过配置）\n\n\n## JSON vs. Caddyfile\n\n现在你知道了：Caddyfile本质上只是先转换为JSON。\n\nCaddyfile看起来比JSON更易用，但是否应该始终使用它？两种方式各有利弊，最终取决于你的需求和场景。\n\nJSON | Caddyfile\n-----|----------\n易于生成 | 便于手工编写\n易于编程 | 自动化较别扭\n表达能力极强 | 表达能力中等\n覆盖Caddy全部功能 | 覆盖Caddy大部分功能\n支持配置遍历 | 不能在Caddyfile内部遍历\n支持局部配置变更 | 只能整份变更\n可导出 | 不可导出\n兼容所有API端点 | 兼容部分API端点\n文档可自动生成 | 文档为手工编写\n通用性强 | 相对小众\n更高效 | 计算开销更高\n略显枯燥 | 更有趣\n**了解更多：[JSON结构](/docs/json/)** | **了解更多：[Caddyfile文档](/docs/caddyfile)**\n\n你需要根据自己的场景决定哪种更合适。\n\n需要注意：JSON和Caddyfile（以及[其他受支持的配置适配器](/docs/config-adapters)）都可配合[Caddy API](/docs/api)使用。但若使用JSON，你能获得完整的Caddy功能与API能力；若使用配置适配器，通过API加载或修改配置的方式仅限[/load端点](/docs/api#post-load)。\n\n<aside class=\"complete\">对比JSON与Caddyfile</aside>\n\n\n## API vs. 配置文件\n\n<aside class=\"tip\">\n\n在底层，即便是配置文件流程也会经过Caddy的API端点；`caddy`命令只是把这些API调用封装好了。\n\n</aside>\n\n\n你还需要决定你的工作流是偏API还是偏CLI。（同一台服务器上你_可以_同时用API和配置文件，但不推荐：最好保持单一事实来源。）\n\nAPI | 配置文件\n----|-------------\n通过HTTP请求修改配置 | 通过Shell命令修改配置\n易于扩展 | 难以扩展\n手工管理困难 | 手工管理容易\n非常有趣 | 也很有趣\n**了解更多：[API教程](/docs/api-tutorial)** | **了解更多：[Caddyfile教程](/docs/caddyfile-tutorial)**\n\n<aside class=\"tip\">\n\t只要工具得当（例如任意REST客户端应用），手工通过API管理服务器配置完全可行。\n</aside>\n\n选择API工作流还是配置文件工作流，与是否使用配置适配器是正交关系：你可以使用JSON并存成文件后走CLI；反过来，也可以通过API使用Caddyfile。\n\n但多数人会采用JSON+API或Caddyfile+CLI这两种组合。\n\n如你所见，Caddy非常适合多种场景与部署方式！\n\n<aside class=\"complete\">对比API与配置文件</aside>\n\n\n\n## Start, stop, run\n\n由于Caddy是服务器程序，它会持续运行。这意味着执行`caddy run`后，终端不会返回，除非进程被终止（通常是<kbd>Ctrl</kbd>+<kbd>C</kbd>）。\n\n虽然`caddy run`最常见且通常是推荐方式（尤其在做系统服务时），你也可以用`caddy start`让Caddy在后台运行：\n\n<pre><code class=\"cmd bash\">caddy start</code></pre>\n\n这样你就能继续使用当前终端，这在某些交互式无头环境里很方便。\n\n之后你需要自行停止进程，因为<kbd>Ctrl</kbd>+<kbd>C</kbd>不能帮你停后台进程：\n\n<pre><code class=\"cmd bash\">caddy stop</code></pre>\n\n也可以调用API的[/stop端点](/docs/api#post-stop)。\n\n<aside class=\"complete\">后台运行</aside>\n\n\n## Reloading config\n\n你的服务支持配置无停机重载/变更。\n\n所有用于加载或修改配置的[API端点](/docs/api)都支持零停机、平滑生效。\n\n但在命令行使用时，很多人会想用<kbd>Ctrl</kbd>+<kbd>C</kbd>停掉服务，再重启以应用新配置。不要这么做：服务启停与配置变更是两件事，这样会造成停机。\n\n<aside class=\"tip\">\n\t停止服务会导致网站中断。\n</aside>\n\n应改用[`caddy reload`](/docs/command-line#caddy-reload)命令做平滑变更：\n\n<pre><code class=\"cmd bash\">caddy reload</code></pre>\n\n它本质上仍是调用底层API：加载配置（必要时先适配为JSON），然后无停机地平滑替换活动配置。\n\n如果加载新配置出现错误，Caddy会回滚到最后一份可用配置。\n\n<aside class=\"tip\">\n\t严格来说，新配置会先启动，旧配置后停止，所以会有极短时间两份配置同时运行！如果新配置失败，会以错误中止，而旧配置不会被停止。\n</aside>\n\n<aside class=\"complete\">配置无停机重载</aside>\n"
  },
  {
    "path": "src/docs/markdown/index.md",
    "content": "---\ntitle: 欢迎\n---\n\n# 欢迎使用Caddy\n\nCaddy是一款基于Go语言编写的强大且可扩展的平台，可以给你的站点、服务和应用程序提供服务。如果你是Caddy的新手，那么你提供Web服务的方式即将改变。\n\n## 介绍\n大多数人将Caddy用作Web服务器或代理，但Caddy的本质是诸多服务器的服务器。在安装了必要的[模块](modules/readme)后，它就可以充当长时间运行的进程的角色！\n\n基于Caddy的[API](api)，它的配置能被动态修改，且能导出来。虽然不需要配置文件，但你仍然可以使用它们；大多数人还是最喜欢通过[Caddyfile](caddyfile)对Caddy进行配置。虽然通过[配置适配器](config-adapters)可以对Caddy使用多种的配置文件格式，但它的原生配置语言是JSON。\n\nCaddy已经编译好了所有主流平台的版本，且没有运行时依赖项。\n\n## 第一次？\n没问题！我们建议每个人，无论经验如何，都可以阅读我们的[入门指南](getting-started)。它将为你提供对这款新 Web服务器的全面了解，这对于你继续学习将是非常实用的。\n\n如果你只有几分钟的时间去开始运行它，可以请尝试我们的[快速入门](quick-starts)。\n\n一些扩充性的内容，如特殊的示例，请查看我们的社区[wiki](https://caddy.community/c/wiki/13) - 也欢迎你贡献更多的内容！\n\n我们建议你坚持使用这些官方资源来[安装](install)、[配置](configure)和[运行](command-line)Caddy，而不是随机从一些博客或者问答版块上直接拿过来运行命令或复制配置片段。你会发现我们的材料通常更准确和及时。我们还鼓励你按照需要定制自己的配置，以确保你了解服务器的工作原理，这样即便以后出现问题，你也有能力更快地解决问题。\n\n不管如何，你都可以享受使用这款新Web服务器的乐趣。Caddy将会带给你前所未有的服务器软件的使用体验！\n\n## 获取帮助\n\n如果你在使用Caddy时需要帮助，请在我们的[社区](https://caddy.community/)中好好提问，我们很乐意为你提供帮助。请你尽可能完整地按照帮助模板填写。当然，你也可以去帮助他人，我们也总是需要更多的帮手。\n\n只有当你确定是Caddy中的错误或有特定功能请求时，才使用我们的[问题跟踪器](https://github.com/caddyserver/caddy/issues)。\n\n该网站在[GitHub](https://github.com/caddyserver/website)上维护。要提交改进，请打开问题或拉取请求。\n\n感谢你参与我们的社区！我们希望Caddy能让你满意。\n"
  },
  {
    "path": "src/docs/markdown/install.md",
    "content": "---\ntitle: 安装\n---\n\n# 安装\n\n本页介绍了在你的系统上安装Caddy的多种方式。\n\n**官方维护：**\n\n- [静态二进制文件](#static-binaries)\n- [Debian、Ubuntu、Raspbian软件包](#debian-ubuntu-raspbian)\n- [Fedora、RedHat、CentOS软件包](#fedora-redhat-centos)\n- [Arch Linux、Manjaro、Parabola软件包](#arch-linux-manjaro-parabola)\n- [Docker镜像](#docker)\n- [Railway模板](#railway)\n\n<aside class=\"tip\">\n\n我们的[官方软件包](https://github.com/caddyserver/dist)只包含标准模块。若你需要第三方插件，可使用[`xcaddy`从源码构建](/docs/build#xcaddy)、使用[下载页面](/download)，或[部署到Railway](#railway)。\n\n</aside>\n\n\n**社区维护：**\n\n- [Gentoo](#gentoo)\n- [Homebrew（Mac）](#homebrew-mac)\n- [Chocolatey（Windows）](#chocolatey-windows)\n- [Scoop（Windows）](#scoop-windows)\n- [Webi](#webi)\n- [Ansible](#ansible)\n- [Termux](#termux)\n- [Nix/Nixpkgs/NixOS](#nixnixpkgsnixos)\n- [Unikraft](#unikraft)\n- [OPNsense](#opnsense)\n- [Mise](#mise)\n\n\n## Static binaries\n\n**如果你是在生产系统安装，建议优先使用下方可用的官方发行版软件包。**\n\n1. 获取Caddy二进制文件：\n\t- [从GitHub Releases下载](https://github.com/caddyserver/caddy/releases)（展开“Assets”）\n\t\t- 关于如何验证资源签名，请参考[验证资源签名](/docs/signature-verification)\n\t- [从下载页面获取](/download)\n\t- [从源码构建](/docs/build)（使用`go`或`xcaddy`）\n2. [将Caddy安装为系统服务。](/docs/running#manual-installation)强烈推荐，特别是生产服务器。\n\n将二进制文件放到你的`$PATH`（Windows为`%PATH%`）目录之一，这样你可以直接运行`caddy`，无需输入完整路径。（运行`echo $PATH`可查看生效目录列表。）\n\n你可以通过替换为新版本二进制并重启Caddy来升级静态安装。使用[`caddy upgrade`命令](/docs/command-line#caddy-upgrade)会更方便。\n\n\n\n## Debian, Ubuntu, Raspbian\n\n安装该软件包后，Caddy会自动以名为`caddy`的[systemd服务](/docs/running#linux-service)启动并运行。包内还包含可选的`caddy-api`服务，默认未启用；如果你主要通过API而不是配置文件管理Caddy，建议使用它。\n\n安装后，请阅读[服务使用说明](/docs/running#using-the-service)。\n\n**稳定版本：**\n\n<pre><code class=\"cmd\"><span class=\"bash\">sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl</span>\n<span class=\"bash\">curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg</span>\n<span class=\"bash\">curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list</span>\n<span class=\"bash\">sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg</span>\n<span class=\"bash\">sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list</span>\n<span class=\"bash\">sudo apt update</span>\n<span class=\"bash\">sudo apt install caddy</span></code></pre>\n\n**测试版本**（包含beta和候选版本）：\n\n<pre><code class=\"cmd\"><span class=\"bash\">sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl</span>\n<span class=\"bash\">curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-testing-archive-keyring.gpg</span>\n<span class=\"bash\">curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-testing.list</span>\n<span class=\"bash\">sudo chmod o+r /usr/share/keyrings/caddy-testing-archive-keyring.gpg</span>\n<span class=\"bash\">sudo chmod o+r /etc/apt/sources.list.d/caddy-testing.list</span>\n<span class=\"bash\">sudo apt update</span>\n<span class=\"bash\">sudo apt install caddy</span></code></pre>\n\n[**查看Cloudsmith仓库**](https://cloudsmith.io/~caddy/repos/)\n\n如果你希望在自定义Caddy构建中复用软件包支持文件（systemd服务、bash补全、默认配置），可参考[这里的说明](/docs/build#package-support-files-for-custom-builds-for-debianubunturaspbian)。\n\n\n## Fedora, RedHat, CentOS\n\n该软件包包含Caddy的两个[systemd服务](/docs/running#linux-service)单元文件，但默认不启用。建议使用服务方式运行；如果使用，请阅读[服务使用说明](/docs/running#using-the-service)。\n\nFedora：\n\n<pre><code class=\"cmd\"><span class=\"bash\">dnf install dnf5-plugins</span>\n<span class=\"bash\">dnf copr enable @caddy/caddy</span>\n<span class=\"bash\">dnf install caddy</span></code></pre>\n\nCentOS/RHEL：\n\n<pre><code class=\"cmd\"><span class=\"bash\">dnf install dnf-plugins-core</span>\n<span class=\"bash\">dnf copr enable @caddy/caddy</span>\n<span class=\"bash\">dnf install caddy</span></code></pre>\n\n[**查看Caddy COPR**](https://copr.fedorainfracloud.org/coprs/g/caddy/caddy/)\n\n\n## Arch Linux, Manjaro, Parabola\n\n该软件包包含Caddy的两个[systemd服务](/docs/running#linux-service)单元文件的重度修改版本，且默认不启用。\n这些修改包括自定义启动/停止行为以及附加沙箱参数，详见[systemd exec文档](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Sandboxing)。这可能导致某些宿主目录对Caddy进程不可用。\n\n<pre><code class=\"cmd\"><span class=\"bash\">pacman -Syu caddy</span></code></pre>\n\n[**查看Arch Linux仓库中的Caddy**](https://archlinux.org/packages/extra/x86_64/caddy/) 与 [**Arch Linux Wiki**](https://wiki.archlinux.org/title/Caddy)\n\n## Docker\n\n<pre><code class=\"cmd bash\">docker pull caddy</code></pre>\n\n[**在Docker Hub查看**](https://hub.docker.com/_/caddy)\n\n参见我们推荐的[Docker Compose配置](/docs/running#docker-compose)与使用说明。\n\n\n## Railway\n\n通过[Railway](https://railway.com)的赞助，我们官方支持此模板：\n\n[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/caddy?referralCode=YOPtw9&utm_medium=integration&utm_source=template&utm_campaign=generic)\n\n\n## Gentoo\n\n_注：这是社区维护的安装方式。_\n\n<pre><code class=\"cmd\">emerge www-servers/caddy</code></pre>\n\n[**查看Gentoo包**](https://packages.gentoo.org/packages/www-servers/caddy)\n\n\n\n## Homebrew (Mac)\n\n_注：这是社区维护的安装方式。_\n\n<pre><code class=\"cmd bash\">brew install caddy</code></pre>\n\n[**查看Homebrew formula**](https://formulae.brew.sh/formula/caddy)\n\n\n\n## Chocolatey (Windows)\n\n_注：这是社区维护的安装方式。_\n\n<pre><code class=\"cmd\">choco install caddy</code></pre>\n\n[**查看Chocolatey包**](https://chocolatey.org/packages/caddy)\n\n\n\n## Scoop (Windows)\n\n_注：这是社区维护的安装方式。_\n\n<pre><code class=\"cmd\">scoop install caddy</code></pre>\n\n[**查看Scoop manifest**](https://github.com/ScoopInstaller/Main/blob/master/bucket/caddy.json)\n\n\n\n## Webi\n\n_注：这是社区维护的安装方式。_\n\nLinux和macOS：\n\n<pre><code class=\"cmd bash\">curl -sS https://webi.sh/caddy | sh</code></pre>\n\nWindows：\n\n<pre><code class=\"cmd\">curl.exe https://webi.ms/caddy | powershell</code></pre>\n\n你可能需要调整Windows防火墙规则，以允许来自非localhost的入站连接。\n\n[**在Webi查看**](https://webinstall.dev/caddy)\n\n\n\n## Ansible\n\n_注：这是社区维护的安装方式。_\n\n<pre><code class=\"cmd bash\">ansible-galaxy install nvjacobo.caddy</code></pre>\n\n[**查看Ansible角色仓库**](https://github.com/nvjacobo/caddy)\n\n\n\n## Termux\n\n_注：这是社区维护的安装方式。_\n\n<pre><code class=\"cmd\">pkg install caddy</code></pre>\n\n[**查看Termux的build.sh文件**](https://github.com/termux/termux-packages/blob/master/packages/caddy/build.sh)\n\n\n\n## Nix/Nixpkgs/NixOS\n\n_注：这是社区维护的安装方式。_\n\n- 包名：[`caddy`](https://search.nixos.org/packages?channel=unstable&show=caddy&query=caddy)\n- NixOS模块：[`services.caddy`](https://search.nixos.org/options?channel=unstable&show=services.caddy.enable&query=services.caddy)\n\n[**在Nixpkgs搜索中查看Caddy**](https://search.nixos.org/packages?channel=unstable&show=caddy&query=caddy) 与 [**NixOS选项搜索**](https://search.nixos.org/options?channel=unstable&show=services.caddy.enable&query=services.caddy)\n\n\n\n## Unikraft\n\n_注：这是社区维护的安装方式。_\n\n先安装Unikraft配套工具[`kraft`](https://unikraft.org/docs/cli)：\n\n<pre><code class=\"cmd\">curl --proto '=https' --tlsv1.2 -sSf https://get.kraftkit.sh | sh</code></pre>\n\n然后通过以下命令在Unikraft中运行Caddy：\n\n<pre><code class=\"cmd\">kraft run --rm -p 2015:2015 --plat qemu --arch x86_64 -M 256M caddy:2.7</code></pre>\n\n若要允许非localhost入站连接，你需要[将unikernel实例连接到网络](https://unikraft.org/docs/cli/running#connecting-a-unikernel-instance-to-a-network)。\n\n[**查看Unikraft应用目录**](https://github.com/unikraft/catalog/tree/main/examples/caddy) 与 [**KraftCloud平台示例（由Unikraft驱动）**](https://github.com/kraftcloud/examples/tree/main/caddy)。\n\n\n\n## OPNsense\n\n_注：这是社区维护的安装方式。_\n\n<pre><code class=\"cmd\">pkg install os-caddy</code></pre>\n\n[**查看FreeBSD caddy-custom makefile**](https://github.com/opnsense/ports/blob/master/www/caddy-custom/Makefile) 与 [**os-caddy插件源码**](https://github.com/opnsense/plugins/tree/master/www/caddy)\n\n## Mise\n\n_注：这是社区维护的安装方式。_\n\n如果你在使用多语言工具版本管理器[mise](https://github.com/jdx/mise)，可以用下面命令安装最新版：\n\n<pre><code class=\"cmd\">mise use -g caddy@latest</code></pre>\n"
  },
  {
    "path": "src/docs/markdown/json.md",
    "content": "<div id=\"json-docs-container\" class=\"fullspan\">\n\t<div class=\"breadcrumbs\">\n\t\t<!--由JS填充-->\n\t</div>\n\t{{include \"/includes/docs/renderbox.html\"}}\n\t{{include \"/includes/docs/details.html\"}}\n</div>\n\n{{include \"/includes/docs/hovercard.html\"}}\n\n<style>\n\tarticle {\n\t\tpadding-top: 0 !important;\n\t}\n\n\tarticle h1 {\n\t\tpadding-top: 8%;\n\t}\n\n\t.renderbox {\n\t\tborder-radius: 0;\n\t\tfont-size: 20px;\n\t\tline-height: 1.6em;\n\t}\n\n\tpre > code.json {\n\t\tborder-radius: 10px;\n\t}\n\n\t@media (prefers-color-scheme: dark) {\n\t\tpre > code.json {\n\t\t\tbackground-color: #30caec0f;\n\t\t}\n\t}\n</style>\n\n"
  },
  {
    "path": "src/docs/markdown/logging.md",
    "content": "---\ntitle: 日志如何工作\n---\n\n日志如何工作\n=================\n\nCaddy 具有强大而灵活的日志记录工具，但它们可能与你习惯的不同，特别是如果你来自更古老的共享主机或其他旧式 Web 服务器。\n\n## 概述\n\n日志记录有两个主要方面：生产和消耗。\n\n**生产** 意味着生产信息。它由三个步骤组成：\n\n1. 收集相关信息（上下文）\n2. 形成有用的表述（编码）\n3. 将该表示发送到输出（写入）\n\n此功能已融入 Caddy 的核心，使Caddy代码库的任何部分或模块（插件）的任何部分都能够生产日志。\n\n**消耗** 是消息的接收和处理。为了有用，生产的日志必须被消耗掉。仅写入但从未读取的日志没有任何价值。使用日志可以像管理员阅读控制台输出一样简单，也可以像附加日志聚合工具或云服务以过滤、计数和索引日志消息一样高级。\n\n### Caddy的作用\n\n_Caddy是一个日志生产器_。它不消耗日志，除了编码和写入日志所需的最少处理。这很重要，因为它使Caddy的核心更简单，从而减少了错误和边缘情况，同时减少了维护负担。最终，日志处理超出了Caddy核心的范围。\n\n但是，Caddy应用程序模块总是有可能使用日志。（据我们所知，它还不存在。）\n\n\n## 结构化日志\n\n与大多数现代应用程序一样，Caddy 的日志是 _结构化_ 的。这意味着消息中的信息不仅仅是不透明的字符串或字节片。相反，数据保持强类型，并由各个 _字段名称_ 键入，直到需要对消息进行编码并将其写出。\n\n比较传统的非结构化日志——如古老的通用日志格式 (CLF)——通常与传统HTTP服务器一起使用：\n\n```\n127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.1\" 200 2326\n```\n\n这种格式“有结构”但不是“结构化”：它只能用于记录 HTTP 请求。没有（有效的）方法可以对其进行不同的编码，因为它是一个不透明的字节串。它也缺少很多信息。它甚至不包括请求的 Host 标头！此日志格式仅在托管单个站点以及获取有关请求的最基本信息时才有用。\n\n<aside class=\"tip\">\n\tCLF 中缺少主机信息是为什么在托管多个站点时通常需要将这些日志写入单独的文件的原因：否则无法从请求中知道 Host 标头！\n</aside>\n\n现在比较来自Caddy的等效结构化日志消息，编码为JSON并格式化为显示：\n\n```json\n{\n\t\"level\": \"info\",\n\t\"ts\": 1585597114.7687502,\n\t\"logger\": \"http.log.access\",\n\t\"msg\": \"handled request\",\n\t\"request\": {\n\t\t\"method\": \"GET\",\n\t\t\"uri\": \"/\",\n\t\t\"proto\": \"HTTP/2.0\",\n\t\t\"remote_addr\": \"127.0.0.1:50876\",\n\t\t\"host\": \"example.com\",\n\t\t\"headers\": {\n\t\t\t\"User-Agent\": [\n\t\t\t\t\"curl/7.64.1\"\n\t\t\t],\n\t\t\t\"Accept\": [\n\t\t\t\t\"*/*\"\n\t\t\t]\n\t\t},\n\t\t\"tls\": {\n\t\t\t\"resumed\": false,\n\t\t\t\"version\": 771,\n\t\t\t\"ciphersuite\": 49196,\n\t\t\t\"proto\": \"h2\",\n\t\t\t\"proto_mutual\": true,\n\t\t\t\"server_name\": \"example.com\"\n\t\t}\n\t},\n\t\"user_id\": \"\",\n\t\"duration\": 0.000014711,\n\t\"size\": 2326,\n\t\"status\": 200,\n\t\"resp_headers\": {\n\t\t\"Server\": [\n\t\t\t\"Caddy\"\n\t\t],\n\t\t\"Content-Type\": [\"text/html\"]\n\t}\n}\n```\n\n<aside class=\"tip\">\n\t在Caddy生产的实际访问日志中，还存在另一个名为“common_log”的字段。该领域的目的只是帮助人们从遗留系统过渡到更现代的系统。\n</aside>\n\n你可以看到结构化日志如何更有用并包含更多信息。此日志消息中的大量信息不仅有用，而且几乎没有性能开销：Caddy 的日志是零分配的。结构化日志对数据类型或上下文没有限制：它们可以用于任何代码路径并包含任何类型的信息。\n\n因为日志是结构化的和强类型的，它们可以被编码成任何格式。因此，如果你不想使用 JSON，可以将日志编码为任何其他表示形式。Caddy 通过[log编码器模块](/docs/json/logging/logs/encoder/)支持其他人，甚至可以添加更多。\n\n**最重要**的是结构化日志和遗留格式之间的区别，结构化日志可以编码为通用日志格式（或其他任何格式！），但不能反过来。从 CLF 到结构化格式并非易事（或至少效率低下），而且考虑到信息的缺乏也是不可能的。\n\n从本质上讲，高效、结构化的日志记录通常会促进以下理念：\n\n- 太多日志总比太少好\n- 过滤比丢弃好\n- 延迟编码以获得更大的灵活性和互操作性\n \n\n## 生产\n\n在代码中，日志生产类似于以下内容：\n\n```go\nlogger.Debug(\"proxy roundtrip\",\n\tzap.String(\"upstream\", di.Upstream.String()),\n\tzap.Object(\"request\", caddyhttp.LoggableHTTPRequest{Request: req}),\n\tzap.Object(\"headers\", caddyhttp.LoggableHTTPHeader(res.Header)),\n\tzap.Duration(\"duration\", duration),\n\tzap.Int(\"status\", res.StatusCode),\n)\n```\n\n<aside class=\"tip\">\n\t这是来自 Caddy 反向代理的实际代码行。此行允许你在启用调试日志记录时检查对已配置上游的请求。这是故障排除时的宝贵数据！\n</aside>\n\n你可以看到这个函数调用包含日志级别、一条消息和几个数据字段。所有这些都是强类型的，Caddy 使用零分配日志库，因此日志排放快速高效，几乎没有开销。\n\n`logger`变量是一个可以有任意数量的上下文关联的变量`zap.Logger`，其中包括名称和数据字段。这允许记录器很好地从父上下文“继承”，从而启用高级跟踪和度量。\n\n从那里，消息通过一个高效的处理管道发送，并在其中进行编码和写入。\n\n## 日志管道\n\n正如你在上面看到的，消息是由**loggers**生产的。然后将消息发送到**logs**进行处理。\n\nCaddy允许你配置处理消息的[多个日志](/docs/json/logging/logs/)。日志由编码器、写入器、最低级别、采样率和要包含或排除的记录器列表组成。在 Caddy 中，总是有一个名为`default`。你可以在配置文件的[这个对象](/docs/json/logging/logs/)中指定一个以“default”作为键的日志来定义它。\n\n<aside class=\"tip\">\n\t现在是<a href=\"/docs/json/logging/\">探索Caddy的日志文档</a>的好时机，这样你就可以熟悉我们正在讨论的结构和参数。\n</aside>\n\n- **Encoder:** 日志的格式。将内存中的数据表示转换为字节切片。编码器可以访问日志消息的所有字段。\n- **Writer:** 日志输出。可以是任何日志写入器模块，例如文件或网络套接字。它只是写入字节。\n- **Level:** 日志有不同的级别，从 DEBUG 到 FATAL。低于指定级别的消息将被日志忽略。\n- **Sampling:** 非常热的路径可能会发出比有效处理更多的日志；启用采样是一种减少负载的方法，同时仍会产生具有代表性的消息样本。\n- **Include/exclude:** 每条消息都由一个记录器发出，它有一个名称（通常来自模块 ID）。日志可以包括或排除来自某些记录器的消息。\n\n\n当从 Caddy 发出日志消息时：\n\n- 根据每个日志的包含/排除列表检查原始记录器的名称；如果包含（或不排除），则将其纳入该日志。\n- 如果启用了采样，则快速计算确定是否保留日志消息。\n- 消息使用日志的配置编码器进行编码。\n- 然后将编码的字节写入日志的配置写入器。\n\n默认情况下，所有消息都会转到所有配置的日志。这符合上述结构化日志记录的值。你可以通过设置它们的包含/排除列表来限制哪些消息进入哪些日志，但这主要用于过滤来自不同模块的消息；它不打算像日志聚合服务一样使用。为了保持 Caddy 的日志流水线精简和高效，日志消息的高级处理被推迟到消费。\n\n## 消耗\n\n消息发送到输出后，消费者将读入、解析并相应地处理它们。\n\n这是一个与发出日志非常不同的问题域，并且 Caddy 的核心不处理消耗（尽管 Caddy 应用程序模块当然可以）。你可以使用许多工具来处理 JSON 消息流（或其他格式）以及查看、过滤、索引和查询日志。你甚至可以编写或实现你自己的。\n\n例如，如果你运行需要根据特定字段（例如主机名）将CLF分成不同文件的旧软件，你可以使用或编写一个简单的工具来读取 JSON，调用`sprintf()`以创建CLF字符串，然后将其写入基于`request.host`字段中的值的文件。\n\nCaddy的日志记录工具也可用于实现度量和跟踪：度量基本上计算具有某些特征的消息，跟踪基于它们之间的共性将多条消息链接在一起。\n\n你可以通过使用Caddy的日志来完成无数种可能性！\n"
  },
  {
    "path": "src/docs/markdown/metrics.md",
    "content": "---\ntitle: 使用Prometheus监控Caddy\n---\n\n# 使用Prometheus监控Caddy\n\n无论你是在云中运行数千个Caddy实例，还是在嵌入式设备上运行单个Caddy服务器，在某些时候你可能希望对Caddy正在做什么以及它持续了多长时间有一个高度的概览。\n换句话说，你将希望能够 _监控_ Caddy。\n\n## Prometheus\n\n[Prometheus](https://prometheus.io)是一个监控平台，它通过在这些目标上抓取指标 HTTP 端点来收集来自被监控目标的指标。\n除了帮助你使用[Grafana](https://grafana.com/docs/grafana/latest/getting-started/what-is-grafana/)等仪表板工具显示指标外，Prometheus还用于[报警](https://prometheus.io/docs/alerting/latest/overview/)。\n\n与 Caddy 一样，Prometheus 是用 Go 编写的，并作为单个二进制文件分发。要安装它，请参阅[Prometheus安装文档](https://prometheus.io/docs/prometheus/latest/installation/)，或者在MacOS上运行`brew install prometheus`。\n\n如果你是 Prometheus 的新手，请阅读[Prometheus文档](https://prometheus.io/docs/introduction/first_steps/)！\n\n要将 Prometheus 配置为从 Caddy 抓取，你需要一个类似于以下的 YAML 配置文件：\n\n```yaml\n# prometheus.yaml\nglobal:\n  scrape_interval: 15s # default is 1 minute\n\nscrape_configs:\n  - job_name: caddy\n    static_configs:\n      - targets: ['localhost:2019']\n```\n\n然后你可以像这样启动Prometheus：\n\n```console\n$ prometheus --config.file=prometheus.yaml\n```\n\n## Caddy的指标\n\n与使用 Prometheus 监控的任何进程一样，Caddy 公开了一个 HTTP 端点，该端点以[Prometheus展示格式](https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format)进行响应。\nCaddy 的 Prometheus 客户端还配置为在协商后以[OpenMetrics展示格式](https://pkg.go.dev/github.com/prometheus/client_golang@v1.7.1/prometheus/promhttp#HandlerOpts)\n相应(即，如果`Accept`标头设置为`application/openmetrics-text; version=0.0.1`）。\n\n默认情况下，[管理API](/docs/api)上的`/metrics`端点是可用的（即http://localhost:2019/metrics）。\n但是如果管理API被禁用或者你希望在不同的端口或路径上监听，你可以使用[`metrics`处理器](/docs/caddyfile/directives/metrics)来配置它。\n\n你可以使用任何浏览器或HTTP客户端查看指标，例如`curl`：\n\n```console\n$ curl http://localhost:2019/metrics\n# HELP caddy_admin_http_requests_total Counter of requests made to the Admin API's HTTP endpoints.\n# TYPE caddy_admin_http_requests_total counter\ncaddy_admin_http_requests_total{code=\"200\",handler=\"metrics\",method=\"GET\",path=\"/metrics\"} 2\n# HELP caddy_http_request_duration_seconds Histogram of round-trip request durations.\n# TYPE caddy_http_request_duration_seconds histogram\ncaddy_http_request_duration_seconds_bucket{code=\"308\",handler=\"static_response\",method=\"GET\",server=\"remaining_auto_https_redirects\",le=\"0.005\"} 1\ncaddy_http_request_duration_seconds_bucket{code=\"308\",handler=\"static_response\",method=\"GET\",server=\"remaining_auto_https_redirects\",le=\"0.01\"} 1\ncaddy_http_request_duration_seconds_bucket{code=\"308\",handler=\"static_response\",method=\"GET\",server=\"remaining_auto_https_redirects\",le=\"0.025\"} 1\n...\n```\n\n你会看到许多指标，大致分为 3 类：\n\n- 运行时指标\n- 管理API指标\n- HTTP中间件指标\n\n### 运行时指标\n\n些指标涵盖了 Caddy 流程的内部，由 Prometheus Go 客户端自动提供。它们以`go_*`和`process_*`为前缀。\n\n请注意，`process_*`指标仅在Linux和Windows系统上被收集。\n\n请参阅[Go收集器](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#NewGoCollector)、进程收集器](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#NewProcessCollector)和[构建信息收集器](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#NewBuildInfoCollector)文档。\n\n### 管理API指标\n\n这些是有助于监控 Caddy 管理 API 的指标。每个管理端点都经过检测以跟踪请求计数和错误。\n\n这些指标的前缀是`caddy_admin_*`。\n\n例如：\n\n```console\n$ curl -s http://localhost:2019/metrics | grep ^caddy_admin\ncaddy_admin_http_requests_total{code=\"200\",handler=\"admin\",method=\"GET\",path=\"/config/\"} 1\ncaddy_admin_http_requests_total{code=\"200\",handler=\"admin\",method=\"GET\",path=\"/debug/pprof/\"} 2\ncaddy_admin_http_requests_total{code=\"200\",handler=\"admin\",method=\"GET\",path=\"/debug/pprof/cmdline\"} 1\ncaddy_admin_http_requests_total{code=\"200\",handler=\"load\",method=\"POST\",path=\"/load\"} 1\ncaddy_admin_http_requests_total{code=\"200\",handler=\"metrics\",method=\"GET\",path=\"/metrics\"} 3\n```\n\n#### `caddy_admin_http_requests_total`\n\n管理端点处理的请求数的计数器，包括`admin.api.*`命名空间中的模块。\n\n标签  | 描述\n-------|------------\n`code` | HTTP状态码\n`handler` | 处理程序或模块名称\n`method` | HTTP方法\n`path` | 管理端点挂载到的URL路径\n\n#### `caddy_admin_http_request_errors_total`\n\n管理端点中遇到的错误数量的计数器，包括`admin.api.*`命名空间中的模块。\n\n标签  | 描述\n-------|------------\n`handler` | 处理程序或模块名称\n`method` | HTTP方法\n`path` | 管理端点挂载到的URL路径\n\n### HTTP中间件指标\n\n所有 Caddy HTTP中间件处理程序都会自动检测，以确定请求延迟、首字节时间、错误和请求/响应正文大小。\n\n<aside class=\"tip\">\n    因为所有中间件处理程序都经过检测，并且许多请求由多个处理程序处理，所以请确保不要简单地将所有计数器加在一起。\n</aside>\n\n对于下面的直方图指标，存储桶当前不可配置。\n对于持续时间，使用默认的桶集[`prometheus.DefBuckets`](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-variables)（5ms、10ms、25ms、50ms、100ms、250ms、500ms、1s、2.5s、5s 和 10s）。\n对于大小，桶是 256b、1kiB、4kiB、16kiB 、64kiB、256kiB、1MiB 和 4MiB。\n\n#### `caddy_http_requests_in_flight`\n\n衡量此服务器当前正在处理的请求数。\n\n标签  | 描述\n-------|------------\n`server` | 服务器名称\n`handler` | 处理程序或模块名称\n\n#### `caddy_http_request_errors_total`\n\n处理请求时遇到的中间件错误计数器。\n\n标签  | 描述\n-------|------------\n`server` | 服务器名称\n`handler` | 处理程序或模块名称\n\n#### `caddy_http_requests_total`\n\n发出的 HTTP(S) 请求的计数器。\n\n标签  | 描述\n-------|------------\n`server` | 服务器名称\n`handler` | 处理程序或模块名称\n\n#### `caddy_http_request_duration_seconds`\n\n往返请求持续时间的直方图。\n\n标签  | 描述\n-------|------------\n`server` | 服务器名称\n`handler` | 处理程序或模块名称\n`code` | HTTP 状态码\n`method` | HTTP 方法\n\n#### `caddy_http_request_size_bytes`\n\n请求的总（估计）大小的直方图。包括请求包体。\n\n标签  | 描述\n-------|------------\n`server` | 服务器名称\n`handler` | 处理程序或模块名称\n`code` | HTTP状态码\n`method` | HTTP方法\n\n#### `caddy_http_response_size_bytes`\n\n返回的响应正文大小的直方图。\n\n标签  | 描述\n-------|------------\n`server` | 服务器名称\n`handler` | 处理程序或模块名称\n`code` | HTTP状态码\n`method` | HTTP方法\n\n#### `caddy_http_response_duration_seconds`\n\n响应的第一个字节的时间直方图。\n\n标签  | 描述\n-------|------------\n`server` | 服务器名称\n`handler` | 处理程序或模块名称\n`code` | HTTP状态码\n`method` | HTTP方法\n\n## 示例查询\n\n一旦你让Prometheus抓取Caddy的指标，你就可以开始看到一些关于Caddy表现的有趣指标。\n\n<aside class=\"tip\">\n    如果你已启动Prometheus服务器以使用上述配置抓取 Caddy，请尝试将这些查询粘贴到位于Prometheus UI的<a href=\"http://localhost:9090/graph\">http://localhost:9090/graph</a>\n</aside>\n\n例如，要查看每秒请求率，平均超过 5 分钟：\n\n```\nrate(caddy_http_requests_total{handler=\"file_server\"}[5m])\n```\n\n要查看超过 100 毫秒延迟阈值的速率：\n\n```\nsum(rate(caddy_http_request_duration_seconds_count{server=\"srv0\"}[5m])) by (handler)\n-\nsum(rate(caddy_http_request_duration_seconds_bucket{le=\"0.100\", server=\"srv0\"}[5m])) by (handler)\n```\n\n要在`file_server`处理程序上查找占95%请求的持续时间，你可以使用如下查询：\n\n```\nhistogram_quantile(0.95, sum(caddy_http_request_duration_seconds_bucket{handler=\"file_server\"}) by (le))\n```\n\n或者查看`file_server`处理程序上`GET`请求成功的中位响应大小（以字节为单位）：\n\n```\nhistogram_quantile(0.5, caddy_http_response_size_bytes_bucket{method=\"GET\", handler=\"file_server\", code=\"200\"})\n```\n"
  },
  {
    "path": "src/docs/markdown/modules.md",
    "content": "<div id=\"module-list-container\">\n\t<h1>全部模块</h1>\n\t<p>\n\t\t本页列出所有已注册的Caddy模块。模块是用于扩展Caddy<a href=\"/docs/json\">JSON配置结构</a>的插件。\n\t</p>\n\t<p>\n\t\t建议使用浏览器的“在页面中查找”功能快速定位。\n\t</p>\n\t<table id=\"module-list\">\n\t\t<tr>\n\t\t\t<th></th>\n\t\t\t<th>模块ID</th>\n\t\t\t<th>描述</th>\n\t\t</tr>\n\t\t<!--由JS填充-->\n\t</table>\n</div>\n\n<div id=\"module-docs-container\">\n\t<div class=\"pad\"><h1 class=\"module-name\"><!--由JS填充--></h1></div>\n\t<div id=\"module-multiple-repos\">\n\t\t存在多个名为<b class=\"module-name\"><!--由JS填充--></b>的模块，请按仓库进行选择。\n\t</div>\n\t<div id=\"module-template\" class=\"module-repo-container\">\n\t\t<div class=\"module-repo-selector\"></div>\n\t\t<article>\n\t\t\t{{include \"/includes/docs/renderbox.html\"}}\n\t\t\t{{include \"/includes/docs/details.html\"}}\n\t\t</article>\n\t</div>\n</div>\n\n{{include \"/includes/docs/hovercard.html\"}}\n\n"
  },
  {
    "path": "src/docs/markdown/profiling.md",
    "content": "---\ntitle: Caddy性能剖析\n---\n\nCaddy性能剖析\n================\n\n**程序剖析（profile）**是程序运行时资源使用情况的快照。它对定位问题区域、排查bug/崩溃、优化代码非常有帮助。\n\nCaddy使用Go自带的剖析工具[pprof](https://github.com/google/pprof)采集profile。\n\nprofile可以反映CPU与内存消耗、goroutine栈信息，也有助于定位死锁或高竞争同步点。\n\n当你向Caddy报告某些问题时，我们可能会要求提供profile。本文说明如何在Caddy中采集profile，以及如何大致阅读pprof输出。\n\n开始前先知道两点：\n\n1. **Caddy profile不是安全敏感信息。**它包含技术统计信息，不包含内存内容，不会赋予系统访问权限，可安全分享。\n2. **profile开销较轻，可在生产环境采集。**很多用户都这么做，这是推荐实践。\n\n## 获取profile\n\nprofile可通过[管理接口](/docs/api)的`/debug/pprof/`访问。在运行Caddy的机器上打开：\n\n```\nhttp://localhost:2019/debug/pprof/\n```\n\n<aside class=\"tip\">\n\t默认情况下，管理API仅允许本机访问。若在远程主机、VM或容器中运行，请看后文“远程访问”章节。\n</aside>\n\n你会看到一个简单列表，例如`allocs`、`goroutine`、`heap`、`profile`等。\n\n这些计数可用于快速识别泄漏：持续刷新页面，如果某个计数一直上涨，就可能有泄漏。例如`heap`增长可能是内存泄漏，`goroutine`增长可能是协程泄漏。\n\n常用的profile是：\n\n- **goroutine**：函数栈\n- **heap**：内存\n- **profile**：CPU\n\n其余profile可用于分析锁竞争、阻塞、死锁等问题。\n\n### 下载profile\n\npprof索引页里直接点击链接通常是文本格式（便于快速阅读）；但二进制才是默认格式。索引页多数链接会附带`?debug=`把它变成文本，CPU profile（`profile`）没有文本表示。\n\n常见查询参数（见[Go文档](https://pkg.go.dev/net/http/pprof#hdr-Parameters)）：\n\n- **`debug=N`（除CPU外）**：N=0返回二进制（默认）；N>0返回纯文本\n- **`gc=N`（heap）**：N>0表示采样前先执行一次GC\n- **`seconds=N`（allocs/block/goroutine/heap/mutex/threadcreate）**：返回差分profile\n- **`seconds=N`（cpu/trace）**：采样持续N秒\n\n这些都是HTTP端点，所以你也可以用`curl`/`wget`下载。\n\n下载后可上传到GitHub issue评论，或用[pprof.me](https://pprof.me/)在线查看。CPU profile也可用[flamegraph.com](https://flamegraph.com/)。\n\n## 远程访问\n\n_如果你已能本地访问管理API，可跳过本节。_\n\n默认管理API只监听回环地址。你可用以下方式远程访问`/debug/pprof`：\n\n### 通过站点反向代理\n\n```caddy-d\nreverse_proxy /debug/pprof/* localhost:2019 {\n\theader_up Host {upstream_hostport}\n}\n```\n\n这会把profile暴露给可访问站点的人。若不希望公开，请增加认证模块。\n\n（注意必须加`/debug/pprof/*`匹配器，否则会代理整个管理API。）\n\n### SSH隧道\n\n在本机执行：\n\n<pre><code class=\"cmd bash\">ssh -N username@example.com -L 8123:localhost:2019</code></pre>\n\n它会把你本地`localhost:8123`转发到远端`example.com`上的`localhost:2019`。\n\n然后在另一个终端请求：\n\n<pre><code class=\"cmd bash\">curl -v http://localhost:8123/debug/pprof/ -H \"Host: localhost:2019\"</code></pre>\n\n如两端都使用2019端口（且本地未占用），可省略`Host`头。\n\n如果想后台长期运行隧道：\n\n<pre><code class=\"cmd bash\">ssh -f -N -M -S /tmp/caddy-tunnel.sock username@example.com -L 8123:localhost:2019</code></pre>\n\n关闭隧道：\n\n<pre><code class=\"cmd bash\">ssh -S /tmp/caddy-tunnel.sock -O exit e</code></pre>\n\n### 远程管理API\n\n你也可以把管理API配置为允许授权客户端远程访问。（待补充专门文章。）\n\n## goroutine profile\n\ngoroutine dump用于查看当前有哪些goroutine及其调用栈，即哪些代码正在执行或阻塞等待。\n\n访问`/debug/pprof/goroutine?debug=1`会看到按调用栈聚合的输出。首行例如：\n\n```\ngoroutine profile: total 88\n```\n\n含义是当前总共有88个goroutine。\n\n后续goroutine条目格式大致为：\n\n```\n<count> @ <addresses...>\n```\n\n- `<count>`：拥有相同调用栈的goroutine数量\n- `@`后地址：函数调用地址（调用帧）\n\n以`#`开头的行是给读者看的注释，显示当前栈轨迹，格式为：\n\n```\n<address> <package/func>+<offset> <filename>:<line>\n```\n\n### 完整goroutine dump\n\n使用`?debug=2`可获取完整dump：每个goroutine都展开，不合并同栈条目，输出会很大。\n\n完整dump中每个goroutine首行还会提供：\n\n- goroutine编号\n- 状态（如`running`、`IO wait`、`select`、`chan receive`、`semacquire`、`syscall`等）\n- 存活时长\n\n这些信息对于排查goroutine泄漏、连接堆积等问题很有价值。\n\n## 内存profile\n\n内存（heap）profile追踪堆分配，是系统内存消耗的核心来源之一。频繁分配也是常见性能问题来源。\n\nheap输出与goroutine输出类似，首行格式一般是：\n\n```\n<live objects> <live memory> [<allocations>: <allocation memory>] @ <addresses...>\n```\n\n通过调用栈你通常可以判断是哪条热点路径产生了大量分配，以及是否有对象池在生效。\n\n## CPU profile\n\nCPU profile用于分析Go程序把CPU时间花在了哪里。\n\nCPU profile没有纯文本格式。下载方式：\n\n`/debug/pprof/profile?seconds=N`\n\n采样N秒后会得到二进制文件（常见文件名为`profile`）。采样期间可能有轻微性能影响（其他profile影响通常很小）。\n\n## `go tool pprof`\n\n使用Go内置分析器读取profile（不仅限CPU）：\n\n<pre><code class=\"cmd bash\">go tool pprof profile</code></pre>\n\n进入交互模式后，常用命令：\n\n- `top`：查看最耗资源的函数\n- `web`：浏览器打开调用图\n- `svg`：生成调用图SVG\n- `tree`：表格形式查看调用树\n\n例如：\n\n```\n(pprof) top\n```\n\n你可进一步过滤，例如忽略`runtime`查看业务代码热点：\n\n```\n(pprof) top -runtime\n```\n\n<aside class=\"tip\">\nCPU profile基于采样（默认采样间隔10ms），因此结果是统计近似值。若要更细粒度时序，可使用trace。\n</aside>\n\n## 可视化\n\n执行`svg`或`web`可得到图形化视图：\n\n![CPU profile visualization](/old/resources/images/profile.png)\n\n如何阅读图请参考[pprof文档](https://github.com/google/pprof/blob/main/doc/README.md#interpreting-the-callgraph)。\n\n## 对比profile（Diff）\n\n改完代码后，可用前后profile做差分分析：\n\n<pre><code class=\"cmd bash\">go tool pprof -diff_base=before.prof after.prof</code></pre>\n\n在交互里再执行`top`可看到增减最明显的函数。\n\n差分同样可视化：\n\n![CPU profile visualization](/old/resources/images/profile-diff.png)\n\n这有助于直观看到改动对性能的影响。\n\n## 延伸阅读\n\n- [pprof Documentation](https://github.com/google/pprof/blob/main/doc/README.md)\n- [A real-world use of profiles with Caddy](https://github.com/caddyserver/caddy/pull/4978)\n- [Performance on the Go wiki](https://github.com/golang/go/wiki/Performance)\n- [The `net/http/pprof` package](https://pkg.go.dev/net/http/pprof)\n\n"
  },
  {
    "path": "src/docs/markdown/quick-starts/api.md",
    "content": "---\ntitle: API快速入门\n---\n\n# API快速入门\n\n**前提条件：**\n- 基本的终端/命令行技能\n- `caddy`和`curl`已在你的PATH中\n\n---\n\n先启动Caddy：\n\n<pre><code class=\"cmd bash\">caddy start</code></pre>\n\nCaddy当前处于空闲状态（配置为空白）。用`curl`给它一个简单配置：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/load \\\n    -H \"Content-Type: application/json\" \\\n    -d @- << EOF\n    {\n        \"apps\": {\n            \"http\": {\n                \"servers\": {\n                    \"hello\": {\n                        \"listen\": [\":2015\"],\n                        \"routes\": [\n                            {\n                                \"handle\": [{\n                                    \"handler\": \"static_response\",\n                                    \"body\": \"Hello, world!\"\n                                }]\n                            }\n                        ]\n                    }\n                }\n            }\n        }\n    }\nEOF</code></pre>\n\n使用[Heredoc](https://en.wikipedia.org/wiki/Here_document#Unix_shells)提供POST请求体可能有点繁琐；如果你更喜欢用文件，可以把JSON保存为`caddy.json`，然后改用这个命令：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/load \\\n  -H \"Content-Type: application/json\" \\\n  -d @caddy.json\n</code></pre>\n\n现在在浏览器访问[localhost:2015](http://localhost:2015)，或使用`curl`：\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl localhost:2015</span>\nHello, world!</code></pre>\n\n我们还可以用这段JSON在不同接口上定义多个站点：\n\n```json\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"hello\": {\n\t\t\t\t\t\"listen\": [\":2015\"],\n\t\t\t\t\t\"routes\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"handle\": [{\n\t\t\t\t\t\t\t\t\"handler\": \"static_response\",\n\t\t\t\t\t\t\t\t\"body\": \"Hello, world!\"\n\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t\"bye\": {\n\t\t\t\t\t\"listen\": [\":2016\"],\n\t\t\t\t\t\"routes\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"handle\": [{\n\t\t\t\t\t\t\t\t\"handler\": \"static_response\",\n\t\t\t\t\t\t\t\t\"body\": \"Goodbye, world!\"\n\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n更新你的JSON后，再执行一次API请求。\n\n在浏览器中访问新的“goodbye”端点（[localhost:2016](http://localhost:2016)），或用`curl`确认其可用：\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl localhost:2016</span>\nGoodbye, world!</code></pre>\n\n使用完Caddy后，记得停止它：\n\n<pre><code class=\"cmd bash\">caddy stop</code></pre>\n\nAPI还能做更多事，包括导出配置、以及对配置做细粒度变更（而不是整份替换）。请继续阅读[完整API教程](/docs/api-tutorial)。\n\n## 进一步阅读\n\n- [完整API教程](/docs/api-tutorial)\n- [API文档](/docs/api)\n"
  },
  {
    "path": "src/docs/markdown/quick-starts/caddyfile.md",
    "content": "---\ntitle: Caddyfile 快速入门\n---\n\n# Caddyfile 快速入门\n\n创建一个名为Caddyfile（无扩展名）的新文本文件。\n\n最先在Caddyfile输入的内容是你的站点访问地址：\n\n```caddy\nlocalhost\n```\n\n<aside class=\"tip\">\n    如果HTTP和HTTPS端口（分别为80和443）是你操作系统上的特权端口，你将需要以提升的特权运行或使用更高的端口。要获得权限，可以使用<code>sudo -E</code>以root身份运行，或运行<code>sudo setcap cap_net_bind_service=+ep $(which caddy)</code>。或者使用更高端口：将地址改为类似<code>localhost:2080</code>，并通过Caddyfile的<a href=\"/docs/caddyfile/options\">http_port</a>选项修改HTTP端口。\n</aside>\n\n然后按回车键并输入你想要它做的事情，所以它看起来像这样：\n\n```caddy\nlocalhost\n\nrespond \"Hello, world!\"\n```\n\n保存并从Caddyfile所在的同一文件夹中运行Caddy：\n\n<pre><code class=\"cmd bash\">caddy start</code></pre>\n\n你可能会被要求输入密码，因为默认情况下，Caddy 通过 HTTPS 为所有站点（甚至本地站点）提供服务。（密码提示应该只在第一次出现！）\n\n<aside class=\"tip\">\n    对于本地 HTTPS，Caddy 会自动为你生成证书和唯一的私钥。根证书被添加到系统的信任库中，这就是密码提示的必要性。它允许你通过 HTTPS 在本地进行开发而不会出现证书错误。\n</aside>\n\n如果你收到权限错误，可能需要提升权限再次运行。\n\n打开浏览器访问[localhost](http://localhost)或者使用`curl`运行：\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl https://localhost</span>\nHello, world!</code></pre>\n\n你可以通过将它们包裹在花括号`{ }`中来在Caddyfile中定义多个站点。将Caddyfile 更改为：\n\n```caddy\nlocalhost {\n\trespond \"Hello, world!\"\n}\n\nlocalhost:2016 {\n\trespond \"Goodbye, world!\"\n}\n```\n\n你可以通过两种方式为Caddy提供更新的配置：直接使用API：\n\n<pre><code class=\"cmd bash\">curl localhost:2019/load \\\n\t-X POST \\\n\t-H \"Content-Type: text/caddyfile\" \\\n\t--data-binary @Caddyfile\n</code></pre>\n\n或使用`reload`命令，它会为你执行相同的API请求：\n\n<pre><code class=\"cmd bash\">caddy reload</code></pre>\n\n在[浏览器](https://localhost:2016)尝试访问新的“goodbye”端点，或者使用`curl`以确保它正常工作：\n\n<pre><code class=\"cmd\"><span class=\"bash\">curl https://localhost:2016</span>\nGoodbye, world!</code></pre>\n\n完成 Caddy 后，请务必停止它：\n\n<pre><code class=\"cmd bash\">caddy stop</code></pre>\n\n## 进一步阅读\n\n- [常见模式](/docs/caddyfile/patterns)\n- [Caddyfile概念](/docs/caddyfile/concepts)\n- [指令](/docs/caddyfile/directives)\n"
  },
  {
    "path": "src/docs/markdown/quick-starts/https.md",
    "content": "---\ntitle: HTTPS快速入门\n---\n\n# HTTPS快速入门\n\n本指南将向你展示如何立即启动并运行[完全托管的HTTPS](/docs/automatic-https)。\n\n<aside class=\"tip\">\n    Caddy默认对所有站点使用HTTPS，只要在配置中提供了主机名。本教程假设你希望通过HTTPS获得一个公共信任的站点（即不是“localhost”），因此我们将使用公共域名和外部端口。\n</aside>\n\n**先决条件：**\n- 基本的终端/命令行技能\n- 对DNS的基本了解\n- 已注册的公共域名\n- 外部访问端口 80 和 443\n- PATH变量中包含`caddy`和`curl`\n\n---\n\n在本教程中，替换`example.com`为你的实际域名。\n\n将你域的`A/AAAA`记录指向你的服务器。你可以通过登录你的DNS提供商并管理你的域名来做到这一点。\n\n在继续之前，请使用权威查找验证正确的记录。替换`example.com`为你的域名，如果你使用的是`IPv6`，请替换`type=A`为`type=AAAA`：\n\n<pre><code class=\"cmd bash\">curl \"https://cloudflare-dns.com/dns-query?name=example.com&type=A\" \\\n  -H \"accept: application/dns-json\"</code></pre>\n\n还要确保你的服务器可从公共接口通过端口`80`和`443`进行外部访问。\n\n<aside class=\"tip\">\n    如果你在家庭或其他受限网络上，你可能需要转发端口或调整防火墙设置。\n</aside>\n\n我们所要做的就是在配置中使用你的域名启动Caddy。有几种方法可以做到这一点。\n\n## Caddyfile\n\n这是获取HTTPS最常用的方法。\n\n创建一个名为`Caddyfile`（无扩展名）的文件，其中第一行是你的域名，例如：\n\n```caddy\nexample.com\n\nrespond \"Hello, privacy!\"\n```\n\n然后从同一目录运行：\n\n<pre><code class=\"cmd bash\">caddy run</code></pre>\n\n你将看到Caddy提供TLS证书并通过HTTPS为你的站点提供服务。这是可能的，因为你的站点在Caddyfile中的地址包含一个域名。\n\n## `file-server`命令\n\n如果你只需要通过 HTTPS 提供静态文件，请运行以下命令（替换你的域名）：\n\n<pre><code class=\"cmd bash\">caddy file-server --domain example.com</code></pre>\n\n你将看到Caddy提供TLS证书并通过HTTPS为你的站点提供服务。\n\n\n## `reverse-proxy`命令\n\n如果你只需要一个基于HTTPS的简单反向代理（作为TLS终结器），请运行以下命令（替换你的域名和实际后端地址）：\n\n<pre><code class=\"cmd bash\">caddy reverse-proxy --from example.com --to localhost:9000</code></pre>\n\n你将看到Caddy提供TLS证书并通过HTTPS为你的站点提供服务。\n\n## JSON配置\n\n一般的经验法则是任何[主机匹配器](/docs/json/apps/http/servers/routes/match/host/)都会触发自动HTTPS。\n\n因此，如下所示的JSON配置将启用生产就绪的[自动HTTPS](/docs/automatic-https)：\n\n```json\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"hello\": {\n\t\t\t\t\t\"listen\": [\":443\"],\n\t\t\t\t\t\"routes\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"match\": [{\n\t\t\t\t\t\t\t\t\"host\": [\"example.com\"]\n\t\t\t\t\t\t\t}],\n\t\t\t\t\t\t\t\"handle\": [{\n\t\t\t\t\t\t\t\t\"handler\": \"static_response\",\n\t\t\t\t\t\t\t\t\"body\": \"Hello, privacy!\"\n\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n```\n"
  },
  {
    "path": "src/docs/markdown/quick-starts/railway.md",
    "content": "---\ntitle: Railway快速入门\n---\n\n# Railway快速入门\n\n在Railway上部署Caddy是一种简单、无负担的方式，可部署带插件的自定义Caddy构建。\n\n**前置条件：**\n- 一个免费的[Railway](https://railway.com)账号\n\n## 在Railway上部署Caddy\n\n前往我们的[下载页](/download)，勾选所需插件，然后点击顶部紫色按钮“Deploy on Railway”。\n\n<details>\n\t<summary>或者手动配置模板</summary>\n\n如果你希望自行配置Railway模板，可按以下步骤操作。\n\n前往Railway模板页：\n\n<a href=\"https://railway.com/deploy/caddy?referralCode=YOPtw9&amp;utm_medium=integration&amp;utm_source=template&amp;utm_campaign=generic\"><img src=\"https://railway.com/button.svg\" alt=\"Deploy on Railway\"></a>\n\n点击“Configure”添加所需插件：\n\n![Deploy screen](/resources/images/railway/deploy-screen.png)\n\n然后把插件以空格分隔，粘贴到`CADDY_PLUGINS`变量中：\n\n![Adding plugins](/resources/images/railway/deploy-config.png)\n\n</details>\n\n点击Deploy。部署完成后，可点击这里的链接访问：\n\n![Visit your deployment](/resources/images/railway/prod-link.png)\n\n你应该能看到欢迎页面，表示新服务已正常运行。\n\n接下来你可以把部署改成托管自己的网站，或反向代理到另一个Railway服务。\n\n## 自定义部署\n\n要托管自己的网站或修改配置，可以把[模板](https://railway.com/deploy/caddy?referralCode=YOPtw9&amp;utm_medium=integration&amp;utm_source=template&amp;utm_campaign=generic)“eject”到你自己的仓库：\n\n![Eject template](/resources/images/railway/eject.png)\n\n在你自己的仓库中，可以：\n\n- 把网站文件放到`www`目录。\n- 修改Caddy配置，即[Caddyfile](/docs/caddyfile)。\n\n提交并推送后，即可在Railway重新部署。\n\n如果你要调整Caddy构建中的插件，只需修改`CADDY_PLUGINS`变量并重新部署：\n\n![Change plugins](/resources/images/railway/plugins-variable.png)\n\n## 提示\n\nRailway会替你终止TLS，所以应按“被代理后的后端服务”来编写Caddy配置（实际也是如此）。因此，如果你在Caddyfile站点地址中使用主机名，建议在全局选项中设置`auto_https off`。在这个模板中，Caddy并非边缘入口。\n\n## 变量\n\n你可在Railway项目中设置以下环境变量（模板会使用）：\n\nName | Description | Default | Example(s)\n---- | ----------- | ------- | ----------\n`CADDY_PLUGINS` | 用空格分隔的Caddy插件列表 | ` ` | `github.com/caddy-dns/cloudflare github.com/mholt/caddy-ratelimit`\n\n"
  },
  {
    "path": "src/docs/markdown/quick-starts/reverse-proxy.md",
    "content": "---\ntitle: 反向代理快速入门\n---\n\n# 反向代理快速入门\n\n本指南将向你展示如何快速启动并运行可用于生产的反向代理。\n\n**先决条件：**\n- 基本的终端/命令行技能\n- PATH变量支持`caddy`\n- 要代理到的正在运行的后端进程\n\n---\n\n本教程假设你有一个运行在`127.0.0.1:9000`的HTTP后端服务。以下命令以Linux为例，其它操作系统原理相同。\n你可以不使用配置文件快速启动反向代理，也可以使用配置文件获得更高灵活性和可控性。\n\n## 命令行\n\n要在本机上启动一个明文HTTP代理（从2080端口到9000端口），运行：\n\n<pre><code class=\"cmd bash\">caddy reverse-proxy --from :2080 --to :9000</code></pre>\n\n然后测试：\n\n<pre><code class=\"cmd bash\">curl -v 127.0.0.1:2080</code></pre>\n\n[`reverse-proxy`命令](/docs/command-line#reverse-proxy)用于快速搭建简单反向代理（需求简单时也可用于生产）。\n\n## Caddyfile\n\n在当前工作目录中，创建一个名为`Caddyfile`的文件，内容如下:\n\n```caddy\n:2080\n\nreverse_proxy :9000\n```\n\n这个配置与上面的`caddy reverse-proxy`命令大致等价。\n\n然后，从同一目录执行如下命令：\n\n<pre><code class=\"cmd bash\">caddy run</code></pre>\n\n然后测试代理：\n\n<pre><code class=\"cmd bash\">curl -v 127.0.0.1:2080</code></pre>\n\n如果你修改了Caddyfile，请确保[重载](/docs/command-line#caddy-reload)Caddy。\n\n你可以使用[`reverse_proxy`指令](/docs/caddyfile/directives/reverse_proxy)完成更多操作。\n\n## 从客户端到代理的HTTPS\n\n如果Caddy知道主机名（域名），它会自动且默认通过[HTTPS](/docs/automatic-https)提供代理服务。`caddy reverse-proxy`命令在省略`--from`时默认使用`localhost`，你也可以在Caddyfile第一行填写代理域名。\n\n- 使用`localhost`或任意`.localhost`结尾的域名时，Caddy会使用自动续期的自签证书。首次使用时可能需要输入密码，以便将其CA根证书安装到系统信任库。\n- 使用其它域名时，Caddy会尝试申请公共信任证书；请确保DNS记录指向当前机器，并且80/443端口对公网开放并转发到Caddy。\n\n如果未显式指定端口，HTTPS默认使用443，这要求你有绑定低端口的权限。在Linux上常见方式：\n\n- 以root运行（例如`sudo -E`）。\n- 或运行`sudo setcap cap_net_bind_service=+ep $(which caddy)`为Caddy授予该能力。\n\n最基础的HTTPS命令示例：\n\n<pre><code class=\"cmd bash\">caddy reverse-proxy --to :9000</code></pre>\n\n然后测试：\n\n<pre><code class=\"cmd bash\">curl -v https://localhost</code></pre>\n\n你可以用`--from`自定义主机名：\n\n<pre><code class=\"cmd bash\">caddy reverse-proxy --from example.com --to :9000</code></pre>\n\n如果没有低端口权限，可以改成高端口：\n\n<pre><code class=\"cmd bash\">caddy reverse-proxy --from example.com:8443 --to :9000</code></pre>\n\n在Caddyfile中，只需把第一行改成你的域名，例如：\n\n```caddy\nexample.com\n\nreverse_proxy :9000\n```\n\n## 从代理到后端的HTTPS\n\n如果后端支持TLS，Caddy也可以与后端通过HTTPS通信；只需在后端地址使用`https://`：\n\n<pre><code class=\"cmd bash\">caddy reverse-proxy --from :2080 --to https://localhost:9000</code></pre>\n\n这要求后端证书被Caddy所在系统信任（默认不会信任自签证书，除非显式配置）。\n\n当然，也可以两端都用HTTPS：\n\n<pre><code class=\"cmd bash\">caddy reverse-proxy --from example.com --to https://example.com:9000</code></pre>\n\n如果“代理来源主机名”与“代理目标主机名”不同，需要加`--change-host-header`：\n\n<pre><code class=\"cmd bash\">caddy reverse-proxy \\\n\t--from example.com \\\n\t--to https://localhost:9000 \\\n\t--change-host-header</code></pre>\n\n默认情况下，Caddy会原样透传HTTP请求头（包括`Host`），且TLS握手中的ServerName默认来自Host头。`--change-host-header`会把Host改为后端主机名，以便TLS握手成功。\n"
  },
  {
    "path": "src/docs/markdown/quick-starts/static-files.md",
    "content": "---\ntitle: 静态文件快速入门\n---\n\n# 静态文件快速入门\n\n本指南将向你展示如何快速启动并运行可用于生产的静态文件服务器。\n\n**先决条件：**\n- 基本的终端/命令行技能\n- PATH变量包含`caddy`\n- 包含你的网站的目录\n\n---\n\n有两种简单的方法可以让快速文件服务器启动并运行。我们将向你展示两种等效的方法来做同样的事情。\n\n## 命令行\n\n在你的终端中，切换到站点的根目录并运行：\n\n<pre><code class=\"cmd bash\">caddy file-server</code></pre>\n\n如果你收到权限错误，这可能意味着你的操作系统不允许你绑定到低端口——因此请改用高端口：\n\n<pre><code class=\"cmd bash\">caddy file-server --listen :2015</code></pre>\n\n然后在浏览器中打开[localhost](http://localhost)（或[localhost:2015](http://localhost:2015)）访问你的站点！\n\n如果你没有索引文件但想要显示文件列表，请使用以下`--browse`选项：\n\n<pre><code class=\"cmd bash\">caddy file-server --browse</code></pre>\n\n你可以使用另一个文件夹作为站点根目录：\n\n<pre><code class=\"cmd bash\">caddy file-server --root ~/mysite</code></pre>\n\n\n\n## Caddyfile\n\n在你站点的根目录中，创建一个名为`Caddyfile`的文件，内容如下：\n\n```caddy\nlocalhost\n\nfile_server\n```\n\n如果你无权绑定到低端口，请替换`localhost`为`localhost:2015`（或其他一些高端口）。\n\n然后，从同一目录运行：\n\n<pre><code class=\"cmd bash\">caddy run</code></pre>\n\n然后，你可以访问[localhost](https://localhost)（或配置中的任何地址）来查看你的站点！\n\n该 [`file_server`指令](/docs/caddyfile/directives/file_server)有更多选项供你自定义站点。更改Caddyfile后，请确保[重新加载](/docs/command-line#caddy-reload)Caddy（或停止并重新启动）！\n\n如果你没有索引文件但想要显示文件列表，请使用以下`browse`参数：\n\n```caddy\nlocalhost\n\nfile_server browse\n```\n\n你还可以使用另一个文件夹作为站点根目录：\n\n```caddy\nlocalhost\n\nroot * /home/me/mysite\nfile_server\n```\n"
  },
  {
    "path": "src/docs/markdown/quick-starts.md",
    "content": "---\ntitle: 快速入门\n---\n\n# 快速入门\n\n这些教程的目的是让你能快速上手并运行，而不需要做太多的解释。\n\n强烈建议你继续阅读其他教程和参考文档，以完全理解web服务器的工作方式。\n\n## 菜单\n\n* [使用API](/docs/quick-starts/api)\n* [使用Caddyfile](/docs/quick-starts/caddyfile)\n* [静态文件](/docs/quick-starts/static-files)\n* [反向代理](/docs/quick-starts/reverse-proxy)\n* [HTTPS](/docs/quick-starts/https)\n* [Railway快速入门](/docs/quick-starts/railway)\n"
  },
  {
    "path": "src/docs/markdown/running.md",
    "content": "---\ntitle: 保持Caddy运行\n---\n\n# 保持Caddy运行\n\n虽然Caddy可以通过直接使用它的[命令行界面](/docs/command-line)成功运行，但使用服务管理器来保持它的运行有很多好处，例如确保它在系统启动时重新启动，并捕捉stdout/stderr日志。\n\n- [Linux服务](#linux-service)\n  - [单元文件](#unit-files)\n  - [使用服务](#using-the-service)\n  - [手动安装](#manual-installation)\n  - [重写](#overrides)\n- [Windows服务](#windows-service)\n- [Docker Compose](#docker-compose)\n\n\n<h2 id=\"linux-service\">Linux服务</h2>\n\n推荐使用我们的官方systemd单元文件，在带有systemd的Linux发行版上运行Caddy。\n\n<h3 id=\"unit-files\">单元文件</h3>\n\n我们提供了两个不同的systemd单元文件，你可以根据你的使用情况来选择:\n\n- 使用[Caddyfile]（/docs/caddyfile）配置Caddy：[**`caddy.service`**](https://github.com/caddyserver/dist/blob/master/init/caddy.service)。如果你喜欢使用JSON配置文件，你可以[覆盖](#overrides) `ExecStart`和`ExecReload`命令。\n\n- 通过[API](/docs/api)配置Caddy：[**`caddy-api.service`**](https://github.com/caddyserver/dist/blob/master/init/caddy-api.service)。 这项服务使用[`--resume`](/docs/command-line#caddy-run)选项，它将使用默认[保存(persisted)](/docs/json/admin/config/)的`autosave.json`启动Caddy。\n\n它们非常相似，但在`ExecStart`和`ExecReload`命令中有所不同，以适应工作流程。\n\n如果需要在服务之间切换，应该先禁用和停止前一个服务，然后再启用和启动另一个服务。例如，要从`caddy`服务切换到`caddy-api`服务：\n\n```bash\nsudo systemctl disable --now caddy\nsudo systemctl enable --now caddy-api\n```\n\n\n<h3 id=\"using-the-service\">使用服务</h3>\n\n如果使用Caddyfile，你可以用`nano`，`vi`或者你喜欢的编辑器来编辑你的配置：\n```bash\nsudo nano /etc/caddy/Caddyfile\n```\n你可以把你的静态站点文件放在`/var/www/html`或`/srv`中。确保caddy用户有读取文件的权限。\n\n查询服务是否正在运行：\n```bash\nsystemctl status caddy\n```\n\nstatus命令还将显示当前运行的服务文件的位置。\n\n当使用我们的官方服务文件运行时，Caddy的输出将被重定向到`journalctl`。为了阅读你的完整日志，避免行数被截断：\n```bash\njournalctl -u caddy --no-pager | less +G\n```\n\n如果使用一个配置文件，你可以在做任何改变后优雅地重新加载Caddy：\n```bash\nsudo systemctl reload caddy\n```\n\n你可以使用下面的命令停止服务：\n```bash\nsudo systemctl stop caddy\n```\n\n<aside class=\"advice\">\n\t不要直接停止服务然后再来修改Caddy的配置，因为这会产生停机时间。请使用reload命令。\n</aside>\n\nCaddy进程将作为`caddy`用户运行，它的`$HOME`设置为`/var/lib/caddy`。这意味着。\n- 默认的[数据存储位置]（/docs/conventions#data-directory）（用于证书和其他状态信息）将在`/var/lib/caddy/.local/share/caddy`。\n- 默认的[配置存储位置](/docs/conventions#configuration-directory)(用于自动保存的JSON配置，主要对`caddy-api`服务有用)将在`/var/lib/caddy/.config/caddy`。\n\n\n<h3 id=\"manual-installation\">手动安装</h3>\n\n一些[安装方法](/docs/install)自动设置Caddy作为服务运行。如果你选择的方法没有这样做，你可以按照这些指示来做。\n\n**要求：**\n\n- [下载](/download)或[从源代码构建](/docs/build)的`caddy`二进制文件\n- `systemctl --version`版本至少为232\n- `sudo`权限\n\n把caddy二进制文件移到`$PATH`目录，比如：\n```bash\nsudo mv caddy /usr/bin/\n```\n\n检查是否能正常工作：\n```bash\ncaddy version\n```\n\n创建一个叫`caddy`的群组：\n```bash\nsudo groupadd --system caddy\n```\n\n创建一个有可写权限家目录的`caddy`用户：\n```bash\nsudo useradd --system \\\n    --gid caddy \\\n    --create-home \\\n    --home-dir /var/lib/caddy \\\n    --shell /usr/sbin/nologin \\\n    --comment \"Caddy web server\" \\\n    caddy\n```\n\n如果有配置文件，需要确保`caddy`用户具有刻度权限。\n\n接下来，选择一个满足使用需求的[systemd单元文件](#unit-files)。\n\n**仔细检查`ExecStart`和`ExecReload`指令。** 确保二进制文件的位置和命令行参数对你的安装来说是正确的！例如：如果使用一个配置文件，如果你的`--config`路径与默认值不同，请改变它。\n\n通常保存服务文件的地方是： `/etc/systemd/system/caddy.service`。\n\n保存好服务文件后，你可以用通常的systemctl方式来首次启动服务。\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl enable --now caddy\n```\n\n验证它是否正在运行。\n```bash\nsystemctl status caddy\n```\n\n现在，你已经准备好[使用服务](#using-the-service)了\n\n\n<h3 id=\"overrides\">覆盖</h3>\n\n覆盖服务文件各个方面的最好方法是使用这个命令：\n```bash\nsudo systemctl edit caddy\n```\n\n这将用你的默认终端文本编辑器打开一个空白文件，你可以在其中覆盖或添加指令到单元定义。这被称为\"插入式\"文件。\n\n例如，如果你需要定义环境变量用于你的配置，你可以这样做：\n```\n```systemd\n[Service]\nEnvironment=\"CF_API_TOKEN=super-secret-cloudflare-tokenvalue\"\n```\n\n或者，例如你需要将配置文件从Caddyfile的默认值改为使用JSON文件（注意`Exec*`指令[必须在设置新值之前用空字符串重置](https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart=))。\n\n```systemd\n[Service]\nExecStart=\nExecStart=/usr/bin/caddy run --environ --config /etc/caddy/caddy.json\nExecReload=\nExecReload=/usr/bin/caddy reload --config /etc/caddy/caddy.json\n```\n\n然后，保存文件并退出文本编辑器，并重新启动服务使之生效：\n```bash\nsudo systemctl restart caddy\n```\n\n\n<h2 id=\"windows-service\">Windows服务</h2>\n\n用这些说明在Windows上把Caddy安装成一个服务。\n\n**要求：**\n\n- [下载](/download)或[从源代码构建](/docs/build)的`caddy.exe`二进制文件。\n- 任何来自[WinSW](https://github.com/winsw/winsw/releases/latest)服务封装器（以下服务配置是为v2.x版本编写的）最新版本的`.exe`。\n\n将所有文件放入一个服务目录。在下面的例子中，我们使用`C:\\caddy`。\n\n将`WinSW-x64.exe`文件重命名为`caddy-service.exe`。\n\n在同一目录下添加一个`caddy-service.xml`。\n\n```xml\n<service>\n  <id>caddy</id>\n  <!-- 服务的显示名称 -->\n  <name>Caddy Web Server (powered by WinSW)</name>\n  <!-- 服务描述 -->\n  <description>Caddy Web Server (https://caddyserver.com/)</description>\n  <executable>%BASE%\\caddy.exe</executable>\n  <arguments>run</arguments>\n  <log mode=\"roll-by-time\">\n    <pattern>yyyy-MM-dd</pattern>\n  </log>\n</service>\n```\n\n现在你可以用以下方式安装该服务：\n```bash\ncaddy-service install\n```\n\n你可能想启动Windows服务控制台，看看该服务是否正确运行：\n```bash\nservices.msc\n```\n\n请注意，Windows服务不能被重新加载，所以你必须直接告诉caddy进行重新加载：\n```bash\ncaddy reload\n```\n\n重启可以通过正常的Windows服务命令进行，例如通过任务管理器的 \"服务 \"标签。\n\n关于定制服务包装器，请参见[WinSW文档](https://github.com/winsw/winsw/tree/master#usage)。\n\n\n<h2 id=\"docker-compose\">Docker Compose</h2>\n\n使用Docker启动和运行的最简单方法是使用Docker Compose。_以下只是摘录，更多细节请参见[Docker Hub](https://hub.docker.com/_/caddy)上的文档_。\n\n首先，创建一个文件`docker-compose.yml`（或者把这个服务添加到你现有的文件中）：\n\n```yaml\nversion: \"3.7\"\n\nservices:\n  caddy:\n    image: caddy:<version>\n    restart: unless-stopped\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - $PWD/Caddyfile:/etc/caddy/Caddyfile\n      - $PWD/site:/srv\n      - caddy_data:/data\n      - caddy_config:/config\n\nvolumes:\n  caddy_data:\n  caddy_config:\n```\n\n确保在`<version>`中填写最新的版本号，你可以在[Docker Hub](https://hub.docker.com/_/caddy)的 \"Tags \"部分找到。\n\n然后，在`docker-compose.yml`旁边创建一个名为`Caddyfile`的文件，并写下你的[Caddyfile](/docs/caddyfile/concepts)配置。\n\n如果你有静态文件需要服务，你可以把它们放在配置旁边的`site/`目录中，然后把[`root`指令](/docs/caddyfile/directives/root)设为`/srv/`。如果你不这样做，那么你可以删除`/srv`卷装载。\n\n然后，你就可以启动这个容器了：\n```bash\ndocker-compose up -d\n```\n\n在对你的Caddy文件进行修改后，要重新加载Caddy：\n```bash\ndocker-compose exec -w /etc/caddy caddy caddy reload\n```\n\n查看caddy的日志：\n```bash\ndocker-compose logs caddy\n```\n\n"
  },
  {
    "path": "src/docs/markdown/signature-verification.md",
    "content": "---\ntitle: 验证构件签名\n---\n\n# 签名验证\n\n构件签名可用于验证你拿到的构件是否与项目工作流生成的一致，以及是否被未授权方篡改（例如中间人攻击）。这能让所有参与方确认自己讨论的是同一个字节集合（可执行文件、SBOM、文本文件等）。\n\n从Caddy v2.6.0开始，CI/CD发布产物使用项目的[Sigstore](https://www.sigstore.dev/)体系进行签名。Sigstore签发的证书中包含签名主体信息。你可以先检查用于签名的证书。证书文件是base64编码的，先解码成PEM再查看。以下示例使用`caddy_2.6.0_checksums.txt`，并假设是类Linux环境。\n\n先下载与目标构件相关的3个文件：\n\n- `<artifact>`：实际构件\n- `<artifact>.sig`：构件签名\n- `<artifact>.pem`：由Sigstore Fulcio体系签发的证书（base64编码）\n\n先解码`.pem`：\n\n<pre><code class=\"cmd bash\">base64 -d < caddy_2.6.0_checksums.txt.pem > cert.pem</code></pre>\n\n然后用`openssl`查看证书：\n\n<pre><code class=\"cmd bash\">openssl x509 -in cert.pem -text</code></pre>\n\n<aside class=\"tip\" id=\"x509-extensions\">\n\n重点看证书的用途与扩展字段。比如`Code Signing`用途、`X509v3 Subject Alternative Name`中的工作流URI，以及OID字段中记录的workflow名称、commit、仓库、ref等。这些信息共同定位到唯一一次签名事件。\n\n</aside>\n\n拿到证书后，可用`cosign`验证签名（注意这里使用未解码的`.pem`文件）：\n\n<pre><code class=\"cmd\"><span class=\"bash\">COSIGN_EXPERIMENTAL=1 cosign verify-blob --certificate ./caddy_2.6.0_checksums.txt.pem --signature ./caddy_2.6.0_checksums.txt.sig ./caddy_2.6.0_checksums.txt</span>\ntlog entry verified with uuid: 04deb84e5a73ba75ea69092c6d700eaeb869c29cae3e0cf98dbfef871361ed09 index: 3618623\nVerified OK\n</code></pre>\n\n接着可以用`rekor-cli`查询透明日志：\n\n<pre><code class=\"cmd bash\">rekor-cli get --uuid 04deb84e5a73ba75ea69092c6d700eaeb869c29cae3e0cf98dbfef871361ed09 --format json | jq -r '.'</code></pre>\n\n你会看到包含以下关键信息的JSON：\n\n- `.Body.HashedRekordObj.signature.content`：应与`.sig`内容一致\n- `.Body.HashedRekordObj.signature.publicKey.content`：应与`.pem`内容一致（base64形式）\n- `.Body.HashedRekordObj.data.hash.value`：应与构件sha256一致\n\n也就是说，证书、签名、哈希三者都能在公开透明日志中相互印证。\n\n## 验证构件真实性\n\n如果你只有构件文件，没有签名和证书，也可先用`rekor-cli`按构件检索：\n\n<pre><code class=\"cmd\"><span class=\"bash\">rekor-cli search --artifact ./caddy_2.6.0_checksums.txt --format json | jq -r '.UUIDs[0]'</span></code></pre>\n\n再根据UUID取详情：\n\n<pre><code class=\"cmd bash\">rekor-cli get --uuid 04deb84e5a73ba75ea69092c6d700eaeb869c29cae3e0cf98dbfef871361ed09 --format json | jq -r '.'</code></pre>\n\n或一行命令完成：\n\n<pre><code class=\"cmd\"><span class=\"bash\">rekor-cli get --uuid $(rekor-cli search --artifact ./caddy_2.6.0_checksums.txt --format json | jq -r '.UUIDs[0]') --format json | jq -r '.'</span></code></pre>\n\n接下来，为确认“该签名确实来自Caddy项目的CI/CD工作流”，可从Rekor响应中提取证书并检查扩展字段：\n\n<pre><code class=\"cmd\"><span class=\"bash\">rekor-cli get --uuid $(rekor-cli search --artifact ./caddy_2.6.0_checksums.txt --format json | jq -r '.UUIDs[0]') --format json | jq -r '.Body.HashedRekordObj.signature.publicKey.content' | base64 -d > cert.pem</span></code></pre>\n\n然后：\n\n<pre><code class=\"cmd\"><span class=\"bash\">openssl x509 -in cert.pem -text</span></code></pre>\n\n检查`X509v3 extensions`中的workflow URI、repo、ref、commit等是否与预期一致。各OID定义可参考[Sigstore OID信息](https://github.com/sigstore/fulcio/blob/a25fb09c3f0561ac43e50357fdfc427e3f0aca4a/docs/oid-info.md)。\n\n## 如果签名验证失败怎么办？\n\n签名验证失败通常意味着：当前构件并非Caddy项目GitHub CI/CD工作流产物，或在构建发布到你拿到文件这条链路上被篡改。\n\n如果你有签名、证书和构件，应以`cosign`成功验证为准。也可通过`rekor-cli`检查透明日志、比对证书扩展字段、签名与哈希是否一致。若Rekor中找不到记录或关键字段不一致，都应视为高风险信号。\n\n"
  },
  {
    "path": "src/docs/markdown/troubleshooting.md",
    "content": "故障排查策略\n=====================\n\n本页给出一套通用、系统化的方法，帮助你在使用Caddy时自行解决大多数问题（不依赖AI）。当你在论坛求助时，我们也建议采用类似步骤。很多情况下，通过严谨思考，你可以自己回答问题或修复问题。\n\n你知道什么？\n-----------------\n\n你可能暂时不知道问题是什么、原因是什么、怎么修复。那就先从你**确定知道**的事实开始。\n\n### 你的预期\n\n把预期明确说出来（口头、心里、或写下来都行）。表达要具体、可验证，不要有歧义。最好还能解释你为什么这样预期。\n\n“它应该能工作”不是一个好的预期。\n\n“我请求这个URI时预期返回301重定向”就好得多。\n\n### 当前行为\n\n观察实际发生了什么。**到底**发生了什么？与预期有哪些差异？把已知事实整理出来。\n\n“它不工作”既无帮助也很偷懒；除非你前面已经详细定义了“它不工作”具体指什么，否则尽量不要这样说。\n\n“我预期301，但实际是200；不过响应里有`Server: Caddy`头”就更有价值，因为它同时表达了预期、现实、差异，以及一个额外事实：请求至少已经到达某个Caddy实例。\n\n### 日志\n\nCaddy日志里有什么？默认会输出到启动进程的终端。如果你是以系统服务等“后台方式”运行，日志可能在别处。\n\n注意：HTTP请求日志（access log）和进程日志不同；access log需要在配置中显式开启。\n\n如果还没开，也可以考虑开启DEBUG级别日志。\n\n无论如何，排查第一步之一就是看日志，而且是看**完整上下文**。单独一行日志通常价值很低。收集比你认为更多的日志，并在整个排查过程中保留。\n\n日志里有没有线索？\n\n识别并质疑假设\n-------------------------------\n\n继续之前，必须强调：质疑自己的假设非常关键。我们都会基于习惯和预期做假设。部署越复杂，假设就越多。\n\n例如常见假设：重新编译Caddy后直接运行`caddy`就会执行新代码。只有当你编译出的二进制覆盖了`$PATH`中的那个`caddy`时才成立。多数情况下应执行`./caddy`。\n\n再比如Docker部署：你不仅要重建镜像，还要确保运行的就是新镜像，这会引入更多假设。\n\n很多提问和“bug报告”最终都不是Caddy本身的问题，而是外部系统或网络配置问题。比如你连不上Caddy但进程明显在运行，你可能会假设“不是DNS”；提示：它经常就是DNS。\n\n甚至“我已经reload了配置”也常常只是误以为。请在每一层都做验证，保持流程严谨。\n\n复现行为\n----------------------\n\n这是关键步骤，而且常常会在这个过程中直接解决问题：**让问题再次出现**。\n\n更具体地说：用**尽可能小**的场景复现。移除不必要的配置、部署步骤和环境因素，直到问题消失。\n\n常见策略是一次只移除一个因素并重试，直到问题消失。最后移除的那个因素大概率就是原因，或者（再次质疑假设）它与之前移除的某个因素组合后才触发问题。再逐步加回去验证。\n\n另一种策略是每轮砍掉约一半因素，问题消失后再对那一半继续二分。这类似二分查找，常更快。\n\n你也可以反过来：不是删减，而是从最小配置逐步往上加，每次都重试，直到问题出现。\n\n很多时候，仅靠这个过程就能定位原因，并且修复方案会变得明显。即便还没修好，至少你能得到“最小复现步骤”。\n\n探索行为\n-----------------\n\n当你有了稳定复现步骤，就具备诊断根因的条件了。接下来是实验（如果熟悉代码也可以读代码）。\n\n如果你还无法解释原因，就主动改变行为。每次做一个小改动再试。例如配置里有正则，就先简化它，甚至先去掉它，观察是否出现“接近预期”的变化。即使结果不是最终想要的，也能缩小范围到正则或配置本身。\n\n在探索中留意“什么有效、什么无效”的模式，它会把你引向解决方案。\n\n如果你找到了方案，再判断它是否是bug。有时并不明显，这时可以带着实验过程发issue，请维护者判断。\n\n如果最终不是bug，也很好：你解决了问题，并且获得了新的理解。\n\n也欢迎把你的排查经验发到[论坛](https://caddy.community)，帮助遇到同类问题的人。\n\n"
  },
  {
    "path": "src/docs/markdown/v2-upgrade.md",
    "content": "---\ntitle: 升级到Caddy 2\n---\n\n升级指南\n=============\n\n为了改进Caddy 1，Caddy 2从头开始编码，是一个全新的代码库。Caddy 2不向后兼容Caddy 1。但也不用太担心，大多数基本设置其实并没有太大不同。本指南将帮助你尽可能平滑地过渡。\n\n本指南不会深入研究可用的新功能——虽然它们真的很酷，顺便说一下，你应该学习它们——这里的目标是让你快速启动并运行 Caddy 2。\n\n### 菜单\n\n- [要点](#high-order-bits)\n- [步骤](#steps)\n- [HTTPS和端口](#https-and-ports)\n- [命令行](#command-line)\n- [Caddyfile](#caddyfile)\n\t- [主要变化](#primary-changes)\n\t- [basicauth](#basicauth)\n\t- [browse](#browse)\n\t- [errors](#errors)\n\t- [ext](#ext)\n\t- [fastcgi](#fastcgi)\n\t- [gzip](#gzip)\n\t- [header](#header)\n\t- [log](#log)\n\t- [proxy](#proxy)\n\t- [redir](#redir)\n\t- [rewrite](#rewrite)\n\t- [root](#root)\n\t- [status](#status)\n\t- [templates](#templates)\n\t- [tls](#tls)\n- [服务文件](#service-files)\n- [插接件](#plugins)\n- [获取帮助](#getting-help)\n\n\n## 要点(High-order bits)\n\n- “Caddy 2”仍然只是被称为`caddy`。我们使用“Caddy 2”仅用来描述阐明是哪个版本，使过渡不那么混乱。\n- 大多数用户只需要替换他们的`caddy`二进制文件和更新的`Caddyfile`配置（在测试它是否有效之后）。\n- 最好不要从`Caddy 1`继承任何假设进入`Caddy 2`。\n- 你可能无法在 v2 中完美复制你的细分(liche)v1 配置。通常，这是有充分理由的。\n- 不再用命令行进行服务器配置。\n- 配置不再需要环境变量。\n- 为Caddy 2提供配置的主要方法是通过其[API](/docs/api)，但也可以使用[`caddy`命令](/docs/command-line)。\n- 你应该知道Caddy 2的原生配置语言是[JSON](/docs/json/)，而Caddyfile只是另一个为你转换为 JSON 的[配置适配器](/docs/config-adapters)。并非所有可能的配置都可以由Caddyfile表示，一些极端情况下的自定义、或者高级使用场景下可能需要JSON。\n- Caddyfile基本相同，但功能更强大；指令已更改。\n\n\n\n## 步骤(steps)\n\n1. 通过我们的[入门](/docs/getting-started)教程熟悉Caddy 2 。\n2. 如果你还没有，请执行第1步。说真的——我们不得不强调至少要知道如何使用Caddy 2的重要性。（它更有趣！）\n3. 使用以下指南转换你的`caddy`命令。\n4. 使用以下指南转换你的`Caddyfile`.\n5. 在本地或暂存中测试你的新配置。\n6. 测试，测试，再测试\n7. 部署并玩得开心！\n\n\n## HTTPS和端口(and ports)\n\nCaddy的默认端口不再是`:2015`。Caddy 2 的默认端口是`:443`，而如果不知道主机名或者IP，端口为`:80`。你始终可以在配置中自定义端口。\n\n如果主机名或者IP是已知的，则Caddy 2的默认协议[_总是_ HTTPS](/docs/automatic-https#overview)。这与 Caddy 1 不同，在 Caddy 1 中，默认情况下只有公开域名使用 HTTPS。现在，每个站点都使用 HTTPS（除非你通过明确指定端口`:80`或通过`http://`禁用它)。\n\nIP 地址和localhost域将从[本地受信任的嵌入式 CA](/docs/automatic-https#local-https)颁发证书。所有其他域将使用ZeroSSL或Let's Encrypt。（这都是可配置的。）\n\n证书和 ACME 资源的存储结构发生了变化。Caddy 2 可能会为你的站点获得新证书；但是如果你有很多证书，如果它不适合你，你可以手动迁移它们。有关详细信息，请参阅问题[#2955](https://github.com/caddyserver/caddy/issues/2955)和[#3124](https://github.com/caddyserver/caddy/issues/3124)。\n\n\n\n## 命令行(Command line)\n\n`caddy`命令现在是`caddy run`。\n\n所有命令行标志都是不同的。删除它们；所有服务器配置现在都存在于实际配置文档中（通常是 Caddyfile 或 JSON）。你可能会从[JSON结构](/docs/json/)或[Caddyfile全局选项](/docs/caddyfile/options)中找到你需要的内容，对v1中的大多数命令行标志进行替换。\n\n像`caddy -conf ../Caddyfile`这样的命令会变成`caddy run --config ../Caddyfile`。\n\n和以前一样，如果你的Caddyfile在当前文件夹中，Caddy会自动找到并使用它；在这种情况下，你不需要使用`--config`标志。\n\n信号基本相同，只是不再支持USR1和USR2。请改用[`caddy reload`](/docs/command-line#caddy-reload)命令或[API](/docs/api)来加载新配置。\n\n在没有任何配置的情况下运行`caddy`用于运行简单的文件服务器。Caddy 2 中的等价物是[`caddy file-server`](/docs/command-line#caddy-file-server)。\n\n环境变量不再相关，除了`HOME`（并且，可选地，`XDG_*`你设置的任何变量）。`CADDYPATH`被[操作系统约束所替代](/docs/conventions#file-locations)。\n\n\n## Caddyfile\n\n[v2 Caddyfile](/docs/caddyfile/concepts)与你已经熟悉的非常相似。你需要做的主要事情是更改指令。\n\n⚠️ **请务必阅读新指令！** 特别是如果你的配置更高级，则需要考虑许多细微差别。这些技巧可以让你快速切换，但请阅读每个指令的完整文档，以便了解升级的含义。当然，在将它们投入生产之前，请务必彻底测试你的配置。\n\n### 主要变化\n\n- 如果你提供静态文件服务器，你需要添加一个[`file_server`指令](/docs/caddyfile/directives/file_server)，因为 Caddy 2 默认不假设这个。出于安全原因，默认情况下 Caddy 2 也不嗅探 MIME。如果缺少 Content-Type，你可能需要使用[header](/docs/caddyfile/directives/header)指令自己设置标头。\n\n- 在v1中，你只能按请求路径过滤（或“匹配”）指令。在 v2 中，[请求匹配](/docs/caddyfile/matchers)功能更加强大。任何向 HTTP 处理程序链添加中间件或以任何方式操纵 HTTP 请求/响应的 v2 指令都利用了这个新的匹配功能。[阅读有关v2请求匹配器的更多信息](/docs/caddyfile/matchers)，你需要了解它们才能理解v2的Caddyfile。\n\n- 尽管许多[占位符](/docs/conventions#placeholders)是相同的，但许多已更改，现在有[很多新](/docs/modules/http#docs)占位符，包括[给Caddyfile的简写](/docs/caddyfile/concepts#placeholders)。\n\n- Caddy 2 的日志都是结构化的，默认格式是 JSON。所有日志级别都可以简单地转到要处理的同一日志（但如果需要，你可以自定义）。\n\n- 在 Caddy 1 中通过路径前缀匹配请求的情况下，现在默认情况下 Caddy 2 中的路径匹配是精确的。如果要匹配类似`/foo/`的前缀，则在Caddy 2中需要使用`/foo/*`进行匹配。\n\n我们将在这里列出一些最常见的 v1 指令，并描述如何转换它们以在 v2 Caddyfile 中使用。\n\n⚠️ **仅仅因为此页面有些v1指令没有给出，并不意味着v2不能做到！** 一些 v1 指令不需要，翻译不好，或者在 v2 中以其他方式实现。对于一些高级定制，你可能需要下拉到 JSON 以获得你想要的。浏览[我们的文档](/docs/caddyfile)以找到你需要的内容！\n\n\n### basicauth\n\nHTTP基本身份验证仍使用该[`basic_auth`](/docs/caddyfile/directives/basic_auth)指令进行配置。但是，Caddy 2 配置不接受明文密码。你必须对它们进行哈希处理，使用[`caddy hash-password`](/docs/command-line#caddy-hash-password)可以帮助你搞定。\n\n- **v1：**\n```\nbasicauth /secret/ Bob hiccup\n```\n\n- **v2：**\n```caddy-d\nbasic_auth /secret/* {\n\tBob JDJhJDEwJEVCNmdaNEg2Ti5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3VWc0tzOEJwZE9TaFlZdEVkZDhX\n}\n```\n\n\n### browse\n\n现在通过[`file_server`](/docs/caddyfile/directives/file_server)指令启用文件浏览。\n\n- **v1：**\n```\nbrowse /subfolder/\n```\n- **v2：**\n```caddy-d\nfile_server /subfolder/* browse\n```\n\n\n### errors\n\n自定义错误页面可以使用[`handle_errors`](/docs/caddyfile/directives/handle_errors)。\n\n\n- **v1：**:\n\n```\nerrors {\n\t404 404.html\n\t500 500.html\n}\n```\n\n- **v2：**:\n\n```\nhandle_errors {\n\trewrite * /{http.error.status_code}.html\n\tfile_server\n}\n```\n\n### ext\n\n隐含的文件扩展名可以用[`try_files`](/docs/caddyfile/directives/try_files)。\n\n- **v1：** `ext .html`\n- **v2：** `try_files {path}.html {path}`\n\n\n### fastcgi\n\n假设你正在使用 PHP，则v2等效项是[`php_fastcgi`](/docs/caddyfile/directives/php_fastcgi)。\n\n- **v1：**\n```\nfastcgi / localhost:9005 php\n```\n- **v2：**\n```caddy-d\nphp_fastcgi localhost:9005\n```\n\n请注意，v1的`fastcgi`指令在后台做了很多工作，包括尝试磁盘上的文件、重写请求，甚至重定向。v2的`php_fastcgi`指令也为你做这些事情，但文档提供了它的[扩展形式](/docs/caddyfile/directives/php_fastcgi#expanded-form)，如果你的要求不同，你可以对其进行修改。\n\nv2版本中不需要预设`php`，因为`php_fastcgi`指令默认采用PHP。像`php_fastcgi 127.0.0.1:9000 php`会导致反向代理认为有第二个后端称为`php`，从而导致连接错误。\n\nv2中的子指令不同——你可能不需要任何PHP指令。\n\n\n### gzip\n\n现在，[`encode`](/docs/caddyfile/directives/encode)指令可以用于所有响应编码，包括多种压缩格式。\n\n- **v1：**\n```\ngzip\n```\n- **v2：**\n```caddy-d\nencode gzip\n```\n\n有趣的事实：Caddy 2也支持`zstd` （但还没有浏览器支持）。\n\n\n### header\n\n[大部分没有改变](/docs/caddyfile/directives/header)，但现在更强大，因为它可以在 v2 中进行子字符串替换。\n\n- **v1：**\n```\nheader / Strict-Transport-Security max-age=31536000;\n```\n- **v2：**\n```caddy-d\nheader Strict-Transport-Security max-age=31536000;\n```\n\n\n### log\n\n启用访问记录；该[`log`](/docs/caddyfile/directives/log)指令仍然可以在 v2 中使用，但默认情况下，所有日志都是结构化的，编码为 JSON。\n\n启用访问日志的推荐方法很简单：\n\n```caddy-d\nlog\n```\n\n它将结构化日志发送到标准错误。（你也可以发送到文件或网络套接字；请参阅[`log`](/docs/caddyfile/directives/log)指令文档。）\n\n默认情况下，日志将采用[结构化](/docs/logging)JSON 格式。如果由于遗留原因你仍然需要通用日志格式 (CLF) 的日志，你可以使用[`format-encoder`](https://github.com/caddyserver/format-encoder)插件。\n\n\n### proxy\n\nv2的等效项是[`reverse_proxy`](/docs/caddyfile/directives/reverse_proxy)。\n\n显著的子指令变化分别是：`header_upstream`和`header_downstream`，已经对应地变成了`header_up`和`header_down`；负载平衡相关的子指令则都带上了前缀`lb_`。\n\n另一个显着的区别是v2代理默认通过所有传入的标头（包括`Host`标头）且设置`X-Forwarded-For`标头。换句话说，v1 的“透明”模式基本上是 v2 中的默认模式（但如果你需要 X-Real-IP 等其他标头，则必须自己设置）。你仍然可以使用`header_up`子指令覆盖或自定义`Host`标头。\n\nWebsocket 代理在 v2 中“正常工作”；无需像v1那样“启用（enable）”websocket。\n\n得益于请求匹配器的改进，v2不再需要进行[重写hack](#rewrite)，因此`without`自治领已经被移除了。\n\n- **v1：**\n```\nproxy / localhost:9005\n```\n- **v2：**\n```caddy-d\nreverse_proxy localhost:9005\n```\n\n\n### redir\n\n[不变](/docs/caddyfile/directives/redir)，除了一些关于可选状态码参数的细节。大多数配置不需要进行任何更改。\n\n- **v1：** `redir https://example.com{uri}`\n- **v2：** `redir https://example.com{uri}`\n\n\n### rewrite\n\n请求重写（“内部重定向”）的语义略有改变。如果你在v1中使用所谓的“重写hack”进行请求的匹配而不是使用简单路径前缀的方式，那么在v2中这是完全没有必要的。\n\n[新`rewrite`指令](/docs/caddyfile/directives/rewrite)简单而强大，因为它的大部分复杂性都被v2中的[匹配器](/docs/caddyfile/matchers)承接了：\n\n- **v1：**\n```\nrewrite {\n\tif {>User-Agent} has mobile\n\tto /mobile{uri}\n}\n```\n- **v2：**\n```caddy-d\n@mobile {\n\theader User-Agent *mobile*\n}\nrewrite @mobile /mobile{uri}\n```\n\n请注意我们如何简单地使用Caddy 2的常用[匹配器指令](/docs/caddyfile/matchers)；它不再是该指令的特例。\n\n首先删除所有的重写hack；将它们变成[命名匹配器](/docs/caddyfile/concepts#named-matchers)。评估每个v1的`rewrite`以查看v2中是否真的需要它。提示：`rewrite`用于添加路径前缀然后删除相同前缀，而带有`without`的`proxy`用来移除相同前缀的写法是一个重写hack，可以去掉了。\n\n你可能会发现新的[`route`](/docs/caddyfile/directives/route)和[`handle`](/docs/caddyfile/directives/handle)指令能更好地控制高级路由逻辑。\n\n\n### root\n\n[未改变](/docs/caddyfile/directives/root)，但如果你的根路径以`/`开头，则需要添加一个`*`匹配器标记以将其与[路径匹配器](/docs/caddyfile/concepts#path-matchers)区分开来。\n\n- **v1：** `root /var/www`\n- **v2：** `root * /var/www`\n\n因为它接受v2的匹配器，这意味着你还可以根据请求更改站点根目录。\n\n如果提供静态文件，请记住添加[`file_server`指令](/docs/caddyfile/directives/file_server)，因为默认情况下 Caddy 2 不假设这一点，而在 v1 中始终启用它。\n\n\n### status\n\nv2等效的是[`respond`](/docs/caddyfile/directives/respond)，它也可以写一个响应体。\n\n- **v1：**\n```\nstatus 404 /secrets/\n```\n- **v2：**\n```caddy-d\nrespond /secrets/* 404\n```\n\n\n### templates\n\n[`templates`](/docs/caddyfile/directives/templates)指令的整体语法没有改变，但实际的模板动作/功能是不同的并且有很大的改进。例如，模板能够包含文件、渲染 markdown、制作内部子请求、解析前端内容等等！\n\n[查阅文档](/docs/modules/http.handlers.templates)了解新功能的更多细节。\n\n- **v1：** `templates`\n- **v2：** `templates`\n\n\n### tls\n\n[`tls`](/docs/caddyfile/directives/tls)指令的基本原理没有改变，例如指定你自己的证书和密钥：\n\n- **v1：** `tls cert.pem key.pem`\n- **v2：** `tls cert.pem key.pem`\n\n但是Caddy的[自动HTTPS逻辑](/docs/automatic-https) _已经_ 已经改变，所以要注意这一点！\n\n密码套件名称也发生了变化。\n\nCaddy 2中的一个常见配置是使用`tls internal`它为非开发主机名`localhost`或 IP 地址提供本地受信任的证书。\n\n大多数网站根本不需要这个指令。\n\n\n## Service files\n\n我们建议使用[我们的官方 systemd 服务文件](/docs/running#linux-service)之一进行 Caddy 部署。\n\n如果你需要自定义服务文件，请以我们的为基础。出于充分的理由，他们已经仔细调整过！如果需要，请务必自定义你的。\n\n\n## Plugins\n\n为 v1 编写的插件不会自动与 v2 兼容。v2 甚至不需要许多 v1 插件。另一方面，v2 比 v1 更容易扩展和灵活！\n\n如果你想为 Caddy 2 编写插件，请[学习如何开发Caddy模块](/docs/extending-caddy)。\n\n\n### 使用插件构建 Caddy 2\n\nCaddy 2 可以在[交互式下载页面](https://caddyserver.com/download)通过插件下载。或者，你可以使用`xcaddy`[自己构建Caddyfile](/docs/build)选择要包含的插件。 `xcaddy`自动执行Caddy的[main.go](https://github.com/caddyserver/caddy/blob/master/cmd/caddy/main.go)文件中的指令。\n\n\n## 获得帮助(Getting help)\n\n如果你难以让 Caddy 正常工作，请先浏览我们的网站以获取文档。花时间尝试新事物并了解正在发生的事情——v2 在很多方面与 v1 非常不同（但也非常熟悉）！\n\n如果你仍然需要帮助，请加入[我们的社区](https://caddy.community)！你可能会发现帮助他人也是帮助自己的最佳方式。\n"
  },
  {
    "path": "src/docs/modules/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>模块 - Caddy V2中文文档</title>\n    {{include \"/includes/docs/head.html\"}}\n    <link rel=\"stylesheet\" href=\"/resources/css/docs-json.css\">\n    <script src=\"/resources/js/marked-0.8.0.min.js\"></script>\n    <script src=\"/resources/js/docs-api.js\"></script>\n    <script src=\"/resources/js/module-docs.js\"></script>\n</head>\n<body>\n{{include \"/includes/docs/header.html\"}}\n<main>\n    {{include \"/includes/docs/nav.html\"}}\n    <div class=\"article-container\">\n        <div class=\"paper\" id=\"paper1\"></div>\n        <div class=\"paper\" id=\"paper2\"></div>\n        <div class=\"paper paper3\">\n            <article id=\"module-list-container\">\n                <h1>所有模块</h1>\n                <p>\n                    此页面列出了所有已注册的 Caddy 模块。模块是扩展Caddy的<a href=\"/docs/json/\">JSON 配置结构</a>的插件。\n                </p>\n                <p>\n                    我们建议使用浏览器的“在页面中查找”功能进行快速查找。\n                </p>\n                <table id=\"module-list\">\n                    <tr>\n                        <th></th>\n                        <th>模块ID</th>\n                        <th>描述</th>\n                    </tr>\n                    <!--Populated by JS-->\n                </table>\n            </article>\n            <div id=\"module-docs-container\">\n                <div class=\"pad\"><h1 class=\"module-name\"><!--Populated by JS--></h1></div>\n                <div id=\"module-multiple-repos\">\n                    There is more than one module named <b class=\"module-name\"><!--Populated by JS--></b>. Choose one by its repository.\n                </div>\n                <div id=\"module-template\" class=\"module-repo-container\">\n                    <div class=\"module-repo-selector\"></div>\n                    <article>\n                        {{include \"/includes/docs/renderbox.html\"}}\n                        {{include \"/includes/docs/details.html\"}}\n                    </article>\n                </div>\n            </div>\n        </div>\n    </div>\n    <div class=\"sidebar\"></div>\n</main>\n{{include \"/includes/docs/hovercard.html\"}}\n{{include \"/includes/footer.html\"}}\n</body>\n</html>"
  },
  {
    "path": "src/includes/docs/details.html",
    "content": "<div>\n    <div class=\"nonstandard-notice\">\n        <b>This module does not come with Caddy.</b> It can be added by using <b><a href=\"/docs/build#xcaddy\">xcaddy</a></b> or our <b><a href=\"/download\">download page</a></b>. Non-standard modules may be developed by the community and are not officially endorsed or maintained by the Caddy project. The documentation is shown here only as a courtesy.\n        <br><br>\n        <b>Code repository: <a href=\"javascript:\" class=\"nonstandard-project-link\"></a></b>\n        <br><br>\n        <b>Custom builds:</b> <code class=\"bash\">xcaddy build --with <span class=\"nonstandard-package-path\"></span></code>\n    </div>\n</div>\n\n<h2 id=\"docs\">Description</h2>\n<div class=\"top-doc\">\n    <!--Populated by JS-->\n</div>\n<h2 class=\"field-list-header\">Field List</h2>\n<dl class=\"field-list-contents\">\n    <!--Populated by JS-->\n</dl>"
  },
  {
    "path": "src/includes/docs/head.html",
    "content": "{{include \"/includes/head.html\"}}\n<link rel=\"stylesheet\" href=\"/resources/css/docs.css\">\n<link rel=\"stylesheet\" href=\"/resources/css/chroma.css\">\n{{$directives := list }}\n{{range $i, $file := (listFiles \"/docs/markdown/caddyfile/directives\")}}\n{{$directives = append $directives ($file | trimSuffix \".md\")}}\n{{end}}\n<script type=\"text/javascript\">window.CaddyfileDirectives = {{$directives | toJson}};</script>\n<script src=\"/resources/js/jquery-3.4.1.min.js\"></script>\n<script src=\"/resources/js/docs.js\"></script>"
  },
  {
    "path": "src/includes/docs/header.html",
    "content": "<header>\n\n    <div>\n        <div id=\"logo-container\">\n            <a href=\"/\"><img src=\"/resources/images/caddy-wordmark.svg\" id=\"logo\" alt=\"Caddy\"></a>\n            <div id=\"logo-docs\">v2中文文档</div>\n        </div>\n        <div id=\"zerossl-project\">\n            <a href=\"https://zerossl.com\"><img src=\"/resources/images/zerossl-logo.svg\" id=\"zerossl-logo\" alt=\"zerossl\"></a>项目\n        </div>\n\n\n    </div>\n    <div style=\"float: right\">\n        <iframe style=\"display:inline;margin:0;\" src=\"https://ghbtns.com/github-btn.html?user=phpple&amp;repo=caddy2-cn-doc&amp;type=star&amp;count=true&amp;size=large\" frameborder=\"0\" scrolling=\"0\" width=\"140px\" height=\"30px\" class=\"github-stars\"></iframe>\n    </div>\n</header>"
  },
  {
    "path": "src/includes/docs/hovercard.html",
    "content": "<div id=\"hovercard\" class=\"arrow-box\">\n    <div id=\"hovercard-docs\" class=\"hovercard-elem\"><!--Populated by JS--></div>\n    <div id=\"hovercard-module\" class=\"hovercard-elem\">\n        <p id=\"hovercard-namespace-box\">\n            Fulfilled by modules in namespace: <span id=\"hovercard-namespace\"><!--Populated by JS--></span>\n        </p>\n        <div id=\"hovercard-module-list\"><!--Populated by JS--></div>\n    </div>\n    <div id=\"hovercard-inline-key\" class=\"hovercard-elem\">\n        <p class=\"explain\">This property is <b>required</b> because it specifies the module name.</p>\n    </div>\n    <div id=\"hovercard-breadcrumb-siblings\" class=\"hovercard-elem\"><!--Populated by JS--></div>\n    <div id=\"hovercard-inline-link\" class=\"hovercard-elem\"><!--Populated by JS--></div>\n</div>"
  },
  {
    "path": "src/includes/docs/nav.html",
    "content": "<nav class=\"sidebar\">\n    <ul>\n        <li><a href=\"/docs/\">欢迎</a></li>\n        <li><a href=\"https://caddy.community/c/wiki/13\">Wiki <img src=\"/resources/images/external-link.svg\"></a></li>\n        <li><a href=\"javascript:document.getElementById('donate').getElementsByTagName('a')[0].click();void(0)\">赞助</a></li>\n\n        <li class=\"heading\">获取Caddy</li>\n        <li><a href=\"/docs/install\">安装</a></li>\n        <li><a href=\"/docs/build\">从源码构建</a></li>\n\n        <li class=\"heading\">教程</li>\n        <li><a href=\"/docs/getting-started\">入门</a></li>\n        <li>\n            <a href=\"/docs/quick-starts\">快速开始</a>\n            <ul>\n                <li><a href=\"/docs/quick-starts/api\">使用API</a></li>\n                <li><a href=\"/docs/quick-starts/caddyfile\">使用Caddyfile</a></li>\n                <li><a href=\"/docs/quick-starts/static-files\">静态文件</a></li>\n                <li><a href=\"/docs/quick-starts/reverse-proxy\">反向代理</a></li>\n                <li><a href=\"/docs/quick-starts/https\">HTTPS</a></li>\n                <li><a href=\"/docs/quick-starts/railway\">Railway快速入门</a></li>\n            </ul>\n        </li>\n        <li><a href=\"/docs/api-tutorial\">API教程</a></li>\n        <li><a href=\"/docs/caddyfile-tutorial\">Caddyfile</a></li>\n\n        <li class=\"heading\">参考</li>\n        <li><a href=\"/docs/command-line\">命令行</a></li>\n        <li><a href=\"/docs/api\">API</a></li>\n        <li>\n            <a href=\"/docs/caddyfile\">Caddyfile</a>\n            <ul>\n                <li><a href=\"/docs/caddyfile/concepts\">概念</a></li>\n                <li><a href=\"/docs/caddyfile/directives\">指令</a></li>\n                <li><a href=\"/docs/caddyfile/matchers\">请求匹配器</a></li>\n                <li><a href=\"/docs/caddyfile/response-matchers\">响应匹配器</a></li>\n                <li><a href=\"/docs/caddyfile/options\">全局选项</a></li>\n                <li><a href=\"/docs/caddyfile/patterns\">常见模式</a></li>\n            </ul>\n        </li>\n        <li><a href=\"/docs/modules/\">模块</a></li>\n        <li><a href=\"/docs/json/\">JSON配置结构</a></li>\n        <li><a href=\"/docs/automatic-https\">自动HTTPS</a></li>\n\n        <li class=\"heading\">文章</li>\n        <li><a href=\"/docs/v2-upgrade\">升级到Caddy 2</a></li>\n        <li><a href=\"/docs/conventions\">约定</a></li>\n        <li><a href=\"/docs/config-adapters\">配置适配器</a></li>\n        <li><a href=\"/docs/logging\">日志如何工作</a></li>\n        <li><a href=\"/docs/metrics\">监控Caddy</a></li>\n        <li><a href=\"/docs/profiling\">Caddy性能剖析</a></li>\n        <li><a href=\"/docs/signature-verification\">签名验证</a></li>\n        <li><a href=\"/docs/troubleshooting\">故障排查策略</a></li>\n        <li><a href=\"/docs/architecture\">Caddy架构</a></li>\n        <li><a href=\"/docs/running\">保持Caddy运行</a></li>\n        <li><a href=\"/docs/examples\">示例</a></li>\n        <li><a href=\"/docs/faq\">常见问题</a></li>\n\n        <li class=\"heading\">开发者</li>\n        <li>\n            <a href=\"/docs/extending-caddy\">扩展Caddy</a>\n            <ul>\n                <li><a href=\"/docs/extending-caddy/caddyfile\">Caddyfile支持</a></li>\n                <li><a href=\"/docs/extending-caddy/config-adapters\">配置适配器</a></li>\n                <li><a href=\"/docs/extending-caddy/placeholders\">占位符支持</a></li>\n            </ul>\n        </li>\n        <li><a href=\"/docs/extending-caddy/namespaces\">模块命名空间</a></li>\n        <br>\n        <li><a href=\"https://dengxiaolong.com/caddy/zh/\">v1文档<img src=\"/resources/images/external-link.svg\"></a></li>\n    </ul>\n</nav>\n"
  },
  {
    "path": "src/includes/docs/renderbox.html",
    "content": "<pre><code class=\"json renderbox\"><!--Populated by JS--></code></pre>"
  },
  {
    "path": "src/includes/donate.html",
    "content": "<style>\n    #donate-modal-container{position:fixed;height:100%;width:100%;top:0;left:0;z-index:999;transform:scale(0)}#donate-modal-container.active{transform:scale(1)}#donate-modal-container .donate-quote{padding:0;margin:0}#donate-modal-container .donate-quote-left{display:inline-block;vertical-align:text-bottom;width:32px;height:32px;font-size:32px;line-height:32px}#donate-modal-container .donate-quote-word{font-size:20px;color:#333}#donate-modal-container .donate-quote-right{display:inline-block;vertical-align:text-bottom;width:32px;height:32px;font-size:32px;line-height:32px}#donate-modal-container .donate-tab{margin-top:20px;font-size:0}#donate-modal-container .donate-tab a{display:inline-flex;align-items:center;justify-content:center;padding:10px 5px;width:120px;font-size:16px;text-decoration:none;color:#333;background-color:#eee;transition:all .3s}#donate-modal-container .donate-tab .donate-wechat.active{background-color:#44b549;color:#fff}#donate-modal-container .donate-tab .donate-alipay.active{background-color:#059ae3;color:#fff}#donate-modal-container .donate-image{display:none;width:300px;height:auto;max-width:100%;margin:20px auto}#donate-modal-container .donate-image.active{display:block}#donate-modal-container .donate-modal-background{width:100%;height:100%;background:rgba(0,0,0,.8)}#donate-modal-container .donate-modal-background .donate-modal{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-sizing:border-box;background:#fff;max-width:900px;padding:50px;border-radius:3px;font-weight:300}.donate-btn{display:inline-block;padding:8px 16px;border-radius:5px;background-color:#44b549;color:#fff;text-decoration:none;margin-bottom:20px;transition:all .3s}.donate-btn:hover{opacity:.8}.donate-btn .donate-qrcode{display:inline-block;padding-right:5px;vertical-align:-2px;width:16px;height:16px;font-size:16px;line-height:16px;text-align:center}@media (max-width:768px){#donate-modal-container .donate-modal-background .donate-modal{padding:20px;width:100%}#donate-modal-container .donate-modal-background .donate-image{width:100%}}\n#donate-modal-container .donate-quote-left:before{content:\"“\"}#donate-modal-container .donate-quote-right:before{content:\"”\"}.donate-btn .donate-qrcode:before{content:\"▦\"}</style>\n<div id=\"donate\" style=\"text-align: center\"><a href=\"javascript:;\" class=\"donate-btn\"><i class=\"donate-qrcode\"></i>打赏支持</a></div>\n<div id=\"donate-modal-container\">\n    <div class=\"donate-modal-background\">\n        <div class=\"donate-modal\">\n            <p class=\"donate-quote\">\n                <i class=\"donate-quote-left\"></i>\n                <span class=\"donate-quote-word\">如果觉得我提供的内容对您有用，请随意打赏。您的支持将鼓励我继续创作!</span>\n                <i class=\"donate-quote-right\"></i>\n            </p>\n            <div class=\"donate-tab\">\n                <a href=\"javascript:;\" class=\"donate-wechat active\" data-index=\"0\">微信</a>\n                <a href=\"javascript:;\" class=\"donate-alipay\" data-index=\"1\">支付宝</a>\n                <img class=\"donate-image active\" data-src=\"/resources/images/donate-wechat.svg\" width=\"600\" height=\"823\" loading=\"lazy\" alt=\"微信收款码\">\n                <img class=\"donate-image\" data-src=\"/resources/images/donate-alipay.svg\" width=\"600\" height=\"900\" loading=\"lazy\" alt=\"支付宝收款码\">\n            </div>\n        </div>\n    </div>\n</div>\n<script>\n(function() {\n    var modal = document.getElementById('donate-modal-container');\n    var donate = document.getElementById('donate');\n    var tabs = modal.querySelectorAll('.donate-tab a');\n    var images = modal.querySelectorAll('.donate-tab .donate-image');\n    function loadImage(index) {\n        var image = images[index];\n        if (image && !image.src) {\n            image.src = image.dataset.src;\n        }\n    }\n\n    donate.addEventListener('click', function(event) {\n        if (event.target.closest('.donate-btn')) {\n            loadImage(0);\n            modal.classList.add('active');\n        }\n    });\n\n    modal.addEventListener('click', function(event) {\n        var tab = event.target.closest('.donate-tab a');\n        if (tab) {\n            event.stopPropagation();\n            var index = Number(tab.dataset.index);\n            loadImage(index);\n            Array.prototype.forEach.call(tabs, function(item, itemIndex) {\n                item.classList.toggle('active', itemIndex === index);\n            });\n            Array.prototype.forEach.call(images, function(item, itemIndex) {\n                item.classList.toggle('active', itemIndex === index);\n            });\n            return;\n        }\n        modal.classList.remove('active');\n    });\n}());\n</script>\n"
  },
  {
    "path": "src/includes/footer.html",
    "content": "<div class=\"wrapper\">\n    <footer>\n        <div>\n            <img src=\"/resources/images/caddy-logo.svg\" alt=\"Caddy\" id=\"footer-logo\">\n            An <a href=\"https://github.com/caddyserver/caddy\">open source</a> Go community project\n            <br>\n            in partnership with <a href=\"https://www.ardanlabs.com/\">Ardan Labs</a>\n            <br>\n        </div>\n        <div class=\"copyright\">\n            &copy; {{now | date \"2006\"}} Stack Holdings. All rights reserved.\n            <br>\n            Caddy&reg; is a registered trademark of Stack Holdings GmbH.\n        </div>\n    </footer>\n</div>\n"
  },
  {
    "path": "src/includes/head.html",
    "content": "<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<link rel=\"icon\" href=\"/resources/images/favicon.png\">\n<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Gantari:wght@400..800&family=Figtree:wght@300..900&family=JetBrains+Mono:wght@400..600&family=Inter:wght@400..600&family=Albert+Sans:wght@400..600&display=swap\">\n<link rel=\"stylesheet\" href=\"/resources/css/common.css\">\n<script src=\"/resources/js/common.js\"></script>\n\n<!-- General metatags -->\n<meta name=\"author\" content=\"Caddy Web Server\">\n<meta name=\"description\" content=\"Caddy is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go\">\n<meta name=\"theme-color\" content=\"#5ea9a2\">\n"
  },
  {
    "path": "src/index.html",
    "content": "<a href=\"/docs/\">docs</a>"
  },
  {
    "path": "src/resources/321140.cast",
    "content": "{\"version\": 2, \"width\": 79, \"height\": 20, \"timestamp\": 1587149050, \"env\": {\"SHELL\": \"/bin/zsh\", \"TERM\": \"xterm-256color\"}}\n[0.022225, \"o\", \"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                              \\r \\r\"]\n[0.023339, \"o\", \"\\u001b]7;file://Shadowfax.local/Users/matt/demo\\u0007\"]\n[0.023501, \"o\", \"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J  \\r\\n\\u001b[90m╔[\\u001b[32m√ \\u001b[38;5;81mmatt\\u001b[90m@\\u001b[38;5;82mShadowfax\\u001b[90m:\\u001b[38;5;81m~/demo\\u001b[90m]  \\r\\n╚>\\u001b[39m% \\u001b[K\"]\n[0.023536, \"o\", \"\\u001b[?2004h\"]\n[1.887934, \"o\", \"n\"]\n[2.000861, \"o\", \"\\bna\"]\n[2.072314, \"o\", \"n\"]\n[2.148447, \"o\", \"o\"]\n[2.304342, \"o\", \" \"]\n[2.444706, \"o\", \"C\"]\n[2.564064, \"o\", \"a\"]\n[2.66464, \"o\", \"d\"]\n[2.825937, \"o\", \"d\"]\n[2.946425, \"o\", \"y\"]\n[3.129396, \"o\", \"f\"]\n[3.287358, \"o\", \"i\"]\n[3.359465, \"o\", \"l\"]\n[3.487032, \"o\", \"e\"]\n[3.66006, \"o\", \"\\u001b[?2004l\\r\\r\\n\"]\n[3.667071, \"o\", \"\\u001b[?1049h\\u001b[1;20r\\u001b(B\\u001b[m\\u001b[4l\\u001b[?7h\\u001b[?12l\\u001b[?25h\\u001b[?1h\\u001b=\"]\n[3.667109, \"o\", \"\\u001b[?1h\\u001b=\\u001b[?1h\\u001b=\"]\n[3.667473, \"o\", \"\\u001b[39;49m\\u001b[39;49m\\u001b(B\\u001b[m\\u001b[H\\u001b[2J\\u001b(B\\u001b[0;7m  GNU nano 2.0.6              File: Caddyfile                                  \\u001b[18;34H[ New File ]\\r\\u001b[19d^G\\u001b(B\\u001b[m Get Help  \\u001b(B\\u001b[0;7m^O\\u001b(B\\u001b[m WriteOut  \\u001b(B\\u001b[0;7m^R\\u001b(B\\u001b[m Read File \\u001b(B\\u001b[0;7m^Y\\u001b(B\\u001b[m Prev Page \\u001b(B\\u001b[0;7m^K\\u001b(B\\u001b[m Cut Text  \\u001b(B\\u001b[0;7m^C\\u001b(B\\u001b[m Cur Pos\\r\\u001b[20d\\u001b(B\\u001b[0;7m^X\\u001b(B\\u001b[m Exit\\u001b[14G\\u001b(B\\u001b[0;7m^J\\u001b(B\\u001b[m Justify   \\u001b(B\\u001b[0;7m^W\\u001b(B\\u001b[m Where Is  \\u001b(B\\u001b[0;7m^V\\u001b(B\\u001b[m Next Page \\u001b(B\\u001b[0;7m^U\\u001b(B\\u001b[m UnCut Text\\u001b(B\\u001b[0;7m^T\\u001b(B\\u001b[m To Spell\\r\\u001b[3d\"]\n[4.535698, \"o\", \"\\u001b[1;70H\\u001b(B\\u001b[0;7mModified\\r\\u001b[3d\\u001b(B\\u001b[md\"]\n[4.67723, \"o\", \"e\"]\n[4.755158, \"o\", \"m\"]\n[4.806295, \"o\", \"o\"]\n[5.029033, \"o\", \".\"]\n[5.165983, \"o\", \"h\"]\n[5.227794, \"o\", \"t\"]\n[5.353038, \"o\", \"t\"]\n[5.414159, \"o\", \"p\"]\n[5.523791, \"o\", \"s\"]\n[5.627998, \"o\", \".\"]\n[5.710455, \"o\", \"d\"]\n[5.840252, \"o\", \"e\"]\n[5.963799, \"o\", \"v\"]\n[6.647743, \"o\", \"\\r\\u001b[4d\"]\n[6.775947, \"o\", \"\\u001b[5d\"]\n[6.933264, \"o\", \"r\"]\n[7.004905, \"o\", \"o\"]\n[7.122765, \"o\", \"o\"]\n[7.163639, \"o\", \"t\"]\n[7.244083, \"o\", \" \"]\n[7.346443, \"o\", \"*\"]\n[7.44394, \"o\", \" \"]\n[7.578595, \"o\", \"/\"]\n[7.826323, \"o\", \"v\"]\n[7.959474, \"o\", \"\\r\\u001b[18d\\u001b[K\\u001b[5;10Ha\"]\n[8.015511, \"o\", \"r\"]\n[8.103944, \"o\", \"/\"]\n[8.244526, \"o\", \"w\"]\n[8.52161, \"o\", \"w\"]\n[8.671104, \"o\", \"w\"]\n[9.968752, \"o\", \"\\r\\u001b[6d\"]\n[10.097106, \"o\", \"\\u001b[7d\"]\n[10.218855, \"o\", \"f\"]\n[10.319563, \"o\", \"i\"]\n[10.391213, \"o\", \"l\"]\n[10.438339, \"o\", \"e\"]\n[10.606098, \"o\", \"_\"]\n[10.68185, \"o\", \"s\"]\n[10.767465, \"o\", \"e\"]\n[10.813916, \"o\", \"r\"]\n[11.008647, \"o\", \"v\"]\n[11.138442, \"o\", \"e\"]\n[11.178214, \"o\", \"r\"]\n[11.300523, \"o\", \"\\r\\u001b[8d\"]\n[11.477734, \"o\", \"l\"]\n[11.606149, \"o\", \"o\"]\n[11.66293, \"o\", \"g\"]\n[12.48292, \"o\", \"\\r\\u001b[9d\"]\n[12.649606, \"o\", \"\\u001b[10d\"]\n[12.849532, \"o\", \"#\"]\n[13.198356, \"o\", \" \"]\n[13.299546, \"o\", \"l\"]\n[13.452749, \"o\", \"o\"]\n[13.51259, \"o\", \"a\"]\n[13.56063, \"o\", \"d\"]\n[13.690761, \"o\", \" \"]\n[13.771, \"o\", \"b\"]\n[13.911226, \"o\", \"a\"]\n[13.950191, \"o\", \"l\"]\n[14.047689, \"o\", \"a\"]\n[14.131014, \"o\", \"n\"]\n[14.214907, \"o\", \"c\"]\n[14.282838, \"o\", \"e\"]\n[14.325811, \"o\", \" \"]\n[14.466908, \"o\", \"b\"]\n[14.612588, \"o\", \"e\"]\n[14.685894, \"o\", \"t\"]\n[14.785785, \"o\", \"w\"]\n[14.84735, \"o\", \"e\"]\n[14.991994, \"o\", \"e\"]\n[15.089862, \"o\", \"n\"]\n[15.198188, \"o\", \" \"]\n[15.269788, \"o\", \"t\"]\n[15.32201, \"o\", \"w\"]\n[15.441701, \"o\", \"o\"]\n[15.517416, \"o\", \" \"]\n[15.614119, \"o\", \"b\"]\n[15.78414, \"o\", \"a\"]\n[15.869248, \"o\", \"c\"]\n[15.945455, \"o\", \"k\"]\n[16.028741, \"o\", \"e\"]\n[16.115068, \"o\", \"n\"]\n[16.19984, \"o\", \"d\"]\n[16.248232, \"o\", \"s\"]\n[16.442928, \"o\", \"\\r\\u001b[11d\"]\n[16.911683, \"o\", \"r\"]\n[16.97932, \"o\", \"e\"]\n[17.141613, \"o\", \"v\"]\n[17.282014, \"o\", \"e\"]\n[17.370161, \"o\", \"r\"]\n[17.490939, \"o\", \"s\"]\n[17.547477, \"o\", \"e\"]\n[17.740609, \"o\", \"_\"]\n[17.940825, \"o\", \"p\"]\n[17.992396, \"o\", \"r\"]\n[18.091587, \"o\", \"o\"]\n[18.185345, \"o\", \"x\"]\n[18.308829, \"o\", \"y\"]\n[18.361527, \"o\", \" \"]\n[18.651444, \"o\", \"/\"]\n[18.825604, \"o\", \"a\"]\n[18.904857, \"o\", \"p\"]\n[18.964969, \"o\", \"i\"]\n[19.188263, \"o\", \"/\"]\n[19.445601, \"o\", \"*\"]\n[19.505049, \"o\", \" \"]\n[20.21481, \"o\", \"l\"]\n[20.366859, \"o\", \"o\"]\n[20.442428, \"o\", \"c\"]\n[20.483119, \"o\", \"a\"]\n[20.542915, \"o\", \"l\"]\n[20.682475, \"o\", \"h\"]\n[20.747016, \"o\", \"o\"]\n[20.757983, \"o\", \"s\"]\n[20.845264, \"o\", \"t\"]\n[20.998991, \"o\", \":\"]\n[21.173825, \"o\", \"8\"]\n[21.281686, \"o\", \"0\"]\n[21.84701, \"o\", \"8\"]\n[21.906424, \"o\", \"0\"]\n[22.062149, \"o\", \" \"]\n[22.305605, \"o\", \"l\"]\n[22.451357, \"o\", \"o\"]\n[22.520031, \"o\", \"c\"]\n[22.568039, \"o\", \"a\"]\n[22.632047, \"o\", \"l\"]\n[22.751563, \"o\", \"h\"]\n[22.816958, \"o\", \"o\"]\n[22.842836, \"o\", \"s\"]\n[22.914777, \"o\", \"t\"]\n[23.051619, \"o\", \":\"]\n[23.221017, \"o\", \"8\"]\n[23.301663, \"o\", \"0\"]\n[23.679979, \"o\", \"8\"]\n[23.82229, \"o\", \"1\"]\n[25.554339, \"o\", \"\\r\\u001b[18d\\u001b(B\\u001b[0;7mFile Name to Write: Caddyfile                                                  \\u001b[19;14H\\u001b(B\\u001b[m      \\u001b(B\\u001b[0;7m^T\\u001b(B\\u001b[m To Files        \\u001b(B\\u001b[0;7mM-M\\u001b(B\\u001b[m Mac Format     \\u001b(B\\u001b[0;7mM-P\\u001b(B\\u001b[m Prepend\\u001b[K\\u001b[20;2H\\u001b(B\\u001b[0;7mC\\u001b(B\\u001b[m Cancel          \\u001b(B\\u001b[0;7mM-D\\u001b(B\\u001b[m DOS Format     \\u001b(B\\u001b[0;7mM-A\\u001b(B\\u001b[m Append         \\u001b(B\\u001b[0;7mM-B\\u001b(B\\u001b[m Backup File\\u001b[K\\u001b[18;30H\"]\n[25.798317, \"o\", \"\\u001b[1;70H\\u001b[39;49m\\u001b(B\\u001b[0;7m        \\u001b[18;31H\\u001b(B\\u001b[m\\u001b[1K \\u001b(B\\u001b[0;7m[ Wrote 9 lines ]\\u001b(B\\u001b[m\\u001b[K\\u001b[19;14H\\u001b(B\\u001b[0;7m^O\\u001b(B\\u001b[m WriteOut  \\u001b(B\\u001b[0;7m^R\\u001b(B\\u001b[m Read File \\u001b(B\\u001b[0;7m^Y\\u001b(B\\u001b[m Prev Page \\u001b(B\\u001b[0;7m^K\\u001b(B\\u001b[m Cut Text  \\u001b(B\\u001b[0;7m^C\\u001b(B\\u001b[m Cur Pos\\u001b[20;2H\\u001b(B\\u001b[0;7mX\\u001b(B\\u001b[m Exit      \\u001b(B\\u001b[0;7m^J\\u001b(B\\u001b[m Justify   \\u001b(B\\u001b[0;7m^W\\u001b(B\\u001b[m Where Is  \\u001b(B\\u001b[0;7m^V\\u001b(B\\u001b[m Next Page \\u001b(B\\u001b[0;7m^U\\u001b(B\\u001b[m UnCut Text\\u001b(B\\u001b[0;7m^T\\u001b(B\\u001b[m To Spell\\u001b[11;51H\"]\n[26.578003, \"o\", \"\\r\\u001b[19d\\u001b[J\\u001b[20;79H\\u001b[20;1H\\u001b[?1049l\\r\\u001b[?1l\\u001b>\"]\n[26.578762, \"o\", \"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                              \\r \\r\"]\n[26.57933, \"o\", \"\\u001b]7;file://Shadowfax.local/Users/matt/demo\\u0007\"]\n[26.579475, \"o\", \"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J  \\r\\n\\u001b[90m╔[\\u001b[32m√ \\u001b[38;5;81mmatt\\u001b[90m@\\u001b[38;5;82mShadowfax\\u001b[90m:\\u001b[38;5;81m~/demo\\u001b[90m]  \\r\\n╚>\\u001b[39m% \\u001b[K\\u001b[?2004h\"]\n[28.181852, \"o\", \"c\"]\n[28.231774, \"o\", \"\\bca\"]\n[28.339992, \"o\", \"d\"]\n[28.467448, \"o\", \"d\"]\n[28.551971, \"o\", \"y\"]\n[28.666558, \"o\", \" \"]\n[28.710012, \"o\", \"s\"]\n[28.773903, \"o\", \"t\"]\n[28.936621, \"o\", \"a\"]\n[28.995575, \"o\", \"r\"]\n[29.128429, \"o\", \"t\"]\n[30.256599, \"o\", \"\\u001b[?2004l\\r\\r\\n\"]\n[30.320169, \"o\", \"2020/04/17 18:44:40.540\\t\\u001b[34mINFO\\u001b[0m\\tusing adjacent Caddyfile\\r\\n\"]\n[30.322723, \"o\", \"2020/04/17 18:44:40.543\\t\\u001b[34mINFO\\u001b[0m\\tadmin\\tadmin endpoint started\\t{\\\"address\\\": \\\"tcp/localhost:2019\\\", \\\"enforce_origin\\\": false, \\\"origins\\\": [\\\"localhost:2019\\\", \\\"[::1]:2019\\\", \\\"127.0.0.1:2019\\\"]}\\r\\n\"]\n[30.322934, \"o\", \"2020/04/17 12:44:40 [INFO][cache:0xc000849d10] Started certificate maintenance routine\\r\\n\"]\n[30.322962, \"o\", \"2020/04/17 18:44:40.543\\t\\u001b[34mINFO\\u001b[0m\\thttp\\tserver is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS\\t{\\\"server_name\\\": \\\"srv0\\\", \\\"https_port\\\": 443}\\r\\n2020/04/17 18:44:40.543\\t\\u001b[34mINFO\\u001b[0m\\thttp\\tenabling automatic HTTP->HTTPS redirects\\t{\\\"server_name\\\": \\\"srv0\\\"}\\r\\n\"]\n[30.329471, \"o\", \"2020/04/17 18:44:40.550\\t\\u001b[34mINFO\\u001b[0m\\ttls\\tcleaned up storage units\\r\\n\"]\n[30.32956, \"o\", \"2020/04/17 18:44:40.550\\t\\u001b[34mINFO\\u001b[0m\\thttp\\tenabling automatic TLS certificate management\\t{\\\"domains\\\": [\\\"demo.https.dev\\\"]}\\r\\n\"]\n[30.329848, \"o\", \"2020/04/17 18:44:40.550\\t\\u001b[34mINFO\\u001b[0m\\tautosaved config\\t{\\\"file\\\": \\\"/Users/matt/Library/Application Support/Caddy/autosave.json\\\"}\\r\\n2020/04/17 18:44:40.550\\t\\u001b[34mINFO\\u001b[0m\\tserving initial configuration\\r\\n\"]\n[30.330126, \"o\", \"Successfully started Caddy (pid=41376) - Caddy is running in the background\\r\\n\"]\n[30.331898, \"o\", \"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                              \\r \\r\"]\n[30.332211, \"o\", \"\\u001b]7;file://Shadowfax.local/Users/matt/demo\\u0007\"]\n[30.332308, \"o\", \"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J  \\r\\n\\u001b[90m╔[\\u001b[32m√ \\u001b[38;5;81mmatt\\u001b[90m@\\u001b[38;5;82mShadowfax\\u001b[90m:\\u001b[38;5;81m~/demo\\u001b[90m]  \\r\\n╚>\\u001b[39m% \\u001b[K\\u001b[?2004h\"]\n[30.336942, \"o\", \"2020/04/17 12:44:40 [INFO][demo.https.dev] Obtain certificate; acquiring lock...\\r\\n\"]\n[30.337162, \"o\", \"2020/04/17 12:44:40 [INFO][demo.https.dev] Obtain: Lock acquired; proceeding...\\r\\n\"]\n[31.96302, \"o\", \"2020/04/17 12:44:42 [INFO][demo.https.dev] Waiting on rate limiter...\\r\\n2020/04/17 12:44:42 [INFO][demo.https.dev] Done waiting\\r\\n2020/04/17 12:44:42 [INFO] [demo.https.dev] acme: Obtaining bundled SAN certificate given a CSR\\r\\n\"]\n[33.570607, \"o\", \"2020/04/17 12:44:43 [INFO] [demo.https.dev] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/4019233382\\r\\n2020/04/17 12:44:43 [INFO] [demo.https.dev] acme: Could not find solver for: tls-alpn-01\\r\\n2020/04/17 12:44:43 [INFO] [demo.https.dev] acme: use http-01 solver\\r\\n2020/04/17 12:44:43 [INFO] [demo.https.dev] acme: Trying to solve HTTP-01\\r\\n\"]\n[34.951173, \"o\", \"2020/04/17 12:44:45 [INFO][demo.https.dev] Served key authentication (HTTP challenge)\\r\\n\"]\n[34.989119, \"o\", \"2020/04/17 12:44:45 [INFO][demo.https.dev] Served key authentication (HTTP challenge)\\r\\n\"]\n[35.027001, \"o\", \"2020/04/17 12:44:45 [INFO][demo.https.dev] Served key authentication (HTTP challenge)\\r\\n\"]\n[35.104125, \"o\", \"2020/04/17 12:44:45 [INFO][demo.https.dev] Served key authentication (HTTP challenge)\\r\\n\"]\n[36.574519, \"o\", \"2020/04/17 12:44:46 [INFO] [demo.https.dev] The server validated our request\\r\\n\"]\n[36.575029, \"o\", \"2020/04/17 12:44:46 [INFO] [demo.https.dev] acme: Validations succeeded; requesting certificates\\r\\n\"]\n[37.414882, \"o\", \"2020/04/17 12:44:47 [INFO] [demo.https.dev] Server responded with a certificate.\\r\\n\"]\n[37.416159, \"o\", \"2020/04/17 12:44:47 [INFO][demo.https.dev] Certificate obtained successfully\\r\\n2020/04/17 12:44:47 [INFO][demo.https.dev] Obtain: Releasing lock\\r\\n\"]\n[39.497929, \"o\", \"\\u001b[?2004l\\r\\r\\n\"]\n[39.498104, \"o\", \"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                              \\r \\r\"]\n[39.498581, \"o\", \"\\u001b]7;file://Shadowfax.local/Users/matt/demo\\u0007\"]\n[39.498738, \"o\", \"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J  \\r\\n\\u001b[90m╔[\\u001b[32m√ \\u001b[38;5;81mmatt\\u001b[90m@\\u001b[38;5;82mShadowfax\\u001b[90m:\\u001b[38;5;81m~/demo\\u001b[90m]  \\r\\n╚>\\u001b[39m% \\u001b[K\\u001b[?2004h\"]\n[40.091454, \"o\", \"c\"]\n[40.173651, \"o\", \"\\bcu\"]\n[40.257827, \"o\", \"r\"]\n[40.350059, \"o\", \"l\"]\n[40.44162, \"o\", \" \"]\n[40.619544, \"o\", \"-\"]\n[40.993026, \"o\", \"L\"]\n[41.085153, \"o\", \" \"]\n[41.865854, \"o\", \"d\"]\n[41.982983, \"o\", \"e\"]\n[42.063097, \"o\", \"m\"]\n[42.113574, \"o\", \"o\"]\n[42.326516, \"o\", \".\"]\n[42.71396, \"o\", \"h\"]\n[42.773776, \"o\", \"t\"]\n[42.896163, \"o\", \"t\"]\n[42.935898, \"o\", \"p\"]\n[43.051751, \"o\", \"s\"]\n[43.155986, \"o\", \".\"]\n[43.238445, \"o\", \"d\"]\n[43.36391, \"o\", \"e\"]\n[43.459323, \"o\", \"v\"]\n[43.674326, \"o\", \"\\u001b[?2004l\\r\\r\\n\"]\n[43.703356, \"o\", \"2020/04/17 18:44:53.923\\t\\u001b[34mINFO\\u001b[0m\\thttp.log.access\\thandled request\\t{\\\"request\\\": {\\\"method\\\": \\\"GET\\\", \\\"uri\\\": \\\"/\\\", \\\"proto\\\": \\\"HTTP/2.0\\\", \\\"remote_addr\\\": \\\"127.0.0.1:51883\\\", \\\"host\\\": \\\"demo.https.dev\\\", \\\"headers\\\": {\\\"User-Agent\\\": [\\\"curl/7.64.1\\\"], \\\"Accept\\\": [\\\"*/*\\\"]}, \\\"tls\\\": {\\\"resumed\\\": false, \\\"version\\\": 771, \\\"ciphersuite\\\": 49196, \\\"proto\\\": \\\"h2\\\", \\\"proto_mutual\\\": true, \\\"server_name\\\": \\\"demo.https.dev\\\"}}, \\\"common_log\\\": \\\"127.0.0.1 - - [17/Apr/2020:12:44:53 -0600] \\\\\\\"GET / HTTP/2.0\\\\\\\" 200 39\\\", \\\"latency\\\": 0.004248752, \\\"size\\\": 39, \\\"status\\\": 200, \\\"resp_headers\\\": {\\\"Server\\\": [\\\"Caddy\\\"], \\\"Etag\\\": [\\\"\\\\\\\"q8y3zo13\\\\\\\"\\\"], \\\"Content-Type\\\": [\\\"text/html; charset=utf-8\\\"], \\\"Last-Modified\\\": [\\\"Fri, 17 Apr 2020 18:43:00 GMT\\\"], \\\"Accept-Ranges\\\": [\\\"bytes\\\"], \\\"Content-Length\\\": [\\\"39\\\"]}}\\r\\n\"]\n[43.703537, \"o\", \"🔒 Easy HTTPS, just like that! 🎉\\r\\n\\r\\n\"]\n[43.706045, \"o\", \"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                              \\r \\r\"]\n[43.706405, \"o\", \"\\u001b]7;file://Shadowfax.local/Users/matt/demo\\u0007\"]\n[43.706517, \"o\", \"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J  \\r\\n\\u001b[90m╔[\\u001b[32m√ \\u001b[38;5;81mmatt\\u001b[90m@\\u001b[38;5;82mShadowfax\\u001b[90m:\\u001b[38;5;81m~/demo\\u001b[90m]  \\r\\n╚>\\u001b[39m% \\u001b[K\\u001b[?2004h\"]\n[48.347301, \"o\", \"\\u001b[?2004l\\r\\r\\n\"]\n"
  },
  {
    "path": "src/resources/css/account/common.css",
    "content": "* {\n\tmargin: 0;\n\tpadding: 0;\n\tbox-sizing: border-box;\n}\n\nhtml, body {\n\tmin-height: 100%;\n}\n\nhtml {\n\t/* by setting the min-height of both html and body to 100%,\n\t   it ensures that flex items are centered on screen, but\n\t   also allows body to overflow screen height for tall content */\n\theight: 100%;\n}\n\nbody {\n\tfont: 16px sans-serif;\n\t-webkit-font-smoothing: antialiased;\n\tbackground: #e8ebf0;\n\tdisplay: flex;\n}\n\na {\n\tcolor: #2b9cff;\n\ttext-decoration: none;\n}\n\na:hover {\n\tcolor: #0f6ab9;\n}\n\n.card {\n\tbackground: white;\n\tmargin: auto;\n\tbox-shadow: 0 20px 40px rgba(0, 0, 0, .1);\n\tdisplay: flex;\n\tborder-radius: 5px;\n\twidth: 100%;\n\tmax-width: 800px;\n}\n\n.card section {\n\tpadding: 50px;\n\ttext-align: center;\n\tflex-grow: 1;\n}\n\n.card section.head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n\tflex-grow: 0;\n\tbackground: #f3f7ff;\n\tborder-right: 1px solid #e8ebf0;\n\tborder-top-left-radius: 5px;\n\tborder-bottom-left-radius: 5px;\n}\n\n.card p {\n\tmargin: 0 auto;\n\tmax-width: 400px;\n\tline-height: 1.25em;\n\tfont-size: 14px;\n}\n\n.card .logo {\n\twidth: 100%;\n\tmax-width: 100px;\n\tdisplay: block;\n}\n\n.card h1 {\n\tfont-size: 42px;\n\tmargin-bottom: 10px;\n}\n\n.form-fields {\n\tpadding: 20px 0;\n}\n\ninput {\n\tfont-size: 16px;\n\tfont-family: 'PT Mono', 'Source Code Pro', monospace;\n}\n\n.card input,\n.card button {\n\tdisplay: block;\n\tmargin: 1em auto;\n\tpadding: 8px;\n}\n\n.card input[type=text],\n.card input[type=email],\n.card input[type=password] {\n\twidth: 100%;\n\tmax-width: 400px;\n}\n\n.card button {\n\tfont-size: 16px;\n\tfont-weight: bold;\n\tpadding: 10px 20px;\n}\n\n@media (max-width: 800px) {\n\t.card {\n\t\tflex-direction: column;\n\t}\n}\n\n.swal-content p {\n\tmargin: .5em 0;\n}\n\n#reset-password-step2 {\n\tdisplay: none;\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n.logo-container {\n\ttext-align: center;\n\tmargin: 20px auto;\n}\n\n.logo {\n\tmax-width: 100px;\n}\n\n.help {\n\tborder-bottom: 1px dotted #222;\n\tcursor: help;\n}\n\n.beta {\n\tfont-size: 12px;\n}\n\n\n.container {\n\tdisplay: flex;\n\tflex-grow: 1;\n\tjustify-content: space-between;\n\tfont-family: Maven Pro, sans-serif;\n}\n\n.container > nav {\n\tbackground: #f8faff;\n\twidth: 25%;\n\tmax-width: 250px;\n}\n\n.container > nav ul {\n\tlist-style: none;\n}\n\n.container > nav ul a {\n\tdisplay: block;\n\tpadding: 10px 20px;\n\tcolor: #546c75;\n}\n\n.container > nav ul a:hover {\n\tcolor: #01324b;\n\tbackground-color: #ebf3fb;\n}\n\n.container > nav ul a.current {\n\tbackground-color: #e0ecfb;\n\tfont-weight: bold;\n\tcolor: #01324b;\n}\n\n.container > main {\n\tdisplay: flex;\n\tflex: 1;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.container > main.dashboard {\n\talign-items: flex-start;\n\tjustify-content: flex-start;\n}\n\n.container section {\n\tbackground: white;\n\tborder-radius: 5px;\n\tbox-shadow: 0 2px 5px rgba(0, 0, 0, .08);\n\tmargin: 25px;\n}\n\n\n\ninput[type=text],\ninput[type=email],\ninput[type=password] {\n\tpadding: 8px;\n\twidth: 100%;\n\tmax-width: 400px;\n\tborder: 1px solid #ccc;\n}\n\nform .field {\n\tpadding: 0 20px 20px;\n\tmax-width: 600px;\n\tmargin: 0 auto 20px auto;\n\tborder-bottom: 1px solid #eee;\n}\n\nform label b {\n\tdisplay: block;\n\tmargin-bottom: 10px;\n}\n\nform .description {\n\tfont-size: 14px;\n\tcolor: #777;\n\tmargin-top: 5px;\n}\n\nform button,\nform input[type=submit] {\n\tfont-size: 16px;\n\tmargin: 0 auto;\n}\n\ninput[type=checkbox] {\n\tcursor: pointer;\n\ttransform: scale(1.25);\n}\n\n\n\nsection .pad {\n\tmargin: 15px 20px;\n}\n\nsection button:not([type=submit]),\nsection .button:not([type=submit]) {\n\tfont-size: 14px;\n\tpadding: 6px 15px;\n\tmargin: auto;\n}\n\n\nsection h1 {\n\tfont-size: 22px;\n\tfont-weight: normal;\n}\n\ntable {\n\tborder-collapse: collapse;\n}\n\ntr {\n\tborder-bottom: 1px solid #ddd;\n}\n\nth,\ntd {\n\tpadding: 10px;\n}\n\nth {\n\tbackground-color: #ebf3ff;\n\ttext-align: left;\n}"
  },
  {
    "path": "src/resources/css/account/dashboard.css",
    "content": "input[name=path] {\n\tfont-size: 14px;\n\tpadding: 4px;\n\tborder-radius: 4px;\n\twidth: auto;\n\t/* max-width: 450px; */\n\tmax-width: none;\n}\n\na.disabled {\n\tcolor: #aaa;\n\tcursor: not-allowed;\n}"
  },
  {
    "path": "src/resources/css/asciinema-player-2.6.1.css",
    "content": ".asciinema-player-wrapper {\n  position: relative;\n  text-align: center;\n  outline: none;\n}\n.asciinema-player-wrapper .title-bar {\n  display: none;\n  top: -78px;\n  transition: top 0.15s linear;\n  position: absolute;\n  left: 0;\n  right: 0;\n  box-sizing: content-box;\n  font-size: 20px;\n  line-height: 1em;\n  padding: 15px;\n  font-family: sans-serif;\n  color: white;\n  background-color: rgba(0, 0, 0, 0.8);\n}\n.asciinema-player-wrapper .title-bar img {\n  vertical-align: middle;\n  height: 48px;\n  margin-right: 16px;\n}\n.asciinema-player-wrapper .title-bar a {\n  color: white;\n  text-decoration: underline;\n}\n.asciinema-player-wrapper .title-bar a:hover {\n  text-decoration: none;\n}\n.asciinema-player-wrapper:fullscreen {\n  background-color: #000;\n  width: 100%;\n  height: 100%;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-justify-content: center;\n  justify-content: center;\n  -webkit-align-items: center;\n  align-items: center;\n}\n.asciinema-player-wrapper:fullscreen .asciinema-player {\n  position: static;\n}\n.asciinema-player-wrapper:fullscreen .title-bar {\n  display: initial;\n}\n.asciinema-player-wrapper:fullscreen.hud .title-bar {\n  top: 0;\n}\n.asciinema-player-wrapper:-webkit-full-screen {\n  background-color: #000;\n  width: 100%;\n  height: 100%;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-justify-content: center;\n  justify-content: center;\n  -webkit-align-items: center;\n  align-items: center;\n}\n.asciinema-player-wrapper:-webkit-full-screen .asciinema-player {\n  position: static;\n}\n.asciinema-player-wrapper:-webkit-full-screen .title-bar {\n  display: initial;\n}\n.asciinema-player-wrapper:-webkit-full-screen.hud .title-bar {\n  top: 0;\n}\n.asciinema-player-wrapper:-moz-full-screen {\n  background-color: #000;\n  width: 100%;\n  height: 100%;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-justify-content: center;\n  justify-content: center;\n  -webkit-align-items: center;\n  align-items: center;\n}\n.asciinema-player-wrapper:-moz-full-screen .asciinema-player {\n  position: static;\n}\n.asciinema-player-wrapper:-moz-full-screen .title-bar {\n  display: initial;\n}\n.asciinema-player-wrapper:-moz-full-screen.hud .title-bar {\n  top: 0;\n}\n.asciinema-player-wrapper:-ms-fullscreen {\n  background-color: #000;\n  width: 100%;\n  height: 100%;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-justify-content: center;\n  justify-content: center;\n  -webkit-align-items: center;\n  align-items: center;\n}\n.asciinema-player-wrapper:-ms-fullscreen .asciinema-player {\n  position: static;\n}\n.asciinema-player-wrapper:-ms-fullscreen .title-bar {\n  display: initial;\n}\n.asciinema-player-wrapper:-ms-fullscreen.hud .title-bar {\n  top: 0;\n}\n.asciinema-player-wrapper .asciinema-player {\n  text-align: left;\n  display: inline-block;\n  padding: 0px;\n  position: relative;\n  box-sizing: content-box;\n  -moz-box-sizing: content-box;\n  -webkit-box-sizing: content-box;\n  overflow: hidden;\n  max-width: 100%;\n}\n.asciinema-terminal {\n  box-sizing: content-box;\n  -moz-box-sizing: content-box;\n  -webkit-box-sizing: content-box;\n  overflow: hidden;\n  padding: 0;\n  margin: 0px;\n  display: block;\n  white-space: pre;\n  border: 0;\n  word-wrap: normal;\n  word-break: normal;\n  border-radius: 0;\n  border-style: solid;\n  cursor: text;\n  border-width: 0.5em;\n  font-family: Consolas, Menlo, 'Bitstream Vera Sans Mono', monospace, 'Powerline Symbols';\n  line-height: 1.3333333333em;\n}\n.asciinema-terminal .line {\n  letter-spacing: normal;\n  overflow: hidden;\n  height: 1.3333333333em;\n}\n.asciinema-terminal .line span {\n  padding: 0;\n  display: inline-block;\n  height: 1.3333333333em;\n}\n.asciinema-terminal .line {\n  display: block;\n  width: 200%;\n}\n.asciinema-terminal .bright {\n  font-weight: bold;\n}\n.asciinema-terminal .underline {\n  text-decoration: underline;\n}\n.asciinema-terminal .italic {\n  font-style: italic;\n}\n.asciinema-terminal.font-small {\n  font-size: 12px;\n}\n.asciinema-terminal.font-medium {\n  font-size: 18px;\n}\n.asciinema-terminal.font-big {\n  font-size: 24px;\n}\n.asciinema-player .control-bar {\n  width: 100%;\n  height: 32px;\n  background: rgba(0, 0, 0, 0.8);\n  /* no gradient fallback */\n  background: -moz-linear-gradient(top, rgba(0, 0, 0, 0.5) 0%, #000000 25%, #000000 100%);\n  /* FF3.6-15 */\n  background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.5) 0%, #000000 25%, #000000 100%);\n  /* Chrome10-25,Safari5.1-6 */\n  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, #000000 25%, #000000 100%);\n  /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */\n  color: #bbbbbb;\n  box-sizing: content-box;\n  line-height: 1;\n  position: absolute;\n  bottom: -35px;\n  left: 0;\n  transition: bottom 0.15s linear;\n}\n.asciinema-player .control-bar * {\n  box-sizing: inherit;\n  font-size: 0;\n}\n.asciinema-player .control-bar svg.icon path {\n  fill: #bbbbbb;\n}\n.asciinema-player .control-bar .playback-button {\n  display: block;\n  float: left;\n  cursor: pointer;\n  height: 12px;\n  width: 12px;\n  padding: 10px;\n}\n.asciinema-player .control-bar .playback-button svg {\n  height: 12px;\n  width: 12px;\n}\n.asciinema-player .control-bar .timer {\n  display: block;\n  float: left;\n  width: 50px;\n  height: 100%;\n  text-align: center;\n  font-family: Helvetica, Arial, sans-serif;\n  font-size: 11px;\n  font-weight: bold;\n  line-height: 32px;\n  cursor: default;\n}\n.asciinema-player .control-bar .timer span {\n  display: inline-block;\n  font-size: inherit;\n}\n.asciinema-player .control-bar .timer .time-remaining {\n  display: none;\n}\n.asciinema-player .control-bar .timer:hover .time-elapsed {\n  display: none;\n}\n.asciinema-player .control-bar .timer:hover .time-remaining {\n  display: inline;\n}\n.asciinema-player .control-bar .progressbar {\n  display: block;\n  overflow: hidden;\n  height: 100%;\n  padding: 0 10px;\n}\n.asciinema-player .control-bar .progressbar .bar {\n  display: block;\n  cursor: pointer;\n  height: 100%;\n  padding-top: 15px;\n  font-size: 0;\n}\n.asciinema-player .control-bar .progressbar .bar .gutter {\n  display: block;\n  height: 3px;\n  background-color: #333;\n}\n.asciinema-player .control-bar .progressbar .bar .gutter span {\n  display: inline-block;\n  height: 100%;\n  background-color: #bbbbbb;\n  border-radius: 3px;\n}\n.asciinema-player .control-bar.live .progressbar .bar {\n  cursor: default;\n}\n.asciinema-player .control-bar .fullscreen-button {\n  display: block;\n  float: right;\n  width: 14px;\n  height: 14px;\n  padding: 9px;\n  cursor: pointer;\n}\n.asciinema-player .control-bar .fullscreen-button svg {\n  width: 14px;\n  height: 14px;\n}\n.asciinema-player .control-bar .fullscreen-button svg:first-child {\n  display: inline;\n}\n.asciinema-player .control-bar .fullscreen-button svg:last-child {\n  display: none;\n}\n.asciinema-player-wrapper.hud .control-bar {\n  bottom: 0px;\n}\n.asciinema-player-wrapper:fullscreen .fullscreen-button svg:first-child {\n  display: none;\n}\n.asciinema-player-wrapper:fullscreen .fullscreen-button svg:last-child {\n  display: inline;\n}\n.asciinema-player-wrapper:-webkit-full-screen .fullscreen-button svg:first-child {\n  display: none;\n}\n.asciinema-player-wrapper:-webkit-full-screen .fullscreen-button svg:last-child {\n  display: inline;\n}\n.asciinema-player-wrapper:-moz-full-screen .fullscreen-button svg:first-child {\n  display: none;\n}\n.asciinema-player-wrapper:-moz-full-screen .fullscreen-button svg:last-child {\n  display: inline;\n}\n.asciinema-player-wrapper:-ms-fullscreen .fullscreen-button svg:first-child {\n  display: none;\n}\n.asciinema-player-wrapper:-ms-fullscreen .fullscreen-button svg:last-child {\n  display: inline;\n}\n.asciinema-player .loading {\n  z-index: 10;\n  background-repeat: no-repeat;\n  background-position: center;\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 32px;\n  background-color: rgba(0, 0, 0, 0.5);\n}\n.asciinema-player .start-prompt {\n  z-index: 10;\n  background-repeat: no-repeat;\n  background-position: center;\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 32px;\n  z-index: 20;\n  cursor: pointer;\n}\n.asciinema-player .start-prompt .play-button {\n  font-size: 0px;\n}\n.asciinema-player .start-prompt .play-button {\n  position: absolute;\n  left: 0;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  text-align: center;\n  color: white;\n  display: table;\n  width: 100%;\n  height: 100%;\n}\n.asciinema-player .start-prompt .play-button div {\n  vertical-align: middle;\n  display: table-cell;\n}\n.asciinema-player .start-prompt .play-button div span {\n  width: 96px;\n  height: 96px;\n  display: inline-block;\n}\n@-webkit-keyframes expand {\n  0% {\n    -webkit-transform: scale(0);\n  }\n  50% {\n    -webkit-transform: scale(1);\n  }\n  100% {\n    z-index: 1;\n  }\n}\n@-moz-keyframes expand {\n  0% {\n    -moz-transform: scale(0);\n  }\n  50% {\n    -moz-transform: scale(1);\n  }\n  100% {\n    z-index: 1;\n  }\n}\n@-o-keyframes expand {\n  0% {\n    -o-transform: scale(0);\n  }\n  50% {\n    -o-transform: scale(1);\n  }\n  100% {\n    z-index: 1;\n  }\n}\n@keyframes expand {\n  0% {\n    transform: scale(0);\n  }\n  50% {\n    transform: scale(1);\n  }\n  100% {\n    z-index: 1;\n  }\n}\n.loader {\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  margin: -20px 0 0 -20px;\n  background-color: white;\n  border-radius: 50%;\n  box-shadow: 0 0 0 6.66667px #141414;\n  width: 40px;\n  height: 40px;\n}\n.loader:before,\n.loader:after {\n  content: \"\";\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  display: block;\n  margin: -21px 0 0 -21px;\n  border-radius: 50%;\n  z-index: 2;\n  width: 42px;\n  height: 42px;\n}\n.loader:before {\n  background-color: #141414;\n  -webkit-animation: expand 1.6s linear infinite both;\n  -moz-animation: expand 1.6s linear infinite both;\n  animation: expand 1.6s linear infinite both;\n}\n.loader:after {\n  background-color: white;\n  -webkit-animation: expand 1.6s linear 0.8s infinite both;\n  -moz-animation: expand 1.6s linear 0.8s infinite both;\n  animation: expand 1.6s linear 0.8s infinite both;\n}\n.asciinema-terminal .fg-16 {\n  color: #000000;\n}\n.asciinema-terminal .bg-16 {\n  background-color: #000000;\n}\n.asciinema-terminal .fg-17 {\n  color: #00005f;\n}\n.asciinema-terminal .bg-17 {\n  background-color: #00005f;\n}\n.asciinema-terminal .fg-18 {\n  color: #000087;\n}\n.asciinema-terminal .bg-18 {\n  background-color: #000087;\n}\n.asciinema-terminal .fg-19 {\n  color: #0000af;\n}\n.asciinema-terminal .bg-19 {\n  background-color: #0000af;\n}\n.asciinema-terminal .fg-20 {\n  color: #0000d7;\n}\n.asciinema-terminal .bg-20 {\n  background-color: #0000d7;\n}\n.asciinema-terminal .fg-21 {\n  color: #0000ff;\n}\n.asciinema-terminal .bg-21 {\n  background-color: #0000ff;\n}\n.asciinema-terminal .fg-22 {\n  color: #005f00;\n}\n.asciinema-terminal .bg-22 {\n  background-color: #005f00;\n}\n.asciinema-terminal .fg-23 {\n  color: #005f5f;\n}\n.asciinema-terminal .bg-23 {\n  background-color: #005f5f;\n}\n.asciinema-terminal .fg-24 {\n  color: #005f87;\n}\n.asciinema-terminal .bg-24 {\n  background-color: #005f87;\n}\n.asciinema-terminal .fg-25 {\n  color: #005faf;\n}\n.asciinema-terminal .bg-25 {\n  background-color: #005faf;\n}\n.asciinema-terminal .fg-26 {\n  color: #005fd7;\n}\n.asciinema-terminal .bg-26 {\n  background-color: #005fd7;\n}\n.asciinema-terminal .fg-27 {\n  color: #005fff;\n}\n.asciinema-terminal .bg-27 {\n  background-color: #005fff;\n}\n.asciinema-terminal .fg-28 {\n  color: #008700;\n}\n.asciinema-terminal .bg-28 {\n  background-color: #008700;\n}\n.asciinema-terminal .fg-29 {\n  color: #00875f;\n}\n.asciinema-terminal .bg-29 {\n  background-color: #00875f;\n}\n.asciinema-terminal .fg-30 {\n  color: #008787;\n}\n.asciinema-terminal .bg-30 {\n  background-color: #008787;\n}\n.asciinema-terminal .fg-31 {\n  color: #0087af;\n}\n.asciinema-terminal .bg-31 {\n  background-color: #0087af;\n}\n.asciinema-terminal .fg-32 {\n  color: #0087d7;\n}\n.asciinema-terminal .bg-32 {\n  background-color: #0087d7;\n}\n.asciinema-terminal .fg-33 {\n  color: #0087ff;\n}\n.asciinema-terminal .bg-33 {\n  background-color: #0087ff;\n}\n.asciinema-terminal .fg-34 {\n  color: #00af00;\n}\n.asciinema-terminal .bg-34 {\n  background-color: #00af00;\n}\n.asciinema-terminal .fg-35 {\n  color: #00af5f;\n}\n.asciinema-terminal .bg-35 {\n  background-color: #00af5f;\n}\n.asciinema-terminal .fg-36 {\n  color: #00af87;\n}\n.asciinema-terminal .bg-36 {\n  background-color: #00af87;\n}\n.asciinema-terminal .fg-37 {\n  color: #00afaf;\n}\n.asciinema-terminal .bg-37 {\n  background-color: #00afaf;\n}\n.asciinema-terminal .fg-38 {\n  color: #00afd7;\n}\n.asciinema-terminal .bg-38 {\n  background-color: #00afd7;\n}\n.asciinema-terminal .fg-39 {\n  color: #00afff;\n}\n.asciinema-terminal .bg-39 {\n  background-color: #00afff;\n}\n.asciinema-terminal .fg-40 {\n  color: #00d700;\n}\n.asciinema-terminal .bg-40 {\n  background-color: #00d700;\n}\n.asciinema-terminal .fg-41 {\n  color: #00d75f;\n}\n.asciinema-terminal .bg-41 {\n  background-color: #00d75f;\n}\n.asciinema-terminal .fg-42 {\n  color: #00d787;\n}\n.asciinema-terminal .bg-42 {\n  background-color: #00d787;\n}\n.asciinema-terminal .fg-43 {\n  color: #00d7af;\n}\n.asciinema-terminal .bg-43 {\n  background-color: #00d7af;\n}\n.asciinema-terminal .fg-44 {\n  color: #00d7d7;\n}\n.asciinema-terminal .bg-44 {\n  background-color: #00d7d7;\n}\n.asciinema-terminal .fg-45 {\n  color: #00d7ff;\n}\n.asciinema-terminal .bg-45 {\n  background-color: #00d7ff;\n}\n.asciinema-terminal .fg-46 {\n  color: #00ff00;\n}\n.asciinema-terminal .bg-46 {\n  background-color: #00ff00;\n}\n.asciinema-terminal .fg-47 {\n  color: #00ff5f;\n}\n.asciinema-terminal .bg-47 {\n  background-color: #00ff5f;\n}\n.asciinema-terminal .fg-48 {\n  color: #00ff87;\n}\n.asciinema-terminal .bg-48 {\n  background-color: #00ff87;\n}\n.asciinema-terminal .fg-49 {\n  color: #00ffaf;\n}\n.asciinema-terminal .bg-49 {\n  background-color: #00ffaf;\n}\n.asciinema-terminal .fg-50 {\n  color: #00ffd7;\n}\n.asciinema-terminal .bg-50 {\n  background-color: #00ffd7;\n}\n.asciinema-terminal .fg-51 {\n  color: #00ffff;\n}\n.asciinema-terminal .bg-51 {\n  background-color: #00ffff;\n}\n.asciinema-terminal .fg-52 {\n  color: #5f0000;\n}\n.asciinema-terminal .bg-52 {\n  background-color: #5f0000;\n}\n.asciinema-terminal .fg-53 {\n  color: #5f005f;\n}\n.asciinema-terminal .bg-53 {\n  background-color: #5f005f;\n}\n.asciinema-terminal .fg-54 {\n  color: #5f0087;\n}\n.asciinema-terminal .bg-54 {\n  background-color: #5f0087;\n}\n.asciinema-terminal .fg-55 {\n  color: #5f00af;\n}\n.asciinema-terminal .bg-55 {\n  background-color: #5f00af;\n}\n.asciinema-terminal .fg-56 {\n  color: #5f00d7;\n}\n.asciinema-terminal .bg-56 {\n  background-color: #5f00d7;\n}\n.asciinema-terminal .fg-57 {\n  color: #5f00ff;\n}\n.asciinema-terminal .bg-57 {\n  background-color: #5f00ff;\n}\n.asciinema-terminal .fg-58 {\n  color: #5f5f00;\n}\n.asciinema-terminal .bg-58 {\n  background-color: #5f5f00;\n}\n.asciinema-terminal .fg-59 {\n  color: #5f5f5f;\n}\n.asciinema-terminal .bg-59 {\n  background-color: #5f5f5f;\n}\n.asciinema-terminal .fg-60 {\n  color: #5f5f87;\n}\n.asciinema-terminal .bg-60 {\n  background-color: #5f5f87;\n}\n.asciinema-terminal .fg-61 {\n  color: #5f5faf;\n}\n.asciinema-terminal .bg-61 {\n  background-color: #5f5faf;\n}\n.asciinema-terminal .fg-62 {\n  color: #5f5fd7;\n}\n.asciinema-terminal .bg-62 {\n  background-color: #5f5fd7;\n}\n.asciinema-terminal .fg-63 {\n  color: #5f5fff;\n}\n.asciinema-terminal .bg-63 {\n  background-color: #5f5fff;\n}\n.asciinema-terminal .fg-64 {\n  color: #5f8700;\n}\n.asciinema-terminal .bg-64 {\n  background-color: #5f8700;\n}\n.asciinema-terminal .fg-65 {\n  color: #5f875f;\n}\n.asciinema-terminal .bg-65 {\n  background-color: #5f875f;\n}\n.asciinema-terminal .fg-66 {\n  color: #5f8787;\n}\n.asciinema-terminal .bg-66 {\n  background-color: #5f8787;\n}\n.asciinema-terminal .fg-67 {\n  color: #5f87af;\n}\n.asciinema-terminal .bg-67 {\n  background-color: #5f87af;\n}\n.asciinema-terminal .fg-68 {\n  color: #5f87d7;\n}\n.asciinema-terminal .bg-68 {\n  background-color: #5f87d7;\n}\n.asciinema-terminal .fg-69 {\n  color: #5f87ff;\n}\n.asciinema-terminal .bg-69 {\n  background-color: #5f87ff;\n}\n.asciinema-terminal .fg-70 {\n  color: #5faf00;\n}\n.asciinema-terminal .bg-70 {\n  background-color: #5faf00;\n}\n.asciinema-terminal .fg-71 {\n  color: #5faf5f;\n}\n.asciinema-terminal .bg-71 {\n  background-color: #5faf5f;\n}\n.asciinema-terminal .fg-72 {\n  color: #5faf87;\n}\n.asciinema-terminal .bg-72 {\n  background-color: #5faf87;\n}\n.asciinema-terminal .fg-73 {\n  color: #5fafaf;\n}\n.asciinema-terminal .bg-73 {\n  background-color: #5fafaf;\n}\n.asciinema-terminal .fg-74 {\n  color: #5fafd7;\n}\n.asciinema-terminal .bg-74 {\n  background-color: #5fafd7;\n}\n.asciinema-terminal .fg-75 {\n  color: #5fafff;\n}\n.asciinema-terminal .bg-75 {\n  background-color: #5fafff;\n}\n.asciinema-terminal .fg-76 {\n  color: #5fd700;\n}\n.asciinema-terminal .bg-76 {\n  background-color: #5fd700;\n}\n.asciinema-terminal .fg-77 {\n  color: #5fd75f;\n}\n.asciinema-terminal .bg-77 {\n  background-color: #5fd75f;\n}\n.asciinema-terminal .fg-78 {\n  color: #5fd787;\n}\n.asciinema-terminal .bg-78 {\n  background-color: #5fd787;\n}\n.asciinema-terminal .fg-79 {\n  color: #5fd7af;\n}\n.asciinema-terminal .bg-79 {\n  background-color: #5fd7af;\n}\n.asciinema-terminal .fg-80 {\n  color: #5fd7d7;\n}\n.asciinema-terminal .bg-80 {\n  background-color: #5fd7d7;\n}\n.asciinema-terminal .fg-81 {\n  color: #5fd7ff;\n}\n.asciinema-terminal .bg-81 {\n  background-color: #5fd7ff;\n}\n.asciinema-terminal .fg-82 {\n  color: #5fff00;\n}\n.asciinema-terminal .bg-82 {\n  background-color: #5fff00;\n}\n.asciinema-terminal .fg-83 {\n  color: #5fff5f;\n}\n.asciinema-terminal .bg-83 {\n  background-color: #5fff5f;\n}\n.asciinema-terminal .fg-84 {\n  color: #5fff87;\n}\n.asciinema-terminal .bg-84 {\n  background-color: #5fff87;\n}\n.asciinema-terminal .fg-85 {\n  color: #5fffaf;\n}\n.asciinema-terminal .bg-85 {\n  background-color: #5fffaf;\n}\n.asciinema-terminal .fg-86 {\n  color: #5fffd7;\n}\n.asciinema-terminal .bg-86 {\n  background-color: #5fffd7;\n}\n.asciinema-terminal .fg-87 {\n  color: #5fffff;\n}\n.asciinema-terminal .bg-87 {\n  background-color: #5fffff;\n}\n.asciinema-terminal .fg-88 {\n  color: #870000;\n}\n.asciinema-terminal .bg-88 {\n  background-color: #870000;\n}\n.asciinema-terminal .fg-89 {\n  color: #87005f;\n}\n.asciinema-terminal .bg-89 {\n  background-color: #87005f;\n}\n.asciinema-terminal .fg-90 {\n  color: #870087;\n}\n.asciinema-terminal .bg-90 {\n  background-color: #870087;\n}\n.asciinema-terminal .fg-91 {\n  color: #8700af;\n}\n.asciinema-terminal .bg-91 {\n  background-color: #8700af;\n}\n.asciinema-terminal .fg-92 {\n  color: #8700d7;\n}\n.asciinema-terminal .bg-92 {\n  background-color: #8700d7;\n}\n.asciinema-terminal .fg-93 {\n  color: #8700ff;\n}\n.asciinema-terminal .bg-93 {\n  background-color: #8700ff;\n}\n.asciinema-terminal .fg-94 {\n  color: #875f00;\n}\n.asciinema-terminal .bg-94 {\n  background-color: #875f00;\n}\n.asciinema-terminal .fg-95 {\n  color: #875f5f;\n}\n.asciinema-terminal .bg-95 {\n  background-color: #875f5f;\n}\n.asciinema-terminal .fg-96 {\n  color: #875f87;\n}\n.asciinema-terminal .bg-96 {\n  background-color: #875f87;\n}\n.asciinema-terminal .fg-97 {\n  color: #875faf;\n}\n.asciinema-terminal .bg-97 {\n  background-color: #875faf;\n}\n.asciinema-terminal .fg-98 {\n  color: #875fd7;\n}\n.asciinema-terminal .bg-98 {\n  background-color: #875fd7;\n}\n.asciinema-terminal .fg-99 {\n  color: #875fff;\n}\n.asciinema-terminal .bg-99 {\n  background-color: #875fff;\n}\n.asciinema-terminal .fg-100 {\n  color: #878700;\n}\n.asciinema-terminal .bg-100 {\n  background-color: #878700;\n}\n.asciinema-terminal .fg-101 {\n  color: #87875f;\n}\n.asciinema-terminal .bg-101 {\n  background-color: #87875f;\n}\n.asciinema-terminal .fg-102 {\n  color: #878787;\n}\n.asciinema-terminal .bg-102 {\n  background-color: #878787;\n}\n.asciinema-terminal .fg-103 {\n  color: #8787af;\n}\n.asciinema-terminal .bg-103 {\n  background-color: #8787af;\n}\n.asciinema-terminal .fg-104 {\n  color: #8787d7;\n}\n.asciinema-terminal .bg-104 {\n  background-color: #8787d7;\n}\n.asciinema-terminal .fg-105 {\n  color: #8787ff;\n}\n.asciinema-terminal .bg-105 {\n  background-color: #8787ff;\n}\n.asciinema-terminal .fg-106 {\n  color: #87af00;\n}\n.asciinema-terminal .bg-106 {\n  background-color: #87af00;\n}\n.asciinema-terminal .fg-107 {\n  color: #87af5f;\n}\n.asciinema-terminal .bg-107 {\n  background-color: #87af5f;\n}\n.asciinema-terminal .fg-108 {\n  color: #87af87;\n}\n.asciinema-terminal .bg-108 {\n  background-color: #87af87;\n}\n.asciinema-terminal .fg-109 {\n  color: #87afaf;\n}\n.asciinema-terminal .bg-109 {\n  background-color: #87afaf;\n}\n.asciinema-terminal .fg-110 {\n  color: #87afd7;\n}\n.asciinema-terminal .bg-110 {\n  background-color: #87afd7;\n}\n.asciinema-terminal .fg-111 {\n  color: #87afff;\n}\n.asciinema-terminal .bg-111 {\n  background-color: #87afff;\n}\n.asciinema-terminal .fg-112 {\n  color: #87d700;\n}\n.asciinema-terminal .bg-112 {\n  background-color: #87d700;\n}\n.asciinema-terminal .fg-113 {\n  color: #87d75f;\n}\n.asciinema-terminal .bg-113 {\n  background-color: #87d75f;\n}\n.asciinema-terminal .fg-114 {\n  color: #87d787;\n}\n.asciinema-terminal .bg-114 {\n  background-color: #87d787;\n}\n.asciinema-terminal .fg-115 {\n  color: #87d7af;\n}\n.asciinema-terminal .bg-115 {\n  background-color: #87d7af;\n}\n.asciinema-terminal .fg-116 {\n  color: #87d7d7;\n}\n.asciinema-terminal .bg-116 {\n  background-color: #87d7d7;\n}\n.asciinema-terminal .fg-117 {\n  color: #87d7ff;\n}\n.asciinema-terminal .bg-117 {\n  background-color: #87d7ff;\n}\n.asciinema-terminal .fg-118 {\n  color: #87ff00;\n}\n.asciinema-terminal .bg-118 {\n  background-color: #87ff00;\n}\n.asciinema-terminal .fg-119 {\n  color: #87ff5f;\n}\n.asciinema-terminal .bg-119 {\n  background-color: #87ff5f;\n}\n.asciinema-terminal .fg-120 {\n  color: #87ff87;\n}\n.asciinema-terminal .bg-120 {\n  background-color: #87ff87;\n}\n.asciinema-terminal .fg-121 {\n  color: #87ffaf;\n}\n.asciinema-terminal .bg-121 {\n  background-color: #87ffaf;\n}\n.asciinema-terminal .fg-122 {\n  color: #87ffd7;\n}\n.asciinema-terminal .bg-122 {\n  background-color: #87ffd7;\n}\n.asciinema-terminal .fg-123 {\n  color: #87ffff;\n}\n.asciinema-terminal .bg-123 {\n  background-color: #87ffff;\n}\n.asciinema-terminal .fg-124 {\n  color: #af0000;\n}\n.asciinema-terminal .bg-124 {\n  background-color: #af0000;\n}\n.asciinema-terminal .fg-125 {\n  color: #af005f;\n}\n.asciinema-terminal .bg-125 {\n  background-color: #af005f;\n}\n.asciinema-terminal .fg-126 {\n  color: #af0087;\n}\n.asciinema-terminal .bg-126 {\n  background-color: #af0087;\n}\n.asciinema-terminal .fg-127 {\n  color: #af00af;\n}\n.asciinema-terminal .bg-127 {\n  background-color: #af00af;\n}\n.asciinema-terminal .fg-128 {\n  color: #af00d7;\n}\n.asciinema-terminal .bg-128 {\n  background-color: #af00d7;\n}\n.asciinema-terminal .fg-129 {\n  color: #af00ff;\n}\n.asciinema-terminal .bg-129 {\n  background-color: #af00ff;\n}\n.asciinema-terminal .fg-130 {\n  color: #af5f00;\n}\n.asciinema-terminal .bg-130 {\n  background-color: #af5f00;\n}\n.asciinema-terminal .fg-131 {\n  color: #af5f5f;\n}\n.asciinema-terminal .bg-131 {\n  background-color: #af5f5f;\n}\n.asciinema-terminal .fg-132 {\n  color: #af5f87;\n}\n.asciinema-terminal .bg-132 {\n  background-color: #af5f87;\n}\n.asciinema-terminal .fg-133 {\n  color: #af5faf;\n}\n.asciinema-terminal .bg-133 {\n  background-color: #af5faf;\n}\n.asciinema-terminal .fg-134 {\n  color: #af5fd7;\n}\n.asciinema-terminal .bg-134 {\n  background-color: #af5fd7;\n}\n.asciinema-terminal .fg-135 {\n  color: #af5fff;\n}\n.asciinema-terminal .bg-135 {\n  background-color: #af5fff;\n}\n.asciinema-terminal .fg-136 {\n  color: #af8700;\n}\n.asciinema-terminal .bg-136 {\n  background-color: #af8700;\n}\n.asciinema-terminal .fg-137 {\n  color: #af875f;\n}\n.asciinema-terminal .bg-137 {\n  background-color: #af875f;\n}\n.asciinema-terminal .fg-138 {\n  color: #af8787;\n}\n.asciinema-terminal .bg-138 {\n  background-color: #af8787;\n}\n.asciinema-terminal .fg-139 {\n  color: #af87af;\n}\n.asciinema-terminal .bg-139 {\n  background-color: #af87af;\n}\n.asciinema-terminal .fg-140 {\n  color: #af87d7;\n}\n.asciinema-terminal .bg-140 {\n  background-color: #af87d7;\n}\n.asciinema-terminal .fg-141 {\n  color: #af87ff;\n}\n.asciinema-terminal .bg-141 {\n  background-color: #af87ff;\n}\n.asciinema-terminal .fg-142 {\n  color: #afaf00;\n}\n.asciinema-terminal .bg-142 {\n  background-color: #afaf00;\n}\n.asciinema-terminal .fg-143 {\n  color: #afaf5f;\n}\n.asciinema-terminal .bg-143 {\n  background-color: #afaf5f;\n}\n.asciinema-terminal .fg-144 {\n  color: #afaf87;\n}\n.asciinema-terminal .bg-144 {\n  background-color: #afaf87;\n}\n.asciinema-terminal .fg-145 {\n  color: #afafaf;\n}\n.asciinema-terminal .bg-145 {\n  background-color: #afafaf;\n}\n.asciinema-terminal .fg-146 {\n  color: #afafd7;\n}\n.asciinema-terminal .bg-146 {\n  background-color: #afafd7;\n}\n.asciinema-terminal .fg-147 {\n  color: #afafff;\n}\n.asciinema-terminal .bg-147 {\n  background-color: #afafff;\n}\n.asciinema-terminal .fg-148 {\n  color: #afd700;\n}\n.asciinema-terminal .bg-148 {\n  background-color: #afd700;\n}\n.asciinema-terminal .fg-149 {\n  color: #afd75f;\n}\n.asciinema-terminal .bg-149 {\n  background-color: #afd75f;\n}\n.asciinema-terminal .fg-150 {\n  color: #afd787;\n}\n.asciinema-terminal .bg-150 {\n  background-color: #afd787;\n}\n.asciinema-terminal .fg-151 {\n  color: #afd7af;\n}\n.asciinema-terminal .bg-151 {\n  background-color: #afd7af;\n}\n.asciinema-terminal .fg-152 {\n  color: #afd7d7;\n}\n.asciinema-terminal .bg-152 {\n  background-color: #afd7d7;\n}\n.asciinema-terminal .fg-153 {\n  color: #afd7ff;\n}\n.asciinema-terminal .bg-153 {\n  background-color: #afd7ff;\n}\n.asciinema-terminal .fg-154 {\n  color: #afff00;\n}\n.asciinema-terminal .bg-154 {\n  background-color: #afff00;\n}\n.asciinema-terminal .fg-155 {\n  color: #afff5f;\n}\n.asciinema-terminal .bg-155 {\n  background-color: #afff5f;\n}\n.asciinema-terminal .fg-156 {\n  color: #afff87;\n}\n.asciinema-terminal .bg-156 {\n  background-color: #afff87;\n}\n.asciinema-terminal .fg-157 {\n  color: #afffaf;\n}\n.asciinema-terminal .bg-157 {\n  background-color: #afffaf;\n}\n.asciinema-terminal .fg-158 {\n  color: #afffd7;\n}\n.asciinema-terminal .bg-158 {\n  background-color: #afffd7;\n}\n.asciinema-terminal .fg-159 {\n  color: #afffff;\n}\n.asciinema-terminal .bg-159 {\n  background-color: #afffff;\n}\n.asciinema-terminal .fg-160 {\n  color: #d70000;\n}\n.asciinema-terminal .bg-160 {\n  background-color: #d70000;\n}\n.asciinema-terminal .fg-161 {\n  color: #d7005f;\n}\n.asciinema-terminal .bg-161 {\n  background-color: #d7005f;\n}\n.asciinema-terminal .fg-162 {\n  color: #d70087;\n}\n.asciinema-terminal .bg-162 {\n  background-color: #d70087;\n}\n.asciinema-terminal .fg-163 {\n  color: #d700af;\n}\n.asciinema-terminal .bg-163 {\n  background-color: #d700af;\n}\n.asciinema-terminal .fg-164 {\n  color: #d700d7;\n}\n.asciinema-terminal .bg-164 {\n  background-color: #d700d7;\n}\n.asciinema-terminal .fg-165 {\n  color: #d700ff;\n}\n.asciinema-terminal .bg-165 {\n  background-color: #d700ff;\n}\n.asciinema-terminal .fg-166 {\n  color: #d75f00;\n}\n.asciinema-terminal .bg-166 {\n  background-color: #d75f00;\n}\n.asciinema-terminal .fg-167 {\n  color: #d75f5f;\n}\n.asciinema-terminal .bg-167 {\n  background-color: #d75f5f;\n}\n.asciinema-terminal .fg-168 {\n  color: #d75f87;\n}\n.asciinema-terminal .bg-168 {\n  background-color: #d75f87;\n}\n.asciinema-terminal .fg-169 {\n  color: #d75faf;\n}\n.asciinema-terminal .bg-169 {\n  background-color: #d75faf;\n}\n.asciinema-terminal .fg-170 {\n  color: #d75fd7;\n}\n.asciinema-terminal .bg-170 {\n  background-color: #d75fd7;\n}\n.asciinema-terminal .fg-171 {\n  color: #d75fff;\n}\n.asciinema-terminal .bg-171 {\n  background-color: #d75fff;\n}\n.asciinema-terminal .fg-172 {\n  color: #d78700;\n}\n.asciinema-terminal .bg-172 {\n  background-color: #d78700;\n}\n.asciinema-terminal .fg-173 {\n  color: #d7875f;\n}\n.asciinema-terminal .bg-173 {\n  background-color: #d7875f;\n}\n.asciinema-terminal .fg-174 {\n  color: #d78787;\n}\n.asciinema-terminal .bg-174 {\n  background-color: #d78787;\n}\n.asciinema-terminal .fg-175 {\n  color: #d787af;\n}\n.asciinema-terminal .bg-175 {\n  background-color: #d787af;\n}\n.asciinema-terminal .fg-176 {\n  color: #d787d7;\n}\n.asciinema-terminal .bg-176 {\n  background-color: #d787d7;\n}\n.asciinema-terminal .fg-177 {\n  color: #d787ff;\n}\n.asciinema-terminal .bg-177 {\n  background-color: #d787ff;\n}\n.asciinema-terminal .fg-178 {\n  color: #d7af00;\n}\n.asciinema-terminal .bg-178 {\n  background-color: #d7af00;\n}\n.asciinema-terminal .fg-179 {\n  color: #d7af5f;\n}\n.asciinema-terminal .bg-179 {\n  background-color: #d7af5f;\n}\n.asciinema-terminal .fg-180 {\n  color: #d7af87;\n}\n.asciinema-terminal .bg-180 {\n  background-color: #d7af87;\n}\n.asciinema-terminal .fg-181 {\n  color: #d7afaf;\n}\n.asciinema-terminal .bg-181 {\n  background-color: #d7afaf;\n}\n.asciinema-terminal .fg-182 {\n  color: #d7afd7;\n}\n.asciinema-terminal .bg-182 {\n  background-color: #d7afd7;\n}\n.asciinema-terminal .fg-183 {\n  color: #d7afff;\n}\n.asciinema-terminal .bg-183 {\n  background-color: #d7afff;\n}\n.asciinema-terminal .fg-184 {\n  color: #d7d700;\n}\n.asciinema-terminal .bg-184 {\n  background-color: #d7d700;\n}\n.asciinema-terminal .fg-185 {\n  color: #d7d75f;\n}\n.asciinema-terminal .bg-185 {\n  background-color: #d7d75f;\n}\n.asciinema-terminal .fg-186 {\n  color: #d7d787;\n}\n.asciinema-terminal .bg-186 {\n  background-color: #d7d787;\n}\n.asciinema-terminal .fg-187 {\n  color: #d7d7af;\n}\n.asciinema-terminal .bg-187 {\n  background-color: #d7d7af;\n}\n.asciinema-terminal .fg-188 {\n  color: #d7d7d7;\n}\n.asciinema-terminal .bg-188 {\n  background-color: #d7d7d7;\n}\n.asciinema-terminal .fg-189 {\n  color: #d7d7ff;\n}\n.asciinema-terminal .bg-189 {\n  background-color: #d7d7ff;\n}\n.asciinema-terminal .fg-190 {\n  color: #d7ff00;\n}\n.asciinema-terminal .bg-190 {\n  background-color: #d7ff00;\n}\n.asciinema-terminal .fg-191 {\n  color: #d7ff5f;\n}\n.asciinema-terminal .bg-191 {\n  background-color: #d7ff5f;\n}\n.asciinema-terminal .fg-192 {\n  color: #d7ff87;\n}\n.asciinema-terminal .bg-192 {\n  background-color: #d7ff87;\n}\n.asciinema-terminal .fg-193 {\n  color: #d7ffaf;\n}\n.asciinema-terminal .bg-193 {\n  background-color: #d7ffaf;\n}\n.asciinema-terminal .fg-194 {\n  color: #d7ffd7;\n}\n.asciinema-terminal .bg-194 {\n  background-color: #d7ffd7;\n}\n.asciinema-terminal .fg-195 {\n  color: #d7ffff;\n}\n.asciinema-terminal .bg-195 {\n  background-color: #d7ffff;\n}\n.asciinema-terminal .fg-196 {\n  color: #ff0000;\n}\n.asciinema-terminal .bg-196 {\n  background-color: #ff0000;\n}\n.asciinema-terminal .fg-197 {\n  color: #ff005f;\n}\n.asciinema-terminal .bg-197 {\n  background-color: #ff005f;\n}\n.asciinema-terminal .fg-198 {\n  color: #ff0087;\n}\n.asciinema-terminal .bg-198 {\n  background-color: #ff0087;\n}\n.asciinema-terminal .fg-199 {\n  color: #ff00af;\n}\n.asciinema-terminal .bg-199 {\n  background-color: #ff00af;\n}\n.asciinema-terminal .fg-200 {\n  color: #ff00d7;\n}\n.asciinema-terminal .bg-200 {\n  background-color: #ff00d7;\n}\n.asciinema-terminal .fg-201 {\n  color: #ff00ff;\n}\n.asciinema-terminal .bg-201 {\n  background-color: #ff00ff;\n}\n.asciinema-terminal .fg-202 {\n  color: #ff5f00;\n}\n.asciinema-terminal .bg-202 {\n  background-color: #ff5f00;\n}\n.asciinema-terminal .fg-203 {\n  color: #ff5f5f;\n}\n.asciinema-terminal .bg-203 {\n  background-color: #ff5f5f;\n}\n.asciinema-terminal .fg-204 {\n  color: #ff5f87;\n}\n.asciinema-terminal .bg-204 {\n  background-color: #ff5f87;\n}\n.asciinema-terminal .fg-205 {\n  color: #ff5faf;\n}\n.asciinema-terminal .bg-205 {\n  background-color: #ff5faf;\n}\n.asciinema-terminal .fg-206 {\n  color: #ff5fd7;\n}\n.asciinema-terminal .bg-206 {\n  background-color: #ff5fd7;\n}\n.asciinema-terminal .fg-207 {\n  color: #ff5fff;\n}\n.asciinema-terminal .bg-207 {\n  background-color: #ff5fff;\n}\n.asciinema-terminal .fg-208 {\n  color: #ff8700;\n}\n.asciinema-terminal .bg-208 {\n  background-color: #ff8700;\n}\n.asciinema-terminal .fg-209 {\n  color: #ff875f;\n}\n.asciinema-terminal .bg-209 {\n  background-color: #ff875f;\n}\n.asciinema-terminal .fg-210 {\n  color: #ff8787;\n}\n.asciinema-terminal .bg-210 {\n  background-color: #ff8787;\n}\n.asciinema-terminal .fg-211 {\n  color: #ff87af;\n}\n.asciinema-terminal .bg-211 {\n  background-color: #ff87af;\n}\n.asciinema-terminal .fg-212 {\n  color: #ff87d7;\n}\n.asciinema-terminal .bg-212 {\n  background-color: #ff87d7;\n}\n.asciinema-terminal .fg-213 {\n  color: #ff87ff;\n}\n.asciinema-terminal .bg-213 {\n  background-color: #ff87ff;\n}\n.asciinema-terminal .fg-214 {\n  color: #ffaf00;\n}\n.asciinema-terminal .bg-214 {\n  background-color: #ffaf00;\n}\n.asciinema-terminal .fg-215 {\n  color: #ffaf5f;\n}\n.asciinema-terminal .bg-215 {\n  background-color: #ffaf5f;\n}\n.asciinema-terminal .fg-216 {\n  color: #ffaf87;\n}\n.asciinema-terminal .bg-216 {\n  background-color: #ffaf87;\n}\n.asciinema-terminal .fg-217 {\n  color: #ffafaf;\n}\n.asciinema-terminal .bg-217 {\n  background-color: #ffafaf;\n}\n.asciinema-terminal .fg-218 {\n  color: #ffafd7;\n}\n.asciinema-terminal .bg-218 {\n  background-color: #ffafd7;\n}\n.asciinema-terminal .fg-219 {\n  color: #ffafff;\n}\n.asciinema-terminal .bg-219 {\n  background-color: #ffafff;\n}\n.asciinema-terminal .fg-220 {\n  color: #ffd700;\n}\n.asciinema-terminal .bg-220 {\n  background-color: #ffd700;\n}\n.asciinema-terminal .fg-221 {\n  color: #ffd75f;\n}\n.asciinema-terminal .bg-221 {\n  background-color: #ffd75f;\n}\n.asciinema-terminal .fg-222 {\n  color: #ffd787;\n}\n.asciinema-terminal .bg-222 {\n  background-color: #ffd787;\n}\n.asciinema-terminal .fg-223 {\n  color: #ffd7af;\n}\n.asciinema-terminal .bg-223 {\n  background-color: #ffd7af;\n}\n.asciinema-terminal .fg-224 {\n  color: #ffd7d7;\n}\n.asciinema-terminal .bg-224 {\n  background-color: #ffd7d7;\n}\n.asciinema-terminal .fg-225 {\n  color: #ffd7ff;\n}\n.asciinema-terminal .bg-225 {\n  background-color: #ffd7ff;\n}\n.asciinema-terminal .fg-226 {\n  color: #ffff00;\n}\n.asciinema-terminal .bg-226 {\n  background-color: #ffff00;\n}\n.asciinema-terminal .fg-227 {\n  color: #ffff5f;\n}\n.asciinema-terminal .bg-227 {\n  background-color: #ffff5f;\n}\n.asciinema-terminal .fg-228 {\n  color: #ffff87;\n}\n.asciinema-terminal .bg-228 {\n  background-color: #ffff87;\n}\n.asciinema-terminal .fg-229 {\n  color: #ffffaf;\n}\n.asciinema-terminal .bg-229 {\n  background-color: #ffffaf;\n}\n.asciinema-terminal .fg-230 {\n  color: #ffffd7;\n}\n.asciinema-terminal .bg-230 {\n  background-color: #ffffd7;\n}\n.asciinema-terminal .fg-231 {\n  color: #ffffff;\n}\n.asciinema-terminal .bg-231 {\n  background-color: #ffffff;\n}\n.asciinema-terminal .fg-232 {\n  color: #080808;\n}\n.asciinema-terminal .bg-232 {\n  background-color: #080808;\n}\n.asciinema-terminal .fg-233 {\n  color: #121212;\n}\n.asciinema-terminal .bg-233 {\n  background-color: #121212;\n}\n.asciinema-terminal .fg-234 {\n  color: #1c1c1c;\n}\n.asciinema-terminal .bg-234 {\n  background-color: #1c1c1c;\n}\n.asciinema-terminal .fg-235 {\n  color: #262626;\n}\n.asciinema-terminal .bg-235 {\n  background-color: #262626;\n}\n.asciinema-terminal .fg-236 {\n  color: #303030;\n}\n.asciinema-terminal .bg-236 {\n  background-color: #303030;\n}\n.asciinema-terminal .fg-237 {\n  color: #3a3a3a;\n}\n.asciinema-terminal .bg-237 {\n  background-color: #3a3a3a;\n}\n.asciinema-terminal .fg-238 {\n  color: #444444;\n}\n.asciinema-terminal .bg-238 {\n  background-color: #444444;\n}\n.asciinema-terminal .fg-239 {\n  color: #4e4e4e;\n}\n.asciinema-terminal .bg-239 {\n  background-color: #4e4e4e;\n}\n.asciinema-terminal .fg-240 {\n  color: #585858;\n}\n.asciinema-terminal .bg-240 {\n  background-color: #585858;\n}\n.asciinema-terminal .fg-241 {\n  color: #626262;\n}\n.asciinema-terminal .bg-241 {\n  background-color: #626262;\n}\n.asciinema-terminal .fg-242 {\n  color: #6c6c6c;\n}\n.asciinema-terminal .bg-242 {\n  background-color: #6c6c6c;\n}\n.asciinema-terminal .fg-243 {\n  color: #767676;\n}\n.asciinema-terminal .bg-243 {\n  background-color: #767676;\n}\n.asciinema-terminal .fg-244 {\n  color: #808080;\n}\n.asciinema-terminal .bg-244 {\n  background-color: #808080;\n}\n.asciinema-terminal .fg-245 {\n  color: #8a8a8a;\n}\n.asciinema-terminal .bg-245 {\n  background-color: #8a8a8a;\n}\n.asciinema-terminal .fg-246 {\n  color: #949494;\n}\n.asciinema-terminal .bg-246 {\n  background-color: #949494;\n}\n.asciinema-terminal .fg-247 {\n  color: #9e9e9e;\n}\n.asciinema-terminal .bg-247 {\n  background-color: #9e9e9e;\n}\n.asciinema-terminal .fg-248 {\n  color: #a8a8a8;\n}\n.asciinema-terminal .bg-248 {\n  background-color: #a8a8a8;\n}\n.asciinema-terminal .fg-249 {\n  color: #b2b2b2;\n}\n.asciinema-terminal .bg-249 {\n  background-color: #b2b2b2;\n}\n.asciinema-terminal .fg-250 {\n  color: #bcbcbc;\n}\n.asciinema-terminal .bg-250 {\n  background-color: #bcbcbc;\n}\n.asciinema-terminal .fg-251 {\n  color: #c6c6c6;\n}\n.asciinema-terminal .bg-251 {\n  background-color: #c6c6c6;\n}\n.asciinema-terminal .fg-252 {\n  color: #d0d0d0;\n}\n.asciinema-terminal .bg-252 {\n  background-color: #d0d0d0;\n}\n.asciinema-terminal .fg-253 {\n  color: #dadada;\n}\n.asciinema-terminal .bg-253 {\n  background-color: #dadada;\n}\n.asciinema-terminal .fg-254 {\n  color: #e4e4e4;\n}\n.asciinema-terminal .bg-254 {\n  background-color: #e4e4e4;\n}\n.asciinema-terminal .fg-255 {\n  color: #eeeeee;\n}\n.asciinema-terminal .bg-255 {\n  background-color: #eeeeee;\n}\n.asciinema-theme-asciinema .asciinema-terminal {\n  color: #cccccc;\n  background-color: #121314;\n  border-color: #121314;\n}\n.asciinema-theme-asciinema .fg-bg {\n  color: #121314;\n}\n.asciinema-theme-asciinema .bg-fg {\n  background-color: #cccccc;\n}\n.asciinema-theme-asciinema .fg-0 {\n  color: #000000;\n}\n.asciinema-theme-asciinema .bg-0 {\n  background-color: #000000;\n}\n.asciinema-theme-asciinema .fg-1 {\n  color: #dd3c69;\n}\n.asciinema-theme-asciinema .bg-1 {\n  background-color: #dd3c69;\n}\n.asciinema-theme-asciinema .fg-2 {\n  color: #4ebf22;\n}\n.asciinema-theme-asciinema .bg-2 {\n  background-color: #4ebf22;\n}\n.asciinema-theme-asciinema .fg-3 {\n  color: #ddaf3c;\n}\n.asciinema-theme-asciinema .bg-3 {\n  background-color: #ddaf3c;\n}\n.asciinema-theme-asciinema .fg-4 {\n  color: #26b0d7;\n}\n.asciinema-theme-asciinema .bg-4 {\n  background-color: #26b0d7;\n}\n.asciinema-theme-asciinema .fg-5 {\n  color: #b954e1;\n}\n.asciinema-theme-asciinema .bg-5 {\n  background-color: #b954e1;\n}\n.asciinema-theme-asciinema .fg-6 {\n  color: #54e1b9;\n}\n.asciinema-theme-asciinema .bg-6 {\n  background-color: #54e1b9;\n}\n.asciinema-theme-asciinema .fg-7 {\n  color: #d9d9d9;\n}\n.asciinema-theme-asciinema .bg-7 {\n  background-color: #d9d9d9;\n}\n.asciinema-theme-asciinema .fg-8 {\n  color: #4d4d4d;\n}\n.asciinema-theme-asciinema .bg-8 {\n  background-color: #4d4d4d;\n}\n.asciinema-theme-asciinema .fg-9 {\n  color: #dd3c69;\n}\n.asciinema-theme-asciinema .bg-9 {\n  background-color: #dd3c69;\n}\n.asciinema-theme-asciinema .fg-10 {\n  color: #4ebf22;\n}\n.asciinema-theme-asciinema .bg-10 {\n  background-color: #4ebf22;\n}\n.asciinema-theme-asciinema .fg-11 {\n  color: #ddaf3c;\n}\n.asciinema-theme-asciinema .bg-11 {\n  background-color: #ddaf3c;\n}\n.asciinema-theme-asciinema .fg-12 {\n  color: #26b0d7;\n}\n.asciinema-theme-asciinema .bg-12 {\n  background-color: #26b0d7;\n}\n.asciinema-theme-asciinema .fg-13 {\n  color: #b954e1;\n}\n.asciinema-theme-asciinema .bg-13 {\n  background-color: #b954e1;\n}\n.asciinema-theme-asciinema .fg-14 {\n  color: #54e1b9;\n}\n.asciinema-theme-asciinema .bg-14 {\n  background-color: #54e1b9;\n}\n.asciinema-theme-asciinema .fg-15 {\n  color: #ffffff;\n}\n.asciinema-theme-asciinema .bg-15 {\n  background-color: #ffffff;\n}\n.asciinema-theme-asciinema .fg-8,\n.asciinema-theme-asciinema .fg-9,\n.asciinema-theme-asciinema .fg-10,\n.asciinema-theme-asciinema .fg-11,\n.asciinema-theme-asciinema .fg-12,\n.asciinema-theme-asciinema .fg-13,\n.asciinema-theme-asciinema .fg-14,\n.asciinema-theme-asciinema .fg-15 {\n  font-weight: bold;\n}\n.asciinema-theme-tango .asciinema-terminal {\n  color: #cccccc;\n  background-color: #121314;\n  border-color: #121314;\n}\n.asciinema-theme-tango .fg-bg {\n  color: #121314;\n}\n.asciinema-theme-tango .bg-fg {\n  background-color: #cccccc;\n}\n.asciinema-theme-tango .fg-0 {\n  color: #000000;\n}\n.asciinema-theme-tango .bg-0 {\n  background-color: #000000;\n}\n.asciinema-theme-tango .fg-1 {\n  color: #cc0000;\n}\n.asciinema-theme-tango .bg-1 {\n  background-color: #cc0000;\n}\n.asciinema-theme-tango .fg-2 {\n  color: #4e9a06;\n}\n.asciinema-theme-tango .bg-2 {\n  background-color: #4e9a06;\n}\n.asciinema-theme-tango .fg-3 {\n  color: #c4a000;\n}\n.asciinema-theme-tango .bg-3 {\n  background-color: #c4a000;\n}\n.asciinema-theme-tango .fg-4 {\n  color: #3465a4;\n}\n.asciinema-theme-tango .bg-4 {\n  background-color: #3465a4;\n}\n.asciinema-theme-tango .fg-5 {\n  color: #75507b;\n}\n.asciinema-theme-tango .bg-5 {\n  background-color: #75507b;\n}\n.asciinema-theme-tango .fg-6 {\n  color: #06989a;\n}\n.asciinema-theme-tango .bg-6 {\n  background-color: #06989a;\n}\n.asciinema-theme-tango .fg-7 {\n  color: #d3d7cf;\n}\n.asciinema-theme-tango .bg-7 {\n  background-color: #d3d7cf;\n}\n.asciinema-theme-tango .fg-8 {\n  color: #555753;\n}\n.asciinema-theme-tango .bg-8 {\n  background-color: #555753;\n}\n.asciinema-theme-tango .fg-9 {\n  color: #ef2929;\n}\n.asciinema-theme-tango .bg-9 {\n  background-color: #ef2929;\n}\n.asciinema-theme-tango .fg-10 {\n  color: #8ae234;\n}\n.asciinema-theme-tango .bg-10 {\n  background-color: #8ae234;\n}\n.asciinema-theme-tango .fg-11 {\n  color: #fce94f;\n}\n.asciinema-theme-tango .bg-11 {\n  background-color: #fce94f;\n}\n.asciinema-theme-tango .fg-12 {\n  color: #729fcf;\n}\n.asciinema-theme-tango .bg-12 {\n  background-color: #729fcf;\n}\n.asciinema-theme-tango .fg-13 {\n  color: #ad7fa8;\n}\n.asciinema-theme-tango .bg-13 {\n  background-color: #ad7fa8;\n}\n.asciinema-theme-tango .fg-14 {\n  color: #34e2e2;\n}\n.asciinema-theme-tango .bg-14 {\n  background-color: #34e2e2;\n}\n.asciinema-theme-tango .fg-15 {\n  color: #eeeeec;\n}\n.asciinema-theme-tango .bg-15 {\n  background-color: #eeeeec;\n}\n.asciinema-theme-tango .fg-8,\n.asciinema-theme-tango .fg-9,\n.asciinema-theme-tango .fg-10,\n.asciinema-theme-tango .fg-11,\n.asciinema-theme-tango .fg-12,\n.asciinema-theme-tango .fg-13,\n.asciinema-theme-tango .fg-14,\n.asciinema-theme-tango .fg-15 {\n  font-weight: bold;\n}\n.asciinema-theme-solarized-dark .asciinema-terminal {\n  color: #839496;\n  background-color: #002b36;\n  border-color: #002b36;\n}\n.asciinema-theme-solarized-dark .fg-bg {\n  color: #002b36;\n}\n.asciinema-theme-solarized-dark .bg-fg {\n  background-color: #839496;\n}\n.asciinema-theme-solarized-dark .fg-0 {\n  color: #073642;\n}\n.asciinema-theme-solarized-dark .bg-0 {\n  background-color: #073642;\n}\n.asciinema-theme-solarized-dark .fg-1 {\n  color: #dc322f;\n}\n.asciinema-theme-solarized-dark .bg-1 {\n  background-color: #dc322f;\n}\n.asciinema-theme-solarized-dark .fg-2 {\n  color: #859900;\n}\n.asciinema-theme-solarized-dark .bg-2 {\n  background-color: #859900;\n}\n.asciinema-theme-solarized-dark .fg-3 {\n  color: #b58900;\n}\n.asciinema-theme-solarized-dark .bg-3 {\n  background-color: #b58900;\n}\n.asciinema-theme-solarized-dark .fg-4 {\n  color: #268bd2;\n}\n.asciinema-theme-solarized-dark .bg-4 {\n  background-color: #268bd2;\n}\n.asciinema-theme-solarized-dark .fg-5 {\n  color: #d33682;\n}\n.asciinema-theme-solarized-dark .bg-5 {\n  background-color: #d33682;\n}\n.asciinema-theme-solarized-dark .fg-6 {\n  color: #2aa198;\n}\n.asciinema-theme-solarized-dark .bg-6 {\n  background-color: #2aa198;\n}\n.asciinema-theme-solarized-dark .fg-7 {\n  color: #eee8d5;\n}\n.asciinema-theme-solarized-dark .bg-7 {\n  background-color: #eee8d5;\n}\n.asciinema-theme-solarized-dark .fg-8 {\n  color: #002b36;\n}\n.asciinema-theme-solarized-dark .bg-8 {\n  background-color: #002b36;\n}\n.asciinema-theme-solarized-dark .fg-9 {\n  color: #cb4b16;\n}\n.asciinema-theme-solarized-dark .bg-9 {\n  background-color: #cb4b16;\n}\n.asciinema-theme-solarized-dark .fg-10 {\n  color: #586e75;\n}\n.asciinema-theme-solarized-dark .bg-10 {\n  background-color: #586e75;\n}\n.asciinema-theme-solarized-dark .fg-11 {\n  color: #657b83;\n}\n.asciinema-theme-solarized-dark .bg-11 {\n  background-color: #657b83;\n}\n.asciinema-theme-solarized-dark .fg-12 {\n  color: #839496;\n}\n.asciinema-theme-solarized-dark .bg-12 {\n  background-color: #839496;\n}\n.asciinema-theme-solarized-dark .fg-13 {\n  color: #6c71c4;\n}\n.asciinema-theme-solarized-dark .bg-13 {\n  background-color: #6c71c4;\n}\n.asciinema-theme-solarized-dark .fg-14 {\n  color: #93a1a1;\n}\n.asciinema-theme-solarized-dark .bg-14 {\n  background-color: #93a1a1;\n}\n.asciinema-theme-solarized-dark .fg-15 {\n  color: #fdf6e3;\n}\n.asciinema-theme-solarized-dark .bg-15 {\n  background-color: #fdf6e3;\n}\n.asciinema-theme-solarized-light .asciinema-terminal {\n  color: #657b83;\n  background-color: #fdf6e3;\n  border-color: #fdf6e3;\n}\n.asciinema-theme-solarized-light .fg-bg {\n  color: #fdf6e3;\n}\n.asciinema-theme-solarized-light .bg-fg {\n  background-color: #657b83;\n}\n.asciinema-theme-solarized-light .fg-0 {\n  color: #073642;\n}\n.asciinema-theme-solarized-light .bg-0 {\n  background-color: #073642;\n}\n.asciinema-theme-solarized-light .fg-1 {\n  color: #dc322f;\n}\n.asciinema-theme-solarized-light .bg-1 {\n  background-color: #dc322f;\n}\n.asciinema-theme-solarized-light .fg-2 {\n  color: #859900;\n}\n.asciinema-theme-solarized-light .bg-2 {\n  background-color: #859900;\n}\n.asciinema-theme-solarized-light .fg-3 {\n  color: #b58900;\n}\n.asciinema-theme-solarized-light .bg-3 {\n  background-color: #b58900;\n}\n.asciinema-theme-solarized-light .fg-4 {\n  color: #268bd2;\n}\n.asciinema-theme-solarized-light .bg-4 {\n  background-color: #268bd2;\n}\n.asciinema-theme-solarized-light .fg-5 {\n  color: #d33682;\n}\n.asciinema-theme-solarized-light .bg-5 {\n  background-color: #d33682;\n}\n.asciinema-theme-solarized-light .fg-6 {\n  color: #2aa198;\n}\n.asciinema-theme-solarized-light .bg-6 {\n  background-color: #2aa198;\n}\n.asciinema-theme-solarized-light .fg-7 {\n  color: #eee8d5;\n}\n.asciinema-theme-solarized-light .bg-7 {\n  background-color: #eee8d5;\n}\n.asciinema-theme-solarized-light .fg-8 {\n  color: #002b36;\n}\n.asciinema-theme-solarized-light .bg-8 {\n  background-color: #002b36;\n}\n.asciinema-theme-solarized-light .fg-9 {\n  color: #cb4b16;\n}\n.asciinema-theme-solarized-light .bg-9 {\n  background-color: #cb4b16;\n}\n.asciinema-theme-solarized-light .fg-10 {\n  color: #586e75;\n}\n.asciinema-theme-solarized-light .bg-10 {\n  background-color: #586e75;\n}\n.asciinema-theme-solarized-light .fg-11 {\n  color: #657c83;\n}\n.asciinema-theme-solarized-light .bg-11 {\n  background-color: #657c83;\n}\n.asciinema-theme-solarized-light .fg-12 {\n  color: #839496;\n}\n.asciinema-theme-solarized-light .bg-12 {\n  background-color: #839496;\n}\n.asciinema-theme-solarized-light .fg-13 {\n  color: #6c71c4;\n}\n.asciinema-theme-solarized-light .bg-13 {\n  background-color: #6c71c4;\n}\n.asciinema-theme-solarized-light .fg-14 {\n  color: #93a1a1;\n}\n.asciinema-theme-solarized-light .bg-14 {\n  background-color: #93a1a1;\n}\n.asciinema-theme-solarized-light .fg-15 {\n  color: #fdf6e3;\n}\n.asciinema-theme-solarized-light .bg-15 {\n  background-color: #fdf6e3;\n}\n.asciinema-theme-seti .asciinema-terminal {\n  color: #cacecd;\n  background-color: #111213;\n  border-color: #111213;\n}\n.asciinema-theme-seti .fg-bg {\n  color: #111213;\n}\n.asciinema-theme-seti .bg-fg {\n  background-color: #cacecd;\n}\n.asciinema-theme-seti .fg-0 {\n  color: #323232;\n}\n.asciinema-theme-seti .bg-0 {\n  background-color: #323232;\n}\n.asciinema-theme-seti .fg-1 {\n  color: #c22832;\n}\n.asciinema-theme-seti .bg-1 {\n  background-color: #c22832;\n}\n.asciinema-theme-seti .fg-2 {\n  color: #8ec43d;\n}\n.asciinema-theme-seti .bg-2 {\n  background-color: #8ec43d;\n}\n.asciinema-theme-seti .fg-3 {\n  color: #e0c64f;\n}\n.asciinema-theme-seti .bg-3 {\n  background-color: #e0c64f;\n}\n.asciinema-theme-seti .fg-4 {\n  color: #43a5d5;\n}\n.asciinema-theme-seti .bg-4 {\n  background-color: #43a5d5;\n}\n.asciinema-theme-seti .fg-5 {\n  color: #8b57b5;\n}\n.asciinema-theme-seti .bg-5 {\n  background-color: #8b57b5;\n}\n.asciinema-theme-seti .fg-6 {\n  color: #8ec43d;\n}\n.asciinema-theme-seti .bg-6 {\n  background-color: #8ec43d;\n}\n.asciinema-theme-seti .fg-7 {\n  color: #eeeeee;\n}\n.asciinema-theme-seti .bg-7 {\n  background-color: #eeeeee;\n}\n.asciinema-theme-seti .fg-8 {\n  color: #323232;\n}\n.asciinema-theme-seti .bg-8 {\n  background-color: #323232;\n}\n.asciinema-theme-seti .fg-9 {\n  color: #c22832;\n}\n.asciinema-theme-seti .bg-9 {\n  background-color: #c22832;\n}\n.asciinema-theme-seti .fg-10 {\n  color: #8ec43d;\n}\n.asciinema-theme-seti .bg-10 {\n  background-color: #8ec43d;\n}\n.asciinema-theme-seti .fg-11 {\n  color: #e0c64f;\n}\n.asciinema-theme-seti .bg-11 {\n  background-color: #e0c64f;\n}\n.asciinema-theme-seti .fg-12 {\n  color: #43a5d5;\n}\n.asciinema-theme-seti .bg-12 {\n  background-color: #43a5d5;\n}\n.asciinema-theme-seti .fg-13 {\n  color: #8b57b5;\n}\n.asciinema-theme-seti .bg-13 {\n  background-color: #8b57b5;\n}\n.asciinema-theme-seti .fg-14 {\n  color: #8ec43d;\n}\n.asciinema-theme-seti .bg-14 {\n  background-color: #8ec43d;\n}\n.asciinema-theme-seti .fg-15 {\n  color: #ffffff;\n}\n.asciinema-theme-seti .bg-15 {\n  background-color: #ffffff;\n}\n.asciinema-theme-seti .fg-8,\n.asciinema-theme-seti .fg-9,\n.asciinema-theme-seti .fg-10,\n.asciinema-theme-seti .fg-11,\n.asciinema-theme-seti .fg-12,\n.asciinema-theme-seti .fg-13,\n.asciinema-theme-seti .fg-14,\n.asciinema-theme-seti .fg-15 {\n  font-weight: bold;\n}\n/* Based on Monokai from base16 collection - https://github.com/chriskempson/base16 */\n.asciinema-theme-monokai .asciinema-terminal {\n  color: #f8f8f2;\n  background-color: #272822;\n  border-color: #272822;\n}\n.asciinema-theme-monokai .fg-bg {\n  color: #272822;\n}\n.asciinema-theme-monokai .bg-fg {\n  background-color: #f8f8f2;\n}\n.asciinema-theme-monokai .fg-0 {\n  color: #272822;\n}\n.asciinema-theme-monokai .bg-0 {\n  background-color: #272822;\n}\n.asciinema-theme-monokai .fg-1 {\n  color: #f92672;\n}\n.asciinema-theme-monokai .bg-1 {\n  background-color: #f92672;\n}\n.asciinema-theme-monokai .fg-2 {\n  color: #a6e22e;\n}\n.asciinema-theme-monokai .bg-2 {\n  background-color: #a6e22e;\n}\n.asciinema-theme-monokai .fg-3 {\n  color: #f4bf75;\n}\n.asciinema-theme-monokai .bg-3 {\n  background-color: #f4bf75;\n}\n.asciinema-theme-monokai .fg-4 {\n  color: #66d9ef;\n}\n.asciinema-theme-monokai .bg-4 {\n  background-color: #66d9ef;\n}\n.asciinema-theme-monokai .fg-5 {\n  color: #ae81ff;\n}\n.asciinema-theme-monokai .bg-5 {\n  background-color: #ae81ff;\n}\n.asciinema-theme-monokai .fg-6 {\n  color: #a1efe4;\n}\n.asciinema-theme-monokai .bg-6 {\n  background-color: #a1efe4;\n}\n.asciinema-theme-monokai .fg-7 {\n  color: #f8f8f2;\n}\n.asciinema-theme-monokai .bg-7 {\n  background-color: #f8f8f2;\n}\n.asciinema-theme-monokai .fg-8 {\n  color: #75715e;\n}\n.asciinema-theme-monokai .bg-8 {\n  background-color: #75715e;\n}\n.asciinema-theme-monokai .fg-9 {\n  color: #f92672;\n}\n.asciinema-theme-monokai .bg-9 {\n  background-color: #f92672;\n}\n.asciinema-theme-monokai .fg-10 {\n  color: #a6e22e;\n}\n.asciinema-theme-monokai .bg-10 {\n  background-color: #a6e22e;\n}\n.asciinema-theme-monokai .fg-11 {\n  color: #f4bf75;\n}\n.asciinema-theme-monokai .bg-11 {\n  background-color: #f4bf75;\n}\n.asciinema-theme-monokai .fg-12 {\n  color: #66d9ef;\n}\n.asciinema-theme-monokai .bg-12 {\n  background-color: #66d9ef;\n}\n.asciinema-theme-monokai .fg-13 {\n  color: #ae81ff;\n}\n.asciinema-theme-monokai .bg-13 {\n  background-color: #ae81ff;\n}\n.asciinema-theme-monokai .fg-14 {\n  color: #a1efe4;\n}\n.asciinema-theme-monokai .bg-14 {\n  background-color: #a1efe4;\n}\n.asciinema-theme-monokai .fg-15 {\n  color: #f9f8f5;\n}\n.asciinema-theme-monokai .bg-15 {\n  background-color: #f9f8f5;\n}\n.asciinema-theme-monokai .fg-8,\n.asciinema-theme-monokai .fg-9,\n.asciinema-theme-monokai .fg-10,\n.asciinema-theme-monokai .fg-11,\n.asciinema-theme-monokai .fg-12,\n.asciinema-theme-monokai .fg-13,\n.asciinema-theme-monokai .fg-14,\n.asciinema-theme-monokai .fg-15 {\n  font-weight: bold;\n}\n"
  },
  {
    "path": "src/resources/css/chroma.css",
    "content": "/*\n\tDerived from https://gist.github.com/nicolashery/5765395\n\tAdjusted to be high-contrast\n*/\n\n\n/*\n\tSolarized Light (High Contrast)\n\tDerived from http://ethanschoonover.com/solarized\n*/\n.chroma {\n\tbackground: linear-gradient(0deg, #f8fbfd 0%, #edf5fd 100%);\n\tcolor: #254048;\n}\n.chroma .c { color: #93a1a1 } /* Comment */\n.chroma .err { color: #586e75 } /* Error */\n.chroma .g { color: #586e75 } /* Generic */\n.chroma .k { color: #577b00 } /* Keyword */\n.chroma .l { color: #586e75 } /* Literal */\n.chroma .n { color: #586e75 } /* Name */\n.chroma .o { color: #577b00 } /* Operator */\n.chroma .x { color: #d03d00 } /* Other */\n.chroma .p { color: #586e75 } /* Punctuation */\n.chroma .cm { color: #93a1a1 } /* Comment.Multiline */\n.chroma .cp { color: #577b00 } /* Comment.Preproc */\n.chroma .c1 { color: #93a1a1 } /* Comment.Single */\n.chroma .cs { color: #577b00 } /* Comment.Special */\n.chroma .gd { color: #dc322f; background-color: #efdede } /* Generic.Deleted */\n.chroma .ge { color: #586e75; font-style: italic } /* Generic.Emph */\n.chroma .gr { color: #dc322f } /* Generic.Error */\n.chroma .gh { color: #d03d00 } /* Generic.Heading */\n.chroma .gi { color: #577b00; background-color: #ddecdc } /* Generic.Inserted */\n.chroma .go { color: #586e75 } /* Generic.Output */\n.chroma .gp { color: #586e75 } /* Generic.Prompt */\n.chroma .gs { color: #586e75; font-weight: bold } /* Generic.Strong */\n.chroma .gu { color: #d03d00 } /* Generic.Subheading */\n.chroma .gt { color: #586e75 } /* Generic.Traceback */\n.chroma .kc { color: #d03d00 } /* Keyword.Constant */\n.chroma .kd { color: #0673bf } /* Keyword.Declaration */\n.chroma .kn { color: #577b00 } /* Keyword.Namespace */\n.chroma .kp { color: #577b00 } /* Keyword.Pseudo */\n.chroma .kr { color: #0673bf } /* Keyword.Reserved */\n.chroma .kt { color: #dc322f } /* Keyword.Type */\n.chroma .ld { color: #586e75 } /* Literal.Date */\n.chroma .m { color: #008076 } /* Literal.Number */\n.chroma .s { color: #008076 } /* Literal.String */\n.chroma .na { color: #586e75 } /* Name.Attribute */\n.chroma .nb { color: #B58900 } /* Name.Builtin */\n.chroma .nc { color: #0673bf } /* Name.Class */\n.chroma .no { color: #d03d00 } /* Name.Constant */\n.chroma .nd { color: #0673bf } /* Name.Decorator */\n.chroma .ni { color: #d03d00 } /* Name.Entity */\n.chroma .ne { color: #d03d00 } /* Name.Exception */\n.chroma .nf { color: #0673bf } /* Name.Function */\n.chroma .nl { color: #586e75 } /* Name.Label */\n.chroma .nn { color: #586e75 } /* Name.Namespace */\n.chroma .nx { color: #586e75 } /* Name.Other */\n.chroma .py { color: #586e75 } /* Name.Property */\n.chroma .nt { color: #0673bf } /* Name.Tag */\n.chroma .nv { color: #0673bf } /* Name.Variable */\n.chroma .ow { color: #577b00 } /* Operator.Word */\n.chroma .w { color: #586e75 } /* Text.Whitespace */\n.chroma .mf { color: #008076 } /* Literal.Number.Float */\n.chroma .mh { color: #008076 } /* Literal.Number.Hex */\n.chroma .mi { color: #008076 } /* Literal.Number.Integer */\n.chroma .mo { color: #008076 } /* Literal.Number.Oct */\n.chroma .sb { color: #93a1a1 } /* Literal.String.Backtick */\n.chroma .sc { color: #008076 } /* Literal.String.Char */\n.chroma .sd { color: #586e75 } /* Literal.String.Doc */\n.chroma .s2 { color: #008076 } /* Literal.String.Double */\n.chroma .se { color: #d03d00 } /* Literal.String.Escape */\n.chroma .sh { color: #586e75 } /* Literal.String.Heredoc */\n.chroma .si { color: #008076 } /* Literal.String.Interpol */\n.chroma .sx { color: #008076 } /* Literal.String.Other */\n.chroma .sr { color: #dc322f } /* Literal.String.Regex */\n.chroma .s1 { color: #008076 } /* Literal.String.Single */\n.chroma .ss { color: #008076 } /* Literal.String.Symbol */\n.chroma .bp { color: #0673bf } /* Name.Builtin.Pseudo */\n.chroma .vc { color: #0673bf } /* Name.Variable.Class */\n.chroma .vg { color: #0673bf } /* Name.Variable.Global */\n.chroma .vi { color: #0673bf } /* Name.Variable.Instance */\n.chroma .il { color: #008076 } /* Literal.Number.Integer.Long */\n\n@media (prefers-color-scheme: dark) {\n\t/*\n\t\tSolarized Dark (High Contrast)\n\t\tDerived from http://ethanschoonover.com/solarized\n\t*/\n\t.chroma {\n\t\tbackground: linear-gradient(0deg, #18384d 0%, #122537 100%);\n\t\tcolor: #93a1a1;\n\t}\n\t.chroma .c { color: #586e75 } /* Comment */\n\t.chroma .err { color: #93a1a1 } /* Error */\n\t.chroma .g { color: #93a1a1 } /* Generic */\n\t.chroma .k { color: #76a507 } /* Keyword */\n\t.chroma .l { color: #93a1a1 } /* Literal */\n\t.chroma .n { color: #93a1a1 } /* Name */\n\t.chroma .o { color: #76a507 } /* Operator */\n\t.chroma .x { color: #ec662e } /* Other */\n\t.chroma .p { color: #93a1a1 } /* Punctuation */\n\t.chroma .cm { color: #586e75 } /* Comment.Multiline */\n\t.chroma .cp { color: #76a507 } /* Comment.Preproc */\n\t.chroma .c1 { color: #586e75 } /* Comment.Single */\n\t.chroma .cs { color: #76a507 } /* Comment.Special */\n\t.chroma .gd { color: #dc322f; background-color: #efdede } /* Generic.Deleted */\n\t.chroma .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */\n\t.chroma .gr { color: #dc322f } /* Generic.Error */\n\t.chroma .gh { color: #ec662e } /* Generic.Heading */\n\t.chroma .gi { color: #76a507 } /* Generic.Inserted */\n\t.chroma .go { color: #93a1a1 } /* Generic.Output */\n\t.chroma .gp { color: #93a1a1 } /* Generic.Prompt */\n\t.chroma .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */\n\t.chroma .gu { color: #ec662e } /* Generic.Subheading */\n\t.chroma .gt { color: #93a1a1 } /* Generic.Traceback */\n\t.chroma .kc { color: #ec662e } /* Keyword.Constant */\n\t.chroma .kd { color: #0090f5 } /* Keyword.Declaration */\n\t.chroma .kn { color: #76a507 } /* Keyword.Namespace */\n\t.chroma .kp { color: #76a507 } /* Keyword.Pseudo */\n\t.chroma .kr { color: #0090f5 } /* Keyword.Reserved */\n\t.chroma .kt { color: #dc322f } /* Keyword.Type */\n\t.chroma .ld { color: #93a1a1 } /* Literal.Date */\n\t.chroma .m { color: #09a598 } /* Literal.Number */\n\t.chroma .s { color: #09a598 } /* Literal.String */\n\t.chroma .na { color: #93a1a1 } /* Name.Attribute */\n\t.chroma .nb { color: #B58900 } /* Name.Builtin */\n\t.chroma .nc { color: #0090f5 } /* Name.Class */\n\t.chroma .no { color: #ec662e } /* Name.Constant */\n\t.chroma .nd { color: #0090f5 } /* Name.Decorator */\n\t.chroma .ni { color: #ec662e } /* Name.Entity */\n\t.chroma .ne { color: #ec662e } /* Name.Exception */\n\t.chroma .nf { color: #0090f5 } /* Name.Function */\n\t.chroma .nl { color: #93a1a1 } /* Name.Label */\n\t.chroma .nn { color: #93a1a1 } /* Name.Namespace */\n\t.chroma .nx { color: #93a1a1 } /* Name.Other */\n\t.chroma .py { color: #93a1a1 } /* Name.Property */\n\t.chroma .nt { color: #0090f5 } /* Name.Tag */\n\t.chroma .nv { color: #0090f5 } /* Name.Variable */\n\t.chroma .ow { color: #76a507 } /* Operator.Word */\n\t.chroma .w { color: #93a1a1 } /* Text.Whitespace */\n\t.chroma .mf { color: #09a598 } /* Literal.Number.Float */\n\t.chroma .mh { color: #09a598 } /* Literal.Number.Hex */\n\t.chroma .mi { color: #09a598 } /* Literal.Number.Integer */\n\t.chroma .mo { color: #09a598 } /* Literal.Number.Oct */\n\t.chroma .sb { color: #586e75 } /* Literal.String.Backtick */\n\t.chroma .sc { color: #09a598 } /* Literal.String.Char */\n\t.chroma .sd { color: #93a1a1 } /* Literal.String.Doc */\n\t.chroma .s2 { color: #09a598 } /* Literal.String.Double */\n\t.chroma .se { color: #ec662e } /* Literal.String.Escape */\n\t.chroma .sh { color: #93a1a1 } /* Literal.String.Heredoc */\n\t.chroma .si { color: #09a598 } /* Literal.String.Interpol */\n\t.chroma .sx { color: #09a598 } /* Literal.String.Other */\n\t.chroma .sr { color: #dc322f } /* Literal.String.Regex */\n\t.chroma .s1 { color: #09a598 } /* Literal.String.Single */\n\t.chroma .ss { color: #09a598 } /* Literal.String.Symbol */\n\t.chroma .bp { color: #0090f5 } /* Name.Builtin.Pseudo */\n\t.chroma .vc { color: #0090f5 } /* Name.Variable.Class */\n\t.chroma .vg { color: #0090f5 } /* Name.Variable.Global */\n\t.chroma .vi { color: #0090f5 } /* Name.Variable.Instance */\n\t.chroma .il { color: #09a598 } /* Literal.Number.Integer.Long */\n}\n"
  },
  {
    "path": "src/resources/css/common.css",
    "content": "* {\n\tmargin: 0;\n\tpadding: 0;\n\tbox-sizing: border-box;\n}\n\nbody {\n\tfont-family: Inter, sans-serif;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\ttab-size: 4;\n\t-moz-tab-size: 4;\n}\n\n.wrapper {\n\tmax-width: 1400px;\n\tmargin-left: auto;\n\tmargin-right: auto;\n\tpadding-left: 40px;\n\tpadding-right: 40px;\n}\n\n.text-center {\n\ttext-align: center;\n}\n\n.text-right {\n\ttext-align: right;\n}\n\n.float-right {\n\tfloat: right;\n}\n\na {\n\tcolor: #0694f1;\n\ttext-decoration: none;\n}\n\na:hover {\n\tcolor: #ff3f2c;\n}\n\nheader {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tpadding: 25px 0;\n}\n\n#logo-container {\n\tflex: 1;\n}\n\n#logo {\n\theight: 40px;\n}\n\n#zerossl-logo {\n\theight: 1.5em;\n\tvertical-align: middle;\n\tmargin: 0 4px;\n}\n\nheader nav {\n\ttext-align: right;\n\tline-height: 40px;\n}\n\nheader nav > a {\n\tdisplay: inline-block;\n\tpadding-left: 12px;\n\tpadding-right: 12px;\n\ttext-decoration: none;\n\tcolor: inherit;\n}\n\nheader nav > a:hover {\n\tcolor: #ff3f2c;\n}\n\nheader nav > a.current {\n\tfont-weight: bold;\n}\n\n/* Algolia search */\n#search {\n\tborder-radius: 5px;\n\tborder: 1px solid rgba(0, 0, 0, .2);\n\tfont-size: 14px;\n\tpadding: 8px;\n\tbackground: none;\n\twidth: 200px;\n\tmax-width: 100%;\n\tmargin-right: 20px;\n\tmargin-top: 4px;\n\toutline: none;\n}\n\n#search:focus {\n\tbackground: #fff;\n}\n\n.algolia-autocomplete .ds-dropdown-menu {\n\twidth: 90%;\n\tmax-width: 800px;\n}\n/* End Algolia search */\n\nheader nav .new {\n\tbackground-color: #ffe300;\n\tfont-size: 75%;\n\tvertical-align: top;\n\tpadding: 4px 8px;\n\tborder-radius: 1em;\n}\n\nheader nav .button {\n\tmargin: 0 0 0 10px;\n\tpadding-top: 2px;\n\tpadding-bottom: 2px;\n}  \n\nbutton,\n.button {\n\tborder-radius: 2em;\n\tpadding: 10px 20px;\n\tmargin: 15px 0;\n\theight: auto;\n\ttransition: all .2s;\n\ttext-decoration: none;\n\tdisplay: inline-block;\n\tborder: 0;\n\tcursor: pointer;\n}\n\nbutton:hover,\n.button:hover {\n\ttransform: scale(1.05);\n}\n\nbutton:active,\n.button:active {\n\ttransform: translateY(2px);\n}\n\nbutton.red,\n.button.red {\n\tbackground-color: #d9552b;\n\tcolor: white;\n}\n\nbutton.red:hover,\n.button.red:hover {\n\tbackground-color: #fd511a;\n}\n\nbutton.blue,\n.button.blue {\n\tbackground-color: #0082d0;\n\tcolor: white;\n}\n\nbutton.blue:hover,\n.button.blue:hover {\n\tbackground-color: #00aaff;\n}\n\nbutton.gray,\n.button.gray {\n\tbackground-color: #4c6a79;\n\tcolor: white;\n}\n\nbutton.gray:hover,\n.button.gray:hover {\n\tbackground-color: #4f8098;\n}\n\nbutton.big,\n.button.big {\n\tfont-size: 125%;\n\ttext-transform: uppercase;\n\tfont-weight: bold;\n\tpadding: 20px 50px;\n\tmargin-right: 20px;\n}\n\nbutton.disabled,\n.button.disabled,\nbutton:disabled,\n.button:disabled {\n\tbackground-color: #aaa !important;\n\tcolor: white !important;\n\ttransform: none !important;\n\tcursor: not-allowed;\n}\n\n\np button,\np .button {\n\tfont-size: 18px;\n\tpadding: 12px 30px;\n}\n\n\narticle a:hover {\n\ttext-decoration: underline;\n}\n\npre,\ncode {\n\tfont-family: 'PT Mono', 'Source Code Pro', monospace;\n\tpadding: 3px 6px;\n\tfont-size: 95%;\n\tline-height: 1.5em;\n}\n\n\nfooter {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tmargin-top: 60px;\n\tpadding-bottom: 20px;\n\tline-height: 125%;\n}\n\nfooter > div {\n\twidth: 50%;\n}\n\n#footer-logo {\n\tfloat: left;\n\tmax-width: 175px;\n\tmargin-right: 35px;\n\tvertical-align: middle;\n\tmargin-top: -4px;\n}\n\n.copyright {\n\ttext-align: right;\n\tfont-size: 14px;\n\tcolor: #999;\n}\n\n@media (max-width: 900px) {\n\theader {\n\t\tflex-direction: column;\n\t}\n\n\t#logo-container {\n\t\ttext-align: center;\n\t}\n\n\theader nav {\n\t\ttext-align: center;\n\t}\n\n\tfooter {\n\t\tflex-direction: column;\n\t\tmargin-top: 0;\n\t\tpadding: 20px 0;\n\t}\n\n\tfooter > div {\n\t\twidth: initial;\n\t\ttext-align: center;\n\t\tmargin: 10px 0;\n\t}\n\n\t#footer-logo {\n\t\tfloat: none;\n\t\tdisplay: block;\n\t\tmargin: 0 auto 25px;\n\t}\n\n\t.copyright {\n\t\ttext-align: center;\n\t}\n}\n"
  },
  {
    "path": "src/resources/css/docs-json.css",
    "content": "article {\n\tpadding-top: 0 !important;\n}\n\narticle h1 {\n\tpadding-top: 8%;\n}\n\n.renderbox {\n\tborder-radius: 0;\n\tfont-size: 20px;\n\tline-height: 1.6em;\n}\n\n.toggle-obj {\n\tpadding: 0 .5em;\n\tvisibility: hidden;\n}\n\n.group:hover > .toggle-obj {\n\tvisibility: visible;\n}\n\n.collapsed {\n\tdisplay: none;\n}\n\n@media (prefers-color-scheme: dark) {\n\tpre > code.json {\n\t\tbackground-color: #07212b;\n\t}\n}"
  },
  {
    "path": "src/resources/css/docs.css",
    "content": "body {\n\tfont-family: Maven Pro, sans-serif;\n\tbackground: #f1f4f5;\n}\n\nheader {\n\tpadding: 20px 40px;\n}\n\n#logo-container {\n\tdisplay: flex;\n\talign-items: center;\n\tline-height: 0;\n}\n\n#logo {\n\theight: 25px;\n\tline-height: 0;\n}\n\n#logo-docs {\n\tfont-family: Montserrat, sans-serif;\n\tfont-weight: bold;\n\ttext-transform: uppercase;\n\tcolor: #0e495e;\n\tmargin-left: 10px;\n\tfont-size: 12px;\n}\n\n#zerossl-project {\n\tfont-size: 12px;\n\tline-height: 1em;\n\tpadding-left: 1.5em;\n}\n\n#search {\n\tmargin-top: 0;\n}\n\nheader nav {\n\tfont-size: 16px;\n\tline-height: 2em;\n}\n\nheader nav .button {\n\tpadding-top: 4px;\n\tpadding-bottom: 4px;\n}\n\n.breadcrumbs {\n\tpadding: 10px 20px !important;\n\tmargin-bottom: 0 !important;\n\tcolor: #2861aa;\n\tfont-size: 16px;\n\tfont-weight: bold;\n\tline-height: 1.75em;\n}\n\n#top-breadcrumb {\n\tfont-weight: bold;\n}\n\n#top-breadcrumb:not(:hover) {\n\tcolor: #2861aa;\n}\n\n.breadcrumbs a {\n\tfont-weight: normal;\n\ttext-decoration: none;\n}\n\n.breadcrumb-siblings-title {\n\tpadding: 10px;\n\tfont-weight: bold;\n}\n\n.breadcrumb-siblings a {\n\tdisplay: block;\n\tpadding: 0 10px;\n\ttext-decoration: none;\n}\n\n.breadcrumb-siblings a+a {\n\tpadding-top: 10px;\n}\n\n.breadcrumb-siblings a:last-child {\n\tpadding-bottom: 10px;\n}\n\nmain {\n\tdisplay: flex;\n\tjustify-content: space-between;\n}\n\nmain > .sidebar {\n\twidth: 18%;\n\tflex-shrink: 0;\n\tflex-grow: 1;\n\tpadding: 20px;\n\tmax-width: 400px;\n}\n\nmain > .sidebar:last-child {\n\tflex-shrink: 1;\n}\n\nmain > nav.sidebar {\n\tposition: relative;\n\tfont-size: 16px;\n}\n\nmain nav li img,\narticle li img {\n\tmax-height: .9em;\n}\n\nmain nav ul {\n\tlist-style-type: none;\n}\n\nmain nav li {\n\tposition: relative;\n}\n\nmain nav li a {\n\tdisplay: block;\n\ttext-decoration: none;\n\tcolor: inherit;\n\tborder-radius: 1.5em;\n\tpadding: 6px 18px 6px 28px;\n\tcolor: #546c75;\n}\n\n\n\nmain nav li a:hover {\n\tcolor: #01324b;\n}\n\nmain nav li a.current {\n\tbackground-color: #e0e8f0;\n}\n\nmain nav li.heading {\n\tfont-weight: bold;\n\ttext-transform: uppercase;\n\tfont-size: 90%;\n\tletter-spacing: 1px;\n\tpadding: 10px 20px 10px 2em;\n}\n\nmain nav li.heading:not(:first-child) {\n\tmargin-top: 20px;\n}\n\nmain nav li li a {\n\tpadding-top: 6px;\n\tpadding-bottom: 4px;\n\tpadding-left: 3em;\n\tfont-size: 90%;\n}\n\n.paper {\n\tposition: relative;\n\tflex-shrink: 0;\n\twidth: 100%;\n\n\tbackground: white;\n\tborder-radius: 4px;\n\tbox-shadow: 1px 2px 4px 1px rgba(0, 0, 0, .15);\n}\n\n#paper1,\n#paper2 {\n\tbackground: #f8fafb;\n\tmax-height: 1500px;\n}\n\n#paper1 {\n\ttop: -20px;\n\tleft: 20px;\n\ttransform: rotate(3deg);\n}\n\n#paper2 {\n\ttop: 30px;\n\tleft: -100%;\n\ttransform: rotate(-5deg);\n}\n\n.paper3 {\n\ttop: 20px;\n\tleft: -200%;\n}\n\n.article-container {\n\tdisplay: flex;\n\talign-content: flex-start;\n\tflex-grow: 1;\n\tmin-width: 0;\n\tmargin: 20px;\n\twidth: 100%;\n\tmax-width: 1100px;\n}\n\n.pad {\n\tpadding-top: 8%;\n}\n\narticle {\n\tpadding-top: 8%;\n\tpadding-bottom: 8%;\n\tfont-size: 20px;\n\tword-wrap: break-word;\n}\n\n/*\nwhile we want most elements that are rendered\nserver-side from markdown to have a constrained\nwidth, a few elements should be allowed to\nextend to the borders of the page\n*/\narticle > :not(.fullwidth),\narticle > .fullwidth > *,\n.pad {\n\tpadding-left: 8%;\n\tpadding-right: 8%;\n}\narticle > :not(h1),\ndd,\narticle p,\narticle ol,\narticle ul,\narticle pre,\narticle table {\n\tmargin-bottom: 1.5rem;\n}\narticle > .fullwidth { margin-bottom: 1.5rem; }\narticle > .fullwidth > * { margin-bottom: 0; }\n\narticle > pre.chroma > code {\n\tbackground: none;\n\tpadding: 0;\n}\narticle > pre.chroma {\n\tpadding-top: 2em;\n\tpadding-bottom: 2em;\n}\n\narticle ul,\narticle ol,\n#hovercard ul,\n#hovercard ol {\n\tmargin-left: 2.5em;\n}\n\narticle ul ul,\narticle ol ol,\narticle ol ul,\narticle ul ol {\n\tmargin-bottom: 0;\n}\n\narticle p,\narticle li {\n\tline-height: 1.5em;\n}\n\narticle li p,\narticle li ul,\narticle li ol {\n\tmargin-bottom: .5em;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n\t/* to ensure that the anchor-link icons stay inside the heading */\n\tposition: relative;\n}\n\nh1,\nh2 {\n\ttext-align: center;\n}\n\nh1 {\n\tfont-size: 72px;\n\tcolor: #0e3e5b;\n\tletter-spacing: -2px;\n\tmargin-bottom: 50px;\n}\n\nh2 {\n\tfont-size: 46px;\n\tpadding-bottom: 15px;\n\tborder-bottom: 4px solid #72abe8;\n\tmargin: 100px 0 40px !important;\n}\n\nh3 {\n\tfont-size: 34px;\n\tmargin: 50px 0 20px;\n}\n\nh4 {\n\tfont-size: 24px;\n\tmargin: 25px 0 15px;\n}\n\nh5 {\n\tfont-size: 22px;\n\tmargin: 2em 0 1em;\n}\n\n.anchor-link {\n\topacity: 0;\n\tfont-size: .6em;\n\tborder-radius: 10px;\n\tpadding: .3em .5em;\n\tposition: absolute;\n\tleft: 0;\n\ttop: 5px;\n}\n\n*:hover > .anchor-link,\n.anchor-link:focus {\n\topacity: 1;\n\ttext-decoration: none;\n}\n\n.anchor-link:hover {\n\tbackground-color: rgba(0, 0, 0, .075);\n}\n\ncode {\n\tbackground-color: #e9f1fb;\n\tborder-radius: 5px;\n}\n\ncode.cmd {\n\tbackground-color: #333;\n\tcolor: #eaeaea;\n}\n\npre > code,\npre.chroma,\n.group {\n\tdisplay: block;\n\twhite-space: pre;\n}\n\npre > code,\narticle > pre {\n\tpadding: 1em;\n\toverflow: auto;\n}\n\npre > code.cmd {\n\tborder-radius: 5px;\n}\n\ncode.cmd.bash,\ncode.cmd .bash {\n\tfont-weight: bold;\n}\n\ncode.cmd.bash::before,\ncode.cmd .bash::before {\n    content: '$';\n\tmargin-right: .5rem;\n}\n\ndt:hover .inline-link {\n\tvisibility: visible;\n}\n\ndd {\n\tmargin-left: 1em;\n}\n\n#field-list-header {\n\tdisplay: none;\n}\n\n.field-name {\n\tdisplay: block;\n\tfont-family: 'Source Code Pro', monospace;\n\tmargin-top: 2em;\n\tfont-weight: bold;\n\tmargin-bottom: .5em;\n}\n\n.inline-link {\n\ttext-decoration: none;\n\tposition: absolute;\n\tmargin-left: -1.5em;\n\t/* margin-top: -.1em; */\n\tpadding-right: .3em;\n\tpadding-left: .2em;\n\tvisibility: hidden;\n}\n\n.inline-link:hover {\n\ttext-decoration: none;\n}\n\nhr {\n\tborder: none;\n\tborder-top: 8px solid #34669b;\n\tmargin: 4em 0;\n}\n\narticle img {\n\tmax-width: 100%;\n}\n\niframe {\n\tmargin: 1em 0 2em;\n}\n\n.nonstandard-notice {\n\tfont-size: 14px;\n\tmax-width: 500px;\n\tmargin: 25px auto;\n\tborder: 1px solid #ecd200;\n\tbackground: #fffddf;\n\tborder-radius: 5px;\n\tpadding: 10px;\n\tcolor: #886c00;\n\tline-height: 1.4em;\n\tdisplay: none;\n}\n\n.nonstandard {\n\tcolor: rgb(214, 145, 16);\n}\n\n.standard-flag,\n.nonstandard-flag {\n\tcursor: help;\n\tfont-size: 8px;\n\tline-height: 1em;\n\tpadding: 4px 8px;\n\ttext-transform: uppercase;\n\tfont-weight: bold;\n\tcolor: white;\n\tborder-radius: 4px;\n\twhite-space: nowrap;\n\tvertical-align: middle;\n}\n\n.standard-flag {\n\tbackground-color: rgb(34, 163, 23);\n}\n\n.nonstandard-flag {\n\tbackground-color: rgb(238, 167, 34);\n}\n\n.module-repo-differentiator {\n\tfont-size: 11px;\n\tfont-weight: bold;\n\tcolor: #555;\n\tline-height: 1em;\n}\n\n\n\n\n\n\n\n.json {\n\tline-height: 1.5em;\n}\n.json .qu { color: #5c91bf; }\n.json .key { color: #1c83dc; font-weight: bold; }\n.json .str { color: #2f8598; }\n.json .num { color: #038a3f; }\n.json .bool { color: #9b5e14; }\n.json .key a:not([href]) { color: #222; }\narticle .json a {\n\ttext-decoration: none;\n\tfont-weight: bold;\n}\n.json .has-popup { border-bottom: 1px dashed #222; }\n.json a[href].has-popup { border-bottom-color: #1c82dc; }\n.json .has-popup.module { border-bottom: none; }\n\n\n#hovercard {\n\tmax-width: 450px;\n\tborder-radius: 10px;\n\tfilter: drop-shadow(0 5px 5px rgba(0, 0, 0, .25));\n\tposition: absolute;\n\tfont-size: 16px;\n\ttransition: transform .25s ease-in-out, opacity .25s ease-in-out;\n\tmax-height: 50%;\n\toverflow-y: auto;\n}\n\n#hovercard:not(.popup) {\n\topacity: 0;\n\tdisplay: none;\n\ttransform: translateY(0);\n}\n\n.popup {\n\tdisplay: block;\n\topacity: 1;\n\ttransform: translateY(-10px);\n}\n\n.arrow-box {\n\tposition: relative;\n\tbackground: white;\n}\n\n.arrow-box:after {\n\tbottom: 100%;\n\tleft: 50%;\n\tborder: solid transparent;\n\tcontent: \" \";\n\theight: 0;\n\twidth: 0;\n\tposition: absolute;\n\tpointer-events: none;\n\tborder-color: rgba(255, 255, 255, 0);\n\tborder-bottom-color: white;\n\tborder-width: 10px;\n\tmargin-left: -10px;\n}\n\n#hovercard p {\n\tpadding: 1em;\n\tline-height: 1.4em;\n}\n\n#hovercard p+p {\n\tpadding-top: 0;\n}\n\n#hovercard li {\n\tmargin: .25em;\n}\n\n#hovercard pre > code {\n\tborder-radius: 0;\n}\n\n#hovercard li {\n\tpadding-top: 0;\n\tpadding-bottom: 0;\n}\n\n#hovercard .nonstandard-flag {\n\tfloat: right;\n}\n\n#hovercard .module-link {\n\tdisplay: block;\n\ttext-decoration: none;\n\tfont-size: 18px;\n\tline-height: 1em;\n\tfont-weight: bold;\n\tpadding: .5em 1em;\n}\n\n#hovercard .module-link:hover {\n\tbackground: #f5f5f5;\n}\n\n#hovercard .module-link:last-child {\n\tborder-bottom-left-radius: 10px;\n\tborder-bottom-right-radius: 10px;\n}\n\n#hovercard .module-link-description {\n\tfont-size: 14px;\n\tcolor: #555;\n\tmargin-left: .5em;\n\tfont-weight: normal;\n}\n\n#hovercard-namespace-box,\n#hovercard-inline-link {\n\tborder: 0px solid #f0f6f7;\n}\n\n#hovercard-namespace-box {\n\tborder-bottom-width: 1px;\n}\n\n#hovercard-inline-link {\n\tborder-top-width: 1px;\n}\n\n#hovercard-inline-link a {\n\tdisplay: block;\n\tpadding: 8px 10px;\n\ttext-decoration: none;\n\tfont-size: 85%;\n\tfont-weight: bold;\n\ttext-align: center;\n}\n\n#hovercard-namespace {\n\tfont-weight: bold;\n}\n\n.explain {\n\tfont-style: italic;\n\tcolor: #777;\n}\n\narticle aside {\n\tposition: relative;\n\tfont-size: 16px;\n\tmargin: 2em auto 3em !important;\n\tmax-width: 800px;\n}\n\narticle aside.tip,\narticle aside.advice {\n\tpadding-left: calc(8% + 50px) !important;\n}\n\narticle aside.tip::before,\narticle aside.advice::before {\n\tfont-size: 45px;\n\tposition: absolute;\n\ttop: -4px;\n\tleft: 8%;\n}\n\narticle aside.tip {\n\tcolor: #706b95;\n}\narticle aside.advice {\n\tcolor: #826848;\n}\n\narticle aside.tip:nth-child(even)::before {\n\tcontent: '💁‍♀️';\n}\narticle aside.tip:nth-child(odd)::before {\n\tcontent: '💁‍♂️';\n}\n\narticle aside.advice::before {\n\tcontent: '🤦';\n}\n\narticle aside.complete {\n\tcolor: #6b6b6b;\n\tborder: 2px dotted #88db88;\n\ttext-align: center;\n\tmax-width: 500px;\n\tpadding: 15px 25px !important;\n}\n\narticle aside.complete::before {\n\tcontent: '✅ complete';\n\tcolor: #39c849;\n\ttext-transform: uppercase;\n\tfont-size: 14px;\n\tfont-weight: bold;\n\tletter-spacing: 1px;\n\tmargin-right: 2em;\n\tmargin-bottom: .5em;\n}\n\ntable {\n\ttable-layout: fixed;\n\tborder-collapse: collapse;\n\tfont-size: 16px;\n}\n\narticle > table {\n\tmargin: 25px auto;\n}\n\nth, td {\n\tborder-bottom: 1px solid #ddd;\n\tpadding: 10px;\n\tline-height: 1.4em;\n\tvertical-align: top;\n\tword-wrap: break-word;\n}\n\nth {\n\ttext-align: left;\n\tbackground: #eee;\n}\n\ntd code {\n\tfont-size: 14px;\n\tword-wrap: break-word;\n}\n\n\n\n\n\n\n#module-docs-container,\n#module-list-container,\n#module-template {\n\tdisplay: none;\n}\n\n#module-list-container {\n\toverflow-x: auto;\n}\n\n#module-list {\n\tmargin-top: 50px;\n}\n\n#module-list td:first-child {\n\tword-wrap: break-word;\n\tmax-width: 400px;\n}\n\n#module-list .module-link {\n\tfont-weight: bold;\n\tfont-size: 20px;\n}\n\n\n#module-multiple-repos {\n\tdisplay: none;\n\tmargin: 25px;\n}\n\n.module-repo-selector {\n\tfont-size: 26px;\n\tpadding: 25px;\n\tfont-weight: bold;\n\tborder-bottom: 1px solid #888;\n\tcursor: pointer;\n}\n\n.module-repo-selector:hover {\n\tcolor: #009cda;\n\tbackground: rgb(246, 250, 252);\n}\n\n.module-repo-selector-arrow {\n\tmargin: 10px;\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n@media (max-width: 1400px) {\n\ttable {\n\t\tfont-size: 14px;\n\t}\n\n\th1 {\n\t\tfont-size: 60px;\n\t}\n\n\th2 {\n\t\tfont-size: 38px;\n\t\tmargin-top: 65px;\n\t}\n\n\th3 {\n\t\tfont-size: 28px;\n\t\tmargin-top: 40px;\n\t}\n}\n\n@media (max-width: 1100px) {\n\theader {\n\t\tpadding-bottom: 0;\n\t}\n\n\tmain > .sidebar {\n\t\tmin-width: 200px;\n\t\tpadding: 20px 10px 0 0;\n\t}\n\n\tmain > .sidebar:last-child {\n\t\tdisplay: none;\n\t}\n\n\t#paper1,\n\t#paper2 {\n\t\tdisplay: none;\n\t}\n\t.paper3 {\n\t\ttop: 0;\n\t\tleft: 0;\n\t}\n\n\tarticle {\n\t\tfont-size: 18px;\n\t\tpadding-top: 40px;\n\t\tpadding-bottom: 40px;\n\t}\n\n\tarticle > :not(.fullwidth),\n\tarticle > .fullwidth > * {\n\t\tpadding-left: 40px;\n\t\tpadding-right: 40px;\n\t}\n\tarticle > :not(h1) {\n\t\tmargin-bottom: 1.5rem;\n\t}\n\n\tarticle > pre.chroma {\n\t\tpadding-top: 1em;\n\t\tpadding-bottom: 1em;\n\t}\n\n\th1 {\n\t\tfont-size: 45px;\n\t}\n\n\th2 {\n\t\tfont-size: 32px;\n\t}\n}\n\n@media (max-width: 900px) {\n\t#logo-container {\n\t\tdisplay: block;\n\t}\n\n\t#zerossl-project {\n\t\ttext-align: center;\n\t\tmargin-bottom: 1em;\n\t}\n\n\t#logo-docs {\n\t\tdisplay: none;\n\t}\n\n\tmain {\n\t\tflex-direction: column-reverse;\n\t\talign-items: center;\n\t}\n\n\tmain > .sidebar {\n\t\twidth: 100%;\n\t\tborder-width: 0;\n\t\tborder-top-width: 2px;\n\t}\n\n\t.article-container {\n\t\tmax-width: 100%; /* TODO: why is this necessary?? without it, article overflows super wide on narrow screen */\n\t\tmargin: 20px 0;\n\t}\n\n\t.paper {\n\t\tborder-radius: 0;\n\t\tbox-shadow: none;\n\t}\n\n\t.anchor-link {\n\t\tdisplay: none;\n\t}\n}\n\n\n\n@media (prefers-color-scheme: dark) {\n\tbody {\n\t\tbackground-color: #060e17;\n\t\tcolor: #bdd6f7;\n\t}\n\n\theader nav .new {\n\t\tcolor: black;\n\t}\n\n\t#search {\n\t\tbackground: rgb(28, 52, 79);\n\t\tcolor: #bdd6f7;\n\t}\n\t#search:focus {\n\t\tbackground: rgb(46, 70, 96);\n\t}\n\n\t#paper1,\n\t#paper2 {\n\t\tbackground-color: #030a11;\n\t}\n\t.paper3 {\n\t\tbackground-color: #051628;\n\t}\n\n\t#logo {\n\t\t/* TODO: Add some color */\n\t\tfilter: invert(1) opacity(.35);\n\t}\n\n\t#logo-docs {\n\t\tcolor: #5aa3dc;\n\t}\n\n\t.breadcrumbs {\n\t\tcolor: #5aa3dc;\n\t}\n\n\tmain nav li a {\n\t\tcolor: #668d9b;\n\t}\n\n\tmain nav ul li a:hover {\n\t\tcolor: #5aa3dc;\n\t}\n\n\tmain nav ul li a.current {\n\t\tbackground-color: #061b35;\n\t}\n\n\t.breadcrumbs {\n\t\tborder-color: #061b35;\n\t}\n\n\th1 {\n\t\tcolor: #5aa3dc;\n\t}\n\n\tcode {\n\t\tbackground-color: #122844;\n\t}\n\n\tcode.cmd {\n\t\tbackground-color: #000;\n\t\tcolor: #ccc;\n\t}\n\t\n\t.json .key a:not([href]) {\n\t\tcolor: #bdd6f7\n\t}\n\n\t#hovercard,\n\t.arrow-box {\n\t\tbackground-color: #0a192b; \n\t}\n\n\t.arrow-box:after {\n\t\tborder-bottom-color: #0a192b;\n\t}\n\n\t#hovercard .module-link:hover {\n\t\tbackground: #0f2c50;\n\t}\n\n\t#hovercard .module-link-description {\n\t\tcolor: rgb(167, 167, 167);\n\t}\n\n\t#hovercard-namespace-box,\n\t#hovercard-inline-link {\n\t\tborder-color: #0a2b53;\n\t}\n\n\tarticle aside.tip {\n\t\tcolor: #8c81e4;\n\t}\n\n\tth {\n\t\tbackground-color: #142638;\n\t}\n\n\tth,\n\ttd {\n\t\tborder-bottom-color: #233444;\n\t}\n}\n\n/* Modern Caddy documentation visual refresh. Kept at the end so it wins the legacy cascade. */\n:root {\n\t--body-bg: #fff;\n\t--text-color: #2c2c2c;\n\t--text-color-muted: #7087a0;\n\t--heading-color: #2c2c2c;\n\t--link-color: #0097f2;\n\t--link-hover-color: rgb(27, 170, 70);\n\t--link-decoration-color: #ddd;\n\t--main-nav-link-color: #546c75;\n\t--main-nav-link-hover-color: #01324b;\n\t--nav-current-bg: linear-gradient(to right, #ecf1f3, transparent);\n\t--nav-link-hover-shadow-color: rgba(0, 0, 0, .15);\n\t--nav-link-hover-before-bg: #d2e5e7;\n\t--nav-link-hover-bg: #f4f7f9;\n\t--code-bg: #f2f8f9;\n\t--cmd-bg: #333;\n\t--table-header-bg: #eaf1f5;\n\t--table-border-color: #cad9e2;\n\t--tip-color: #5c50bb;\n\t--monospace-fonts: 'JetBrains Mono', monospace;\n}\n\nhtml,\nbody {\n\tmin-height: 100%;\n}\n\nbody {\n\tfont-family: Inter, system-ui, sans-serif;\n\tfont-size: 18px;\n\tbackground-color: var(--body-bg);\n\tbackground-image:\n\t\tradial-gradient(circle at 30% top, hsl(138deg 100% 75% / 19%) 0, transparent min(15%, 700px)),\n\t\tradial-gradient(circle at 70% top, hsl(201deg 100% 69% / 24%) 0, transparent min(15%, 700px));\n\tbackground-repeat: no-repeat;\n\tcolor: var(--text-color);\n}\n\nheader {\n\talign-items: center;\n\tpadding: 28px 50px;\n\tborder-bottom: 1px solid #ebf0f2;\n\tbackground: rgba(118, 179, 194, .11);\n}\n\n#logo {\n\theight: 36px;\n\tfilter: none;\n}\n\n#logo-docs {\n\tfont-family: Figtree, system-ui, sans-serif;\n\tfont-size: 13px;\n\tfont-weight: 700;\n\tletter-spacing: .08em;\n\tcolor: #4a6c7c;\n}\n\n#zerossl-project {\n\tcolor: var(--text-color-muted);\n\tfont-size: 12px;\n}\n\nmain {\n\tmax-width: 2000px;\n\tmargin: 50px auto 100px;\n\tpadding: 0 50px;\n\tgap: 1em;\n\tjustify-content: center;\n\tfont-family: Inter, system-ui, sans-serif;\n}\n\nmain > .sidebar {\n\tflex: 1 1 0;\n\twidth: auto;\n\tmax-width: none;\n\tpadding: 0;\n\tborder: 0;\n}\n\nmain > nav.sidebar {\n\tmin-width: 250px;\n\tfont-size: 16px;\n}\n\nmain > .sidebar:last-child {\n\tmax-width: 250px;\n}\n\nmain nav ul {\n\tpadding-left: 0;\n}\n\nmain nav li li::before {\n\tcontent: '';\n\tdisplay: block;\n\tposition: absolute;\n\twidth: 1px;\n\theight: 100%;\n\tbackground-color: var(--nav-link-hover-before-bg);\n\ttransition: .15s;\n}\n\nmain nav li li:hover::before {\n\twidth: 4px;\n\tbackground-color: #62868d;\n}\n\nmain nav li a {\n\tcolor: var(--main-nav-link-color);\n\tpadding: 8px 18px 8px 28px;\n\ttransition: .15s;\n}\n\nmain nav li:hover > a,\nmain nav li a:hover {\n\tcolor: var(--main-nav-link-hover-color);\n\tbackground: var(--nav-link-hover-bg);\n}\n\nmain nav li:hover > a {\n\tbox-shadow: -10px 0 10px -3px var(--nav-link-hover-shadow-color);\n}\n\nmain nav li a.current {\n\tbackground: var(--nav-current-bg);\n}\n\nmain nav li.heading {\n\tfont-size: 80%;\n\tpadding: 8px 18px 8px 28px;\n\tcolor: var(--text-color);\n}\n\nmain nav li.heading:not(:first-child) {\n\tmargin-top: 2.5em;\n}\n\nmain nav li li a {\n\tborder-top-left-radius: 0;\n\tborder-bottom-left-radius: 0;\n\tpadding-left: 1em;\n\tfont-size: 90%;\n}\n\nmain nav li ul {\n\tmargin-left: 2.5em;\n\tmargin-bottom: .5em;\n}\n\n.article-container {\n\tdisplay: block;\n\tflex: 3 1 900px;\n\twidth: 100%;\n\tmax-width: 1200px;\n\tmargin: 0;\n}\n\n#paper1,\n#paper2 {\n\tdisplay: none;\n}\n\n.paper,\n.paper3 {\n\tposition: static;\n\twidth: auto;\n\tbackground: transparent;\n\tborder-radius: 0;\n\tbox-shadow: none;\n\ttransform: none;\n}\n\narticle {\n\tmax-width: 1200px;\n\twidth: 100%;\n\tmin-width: 0;\n\tpadding: 0 0 8%;\n\tfont-family: 'Albert Sans', Figtree, Inter, system-ui, sans-serif;\n\tfont-size: 20px;\n\tword-wrap: break-word;\n}\n\narticle > :not(.fullwidth),\narticle > .fullwidth > *,\n.pad {\n\tpadding-left: 8%;\n\tpadding-right: 8%;\n}\n\narticle a {\n\tcolor: var(--link-color);\n\ttext-decoration-line: underline;\n\ttext-decoration-thickness: 2px;\n\ttext-underline-offset: 2px;\n\ttext-decoration-color: var(--link-decoration-color);\n\ttransition: all .15s;\n}\n\narticle a:hover {\n\tcolor: var(--link-hover-color);\n\ttext-decoration-color: var(--link-color);\n}\n\narticle p,\narticle li {\n\tline-height: 1.6;\n}\n\narticle h1,\narticle h2,\narticle h3,\narticle h4,\narticle h5,\narticle h6 {\n\tfont-family: Gantari, Inter, system-ui, sans-serif;\n\tfont-weight: 800;\n\tcolor: var(--heading-color);\n}\n\narticle h1,\narticle h2,\narticle h3 {\n\ttext-align: center;\n}\n\nh1,\narticle h1 {\n\tfont-size: clamp(3rem, 4vw, 78px);\n\tcolor: #0e3e5b;\n\tletter-spacing: -2px;\n\tmargin-top: 5%;\n\tmargin-bottom: 50px;\n\tbackground: linear-gradient(to right, #23a1ec, #3fd53a);\n\tbackground-clip: text;\n\t-webkit-background-clip: text;\n\t-webkit-text-fill-color: transparent;\n}\n\nh2,\narticle h2 {\n\tfont-size: clamp(2.25rem, 3vw, 56px);\n\tpadding-bottom: 15px;\n\tmargin: 100px 0 40px !important;\n\tborder-bottom: 10px solid;\n\tborder-image-slice: 1;\n\tborder-image-source: linear-gradient(to left, #23a1ec, #3fd53a);\n}\n\nh3,\narticle h3 {\n\tfont-size: 36px;\n\tmargin: 50px 0 20px;\n\tfont-weight: 600;\n\ttext-align: center;\n}\n\ncode {\n\tfont-family: var(--monospace-fonts);\n\tfont-feature-settings: \"liga\" 0;\n\tbackground-color: var(--code-bg);\n\tborder-radius: 6px;\n\tpadding: 2px 5px;\n\tfont-size: 90%;\n}\n\ncode.cmd {\n\tbackground-color: var(--cmd-bg);\n}\n\npre > code,\narticle > pre {\n\tline-height: 1.6;\n}\n\npre > code.cmd,\n.chroma {\n\tborder-radius: 10px;\n}\n\nhr {\n\twidth: 35%;\n\tmargin: 4em auto;\n\tborder: none;\n\tborder-top: 4px solid var(--link-decoration-color);\n}\n\narticle aside.tip {\n\tcolor: var(--tip-color);\n}\n\narticle aside.complete {\n\tcolor: #65a5b9;\n}\n\nth,\ntd {\n\tborder-bottom-color: var(--table-border-color);\n}\n\nth {\n\tbackground: var(--table-header-bg);\n}\n\n.module-repo-selector:hover {\n\tbackground: var(--table-header-bg);\n}\n\n@media (max-width: 1100px) {\n\tmain {\n\t\tpadding-left: 20px;\n\t\tpadding-right: 20px;\n\t}\n\n\tmain > .sidebar {\n\t\tpadding: 0;\n\t}\n\n\tmain > .sidebar:last-child {\n\t\tdisplay: none;\n\t}\n\n\t.article-container {\n\t\tmax-width: 100%;\n\t}\n}\n\n@media (max-width: 900px) {\n\theader {\n\t\tpadding: 24px 20px;\n\t}\n\n\tmain {\n\t\tmargin-top: 28px;\n\t\tmargin-bottom: 50px;\n\t\tflex-direction: column-reverse;\n\t\talign-items: stretch;\n\t}\n\n\tmain > nav.sidebar {\n\t\twidth: 100%;\n\t}\n\n\t.article-container {\n\t\tmargin: 0;\n\t}\n\n\tarticle {\n\t\tfont-size: 18px;\n\t}\n\n\tarticle > :not(.fullwidth),\n\tarticle > .fullwidth > *,\n\t.pad {\n\t\tpadding-left: 4%;\n\t\tpadding-right: 4%;\n\t}\n\n\th2,\n\tarticle h2 {\n\t\tmargin: 50px 0 20px !important;\n\t}\n\n\th3,\n\tarticle h3 {\n\t\tfont-size: 32px;\n\t}\n}\n\n@media (prefers-color-scheme: dark) {\n\t:root {\n\t\t--body-bg: #0d171a;\n\t\t--text-color: #cbe2e4;\n\t\t--text-color-muted: #92b2d5;\n\t\t--heading-color: #dee8ee;\n\t\t--link-color: #34a1e4;\n\t\t--link-hover-color: rgb(42, 228, 98);\n\t\t--link-decoration-color: #375862;\n\t\t--main-nav-link-color: #86a7b1;\n\t\t--main-nav-link-hover-color: #e2e9ec;\n\t\t--nav-current-bg: linear-gradient(to right, #1a3c4d, transparent);\n\t\t--nav-link-hover-shadow-color: rgb(0 0 0 / 32%);\n\t\t--nav-link-hover-before-bg: #32494f;\n\t\t--nav-link-hover-bg: #e3fffe0d;\n\t\t--code-bg: #1f3237;\n\t\t--cmd-bg: #000;\n\t\t--table-header-bg: #1d2d38;\n\t\t--table-border-color: #1a373d;\n\t\t--tip-color: #a6b2f7;\n\t}\n\n\tbody {\n\t\tbackground-color: var(--body-bg);\n\t\tbackground-image:\n\t\t\tradial-gradient(circle at calc(50% - min(35vw, 700px)) -50px, hsl(206deg 100% 41% / 16%) 0, transparent min(25%, 800px)),\n\t\t\tradial-gradient(circle at calc(50% + min(35vw, 700px)) -50px, hsl(142deg 100% 32% / 17%) 0, transparent min(25%, 800px));\n\t\tcolor: var(--text-color);\n\t}\n\n\theader {\n\t\tbackground: rgba(44, 130, 164, .11);\n\t\tborder-bottom-color: transparent;\n\t}\n\n\t#logo {\n\t\tfilter: invert(1) opacity(.55);\n\t}\n\n\t#logo-docs {\n\t\tcolor: #bdd6f7;\n\t}\n\n\t.paper3 {\n\t\tbackground: transparent;\n\t}\n\n\th1,\n\tarticle h1 {\n\t\tbackground: linear-gradient(to right, #23a1ec, #3fd53a);\n\t\tbackground-clip: text;\n\t\t-webkit-background-clip: text;\n\t\t-webkit-text-fill-color: transparent;\n\t}\n\n\t.arrow-box {\n\t\tbackground-color: var(--body-bg);\n\t}\n\n\t.arrow-box:after {\n\t\tborder-bottom-color: var(--body-bg);\n\t}\n}\n"
  },
  {
    "path": "src/resources/css/download.css",
    "content": "body {\n\tbackground-color: #f0f6f9;\n}\n\n.download-bar {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tbackground: white;\n\tborder-radius: 4px;\n\tbox-shadow: 0 3px 6px rgba(0, 0, 0, .1);\n\tfont-size: 20px;\n\tmargin-top: 20px;\n\tposition: sticky; /* uwu 💓 */\n\ttop: 0;\n\tz-index: 1;\n}\n\n.download-bar > * {\n\tpadding: 15px;\n\tdisplay: flex;\n\talign-items: center;\n}\n\n#platform {\n\tpadding: 5px 15px;\n\tfont-size: 16px;\n}\n\n#download {\n\tmargin: 0;\n\tpadding-left: 30px;\n\tpadding-right: 30px;\n\tfont-weight: bold;\n}\n\n#filter {\n\tmargin-top: 1em;\n\twidth: 100%;\n\tpadding: 15px;\n\tfont-size: 24px;\n\ttext-align: center;\n\tborder: 0;\n\tborder-bottom: 1px solid #ccc;\n\toutline: none;\n}\n\n#filter.found {\n\tcolor: green;\n}\n#filter.not-found {\n\tcolor: #cc0000;\n}\n\n.warning {\n\tmargin-top: 20px;\n\tdisplay: inline-block;\n\tfont-size: 14px;\n\tfont-weight: bold;\n\tpadding: 5px 15px;\n\tborder-radius: 1em;\n\tcolor: rgb(255, 208, 0);\n\tbackground: #333;\n}\n\ninput:disabled,\nselect:disabled,\n#optional-packages.disabled {\n\tcursor: not-allowed !important;\n}\n\n\n\n\n\n\n\n.loader {\n\tdisplay: inline-block;\n\tfont-size: 10px;\n\ttext-indent: -9999em;\n\twidth: 20px;\n\theight: 20px;\n\tborder-radius: 50%;\n\tbackground: #aaa;\n\tbackground: -moz-linear-gradient(left, #aaa 10%, rgba(255, 255, 255, 0) 42%);\n\tbackground: -webkit-linear-gradient(left, #aaa 10%, rgba(255, 255, 255, 0) 42%);\n\tbackground: -o-linear-gradient(left, #aaa 10%, rgba(255, 255, 255, 0) 42%);\n\tbackground: -ms-linear-gradient(left, #aaa 10%, rgba(255, 255, 255, 0) 42%);\n\tbackground: linear-gradient(to right, #aaa 10%, rgba(255, 255, 255, 0) 42%);\n\tanimation: load3 1.4s infinite linear;\n\ttransform: translateZ(0);\n\tvertical-align: middle;\n\tmargin-right: .5em;\n\tmargin-top: -2px;\n}\n#signature .loader {\n\twidth: 12px;\n\theight: 12px;\n}\n.loader:before {\n\twidth: 50%;\n\theight: 50%;\n\tbackground: #fff;\n\tborder-radius: 100% 0 0 0;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tcontent: '';\n}\n.loader:after {\n\tbackground: #aaa;\n\twidth: 75%;\n\theight: 75%;\n\tborder-radius: 50%;\n\tcontent: '';\n\tmargin: auto;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tbottom: 0;\n\tright: 0;\n}\n@-webkit-keyframes load3 {\n\t0% {\n\t\t-webkit-transform: rotate(0deg);\n\t\ttransform: rotate(0deg);\n\t}\n\t100% {\n\t\t-webkit-transform: rotate(360deg);\n\t\ttransform: rotate(360deg);\n\t}\n}\n@keyframes load3 {\n\t0% {\n\t\t-webkit-transform: rotate(0deg);\n\t\ttransform: rotate(0deg);\n\t}\n\t100% {\n\t\t-webkit-transform: rotate(360deg);\n\t\ttransform: rotate(360deg);\n\t}\n}\n\n\n\n\n\n#optional-packages {\n\tmargin-top: 2em;\n}\n\n.package {\n\tdisplay: flex;\n\tpadding: 2em;\n\tbackground: rgba(255, 255, 255, .4);\n\tborder: 10px solid transparent;\n\tbox-shadow: 0 1px 1px rgba(0, 0, 0, .2);\n\tcursor: pointer;\n\ttransition: all 200ms ease-out;\n}\n\n.package:hover {\n\ttransform: scale(1.02);\n\tbackground: #fff;\n\tbox-shadow: 0 2px 20px -2px rgba(0, 0, 0, .05);\n}\n\n.package.selected {\n\tborder-color: rgb(25, 97, 192);\n\tbackground: #fff;\n}\n\n.package-icon {\n\tfont-size: 48px;\n\tmargin-right: 20px;\n}\n\n.package-data {\n\tflex-grow: 1;\n}\n\n.package-meta {\n\tfloat: right;\n\tfont-size: 14px;\n}\n\n.package-downloads {\n\tmargin-right: 1em;\n}\n\n.package-version-input {\n\ttext-align: center;\n\tmax-width: 75px;\n\tpadding: 4px;\n\tmargin-left: 5px;\n\tborder-radius: 5px;\n\tborder: 1px solid #aaa;\n\toutline: none;\n}\n\n.package-link {\n\tfont-size: 20px;\n\tfont-weight: bold;\n\tcolor: inherit;\n\tword-break: break-all;\n}\n\n.package-host {\n\tfont-size: 14px;\n}\n\n.package-modules {\n\tmargin-top: 1em;\n}\n\n.package-no-modules {\n\tfont-style: italic;\n\tcolor: #555;\n\tfont-size: 14px;\n}\n\n.module {\n\tmargin: 10px 0;\n\tword-wrap: break-word;\n}\n\n.module-link {\n\tfont-weight: bold;\n\tfont-family: 'PT Mono', monospace;\n}\n\n.module-desc {\n\tfont-size: 14px;\n\tmargin-left: 1em;\n}\n\n\n\n\n\n\n\n\n\n\n.swal-custom-content {\n\ttext-align: left;\n}\n\n.swal-custom-content ol {\n\tmargin: 1em 0 1em 2em;\n}\n\n.swal-custom-content li {\n\tmargin-bottom: 10px;\n}\n\n\n\n\n\n\n\n\n\n\n@media (max-width: 860px) {\n\t.download-bar {\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t}\n}\n\n@media (max-width: 600px) {\n\t.download-bar {\n\t\tfont-size: inherit;\n\t}\n\n\t.download-bar > * {\n\t\tpadding: 5px;\n\t}\n}\n"
  },
  {
    "path": "src/resources/css/home.css",
    "content": "body {\n\tfont-family: 'Inter', sans-serif;\n}\n\n.hero {\n\tbackground-repeat: no-repeat;\n\tbackground-size: cover;\n\tpadding-bottom: 100px;\n}\n\nh1 {\n\ttext-transform: uppercase;\n\tfont-size: 70px;\n\tfont-family: Montserrat, sans-serif;\n\ttext-align: center;\n\tmargin: 70px 0 20px;\n}\n\nh2 {\n\tfont-size: 28px;\n\tfont-weight: normal;\n\ttext-align: center;\n\tmax-width: 60rem;\n\tmargin: 0 auto 50px;\n\tline-height: 1.5em;\n}\n\nh3 {\n\tfont-family: Montserrat, sans-serif;\n\tfont-weight: 400;\n\tfont-size: 55px;\n}\n\np {\n\tfont-size: 20px;\n\tmax-width: 600px;\n\tmargin-top: 20px;\n\tline-height: 1.5em;\n}\n\n.download-container {\n\ttext-align: center;\n\tmargin-top: 50px;\n}\n\n.download-container .button.big {\n\tmargin-bottom: 15px;\n}\n\nsection {\n\tpadding: 100px 0;\n}\n\nsection.alternate:nth-child(even) {\n\tbackground-color: #f5f8f9;\n}\n\nsection.alternate:nth-child(odd) .side-by-side {\n\tflex-direction: row-reverse;\n}\n\n.side-by-side {\n\twidth: 100%;\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n}\n\n.side-by-side > * {\n\twidth: 48%;\n}\n\n.side-by-side img {\n\tmax-height: 400px;\n}\n\n.code-caption {\n\tfont-size: 24px;\n\tfont-weight: bold;\n\tmargin: 75px auto 20px;\n}\n\ncode {\n\tfont-family: 'PT Mono', monospace;\n}\n\ncode.block {\n\tdisplay: block;\n\tbackground: #2f2f2f;\n\tcolor: #eee;\n\tfont-size: 20px;\n\tpadding-top: 30px;\n\tpadding-bottom: 30px;\n\tline-height: 1.25em;\n\twhite-space: pre-wrap;\n}\n\n.actions {\n\tpadding-top: 100px;\n}\n\ncode.caddyfile {\n\tbackground-color: #dbebf3;\n\tcolor: black;\n}\n\n.cf-key {\n\tcolor: #d22500;\n \tfont-weight: bold;\n}\n\n.cf-comment {\n\tcolor: #7291a0;\n}\n\n.cf-dir {\n\tcolor: #006c96;\n\tfont-weight: bold;\n}\n\n.cf-arg {\n\tcolor: #008000;\n}\n\n.cf-subdir {\n\tcolor: #835234;\n}\n\ncode.rest {\n\tbackground-color: #f0f5f4; /*#073d59;*/\n\tcolor: #253a28;\n}\n\n.footnote {\n\tfont-size: 18px;\n\ttext-align: center;\n}\n\niframe.github-stars {\n\tvertical-align: middle;\n}\n\n\n@media (max-width: 1100px) {\n\t.side-by-side,\n\tsection.alternate:nth-child(odd) .side-by-side {\n\t\tflex-direction: column;\n\t}\n\tsection.alternate:nth-child(odd) .side-by-side > img {\n\t\tflex-direction: column-reverse;\n\t\tmargin-bottom: 50px;\n\t}\n\n\t.side-by-side-content {\n\t\tmargin-bottom: 50px;\n\t}\n\n\t.side-by-side > * {\n\t\twidth: initial;\n\t}\n\n\tp {\n\t\tmax-width: 900px;\n\t}\n}\n\n@media (max-width: 900px) {\n\th1 {\n\t\tfont-size: 50px;\n\t\tword-wrap: break-word;\n\t}\n\n\th2 {\n\t\tfont-size: 24px;\n\t}\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* TODO: */\n/* TAKEN FROM features.css */\n\n.features-start {\n\tbackground: linear-gradient(0deg, #13a8f5 0%, #18b125 100%);\n\tcolor: white;\n\tpadding: 35px 0;\n}\n\n\n.main-features {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tflex-wrap: wrap;\n\tpadding-top: 2rem;\n}\n\n.main-feature {\n\ttext-align: center;\n\tpadding: 0 2rem;\n\tmargin: 3rem 0;\n\twidth: 25%;\n}\n\n.main-feature img {\n\theight: 130px;\n\tmargin-bottom: 15px;\n}\n\n.main-feature .title {\n\tfont-size: 24px;\n\tfont-weight: bold;\n\tmargin-bottom: 15px;\n}\n\n.main-feature p {\n\ttext-align: left;\n\tfont-size: 16px;\n\tmargin: 0;\n}\n\n.feature-points {\n\tdisplay: flex;\n\tjustify-content: space-around;\n\tflex-wrap: wrap;\n\tfont-size: 28px;\n}\n\n.feature-point {\n\twidth: 33.333%;\n\tmin-width: 300px;\n\tpadding: 1rem 3rem;\n}\n\n#features-title {\n\twidth: 80%;\n\tmax-width: 700px;\n\tdisplay: block;\n\tmargin: 50px auto 0;\n\tposition: relative;\n\ttop: 40px;\n}\n\n.features-area h3 {\n\tborder-bottom: 3px solid #2BABED;\n}\n\n.features-area h4 {\n\ttext-align: center;\n\tpadding-top: 80px;\n\tpadding-bottom: 40px;\n\tfont-family: Montserrat, sans-serif;\n\tfont-size: 40px;\n}\n\n.features-area h5 {\n\tfont-size: 18px;\n\ttext-transform: uppercase;\n\tletter-spacing: 1px;\n}\n\n.section-heading {\n\tfont-style: italic;\n\tfont-size: 20px;\n\tline-height: 2rem;\n\tcolor: #000;\n\tmax-width: 550px;\n\ttext-align: center;\n\tmargin: 2rem auto 0;\n}\n\n.features {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n.feature {\n\twidth: 33.333%;\n\tpadding: 25px;\n}\n\n.feature p {\n\tmargin-top: 5px;\n\tcolor: #333;\n\tfont-size: 18px;\n}\n\n@media (max-width: 900px) {\n\t.main-feature {\n\t\twidth: 50%;\n\t\tmargin: 3rem 0;\n\t\tpadding: 0 1rem;\n\t}\n\n\t.feature {\n\t\twidth: 50%;\n\t}\n}\n\n@media (max-width: 700px) {\n\t.pitch {\n\t\twidth: 100%;\n\t\tborder-right: none;\n\t}\n\n\t.feature-highlight {\n\t\tpadding: 1rem;\n\t}\n}\n\n@media (max-width: 500px) {\n\t.main-feature {\n\t\twidth: 100%;\n\t}\n\n\t.feature {\n\t\twidth: 100%;\n\t}\n}"
  },
  {
    "path": "src/resources/css/v2-landing.css",
    "content": "body {\n\tbackground: #fcfcfc;\n}\n\n#v2logo {\n\twidth: 100%;\n\tmax-width: 600px;\n\tmargin: 100px auto 0;\n\tdisplay: block;\n}\n\nsection {\n\tpadding: 150px 0;\n\tfont-size: 20px;\n}\n\np, ul, ol {\n\tfont-size: 16px;\n\tline-height: 175%;\n\tmax-width: 600px;\n\tmargin: 0 auto 1.5em;\n}\n\nul, ol {\n\tmargin-left: 2em;\n}\n\nh1 {\n\tfont-family: Montserrat;\n\ttext-align: center;\n}\n\n.hero h1 {\n\tfont-size: 40px;\n\tfont-weight: normal;\n\tmargin: 0 auto 50px;\n\tmax-width: 650px;\n\tline-height: 120%;\n}\n\nh2 {\n\tfont: 48px Inter;\n\tmax-width: 1000px;\n\ttext-align: center;\n\tmargin: 0 auto 80px;\n}\n\n.hero h2 {\n\tfont-size: 22px;\n\tfont-family: Montserrat;\n\ttext-align: center;\n\tfont-weight: normal;\n\tmax-width: 800px;\n\tline-height: 150%;\n}\n\nh3 {\n\tfont-size: 22px;\n\tmargin-bottom: 1rem;\n}\n\np + h3 {\n\tmargin-top: 2em;\n}\n\n.lead h3 {\n\tfont-size: 32px;\n\tfont-weight: bold;\n\tmargin-top: 1em;\n\tmargin-bottom: 1rem;\n}\n\n\n.lead p {\n\tfont-size: 18px;\n\tline-height: 150%;\n}\n\n.button {\n\tcolor: #fff;\n\tfont-size: 16px;\n}\n\n.button:hover {\n\tcolor: #fff;\n\tfilter: brightness(120%);\n}\n\n.button.purple { background-color: #3a43be; /* #7615d7 */ } \n.button.cyan { background-color: #00a8dd; }\n.button.green { background-color: #00a80d; }\n\n\ncode {\n\tbackground: #e7e7e7;\n\tborder-radius: 3px;\n}\n\nimg {\n\tmax-width: 100%;\n}\n\nimg.smallstep {\n\twidth: 200px;\n}\n\n.action {\n\tmargin: 0 auto;\n\ttext-align: center;\n\tfont-size: 14px;\n\tcolor: #555;\n}\n\n.action .button {\n\tpadding: 15px 45px;\n\tcolor: #fff;\n\tfont-size: 20px;\n}\n\n\n\n.asides {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: space-between;\n\tmargin-bottom: 75px;\n\talign-items: center;\n}\n\n.asides.top {\n\talign-items: stretch;\n}\n\n.asides > * { \n\tflex: 1;\n\tmargin-right: 50px;\n\tmin-width: 0; /* kind of a hack that allows proper sizing of pre children; see https://weblog.west-wind.com/posts/2016/feb/15/flexbox-containers-pre-tags-and-managing-overflow */\n\tmin-width: 300px;\n}\n\n.asides > :last-child {\n\tmargin-right: 0;\n}\n\n.asides h2 {\n\ttext-align: left;\n}\n\n.asides p {\n\tmargin-left: 0;\n\tmargin-right: 0;\n}\n\n.thanks {\n\twidth: 90%;\n\tmax-width: 750px;\n\tpadding: 2em;\n\tmargin: 0 auto;\n\tpadding: 40px;\n\tanimation: colorchange 20s infinite;\n\tfont-size: 16px;\n\tborder: 20px solid;\n\tbox-shadow: 0 15px 30px rgba(0, 0, 0, .15);\n}\n\n@keyframes colorchange {\n\tfrom, to { border-color: #fd9898; }\n\t10%  { border-color: #fdb998; }\n\t20%  { border-color: #fdda98; }\n\t30%  { border-color: #fdf298; }\n\t40%  { border-color: #a0fd98; }\n\t50%  { border-color: #98fdef; }\n\t60%  { border-color: #98bbfd; }\n\t70%  { border-color: #a098fd; }\n\t80%  { border-color: #f598fd; }\n\t90%  { border-color: #fd98be; }\n}\n\n.thanks h2 {\n\tfont-family: 'Dancing Script';\n\tfont-size: 52px;\n\tmargin-bottom: 20px;\n}\n\n.thanks .to {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: space-around;\n\talign-items: center;\n}\n\n.thanks .to a {\n\tmargin: 10px;\n\tcolor: black;\n\tfont-weight: bold;\n}\n\n.thanks .to a:hover {\n\ttext-decoration: underline;\n}\n\n.thanks .to img {\n\twidth: 150px;\n\tmax-height: 60px;\n\t/* width: 150px; */\n\t/* max-width: 150px; */\n}\n\n.thanks .to .sponsors {\n\tfont-size: 125%;\n\tflex-basis: 100%;\n\ttext-align: center;\n}\n\n.thanks hr {\n\tmax-width: 120px;\n\tborder: none;\n\tborder-top: 3px solid #dbdbdb;\n\tmargin: 1em auto;\n}\n\n.thanks p {\n\tline-height: 140%;\n\tmax-width: none;\n\tmargin: 0 0 1.5em;\n}\n\n\n.grid {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\ttext-align: center;\n\tfont-size: 20px;\n\tmargin-bottom: 50px;\n}\n\n.grid > div {\n\tmargin-left: -1px;\n\tmargin-bottom: -1px;\n\tflex-grow: 1;\n\tborder: 1px solid #aaa;\n\tmin-width: 33.3333%;\n\tpadding: 20px;\n}\n\n.grid > div b {\n\tfont-weight: 300;\n\tfont-family: Montserrat;\n\ttext-transform: uppercase;\n\tletter-spacing: 6px;\n\tdisplay: block;\n\tfont-size: 140%;\n\tmargin-bottom: 5px;\n}\n\n\n\n\n\n\n.caption {\n\tmargin-top: 10px;\n\tfont-size: 14px;\n\ttext-align: center;\n}\n\nasciinema-player {\n\tdisplay: block;\n\tmargin-bottom: 2em;\n}\n\n.asciinema-player-wrapper .asciinema-player {\n\tbox-shadow: -10px 10px 15px rgba(0, 0, 0, 0.25);\n\ttransform: scale(1.1);\n\tborder-radius: 8px;\n}\n\n.asciinema-terminal {\n\tborder: none;\n\tpadding: 10px;\n\tpadding-bottom: 35px; /* for when the control bar is visible */\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n.display {\n\tperspective: 1000px;\n}\n\n\n.display code {\n\tborder-radius: 10px;\n\twidth: 100%;\n}\n\n.display code {\n\tmax-width: 800px;\n\tfont-size: 110%;\n\tdisplay: block;\n\tposition: relative;\n\tpadding: 20px 40px;\n\twhite-space: pre;\n\tfont-weight: bold;\n\toverflow-x: auto;\n\tbox-shadow: 10px 10px 25px rgba(0, 0, 0, 0.1);\n}\n\n.display.right > * {\n\ttransform: rotateY(-25deg);\n}\n.display.left > * {\n\ttransform: rotateY(25deg);\n}\n\n.display code.light {\n\tbackground: #fff linear-gradient(135deg, rgba(255,255,255,0) 0%,rgba(241,241,241,0.5) 46%,rgba(225,225,225,0.5) 46.5%,rgba(246,246,246,0) 100%);\n}\n\n.display code.dark,\n.asciinema-terminal {\n\tbackground: #333 linear-gradient(135deg, rgba(0, 0, 0, 0) 0%,rgba(125, 125, 125, 0.5) 46%,rgba(45, 45, 45, 0.5) 46.5%,rgba(0, 0, 0, 0) 100%);\n\tcolor: #fff;\n}\n\n.blinking {\n\tanimation: blinking 1s infinite;\n\tbackground-color: #fff;\n\twidth: .5em;\n\theight: 1.2em;\n\tposition: relative;\n\ttop: 5px;\n\tdisplay: inline-block;\n}\n\n@keyframes blinking {\n\t0%   { background-color: #fff; }\n\t45%  { background-color: #fff; }\n\t60%  { background-color: transparent; }\n\t99%  { background-color: transparent; }\n\t100% { background-color: #fff; }\n}\n\n\n\n\n.diagonal {\n\tpadding: 200px 0;\n\tmargin: 50px 0;\n}\n\n.diagonal.up { transform: skewY(-5deg); }\n.diagonal.up > * { transform: skewY(5deg); }\n\n.diagonal.down { transform: skewY(5deg); }\n.diagonal.down > * { transform: skewY(-5deg); }\n\nsection.gray {\n\tbackground-color: #f0f0f0;\n}\n\n\n\n\n\n\n@media (max-width: 720px) {\n\t.display {\n\t\tmargin-top: 50px;\n\t\tmargin-bottom: 50px;\n\t}\n\t.display.right > * {\n\t\ttransform: none;\n\t}\n\t.display.left > * {\n\t\ttransform: none;\n\t}\n\t.asides > * { \n\t\tmargin-right: 0;\n\t}\n}"
  },
  {
    "path": "src/resources/js/common.js",
    "content": "const caddyImportPath = 'github.com/caddyserver/caddy/v2';\n\nfunction isStandard(packagePath) {\n\treturn packagePath.startsWith(caddyImportPath);\n}\n\nfunction truncate(str, maxLen) {\n\tif (!str) return \"\";\n\tstr = str.trim();\n\tlet firstPeriod = str.match(/\\.(\\s|$)/); // first dot not in the middle of a word, or at end of string\n\tlet terminate = firstPeriod ? firstPeriod.index+1 : str.length;\n\tstr = str.substring(0, terminate);\n\tif (str.length <= maxLen) {\n\t\treturn str;\n\t}\n\treturn str+\"...\";\n}\n\nfunction moduleDocsPreview(mod, maxLen) {\n\tif (!mod || !mod.docs) return \"\";\n\tlet short = truncate(mod.docs, maxLen);\n\tif (short.indexOf(mod.name) === 0) {\n\t\tshort = short.substr(mod.name.length).trim();\n\t}\n\treturn short;\n}"
  },
  {
    "path": "src/resources/js/docs-api.js",
    "content": "// TODO: sanitize all HTML renderings, especially markdown: https://github.com/cure53/DOMPurify\n\nvar pageDocs = {};\nvar pageData = {};\nvar $hovercard;\n\nconst nonStandardFlag = '<span class=\"nonstandard-flag\" title=\"这个模块不随官方Caddy默认发行;它需要添加到自定义的Caddy构建。\">非标</span>';\nconst standardFlag = '<span class=\"standard-flag\" title=\"这个模块默认随正式的Caddy发行版一起提供。\">标准</span>';\n\n\n$(function() {\n\t$hovercard = $('#hovercard');\n\n\tvar hoverTimeout;\n\t$hovercard.hover(function() {\n\t\tclearTimeout(hoverTimeout);\n\t}, function() {\n\t\tclearTimeout(hoverTimeout);\n\t\t$hovercard.removeClass('popup');\n\t});\n\n\t// toggle an object as expanded or collapsed\n\t$('body').on('click', '.renderbox .toggle-obj', function() {\n\t\tif ($(this).hasClass('expanded')) {\n\t\t\t// collapse\n\t\t\t$(this).html('&#9656;');\n\t\t} else {\n\t\t\t// expand\n\t\t\t$(this).html('&#9662;');\n\t\t}\n\t\t$(this).nextUntil('.end-obj').toggleClass('collapsed');\n\t\t$(this).toggleClass('expanded');\n\t});\n\n\t$('body').on({\n\t\tmouseenter: function() {\n\t\t\t// don't allow hoverbox to close anymore, we're re-opening it\n\t\t\tclearTimeout(hoverTimeout);\n\n\t\t\tvar pos = $(this).offset();\n\t\t\tvar moduleID = $(this).closest('.module-repo-container').data('module-id') || '';\n\t\t\tvar moduleData = pageData[moduleID];\n \n\t\t\t// there is a gap between the hoverbox and the link that originated it;\n\t\t\t// there may be a different link in this gap; if the hover box is visible,\n\t\t\t// then we should ignore the hover on this link to allow cursor to visit\n\t\t\t// the hoverbox if that is where it is going; this makes it possible to\n\t\t\t// visit the hoverbox while it is over a list of links that are tightly\n\t\t\t// stacked vertically; if user wants to visit hoverbox for link in this\n\t\t\t// gap, they can just move the cursor slow enough to fire the timeout\n\t\t\tif ($hovercard.is(':visible') && $hovercard.offset().top - 10 < pos.top) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// fill out hovercard\n\n\t\t\tvar elemPath = $(this).data('path');\n\t\t\tvar modNamespace = $(this).data('namespace');\n\n\t\t\t$('.hovercard-elem').hide();\n\n\t\t\tif ($(this).hasClass('module')) {\n\t\t\t\t// module\n\t\t\t\tvar $list =$('<div/>');\n\t\t\t\tif (moduleData.namespaces && moduleData.namespaces[modNamespace]) {\n\t\t\t\t\tfor (var i = 0; i < moduleData.namespaces[modNamespace].length; i++) {\n\t\t\t\t\t\tvar modInfo = moduleData.namespaces[modNamespace][i];\n\t\t\t\t\t\tvar href = canTraverse(moduleData) ? '.'+elemPath+'/'+modInfo.name+'/' : './'+modNamespace+'.'+modInfo.name;\n\t\t\t\t\t\tvar content = '<a href=\"'+href+'\" class=\"module-link\"> '+modInfo.name;\n\t\t\t\t\t\tif (!isStandard(modInfo.package)) {\n\t\t\t\t\t\t\tcontent += nonStandardFlag;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontent += '<span class=\"module-link-description\">'+truncate(modInfo.docs, 115)+'</span></a>';\n\t\t\t\t\t\t$list.append(content);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t$('#hovercard-module-list').html($list);\n\t\t\t\t$('#hovercard-namespace').text(modNamespace)\n\t\t\t\t$('#hovercard-module').show();\n\t\t\t\t\n\t\t\t} else if ($(this).hasClass('module-inline-key')) {\n\t\t\t\t// inline key\n\t\t\t\t$('#hovercard-inline-key').show();\n\t\t\t\t\n\t\t\t} else if ($(this).hasClass('breadcrumb')) {\n\t\t\t\t// breadcrumb siblings\n\t\t\t\t\n\t\t\t\tvar siblingPath = $(this).data('sibling-path');\n\t\t\t\tvar bcVal = moduleData.breadcrumb[siblingPath];\n\t\t\t\tvar bcSiblings = [];\n\n\t\t\t\t// drill down to the true underlying type\n\t\t\t\twhile (bcVal.elems) {\n\t\t\t\t\tbcVal = bcVal.elems;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tswitch (bcVal.type) {\n\t\t\t\tcase \"struct\":\n\t\t\t\t\tfor (var j = 0; j < bcVal.struct_fields.length; j++) {\n\t\t\t\t\t\tvar sf = bcVal.struct_fields[j];\n\t\t\t\t\t\tbcSiblings.push({name: sf.key, path: siblingPath, isStandard: isStandard(bcVal.type_name)})\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"module\":\n\t\t\t\tcase \"module_map\":\n\t\t\t\t\tfor (var j = 0; j < moduleData.namespaces[bcVal.module_namespace].length; j++) {\n\t\t\t\t\t\tvar mod = moduleData.namespaces[bcVal.module_namespace][j];\n\t\t\t\t\t\tbcSiblings.push({name: mod.name, path: siblingPath, isStandard: isStandard(mod.package)})\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar $siblings = $('<div class=\"breadcrumb-siblings\"/>').append('<div class=\"breadcrumb-siblings-title\">Siblings:</div>');\n\t\t\t\tfor (var j = 0; j < bcSiblings.length; j++) {\n\t\t\t\t\tvar sib = bcSiblings[j];\n\t\t\t\t\tvar sibPath = sib.path;\n\t\t\t\t\tif (sibPath) {\n\t\t\t\t\t\tsibPath += \"/\";\n\t\t\t\t\t}\n\t\t\t\t\tsibPath += sib.name+\"/\";\n\t\t\t\t\tvar aTag = '<a href=\"'+jsonDocsPathPrefix+sibPath+'\"';\n\t\t\t\t\tif (!sib.isStandard) {\n\t\t\t\t\t\taTag += ' class=\"nonstandard\" title=\"Non-standard module\"';\n\t\t\t\t\t}\n\t\t\t\t\taTag += '>'+sib.name+'</a>';\n\t\t\t\t\t$siblings.append(aTag);\n\t\t\t\t}\n\t\t\t\t$('#hovercard-breadcrumb-siblings').html($siblings).show();\n\n\t\t\t} else if ($(this).hasClass('documented')) {\n\t\t\t\t// docs\n\t\t\t\tvar elemDocs = truncate(pageDocs[elemPath], 500);\n\t\t\t\tif (!elemDocs) {\n\t\t\t\t\telemDocs = '<p class=\"explain\">There are no docs for this property.</p>';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t$('#hovercard-docs').html(markdown(elemDocs)).show();\n\t\t\t\t$('#hovercard-inline-link').html('<a href=\"#'+elemPath.substr(1)+'\">View docs below &#8595;</a>').show();\n\t\t\t}\n\n\t\t\t// show hoverbox for this link\n\t\t\tvar height = $(this).height();\n\t\t\tvar linkWidth = $(this).width();\n\t\t\tvar boxWidth = $hovercard.width();\n\t\t\t$hovercard.css({\n\t\t\t\t'top': pos.top + height*1.5 + 10, // '+10' counters 'translateY(-10px)'\n\t\t\t\t'left': pos.left + (linkWidth/2) - (boxWidth/2)\n\t\t\t}).addClass('popup');\n\t\t},\n\t\tmouseleave: function() {\n\t\t\t// don't hide the hoverbox right away; user might need a\n\t\t\t// few milliseconds to get the cursor over the hovercard\n\t\t\thoverTimeout = setTimeout(function() {\n\t\t\t\t$hovercard.removeClass('popup');\n\t\t\t}, 200);\n\t\t}\n\t}, '.has-popup');\n});\n\nfunction beginRenderingInto($tpl, moduleID, module) {\n\tconsole.log(\"RENDERING:\", moduleID, module);\n\t$tpl.data('module-id', moduleID);\n\tpageData[moduleID] = module;\n\n\t// show notice if module is non-standard\n\tif (module.repo) {\n\t\tif (!isStandard(module.structure.type_name)) {\n\t\t\tlet { pkg, _ } = splitTypeName(module.structure.type_name);\n\t\t\t$('.nonstandard-project-link', $tpl).attr('href', module.repo).text(module.repo);\n\t\t\t$('.nonstandard-package-path', $tpl).text(pkg);\n\t\t\t$('.nonstandard-notice', $tpl).prepend(nonStandardFlag).show();\n\t\t}\n\n\t\tvar $repoName = $('<span/>').text(stripScheme(module.repo));\n\t\t$('.module-repo-selector', $tpl).html('<span class=\"module-repo-selector-arrow\">&#9656;</span>').append($repoName);\n\t}\n\n\t// for most types, just render their docs; but for maps or arrays, fall through to underlying type for docs\n\tlet rawDocs = module.structure.doc ?? module.structure.elems;\n\n\t$('.top-doc', $tpl).html(markdown(replaceGoTypeNameWithCaddyModuleName(rawDocs, module, moduleID)));\n\t$('.top-doc', $tpl).append(makeSubmoduleList(module, \"\", module.structure));\n\n\tlet $group = newGroup();\n\trenderData($tpl, module, module.structure, 0, \"\", $group);\n\t$('.renderbox', $tpl).append($group);\n\n\tif ($('.field-list-contents', $tpl).text().trim()) {\n\t\t$('.field-list-header', $tpl).show();\n\t}\n\n\t// TODO: see about fixing this for module and JSON docs pages\n\t// // if the browser tried to navigate directly to an element\n\t// // on the page when it loaded, it would have failed since\n\t// // we hadn't rendered it yet; but now we can scroll to it\n\t// // directly since rendering has finished\n\t// if (window.location.hash.length > 1) {\n\t// \tdocument.getElementById(window.location.hash.substr(1)).scrollIntoView();\n\t// }\n}\n\nfunction renderData($tpl, module, data, nesting, path, $group) {\n\tswitch (data.type) {\n\tcase \"struct\":\n\t\t$group.append('{<a href=\"javascript:\" class=\"toggle-obj expanded\" title=\"Collapse/expand\">&#9662;</a>');\n\t\tnesting++;\n\n\t\tvar $fieldGroup = newGroup();\n\t\trenderModuleInlineKey($tpl, module, data, nesting, $fieldGroup);\n\t\t$group.append($fieldGroup);\n\t\tif (data.struct_fields) {\n\t\t\t// TODO: Not sure if sorting the struct fields is a good idea...\n\t\t\t// data.struct_fields.sort(function(a, b) {\n\t\t\t// \tif (a.key > b.key) return 1;\n\t\t\t// \tif (b.key > a.key) return -1;\n\t\t\t// \treturn 0;\n\t\t\t// });\n\t\t\tfor (var i = 0; i < data.struct_fields.length; i++) {\n\t\t\t\tvar field = data.struct_fields[i];\n\t\t\t\tvar fieldPath = path+\"/\"+field.key;\n\t\t\t\tvar cleanFieldPath = fieldPath.slice(1); // trim leading slash\n\n\t\t\t\t// store the docs for this path\n\t\t\t\tlet linkClass = \"documented\";\n\t\t\t\tif (field.doc) {\n\t\t\t\t\tpageDocs[fieldPath] = field.doc;\n\t\t\t\t\tlinkClass += \" has-popup\";\n\t\t\t\t}\n\n\t\t\t\t// render the docs to the page\n\t\t\t\tvar fieldDoc = markdown(field.doc) || '<p class=\"explain\">There are no docs for this property.</p>';\n\t\t\t\tfieldDoc += makeSubmoduleList(module, fieldPath, field.value);\n\t\t\t\tappendToFieldDocs($tpl, module, cleanFieldPath, fieldDoc);\n\n\t\t\t\t// render the field to the JSON box\n\t\t\t\tvar $fieldGroup = newGroup();\n\t\t\t\tindent(nesting, $fieldGroup);\n\t\t\t\tvar keyATag = '<a ';\n\t\t\t\tif (canTraverse(module)) {\n\t\t\t\t\tkeyATag += 'href=\".'+fieldPath+'/\" ';\n\t\t\t\t}\n\t\t\t\tkeyATag += 'data-path=\"'+fieldPath+'\" class=\"'+linkClass+'\">'+field.key+'</a>';\n\t\t\t\t$fieldGroup.append('<span class=\"qu\">\"</span><span class=\"key\">'+keyATag+'</span><span class=\"qu\">\"</span>: ');\n\t\t\t\trenderData($tpl, module, field.value, nesting, fieldPath, $fieldGroup);\n\t\t\t\tif (i < data.struct_fields.length-1) {\n\t\t\t\t\t$fieldGroup.append(',');\n\t\t\t\t}\n\t\t\t\t$group.append($fieldGroup);\n\t\t\t}\n\t\t}\n\t\tnesting--;\n\t\tindent(nesting, $group);\n\t\t$group.append('<span class=\"end-obj\">}</span>');\n\t\tbreak;\n\n\tcase \"bool\":\n\t\t$group.append('<span class=\"bool\">false</span>'); // TODO: default value?\n\t\tbreak;\n\n\tcase \"int\":\n\tcase \"uint\":\n\tcase \"float\":\n\tcase \"complex\":\n\t\t$group.append('<span class=\"num\">0</span>'); // TODO: default value?\n\t\tbreak;\n\n\tcase \"string\":\n\t\t$group.append('<span class=\"qu\">\"</span><span class=\"str\"></span><span class=\"qu\">\"</span>'); // TODO: default value?\n\t\tbreak;\n\n\tcase \"array\":\n\t\t$group.append('[');\n\t\tif (data.elems.type == \"module_map\") {\n\t\t\t$group.append('{<a href=\".'+path+'/\" class=\"module has-popup\" data-namespace=\"'+(data.elems.module_namespace || '')+'\" data-path=\"'+path+'\">&bull;&bull;&bull;</a>}');\n\t\t} else {\n\t\t\trenderData($tpl, module, data.elems, nesting, path, $group);\n\t\t}\n\t\t$group.append(']');\n\t\tbreak;\n\n\tcase \"map\":\n\t\t$group.append('{\\n')\n\t\tnesting++;\n\t\trenderModuleInlineKey($tpl, module, data, nesting, $group);\n\t\tindent(nesting, $group);\n\t\trenderData($tpl, module, data.map_keys, nesting, path, $group);\n\t\t$group.append(': ');\n\t\trenderData($tpl, module, data.elems, nesting, path, $group);\n\t\t$group.append('\\n');\n\t\tnesting--;\n\t\tindent(nesting, $group);\n\t\t$group.append('}');\n\t\tbreak;\n\n\tcase \"module\":\n\tcase \"module_map\":\n\t\tvar aTag = '<a';\n\t\tif (canTraverse(module)) {\n\t\t\taTag += ' href=\".'+path+'/\"';\n\t\t}\n\t\taTag += ' class=\"module has-popup\" data-namespace=\"'+(data.module_namespace || '')+'\" data-path=\"'+path+'\">&bull;&bull;&bull;</a>';\n\t\t$group.append('{'+aTag+'}');\n\t\tbreak;\n\t}\n}\n\nfunction renderModuleInlineKey($tpl, module, data, nesting, $group) {\n\tif (!data.module_inline_key) {\n\t\treturn\n\t}\n\tvar moduleName = pathComponents[pathComponents.length-2];\n\tindent(nesting, $group);\n\t$group.append('<span class=\"qu\">\"</span><span class=\"key module-inline-key has-popup\">'+data.module_inline_key+'</span><span class=\"qu\">\"</span>: <span class=\"qu\">\"</span><span class=\"str\"><b>'+moduleName+'</b></span><span class=\"qu\">\"</span>');\n\tif (data.struct_fields && data.struct_fields.length > 0) {\n\t\t$group.append(',');\n\t}\n\t$group.append('\\n');\n\n\tappendToFieldDocs($tpl, module, data.module_inline_key, $('#hovercard-inline-key').html());\n}\n\nfunction appendToFieldDocs($tpl, module, cleanFieldPath, fieldDoc) {\n\tvar dt = cleanFieldPath;\n\tif (canTraverse(module)) {\n\t\tdt = '<a href=\"./'+cleanFieldPath+'/\">'+dt+'</a>';\n\t}\n\t$('.field-list-contents', $tpl).append('<dt class=\"field-name\" id=\"'+cleanFieldPath+'\"><a href=\"#'+cleanFieldPath+'\" class=\"inline-link\">&#128279;</a>'+dt+'</dt> <dd>'+fieldDoc+'</dd>');\n}\n\nfunction indent(nesting, $group) {\n\tvar $span = $('<span class=\"indent\"></span>');\n\t$span.append('\\t'.repeat(nesting));\n\t$group.append($span);\n}\n\nfunction makeSubmoduleList(module, path, value) {\n\twhile (value.elems) {\n\t\tvalue = value.elems;\n\t}\n\tif (value.type != \"module\" && value.type != \"module_map\") {\n\t\treturn '';\n\t}\n\tvar submodList = '<ul>';\n\tif (module.namespaces && module.namespaces[value.module_namespace]) {\n\t\tfor (var j = 0; j < module.namespaces[value.module_namespace].length; j++) {\n\t\t\tvar submod = module.namespaces[value.module_namespace][j];\n\t\t\tvar href = canTraverse(module) ? '.'+path+'/'+submod.name+'/' : './'+value.module_namespace+'.'+submod.name;\n\t\t\tvar submodLink = '<a href=\"'+href+'\">'+submod.name+'</a>';\n\t\t\tif (!isStandard(submod.package)) {\n\t\t\t\tsubmodLink += ' '+nonStandardFlag;\n\t\t\t}\n\t\t\tsubmodList += '<li>'+submodLink+'</li>';\n\t\t}\n\t}\n\tsubmodList += '</ul>';\n\treturn '<div><p>Fulfilled by modules in namespace: <b>'+value.module_namespace+'</b></p>'+submodList+'</div>';\n}\n\n// canTraverse returns true if the page data\n// includes breadcrumbs; i.e. we are on a page\n// that can traverse the JSON structure, not\n// only render part of it in isolation.\nfunction canTraverse(data) {\n\treturn data.breadcrumb != null;\n}\n\nfunction newGroup() {\n\treturn $('<div class=\"group\"/>');\n}\n\nfunction replaceGoTypeNameWithCaddyModuleName(docs, module, moduleID) {\n\tif (!docs || !moduleID) return docs;\n\n\t// fully qualified type name\n\tlet fqtn = module.structure.type_name;\n\n\t// extract just the local type name\n\tlet {_, typeName} = splitTypeName(fqtn);\n\n\t// replace the type name with the Caddy module ID if it starts the docs.\n\tif (docs.indexOf(typeName) === 0) {\n\t\tdocs = moduleID + docs.substr(typeName.length);\n\t}\n\n\treturn docs;\n}\n\nfunction markdown(input) {\n\tif (!input) {\n\t\treturn \"\";\n\t}\n\treturn marked(input);\n}"
  },
  {
    "path": "src/resources/js/docs.js",
    "content": "$(function() {\n\tfunction hasPrefix(str, prefix) {\n\t\tif (!prefix) return true;\n\t\tif (!str)    return false;\n\t\treturn str.indexOf(prefix) === 0;\n\t}\n\n\t// highlight current page in left nav\n\tvar $currentPageLink = $('main nav a[href=\"'+window.location.pathname+'\"]');\n\tif (hasPrefix(window.location.pathname, \"/docs/json/\")) {\n\t\t// as a special case, highlight the JSON structure link anywhere within it\n\t\t$currentPageLink = $('main nav a[href=\"/docs/json/\"]');\n\t}\n\tif (hasPrefix(window.location.pathname, \"/docs/modules/\")) {\n\t\t// as another special case, highlight the modules link anywhere within it\n\t\t$currentPageLink = $('main nav a[href=\"/docs/modules/\"]');\n\t}\n\t$currentPageLink.addClass('current');\n\n\t// add anchor links, inspired by https://github.com/bryanbraun/anchorjs\n\t$('article > h1[id], article > h2[id], article > h3[id], article > h4[id], article > h5[id], article > h6[id]').each(function() {\n\t\tvar $anchor = $('<a href=\"#'+this.id+'\" class=\"anchor-link\" title=\"Direct link\">🔗</a>');\n\t\t$(this).prepend($anchor);\n\t});\n\n\t// the server-side markdown renderer annoyingly renders\n\t// colored code blocks differently from plain ones, in that\n\t// colorized ones do not have the additional <code> inside\n\t// the <pre>; this line finds those and adds a .chroma class\n\t// to the outer pre element, and our CSS file has a style to\n\t// ensure the inner code block does not produce extra padding\n\t$('article > pre:not(.chroma) > code:not(.cmd)').parent().addClass('chroma');\n\n\t// Add links to Caddyfile directive tokens in code blocks.\n\t// See include/docs-head.html for the whitelist bootstrapping logic\n\t$('pre.chroma .k')\n\t\t.filter((k, item) =>\n\t\t\twindow.CaddyfileDirectives.includes(item.innerText)\n\t\t\t\t|| item.innerText === '<directives...>'\n\t\t)\n\t\t.map(function(k, item) {\n\t\t\tlet text = item.innerText;\n\t\t\tlet url = text === '<directives...>'\n\t\t\t\t? '/docs/caddyfile/directives'\n\t\t\t\t: '/docs/caddyfile/directives/' + text;\n\t\t\ttext = text.replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\t\t\t$(item).html('<a href=\"' + url + '\" style=\"color: inherit;\" title=\"Directive\">' + text + '</a>');\n\t\t});\n\n\t// Add links to [<matcher>] or named matcher tokens in code blocks.\n\t// The matcher text includes <> characters which are parsed as HTML,\n\t// so we must use text() to change the link text.\n\t$('pre.chroma .nd')\n\t\t.map(function(k, item) {\n\t\t\tlet text = item.innerText.replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\t\t\t$(item).html('<a href=\"/docs/caddyfile/matchers#syntax\" style=\"color: inherit;\" title=\"Matcher token\">' + text + '</a>');\n\t\t});\n});\n\n// addLinkaddLinksToSubdirectivessToAnchors finds all the ID anchors\n// in the article, and turns any directive or subdirective span into\n// links that have an ID on the page. This is opt-in for each page,\n// because it's not necessary to run everywhere.\nfunction addLinksToSubdirectives() {\n\tlet anchors = $('article *[id]').map((i, el) => el.id).toArray();\n\t$('pre.chroma .k')\n\t\t.filter((k, item) => anchors.includes(item.innerText))\n\t\t.map(function(k, item) {\n\t\t\tlet text = item.innerText.replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\t\t\tlet url = '#' + item.innerText;\n\t\t\t$(item).html('<a href=\"' + url + '\" style=\"color: inherit;\" title=\"' + text + '\">' + text + '</a>');\n\t\t});\n}\n\nfunction stripScheme(url) {\n\treturn url.substring(url.indexOf(\"://\")+3);\n}\n\n// splitTypeName splits a fully qualified type name into\n// its package path and type name components, for example:\n// \"github.com/foo/bar.Type\" => \"github.com/foo/bar\" and \"Type\".\nfunction splitTypeName(fqtn) {\n\tlet lastDotPos = fqtn.lastIndexOf('.');\n\tlet pkg = fqtn.substr(0, lastDotPos);\n\tlet typeName = fqtn.substr(lastDotPos+1);\n\treturn {pkg: pkg, typeName: typeName};\n}\n"
  },
  {
    "path": "src/resources/js/json-docs.js",
    "content": "const jsonDocsPathPrefix = \"/docs/json/\";\n\nvar configPath = window.location.pathname.substr(jsonDocsPathPrefix.length-1); // keep trailing slash\nvar pathComponents = configPath.split('/');\n\nsetPageTitle();\n\n// load the docs for this path\n$.get(\"/api/docs/config\"+configPath, function(json) {\n\t// wait until the DOM has finished loading before rendering the results\n\t$(function() {\n\t\tbeginRenderingInto($('#json-docs-container'), '', json.result);\n\n\t\t// establish the breadcrumb\n\t\tvar $bc = $('.breadcrumbs');\n\t\t$('<a href=\"'+jsonDocsPathPrefix+'\" id=\"top-breadcrumb\">JSON配置结构</a> &rsaquo;').appendTo($bc);\n\t\tfor (var i = 1; i < pathComponents.length-1; i++) {\n\t\t\tvar bcPath = pathComponents.slice(0, i+1).join('/');\n\t\t\tvar bcSiblingPath = pathComponents.slice(1, i).join('/');\n\t\t\t\n\t\t\t// enclosing with span is a hack so jQuery treats this as a HTML DOM object\n\t\t\t$('<span> &rsaquo; <a href=\"'+jsonDocsPathPrefix+bcPath.substr(1)+'/\" class=\"breadcrumb has-popup\" data-sibling-path=\"'+bcSiblingPath+'\">'+pathComponents[i]+'</a></span>').appendTo($bc);\n\t\t}\n\n\t});\n});\n\nfunction setPageTitle() {\n\t// set the page title with something useful\n\tvar parts = configPath.split(\"/\");\n\tif (parts.length > 1) {\n\t\tif (!parts[0]) {\n\t\t\tparts.shift();\n\t\t}\n\t\tif (!parts[parts.length-1]) {\n\t\t\tparts.pop();\n\t\t}\n\t\tvar titlePrefix = parts.slice(-2).join(\"/\");\n\t\tif (parts.length > 4) {\n\t\t\ttitlePrefix = parts.slice(0, 2).join(\"/\")+\"/.../\"+titlePrefix;\n\t\t}\n\t\tif (titlePrefix) {\n\t\t\tdocument.title = titlePrefix + \" - \" + document.title;\n\t\t}\n\t}\n}"
  },
  {
    "path": "src/resources/js/module-docs.js",
    "content": "const moduleDocsPathPrefix = \"/docs/modules/\";\n\nvar moduleID = window.location.pathname.substr(moduleDocsPathPrefix.length);\nif (moduleID) {\n\t// update page title and load the docs for these modules (possibly more than 1 with this ID)\n\tdocument.title = \"Module \" + moduleID + \" - Caddy Documentation\";\n\t$.get(\"/api/docs/module/\"+moduleID, function(json) {\n\t\tvar modules = json.result;\n\n\t\t// wait until the DOM has finished loading before rendering the results\n\t\t$(function() {\n\t\t\t$('#module-docs-container').show();\n\t\t\t$('.module-name').text(\"Module \"+moduleID);\n\t\t\tmodules.forEach((module) => {\n\t\t\t\t$tpl = $('#module-template').clone().attr('id', stripScheme(module.repo));\n\t\t\t\tif (modules.length > 1) {\n\t\t\t\t\t$('article', $tpl).hide();\n\t\t\t\t}\n\t\t\t\tbeginRenderingInto($tpl, moduleID, module);\n\t\t\t\t$('#module-docs-container').append($tpl);\n\t\t\t});\n\t\t\tif (modules.length > 1) {\n\t\t\t\t$('#module-multiple-repos .module-name').text(moduleID);\n\t\t\t\t$('#module-multiple-repos').show();\n\t\t\t} else {\n\t\t\t\t$('.module-repo-selector').hide();\n\t\t\t}\n\n\t\t\t// if a specific repo's module is wanted, expand and scroll to it\n\t\t\tif (window.location.hash.length > 1) {\n\t\t\t\t// TODO: weird bug in jQuery(??) that it can't select IDs with slashes in them, so we use vanilla JS\n\t\t\t\tvar container = document.getElementById(window.location.hash.substr(1));\n\t\t\t\t$('.module-repo-selector', container).click();\n\t\t\t\tcontainer.scrollIntoView();\n\t\t\t}\n\t\t});\n\t});\n\n\t$(function() {\n\t\t$('body').on('click', '.module-repo-selector', function() {\n\t\t\tif ($(this).hasClass('expanded')) {\n\t\t\t\t// collapse\n\t\t\t\t$('.module-repo-selector-arrow', this).html('&#9656;');\n\t\t\t} else {\n\t\t\t\t// expand\n\t\t\t\t$('.module-repo-selector-arrow', this).html('&#9662;');\n\t\t\t}\n\t\t\t$(this).toggleClass('expanded');\n\t\t\t$(this).next('article').toggle();\n\t\t});\n\t});\n} else {\n\t// populate the module list\n\t$.get(\"/api/modules\", function(json) {\n\t\tvar moduleList = json.result;\n\n\t\tconsole.log(\"MODULE LIST:\", moduleList);\n\t\t\n\t\t// wait until the DOM has finished loading before rendering the results\n\t\t$(function() {\n\t\t\t$('#module-list-container').show();\n\t\t\t$table = $('#module-list');\n\t\t\tfor (modID in moduleList) {\n\t\t\t\tvar infos = moduleList[modID];\n\t\t\t\t\n\t\t\t\tinfos.forEach((info) => {\n\t\t\t\t\t// refine a short preview of the module's docs\n\t\t\t\t\tlet shortDoc = truncate(info.docs, 200);\n\t\t\t\t\tif (shortDoc && shortDoc.indexOf(modID) === 0) {\n\t\t\t\t\t\tshortDoc = shortDoc.substr(modID.length).trim();\n\t\t\t\t\t}\n\n\t\t\t\t\tlet modLink = \"./\"+modID;\n\t\t\t\t\tif (infos.length > 1) {\n\t\t\t\t\t\tmodLink += \"#\"+stripScheme(info.repo);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tvar standard = isStandard(info.package);\n\t\t\t\t\tvar $tr = $('<tr/>');\n\t\t\t\t\t$tr.append('<td>'+(standard ? standardFlag : nonStandardFlag)+'</td>');\n\t\t\t\t\tvar $tdLink = $('<td><a target=\"_blank\" href=\"https://caddyserver.com/docs/modules/'+modLink+'\" class=\"module-link\">'+modID+'</a></td>');\n\t\t\t\t\tif (infos.length > 1) {\n\t\t\t\t\t\t$tdLink.append($('<div class=\"module-repo-differentiator\">').text('('+stripScheme(info.repo)+')'));\n\t\t\t\t\t}\n\t\t\t\t\t$tr.append($tdLink);\n\t\t\t\t\t$tr.append($('<td/>').text(shortDoc));\n\t\t\t\t\t$table.append($tr);\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t});\n}\n"
  }
]