diff --git a/internal/home/ready_linux.go b/internal/home/ready_linux.go new file mode 100644 index 00000000000..7ec9311a501 --- /dev/null +++ b/internal/home/ready_linux.go @@ -0,0 +1,49 @@ +//go:build linux + +package home + +import ( + "fmt" + "net" + "os" + "time" +) + +// Notifies the service manager that the program is ready to serve +func notifyReady() error { + return sdNotify("READY=1") +} + +// Notifies the service manager that the program is beginning to reload its +// configuration +func notifyReload() error { + now := time.Now().UnixMicro() + return sdNotify(fmt.Sprintf("RELOADING=1\nMONOTONIC_USEC=%v", now)) +} + +// Implements the sd_notify mechanism +// +// Reference: https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html +func sdNotify(message string) error { + socketPath := os.Getenv("NOTIFY_SOCKET") + if socketPath == "" { + return nil + } + socketAddr := net.UnixAddr{ + Name: socketPath, + Net: "unixgram", + } + + conn, err := net.DialUnix("unixgram", nil, &socketAddr) + if err != nil { + return fmt.Errorf("connecting to %q: %w", socketAddr.String(), err) + } + defer conn.Close() + + _, err = conn.Write([]byte(message)) + if err != nil { + return fmt.Errorf("sending %q: %w", message, err) + } + + return nil +} diff --git a/internal/home/ready_others.go b/internal/home/ready_others.go new file mode 100644 index 00000000000..b095abe54fe --- /dev/null +++ b/internal/home/ready_others.go @@ -0,0 +1,14 @@ +//go:build !linux + +package home + +// Notifies the service manager that the program is ready to serve +func notifyReady() error { + return nil +} + +// Notifies the service manager that the program is beginning to reload its +// configuration +func notifyReload() error { + return nil +} diff --git a/internal/home/service.go b/internal/home/service.go index 7075e5d10bc..12b1c5990c8 100644 --- a/internal/home/service.go +++ b/internal/home/service.go @@ -466,6 +466,9 @@ var launchdConfig = ` // 2. The StandardOutput and StandardError settings are set to redirect the // output to the systemd journal, see // https://man7.org/linux/man-pages/man5/systemd.exec.5.html#LOGGING_AND_STANDARD_INPUT/OUTPUT. +// +// 3. The Type setting has been configured to enable service readiness +// notification support. const systemdScript = `[Unit] Description={{.Description}} ConditionFileIsExecutable={{.Path|cmdEscape}} @@ -473,6 +476,7 @@ ConditionFileIsExecutable={{.Path|cmdEscape}} {{$dep}} {{end}} [Service] +Type=notify StartLimitInterval=5 StartLimitBurst=10 ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}} diff --git a/internal/home/web.go b/internal/home/web.go index 9de27782e0f..02985243ec3 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -235,7 +235,13 @@ func (web *webAPI) start(ctx context.Context) { errs <- web.httpServer.ListenAndServe() }() - err := <-errs + // Tell the service manager that we are ready to serve requests + err := notifyReady() + if err != nil { + logger.ErrorContext(ctx, "sending service ready notification: %v", slogutil.KeyError, err) + } + + err = <-errs if !errors.Is(err, http.ErrServerClosed) { cleanupAlways() panic(err) @@ -243,6 +249,12 @@ func (web *webAPI) start(ctx context.Context) { // We use ErrServerClosed as a sign that we need to rebind on a new // address, so go back to the start of the loop. + // + // Let the service manager know that we are reloading + err = notifyReload() + if err != nil { + logger.ErrorContext(ctx, "sending service reload notification: %v", slogutil.KeyError, err) + } } }