Skip to main content

vyn_relay/
lib.rs

1pub mod auth;
2pub mod service;
3pub mod store;
4
5use std::net::SocketAddr;
6
7use anyhow::{Context, Result};
8use tokio::net::TcpListener;
9use tokio_stream::wrappers::TcpListenerStream;
10use tonic::transport::Server;
11
12pub mod proto {
13    tonic::include_proto!("vyn");
14}
15
16#[derive(Debug, Clone, Default)]
17pub struct ServeConfig {
18    pub s3_bucket: Option<String>,
19    pub s3_region: Option<String>,
20    pub s3_endpoint: Option<String>,
21    pub s3_prefix: Option<String>,
22}
23
24pub async fn serve(port: u16, data_dir: String) -> Result<()> {
25    serve_with_config(port, data_dir, ServeConfig::default()).await
26}
27
28pub async fn serve_with_config(port: u16, data_dir: String, config: ServeConfig) -> Result<()> {
29    let backend_mode = if config.s3_bucket.is_some() && config.s3_region.is_some() {
30        "local + s3-mirror"
31    } else {
32        "local-only"
33    };
34
35    if config.s3_bucket.is_some() ^ config.s3_region.is_some() {
36        eprintln!(
37            "relay warning: partial S3 config detected (need both --s3-bucket and --s3-region); running local-only"
38        );
39    }
40
41    println!("relay startup: backend_mode={backend_mode}, data_dir={data_dir}, port={port}");
42
43    let store = store::FileStore::new(&data_dir, config)
44        .await
45        .context("failed to initialize relay store backend")?;
46    store.init().context("failed to initialize relay store")?;
47
48    let addr: SocketAddr = format!("0.0.0.0:{port}")
49        .parse()
50        .context("failed to parse relay bind address")?;
51
52    let service = service::RelayService::new(store);
53
54    // TLS: to enable TLS, use Server::builder().tls_config(...) before add_service,
55    // or terminate TLS at a reverse proxy (nginx, caddy) in front of this server.
56    Server::builder()
57        .add_service(proto::vyn_relay_server::VynRelayServer::new(service))
58        .serve(addr)
59        .await
60        .context("relay server terminated with an error")
61}
62
63/// Start the relay server on an already-bound listener. Used in tests to avoid
64/// the TOCTOU race between binding a port and handing it to tonic.
65pub async fn serve_with_listener(listener: TcpListener, data_dir: String) -> Result<()> {
66    let store = store::FileStore::new(&data_dir, ServeConfig::default())
67        .await
68        .context("failed to initialize relay store backend")?;
69    store.init().context("failed to initialize relay store")?;
70
71    let service = service::RelayService::new(store);
72
73    Server::builder()
74        .add_service(proto::vyn_relay_server::VynRelayServer::new(service))
75        .serve_with_incoming(TcpListenerStream::new(listener))
76        .await
77        .context("relay server terminated with an error")
78}