// 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 . package sync import ( "fmt" "strings" "ruderich.org/simon/safcm" ) func (s *Sync) syncServicesSystemd() error { s.log.Debugf("services: checking %s (systemd detected)", 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 }