diff --git a/admin.go b/admin.go index 1d190667..e584a3bb 100644 --- a/admin.go +++ b/admin.go @@ -21,6 +21,7 @@ import ( "expvar" "fmt" "io" + "io/ioutil" "net" "net/http" "net/http/pprof" @@ -546,13 +547,6 @@ func handleUnload(w http.ResponseWriter, r *http.Request) error { 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") if err := stopAndCleanup(); err != nil { 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 // in the config. It also matches adjacent commas so that syntax // can be preserved no matter where in the object the field appears. // It supports string and most numeric values. 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 ( rawConfigKey = "config" idKey = "@id" diff --git a/caddy.go b/caddy.go index 1184bc97..00926410 100644 --- a/caddy.go +++ b/caddy.go @@ -470,6 +470,9 @@ func stopAndCleanup() error { return err } certmagic.CleanUpOwnLocks() + if pidfile != "" { + os.Remove(pidfile) + } return nil } diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index efdcfdca..f9e10331 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -41,6 +41,7 @@ import ( func cmdStart(fl Flags) (int, error) { startCmdConfigFlag := fl.String("config") startCmdConfigAdapterFlag := fl.String("adapter") + startCmdPidfileFlag := fl.String("pidfile") startCmdWatchFlag := fl.Bool("watch") // open a listener to which the child process will connect when @@ -71,6 +72,9 @@ func cmdStart(fl Flags) (int, error) { if startCmdWatchFlag { cmd.Args = append(cmd.Args, "--watch") } + if startCmdPidfileFlag != "" { + cmd.Args = append(cmd.Args, "--pidfile", startCmdPidfileFlag) + } stdinpipe, err := cmd.StdinPipe() if err != nil { return caddy.ExitCodeFailedStartup, @@ -149,6 +153,7 @@ func cmdRun(fl Flags) (int, error) { runCmdResumeFlag := fl.Bool("resume") runCmdPrintEnvFlag := fl.Bool("environ") runCmdWatchFlag := fl.Bool("watch") + runCmdPidfileFlag := fl.String("pidfile") runCmdPingbackFlag := fl.String("pingback") // 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) } + // 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 hasXDG := os.Getenv("XDG_DATA_HOME") != "" && os.Getenv("XDG_CONFIG_HOME") != "" && diff --git a/cmd/commands.go b/cmd/commands.go index e02f6a08..5a40b1d9 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -74,7 +74,7 @@ func init() { RegisterCommand(Command{ Name: "start", Func: cmdStart, - Usage: "[--config [--adapter ]] [--watch]", + Usage: "[--config [--adapter ]] [--watch] [--pidfile ]", Short: "Starts the Caddy process in the background and then returns", Long: ` 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.String("config", "", "Configuration file") 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") return fs }(), @@ -95,7 +96,7 @@ using 'caddy run' instead to keep it in the foreground.`, RegisterCommand(Command{ Name: "run", Func: cmdRun, - Usage: "[--config [--adapter ]] [--environ] [--watch]", + Usage: "[--config [--adapter ]] [--environ] [--resume] [--watch] [--pidfile ]", Short: `Starts the Caddy process and blocks indefinitely`, Long: ` 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("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.String("pidfile", "", "Path of file to which to write process ID") fs.String("pingback", "", "Echo confirmation bytes to this address on success") return fs }(),