package systemd import ( "fmt" "strings" "text/template" "github.com/warkanum/unitdore/config" "github.com/warkanum/unitdore/service" ) const systemUnitTemplate = `# /etc/systemd/system/{{.ServiceName}} # Generated by unitdore — do not edit manually [Unit] Description=Unitdore: {{.Unit.Name}} ({{.Label}}) After=network.target{{with .RuntimeService}} {{.}} Requires={{.}}{{end}} [Service] Type={{.ServiceType}} {{- if eq .ServiceType "oneshot"}} RemainAfterExit=yes {{- end}} {{- with .WorkingDir}} WorkingDirectory={{.}} {{- end}} ExecStart={{.ExecStart}} ExecStop={{if .ExecStop}}-{{.ExecStop}}{{end}} {{- if .Unit.Restart}} Restart=on-failure RestartSec={{.Unit.RestartSec}} {{- if gt .Unit.RestartRetries 0}} StartLimitBurst={{.Unit.RestartRetries}} {{- end}} {{- end}} [Install] WantedBy=multi-user.target ` const userUnitTemplate = `# ~/.config/systemd/user/{{.ServiceName}} # Generated by unitdore — do not edit manually [Unit] Description=Unitdore: {{.Unit.Name}} ({{.Label}}) After=default.target [Service] Type={{.ServiceType}} {{- if eq .ServiceType "oneshot"}} RemainAfterExit=yes {{- end}} {{- with .WorkingDir}} WorkingDirectory={{.}} {{- end}} ExecStart={{.ExecStart}} ExecStop={{if .ExecStop}}-{{.ExecStop}}{{end}} {{- if .Unit.Restart}} Restart=on-failure RestartSec={{.Unit.RestartSec}} {{- if gt .Unit.RestartRetries 0}} StartLimitBurst={{.Unit.RestartRetries}} {{- end}} {{- end}} [Install] WantedBy=default.target ` type templateData struct { ServiceName string Unit config.Unit ExecStart string ExecStop string RuntimeService string Label string // shown in Description ServiceType string // simple | oneshot WorkingDir string } // ServiceName returns the systemd service name for a unit, with optional prefix/suffix. func ServiceName(u config.Unit, prefix, suffix string) string { return service.BaseName(u, prefix, suffix) + ".service" } // Generate produces the .service file content for a unit. func Generate(u config.Unit, prefix, suffix string) (string, error) { if err := service.Validate(u); err != nil { return "", err } spec := service.Resolve(u) data := templateData{ ServiceName: ServiceName(u, prefix, suffix), Unit: u, ExecStart: spec.Start, ExecStop: spec.Stop, RuntimeService: runtimeService(u.Runtime), Label: spec.Label, ServiceType: spec.Type, WorkingDir: spec.WorkingDir, } tmplStr := systemUnitTemplate if u.User != "" { tmplStr = userUnitTemplate } tmpl, err := template.New("unit").Parse(tmplStr) if err != nil { return "", fmt.Errorf("parsing template: %w", err) } var buf strings.Builder if err := tmpl.Execute(&buf, data); err != nil { return "", fmt.Errorf("rendering template: %w", err) } return buf.String(), nil } // runtimeService returns the systemd service name that must be running for the // given container runtime, or "" if none is required (e.g. daemonless podman). func runtimeService(runtime string) string { if runtime == "docker" { return "docker.service" } return "" }