From 54bb5450f6164fd57e5ece9f2dcee19e285fe75a Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sat, 16 Mar 2024 12:57:53 +1300 Subject: [PATCH] Reload config on HUP signal; change and synchronize default config --- chorus-bin/src/globals.rs | 5 +++-- chorus-bin/src/main.rs | 19 +++++++++++++++++-- chorus-bin/src/nostr.rs | 39 +++++++++++++++++++++------------------ chorus-bin/src/web.rs | 2 +- chorus-lib/src/config.rs | 31 ++++++++++++++++++++----------- contrib/chorus.toml | 16 ++++++++-------- docs/CONFIG.md | 24 +++++++++++++----------- 7 files changed, 83 insertions(+), 53 deletions(-) diff --git a/chorus-bin/src/globals.rs b/chorus-bin/src/globals.rs index ad43e69..f3e3fb9 100644 --- a/chorus-bin/src/globals.rs +++ b/chorus-bin/src/globals.rs @@ -6,9 +6,10 @@ use std::sync::atomic::AtomicUsize; use std::sync::OnceLock; use tokio::sync::broadcast::Sender as BroadcastSender; use tokio::sync::watch::Sender as WatchSender; +use tokio::sync::RwLock; pub struct Globals { - pub config: OnceLock, + pub config: RwLock, pub store: OnceLock, pub http_server: Http, pub rid: OnceLock, @@ -33,7 +34,7 @@ lazy_static! { let (shutting_down, _) = tokio::sync::watch::channel(false); Globals { - config: OnceLock::new(), + config: RwLock::new(Default::default()), store: OnceLock::new(), http_server, rid: OnceLock::new(), diff --git a/chorus-bin/src/main.rs b/chorus-bin/src/main.rs index b00d846..168dbbe 100644 --- a/chorus-bin/src/main.rs +++ b/chorus-bin/src/main.rs @@ -45,7 +45,7 @@ async fn main() -> Result<(), Error> { let config_path = args.next().unwrap(); // Read config file - let mut file = OpenOptions::new().read(true).open(config_path)?; + let mut file = OpenOptions::new().read(true).open(config_path.clone())?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let friendly_config: FriendlyConfig = toml::from_str(&contents)?; @@ -80,11 +80,12 @@ async fn main() -> Result<(), Error> { log::info!(target: "Server", "Running on {}:{}", config.ip_address, config.port); // Store config into GLOBALS - let _ = GLOBALS.config.set(config); + *GLOBALS.config.write().await = config; let mut interrupt_signal = signal(SignalKind::interrupt())?; let mut quit_signal = signal(SignalKind::quit())?; let mut terminate_signal = signal(SignalKind::terminate())?; + let mut hup_signal = signal(SignalKind::hangup())?; loop { tokio::select! { @@ -102,6 +103,20 @@ async fn main() -> Result<(), Error> { break; }, + // Reload config on HUP + v = hup_signal.recv() => if v.is_some() { + log::info!(target: "Server", "SIGHUP: Reloading configuration"); + + // Reload the config file + let mut file = OpenOptions::new().read(true).open(config_path.clone())?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let friendly_config: FriendlyConfig = toml::from_str(&contents)?; + let config: Config = friendly_config.into_config()?; + + *GLOBALS.config.write().await = config; + }, + // Accepts network connections and spawn a task to serve each one v = listener.accept() => { let (tcp_stream, peer_addr) = v?; diff --git a/chorus-bin/src/nostr.rs b/chorus-bin/src/nostr.rs index 8f76c50..54e6462 100644 --- a/chorus-bin/src/nostr.rs +++ b/chorus-bin/src/nostr.rs @@ -82,7 +82,7 @@ impl WebSocketService { if let Err(e) = self.req_inner(&subid, filters).await { let reply = match e.inner { ChorusError::TooManySubscriptions => { - let max_subscriptions = GLOBALS.config.get().unwrap().max_subscriptions; + let max_subscriptions = GLOBALS.config.read().await.max_subscriptions; NostrReply::Closed( &subid, NostrReplyPrefix::Blocked, @@ -105,7 +105,7 @@ impl WebSocketService { } async fn req_inner(&mut self, subid: &String, filters: Vec) -> Result<(), Error> { - let max_subscriptions = GLOBALS.config.get().unwrap().max_subscriptions; + let max_subscriptions = GLOBALS.config.read().await.max_subscriptions; if self.subscriptions.len() >= max_subscriptions { return Err(ChorusError::TooManySubscriptions.into()); } @@ -233,8 +233,7 @@ impl WebSocketService { let event_flags = event_flags(&event, &user); - if !event_flags.author_is_an_authorized_user || GLOBALS.config.get().unwrap().verify_events - { + if !event_flags.author_is_an_authorized_user || GLOBALS.config.read().await.verify_events { // Verify the event is valid (id is hash, signature is valid) if let Err(e) = event.verify() { return Err(ChorusError::EventIsInvalid(format!("{}", e)).into()); @@ -345,7 +344,7 @@ impl WebSocketService { }; if let Some(h) = url.host() { let theirhost = h.to_owned(); - if theirhost == GLOBALS.config.get().unwrap().hostname { + if theirhost == GLOBALS.config.read().await.hostname { relay_ok = true; } } @@ -402,7 +401,7 @@ async fn screen_incoming_event( } // Accept if an open relay - if GLOBALS.config.get().unwrap().open_relay { + if GLOBALS.config.read().await.open_relay { return Ok(true); } @@ -412,20 +411,20 @@ async fn screen_incoming_event( } // Accept relay lists from anybody - if event.kind() == Kind(10002) && GLOBALS.config.get().unwrap().serve_relay_lists { + if event.kind() == Kind(10002) && GLOBALS.config.read().await.serve_relay_lists { return Ok(true); } // Allow if event kind ephemeral - if event.kind().is_ephemeral() && GLOBALS.config.get().unwrap().serve_ephemeral { + if event.kind().is_ephemeral() && GLOBALS.config.read().await.serve_ephemeral { return Ok(true); } // If the author is one of our users, always accept it if GLOBALS .config - .get() - .unwrap() + .read() + .await .user_keys .contains(&event.pubkey()) { @@ -436,7 +435,7 @@ async fn screen_incoming_event( for mut tag in event.tags()?.iter() { if tag.next() == Some(b"p") { if let Some(value) = tag.next() { - for ukhex in &GLOBALS.config.get().unwrap().user_hex_keys { + for ukhex in &GLOBALS.config.read().await.user_hex_keys { if value == ukhex.as_bytes() { return Ok(true); } @@ -460,17 +459,17 @@ pub fn screen_outgoing_event( } // Allow if an open relay - if GLOBALS.config.get().unwrap().open_relay { + if GLOBALS.config.blocking_read().open_relay { return true; } // Allow Relay Lists - if event.kind() == Kind(10002) && GLOBALS.config.get().unwrap().serve_relay_lists { + if event.kind() == Kind(10002) && GLOBALS.config.blocking_read().serve_relay_lists { return true; } // Allow if event kind ephemeral - if event.kind().is_ephemeral() && GLOBALS.config.get().unwrap().serve_ephemeral { + if event.kind().is_ephemeral() && GLOBALS.config.blocking_read().serve_ephemeral { return true; } @@ -491,7 +490,7 @@ pub fn screen_outgoing_event( pub async fn authorized_user(user: &Option) -> bool { match user { None => false, - Some(pk) => GLOBALS.config.get().unwrap().user_keys.contains(pk), + Some(pk) => GLOBALS.config.read().await.user_keys.contains(pk), } } @@ -505,8 +504,7 @@ pub struct EventFlags { pub fn event_flags(event: &Event<'_>, user: &Option) -> EventFlags { let author_is_an_authorized_user = GLOBALS .config - .get() - .unwrap() + .blocking_read() .user_keys .contains(&event.pubkey()); @@ -529,7 +527,12 @@ pub fn event_flags(event: &Event<'_>, user: &Option) -> EventFlags { } } - if GLOBALS.config.get().unwrap().user_keys.contains(&tagged_pk) { + if GLOBALS + .config + .blocking_read() + .user_keys + .contains(&tagged_pk) + { tags_an_authorized_user = true; } } diff --git a/chorus-bin/src/web.rs b/chorus-bin/src/web.rs index a9f2754..da50ff7 100644 --- a/chorus-bin/src/web.rs +++ b/chorus-bin/src/web.rs @@ -18,7 +18,7 @@ pub async fn serve_http(peer: SocketAddr, request: Request) -> Result Result, Error> { log::debug!(target: "Client", "{}: sent NIP-11", peer); let rid = { - let config = GLOBALS.config.get().unwrap(); + let config = &*GLOBALS.config.read().await; GLOBALS.rid.get_or_init(|| build_rid(config)) }; diff --git a/chorus-lib/src/config.rs b/chorus-lib/src/config.rs index c385f46..ecbfb10 100644 --- a/chorus-lib/src/config.rs +++ b/chorus-lib/src/config.rs @@ -35,15 +35,15 @@ pub struct FriendlyConfig { impl Default for FriendlyConfig { fn default() -> FriendlyConfig { FriendlyConfig { - data_directory: "/tmp".to_string(), + data_directory: "/opt/chorus/var/chorus".to_string(), ip_address: "127.0.0.1".to_string(), - port: 80, + port: 443, hostname: "localhost".to_string(), - use_tls: false, - certchain_pem_path: "./tls/fullchain.pem".to_string(), - key_pem_path: "./tls/privkey.pem".to_string(), - name: None, - description: None, + use_tls: true, + certchain_pem_path: "/opt/chorus/etc/tls/fullchain.pem".to_string(), + key_pem_path: "/opt/chorus/etc/tls/privkey.pem".to_string(), + name: Some("Chorus Default".to_string()), + description: Some("A default config of the Chorus relay".to_string()), contact: None, public_key_hex: None, open_relay: false, @@ -56,8 +56,8 @@ impl Default for FriendlyConfig { serve_ephemeral: true, serve_relay_lists: true, server_log_level: "Info".to_string(), - library_log_level: "Warn".to_string(), - client_log_level: "Error".to_string(), + library_log_level: "Info".to_string(), + client_log_level: "Info".to_string(), } } } @@ -103,9 +103,9 @@ impl FriendlyConfig { let hostname = Host::parse(&hostname)?; let server_log_level = - log::LevelFilter::from_str(&server_log_level).unwrap_or(log::LevelFilter::Error); + log::LevelFilter::from_str(&server_log_level).unwrap_or(log::LevelFilter::Info); let library_log_level = - log::LevelFilter::from_str(&library_log_level).unwrap_or(log::LevelFilter::Warn); + log::LevelFilter::from_str(&library_log_level).unwrap_or(log::LevelFilter::Info); let client_log_level = log::LevelFilter::from_str(&client_log_level).unwrap_or(log::LevelFilter::Info); @@ -165,3 +165,12 @@ pub struct Config { pub library_log_level: log::LevelFilter, pub client_log_level: log::LevelFilter, } + +impl Default for Config { + fn default() -> Config { + let friendly = FriendlyConfig::default(); + + // We know the default config passes into_config without error: + friendly.into_config().unwrap() + } +} diff --git a/contrib/chorus.toml b/contrib/chorus.toml index 9a5c74d..58aeba3 100644 --- a/contrib/chorus.toml +++ b/contrib/chorus.toml @@ -67,16 +67,16 @@ certchain_pem_path = "/opt/chorus/etc/tls/fullchain.pem" key_pem_path = "/opt/chorus/etc/tls/privkey.pem" -# This is an optional name for your relay, displayed in the NIP-11 response. +# This is a name for your relay, displayed in the NIP-11 response. # -# Default is not set +# Default is "Chorus Default" # # name = "Chorus Default" -# This is an optional description for your relay, displayed in the NIP-11 response. +# This is a description for your relay, displayed in the NIP-11 response. # -# Default is not set +# Default is "A default config of the Chorus relay" # # description = "A default config of the Chorus relay" @@ -203,15 +203,15 @@ server_log_level = "Info" # # Possible values are: Trace, Debug, Info, Warn, Error # -# Default is Warn +# Default is Info # -library_log_level = "Warn" +library_log_level = "Info" # How verbose to log issues with client requests # # Possible values are: Trace, Debug, Info, Warn, Error # -# Default is Error +# Default is Info # -client_log_level = "Error" +client_log_level = "Info" diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 7e0679c..a40a7ad 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -10,7 +10,7 @@ The config file must be in TOML format. See the [TOML documentation](https://git This is the directory where chorus stores data. -Default is "/tmp". +Default is "/opt/chorus/var/chorus". If deployed according to [docs/DEPLOYING.md](docs/DEPLOYING.md), is "/opt/chorus/var/chorus". @@ -34,6 +34,8 @@ Default is 443. This is the DNS hostname of your relay. This is used for verifying AUTH events, which specify your relay host name. +Default is localhost + ### use_tls If true, chorus will handle TLS, running over HTTPS. If false, chorus run over HTTP. @@ -41,17 +43,18 @@ If true, chorus will handle TLS, running over HTTPS. If false, chorus run over If you are proxying via nginx, normally you will set this to false and allow nginx to handle TLS. +Default is true + ### certchain_pem_path This is the path to your TLS certificate chain file. If `use_tls` is false, this value is irrelevant. -Default is "./tls/fullchain.pem". +Default is "/opt/chorus/etc/tls/fullchain.pem" If deployed according to [docs/DEPLOYING.md](docs/DEPLOYING.md) using the direct method, -this is set to "/opt/chorus/etc/tls/fullchain.pem" and the systemd service copies letsencrypt -TLS certificates into this position on start. +systemd service copies letsencrypt TLS certificates into this position on start. ### key_pem_path @@ -59,23 +62,22 @@ This is the path to yoru TLS private key file. If `use_tls` is false, this value is irrelevant. -Default is "./tls/privkey.pem". +Default is "/opt/chorus/etc/tls/privkey.pem" If deployed according to [docs/DEPLOYING.md](docs/DEPLOYING.md) using the direct method, -this is set to "/opt/chorus/etc/tls/privkey.pem" and the systemd service copies letsencrypt -TLS certificates into this position on start. +systemd service copies letsencrypt TLS certificates into this position on start. ### name This is an optional name for your relay, displayed in the NIP-11 response. -Default is None. +Default is "Chorus Default" ### description This is an optional description for your relay, displayed in the NIP-11 response. -Default is None. +Default is "A default config of the Chorus relay". ### contact @@ -177,7 +179,7 @@ How verbose to log library issues and other general issues Possible values are: Trace, Debug, Info, Warn, Error -Default is Warn +Default is Info ### client_log_level @@ -185,5 +187,5 @@ How verbose to log issues with client requests Possible values are: Trace, Debug, Info, Warn, Error -Default is Error +Default is Info