This commit is contained in:
Mateusz Gruszczyński
2026-07-26 23:32:44 +02:00
parent 364af50a7c
commit 2857006949
28 changed files with 961 additions and 152 deletions
+95 -2
View File
@@ -10,7 +10,7 @@ mod state;
mod storage;
mod websocket;
use std::{net::SocketAddr, sync::Arc};
use std::{net::SocketAddr, path::PathBuf, sync::Arc};
use config::Config;
use database::{Database, DatabaseKind};
@@ -19,12 +19,18 @@ use tokio::net::TcpListener;
use tracing::{info, warn};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let cli = parse_command()?;
init_tracing();
let config = Config::from_env()?;
let config = Config::load(cli.config.as_deref())?;
if matches!(cli.command, Command::CheckConfig) {
println!("configuration is valid{}", cli.config.as_ref().map(|path| format!(" ({})", path.display())).unwrap_or_default());
return Ok(());
}
info!(
host = %config.host,
port = config.port,
@@ -61,6 +67,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!(database_kind = ?db.kind(), "database connection established");
run_migrations(&db).await?;
info!(database_kind = ?db.kind(), "database migrations completed");
if matches!(cli.command, Command::Migrate) {
println!("database migrations completed");
return Ok(());
}
let storage = storage::Storage::from_config(config.storage.clone()).await?;
info!(
@@ -80,8 +90,23 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
config.frontend_log_level.clone(),
config.anonymous_access_token_ttl_days,
config.user_session_ttl_days,
config.unconfirmed_account_ttl_days,
config.ldap.clone(),
));
let cleanup_state = state.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(24 * 60 * 60));
loop {
interval.tick().await;
let cutoff = (chrono::Utc::now() - chrono::Duration::days(cleanup_state.unconfirmed_account_ttl_days)).to_rfc3339();
match sqlx::query(crate::queries::get(cleanup_state.db.kind(), crate::queries::AUTH_DELETE_EXPIRED_UNCONFIRMED_USERS))
.bind(cutoff).execute(cleanup_state.db.pool()).await {
Ok(result) if result.rows_affected() > 0 => info!(deleted = result.rows_affected(), "removed expired unconfirmed accounts"),
Ok(_) => {},
Err(error) => tracing::error!(%error, "failed to remove expired unconfirmed accounts"),
}
}
});
let app = app::router(
state,
&config.static_dir,
@@ -99,6 +124,74 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[derive(Clone, Copy)]
enum Command { Run, CheckConfig, Migrate }
struct Cli {
command: Command,
config: Option<PathBuf>,
}
fn parse_command() -> Result<Cli, Box<dyn std::error::Error>> {
let mut command = Command::Run;
let mut config = None;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"-v" | "--version" => {
println!("rustpad {}", env!("CARGO_PKG_VERSION"));
std::process::exit(0);
}
"-h" | "--help" => {
print_help();
std::process::exit(0);
}
"-c" | "--config" => {
let path = args.next().ok_or("--config requires a file path")?;
if config.replace(PathBuf::from(path)).is_some() {
return Err("--config can only be specified once".into());
}
}
"check-config" => {
if !matches!(command, Command::Run) {
return Err("only one command may be specified".into());
}
command = Command::CheckConfig;
}
"migrate" => {
if !matches!(command, Command::Run) {
return Err("only one command may be specified".into());
}
command = Command::Migrate;
}
_ if arg.starts_with('-') => return Err(format!("unknown option: {arg}; use --help").into()),
_ => return Err(format!("unknown command: {arg}; use --help").into()),
}
}
Ok(Cli { command, config })
}
fn print_help() {
println!(
"rustpad {version}
USAGE:
rustpad [OPTIONS] [COMMAND]
OPTIONS:
-c, --config <FILE> Load YAML configuration file; environment variables override it
-h, --help Show help
-v, --version Show version
COMMANDS:
check-config Parse and validate configuration, then exit
migrate Validate configuration, apply database migrations, then exit",
version = env!("CARGO_PKG_VERSION")
);
}
fn init_tracing() {
tracing_subscriber::registry()
.with(