1 use std
::process
::{Command, Stdio}
;
3 use anyhow
::{bail, Error}
;
4 use serde_json
::{json, Value}
;
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
::*;
11 use crate::api2
::types
::*;
12 use crate::config
::acl
::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}
;
13 use crate::server
::WorkerTask
;
15 static SERVICE_NAME_LIST
: [&str; 7] = [
17 "proxmox-backup-proxy",
25 fn real_service_name(service
: &str) -> &str {
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
31 if service
== "postfix" {
38 fn get_full_service_state(service
: &str) -> Result
<Value
, Error
> {
40 let real_service_name
= real_service_name(service
);
42 let mut child
= Command
::new("systemctl")
43 .args(&["show", real_service_name
])
44 .stdout(Stdio
::piped())
47 use std
::io
::{BufRead,BufReader}
;
49 let mut result
= json
!({}
);
51 if let Some(ref mut stdout
) = child
.stdout
{
52 for line
in BufReader
::new(stdout
).lines() {
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
);
63 log
::error
!("reading service config failed: {}", err
);
71 let status
= child
.wait().unwrap();
72 if !status
.success() {
73 bail
!("systemctl show failed with {}", status
);
79 fn json_service_state(service
: &str, status
: Value
) -> Value
{
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");
104 description
: "Returns a list of systemd services.",
107 description
: "Service details.",
110 schema
: SERVICE_ID_SCHEMA
,
114 description
: "systemd service name.",
118 description
: "systemd service description.",
122 description
: "systemd service 'SubState'.",
128 permission
: &Permission
::Privilege(&["system", "services"], PRIV_SYS_AUDIT
, false),
134 ) -> Result
<Value
, Error
> {
136 let mut list
= vec
![];
138 for service
in &SERVICE_NAME_LIST
{
139 match get_full_service_state(service
) {
141 let state
= json_service_state(service
, status
);
142 if state
!= Value
::Null
{
146 Err(err
) => log
::error
!("{}", err
),
150 Ok(Value
::from(list
))
160 schema
: SERVICE_ID_SCHEMA
,
165 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_AUDIT
, false),
168 /// Read service properties.
169 fn get_service_state(
172 ) -> Result
<Value
, Error
> {
174 let service
= service
.as_str();
176 if !SERVICE_NAME_LIST
.contains(&service
) {
177 bail
!("unknown service name '{}'", service
);
180 let status
= get_full_service_state(&service
)?
;
182 Ok(json_service_state(&service
, status
))
185 fn run_service_command(service
: &str, cmd
: &str, auth_id
: Authid
) -> Result
<Value
, Error
> {
187 let workerid
= format
!("srv{}", &cmd
);
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
),
194 let service
= service
.to_string();
196 let upid
= WorkerTask
::new_thread(
198 Some(service
.clone()),
203 if service
== "proxmox-backup" && cmd
== "stop" {
204 bail
!("invalid service cmd '{} {}' cannot stop essential service!", service
, cmd
);
207 let real_service_name
= real_service_name(&service
);
209 let status
= Command
::new("systemctl")
210 .args(&[&cmd
, real_service_name
])
213 if !status
.success() {
214 bail
!("systemctl {} failed with {}", cmd
, status
);
232 schema
: SERVICE_ID_SCHEMA
,
237 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
244 rpcenv
: &mut dyn RpcEnvironment
,
245 ) -> Result
<Value
, Error
> {
247 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
249 log
::info
!("starting service {}", service
);
251 run_service_command(&service
, "start", auth_id
)
262 schema
: SERVICE_ID_SCHEMA
,
267 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
274 rpcenv
: &mut dyn RpcEnvironment
,
275 ) -> Result
<Value
, Error
> {
277 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
279 log
::info
!("stopping service {}", service
);
281 run_service_command(&service
, "stop", auth_id
)
292 schema
: SERVICE_ID_SCHEMA
,
297 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
304 rpcenv
: &mut dyn RpcEnvironment
,
305 ) -> Result
<Value
, Error
> {
307 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
309 log
::info
!("re-starting service {}", service
);
311 if &service
== "proxmox-backup-proxy" {
312 // special case, avoid aborting running tasks
313 run_service_command(&service
, "reload", auth_id
)
315 run_service_command(&service
, "restart", auth_id
)
327 schema
: SERVICE_ID_SCHEMA
,
332 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
339 rpcenv
: &mut dyn RpcEnvironment
,
340 ) -> Result
<Value
, Error
> {
342 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
344 log
::info
!("reloading service {}", service
);
346 run_service_command(&service
, "reload", auth_id
)
350 const SERVICE_ID_SCHEMA
: Schema
= StringSchema
::new("Service ID.")
355 const SERVICE_SUBDIRS
: SubdirMap
= &sorted
!([
357 "reload", &Router
::new()
358 .post(&API_METHOD_RELOAD_SERVICE
)
361 "restart", &Router
::new()
362 .post(&API_METHOD_RESTART_SERVICE
)
365 "start", &Router
::new()
366 .post(&API_METHOD_START_SERVICE
)
369 "state", &Router
::new()
370 .get(&API_METHOD_GET_SERVICE_STATE
)
373 "stop", &Router
::new()
374 .post(&API_METHOD_STOP_SERVICE
)
378 const SERVICE_ROUTER
: Router
= Router
::new()
379 .get(&list_subdirs_api_method
!(SERVICE_SUBDIRS
))
380 .subdirs(SERVICE_SUBDIRS
);
382 pub const ROUTER
: Router
= Router
::new()
383 .get(&API_METHOD_LIST_SERVICES
)
384 .match_all("service", &SERVICE_ROUTER
);