pub mod auth;
pub mod config;
pub mod plugins;
pub mod server;
pub mod tools;
pub mod vfs;
use auth::AuthManager;
use config::AppConfig;
use plugins::PluginManager;
use server::{create_router, AppState};
use std::path::PathBuf;
use std::sync::Arc;
use tracing::info;
pub fn create_app_state(config: &AppConfig) -> Result<AppState, Box<dyn std::error::Error + Send + Sync>> {
let auth_mgr = AuthManager::new(
&config.server.database_path,
&config.server.jwt_secret,
config.server.session_duration_hours,
&config.auth.mode,
&config.auth.pam_service,
&config.auth.default_admin_user,
&config.auth.default_admin_pass,
)?;
let task_mgr = tools::tasks::TaskManager::new();
let tag_mgr = tools::tags::TagManager::new(auth_mgr.db())?;
let vault_mgr = vfs::vault::VaultManager::new();
let backup_mgr = tools::sync::BackupManager::new(auth_mgr.db())?;
let plugin_mgr = PluginManager::new(
PathBuf::from(&config.plugins.directory),
PathBuf::from(&config.plugins.user_directory),
config.plugins.allow_user_installs,
config.plugins.default_policy.clone(),
config.plugins.global_whitelist.clone(),
config.plugins.global_blacklist.clone(),
);
let backup_mgr_arc = Arc::new(backup_mgr);
let task_mgr_arc = Arc::new(task_mgr);
backup_mgr_arc.clone().start_scheduler(task_mgr_arc.clone());
let auth_mgr_arc = Arc::new(auth_mgr);
let oidc_mgr = crate::auth::oidc::OidcManager::new(config.auth.oidc.clone(), auth_mgr_arc.clone());
Ok(AppState {
config: Arc::new(config.clone()),
auth: auth_mgr_arc,
oidc: Arc::new(oidc_mgr),
tasks: task_mgr_arc,
tags: Arc::new(tag_mgr),
vaults: Arc::new(vault_mgr),
backup: backup_mgr_arc,
plugins: Arc::new(plugin_mgr),
})
}
pub async fn start_background_server(mut config: AppConfig) -> Result<u16, Box<dyn std::error::Error + Send + Sync>> {
let state = create_app_state(&config)?;
let app = create_router(state);
let bind_host = if config.server.host.is_empty() {
"127.0.0.1"
} else {
&config.server.host
};
let addr_str = format!("{}:{}", bind_host, config.server.port);
let listener = match tokio::net::TcpListener::bind(&addr_str).await {
Ok(l) => l,
Err(e) if config.server.port != 0 => {
tracing::warn!(
"Preferred port {} is unavailable ({}), falling back to dynamic port",
config.server.port,
e
);
tokio::net::TcpListener::bind(format!("{}:0", bind_host)).await?
}
Err(e) => return Err(Box::new(e)),
};
let local_addr = listener.local_addr()?;
let bound_port = local_addr.port();
config.server.port = bound_port;
info!("Brum embedded server listening on http://{}", local_addr);
tokio::spawn(async move {
if let Err(e) = axum::serve(listener, app).await {
tracing::error!("Brum server error: {}", e);
}
});
Ok(bound_port)
}
pub fn setup_linux_desktop_env() {
#[cfg(target_os = "linux")]
{
if std::env::var("WEBKIT_DISABLE_DMABUF_RENDERER").is_err() {
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
}
if std::env::var("__NV_DISABLE_EXPLICIT_SYNC").is_err() {
std::env::set_var("__NV_DISABLE_EXPLICIT_SYNC", "1");
}
let home = std::env::var("HOME").unwrap_or_else(|_| "/home/bolt".to_string());
if std::env::var("XCURSOR_PATH").is_err() {
let paths = format!("{}/.local/share/icons:{}/.icons:/usr/share/icons:/usr/local/share/icons:/usr/share/pixmaps", home, home);
std::env::set_var("XCURSOR_PATH", paths);
}
let current_theme = std::env::var("XCURSOR_THEME").unwrap_or_default();
let is_valid = !current_theme.is_empty() && (
std::path::Path::new(&format!("/usr/share/icons/{}/cursors", current_theme)).exists()
|| std::path::Path::new(&format!("{}/.local/share/icons/{}/cursors", home, current_theme)).exists()
|| std::path::Path::new(&format!("{}/.icons/{}/cursors", home, current_theme)).exists()
);
if !is_valid {
let candidate_themes = ["Adwaita", "default", "breeze_cursors", "mate", "HighContrast"];
let fallback = candidate_themes.iter().find(|t| {
std::path::Path::new(&format!("/usr/share/icons/{}/cursors", t)).exists()
}).unwrap_or(&"Adwaita");
std::env::set_var("XCURSOR_THEME", fallback);
}
if std::env::var("XCURSOR_SIZE").is_err() {
std::env::set_var("XCURSOR_SIZE", "24");
}
std::env::set_var("G_ENABLE_DIAGNOSTIC", "0");
std::env::set_var("G_MESSAGES_DEBUG", "");
silence_gdk_pointer_logs();
}
}
#[cfg(target_os = "linux")]
#[repr(C)]
struct GLogField {
key: *const std::ffi::c_char,
value: *const std::ffi::c_void,
length: isize,
}
#[cfg(target_os = "linux")]
type GLogWriterFunc = unsafe extern "C" fn(
log_level: i32,
fields: *const GLogField,
n_fields: usize,
user_data: *mut std::ffi::c_void,
) -> i32;
#[cfg(target_os = "linux")]
type GLogFunc = unsafe extern "C" fn(
log_domain: *const std::ffi::c_char,
log_level: i32,
message: *const std::ffi::c_char,
user_data: *mut std::ffi::c_void,
);
#[cfg(target_os = "linux")]
static mut DEFAULT_LOG_WRITER: Option<GLogWriterFunc> = None;
#[cfg(target_os = "linux")]
unsafe extern "C" fn structured_log_filter(
log_level: i32,
fields: *const GLogField,
n_fields: usize,
user_data: *mut std::ffi::c_void,
) -> i32 {
if !fields.is_null() && n_fields > 0 {
let slice = std::slice::from_raw_parts(fields, n_fields);
for field in slice {
if !field.key.is_null() && !field.value.is_null() {
let key = std::ffi::CStr::from_ptr(field.key).to_bytes();
if key == b"MESSAGE" {
let msg = if field.length < 0 {
std::ffi::CStr::from_ptr(field.value as *const std::ffi::c_char).to_string_lossy()
} else {
let bytes = std::slice::from_raw_parts(field.value as *const u8, field.length as usize);
String::from_utf8_lossy(bytes)
};
if msg.contains("cursor") || msg.contains("pointer") || msg.contains("Unable to load") {
return 1; }
}
}
}
}
if let Some(def_writer) = DEFAULT_LOG_WRITER {
def_writer(log_level, fields, n_fields, user_data)
} else {
1
}
}
#[cfg(target_os = "linux")]
unsafe extern "C" fn classic_log_filter(
_log_domain: *const std::ffi::c_char,
_log_level: i32,
message: *const std::ffi::c_char,
_user_data: *mut std::ffi::c_void,
) {
if !message.is_null() {
let msg = std::ffi::CStr::from_ptr(message).to_string_lossy();
if msg.contains("cursor") || msg.contains("pointer") || msg.contains("Unable to load") {
return; }
eprintln!("{}", msg);
}
}
#[cfg(target_os = "linux")]
fn silence_gdk_pointer_logs() {
unsafe {
let glib_libs = ["libglib-2.0.so.0\0", "libglib-2.0.so\0"];
for lib_name in &glib_libs {
let handle = libc::dlopen(lib_name.as_ptr() as *const _, libc::RTLD_LAZY | libc::RTLD_GLOBAL);
if !handle.is_null() {
let def_writer_sym = b"g_log_writer_default\0";
let def_writer_ptr = libc::dlsym(handle, def_writer_sym.as_ptr() as *const _);
if !def_writer_ptr.is_null() {
DEFAULT_LOG_WRITER = Some(std::mem::transmute(def_writer_ptr));
}
let set_writer_sym = b"g_log_set_writer_func\0";
let set_writer_ptr = libc::dlsym(handle, set_writer_sym.as_ptr() as *const _);
if !set_writer_ptr.is_null() {
let g_log_set_writer_func: unsafe extern "C" fn(
GLogWriterFunc,
*mut std::ffi::c_void,
Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
) = std::mem::transmute(set_writer_ptr);
g_log_set_writer_func(structured_log_filter, std::ptr::null_mut(), None);
}
let set_handler_sym = b"g_log_set_handler\0";
let func_ptr = libc::dlsym(handle, set_handler_sym.as_ptr() as *const _);
if !func_ptr.is_null() {
let g_log_set_handler: unsafe extern "C" fn(
*const std::ffi::c_char,
i32,
GLogFunc,
*mut std::ffi::c_void,
) -> u32 = std::mem::transmute(func_ptr);
for dom in [b"Gdk\0".as_ptr(), b"Gdk-Wayland\0".as_ptr(), b"Gtk\0".as_ptr(), b"WebKit\0".as_ptr()] {
g_log_set_handler(dom as *const _, 0xFF, classic_log_filter, std::ptr::null_mut());
}
}
let default_sym = b"g_log_set_default_handler\0";
let def_ptr = libc::dlsym(handle, default_sym.as_ptr() as *const _);
if !def_ptr.is_null() {
let g_log_set_default_handler: unsafe extern "C" fn(
GLogFunc,
*mut std::ffi::c_void,
) -> GLogFunc = std::mem::transmute(def_ptr);
g_log_set_default_handler(classic_log_filter, std::ptr::null_mut());
}
break;
}
}
}
}