diff --git a/config/config.go b/config/config.go index 5c99319..83d4223 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,9 @@ type Unit struct { Delay string `yaml:"delay,omitempty"` // e.g. "5s" Enabled bool `yaml:"enabled"` DisabledReason string `yaml:"disabled_reason,omitempty"` + Restart bool `yaml:"restart,omitempty"` // enable Restart=on-failure + RestartSec int `yaml:"restart_sec,omitempty"` // seconds between restarts + RestartRetries int `yaml:"restart_retries,omitempty"` // max restart attempts (StartLimitBurst) } // Config is the root config structure. diff --git a/systemd/generator.go b/systemd/generator.go index 6a89c72..0650191 100644 --- a/systemd/generator.go +++ b/systemd/generator.go @@ -20,8 +20,13 @@ Requires={{.}}{{end}} Type=simple ExecStart={{.ExecStart}} ExecStop={{.ExecStop}} +{{- if .Unit.Restart}} Restart=on-failure -RestartSec=5 +RestartSec={{.Unit.RestartSec}} +{{- if gt .Unit.RestartRetries 0}} +StartLimitBurst={{.Unit.RestartRetries}} +{{- end}} +{{- end}} [Install] WantedBy=multi-user.target @@ -38,8 +43,13 @@ After=default.target Type=simple ExecStart={{.ExecStart}} ExecStop={{.ExecStop}} +{{- if .Unit.Restart}} Restart=on-failure -RestartSec=5 +RestartSec={{.Unit.RestartSec}} +{{- if gt .Unit.RestartRetries 0}} +StartLimitBurst={{.Unit.RestartRetries}} +{{- end}} +{{- end}} [Install] WantedBy=default.target diff --git a/systemd/generator_test.go b/systemd/generator_test.go index 89c50ef..493d7aa 100644 --- a/systemd/generator_test.go +++ b/systemd/generator_test.go @@ -50,7 +50,6 @@ func TestGenerate_SystemUnit(t *testing.T) { "WantedBy=multi-user.target", "ExecStart=/usr/bin/podman start -a nginx", "ExecStop=/usr/bin/podman stop nginx", - "Restart=on-failure", "Generated by unitdore", } @@ -66,6 +65,9 @@ func TestGenerate_SystemUnit(t *testing.T) { if strings.Contains(content, "Requires=") { t.Errorf("Generate() podman system unit should not contain Requires= (podman is daemonless):\n%s", content) } + if strings.Contains(content, "Restart=") { + t.Errorf("Generate() restart should be disabled by default:\n%s", content) + } } func TestGenerate_UserUnit(t *testing.T) { @@ -100,6 +102,51 @@ func TestGenerate_UserUnit(t *testing.T) { } } +func TestGenerate_WithRestart(t *testing.T) { + t.Run("restart with retries", func(t *testing.T) { + u := config.Unit{ + Name: "nginx", + Runtime: "podman", + Enabled: true, + Restart: true, + RestartSec: 5, + RestartRetries: 3, + } + content, err := Generate(u, "", "") + if err != nil { + t.Fatalf("Generate() error: %v", err) + } + for _, want := range []string{"Restart=on-failure", "RestartSec=5", "StartLimitBurst=3"} { + if !strings.Contains(content, want) { + t.Errorf("Generate() missing %q in output:\n%s", want, content) + } + } + }) + + t.Run("restart without retries", func(t *testing.T) { + u := config.Unit{ + Name: "nginx", + Runtime: "podman", + Enabled: true, + Restart: true, + RestartSec: 10, + } + content, err := Generate(u, "", "") + if err != nil { + t.Fatalf("Generate() error: %v", err) + } + if !strings.Contains(content, "Restart=on-failure") { + t.Errorf("Generate() missing Restart=on-failure:\n%s", content) + } + if !strings.Contains(content, "RestartSec=10") { + t.Errorf("Generate() missing RestartSec=10:\n%s", content) + } + if strings.Contains(content, "StartLimitBurst") { + t.Errorf("Generate() should not contain StartLimitBurst when retries=0:\n%s", content) + } + }) +} + func TestGenerate_CustomCommand(t *testing.T) { u := config.Unit{ Name: "custom",