use std::path::PathBuf;
use std::time::Duration;
use super::key_republisher::HomeserverKeyRepublisher;
use super::periodic_backup::PeriodicBackup;
use crate::app_context::AppContextConversionError;
use crate::core::user_keys_republisher::UserKeysRepublisher;
use crate::persistence::files::FileService;
use crate::persistence::lmdb::LmDB;
#[cfg(any(test, feature = "testing"))]
use crate::MockDataDir;
use crate::{app_context::AppContext, PersistentDataDir};
use crate::{DataDir, SignupMode};
use anyhow::Result;
use axum::Router;
use axum_server::{
tls_rustls::{RustlsAcceptor, RustlsConfig},
Handle,
};
use futures_util::TryFutureExt;
use pubky_common::auth::AuthVerifier;
use std::{
net::{SocketAddr, TcpListener},
sync::Arc,
};
#[derive(Clone, Debug)]
pub(crate) struct AppState {
pub(crate) verifier: AuthVerifier,
pub(crate) db: LmDB,
pub(crate) file_service: FileService,
pub(crate) signup_mode: SignupMode,
pub(crate) user_quota_bytes: Option<u64>,
}
const INITIAL_DELAY_BEFORE_REPUBLISH: Duration = Duration::from_secs(60);
#[derive(Debug, thiserror::Error)]
pub enum HomeserverBuildError {
#[error("Key republisher error: {0}")]
KeyRepublisher(anyhow::Error),
#[error("ICANN web server error: {0}")]
IcannWebServer(anyhow::Error),
#[error("Pubky TLS web server error: {0}")]
PubkyTlsServer(anyhow::Error),
#[error("AppContext conversion error: {0}")]
AppContext(AppContextConversionError),
}
pub struct HomeserverCore {
#[allow(dead_code)]
pub(crate) user_keys_republisher: UserKeysRepublisher,
#[allow(dead_code)]
pub(crate) key_republisher: HomeserverKeyRepublisher,
#[allow(dead_code)] pub(crate) periodic_backup: PeriodicBackup,
context: AppContext,
pub(crate) icann_http_handle: Handle,
pub(crate) pubky_tls_handle: Handle,
pub(crate) icann_http_socket: SocketAddr,
pub(crate) pubky_tls_socket: SocketAddr,
}
impl HomeserverCore {
pub async fn from_persistent_data_dir_path(
dir_path: PathBuf,
) -> std::result::Result<Self, HomeserverBuildError> {
let data_dir = PersistentDataDir::new(dir_path);
Self::from_persistent_data_dir(data_dir).await
}
pub async fn from_persistent_data_dir(
data_dir: PersistentDataDir,
) -> std::result::Result<Self, HomeserverBuildError> {
Self::from_data_dir(Arc::new(data_dir)).await
}
#[cfg(any(test, feature = "testing"))]
pub async fn from_mock_data_dir(
mock_dir: MockDataDir,
) -> std::result::Result<Self, HomeserverBuildError> {
Self::from_data_dir(Arc::new(mock_dir)).await
}
pub(crate) async fn from_data_dir(
dir: Arc<dyn DataDir>,
) -> std::result::Result<Self, HomeserverBuildError> {
let context = AppContext::try_from(dir).map_err(HomeserverBuildError::AppContext)?;
Self::new(context).await
}
pub async fn new(context: AppContext) -> std::result::Result<Self, HomeserverBuildError> {
let router = Self::create_router(&context);
let (icann_http_handle, icann_http_socket) =
Self::start_icann_http_server(&context, router.clone())
.await
.map_err(HomeserverBuildError::IcannWebServer)?;
let (pubky_tls_handle, pubky_tls_socket) = Self::start_pubky_tls_server(&context, router)
.await
.map_err(HomeserverBuildError::PubkyTlsServer)?;
let key_republisher = HomeserverKeyRepublisher::start(
&context,
icann_http_socket.port(),
pubky_tls_socket.port(),
)
.await
.map_err(HomeserverBuildError::KeyRepublisher)?;
let user_keys_republisher =
UserKeysRepublisher::start_delayed(&context, INITIAL_DELAY_BEFORE_REPUBLISH);
let periodic_backup = PeriodicBackup::start(&context);
Ok(Self {
user_keys_republisher,
key_republisher,
periodic_backup,
context,
icann_http_handle,
pubky_tls_handle,
icann_http_socket,
pubky_tls_socket,
})
}
pub(crate) fn create_router(context: &AppContext) -> Router {
let quota_mb = context.config_toml.general.user_storage_quota_mb;
let quota_bytes = if quota_mb == 0 {
None
} else {
Some(quota_mb * 1024 * 1024)
};
let state = AppState {
verifier: AuthVerifier::default(),
db: context.db.clone(),
file_service: context.file_service.clone(),
signup_mode: context.config_toml.general.signup_mode.clone(),
user_quota_bytes: quota_bytes,
};
super::routes::create_app(state.clone(), context)
}
async fn start_icann_http_server(
context: &AppContext,
router: Router,
) -> Result<(Handle, SocketAddr)> {
let http_listener = TcpListener::bind(context.config_toml.drive.icann_listen_socket)?;
let http_socket = http_listener.local_addr()?;
let http_handle = Handle::new();
tokio::spawn(
axum_server::from_tcp(http_listener)
.handle(http_handle.clone())
.serve(router.into_make_service_with_connect_info::<SocketAddr>())
.map_err(|error| {
tracing::error!(?error, "Homeserver icann http server error");
println!("Homeserver icann http server error: {:?}", error);
}),
);
Ok((http_handle, http_socket))
}
async fn start_pubky_tls_server(
context: &AppContext,
router: Router,
) -> Result<(Handle, SocketAddr)> {
let https_listener = TcpListener::bind(context.config_toml.drive.pubky_listen_socket)?;
let https_socket = https_listener.local_addr()?;
let https_handle = Handle::new();
tokio::spawn(
axum_server::from_tcp(https_listener)
.acceptor(RustlsAcceptor::new(RustlsConfig::from_config(Arc::new(
context.keypair.to_rpk_rustls_server_config(),
))))
.handle(https_handle.clone())
.serve(router.into_make_service_with_connect_info::<SocketAddr>())
.map_err(|error| {
tracing::error!(?error, "Homeserver pubky tls server error");
println!("Homeserver pubky tls server error: {:?}", error);
}),
);
Ok((https_handle, https_socket))
}
pub fn icann_http_url(&self) -> String {
format!("http://{}", self.icann_http_socket)
}
pub fn pubky_tls_dns_url(&self) -> String {
format!("https://{}", self.context.keypair.public_key())
}
pub fn pubky_tls_ip_url(&self) -> String {
format!("https://{}", self.pubky_tls_socket)
}
pub fn shutdown(&self) {
self.icann_http_handle
.graceful_shutdown(Some(Duration::from_secs(5)));
self.pubky_tls_handle
.graceful_shutdown(Some(Duration::from_secs(5)));
}
}
impl Drop for HomeserverCore {
fn drop(&mut self) {
self.shutdown();
}
}