embed static files from frontend in ffpapi, add db path argument

This commit is contained in:
jb-alvarado 2023-10-31 23:43:33 +01:00
parent 688bac8c8c
commit b4cde6e12c
12 changed files with 133 additions and 56 deletions

63
Cargo.lock generated
View File

@ -278,6 +278,18 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "actix-web-static-files"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adf6d1ef6d7a60e084f9e0595e2a5234abda14e76c105ecf8e2d0e8800c41a1f"
dependencies = [
"actix-web",
"derive_more",
"futures-util",
"static-files",
]
[[package]]
name = "addr2line"
version = "0.21.0"
@ -731,6 +743,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "change-detection"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "159fa412eae48a1d94d0b9ecdb85c97ce56eb2a347c62394d3fdbf221adabc1a"
dependencies = [
"path-matchers",
"path-slash",
]
[[package]]
name = "chrono"
version = "0.4.31"
@ -1141,6 +1163,7 @@ dependencies = [
"actix-web",
"actix-web-grants",
"actix-web-httpauth",
"actix-web-static-files",
"argon2",
"chrono",
"clap",
@ -1151,6 +1174,7 @@ dependencies = [
"jsonwebtoken",
"lexical-sort",
"once_cell",
"path-clean",
"rand",
"regex",
"relative-path",
@ -1162,6 +1186,7 @@ dependencies = [
"serde_yaml",
"simplelog",
"sqlx",
"static-files",
"tokio",
]
@ -1424,6 +1449,12 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gloo-timers"
version = "0.2.6"
@ -2167,6 +2198,27 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "path-clean"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef"
[[package]]
name = "path-matchers"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36cd9b72a47679ec193a5f0229d9ab686b7bd45e1fbc59ccf953c9f3d83f7b2b"
dependencies = [
"glob",
]
[[package]]
name = "path-slash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696"
[[package]]
name = "pem"
version = "1.1.1"
@ -3036,6 +3088,17 @@ dependencies = [
"url",
]
[[package]]
name = "static-files"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64712ea1e3e140010e1d9605872ba205afa2ab5bd38191cc6ebd248ae1f6a06b"
dependencies = [
"change-detection",
"mime_guess",
"path-slash",
]
[[package]]
name = "stringprep"
version = "0.1.4"

2
debian/postinst vendored
View File

@ -23,8 +23,6 @@ if [ ! -d "/usr/share/ffplayout/db" ]; then
chown -R ${sysUser}: "/usr/share/ffplayout"
chown -R ${sysUser}: "/var/lib/ffplayout"
chown -R ${sysUser}: "/etc/ffplayout"
ln -s "/var/lib/ffplayout/tv-media" "/usr/share/ffplayout/public/"
fi
if [ ! -d "/var/log/ffplayout" ]; then

View File

@ -13,7 +13,7 @@ ffplayout provides ***.deb** and ***.rpm** packages, which makes it more easy to
6. use a revers proxy for SSL, Port is **8787**.
7. login with your browser, address without proxy would be: **http://[IP ADDRESS]:8787**
Default location for playlists and media files are: **/var/lib/ffplayout/**. If you need to change them, the media storage folder needs a symlink to **/usr/share/ffplayout/public/**.
Default location for playlists and media files are: **/var/lib/ffplayout/**.
When you don't need the frontend and API, skip enable the systemd service **ffpapi**.

View File

@ -15,6 +15,7 @@ actix-multipart = "0.6"
actix-web = "4"
actix-web-grants = "3"
actix-web-httpauth = "0.8"
actix-web-static-files = "4.0"
argon2 = "0.5"
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
clap = { version = "4.3", features = ["derive"] }
@ -24,6 +25,7 @@ futures-util = { version = "0.3", default-features = false, features = ["std"] }
jsonwebtoken = "8"
lexical-sort = "0.3"
once_cell = "1.18"
path-clean = "1.0"
rand = "0.8"
regex = "1"
relative-path = "1.8"
@ -34,9 +36,13 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
simplelog = { version = "0.12", features = ["paris"] }
static-files = "0.2"
sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] }
tokio = { version = "1.29", features = ["full"] }
[build-dependencies]
static-files = "0.2"
[[bin]]
name = "ffpapi"
path = "src/main.rs"

11
ffplayout-api/build.rs Normal file
View File

@ -0,0 +1,11 @@
use static_files::NpmBuild;
fn main() -> std::io::Result<()> {
NpmBuild::new("../ffplayout-frontend")
.install()?
.run("generate")?
.target("../ffplayout-frontend/.output/public")
.change_detection()
.to_resource_dir()
.build()
}

View File

@ -79,15 +79,16 @@ async fn create_schema(conn: &Pool<Sqlite>) -> Result<SqliteQueryResult, sqlx::E
sqlx::query(query).execute(conn).await
}
pub async fn db_init(domain: Option<String>) -> Result<&'static str, Box<dyn std::error::Error>> {
let db_path = db_path()?;
println!("--- db_path: {db_path:?}");
pub async fn db_init(
domain: Option<String>,
db: &Option<String>,
) -> Result<&'static str, Box<dyn std::error::Error>> {
let db_path = db_path(db.clone())?;
if !Sqlite::database_exists(db_path).await.unwrap_or(false) {
Sqlite::create_database(db_path).await.unwrap();
let pool = db_pool().await?;
let pool = db_pool(db).await?;
match create_schema(&pool).await {
Ok(_) => info!("Database created Successfully"),
@ -129,7 +130,7 @@ pub async fn db_init(domain: Option<String>) -> Result<&'static str, Box<dyn std
('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', '#000000@0x80', '4', '1.0', '1');";
let pool = db_pool().await?;
let pool = db_pool(db).await?;
sqlx::query(query)
.bind(secret)

View File

@ -5,8 +5,8 @@ pub mod models;
use crate::utils::db_path;
pub async fn db_pool() -> Result<Pool<Sqlite>, sqlx::Error> {
let db_path = db_path().unwrap();
pub async fn db_pool(db: &Option<String>) -> Result<Pool<Sqlite>, sqlx::Error> {
let db_path = db_path(db.clone()).unwrap();
let conn = SqlitePool::connect(db_path).await?;
Ok(conn)

View File

@ -1,9 +1,11 @@
use std::{path::Path, process::exit};
use std::process::exit;
use actix_files::Files;
use actix_web::{dev::ServiceRequest, middleware, web, App, Error, HttpMessage, HttpServer};
use actix_web::{
dev::ServiceRequest, middleware::Logger, web, App, Error, HttpMessage, HttpServer,
};
use actix_web_grants::permissions::AttachPermissions;
use actix_web_httpauth::{extractors::bearer::BearerAuth, middleware::HttpAuthentication};
use actix_web_static_files::ResourceFiles;
use clap::Parser;
use simplelog::*;
@ -18,6 +20,8 @@ use utils::{args_parse::Args, control::ProcessControl, db_path, init_config, run
use ffplayout_lib::utils::{init_logging, PlayoutConfig};
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
async fn validator(
req: ServiceRequest,
credentials: BearerAuth,
@ -36,18 +40,6 @@ async fn validator(
}
}
fn public_path() -> &'static str {
if Path::new("/usr/share/ffplayout/public/").is_dir() {
return "/usr/share/ffplayout/public/";
}
if Path::new("./public/").is_dir() {
return "./public/";
}
"./ffplayout-frontend/.output/public/"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let args = Args::parse();
@ -64,7 +56,7 @@ async fn main() -> std::io::Result<()> {
exit(c);
}
let pool = match db_pool().await {
let pool = match db_pool(&args.db).await {
Ok(p) => p,
Err(e) => {
error!("{e}");
@ -73,7 +65,7 @@ async fn main() -> std::io::Result<()> {
};
if let Some(conn) = args.listen {
if db_path().is_err() {
if db_path(args.db).is_err() {
error!("Database is not initialized! Init DB first and add admin user.");
exit(1);
}
@ -89,11 +81,15 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
let auth = HttpAuthentication::bearer(validator);
let db_pool = web::Data::new(pool.clone());
let generated = generate();
let logger = Logger::new("%{r}a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T")
.exclude_regex(r"/_nuxt/*");
App::new()
.app_data(db_pool)
.app_data(engine_process.clone())
.wrap(middleware::Logger::default())
.wrap(logger)
.service(login)
.service(
web::scope("/api")
@ -132,7 +128,7 @@ async fn main() -> std::io::Result<()> {
.service(get_program),
)
.service(get_file)
.service(Files::new("/", public_path()).index_file("index.html"))
.service(ResourceFiles::new("/", generated))
})
.bind((addr, port))?
.run()

View File

@ -8,6 +8,9 @@ pub struct Args {
#[clap(short, long, help = "ask for user credentials")]
pub ask: bool,
#[clap(long, help = "path to database file")]
pub db: Option<String>,
#[clap(short, long, help = "Listen on IP:PORT, like: 127.0.0.1:8787")]
pub listen: Option<String>,

View File

@ -1,4 +1,5 @@
use std::{
env,
error::Error,
fs::{self, File},
io::{stdin, stdout, Write},
@ -8,6 +9,7 @@ use std::{
use chrono::{format::ParseErrorKind, prelude::*};
use faccess::PathExt;
use once_cell::sync::OnceCell;
use path_clean::PathClean;
use rpassword::read_password;
use serde::{de, Deserialize, Deserializer};
use simplelog::*;
@ -74,7 +76,27 @@ pub async fn init_config(conn: &Pool<Sqlite>) {
INSTANCE.set(config).unwrap();
}
pub fn db_path() -> Result<&'static str, Box<dyn std::error::Error>> {
pub fn db_path(db: Option<String>) -> Result<&'static str, Box<dyn std::error::Error>> {
if let Some(path) = db {
let path_buf = Path::new(&path);
let absolute_path = if path_buf.is_absolute() {
path_buf.to_path_buf()
} else {
env::current_dir()?.join(path_buf)
}
.clean();
if let Some(abs_path) = absolute_path.parent() {
if abs_path.writable() {
return Ok(Box::leak(
absolute_path.to_string_lossy().to_string().into_boxed_str(),
));
} else {
error!("Given database path is not writable!");
}
}
}
let sys_path = Path::new("/usr/share/ffplayout/db");
let mut db_path = "./ffplayout.db";
@ -99,7 +121,7 @@ pub async fn run_args(mut args: Args) -> Result<(), i32> {
}
if args.init {
if let Err(e) = db_init(args.domain).await {
if let Err(e) = db_init(args.domain, &args.db).await {
panic!("{e}");
};
@ -163,7 +185,7 @@ pub async fn run_args(mut args: Args) -> Result<(), i32> {
token: None,
};
match db_pool().await {
match db_pool(&args.db).await {
Ok(conn) => {
if let Err(e) = insert_user(&conn, user).await {
error!("{e}");

View File

@ -112,11 +112,6 @@ assets = [
"/usr/share/man/man1/",
"644",
],
[
"../public/**/*",
"/usr/share/ffplayout/public/",
"644",
],
]
maintainer-scripts = "../debian/"
systemd-units = { enable = false, unit-scripts = "../assets" }
@ -188,11 +183,6 @@ assets = [
"/usr/share/man/man1/",
"644",
],
[
"../public/**/*",
"/usr/share/ffplayout/public/",
"644",
],
]
# REHL RPM PACKAGE
@ -214,7 +204,6 @@ assets = [
{ source = "../assets/logo.png", dest = "/usr/share/ffplayout/logo.png", mode = "644" },
{ source = "../assets/ffplayout.yml", dest = "/usr/share/ffplayout/ffplayout.yml.orig", mode = "644" },
{ source = "../assets/ffplayout.conf", dest = "/usr/share/ffplayout/ffplayout.conf.example", mode = "644" },
{ source = "../public/**/*", dest = "/usr/share/ffplayout/public/", mode = "644" },
{ source = "../debian/postinst", dest = "/usr/share/ffplayout/postinst", mode = "755" },
]
auto-req = "no"

View File

@ -3,22 +3,10 @@
source $(dirname "$0")/man_create.sh
target=$1
echo "build frontend"
echo
if [ ! -f 'ffplayout-frontend/package.json' ]; then
git submodule update --init
fi
yes | rm -rf public
cd ffplayout-frontend
npm install
npm run generate
cp -r .output/public ../public
cd ..
if [[ -n $target ]]; then
targets=($target)
else