/* 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) 2026 metamuffin */ #![feature(int_roundings, str_as_str, duration_constructors)] #![allow(clippy::needless_borrows_for_generic_args)] #![recursion_limit = "4096"] use crate::{ auth::{hash_password, token::SessionKey}, logger::setup_logger, }; use anyhow::{Result, anyhow}; use jellycache::Cache; use jellycommon::{ USER_ADMIN, USER_LOGIN, USER_PASSWORD, jellyobject::{ObjectBuffer, Path}, }; use jellydb::{Database, Filter, Query, Sort}; use log::{error, info}; use routes::build_rocket; use serde::Deserialize; use std::{env::args, fs::read_to_string, path::PathBuf, process::exit, sync::Arc}; pub mod api; pub mod auth; pub mod compat; pub mod logger; pub mod logic; pub mod request_info; pub mod responders; pub mod routes; pub mod ui; pub mod ui_responder; #[rocket::main] async fn main() { setup_logger(); let state = match create_state() { Ok(s) => s, Err(e) => { error!("unable to start: {e:#}"); exit(1); } }; let r = build_rocket(state).launch().await; match r { Ok(_) => info!("server shutdown"), Err(e) => { error!("server exited: {e}"); exit(1); } } } pub struct State { pub config: Config, pub cache: Arc, pub database: Arc, pub session_key: SessionKey, } #[derive(Debug, Deserialize)] pub struct Config { pub import: jellyimport::Config, pub ui: jellyui::Config, pub stream: Arc, 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, } pub fn create_state() -> Result> { 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 cache_storage = jellykv::rocksdb::new(&config.cache_path)?; let db_storage = jellykv::rocksdb::new(&config.database_path)?; // let db_storage = jellykv::memory::new(); 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)?, config, }); create_admin_user(&state)?; Ok(state) } fn create_admin_user(state: &State) -> Result<()> { state.database.transaction(&mut |txn| { let admin_row = txn.query_single(Query { filter: Filter::Match(Path(vec![USER_LOGIN.0]), "admin".into()), sort: Sort::None, })?; if admin_row.is_none() { info!("Creating new admin user"); let pwhash = hash_password("admin", &state.config.admin_password); txn.insert(ObjectBuffer::new(&mut [ (USER_LOGIN.0, &"admin"), (USER_PASSWORD.0, &pwhash.as_slice()), (USER_ADMIN.0, &()), ]))?; } Ok(()) })?; Ok(()) }