Skip to main content

pubky_homeserver/
homeserver_app.rs

1//! Top-level application orchestrator.
2//!
3//! [`HomeserverApp`] owns and coordinates the three HTTP servers
4//! ([`ClientServer`], [`AdminServer`], [`MetricsServer`]) and the background
5//! DHT republishers. It handles startup (config loading, database connection,
6//! migration) and graceful shutdown.
7
8use crate::admin_server::{AdminServer, AdminServerBuildError};
9use crate::client_server::{ClientServer, ClientServerBuildError};
10use crate::metrics_server::{MetricsServer, MetricsServerBuildError};
11use crate::republishers::{
12    HomeserverKeyRepublisher, KeyRepublisherBuildError, UserKeysRepublisher,
13};
14use crate::tracing::init_tracing_logs_with_config_if_set;
15#[cfg(any(test, feature = "testing"))]
16use crate::MockDataDir;
17use crate::{app_context::AppContext, data_directory::PersistentDataDir};
18use anyhow::Result;
19use pubky_common::crypto::PublicKey;
20use std::path::PathBuf;
21use std::time::Duration;
22
23const INITIAL_DELAY_BEFORE_REPUBLISH: Duration = Duration::from_secs(60);
24
25/// Errors that can occur when building a `HomeserverApp`.
26#[derive(thiserror::Error, Debug)]
27pub enum HomeserverAppBuildError {
28    /// Failed to build the homeserver.
29    #[error("Failed to build homeserver: {0}")]
30    Homeserver(ClientServerBuildError),
31    /// Failed to build the admin server.
32    #[error("Failed to build admin server: {0}")]
33    Admin(AdminServerBuildError),
34    /// Failed to build the metrics server.
35    #[error("Failed to build metrics server: {0}")]
36    Metrics(MetricsServerBuildError),
37}
38
39/// Homeserver with all bells and whistles.
40/// Core + Admin + Metrics servers.
41///
42/// When dropped, the homeserver will stop.
43pub struct HomeserverApp {
44    context: AppContext,
45
46    #[allow(dead_code)] // Keep this alive. When dropped, the homeserver will stop.
47    client_server: ClientServer,
48
49    #[allow(dead_code)]
50    // Keep this alive. Republishing is stopped when the UserKeysRepublisher is dropped.
51    pub(crate) user_keys_republisher: UserKeysRepublisher,
52
53    #[allow(dead_code)]
54    // Keep this alive. Republishing is stopped when the HomeserverKeyRepublisher is dropped.
55    pub(crate) key_republisher: HomeserverKeyRepublisher,
56
57    #[allow(dead_code)] // Keep this alive. When dropped, the admin server will stop.
58    admin_server: Option<AdminServer>,
59
60    #[allow(dead_code)] // Keep this alive. When dropped, the metrics server will stop.
61    metrics_server: Option<MetricsServer>,
62}
63
64impl HomeserverApp {
65    /// Run the homeserver with configurations from a data directory.
66    pub async fn start_with_persistent_data_dir_path(dir_path: PathBuf) -> Result<Self> {
67        let data_dir = PersistentDataDir::new(dir_path);
68        let context = AppContext::read_from(data_dir).await?;
69        Self::start(context).await
70    }
71
72    /// Run the homeserver with configurations from a data directory.
73    pub async fn start_with_persistent_data_dir(dir: PersistentDataDir) -> Result<Self> {
74        let context = AppContext::read_from(dir).await?;
75        Self::start(context).await
76    }
77
78    /// Run the homeserver with configurations from a data directory mock.
79    #[cfg(any(test, feature = "testing"))]
80    pub async fn start_with_mock_data_dir(dir: MockDataDir) -> Result<Self> {
81        let context = AppContext::read_from(dir).await?;
82        Self::start(context).await
83    }
84
85    /// Run a Homeserver
86    pub async fn start(context: AppContext) -> Result<Self> {
87        // Tracing Subscriber initialization based on the config file.
88        let _ = init_tracing_logs_with_config_if_set(&context.config_toml);
89
90        tracing::debug!("Homeserver data dir: {}", context.data_dir.path().display());
91
92        let user_keys_republisher =
93            UserKeysRepublisher::start_delayed(&context, INITIAL_DELAY_BEFORE_REPUBLISH);
94
95        let admin_server = if context.config_toml.admin.enabled {
96            Some(AdminServer::start(&context).await?)
97        } else {
98            None
99        };
100        let metrics_server = if context.config_toml.metrics.enabled {
101            Some(MetricsServer::start(&context).await?)
102        } else {
103            None
104        };
105        let client_server = ClientServer::start(context.clone()).await?;
106
107        let key_republisher = HomeserverKeyRepublisher::start(
108            &context,
109            client_server.icann_http_socket.port(),
110            client_server.pubky_tls_socket.port(),
111        )
112        .await
113        .map_err(KeyRepublisherBuildError::KeyRepublisher)?;
114
115        Ok(Self {
116            context,
117            client_server,
118            admin_server,
119            metrics_server,
120            user_keys_republisher,
121            key_republisher,
122        })
123    }
124
125    /// Get the core of the homeserver app.
126    pub fn client_server(&self) -> &ClientServer {
127        &self.client_server
128    }
129
130    /// Get the admin server of the homeserver app.
131    pub fn admin_server(&self) -> Option<&AdminServer> {
132        self.admin_server.as_ref()
133    }
134
135    /// Get the metrics server of the homeserver app.
136    pub fn metrics_server(&self) -> Option<&MetricsServer> {
137        self.metrics_server.as_ref()
138    }
139
140    /// Returns the public_key of this server.
141    pub fn public_key(&self) -> PublicKey {
142        self.context.keypair.public_key()
143    }
144
145    /// Returns the `https://<server public key>` url
146    pub fn pubky_url(&self) -> url::Url {
147        url::Url::parse(&format!("https://{}", self.public_key().z32())).expect("valid url")
148    }
149
150    /// Returns the `https://<server public key>` url
151    pub fn icann_http_url(&self) -> url::Url {
152        url::Url::parse(&self.client_server.icann_http_url_string()).expect("valid url")
153    }
154}