aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat
-rw-r--r--README.md13+13 −0
-rw-r--r--Taskfile.yml3+3 −0
-rw-r--r--cmd/mirumd/main.go40+11 −29
-rw-r--r--cmd/mirumw/main.go54+54 −0
-rw-r--r--internal/supervisor/supervisor.go52+52 −0
-rw-r--r--internal/supervisor/systemd.go54+54 −0
-rw-r--r--nfpm.yaml35+30 −5
-rw-r--r--pkg/mirumd.service6+3 −3
-rw-r--r--pkg/mirumw-default.yaml4+4 −0
-rw-r--r--pkg/mirumw@.service48+48 −0
-rw-r--r--pkg/scripts/postinstall.sh5+4 −1
-rw-r--r--pkg/scripts/preinstall.sh19+8 −11
-rw-r--r--pkg/scripts/preremove.sh5+4 −1
13 files changed, 288 insertions, 50 deletions
diff --git a/README.md b/README.md
index 4de2c4e..622bc02 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,11 @@ curl -fsSL https://dl.mirum.dev/public.gpg | sudo gpg --dearmor -o /usr/share/ke
echo "deb [signed-by=/usr/share/keyrings/mirum.gpg] https://dl.mirum.dev/apt/ nightly main" | sudo tee /etc/apt/sources.list.d/mirum.list
sudo apt update && sudo apt install mirum
+# Start the server
sudo systemctl enable --now mirumd
+
+# Start a worker (optional, can run on a different host)
+sudo systemctl enable --now mirumw@default
```
**Fedora/RHEL:**
@@ -28,7 +32,12 @@ sudo dnf config-manager addrepo --from-repofile=https://dl.mirum.dev/rpm/nightly
sudo curl -o /etc/yum.repos.d/mirum-nightly.repo https://dl.mirum.dev/rpm/nightly/mirum-nightly.repo
sudo dnf install mirum
+
+# Start the server
sudo systemctl enable --now mirumd
+
+# Start a worker (optional, can run on a different host)
+sudo systemctl enable --now mirumw@default
```
**openSUSE:**
@@ -39,7 +48,11 @@ sudo zypper addrepo https://dl.mirum.dev/rpm/nightly/ mirum-nightly
sudo zypper refresh
sudo zypper install mirum
+# Start the server
sudo systemctl enable --now mirumd
+
+# Start a worker (optional, can run on a different host)
+sudo systemctl enable --now mirumw@default
```
## License
diff --git a/Taskfile.yml b/Taskfile.yml
index 8693c22..259fb37 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -14,6 +14,7 @@ tasks:
cmds:
- mkdir -p {{.BUILD_DIR}}
- go build -o {{.BUILD_DIR}}/mirumd ./cmd/mirumd
+ - go build -o {{.BUILD_DIR}}/mirumw ./cmd/mirumw
package:
desc: Build deb/rpm packages for all architectures
@@ -27,7 +28,9 @@ tasks:
- for: { var: ARCHES }
cmd: |
GOARCH={{.ITEM}} go build -o {{.BUILD_DIR}}/mirumd-linux-{{.ITEM}} ./cmd/mirumd
+ GOARCH={{.ITEM}} go build -o {{.BUILD_DIR}}/mirumw-linux-{{.ITEM}} ./cmd/mirumw
cp {{.BUILD_DIR}}/mirumd-linux-{{.ITEM}} {{.BUILD_DIR}}/mirumd
+ cp {{.BUILD_DIR}}/mirumw-linux-{{.ITEM}} {{.BUILD_DIR}}/mirumw
ARCH={{.ITEM}} nfpm package --packager deb --target {{.DIST_DIR}}/
ARCH={{.ITEM}} nfpm package --packager rpm --target {{.DIST_DIR}}/
diff --git a/cmd/mirumd/main.go b/cmd/mirumd/main.go
index 19afd6a..f5ee0d5 100644
--- a/cmd/mirumd/main.go
+++ b/cmd/mirumd/main.go
@@ -19,15 +19,13 @@ import (
"net/url"
"os"
"os/exec"
- "os/signal"
"path/filepath"
- "strconv"
"strings"
- "syscall"
"time"
+ "mrdimidium/mirum/internal/supervisor"
+
"github.com/coreos/go-systemd/v22/activation"
- "github.com/coreos/go-systemd/v22/daemon"
"go.starlark.net/starlark"
"gopkg.in/yaml.v3"
)
@@ -301,21 +299,21 @@ func main() {
srv := &http.Server{Handler: mux}
- go func() {
- sig := make(chan os.Signal, 1)
- signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
- <-sig
+ sup := supervisor.Detect()
+ ctx := sup.WaitForStop(context.Background())
+ go func() {
+ <-ctx.Done()
slog.Info("shutting down")
- daemon.SdNotify(false, daemon.SdNotifyStopping)
+ sup.Stopping()
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
- srv.Shutdown(ctx)
+ srv.Shutdown(shutdownCtx)
}()
- daemon.SdNotify(false, daemon.SdNotifyReady)
- go watchdog()
+ sup.Ready()
+ go sup.StartWatchdog()
if err := srv.Serve(ln); err != http.ErrServerClosed {
fmt.Fprintln(os.Stderr, err)
@@ -330,19 +328,3 @@ func socketActivationListener() (net.Listener, error) {
}
return net.Listen("tcp", cfg.Address)
}
-
-func watchdog() {
- usecStr := os.Getenv("WATCHDOG_USEC")
- if usecStr == "" {
- return
- }
- usec, err := strconv.ParseInt(usecStr, 10, 64)
- if err != nil || usec <= 0 {
- return
- }
- interval := time.Duration(usec) * time.Microsecond / 2
- for {
- daemon.SdNotify(false, daemon.SdNotifyWatchdog)
- time.Sleep(interval)
- }
-}
diff --git a/cmd/mirumw/main.go b/cmd/mirumw/main.go
new file mode 100644
--- /dev/null
+++ b/cmd/mirumw/main.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2026 Nikolay Govorov
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "log/slog"
+ "os"
+
+ "mrdimidium/mirum/internal/supervisor"
+
+ "gopkg.in/yaml.v3"
+)
+
+type config struct {
+ Server string `yaml:"server"`
+}
+
+var cfg = config{
+ Server: "localhost:2026",
+}
+
+var configFile = flag.String("config", "", "path to config file")
+
+func main() {
+ flag.Parse()
+
+ if *configFile != "" {
+ data, err := os.ReadFile(*configFile)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ if err := yaml.Unmarshal(data, &cfg); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ }
+
+ sup := supervisor.Detect()
+ ctx := sup.WaitForStop(context.Background())
+
+ slog.Info("connecting", "server", cfg.Server)
+
+ sup.Ready()
+ go sup.StartWatchdog()
+
+ <-ctx.Done()
+ slog.Info("shutting down")
+ sup.Stopping()
+}
diff --git a/internal/supervisor/supervisor.go b/internal/supervisor/supervisor.go
new file mode 100644
--- /dev/null
+++ b/internal/supervisor/supervisor.go
@@ -0,0 +1,52 @@
+// Copyright (c) 2026 Nikolay Govorov
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+// Package supervisor provides a platform-agnostic interface for process
+// supervision (systemd, launchd, Windows Services, etc.).
+//
+// On systems without a recognized supervisor the functions are no-ops.
+package supervisor
+
+import (
+ "context"
+ "os/signal"
+ "syscall"
+)
+
+// Supervisor communicates lifecycle events to the process supervisor
+// and handles platform-specific shutdown signals.
+type Supervisor interface {
+ // Ready signals that the service has started and is ready to serve.
+ Ready()
+
+ // Stopping signals that the service has begun graceful shutdown.
+ Stopping()
+
+ // StartWatchdog begins sending periodic keepalive pings.
+ // Blocks forever; call as a goroutine. Returns immediately if the
+ // supervisor does not require keepalives.
+ StartWatchdog()
+
+ // WaitForStop blocks until the supervisor or OS requests shutdown.
+ WaitForStop(ctx context.Context) context.Context
+}
+
+// Detect returns a Supervisor for the current platform.
+func Detect() Supervisor {
+ if n := detectSystemd(); n != nil {
+ return n
+ }
+ return &noop{}
+}
+
+type noop struct{}
+
+func (*noop) Ready() {}
+func (*noop) Stopping() {}
+func (*noop) StartWatchdog() {}
+
+func (*noop) WaitForStop(ctx context.Context) context.Context {
+ ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
+ _ = stop
+ return ctx
+}
diff --git a/internal/supervisor/systemd.go b/internal/supervisor/systemd.go
new file mode 100644
--- /dev/null
+++ b/internal/supervisor/systemd.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2026 Nikolay Govorov
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+package supervisor
+
+import (
+ "context"
+ "os"
+ "os/signal"
+ "strconv"
+ "syscall"
+ "time"
+
+ "github.com/coreos/go-systemd/v22/daemon"
+)
+
+type systemd struct{}
+
+func detectSystemd() Supervisor {
+ if os.Getenv("NOTIFY_SOCKET") == "" {
+ return nil
+ }
+ return &systemd{}
+}
+
+func (*systemd) WaitForStop(ctx context.Context) context.Context {
+ ctx, stop := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT)
+ _ = stop
+ return ctx
+}
+
+func (*systemd) Ready() {
+ daemon.SdNotify(false, daemon.SdNotifyReady)
+}
+
+func (*systemd) Stopping() {
+ daemon.SdNotify(false, daemon.SdNotifyStopping)
+}
+
+func (*systemd) StartWatchdog() {
+ usecStr := os.Getenv("WATCHDOG_USEC")
+ if usecStr == "" {
+ return
+ }
+ usec, err := strconv.ParseInt(usecStr, 10, 64)
+ if err != nil || usec <= 0 {
+ return
+ }
+ interval := time.Duration(usec) * time.Microsecond / 2
+ for {
+ daemon.SdNotify(false, daemon.SdNotifyWatchdog)
+ time.Sleep(interval)
+ }
+}
diff --git a/nfpm.yaml b/nfpm.yaml
index beea2ff..f19f0a3 100644
--- a/nfpm.yaml
+++ b/nfpm.yaml
@@ -23,25 +23,50 @@ contents:
file_info:
mode: 0755
+ - src: build/mirumw
+ dst: /usr/local/bin/mirumw
+ file_info:
+ mode: 0755
+
- src: pkg/mirumd.yaml
- dst: /etc/mirumd.yaml
+ dst: /etc/mirum/mirumd.yaml
+ type: config|noreplace
+ file_info:
+ mode: 0640
+ owner: root
+ group: mirumd
+
+ - src: pkg/mirumw-default.yaml
+ dst: /etc/mirum/mirumw-default.yaml
type: config|noreplace
file_info:
mode: 0640
owner: root
- group: mirum
+ group: mirumw
- src: pkg/mirumd.service
dst: /usr/lib/systemd/system/mirumd.service
file_info:
mode: 0644
+ - src: pkg/mirumw@.service
+ dst: /usr/lib/systemd/system/mirumw@.service
+ file_info:
+ mode: 0644
+
- dst: /var/lib/mirumd
type: dir
file_info:
- mode: 0755
- owner: root
- group: mirum
+ mode: 0750
+ owner: mirumd
+ group: mirumd
+
+ - dst: /var/lib/mirumw
+ type: dir
+ file_info:
+ mode: 0750
+ owner: mirumw
+ group: mirumw
scripts:
diff --git a/pkg/mirumd.service b/pkg/mirumd.service
index 7a424d0..5b1b78c 100644
--- a/pkg/mirumd.service
+++ b/pkg/mirumd.service
@@ -9,14 +9,14 @@ Wants=time-sync.target
[Service]
Type=notify
-User=mirum
-Group=mirum
+User=mirumd
+Group=mirumd
Restart=always
RestartSec=30
WatchdogSec=30
NotifyAccess=main
ExecPaths=/usr/local/bin/mirumd /usr/lib
-ExecStart=/usr/local/bin/mirumd -config=/etc/mirumd.yaml
+ExecStart=/usr/local/bin/mirumd --config=/etc/mirum/mirumd.yaml
LimitCORE=infinity
LimitNOFILE=500000
AmbientCapabilities=CAP_NET_BIND_SERVICE
diff --git a/pkg/mirumw-default.yaml b/pkg/mirumw-default.yaml
new file mode 100644
--- /dev/null
+++ b/pkg/mirumw-default.yaml
@@ -0,0 +1,4 @@
+# Copyright (c) 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+server: localhost:2026
diff --git a/pkg/mirumw@.service b/pkg/mirumw@.service
new file mode 100644
--- /dev/null
+++ b/pkg/mirumw@.service
@@ -0,0 +1,48 @@
+# Copyright (c) 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+[Unit]
+Description=Mirum worker %i (modern CI platform)
+Requires=network-online.target
+After=time-sync.target network-online.target remote-fs.target nss-lookup.target
+Wants=time-sync.target
+
+[Service]
+Type=notify
+User=mirumw
+Group=mirumw
+Restart=always
+RestartSec=30
+WatchdogSec=30
+NotifyAccess=main
+ExecPaths=/usr/local/bin/mirumw
+ExecStart=/usr/local/bin/mirumw --config=/etc/mirum/mirumw-%i.yaml
+LimitCORE=infinity
+LimitNOFILE=500000
+AmbientCapabilities=
+
+# %p is resolved to the systemd unit name
+LogsDirectory=%p
+StateDirectory=%p
+CacheDirectory=%p
+RuntimeDirectory=%p
+
+UMask=0077
+LockPersonality=yes
+NoNewPrivileges=yes
+PrivateTmp=true
+ProtectClock=yes
+ProtectControlGroups=yes
+ProtectHome=yes
+ProtectHostname=yes
+ProtectKernelLogs=yes
+ProtectKernelModules=yes
+ProtectKernelTunables=yes
+ProtectSystem=strict
+RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
+RestrictNamespaces=yes
+RestrictSUIDSGID=yes
+
+[Install]
+# service should not start from the rescue shell (rescue.target).
+WantedBy=multi-user.target
diff --git a/pkg/scripts/postinstall.sh b/pkg/scripts/postinstall.sh
index 13b970c..2c80442 100644
--- a/pkg/scripts/postinstall.sh
+++ b/pkg/scripts/postinstall.sh
@@ -6,5 +6,8 @@ set -e
if [ -x "/bin/systemctl" ] && [ -d /run/systemd/system ] && [ -f /usr/lib/systemd/system/mirumd.service ]; then
/bin/systemctl daemon-reload
- /bin/systemctl enable mirumd
+
+ # Don't enable by default, don't know in advance whether it's a daemon or a worker
+ # /bin/systemctl enable mirumd
+ # /bin/systemctl enable mirumw
fi
diff --git a/pkg/scripts/preinstall.sh b/pkg/scripts/preinstall.sh
index 6f7b91f..d15fee2 100644
--- a/pkg/scripts/preinstall.sh
+++ b/pkg/scripts/preinstall.sh
@@ -4,14 +4,11 @@
set -e
-PROGRAM=mirum
-MIRUM_USER=${MIRUM_USER:-mirum}
-MIRUM_GROUP=${MIRUM_GROUP:-${MIRUM_USER}}
-
-if ! getent group $MIRUM_GROUP >/dev/null; then
- groupadd --system $MIRUM_GROUP
-fi
-
-if ! getent passwd $MIRUM_USER >/dev/null; then
- useradd --system --gid $MIRUM_GROUP --no-create-home --shell /usr/sbin/nologin $MIRUM_USER
-fi
+for svc in mirumd mirumw; do
+ if ! getent group $svc >/dev/null; then
+ groupadd --system $svc
+ fi
+ if ! getent passwd $svc >/dev/null; then
+ useradd --system --gid $svc --no-create-home --shell /usr/sbin/nologin $svc
+ fi
+done
diff --git a/pkg/scripts/preremove.sh b/pkg/scripts/preremove.sh
index c77f55b..fdb71a8 100644
--- a/pkg/scripts/preremove.sh
+++ b/pkg/scripts/preremove.sh
@@ -4,7 +4,10 @@
set -e
-if [ -x "/bin/systemctl" ] && [ -d /run/systemd/system ] && [ -f /usr/lib/systemd/system/mirumd.service ]; then
+if [ -x "/bin/systemctl" ] && [ -d /run/systemd/system ]; then
/bin/systemctl stop mirumd.service || true
/bin/systemctl disable mirumd.service || true
+
+ /bin/systemctl stop 'mirumw@*' || true
+ /bin/systemctl disable mirumw@.service || true
fi