diff --git a/cmd/cloudflared/linux_service.go b/cmd/cloudflared/linux_service.go index fede6b23..62559bf4 100644 --- a/cmd/cloudflared/linux_service.go +++ b/cmd/cloudflared/linux_service.go @@ -118,10 +118,6 @@ case "$1" in echo "Starting $name" $cmd >> "$stdout_log" 2>> "$stderr_log" & echo $! > "$pid_file" - if ! is_running; then - echo "Unable to start, see $stdout_log and $stderr_log" - exit 1 - fi fi ;; stop) diff --git a/cmd/cloudflared/macos_service.go b/cmd/cloudflared/macos_service.go index d4fd3f20..9ec162fa 100644 --- a/cmd/cloudflared/macos_service.go +++ b/cmd/cloudflared/macos_service.go @@ -60,7 +60,7 @@ func newLaunchdTemplate(installPath, stdoutPath, stderrPath string) *ServiceTemp ThrottleInterval - 20 + 5 `, launchdIdentifier, stdoutPath, stderrPath), } diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index e2a6488c..3f6198ff 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -39,6 +39,7 @@ import ( "github.com/getsentry/raven-go" "github.com/gliderlabs/ssh" "github.com/google/uuid" + "github.com/mitchellh/go-homedir" "github.com/pkg/errors" "gopkg.in/urfave/cli.v2" "gopkg.in/urfave/cli.v2/altsrc" @@ -433,7 +434,6 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan defer wg.Done() errC <- origin.StartTunnelDaemon(ctx, tunnelConfig, connectedSignal, cloudflaredID, reconnectCh) }() - return waitToShutdown(&wg, errC, shutdownC, graceShutdownC, c.Duration("grace-period")) } @@ -607,9 +607,15 @@ func notifySystemd(waitForSignal *signal.Signal) { func writePidFile(waitForSignal *signal.Signal, pidFile string) { <-waitForSignal.Wait() - file, err := os.Create(pidFile) + expandedPath, err := homedir.Expand(pidFile) if err != nil { - logger.WithError(err).Errorf("Unable to write pid to %s", pidFile) + logger.WithError(err).Errorf("Unable to expand %s, try to use absolute path in --pidfile", pidFile) + return + } + file, err := os.Create(expandedPath) + if err != nil { + logger.WithError(err).Errorf("Unable to write pid to %s", expandedPath) + return } defer file.Close() fmt.Fprintf(file, "%d", os.Getpid()) diff --git a/cmd/cloudflared/tunnel/signal.go b/cmd/cloudflared/tunnel/signal.go index afc26ccf..b1ec77e2 100644 --- a/cmd/cloudflared/tunnel/signal.go +++ b/cmd/cloudflared/tunnel/signal.go @@ -17,9 +17,11 @@ func waitForSignal(errC chan error, shutdownC chan struct{}) error { select { case err := <-errC: + logger.Infof("terminating due to error: %v", err) close(shutdownC) return err - case <-signals: + case s := <-signals: + logger.Infof("terminating due to signal %s", s) close(shutdownC) case <-shutdownC: } @@ -44,10 +46,12 @@ func waitForSignalWithGraceShutdown(errC chan error, select { case err := <-errC: + logger.Infof("Initiating graceful shutdown due to %v ...", err) close(graceShutdownC) close(shutdownC) return err - case <-signals: + case s := <-signals: + logger.Infof("Initiating graceful shutdown due to signal %s ...", s) close(graceShutdownC) waitForGracePeriod(signals, errC, shutdownC, gracePeriod) case <-graceShutdownC: @@ -64,7 +68,6 @@ func waitForGracePeriod(signals chan os.Signal, shutdownC chan struct{}, gracePeriod time.Duration, ) { - logger.Infof("Initiating graceful shutdown...") // Unregister signal handler early, so the client can send a second SIGTERM/SIGINT // to force shutdown cloudflared signal.Stop(signals) diff --git a/cmd/cloudflared/updater/update.go b/cmd/cloudflared/updater/update.go index 96cdc95b..8f9c481b 100644 --- a/cmd/cloudflared/updater/update.go +++ b/cmd/cloudflared/updater/update.go @@ -2,6 +2,7 @@ package updater import ( "context" + "fmt" "os" "runtime" "time" @@ -32,6 +33,33 @@ EKx0BZogHSor9Wy5VztdFaAaVbsJiCbO logger = log.CreateLogger() ) +// BinaryUpdated implements ExitCoder interface, the app will exit with status code 11 +// https://pkg.go.dev/gopkg.in/urfave/cli.v2?tab=doc#ExitCoder +type statusSuccess struct { + newVersion string +} + +func (u *statusSuccess) Error() string { + return fmt.Sprintf("cloudflared has been updated to version %s", u.newVersion) +} + +func (u *statusSuccess) ExitCode() int { + return 11 +} + +// UpdateErr implements ExitCoder interface, the app will exit with status code 10 +type statusErr struct { + err error +} + +func (e *statusErr) Error() string { + return fmt.Sprintf("failed to update cloudflared: %v", e.err) +} + +func (e *statusErr) ExitCode() int { + return 10 +} + type UpdateOutcome struct { Updated bool Version string @@ -67,14 +95,15 @@ func checkForUpdateAndApply() UpdateOutcome { func Update(_ *cli.Context) error { updateOutcome := loggedUpdate() if updateOutcome.Error != nil { - os.Exit(10) + return &statusErr{updateOutcome.Error} } if updateOutcome.noUpdate() { logger.Infof("cloudflared is up to date (%s)", updateOutcome.Version) + return nil } - return updateOutcome.Error + return &statusSuccess{newVersion: updateOutcome.Version} } // Checks for an update and applies it if one is available @@ -126,15 +155,19 @@ func (a *AutoUpdater) Run(ctx context.Context) error { updateOutcome := loggedUpdate() if updateOutcome.Updated { os.Args = append(os.Args, "--is-autoupdated=true") - pid, err := a.listeners.StartProcess() - if err != nil { - logger.WithError(err).Error("Unable to restart server automatically") - return err + if IsSysV() { + // SysV doesn't have a mechanism to keep service alive, we have to restart the process + logger.Infof("Restarting service managed by SysV...") + pid, err := a.listeners.StartProcess() + if err != nil { + logger.WithError(err).Error("Unable to restart server automatically") + return &statusErr{err: err} + } + // stop old process after autoupdate. Otherwise we create a new process + // after each update + logger.Infof("PID of the new process is %d", pid) } - // stop old process after autoupdate. Otherwise we create a new process - // after each update - logger.Infof("PID of the new process is %d", pid) - return nil + return &statusSuccess{newVersion: updateOutcome.Version} } } select { @@ -187,3 +220,14 @@ func SupportAutoUpdate() bool { func isRunningFromTerminal() bool { return terminal.IsTerminal(int(os.Stdout.Fd())) } + +func IsSysV() bool { + if runtime.GOOS != "linux" { + return false + } + + if _, err := os.Stat("/run/systemd/system"); err == nil { + return false + } + return true +} diff --git a/go.mod b/go.mod index da0d2528..974a0b3f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/cloudflare/cloudflared go 1.12 require ( + github.com/BurntSushi/go-sumtype v0.0.0-20190304192233-fcb4a6205bdc // indirect github.com/DATA-DOG/go-sqlmock v1.3.3 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect github.com/aws/aws-sdk-go v1.25.8 diff --git a/go.sum b/go.sum index 985e4821..5c41f7d0 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/go-sumtype v0.0.0-20190304192233-fcb4a6205bdc h1:nvTP+jmloR0+J4YQur/rLRdLcGVEU4SquDgH+Bo7gBY= +github.com/BurntSushi/go-sumtype v0.0.0-20190304192233-fcb4a6205bdc/go.mod h1:7yTWMMG2vOm4ABVciEt4EgNVP7fxwtcKIb/EuiLiKqY= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= @@ -244,9 +246,11 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=