mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	cmd: Refactor subcommands, add help, make them pluggable
* cli: Change command structure, add help subcommand (#328) * cli: improve subcommand structure - make help command as normal subcommand - add flag usage message for each command * cmd: Refactor subcommands and command line help; make commands pluggable
This commit is contained in:
		
							parent
							
								
									c95db3551d
								
							
						
					
					
						commit
						0006df6026
					
				
							
								
								
									
										391
									
								
								cmd/commandfuncs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										391
									
								
								cmd/commandfuncs.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,391 @@
 | 
			
		||||
// Copyright 2015 Matthew Holt and The Caddy Authors
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package caddycmd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/caddyserver/caddy/v2"
 | 
			
		||||
	"github.com/caddyserver/caddy/v2/caddyconfig"
 | 
			
		||||
	"github.com/keybase/go-ps"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func cmdStart(fl Flags) (int, error) {
 | 
			
		||||
	startCmdConfigFlag := fl.String("config")
 | 
			
		||||
	startCmdConfigAdapterFlag := fl.String("config-adapter")
 | 
			
		||||
 | 
			
		||||
	// open a listener to which the child process will connect when
 | 
			
		||||
	// it is ready to confirm that it has successfully started
 | 
			
		||||
	ln, err := net.Listen("tcp", "127.0.0.1:0")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("opening listener for success confirmation: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer ln.Close()
 | 
			
		||||
 | 
			
		||||
	// craft the command with a pingback address and with a
 | 
			
		||||
	// pipe for its stdin, so we can tell it our confirmation
 | 
			
		||||
	// code that we expect so that some random port scan at
 | 
			
		||||
	// the most unfortunate time won't fool us into thinking
 | 
			
		||||
	// the child succeeded (i.e. the alternative is to just
 | 
			
		||||
	// wait for any connection on our listener, but better to
 | 
			
		||||
	// ensure it's the process we're expecting - we can be
 | 
			
		||||
	// sure by giving it some random bytes and having it echo
 | 
			
		||||
	// them back to us)
 | 
			
		||||
	cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
 | 
			
		||||
	if startCmdConfigFlag != "" {
 | 
			
		||||
		cmd.Args = append(cmd.Args, "--config", startCmdConfigFlag)
 | 
			
		||||
	}
 | 
			
		||||
	if startCmdConfigAdapterFlag != "" {
 | 
			
		||||
		cmd.Args = append(cmd.Args, "--config-adapter", startCmdConfigAdapterFlag)
 | 
			
		||||
	}
 | 
			
		||||
	stdinpipe, err := cmd.StdinPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("creating stdin pipe: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Stdout = os.Stdout
 | 
			
		||||
	cmd.Stderr = os.Stderr
 | 
			
		||||
 | 
			
		||||
	// generate the random bytes we'll send to the child process
 | 
			
		||||
	expect := make([]byte, 32)
 | 
			
		||||
	_, err = rand.Read(expect)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("generating random confirmation bytes: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// begin writing the confirmation bytes to the child's
 | 
			
		||||
	// stdin; use a goroutine since the child hasn't been
 | 
			
		||||
	// started yet, and writing sychronously would result
 | 
			
		||||
	// in a deadlock
 | 
			
		||||
	go func() {
 | 
			
		||||
		stdinpipe.Write(expect)
 | 
			
		||||
		stdinpipe.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// start the process
 | 
			
		||||
	err = cmd.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy process: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// there are two ways we know we're done: either
 | 
			
		||||
	// the process will connect to our listener, or
 | 
			
		||||
	// it will exit with an error
 | 
			
		||||
	success, exit := make(chan struct{}), make(chan error)
 | 
			
		||||
 | 
			
		||||
	// in one goroutine, we await the success of the child process
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			conn, err := ln.Accept()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if !strings.Contains(err.Error(), "use of closed network connection") {
 | 
			
		||||
					log.Println(err)
 | 
			
		||||
				}
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			err = handlePingbackConn(conn, expect)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				close(success)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// in another goroutine, we await the failure of the child process
 | 
			
		||||
	go func() {
 | 
			
		||||
		err := cmd.Wait() // don't send on this line! Wait blocks, but send starts before it unblocks
 | 
			
		||||
		exit <- err       // sending on separate line ensures select won't trigger until after Wait unblocks
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// when one of the goroutines unblocks, we're done and can exit
 | 
			
		||||
	select {
 | 
			
		||||
	case <-success:
 | 
			
		||||
		fmt.Printf("Successfully started Caddy (pid=%d)\n", cmd.Process.Pid)
 | 
			
		||||
	case err := <-exit:
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("caddy process exited with error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdRun(fl Flags) (int, error) {
 | 
			
		||||
	runCmdConfigFlag := fl.String("config")
 | 
			
		||||
	runCmdConfigAdapterFlag := fl.String("config-adapter")
 | 
			
		||||
	runCmdPrintEnvFlag := fl.Bool("print-env")
 | 
			
		||||
	runCmdPingbackFlag := fl.String("pingback")
 | 
			
		||||
 | 
			
		||||
	// if we are supposed to print the environment, do that first
 | 
			
		||||
	if runCmdPrintEnvFlag {
 | 
			
		||||
		printEnvironment()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the config in caddy's native format
 | 
			
		||||
	config, err := loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// set a fitting User-Agent for ACME requests
 | 
			
		||||
	goModule := caddy.GoModule()
 | 
			
		||||
	cleanModVersion := strings.TrimPrefix(goModule.Version, "v")
 | 
			
		||||
	certmagic.UserAgent = "Caddy/" + cleanModVersion
 | 
			
		||||
 | 
			
		||||
	// start the admin endpoint along with any initial config
 | 
			
		||||
	err = caddy.StartAdmin(config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("starting caddy administration endpoint: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer caddy.StopAdmin()
 | 
			
		||||
 | 
			
		||||
	// if we are to report to another process the successful start
 | 
			
		||||
	// of the server, do so now by echoing back contents of stdin
 | 
			
		||||
	if runCmdPingbackFlag != "" {
 | 
			
		||||
		confirmationBytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("reading confirmation bytes from stdin: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		conn, err := net.Dial("tcp", runCmdPingbackFlag)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("dialing confirmation address: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		defer conn.Close()
 | 
			
		||||
		_, err = conn.Write(confirmationBytes)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("writing confirmation bytes to %s: %v", runCmdPingbackFlag, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	select {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdStop(_ Flags) (int, error) {
 | 
			
		||||
	processList, err := ps.Processes()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("listing processes: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	thisProcName := getProcessName()
 | 
			
		||||
	var found bool
 | 
			
		||||
	for _, p := range processList {
 | 
			
		||||
		// the process we're looking for should have the same name but different PID
 | 
			
		||||
		if p.Executable() == thisProcName && p.Pid() != os.Getpid() {
 | 
			
		||||
			found = true
 | 
			
		||||
			fmt.Printf("pid=%d\n", p.Pid())
 | 
			
		||||
 | 
			
		||||
			if err := gracefullyStopProcess(p.Pid()); err != nil {
 | 
			
		||||
				return caddy.ExitCodeFailedStartup, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("Caddy is not running")
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(" success")
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdReload(fl Flags) (int, error) {
 | 
			
		||||
	reloadCmdConfigFlag := fl.String("config")
 | 
			
		||||
	reloadCmdConfigAdapterFlag := fl.String("config-adapter")
 | 
			
		||||
	reloadCmdAddrFlag := fl.String("address")
 | 
			
		||||
 | 
			
		||||
	// a configuration is required
 | 
			
		||||
	if reloadCmdConfigFlag == "" {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("no configuration to load (use --config)")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the config in caddy's native format
 | 
			
		||||
	config, err := loadConfig(reloadCmdConfigFlag, reloadCmdConfigAdapterFlag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the address of the admin listener and craft endpoint URL
 | 
			
		||||
	adminAddr := reloadCmdAddrFlag
 | 
			
		||||
	if adminAddr == "" {
 | 
			
		||||
		var tmpStruct struct {
 | 
			
		||||
			Admin caddy.AdminConfig `json:"admin"`
 | 
			
		||||
		}
 | 
			
		||||
		err = json.Unmarshal(config, &tmpStruct)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("unmarshaling admin listener address from config: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		adminAddr = tmpStruct.Admin.Listen
 | 
			
		||||
	}
 | 
			
		||||
	if adminAddr == "" {
 | 
			
		||||
		adminAddr = caddy.DefaultAdminListen
 | 
			
		||||
	}
 | 
			
		||||
	adminEndpoint := fmt.Sprintf("http://%s/load", adminAddr)
 | 
			
		||||
 | 
			
		||||
	// send the configuration to the instance
 | 
			
		||||
	resp, err := http.Post(adminEndpoint, "application/json", bytes.NewReader(config))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("sending configuration to instance: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	// if it didn't work, let the user know
 | 
			
		||||
	if resp.StatusCode >= 400 {
 | 
			
		||||
		respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*10))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
 | 
			
		||||
		}
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdVersion(_ Flags) (int, error) {
 | 
			
		||||
	goModule := caddy.GoModule()
 | 
			
		||||
	if goModule.Sum != "" {
 | 
			
		||||
		// a build with a known version will also have a checksum
 | 
			
		||||
		fmt.Printf("%s %s\n", goModule.Version, goModule.Sum)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println(goModule.Version)
 | 
			
		||||
	}
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdListModules(_ Flags) (int, error) {
 | 
			
		||||
	for _, m := range caddy.Modules() {
 | 
			
		||||
		fmt.Println(m)
 | 
			
		||||
	}
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdEnviron(_ Flags) (int, error) {
 | 
			
		||||
	printEnvironment()
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdAdaptConfig(fl Flags) (int, error) {
 | 
			
		||||
	adaptCmdAdapterFlag := fl.String("adapter")
 | 
			
		||||
	adaptCmdInputFlag := fl.String("input")
 | 
			
		||||
	adaptCmdPrettyFlag := fl.Bool("pretty")
 | 
			
		||||
 | 
			
		||||
	if adaptCmdAdapterFlag == "" || adaptCmdInputFlag == "" {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("usage: caddy adapt-config --adapter <name> --input <file>")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfgAdapter := caddyconfig.GetAdapter(adaptCmdAdapterFlag)
 | 
			
		||||
	if cfgAdapter == nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("unrecognized config adapter: %s", adaptCmdAdapterFlag)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	input, err := ioutil.ReadFile(adaptCmdInputFlag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("reading input file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opts := make(map[string]interface{})
 | 
			
		||||
	if adaptCmdPrettyFlag {
 | 
			
		||||
		opts["pretty"] = "true"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// print warnings to stderr
 | 
			
		||||
	for _, warn := range warnings {
 | 
			
		||||
		msg := warn.Message
 | 
			
		||||
		if warn.Directive != "" {
 | 
			
		||||
			msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
 | 
			
		||||
		}
 | 
			
		||||
		log.Printf("[WARNING][%s] %s:%d: %s", adaptCmdAdapterFlag, warn.File, warn.Line, msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// print result to stdout
 | 
			
		||||
	fmt.Println(string(adaptedConfig))
 | 
			
		||||
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdHelp(fl Flags) (int, error) {
 | 
			
		||||
	const fullDocs = `Full documentation is available at:
 | 
			
		||||
https://github.com/caddyserver/caddy/wiki/v2:-Documentation`
 | 
			
		||||
 | 
			
		||||
	args := fl.Args()
 | 
			
		||||
	if len(args) == 0 {
 | 
			
		||||
		s := `Caddy is an extensible server platform.
 | 
			
		||||
 | 
			
		||||
usage:
 | 
			
		||||
  caddy <command> [<args...>]
 | 
			
		||||
 | 
			
		||||
commands:
 | 
			
		||||
`
 | 
			
		||||
		for _, cmd := range commands {
 | 
			
		||||
			s += fmt.Sprintf("  %-15s %s\n", cmd.Name, cmd.Short)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s += "\nUse 'caddy help <command>' for more information about a command.\n"
 | 
			
		||||
		s += "\n" + fullDocs + "\n"
 | 
			
		||||
 | 
			
		||||
		fmt.Print(s)
 | 
			
		||||
 | 
			
		||||
		return caddy.ExitCodeSuccess, nil
 | 
			
		||||
	} else if len(args) > 1 {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("can only give help with one command")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subcommand, ok := commands[args[0]]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("unknown command: %s", args[0])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result := fmt.Sprintf("%s\n\nusage:\n  caddy %s %s\n",
 | 
			
		||||
		strings.TrimSpace(subcommand.Long),
 | 
			
		||||
		subcommand.Name,
 | 
			
		||||
		strings.TrimSpace(subcommand.Usage),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if help := flagHelp(subcommand.Flags); help != "" {
 | 
			
		||||
		result += fmt.Sprintf("\nflags:\n%s", help)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result += "\n" + fullDocs + "\n"
 | 
			
		||||
 | 
			
		||||
	fmt.Print(result)
 | 
			
		||||
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										509
									
								
								cmd/commands.go
									
									
									
									
									
								
							
							
						
						
									
										509
									
								
								cmd/commands.go
									
									
									
									
									
								
							@ -15,343 +15,204 @@
 | 
			
		||||
package caddycmd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/caddyserver/caddy/v2"
 | 
			
		||||
	"github.com/caddyserver/caddy/v2/caddyconfig"
 | 
			
		||||
	"github.com/keybase/go-ps"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
	"regexp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func cmdStart() (int, error) {
 | 
			
		||||
	startCmd := flag.NewFlagSet("start", flag.ExitOnError)
 | 
			
		||||
	startCmdConfigFlag := startCmd.String("config", "", "Configuration file")
 | 
			
		||||
	startCmdConfigAdapterFlag := startCmd.String("config-adapter", "", "Name of config adapter to apply")
 | 
			
		||||
	startCmd.Parse(os.Args[2:])
 | 
			
		||||
// Command represents a subcommand. All fields
 | 
			
		||||
// are required to be set except for Flags if
 | 
			
		||||
// there are no flags and Usage if there are
 | 
			
		||||
// no flags or arguments.
 | 
			
		||||
type Command struct {
 | 
			
		||||
	Name string
 | 
			
		||||
 | 
			
		||||
	// open a listener to which the child process will connect when
 | 
			
		||||
	// it is ready to confirm that it has successfully started
 | 
			
		||||
	ln, err := net.Listen("tcp", "127.0.0.1:0")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("opening listener for success confirmation: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer ln.Close()
 | 
			
		||||
	// Run is a function that executes a subcommand.
 | 
			
		||||
	// It returns an exit code and any associated error.
 | 
			
		||||
	// Takes non-flag commandline arguments as args.
 | 
			
		||||
	// Flag must be parsed before Run is executed.
 | 
			
		||||
	Func CommandFunc
 | 
			
		||||
 | 
			
		||||
	// craft the command with a pingback address and with a
 | 
			
		||||
	// pipe for its stdin, so we can tell it our confirmation
 | 
			
		||||
	// code that we expect so that some random port scan at
 | 
			
		||||
	// the most unfortunate time won't fool us into thinking
 | 
			
		||||
	// the child succeeded (i.e. the alternative is to just
 | 
			
		||||
	// wait for any connection on our listener, but better to
 | 
			
		||||
	// ensure it's the process we're expecting - we can be
 | 
			
		||||
	// sure by giving it some random bytes and having it echo
 | 
			
		||||
	// them back to us)
 | 
			
		||||
	cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
 | 
			
		||||
	if *startCmdConfigFlag != "" {
 | 
			
		||||
		cmd.Args = append(cmd.Args, "--config", *startCmdConfigFlag)
 | 
			
		||||
	}
 | 
			
		||||
	if *startCmdConfigAdapterFlag != "" {
 | 
			
		||||
		cmd.Args = append(cmd.Args, "--config-adapter", *startCmdConfigAdapterFlag)
 | 
			
		||||
	}
 | 
			
		||||
	stdinpipe, err := cmd.StdinPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("creating stdin pipe: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Stdout = os.Stdout
 | 
			
		||||
	cmd.Stderr = os.Stderr
 | 
			
		||||
	// Usage is the one-line message explaining args, flags.
 | 
			
		||||
	Usage string
 | 
			
		||||
 | 
			
		||||
	// generate the random bytes we'll send to the child process
 | 
			
		||||
	expect := make([]byte, 32)
 | 
			
		||||
	_, err = rand.Read(expect)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("generating random confirmation bytes: %v", err)
 | 
			
		||||
	// Short is the short description for command.
 | 
			
		||||
	Short string
 | 
			
		||||
 | 
			
		||||
	// Long is the message for 'caddy help <command>'
 | 
			
		||||
	Long string
 | 
			
		||||
 | 
			
		||||
	// Flags is flagset for command.
 | 
			
		||||
	Flags *flag.FlagSet
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// begin writing the confirmation bytes to the child's
 | 
			
		||||
	// stdin; use a goroutine since the child hasn't been
 | 
			
		||||
	// started yet, and writing sychronously would result
 | 
			
		||||
	// in a deadlock
 | 
			
		||||
	go func() {
 | 
			
		||||
		stdinpipe.Write(expect)
 | 
			
		||||
		stdinpipe.Close()
 | 
			
		||||
	}()
 | 
			
		||||
// CommandFunc is a command's function. It runs the
 | 
			
		||||
// command and returns the proper exit code along with
 | 
			
		||||
// any error that occurred.
 | 
			
		||||
type CommandFunc func(Flags) (int, error)
 | 
			
		||||
 | 
			
		||||
	// start the process
 | 
			
		||||
	err = cmd.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy process: %v", err)
 | 
			
		||||
var commands = map[string]Command{
 | 
			
		||||
	"start": {
 | 
			
		||||
		Name:  "start",
 | 
			
		||||
		Func:  cmdStart,
 | 
			
		||||
		Usage: "[--config <path>] [--config-adapter <name>]",
 | 
			
		||||
		Short: "Starts the Caddy process and returns after server has started.",
 | 
			
		||||
		Long: `
 | 
			
		||||
Starts the Caddy process, optionally bootstrapped with an initial
 | 
			
		||||
config file. Blocks until server is successfully running (or fails to run),
 | 
			
		||||
then returns. On Windows, the child process will remain attached to the
 | 
			
		||||
terminal, so closing the window will forcefully stop Caddy. See run for more
 | 
			
		||||
details.`,
 | 
			
		||||
		Flags: func() *flag.FlagSet {
 | 
			
		||||
			fs := flag.NewFlagSet("start", flag.ExitOnError)
 | 
			
		||||
			fs.String("config", "", "Configuration file")
 | 
			
		||||
			fs.String("config-adapter", "", "Name of config adapter to apply")
 | 
			
		||||
			return fs
 | 
			
		||||
		}(),
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"run": {
 | 
			
		||||
		Name:  "run",
 | 
			
		||||
		Func:  cmdRun,
 | 
			
		||||
		Usage: "[--config <path>] [--config-adapter <name>] [--print-env]",
 | 
			
		||||
		Short: `Starts the Caddy process and blocks indefinitely.`,
 | 
			
		||||
		Long: `
 | 
			
		||||
Same as start, but blocks indefinitely; i.e. runs Caddy in "daemon" mode. On
 | 
			
		||||
Windows, this is recommended over caddy start when running Caddy manually since
 | 
			
		||||
it will be more obvious that Caddy is still running and bound to the terminal
 | 
			
		||||
window.
 | 
			
		||||
 | 
			
		||||
If a config file is specified, it will be applied immediately after the process
 | 
			
		||||
is running. If the config file is not in Caddy's native JSON format, you can
 | 
			
		||||
specify an adapter with --config-adapter to adapt the given config file to
 | 
			
		||||
Caddy's native format. The config adapter must be a registered module. Any
 | 
			
		||||
warnings will be printed to the log, but beware that any adaptation without
 | 
			
		||||
errors will immediately be used. If you want to review the results of the
 | 
			
		||||
adaptation first, use adapt-config.
 | 
			
		||||
 | 
			
		||||
As a special case, if the current working directory has a file called
 | 
			
		||||
"Caddyfile" and the caddyfile config adapter is plugged in (default), then that
 | 
			
		||||
file will be loaded and used to configure Caddy, even without any command line
 | 
			
		||||
flags.
 | 
			
		||||
 | 
			
		||||
If --print-env is specified, the environment as seen by the Caddy process will
 | 
			
		||||
be printed before starting. This is the same as the environ command but does
 | 
			
		||||
not quit after printing.`,
 | 
			
		||||
		Flags: func() *flag.FlagSet {
 | 
			
		||||
			fs := flag.NewFlagSet("run", flag.ExitOnError)
 | 
			
		||||
			fs.String("config", "", "Configuration file")
 | 
			
		||||
			fs.String("config-adapter", "", "Name of config adapter to apply")
 | 
			
		||||
			fs.Bool("print-env", false, "Print environment")
 | 
			
		||||
			fs.String("pingback", "", "Echo confirmation bytes to this address on success")
 | 
			
		||||
			return fs
 | 
			
		||||
		}(),
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"stop": {
 | 
			
		||||
		Name:  "stop",
 | 
			
		||||
		Func:  cmdStop,
 | 
			
		||||
		Short: "Gracefully stops the running Caddy process",
 | 
			
		||||
		Long: `Gracefully stops the running Caddy process. (Note: this will stop any process
 | 
			
		||||
named the same as the executable.) On Windows, this stop is forceful and Caddy
 | 
			
		||||
will not have an opportunity to clean up any active locks; for a graceful
 | 
			
		||||
shutdown on Windows, use Ctrl+C or the /stop endpoint.`,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"reload": {
 | 
			
		||||
		Name:  "reload",
 | 
			
		||||
		Func:  cmdReload,
 | 
			
		||||
		Usage: "--config <path> [--config-adapter <name>] [--address <interface>]",
 | 
			
		||||
		Short: "Gives the running Caddy instance a new configuration",
 | 
			
		||||
		Long: `Gives the running Caddy instance a new configuration. This has the same effect
 | 
			
		||||
as POSTing a document to the /load endpoint, but is convenient for simple
 | 
			
		||||
workflows revolving around config files. Since the admin endpoint is
 | 
			
		||||
configurable, the endpoint configuration is loaded from the --address flag if
 | 
			
		||||
specified; otherwise it is loaded from the given config file; otherwise the
 | 
			
		||||
default is assumed.`,
 | 
			
		||||
		Flags: func() *flag.FlagSet {
 | 
			
		||||
			fs := flag.NewFlagSet("load", flag.ExitOnError)
 | 
			
		||||
			fs.String("config", "", "Configuration file")
 | 
			
		||||
			fs.String("config-adapter", "", "Name of config adapter to apply")
 | 
			
		||||
			fs.String("address", "", "Address of the administration listener, if different from config")
 | 
			
		||||
			return fs
 | 
			
		||||
		}(),
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"version": {
 | 
			
		||||
		Name:  "version",
 | 
			
		||||
		Func:  cmdVersion,
 | 
			
		||||
		Short: "Prints the version.",
 | 
			
		||||
		Long:  `Prints the version.`,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"list-modules": {
 | 
			
		||||
		Name:  "list-modules",
 | 
			
		||||
		Func:  cmdListModules,
 | 
			
		||||
		Short: "List installed Caddy modules.",
 | 
			
		||||
		Long:  `List installed Caddy modules.`,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"environ": {
 | 
			
		||||
		Name:  "environ",
 | 
			
		||||
		Func:  cmdEnviron,
 | 
			
		||||
		Short: "Prints the environment as seen by Caddy.",
 | 
			
		||||
		Long:  `Prints the environment as seen by Caddy.`,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"adapt-config": {
 | 
			
		||||
		Name:  "adapt-config",
 | 
			
		||||
		Func:  cmdAdaptConfig,
 | 
			
		||||
		Usage: "--input <path> --adapter <name> [--pretty]",
 | 
			
		||||
		Short: "Adapts a configuration to Caddy's native JSON config structure",
 | 
			
		||||
		Long: `
 | 
			
		||||
Adapts a configuration to Caddy's native JSON config structure and writes the
 | 
			
		||||
output to stdout, along with any warnings to stderr. If --pretty is specified,
 | 
			
		||||
the output will be formatted with indentation for human readability.`,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// there are two ways we know we're done: either
 | 
			
		||||
	// the process will connect to our listener, or
 | 
			
		||||
	// it will exit with an error
 | 
			
		||||
	success, exit := make(chan struct{}), make(chan error)
 | 
			
		||||
 | 
			
		||||
	// in one goroutine, we await the success of the child process
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			conn, err := ln.Accept()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if !strings.Contains(err.Error(), "use of closed network connection") {
 | 
			
		||||
					log.Println(err)
 | 
			
		||||
				}
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			err = handlePingbackConn(conn, expect)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				close(success)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// in another goroutine, we await the failure of the child process
 | 
			
		||||
	go func() {
 | 
			
		||||
		err := cmd.Wait() // don't send on this line! Wait blocks, but send starts before it unblocks
 | 
			
		||||
		exit <- err       // sending on separate line ensures select won't trigger until after Wait unblocks
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// when one of the goroutines unblocks, we're done and can exit
 | 
			
		||||
	select {
 | 
			
		||||
	case <-success:
 | 
			
		||||
		fmt.Printf("Successfully started Caddy (pid=%d)\n", cmd.Process.Pid)
 | 
			
		||||
	case err := <-exit:
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("caddy process exited with error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdRun() (int, error) {
 | 
			
		||||
	runCmd := flag.NewFlagSet("run", flag.ExitOnError)
 | 
			
		||||
	runCmdConfigFlag := runCmd.String("config", "", "Configuration file")
 | 
			
		||||
	runCmdConfigAdapterFlag := runCmd.String("config-adapter", "", "Name of config adapter to apply")
 | 
			
		||||
	runCmdPrintEnvFlag := runCmd.Bool("print-env", false, "Print environment")
 | 
			
		||||
	runCmdPingbackFlag := runCmd.String("pingback", "", "Echo confirmation bytes to this address on success")
 | 
			
		||||
	runCmd.Parse(os.Args[2:])
 | 
			
		||||
 | 
			
		||||
	// if we are supposed to print the environment, do that first
 | 
			
		||||
	if *runCmdPrintEnvFlag {
 | 
			
		||||
		exitCode, err := cmdEnviron()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return exitCode, err
 | 
			
		||||
func init() {
 | 
			
		||||
	// the help command is special in that its func
 | 
			
		||||
	// refers to the commands map; thus, defining it
 | 
			
		||||
	// inline with the commands map's initialization
 | 
			
		||||
	// yields a compile-time error, so we have to
 | 
			
		||||
	// define this command separately
 | 
			
		||||
	commands["help"] = Command{
 | 
			
		||||
		Name:  "help",
 | 
			
		||||
		Func:  cmdHelp,
 | 
			
		||||
		Usage: "<command>",
 | 
			
		||||
		Short: "Shows help for a Caddy subcommand.",
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// get the config in caddy's native format
 | 
			
		||||
	config, err := loadConfig(*runCmdConfigFlag, *runCmdConfigAdapterFlag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, err
 | 
			
		||||
// RegisterCommand registers the command cmd.
 | 
			
		||||
// cmd.Name must be unique and conform to the
 | 
			
		||||
// following format:
 | 
			
		||||
//
 | 
			
		||||
//    - lowercase
 | 
			
		||||
//    - alphanumeric and hyphen characters only
 | 
			
		||||
//    - cannot start or end with a hyphen
 | 
			
		||||
//    - hyphen cannot be adjacent to another hyphen
 | 
			
		||||
//
 | 
			
		||||
// This function panics if the name is already registered,
 | 
			
		||||
// if the name does not meet the described format, or if
 | 
			
		||||
// any of the fields are missing from cmd.
 | 
			
		||||
func RegisterCommand(cmd Command) {
 | 
			
		||||
	if cmd.Name == "" {
 | 
			
		||||
		panic("command name is required")
 | 
			
		||||
	}
 | 
			
		||||
	if cmd.Func == nil {
 | 
			
		||||
		panic("command function missing")
 | 
			
		||||
	}
 | 
			
		||||
	if cmd.Short == "" {
 | 
			
		||||
		panic("command short string is required")
 | 
			
		||||
	}
 | 
			
		||||
	if _, exists := commands[cmd.Name]; exists {
 | 
			
		||||
		panic("command already registered: " + cmd.Name)
 | 
			
		||||
	}
 | 
			
		||||
	if !commandNameRegex.MatchString(cmd.Name) {
 | 
			
		||||
		panic("invalid command name")
 | 
			
		||||
	}
 | 
			
		||||
	commands[cmd.Name] = cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// set a fitting User-Agent for ACME requests
 | 
			
		||||
	goModule := caddy.GoModule()
 | 
			
		||||
	cleanModVersion := strings.TrimPrefix(goModule.Version, "v")
 | 
			
		||||
	certmagic.UserAgent = "Caddy/" + cleanModVersion
 | 
			
		||||
 | 
			
		||||
	// start the admin endpoint along with any initial config
 | 
			
		||||
	err = caddy.StartAdmin(config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("starting caddy administration endpoint: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer caddy.StopAdmin()
 | 
			
		||||
 | 
			
		||||
	// if we are to report to another process the successful start
 | 
			
		||||
	// of the server, do so now by echoing back contents of stdin
 | 
			
		||||
	if *runCmdPingbackFlag != "" {
 | 
			
		||||
		confirmationBytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("reading confirmation bytes from stdin: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		conn, err := net.Dial("tcp", *runCmdPingbackFlag)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("dialing confirmation address: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		defer conn.Close()
 | 
			
		||||
		_, err = conn.Write(confirmationBytes)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("writing confirmation bytes to %s: %v", *runCmdPingbackFlag, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	select {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdStop() (int, error) {
 | 
			
		||||
	processList, err := ps.Processes()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("listing processes: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	thisProcName := getProcessName()
 | 
			
		||||
	var found bool
 | 
			
		||||
	for _, p := range processList {
 | 
			
		||||
		// the process we're looking for should have the same name but different PID
 | 
			
		||||
		if p.Executable() == thisProcName && p.Pid() != os.Getpid() {
 | 
			
		||||
			found = true
 | 
			
		||||
			fmt.Printf("pid=%d\n", p.Pid())
 | 
			
		||||
 | 
			
		||||
			if err := gracefullyStopProcess(p.Pid()); err != nil {
 | 
			
		||||
				return caddy.ExitCodeFailedStartup, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, fmt.Errorf("Caddy is not running")
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(" success")
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdReload() (int, error) {
 | 
			
		||||
	reloadCmd := flag.NewFlagSet("load", flag.ExitOnError)
 | 
			
		||||
	reloadCmdConfigFlag := reloadCmd.String("config", "", "Configuration file")
 | 
			
		||||
	reloadCmdConfigAdapterFlag := reloadCmd.String("config-adapter", "", "Name of config adapter to apply")
 | 
			
		||||
	reloadCmdAddrFlag := reloadCmd.String("address", "", "Address of the administration listener, if different from config")
 | 
			
		||||
	reloadCmd.Parse(os.Args[2:])
 | 
			
		||||
 | 
			
		||||
	// a configuration is required
 | 
			
		||||
	if *reloadCmdConfigFlag == "" {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("no configuration to load (use --config)")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the config in caddy's native format
 | 
			
		||||
	config, err := loadConfig(*reloadCmdConfigFlag, *reloadCmdConfigAdapterFlag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the address of the admin listener and craft endpoint URL
 | 
			
		||||
	adminAddr := *reloadCmdAddrFlag
 | 
			
		||||
	if adminAddr == "" {
 | 
			
		||||
		var tmpStruct struct {
 | 
			
		||||
			Admin caddy.AdminConfig `json:"admin"`
 | 
			
		||||
		}
 | 
			
		||||
		err = json.Unmarshal(config, &tmpStruct)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("unmarshaling admin listener address from config: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		adminAddr = tmpStruct.Admin.Listen
 | 
			
		||||
	}
 | 
			
		||||
	if adminAddr == "" {
 | 
			
		||||
		adminAddr = caddy.DefaultAdminListen
 | 
			
		||||
	}
 | 
			
		||||
	adminEndpoint := fmt.Sprintf("http://%s/load", adminAddr)
 | 
			
		||||
 | 
			
		||||
	// send the configuration to the instance
 | 
			
		||||
	resp, err := http.Post(adminEndpoint, "application/json", bytes.NewReader(config))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("sending configuration to instance: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	// if it didn't work, let the user know
 | 
			
		||||
	if resp.StatusCode >= 400 {
 | 
			
		||||
		respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*10))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return caddy.ExitCodeFailedStartup,
 | 
			
		||||
				fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
 | 
			
		||||
		}
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdVersion() (int, error) {
 | 
			
		||||
	goModule := caddy.GoModule()
 | 
			
		||||
	if goModule.Sum != "" {
 | 
			
		||||
		// a build with a known version will also have a checksum
 | 
			
		||||
		fmt.Printf("%s %s\n", goModule.Version, goModule.Sum)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println(goModule.Version)
 | 
			
		||||
	}
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdListModules() (int, error) {
 | 
			
		||||
	for _, m := range caddy.Modules() {
 | 
			
		||||
		fmt.Println(m)
 | 
			
		||||
	}
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdEnviron() (int, error) {
 | 
			
		||||
	for _, v := range os.Environ() {
 | 
			
		||||
		fmt.Println(v)
 | 
			
		||||
	}
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdAdaptConfig() (int, error) {
 | 
			
		||||
	adaptCmd := flag.NewFlagSet("adapt", flag.ExitOnError)
 | 
			
		||||
	adaptCmdAdapterFlag := adaptCmd.String("adapter", "", "Name of config adapter")
 | 
			
		||||
	adaptCmdInputFlag := adaptCmd.String("input", "", "Configuration file to adapt")
 | 
			
		||||
	adaptCmdPrettyFlag := adaptCmd.Bool("pretty", false, "Format the output for human readability")
 | 
			
		||||
	adaptCmd.Parse(os.Args[2:])
 | 
			
		||||
 | 
			
		||||
	if *adaptCmdAdapterFlag == "" || *adaptCmdInputFlag == "" {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("usage: caddy adapt-config --adapter <name> --input <file>")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfgAdapter := caddyconfig.GetAdapter(*adaptCmdAdapterFlag)
 | 
			
		||||
	if cfgAdapter == nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("unrecognized config adapter: %s", *adaptCmdAdapterFlag)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	input, err := ioutil.ReadFile(*adaptCmdInputFlag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup,
 | 
			
		||||
			fmt.Errorf("reading input file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opts := make(map[string]interface{})
 | 
			
		||||
	if *adaptCmdPrettyFlag {
 | 
			
		||||
		opts["pretty"] = "true"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return caddy.ExitCodeFailedStartup, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// print warnings to stderr
 | 
			
		||||
	for _, warn := range warnings {
 | 
			
		||||
		msg := warn.Message
 | 
			
		||||
		if warn.Directive != "" {
 | 
			
		||||
			msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
 | 
			
		||||
		}
 | 
			
		||||
		log.Printf("[WARNING][%s] %s:%d: %s", *adaptCmdAdapterFlag, warn.File, warn.Line, msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// print result to stdout
 | 
			
		||||
	fmt.Println(string(adaptedConfig))
 | 
			
		||||
 | 
			
		||||
	return caddy.ExitCodeSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										134
									
								
								cmd/main.go
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								cmd/main.go
									
									
									
									
									
								
							@ -23,6 +23,9 @@ import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/caddyserver/caddy/v2"
 | 
			
		||||
	"github.com/caddyserver/caddy/v2/caddyconfig"
 | 
			
		||||
@ -33,46 +36,44 @@ import (
 | 
			
		||||
func Main() {
 | 
			
		||||
	caddy.TrapSignals()
 | 
			
		||||
 | 
			
		||||
	if len(os.Args) < 2 {
 | 
			
		||||
		fmt.Println(usageString())
 | 
			
		||||
		return
 | 
			
		||||
	switch len(os.Args) {
 | 
			
		||||
	case 0:
 | 
			
		||||
		log.Printf("[FATAL] no arguments provided by OS; args[0] must be command")
 | 
			
		||||
		os.Exit(caddy.ExitCodeFailedStartup)
 | 
			
		||||
	case 1:
 | 
			
		||||
		os.Args = append(os.Args, "help")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subcommand, ok := commands[os.Args[1]]
 | 
			
		||||
	subcommandName := os.Args[1]
 | 
			
		||||
	subcommand, ok := commands[subcommandName]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		fmt.Printf("%q is not a valid command\n", os.Args[1])
 | 
			
		||||
		if strings.HasPrefix(os.Args[1], "-") {
 | 
			
		||||
			// user probably forgot to type the subcommand
 | 
			
		||||
			log.Println("[ERROR] first argument must be a subcommand; see 'caddy help'")
 | 
			
		||||
		} else {
 | 
			
		||||
			log.Printf("[ERROR] '%s' is not a recognized subcommand; see 'caddy help'", os.Args[1])
 | 
			
		||||
		}
 | 
			
		||||
		os.Exit(caddy.ExitCodeFailedStartup)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if exitCode, err := subcommand(); err != nil {
 | 
			
		||||
	fs := subcommand.Flags
 | 
			
		||||
	if fs == nil {
 | 
			
		||||
		fs = flag.NewFlagSet(subcommand.Name, flag.ExitOnError)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := fs.Parse(os.Args[2:])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		os.Exit(caddy.ExitCodeFailedStartup)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exitCode, err := subcommand.Func(Flags{fs})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("%s: %v", subcommand.Name, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	os.Exit(exitCode)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// commandFunc is a function that executes
 | 
			
		||||
// a subcommand. It returns an exit code and
 | 
			
		||||
// any associated error.
 | 
			
		||||
type commandFunc func() (int, error)
 | 
			
		||||
 | 
			
		||||
var commands = map[string]commandFunc{
 | 
			
		||||
	"start":        cmdStart,
 | 
			
		||||
	"run":          cmdRun,
 | 
			
		||||
	"stop":         cmdStop,
 | 
			
		||||
	"reload":       cmdReload,
 | 
			
		||||
	"version":      cmdVersion,
 | 
			
		||||
	"list-modules": cmdListModules,
 | 
			
		||||
	"environ":      cmdEnviron,
 | 
			
		||||
	"adapt-config": cmdAdaptConfig,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func usageString() string {
 | 
			
		||||
	buf := new(bytes.Buffer)
 | 
			
		||||
	buf.WriteString("usage: caddy <command> [<args>]")
 | 
			
		||||
	flag.CommandLine.SetOutput(buf)
 | 
			
		||||
	flag.CommandLine.PrintDefaults()
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handlePingbackConn reads from conn and ensures it matches
 | 
			
		||||
// the bytes in expect, or returns an error if it doesn't.
 | 
			
		||||
@ -156,3 +157,74 @@ func loadConfig(configFile, adapterName string) ([]byte, error) {
 | 
			
		||||
 | 
			
		||||
	return config, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Flags wraps a FlagSet so that typed values
 | 
			
		||||
// from flags can be easily retrieved.
 | 
			
		||||
type Flags struct {
 | 
			
		||||
	*flag.FlagSet
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String returns the string representation of the
 | 
			
		||||
// flag given by name. It panics if the flag is not
 | 
			
		||||
// in the flag set.
 | 
			
		||||
func (f Flags) String(name string) string {
 | 
			
		||||
	return f.FlagSet.Lookup(name).Value.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Bool returns the boolean representation of the
 | 
			
		||||
// flag given by name. It returns false if the flag
 | 
			
		||||
// is not a boolean type. It panics if the flag is
 | 
			
		||||
// not in the flag set.
 | 
			
		||||
func (f Flags) Bool(name string) bool {
 | 
			
		||||
	val, _ := strconv.ParseBool(f.String(name))
 | 
			
		||||
	return val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Int returns the integer representation of the
 | 
			
		||||
// flag given by name. It returns 0 if the flag
 | 
			
		||||
// is not an integer type. It panics if the flag is
 | 
			
		||||
// not in the flag set.
 | 
			
		||||
func (f Flags) Int(name string) int {
 | 
			
		||||
	val, _ := strconv.ParseInt(f.String(name), 0, strconv.IntSize)
 | 
			
		||||
	return int(val)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Float64 returns the float64 representation of the
 | 
			
		||||
// flag given by name. It returns false if the flag
 | 
			
		||||
// is not a float63 type. It panics if the flag is
 | 
			
		||||
// not in the flag set.
 | 
			
		||||
func (f Flags) Float64(name string) float64 {
 | 
			
		||||
	val, _ := strconv.ParseFloat(f.String(name), 64)
 | 
			
		||||
	return val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Duration returns the duration representation of the
 | 
			
		||||
// flag given by name. It returns false if the flag
 | 
			
		||||
// is not a duration type. It panics if the flag is
 | 
			
		||||
// not in the flag set.
 | 
			
		||||
func (f Flags) Duration(name string) time.Duration {
 | 
			
		||||
	val, _ := time.ParseDuration(f.String(name))
 | 
			
		||||
	return val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// flagHelp returns the help text for fs.
 | 
			
		||||
func flagHelp(fs *flag.FlagSet) string {
 | 
			
		||||
	if fs == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// temporarily redirect output
 | 
			
		||||
	out := fs.Output()
 | 
			
		||||
	defer fs.SetOutput(out)
 | 
			
		||||
 | 
			
		||||
	buf := new(bytes.Buffer)
 | 
			
		||||
	fs.SetOutput(buf)
 | 
			
		||||
	fs.PrintDefaults()
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printEnvironment() {
 | 
			
		||||
	for _, v := range os.Environ() {
 | 
			
		||||
		fmt.Println(v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user