]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/services.rs
56c642485610f8dbe40aa73456dff23aaa1b3c3a
[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};
7 use proxmox::api::list_subdirs_api_method;
8 use proxmox::api::{ApiHandler, ApiMethod, Router, RpcEnvironment};
9 use proxmox::api::router::SubdirMap;
10 use proxmox::api::schema::*;
11
12 use crate::api2::types::*;
13 use crate::tools;
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("/bin/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
96 fn list_services(
97 _param: Value,
98 _info: &ApiMethod,
99 _rpcenv: &mut dyn RpcEnvironment,
100 ) -> Result<Value, Error> {
101
102 let mut list = vec![];
103
104 for service in &SERVICE_NAME_LIST {
105 match get_full_service_state(service) {
106 Ok(status) => {
107 let state = json_service_state(service, status);
108 if state != Value::Null {
109 list.push(state);
110 }
111 }
112 Err(err) => log::error!("{}", err),
113 }
114 }
115
116 Ok(Value::from(list))
117 }
118
119 fn get_service_state(
120 param: Value,
121 _info: &ApiMethod,
122 _rpcenv: &mut dyn RpcEnvironment,
123 ) -> Result<Value, Error> {
124
125 let service = tools::required_string_param(&param, "service")?;
126
127 if !SERVICE_NAME_LIST.contains(&service) {
128 bail!("unknown service name '{}'", service);
129 }
130
131 let status = get_full_service_state(service)?;
132
133 Ok(json_service_state(service, status))
134 }
135
136 fn run_service_command(service: &str, cmd: &str) -> Result<Value, Error> {
137
138 // fixme: run background worker (fork_worker) ???
139
140 match cmd {
141 "start"|"stop"|"restart"|"reload" => {},
142 _ => bail!("unknown service command '{}'", cmd),
143 }
144
145 if service == "proxmox-backup" && cmd != "restart" {
146 bail!("invalid service cmd '{} {}'", service, cmd);
147 }
148
149 let real_service_name = real_service_name(service);
150
151 let status = Command::new("/bin/systemctl")
152 .args(&[cmd, real_service_name])
153 .status()?;
154
155 if !status.success() {
156 bail!("systemctl {} failed with {}", cmd, status);
157 }
158
159 Ok(Value::Null)
160 }
161
162 fn start_service(
163 param: Value,
164 _info: &ApiMethod,
165 _rpcenv: &mut dyn RpcEnvironment,
166 ) -> Result<Value, Error> {
167
168 let service = tools::required_string_param(&param, "service")?;
169
170 log::info!("starting service {}", service);
171
172 run_service_command(service, "start")
173 }
174
175 fn stop_service(
176 param: Value,
177 _info: &ApiMethod,
178 _rpcenv: &mut dyn RpcEnvironment,
179 ) -> Result<Value, Error> {
180
181 let service = tools::required_string_param(&param, "service")?;
182
183 log::info!("stoping service {}", service);
184
185 run_service_command(service, "stop")
186 }
187
188 fn restart_service(
189 param: Value,
190 _info: &ApiMethod,
191 _rpcenv: &mut dyn RpcEnvironment,
192 ) -> Result<Value, Error> {
193
194 let service = tools::required_string_param(&param, "service")?;
195
196 log::info!("re-starting service {}", service);
197
198 if service == "proxmox-backup-proxy" {
199 // special case, avoid aborting running tasks
200 run_service_command(service, "reload")
201 } else {
202 run_service_command(service, "restart")
203 }
204 }
205
206 fn reload_service(
207 param: Value,
208 _info: &ApiMethod,
209 _rpcenv: &mut dyn RpcEnvironment,
210 ) -> Result<Value, Error> {
211
212 let service = tools::required_string_param(&param, "service")?;
213
214 log::info!("reloading service {}", service);
215
216 run_service_command(service, "reload")
217 }
218
219
220 const SERVICE_ID_SCHEMA: Schema = StringSchema::new("Service ID.")
221 .max_length(256)
222 .schema();
223
224 #[sortable]
225 const SERVICE_SUBDIRS: SubdirMap = &[
226 (
227 "reload", &Router::new()
228 .post(
229 &ApiMethod::new(
230 &ApiHandler::Sync(&reload_service),
231 &ObjectSchema::new(
232 "Reload service.",
233 &sorted!([
234 ("node", false, &NODE_SCHEMA),
235 ("service", false, &SERVICE_ID_SCHEMA),
236 ]),
237 )
238 ).protected(true)
239 )
240 ),
241 (
242 "restart", &Router::new()
243 .post(
244 &ApiMethod::new(
245 &ApiHandler::Sync(&restart_service),
246 &ObjectSchema::new(
247 "Restart service.",
248 &sorted!([
249 ("node", false, &NODE_SCHEMA),
250 ("service", false, &SERVICE_ID_SCHEMA),
251 ]),
252 )
253 ).protected(true)
254 )
255 ),
256 (
257 "start", &Router::new()
258 .post(
259 &ApiMethod::new(
260 &ApiHandler::Sync(&start_service),
261 &ObjectSchema::new(
262 "Start service.",
263 &sorted!([
264 ("node", false, &NODE_SCHEMA),
265 ("service", false, &SERVICE_ID_SCHEMA),
266 ]),
267 )
268 ).protected(true)
269 )
270 ),
271 (
272 "state", &Router::new()
273 .get(
274 &ApiMethod::new(
275 &ApiHandler::Sync(&get_service_state),
276 &ObjectSchema::new(
277 "Read service properties.",
278 &sorted!([
279 ("node", false, &NODE_SCHEMA),
280 ("service", false, &SERVICE_ID_SCHEMA),
281 ]),
282 )
283 )
284 )
285 ),
286 (
287 "stop", &Router::new()
288 .post(
289 &ApiMethod::new(
290 &ApiHandler::Sync(&stop_service),
291 &ObjectSchema::new(
292 "Stop service.",
293 &sorted!([
294 ("node", false, &NODE_SCHEMA),
295 ("service", false, &SERVICE_ID_SCHEMA),
296 ]),
297 )
298 ).protected(true)
299 )
300 ),
301 ];
302
303 const SERVICE_ROUTER: Router = Router::new()
304 .get(&list_subdirs_api_method!(SERVICE_SUBDIRS))
305 .subdirs(SERVICE_SUBDIRS);
306
307 #[sortable]
308 pub const ROUTER: Router = Router::new()
309 .get(
310 &ApiMethod::new(
311 &ApiHandler::Sync(&list_services),
312 &ObjectSchema::new(
313 "Service list.",
314 &sorted!([ ("node", false, &NODE_SCHEMA) ]),
315 )
316 ).returns(
317 &ArraySchema::new(
318 "Returns a list of systemd services.",
319 &ObjectSchema::new(
320 "Service details.",
321 &sorted!([
322 ("service", false, &SERVICE_ID_SCHEMA),
323 ("name", false, &StringSchema::new("systemd service name.").schema()),
324 ("desc", false, &StringSchema::new("systemd service description.").schema()),
325 ("state", false, &StringSchema::new("systemd service 'SubState'.").schema()),
326 ]),
327 ).schema()
328 ).schema()
329 )
330 )
331 .match_all("service", &SERVICE_ROUTER);
332