control systemd service

This commit is contained in:
jb-alvarado 2022-06-24 17:41:55 +02:00
parent a8578a10cc
commit 3bd18b3fa6
11 changed files with 109 additions and 10 deletions

2
Cargo.lock generated
View File

@ -1027,7 +1027,7 @@ dependencies = [
[[package]] [[package]]
name = "ffplayout-api" name = "ffplayout-api"
version = "0.3.0" version = "0.3.1"
dependencies = [ dependencies = [
"actix-multipart", "actix-multipart",
"actix-web", "actix-web",

3
assets/11-ffplayout Normal file
View File

@ -0,0 +1,3 @@
# give user www-data permission to control the ffplayout systemd service
www-data ALL = NOPASSWD: /bin/systemctl start ffplayout.service, /bin/systemctl stop ffplayout.service, /bin/systemctl reload ffplayout.service, /bin/systemctl restart ffplayout.service, /bin/systemctl status ffplayout.service, /bin/systemctl is-active ffplayout.service

View File

@ -138,6 +138,10 @@ Response is in JSON format
- **GET** `/api/control/{id}/media/last/`\ - **GET** `/api/control/{id}/media/last/`\
Response is in JSON format Response is in JSON format
- **POST** `/api/control/{id}/process/`\
JSON Data: `{"command": "<start/stop/restart/status>"}`
Response is in TEXT format
#### Playlist Operations #### Playlist Operations
- **GET** `/api/playlist/{id}/2022-06-20`\ - **GET** `/api/playlist/{id}/2022-06-20`\

View File

@ -4,7 +4,7 @@ description = "Rest API for ffplayout"
license = "GPL-3.0" license = "GPL-3.0"
authors = ["Jonathan Baecker jonbae77@gmail.com"] authors = ["Jonathan Baecker jonbae77@gmail.com"]
readme = "README.md" readme = "README.md"
version = "0.3.0" version = "0.3.1"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@ -17,9 +17,9 @@ use utils::{
routes::{ routes::{
add_preset, add_user, del_playlist, file_browser, gen_playlist, get_playlist, add_preset, add_user, del_playlist, file_browser, gen_playlist, get_playlist,
get_playout_config, get_presets, get_settings, jump_to_last, jump_to_next, login, get_playout_config, get_presets, get_settings, jump_to_last, jump_to_next, login,
media_current, media_last, media_next, move_rename, patch_settings, remove, reset_playout, media_current, media_last, media_next, move_rename, patch_settings, process_control,
save_file, save_playlist, send_text_message, update_playout_config, update_preset, remove, reset_playout, save_file, save_playlist, send_text_message, update_playout_config,
update_user, update_preset, update_user,
}, },
run_args, Role, run_args, Role,
}; };
@ -92,6 +92,7 @@ async fn main() -> std::io::Result<()> {
.service(media_current) .service(media_current)
.service(media_next) .service(media_next)
.service(media_last) .service(media_last)
.service(process_control)
.service(get_playlist) .service(get_playlist)
.service(save_playlist) .service(save_playlist)
.service(gen_playlist) .service(gen_playlist)

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::{collections::HashMap, process::Command};
use reqwest::{ use reqwest::{
header::{HeaderMap, AUTHORIZATION, CONTENT_TYPE}, header::{HeaderMap, AUTHORIZATION, CONTENT_TYPE},
@ -7,7 +7,8 @@ use reqwest::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use simplelog::*; use simplelog::*;
use crate::utils::{errors::ServiceError, playout_config}; use crate::utils::{errors::ServiceError, handles::db_get_settings, playout_config};
use ffplayout_lib::vec_strings;
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
struct RpcObj<T> { struct RpcObj<T> {
@ -44,6 +45,62 @@ impl<T> RpcObj<T> {
} }
} }
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Process {
pub command: String,
}
struct SystemD {
service: String,
cmd: Vec<String>,
}
impl SystemD {
async fn new(id: i64) -> Result<Self, ServiceError> {
let settings = db_get_settings(&id).await?;
Ok(Self {
service: settings.service,
cmd: vec_strings!["systemctl"],
})
}
fn start(mut self) -> Result<String, ServiceError> {
self.cmd
.append(&mut vec!["start".to_string(), self.service]);
Command::new("sudo").args(self.cmd).spawn()?;
Ok("Success".to_string())
}
fn stop(mut self) -> Result<String, ServiceError> {
self.cmd.append(&mut vec!["stop".to_string(), self.service]);
Command::new("sudo").args(self.cmd).spawn()?;
Ok("Success".to_string())
}
fn restart(mut self) -> Result<String, ServiceError> {
self.cmd
.append(&mut vec!["restart".to_string(), self.service]);
Command::new("sudo").args(self.cmd).spawn()?;
Ok("Success".to_string())
}
fn status(mut self) -> Result<String, ServiceError> {
self.cmd
.append(&mut vec!["is-active".to_string(), self.service]);
let output = Command::new("sudo").args(self.cmd).output()?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
}
fn create_header(auth: &str) -> HeaderMap { fn create_header(auth: &str) -> HeaderMap {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert( headers.insert(
@ -105,3 +162,15 @@ pub async fn media_info(id: i64, command: String) -> Result<Response, ServiceErr
post_request(id, json_obj).await post_request(id, json_obj).await
} }
pub async fn control_service(id: i64, command: &str) -> Result<String, ServiceError> {
let system_d = SystemD::new(id).await?;
match command {
"start" => system_d.start(),
"stop" => system_d.stop(),
"restart" => system_d.restart(),
"status" => system_d.status(),
_ => Err(ServiceError::BadRequest("Command not found!".to_string())),
}
}

View File

@ -59,3 +59,9 @@ impl From<actix_web::error::BlockingError> for ServiceError {
ServiceError::BadRequest(err.to_string()) ServiceError::BadRequest(err.to_string())
} }
} }
impl From<sqlx::Error> for ServiceError {
fn from(err: sqlx::Error) -> ServiceError {
ServiceError::BadRequest(err.to_string())
}
}

View File

@ -56,6 +56,7 @@ async fn create_schema() -> Result<SqliteQueryResult, sqlx::Error> {
preview_url TEXT NOT NULL, preview_url TEXT NOT NULL,
config_path TEXT NOT NULL, config_path TEXT NOT NULL,
extra_extensions TEXT NOT NULL, extra_extensions TEXT NOT NULL,
service TEXT NOT NULL,
UNIQUE(channel_name) UNIQUE(channel_name)
); );
CREATE TABLE IF NOT EXISTS user CREATE TABLE IF NOT EXISTS user
@ -108,9 +109,9 @@ pub async fn db_init() -> Result<&'static str, Box<dyn std::error::Error>> {
('Scrolling Text', 'We have a very important announcement to make.', 'ifnot(ld(1),st(1,t));if(lt(t,ld(1)+1),w+4,w-w/12*mod(t-ld(1),12*(w+tw)/w))', '(h-line_h)*0.9', ('Scrolling Text', 'We have a very important announcement to make.', 'ifnot(ld(1),st(1,t));if(lt(t,ld(1)+1),w+4,w-w/12*mod(t-ld(1),12*(w+tw)/w))', '(h-line_h)*0.9',
'24', '4', '#ffffff', '1.0', '1', '#000000@0x80', '4'); '24', '4', '#ffffff', '1.0', '1', '#000000@0x80', '4');
INSERT INTO roles(name) VALUES('admin'), ('user'), ('guest'); INSERT INTO roles(name) VALUES('admin'), ('user'), ('guest');
INSERT INTO settings(channel_name, preview_url, config_path, extra_extensions) INSERT INTO settings(channel_name, preview_url, config_path, extra_extensions, service)
VALUES('Channel 1', 'http://localhost/live/preview.m3u8', VALUES('Channel 1', 'http://localhost/live/preview.m3u8',
'/etc/ffplayout/ffplayout.yml', '.jpg,.jpeg,.png');"; '/etc/ffplayout/ffplayout.yml', '.jpg,.jpeg,.png', 'ffplayout.service');";
sqlx::query(query).bind(secret).execute(&instances).await?; sqlx::query(query).bind(secret).execute(&instances).await?;
instances.close().await; instances.close().await;

View File

@ -66,4 +66,5 @@ pub struct Settings {
#[sqlx(default)] #[sqlx(default)]
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub secret: String, pub secret: String,
pub service: String,
} }

View File

@ -12,7 +12,7 @@ use simplelog::*;
use crate::utils::{ use crate::utils::{
auth::{create_jwt, Claims}, auth::{create_jwt, Claims},
control::{control_state, media_info, send_message}, control::{control_service, control_state, media_info, send_message, Process},
errors::ServiceError, errors::ServiceError,
files::{browser, remove_file_or_folder, rename_file, upload, MoveObject, PathObject}, files::{browser, remove_file_or_folder, rename_file, upload, MoveObject, PathObject},
handles::{ handles::{
@ -352,6 +352,18 @@ pub async fn media_last(id: web::Path<i64>) -> Result<impl Responder, ServiceErr
} }
} }
/// curl -X GET http://localhost:8080/api/control/1/process/
/// --header 'Content-Type: application/json' --header 'Authorization: <TOKEN>'
/// -d '{"command": "start"}'
#[post("/control/{id}/process/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn process_control(
id: web::Path<i64>,
proc: web::Json<Process>,
) -> Result<impl Responder, ServiceError> {
control_service(*id, &proc.command).await
}
/// ---------------------------------------------------------------------------- /// ----------------------------------------------------------------------------
/// ffplayout playlist operations /// ffplayout playlist operations
/// ///

View File

@ -52,6 +52,7 @@ assets = [
"755" "755"
], ],
["../assets/ffpapi.service", "/lib/systemd/system/ffpapi.service", "644"], ["../assets/ffpapi.service", "/lib/systemd/system/ffpapi.service", "644"],
["../assets/11-ffplayout", "/etc/sudoers.d/11-ffplayout", "644"],
["../assets/ffplayout.yml", "/etc/ffplayout/ffplayout.yml", "644"], ["../assets/ffplayout.yml", "/etc/ffplayout/ffplayout.yml", "644"],
["../assets/logo.png", "/usr/share/ffplayout/logo.png", "644"], ["../assets/logo.png", "/usr/share/ffplayout/logo.png", "644"],
["../README.md", "/usr/share/doc/ffplayout/README", "644"], ["../README.md", "/usr/share/doc/ffplayout/README", "644"],
@ -69,6 +70,7 @@ assets = [
{ source = "../assets/ffplayout.yml", dest = "/etc/ffplayout/ffplayout.yml", mode = "644", config = true }, { source = "../assets/ffplayout.yml", dest = "/etc/ffplayout/ffplayout.yml", mode = "644", config = true },
{ source = "../assets/ffpapi.service", dest = "/lib/systemd/system/ffpapi.service", mode = "644" }, { source = "../assets/ffpapi.service", dest = "/lib/systemd/system/ffpapi.service", mode = "644" },
{ source = "../assets/ffplayout.service", dest = "/lib/systemd/system/ffplayout.service", mode = "644" }, { source = "../assets/ffplayout.service", dest = "/lib/systemd/system/ffplayout.service", mode = "644" },
{ source = "../assets/11-ffplayout", dest = "/etc/sudoers.d/11-ffplayout", mode = "644" },
{ source = "../README.md", dest = "/usr/share/doc/ffplayout/README", mode = "644", doc = true }, { source = "../README.md", dest = "/usr/share/doc/ffplayout/README", mode = "644", doc = true },
{ source = "../LICENSE", dest = "/usr/share/doc/ffplayout/LICENSE", mode = "644" }, { source = "../LICENSE", dest = "/usr/share/doc/ffplayout/LICENSE", mode = "644" },
{ source = "../assets/logo.png", dest = "/usr/share/ffplayout/logo.png", mode = "644" }, { source = "../assets/logo.png", dest = "/usr/share/ffplayout/logo.png", mode = "644" },