feat: order/delay in service files, enable cmd, --all on unit commands
- install/uninstall/start/stop/enable take <name> or --all; *all kept as shortcuts - add enable (enable at boot + start); startall no longer enables - order/delay resolved by config.ResolveOrder and written into systemd, OpenRC and runit service files (After=/sleep), so they apply at boot - install --all stops and disables units before removing their files - update regenerates all installed, enabled units and removes disabled ones - edit creates the config dir; docs and man page updated - tests: fake Init for cmd, ResolveOrder, backend ordering output
This commit is contained in:
+22
-2
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/warkanum/unitdore/config"
|
||||
"github.com/warkanum/unitdore/service"
|
||||
@@ -14,7 +15,7 @@ const systemUnitTemplate = `# /etc/systemd/system/{{.ServiceName}}
|
||||
|
||||
[Unit]
|
||||
Description=Unitdore: {{.Unit.Name}} ({{.Label}})
|
||||
After=network.target{{with .RuntimeService}} {{.}}
|
||||
After=network.target{{with .RuntimeService}} {{.}}{{end}}{{range .After}} {{.}}{{end}}{{with .RuntimeService}}
|
||||
Requires={{.}}{{end}}
|
||||
|
||||
[Service]
|
||||
@@ -25,6 +26,10 @@ RemainAfterExit=yes
|
||||
{{- with .WorkingDir}}
|
||||
WorkingDirectory={{.}}
|
||||
{{- end}}
|
||||
{{- with .DelaySec}}
|
||||
ExecStartPre=/usr/bin/env sleep {{.}}
|
||||
TimeoutStartSec={{$.StartTimeout}}
|
||||
{{- end}}
|
||||
ExecStart={{.ExecStart}}
|
||||
ExecStop={{if .ExecStop}}-{{.ExecStop}}{{end}}
|
||||
{{- if .Unit.Restart}}
|
||||
@@ -44,7 +49,7 @@ const userUnitTemplate = `# ~/.config/systemd/user/{{.ServiceName}}
|
||||
|
||||
[Unit]
|
||||
Description=Unitdore: {{.Unit.Name}} ({{.Label}})
|
||||
After=default.target
|
||||
After=default.target{{range .After}} {{.}}{{end}}
|
||||
|
||||
[Service]
|
||||
Type={{.ServiceType}}
|
||||
@@ -54,6 +59,10 @@ RemainAfterExit=yes
|
||||
{{- with .WorkingDir}}
|
||||
WorkingDirectory={{.}}
|
||||
{{- end}}
|
||||
{{- with .DelaySec}}
|
||||
ExecStartPre=/usr/bin/env sleep {{.}}
|
||||
TimeoutStartSec={{$.StartTimeout}}
|
||||
{{- end}}
|
||||
ExecStart={{.ExecStart}}
|
||||
ExecStop={{if .ExecStop}}-{{.ExecStop}}{{end}}
|
||||
{{- if .Unit.Restart}}
|
||||
@@ -77,6 +86,9 @@ type templateData struct {
|
||||
Label string // shown in Description
|
||||
ServiceType string // simple | oneshot
|
||||
WorkingDir string
|
||||
After []string // service names of the previous order group
|
||||
DelaySec int // ExecStartPre sleep, from unit delay
|
||||
StartTimeout int // TimeoutStartSec when a delay is set
|
||||
}
|
||||
|
||||
// ServiceName returns the systemd service name for a unit, with optional prefix/suffix.
|
||||
@@ -102,6 +114,14 @@ func Generate(u config.Unit, prefix, suffix string) (string, error) {
|
||||
WorkingDir: spec.WorkingDir,
|
||||
}
|
||||
|
||||
for _, n := range u.After {
|
||||
data.After = append(data.After, ServiceName(config.Unit{Name: n}, prefix, suffix))
|
||||
}
|
||||
if u.StartDelay > 0 {
|
||||
data.DelaySec = int((u.StartDelay + time.Second - 1) / time.Second)
|
||||
data.StartTimeout = data.DelaySec + 90
|
||||
}
|
||||
|
||||
tmplStr := systemUnitTemplate
|
||||
if u.User != "" {
|
||||
tmplStr = userUnitTemplate
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/warkanum/unitdore/config"
|
||||
)
|
||||
@@ -437,3 +438,44 @@ func TestGenerate_ExecutableMissingPath(t *testing.T) {
|
||||
t.Error("Generate() should error when executable is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_OrderAndDelay(t *testing.T) {
|
||||
u := config.Unit{Name: "web", Runtime: "podman", Enabled: true, After: []string{"db", "cache"}, StartDelay: 1500 * time.Millisecond}
|
||||
content, err := Generate(u, "p-", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"After=network.target unitdore-p-db.service unitdore-p-cache.service\n",
|
||||
"ExecStartPre=/usr/bin/env sleep 2\n",
|
||||
"TimeoutStartSec=92\n",
|
||||
} {
|
||||
if !strings.Contains(content, want) {
|
||||
t.Errorf("missing %q in:\n%s", want, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_UserUnitOrder(t *testing.T) {
|
||||
u := config.Unit{Name: "web", Runtime: "podman", User: "hein", Enabled: true, After: []string{"db"}}
|
||||
content, err := Generate(u, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(content, "After=default.target unitdore-db.service\n") {
|
||||
t.Errorf("user unit missing ordering:\n%s", content)
|
||||
}
|
||||
if strings.Contains(content, "ExecStartPre") {
|
||||
t.Errorf("no delay expected:\n%s", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_NoOrderNoExtras(t *testing.T) {
|
||||
content, err := Generate(config.Unit{Name: "a", Runtime: "podman", Enabled: true}, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(content, "ExecStartPre") || strings.Contains(content, "TimeoutStartSec") || !strings.Contains(content, "After=network.target\n") {
|
||||
t.Errorf("unexpected ordering output:\n%s", content)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user