Skip to main content

gonzalod/
gonzalod.rs

1//! `gonzalod` — the gonzalo daemon binary. Serves a record + blob store over
2//! gRPC and HTTP/JSON. Configuration via environment variables:
3//!
4//! - `GONZALO_STORE`     — substrate: `fs` (default) or `s3`
5//! - `GONZALO_ROOT`      — fs store root directory (default `./gonzalo-data`)
6//! - `GONZALO_S3_BUCKET` — s3 bucket (required when `GONZALO_STORE=s3`)
7//! - `GONZALO_S3_ENDPOINT` — s3 endpoint for MinIO/Garage (optional)
8//! - `GONZALO_S3_REGION` — s3 region override (optional)
9//! - `GONZALO_HTTP_ADDR` — HTTP/JSON bind address (default `127.0.0.1:8080`)
10//! - `GONZALO_GRPC_ADDR` — gRPC bind address (default `127.0.0.1:50051`)
11//! - `GONZALO_MAX_BLOB_SIZE` — max bytes per blob over the transports (default 64 MiB)
12//! - `GONZALO_AUTH_FILE` — TOML principals file for namespace-scoped auth
13//! - `GONZALO_TOKEN`     — single admin token (used when no auth file is set)
14//!
15//! Blob endpoints (`/v1/blobs`) are served for the `fs` and `s3` substrates,
16//! which implement [`BlobStore`](gonzalo_core::BlobStore). Git is not a content-addressed blob store, so a
17//! git-backed deployment does not serve blobs (gonzalo#184).
18//!
19//! Credentials for s3 come from the standard `AWS_*` environment.
20
21use gonzalo_core::{BlobStore, Store};
22use gonzalo_server::{Auth, Service, StoreConfig, serve_grpc, serve_http};
23use gonzalo_store_fs::FsStore;
24use gonzalo_store_s3::S3Store;
25use std::sync::Arc;
26
27#[tokio::main]
28async fn main() -> Result<(), Box<dyn std::error::Error>> {
29    let http_addr = std::env::var("GONZALO_HTTP_ADDR").unwrap_or_else(|_| "127.0.0.1:8080".into());
30    let grpc_addr = std::env::var("GONZALO_GRPC_ADDR").unwrap_or_else(|_| "127.0.0.1:50051".into());
31
32    // GONZALO_AUTH_FILE (scoped principals) > GONZALO_TOKEN (single admin) > open.
33    let auth = Arc::new(Auth::from_env(
34        |k| std::env::var(k).ok(),
35        |path| std::fs::read_to_string(path).map_err(|e| e.to_string()),
36    )?);
37    let auth_on = !matches!(*auth, Auth::Disabled);
38
39    // Select the storage substrate from the environment. One store backs both
40    // the record store and the content-addressed blob store (each backend
41    // implements both traits).
42    let config = StoreConfig::from_env(|k| std::env::var(k).ok())?;
43    let (store, blobs, graph_root): (
44        Arc<dyn Store>,
45        Arc<dyn BlobStore>,
46        Option<std::path::PathBuf>,
47    ) = match &config {
48        StoreConfig::Fs { root } => {
49            // Per-view SQLite graphs written by `gonzalo index` live under
50            // `<root>/graphs` and are queried directly.
51            let fs = Arc::new(FsStore::new(root));
52            let graphs = std::path::Path::new(root).join("graphs");
53            (fs.clone(), fs, Some(graphs))
54        }
55        StoreConfig::S3 {
56            bucket,
57            endpoint,
58            region,
59        } => {
60            // No local SQLite graph cache under S3: views assemble from the
61            // manifest + content-addressed slices (blobs) on demand.
62            let s3 =
63                Arc::new(S3Store::connect(bucket.clone(), endpoint.clone(), region.clone()).await);
64            (s3.clone(), s3, None)
65        }
66    };
67    let mut service = Service::new(store, blobs);
68    // Optional per-blob size ceiling (bytes). Defaults to the shared constant;
69    // a malformed value is a hard startup error rather than a silent fallback.
70    let max_blob_size = match std::env::var("GONZALO_MAX_BLOB_SIZE") {
71        Ok(v) if !v.is_empty() => v
72            .parse::<usize>()
73            .map_err(|e| format!("GONZALO_MAX_BLOB_SIZE must be a byte count: {e}"))?,
74        _ => gonzalo_proto::DEFAULT_MAX_BLOB_SIZE,
75    };
76    service = service.with_max_blob_size(max_blob_size);
77    if let Some(graph_root) = graph_root {
78        service = service.with_graph_root(graph_root);
79    }
80
81    let substrate = match &config {
82        StoreConfig::Fs { root } => format!("fs({root})"),
83        StoreConfig::S3 { bucket, .. } => format!("s3({bucket})"),
84    };
85    let http_listener = tokio::net::TcpListener::bind(&http_addr).await?;
86    let grpc_listener = tokio::net::TcpListener::bind(&grpc_addr).await?;
87    eprintln!(
88        "gonzalod: store {substrate}, HTTP on {http_addr}, gRPC on {grpc_addr}, auth {}",
89        if auth_on { "on" } else { "off" }
90    );
91
92    let http = tokio::spawn(serve_http(http_listener, service.clone(), auth.clone()));
93    let grpc = tokio::spawn(serve_grpc(grpc_listener, service, auth));
94
95    tokio::select! {
96        r = http => { r??; }
97        r = grpc => { r??; }
98        _ = tokio::signal::ctrl_c() => { eprintln!("gonzalod: shutting down"); }
99    }
100    Ok(())
101}