commit d730bf0c59d22aa52e44bb722dccd23167d5b5b2
parent 2e2e01203b7bfd77c3998a69e538772aeced1ed7
Author: Greg Heartsfield <scsibug@imap.cc>
Date: Wed, 29 Dec 2021 22:13:02 -0600
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
Diffstat:
6 files changed, 249 insertions(+), 19 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -23,6 +23,12 @@ dependencies = [
]
[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -57,7 +63,7 @@ version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1"
dependencies = [
- "serde",
+ "serde 1.0.131",
]
[[package]]
@@ -109,6 +115,22 @@ dependencies = [
]
[[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]]
name = "cpufeatures"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -366,6 +388,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "libc"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -382,6 +423,12 @@ dependencies = [
]
[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
name = "lock_api"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -434,18 +481,31 @@ dependencies = [
]
[[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]]
name = "nostr-rs-relay"
version = "0.1.6"
dependencies = [
"bitcoin_hashes",
+ "config",
"env_logger",
"futures",
"futures-util",
"hex",
+ "lazy_static",
"log",
"rusqlite",
"secp256k1",
- "serde",
+ "serde 1.0.131",
"serde_json",
"thiserror",
"tokio",
@@ -464,6 +524,24 @@ dependencies = [
]
[[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]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -755,6 +833,12 @@ dependencies = [
]
[[package]]
+name = "rust-ini"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
+
+[[package]]
name = "ryu"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -775,7 +859,7 @@ dependencies = [
"bitcoin_hashes",
"rand 0.6.5",
"secp256k1-sys",
- "serde",
+ "serde 1.0.131",
]
[[package]]
@@ -789,6 +873,12 @@ dependencies = [
[[package]]
name = "serde"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
+
+[[package]]
+name = "serde"
version = "1.0.131"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1"
@@ -797,6 +887,18 @@ dependencies = [
]
[[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]]
name = "serde_derive"
version = "1.0.131"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -815,7 +917,7 @@ checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
dependencies = [
"itoa",
"ryu",
- "serde",
+ "serde 1.0.131",
]
[[package]]
@@ -853,6 +955,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
name = "syn"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -951,6 +1059,15 @@ dependencies = [
]
[[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]]
name = "tungstenite"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1071,3 +1188,12 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
+]
diff --git a/Cargo.toml b/Cargo.toml
@@ -13,10 +13,11 @@ tokio-tungstenite = "^0.16"
tungstenite = "^0.16"
thiserror = "^1"
uuid = { version = "^0.8", features = ["v4"] }
-
+config = { version = "0.11", features = ["toml"] }
bitcoin_hashes = { version = "^0.9", features = ["serde"] }
secp256k1 = { version = "^0.20", features = ["rand", "rand-std", "serde", "bitcoin_hashes"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
hex = "^0.4"
rusqlite = "^0.26"
+lazy_static = "^1.4"
diff --git a/src/config.rs b/src/config.rs
@@ -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)
+ },
+ }
+ }
+}
diff --git a/src/error.rs b/src/error.rs
@@ -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)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,4 +1,5 @@
pub mod close;
+pub mod config;
pub mod conn;
pub mod db;
pub mod error;
diff --git a/src/main.rs b/src/main.rs
@@ -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");