Files
RenewCMS/internal/domain/update/updateInstall.go
T

139 lines
3.0 KiB
Go

package update
import (
"errors"
"strconv"
"strings"
)
var (
ErrNeedsLinux = errors.New("Browser updates need Linux with systemd.")
ErrNeedsSystemd = errors.New("Browser updates need systemd, which this machine is not running.")
ErrNeedsRoot = errors.New("Run this command as root, for example with sudo.")
ErrServiceNotFound = errors.New("Could not find the RenewCMS service. Pass --service, --binary and --data-dir.")
ErrDataDirNotFound = errors.New("Could not find the data directory. Pass --data-dir.")
ErrInvalidPort = errors.New("The port must be a number between 1 and 65535.")
)
const (
OSLinux = "linux"
pathUnitDescription = "Watch for a RenewCMS restart request"
)
type ServiceDescription struct {
Name string
BinaryPath string
DataDir string
EnvironmentFile string
}
type InstallOptions struct {
Service string
Binary string
DataDir string
Port string
}
type MachineProbe interface {
OS() string
SystemdRunning() bool
IsRoot() bool
}
type ServiceLocator interface {
Locate(name string) (ServiceDescription, bool)
PortFromEnvironmentFile(path string) (string, bool)
}
type UnitWriter interface {
Write(name string, content string) error
Remove(name string) (bool, error)
}
type UnitManager interface {
DaemonReload() error
EnableNow(unit string) error
DisableNow(unit string) error
}
type HelperStatusWriter interface {
Write(dataDir string, status HelperStatus) error
Remove(dataDir string) (bool, error)
}
func CheckInstallEnvironment(probe MachineProbe) error {
if probe.OS() != OSLinux {
return ErrNeedsLinux
}
if !probe.SystemdRunning() {
return ErrNeedsSystemd
}
if !probe.IsRoot() {
return ErrNeedsRoot
}
return nil
}
func ValidatePort(port string) error {
number, err := strconv.Atoi(port)
if err != nil || number < 1 || number > 65535 {
return ErrInvalidPort
}
return nil
}
func RenderPathUnit(layout HelperLayout) string {
lines := []string{
"[Unit]",
"Description=" + pathUnitDescription,
"",
"[Path]",
"PathExists=" + layout.RestartRequestPath(),
"Unit=" + ServiceUnitName,
"",
"[Install]",
"WantedBy=multi-user.target",
"",
}
return strings.Join(lines, "\n")
}
func RenderServiceUnit(options InstallOptions) string {
command := []string{
quoteUnitArgument(options.Binary),
"apply-update",
"--service", quoteUnitArgument(options.Service),
"--binary", quoteUnitArgument(options.Binary),
"--data-dir", quoteUnitArgument(options.DataDir),
"--port", options.Port,
}
lines := []string{
"[Unit]",
"Description=RenewCMS update helper",
"",
"[Service]",
"Type=oneshot",
"User=root",
"ExecStart=" + strings.Join(command, " "),
"",
}
return strings.Join(lines, "\n")
}
var unitArgumentEscaper = strings.NewReplacer(
`\`, `\\`,
`"`, `\"`,
`%`, `%%`,
`$`, `$$`,
"\n", `\n`,
"\r", `\r`,
"\t", `\t`,
)
func quoteUnitArgument(value string) string {
if !strings.ContainsAny(value, " \t\r\n\"'\\%$;") {
return value
}
return `"` + unitArgumentEscaper.Replace(value) + `"`
}