diff --git a/dist/systemd/maddy.service b/dist/systemd/maddy.service index 2a36502..e326eea 100644 --- a/dist/systemd/maddy.service +++ b/dist/systemd/maddy.service @@ -6,6 +6,9 @@ Documentation=https://github.com/foxcpp/maddy/wiki After=network.target [Service] +Type=notify +NotifyAccess=main + # For systemd before 235. Assumes pre-existing user & group. User=maddy Group=maddy diff --git a/dist/systemd/maddy@.service b/dist/systemd/maddy@.service index 34a29bf..c5a820d 100644 --- a/dist/systemd/maddy@.service +++ b/dist/systemd/maddy@.service @@ -3,6 +3,9 @@ Description=maddy mail server (using %i.conf) After=network.target [Service] +Type=notify +NotifyAccess=main + # For systemd before 235. Assumes pre-existing user & group. User=maddy Group=maddy diff --git a/module_main.go b/module_main.go index 7059fc7..3592327 100644 --- a/module_main.go +++ b/module_main.go @@ -55,8 +55,12 @@ func moduleMain(cfg []config.Node) error { return err } + systemdStatus(SDReady, "Listening for incoming connections...") + handleSignals() + systemdStatus(SDStopping, "Waiting for running transactions to complete...") + for _, inst := range insts { if closer, ok := inst.(io.Closer); ok { if err := closer.Close(); err != nil { diff --git a/run.go b/run.go index f7ac7cb..49d059f 100644 --- a/run.go +++ b/run.go @@ -43,6 +43,7 @@ func Run() int { var err error log.DefaultLogger.Out, err = LogOutputOption(strings.Split(*logTargets, " ")) if err != nil { + systemdStatusErr(err) log.Println(err) return 2 } @@ -51,18 +52,21 @@ func Run() int { f, err := os.Open(*configPath) if err != nil { - log.Printf("cannot open %q: %v\n", *configPath, err) + systemdStatusErr(err) + log.Println(err) return 2 } defer f.Close() cfg, err := parser.Read(f, *configPath) if err != nil { - log.Printf("cannot parse %q: %v\n", *configPath, err) + systemdStatusErr(err) + log.Println(err) return 2 } if err := moduleMain(cfg); err != nil { + systemdStatusErr(err) log.Println(err) return 2 } diff --git a/signal_nonposix.go b/signal_nonposix.go index 45da4d2..a7dc58e 100644 --- a/signal_nonposix.go +++ b/signal_nonposix.go @@ -16,7 +16,7 @@ func handleSignals() os.Signal { s := <-sig go func() { - s := waitForSignal() + s := handleSignals() log.Printf("forced shutdown due to signal (%v)!", s) os.Exit(1) }() diff --git a/systemd.go b/systemd.go new file mode 100644 index 0000000..636fb57 --- /dev/null +++ b/systemd.go @@ -0,0 +1,110 @@ +//+build linux + +package maddy + +import ( + "errors" + "fmt" + "io" + "net" + "os" + "strings" + "syscall" + + "github.com/foxcpp/maddy/log" +) + +type SDStatus string + +const ( + SDReady = "READY=1" + SDStopping = "STOPPING=1" +) + +var ( + ErrNoNotifySock = errors.New("no systemd socket") +) + +func sdNotifySock() (*net.UnixConn, error) { + sockAddr := os.Getenv("NOTIFY_SOCKET") + if sockAddr == "" { + return nil, ErrNoNotifySock + } + if strings.HasPrefix(sockAddr, "@") { + sockAddr = "\x00" + sockAddr[1:] + } + + return net.DialUnix("unixgram", nil, &net.UnixAddr{ + Name: sockAddr, + Net: "unixgram", + }) +} + +func setScmPassCred(sock *net.UnixConn) error { + sConn, err := sock.SyscallConn() + if err != nil { + return err + } + if err := sConn.Control(func(fd uintptr) { + syscall.SetsockoptByte(int(fd), syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1) + }); err != nil { + return err + } + return nil +} + +func systemdStatus(status SDStatus, desc string) { + sock, err := sdNotifySock() + if err != nil { + if err != ErrNoNotifySock { + log.Println("systemd: failed to acquire notify socket:", err) + } + return + } + defer sock.Close() + + if err := setScmPassCred(sock); err != nil { + log.Println("failed to set SCM_PASSCRED on the socket for communication with systemd:", err) + } + + if desc != "" { + if _, err := io.WriteString(sock, fmt.Sprintf("%s\nSTATUS=%s", status, desc)); err != nil { + log.Println("systemd: I/O error:", err) + } + log.Debugf(`systemd: %s STATUS="%s"`, status, desc) + } else { + if _, err := io.WriteString(sock, string(status)); err != nil { + log.Println("systemd: I/O error:", err) + } + log.Debugf(`systemd: %s`, status) + } +} + +func systemdStatusErr(reportedErr error) { + sock, err := sdNotifySock() + if err != nil { + if err != ErrNoNotifySock { + log.Println("systemd: failed to acquire notify socket:", err) + } + return + } + defer sock.Close() + + if err := setScmPassCred(sock); err != nil { + log.Println("systemd: failed to set SCM_PASSCRED on the socket:", err) + } + + var errno syscall.Errno + if errors.As(reportedErr, &errno) { + log.Debugf(`systemd: ERRNO=%d STATUS="%v"`, errno, reportedErr) + if _, err := io.WriteString(sock, fmt.Sprintf("ERRNO=%d\nSTATUS=%v", errno, reportedErr)); err != nil { + log.Println("systemd: I/O error:", err) + } + return + } + + if _, err := io.WriteString(sock, fmt.Sprintf("STATUS=%v\n", reportedErr)); err != nil { + log.Println("systemd: I/O error:", err) + } + log.Debugf(`systemd: STATUS="%v"`, reportedErr) +} diff --git a/systemd_nonlinux.go b/systemd_nonlinux.go new file mode 100644 index 0000000..c03d687 --- /dev/null +++ b/systemd_nonlinux.go @@ -0,0 +1,14 @@ +//+build !linux + +package maddy + +type SDStatus string + +const ( + SDReady = "READY=1" + SDStopping = "STOPPING=1" +) + +func systemdStatus(SDStatus, string) {} + +func systemdStatusErr(error) {}