Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 10, 23:45 UTC
| 1 | // Copyright 2015 Matthew Holt and The Caddy Authors |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package caddycmd |
| 16 | |
| 17 | import ( |
| 18 | "flag" |
| 19 | "fmt" |
| 20 | "os" |
| 21 | "regexp" |
| 22 | "strings" |
| 23 | "sync" |
| 24 | |
| 25 | "github.com/spf13/cobra" |
| 26 | "github.com/spf13/cobra/doc" |
| 27 | |
| 28 | "github.com/caddyserver/caddy/v2" |
| 29 | ) |
| 30 | |
| 31 | // Command represents a subcommand. Name, Func, |
| 32 | // and Short are required. |
| 33 | type Command struct { |
| 34 | // The name of the subcommand. Must conform to the |
| 35 | // format described by the RegisterCommand() godoc. |
| 36 | // Required. |
| 37 | Name string |
| 38 | |
| 39 | // Usage is a brief message describing the syntax of |
| 40 | // the subcommand's flags and args. Use [] to indicate |
| 41 | // optional parameters and <> to enclose literal values |
| 42 | // intended to be replaced by the user. Do not prefix |
| 43 | // the string with "caddy" or the name of the command |
| 44 | // since these will be prepended for you; only include |
| 45 | // the actual parameters for this command. |
| 46 | Usage string |
| 47 | |
| 48 | // Short is a one-line message explaining what the |
| 49 | // command does. Should not end with punctuation. |
| 50 | // Required. |
| 51 | Short string |
| 52 | |
| 53 | // Long is the full help text shown to the user. |
| 54 | // Will be trimmed of whitespace on both ends before |
| 55 | // being printed. |
| 56 | Long string |
| 57 | |
| 58 | // Flags is the flagset for command. |
| 59 | // This is ignored if CobraFunc is set. |
| 60 | Flags *flag.FlagSet |
| 61 | |
| 62 | // Func is a function that executes a subcommand using |
| 63 | // the parsed flags. It returns an exit code and any |
| 64 | // associated error. |
| 65 | // Required if CobraFunc is not set. |
| 66 | Func CommandFunc |
| 67 | |
| 68 | // CobraFunc allows further configuration of the command |
| 69 | // via cobra's APIs. If this is set, then Func and Flags |
| 70 | // are ignored, with the assumption that they are set in |
| 71 | // this function. A caddycmd.WrapCommandFuncForCobra helper |
| 72 | // exists to simplify porting CommandFunc to Cobra's RunE. |
| 73 | CobraFunc func(*cobra.Command) |
| 74 | } |
| 75 | |
| 76 | // CommandFunc is a command's function. It runs the |
| 77 | // command and returns the proper exit code along with |
| 78 | // any error that occurred. |
| 79 | type CommandFunc func(Flags) (int, error) |
| 80 | |
| 81 | // Commands returns a list of commands initialised by |
| 82 | // RegisterCommand |
| 83 | func Commands() map[string]Command { |
| 84 | commandsMu.RLock() |
| 85 | defer commandsMu.RUnlock() |
| 86 | |
| 87 | return commands |
| 88 | } |
| 89 | |
| 90 | var ( |
| 91 | commandsMu sync.RWMutex |
| 92 | commands = make(map[string]Command) |
| 93 | ) |
| 94 | |
| 95 | func init() { |
| 96 | RegisterCommand(Command{ |
| 97 | Name: "start", |
| 98 | Usage: "[--config <path> [--adapter <name>]] [--envfile <path>] [--watch] [--pidfile <file>]", |
| 99 | Short: "Starts the Caddy process in the background and then returns", |
| 100 | Long: ` |
| 101 | Starts the Caddy process, optionally bootstrapped with an initial config file. |
| 102 | This command unblocks after the server starts running or fails to run. |
| 103 | |
| 104 | If --envfile is specified, an environment file with environment variables |
| 105 | in the KEY=VALUE format will be loaded into the Caddy process. |
| 106 | |
| 107 | On Windows, the spawned child process will remain attached to the terminal, so |
| 108 | closing the window will forcefully stop Caddy; to avoid forgetting this, try |
| 109 | using 'caddy run' instead to keep it in the foreground. |
| 110 | `, |
| 111 | CobraFunc: func(cmd *cobra.Command) { |
| 112 | cmd.Flags().StringP("config", "c", "", "Configuration file") |
| 113 | cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") |
| 114 | cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") |
| 115 | cmd.Flags().BoolP("watch", "w", false, "Reload changed config file automatically") |
| 116 | cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID") |
| 117 | cmd.RunE = WrapCommandFuncForCobra(cmdStart) |
| 118 | }, |
| 119 | }) |
| 120 | |
| 121 | RegisterCommand(Command{ |
| 122 | Name: "run", |
| 123 | Usage: "[--config <path> [--adapter <name>]] [--envfile <path>] [--environ] [--resume] [--watch] [--pidfile <file>]", |
| 124 | Short: `Starts the Caddy process and blocks indefinitely`, |
| 125 | Long: ` |
| 126 | Starts the Caddy process, optionally bootstrapped with an initial config file, |
| 127 | and blocks indefinitely until the server is stopped; i.e. runs Caddy in |
| 128 | "daemon" mode (foreground). |
| 129 | |
| 130 | If a config file is specified, it will be applied immediately after the process |
| 131 | is running. If the config file is not in Caddy's native JSON format, you can |
| 132 | specify an adapter with --adapter to adapt the given config file to |
| 133 | Caddy's native format. The config adapter must be a registered module. Any |
| 134 | warnings will be printed to the log, but beware that any adaptation without |
| 135 | errors will immediately be used. If you want to review the results of the |
| 136 | adaptation first, use the 'adapt' subcommand. |
| 137 | |
| 138 | As a special case, if the current working directory has a file called |
| 139 | "Caddyfile" and the caddyfile config adapter is plugged in (default), then |
| 140 | that file will be loaded and used to configure Caddy, even without any command |
| 141 | line flags. |
| 142 | |
| 143 | If --envfile is specified, an environment file with environment variables |
| 144 | in the KEY=VALUE format will be loaded into the Caddy process. |
| 145 | |
| 146 | If --environ is specified, the environment as seen by the Caddy process will |
| 147 | be printed before starting. This is the same as the environ command but does |
| 148 | not quit after printing, and can be useful for troubleshooting. |
| 149 | |
| 150 | The --resume flag will override the --config flag if there is a config auto- |
| 151 | save file. It is not an error if --resume is used and no autosave file exists. |
| 152 | |
| 153 | If --watch is specified, the config file will be loaded automatically after |
| 154 | changes. ⚠️ This can make unintentional config changes easier; only use this |
| 155 | option in a local development environment. |
| 156 | `, |
| 157 | CobraFunc: func(cmd *cobra.Command) { |
| 158 | cmd.Flags().StringP("config", "c", "", "Configuration file") |
| 159 | cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") |
| 160 | cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") |
| 161 | cmd.Flags().BoolP("environ", "e", false, "Print environment") |
| 162 | cmd.Flags().BoolP("resume", "r", false, "Use saved config, if any (and prefer over --config file)") |
| 163 | cmd.Flags().BoolP("watch", "w", false, "Watch config file for changes and reload it automatically") |
| 164 | cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID") |
| 165 | cmd.Flags().StringP("pingback", "", "", "Echo confirmation bytes to this address on success") |
| 166 | cmd.RunE = WrapCommandFuncForCobra(cmdRun) |
| 167 | }, |
| 168 | }) |
| 169 | |
| 170 | RegisterCommand(Command{ |
| 171 | Name: "stop", |
| 172 | Usage: "[--config <path> [--adapter <name>]] [--address <interface>]", |
| 173 | Short: "Gracefully stops a started Caddy process", |
| 174 | Long: ` |
| 175 | Stops the background Caddy process as gracefully as possible. |
| 176 | |
| 177 | It requires that the admin API is enabled and accessible, since it will |
| 178 | use the API's /stop endpoint. The address of this request can be customized |
| 179 | using the --address flag, or from the given --config, if not the default. |
| 180 | `, |
| 181 | CobraFunc: func(cmd *cobra.Command) { |
| 182 | cmd.Flags().StringP("config", "c", "", "Configuration file to use to parse the admin address, if --address is not used") |
| 183 | cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (when --config is used)") |
| 184 | cmd.Flags().StringP("address", "", "", "The address to use to reach the admin API endpoint, if not the default") |
| 185 | cmd.RunE = WrapCommandFuncForCobra(cmdStop) |
| 186 | }, |
| 187 | }) |
| 188 | |
| 189 | RegisterCommand(Command{ |
| 190 | Name: "reload", |
| 191 | Usage: "--config <path> [--adapter <name>] [--address <interface>]", |
| 192 | Short: "Changes the config of the running Caddy instance", |
| 193 | Long: ` |
| 194 | Gives the running Caddy instance a new configuration. This has the same effect |
| 195 | as POSTing a document to the /load API endpoint, but is convenient for simple |
| 196 | workflows revolving around config files. |
| 197 | |
| 198 | Since the admin endpoint is configurable, the endpoint configuration is loaded |
| 199 | from the --address flag if specified; otherwise it is loaded from the given |
| 200 | config file; otherwise the default is assumed. |
| 201 | `, |
| 202 | CobraFunc: func(cmd *cobra.Command) { |
| 203 | cmd.Flags().StringP("config", "c", "", "Configuration file (required)") |
| 204 | cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") |
| 205 | cmd.Flags().StringP("address", "", "", "Address of the administration listener, if different from config") |
| 206 | cmd.Flags().BoolP("force", "f", false, "Force config reload, even if it is the same") |
| 207 | cmd.RunE = WrapCommandFuncForCobra(cmdReload) |
| 208 | }, |
| 209 | }) |
| 210 | |
| 211 | RegisterCommand(Command{ |
| 212 | Name: "version", |
| 213 | Short: "Prints the version", |
| 214 | Long: ` |
| 215 | Prints the version of this Caddy binary. |
| 216 | |
| 217 | Version information must be embedded into the binary at compile-time in |
| 218 | order for Caddy to display anything useful with this command. If Caddy |
| 219 | is built from within a version control repository, the Go command will |
| 220 | embed the revision hash if available. However, if Caddy is built in the |
| 221 | way specified by our online documentation (or by using xcaddy), more |
| 222 | detailed version information is printed as given by Go modules. |
| 223 | |
| 224 | For more details about the full version string, see the Go module |
| 225 | documentation: https://go.dev/doc/modules/version-numbers |
| 226 | `, |
| 227 | Func: cmdVersion, |
| 228 | }) |
| 229 | |
| 230 | RegisterCommand(Command{ |
| 231 | Name: "list-modules", |
| 232 | Usage: "[--packages] [--versions] [--skip-standard] [--json]", |
| 233 | Short: "Lists the installed Caddy modules", |
| 234 | CobraFunc: func(cmd *cobra.Command) { |
| 235 | cmd.Flags().BoolP("packages", "", false, "Print package paths") |
| 236 | cmd.Flags().BoolP("versions", "", false, "Print version information") |
| 237 | cmd.Flags().BoolP("skip-standard", "s", false, "Skip printing standard modules") |
| 238 | cmd.Flags().BoolP("json", "", false, "Print modules in JSON format") |
| 239 | cmd.RunE = WrapCommandFuncForCobra(cmdListModules) |
| 240 | }, |
| 241 | }) |
| 242 | |
| 243 | RegisterCommand(Command{ |
| 244 | Name: "build-info", |
| 245 | Short: "Prints information about this build", |
| 246 | Func: cmdBuildInfo, |
| 247 | }) |
| 248 | |
| 249 | RegisterCommand(Command{ |
| 250 | Name: "environ", |
| 251 | Usage: "[--envfile <path>]", |
| 252 | Short: "Prints the environment", |
| 253 | Long: ` |
| 254 | Prints the environment as seen by this Caddy process. |
| 255 | |
| 256 | The environment includes variables set in the system. If your Caddy |
| 257 | configuration uses environment variables (e.g. "{env.VARIABLE}") then |
| 258 | this command can be useful for verifying that the variables will have |
| 259 | the values you expect in your config. |
| 260 | |
| 261 | If --envfile is specified, an environment file with environment variables |
| 262 | in the KEY=VALUE format will be loaded into the Caddy process. |
| 263 | |
| 264 | Note that environments may be different depending on how you run Caddy. |
| 265 | Environments for Caddy instances started by service managers such as |
| 266 | systemd are often different than the environment inherited from your |
| 267 | shell or terminal. |
| 268 | |
| 269 | You can also print the environment the same time you use "caddy run" |
| 270 | by adding the "--environ" flag. |
| 271 | |
| 272 | Environments may contain sensitive data. |
| 273 | `, |
| 274 | CobraFunc: func(cmd *cobra.Command) { |
| 275 | cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") |
| 276 | cmd.RunE = WrapCommandFuncForCobra(cmdEnviron) |
| 277 | }, |
| 278 | }) |
| 279 | |
| 280 | RegisterCommand(Command{ |
| 281 | Name: "adapt", |
| 282 | Usage: "--config <path> [--adapter <name>] [--pretty] [--validate] [--envfile <path>]", |
| 283 | Short: "Adapts a configuration to Caddy's native JSON", |
| 284 | Long: ` |
| 285 | Adapts a configuration to Caddy's native JSON format and writes the |
| 286 | output to stdout, along with any warnings to stderr. |
| 287 | |
| 288 | If --pretty is specified, the output will be formatted with indentation |
| 289 | for human readability. |
| 290 | |
| 291 | If --validate is used, the adapted config will be checked for validity. |
| 292 | If the config is invalid, an error will be printed to stderr and a non- |
| 293 | zero exit status will be returned. |
| 294 | |
| 295 | If --envfile is specified, an environment file with environment variables |
| 296 | in the KEY=VALUE format will be loaded into the Caddy process. |
| 297 | |
| 298 | If you wish to use stdin instead of a regular file, use - as the path. |
| 299 | `, |
| 300 | CobraFunc: func(cmd *cobra.Command) { |
| 301 | cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)") |
| 302 | cmd.Flags().StringP("adapter", "a", "caddyfile", "Name of config adapter") |
| 303 | cmd.Flags().BoolP("pretty", "p", false, "Format the output for human readability") |
| 304 | cmd.Flags().BoolP("validate", "", false, "Validate the output") |
| 305 | cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") |
| 306 | cmd.RunE = WrapCommandFuncForCobra(cmdAdaptConfig) |
| 307 | }, |
| 308 | }) |
| 309 | |
| 310 | RegisterCommand(Command{ |
| 311 | Name: "validate", |
| 312 | Usage: "--config <path> [--adapter <name>] [--envfile <path>]", |
| 313 | Short: "Tests whether a configuration file is valid", |
| 314 | Long: ` |
| 315 | Loads and provisions the provided config, but does not start running it. |
| 316 | This reveals any errors with the configuration through the loading and |
| 317 | provisioning stages. |
| 318 | |
| 319 | If --envfile is specified, an environment file with environment variables |
| 320 | in the KEY=VALUE format will be loaded into the Caddy process. |
| 321 | `, |
| 322 | CobraFunc: func(cmd *cobra.Command) { |
| 323 | cmd.Flags().StringP("config", "c", "", "Input configuration file") |
| 324 | cmd.Flags().StringP("adapter", "a", "", "Name of config adapter") |
| 325 | cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") |
| 326 | cmd.RunE = WrapCommandFuncForCobra(cmdValidateConfig) |
| 327 | }, |
| 328 | }) |
| 329 | |
| 330 | RegisterCommand(Command{ |
| 331 | Name: "storage", |
| 332 | Short: "Commands for working with Caddy's storage (EXPERIMENTAL)", |
| 333 | Long: ` |
| 334 | Allows exporting and importing Caddy's storage contents. The two commands can be |
| 335 | combined in a pipeline to transfer directly from one storage to another: |
| 336 | |
| 337 | $ caddy storage export --config Caddyfile.old --output - | |
| 338 | > caddy storage import --config Caddyfile.new --input - |
| 339 | |
| 340 | The - argument refers to stdout and stdin, respectively. |
| 341 | |
| 342 | NOTE: When importing to or exporting from file_system storage (the default), the command |
| 343 | should be run as the user that owns the associated root path. |
| 344 | |
| 345 | EXPERIMENTAL: May be changed or removed. |
| 346 | `, |
| 347 | CobraFunc: func(cmd *cobra.Command) { |
| 348 | exportCmd := &cobra.Command{ |
| 349 | Use: "export --config <path> --output <path>", |
| 350 | Short: "Exports storage assets as a tarball", |
| 351 | Long: ` |
| 352 | The contents of the configured storage module (TLS certificates, etc) |
| 353 | are exported via a tarball. |
| 354 | |
| 355 | --output is required, - can be given for stdout. |
| 356 | `, |
| 357 | RunE: WrapCommandFuncForCobra(cmdExportStorage), |
| 358 | } |
| 359 | exportCmd.Flags().StringP("config", "c", "", "Input configuration file (required)") |
| 360 | exportCmd.Flags().StringP("output", "o", "", "Output path") |
| 361 | cmd.AddCommand(exportCmd) |
| 362 | |
| 363 | importCmd := &cobra.Command{ |
| 364 | Use: "import --config <path> --input <path>", |
| 365 | Short: "Imports storage assets from a tarball.", |
| 366 | Long: ` |
| 367 | Imports storage assets to the configured storage module. The import file must be |
| 368 | a tar archive. |
| 369 | |
| 370 | --input is required, - can be given for stdin. |
| 371 | `, |
| 372 | RunE: WrapCommandFuncForCobra(cmdImportStorage), |
| 373 | } |
| 374 | importCmd.Flags().StringP("config", "c", "", "Configuration file to load (required)") |
| 375 | importCmd.Flags().StringP("input", "i", "", "Tar of assets to load (required)") |
| 376 | cmd.AddCommand(importCmd) |
| 377 | }, |
| 378 | }) |
| 379 | |
| 380 | RegisterCommand(Command{ |
| 381 | Name: "fmt", |
| 382 | Usage: "[--overwrite] [--diff] [--imports] [<path>]", |
| 383 | Short: "Formats a Caddyfile", |
| 384 | Long: ` |
| 385 | Formats the Caddyfile by adding proper indentation and spaces to improve |
| 386 | human readability. It prints the result to stdout. |
| 387 | |
| 388 | If --overwrite is specified, the output will be written to the config file |
| 389 | directly instead of printing it. |
| 390 | |
| 391 | If --diff is specified, the output will be compared against the input, and |
| 392 | lines will be prefixed with '-' and '+' where they differ. Note that |
| 393 | unchanged lines are prefixed with two spaces for alignment, and that this |
| 394 | is not a valid patch format. |
| 395 | |
| 396 | If --imports is specified, all files referenced by import directives will |
| 397 | also be formatted. Each file's output is preceded by a header line (# <path>) |
| 398 | when printing to stdout, so multiple files are distinguishable. The --imports |
| 399 | flag cannot be used when reading from stdin. |
| 400 | |
| 401 | If you wish to use stdin instead of a regular file, use - as the path. |
| 402 | When reading from stdin, the --overwrite flag has no effect: the result |
| 403 | is always printed to stdout. |
| 404 | `, |
| 405 | CobraFunc: func(cmd *cobra.Command) { |
| 406 | cmd.Flags().StringP("config", "c", "", "Configuration file") |
| 407 | cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results") |
| 408 | cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output") |
| 409 | cmd.Flags().Bool("imports", false, "Also format files referenced by import directives") |
| 410 | cmd.RunE = WrapCommandFuncForCobra(cmdFmt) |
| 411 | }, |
| 412 | }) |
| 413 | |
| 414 | RegisterCommand(Command{ |
| 415 | Name: "upgrade", |
| 416 | Short: "Upgrade Caddy (EXPERIMENTAL)", |
| 417 | Long: ` |
| 418 | Downloads an updated Caddy binary with the same modules/plugins at the |
| 419 | latest versions. EXPERIMENTAL: May be changed or removed. |
| 420 | `, |
| 421 | CobraFunc: func(cmd *cobra.Command) { |
| 422 | cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") |
| 423 | cmd.RunE = WrapCommandFuncForCobra(cmdUpgrade) |
| 424 | }, |
| 425 | }) |
| 426 | |
| 427 | RegisterCommand(Command{ |
| 428 | Name: "add-package", |
| 429 | Usage: "<package[@version]...>", |
| 430 | Short: "Adds Caddy packages (EXPERIMENTAL)", |
| 431 | Long: ` |
| 432 | Downloads an updated Caddy binary with the specified packages (module/plugin) |
| 433 | added, with an optional version specified (e.g., "package@version"). Retains |
| 434 | existing packages. Returns an error if any of the specified packages are already |
| 435 | included. EXPERIMENTAL: May be changed or removed. |
| 436 | `, |
| 437 | CobraFunc: func(cmd *cobra.Command) { |
| 438 | cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") |
| 439 | cmd.RunE = WrapCommandFuncForCobra(cmdAddPackage) |
| 440 | }, |
| 441 | }) |
| 442 | |
| 443 | RegisterCommand(Command{ |
| 444 | Name: "remove-package", |
| 445 | Func: cmdRemovePackage, |
| 446 | Usage: "<packages...>", |
| 447 | Short: "Removes Caddy packages (EXPERIMENTAL)", |
| 448 | Long: ` |
| 449 | Downloads an updated Caddy binaries without the specified packages (module/plugin). |
| 450 | Returns an error if any of the packages are not included. |
| 451 | EXPERIMENTAL: May be changed or removed. |
| 452 | `, |
| 453 | CobraFunc: func(cmd *cobra.Command) { |
| 454 | cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") |
| 455 | cmd.RunE = WrapCommandFuncForCobra(cmdRemovePackage) |
| 456 | }, |
| 457 | }) |
| 458 | |
| 459 | defaultFactory.Use(func(rootCmd *cobra.Command) { |
| 460 | manpageCommand := Command{ |
| 461 | Name: "manpage", |
| 462 | Usage: "--directory <path>", |
| 463 | Short: "Generates the manual pages for Caddy commands", |
| 464 | Long: ` |
| 465 | Generates the manual pages for Caddy commands into the designated directory |
| 466 | tagged into section 8 (System Administration). |
| 467 | |
| 468 | The manual page files are generated into the directory specified by the |
| 469 | argument of --directory. If the directory does not exist, it will be created. |
| 470 | `, |
| 471 | CobraFunc: func(cmd *cobra.Command) { |
| 472 | cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated") |
| 473 | cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) { |
| 474 | dir := strings.TrimSpace(fl.String("directory")) |
| 475 | if dir == "" { |
| 476 | return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") |
| 477 | } |
| 478 | if err := os.MkdirAll(dir, 0o755); err != nil { |
| 479 | return caddy.ExitCodeFailedQuit, err |
| 480 | } |
| 481 | if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ |
| 482 | Title: "Caddy", |
| 483 | Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections |
| 484 | }, dir); err != nil { |
| 485 | return caddy.ExitCodeFailedQuit, err |
| 486 | } |
| 487 | return caddy.ExitCodeSuccess, nil |
| 488 | }) |
| 489 | }, |
| 490 | } |
| 491 | |
| 492 | // source: https://github.com/spf13/cobra/blob/6dec1ae26659a130bdb4c985768d1853b0e1bc06/site/content/completions/_index.md |
| 493 | completionCommand := Command{ |
| 494 | Name: "completion", |
| 495 | Usage: "[bash|zsh|fish|powershell]", |
| 496 | Short: "Generate completion script", |
| 497 | Long: fmt.Sprintf(`To load completions: |
| 498 | |
| 499 | Bash: |
| 500 | |
| 501 | $ source <(%[1]s completion bash) |
| 502 | |
| 503 | # To load completions for each session, execute once: |
| 504 | # Linux: |
| 505 | $ %[1]s completion bash > /etc/bash_completion.d/%[1]s |
| 506 | # macOS: |
| 507 | $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s |
| 508 | |
| 509 | Zsh: |
| 510 | |
| 511 | # If shell completion is not already enabled in your environment, |
| 512 | # you will need to enable it. You can execute the following once: |
| 513 | |
| 514 | $ echo "autoload -U compinit; compinit" >> ~/.zshrc |
| 515 | |
| 516 | # To load completions for each session, execute once: |
| 517 | $ %[1]s completion zsh > "${fpath[1]}/_%[1]s" |
| 518 | |
| 519 | # You will need to start a new shell for this setup to take effect. |
| 520 | |
| 521 | fish: |
| 522 | |
| 523 | $ %[1]s completion fish | source |
| 524 | |
| 525 | # To load completions for each session, execute once: |
| 526 | $ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish |
| 527 | |
| 528 | PowerShell: |
| 529 | |
| 530 | PS> %[1]s completion powershell | Out-String | Invoke-Expression |
| 531 | |
| 532 | # To load completions for every new session, run: |
| 533 | PS> %[1]s completion powershell > %[1]s.ps1 |
| 534 | # and source this file from your PowerShell profile. |
| 535 | `, rootCmd.Root().Name()), |
| 536 | CobraFunc: func(cmd *cobra.Command) { |
| 537 | cmd.DisableFlagsInUseLine = true |
| 538 | cmd.ValidArgs = []string{"bash", "zsh", "fish", "powershell"} |
| 539 | cmd.Args = cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs) |
| 540 | cmd.RunE = func(cmd *cobra.Command, args []string) error { |
| 541 | switch args[0] { |
| 542 | case "bash": |
| 543 | return cmd.Root().GenBashCompletion(os.Stdout) |
| 544 | case "zsh": |
| 545 | return cmd.Root().GenZshCompletion(os.Stdout) |
| 546 | case "fish": |
| 547 | return cmd.Root().GenFishCompletion(os.Stdout, true) |
| 548 | case "powershell": |
| 549 | return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) |
| 550 | default: |
| 551 | return fmt.Errorf("unrecognized shell: %s", args[0]) |
| 552 | } |
| 553 | } |
| 554 | }, |
| 555 | } |
| 556 | |
| 557 | rootCmd.AddCommand(caddyCmdToCobra(manpageCommand)) |
| 558 | rootCmd.AddCommand(caddyCmdToCobra(completionCommand)) |
| 559 | |
| 560 | // add manpage and completion commands to the map of |
| 561 | // available commands, because they're not registered |
| 562 | // through RegisterCommand. |
| 563 | commandsMu.Lock() |
| 564 | commands[manpageCommand.Name] = manpageCommand |
| 565 | commands[completionCommand.Name] = completionCommand |
| 566 | commandsMu.Unlock() |
| 567 | }) |
| 568 | } |
| 569 | |
| 570 | // RegisterCommand registers the command cmd. |
| 571 | // cmd.Name must be unique and conform to the |
| 572 | // following format: |
| 573 | // |
| 574 | // - lowercase |
| 575 | // - ASCII lowercase letters, digits and hyphens only |
| 576 | // - cannot start or end with a hyphen |
| 577 | // - hyphen cannot be adjacent to another hyphen |
| 578 | // |
| 579 | // This function panics if the name is already registered, |
| 580 | // if the name does not meet the described format, or if |
| 581 | // any of the fields are missing from cmd. |
| 582 | // |
| 583 | // This function should be used in init(). |
| 584 | func RegisterCommand(cmd Command) { |
| 585 | commandsMu.Lock() |
| 586 | defer commandsMu.Unlock() |
| 587 | |
| 588 | if cmd.Name == "" { |
| 589 | panic("command name is required") |
| 590 | } |
| 591 | if cmd.Func == nil && cmd.CobraFunc == nil { |
| 592 | panic("command function missing") |
| 593 | } |
| 594 | if cmd.Short == "" { |
| 595 | panic("command short string is required") |
| 596 | } |
| 597 | if _, exists := commands[cmd.Name]; exists { |
| 598 | panic("command already registered: " + cmd.Name) |
| 599 | } |
| 600 | if !commandNameRegex.MatchString(cmd.Name) { |
| 601 | panic("invalid command name") |
| 602 | } |
| 603 | defaultFactory.Use(func(rootCmd *cobra.Command) { |
| 604 | rootCmd.AddCommand(caddyCmdToCobra(cmd)) |
| 605 | }) |
| 606 | commands[cmd.Name] = cmd |
| 607 | } |
| 608 | |
| 609 | var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`) |