diff --git a/Cargo.lock b/Cargo.lock index a7982d4..762c860 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1245,22 +1245,6 @@ dependencies = [ "url", ] -[[package]] -name = "http-relay" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e21bdfe99224d6d446eb1b6ecc139ed96a7a8f0a77a123158b9c31d1dbbbcb08" -dependencies = [ - "anyhow", - "axum", - "axum-server", - "futures-util", - "tokio", - "tower-http", - "tracing", - "url", -] - [[package]] name = "httparse" version = "1.9.5" @@ -1275,9 +1259,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -2181,12 +2165,9 @@ dependencies = [ "futures-util", "heed", "hex", - "http-relay 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "httpdate", - "mainline", "page_size", "pkarr", - "pkarr-relay", "postcard", "pubky-common 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index f017732..d0fc79d 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -16,13 +16,13 @@ name = "request" path = "./request/main.rs" [dependencies] -anyhow = "1.0.94" +anyhow = "1.0.95" base64 = "0.22.1" -clap = { version = "4.5.23", features = ["derive"] } +clap = { version = "4.5.29", features = ["derive"] } pubky = { path = "../pubky" } pubky-common = { version = "0.2.0", path = "../pubky-common" } -reqwest = "0.12.9" +reqwest = "0.12.12" rpassword = "7.3.1" -tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread"] } tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } url = "2.5.4" diff --git a/pubky-homeserver/Cargo.toml b/pubky-homeserver/Cargo.toml index 77b53cd..2fc3094 100644 --- a/pubky-homeserver/Cargo.toml +++ b/pubky-homeserver/Cargo.toml @@ -30,7 +30,3 @@ url = "2.5.4" axum-server = { version = "0.7.1", features = ["tls-rustls-no-provider"] } tower = "0.5.2" page_size = "0.6.0" - -pkarr-relay = "0.2.0" -mainline = "5.2.0" -http-relay = "0.1.0" diff --git a/pubky-homeserver/README.md b/pubky-homeserver/README.md index ee11bc0..8aa2c26 100644 --- a/pubky-homeserver/README.md +++ b/pubky-homeserver/README.md @@ -23,11 +23,3 @@ Run with an optional config file ```bash ../target/release/pubky-homeserver --config=./src/config.toml ``` - -## Testnet - -To run a local homeserver for testing with an internal Pkarr Relay, hardcoded well known publickey and only connected to local Mainline testnet: - -```bash -cargo run -- --testnet -``` diff --git a/pubky-homeserver/src/config.example.toml b/pubky-homeserver/src/config.example.toml index 7822cb9..5a4ee5c 100644 --- a/pubky-homeserver/src/config.example.toml +++ b/pubky-homeserver/src/config.example.toml @@ -36,6 +36,6 @@ public_port = 6287 # # This domain should point to the `:`. # -# Currently we don't support ICANN TLS, so you should be runing -# a reverse proxy and managing certifcates there for this endpoint. +# Currently we don't support ICANN TLS, so you should be running +# a reverse proxy and managing certificates there for this endpoint. domain = "example.com" diff --git a/pubky-homeserver/src/core/config.rs b/pubky-homeserver/src/config.rs similarity index 66% rename from pubky-homeserver/src/core/config.rs rename to pubky-homeserver/src/config.rs index 1d74b86..7eeb4e7 100644 --- a/pubky-homeserver/src/core/config.rs +++ b/pubky-homeserver/src/config.rs @@ -8,20 +8,21 @@ use std::{ fs, net::{IpAddr, SocketAddr}, path::{Path, PathBuf}, - time::Duration, }; -const DEFAULT_HTTP_PORT: u16 = 6286; -const DEFAULT_HTTPS_PORT: u16 = 6287; +use crate::{core::CoreConfig, io::IoConfig}; -// === Database === -const DEFAULT_STORAGE_DIR: &str = "pubky"; +// === Core == +pub const DEFAULT_STORAGE_DIR: &str = "pubky"; pub const DEFAULT_MAP_SIZE: usize = 10995116277760; // 10TB (not = disk-space used) -// === Server == pub const DEFAULT_LIST_LIMIT: u16 = 100; pub const DEFAULT_MAX_LIST_LIMIT: u16 = 1000; +// === IO === +pub const DEFAULT_HTTP_PORT: u16 = 6286; +pub const DEFAULT_HTTPS_PORT: u16 = 6287; + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] struct DatabaseToml { storage: Option, @@ -55,45 +56,16 @@ struct ConfigToml { io: Option, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IoConfig { - pub http_port: u16, - pub https_port: u16, - pub public_addr: Option, - pub domain: Option, -} - /// Server configuration #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { - /// Run in [testnet](crate::Homeserver::start_testnet) mode. - pub testnet: bool, - /// Bootstrapping DHT nodes. - /// - /// Helpful to run the server locally or in testnet. - pub bootstrap: Option>, - /// Path to the storage directory. - /// - /// Defaults to a directory in the OS data directory - pub storage: PathBuf, /// Server keypair. /// /// Defaults to a random keypair. pub keypair: Keypair, - pub dht_request_timeout: Option, - /// The default limit of a list api if no `limit` query parameter is provided. - /// - /// Defaults to `100` - pub default_list_limit: u16, - /// The maximum limit of a list api, even if a `limit` query parameter is provided. - /// - /// Defaults to `1000` - pub max_list_limit: u16, - - // === Database params === - pub db_map_size: usize, pub io: IoConfig, + pub core: CoreConfig, } impl Config { @@ -114,36 +86,29 @@ impl Config { let mut config = Config::try_from_str(&s)?; // support relative path. - if config.storage.is_relative() { - config.storage = config_file_path + if config.core.storage.is_relative() { + config.core.storage = config_file_path .parent() .unwrap_or_else(|| Path::new(".")) - .join(config.storage.clone()); + .join(config.core.storage.clone()); } - fs::create_dir_all(&config.storage)?; - config.storage = config.storage.canonicalize()?; + fs::create_dir_all(&config.core.storage)?; + config.core.storage = config.core.storage.canonicalize()?; Ok(config) } - /// Test configurations - pub fn test(testnet: &mainline::Testnet) -> Self { - let bootstrap = Some(testnet.bootstrap.to_owned()); - let storage = std::env::temp_dir() - .join(pubky_common::timestamp::Timestamp::now().to_string()) - .join(DEFAULT_STORAGE_DIR); + /// Create test configurations + pub fn test(bootstrap: &[String]) -> Self { + let bootstrap = Some(bootstrap.to_vec()); Self { - bootstrap, - storage, - db_map_size: 10485760, io: IoConfig { - http_port: 0, - https_port: 0, - public_addr: None, - domain: None, + bootstrap, + ..Default::default() }, + core: CoreConfig::test(), ..Default::default() } } @@ -152,21 +117,9 @@ impl Config { impl Default for Config { fn default() -> Self { Self { - testnet: false, keypair: Keypair::random(), - bootstrap: None, - storage: storage(None) - .expect("operating environment provides no directory for application data"), - dht_request_timeout: None, - default_list_limit: DEFAULT_LIST_LIMIT, - max_list_limit: DEFAULT_MAX_LIST_LIMIT, - db_map_size: DEFAULT_MAP_SIZE, - io: IoConfig { - https_port: DEFAULT_HTTPS_PORT, - http_port: DEFAULT_HTTP_PORT, - domain: None, - public_addr: None, - }, + io: IoConfig::default(), + core: CoreConfig::default(), } } } @@ -209,28 +162,24 @@ impl TryFrom for Config { .unwrap_or(io.https_port.unwrap_or(0)), )) }), + ..Default::default() } } else { IoConfig { http_port: DEFAULT_HTTP_PORT, https_port: DEFAULT_HTTPS_PORT, - domain: None, - public_addr: None, + ..Default::default() } }; Ok(Config { - testnet: false, keypair, - storage, - dht_request_timeout: None, - bootstrap: None, - default_list_limit: DEFAULT_LIST_LIMIT, - max_list_limit: DEFAULT_MAX_LIST_LIMIT, - db_map_size: DEFAULT_MAP_SIZE, - io, + core: CoreConfig { + storage, + ..Default::default() + }, }) } } @@ -252,22 +201,8 @@ fn deserialize_secret_key(s: String) -> anyhow::Result<[u8; 32]> { Ok(arr) } -fn storage(storage: Option) -> Result { - let dir = if let Some(storage) = storage { - PathBuf::from(storage) - } else { - let path = dirs_next::data_dir().ok_or_else(|| { - anyhow!("operating environment provides no directory for application data") - })?; - path.join(DEFAULT_STORAGE_DIR) - }; - - Ok(dir.join("homeserver")) -} - #[cfg(test)] mod tests { - use mainline::Testnet; use super::*; @@ -293,31 +228,31 @@ mod tests { let config = Config::load(canonical_file_path).await.unwrap(); assert!(config + .core .storage .ends_with("pubky-homeserver/src/storage/homeserver")); } #[test] fn config_test() { - let testnet = Testnet::new(3).unwrap(); - let config = Config::test(&testnet); + let config = Config::test(&[]); assert_eq!( config, Config { - bootstrap: testnet.bootstrap.into(), - db_map_size: 10485760, - - storage: config.storage.clone(), keypair: config.keypair.clone(), io: IoConfig { - http_port: 0, - https_port: 0, - public_addr: None, - domain: None + bootstrap: Some(vec![]), + + ..Default::default() + }, + core: CoreConfig { + db_map_size: 10485760, + storage: config.core.storage.clone(), + + ..Default::default() }, - ..Default::default() } ) } @@ -362,8 +297,8 @@ public_port = 6287 # # This domain should point to the `:`. # -# Currently we don't support ICANN TLS, so you should be runing -# a reverse proxy and managing certifcates there for this endpoint. +# Currently we don't support ICANN TLS, so you should be running +# a reverse proxy and managing certificates there for this endpoint. domain = "example.com" "#, ) diff --git a/pubky-homeserver/src/core/database/mod.rs b/pubky-homeserver/src/core/database/mod.rs index dd05095..cf4e9e4 100644 --- a/pubky-homeserver/src/core/database/mod.rs +++ b/pubky-homeserver/src/core/database/mod.rs @@ -7,14 +7,15 @@ use heed::{Env, EnvOpenOptions}; mod migrations; pub mod tables; -use crate::core::config::Config; - use tables::{Tables, TABLES_COUNT}; pub use protected::DB; /// Protecting fields from being mutated by modules in crate::database mod protected { + + use crate::core::CoreConfig; + use super::*; #[derive(Debug, Clone)] @@ -23,14 +24,14 @@ mod protected { pub(crate) tables: Tables, pub(crate) buffers_dir: PathBuf, pub(crate) max_chunk_size: usize, - config: Config, + config: CoreConfig, } impl DB { /// # Safety /// DB uses LMDB, [opening][heed::EnvOpenOptions::open] which is marked unsafe, /// because the possible Undefined Behavior (UB) if the lock file is broken. - pub unsafe fn open(config: Config) -> anyhow::Result { + pub unsafe fn open(config: CoreConfig) -> anyhow::Result { let buffers_dir = config.storage.clone().join("buffers"); // Cleanup buffers. @@ -57,9 +58,14 @@ mod protected { Ok(db) } + // Create an ephemeral database for testing purposes. + pub fn test() -> DB { + unsafe { DB::open(CoreConfig::test()).unwrap() } + } + // === Getters === - pub fn config(&self) -> &Config { + pub fn config(&self) -> &CoreConfig { &self.config } } @@ -73,7 +79,7 @@ fn max_chunk_size() -> usize { // - 16 bytes Header per page (LMDB) // - Each page has to contain 2 records - // - 8 bytes per record (LMDB) (imperically, it seems to be 10 not 8) + // - 8 bytes per record (LMDB) (empirically, it seems to be 10 not 8) // - 12 bytes key: // - timestamp : 8 bytes // - chunk index: 4 bytes diff --git a/pubky-homeserver/src/core/database/tables/entries.rs b/pubky-homeserver/src/core/database/tables/entries.rs index 573c2d6..cc4e7d1 100644 --- a/pubky-homeserver/src/core/database/tables/entries.rs +++ b/pubky-homeserver/src/core/database/tables/entries.rs @@ -113,7 +113,7 @@ impl DB { /// Return a list of pubky urls. /// - /// - limit defaults to [crate::Config::default_list_limit] and capped by [crate::Config::max_list_limit] + /// - limit defaults to [crate::core::Config::default_list_limit] and capped by [crate::core::Config::max_list_limit] pub fn list( &self, txn: &RoTxn, @@ -451,16 +451,13 @@ impl<'db> std::io::Write for EntryWriter<'db> { #[cfg(test)] mod tests { use bytes::Bytes; - use mainline::Testnet; use pkarr::Keypair; - use crate::Config; - use super::DB; #[tokio::test] async fn entries() -> anyhow::Result<()> { - let mut db = unsafe { DB::open(Config::test(&Testnet::new(0).unwrap())).unwrap() }; + let mut db = DB::test(); let keypair = Keypair::random(); let public_key = keypair.public_key(); @@ -502,7 +499,7 @@ mod tests { #[tokio::test] async fn chunked_entry() -> anyhow::Result<()> { - let mut db = unsafe { DB::open(Config::test(&Testnet::new(0).unwrap())).unwrap() }; + let mut db = DB::test(); let keypair = Keypair::random(); let public_key = keypair.public_key(); diff --git a/pubky-homeserver/src/core/mod.rs b/pubky-homeserver/src/core/mod.rs index 3a51235..d81ed82 100644 --- a/pubky-homeserver/src/core/mod.rs +++ b/pubky-homeserver/src/core/mod.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use anyhow::Result; use axum::{ body::Body, @@ -6,23 +8,24 @@ use axum::{ response::Response, Router, }; -use pkarr::{Keypair, PublicKey}; +use pkarr::Keypair; use pubky_common::{ auth::{AuthToken, AuthVerifier}, capabilities::Capability, }; use tower::ServiceExt; -mod config; -mod database; +pub mod database; mod error; mod extractors; mod layers; mod routes; -use database::DB; +use crate::config::{ + DEFAULT_LIST_LIMIT, DEFAULT_MAP_SIZE, DEFAULT_MAX_LIST_LIMIT, DEFAULT_STORAGE_DIR, +}; -pub use config::Config; +use database::DB; #[derive(Clone, Debug)] pub(crate) struct AppState { @@ -33,7 +36,7 @@ pub(crate) struct AppState { #[derive(Debug, Clone)] /// A side-effect-free Core of the [Homeserver]. pub struct HomeserverCore { - config: Config, + config: CoreConfig, pub(crate) router: Router, } @@ -43,7 +46,7 @@ impl HomeserverCore { /// # Safety /// HomeserverCore uses LMDB, [opening][heed::EnvOpenOptions::open] which is marked unsafe, /// because the possible Undefined Behavior (UB) if the lock file is broken. - pub unsafe fn new(config: &Config) -> Result { + pub unsafe fn new(config: CoreConfig) -> Result { let db = unsafe { DB::open(config.clone())? }; let state = AppState { @@ -53,34 +56,20 @@ impl HomeserverCore { let router = routes::create_app(state.clone()); - Ok(Self { - router, - config: config.clone(), - }) + Ok(Self { router, config }) } - #[cfg(test)] - /// Test version of [HomeserverCore::new], using a temporary storage. + /// Test version of [HomeserverCore::new], using an ephemeral small storage. pub fn test() -> Result { - let testnet = mainline::Testnet::new(0).expect("ignore"); - - unsafe { HomeserverCore::new(&Config::test(&testnet)) } + unsafe { HomeserverCore::new(CoreConfig::test()) } } // === Getters === - pub fn config(&self) -> &Config { + pub fn config(&self) -> &CoreConfig { &self.config } - pub fn keypair(&self) -> &Keypair { - &self.config.keypair - } - - pub fn public_key(&self) -> PublicKey { - self.config.keypair.public_key() - } - // === Public Methods === pub async fn create_root_user(&mut self, keypair: &Keypair) -> Result { @@ -111,3 +100,63 @@ impl HomeserverCore { Ok(self.router.clone().oneshot(request).await?) } } + +#[derive(Debug, Clone, PartialEq, Eq)] +/// Database configurations +pub struct CoreConfig { + /// Path to the storage directory. + /// + /// Defaults to a directory in the OS data directory + pub storage: PathBuf, + pub db_map_size: usize, + + /// The default limit of a list api if no `limit` query parameter is provided. + /// + /// Defaults to `100` + pub default_list_limit: u16, + /// The maximum limit of a list api, even if a `limit` query parameter is provided. + /// + /// Defaults to `1000` + pub max_list_limit: u16, +} + +impl Default for CoreConfig { + fn default() -> Self { + Self { + storage: storage(None) + .expect("operating environment provides no directory for application data"), + db_map_size: DEFAULT_MAP_SIZE, + + default_list_limit: DEFAULT_LIST_LIMIT, + max_list_limit: DEFAULT_MAX_LIST_LIMIT, + } + } +} + +impl CoreConfig { + pub fn test() -> Self { + let storage = std::env::temp_dir() + .join(pubky_common::timestamp::Timestamp::now().to_string()) + .join(DEFAULT_STORAGE_DIR); + + Self { + storage, + db_map_size: 10485760, + + ..Default::default() + } + } +} + +pub fn storage(storage: Option) -> Result { + let dir = if let Some(storage) = storage { + PathBuf::from(storage) + } else { + let path = dirs_next::data_dir().ok_or_else(|| { + anyhow::anyhow!("operating environment provides no directory for application data") + })?; + path.join(DEFAULT_STORAGE_DIR) + }; + + Ok(dir.join("homeserver")) +} diff --git a/pubky-homeserver/src/io/http.rs b/pubky-homeserver/src/io/http.rs index 7a27499..4e4f895 100644 --- a/pubky-homeserver/src/io/http.rs +++ b/pubky-homeserver/src/io/http.rs @@ -6,13 +6,15 @@ use std::{ }; use anyhow::Result; +use axum::Router; use axum_server::{ tls_rustls::{RustlsAcceptor, RustlsConfig}, Handle, }; use futures_util::TryFutureExt; +use pkarr::Keypair; -use crate::core::HomeserverCore; +use super::IoConfig; #[derive(Debug)] pub struct HttpServers { @@ -26,9 +28,8 @@ pub struct HttpServers { } impl HttpServers { - pub async fn start(core: &HomeserverCore) -> Result { - let http_listener = - TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], core.config().io.http_port)))?; + pub async fn run(keypair: &Keypair, config: &IoConfig, router: &Router) -> Result { + let http_listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], config.http_port)))?; let http_address = http_listener.local_addr()?; let http_handle = Handle::new(); @@ -37,17 +38,15 @@ impl HttpServers { axum_server::from_tcp(http_listener) .handle(http_handle.clone()) .serve( - core.router + router .clone() .into_make_service_with_connect_info::(), ) .map_err(|error| tracing::error!(?error, "Homeserver http server error")), ); - let https_listener = TcpListener::bind(SocketAddr::from(( - [0, 0, 0, 0], - core.config().io.https_port, - )))?; + let https_listener = + TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], config.https_port)))?; let https_address = https_listener.local_addr()?; let https_handle = Handle::new(); @@ -55,11 +54,11 @@ impl HttpServers { tokio::spawn( axum_server::from_tcp(https_listener) .acceptor(RustlsAcceptor::new(RustlsConfig::from_config(Arc::new( - core.keypair().to_rpk_rustls_server_config(), + keypair.to_rpk_rustls_server_config(), )))) .handle(https_handle.clone()) .serve( - core.router + router .clone() .into_make_service_with_connect_info::(), ) diff --git a/pubky-homeserver/src/io/mod.rs b/pubky-homeserver/src/io/mod.rs index 6fb1df9..d5a8f41 100644 --- a/pubky-homeserver/src/io/mod.rs +++ b/pubky-homeserver/src/io/mod.rs @@ -1,4 +1,8 @@ -use std::path::PathBuf; +use std::{ + net::SocketAddr, + path::{Path, PathBuf}, + time::Duration, +}; use ::pkarr::{Keypair, PublicKey}; use anyhow::Result; @@ -6,7 +10,10 @@ use http::HttpServers; use pkarr::PkarrServer; use tracing::info; -use crate::{Config, HomeserverCore}; +use crate::{ + config::{Config, DEFAULT_HTTPS_PORT, DEFAULT_HTTP_PORT}, + HomeserverCore, +}; mod http; mod pkarr; @@ -15,40 +22,40 @@ mod pkarr; pub struct HomeserverBuilder(Config); impl HomeserverBuilder { - pub fn testnet(mut self) -> Self { - self.0.testnet = true; + pub fn testnet(&mut self) -> &mut Self { + self.0.io.testnet = true; self } /// Configure the Homeserver's keypair - pub fn keypair(mut self, keypair: Keypair) -> Self { + pub fn keypair(&mut self, keypair: Keypair) -> &mut Self { self.0.keypair = keypair; self } /// Configure the Mainline DHT bootstrap nodes. Useful for testnet configurations. - pub fn bootstrap(mut self, bootstrap: Vec) -> Self { - self.0.bootstrap = Some(bootstrap); + pub fn bootstrap(&mut self, bootstrap: Vec) -> &mut Self { + self.0.io.bootstrap = Some(bootstrap); self } /// Configure the storage path of the Homeserver - pub fn storage(mut self, storage: PathBuf) -> Self { - self.0.storage = storage; + pub fn storage(&mut self, storage: PathBuf) -> &mut Self { + self.0.core.storage = storage; self } - /// Start running a Homeserver + /// Run a Homeserver /// /// # Safety /// Homeserver uses LMDB, [opening][heed::EnvOpenOptions::open] which is marked unsafe, /// because the possible Undefined Behavior (UB) if the lock file is broken. - pub async unsafe fn start(self) -> Result { - Homeserver::start(self.0).await + pub async unsafe fn run(self) -> Result { + Homeserver::run(self.0).await } } @@ -56,25 +63,44 @@ impl HomeserverBuilder { /// Homeserver [Core][HomeserverCore] + I/O (http server and pkarr publishing). pub struct Homeserver { http_servers: HttpServers, - core: HomeserverCore, + keypair: Keypair, } impl Homeserver { + /// Returns a Homeserver builder. pub fn builder() -> HomeserverBuilder { HomeserverBuilder::default() } - /// Start running a Homeserver + /// Run a Homeserver with a configuration file path. /// /// # Safety /// Homeserver uses LMDB, [opening][heed::EnvOpenOptions::open] which is marked unsafe, /// because the possible Undefined Behavior (UB) if the lock file is broken. - pub async unsafe fn start(config: Config) -> Result { - tracing::debug!(?config, "Starting homeserver with configurations"); + pub async fn run_with_config_file(config_path: impl AsRef) -> Result { + unsafe { Self::run(Config::load(config_path).await?) }.await + } - let core = unsafe { HomeserverCore::new(&config)? }; + /// Run a Homeserver with configurations suitable for ephemeral tests. + pub async fn run_test(bootstrap: &[String]) -> Result { + let config = Config::test(bootstrap); - let http_servers = HttpServers::start(&core).await?; + unsafe { Self::run(config) }.await + } + + /// Run a Homeserver + /// + /// # Safety + /// Homeserver uses LMDB, [opening][heed::EnvOpenOptions::open] which is marked unsafe, + /// because the possible Undefined Behavior (UB) if the lock file is broken. + async unsafe fn run(config: Config) -> Result { + tracing::debug!(?config, "Running homeserver with configurations"); + + let keypair = config.keypair; + + let core = unsafe { HomeserverCore::new(config.core)? }; + + let http_servers = HttpServers::run(&keypair, &config.io, &core.router).await?; info!( "Homeserver listening on http://localhost:{}", @@ -84,75 +110,25 @@ impl Homeserver { info!("Publishing Pkarr packet.."); let pkarr_server = PkarrServer::new( - &config, + &keypair, + &config.io, http_servers.https_address().port(), http_servers.http_address().port(), )?; pkarr_server.publish_server_packet().await?; - info!("Homeserver listening on https://{}", core.public_key()); + info!("Homeserver listening on https://{}", keypair.public_key()); - Ok(Self { http_servers, core }) - } - - /// Start a homeserver in a Testnet mode. - /// - /// - Homeserver address is hardcoded to `8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo` - /// - Run a pkarr Relay on port [15411](pubky_common::constants::testnet_ports::PKARR_RELAY) - /// - Use a temporary storage for the both homeserver and relay - /// - Publish http port on a [reserved service parameter key](pubky_common::constants::reserved_param_keys::HTTP_PORT) - /// - Run an HTTP relay on port [15412](pubky_common::constants::testnet_ports::HTTP_RELAY) - /// - /// # Safety - /// See [Self::start] - pub async unsafe fn start_testnet() -> Result { - let testnet = mainline::Testnet::new(10)?; - testnet.leak(); - - let storage = - std::env::temp_dir().join(pubky_common::timestamp::Timestamp::now().to_string()); - - let pkarr_relay = unsafe { - let mut config = pkarr_relay::Config { - http_port: pubky_common::constants::testnet_ports::PKARR_RELAY, - cache_path: Some(storage.join("pkarr-relay")), - rate_limiter: None, - ..Default::default() - }; - - config.pkarr.bootstrap(&testnet.bootstrap); - - pkarr_relay::Relay::run(config).await? - }; - - let http_relay = http_relay::HttpRelay::builder() - .http_port(pubky_common::constants::testnet_ports::HTTP_RELAY) - .build() - .await?; - - tracing::info!(http_relay=?http_relay.local_link_url().as_str(), "Running http relay in Testnet mode"); - tracing::info!(relay_address=?pkarr_relay.relay_address(), bootstrap=? testnet.bootstrap,"Running pkarr relay in Testnet mode"); - - unsafe { - Homeserver::builder() - .testnet() - .keypair(Keypair::from_secret_key(&[0; 32])) - .bootstrap(testnet.bootstrap) - .storage(storage.join("pubky-homeserver")) - .start() - .await - } - } - - /// Unit tests version of [Homeserver::start], using mainline Testnet, and a temporary storage. - pub async fn start_test(testnet: &mainline::Testnet) -> Result { - unsafe { Homeserver::start(Config::test(testnet)).await } + Ok(Self { + http_servers, + keypair, + }) } // === Getters === pub fn public_key(&self) -> PublicKey { - self.core.public_key() + self.keypair.public_key() } /// Return the `https://` url @@ -167,3 +143,36 @@ impl Homeserver { self.http_servers.shutdown(); } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IoConfig { + // TODO: why does this matter? + /// Run in [testnet](crate::Homeserver::run_testnet) mode. + pub testnet: bool, + + pub http_port: u16, + pub https_port: u16, + pub public_addr: Option, + pub domain: Option, + + /// Bootstrapping DHT nodes. + /// + /// Helpful to run the server locally or in testnet. + pub bootstrap: Option>, + pub dht_request_timeout: Option, +} + +impl Default for IoConfig { + fn default() -> Self { + IoConfig { + https_port: DEFAULT_HTTPS_PORT, + http_port: DEFAULT_HTTP_PORT, + + testnet: false, + public_addr: None, + domain: None, + bootstrap: None, + dht_request_timeout: None, + } + } +} diff --git a/pubky-homeserver/src/io/pkarr.rs b/pubky-homeserver/src/io/pkarr.rs index d0a8af7..2f6bf6d 100644 --- a/pubky-homeserver/src/io/pkarr.rs +++ b/pubky-homeserver/src/io/pkarr.rs @@ -1,9 +1,9 @@ //! Pkarr related task use anyhow::Result; -use pkarr::{dns::rdata::SVCB, SignedPacket}; +use pkarr::{dns::rdata::SVCB, Keypair, SignedPacket}; -use crate::Config; +use super::IoConfig; pub struct PkarrServer { client: pkarr::Client, @@ -11,7 +11,12 @@ pub struct PkarrServer { } impl PkarrServer { - pub fn new(config: &Config, https_port: u16, http_port: u16) -> Result { + pub fn new( + keypair: &Keypair, + config: &IoConfig, + https_port: u16, + http_port: u16, + ) -> Result { let mut builder = pkarr::Client::builder(); // TODO: should we enable relays in homeservers for udp restricted environments? @@ -27,7 +32,7 @@ impl PkarrServer { let client = builder.build()?; - let signed_packet = create_signed_packet(config, https_port, http_port)?; + let signed_packet = create_signed_packet(keypair, config, https_port, http_port)?; Ok(Self { client, @@ -46,7 +51,8 @@ impl PkarrServer { } pub fn create_signed_packet( - config: &Config, + keypair: &Keypair, + config: &IoConfig, https_port: u16, http_port: u16, ) -> Result { @@ -60,7 +66,6 @@ pub fn create_signed_packet( signed_packet_builder = signed_packet_builder.address( ".".try_into().unwrap(), config - .io .public_addr .map(|addr| addr.ip()) .unwrap_or("127.0.0.1".parse().expect("localhost is valid ip")), @@ -70,7 +75,6 @@ pub fn create_signed_packet( // Set the public port or the local https_port svcb.set_port( config - .io .public_addr .map(|addr| addr.port()) .unwrap_or(https_port), @@ -91,12 +95,12 @@ pub fn create_signed_packet( svcb.target = "localhost".try_into().expect("localhost is valid dns name"); signed_packet_builder = signed_packet_builder.https(".".try_into().unwrap(), svcb, 60 * 60) - } else if let Some(ref domain) = config.io.domain { + } else if let Some(ref domain) = config.domain { let mut svcb = SVCB::new(10, ".".try_into()?); svcb.target = domain.as_str().try_into()?; signed_packet_builder = signed_packet_builder.https(".".try_into().unwrap(), svcb, 60 * 60); } - Ok(signed_packet_builder.build(&config.keypair)?) + Ok(signed_packet_builder.build(keypair)?) } diff --git a/pubky-homeserver/src/lib.rs b/pubky-homeserver/src/lib.rs index ee44b10..85f86fe 100644 --- a/pubky-homeserver/src/lib.rs +++ b/pubky-homeserver/src/lib.rs @@ -1,6 +1,7 @@ +mod config; mod core; mod io; -pub use core::Config; pub use core::HomeserverCore; pub use io::Homeserver; +pub use io::HomeserverBuilder; diff --git a/pubky-homeserver/src/main.rs b/pubky-homeserver/src/main.rs index 7eedba3..2387d92 100644 --- a/pubky-homeserver/src/main.rs +++ b/pubky-homeserver/src/main.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use anyhow::Result; -use pubky_homeserver::{Config, Homeserver}; +use pubky_homeserver::Homeserver; use clap::Parser; @@ -11,10 +11,6 @@ struct Cli { #[clap(short, long)] tracing_env_filter: Option, - /// Run Homeserver in a local testnet - #[clap(long)] - testnet: bool, - /// Optional Path to config file. #[clap(short, long)] config: Option, @@ -32,12 +28,10 @@ async fn main() -> Result<()> { .init(); let server = unsafe { - if args.testnet { - Homeserver::start_testnet().await? - } else if let Some(config_path) = args.config { - Homeserver::start(Config::load(config_path).await?).await? + if let Some(config_path) = args.config { + Homeserver::run_with_config_file(config_path).await? } else { - Homeserver::builder().start().await? + Homeserver::builder().run().await? } };