aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-30 10:47:54 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-30 10:47:54 +0200
commita2ef3f6ec4c830611fde1a2e935588ccbbc61c03 (patch)
treeddcc1cb501e6c7237edd491aa7136d02150d03d3 /server
parent212a0f23bc894faf88e159560c113f504349cc05 (diff)
downloadjellything-a2ef3f6ec4c830611fde1a2e935588ccbbc61c03.tar
jellything-a2ef3f6ec4c830611fde1a2e935588ccbbc61c03.tar.bz2
jellything-a2ef3f6ec4c830611fde1a2e935588ccbbc61c03.tar.zst
config works
Diffstat (limited to 'server')
-rw-r--r--server/Cargo.toml24
-rw-r--r--server/src/compat/jellyfin/mod.rs10
-rw-r--r--server/src/config.rs43
-rw-r--r--server/src/logic/stream.rs3
-rw-r--r--server/src/main.rs48
-rw-r--r--server/src/routes.rs13
-rw-r--r--server/src/ui/admin/mod.rs23
-rw-r--r--server/src/ui/assets.rs59
-rw-r--r--server/src/ui/error.rs2
-rw-r--r--server/src/ui/mod.rs3
-rw-r--r--server/src/ui/player.rs3
11 files changed, 132 insertions, 99 deletions
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 57d0c29..be8abdb 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -13,25 +13,23 @@ jellycache = { path = "../cache" }
jellyui = { path = "../ui" }
jellylogic = { path = "../logic" }
-serde = { version = "1.0.217", features = ["derive", "rc"] }
-bincode = { version = "2.0.0-rc.3", features = ["serde", "derive"] }
-serde_json = "1.0.138"
-
-log = { workspace = true }
anyhow = { workspace = true }
-env_logger = "0.11.6"
-rand = "0.9.0"
+async-recursion = "1.1.1"
base64 = "0.22.1"
-chrono = { version = "0.4.39", features = ["serde"] }
+bincode = { version = "2.0.0-rc.3", features = ["serde", "derive"] }
chashmap = "2.2.2"
-
-async-recursion = "1.1.1"
+chrono = { version = "0.4.39", features = ["serde"] }
+env_logger = "0.11.6"
futures = "0.3.31"
-tokio = { workspace = true }
-tokio-util = { version = "0.7.13", features = ["io", "io-util"] }
-
+log = { workspace = true }
+rand = "0.9.0"
rocket = { workspace = true, features = ["secrets", "json"] }
rocket_ws = { workspace = true }
+serde = { version = "1.0.217", features = ["derive", "rc"] }
+serde_json = "1.0.138"
+serde_yml = "0.0.12"
+tokio = { workspace = true }
+tokio-util = { version = "0.7.13", features = ["io", "io-util"] }
[build-dependencies]
glob = "0.3.2"
diff --git a/server/src/compat/jellyfin/mod.rs b/server/src/compat/jellyfin/mod.rs
index f999060..1c602ca 100644
--- a/server/src/compat/jellyfin/mod.rs
+++ b/server/src/compat/jellyfin/mod.rs
@@ -7,7 +7,7 @@ pub mod models;
use crate::{helper::A, ui::error::MyResult};
use anyhow::{anyhow, Context};
-use jellybase::{database::Database, CONF};
+use jellybase::database::Database;
use jellycommon::{
api::{FilterProperty, NodeFilterSort, SortOrder, SortProperty},
routes::{u_asset, u_node_slug_backdrop, u_node_slug_poster},
@@ -19,7 +19,7 @@ use jellylogic::{
filter_sort::filter_and_sort_nodes, login::login_logic, node::DatabaseNodeUserDataExt,
session::Session,
};
-use jellyui::node_page::aspect_class;
+use jellyui::{get_brand, get_slogan, node_page::aspect_class};
use models::*;
use rocket::{
get,
@@ -47,7 +47,7 @@ pub fn r_jellyfin_system_info_public_case() -> Json<Value> {
pub fn r_jellyfin_system_info_public() -> Json<Value> {
Json(json!({
"LocalAddress": LOCAL_ADDRESS,
- "ServerName": CONF.brand.clone(),
+ "ServerName": get_brand(),
"Version": VERSION,
"ProductName": "Jellything",
"OperatingSystem": "",
@@ -59,7 +59,7 @@ pub fn r_jellyfin_system_info_public() -> Json<Value> {
#[get("/Branding/Configuration")]
pub fn r_jellyfin_branding_configuration() -> Json<Value> {
Json(json!({
- "LoginDisclaimer": format!("{} - {}", CONF.brand, CONF.slogan),
+ "LoginDisclaimer": format!("{} - {}", get_brand(), get_slogan()),
"CustomCss": "",
"SplashscreenEnabled": false,
}))
@@ -122,7 +122,7 @@ pub fn r_jellyfin_system_info(_session: A<Session>) -> Json<Value> {
"EncoderLocation": "System",
"SystemArchitecture": "X64",
"LocalAddress": LOCAL_ADDRESS,
- "ServerName": CONF.brand,
+ "ServerName": get_brand(),
"Version": VERSION,
"OperatingSystem": "",
"Id": SERVER_ID
diff --git a/server/src/config.rs b/server/src/config.rs
new file mode 100644
index 0000000..27074b4
--- /dev/null
+++ b/server/src/config.rs
@@ -0,0 +1,43 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2025 metamuffin <metamuffin.org>
+*/
+
+use anyhow::{anyhow, Context, Result};
+use serde::Deserialize;
+use std::env::{args, var};
+use tokio::fs::read_to_string;
+
+#[derive(Debug, Deserialize)]
+struct Config {
+ transcoder: jellytranscoder::Config,
+ ui: jellyui::Config,
+ stream: jellystream::Config,
+ cache: jellycache::Config,
+ server: crate::Config,
+ base: jellybase::Config,
+ logic: jellylogic::Config,
+}
+
+pub async fn load_config() -> Result<()> {
+ let path = args()
+ .nth(1)
+ .or_else(|| var("JELLYTHING_CONFIG").ok())
+ .ok_or(anyhow!(
+ "No config supplied. Use first argument or JELLYTHING_CONFIG environment variable."
+ ))?;
+
+ let config_raw = read_to_string(path).await.context("reading main config")?;
+ let config: Config = serde_yml::from_str(&config_raw).context("parsing main config")?;
+
+ *jellystream::CONF_PRELOAD.lock().unwrap() = Some(config.stream);
+ *jellytranscoder::CONF_PRELOAD.lock().unwrap() = Some(config.transcoder);
+ *jellycache::CONF_PRELOAD.lock().unwrap() = Some(config.cache);
+ *jellylogic::CONF_PRELOAD.lock().unwrap() = Some(config.logic);
+ *jellybase::CONF_PRELOAD.lock().unwrap() = Some(config.base);
+ *crate::CONF_PRELOAD.lock().unwrap() = Some(config.server);
+ *jellyui::CONF_PRELOAD.lock().unwrap() = Some(config.ui);
+
+ Ok(())
+}
diff --git a/server/src/logic/stream.rs b/server/src/logic/stream.rs
index 9d4db6d..89589c7 100644
--- a/server/src/logic/stream.rs
+++ b/server/src/logic/stream.rs
@@ -5,7 +5,7 @@
*/
use crate::{database::Database, helper::A, ui::error::MyError};
use anyhow::{anyhow, Result};
-use jellybase::{assetfed::AssetInner, federation::Federation};
+use jellybase::assetfed::AssetInner;
use jellycommon::{stream::StreamSpec, TrackSource};
use jellylogic::session::Session;
use jellystream::SMediaInfo;
@@ -43,7 +43,6 @@ pub async fn r_stream_head(
#[get("/n/<id>/stream?<spec..>")]
pub async fn r_stream(
_session: A<Session>,
- _federation: &State<Federation>,
db: &State<Database>,
id: &str,
range: Option<RequestRange>,
diff --git a/server/src/main.rs b/server/src/main.rs
index 94a53c7..ea75208 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -8,45 +8,65 @@
#![recursion_limit = "4096"]
use anyhow::Context;
+use config::load_config;
use database::Database;
-use jellybase::{federation::Federation, CONF, SECRETS};
-use jellylogic::{admin::log::enable_logging, login::hash_password};
+use jellylogic::{admin::log::enable_logging, login::create_admin_account};
use log::{error, info, warn};
use routes::build_rocket;
-use tokio::fs::create_dir_all;
+use serde::{Deserialize, Serialize};
+use std::sync::Mutex;
+use std::{path::PathBuf, process::exit, sync::LazyLock};
pub use jellybase::database;
pub mod api;
pub mod compat;
+pub mod config;
pub mod helper;
pub mod locale;
pub mod logic;
pub mod routes;
pub mod ui;
+#[rustfmt::skip]
+#[derive(Debug, Deserialize, Serialize, Default)]
+pub struct Config {
+ database_path: PathBuf,
+ asset_path: PathBuf,
+ cookie_key: Option<String>,
+ tls:bool,
+ hostname: String,
+}
+
+pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None);
+static CONF: LazyLock<Config> = LazyLock::new(|| {
+ CONF_PRELOAD
+ .lock()
+ .unwrap()
+ .take()
+ .expect("cache config not preloaded. logic error")
+});
+
#[rocket::main]
async fn main() {
enable_logging();
+ info!("loading config...");
+ if let Err(e) = load_config().await {
+ error!("error {e:?}");
+ exit(1);
+ }
+
#[cfg(feature = "bypass-auth")]
log::warn!("authentification bypass enabled");
- create_dir_all(&CONF.cache_path).await.unwrap();
let database = Database::open(&CONF.database_path)
.context("opening database")
.unwrap();
- let federation = Federation::initialize();
- if let Some(username) = &CONF.admin_username
- && let Some(password) = &SECRETS.admin_password
- {
- database
- .create_admin_user(username, hash_password(username, password))
- .unwrap();
- } else {
- info!("admin account disabled")
+ if let Err(e) = create_admin_account(&database) {
+ error!("failed to create admin account: {e:?}");
}
- let r = build_rocket(database, federation).launch().await;
+ let r = build_rocket(database).launch().await;
match r {
Ok(_) => warn!("server shutdown"),
Err(e) => error!("server exited: {e}"),
diff --git a/server/src/routes.rs b/server/src/routes.rs
index ef7b067..da3a389 100644
--- a/server/src/routes.rs
+++ b/server/src/routes.rs
@@ -13,8 +13,8 @@ use crate::ui::{
},
admin::{
log::{r_admin_log, r_admin_log_stream},
- r_admin_dashboard, r_admin_delete_cache, r_admin_import, r_admin_invite,
- r_admin_remove_invite, r_admin_transcode_posters, r_admin_update_search,
+ r_admin_dashboard, r_admin_import, r_admin_invite, r_admin_remove_invite,
+ r_admin_transcode_posters, r_admin_update_search,
user::{r_admin_remove_user, r_admin_user, r_admin_user_permission, r_admin_users},
},
assets::{r_asset, r_item_backdrop, r_item_poster, r_node_thumbnail, r_person_asset},
@@ -28,6 +28,7 @@ use crate::ui::{
stats::r_stats,
style::{r_assets_font, r_assets_js, r_assets_js_map, r_assets_style},
};
+use crate::CONF;
use crate::{
api::{
r_api_account_login, r_api_asset_token_raw, r_api_nodes_modified_since, r_api_root,
@@ -61,7 +62,6 @@ use crate::{
},
};
use base64::Engine;
-use jellybase::{federation::Federation, CONF, SECRETS};
use log::warn;
use rand::random;
use rocket::{
@@ -76,7 +76,7 @@ macro_rules! uri {
};
}
-pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build> {
+pub fn build_rocket(database: Database) -> Rocket<Build> {
rocket::build()
.configure(Config {
address: std::env::var("BIND_ADDR")
@@ -86,8 +86,7 @@ pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build>
.map(|e| e.parse().unwrap())
.unwrap_or(8000),
secret_key: SecretKey::derive_from(
- SECRETS
- .cookie_key
+ CONF.cookie_key
.clone()
.unwrap_or_else(|| {
warn!("cookie_key not configured, generating a random one.");
@@ -99,7 +98,6 @@ pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build>
..Default::default()
})
.manage(database)
- .manage(federation)
.manage(PlayersyncChannels::default())
.attach(AdHoc::on_response("set server header", |_req, res| {
res.set_header(Header::new("server", "jellything"));
@@ -129,7 +127,6 @@ pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build>
r_account_settings_post,
r_account_settings,
r_admin_dashboard,
- r_admin_delete_cache,
r_admin_import,
r_admin_invite,
r_admin_log_stream,
diff --git a/server/src/ui/admin/mod.rs b/server/src/ui/admin/mod.rs
index 3a9e4e2..62c5940 100644
--- a/server/src/ui/admin/mod.rs
+++ b/server/src/ui/admin/mod.rs
@@ -12,7 +12,7 @@ use super::{
};
use crate::{database::Database, helper::A, locale::AcceptLanguage};
use anyhow::{anyhow, Context};
-use jellybase::{assetfed::AssetInner, federation::Federation, CONF};
+use jellybase::assetfed::AssetInner;
use jellycommon::routes::u_admin_dashboard;
use jellyimport::{import_wrap, is_importing, IMPORT_ERRORS};
use jellylogic::session::AdminSession;
@@ -103,7 +103,6 @@ pub async fn r_admin_remove_invite(
pub async fn r_admin_import(
session: A<AdminSession>,
database: &State<Database>,
- _federation: &State<Federation>,
incremental: bool,
) -> MyResult<Redirect> {
drop(session);
@@ -139,26 +138,6 @@ pub async fn r_admin_update_search(
Ok(Redirect::temporary(u_admin_dashboard()))
}
-#[post("/admin/delete_cache")]
-pub async fn r_admin_delete_cache(
- session: A<AdminSession>,
- database: &State<Database>,
-) -> MyResult<Redirect> {
- drop(session);
- let t = Instant::now();
- let r = tokio::fs::remove_dir_all(&CONF.cache_path).await;
- tokio::fs::create_dir(&CONF.cache_path).await?;
- // admin_dashboard(
- // database,
- // Some(
- // r.map_err(|e| e.into())
- // .map(|_| format!("Cache deleted; took {:?}", t.elapsed())),
- // ),
- // )
- // .await
- Ok(Redirect::temporary(u_admin_dashboard()))
-}
-
static SEM_TRANSCODING: Semaphore = Semaphore::const_new(1);
fn is_transcoding() -> bool {
SEM_TRANSCODING.available_permits() == 0
diff --git a/server/src/ui/assets.rs b/server/src/ui/assets.rs
index 3b9319e..596661a 100644
--- a/server/src/ui/assets.rs
+++ b/server/src/ui/assets.rs
@@ -4,11 +4,9 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::error::MyResult;
-use crate::helper::{cache::CacheControlFile, A};
+use crate::{helper::{cache::CacheControlFile, A}, CONF};
use anyhow::{anyhow, bail, Context};
-use base64::Engine;
-use jellybase::{assetfed::AssetInner, database::Database, federation::Federation, CONF};
-use jellycache::async_cache_file;
+use jellybase::{assetfed::AssetInner, database::Database};
use jellycommon::{LocalTrack, NodeID, PeopleGroup, SourceTrackKind, TrackSource};
use jellylogic::session::Session;
use log::info;
@@ -21,22 +19,23 @@ pub const AVIF_SPEED: u8 = 5;
#[get("/asset/<token>?<width>")]
pub async fn r_asset(
_session: A<Session>,
- fed: &State<Federation>,
token: &str,
width: Option<usize>,
) -> MyResult<(ContentType, CacheControlFile)> {
let width = width.unwrap_or(2048);
let asset = AssetInner::deser(token)?;
- let path = if let AssetInner::Federated { host, asset } = asset {
- let session = fed.get_session(&host).await?;
+ let path =
+ // if let AssetInner::Federated { host, asset } = asset {
+ // let session = fed.get_session(&host).await?;
- let asset = base64::engine::general_purpose::URL_SAFE.encode(asset);
- async_cache_file("fed-asset", &asset, |out| async {
- session.asset(out, &asset, width).await
- })
- .await?
- } else {
+ // let asset = base64::engine::general_purpose::URL_SAFE.encode(asset);
+ // async_cache_file("fed-asset", &asset, |out| async {
+ // session.asset(out, &asset, width).await
+ // })
+ // .await?
+ // } else
+ {
let source = resolve_asset(asset).await.context("resolving asset")?;
// fit the resolution into a finite set so the maximum cache is finite too.
@@ -56,7 +55,7 @@ pub async fn resolve_asset(asset: AssetInner) -> anyhow::Result<PathBuf> {
match asset {
AssetInner::Cache(c) => Ok(c.abs()),
AssetInner::Assets(c) => Ok(CONF.asset_path.join(c)),
- AssetInner::Media(c) => Ok(CONF.media_path.join(c)),
+ AssetInner::Media(c) => Ok(c),
_ => bail!("wrong asset type"),
}
}
@@ -138,7 +137,6 @@ pub async fn r_person_asset(
pub async fn r_node_thumbnail(
_session: A<Session>,
db: &State<Database>,
- fed: &State<Federation>,
id: A<NodeID>,
t: f64,
width: Option<usize>,
@@ -146,7 +144,7 @@ pub async fn r_node_thumbnail(
let node = db.get_node(id.0)?.ok_or(anyhow!("node does not exist"))?;
let media = node.media.as_ref().ok_or(anyhow!("no media"))?;
- let (thumb_track_index, thumb_track) = media
+ let (thumb_track_index, _thumb_track) = media
.tracks
.iter()
.enumerate()
@@ -171,23 +169,24 @@ pub async fn r_node_thumbnail(
return Err(anyhow!("track set to wrong asset type").into());
};
// the track selected might be different from thumb_track
- jellytranscoder::thumbnail::create_thumbnail(&CONF.media_path.join(path), t).await?
+ jellytranscoder::thumbnail::create_thumbnail(&path, t).await?
}
TrackSource::Remote(_) => {
- // TODO in the new system this is preferrably a property of node ext for regular fed
- let session = fed
- .get_session(
- thumb_track
- .federated
- .last()
- .ok_or(anyhow!("federation broken"))?,
- )
- .await?;
+ // // TODO in the new system this is preferrably a property of node ext for regular fed
+ // let session = fed
+ // .get_session(
+ // thumb_track
+ // .federated
+ // .last()
+ // .ok_or(anyhow!("federation broken"))?,
+ // )
+ // .await?;
- async_cache_file("fed-thumb", (id.0, t as i64), |out| {
- session.node_thumbnail(out, id.0.into(), 2048, t)
- })
- .await?
+ // async_cache_file("fed-thumb", (id.0, t as i64), |out| {
+ // session.node_thumbnail(out, id.0.into(), 2048, t)
+ // })
+ // .await?
+ todo!()
}
};
diff --git a/server/src/ui/error.rs b/server/src/ui/error.rs
index 0ea1a8d..05249af 100644
--- a/server/src/ui/error.rs
+++ b/server/src/ui/error.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use jellybase::CONF;
+use crate::CONF;
use log::info;
use rocket::{
catch,
diff --git a/server/src/ui/mod.rs b/server/src/ui/mod.rs
index 17a8b6f..041dadc 100644
--- a/server/src/ui/mod.rs
+++ b/server/src/ui/mod.rs
@@ -3,10 +3,9 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use crate::{helper::A, locale::AcceptLanguage};
+use crate::{helper::A, locale::AcceptLanguage, CONF};
use error::MyResult;
use home::rocket_uri_macro_r_home;
-use jellybase::CONF;
use jellylogic::session::Session;
use jellyui::{render_page, scaffold::RenderInfo, CustomPage};
use rocket::{
diff --git a/server/src/ui/player.rs b/server/src/ui/player.rs
index 94ca6ac..300e9d2 100644
--- a/server/src/ui/player.rs
+++ b/server/src/ui/player.rs
@@ -4,8 +4,7 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::error::MyResult;
-use crate::{database::Database, helper::A, locale::AcceptLanguage};
-use jellybase::CONF;
+use crate::{database::Database, helper::A, locale::AcceptLanguage, CONF};
use jellycommon::{
api::NodeFilterSort,
stream::{StreamContainer, StreamSpec},