diff --git a/src/api2/node.rs b/src/api2/node.rs index b40c46bc..6c3b13fc 100644 --- a/src/api2/node.rs +++ b/src/api2/node.rs @@ -13,7 +13,9 @@ pub fn router() -> Router { let route = Router::new() .get(ApiMethod::new( |_,_,_| Ok(json!([ + {"subdir": "dns"}, {"subdir": "network"}, + {"subdir": "services"}, {"subdir": "syslog"}, {"subdir": "time"}, ])), diff --git a/src/api2/node/services.rs b/src/api2/node/services.rs index d1c19962..76279f7c 100644 --- a/src/api2/node/services.rs +++ b/src/api2/node/services.rs @@ -19,15 +19,22 @@ static SERVICE_NAME_LIST: [&str; 6] = [ "systemd-timesyncd", ]; -fn get_full_service_state(service: &str) -> Result { - - let mut real_service_name = service; +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 "-". // This is where we look for the daemon status - if service == "postfix" { real_service_name = "postfix@-"; } + if service == "postfix" { + "postfix@-" + } else { + service + } +} + +fn get_full_service_state(service: &str) -> Result { + + let real_service_name = real_service_name(service); let mut child = Command::new("/bin/systemctl") .args(&["show", real_service_name]) @@ -66,6 +73,23 @@ fn get_full_service_state(service: &str) -> Result { Ok(result) } +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 +} + + fn list_services( param: Value, _info: &ApiMethod, @@ -77,15 +101,9 @@ fn list_services( for service in &SERVICE_NAME_LIST { match get_full_service_state(service) { Ok(status) => { - 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"); - list.push(json!({ - "service": service, - "name": name, - "desc": desc, - "state": state, - })); + let state = json_service_state(service, status); + if state != Value::Null { + list.push(state); } } Err(err) => log::error!("{}", err), @@ -95,8 +113,170 @@ fn list_services( Ok(Value::from(list)) } +fn get_service_state( + param: Value, + _info: &ApiMethod, + rpcenv: &mut RpcEnvironment, +) -> Result { + + let service = tools::required_string_param(¶m, "service")?; + + if !SERVICE_NAME_LIST.contains(&service) { + bail!("unknown service name '{}'", service); + } + + let status = get_full_service_state(service)?; + + Ok(json_service_state(service, status)) +} + +fn run_service_command(service: &str, cmd: &str) -> Result { + + // fixme: run background worker (fork_worker) ??? + + match cmd { + "start"|"stop"|"restart"|"reload" => {}, + _ => bail!("unknown service command '{}'", cmd), + } + + if service == "proxmox-backup" { + if cmd != "restart" { + bail!("invalid service cmd '{} {}'", service, cmd); + } + } + + let real_service_name = real_service_name(service); + + let status = Command::new("/bin/systemctl") + .args(&[cmd, real_service_name]) + .status()?; + + if !status.success() { + bail!("systemctl {} failed with {}", cmd, status); + } + + Ok(Value::Null) +} + +fn start_service( + param: Value, + _info: &ApiMethod, + rpcenv: &mut RpcEnvironment, +) -> Result { + + let service = tools::required_string_param(¶m, "service")?; + + log::info!("starting service {}", service); + + run_service_command(service, "start") +} + +fn stop_service( + param: Value, + _info: &ApiMethod, + rpcenv: &mut RpcEnvironment, +) -> Result { + + let service = tools::required_string_param(¶m, "service")?; + + log::info!("stoping service {}", service); + + run_service_command(service, "stop") +} + +fn restart_service( + param: Value, + _info: &ApiMethod, + rpcenv: &mut RpcEnvironment, +) -> Result { + + let service = tools::required_string_param(¶m, "service")?; + + log::info!("re-starting service {}", service); + + run_service_command(service, "restart") +} + +fn reload_service( + param: Value, + _info: &ApiMethod, + rpcenv: &mut RpcEnvironment, +) -> Result { + + let service = tools::required_string_param(¶m, "service")?; + + log::info!("reloading service {}", service); + + run_service_command(service, "reload") +} + pub fn router() -> Router { + let service_id_schema : Arc = Arc::new( + StringSchema::new("Service ID.") + .max_length(256) + .into() + ); + + let service_api = Router::new() + .get(ApiMethod::new( + |_,_,_| { + let mut result = vec![]; + for cmd in &["state", "start", "stop", "restart", "reload"] { + result.push(json!({"subdir": cmd })); + } + Ok(Value::from(result)) + }, + ObjectSchema::new("Directory index.") + .required("service", service_id_schema.clone())) + ) + .subdir( + "state", + Router::new() + .get(ApiMethod::new( + get_service_state, + ObjectSchema::new("Read service properties.") + .required("service", service_id_schema.clone())) + ) + ) + .subdir( + "start", + Router::new() + .post(ApiMethod::new( + start_service, + ObjectSchema::new("Start service.") + .required("service", service_id_schema.clone())) + ) + ) + .subdir( + "stop", + Router::new() + .post(ApiMethod::new( + stop_service, + ObjectSchema::new("Stop service.") + .required("service", service_id_schema.clone())) + ) + ) + .subdir( + "restart", + Router::new() + .post(ApiMethod::new( + restart_service, + ObjectSchema::new("Restart service.") + .required("service", service_id_schema.clone())) + ) + ) + .subdir( + "reload", + Router::new() + .post(ApiMethod::new( + reload_service, + ObjectSchema::new("Reload service.") + .required("service", service_id_schema.clone())) + ) + ) + ; + let route = Router::new() .get( ApiMethod::new( @@ -106,14 +286,15 @@ pub fn router() -> Router { ArraySchema::new( "Returns a list of systemd services.", ObjectSchema::new("Service details.") - .required("service", StringSchema::new("Service ID.")) + .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 }