1 use std
::process
::{Command, Stdio}
;
3 use anyhow
::{bail, Error}
;
4 use serde_json
::{json, Value}
;
6 use proxmox_router
::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap}
;
7 use proxmox_schema
::api
;
8 use proxmox_sortable_macro
::sortable
;
10 use pbs_api_types
::{Authid, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, SERVICE_ID_SCHEMA}
;
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 {
25 // since postfix package 3.1.0-3.1 the postfix unit is only here
26 // to manage subinstances, of which the default is called "-".
27 // This is where we look for the daemon status
29 if service
== "postfix" {
36 fn get_full_service_state(service
: &str) -> Result
<Value
, Error
> {
37 let real_service_name
= real_service_name(service
);
39 let mut child
= Command
::new("systemctl")
40 .args(["show", real_service_name
])
41 .stdout(Stdio
::piped())
44 use std
::io
::{BufRead, BufReader}
;
46 let mut result
= json
!({}
);
48 if let Some(ref mut stdout
) = child
.stdout
{
49 for line
in BufReader
::new(stdout
).lines() {
52 let mut iter
= line
.splitn(2, '
='
);
53 let key
= iter
.next();
54 let value
= iter
.next();
55 if let (Some(key
), Some(value
)) = (key
, value
) {
56 result
[key
] = Value
::from(value
);
60 log
::error
!("reading service config failed: {}", err
);
68 let status
= child
.wait().unwrap();
69 if !status
.success() {
70 bail
!("systemctl show failed with {}", status
);
76 fn json_service_state(service
: &str, status
: Value
) -> Value
{
77 if let Some(desc
) = status
["Description"].as_str() {
78 let name
= status
["Name"].as_str().unwrap_or(service
);
80 let state
= if status
["Type"] == "oneshot" && status
["SubState"] == "dead" {
81 status
["Result"].as_str().or(status
["SubState"].as_str())
83 status
["SubState"].as_str()
85 .unwrap_or("unknown");
87 let unit_state
= if status
["LoadState"] == "not-found" {
90 status
["UnitFileState"].as_str().unwrap_or("unknown")
98 "unit-state": unit_state
,
114 description
: "Returns a list of systemd services.",
117 description
: "Service details.",
120 schema
: SERVICE_ID_SCHEMA
,
124 description
: "systemd service name.",
128 description
: "systemd service description.",
132 description
: "systemd service 'SubState'.",
136 description
: "systemd service unit state.",
142 permission
: &Permission
::Privilege(&["system", "services"], PRIV_SYS_AUDIT
, false),
146 fn list_services(_param
: Value
) -> Result
<Value
, Error
> {
147 let mut list
= vec
![];
149 for service
in &SERVICE_NAME_LIST
{
150 match get_full_service_state(service
) {
152 let state
= json_service_state(service
, status
);
153 if state
!= Value
::Null
{
157 Err(err
) => log
::error
!("{}", err
),
161 Ok(Value
::from(list
))
171 schema
: SERVICE_ID_SCHEMA
,
176 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_AUDIT
, false),
179 /// Read service properties.
180 fn get_service_state(service
: String
, _param
: Value
) -> Result
<Value
, Error
> {
181 let service
= service
.as_str();
183 if !SERVICE_NAME_LIST
.contains(&service
) {
184 bail
!("unknown service name '{}'", service
);
187 let status
= get_full_service_state(service
)?
;
189 Ok(json_service_state(service
, status
))
192 fn run_service_command(service
: &str, cmd
: &str, auth_id
: Authid
) -> Result
<Value
, Error
> {
193 let workerid
= format
!("srv{}", &cmd
);
195 let cmd
= match cmd
{
196 "start" | "stop" | "restart" => cmd
.to_string(),
197 "reload" => "try-reload-or-restart".to_string(), // some services do not implement reload
198 _
=> bail
!("unknown service command '{}'", cmd
),
200 let service
= service
.to_string();
202 let upid
= WorkerTask
::new_thread(
204 Some(service
.clone()),
208 if service
== "proxmox-backup" && cmd
== "stop" {
210 "invalid service cmd '{} {}' cannot stop essential service!",
216 let real_service_name
= real_service_name(&service
);
218 let status
= Command
::new("systemctl")
219 .args([&cmd
, real_service_name
])
222 if !status
.success() {
223 bail
!("systemctl {} failed with {}", cmd
, status
);
241 schema
: SERVICE_ID_SCHEMA
,
246 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
253 rpcenv
: &mut dyn RpcEnvironment
,
254 ) -> Result
<Value
, Error
> {
255 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
257 log
::info
!("starting service {}", service
);
259 run_service_command(&service
, "start", auth_id
)
270 schema
: SERVICE_ID_SCHEMA
,
275 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
282 rpcenv
: &mut dyn RpcEnvironment
,
283 ) -> Result
<Value
, Error
> {
284 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
286 log
::info
!("stopping service {}", service
);
288 run_service_command(&service
, "stop", auth_id
)
299 schema
: SERVICE_ID_SCHEMA
,
304 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
311 rpcenv
: &mut dyn RpcEnvironment
,
312 ) -> Result
<Value
, Error
> {
313 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
315 log
::info
!("re-starting service {}", service
);
317 if &service
== "proxmox-backup-proxy" {
318 // special case, avoid aborting running tasks
319 run_service_command(&service
, "reload", auth_id
)
321 run_service_command(&service
, "restart", auth_id
)
333 schema
: SERVICE_ID_SCHEMA
,
338 permission
: &Permission
::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY
, false),
345 rpcenv
: &mut dyn RpcEnvironment
,
346 ) -> Result
<Value
, Error
> {
347 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
349 log
::info
!("reloading service {}", service
);
351 run_service_command(&service
, "reload", auth_id
)
355 const SERVICE_SUBDIRS
: SubdirMap
= &sorted
!([
356 ("reload", &Router
::new().post(&API_METHOD_RELOAD_SERVICE
)),
357 ("restart", &Router
::new().post(&API_METHOD_RESTART_SERVICE
)),
358 ("start", &Router
::new().post(&API_METHOD_START_SERVICE
)),
359 ("state", &Router
::new().get(&API_METHOD_GET_SERVICE_STATE
)),
360 ("stop", &Router
::new().post(&API_METHOD_STOP_SERVICE
)),
363 const SERVICE_ROUTER
: Router
= Router
::new()
364 .get(&list_subdirs_api_method
!(SERVICE_SUBDIRS
))
365 .subdirs(SERVICE_SUBDIRS
);
367 pub const ROUTER
: Router
= Router
::new()
368 .get(&API_METHOD_LIST_SERVICES
)
369 .match_all("service", &SERVICE_ROUTER
);