package systemd import ( "fmt" "strings" "text/template" "github.com/warkanum/unitdore/config" ) const systemUnitTemplate = `# /etc/systemd/system/{{.ServiceName}} # Generated by unitdore — do not edit manually [Unit] Description=Unitdore: {{.Unit.Name}} ({{.Unit.Runtime}}) After=network.target{{with .RuntimeService}} {{.}} Requires={{.}}{{end}} [Service] Type=simple ExecStart={{.ExecStart}} ExecStop={{.ExecStop}} Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target ` const userUnitTemplate = `# ~/.config/systemd/user/{{.ServiceName}} # Generated by unitdore — do not edit manually [Unit] Description=Unitdore: {{.Unit.Name}} ({{.Unit.Runtime}}) After=default.target [Service] Type=simple ExecStart={{.ExecStart}} ExecStop={{.ExecStop}} Restart=on-failure RestartSec=5 [Install] WantedBy=default.target ` type templateData struct { ServiceName string Unit config.Unit ExecStart string ExecStop string RuntimeService string } // ServiceName returns the systemd service name for a unit, with optional prefix/suffix. func ServiceName(u config.Unit, prefix, suffix string) string { return fmt.Sprintf("unitdore-%s%s%s.service", prefix, u.Name, suffix) } // Generate produces the .service file content for a unit. func Generate(u config.Unit, prefix, suffix string) (string, error) { execStart, execStop := buildExecCommands(u) data := templateData{ ServiceName: ServiceName(u, prefix, suffix), Unit: u, ExecStart: execStart, ExecStop: execStop, RuntimeService: runtimeService(u.Runtime), } 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 "" } func buildExecCommands(u config.Unit) (start, stop string) { // If a custom command is provided, use it directly if u.Command != "" { return u.Command, "" } switch u.Runtime { case "podman": start = fmt.Sprintf("/usr/bin/podman start -a %s", u.Name) stop = fmt.Sprintf("/usr/bin/podman stop %s", u.Name) case "docker": start = fmt.Sprintf("/usr/bin/docker start %s", u.Name) stop = fmt.Sprintf("/usr/bin/docker stop %s", u.Name) default: start = fmt.Sprintf("/usr/bin/%s start %s", u.Runtime, u.Name) stop = fmt.Sprintf("/usr/bin/%s stop %s", u.Runtime, u.Name) } return start, stop }