pinakes-server: wire backup, session refresh, webhooks, and rate limit config

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: If2855d44cc700c0f65a5f5ac850ee3866a6a6964
This commit is contained in:
raf 2026-03-08 00:42:14 +03:00
commit 52f0b5defc
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
8 changed files with 257 additions and 105 deletions

View file

@ -39,8 +39,8 @@ struct Cli {
}
/// Resolve the configuration file path.
/// Returns (path, was_explicit) where was_explicit indicates if the path was
/// explicitly provided by the user (vs discovered).
/// Returns (path, `was_explicit`) where `was_explicit` indicates if the path
/// was explicitly provided by the user (vs discovered).
fn resolve_config_path(explicit: Option<&std::path::Path>) -> (PathBuf, bool) {
if let Some(path) = explicit {
return (path.to_path_buf(), true);
@ -219,16 +219,34 @@ async fn main() -> Result<()> {
None
};
// Initialize webhook dispatcher early so the job queue executor can use it
let webhook_dispatcher: Option<
std::sync::Arc<pinakes_core::webhooks::WebhookDispatcher>,
> = if config.webhooks.is_empty() {
None
} else {
tracing::info!(
count = config.webhooks.len(),
"webhook dispatcher initialized"
);
Some(pinakes_core::webhooks::WebhookDispatcher::new(
config.webhooks.clone(),
))
};
// Initialize job queue with executor
let job_storage = storage.clone();
let job_config = config.clone();
let job_transcode = transcode_service.clone();
let job_webhooks = webhook_dispatcher.clone();
let job_queue = pinakes_core::jobs::JobQueue::new(
config.jobs.worker_count,
config.jobs.job_timeout_secs,
move |job_id, kind, cancel, jobs| {
let storage = job_storage.clone();
let config = job_config.clone();
let transcode_svc = job_transcode.clone();
let webhooks = job_webhooks.clone();
tokio::spawn(async move {
use pinakes_core::jobs::{JobKind, JobQueue};
match kind {
@ -257,6 +275,14 @@ async fn main() -> Result<()> {
};
match res {
Ok(status) => {
if let Some(ref dispatcher) = webhooks {
dispatcher.dispatch(
pinakes_core::webhooks::WebhookEvent::ScanCompleted {
files_found: status.files_found,
files_processed: status.files_processed,
},
);
}
JobQueue::complete(
&jobs,
job_id,
@ -287,7 +313,7 @@ async fn main() -> Result<()> {
&jobs,
job_id,
i as f32 / total as f32,
format!("{}/{}", i, total),
format!("{i}/{total}"),
)
.await;
match storage.get_media(*mid).await {
@ -299,7 +325,7 @@ async fn main() -> Result<()> {
let tc = thumb_config.clone();
let res = tokio::task::spawn_blocking(move || {
pinakes_core::thumbnail::generate_thumbnail_with_config(
id, &source, mt, &td, &tc,
id, &source, &mt, &td, &tc,
)
})
.await;
@ -311,11 +337,11 @@ async fn main() -> Result<()> {
generated += 1;
},
Ok(Ok(None)) => {},
Ok(Err(e)) => errors.push(format!("{}: {}", mid, e)),
Err(e) => errors.push(format!("{}: {}", mid, e)),
Ok(Err(e)) => errors.push(format!("{mid}: {e}")),
Err(e) => errors.push(format!("{mid}: {e}")),
}
},
Err(e) => errors.push(format!("{}: {}", mid, e)),
Err(e) => errors.push(format!("{mid}: {e}")),
}
}
JobQueue::complete(
@ -422,7 +448,7 @@ async fn main() -> Result<()> {
.await;
},
Err(e) => {
JobQueue::fail(&jobs, job_id, e.to_string()).await
JobQueue::fail(&jobs, job_id, e.to_string()).await;
},
}
},
@ -593,7 +619,7 @@ async fn main() -> Result<()> {
},
}
},
};
}
drop(cancel);
})
},
@ -714,6 +740,7 @@ async fn main() -> Result<()> {
transcode_service,
managed_storage,
chunked_upload_manager,
webhook_dispatcher,
session_semaphore: std::sync::Arc::new(tokio::sync::Semaphore::new(
pinakes_server::state::MAX_SESSION_BACKGROUND_TASKS,
)),
@ -725,7 +752,7 @@ async fn main() -> Result<()> {
let cancel = shutdown_token.clone();
tokio::spawn(async move {
let mut interval =
tokio::time::interval(std::time::Duration::from_secs(15 * 60));
tokio::time::interval(std::time::Duration::from_mins(15));
loop {
tokio::select! {
_ = interval.tick() => {
@ -739,7 +766,7 @@ async fn main() -> Result<()> {
_ => {}
}
}
_ = cancel.cancelled() => {
() = cancel.cancelled() => {
break;
}
}
@ -753,7 +780,7 @@ async fn main() -> Result<()> {
let cancel = shutdown_token.clone();
tokio::spawn(async move {
let mut interval =
tokio::time::interval(std::time::Duration::from_secs(60 * 60));
tokio::time::interval(std::time::Duration::from_hours(1));
loop {
tokio::select! {
_ = interval.tick() => {
@ -767,7 +794,7 @@ async fn main() -> Result<()> {
_ => {}
}
}
_ = cancel.cancelled() => {
() = cancel.cancelled() => {
break;
}
}
@ -777,13 +804,14 @@ async fn main() -> Result<()> {
let config_read = config_arc.read().await;
let tls_config = config_read.server.tls.clone();
let rate_limits = config_read.rate_limits.clone();
drop(config_read);
// Create router with TLS config for HSTS headers
let router = if tls_config.enabled {
app::create_router_with_tls(state, Some(&tls_config))
app::create_router_with_tls(state, &rate_limits, Some(&tls_config))
} else {
app::create_router(state)
app::create_router(state, &rate_limits)
};
if tls_config.enabled {
@ -836,7 +864,7 @@ async fn main() -> Result<()> {
tracing::warn!(error = %e, "HTTP redirect server error");
}
}
_ = shutdown.cancelled() => {
() = shutdown.cancelled() => {
info!("HTTP redirect server shutting down");
}
}
@ -884,13 +912,14 @@ fn create_https_redirect_router(https_host: String, https_port: u16) -> Router {
Router::new().fallback(any(move |uri: axum::http::Uri| {
let https_host = https_host.clone();
async move {
let path_and_query =
uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/");
let path_and_query = uri
.path_and_query()
.map_or("/", axum::http::uri::PathAndQuery::as_str);
let https_url = if https_port == 443 {
format!("https://{}{}", https_host, path_and_query)
format!("https://{https_host}{path_and_query}")
} else {
format!("https://{}:{}{}", https_host, https_port, path_and_query)
format!("https://{https_host}:{https_port}{path_and_query}")
};
Redirect::permanent(&https_url)
@ -928,7 +957,7 @@ async fn shutdown_signal() {
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => info!("received Ctrl+C, shutting down"),
_ = terminate => info!("received SIGTERM, shutting down"),
() = ctrl_c => info!("received Ctrl+C, shutting down"),
() = terminate => info!("received SIGTERM, shutting down"),
}
}