Compare commits

..

11 Commits

Author SHA1 Message Date
Mike Dilger
61fcbcf257
2.0.1 2026-02-21 12:24:57 +13:00
Mike Dilger
4ff23fb2a6
update pocket, smaller error variants 2026-02-21 12:24:57 +13:00
Mike Dilger
1f2c33d532
Shrink Error type enum to 48 bytes 2026-02-21 12:18:50 +13:00
Mike Dilger
a1e52a2d07
cargo update 2026-02-21 11:53:26 +13:00
Mike Dilger
b68431bba0
Implement PR #1881, Allow multi-user AUTH 2025-08-01 21:41:27 +12:00
Mike Dilger
ce612a1fc5
FIX: test_blossom needed to add a user first 2025-05-27 09:15:20 +12:00
Mike Dilger
6f77156684
update contrib/chorus.toml for last commit 2025-04-22 12:40:03 +12:00
Mike Dilger
17f9687b4c
Add configs for: banner_url, privacy_policy, terms_of_service (the latter 2 locally served) 2025-04-22 12:23:46 +12:00
Mike Dilger
3f15894aaa
Files for testing chorus with relay-tester 2025-03-28 09:51:24 +13:00
Mike Dilger
2ca4b17b73
Doc fixes 2025-03-28 09:40:47 +13:00
Mike Dilger
e5251e0f57
chorus_init binary (just setup the database and exit) 2025-03-28 09:40:11 +13:00
21 changed files with 956 additions and 712 deletions

1282
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "chorus"
version = "2.0.0"
version = "2.0.1"
description = "A personal relay for nostr"
authors = ["Mike Dilger <mike@mikedilger.com>"]
license = "MIT"
@ -9,7 +9,7 @@ edition = "2021"
[dependencies]
base64 = "0.22"
bitcoin_hashes = { version = "0.16", features = [ "bitcoin-io" ] }
bitcoin_hashes = "0.19"
dashmap = "6"
env_logger = "0.11"
futures = "0.3"
@ -24,12 +24,12 @@ log = "0.4"
mime-sniffer = "0.1"
mime2ext = "0.1"
negentropy = "0.5"
pocket-types = { git = "https://github.com/mikedilger/pocket", branch = "master" }
pocket-db = { git = "https://github.com/mikedilger/pocket", branch = "master" }
pocket-types = { git = "https://github.com/mikedilger/pocket", ref = "43d35015f7caf1db48eb846a1d6916a5716048da" }
pocket-db = { git = "https://github.com/mikedilger/pocket", ref = "43d35015f7caf1db48eb846a1d6916a5716048da" }
parking_lot = "0.12"
rustls-pki-types = "1.11"
rustls-pemfile = "2.2"
secp256k1 = { version = "0.30", features = [ "hashes", "global-context" ] }
secp256k1 = { version = "0.31", features = [ "hashes", "global-context" ] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
speedy = "0.8"

View File

@ -97,6 +97,14 @@ key_pem_path = "/opt/chorus/etc/tls/privkey.pem"
# description = "A default config of the Chorus relay"
# This is a banner URL pointing to an image representing your relay, displayed in the NIP-11
# response.
#
# Default is not set
#
# banner_url =
# This is an icon URL pointing to an image representing your relay, displayed in the NIP-11
# response.
#
@ -105,6 +113,20 @@ key_pem_path = "/opt/chorus/etc/tls/privkey.pem"
# icon_url =
# This is an optional privacy policy as a blob of text (not a URL, not HTML).
#
# Default is not set
#
# privacy_policy = ""
# This is an optional terms of service document as a blob of text (not a URL, not HTML).
#
# Default is not set
#
# terms_of_service = ""
# This is an optional contact for your relay, displayed in the NIP-11 response.
#
# Default is not set

View File

@ -94,10 +94,22 @@ This is an optional description for your relay, displayed in the NIP-11 response
Default is "A default config of the Chorus relay".
### banner_url
This is an optional URL for an graphical banner representing your relay, displayed in the NIP-11 response.
### icon_url
This is an optional URL for an graphical icon representing your relay, displayed in the NIP-11 response.
### privacy_policy
This is an optional privacy policy as a blob of text (not a URL, not HTML).
### terms_of_service
This is an optional terms of service document as a blob of text (not a URL, not HTML).
### contact
This is an optional administrative contact for your relay, displayed in the NIP-11 response.

View File

@ -83,7 +83,8 @@ Go ahead and edit that file to your liking. In particular:
- Change the `ip_address` to your internet-accessible IP address (if you are running directly)
or to 127.0.0.1 with a local port like 8080 (if you are proxying behind nginx)
- Change the port if necessary
- Change the name, description, icon_url and contact (e.g. your email address) as desired
- Change the name, description, banner_url, icon_url, privacy_policy, terms_of_service and
contact (e.g. your email address) as desired
- Set your contact_public_key_hex (it is an option, so use `Some()`)
- Set hex keys of users for which this relay will act as a personal relay

View File

@ -10,10 +10,10 @@
2) Users and moderators are now dynamically configured in the database. Use `chorus_cmd` to
manage them from the command line:
* Adding a user: `chorus_cmd add_user <pubkey> 0`
* Adding a moderator: `chorus_cmd add_user <pubkey> 1`
* Removing a user or moderator: `chorus_cmd rm_user <pubkey>`
* Listing users and moderators: `chorus_cmd dump_users`
* Adding a user: `chorus_cmd <chorus.toml> add_user <pubkey> 0`
* Adding a moderator: `chorus_cmd <chorus.toml> add_user <pubkey> 1`
* Removing a user or moderator: `chorus_cmd <chorus.toml> rm_user <pubkey>`
* Listing users and moderators: `chorus_cmd <chorus.toml> dump_users`
3) Remove the following from your config file as these are no longer used:

View File

@ -10,6 +10,8 @@ certchain_pem_path = "tls/fullchain.pem"
key_pem_path = "tls/privkey.pem"
name = "Chorus Sample"
description = "A sample run of the Chorus relay"
privacy_policy = "This relay guarantees nothing. Privacy is your concern, not ours."
terms_of_service = "This relay guarantees nothing. Use at your own risk."
# icon_url =
open_relay = false
admin_hex_keys = [

View File

@ -10,7 +10,7 @@ fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus_moderate <config_path>");
panic!("USAGE: chorus_cmd <config_path> <command> [args...]");
}
let _ = args.next(); // ignore program name

27
src/bin/chorus_init.rs Normal file
View File

@ -0,0 +1,27 @@
use chorus::error::Error;
use chorus::globals::GLOBALS;
use std::env;
#[tokio::main]
async fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus <config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
let config = chorus::load_config(&config_path)?;
chorus::setup_logging(&config);
// Log host name
log::info!(target: "Server", "HOSTNAME = {}", config.hostname);
chorus::setup_store(&config)?;
let _ = GLOBALS.store.get().unwrap().sync();
Ok(())
}

View File

@ -19,7 +19,10 @@ pub struct FriendlyConfig {
pub key_pem_path: String,
pub name: Option<String>,
pub description: Option<String>,
pub banner_url: Option<String>,
pub icon_url: Option<String>,
pub privacy_policy: Option<String>,
pub terms_of_service: Option<String>,
pub contact: Option<String>,
#[serde(alias = "public_key_hex")]
pub contact_public_key_hex: Option<String>,
@ -60,7 +63,10 @@ impl Default for FriendlyConfig {
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()),
banner_url: None,
icon_url: None,
privacy_policy: None,
terms_of_service: None,
contact: None,
contact_public_key_hex: None,
open_relay: false,
@ -102,7 +108,10 @@ impl FriendlyConfig {
key_pem_path,
name,
description,
banner_url,
icon_url,
privacy_policy,
terms_of_service,
contact,
contact_public_key_hex,
open_relay,
@ -159,7 +168,10 @@ impl FriendlyConfig {
key_pem_path,
name,
description,
banner_url,
icon_url,
privacy_policy,
terms_of_service,
contact,
contact_public_key,
open_relay,
@ -201,7 +213,10 @@ pub struct Config {
pub key_pem_path: String,
pub name: Option<String>,
pub description: Option<String>,
pub banner_url: Option<String>,
pub icon_url: Option<String>,
pub privacy_policy: Option<String>,
pub terms_of_service: Option<String>,
pub contact: Option<String>,
pub contact_public_key: Option<Pubkey>,
pub open_relay: bool,
@ -238,6 +253,8 @@ impl Default for Config {
}
impl Config {
/// Get the URI for our server matching the inner Uri, overridden with either
/// our base_url parts or our hostname/port.
pub fn uri_parts(&self, inner: Uri, http: bool) -> Result<http::uri::Parts, Error> {
let mut uri_parts = inner.into_parts();

View File

@ -45,7 +45,7 @@ pub enum ChorusError {
BannedUser,
// Base64 Decode Error
Base64Decode(base64::DecodeError),
Base64Decode(base64::DecodeError), // 24b
// Blocked IP
BlockedIp,
@ -54,16 +54,16 @@ pub enum ChorusError {
BlossomAuthFailure(String),
// Channel Recv
ChannelRecv(tokio::sync::broadcast::error::RecvError),
ChannelRecv(tokio::sync::broadcast::error::RecvError), // 24b
// Channel Send
ChannelSend(tokio::sync::broadcast::error::SendError<u64>),
ChannelSend(tokio::sync::broadcast::error::SendError<u64>), // 16b
// Config
Config(toml::de::Error),
Config(Box<toml::de::Error>), // 16b
// Crypto
Crypto(secp256k1::Error),
Crypto(secp256k1::Error), // 16b
// Closing on error(s)
ErrorClose,
@ -72,16 +72,16 @@ pub enum ChorusError {
EventIsInvalid(String),
// From hex
FromHex(hex::FromHexError),
FromHex(hex::FromHexError), // 24b
// From UTF8
FromUtf8(std::string::FromUtf8Error),
FromUtf8(Box<std::string::FromUtf8Error>),
// General
General(String),
// Http
Http(hyper::http::Error),
Http(Box<hyper::http::Error>),
// Hyper
Hyper(hyper::Error),
@ -126,7 +126,7 @@ pub enum ChorusError {
PocketDb(pocket_db::Error),
// Pocket Db Heed Error
PocketDbHeed(pocket_db::heed::Error),
PocketDbHeed(Box<pocket_db::heed::Error>),
// Pocket Types Error
PocketType(pocket_types::Error),
@ -141,7 +141,7 @@ pub enum ChorusError {
Restricted,
// Rustls
Rustls(tokio_rustls::rustls::Error),
Rustls(Box<tokio_rustls::rustls::Error>),
// Filter is underspecified
Scraper,
@ -165,7 +165,7 @@ pub enum ChorusError {
TooManySubscriptions,
// Tungstenite
Tungstenite(hyper_tungstenite::tungstenite::error::Error),
Tungstenite(Box<hyper_tungstenite::tungstenite::error::Error>),
// URL Parse
UrlParse(url::ParseError),
@ -382,7 +382,7 @@ impl From<toml::de::Error> for Error {
#[track_caller]
fn from(err: toml::de::Error) -> Self {
Error {
inner: ChorusError::Config(err),
inner: ChorusError::Config(Box::new(err)),
location: std::panic::Location::caller(),
}
}
@ -402,7 +402,7 @@ impl From<hyper::http::Error> for Error {
#[track_caller]
fn from(err: hyper::http::Error) -> Self {
Error {
inner: ChorusError::Http(err),
inner: ChorusError::Http(Box::new(err)),
location: std::panic::Location::caller(),
}
}
@ -472,7 +472,7 @@ impl From<pocket_db::heed::Error> for Error {
#[track_caller]
fn from(err: pocket_db::heed::Error) -> Self {
Error {
inner: ChorusError::PocketDbHeed(err),
inner: ChorusError::PocketDbHeed(Box::new(err)),
location: std::panic::Location::caller(),
}
}
@ -492,7 +492,7 @@ impl From<tokio_rustls::rustls::Error> for Error {
#[track_caller]
fn from(err: tokio_rustls::rustls::Error) -> Self {
Error {
inner: ChorusError::Rustls(err),
inner: ChorusError::Rustls(Box::new(err)),
location: std::panic::Location::caller(),
}
}
@ -502,7 +502,7 @@ impl From<hyper_tungstenite::tungstenite::error::Error> for Error {
#[track_caller]
fn from(err: hyper_tungstenite::tungstenite::error::Error) -> Self {
Error {
inner: ChorusError::Tungstenite(err),
inner: ChorusError::Tungstenite(Box::new(err)),
location: std::panic::Location::caller(),
}
}
@ -572,7 +572,7 @@ impl From<std::string::FromUtf8Error> for Error {
#[track_caller]
fn from(err: std::string::FromUtf8Error) -> Self {
Error {
inner: ChorusError::FromUtf8(err),
inner: ChorusError::FromUtf8(Box::new(err)),
location: std::panic::Location::caller(),
}
}

View File

@ -180,9 +180,9 @@ async fn handle_http_request(
}
let mut web_socket_config = WebSocketConfig::default();
web_socket_config.max_write_buffer_size = 1024 * 1024; // 1 MB
web_socket_config.max_write_buffer_size = 1024 * 1024; // 1 MB
web_socket_config.max_message_size = Some(1024 * 1024); // 1 MB
web_socket_config.max_frame_size = Some(1024 * 1024); // 1 MB
web_socket_config.max_frame_size = Some(1024 * 1024); // 1 MB
let (mut response, websocket) =
hyper_tungstenite::upgrade(&mut request, Some(web_socket_config))?;
@ -220,7 +220,7 @@ async fn websocket_thread(peer: HashedPeer, websocket: HyperWebsocket, origin: S
last_message: Instant::now(),
burst_tokens: GLOBALS.config.read().throttling_burst,
challenge: TextNonce::new().into_string(),
user: None,
authed_as: Vec::new(),
error_punishment: 0.0,
replied: false,
negentropy_sub: None,
@ -254,21 +254,30 @@ async fn websocket_thread(peer: HashedPeer, websocket: HyperWebsocket, origin: S
// Handle the websocket
if let Err(e) = ws_service.handle_websocket_stream().await {
match e.inner {
ChorusError::Tungstenite(tungstenite::error::Error::Protocol(
tungstenite::error::ProtocolError::ResetWithoutClosingHandshake,
)) => {
// So they disconnected ungracefully.
// No big deal, still SessionExit::Ok
msg = "Reset";
}
ChorusError::Tungstenite(tungstenite::error::Error::Io(ref ioerror)) => {
match ioerror.kind() {
std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::UnexpectedEof => {
// no biggie.
ChorusError::Tungstenite(ref t) => {
match *t.as_ref() {
tungstenite::error::Error::Protocol(
tungstenite::error::ProtocolError::ResetWithoutClosingHandshake,
) => {
// So they disconnected ungracefully.
// No big deal, still SessionExit::Ok
msg = "Reset";
}
tungstenite::error::Error::Io(ref ioerror) => {
match ioerror.kind() {
std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::UnexpectedEof => {
// no biggie.
msg = "Reset";
}
_ => {
log::error!(target: "Client", "{}: {}", peer, e);
session_exit = SessionExit::ErrorExit;
msg = "Error Exited";
}
}
}
_ => {
log::error!(target: "Client", "{}: {}", peer, e);
session_exit = SessionExit::ErrorExit;
@ -356,7 +365,7 @@ struct WebSocketService {
pub last_message: Instant,
pub burst_tokens: usize,
pub challenge: String,
pub user: Option<Pubkey>,
pub authed_as: Vec<Pubkey>,
pub error_punishment: f32,
pub replied: bool,
pub negentropy_sub: Option<String>,
@ -473,8 +482,8 @@ impl WebSocketService {
.unwrap()
.get_event_by_offset(new_event_offset)?;
let event_flags = nostr::event_flags(event, &self.user);
let authorized_user = self.user.map(is_authorized_user).unwrap_or(false);
let event_flags = nostr::event_flags(event, self.authed_as.as_slice());
let authorized_user = self.authed_as.iter().any(|u| is_authorized_user(*u));
'subs: for (subid, filters) in self.subscriptions.iter() {
for filter in filters.iter() {

View File

@ -124,10 +124,9 @@ impl WebSocketService {
return Err(ChorusError::TooManySubscriptions.into());
}
let user = self.user;
let authorized_user = self.user.map(crate::is_authorized_user).unwrap_or(false);
let authorized_user = self.authed_as.iter().any(|u| crate::is_authorized_user(*u));
if user.is_none() {
if self.authed_as.is_empty() {
for filter in filters.iter() {
// If any DM kinds were requested, complain.
// But if NO kinds were requested, we will just silently not return DMs (elsewhere)
@ -163,7 +162,7 @@ impl WebSocketService {
for filter in filters.iter() {
let screen = |event: &Event| -> ScreenResult {
let event_flags = event_flags(event, &user);
let event_flags = event_flags(event, self.authed_as.as_slice());
screen_outgoing_event(event, &event_flags, authorized_user)
};
let (filter_events, was_redacted) = {
@ -317,13 +316,12 @@ impl WebSocketService {
}
async fn event_inner(&mut self) -> Result<(), Error> {
let user = self.user;
let authorized_user = self.user.map(crate::is_authorized_user).unwrap_or(false);
let authorized_user = self.authed_as.iter().any(|u| crate::is_authorized_user(*u));
// Delineate the event back out of the session buffer
let event = unsafe { Event::delineate(&self.buffer)? };
let event_flags = event_flags(event, &user);
let event_flags = event_flags(event, self.authed_as.as_slice());
if GLOBALS.config.read().verify_events {
// Verify the event is valid (id is hash, signature is valid)
@ -347,10 +345,10 @@ impl WebSocketService {
// Screen the event to see if we are willing to accept it
if !screen_incoming_event(event, event_flags, authorized_user).await? {
if self.user.is_some() {
return Err(ChorusError::Restricted.into());
} else {
if self.authed_as.is_empty() {
return Err(ChorusError::AuthRequired.into());
} else {
return Err(ChorusError::Restricted.into());
}
}
@ -463,7 +461,7 @@ impl WebSocketService {
}
// They are now authenticated
self.user = Some(event.pubkey());
self.authed_as.push(event.pubkey());
Ok(())
}
@ -542,13 +540,12 @@ impl WebSocketService {
return Ok(());
}
let user = self.user;
let authorized_user = self.user.map(crate::is_authorized_user).unwrap_or(false);
let authorized_user = self.authed_as.iter().any(|u| crate::is_authorized_user(*u));
// Find all matching events
let mut events: Vec<&Event> = Vec::new();
let screen = |event: &Event| -> ScreenResult {
let event_flags = event_flags(event, &user);
let event_flags = event_flags(event, self.authed_as.as_slice());
screen_outgoing_event(event, &event_flags, authorized_user)
};
let (filter_events, _redacted) = {
@ -871,13 +868,10 @@ pub struct EventFlags {
pub tags_current_user: bool,
}
pub fn event_flags(event: &Event, user: &Option<Pubkey>) -> EventFlags {
pub fn event_flags(event: &Event, authed_as: &[Pubkey]) -> EventFlags {
let author_is_an_authorized_user = crate::is_authorized_user(event.pubkey());
let author_is_current_user = match user {
None => false,
Some(pk) => event.pubkey() == *pk,
};
let author_is_current_user = authed_as.iter().any(|u| *u == event.pubkey());
let mut tags_an_authorized_user = false;
let mut tags_current_user = false;
@ -887,10 +881,8 @@ pub fn event_flags(event: &Event, user: &Option<Pubkey>) -> EventFlags {
if let Some(b"p") = tag.next() {
if let Some(value) = tag.next() {
if let Ok(tagged_pk) = Pubkey::read_hex(value) {
if let Some(current_user) = user {
if *current_user == tagged_pk {
tags_current_user = true;
}
if authed_as.contains(&tagged_pk) {
tags_current_user = true;
}
if crate::is_authorized_user(tagged_pk) {

View File

@ -53,6 +53,36 @@ pub async fn serve_http(
let uri = request.uri().to_owned();
if p == "/privacy-policy" {
let config = &*GLOBALS.config.read();
if let Some(pp) = &config.privacy_policy {
let response = Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Headers", "Authorization, *")
.header("Access-Control-Allow-Methods", "*")
.header("Allow", "OPTIONS, GET, HEAD")
.header("Content-Type", "text/plain")
.status(StatusCode::OK)
.body(Full::new(pp.clone().into()).map_err(|e| e.into()).boxed())?;
return Ok(response);
}
}
if p == "/terms-of-service" {
let config = &*GLOBALS.config.read();
if let Some(tos) = &config.terms_of_service {
let response = Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Headers", "Authorization, *")
.header("Access-Control-Allow-Methods", "*")
.header("Allow", "OPTIONS, GET, HEAD")
.header("Content-Type", "text/plain")
.status(StatusCode::OK)
.body(Full::new(tos.clone().into()).map_err(|e| e.into()).boxed())?;
return Ok(response);
}
}
// Try blossom if enabled
if GLOBALS.config.read().blossom_directory.is_some() {
match blossom::handle(request).await {

View File

@ -5,6 +5,7 @@ use crate::ip::HashedPeer;
use http_body_util::combinators::BoxBody;
use http_body_util::{BodyExt, Full};
use hyper::body::Bytes;
use hyper::http::uri::Uri;
use hyper::{Response, StatusCode};
pub async fn serve_nip11(peer: HashedPeer) -> Result<Response<BoxBody<Bytes, Error>>, Error> {
@ -80,18 +81,18 @@ fn build_rid(config: &Config) -> String {
rid.push_str(description);
rid.push('\"');
}
if let Some(banner_url) = &config.banner_url {
rid.push(',');
rid.push_str("\"banner\":\"");
rid.push_str(banner_url);
rid.push('\"');
}
if let Some(icon_url) = &config.icon_url {
rid.push(',');
rid.push_str("\"icon\":\"");
rid.push_str(icon_url);
rid.push('\"');
}
if let Some(contact) = &config.contact {
rid.push(',');
rid.push_str("\"contact\":\"");
rid.push_str(contact);
rid.push('\"');
}
if let Some(pubkey) = &config.contact_public_key {
let mut pkh: [u8; 64] = [0; 64];
pubkey.write_hex(&mut pkh).unwrap();
@ -100,6 +101,44 @@ fn build_rid(config: &Config) -> String {
rid.push_str(unsafe { std::str::from_utf8_unchecked(pkh.as_slice()) });
rid.push('\"');
}
if let Some(contact) = &config.contact {
rid.push(',');
rid.push_str("\"contact\":\"");
rid.push_str(contact);
rid.push('\"');
}
if config.privacy_policy.is_some() {
rid.push(',');
rid.push_str("\"privacy_policy\":\"");
let url = match config.uri_parts(
Uri::from_static("https://authority-will-be-replaced/privacy-policy"),
true,
) {
Ok(parts) => match Uri::from_parts(parts) {
Ok(uri) => format!("{}", uri),
Err(_) => "".to_owned(),
},
Err(_) => "".to_owned(),
};
rid.push_str(&url);
rid.push('\"');
}
if config.terms_of_service.is_some() {
rid.push(',');
rid.push_str("\"terms_of_service\":\"");
let url = match config.uri_parts(
Uri::from_static("https://authority-will-be-replaced/terms-of-service"),
true,
) {
Ok(parts) => match Uri::from_parts(parts) {
Ok(uri) => format!("{}", uri),
Err(_) => "".to_owned(),
},
Err(_) => "".to_owned(),
};
rid.push_str(&url);
rid.push('\"');
}
// Limitation
rid.push(',');

View File

@ -18,6 +18,12 @@ then
exit 1
fi
# ADD ADMIN AS A USER ------------
echo "Adding user..."
PUBKEY=12bb541d03bfc3cab0f4a8e4db28947f60faae6fca4e315eb27f809c6eff9a0b
../target/release/chorus_cmd ./config.toml add_user $PUBKEY 0
# UPLOAD TEST ------------
FILE="./Example.png"

3
test_with_relay_tester/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
data
relay-tester

View File

@ -0,0 +1,12 @@
# Testing Chorus with relay-tester
First, git clone https://github.com/mikedilger/relay-tester and build that project
(cargo build --release).
Then copy that target/release/relay-tester binary into this directory.
Then run from two different shells, in this order:
shell1: ./test_chorus.sh
shell2: ./run_relay_tester.sh

View File

@ -0,0 +1,12 @@
#!/bin/bash
if [ ! -x relay-tester ] ; then
echo "You must build https://github.com/mikedilge/relay-tester and copy the "
echo "resultant target/release/relay-tester binary into this directory."
exit 1
fi
./relay-tester \
ws://localhost:8080/ \
nsec16xfd467kyd3xpu9x5u4933u00v73xrl0jyq9rk5ktd9t2j38k20qtwxuj3 \
nsec1l50yuf6uxm2l5qxm87fkm56z3m7g88jnfy5s6az5wscxpu5l2yqq6qwk88

View File

@ -0,0 +1,10 @@
#!/bin/bash
echo "Use ws://localhost:8080/ as the relay url"
cargo build --release
rm -rf ./data/
../target/release/chorus_init ./test_chorus.toml
../target/release/chorus_cmd ./test_chorus.toml add_user de16d3ed2d5ceb91d33e39dbe30585164e0c19f3f2e2a5b121def086b447a2e5 0
../target/release/chorus_cmd ./test_chorus.toml add_user 35d6bbcf17fc31a9c4f7a2f68aa40ad32c8f9de1ae77505dc5eb3722d8b2987d 0
../target/release/chorus ./test_chorus.toml

View File

@ -0,0 +1,40 @@
# See contrib/chorus.toml for a documented config file
data_directory = "./data"
ip_address = "127.0.0.1"
port = 8080
hostname = "localhost"
chorus_is_behind_a_proxy = false
use_tls = false
certchain_pem_path = "tls/fullchain.pem"
key_pem_path = "tls/privkey.pem"
name = "Chorus Sample"
description = "A sample run of the Chorus relay"
#icon_url =
open_relay = false
admin_hex_keys = [
"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49",
# npub1mctd8mfdtn4er5e788d7xpv9ze8qcx0n7t32tvfpmmcgddz85tjsuyxe7z
"de16d3ed2d5ceb91d33e39dbe30585164e0c19f3f2e2a5b121def086b447a2e5",
# npub1xhtthnchlsc6n38h5tmg4fq26vkgl80p4em4qhw9avmj9k9jnp7sm78ql6
"35d6bbcf17fc31a9c4f7a2f68aa40ad32c8f9de1ae77505dc5eb3722d8b2987d"
]
verify_events = true
allow_scraping = false
allow_scrape_if_limited_to = 100
allow_scrape_if_max_seconds = 7200
max_subscriptions = 128
serve_ephemeral = true
serve_relay_lists = true
server_log_level = "Info"
library_log_level = "Info"
client_log_level = "Debug"
enable_ip_blocking = false
minimum_ban_seconds = 1
timeout_seconds = 60
max_connections_per_ip = 5
throttling_bytes_per_second = 131072
throttling_burst = 4194304
enable_negentropy = true