aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-03-14 22:00:01 +0100
committermetamuffin <metamuffin@disroot.org>2026-03-14 22:00:01 +0100
commit1f3bcb47bc27768c67b05305f528e01ed491a6d3 (patch)
treea0b7d515112e7ac755bec8e77c5aa2edbbdb7803
parent577227d69fbc619c4d30a262174dbf2e173c49de (diff)
downloadjellything-1f3bcb47bc27768c67b05305f528e01ed491a6d3.tar
jellything-1f3bcb47bc27768c67b05305f528e01ed491a6d3.tar.bz2
jellything-1f3bcb47bc27768c67b05305f528e01ed491a6d3.tar.zst
default for every config field
-rw-r--r--common/src/internal.rs6
-rw-r--r--import/src/lib.rs43
-rw-r--r--server/src/main.rs124
-rw-r--r--stream/src/lib.rs2
-rw-r--r--ui/src/lib.rs30
5 files changed, 162 insertions, 43 deletions
diff --git a/common/src/internal.rs b/common/src/internal.rs
index 3f2c622..864eed4 100644
--- a/common/src/internal.rs
+++ b/common/src/internal.rs
@@ -10,8 +10,10 @@ use jellyobject::fields;
use serde::{Deserialize, Serialize};
fields! {
- IM_PATH: str = b"Ipth";
- IM_MTIME: u64 = b"Imtm";
+ IMPORT_PATH: str = b"Ipth";
+ IMPORT_MTIME: u64 = b"Imtm";
+
+ SESSION_KEY: str = b"Gskx";
}
#[derive(Serialize, Deserialize, Clone)]
diff --git a/import/src/lib.rs b/import/src/lib.rs
index 97cd373..d138f75 100644
--- a/import/src/lib.rs
+++ b/import/src/lib.rs
@@ -17,7 +17,7 @@ use crate::{
use anyhow::{Context, Result, anyhow};
use jellycache::{Cache, HashKey};
use jellycommon::{
- internal::{IM_MTIME, IM_PATH},
+ internal::{IMPORT_MTIME, IMPORT_PATH},
jellyobject::{self, OBB, Object, Path as TagPath, Tag},
*,
};
@@ -44,11 +44,30 @@ use tokio::{runtime::Handle, sync::Semaphore, task::spawn_blocking};
pub use jellyimport_fallback_generator::generate_person_fallback;
-#[derive(Debug, Deserialize, Serialize, Default, Clone)]
+#[derive(Debug, Deserialize, Serialize, Clone)]
+#[rustfmt::skip]
pub struct Config {
- media_path: PathBuf,
- api: ApiSecrets,
- num_threads: usize,
+ #[serde(default = "defaults::media_path")] media_path: PathBuf,
+ #[serde(default)] api: ApiSecrets,
+ #[serde(default = "defaults::num_threads")] num_threads: usize,
+}
+mod defaults {
+ use std::path::PathBuf;
+ impl Default for super::Config {
+ fn default() -> Self {
+ Self {
+ api: Default::default(),
+ media_path: media_path(),
+ num_threads: num_threads(),
+ }
+ }
+ }
+ pub fn num_threads() -> usize {
+ rayon::current_num_threads()
+ }
+ pub fn media_path() -> PathBuf {
+ "media".into()
+ }
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
@@ -451,7 +470,7 @@ fn compare_mtime(dba: &ImportConfig, path: &Path) -> Result<bool> {
dba.db.transaction(&mut |txn| {
match txn.query_single(Query {
filter: Filter::Match(
- TagPath(vec![IM_PATH.0]),
+ TagPath(vec![IMPORT_PATH.0]),
path.to_string_lossy().to_string().into(),
),
..Default::default()
@@ -459,7 +478,7 @@ fn compare_mtime(dba: &ImportConfig, path: &Path) -> Result<bool> {
None => was_changed = true,
Some(row) => {
let meta = txn.get(row)?.unwrap();
- let prev_mtime = meta.get(IM_MTIME).unwrap_or_default();
+ let prev_mtime = meta.get(IMPORT_MTIME).unwrap_or_default();
was_changed = mtime > prev_mtime
}
}
@@ -474,17 +493,21 @@ fn update_mtime(dba: &ImportConfig, path: &Path) -> Result<()> {
dba.db.transaction(&mut |txn| {
let row = match txn.query_single(Query {
filter: Filter::Match(
- TagPath(vec![IM_PATH.0]),
+ TagPath(vec![IMPORT_PATH.0]),
path.to_string_lossy().to_string().into(),
),
..Default::default()
})? {
Some(row) => row,
- None => txn.insert(OBB::new().with(IM_PATH, &path.to_string_lossy()).finish())?,
+ None => txn.insert(
+ OBB::new()
+ .with(IMPORT_PATH, &path.to_string_lossy())
+ .finish(),
+ )?,
};
let mut ob = txn.get(row)?.unwrap();
- ob = ob.insert(IM_MTIME, mtime);
+ ob = ob.insert(IMPORT_MTIME, mtime);
txn.update(row, ob)?;
Ok(())
diff --git a/server/src/main.rs b/server/src/main.rs
index 564f320..e2f162e 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-#![feature(never_type)]
+#![feature(never_type, exit_status_error)]
#![allow(clippy::needless_borrows_for_generic_args)]
#![recursion_limit = "4096"]
@@ -12,16 +12,25 @@ use crate::{
logger::setup_logger,
};
use anyhow::{Result, anyhow};
+use base64::{Engine, prelude::BASE64_STANDARD};
use jellycache::Cache;
use jellycommon::{
- USER_ADMIN, USER_LOGIN, USER_PASSWORD,
- jellyobject::{OBB, Path},
+ USER_ADMIN, USER_LOGIN, USER_PASSWORD, USER_PASSWORD_REQUIRE_CHANGE,
+ internal::SESSION_KEY,
+ jellyobject::{EMPTY, OBB, Path},
};
use jellydb::{Database, Filter, Query};
use log::{error, info};
+use rand::random;
use routes::build_rocket;
use serde::Deserialize;
-use std::{env::args, fs::read_to_string, path::PathBuf, process::exit, sync::Arc};
+use std::{
+ env::args,
+ fs::read_to_string,
+ path::PathBuf,
+ process::{Command, exit},
+ sync::Arc,
+};
pub mod auth;
pub mod logger;
@@ -57,33 +66,65 @@ pub struct State {
}
#[derive(Debug, Deserialize)]
+#[rustfmt::skip]
pub struct Config {
pub import: jellyimport::Config,
- pub ui: jellyui::Config,
- pub stream: Arc<jellystream::Config>,
- pub session_key: String,
- pub admin_password: String,
- pub asset_path: PathBuf,
- pub database_path: PathBuf,
- pub cache_path: PathBuf,
- pub max_memory_cache_size: usize,
- pub tls: bool,
- pub hostname: String,
+ #[serde(default)] pub ui: jellyui::Config,
+ #[serde(default)] pub stream: Arc<jellystream::Config>,
+ #[serde(default = "defaults::base_path")] pub base_path: PathBuf,
+ #[serde(default = "defaults::asset_path")] pub asset_path: PathBuf,
+ #[serde(default = "defaults::database_path")] pub database_path: PathBuf,
+ #[serde(default = "defaults::cache_path")] pub cache_path: PathBuf,
+ #[serde(default = "defaults::admin_password")] pub admin_password: String,
+ #[serde(default = "defaults::max_memory_cache_size")] pub max_memory_cache_size: usize,
+ pub session_key: Option<String>,
+}
+mod defaults {
+ use std::path::PathBuf;
+ pub fn base_path() -> PathBuf {
+ ".".into()
+ }
+ pub fn asset_path() -> PathBuf {
+ "assets".into()
+ }
+ pub fn database_path() -> PathBuf {
+ "db".into()
+ }
+ pub fn cache_path() -> PathBuf {
+ "cachedb".into()
+ }
+ pub fn max_memory_cache_size() -> usize {
+ 100_000_000
+ }
+ pub fn admin_password() -> String {
+ "admin".to_string()
+ }
}
pub fn create_state() -> Result<Arc<State>> {
let config_path = args()
.nth(1)
.ok_or(anyhow!("first argument (config path) missing"))?;
- let config: Config = serde_yaml_ng::from_str(&read_to_string(config_path)?)?;
+ let mut config: Config = serde_yaml_ng::from_str(&read_to_string(config_path)?)?;
+ config.cache_path = config.base_path.join(config.cache_path);
+ config.asset_path = config.base_path.join(config.asset_path);
+ config.database_path = config.base_path.join(config.database_path);
+
+ init_asset_dir(&config.asset_path)?;
let cache_storage = jellykv::rocksdb::new(&config.cache_path)?;
let db_storage = jellykv::rocksdb::new(&config.database_path)?;
+ let session_key = config
+ .session_key
+ .clone()
+ .map(Ok)
+ .unwrap_or_else(|| session_key_from_db(&db_storage))?;
+
let state = Arc::new(State {
cache: Cache::new(Box::new(cache_storage), config.max_memory_cache_size).into(),
database: Arc::new(db_storage),
- session_key: SessionKey::parse(&config.session_key)?,
+ session_key: SessionKey::parse(&session_key)?,
config,
});
@@ -101,16 +142,53 @@ fn create_admin_user(state: &State) -> Result<()> {
if admin_row.is_none() {
info!("Creating new admin user");
let pwhash = hash_password("admin", &state.config.admin_password);
- txn.insert(
- OBB::new()
- .with(USER_LOGIN, "admin")
- .with(USER_PASSWORD, &pwhash)
- .with(USER_ADMIN, ())
- .finish(),
- )?;
+ let mut user = OBB::new()
+ .with(USER_LOGIN, "admin")
+ .with(USER_PASSWORD, &pwhash)
+ .with(USER_ADMIN, ())
+ .finish();
+ if state.config.admin_password == "admin" {
+ user = user.insert(USER_PASSWORD_REQUIRE_CHANGE, ());
+ }
+ txn.insert(user)?;
}
Ok(())
})?;
Ok(())
}
+
+fn session_key_from_db(db: &dyn Database) -> Result<String> {
+ let mut sk = None;
+ db.transaction(&mut |txn| match txn.query_single(Query {
+ filter: Filter::Has(Path(vec![SESSION_KEY.0])),
+ ..Default::default()
+ })? {
+ Some(r) => {
+ sk = Some(txn.get(r)?.unwrap().get(SESSION_KEY).unwrap().to_string());
+ Ok(())
+ }
+ None => {
+ let k = BASE64_STANDARD.encode([(); 32].map(|()| random()));
+ txn.insert(EMPTY.insert(SESSION_KEY, &k))?;
+ sk = Some(k);
+ Ok(())
+ }
+ })?;
+ Ok(sk.unwrap())
+}
+
+fn init_asset_dir(path: &std::path::Path) -> Result<()> {
+ if path.exists() {
+ return Ok(());
+ }
+ info!("git-cloning assets repo to {path:?}");
+ Command::new("git")
+ .arg("clone")
+ .arg("https://codeberg.org/metamuffin/jellything-assets.git")
+ .arg(path)
+ .output()?
+ .exit_ok()?;
+ info!("done");
+ Ok(())
+}
diff --git a/stream/src/lib.rs b/stream/src/lib.rs
index 9171595..0070860 100644
--- a/stream/src/lib.rs
+++ b/stream/src/lib.rs
@@ -40,7 +40,7 @@ pub struct Config {
#[serde(default)] pub offer_vp8: bool,
#[serde(default)] pub offer_vp9: bool,
#[serde(default)] pub offer_av1: bool,
- pub transcoder: jellytranscoder::Config,
+ #[serde(default)] pub transcoder: jellytranscoder::Config,
}
pub struct SMediaInfo {
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index b4f8d68..7afdd28 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -7,23 +7,39 @@ pub mod components;
pub(crate) mod format;
mod scaffold;
-use std::borrow::Cow;
-
pub use jellyui_client_scripts::*;
pub use jellyui_client_style::*;
pub use jellyui_locale::tr;
+pub use scaffold::Scaffold;
use jellycommon::jellyobject::Object;
use markup::DynRender;
-pub use scaffold::Scaffold;
use serde::{Deserialize, Serialize};
+use std::borrow::Cow;
#[rustfmt::skip]
-#[derive(Debug, Deserialize, Serialize, Default)]
+#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
- pub brand: String,
- pub slogan: String,
- pub logo: bool,
+ #[serde(default = "defaults::brand")] pub brand: String,
+ #[serde(default = "defaults::slogan")] pub slogan: String,
+ #[serde(default)] pub logo: bool,
+}
+mod defaults {
+ impl Default for super::Config {
+ fn default() -> Self {
+ Self {
+ brand: brand(),
+ slogan: slogan(),
+ logo: false,
+ }
+ }
+ }
+ pub fn brand() -> String {
+ "Jellything".to_string()
+ }
+ pub fn slogan() -> String {
+ "Somebody put a nice slogan here".to_string()
+ }
}
pub trait Page {