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