]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - remote/sync/services_systemd.go
go fmt
[safcm/safcm.git] / 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: checking %s (systemd detected)",
29                 strings.Join(s.req.Services, " "))
30         services, err := s.systemdServiceState(s.req.Services)
31         if err != nil {
32                 return err
33         }
34
35         var start, enable []string
36         for _, name := range s.req.Services {
37                 var change safcm.ServiceChange
38
39                 x := services[name]
40                 if x.ActiveState != "active" {
41                         start = append(start, name)
42                         change.Started = true
43                 }
44                 if x.UnitFileState != "enabled" {
45                         enable = append(enable, name)
46                         change.Enabled = true
47                 }
48
49                 if change.Started || change.Enabled {
50                         change.Name = name
51                         s.resp.ServiceChanges = append(s.resp.ServiceChanges,
52                                 change)
53                 }
54         }
55         if len(start) == 0 && len(enable) == 0 {
56                 return nil
57         }
58
59         if s.req.DryRun {
60                 return nil
61         }
62
63         // Reload service files which were possibly changed during file sync
64         // or package installation
65         _, _, err = s.cmd.Run("services", "/bin/systemctl", "daemon-reload")
66         if err != nil {
67                 return err
68         }
69         if len(start) != 0 {
70                 s.log.Verbosef("services: starting %s",
71                         strings.Join(start, " "))
72                 _, _, err := s.cmd.Run("services", append([]string{
73                         "/bin/systemctl", "start", "--",
74                 }, start...)...)
75                 if err != nil {
76                         return err
77                 }
78         }
79         if len(enable) != 0 {
80                 s.log.Verbosef("services: enabling %s",
81                         strings.Join(enable, " "))
82                 _, _, err := s.cmd.Run("services", append([]string{
83                         "/bin/systemctl", "enable", "--",
84                 }, enable...)...)
85                 if err != nil {
86                         return err
87                 }
88         }
89
90         return nil
91 }
92
93 type SystemdService struct {
94         ActiveState   string
95         UnitFileState string
96 }
97
98 func (s *Sync) systemdServiceState(services []string) (
99         map[string]SystemdService, error) {
100
101         out, _, err := s.cmd.Run("services", append([]string{
102                 "/bin/systemctl",
103                 "show",
104                 "--property=ActiveState,UnitFileState,LoadError",
105                 "--",
106         }, services...)...)
107         if err != nil {
108                 return nil, err
109         }
110
111         i := 0
112
113         res := make(map[string]SystemdService)
114         for _, block := range strings.Split(string(out), "\n\n") {
115                 lines := strings.Split(strings.TrimSpace(block), "\n")
116                 if len(lines) != 3 {
117                         return nil, fmt.Errorf("invalid systemctl output: %q",
118                                 block)
119                 }
120
121                 var service SystemdService
122                 for _, x := range lines {
123                         const (
124                                 activePrefix = "ActiveState="
125                                 unitPrefix   = "UnitFileState="
126                                 errorPrefix  = "LoadError="
127                         )
128
129                         if strings.HasPrefix(x, activePrefix) {
130                                 service.ActiveState = strings.TrimPrefix(x,
131                                         activePrefix)
132                         } else if strings.HasPrefix(x, unitPrefix) {
133                                 service.UnitFileState = strings.TrimPrefix(x,
134                                         unitPrefix)
135                         } else if strings.HasPrefix(x, errorPrefix) {
136                                 x := strings.TrimPrefix(x, errorPrefix)
137                                 // Older systemd versions (e.g. 237) add empty
138                                 // quotes even if there is no error
139                                 if x != "" && x != ` ""` {
140                                         return nil, fmt.Errorf(
141                                                 "systemd unit %q not found",
142                                                 services[i])
143                                 }
144                         } else {
145                                 return nil, fmt.Errorf(
146                                         "invalid systemctl show line %q", x)
147                         }
148                 }
149                 res[services[i]] = service
150
151                 i++
152         }
153
154         return res, nil
155 }