139 lines
3.0 KiB
Go
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) + `"`
|
|
}
|