feat(homeserver): remove testnet logic, use pubky_testnet instead

This commit is contained in:
nazeh
2025-02-12 16:22:07 +03:00
parent 1b932cb27b
commit e9fe570719
14 changed files with 251 additions and 288 deletions

23
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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"

View File

@@ -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
```

View File

@@ -36,6 +36,6 @@ public_port = 6287
#
# This domain should point to the `<public_ip>:<http_port>`.
#
# 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"

View File

@@ -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<PathBuf>,
@@ -55,45 +56,16 @@ struct ConfigToml {
io: Option<IoToml>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IoConfig {
pub http_port: u16,
pub https_port: u16,
pub public_addr: Option<SocketAddr>,
pub domain: Option<String>,
}
/// 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<Vec<String>>,
/// 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<Duration>,
/// 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<ConfigToml> 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<String>) -> Result<PathBuf> {
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 `<public_ip>:<http_port>`.
#
# 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"
"#,
)

View File

@@ -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<Self> {
pub unsafe fn open(config: CoreConfig) -> anyhow::Result<Self> {
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

View File

@@ -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();

View File

@@ -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<Self> {
pub unsafe fn new(config: CoreConfig) -> Result<Self> {
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<Self> {
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<String> {
@@ -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<String>) -> Result<PathBuf> {
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"))
}

View File

@@ -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<Self> {
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<Self> {
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::<SocketAddr>(),
)
.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::<SocketAddr>(),
)

View File

@@ -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<String>) -> Self {
self.0.bootstrap = Some(bootstrap);
pub fn bootstrap(&mut self, bootstrap: Vec<String>) -> &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> {
Homeserver::start(self.0).await
pub async unsafe fn run(self) -> Result<Homeserver> {
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<Self> {
tracing::debug!(?config, "Starting homeserver with configurations");
pub async fn run_with_config_file(config_path: impl AsRef<Path>) -> Result<Self> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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://<server public key>` 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<SocketAddr>,
pub domain: Option<String>,
/// Bootstrapping DHT nodes.
///
/// Helpful to run the server locally or in testnet.
pub bootstrap: Option<Vec<String>>,
pub dht_request_timeout: Option<Duration>,
}
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,
}
}
}

View File

@@ -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<Self> {
pub fn new(
keypair: &Keypair,
config: &IoConfig,
https_port: u16,
http_port: u16,
) -> Result<Self> {
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<SignedPacket> {
@@ -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)?)
}

View File

@@ -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;

View File

@@ -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<String>,
/// Run Homeserver in a local testnet
#[clap(long)]
testnet: bool,
/// Optional Path to config file.
#[clap(short, long)]
config: Option<PathBuf>,
@@ -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?
}
};