mirror of
https://github.com/aljazceru/pubky-core.git
synced 2025-12-31 04:44:37 +01:00
feat(homeserver): remove testnet logic, use pubky_testnet instead
This commit is contained in:
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
"#,
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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>(),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user