diff options
| author | Shulhan <ms@kilabit.info> | 2026-02-02 15:16:01 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2026-02-02 16:28:11 +0700 |
| commit | 23c576b28ac9bd180155e99f5f07f9d0bac3436e (patch) | |
| tree | 6eab625a6ed136a7c7016ad1370ebd60d1ca0f2f | |
| parent | bebcc50aa6fcaf13c4cbb2116e73d107bc7d9576 (diff) | |
| download | pakakeh.go-23c576b28ac9bd180155e99f5f07f9d0bac3436e.tar.xz | |
lib/systemd: new package for socket-based activation
The `lib/systemd` package implement function `Listeners` that return list
of file descriptor as [net.Listener], that enable program to run with
systemd.socket(5) based activation.
| -rw-r--r-- | CHANGELOG.adoc | 6 | ||||
| -rw-r--r-- | lib/systemd/daemon.go | 103 | ||||
| -rw-r--r-- | lib/systemd/systemd.go | 5 | ||||
| -rw-r--r-- | lib/systemd/testdata/cmd/daemonTCP/daemonTCP.service | 8 | ||||
| -rw-r--r-- | lib/systemd/testdata/cmd/daemonTCP/daemonTCP.socket | 8 | ||||
| -rw-r--r-- | lib/systemd/testdata/cmd/daemonTCP/main.go | 21 | ||||
| -rw-r--r-- | pakakeh.go | 2 |
7 files changed, 152 insertions, 1 deletions
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 523aa2a6..551b0d3f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -38,6 +38,12 @@ Legend, [#v0_61_0] == pakakeh.go v0.61.0 (2026-xx-xx) +**🌱 lib/systemd: new package for socket-based activation** + +The `lib/systemd` package implement function `Listeners` that return list of +file descriptor as [net.Listener], that enable program to run with +systemd.socket(5) based activation. + **🌼 lib/dns: use separate ServeMux for handling DoH** Using the [http.DefaultServeMux] will cause panic when the server diff --git a/lib/systemd/daemon.go b/lib/systemd/daemon.go new file mode 100644 index 00000000..9bbedc1c --- /dev/null +++ b/lib/systemd/daemon.go @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2026 M. Shulhan <ms@kilabit.info> + +package systemd + +import ( + "fmt" + "math" + "net" + "os" + "strconv" + + "golang.org/x/sys/unix" +) + +const envListenPID = `LISTEN_PID` +const envListenFDS = `LISTEN_FDS` + +// Listeners return the list of [net.Listener] passed by systemd for socket +// activation. +// For each returned [net.Listener], the main program must check if the +// listener address match with expected listen address. +// +// listenAddress := `127.0.0.1:8080` +// listeners, _ := systemd.Listeners(true) +// for _, l := range listeners { +// if l.Addr().String() == listenAddress { +// // The listener match with expected address. +// } +// } +// +// Program should ignore the listeners or continue with its default address if +// no address matched. +// +// References, +// - https://github.com/systemd/systemd/blob/v259/src/libsystemd/sd-daemon/sd-daemon.c +// - https://0pointer.de/blog/projects/socket-activation.html +// - https://0pointer.de/blog/projects/socket-activation2.html +func Listeners(unsetEnv bool) (list []net.Listener, err error) { + logp := `Listeners` + + if unsetEnv { + defer func() { + _ = os.Unsetenv(envListenPID) + _ = os.Unsetenv(envListenFDS) + }() + } + + v := os.Getenv(envListenPID) + if v == `` { + return nil, nil + } + listenPid, err := strconv.Atoi(v) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + + pid := os.Getpid() + if listenPid != pid { + return nil, fmt.Errorf(`%s: mismatch PID, got %d, want %d`, + logp, listenPid, pid) + } + + v = os.Getenv(envListenFDS) + if v == `` { + return nil, nil + } + n, err := strconv.Atoi(v) + if err != nil { + return nil, fmt.Errorf(`%s: invalid LISTEN_FDS value %s: %w`, logp, v, err) + } + if n < 0 { + return nil, fmt.Errorf(`%s: invalid LISTEN_FDS value %d`, logp, n) + } + const listenFDSStart = 3 + if n > math.MaxInt-listenFDSStart { + return nil, fmt.Errorf(`%s: invalid LISTEN_FDS value %d`, logp, n) + } + + for fd := listenFDSStart; fd < listenFDSStart+n; fd++ { + fdptr := uintptr(fd) + flags, err := unix.FcntlInt(fdptr, unix.F_GETFD, 0) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + newflags := flags | unix.FD_CLOEXEC + if flags != newflags { + _, err = unix.FcntlInt(fdptr, unix.F_SETFD, newflags) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + } + + file := os.NewFile(fdptr, ``) + fileListener, err := net.FileListener(file) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + list = append(list, fileListener) + } + + return list, nil +} diff --git a/lib/systemd/systemd.go b/lib/systemd/systemd.go new file mode 100644 index 00000000..40a3da5a --- /dev/null +++ b/lib/systemd/systemd.go @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2026 M. Shulhan <ms@kilabit.info> + +// Package systemd implement native interface for systemd. +package systemd diff --git a/lib/systemd/testdata/cmd/daemonTCP/daemonTCP.service b/lib/systemd/testdata/cmd/daemonTCP/daemonTCP.service new file mode 100644 index 00000000..cb37dd67 --- /dev/null +++ b/lib/systemd/testdata/cmd/daemonTCP/daemonTCP.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause +# SPDX-FileCopyrightText: 2026 M. Shulhan <ms@kilabit.info> + +[Service] +ExecStart=/home/ms/go/bin/daemonTCP + +[Install] +WantedBy=multi-user.target diff --git a/lib/systemd/testdata/cmd/daemonTCP/daemonTCP.socket b/lib/systemd/testdata/cmd/daemonTCP/daemonTCP.socket new file mode 100644 index 00000000..fe03e343 --- /dev/null +++ b/lib/systemd/testdata/cmd/daemonTCP/daemonTCP.socket @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause +# SPDX-FileCopyrightText: 2026 M. Shulhan <ms@kilabit.info> + +[Socket] +ListenStream=127.0.0.1:20001 + +[Install] +WantedBy=sockets.target diff --git a/lib/systemd/testdata/cmd/daemonTCP/main.go b/lib/systemd/testdata/cmd/daemonTCP/main.go new file mode 100644 index 00000000..e667d8a0 --- /dev/null +++ b/lib/systemd/testdata/cmd/daemonTCP/main.go @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2026 M. Shulhan <ms@kilabit.info> + +package main + +import ( + "fmt" + "log" + + "git.sr.ht/~shulhan/pakakeh.go/lib/systemd" +) + +func main() { + lns, err := systemd.Listeners(true) + if err != nil { + log.Fatal(err) + } + for _, ln := range lns { + fmt.Printf(`listener network address: %s`, ln.Addr()) + } +} @@ -8,5 +8,5 @@ package pakakeh var ( // Version of this module. - Version = `0.60.2` + Version = `0.61.0` ) |
