aboutsummaryrefslogtreecommitdiff
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
parent212a0f23bc894faf88e159560c113f504349cc05 (diff)
downloadjellything-a2ef3f6ec4c830611fde1a2e935588ccbbc61c03.tar
jellything-a2ef3f6ec4c830611fde1a2e935588ccbbc61c03.tar.bz2
jellything-a2ef3f6ec4c830611fde1a2e935588ccbbc61c03.tar.zst
config works
-rw-r--r--Cargo.lock26
-rw-r--r--base/src/assetfed.rs4
-rw-r--r--base/src/federation.rs108
-rw-r--r--base/src/lib.rs56
-rw-r--r--base/src/permission.rs3
-rw-r--r--cache/src/lib.rs5
-rw-r--r--common/src/config.rs12
-rw-r--r--import/src/lib.rs64
-rw-r--r--logic/src/lib.rs30
-rw-r--r--logic/src/login.rs18
-rw-r--r--logic/src/session.rs19
-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
-rw-r--r--stream/src/lib.rs10
-rw-r--r--tool/src/add.rs12
-rw-r--r--tool/src/main.rs62
-rw-r--r--transcoder/src/lib.rs11
-rw-r--r--ui/src/admin/mod.rs2
-rw-r--r--ui/src/lib.rs15
28 files changed, 394 insertions, 294 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 557e5ce..23e258c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1927,6 +1927,7 @@ dependencies = [
"rocket_ws",
"serde",
"serde_json",
+ "serde_yml",
"tokio",
"tokio-util",
]
@@ -2117,6 +2118,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
+name = "libyml"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980"
+dependencies = [
+ "anyhow",
+ "version_check",
+]
+
+[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3436,6 +3447,21 @@ dependencies = [
]
[[package]]
+name = "serde_yml"
+version = "0.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "libyml",
+ "memchr",
+ "ryu",
+ "serde",
+ "version_check",
+]
+
+[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/base/src/assetfed.rs b/base/src/assetfed.rs
index 621169f..ea62e0d 100644
--- a/base/src/assetfed.rs
+++ b/base/src/assetfed.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 crate::SECRETS;
+use crate::CONF;
use aes_gcm_siv::{
aead::{generic_array::GenericArray, Aead},
Aes256GcmSiv, KeyInit,
@@ -20,7 +20,7 @@ use std::{path::PathBuf, sync::LazyLock};
const VERSION: u32 = 3;
static ASSET_KEY: LazyLock<Aes256GcmSiv> = LazyLock::new(|| {
- if let Some(sk) = &SECRETS.session_key {
+ if let Some(sk) = &CONF.asset_key {
let r = base64::engine::general_purpose::STANDARD
.decode(sk)
.expect("key invalid; should be valid base64");
diff --git a/base/src/federation.rs b/base/src/federation.rs
index 879ce96..b24d113 100644
--- a/base/src/federation.rs
+++ b/base/src/federation.rs
@@ -3,62 +3,62 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use crate::SECRETS;
-use anyhow::anyhow;
-use jellyclient::{Instance, Session};
-use jellycommon::{config::FederationAccount, user::CreateSessionParams};
-use std::{collections::HashMap, sync::Arc};
-use tokio::sync::RwLock;
-pub struct Federation {
- instances: HashMap<String, Instance>,
- sessions: RwLock<HashMap<String, Arc<Session>>>,
-}
+// use anyhow::anyhow;
+// use jellyclient::{Instance, Session};
+// use jellycommon::{config::FederationAccount, user::CreateSessionParams};
+// use std::{collections::HashMap, sync::Arc};
+// use tokio::sync::RwLock;
-impl Federation {
- pub fn initialize() -> Self {
- let instances = SECRETS
- .federation
- .iter()
- .map(|(k, FederationAccount { tls, .. })| {
- (k.to_owned(), Instance::new(k.to_owned(), *tls))
- })
- .collect::<HashMap<_, _>>();
+// pub struct Federation {
+// instances: HashMap<String, Instance>,
+// sessions: RwLock<HashMap<String, Arc<Session>>>,
+// }
- Self {
- instances,
- sessions: Default::default(),
- }
- }
+// impl Federation {
+// pub fn initialize() -> Self {
+// let instances = SECRETS
+// .federation
+// .iter()
+// .map(|(k, FederationAccount { tls, .. })| {
+// (k.to_owned(), Instance::new(k.to_owned(), *tls))
+// })
+// .collect::<HashMap<_, _>>();
- pub fn get_instance(&self, host: &String) -> anyhow::Result<&Instance> {
- self.instances.get(host).ok_or(anyhow!("unknown instance"))
- }
+// Self {
+// instances,
+// sessions: Default::default(),
+// }
+// }
- pub async fn get_session(&self, host: &String) -> anyhow::Result<Arc<Session>> {
- let mut w = self.sessions.write().await;
- if let Some(s) = w.get(host) {
- Ok(s.to_owned())
- } else {
- let FederationAccount {
- username, password, ..
- } = SECRETS
- .federation
- .get(host)
- .ok_or(anyhow!("no credentials of the remote server"))?;
- let s = Arc::new(
- self.get_instance(host)?
- .to_owned()
- .login(CreateSessionParams {
- username: username.to_owned(),
- password: password.to_owned(),
- expire: None,
- drop_permissions: None,
- })
- .await?,
- );
- w.insert(host.to_owned(), s.clone());
- Ok(s)
- }
- }
-}
+// pub fn get_instance(&self, host: &String) -> anyhow::Result<&Instance> {
+// self.instances.get(host).ok_or(anyhow!("unknown instance"))
+// }
+
+// pub async fn get_session(&self, host: &String) -> anyhow::Result<Arc<Session>> {
+// let mut w = self.sessions.write().await;
+// if let Some(s) = w.get(host) {
+// Ok(s.to_owned())
+// } else {
+// let FederationAccount {
+// username, password, ..
+// } = SECRETS
+// .federation
+// .get(host)
+// .ok_or(anyhow!("no credentials of the remote server"))?;
+// let s = Arc::new(
+// self.get_instance(host)?
+// .to_owned()
+// .login(CreateSessionParams {
+// username: username.to_owned(),
+// password: password.to_owned(),
+// expire: None,
+// drop_permissions: None,
+// })
+// .await?,
+// );
+// w.insert(host.to_owned(), s.clone());
+// Ok(s)
+// }
+// }
+// }
diff --git a/base/src/lib.rs b/base/src/lib.rs
index 010e908..55a9927 100644
--- a/base/src/lib.rs
+++ b/base/src/lib.rs
@@ -9,47 +9,21 @@ pub mod federation;
pub mod permission;
pub use jellycommon as common;
+use serde::{Deserialize, Serialize};
+use std::sync::LazyLock;
+use std::sync::Mutex;
-use jellycommon::config::{GlobalConfig, SecretsConfig};
-use std::sync::{
- atomic::{AtomicBool, Ordering},
- LazyLock,
-};
-
-pub static CONF: LazyLock<GlobalConfig> = LazyLock::new(load_config);
-pub static SECRETS: LazyLock<SecretsConfig> = LazyLock::new(load_secrets);
-pub static USE_TEST: AtomicBool = AtomicBool::new(false);
-
-pub fn use_test_config() {
- USE_TEST.store(true, Ordering::Relaxed)
+#[rustfmt::skip]
+#[derive(Debug, Deserialize, Serialize, Default)]
+pub struct Config {
+ asset_key: Option<String>,
}
-pub fn load_config() -> GlobalConfig {
- if USE_TEST.load(Ordering::Relaxed) {
- return GlobalConfig::default();
- }
- serde_yaml::from_reader(
- std::fs::File::open(std::env::var("JELLYTHING_CONFIG").unwrap_or_else(|_| {
- if std::env::args()
- .next()
- .unwrap_or_default()
- .ends_with("jellything")
- {
- std::env::args().nth(1).expect(
- "First argument or JELLYTHING_CONFIG must specify the configuration to use.",
- )
- } else {
- panic!("JELLYTHING_CONFIG variable is required.")
- }
- }))
- .expect("config cannot be read"),
- )
- .expect("config invalid")
-}
-fn load_secrets() -> SecretsConfig {
- if USE_TEST.load(Ordering::Relaxed) {
- return SecretsConfig::default();
- }
- serde_yaml::from_reader(std::fs::File::open(&CONF.secrets_path).expect("secrets file missing"))
- .expect("secrets config invalid")
-}
+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")
+});
diff --git a/base/src/permission.rs b/base/src/permission.rs
index 55d0870..7914f0b 100644
--- a/base/src/permission.rs
+++ b/base/src/permission.rs
@@ -3,7 +3,6 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use crate::CONF;
use anyhow::anyhow;
use jellycommon::{
user::{PermissionSet, UserPermission},
@@ -22,7 +21,7 @@ impl PermissionSetExt for PermissionSet {
fn check_explicit(&self, perm: &UserPermission) -> Option<bool> {
self.0
.get(perm)
- .or(CONF.default_permission_set.0.get(perm))
+ // .or(CONF.default_permission_set.0.get(perm))
.copied()
}
fn assert(&self, perm: &UserPermission) -> Result<(), anyhow::Error> {
diff --git a/cache/src/lib.rs b/cache/src/lib.rs
index 2d2cfa3..2d31d6c 100644
--- a/cache/src/lib.rs
+++ b/cache/src/lib.rs
@@ -35,13 +35,14 @@ pub struct Config {
max_in_memory_cache_size: usize,
}
+pub static CONF_PRELOAD: std::sync::Mutex<Option<Config>> = std::sync::Mutex::new(None);
static CONF: LazyLock<Config> = LazyLock::new(|| {
CONF_PRELOAD
- .blocking_lock()
+ .lock()
+ .unwrap()
.take()
.expect("cache config not preloaded. logic error")
});
-static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::const_new(None);
#[derive(Debug, Encode, Decode, Serialize, Clone)]
pub struct CachePath(pub PathBuf);
diff --git a/common/src/config.rs b/common/src/config.rs
index 5dc2d14..9368247 100644
--- a/common/src/config.rs
+++ b/common/src/config.rs
@@ -35,8 +35,6 @@ pub struct SecretsConfig {
#[serde(default)]
pub federation: HashMap<String, FederationAccount>,
#[serde(default)]
- pub api: ApiSecrets,
- #[serde(default)]
pub cookie_key: Option<String>,
#[serde(default)]
pub session_key: Option<String>,
@@ -51,16 +49,6 @@ pub struct FederationAccount {
pub tls: bool,
}
-#[derive(Serialize, Deserialize, Debug, Default)]
-pub struct ApiSecrets {
- pub acoustid: Option<String>,
- pub tmdb: Option<String>,
- pub tvdb: Option<String>,
- pub imdb: Option<String>,
- pub omdb: Option<String>,
- pub fanart_tv: Option<String>,
- pub trakt: Option<String>,
-}
fn login_expire() -> i64 {
60 * 60 * 24
diff --git a/import/src/lib.rs b/import/src/lib.rs
index 2f7383a..da339d8 100644
--- a/import/src/lib.rs
+++ b/import/src/lib.rs
@@ -4,6 +4,16 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
#![feature(duration_constants)]
+
+pub mod acoustid;
+pub mod infojson;
+pub mod musicbrainz;
+pub mod tmdb;
+pub mod trakt;
+pub mod vgmdb;
+pub mod wikidata;
+pub mod wikimedia_commons;
+
use acoustid::{acoustid_fingerprint, AcoustID};
use anyhow::{anyhow, bail, Context, Result};
use infojson::YVideo;
@@ -11,7 +21,6 @@ use jellybase::{
assetfed::AssetInner,
common::{Chapter, MediaInfo, Node, NodeID, NodeKind, Rating, SourceTrack, SourceTrackKind},
database::Database,
- CONF, SECRETS,
};
use jellycache::cache_file;
use jellyclient::{
@@ -24,18 +33,19 @@ use log::info;
use musicbrainz::MusicBrainz;
use rayon::iter::{ParallelBridge, ParallelIterator};
use regex::Regex;
+use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, HashMap},
fs::{read_to_string, File},
io::BufReader,
- path::Path,
+ path::{Path, PathBuf},
sync::LazyLock,
time::UNIX_EPOCH,
};
use tmdb::Tmdb;
use tokio::{
runtime::Handle,
- sync::{RwLock, Semaphore},
+ sync::{Mutex, RwLock, Semaphore},
task::spawn_blocking,
};
use trakt::Trakt;
@@ -43,14 +53,31 @@ use vgmdb::Vgmdb;
use wikidata::Wikidata;
use wikimedia_commons::WikimediaCommons;
-pub mod acoustid;
-pub mod infojson;
-pub mod musicbrainz;
-pub mod tmdb;
-pub mod trakt;
-pub mod vgmdb;
-pub mod wikidata;
-pub mod wikimedia_commons;
+#[rustfmt::skip]
+#[derive(Debug, Deserialize, Serialize, Default)]
+pub struct Config {
+ media_path: PathBuf,
+ api: ApiSecrets,
+}
+
+#[derive(Serialize, Deserialize, Debug, Default)]
+pub struct ApiSecrets {
+ pub acoustid: Option<String>,
+ pub tmdb: Option<String>,
+ pub tvdb: Option<String>,
+ pub imdb: Option<String>,
+ pub omdb: Option<String>,
+ pub fanart_tv: Option<String>,
+ pub trakt: Option<String>,
+}
+
+pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::const_new(None);
+static CONF: LazyLock<Config> = LazyLock::new(|| {
+ CONF_PRELOAD
+ .blocking_lock()
+ .take()
+ .expect("cache config not preloaded. logic error")
+});
pub const USER_AGENT: &'static str = concat!(
"jellything/",
@@ -78,6 +105,15 @@ pub fn is_importing() -> bool {
IMPORT_SEM.available_permits() == 0
}
+pub fn get_trakt() -> Result<Trakt> {
+ Ok(Trakt::new(
+ CONF.api
+ .trakt
+ .as_ref()
+ .ok_or(anyhow!("no trakt api key configured"))?,
+ ))
+}
+
pub async fn import_wrap(db: Database, incremental: bool) -> Result<()> {
let _sem = IMPORT_SEM.try_acquire()?;
@@ -95,9 +131,9 @@ pub async fn import_wrap(db: Database, incremental: bool) -> Result<()> {
fn import(db: &Database, incremental: bool) -> Result<()> {
let apis = Apis {
- trakt: SECRETS.api.trakt.as_ref().map(|key| Trakt::new(key)),
- tmdb: SECRETS.api.tmdb.as_ref().map(|key| Tmdb::new(key)),
- acoustid: SECRETS.api.acoustid.as_ref().map(|key| AcoustID::new(key)),
+ trakt: CONF.api.trakt.as_ref().map(|key| Trakt::new(key)),
+ tmdb: CONF.api.tmdb.as_ref().map(|key| Tmdb::new(key)),
+ acoustid: CONF.api.acoustid.as_ref().map(|key| AcoustID::new(key)),
musicbrainz: MusicBrainz::new(),
wikidata: Wikidata::new(),
wikimedia_commons: WikimediaCommons::new(),
diff --git a/logic/src/lib.rs b/logic/src/lib.rs
index 54c9c40..64656f5 100644
--- a/logic/src/lib.rs
+++ b/logic/src/lib.rs
@@ -3,14 +3,36 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-#![feature(duration_constructors)]
+#![feature(duration_constructors, let_chains)]
+pub mod admin;
pub mod filter_sort;
pub mod home;
+pub mod items;
+pub mod login;
pub mod node;
pub mod search;
pub mod session;
pub mod stats;
-pub mod items;
-pub mod admin;
-pub mod login;
+
+use serde::{Deserialize, Serialize};
+use std::sync::LazyLock;
+use std::sync::Mutex;
+
+#[rustfmt::skip]
+#[derive(Debug, Deserialize, Serialize, Default)]
+pub struct Config {
+ login_expire: i64,
+ session_key: Option<String>,
+ admin_username:Option<String>,
+ admin_password:Option<String>,
+}
+
+pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None);
+static CONF: LazyLock<Config> = LazyLock::new(|| {
+ CONF_PRELOAD
+ .lock()
+ .unwrap()
+ .take()
+ .expect("logic config not preloaded. logic error")
+});
diff --git a/logic/src/login.rs b/logic/src/login.rs
index e9c2f93..26a6b7f 100644
--- a/logic/src/login.rs
+++ b/logic/src/login.rs
@@ -3,13 +3,27 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use crate::session::create;
+use crate::{CONF, session::create};
use anyhow::{Result, anyhow};
use argon2::{Argon2, PasswordHasher, password_hash::Salt};
-use jellybase::{CONF, database::Database};
+use jellybase::database::Database;
use jellycommon::user::UserPermission;
+use log::info;
use std::{collections::HashSet, time::Duration};
+pub fn create_admin_account(database: &Database) -> Result<()> {
+ if let Some(username) = &CONF.admin_username
+ && let Some(password) = &CONF.admin_password
+ {
+ database
+ .create_admin_user(username, hash_password(username, password))
+ .unwrap();
+ } else {
+ info!("admin account disabled")
+ }
+ Ok(())
+}
+
pub fn login_logic(
database: &Database,
username: &str,
diff --git a/logic/src/session.rs b/logic/src/session.rs
index bc7f137..72a1089 100644
--- a/logic/src/session.rs
+++ b/logic/src/session.rs
@@ -3,13 +3,13 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
+use crate::CONF;
use aes_gcm_siv::{
KeyInit,
aead::{Aead, generic_array::GenericArray},
};
use anyhow::anyhow;
use base64::Engine;
-use jellybase::SECRETS;
use jellycommon::{
chrono::{DateTime, Utc},
user::{PermissionSet, User},
@@ -32,7 +32,7 @@ pub struct SessionData {
}
static SESSION_KEY: LazyLock<[u8; 32]> = LazyLock::new(|| {
- if let Some(sk) = &SECRETS.session_key {
+ if let Some(sk) = &CONF.session_key {
let r = base64::engine::general_purpose::STANDARD
.decode(sk)
.expect("key invalid; should be valid base64");
@@ -85,9 +85,20 @@ pub fn validate(token: &str) -> anyhow::Result<String> {
Ok(session_data.username)
}
+#[cfg(test)]
+fn load_test_config() {
+ use crate::{CONF_PRELOAD, Config};
+ *CONF_PRELOAD.lock().unwrap() = Some(Config {
+ login_expire: 10,
+ session_key: None,
+ admin_password: None,
+ admin_username: None,
+ });
+}
+
#[test]
fn test() {
- jellybase::use_test_config();
+ load_test_config();
let tok = create(
"blub".to_string(),
jellycommon::user::PermissionSet::default(),
@@ -98,7 +109,7 @@ fn test() {
#[test]
fn test_crypto() {
- jellybase::use_test_config();
+ load_test_config();
let nonce = [(); 12].map(|_| rand::random());
let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
let plaintext = b"testing stuff---";
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},
diff --git a/stream/src/lib.rs b/stream/src/lib.rs
index ccc5cb9..8352eaf 100644
--- a/stream/src/lib.rs
+++ b/stream/src/lib.rs
@@ -24,13 +24,12 @@ use std::{
io::SeekFrom,
ops::Range,
path::PathBuf,
- sync::{Arc, LazyLock},
+ sync::{Arc, LazyLock, Mutex},
};
use stream_info::{stream_info, write_stream_info};
use tokio::{
fs::File,
io::{duplex, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, DuplexStream},
- sync::Mutex,
};
#[rustfmt::skip]
@@ -43,13 +42,14 @@ pub struct Config {
#[serde(default)] pub offer_av1: bool,
}
+pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None);
static CONF: LazyLock<Config> = LazyLock::new(|| {
CONF_PRELOAD
- .blocking_lock()
+ .lock()
+ .unwrap()
.take()
- .expect("cache config not preloaded. logic error")
+ .expect("stream config not preloaded. logic error")
});
-static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::const_new(None);
#[derive(Debug)]
pub struct SMediaInfo {
diff --git a/tool/src/add.rs b/tool/src/add.rs
index 2179a40..04328b2 100644
--- a/tool/src/add.rs
+++ b/tool/src/add.rs
@@ -4,11 +4,9 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use crate::cli::Action;
-use anyhow::anyhow;
use dialoguer::{theme::ColorfulTheme, Confirm, FuzzySelect, Input};
-use jellybase::SECRETS;
use jellycommon::TraktKind;
-use jellyimport::trakt::Trakt;
+use jellyimport::get_trakt;
use log::warn;
use std::{
fmt::Display,
@@ -37,13 +35,7 @@ pub async fn add(action: Action) -> anyhow::Result<()> {
.interact_text()
.unwrap();
- let trakt = Trakt::new(
- SECRETS
- .api
- .trakt
- .as_ref()
- .ok_or(anyhow!("no trakt api key configured"))?,
- );
+ let trakt = get_trakt()?;
let results = trakt.search(search_kinds, &name).await?;
diff --git a/tool/src/main.rs b/tool/src/main.rs
index 9ce7cf1..adf0a35 100644
--- a/tool/src/main.rs
+++ b/tool/src/main.rs
@@ -4,17 +4,12 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use anyhow::anyhow;
use clap::Parser;
-use jellybase::{CONF, SECRETS};
-use jellyclient::Instance;
-use jellycommon::user::CreateSessionParams;
use jellytool::{
add::add,
cli::{Action, Args},
migrate::migrate,
};
-use log::info;
fn main() -> anyhow::Result<()> {
env_logger::builder()
@@ -30,34 +25,35 @@ fn main() -> anyhow::Result<()> {
.unwrap()
.block_on(add(a)),
a @ Action::Migrate { .. } => migrate(a),
- Action::Reimport {
- hostname,
- no_tls,
- no_incremental,
- } => tokio::runtime::Builder::new_multi_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(async move {
- let inst = Instance::new(hostname.unwrap_or(CONF.hostname.clone()), !no_tls);
- info!("login");
- let session = inst
- .login(CreateSessionParams {
- drop_permissions: None,
- expire: None,
- password: SECRETS
- .admin_password
- .clone()
- .ok_or(anyhow!("admin account required"))?,
- username: CONF
- .admin_username
- .clone()
- .ok_or(anyhow!("admin account required"))?,
- })
- .await?;
+ _ => Ok(()),
+ // Action::Reimport {
+ // hostname,
+ // no_tls,
+ // no_incremental,
+ // } => tokio::runtime::Builder::new_multi_thread()
+ // .enable_all()
+ // .build()
+ // .unwrap()
+ // .block_on(async move {
+ // let inst = Instance::new(hostname.unwrap_or(CONF.hostname.clone()), !no_tls);
+ // info!("login");
+ // let session = inst
+ // .login(CreateSessionParams {
+ // drop_permissions: None,
+ // expire: None,
+ // password: SECRETS
+ // .admin_password
+ // .clone()
+ // .ok_or(anyhow!("admin account required"))?,
+ // username: CONF
+ // .admin_username
+ // .clone()
+ // .ok_or(anyhow!("admin account required"))?,
+ // })
+ // .await?;
- session.reimport(!no_incremental).await?;
- Ok(())
- }),
+ // session.reimport(!no_incremental).await?;
+ // Ok(())
+ // }),
}
}
diff --git a/transcoder/src/lib.rs b/transcoder/src/lib.rs
index c49f52c..665f470 100644
--- a/transcoder/src/lib.rs
+++ b/transcoder/src/lib.rs
@@ -6,8 +6,8 @@
#![feature(exit_status_error)]
use serde::{Deserialize, Serialize};
-use std::sync::LazyLock;
-use tokio::sync::{Mutex, Semaphore};
+use std::sync::{LazyLock, Mutex};
+use tokio::sync::Semaphore;
pub mod fragment;
pub mod image;
@@ -27,13 +27,14 @@ pub struct Config {
pub x264_preset: Option<String>,
}
+pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None);
static CONF: LazyLock<Config> = LazyLock::new(|| {
CONF_PRELOAD
- .blocking_lock()
+ .lock()
+ .unwrap()
.take()
- .expect("cache config not preloaded. logic error")
+ .expect("transcoder config not preloaded. logic error")
});
-static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::const_new(None);
static LOCAL_IMAGE_TRANSCODING_TASKS: Semaphore = Semaphore::const_new(8);
static LOCAL_VIDEO_TRANSCODING_TASKS: Semaphore = Semaphore::const_new(2);
diff --git a/ui/src/admin/mod.rs b/ui/src/admin/mod.rs
index 74a5e1a..ade0d97 100644
--- a/ui/src/admin/mod.rs
+++ b/ui/src/admin/mod.rs
@@ -4,8 +4,8 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-pub mod user;
pub mod log;
+pub mod user;
use crate::{Page, locale::Language, scaffold::FlashDisplay};
use jellycommon::routes::{
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index 4f1901a..cbfc298 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -3,9 +3,12 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
+pub mod account;
+pub mod admin;
pub mod filter_sort;
pub mod format;
pub mod home;
+pub mod items;
pub mod locale;
pub mod node_card;
pub mod node_page;
@@ -13,9 +16,6 @@ pub mod props;
pub mod scaffold;
pub mod search;
pub mod stats;
-pub mod items;
-pub mod admin;
-pub mod account;
use locale::Language;
use markup::DynRender;
@@ -41,7 +41,14 @@ static CONF: LazyLock<Config> = LazyLock::new(|| {
.take()
.expect("cache config not preloaded. logic error")
});
-static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None);
+pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None);
+
+pub fn get_brand() -> String {
+ CONF.brand.clone()
+}
+pub fn get_slogan() -> String {
+ CONF.slogan.clone()
+}
/// render as supertrait would be possible but is not
/// dyn compatible and I really dont want to expose generics