cmd: Add pidfile support (closes #3235)

This commit is contained in:
Matthew Holt 2020-05-13 11:28:15 -06:00
parent cee5589b98
commit 4df56c77e3
No known key found for this signature in database
GPG Key ID: 2A349DD577D586A5
4 changed files with 38 additions and 9 deletions

View File

@ -21,6 +21,7 @@ import (
"expvar" "expvar"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
@ -546,13 +547,6 @@ func handleUnload(w http.ResponseWriter, r *http.Request) error {
Err: fmt.Errorf("method not allowed"), Err: fmt.Errorf("method not allowed"),
} }
} }
currentCfgMu.RLock()
hasCfg := currentCfg != nil
currentCfgMu.RUnlock()
if !hasCfg {
Log().Named("admin.api").Info("nothing to unload")
return nil
}
Log().Named("admin.api").Info("unloading") Log().Named("admin.api").Info("unloading")
if err := stopAndCleanup(); err != nil { if err := stopAndCleanup(); err != nil {
Log().Named("admin.api").Error("error unloading", zap.Error(err)) Log().Named("admin.api").Error("error unloading", zap.Error(err))
@ -801,12 +795,27 @@ var (
} }
) )
// PIDFile writes a pidfile to the file at filename. It
// will get deleted before the process gracefully exits.
func PIDFile(filename string) error {
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
err := ioutil.WriteFile(filename, pid, 0644)
if err != nil {
return err
}
pidfile = filename
return nil
}
// idRegexp is used to match ID fields and their associated values // idRegexp is used to match ID fields and their associated values
// in the config. It also matches adjacent commas so that syntax // in the config. It also matches adjacent commas so that syntax
// can be preserved no matter where in the object the field appears. // can be preserved no matter where in the object the field appears.
// It supports string and most numeric values. // It supports string and most numeric values.
var idRegexp = regexp.MustCompile(`(?m),?\s*"` + idKey + `"\s*:\s*(-?[0-9]+(\.[0-9]+)?|(?U)".*")\s*,?`) var idRegexp = regexp.MustCompile(`(?m),?\s*"` + idKey + `"\s*:\s*(-?[0-9]+(\.[0-9]+)?|(?U)".*")\s*,?`)
// pidfile is the name of the pidfile, if any.
var pidfile string
const ( const (
rawConfigKey = "config" rawConfigKey = "config"
idKey = "@id" idKey = "@id"

View File

@ -470,6 +470,9 @@ func stopAndCleanup() error {
return err return err
} }
certmagic.CleanUpOwnLocks() certmagic.CleanUpOwnLocks()
if pidfile != "" {
os.Remove(pidfile)
}
return nil return nil
} }

View File

@ -41,6 +41,7 @@ import (
func cmdStart(fl Flags) (int, error) { func cmdStart(fl Flags) (int, error) {
startCmdConfigFlag := fl.String("config") startCmdConfigFlag := fl.String("config")
startCmdConfigAdapterFlag := fl.String("adapter") startCmdConfigAdapterFlag := fl.String("adapter")
startCmdPidfileFlag := fl.String("pidfile")
startCmdWatchFlag := fl.Bool("watch") startCmdWatchFlag := fl.Bool("watch")
// open a listener to which the child process will connect when // open a listener to which the child process will connect when
@ -71,6 +72,9 @@ func cmdStart(fl Flags) (int, error) {
if startCmdWatchFlag { if startCmdWatchFlag {
cmd.Args = append(cmd.Args, "--watch") cmd.Args = append(cmd.Args, "--watch")
} }
if startCmdPidfileFlag != "" {
cmd.Args = append(cmd.Args, "--pidfile", startCmdPidfileFlag)
}
stdinpipe, err := cmd.StdinPipe() stdinpipe, err := cmd.StdinPipe()
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
@ -149,6 +153,7 @@ func cmdRun(fl Flags) (int, error) {
runCmdResumeFlag := fl.Bool("resume") runCmdResumeFlag := fl.Bool("resume")
runCmdPrintEnvFlag := fl.Bool("environ") runCmdPrintEnvFlag := fl.Bool("environ")
runCmdWatchFlag := fl.Bool("watch") runCmdWatchFlag := fl.Bool("watch")
runCmdPidfileFlag := fl.String("pidfile")
runCmdPingbackFlag := fl.String("pingback") runCmdPingbackFlag := fl.String("pingback")
// if we are supposed to print the environment, do that first // if we are supposed to print the environment, do that first
@ -225,6 +230,16 @@ func cmdRun(fl Flags) (int, error) {
go watchConfigFile(configFile, runCmdConfigAdapterFlag) go watchConfigFile(configFile, runCmdConfigAdapterFlag)
} }
// create pidfile
if runCmdPidfileFlag != "" {
err := caddy.PIDFile(runCmdPidfileFlag)
if err != nil {
caddy.Log().Error("unable to write PID file",
zap.String("pidfile", runCmdPidfileFlag),
zap.Error(err))
}
}
// warn if the environment does not provide enough information about the disk // warn if the environment does not provide enough information about the disk
hasXDG := os.Getenv("XDG_DATA_HOME") != "" && hasXDG := os.Getenv("XDG_DATA_HOME") != "" &&
os.Getenv("XDG_CONFIG_HOME") != "" && os.Getenv("XDG_CONFIG_HOME") != "" &&

View File

@ -74,7 +74,7 @@ func init() {
RegisterCommand(Command{ RegisterCommand(Command{
Name: "start", Name: "start",
Func: cmdStart, Func: cmdStart,
Usage: "[--config <path> [--adapter <name>]] [--watch]", Usage: "[--config <path> [--adapter <name>]] [--watch] [--pidfile <file>]",
Short: "Starts the Caddy process in the background and then returns", Short: "Starts the Caddy process in the background and then returns",
Long: ` Long: `
Starts the Caddy process, optionally bootstrapped with an initial config file. Starts the Caddy process, optionally bootstrapped with an initial config file.
@ -87,6 +87,7 @@ using 'caddy run' instead to keep it in the foreground.`,
fs := flag.NewFlagSet("start", flag.ExitOnError) fs := flag.NewFlagSet("start", flag.ExitOnError)
fs.String("config", "", "Configuration file") fs.String("config", "", "Configuration file")
fs.String("adapter", "", "Name of config adapter to apply") fs.String("adapter", "", "Name of config adapter to apply")
fs.String("pidfile", "", "Path of file to which to write process ID")
fs.Bool("watch", false, "Reload changed config file automatically") fs.Bool("watch", false, "Reload changed config file automatically")
return fs return fs
}(), }(),
@ -95,7 +96,7 @@ using 'caddy run' instead to keep it in the foreground.`,
RegisterCommand(Command{ RegisterCommand(Command{
Name: "run", Name: "run",
Func: cmdRun, Func: cmdRun,
Usage: "[--config <path> [--adapter <name>]] [--environ] [--watch]", Usage: "[--config <path> [--adapter <name>]] [--environ] [--resume] [--watch] [--pidfile <fil>]",
Short: `Starts the Caddy process and blocks indefinitely`, Short: `Starts the Caddy process and blocks indefinitely`,
Long: ` Long: `
Starts the Caddy process, optionally bootstrapped with an initial config file, Starts the Caddy process, optionally bootstrapped with an initial config file,
@ -132,6 +133,7 @@ development environment.`,
fs.Bool("environ", false, "Print environment") fs.Bool("environ", false, "Print environment")
fs.Bool("resume", false, "Use saved config, if any (and prefer over --config file)") fs.Bool("resume", false, "Use saved config, if any (and prefer over --config file)")
fs.Bool("watch", false, "Watch config file for changes and reload it automatically") fs.Bool("watch", false, "Watch config file for changes and reload it automatically")
fs.String("pidfile", "", "Path of file to which to write process ID")
fs.String("pingback", "", "Echo confirmation bytes to this address on success") fs.String("pingback", "", "Echo confirmation bytes to this address on success")
return fs return fs
}(), }(),