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:
Greg Heartsfield 2021-12-29 22:13:02 -06:00
parent 2e2e01203b
commit d730bf0c59
6 changed files with 249 additions and 19 deletions

134
Cargo.lock generated
View File

@ -22,6 +22,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -57,7 +63,7 @@ version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1" checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1"
dependencies = [ dependencies = [
"serde", "serde 1.0.131",
] ]
[[package]] [[package]]
@ -108,6 +114,22 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "config"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369"
dependencies = [
"lazy_static",
"nom",
"rust-ini",
"serde 1.0.131",
"serde-hjson",
"serde_json",
"toml",
"yaml-rust",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.1" version = "0.2.1"
@ -365,6 +387,25 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexical-core"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if",
"ryu",
"static_assertions",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.111" version = "0.2.111"
@ -381,6 +422,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.5" version = "0.4.5"
@ -433,19 +480,32 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"lexical-core",
"memchr",
"version_check",
]
[[package]] [[package]]
name = "nostr-rs-relay" name = "nostr-rs-relay"
version = "0.1.6" version = "0.1.6"
dependencies = [ dependencies = [
"bitcoin_hashes", "bitcoin_hashes",
"config",
"env_logger", "env_logger",
"futures", "futures",
"futures-util", "futures-util",
"hex", "hex",
"lazy_static",
"log", "log",
"rusqlite", "rusqlite",
"secp256k1", "secp256k1",
"serde", "serde 1.0.131",
"serde_json", "serde_json",
"thiserror", "thiserror",
"tokio", "tokio",
@ -463,6 +523,24 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
dependencies = [
"num-traits 0.2.14",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg 1.0.1",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.13.0" version = "1.13.0"
@ -754,6 +832,12 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "rust-ini"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.7" version = "1.0.7"
@ -775,7 +859,7 @@ dependencies = [
"bitcoin_hashes", "bitcoin_hashes",
"rand 0.6.5", "rand 0.6.5",
"secp256k1-sys", "secp256k1-sys",
"serde", "serde 1.0.131",
] ]
[[package]] [[package]]
@ -787,6 +871,12 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "serde"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.131" version = "1.0.131"
@ -796,6 +886,18 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-hjson"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8"
dependencies = [
"lazy_static",
"num-traits 0.1.43",
"regex",
"serde 0.8.23",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.131" version = "1.0.131"
@ -815,7 +917,7 @@ checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde 1.0.131",
] ]
[[package]] [[package]]
@ -852,6 +954,12 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.82" version = "1.0.82"
@ -950,6 +1058,15 @@ dependencies = [
"tungstenite", "tungstenite",
] ]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde 1.0.131",
]
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.16.0" version = "0.16.0"
@ -1071,3 +1188,12 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View File

@ -13,10 +13,11 @@ tokio-tungstenite = "^0.16"
tungstenite = "^0.16" tungstenite = "^0.16"
thiserror = "^1" thiserror = "^1"
uuid = { version = "^0.8", features = ["v4"] } uuid = { version = "^0.8", features = ["v4"] }
config = { version = "0.11", features = ["toml"] }
bitcoin_hashes = { version = "^0.9", features = ["serde"] } bitcoin_hashes = { version = "^0.9", features = ["serde"] }
secp256k1 = { version = "^0.20", features = ["rand", "rand-std", "serde", "bitcoin_hashes"] } secp256k1 = { version = "^0.20", features = ["rand", "rand-std", "serde", "bitcoin_hashes"] }
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0" serde_json = "^1.0"
hex = "^0.4" hex = "^0.4"
rusqlite = "^0.26" rusqlite = "^0.26"
lazy_static = "^1.4"

81
src/config.rs Normal file
View File

@ -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)
},
}
}
}

View File

@ -34,6 +34,8 @@ pub enum Error {
CommandUnknownError, CommandUnknownError,
#[error("SQL error")] #[error("SQL error")]
SqlError(rusqlite::Error), SqlError(rusqlite::Error),
#[error("Config error")]
ConfigError(config::ConfigError),
} }
impl From<rusqlite::Error> for Error { impl From<rusqlite::Error> for Error {
@ -56,3 +58,10 @@ impl From<WsError> for Error {
Error::WebsocketError(r) Error::WebsocketError(r)
} }
} }
impl From<config::ConfigError> for Error {
/// Wrap Config error
fn from(r: config::ConfigError) -> Self {
Error::ConfigError(r)
}
}

View File

@ -1,4 +1,5 @@
pub mod close; pub mod close;
pub mod config;
pub mod conn; pub mod conn;
pub mod db; pub mod db;
pub mod error; pub mod error;

View File

@ -1,8 +1,10 @@
//! Server process //! Server process
use futures::SinkExt; use futures::SinkExt;
use futures::StreamExt; use futures::StreamExt;
use lazy_static::lazy_static;
use log::*; use log::*;
use nostr_rs_relay::close::Close; use nostr_rs_relay::close::Close;
use nostr_rs_relay::config;
use nostr_rs_relay::conn; use nostr_rs_relay::conn;
use nostr_rs_relay::db; use nostr_rs_relay::db;
use nostr_rs_relay::error::{Error, Result}; 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::NostrMessage::*;
use nostr_rs_relay::protostream::NostrResponse::*; use nostr_rs_relay::protostream::NostrResponse::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::sync::RwLock;
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use tokio::runtime::Builder; use tokio::runtime::Builder;
use tokio::sync::broadcast; use tokio::sync::broadcast;
@ -19,14 +21,24 @@ use tokio::sync::broadcast::{Receiver, Sender};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use tungstenite::protocol::WebSocketConfig; 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. /// Start running a Nostr relay server.
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
// setup logger and environment // setup logger
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let addr = env::args() {
.nth(1) let mut settings = SETTINGS.write().unwrap();
.unwrap_or_else(|| "0.0.0.0:8080".to_string()); // 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 // configure tokio runtime
let rt = Builder::new_multi_thread() let rt = Builder::new_multi_thread()
.enable_all() .enable_all()
@ -35,16 +47,17 @@ fn main() -> Result<(), Error> {
.unwrap(); .unwrap();
// start tokio // start tokio
rt.block_on(async { rt.block_on(async {
let settings = SETTINGS.read().unwrap();
let listener = TcpListener::bind(&addr).await.expect("Failed to bind"); let listener = TcpListener::bind(&addr).await.expect("Failed to bind");
info!("listening on: {}", addr); info!("listening on: {}", addr);
// all client-submitted valid events are broadcast to every // all client-submitted valid events are broadcast to every
// other client on this channel. This should be large enough // other client on this channel. This should be large enough
// to accomodate slower readers (messages are dropped if // to accomodate slower readers (messages are dropped if
// clients can not keep up). // 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 // validated events that need to be persisted are sent to the
// database on via this channel. // 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 // start the database writer thread. Give it a channel for
// writing events, and for publishing events that have been // writing events, and for publishing events that have been
// written (to all connected clients). // written (to all connected clients).
@ -94,13 +107,12 @@ async fn nostr_server(
) { ) {
// get a broadcast channel for clients to communicate on // get a broadcast channel for clients to communicate on
let mut bcast_rx = broadcast.subscribe(); let mut bcast_rx = broadcast.subscribe();
// websocket configuration / limits let mut config = WebSocketConfig::default();
let config = WebSocketConfig { {
max_send_queue: None, let settings = SETTINGS.read().unwrap();
max_message_size: Some(2 << 19), // 512K config.max_message_size = settings.limits.max_ws_message_bytes;
max_frame_size: Some(2 << 19), // 512k config.max_frame_size = settings.limits.max_ws_frame_bytes;
accept_unmasked_frames: false, // follow the spec }
};
// upgrade the TCP connection to WebSocket // upgrade the TCP connection to WebSocket
let conn = tokio_tungstenite::accept_async_with_config(stream, Some(config)).await; let conn = tokio_tungstenite::accept_async_with_config(stream, Some(config)).await;
let ws_stream = conn.expect("websocket handshake error"); let ws_stream = conn.expect("websocket handshake error");