anvilsign in

collin/anvil

main / crates / anvil-web / src / lib.rs
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::{
12 App,
13 Result,
14};
15use axum::{
16 Router,
17 routing::{
18 get,
19 post,
20 },
21};
22
23pub mod admin;
24pub mod agent;
25pub mod artifacts;
26pub mod attachments;
27pub mod auth;
28pub mod git_http;
29pub mod oidc;
30pub mod pages;
31pub mod runner;
32pub mod secrets;
33pub mod todomd;
34pub mod ui;
35
36/// Build the application router.
37pub fn router(app: App) -> Router {
38 let mut router = Router::new()
39 .route("/-/healthz", get(healthz))
40 .route("/-/login", get(auth::login_form).post(auth::login_submit))
41 .route("/-/logout", post(auth::logout));
42 router = ui::routes(router); // web UI, including `/`
43 router = admin::routes(router); // admin-only dashboard
44 router = agent::routes(router); // agent sessions + the browser terminal
45 router = pages::routes(router); // static sites from `pages` branches
46 router = artifacts::routes(router); // CI artifact downloads + sites
47 router = attachments::routes(
48 router,
49 app.config.http.attachment_max_mb.saturating_mul(1 << 20),
50 ); // uploaded image attachments
51 router = oidc::routes(router); // single sign-on, when configured
52 router = secrets::routes(router); // sealed per-repo secrets + unlock API
53 router = runner::routes(router); // job runners claiming and reporting CI
54 router = git_http::routes(router); // smart-HTTP git endpoints
55 router
56 // Derives the per-request CSRF token so the layout can attach it to
57 // htmx requests (hx-headers). Runs for all routes; cheap.
58 .layer(axum::middleware::from_fn_with_state(
59 app.clone(),
60 auth::csrf_context,
61 ))
62 .with_state(app)
63}
64
65/// Bind to the configured HTTP address and serve until shutdown.
66pub async fn serve(app: App) -> Result<()> {
67 let listen = app.config.http.listen.clone();
68 let router = router(app);
69
70 let listener = tokio::net::TcpListener::bind(&listen).await?;
71 tracing::info!("anvil web server listening on http://{listen}");
72 axum::serve(listener, router).await?;
73 Ok(())
74}
75
76async fn healthz() -> &'static str {
77 "ok"
78}