Reload config on HUP signal; change and synchronize default config

This commit is contained in:
Mike Dilger 2024-03-16 12:57:53 +13:00
parent a701da6dda
commit 54bb5450f6
7 changed files with 83 additions and 53 deletions

View File

@ -6,9 +6,10 @@ use std::sync::atomic::AtomicUsize;
use std::sync::OnceLock; use std::sync::OnceLock;
use tokio::sync::broadcast::Sender as BroadcastSender; use tokio::sync::broadcast::Sender as BroadcastSender;
use tokio::sync::watch::Sender as WatchSender; use tokio::sync::watch::Sender as WatchSender;
use tokio::sync::RwLock;
pub struct Globals { pub struct Globals {
pub config: OnceLock<Config>, pub config: RwLock<Config>,
pub store: OnceLock<Store>, pub store: OnceLock<Store>,
pub http_server: Http, pub http_server: Http,
pub rid: OnceLock<String>, pub rid: OnceLock<String>,
@ -33,7 +34,7 @@ lazy_static! {
let (shutting_down, _) = tokio::sync::watch::channel(false); let (shutting_down, _) = tokio::sync::watch::channel(false);
Globals { Globals {
config: OnceLock::new(), config: RwLock::new(Default::default()),
store: OnceLock::new(), store: OnceLock::new(),
http_server, http_server,
rid: OnceLock::new(), rid: OnceLock::new(),

View File

@ -45,7 +45,7 @@ async fn main() -> Result<(), Error> {
let config_path = args.next().unwrap(); let config_path = args.next().unwrap();
// Read config file // 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(); let mut contents = String::new();
file.read_to_string(&mut contents)?; file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&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); log::info!(target: "Server", "Running on {}:{}", config.ip_address, config.port);
// Store config into GLOBALS // Store config into GLOBALS
let _ = GLOBALS.config.set(config); *GLOBALS.config.write().await = config;
let mut interrupt_signal = signal(SignalKind::interrupt())?; let mut interrupt_signal = signal(SignalKind::interrupt())?;
let mut quit_signal = signal(SignalKind::quit())?; let mut quit_signal = signal(SignalKind::quit())?;
let mut terminate_signal = signal(SignalKind::terminate())?; let mut terminate_signal = signal(SignalKind::terminate())?;
let mut hup_signal = signal(SignalKind::hangup())?;
loop { loop {
tokio::select! { tokio::select! {
@ -102,6 +103,20 @@ async fn main() -> Result<(), Error> {
break; 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 // Accepts network connections and spawn a task to serve each one
v = listener.accept() => { v = listener.accept() => {
let (tcp_stream, peer_addr) = v?; let (tcp_stream, peer_addr) = v?;

View File

@ -82,7 +82,7 @@ impl WebSocketService {
if let Err(e) = self.req_inner(&subid, filters).await { if let Err(e) = self.req_inner(&subid, filters).await {
let reply = match e.inner { let reply = match e.inner {
ChorusError::TooManySubscriptions => { ChorusError::TooManySubscriptions => {
let max_subscriptions = GLOBALS.config.get().unwrap().max_subscriptions; let max_subscriptions = GLOBALS.config.read().await.max_subscriptions;
NostrReply::Closed( NostrReply::Closed(
&subid, &subid,
NostrReplyPrefix::Blocked, NostrReplyPrefix::Blocked,
@ -105,7 +105,7 @@ impl WebSocketService {
} }
async fn req_inner(&mut self, subid: &String, filters: Vec<OwnedFilter>) -> Result<(), Error> { async fn req_inner(&mut self, subid: &String, filters: Vec<OwnedFilter>) -> 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 { if self.subscriptions.len() >= max_subscriptions {
return Err(ChorusError::TooManySubscriptions.into()); return Err(ChorusError::TooManySubscriptions.into());
} }
@ -233,8 +233,7 @@ impl WebSocketService {
let event_flags = event_flags(&event, &user); 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) // Verify the event is valid (id is hash, signature is valid)
if let Err(e) = event.verify() { if let Err(e) = event.verify() {
return Err(ChorusError::EventIsInvalid(format!("{}", e)).into()); return Err(ChorusError::EventIsInvalid(format!("{}", e)).into());
@ -345,7 +344,7 @@ impl WebSocketService {
}; };
if let Some(h) = url.host() { if let Some(h) = url.host() {
let theirhost = h.to_owned(); let theirhost = h.to_owned();
if theirhost == GLOBALS.config.get().unwrap().hostname { if theirhost == GLOBALS.config.read().await.hostname {
relay_ok = true; relay_ok = true;
} }
} }
@ -402,7 +401,7 @@ async fn screen_incoming_event(
} }
// Accept if an open relay // Accept if an open relay
if GLOBALS.config.get().unwrap().open_relay { if GLOBALS.config.read().await.open_relay {
return Ok(true); return Ok(true);
} }
@ -412,20 +411,20 @@ async fn screen_incoming_event(
} }
// Accept relay lists from anybody // 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); return Ok(true);
} }
// Allow if event kind ephemeral // 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); return Ok(true);
} }
// If the author is one of our users, always accept it // If the author is one of our users, always accept it
if GLOBALS if GLOBALS
.config .config
.get() .read()
.unwrap() .await
.user_keys .user_keys
.contains(&event.pubkey()) .contains(&event.pubkey())
{ {
@ -436,7 +435,7 @@ async fn screen_incoming_event(
for mut tag in event.tags()?.iter() { for mut tag in event.tags()?.iter() {
if tag.next() == Some(b"p") { if tag.next() == Some(b"p") {
if let Some(value) = tag.next() { 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() { if value == ukhex.as_bytes() {
return Ok(true); return Ok(true);
} }
@ -460,17 +459,17 @@ pub fn screen_outgoing_event(
} }
// Allow if an open relay // Allow if an open relay
if GLOBALS.config.get().unwrap().open_relay { if GLOBALS.config.blocking_read().open_relay {
return true; return true;
} }
// Allow Relay Lists // 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; return true;
} }
// Allow if event kind ephemeral // 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; return true;
} }
@ -491,7 +490,7 @@ pub fn screen_outgoing_event(
pub async fn authorized_user(user: &Option<Pubkey>) -> bool { pub async fn authorized_user(user: &Option<Pubkey>) -> bool {
match user { match user {
None => false, 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<Pubkey>) -> EventFlags { pub fn event_flags(event: &Event<'_>, user: &Option<Pubkey>) -> EventFlags {
let author_is_an_authorized_user = GLOBALS let author_is_an_authorized_user = GLOBALS
.config .config
.get() .blocking_read()
.unwrap()
.user_keys .user_keys
.contains(&event.pubkey()); .contains(&event.pubkey());
@ -529,7 +527,12 @@ pub fn event_flags(event: &Event<'_>, user: &Option<Pubkey>) -> 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; tags_an_authorized_user = true;
} }
} }

View File

@ -18,7 +18,7 @@ pub async fn serve_http(peer: SocketAddr, request: Request<Body>) -> Result<Resp
pub async fn serve_nip11(peer: SocketAddr) -> Result<Response<Body>, Error> { pub async fn serve_nip11(peer: SocketAddr) -> Result<Response<Body>, Error> {
log::debug!(target: "Client", "{}: sent NIP-11", peer); log::debug!(target: "Client", "{}: sent NIP-11", peer);
let rid = { let rid = {
let config = GLOBALS.config.get().unwrap(); let config = &*GLOBALS.config.read().await;
GLOBALS.rid.get_or_init(|| build_rid(config)) GLOBALS.rid.get_or_init(|| build_rid(config))
}; };

View File

@ -35,15 +35,15 @@ pub struct FriendlyConfig {
impl Default for FriendlyConfig { impl Default for FriendlyConfig {
fn default() -> FriendlyConfig { fn default() -> FriendlyConfig {
FriendlyConfig { FriendlyConfig {
data_directory: "/tmp".to_string(), data_directory: "/opt/chorus/var/chorus".to_string(),
ip_address: "127.0.0.1".to_string(), ip_address: "127.0.0.1".to_string(),
port: 80, port: 443,
hostname: "localhost".to_string(), hostname: "localhost".to_string(),
use_tls: false, use_tls: true,
certchain_pem_path: "./tls/fullchain.pem".to_string(), certchain_pem_path: "/opt/chorus/etc/tls/fullchain.pem".to_string(),
key_pem_path: "./tls/privkey.pem".to_string(), key_pem_path: "/opt/chorus/etc/tls/privkey.pem".to_string(),
name: None, name: Some("Chorus Default".to_string()),
description: None, description: Some("A default config of the Chorus relay".to_string()),
contact: None, contact: None,
public_key_hex: None, public_key_hex: None,
open_relay: false, open_relay: false,
@ -56,8 +56,8 @@ impl Default for FriendlyConfig {
serve_ephemeral: true, serve_ephemeral: true,
serve_relay_lists: true, serve_relay_lists: true,
server_log_level: "Info".to_string(), server_log_level: "Info".to_string(),
library_log_level: "Warn".to_string(), library_log_level: "Info".to_string(),
client_log_level: "Error".to_string(), client_log_level: "Info".to_string(),
} }
} }
} }
@ -103,9 +103,9 @@ impl FriendlyConfig {
let hostname = Host::parse(&hostname)?; let hostname = Host::parse(&hostname)?;
let server_log_level = 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 = 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 = let client_log_level =
log::LevelFilter::from_str(&client_log_level).unwrap_or(log::LevelFilter::Info); 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 library_log_level: log::LevelFilter,
pub client_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()
}
}

View File

@ -67,16 +67,16 @@ certchain_pem_path = "/opt/chorus/etc/tls/fullchain.pem"
key_pem_path = "/opt/chorus/etc/tls/privkey.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" # 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" # description = "A default config of the Chorus relay"
@ -203,15 +203,15 @@ server_log_level = "Info"
# #
# Possible values are: Trace, Debug, Info, Warn, Error # 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 # How verbose to log issues with client requests
# #
# Possible values are: Trace, Debug, Info, Warn, Error # Possible values are: Trace, Debug, Info, Warn, Error
# #
# Default is Error # Default is Info
# #
client_log_level = "Error" client_log_level = "Info"

View File

@ -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. 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". 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 This is the DNS hostname of your relay. This is used for verifying AUTH events, which specify
your relay host name. your relay host name.
Default is localhost
### use_tls ### use_tls
If true, chorus will handle TLS, running over HTTPS. If false, chorus run over HTTP. 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 If you are proxying via nginx, normally you will set this to false and allow nginx to handle
TLS. TLS.
Default is true
### certchain_pem_path ### certchain_pem_path
This is the path to your TLS certificate chain file. This is the path to your TLS certificate chain file.
If `use_tls` is false, this value is irrelevant. 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, 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 systemd service copies letsencrypt TLS certificates into this position on start.
TLS certificates into this position on start.
### key_pem_path ### 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. 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, 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 systemd service copies letsencrypt TLS certificates into this position on start.
TLS certificates into this position on start.
### name ### name
This is an optional name for your relay, displayed in the NIP-11 response. This is an optional name for your relay, displayed in the NIP-11 response.
Default is None. Default is "Chorus Default"
### description ### description
This is an optional description for your relay, displayed in the NIP-11 response. 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 ### contact
@ -177,7 +179,7 @@ How verbose to log library issues and other general issues
Possible values are: Trace, Debug, Info, Warn, Error Possible values are: Trace, Debug, Info, Warn, Error
Default is Warn Default is Info
### client_log_level ### client_log_level
@ -185,5 +187,5 @@ How verbose to log issues with client requests
Possible values are: Trace, Debug, Info, Warn, Error Possible values are: Trace, Debug, Info, Warn, Error
Default is Error Default is Info