2019-11-21 13:53:15 +00:00
|
|
|
use std::process::{Command, Stdio};
|
2019-11-21 12:10:49 +00:00
|
|
|
|
2020-04-17 12:11:25 +00:00
|
|
|
use anyhow::{bail, Error};
|
2019-01-26 16:54:18 +00:00
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
2020-01-21 11:28:01 +00:00
|
|
|
use proxmox::{sortable, identity, list_subdirs_api_method};
|
2020-08-14 10:03:08 +00:00
|
|
|
use proxmox::api::{api, Router, Permission, RpcEnvironment};
|
2019-11-21 13:53:15 +00:00
|
|
|
use proxmox::api::router::SubdirMap;
|
|
|
|
use proxmox::api::schema::*;
|
2019-01-26 16:54:18 +00:00
|
|
|
|
2019-05-09 05:44:09 +00:00
|
|
|
use crate::api2::types::*;
|
2020-04-16 10:46:39 +00:00
|
|
|
use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
|
2020-08-14 10:03:08 +00:00
|
|
|
use crate::server::WorkerTask;
|
2019-05-09 05:44:09 +00:00
|
|
|
|
2019-04-15 07:48:02 +00:00
|
|
|
static SERVICE_NAME_LIST: [&str; 7] = [
|
2019-01-26 16:54:18 +00:00
|
|
|
"proxmox-backup",
|
2019-04-15 07:48:02 +00:00
|
|
|
"proxmox-backup-proxy",
|
2019-01-26 16:54:18 +00:00
|
|
|
"sshd",
|
|
|
|
"syslog",
|
|
|
|
"cron",
|
|
|
|
"postfix",
|
|
|
|
"systemd-timesyncd",
|
|
|
|
];
|
|
|
|
|
2019-01-27 11:40:31 +00:00
|
|
|
fn real_service_name(service: &str) -> &str {
|
2019-01-26 16:54:18 +00:00
|
|
|
|
|
|
|
// since postfix package 3.1.0-3.1 the postfix unit is only here
|
|
|
|
// to manage subinstances, of which the default is called "-".
|
|
|
|
// This is where we look for the daemon status
|
|
|
|
|
2019-01-27 11:40:31 +00:00
|
|
|
if service == "postfix" {
|
|
|
|
"postfix@-"
|
|
|
|
} else {
|
|
|
|
service
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_full_service_state(service: &str) -> Result<Value, Error> {
|
|
|
|
|
|
|
|
let real_service_name = real_service_name(service);
|
2019-01-26 16:54:18 +00:00
|
|
|
|
2020-06-15 09:16:11 +00:00
|
|
|
let mut child = Command::new("systemctl")
|
2019-01-26 16:54:18 +00:00
|
|
|
.args(&["show", real_service_name])
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.spawn()?;
|
|
|
|
|
|
|
|
use std::io::{BufRead,BufReader};
|
|
|
|
|
|
|
|
let mut result = json!({});
|
|
|
|
|
|
|
|
if let Some(ref mut stdout) = child.stdout {
|
|
|
|
for line in BufReader::new(stdout).lines() {
|
|
|
|
match line {
|
|
|
|
Ok(line) => {
|
|
|
|
let mut iter = line.splitn(2, '=');
|
|
|
|
let key = iter.next();
|
|
|
|
let value = iter.next();
|
|
|
|
if let (Some(key), Some(value)) = (key, value) {
|
|
|
|
result[key] = Value::from(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
log::error!("reading service config failed: {}", err);
|
|
|
|
let _ = child.kill();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let status = child.wait().unwrap();
|
|
|
|
if !status.success() {
|
|
|
|
bail!("systemctl show failed with {}", status);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
|
2019-01-27 11:40:31 +00:00
|
|
|
fn json_service_state(service: &str, status: Value) -> Value {
|
|
|
|
|
|
|
|
if let Some(desc) = status["Description"].as_str() {
|
|
|
|
let name = status["Name"].as_str().unwrap_or(service);
|
|
|
|
let state = status["SubState"].as_str().unwrap_or("unknown");
|
|
|
|
return json!({
|
|
|
|
"service": service,
|
|
|
|
"name": name,
|
|
|
|
"desc": desc,
|
|
|
|
"state": state,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Value::Null
|
|
|
|
}
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
node: {
|
|
|
|
schema: NODE_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
returns: {
|
|
|
|
description: "Returns a list of systemd services.",
|
|
|
|
type: Array,
|
|
|
|
items: {
|
|
|
|
description: "Service details.",
|
|
|
|
properties: {
|
|
|
|
service: {
|
|
|
|
schema: SERVICE_ID_SCHEMA,
|
|
|
|
},
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
description: "systemd service name.",
|
|
|
|
},
|
|
|
|
desc: {
|
|
|
|
type: String,
|
|
|
|
description: "systemd service description.",
|
|
|
|
},
|
|
|
|
state: {
|
|
|
|
type: String,
|
|
|
|
description: "systemd service 'SubState'.",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
access: {
|
2020-04-30 07:30:00 +00:00
|
|
|
permission: &Permission::Privilege(&["system", "services"], PRIV_SYS_AUDIT, false),
|
2020-04-16 10:46:39 +00:00
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Service list.
|
2019-01-26 16:54:18 +00:00
|
|
|
fn list_services(
|
2019-01-30 17:25:37 +00:00
|
|
|
_param: Value,
|
2019-01-26 16:54:18 +00:00
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
|
|
|
let mut list = vec![];
|
|
|
|
|
|
|
|
for service in &SERVICE_NAME_LIST {
|
|
|
|
match get_full_service_state(service) {
|
|
|
|
Ok(status) => {
|
2019-01-27 11:40:31 +00:00
|
|
|
let state = json_service_state(service, status);
|
|
|
|
if state != Value::Null {
|
|
|
|
list.push(state);
|
2019-01-26 16:54:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => log::error!("{}", err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::from(list))
|
|
|
|
}
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
node: {
|
|
|
|
schema: NODE_SCHEMA,
|
|
|
|
},
|
|
|
|
service: {
|
|
|
|
schema: SERVICE_ID_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
access: {
|
2020-04-30 07:30:00 +00:00
|
|
|
permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_AUDIT, false),
|
2020-04-16 10:46:39 +00:00
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Read service properties.
|
2019-01-27 11:40:31 +00:00
|
|
|
fn get_service_state(
|
2020-04-16 10:46:39 +00:00
|
|
|
service: String,
|
|
|
|
_param: Value,
|
2019-01-27 11:40:31 +00:00
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
let service = service.as_str();
|
2019-01-27 11:40:31 +00:00
|
|
|
|
|
|
|
if !SERVICE_NAME_LIST.contains(&service) {
|
|
|
|
bail!("unknown service name '{}'", service);
|
|
|
|
}
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
let status = get_full_service_state(&service)?;
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
Ok(json_service_state(&service, status))
|
2019-01-27 11:40:31 +00:00
|
|
|
}
|
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
fn run_service_command(service: &str, cmd: &str, auth_id: Authid) -> Result<Value, Error> {
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-08-14 10:03:08 +00:00
|
|
|
let workerid = format!("srv{}", &cmd);
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-07-25 18:23:12 +00:00
|
|
|
let cmd = match cmd {
|
2020-08-14 10:03:08 +00:00
|
|
|
"start"|"stop"|"restart"=> cmd.to_string(),
|
|
|
|
"reload" => "try-reload-or-restart".to_string(), // some services do not implement reload
|
2019-01-27 11:40:31 +00:00
|
|
|
_ => bail!("unknown service command '{}'", cmd),
|
2020-07-25 18:23:12 +00:00
|
|
|
};
|
2020-08-14 10:03:08 +00:00
|
|
|
let service = service.to_string();
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-08-14 10:03:08 +00:00
|
|
|
let upid = WorkerTask::new_thread(
|
|
|
|
&workerid,
|
|
|
|
Some(service.clone()),
|
2020-10-23 11:33:21 +00:00
|
|
|
auth_id,
|
2020-08-14 10:03:08 +00:00
|
|
|
false,
|
|
|
|
move |_worker| {
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-08-14 10:03:08 +00:00
|
|
|
if service == "proxmox-backup" && cmd == "stop" {
|
|
|
|
bail!("invalid service cmd '{} {}' cannot stop essential service!", service, cmd);
|
|
|
|
}
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-08-14 10:03:08 +00:00
|
|
|
let real_service_name = real_service_name(&service);
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-08-14 10:03:08 +00:00
|
|
|
let status = Command::new("systemctl")
|
|
|
|
.args(&[&cmd, real_service_name])
|
|
|
|
.status()?;
|
|
|
|
|
|
|
|
if !status.success() {
|
|
|
|
bail!("systemctl {} failed with {}", cmd, status);
|
|
|
|
}
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-08-14 10:03:08 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(upid.into())
|
2019-01-27 11:40:31 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
#[api(
|
|
|
|
protected: true,
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
node: {
|
|
|
|
schema: NODE_SCHEMA,
|
|
|
|
},
|
|
|
|
service: {
|
|
|
|
schema: SERVICE_ID_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
access: {
|
2020-04-30 07:30:00 +00:00
|
|
|
permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
|
2020-04-16 10:46:39 +00:00
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Start service.
|
2019-01-27 11:40:31 +00:00
|
|
|
fn start_service(
|
2020-04-16 10:46:39 +00:00
|
|
|
service: String,
|
|
|
|
_param: Value,
|
2020-08-14 10:03:08 +00:00
|
|
|
rpcenv: &mut dyn RpcEnvironment,
|
2019-01-27 11:40:31 +00:00
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
2020-08-14 10:03:08 +00:00
|
|
|
|
2019-01-27 11:40:31 +00:00
|
|
|
log::info!("starting service {}", service);
|
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
run_service_command(&service, "start", auth_id)
|
2019-01-27 11:40:31 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
#[api(
|
|
|
|
protected: true,
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
node: {
|
|
|
|
schema: NODE_SCHEMA,
|
|
|
|
},
|
|
|
|
service: {
|
|
|
|
schema: SERVICE_ID_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
access: {
|
2020-04-30 07:30:00 +00:00
|
|
|
permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
|
2020-04-16 10:46:39 +00:00
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Stop service.
|
2019-01-27 11:40:31 +00:00
|
|
|
fn stop_service(
|
2020-04-16 10:46:39 +00:00
|
|
|
service: String,
|
|
|
|
_param: Value,
|
2020-08-14 10:03:08 +00:00
|
|
|
rpcenv: &mut dyn RpcEnvironment,
|
2020-04-16 10:46:39 +00:00
|
|
|
) -> Result<Value, Error> {
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
2020-08-14 10:03:08 +00:00
|
|
|
|
2020-05-30 14:37:33 +00:00
|
|
|
log::info!("stopping service {}", service);
|
2019-01-27 11:40:31 +00:00
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
run_service_command(&service, "stop", auth_id)
|
2019-01-27 11:40:31 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
#[api(
|
|
|
|
protected: true,
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
node: {
|
|
|
|
schema: NODE_SCHEMA,
|
|
|
|
},
|
|
|
|
service: {
|
|
|
|
schema: SERVICE_ID_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
access: {
|
2020-04-30 07:30:00 +00:00
|
|
|
permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
|
2020-04-16 10:46:39 +00:00
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Retart service.
|
2019-01-27 11:40:31 +00:00
|
|
|
fn restart_service(
|
2020-04-16 10:46:39 +00:00
|
|
|
service: String,
|
|
|
|
_param: Value,
|
2020-08-14 10:03:08 +00:00
|
|
|
rpcenv: &mut dyn RpcEnvironment,
|
2019-01-27 11:40:31 +00:00
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
2020-08-14 10:03:08 +00:00
|
|
|
|
2019-01-27 11:40:31 +00:00
|
|
|
log::info!("re-starting service {}", service);
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
if &service == "proxmox-backup-proxy" {
|
2019-04-15 07:48:02 +00:00
|
|
|
// special case, avoid aborting running tasks
|
2020-10-23 11:33:21 +00:00
|
|
|
run_service_command(&service, "reload", auth_id)
|
2019-04-15 07:48:02 +00:00
|
|
|
} else {
|
2020-10-23 11:33:21 +00:00
|
|
|
run_service_command(&service, "restart", auth_id)
|
2019-04-15 07:48:02 +00:00
|
|
|
}
|
2019-01-27 11:40:31 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 10:46:39 +00:00
|
|
|
#[api(
|
|
|
|
protected: true,
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
node: {
|
|
|
|
schema: NODE_SCHEMA,
|
|
|
|
},
|
|
|
|
service: {
|
|
|
|
schema: SERVICE_ID_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
access: {
|
2020-04-30 07:30:00 +00:00
|
|
|
permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
|
2020-04-16 10:46:39 +00:00
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Reload service.
|
2019-01-27 11:40:31 +00:00
|
|
|
fn reload_service(
|
2020-04-16 10:46:39 +00:00
|
|
|
service: String,
|
|
|
|
_param: Value,
|
2020-08-14 10:03:08 +00:00
|
|
|
rpcenv: &mut dyn RpcEnvironment,
|
2019-01-27 11:40:31 +00:00
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
2020-08-14 10:03:08 +00:00
|
|
|
|
2019-01-27 11:40:31 +00:00
|
|
|
log::info!("reloading service {}", service);
|
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
run_service_command(&service, "reload", auth_id)
|
2019-01-27 11:40:31 +00:00
|
|
|
}
|
|
|
|
|
2019-11-21 08:36:41 +00:00
|
|
|
|
|
|
|
const SERVICE_ID_SCHEMA: Schema = StringSchema::new("Service ID.")
|
|
|
|
.max_length(256)
|
|
|
|
.schema();
|
|
|
|
|
2019-11-21 12:10:49 +00:00
|
|
|
#[sortable]
|
2020-04-16 10:46:39 +00:00
|
|
|
const SERVICE_SUBDIRS: SubdirMap = &sorted!([
|
2019-11-21 08:36:41 +00:00
|
|
|
(
|
|
|
|
"reload", &Router::new()
|
2020-04-16 10:46:39 +00:00
|
|
|
.post(&API_METHOD_RELOAD_SERVICE)
|
2019-11-21 08:36:41 +00:00
|
|
|
),
|
|
|
|
(
|
|
|
|
"restart", &Router::new()
|
2020-04-16 10:46:39 +00:00
|
|
|
.post(&API_METHOD_RESTART_SERVICE)
|
2019-11-21 08:36:41 +00:00
|
|
|
),
|
|
|
|
(
|
|
|
|
"start", &Router::new()
|
2020-04-16 10:46:39 +00:00
|
|
|
.post(&API_METHOD_START_SERVICE)
|
2019-11-21 08:36:41 +00:00
|
|
|
),
|
|
|
|
(
|
|
|
|
"state", &Router::new()
|
2020-04-16 10:46:39 +00:00
|
|
|
.get(&API_METHOD_GET_SERVICE_STATE)
|
2019-11-21 08:36:41 +00:00
|
|
|
),
|
|
|
|
(
|
|
|
|
"stop", &Router::new()
|
2020-04-16 10:46:39 +00:00
|
|
|
.post(&API_METHOD_STOP_SERVICE)
|
2019-11-21 08:36:41 +00:00
|
|
|
),
|
2020-04-16 10:46:39 +00:00
|
|
|
]);
|
2019-11-21 08:36:41 +00:00
|
|
|
|
|
|
|
const SERVICE_ROUTER: Router = Router::new()
|
|
|
|
.get(&list_subdirs_api_method!(SERVICE_SUBDIRS))
|
|
|
|
.subdirs(SERVICE_SUBDIRS);
|
|
|
|
|
|
|
|
pub const ROUTER: Router = Router::new()
|
2020-04-16 10:46:39 +00:00
|
|
|
.get(&API_METHOD_LIST_SERVICES)
|
2019-11-21 08:36:41 +00:00
|
|
|
.match_all("service", &SERVICE_ROUTER);
|