mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
fix(service): rewrite systemd service template for kardianos/service v1.3.0 (#5743)
kardianos/service v1.3.0 (shipped in Navidrome 0.63.0) replaced Go's
text/template with a small custom engine that uses bare keys ({{Description}})
instead of dotted fields ({{.Description}}). Our custom SystemdScript was
still written in text/template syntax, so 'navidrome service install' failed
with 'FATA service: unknown template key ".Description"', breaking the .deb
postinst and leaving an empty/masked systemd unit.
Rewrite the template in the new engine's syntax and add a test that validates
every key and pipeline function used in the template against the set the
library provides to systemd templates, so future engine/key drift is caught
at test time. Verified end-to-end on Linux: the previous binary reproduces
the reported fatal error and the fixed one generates a complete unit file.
Fixes #5742
This commit is contained in:
parent
4652b46602
commit
42d4363f61
23
cmd/svc.go
23
cmd/svc.go
@ -232,22 +232,21 @@ func buildExecuteCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
const systemdScript = `[Unit]
|
||||
Description={{.Description}}
|
||||
ConditionFileIsExecutable={{.Path|cmdEscape}}
|
||||
{{range $i, $dep := .Dependencies}}
|
||||
{{$dep}} {{end}}
|
||||
|
||||
Description={{Description}}
|
||||
ConditionFileIsExecutable={{Path | cmdEscape}}
|
||||
{{range Dependencies}}{{.}}
|
||||
{{end}}
|
||||
[Service]
|
||||
StartLimitInterval=5
|
||||
StartLimitBurst=10
|
||||
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
|
||||
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
|
||||
{{if .UserName}}User={{.UserName}}{{end}}
|
||||
{{if .Restart}}Restart={{.Restart}}{{end}}
|
||||
{{if .SuccessExitStatus}}SuccessExitStatus={{.SuccessExitStatus}}{{end}}
|
||||
ExecStart={{Path | cmdEscape}}{{range Arguments}} {{. | cmd}}{{end}}
|
||||
{{if WorkingDirectory}}WorkingDirectory={{WorkingDirectory | cmdEscape}}{{end}}
|
||||
{{if UserName}}User={{UserName}}{{end}}
|
||||
{{if Restart}}Restart={{Restart}}{{end}}
|
||||
{{if SuccessExitStatus}}SuccessExitStatus={{SuccessExitStatus}}{{end}}
|
||||
TimeoutStopSec=20
|
||||
RestartSec=120
|
||||
EnvironmentFile=-/etc/sysconfig/{{.Name}}
|
||||
EnvironmentFile=-/etc/sysconfig/{{Name}}
|
||||
Environment="ND_SYSTEMD_PRIORITY_LOGGING=1"
|
||||
|
||||
DevicePolicy=closed
|
||||
@ -260,7 +259,7 @@ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap
|
||||
{{if .WorkingDirectory}}ReadWritePaths={{.WorkingDirectory|cmdEscape}}{{end}}
|
||||
{{if WorkingDirectory}}ReadWritePaths={{WorkingDirectory | cmdEscape}}{{end}}
|
||||
ProtectSystem=full
|
||||
|
||||
[Install]
|
||||
|
||||
55
cmd/svc_test.go
Normal file
55
cmd/svc_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("systemdScript template", func() {
|
||||
systemdKeys := map[string]bool{
|
||||
"Description": true, "Path": true, "Name": true, "Dependencies": true,
|
||||
"Arguments": true, "ChRoot": true, "WorkingDirectory": true,
|
||||
"UserName": true, "ReloadSignal": true, "PIDFile": true,
|
||||
"LogDirectory": true, "OutputFileSupport": true, "LimitNOFILE": true,
|
||||
"Restart": true, "SuccessExitStatus": true, "EnvVars": true,
|
||||
}
|
||||
systemdFuncs := map[string]bool{"cmd": true, "cmdEscape": true}
|
||||
|
||||
actionRe := regexp.MustCompile(`\{\{(.*?)\}\}`)
|
||||
|
||||
parseAction := func(action string) (key string, funcs []string) {
|
||||
action = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(action, "-"), "-"))
|
||||
kw, rest, _ := strings.Cut(action, " ")
|
||||
switch kw {
|
||||
case "end", "else":
|
||||
return "", nil
|
||||
case "if", "range":
|
||||
return strings.TrimSpace(rest), nil
|
||||
}
|
||||
parts := strings.Split(action, "|")
|
||||
for _, p := range parts[1:] {
|
||||
funcs = append(funcs, strings.TrimSpace(p))
|
||||
}
|
||||
return strings.TrimSpace(parts[0]), funcs
|
||||
}
|
||||
|
||||
It("only references keys and functions the service library provides", func() {
|
||||
matches := actionRe.FindAllStringSubmatch(systemdScript, -1)
|
||||
Expect(matches).ToNot(BeEmpty())
|
||||
|
||||
for _, m := range matches {
|
||||
key, funcs := parseAction(m[1])
|
||||
if key != "" && key != "." {
|
||||
Expect(systemdKeys).To(HaveKey(key),
|
||||
"template action %q uses a key unknown to kardianos/service", m[0])
|
||||
}
|
||||
for _, fn := range funcs {
|
||||
Expect(systemdFuncs).To(HaveKey(fn),
|
||||
"template action %q uses an unknown pipeline function", m[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user