]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/services.rs
update to first proxmox crate split
[proxmox-backup.git] / src / api2 / node / services.rs
1 use std::process::{Command, Stdio};
2
3 use anyhow::{bail, Error};
4 use serde_json::{json, Value};
5
6 use proxmox::{sortable, identity};
7 use proxmox_router::{list_subdirs_api_method, Router, Permission, RpcEnvironment, SubdirMap};
8 use proxmox_schema::api;
9
10 use pbs_api_types::{Authid, NODE_SCHEMA, SERVICE_ID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
11
12 use proxmox_rest_server::WorkerTask;
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 pub 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("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(&["system", "services"], 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(&["system", "services", "{service}"], 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, auth_id: Authid) -> Result<Value, Error> {
185
186 let workerid = format!("srv{}", &cmd);
187
188 let cmd = match cmd {
189 "start"|"stop"|"restart"=> cmd.to_string(),
190 "reload" => "try-reload-or-restart".to_string(), // some services do not implement reload
191 _ => bail!("unknown service command '{}'", cmd),
192 };
193 let service = service.to_string();
194
195 let upid = WorkerTask::new_thread(
196 &workerid,
197 Some(service.clone()),
198 auth_id.to_string(),
199 false,
200 move |_worker| {
201
202 if service == "proxmox-backup" && cmd == "stop" {
203 bail!("invalid service cmd '{} {}' cannot stop essential service!", service, cmd);
204 }
205
206 let real_service_name = real_service_name(&service);
207
208 let status = Command::new("systemctl")
209 .args(&[&cmd, real_service_name])
210 .status()?;
211
212 if !status.success() {
213 bail!("systemctl {} failed with {}", cmd, status);
214 }
215
216 Ok(())
217 }
218 )?;
219
220 Ok(upid.into())
221 }
222
223 #[api(
224 protected: true,
225 input: {
226 properties: {
227 node: {
228 schema: NODE_SCHEMA,
229 },
230 service: {
231 schema: SERVICE_ID_SCHEMA,
232 },
233 },
234 },
235 access: {
236 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
237 },
238 )]
239 /// Start service.
240 fn start_service(
241 service: String,
242 _param: Value,
243 rpcenv: &mut dyn RpcEnvironment,
244 ) -> Result<Value, Error> {
245
246 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
247
248 log::info!("starting service {}", service);
249
250 run_service_command(&service, "start", auth_id)
251 }
252
253 #[api(
254 protected: true,
255 input: {
256 properties: {
257 node: {
258 schema: NODE_SCHEMA,
259 },
260 service: {
261 schema: SERVICE_ID_SCHEMA,
262 },
263 },
264 },
265 access: {
266 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
267 },
268 )]
269 /// Stop service.
270 fn stop_service(
271 service: String,
272 _param: Value,
273 rpcenv: &mut dyn RpcEnvironment,
274 ) -> Result<Value, Error> {
275
276 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
277
278 log::info!("stopping service {}", service);
279
280 run_service_command(&service, "stop", auth_id)
281 }
282
283 #[api(
284 protected: true,
285 input: {
286 properties: {
287 node: {
288 schema: NODE_SCHEMA,
289 },
290 service: {
291 schema: SERVICE_ID_SCHEMA,
292 },
293 },
294 },
295 access: {
296 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
297 },
298 )]
299 /// Retart service.
300 fn restart_service(
301 service: String,
302 _param: Value,
303 rpcenv: &mut dyn RpcEnvironment,
304 ) -> Result<Value, Error> {
305
306 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
307
308 log::info!("re-starting service {}", service);
309
310 if &service == "proxmox-backup-proxy" {
311 // special case, avoid aborting running tasks
312 run_service_command(&service, "reload", auth_id)
313 } else {
314 run_service_command(&service, "restart", auth_id)
315 }
316 }
317
318 #[api(
319 protected: true,
320 input: {
321 properties: {
322 node: {
323 schema: NODE_SCHEMA,
324 },
325 service: {
326 schema: SERVICE_ID_SCHEMA,
327 },
328 },
329 },
330 access: {
331 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
332 },
333 )]
334 /// Reload service.
335 fn reload_service(
336 service: String,
337 _param: Value,
338 rpcenv: &mut dyn RpcEnvironment,
339 ) -> Result<Value, Error> {
340
341 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
342
343 log::info!("reloading service {}", service);
344
345 run_service_command(&service, "reload", auth_id)
346 }
347
348 #[sortable]
349 const SERVICE_SUBDIRS: SubdirMap = &sorted!([
350 (
351 "reload", &Router::new()
352 .post(&API_METHOD_RELOAD_SERVICE)
353 ),
354 (
355 "restart", &Router::new()
356 .post(&API_METHOD_RESTART_SERVICE)
357 ),
358 (
359 "start", &Router::new()
360 .post(&API_METHOD_START_SERVICE)
361 ),
362 (
363 "state", &Router::new()
364 .get(&API_METHOD_GET_SERVICE_STATE)
365 ),
366 (
367 "stop", &Router::new()
368 .post(&API_METHOD_STOP_SERVICE)
369 ),
370 ]);
371
372 const SERVICE_ROUTER: Router = Router::new()
373 .get(&list_subdirs_api_method!(SERVICE_SUBDIRS))
374 .subdirs(SERVICE_SUBDIRS);
375
376 pub const ROUTER: Router = Router::new()
377 .get(&API_METHOD_LIST_SERVICES)
378 .match_all("service", &SERVICE_ROUTER);