]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/services.rs
fc8c01e70fd2d21132b1c2b7136e91243ae33ae1
[proxmox-backup.git] / src / api2 / node / services.rs
1 use std::process::{Command, Stdio};
2
3 use failure::*;
4 use serde_json::{json, Value};
5
6 use proxmox::{sortable, identity, list_subdirs_api_method};
7 use proxmox::api::{api, Router, Permission};
8 use proxmox::api::router::SubdirMap;
9 use proxmox::api::schema::*;
10
11 use crate::api2::types::*;
12 use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
13
14 static SERVICE_NAME_LIST: [&str; 7] = [
15 "proxmox-backup",
16 "proxmox-backup-proxy",
17 "sshd",
18 "syslog",
19 "cron",
20 "postfix",
21 "systemd-timesyncd",
22 ];
23
24 fn real_service_name(service: &str) -> &str {
25
26 // since postfix package 3.1.0-3.1 the postfix unit is only here
27 // to manage subinstances, of which the default is called "-".
28 // This is where we look for the daemon status
29
30 if service == "postfix" {
31 "postfix@-"
32 } else {
33 service
34 }
35 }
36
37 fn get_full_service_state(service: &str) -> Result<Value, Error> {
38
39 let real_service_name = real_service_name(service);
40
41 let mut child = Command::new("/bin/systemctl")
42 .args(&["show", real_service_name])
43 .stdout(Stdio::piped())
44 .spawn()?;
45
46 use std::io::{BufRead,BufReader};
47
48 let mut result = json!({});
49
50 if let Some(ref mut stdout) = child.stdout {
51 for line in BufReader::new(stdout).lines() {
52 match line {
53 Ok(line) => {
54 let mut iter = line.splitn(2, '=');
55 let key = iter.next();
56 let value = iter.next();
57 if let (Some(key), Some(value)) = (key, value) {
58 result[key] = Value::from(value);
59 }
60 }
61 Err(err) => {
62 log::error!("reading service config failed: {}", err);
63 let _ = child.kill();
64 break;
65 }
66 }
67 }
68 }
69
70 let status = child.wait().unwrap();
71 if !status.success() {
72 bail!("systemctl show failed with {}", status);
73 }
74
75 Ok(result)
76 }
77
78 fn json_service_state(service: &str, status: Value) -> Value {
79
80 if let Some(desc) = status["Description"].as_str() {
81 let name = status["Name"].as_str().unwrap_or(service);
82 let state = status["SubState"].as_str().unwrap_or("unknown");
83 return json!({
84 "service": service,
85 "name": name,
86 "desc": desc,
87 "state": state,
88 });
89 }
90
91 Value::Null
92 }
93
94 #[api(
95 input: {
96 properties: {
97 node: {
98 schema: NODE_SCHEMA,
99 },
100 },
101 },
102 returns: {
103 description: "Returns a list of systemd services.",
104 type: Array,
105 items: {
106 description: "Service details.",
107 properties: {
108 service: {
109 schema: SERVICE_ID_SCHEMA,
110 },
111 name: {
112 type: String,
113 description: "systemd service name.",
114 },
115 desc: {
116 type: String,
117 description: "systemd service description.",
118 },
119 state: {
120 type: String,
121 description: "systemd service 'SubState'.",
122 },
123 },
124 },
125 },
126 access: {
127 permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false),
128 },
129 )]
130 /// Service list.
131 fn list_services(
132 _param: Value,
133 ) -> Result<Value, Error> {
134
135 let mut list = vec![];
136
137 for service in &SERVICE_NAME_LIST {
138 match get_full_service_state(service) {
139 Ok(status) => {
140 let state = json_service_state(service, status);
141 if state != Value::Null {
142 list.push(state);
143 }
144 }
145 Err(err) => log::error!("{}", err),
146 }
147 }
148
149 Ok(Value::from(list))
150 }
151
152 #[api(
153 input: {
154 properties: {
155 node: {
156 schema: NODE_SCHEMA,
157 },
158 service: {
159 schema: SERVICE_ID_SCHEMA,
160 },
161 },
162 },
163 access: {
164 permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false),
165 },
166 )]
167 /// Read service properties.
168 fn get_service_state(
169 service: String,
170 _param: Value,
171 ) -> Result<Value, Error> {
172
173 let service = service.as_str();
174
175 if !SERVICE_NAME_LIST.contains(&service) {
176 bail!("unknown service name '{}'", service);
177 }
178
179 let status = get_full_service_state(&service)?;
180
181 Ok(json_service_state(&service, status))
182 }
183
184 fn run_service_command(service: &str, cmd: &str) -> Result<Value, Error> {
185
186 // fixme: run background worker (fork_worker) ???
187
188 match cmd {
189 "start"|"stop"|"restart"|"reload" => {},
190 _ => bail!("unknown service command '{}'", cmd),
191 }
192
193 if service == "proxmox-backup" && cmd != "restart" {
194 bail!("invalid service cmd '{} {}'", service, cmd);
195 }
196
197 let real_service_name = real_service_name(service);
198
199 let status = Command::new("/bin/systemctl")
200 .args(&[cmd, real_service_name])
201 .status()?;
202
203 if !status.success() {
204 bail!("systemctl {} failed with {}", cmd, status);
205 }
206
207 Ok(Value::Null)
208 }
209
210 #[api(
211 protected: true,
212 input: {
213 properties: {
214 node: {
215 schema: NODE_SCHEMA,
216 },
217 service: {
218 schema: SERVICE_ID_SCHEMA,
219 },
220 },
221 },
222 access: {
223 permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
224 },
225 )]
226 /// Start service.
227 fn start_service(
228 service: String,
229 _param: Value,
230 ) -> Result<Value, Error> {
231
232 log::info!("starting service {}", service);
233
234 run_service_command(&service, "start")
235 }
236
237 #[api(
238 protected: true,
239 input: {
240 properties: {
241 node: {
242 schema: NODE_SCHEMA,
243 },
244 service: {
245 schema: SERVICE_ID_SCHEMA,
246 },
247 },
248 },
249 access: {
250 permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
251 },
252 )]
253 /// Stop service.
254 fn stop_service(
255 service: String,
256 _param: Value,
257 ) -> Result<Value, Error> {
258
259 log::info!("stoping service {}", service);
260
261 run_service_command(&service, "stop")
262 }
263
264 #[api(
265 protected: true,
266 input: {
267 properties: {
268 node: {
269 schema: NODE_SCHEMA,
270 },
271 service: {
272 schema: SERVICE_ID_SCHEMA,
273 },
274 },
275 },
276 access: {
277 permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
278 },
279 )]
280 /// Retart service.
281 fn restart_service(
282 service: String,
283 _param: Value,
284 ) -> Result<Value, Error> {
285
286 log::info!("re-starting service {}", service);
287
288 if &service == "proxmox-backup-proxy" {
289 // special case, avoid aborting running tasks
290 run_service_command(&service, "reload")
291 } else {
292 run_service_command(&service, "restart")
293 }
294 }
295
296 #[api(
297 protected: true,
298 input: {
299 properties: {
300 node: {
301 schema: NODE_SCHEMA,
302 },
303 service: {
304 schema: SERVICE_ID_SCHEMA,
305 },
306 },
307 },
308 access: {
309 permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
310 },
311 )]
312 /// Reload service.
313 fn reload_service(
314 service: String,
315 _param: Value,
316 ) -> Result<Value, Error> {
317
318 log::info!("reloading service {}", service);
319
320 run_service_command(&service, "reload")
321 }
322
323
324 const SERVICE_ID_SCHEMA: Schema = StringSchema::new("Service ID.")
325 .max_length(256)
326 .schema();
327
328 #[sortable]
329 const SERVICE_SUBDIRS: SubdirMap = &sorted!([
330 (
331 "reload", &Router::new()
332 .post(&API_METHOD_RELOAD_SERVICE)
333 ),
334 (
335 "restart", &Router::new()
336 .post(&API_METHOD_RESTART_SERVICE)
337 ),
338 (
339 "start", &Router::new()
340 .post(&API_METHOD_START_SERVICE)
341 ),
342 (
343 "state", &Router::new()
344 .get(&API_METHOD_GET_SERVICE_STATE)
345 ),
346 (
347 "stop", &Router::new()
348 .post(&API_METHOD_STOP_SERVICE)
349 ),
350 ]);
351
352 const SERVICE_ROUTER: Router = Router::new()
353 .get(&list_subdirs_api_method!(SERVICE_SUBDIRS))
354 .subdirs(SERVICE_SUBDIRS);
355
356 pub const ROUTER: Router = Router::new()
357 .get(&API_METHOD_LIST_SERVICES)
358 .match_all("service", &SERVICE_ROUTER);