5 Commits
v0.0.7 ... main

Author SHA1 Message Date
e6ef6e11d6 chore(release): update package version to 0.0.9
All checks were successful
Release / test (push) Successful in -33m2s
Release / release (push) Successful in -32m47s
Release / pkg-aur (push) Successful in -33m46s
Release / pkg-deb (push) Successful in -33m19s
Release / pkg-rpm (push) Successful in -32m33s
2026-04-12 11:08:47 +02:00
f9dcb0b561 Merge branch 'main' of git.warky.dev:wdevs/unitdore 2026-04-12 11:08:38 +02:00
69069a2196 fix(podman): handle invalid JSON output gracefully 2026-04-12 11:08:20 +02:00
Hein
2efccc5d4f chore(release): update package version to 0.0.8
Some checks failed
Release / test (push) Successful in -30m10s
Release / release (push) Successful in -30m3s
Release / pkg-aur (push) Successful in -29m57s
Release / pkg-rpm (push) Failing after 14m44s
Release / pkg-deb (push) Failing after 14m51s
2026-04-10 09:38:59 +02:00
Hein
b58373ad2f feat(config): add restart options for unit configuration
* include Restart, RestartSec, and RestartRetries fields
* update service file generation to support restart settings
* add tests for restart behavior in unit generation
2026-04-10 09:38:45 +02:00
7 changed files with 68 additions and 7 deletions

View File

@@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var version = "0.0.7" var version = "0.0.9"
var configPath string var configPath string

View File

@@ -20,6 +20,9 @@ type Unit struct {
Delay string `yaml:"delay,omitempty"` // e.g. "5s" Delay string `yaml:"delay,omitempty"` // e.g. "5s"
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
DisabledReason string `yaml:"disabled_reason,omitempty"` 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. // Config is the root config structure.

View File

@@ -1,6 +1,6 @@
# Maintainer: Hein (Warky Devs) <hein@warky.dev> # Maintainer: Hein (Warky Devs) <hein@warky.dev>
pkgname=unitdore pkgname=unitdore
pkgver=0.0.7 pkgver=0.0.9
pkgrel=1 pkgrel=1
pkgdesc="A door you open and close for container units — manage containers via systemd" pkgdesc="A door you open and close for container units — manage containers via systemd"
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')

View File

@@ -1,5 +1,5 @@
Name: unitdore Name: unitdore
Version: 0.0.7 Version: 0.0.9
Release: 1%{?dist} Release: 1%{?dist}
Summary: Manage container units via systemd Summary: Manage container units via systemd

View File

@@ -33,7 +33,8 @@ func (p *Podman) ListRunning() ([]Container, error) {
var raw []podmanContainer var raw []podmanContainer
if err := json.Unmarshal(out, &raw); err != nil { if err := json.Unmarshal(out, &raw); err != nil {
return nil, fmt.Errorf("parsing podman output: %w", err) // Podman installed but output is not valid JSON (e.g. OCI runtime misconfigured)
return nil, nil
} }
var containers []Container var containers []Container

View File

@@ -20,8 +20,13 @@ Requires={{.}}{{end}}
Type=simple Type=simple
ExecStart={{.ExecStart}} ExecStart={{.ExecStart}}
ExecStop={{.ExecStop}} ExecStop={{.ExecStop}}
{{- if .Unit.Restart}}
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec={{.Unit.RestartSec}}
{{- if gt .Unit.RestartRetries 0}}
StartLimitBurst={{.Unit.RestartRetries}}
{{- end}}
{{- end}}
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -38,8 +43,13 @@ After=default.target
Type=simple Type=simple
ExecStart={{.ExecStart}} ExecStart={{.ExecStart}}
ExecStop={{.ExecStop}} ExecStop={{.ExecStop}}
{{- if .Unit.Restart}}
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec={{.Unit.RestartSec}}
{{- if gt .Unit.RestartRetries 0}}
StartLimitBurst={{.Unit.RestartRetries}}
{{- end}}
{{- end}}
[Install] [Install]
WantedBy=default.target WantedBy=default.target

View File

@@ -50,7 +50,6 @@ func TestGenerate_SystemUnit(t *testing.T) {
"WantedBy=multi-user.target", "WantedBy=multi-user.target",
"ExecStart=/usr/bin/podman start -a nginx", "ExecStart=/usr/bin/podman start -a nginx",
"ExecStop=/usr/bin/podman stop nginx", "ExecStop=/usr/bin/podman stop nginx",
"Restart=on-failure",
"Generated by unitdore", "Generated by unitdore",
} }
@@ -66,6 +65,9 @@ func TestGenerate_SystemUnit(t *testing.T) {
if strings.Contains(content, "Requires=") { if strings.Contains(content, "Requires=") {
t.Errorf("Generate() podman system unit should not contain Requires= (podman is daemonless):\n%s", content) 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) { 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) { func TestGenerate_CustomCommand(t *testing.T) {
u := config.Unit{ u := config.Unit{
Name: "custom", Name: "custom",