]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/node/services.rs
move worker_task.rs into proxmox-rest-server crate
[proxmox-backup.git] / src / api2 / node / services.rs
CommitLineData
cad540e9 1use std::process::{Command, Stdio};
552c2259 2
f7d4e4b5 3use anyhow::{bail, Error};
d2ab5f19
DM
4use serde_json::{json, Value};
5
9ea4bce4 6use proxmox::{sortable, identity, list_subdirs_api_method};
a1e9c057 7use proxmox::api::{api, Router, Permission, RpcEnvironment};
cad540e9 8use proxmox::api::router::SubdirMap;
d2ab5f19 9
8cc3760e
DM
10use pbs_api_types::{Authid, NODE_SCHEMA, SERVICE_ID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
11
b9700a9f 12use proxmox_rest_server::WorkerTask;
4ebf0eab 13
1a6c9415 14static SERVICE_NAME_LIST: [&str; 7] = [
d2ab5f19 15 "proxmox-backup",
1a6c9415 16 "proxmox-backup-proxy",
d2ab5f19
DM
17 "sshd",
18 "syslog",
19 "cron",
20 "postfix",
21 "systemd-timesyncd",
22];
23
1d8f8494 24pub fn real_service_name(service: &str) -> &str {
d2ab5f19
DM
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
48849593
DM
30 if service == "postfix" {
31 "postfix@-"
32 } else {
33 service
34 }
35}
36
37fn get_full_service_state(service: &str) -> Result<Value, Error> {
38
39 let real_service_name = real_service_name(service);
d2ab5f19 40
cbef49bf 41 let mut child = Command::new("systemctl")
d2ab5f19
DM
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
48849593
DM
78fn 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
1cf7bbf4
DM
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: {
74c08a57 127 permission: &Permission::Privilege(&["system", "services"], PRIV_SYS_AUDIT, false),
1cf7bbf4
DM
128 },
129)]
130/// Service list.
d2ab5f19 131fn list_services(
9f49fe1d 132 _param: Value,
d2ab5f19
DM
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) => {
48849593
DM
140 let state = json_service_state(service, status);
141 if state != Value::Null {
142 list.push(state);
d2ab5f19
DM
143 }
144 }
145 Err(err) => log::error!("{}", err),
146 }
147 }
148
149 Ok(Value::from(list))
150}
151
1cf7bbf4
DM
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: {
74c08a57 164 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_AUDIT, false),
1cf7bbf4
DM
165 },
166)]
167/// Read service properties.
48849593 168fn get_service_state(
1cf7bbf4
DM
169 service: String,
170 _param: Value,
48849593
DM
171) -> Result<Value, Error> {
172
1cf7bbf4 173 let service = service.as_str();
48849593
DM
174
175 if !SERVICE_NAME_LIST.contains(&service) {
176 bail!("unknown service name '{}'", service);
177 }
178
1cf7bbf4 179 let status = get_full_service_state(&service)?;
48849593 180
1cf7bbf4 181 Ok(json_service_state(&service, status))
48849593
DM
182}
183
e6dc35ac 184fn run_service_command(service: &str, cmd: &str, auth_id: Authid) -> Result<Value, Error> {
48849593 185
a1e9c057 186 let workerid = format!("srv{}", &cmd);
48849593 187
e6b599aa 188 let cmd = match cmd {
a1e9c057
DC
189 "start"|"stop"|"restart"=> cmd.to_string(),
190 "reload" => "try-reload-or-restart".to_string(), // some services do not implement reload
48849593 191 _ => bail!("unknown service command '{}'", cmd),
e6b599aa 192 };
a1e9c057 193 let service = service.to_string();
48849593 194
a1e9c057
DC
195 let upid = WorkerTask::new_thread(
196 &workerid,
197 Some(service.clone()),
049a22a3 198 auth_id.to_string(),
a1e9c057
DC
199 false,
200 move |_worker| {
48849593 201
a1e9c057
DC
202 if service == "proxmox-backup" && cmd == "stop" {
203 bail!("invalid service cmd '{} {}' cannot stop essential service!", service, cmd);
204 }
48849593 205
a1e9c057 206 let real_service_name = real_service_name(&service);
48849593 207
a1e9c057
DC
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 }
48849593 215
a1e9c057
DC
216 Ok(())
217 }
218 )?;
219
220 Ok(upid.into())
48849593
DM
221}
222
1cf7bbf4
DM
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: {
74c08a57 236 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
1cf7bbf4
DM
237 },
238)]
239/// Start service.
48849593 240fn start_service(
1cf7bbf4
DM
241 service: String,
242 _param: Value,
a1e9c057 243 rpcenv: &mut dyn RpcEnvironment,
48849593
DM
244) -> Result<Value, Error> {
245
e6dc35ac 246 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
a1e9c057 247
48849593
DM
248 log::info!("starting service {}", service);
249
e6dc35ac 250 run_service_command(&service, "start", auth_id)
48849593
DM
251}
252
1cf7bbf4
DM
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: {
74c08a57 266 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
1cf7bbf4
DM
267 },
268)]
269/// Stop service.
48849593 270fn stop_service(
1cf7bbf4
DM
271 service: String,
272 _param: Value,
a1e9c057 273 rpcenv: &mut dyn RpcEnvironment,
1cf7bbf4 274 ) -> Result<Value, Error> {
48849593 275
e6dc35ac 276 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
a1e9c057 277
add5861e 278 log::info!("stopping service {}", service);
48849593 279
e6dc35ac 280 run_service_command(&service, "stop", auth_id)
48849593
DM
281}
282
1cf7bbf4
DM
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: {
74c08a57 296 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
1cf7bbf4
DM
297 },
298)]
299/// Retart service.
48849593 300fn restart_service(
1cf7bbf4
DM
301 service: String,
302 _param: Value,
a1e9c057 303 rpcenv: &mut dyn RpcEnvironment,
48849593
DM
304) -> Result<Value, Error> {
305
e6dc35ac 306 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
a1e9c057 307
48849593
DM
308 log::info!("re-starting service {}", service);
309
1cf7bbf4 310 if &service == "proxmox-backup-proxy" {
1a6c9415 311 // special case, avoid aborting running tasks
e6dc35ac 312 run_service_command(&service, "reload", auth_id)
1a6c9415 313 } else {
e6dc35ac 314 run_service_command(&service, "restart", auth_id)
1a6c9415 315 }
48849593
DM
316}
317
1cf7bbf4
DM
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: {
74c08a57 331 permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
1cf7bbf4
DM
332 },
333)]
334/// Reload service.
48849593 335fn reload_service(
1cf7bbf4
DM
336 service: String,
337 _param: Value,
a1e9c057 338 rpcenv: &mut dyn RpcEnvironment,
48849593
DM
339) -> Result<Value, Error> {
340
e6dc35ac 341 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
a1e9c057 342
48849593
DM
343 log::info!("reloading service {}", service);
344
e6dc35ac 345 run_service_command(&service, "reload", auth_id)
48849593
DM
346}
347
552c2259 348#[sortable]
1cf7bbf4 349const SERVICE_SUBDIRS: SubdirMap = &sorted!([
255f378a
DM
350 (
351 "reload", &Router::new()
1cf7bbf4 352 .post(&API_METHOD_RELOAD_SERVICE)
255f378a
DM
353 ),
354 (
355 "restart", &Router::new()
1cf7bbf4 356 .post(&API_METHOD_RESTART_SERVICE)
255f378a
DM
357 ),
358 (
359 "start", &Router::new()
1cf7bbf4 360 .post(&API_METHOD_START_SERVICE)
255f378a
DM
361 ),
362 (
363 "state", &Router::new()
1cf7bbf4 364 .get(&API_METHOD_GET_SERVICE_STATE)
255f378a
DM
365 ),
366 (
367 "stop", &Router::new()
1cf7bbf4 368 .post(&API_METHOD_STOP_SERVICE)
255f378a 369 ),
1cf7bbf4 370]);
255f378a
DM
371
372const SERVICE_ROUTER: Router = Router::new()
373 .get(&list_subdirs_api_method!(SERVICE_SUBDIRS))
374 .subdirs(SERVICE_SUBDIRS);
375
376pub const ROUTER: Router = Router::new()
1cf7bbf4 377 .get(&API_METHOD_LIST_SERVICES)
255f378a 378 .match_all("service", &SERVICE_ROUTER);