]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - cmd/safcm-remote/sync/services_systemd.go
68bbc7d3e08c07e1e665e2cc7ac175d25a1e7679
[safcm/safcm.git] / cmd / safcm-remote / sync / services_systemd.go
1 // MsgSyncReq: enable and start services on the remote host (systemd)
2
3 // Copyright (C) 2021  Simon Ruderich
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 package sync
19
20 import (
21         "fmt"
22         "strings"
23
24         "ruderich.org/simon/safcm"
25 )
26
27 func (s *Sync) syncServicesSystemd() error {
28         s.log.Debugf("services: detected systemd")
29
30         s.log.Debugf("services: checking %s",
31                 strings.Join(s.req.Services, " "))
32         services, err := s.systemdServiceState(s.req.Services)
33         if err != nil {
34                 return err
35         }
36
37         var start, enable []string
38         for _, name := range s.req.Services {
39                 var change safcm.ServiceChange
40
41                 x := services[name]
42                 if x.ActiveState != "active" {
43                         start = append(start, name)
44                         change.Started = true
45                 }
46                 if x.UnitFileState != "enabled" {
47                         enable = append(enable, name)
48                         change.Enabled = true
49                 }
50
51                 if change.Started || change.Enabled {
52                         change.Name = name
53                         s.resp.ServiceChanges = append(s.resp.ServiceChanges,
54                                 change)
55                 }
56         }
57         if len(start) == 0 && len(enable) == 0 {
58                 return nil
59         }
60
61         if s.req.DryRun {
62                 return nil
63         }
64
65         // Reload service files which were possibly changed during file sync
66         // or package installation
67         _, _, err = s.cmd.Run("services", "/bin/systemctl", "daemon-reload")
68         if err != nil {
69                 return err
70         }
71         if len(start) != 0 {
72                 s.log.Verbosef("services: starting %s",
73                         strings.Join(start, " "))
74                 _, _, err := s.cmd.Run("services", append([]string{
75                         "/bin/systemctl", "start", "--",
76                 }, start...)...)
77                 if err != nil {
78                         return err
79                 }
80         }
81         if len(enable) != 0 {
82                 s.log.Verbosef("services: enabling %s",
83                         strings.Join(enable, " "))
84                 _, _, err := s.cmd.Run("services", append([]string{
85                         "/bin/systemctl", "enable", "--",
86                 }, enable...)...)
87                 if err != nil {
88                         return err
89                 }
90         }
91
92         return nil
93 }
94
95 type SystemdService struct {
96         ActiveState   string
97         UnitFileState string
98 }
99
100 func (s *Sync) systemdServiceState(services []string) (
101         map[string]SystemdService, error) {
102
103         out, _, err := s.cmd.Run("services", append([]string{
104                 "/bin/systemctl",
105                 "show",
106                 "--property=ActiveState,UnitFileState,LoadError",
107                 "--",
108         }, services...)...)
109         if err != nil {
110                 return nil, err
111         }
112
113         i := 0
114
115         res := make(map[string]SystemdService)
116         for _, block := range strings.Split(string(out), "\n\n") {
117                 lines := strings.Split(strings.TrimSpace(block), "\n")
118                 if len(lines) != 3 {
119                         return nil, fmt.Errorf("invalid systemctl output: %q",
120                                 block)
121                 }
122
123                 var service SystemdService
124                 for _, x := range lines {
125                         const (
126                                 activePrefix = "ActiveState="
127                                 unitPrefix   = "UnitFileState="
128                                 errorPrefix  = "LoadError="
129                         )
130
131                         if strings.HasPrefix(x, activePrefix) {
132                                 service.ActiveState = strings.TrimPrefix(x,
133                                         activePrefix)
134                         } else if strings.HasPrefix(x, unitPrefix) {
135                                 service.UnitFileState = strings.TrimPrefix(x,
136                                         unitPrefix)
137                         } else if strings.HasPrefix(x, errorPrefix) {
138                                 x := strings.TrimPrefix(x, errorPrefix)
139                                 // Older systemd versions (e.g. 237) add empty
140                                 // quotes even if there is no error
141                                 if x != "" && x != ` ""` {
142                                         return nil, fmt.Errorf(
143                                                 "systemd unit %q not found",
144                                                 services[i])
145                                 }
146                         } else {
147                                 return nil, fmt.Errorf(
148                                         "invalid systemctl show line %q", x)
149                         }
150                 }
151                 res[services[i]] = service
152
153                 i++
154         }
155
156         return res, nil
157 }