-use failure::*;
+use std::process::{Command, Stdio};
-use crate::tools;
-use crate::api_schema::*;
-use crate::api_schema::router::*;
+use anyhow::{bail, Error};
use serde_json::{json, Value};
-use std::sync::Arc;
-use std::process::{Command, Stdio};
+use proxmox::{sortable, identity, list_subdirs_api_method};
+use proxmox::api::{api, Router, Permission, RpcEnvironment};
+use proxmox::api::router::SubdirMap;
+
+use pbs_api_types::{Authid, NODE_SCHEMA, SERVICE_ID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
+
+use proxmox_rest_server::WorkerTask;
static SERVICE_NAME_LIST: [&str; 7] = [
"proxmox-backup",
"systemd-timesyncd",
];
-fn real_service_name(service: &str) -> &str {
+pub fn real_service_name(service: &str) -> &str {
// since postfix package 3.1.0-3.1 the postfix unit is only here
// to manage subinstances, of which the default is called "-".
let real_service_name = real_service_name(service);
- let mut child = Command::new("/bin/systemctl")
+ let mut child = Command::new("systemctl")
.args(&["show", real_service_name])
.stdout(Stdio::piped())
.spawn()?;
Value::Null
}
-
+#[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: {
+ permission: &Permission::Privilege(&["system", "services"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Service list.
fn list_services(
_param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut RpcEnvironment,
) -> Result<Value, Error> {
let mut list = vec![];
Ok(Value::from(list))
}
+#[api(
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ service: {
+ schema: SERVICE_ID_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Read service properties.
fn get_service_state(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut RpcEnvironment,
+ service: String,
+ _param: Value,
) -> Result<Value, Error> {
- let service = tools::required_string_param(¶m, "service")?;
+ let service = service.as_str();
if !SERVICE_NAME_LIST.contains(&service) {
bail!("unknown service name '{}'", service);
}
- let status = get_full_service_state(service)?;
+ let status = get_full_service_state(&service)?;
- Ok(json_service_state(service, status))
+ Ok(json_service_state(&service, status))
}
-fn run_service_command(service: &str, cmd: &str) -> Result<Value, Error> {
+fn run_service_command(service: &str, cmd: &str, auth_id: Authid) -> Result<Value, Error> {
- // fixme: run background worker (fork_worker) ???
+ let workerid = format!("srv{}", &cmd);
- match cmd {
- "start"|"stop"|"restart"|"reload" => {},
+ let cmd = match cmd {
+ "start"|"stop"|"restart"=> cmd.to_string(),
+ "reload" => "try-reload-or-restart".to_string(), // some services do not implement reload
_ => bail!("unknown service command '{}'", cmd),
- }
+ };
+ let service = service.to_string();
+
+ let upid = WorkerTask::new_thread(
+ &workerid,
+ Some(service.clone()),
+ auth_id.to_string(),
+ false,
+ move |_worker| {
+
+ if service == "proxmox-backup" && cmd == "stop" {
+ bail!("invalid service cmd '{} {}' cannot stop essential service!", service, cmd);
+ }
- if service == "proxmox-backup" {
- if cmd != "restart" {
- bail!("invalid service cmd '{} {}'", service, cmd);
- }
- }
+ let real_service_name = real_service_name(&service);
- let real_service_name = real_service_name(service);
+ let status = Command::new("systemctl")
+ .args(&[&cmd, real_service_name])
+ .status()?;
- let status = Command::new("/bin/systemctl")
- .args(&[cmd, real_service_name])
- .status()?;
+ if !status.success() {
+ bail!("systemctl {} failed with {}", cmd, status);
+ }
- if !status.success() {
- bail!("systemctl {} failed with {}", cmd, status);
- }
+ Ok(())
+ }
+ )?;
- Ok(Value::Null)
+ Ok(upid.into())
}
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ service: {
+ schema: SERVICE_ID_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Start service.
fn start_service(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut RpcEnvironment,
+ service: String,
+ _param: Value,
+ rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
- let service = tools::required_string_param(¶m, "service")?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
log::info!("starting service {}", service);
- run_service_command(service, "start")
+ run_service_command(&service, "start", auth_id)
}
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ service: {
+ schema: SERVICE_ID_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Stop service.
fn stop_service(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut RpcEnvironment,
-) -> Result<Value, Error> {
+ service: String,
+ _param: Value,
+ rpcenv: &mut dyn RpcEnvironment,
+ ) -> Result<Value, Error> {
- let service = tools::required_string_param(¶m, "service")?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
- log::info!("stoping service {}", service);
+ log::info!("stopping service {}", service);
- run_service_command(service, "stop")
+ run_service_command(&service, "stop", auth_id)
}
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ service: {
+ schema: SERVICE_ID_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Retart service.
fn restart_service(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut RpcEnvironment,
+ service: String,
+ _param: Value,
+ rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
- let service = tools::required_string_param(¶m, "service")?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
log::info!("re-starting service {}", service);
- if service == "proxmox-backup-proxy" {
+ if &service == "proxmox-backup-proxy" {
// special case, avoid aborting running tasks
- run_service_command(service, "reload")
+ run_service_command(&service, "reload", auth_id)
} else {
- run_service_command(service, "restart")
+ run_service_command(&service, "restart", auth_id)
}
}
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ service: {
+ schema: SERVICE_ID_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Reload service.
fn reload_service(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut RpcEnvironment,
+ service: String,
+ _param: Value,
+ rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
- let service = tools::required_string_param(¶m, "service")?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
log::info!("reloading service {}", service);
- run_service_command(service, "reload")
+ run_service_command(&service, "reload", auth_id)
}
-pub fn router() -> Router {
-
- let service_id_schema : Arc<Schema> = Arc::new(
- StringSchema::new("Service ID.")
- .max_length(256)
- .into()
- );
-
- let service_api = Router::new()
- .subdir(
- "state",
- Router::new()
- .get(ApiMethod::new(
- get_service_state,
- ObjectSchema::new("Read service properties.")
- .required("node", crate::api2::node::NODE_SCHEMA.clone())
- .required("service", service_id_schema.clone()))
- )
- )
- .subdir(
- "start",
- Router::new()
- .post(
- ApiMethod::new(
- start_service,
- ObjectSchema::new("Start service.")
- .required("node", crate::api2::node::NODE_SCHEMA.clone())
- .required("service", service_id_schema.clone())
- ).protected(true)
- )
- )
- .subdir(
- "stop",
- Router::new()
- .post(
- ApiMethod::new(
- stop_service,
- ObjectSchema::new("Stop service.")
- .required("node", crate::api2::node::NODE_SCHEMA.clone())
- .required("service", service_id_schema.clone())
- ).protected(true)
- )
- )
- .subdir(
- "restart",
- Router::new()
- .post(
- ApiMethod::new(
- restart_service,
- ObjectSchema::new("Restart service.")
- .required("node", crate::api2::node::NODE_SCHEMA.clone())
- .required("service", service_id_schema.clone())
- ).protected(true)
- )
- )
- .subdir(
- "reload",
- Router::new()
- .post(
- ApiMethod::new(
- reload_service,
- ObjectSchema::new("Reload service.")
- .required("node", crate::api2::node::NODE_SCHEMA.clone())
- .required("service", service_id_schema.clone())
- ).protected(true)
- )
- )
- .list_subdirs();
-
- let route = Router::new()
- .get(
- ApiMethod::new(
- list_services,
- ObjectSchema::new("Service list.")
- .required("node", crate::api2::node::NODE_SCHEMA.clone())
- ).returns(
- ArraySchema::new(
- "Returns a list of systemd services.",
- ObjectSchema::new("Service details.")
- .required("service", service_id_schema.clone())
- .required("name", StringSchema::new("systemd service name."))
- .required("desc", StringSchema::new("systemd service description."))
- .required("state", StringSchema::new("systemd service 'SubState'."))
- .into()
- )
- )
- )
- .match_all("service", service_api);
-
- route
-}
+#[sortable]
+const SERVICE_SUBDIRS: SubdirMap = &sorted!([
+ (
+ "reload", &Router::new()
+ .post(&API_METHOD_RELOAD_SERVICE)
+ ),
+ (
+ "restart", &Router::new()
+ .post(&API_METHOD_RESTART_SERVICE)
+ ),
+ (
+ "start", &Router::new()
+ .post(&API_METHOD_START_SERVICE)
+ ),
+ (
+ "state", &Router::new()
+ .get(&API_METHOD_GET_SERVICE_STATE)
+ ),
+ (
+ "stop", &Router::new()
+ .post(&API_METHOD_STOP_SERVICE)
+ ),
+]);
+
+const SERVICE_ROUTER: Router = Router::new()
+ .get(&list_subdirs_api_method!(SERVICE_SUBDIRS))
+ .subdirs(SERVICE_SUBDIRS);
+
+pub const ROUTER: Router = Router::new()
+ .get(&API_METHOD_LIST_SERVICES)
+ .match_all("service", &SERVICE_ROUTER);