util: Add a simple template system for systemd unit files

Just the basics for what we need, nothing more. Not intended as a
general-purpose library for use elsewhere.
This commit is contained in:
James Shubin
2024-10-24 17:29:00 -04:00
parent 202a8e1fba
commit 2c967e3897

134
util/systemd_unit.go Normal file
View File

@@ -0,0 +1,134 @@
// Mgmt
// Copyright (C) 2013-2024+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package util
import (
"fmt"
)
// UnitData is the data struct used to build a systemd unit file. This isn't an
// exhaustive representation of what's possible, but is meant to handle most
// common cases. Alternatively we could have used the
// github.com/coreos/go-systemd/v22/unit library, but it didn't provide much
// value. More documentation on these fields can be seen at:
// https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
type UnitData struct {
// Description field for the unit file.
Description string
// Documentation field for the unit file.
Documentation string
// After makes this happen after that target is ready. Usually you want
// "network.target" or "systemd-networkd.service" here I think.
After []string
// Type is the type of service to run, such as "oneshot".
Type string
// ExecStart is the main command to run.
ExecStart string
// RestartSec is the number of seconds to wait between restarts.
RestartSec string
// Restart is the restart policy. Usually you want "always".
Restart string
// RemainAfterExit can be set to true to make it look like the service
// is still active if it exits successfully.
RemainAfterExit bool
// StandardOutput specifies what happens with stdout.
StandardOutput string
// StandardError specifies what happens with stderr.
StandardError string
// WantedBy makes this a dependency of this target. Usually you want
// "multi-user.target" here.
WantedBy []string
}
// Template uses the UnitData to build a systemd unit file. This API is subject
// to change, don't assume the output is stable either.
// TODO: Should this return an error or not?
func (obj *UnitData) Template() (string, error) {
data := ""
data += "[Unit]\n"
if obj.Description != "" {
data += fmt.Sprintf("Description=%s\n", obj.Description)
}
if obj.Documentation != "" {
data += fmt.Sprintf("Documentation=%s\n", obj.Documentation)
}
for _, x := range obj.After {
if x == "" {
continue
}
data += fmt.Sprintf("After=%s\n", x)
}
data += "\n"
data += "[Service]\n"
if obj.Type != "" {
data += fmt.Sprintf("Type=%s\n", obj.Type)
}
if obj.ExecStart != "" {
data += fmt.Sprintf("ExecStart=%s\n", obj.ExecStart)
}
if obj.RestartSec != "" {
data += fmt.Sprintf("RestartSec=%s\n", obj.RestartSec)
}
if obj.Restart != "" {
data += fmt.Sprintf("Restart=%s\n", obj.Restart)
}
if obj.RemainAfterExit {
data += "RemainAfterExit=yes\n"
}
if obj.StandardOutput != "" {
data += fmt.Sprintf("StandardOutput=%s\n", obj.StandardOutput)
}
if obj.StandardError != "" {
data += fmt.Sprintf("StandardError=%s\n", obj.StandardError)
}
data += "\n"
data += "[Install]\n"
for _, x := range obj.WantedBy {
if x == "" {
continue
}
data += fmt.Sprintf("WantedBy=%s\n", x)
}
return data, nil
}