package update import ( domain "RenewCMS/internal/domain/update" "os" "path/filepath" "strconv" "strings" ) const ( systemUnitDirectory = "/etc/systemd/system" unitFilePermissions = 0o644 unitFileSuffix = ".service" ) type SystemdUnits struct { directory string } var ( _ domain.UnitWriter = &SystemdUnits{} _ domain.ServiceLocator = &SystemdUnits{} ) func NewSystemdUnits() *SystemdUnits { return NewSystemdUnitsIn(systemUnitDirectory) } func NewSystemdUnitsIn(directory string) *SystemdUnits { return &SystemdUnits{directory: directory} } func (u *SystemdUnits) Write(name string, content string) error { return os.WriteFile(filepath.Join(u.directory, name), []byte(content), unitFilePermissions) } func (u *SystemdUnits) Remove(name string) (bool, error) { return removeFile(filepath.Join(u.directory, name)) } func (u *SystemdUnits) Locate(name string) (domain.ServiceDescription, bool) { content, err := os.ReadFile(filepath.Join(u.directory, name+unitFileSuffix)) if err != nil { return domain.ServiceDescription{}, false } description := ParseServiceUnit(string(content)) description.Name = name return description, true } func (u *SystemdUnits) PortFromEnvironmentFile(path string) (string, bool) { if path == "" { return "", false } content, err := os.ReadFile(path) if err != nil { return "", false } return portFromEnvironment(string(content)) } func ParseServiceUnit(content string) domain.ServiceDescription { var description domain.ServiceDescription for _, line := range strings.Split(content, "\n") { key, value, found := strings.Cut(strings.TrimSpace(line), "=") if !found { continue } switch key { case "ExecStart": description.BinaryPath = firstUnitPath(value, "@-:+!") case "EnvironmentFile": description.EnvironmentFile = firstUnitPath(value, "-") case "ReadWritePaths": if description.DataDir == "" { description.DataDir = firstUnitPath(value, "-+!") } } } return description } func firstUnitPath(value string, prefixes string) string { fields := strings.Fields(value) if len(fields) == 0 { return "" } return strings.Trim(strings.TrimLeft(fields[0], prefixes), `"`) } func portFromEnvironment(content string) (string, bool) { for _, line := range strings.Split(content, "\n") { key, value, found := strings.Cut(strings.TrimSpace(line), "=") if !found || strings.TrimSpace(key) != "PORT" { continue } port := strings.Trim(strings.TrimSpace(value), `"'`) if _, err := strconv.Atoi(port); err == nil { return port, true } } return "", false }