mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-04 21:47:40 +03:00
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:
parent
cd514fd91a
commit
974dd3c7f8
7 changed files with 141 additions and 3 deletions
3
dist/systemd/maddy.service
vendored
3
dist/systemd/maddy.service
vendored
|
@ -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
|
||||||
|
|
3
dist/systemd/maddy@.service
vendored
3
dist/systemd/maddy@.service
vendored
|
@ -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
|
||||||
|
|
|
@ -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
8
run.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
110
systemd.go
Normal 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
14
systemd_nonlinux.go
Normal 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) {}
|
Loading…
Add table
Add a link
Reference in a new issue