feat: add configuration through file
A file named `config.toml` can now be used to load the address, port, and some websocket configuration settings. Fixes https://todo.sr.ht/~gheartsfield/nostr-rs-relay/3
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[allow(unused)]
|
||||
pub struct Network {
|
||||
pub port: u16,
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[allow(unused)]
|
||||
pub struct Retention {
|
||||
// TODO: implement
|
||||
pub max_events: Option<usize>, // max events
|
||||
pub max_bytes: Option<usize>, // max size
|
||||
pub persist_days: Option<usize>, // oldest message
|
||||
pub whitelist_addresses: Vec<String>, // whitelisted addresses (never delete)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[allow(unused)]
|
||||
pub struct Limits {
|
||||
pub messages_per_sec: Option<usize>, // Artificially slow down event writing to limit disk consumption
|
||||
pub max_event_bytes: Option<usize>,
|
||||
pub max_ws_message_bytes: Option<usize>,
|
||||
pub max_ws_frame_bytes: Option<usize>,
|
||||
pub broadcast_buffer: usize, // events to buffer for subscribers (prevents slow readers from consuming memory)
|
||||
pub event_persist_buffer: usize, // events to buffer for database commits (block senders if database writes are too slow)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[allow(unused)]
|
||||
pub struct Settings {
|
||||
pub network: Network,
|
||||
pub limits: Limits,
|
||||
pub retention: Retention,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn new() -> Self {
|
||||
let d = Self::default();
|
||||
// attempt to construct settings with file
|
||||
Self::new_from_default(&d).unwrap_or(d)
|
||||
}
|
||||
|
||||
fn new_from_default(default: &Settings) -> Result<Self, config::ConfigError> {
|
||||
let config: config::Config = config::Config::new();
|
||||
let settings: Settings = config
|
||||
// use defaults
|
||||
.with_merged(config::Config::try_from(default).unwrap())?
|
||||
// override with file contents
|
||||
.with_merged(config::File::with_name("config"))?
|
||||
.try_into()?;
|
||||
Ok(settings)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings {
|
||||
network: Network {
|
||||
port: 8080,
|
||||
address: "0.0.0.0".to_owned(),
|
||||
},
|
||||
limits: Limits {
|
||||
messages_per_sec: None,
|
||||
max_event_bytes: Some(2 << 17), // 128K
|
||||
max_ws_message_bytes: Some(2 << 17), // 128K
|
||||
max_ws_frame_bytes: Some(2 << 17), // 128K
|
||||
broadcast_buffer: 4096,
|
||||
event_persist_buffer: 16,
|
||||
},
|
||||
retention: Retention {
|
||||
max_events: None, // max events
|
||||
max_bytes: None, // max size
|
||||
persist_days: None, // oldest message
|
||||
whitelist_addresses: vec![], // whitelisted addresses (never delete)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ pub enum Error {
|
||||
CommandUnknownError,
|
||||
#[error("SQL error")]
|
||||
SqlError(rusqlite::Error),
|
||||
#[error("Config error")]
|
||||
ConfigError(config::ConfigError),
|
||||
}
|
||||
|
||||
impl From<rusqlite::Error> for Error {
|
||||
@@ -56,3 +58,10 @@ impl From<WsError> for Error {
|
||||
Error::WebsocketError(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<config::ConfigError> for Error {
|
||||
/// Wrap Config error
|
||||
fn from(r: config::ConfigError) -> Self {
|
||||
Error::ConfigError(r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod close;
|
||||
pub mod config;
|
||||
pub mod conn;
|
||||
pub mod db;
|
||||
pub mod error;
|
||||
|
||||
+26
-14
@@ -1,8 +1,10 @@
|
||||
//! Server process
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
use log::*;
|
||||
use nostr_rs_relay::close::Close;
|
||||
use nostr_rs_relay::config;
|
||||
use nostr_rs_relay::conn;
|
||||
use nostr_rs_relay::db;
|
||||
use nostr_rs_relay::error::{Error, Result};
|
||||
@@ -11,7 +13,7 @@ use nostr_rs_relay::protostream;
|
||||
use nostr_rs_relay::protostream::NostrMessage::*;
|
||||
use nostr_rs_relay::protostream::NostrResponse::*;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::sync::RwLock;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::sync::broadcast;
|
||||
@@ -19,14 +21,24 @@ use tokio::sync::broadcast::{Receiver, Sender};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
use tungstenite::protocol::WebSocketConfig;
|
||||
// initialize a singleton default configuration
|
||||
lazy_static! {
|
||||
static ref SETTINGS: RwLock<config::Settings> = RwLock::new(config::Settings::default());
|
||||
}
|
||||
|
||||
/// Start running a Nostr relay server.
|
||||
fn main() -> Result<(), Error> {
|
||||
// setup logger and environment
|
||||
// setup logger
|
||||
let _ = env_logger::try_init();
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "0.0.0.0:8080".to_string());
|
||||
{
|
||||
let mut settings = SETTINGS.write().unwrap();
|
||||
// replace default settings with those read from config.toml
|
||||
let c = config::Settings::new();
|
||||
debug!("using settings: {:?}", c);
|
||||
*settings = c;
|
||||
}
|
||||
let config = SETTINGS.read().unwrap();
|
||||
let addr = format!("{}:{}", config.network.address.trim(), config.network.port);
|
||||
// configure tokio runtime
|
||||
let rt = Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
@@ -35,16 +47,17 @@ fn main() -> Result<(), Error> {
|
||||
.unwrap();
|
||||
// start tokio
|
||||
rt.block_on(async {
|
||||
let settings = SETTINGS.read().unwrap();
|
||||
let listener = TcpListener::bind(&addr).await.expect("Failed to bind");
|
||||
info!("listening on: {}", addr);
|
||||
// all client-submitted valid events are broadcast to every
|
||||
// other client on this channel. This should be large enough
|
||||
// to accomodate slower readers (messages are dropped if
|
||||
// clients can not keep up).
|
||||
let (bcast_tx, _) = broadcast::channel::<Event>(4096);
|
||||
let (bcast_tx, _) = broadcast::channel::<Event>(settings.limits.broadcast_buffer);
|
||||
// validated events that need to be persisted are sent to the
|
||||
// database on via this channel.
|
||||
let (event_tx, event_rx) = mpsc::channel::<Event>(16);
|
||||
let (event_tx, event_rx) = mpsc::channel::<Event>(settings.limits.event_persist_buffer);
|
||||
// start the database writer thread. Give it a channel for
|
||||
// writing events, and for publishing events that have been
|
||||
// written (to all connected clients).
|
||||
@@ -94,13 +107,12 @@ async fn nostr_server(
|
||||
) {
|
||||
// get a broadcast channel for clients to communicate on
|
||||
let mut bcast_rx = broadcast.subscribe();
|
||||
// websocket configuration / limits
|
||||
let config = WebSocketConfig {
|
||||
max_send_queue: None,
|
||||
max_message_size: Some(2 << 19), // 512K
|
||||
max_frame_size: Some(2 << 19), // 512k
|
||||
accept_unmasked_frames: false, // follow the spec
|
||||
};
|
||||
let mut config = WebSocketConfig::default();
|
||||
{
|
||||
let settings = SETTINGS.read().unwrap();
|
||||
config.max_message_size = settings.limits.max_ws_message_bytes;
|
||||
config.max_frame_size = settings.limits.max_ws_frame_bytes;
|
||||
}
|
||||
// upgrade the TCP connection to WebSocket
|
||||
let conn = tokio_tungstenite::accept_async_with_config(stream, Some(config)).await;
|
||||
let ws_stream = conn.expect("websocket handshake error");
|
||||
|
||||
Reference in New Issue
Block a user