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
;
10 use pbs_api_types
::{Authid, NODE_SCHEMA, SERVICE_ID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}
;
12 use proxmox_rest_server
::WorkerTask
;
14 static SERVICE_NAME_LIST
: [&str; 7] = [
16 "proxmox-backup-proxy",
24 pub fn real_service_name(service
: &str) -> &str {
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
30 if service
== "postfix" {
37 fn get_full_service_state(service
: &str) -> Result
<Value
, Error
> {
39 let real_service_name
= real_service_name(service
);
41 let mut child
= Command
::new("systemctl")
42 .args(&["show", real_service_name
])
43 .stdout(Stdio
::piped())
46 use std
::io
::{BufRead,BufReader}
;
48 let mut result
= json
!({}
);
50 if let Some(ref mut stdout
) = child
.stdout
{
51 for line
in BufReader
::new(stdout
).lines() {
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
);
62 log
::error
!("reading service config failed: {}", err
);
70 let status
= child
.wait().unwrap();
71 if !status
.success() {
72 bail
!("systemctl show failed with {}", status
);
78 fn json_service_state(service
: &str, status
: Value
) -> Value
{
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");
103 description
: "Returns a list of systemd services.",
106 description
: "Service details.",
109 schema
: SERVICE_ID_SCHEMA
,
113 description
: "systemd service name.",
117 description
: "systemd service description.",
121 description
: "systemd service 'SubState'.",
127 permission
: &Permission
::Privilege(&["system", "services"], PRIV_SYS_AUDIT
, false),
133 ) -> Result
<Value
, Error
> {
135 let mut list
= vec
![];
137 for service
in &SERVICE_NAME_LIST
{
138 match get_full_service_state(service
) {
140 let state
= json_service_state(service
, status
);
141 if state
!= Value
::Null
{
145 Err(err
) => log
::error
!("{}", err
),
149 Ok(Value
::from(list
))
159 schema
: SERVICE_ID_SCHEMA
,
164 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_AUDIT
, false),
167 /// Read service properties.
168 fn get_service_state(
171 ) -> Result
<Value
, Error
> {
173 let service
= service
.as_str();
175 if !SERVICE_NAME_LIST
.contains(&service
) {
176 bail
!("unknown service name '{}'", service
);
179 let status
= get_full_service_state(&service
)?
;
181 Ok(json_service_state(&service
, status
))
184 fn run_service_command(service
: &str, cmd
: &str, auth_id
: Authid
) -> Result
<Value
, Error
> {
186 let workerid
= format
!("srv{}", &cmd
);
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
),
193 let service
= service
.to_string();
195 let upid
= WorkerTask
::new_thread(
197 Some(service
.clone()),
202 if service
== "proxmox-backup" && cmd
== "stop" {
203 bail
!("invalid service cmd '{} {}' cannot stop essential service!", service
, cmd
);
206 let real_service_name
= real_service_name(&service
);
208 let status
= Command
::new("systemctl")
209 .args(&[&cmd
, real_service_name
])
212 if !status
.success() {
213 bail
!("systemctl {} failed with {}", cmd
, status
);
231 schema
: SERVICE_ID_SCHEMA
,
236 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
243 rpcenv
: &mut dyn RpcEnvironment
,
244 ) -> Result
<Value
, Error
> {
246 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
248 log
::info
!("starting service {}", service
);
250 run_service_command(&service
, "start", auth_id
)
261 schema
: SERVICE_ID_SCHEMA
,
266 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
273 rpcenv
: &mut dyn RpcEnvironment
,
274 ) -> Result
<Value
, Error
> {
276 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
278 log
::info
!("stopping service {}", service
);
280 run_service_command(&service
, "stop", auth_id
)
291 schema
: SERVICE_ID_SCHEMA
,
296 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
303 rpcenv
: &mut dyn RpcEnvironment
,
304 ) -> Result
<Value
, Error
> {
306 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
308 log
::info
!("re-starting service {}", service
);
310 if &service
== "proxmox-backup-proxy" {
311 // special case, avoid aborting running tasks
312 run_service_command(&service
, "reload", auth_id
)
314 run_service_command(&service
, "restart", auth_id
)
326 schema
: SERVICE_ID_SCHEMA
,
331 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
338 rpcenv
: &mut dyn RpcEnvironment
,
339 ) -> Result
<Value
, Error
> {
341 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
343 log
::info
!("reloading service {}", service
);
345 run_service_command(&service
, "reload", auth_id
)
349 const SERVICE_SUBDIRS
: SubdirMap
= &sorted
!([
351 "reload", &Router
::new()
352 .post(&API_METHOD_RELOAD_SERVICE
)
355 "restart", &Router
::new()
356 .post(&API_METHOD_RESTART_SERVICE
)
359 "start", &Router
::new()
360 .post(&API_METHOD_START_SERVICE
)
363 "state", &Router
::new()
364 .get(&API_METHOD_GET_SERVICE_STATE
)
367 "stop", &Router
::new()
368 .post(&API_METHOD_STOP_SERVICE
)
372 const SERVICE_ROUTER
: Router
= Router
::new()
373 .get(&list_subdirs_api_method
!(SERVICE_SUBDIRS
))
374 .subdirs(SERVICE_SUBDIRS
);
376 pub const ROUTER
: Router
= Router
::new()
377 .get(&API_METHOD_LIST_SERVICES
)
378 .match_all("service", &SERVICE_ROUTER
);