--- /dev/null
+// MsgSyncReq: enable and start services on the remote host (systemd)
+
+// Copyright (C) 2021 Simon Ruderich
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+package sync
+
+import (
+ "fmt"
+ "strings"
+
+ "ruderich.org/simon/safcm"
+)
+
+func (s *Sync) syncServicesSystemd() error {
+ s.log.Debugf("services: detected systemd")
+
+ s.log.Debugf("services: checking %s",
+ strings.Join(s.req.Services, " "))
+ services, err := s.systemdServiceState(s.req.Services)
+ if err != nil {
+ return err
+ }
+
+ var start, enable []string
+ for _, name := range s.req.Services {
+ var change safcm.ServiceChange
+
+ x := services[name]
+ if x.ActiveState != "active" {
+ start = append(start, name)
+ change.Started = true
+ }
+ if x.UnitFileState != "enabled" {
+ enable = append(enable, name)
+ change.Enabled = true
+ }
+
+ if change.Started || change.Enabled {
+ change.Name = name
+ s.resp.ServiceChanges = append(s.resp.ServiceChanges,
+ change)
+ }
+ }
+ if len(start) == 0 && len(enable) == 0 {
+ return nil
+ }
+
+ if s.req.DryRun {
+ return nil
+ }
+
+ // Reload service files which were possibly changed during file sync
+ // or package installation
+ _, _, err = s.cmd.Run("services", "/bin/systemctl", "daemon-reload")
+ if err != nil {
+ return err
+ }
+ if len(start) != 0 {
+ s.log.Verbosef("services: starting %s",
+ strings.Join(start, " "))
+ _, _, err := s.cmd.Run("services", append([]string{
+ "/bin/systemctl", "start", "--",
+ }, start...)...)
+ if err != nil {
+ return err
+ }
+ }
+ if len(enable) != 0 {
+ s.log.Verbosef("services: enabling %s",
+ strings.Join(enable, " "))
+ _, _, err := s.cmd.Run("services", append([]string{
+ "/bin/systemctl", "enable", "--",
+ }, enable...)...)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+type SystemdService struct {
+ ActiveState string
+ UnitFileState string
+}
+
+func (s *Sync) systemdServiceState(services []string) (
+ map[string]SystemdService, error) {
+
+ out, _, err := s.cmd.Run("services", append([]string{
+ "/bin/systemctl",
+ "show",
+ "--property=ActiveState,UnitFileState,LoadError",
+ "--",
+ }, services...)...)
+ if err != nil {
+ return nil, err
+ }
+
+ i := 0
+
+ res := make(map[string]SystemdService)
+ for _, block := range strings.Split(string(out), "\n\n") {
+ lines := strings.Split(strings.TrimSpace(block), "\n")
+ if len(lines) != 3 {
+ return nil, fmt.Errorf("invalid systemctl output: %q",
+ block)
+ }
+
+ var service SystemdService
+ for _, x := range lines {
+ const (
+ activePrefix = "ActiveState="
+ unitPrefix = "UnitFileState="
+ errorPrefix = "LoadError="
+ )
+
+ if strings.HasPrefix(x, activePrefix) {
+ service.ActiveState = strings.TrimPrefix(x,
+ activePrefix)
+ } else if strings.HasPrefix(x, unitPrefix) {
+ service.UnitFileState = strings.TrimPrefix(x,
+ unitPrefix)
+ } else if strings.HasPrefix(x, errorPrefix) {
+ x := strings.TrimPrefix(x, errorPrefix)
+ // Older systemd versions (e.g. 237) add empty
+ // quotes even if there is no error
+ if x != "" && x != ` ""` {
+ return nil, fmt.Errorf(
+ "systemd unit %q not found",
+ services[i])
+ }
+ } else {
+ return nil, fmt.Errorf(
+ "invalid systemctl show line %q", x)
+ }
+ }
+ res[services[i]] = service
+
+ i++
+ }
+
+ return res, nil
+}