1use mimalloc::MiMalloc;
9use std::sync::Arc;
10use std::time::Duration;
11use tokio::net::TcpListener;
12use tokio::signal;
13#[cfg(feature = "quic")]
14use tokio::sync::Notify;
15use tracing_subscriber::{
16 fmt::format::FmtSpan,
17 layer::SubscriberExt,
18 util::SubscriberInitExt,
19};
20
21#[global_allocator]
22static GLOBAL: MiMalloc = MiMalloc;
23
24mod auth;
25mod config;
26mod db;
27mod error;
28mod jobs;
29mod juicehost;
30#[cfg(feature = "quic")]
31mod quic;
32mod routes;
33mod state;
34mod tus;
35
36use crate::config::Config;
37use crate::state::AppState;
38
39async fn shutdown_signal() {
40 let ctrl_c = async {
41 signal::ctrl_c()
42 .await
43 .expect("Failed to install Ctrl+C handler");
44 };
45
46 #[cfg(unix)]
47 let terminate = async {
48 signal::unix::signal(signal::unix::SignalKind::terminate())
49 .expect("Failed to install signal handler")
50 .recv()
51 .await;
52 };
53
54 #[cfg(not(unix))]
55 let terminate = std::future::pending::<()>();
56
57 tokio::select! {
58 _ = ctrl_c => {
59 tracing::info!("juiceback shutting down...");
60 },
61 _ = terminate => {
62 tracing::info!("juiceback terminated...");
63 },
64 }
65}
66
67#[tokio::main]
68async fn main() -> Result<(), Box<dyn std::error::Error>> {
69 let args: Vec<String> = std::env::args().collect();
70 if args.len() > 1 && args[1] == "hash-password" {
71 let password = args.get(2).expect("usage: juiceback hash-password <password>");
72 let hash = auth::hash_password(password).expect("hashing failed");
73 println!("{}", hash);
74 return Ok(());
75 }
76
77 dotenvy::dotenv().ok();
78
79 let config = Config::load().expect("Failed to load configuration");
80
81 tracing_subscriber::registry()
82 .with(
83 tracing_subscriber::EnvFilter::try_from_default_env()
84 .unwrap_or_else(|_| config.log_level.clone().into()),
85 )
86 .with(tracing_subscriber::fmt::layer().with_span_events(FmtSpan::CLOSE))
87 .init();
88
89 tracing::info!("juiceback starting");
90 tracing::info!("files: {:?}", config.files_dir);
91 tracing::info!("db: {}", config.database_path);
92
93 tokio::fs::create_dir_all(&config.files_dir)
94 .await
95 .expect("did a great job with permissions congrats, i can't make the folder");
96
97 let manager = r2d2_sqlite::SqliteConnectionManager::file(&config.database_path);
98 let pool = r2d2::Pool::builder()
99 .max_size(config.db_pool_size)
100 .connection_timeout(Duration::from_secs(5))
101 .build(manager)
102 .expect("Failed to create database connection pool");
103
104 {
105 let conn = pool.get().expect("Failed to get connection for initialization");
106 conn.execute_batch(
107 "PRAGMA journal_mode=WAL;
108 PRAGMA synchronous=NORMAL;
109 PRAGMA busy_timeout=5000;"
110 ).expect("Failed to configure SQLite pragmas");
111 db::init_db(&conn).expect("Failed to initialize database schema");
112 }
113 tracing::info!("Database initialized");
114
115 let state = AppState::new(pool, config);
116
117 let state_cleanup = Arc::clone(&state);
118 tokio::spawn(async move {
119 jobs::cleanup::run_cleanup_loop(state_cleanup).await;
120 });
121
122 let app = routes::build_router(Arc::clone(&state));
123
124 let addr = format!("{}:{}", state.config.host, state.config.port);
125 let listener = TcpListener::bind(&addr)
126 .await
127 .expect("Failed to bind to address");
128
129 #[cfg(feature = "quic")]
130 {
131 let shutdown = Arc::new(Notify::new());
132 let quic_shutdown = Arc::clone(&shutdown);
133
134 let quic_listen: std::net::SocketAddr = format!("{}:{}", state.config.host, 6402)
135 .parse()
136 .expect("Invalid QUIC address");
137
138 tokio::select! {
139 _ = async {
140 quic::start_quic_server(Arc::clone(&state), quic_listen, quic_shutdown).await;
141 } => {}
142 _ = async {
143 tracing::info!("Listening on http://{}", addr);
144 axum::serve(
145 listener,
146 app.into_make_service_with_connect_info::<std::net::SocketAddr>(),
147 )
148 .with_graceful_shutdown(shutdown_signal())
149 .await
150 .expect("Server failed");
151 } => {}
152 _ = shutdown_signal() => {
153 tracing::info!("juiceback shutting down...");
154 shutdown.notify_one();
155 }
156 }
157 }
158
159 #[cfg(not(feature = "quic"))]
160 {
161 tracing::info!("Listening on http://{}", addr);
162 axum::serve(
163 listener,
164 app.into_make_service_with_connect_info::<std::net::SocketAddr>(),
165 )
166 .with_graceful_shutdown(shutdown_signal())
167 .await
168 .expect("Server failed");
169 }
170
171 Ok(())
172}