anvilsign in

collin/anvil

1//! HTTP server for anvil: the web UI and the smart-HTTP git endpoints
2//! (`/<owner>/<repo>.git/info/refs`, `/git-upload-pack`, `/git-receive-pack`),
3//! plus cookie-session auth.
4
5// Handlers return `Result<_, Response>` — the idiomatic axum pattern where the
6// error arm is a ready-made HTTP response (404/500/redirect). `Response` is
7// intrinsically large, so `result_large_err` fires across the crate; the
8// pattern is intentional and the responses are never hot-path allocated en masse.
9#![allow(clippy::result_large_err)]
10
11use anvil_core::{App, Result};
12use axum::{
13 Router,
14 routing::{get, post},
15};
16
17pub mod auth;
18pub mod git_http;
19pub mod ui;
20
21/// Build the application router.
22pub fn router(app: App) -> Router {
23 let mut router = Router::new()
24 .route("/-/healthz", get(healthz))
25 .route("/-/login", get(auth::login_form).post(auth::login_submit))
26 .route("/-/logout", post(auth::logout));
27 router = ui::routes(router); // web UI, including `/`
28 router = git_http::routes(router); // smart-HTTP git endpoints
29 router
30 // Derives the per-request CSRF token so the layout can attach it to
31 // htmx requests (hx-headers). Runs for all routes; cheap.
32 .layer(axum::middleware::from_fn_with_state(
33 app.clone(),
34 auth::csrf_context,
35 ))
36 .with_state(app)
37}
38
39/// Bind to the configured HTTP address and serve until shutdown.
40pub async fn serve(app: App) -> Result<()> {
41 let listen = app.config.http.listen.clone();
42 let router = router(app);
43
44 let listener = tokio::net::TcpListener::bind(&listen).await?;
45 tracing::info!("anvil web server listening on http://{listen}");
46 axum::serve(listener, router).await?;
47 Ok(())
48}
49
50async fn healthz() -> &'static str {
51 "ok"
52}