atagen

malice

public
Star0
Watch0
973f8fbdaf80e2ef6679dd071233fe6b853344b2
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: crates/malice-server/src/main.rs

973f8fbdaf80atagen2026-09-28 09:10 UTC1
use std::{future::Future, net::SocketAddr, path::PathBuf, time::Duration};
973f8fbdaf80atagen2026-09-28 09:10 UTC2
973f8fbdaf80atagen2026-09-28 09:10 UTC3
use axum::{Router, middleware};
973f8fbdaf80atagen2026-09-28 09:10 UTC4
use listenfd::ListenFd;
973f8fbdaf80atagen2026-09-28 09:10 UTC5
use malice::{
973f8fbdaf80atagen2026-09-28 09:10 UTC6
    idle::IdleTracker,
973f8fbdaf80atagen2026-09-28 09:10 UTC7
    routes::{router, static_router},
973f8fbdaf80atagen2026-09-28 09:10 UTC8
    state::{AppState, ensure_dirs, load_or_create_master_key},
973f8fbdaf80atagen2026-09-28 09:10 UTC9
};
973f8fbdaf80atagen2026-09-28 09:10 UTC10
973f8fbdaf80atagen2026-09-28 09:10 UTC11
fn env_flag(name: &str, default: bool) -> bool {
973f8fbdaf80atagen2026-09-28 09:10 UTC12
    match std::env::var(name) {
973f8fbdaf80atagen2026-09-28 09:10 UTC13
        Ok(value) => {
973f8fbdaf80atagen2026-09-28 09:10 UTC14
            let value = value.trim().to_ascii_lowercase();
973f8fbdaf80atagen2026-09-28 09:10 UTC15
            match value.as_str() {
973f8fbdaf80atagen2026-09-28 09:10 UTC16
                "1" | "true" | "yes" | "on" => true,
973f8fbdaf80atagen2026-09-28 09:10 UTC17
                "0" | "false" | "no" | "off" => false,
973f8fbdaf80atagen2026-09-28 09:10 UTC18
                _ => default,
973f8fbdaf80atagen2026-09-28 09:10 UTC19
            }
973f8fbdaf80atagen2026-09-28 09:10 UTC20
        }
973f8fbdaf80atagen2026-09-28 09:10 UTC21
        Err(_) => default,
973f8fbdaf80atagen2026-09-28 09:10 UTC22
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC23
}
973f8fbdaf80atagen2026-09-28 09:10 UTC24
973f8fbdaf80atagen2026-09-28 09:10 UTC25
fn data_dir() -> PathBuf {
973f8fbdaf80atagen2026-09-28 09:10 UTC26
    std::env::var("MALICE_DATA_DIR")
973f8fbdaf80atagen2026-09-28 09:10 UTC27
        .map(PathBuf::from)
973f8fbdaf80atagen2026-09-28 09:10 UTC28
        .unwrap_or_else(|_| PathBuf::from("local-data"))
973f8fbdaf80atagen2026-09-28 09:10 UTC29
}
973f8fbdaf80atagen2026-09-28 09:10 UTC30
973f8fbdaf80atagen2026-09-28 09:10 UTC31
fn bind_addr() -> SocketAddr {
973f8fbdaf80atagen2026-09-28 09:10 UTC32
    std::env::var("MALICE_BIND")
973f8fbdaf80atagen2026-09-28 09:10 UTC33
        .unwrap_or_else(|_| "127.0.0.1:3000".to_owned())
973f8fbdaf80atagen2026-09-28 09:10 UTC34
        .parse()
973f8fbdaf80atagen2026-09-28 09:10 UTC35
        .expect("MALICE_BIND must be host:port")
973f8fbdaf80atagen2026-09-28 09:10 UTC36
}
973f8fbdaf80atagen2026-09-28 09:10 UTC37
973f8fbdaf80atagen2026-09-28 09:10 UTC38
fn idle_exit_secs() -> Option<u64> {
973f8fbdaf80atagen2026-09-28 09:10 UTC39
    std::env::var("MALICE_IDLE_EXIT_SECS")
973f8fbdaf80atagen2026-09-28 09:10 UTC40
        .ok()
973f8fbdaf80atagen2026-09-28 09:10 UTC41
        .and_then(|value| value.trim().parse().ok())
973f8fbdaf80atagen2026-09-28 09:10 UTC42
}
973f8fbdaf80atagen2026-09-28 09:10 UTC43
973f8fbdaf80atagen2026-09-28 09:10 UTC44
fn web_dir() -> Option<String> {
973f8fbdaf80atagen2026-09-28 09:10 UTC45
    if let Ok(dir) = std::env::var("MALICE_WEB_DIR") {
973f8fbdaf80atagen2026-09-28 09:10 UTC46
        assert!(
973f8fbdaf80atagen2026-09-28 09:10 UTC47
            std::path::Path::new(&dir).join("index.html").is_file(),
973f8fbdaf80atagen2026-09-28 09:10 UTC48
            "MALICE_WEB_DIR must point to built frontend assets containing index.html"
973f8fbdaf80atagen2026-09-28 09:10 UTC49
        );
973f8fbdaf80atagen2026-09-28 09:10 UTC50
        return Some(dir);
973f8fbdaf80atagen2026-09-28 09:10 UTC51
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC52
973f8fbdaf80atagen2026-09-28 09:10 UTC53
    let local = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("web/dist");
973f8fbdaf80atagen2026-09-28 09:10 UTC54
    local
973f8fbdaf80atagen2026-09-28 09:10 UTC55
        .join("index.html")
973f8fbdaf80atagen2026-09-28 09:10 UTC56
        .is_file()
973f8fbdaf80atagen2026-09-28 09:10 UTC57
        .then(|| local.to_string_lossy().into_owned())
973f8fbdaf80atagen2026-09-28 09:10 UTC58
}
973f8fbdaf80atagen2026-09-28 09:10 UTC59
973f8fbdaf80atagen2026-09-28 09:10 UTC60
/// Takes over a systemd-activated listener from fd 3 when present, so the
973f8fbdaf80atagen2026-09-28 09:10 UTC61
/// socket unit can hold the port before the process ever starts. Falls back
973f8fbdaf80atagen2026-09-28 09:10 UTC62
/// to a plain bind otherwise.
973f8fbdaf80atagen2026-09-28 09:10 UTC63
async fn bind_listener() -> tokio::net::TcpListener {
973f8fbdaf80atagen2026-09-28 09:10 UTC64
    let mut listenfd = ListenFd::from_env();
973f8fbdaf80atagen2026-09-28 09:10 UTC65
    if let Some(listener) = listenfd
973f8fbdaf80atagen2026-09-28 09:10 UTC66
        .take_tcp_listener(0)
973f8fbdaf80atagen2026-09-28 09:10 UTC67
        .expect("inspecting inherited sockets")
973f8fbdaf80atagen2026-09-28 09:10 UTC68
    {
973f8fbdaf80atagen2026-09-28 09:10 UTC69
        listener
973f8fbdaf80atagen2026-09-28 09:10 UTC70
            .set_nonblocking(true)
973f8fbdaf80atagen2026-09-28 09:10 UTC71
            .expect("setting inherited socket nonblocking");
973f8fbdaf80atagen2026-09-28 09:10 UTC72
        return tokio::net::TcpListener::from_std(listener)
973f8fbdaf80atagen2026-09-28 09:10 UTC73
            .expect("adopting inherited socket into tokio");
973f8fbdaf80atagen2026-09-28 09:10 UTC74
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC75
    let addr = bind_addr();
973f8fbdaf80atagen2026-09-28 09:10 UTC76
    tracing::info!("listening on {addr}");
973f8fbdaf80atagen2026-09-28 09:10 UTC77
    tokio::net::TcpListener::bind(addr)
973f8fbdaf80atagen2026-09-28 09:10 UTC78
        .await
973f8fbdaf80atagen2026-09-28 09:10 UTC79
        .expect("binding the listener must succeed")
973f8fbdaf80atagen2026-09-28 09:10 UTC80
}
973f8fbdaf80atagen2026-09-28 09:10 UTC81
973f8fbdaf80atagen2026-09-28 09:10 UTC82
/// Serves `app` on `listener`, exiting on ctrl-c or, when
973f8fbdaf80atagen2026-09-28 09:10 UTC83
/// `MALICE_IDLE_EXIT_SECS` is set, after that long with no request in flight
973f8fbdaf80atagen2026-09-28 09:10 UTC84
/// or started. `on_shutdown` runs after the exit signal either way.
973f8fbdaf80atagen2026-09-28 09:10 UTC85
async fn serve(
973f8fbdaf80atagen2026-09-28 09:10 UTC86
    listener: tokio::net::TcpListener,
973f8fbdaf80atagen2026-09-28 09:10 UTC87
    app: Router,
973f8fbdaf80atagen2026-09-28 09:10 UTC88
    on_shutdown: impl Future<Output = ()> + Send + 'static,
973f8fbdaf80atagen2026-09-28 09:10 UTC89
) {
973f8fbdaf80atagen2026-09-28 09:10 UTC90
    let idle = idle_exit_secs().map(|secs| (IdleTracker::new(), secs));
973f8fbdaf80atagen2026-09-28 09:10 UTC91
    let app = match &idle {
973f8fbdaf80atagen2026-09-28 09:10 UTC92
        Some((tracker, _)) => app.layer(middleware::from_fn_with_state(
973f8fbdaf80atagen2026-09-28 09:10 UTC93
            tracker.clone(),
973f8fbdaf80atagen2026-09-28 09:10 UTC94
            malice::idle::track,
973f8fbdaf80atagen2026-09-28 09:10 UTC95
        )),
973f8fbdaf80atagen2026-09-28 09:10 UTC96
        None => app,
973f8fbdaf80atagen2026-09-28 09:10 UTC97
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC98
    axum::serve(listener, app)
973f8fbdaf80atagen2026-09-28 09:10 UTC99
        .with_graceful_shutdown(async move {
973f8fbdaf80atagen2026-09-28 09:10 UTC100
            match idle {
973f8fbdaf80atagen2026-09-28 09:10 UTC101
                Some((tracker, secs)) => {
973f8fbdaf80atagen2026-09-28 09:10 UTC102
                    tokio::select! {
973f8fbdaf80atagen2026-09-28 09:10 UTC103
                        _ = tokio::signal::ctrl_c() => {}
973f8fbdaf80atagen2026-09-28 09:10 UTC104
                        _ = tracker.wait_idle(Duration::from_secs(secs)) => {
973f8fbdaf80atagen2026-09-28 09:10 UTC105
                            tracing::info!("idle for {secs}s with no requests, exiting");
973f8fbdaf80atagen2026-09-28 09:10 UTC106
                        }
973f8fbdaf80atagen2026-09-28 09:10 UTC107
                    }
973f8fbdaf80atagen2026-09-28 09:10 UTC108
                }
973f8fbdaf80atagen2026-09-28 09:10 UTC109
                None => {
973f8fbdaf80atagen2026-09-28 09:10 UTC110
                    let _ = tokio::signal::ctrl_c().await;
973f8fbdaf80atagen2026-09-28 09:10 UTC111
                }
973f8fbdaf80atagen2026-09-28 09:10 UTC112
            }
973f8fbdaf80atagen2026-09-28 09:10 UTC113
            on_shutdown.await;
973f8fbdaf80atagen2026-09-28 09:10 UTC114
        })
973f8fbdaf80atagen2026-09-28 09:10 UTC115
        .await
973f8fbdaf80atagen2026-09-28 09:10 UTC116
        .expect("serving must succeed");
973f8fbdaf80atagen2026-09-28 09:10 UTC117
}
973f8fbdaf80atagen2026-09-28 09:10 UTC118
973f8fbdaf80atagen2026-09-28 09:10 UTC119
async fn run_hosted() {
973f8fbdaf80atagen2026-09-28 09:10 UTC120
    let data_dir = data_dir();
973f8fbdaf80atagen2026-09-28 09:10 UTC121
    ensure_dirs(&data_dir);
973f8fbdaf80atagen2026-09-28 09:10 UTC122
    let master_key =
973f8fbdaf80atagen2026-09-28 09:10 UTC123
        load_or_create_master_key(&data_dir).expect("loading the master key must succeed");
973f8fbdaf80atagen2026-09-28 09:10 UTC124
    let cookie_secure = env_flag("MALICE_SESSION_COOKIE_SECURE", true);
973f8fbdaf80atagen2026-09-28 09:10 UTC125
    let allow_http_homeserver = env_flag("MALICE_ALLOW_HTTP_HOMESERVER", false);
973f8fbdaf80atagen2026-09-28 09:10 UTC126
    let state = AppState::new(data_dir, master_key, cookie_secure, allow_http_homeserver);
973f8fbdaf80atagen2026-09-28 09:10 UTC127
    let app = router(state.clone(), web_dir());
973f8fbdaf80atagen2026-09-28 09:10 UTC128
    let listener = bind_listener().await;
973f8fbdaf80atagen2026-09-28 09:10 UTC129
    serve(
973f8fbdaf80atagen2026-09-28 09:10 UTC130
        listener,
973f8fbdaf80atagen2026-09-28 09:10 UTC131
        app,
973f8fbdaf80atagen2026-09-28 09:10 UTC132
        async move { state.shutdown_gracefully().await },
973f8fbdaf80atagen2026-09-28 09:10 UTC133
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC134
    .await;
973f8fbdaf80atagen2026-09-28 09:10 UTC135
}
973f8fbdaf80atagen2026-09-28 09:10 UTC136
973f8fbdaf80atagen2026-09-28 09:10 UTC137
async fn run_static_only() {
973f8fbdaf80atagen2026-09-28 09:10 UTC138
    let app = static_router(web_dir());
973f8fbdaf80atagen2026-09-28 09:10 UTC139
    let listener = bind_listener().await;
973f8fbdaf80atagen2026-09-28 09:10 UTC140
    serve(listener, app, async {}).await;
973f8fbdaf80atagen2026-09-28 09:10 UTC141
}
973f8fbdaf80atagen2026-09-28 09:10 UTC142
973f8fbdaf80atagen2026-09-28 09:10 UTC143
#[tokio::main]
973f8fbdaf80atagen2026-09-28 09:10 UTC144
async fn main() {
973f8fbdaf80atagen2026-09-28 09:10 UTC145
    tracing_subscriber::fmt()
973f8fbdaf80atagen2026-09-28 09:10 UTC146
        .with_env_filter(
973f8fbdaf80atagen2026-09-28 09:10 UTC147
            tracing_subscriber::EnvFilter::try_from_default_env()
973f8fbdaf80atagen2026-09-28 09:10 UTC148
                .unwrap_or_else(|_| "malice=info,tower_http=info".into()),
973f8fbdaf80atagen2026-09-28 09:10 UTC149
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC150
        .with_target(false)
973f8fbdaf80atagen2026-09-28 09:10 UTC151
        .compact()
973f8fbdaf80atagen2026-09-28 09:10 UTC152
        .init();
973f8fbdaf80atagen2026-09-28 09:10 UTC153
973f8fbdaf80atagen2026-09-28 09:10 UTC154
    if env_flag("MALICE_STATIC_ONLY", false) {
973f8fbdaf80atagen2026-09-28 09:10 UTC155
        run_static_only().await;
973f8fbdaf80atagen2026-09-28 09:10 UTC156
    } else {
973f8fbdaf80atagen2026-09-28 09:10 UTC157
        run_hosted().await;
973f8fbdaf80atagen2026-09-28 09:10 UTC158
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC159
}