Use systemd notify socket to report process status

It has all sorts of benefits due to the service manager being aware of
the starting/running/stopping state, see systemd.service(5)

On top of that, start-up errors are reported using STATUS= key, so they
will be easier to see in the 'systemctl status' output.
This commit is contained in:
fox.cpp 2019-11-21 23:58:06 +03:00
parent cd514fd91a
commit 974dd3c7f8
No known key found for this signature in database
GPG key ID: E76D97CCEDE90B6C
7 changed files with 141 additions and 3 deletions

View file

@ -6,6 +6,9 @@ Documentation=https://github.com/foxcpp/maddy/wiki
After=network.target After=network.target
[Service] [Service]
Type=notify
NotifyAccess=main
# For systemd before 235. Assumes pre-existing user & group. # For systemd before 235. Assumes pre-existing user & group.
User=maddy User=maddy
Group=maddy Group=maddy

View file

@ -3,6 +3,9 @@ Description=maddy mail server (using %i.conf)
After=network.target After=network.target
[Service] [Service]
Type=notify
NotifyAccess=main
# For systemd before 235. Assumes pre-existing user & group. # For systemd before 235. Assumes pre-existing user & group.
User=maddy User=maddy
Group=maddy Group=maddy

View file

@ -55,8 +55,12 @@ func moduleMain(cfg []config.Node) error {
return err return err
} }
systemdStatus(SDReady, "Listening for incoming connections...")
handleSignals() handleSignals()
systemdStatus(SDStopping, "Waiting for running transactions to complete...")
for _, inst := range insts { for _, inst := range insts {
if closer, ok := inst.(io.Closer); ok { if closer, ok := inst.(io.Closer); ok {
if err := closer.Close(); err != nil { if err := closer.Close(); err != nil {

8
run.go
View file

@ -43,6 +43,7 @@ func Run() int {
var err error var err error
log.DefaultLogger.Out, err = LogOutputOption(strings.Split(*logTargets, " ")) log.DefaultLogger.Out, err = LogOutputOption(strings.Split(*logTargets, " "))
if err != nil { if err != nil {
systemdStatusErr(err)
log.Println(err) log.Println(err)
return 2 return 2
} }
@ -51,18 +52,21 @@ func Run() int {
f, err := os.Open(*configPath) f, err := os.Open(*configPath)
if err != nil { if err != nil {
log.Printf("cannot open %q: %v\n", *configPath, err) systemdStatusErr(err)
log.Println(err)
return 2 return 2
} }
defer f.Close() defer f.Close()
cfg, err := parser.Read(f, *configPath) cfg, err := parser.Read(f, *configPath)
if err != nil { if err != nil {
log.Printf("cannot parse %q: %v\n", *configPath, err) systemdStatusErr(err)
log.Println(err)
return 2 return 2
} }
if err := moduleMain(cfg); err != nil { if err := moduleMain(cfg); err != nil {
systemdStatusErr(err)
log.Println(err) log.Println(err)
return 2 return 2
} }

View file

@ -16,7 +16,7 @@ func handleSignals() os.Signal {
s := <-sig s := <-sig
go func() { go func() {
s := waitForSignal() s := handleSignals()
log.Printf("forced shutdown due to signal (%v)!", s) log.Printf("forced shutdown due to signal (%v)!", s)
os.Exit(1) os.Exit(1)
}() }()

110
systemd.go Normal file
View file

@ -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)
}

14
systemd_nonlinux.go Normal file
View file

@ -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) {}