]>
Commit | Line | Data |
---|---|---|
d2ab5f19 DM |
1 | use failure::*; |
2 | ||
3 | use crate::tools; | |
dc9a007b DM |
4 | use crate::api_schema::schema::*; |
5 | use crate::api_schema::router::*; | |
d2ab5f19 DM |
6 | use serde_json::{json, Value}; |
7 | ||
8 | use std::sync::Arc; | |
d2ab5f19 DM |
9 | use std::process::{Command, Stdio}; |
10 | ||
11 | static SERVICE_NAME_LIST: [&str; 6] = [ | |
12 | "proxmox-backup", | |
13 | "sshd", | |
14 | "syslog", | |
15 | "cron", | |
16 | "postfix", | |
17 | "systemd-timesyncd", | |
18 | ]; | |
19 | ||
48849593 | 20 | fn real_service_name(service: &str) -> &str { |
d2ab5f19 DM |
21 | |
22 | // since postfix package 3.1.0-3.1 the postfix unit is only here | |
23 | // to manage subinstances, of which the default is called "-". | |
24 | // This is where we look for the daemon status | |
25 | ||
48849593 DM |
26 | if service == "postfix" { |
27 | "postfix@-" | |
28 | } else { | |
29 | service | |
30 | } | |
31 | } | |
32 | ||
33 | fn get_full_service_state(service: &str) -> Result<Value, Error> { | |
34 | ||
35 | let real_service_name = real_service_name(service); | |
d2ab5f19 DM |
36 | |
37 | let mut child = Command::new("/bin/systemctl") | |
38 | .args(&["show", real_service_name]) | |
39 | .stdout(Stdio::piped()) | |
40 | .spawn()?; | |
41 | ||
42 | use std::io::{BufRead,BufReader}; | |
43 | ||
44 | let mut result = json!({}); | |
45 | ||
46 | if let Some(ref mut stdout) = child.stdout { | |
47 | for line in BufReader::new(stdout).lines() { | |
48 | match line { | |
49 | Ok(line) => { | |
50 | let mut iter = line.splitn(2, '='); | |
51 | let key = iter.next(); | |
52 | let value = iter.next(); | |
53 | if let (Some(key), Some(value)) = (key, value) { | |
54 | result[key] = Value::from(value); | |
55 | } | |
56 | } | |
57 | Err(err) => { | |
58 | log::error!("reading service config failed: {}", err); | |
59 | let _ = child.kill(); | |
60 | break; | |
61 | } | |
62 | } | |
63 | } | |
64 | } | |
65 | ||
66 | let status = child.wait().unwrap(); | |
67 | if !status.success() { | |
68 | bail!("systemctl show failed with {}", status); | |
69 | } | |
70 | ||
71 | Ok(result) | |
72 | } | |
73 | ||
48849593 DM |
74 | fn json_service_state(service: &str, status: Value) -> Value { |
75 | ||
76 | if let Some(desc) = status["Description"].as_str() { | |
77 | let name = status["Name"].as_str().unwrap_or(service); | |
78 | let state = status["SubState"].as_str().unwrap_or("unknown"); | |
79 | return json!({ | |
80 | "service": service, | |
81 | "name": name, | |
82 | "desc": desc, | |
83 | "state": state, | |
84 | }); | |
85 | } | |
86 | ||
87 | Value::Null | |
88 | } | |
89 | ||
90 | ||
d2ab5f19 | 91 | fn list_services( |
9f49fe1d | 92 | _param: Value, |
d2ab5f19 | 93 | _info: &ApiMethod, |
9f49fe1d | 94 | _rpcenv: &mut RpcEnvironment, |
d2ab5f19 DM |
95 | ) -> Result<Value, Error> { |
96 | ||
97 | let mut list = vec![]; | |
98 | ||
99 | for service in &SERVICE_NAME_LIST { | |
100 | match get_full_service_state(service) { | |
101 | Ok(status) => { | |
48849593 DM |
102 | let state = json_service_state(service, status); |
103 | if state != Value::Null { | |
104 | list.push(state); | |
d2ab5f19 DM |
105 | } |
106 | } | |
107 | Err(err) => log::error!("{}", err), | |
108 | } | |
109 | } | |
110 | ||
111 | Ok(Value::from(list)) | |
112 | } | |
113 | ||
48849593 DM |
114 | fn get_service_state( |
115 | param: Value, | |
116 | _info: &ApiMethod, | |
9f49fe1d | 117 | _rpcenv: &mut RpcEnvironment, |
48849593 DM |
118 | ) -> Result<Value, Error> { |
119 | ||
120 | let service = tools::required_string_param(¶m, "service")?; | |
121 | ||
122 | if !SERVICE_NAME_LIST.contains(&service) { | |
123 | bail!("unknown service name '{}'", service); | |
124 | } | |
125 | ||
126 | let status = get_full_service_state(service)?; | |
127 | ||
128 | Ok(json_service_state(service, status)) | |
129 | } | |
130 | ||
131 | fn run_service_command(service: &str, cmd: &str) -> Result<Value, Error> { | |
132 | ||
133 | // fixme: run background worker (fork_worker) ??? | |
134 | ||
135 | match cmd { | |
136 | "start"|"stop"|"restart"|"reload" => {}, | |
137 | _ => bail!("unknown service command '{}'", cmd), | |
138 | } | |
139 | ||
140 | if service == "proxmox-backup" { | |
141 | if cmd != "restart" { | |
142 | bail!("invalid service cmd '{} {}'", service, cmd); | |
143 | } | |
144 | } | |
145 | ||
146 | let real_service_name = real_service_name(service); | |
147 | ||
148 | let status = Command::new("/bin/systemctl") | |
149 | .args(&[cmd, real_service_name]) | |
150 | .status()?; | |
151 | ||
152 | if !status.success() { | |
153 | bail!("systemctl {} failed with {}", cmd, status); | |
154 | } | |
155 | ||
156 | Ok(Value::Null) | |
157 | } | |
158 | ||
159 | fn start_service( | |
160 | param: Value, | |
161 | _info: &ApiMethod, | |
9f49fe1d | 162 | _rpcenv: &mut RpcEnvironment, |
48849593 DM |
163 | ) -> Result<Value, Error> { |
164 | ||
165 | let service = tools::required_string_param(¶m, "service")?; | |
166 | ||
167 | log::info!("starting service {}", service); | |
168 | ||
169 | run_service_command(service, "start") | |
170 | } | |
171 | ||
172 | fn stop_service( | |
173 | param: Value, | |
174 | _info: &ApiMethod, | |
9f49fe1d | 175 | _rpcenv: &mut RpcEnvironment, |
48849593 DM |
176 | ) -> Result<Value, Error> { |
177 | ||
178 | let service = tools::required_string_param(¶m, "service")?; | |
179 | ||
180 | log::info!("stoping service {}", service); | |
181 | ||
182 | run_service_command(service, "stop") | |
183 | } | |
184 | ||
185 | fn restart_service( | |
186 | param: Value, | |
187 | _info: &ApiMethod, | |
9f49fe1d | 188 | _rpcenv: &mut RpcEnvironment, |
48849593 DM |
189 | ) -> Result<Value, Error> { |
190 | ||
191 | let service = tools::required_string_param(¶m, "service")?; | |
192 | ||
193 | log::info!("re-starting service {}", service); | |
194 | ||
195 | run_service_command(service, "restart") | |
196 | } | |
197 | ||
198 | fn reload_service( | |
199 | param: Value, | |
200 | _info: &ApiMethod, | |
9f49fe1d | 201 | _rpcenv: &mut RpcEnvironment, |
48849593 DM |
202 | ) -> Result<Value, Error> { |
203 | ||
204 | let service = tools::required_string_param(¶m, "service")?; | |
205 | ||
206 | log::info!("reloading service {}", service); | |
207 | ||
208 | run_service_command(service, "reload") | |
209 | } | |
210 | ||
d2ab5f19 DM |
211 | pub fn router() -> Router { |
212 | ||
48849593 DM |
213 | let service_id_schema : Arc<Schema> = Arc::new( |
214 | StringSchema::new("Service ID.") | |
215 | .max_length(256) | |
216 | .into() | |
217 | ); | |
218 | ||
219 | let service_api = Router::new() | |
220 | .get(ApiMethod::new( | |
221 | |_,_,_| { | |
222 | let mut result = vec![]; | |
223 | for cmd in &["state", "start", "stop", "restart", "reload"] { | |
224 | result.push(json!({"subdir": cmd })); | |
225 | } | |
226 | Ok(Value::from(result)) | |
227 | }, | |
228 | ObjectSchema::new("Directory index.") | |
229 | .required("service", service_id_schema.clone())) | |
230 | ) | |
231 | .subdir( | |
232 | "state", | |
233 | Router::new() | |
234 | .get(ApiMethod::new( | |
235 | get_service_state, | |
236 | ObjectSchema::new("Read service properties.") | |
237 | .required("service", service_id_schema.clone())) | |
238 | ) | |
239 | ) | |
240 | .subdir( | |
241 | "start", | |
242 | Router::new() | |
a859fa95 DM |
243 | .post( |
244 | ApiMethod::new( | |
245 | start_service, | |
246 | ObjectSchema::new("Start service.") | |
247 | .required("service", service_id_schema.clone()) | |
248 | ).protected(true) | |
48849593 DM |
249 | ) |
250 | ) | |
251 | .subdir( | |
252 | "stop", | |
253 | Router::new() | |
a859fa95 DM |
254 | .post( |
255 | ApiMethod::new( | |
256 | stop_service, | |
257 | ObjectSchema::new("Stop service.") | |
258 | .required("service", service_id_schema.clone()) | |
259 | ).protected(true) | |
48849593 DM |
260 | ) |
261 | ) | |
262 | .subdir( | |
263 | "restart", | |
264 | Router::new() | |
a859fa95 DM |
265 | .post( |
266 | ApiMethod::new( | |
267 | restart_service, | |
268 | ObjectSchema::new("Restart service.") | |
269 | .required("service", service_id_schema.clone()) | |
270 | ).protected(true) | |
48849593 DM |
271 | ) |
272 | ) | |
273 | .subdir( | |
274 | "reload", | |
275 | Router::new() | |
a859fa95 DM |
276 | .post( |
277 | ApiMethod::new( | |
278 | reload_service, | |
279 | ObjectSchema::new("Reload service.") | |
280 | .required("service", service_id_schema.clone()) | |
281 | ).protected(true) | |
48849593 DM |
282 | ) |
283 | ) | |
284 | ; | |
285 | ||
d2ab5f19 DM |
286 | let route = Router::new() |
287 | .get( | |
288 | ApiMethod::new( | |
289 | list_services, | |
290 | ObjectSchema::new("Service list.") | |
291 | ).returns( | |
292 | ArraySchema::new( | |
293 | "Returns a list of systemd services.", | |
294 | ObjectSchema::new("Service details.") | |
48849593 | 295 | .required("service", service_id_schema.clone()) |
d2ab5f19 DM |
296 | .required("name", StringSchema::new("systemd service name.")) |
297 | .required("desc", StringSchema::new("systemd service description.")) | |
298 | .required("state", StringSchema::new("systemd service 'SubState'.")) | |
299 | .into() | |
300 | ) | |
301 | ) | |
48849593 DM |
302 | ) |
303 | .match_all("service", service_api); | |
d2ab5f19 DM |
304 | |
305 | route | |
306 | } |