<oembed><type>rich</type><version>1.0</version><author_name>npub1xe6ua0ef6ehwhzvyc06jn5mayjc4hkzhcpqv5p858l3897sx3suqy0d9r6</author_name><author_url>https://nostr.ae/npub1xe6ua0ef6ehwhzvyc06jn5mayjc4hkzhcpqv5p858l3897sx3suqy0d9r6</author_url><provider_name>njump</provider_name><provider_url>https://nostr.ae</provider_url><html>// n34-relay - A nostr GRASP relay implementation&#xA;// Copyright (C) 2025 Awiteb &lt;a@4rs.nl&gt;&#xA;//&#xA;// This program is free software: you can redistribute it and/or modify&#xA;// it under the terms of the GNU Affero General Public License as published&#xA;// by the Free Software Foundation, either version 3 of the License, or&#xA;// (at your option) any later version.&#xA;//&#xA;// This program is distributed in the hope that it will be useful,&#xA;// but WITHOUT ANY WARRANTY; without even the implied warranty of&#xA;// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&#xA;// GNU Affero General Public License for more details.&#xA;//&#xA;// You should have received a copy of the GNU Affero General Public License&#xA;// along with this program. If not, see &lt;https://gnu.org/licenses/agpl-3.0&gt;.&#xA;&#xA;use std::{&#xA;    borrow::Cow,&#xA;    fs,&#xA;    net::IpAddr,&#xA;    num::{NonZeroU8, NonZeroU64, NonZeroUsize},&#xA;    path::PathBuf,&#xA;    sync::{&#xA;        Arc,&#xA;        atomic::{AtomicBool, Ordering},&#xA;    },&#xA;};&#xA;&#xA;use config::{Config, File as FileSource, FileFormat};&#xA;use nostr::{event::Kind, key::PublicKey, types::Url};&#xA;use parking_lot::RwLock;&#xA;use serde::{Deserialize, Serialize};&#xA;&#xA;use crate::{&#xA;    errors::{RelayError, RelayResult},&#xA;    ext_traits::{RwlockOption, RwlockVecExt},&#xA;    pathes,&#xA;    relay_config::env::ConfigFromEnv,&#xA;};&#xA;&#xA;/// Config defaults&#xA;pub mod defaults;&#xA;/// Struct the config from environment variables&#xA;pub mod env;&#xA;/// Config parsers&#xA;mod parsers;&#xA;&#xA;/// A type for public keys list&#xA;type ArcRwVec&lt;T&gt; = Arc&lt;RwLock&lt;Vec&lt;T&gt;&gt;&gt;;&#xA;&#xA;/// Indicates whether serialization is for TOML format.&#xA;///&#xA;/// Some fields are skipped during deserialization from config file,&#xA;/// but should be included when serializing to JSON.&#xA;pub static SERIALIZE_TO_TOML: AtomicBool = AtomicBool::new(false);&#xA;&#xA;pub fn load_config() {&#xA;    let config_path = pathes::get_default_config_path();&#xA;    &#xA;    println!(&#34;Loading config from: {:?}&#34;, config_path);&#xA;    &#xA;    if !config_path.exists() {&#xA;        // Implementation for creating default config...&#xA;    }&#xA;}&#xA;&#xA;/// Configuration for the relay network.&#xA;#[derive(Debug, Deserialize, Serialize)]&#xA;pub struct NetworkConfig {&#xA;    /// The IP address the relay will bind to.&#xA;    #[serde(default = &#34;defaults::net::ip_addr&#34;)]&#xA;    pub ip:   IpAddr,&#xA;    /// The port the relay will listen on.&#xA;    #[serde(default = &#34;defaults::net::port&#34;)]&#xA;    pub port: u16,&#xA;}&#xA;&#xA;/// Configuration for an LMDB database instance.&#xA;#[derive(Debug, Deserialize, Serialize)]&#xA;pub struct LmdbConfig {&#xA;    /// Path to the directory where the database files are stored. Defaults&#xA;    /// `/etc/n34-relay/config.toml`&#xA;    #[serde(default = &#34;defaults::lmdb::dir&#34;)]&#xA;    pub dir:            PathBuf,&#xA;    /// Maximum size of the memory map (in bytes). Defaults to `32GB` on 64-bit&#xA;    /// and `4GB` on 32-bit systems.&#xA;    #[serde(default = &#34;defaults::lmdb::map_size&#34;)]&#xA;    pub map_size:       usize,&#xA;    /// Maximum number of concurrent reader slots. Defaults to `126`.&#xA;    #[serde(default = &#34;defaults::lmdb::max_readers&#34;)]&#xA;    pub max_readers:    u32,&#xA;    /// Number of extra databases to allocate in addition to the 9 internal&#xA;    /// ones. Defaults `0`&#xA;    #[serde(default = &#34;defaults::lmdb::additional_dbs&#34;)]&#xA;    pub additional_dbs: u32,&#xA;}&#xA;&#xA;/// Ratelimit config&#xA;#[derive(Clone, Debug, Deserialize, Serialize)]&#xA;pub struct RatelimitConfig {&#xA;    /// Maximum number of active queries allowed. Defaults 500&#xA;    #[serde(default = &#34;defaults::ratelimit::max_queries&#34;)]&#xA;    pub max_queries:       usize,&#xA;    /// Number of events allowed per minute. Defaults 120&#xA;    #[serde(default = &#34;defaults::ratelimit::events_per_minute&#34;)]&#xA;    pub events_per_minute: u32,&#xA;}&#xA;&#xA;/// Relay metadata for NIP-11.&#xA;#[serde_with::skip_serializing_none]&#xA;#[derive(Debug, Deserialize, Serialize)]&#xA;pub struct Nip11Config {&#xA;    /// Name of the relay.&#xA;    #[serde(default, skip_serializing_if = &#34;RwlockOption::is_none&#34;)]&#xA;    pub name:             RwLock&lt;Option&lt;String&gt;&gt;,&#xA;    /// Description of the relay.&#xA;    #[serde(default, skip_serializing_if = &#34;RwlockOption::is_none&#34;)]&#xA;    pub description:      RwLock&lt;Option&lt;String&gt;&gt;,&#xA;    /// URL of the relay&#39;s banner image.&#xA;    #[serde(default, skip_serializing_if = &#34;RwlockOption::is_none&#34;)]&#xA;    pub banner:           RwLock&lt;Option&lt;Url&gt;&gt;,&#xA;    /// URL of the relay&#39;s icon image.&#xA;    #[serde(default, skip_serializing_if = &#34;RwlockOption::is_none&#34;)]&#xA;    pub icon:             RwLock&lt;Option&lt;Url&gt;&gt;,&#xA;    /// Public key of the relay&#39;s administrator.&#xA;    pub admin:            Option&lt;PublicKey&gt;,&#xA;    /// Alternate contact information for the relay administrator.&#xA;    pub contact:          Option&lt;String&gt;,&#xA;    /// URL to the relay&#39;s privacy policy document.&#xA;    pub privacy_policy:   Option&lt;Url&gt;,&#xA;    /// URL to the relay&#39;s terms of service document.&#xA;    pub terms_of_service: Option&lt;Url&gt;,&#xA;    /// Relay limitation&#xA;    #[serde(default)]&#xA;    pub limitation:       Nip11Limitation,&#xA;&#xA;    // The following values are defined by the relay and not customizable by the user.&#xA;    /// List of NIPs supported by the relay.&#xA;    #[serde(&#xA;        skip_deserializing,&#xA;        skip_serializing_if = &#34;ser_for_toml&#34;,&#xA;        default = &#34;defaults::nip11::supported_nips&#34;&#xA;    )]&#xA;    pub supported_nips:   &amp;&#39;static [u16],&#xA;    /// List of supported GRASPs&#xA;    #[serde(&#xA;        skip_deserializing,&#xA;        skip_serializing_if = &#34;ser_for_toml&#34;,&#xA;        default = &#34;defaults::nip11::supported_grasps&#34;&#xA;    )]&#xA;    pub supported_grasps: &amp;&#39;static [&amp;&#39;static str],&#xA;    /// Name of the relay&#39;s software.&#xA;    #[serde(&#xA;        skip_deserializing,&#xA;        skip_serializing_if = &#34;ser_for_toml&#34;,&#xA;        default = &#34;defaults::nip11::software&#34;&#xA;    )]&#xA;    pub software:         &amp;&#39;static str,&#xA;    /// Version of the relay&#39;s software.&#xA;    #[serde(&#xA;        skip_deserializing,&#xA;        skip_serializing_if = &#34;ser_for_toml&#34;,&#xA;        default = &#34;defaults::nip11::version&#34;&#xA;    )]&#xA;    pub version:          &amp;&#39;static str,&#xA;}&#xA;&#xA;// TODO: Add `max_event_tags` and `max_content_length`&#xA;/// Represents limitations and requirements for a relay as specified in NIP-11.&#xA;#[derive(Default, Debug, Deserialize, Serialize)]&#xA;pub struct Nip11Limitation {&#xA;    /// Indicates if payment is required to use the relay.&#xA;    #[serde(default)]&#xA;    pub payment_required: bool,&#xA;    /// URL where payments can be made.&#xA;    #[serde(skip_serializing_if = &#34;Option::is_none&#34;)]&#xA;    pub payments_url:     Option&lt;Url&gt;,&#xA;&#xA;    // The following values are defined by the relay and not customizable by the user.&#xA;    /// Maximum allowed size in bytes for incoming JSON messages.&#xA;    #[serde(skip_deserializing, skip_serializing_if = &#34;ser_for_toml&#34;, default)]&#xA;    pub max_message_length: usize,&#xA;    /// Maximum number of active subscriptions allowed per websocket connection.&#xA;    #[serde(skip_deserializing, skip_serializing_if = &#34;ser_for_toml&#34;, default)]&#xA;    pub max_subscriptions:  usize,&#xA;    /// Minimum Proof of Work difficulty required for new events.&#xA;    #[serde(skip_deserializing, skip_serializing_if = &#34;ser_for_toml&#34;, default)]&#xA;    pub min_pow_difficulty: u8,&#xA;    /// Indicates if NIP-42 authentication is required before performing any&#xA;    /// action.&#xA;    #[serde(skip_deserializing, skip_serializing_if = &#34;ser_for_toml&#34;, default)]&#xA;    pub auth_required:      bool,&#xA;    /// Indicates if the relay enforces conditions to accept events.&#xA;    #[serde(skip_deserializing, skip_serializing_if = &#34;ser_for_toml&#34;, default)]&#xA;    pub restricted_writes:  bool,&#xA;    /// Limits the maximum number of events a client can fetch from a single&#xA;    /// subscription filter.&#xA;    #[serde(skip_deserializing, skip_serializing_if = &#34;ser_for_toml&#34;, default)]&#xA;    pub max_limit:          usize,&#xA;    /// Sets the default number of events returned when no limit is specified in&#xA;    /// a filter.&#xA;    #[serde(skip_deserializing, skip_serializing_if = &#34;ser_for_toml&#34;, default)]&#xA;    pub default_limit:      usize,&#xA;    /// The maximum allowed length for a subscription ID as a string.&#xA;    #[serde(skip_deserializing, skip_serializing_if = &#34;ser_for_toml&#34;, default)]&#xA;    pub max_subid_length:   usize,&#xA;}&#xA;&#xA;#[derive(Debug, Deserialize, Serialize)]&#xA;pub struct RhaiPluginsConfig {&#xA;    /// Rhai engine workers. Defaults 3&#xA;    #[serde(default = &#34;defaults::rhai::workers&#34;)]&#xA;    pub workers: NonZeroU8,&#xA;    /// Rhai plugins&#xA;    #[serde(default)]&#xA;    pub plugins: Vec&lt;String&gt;,&#xA;}&#xA;&#xA;#[derive(Default, Debug, Deserialize, Serialize)]&#xA;pub struct PluginsConfig {&#xA;    /// gRPC plugins services&#xA;    #[serde(&#xA;        serialize_with = &#34;parsers::uris_ser&#34;,&#xA;        deserialize_with = &#34;parsers::uris_de&#34;,&#xA;        skip_serializing_if = &#34;Vec::is_empty&#34;,&#xA;        default&#xA;    )]&#xA;    pub grpc: Vec&lt;hyper::Uri&gt;,&#xA;    /// Rhai plugins&#xA;    pub rhai: RhaiPluginsConfig,&#xA;}&#xA;&#xA;/// Configuration settings for GRASP, including Git-related options.&#xA;#[derive(Debug, Deserialize, Serialize)]&#xA;pub struct GraspConfig {&#xA;    /// Determines if GRASP is enabled or disabled.&#xA;    #[serde(default = &#34;defaults::grasp::enable&#34;)]&#xA;    pub enable:      bool,&#xA;    /// Specifies the path to the Git executable. Defaults to &#39;git&#39;.&#xA;    #[serde(default = &#34;defaults::grasp::git_path&#34;)]&#xA;    pub git_path:    Cow&lt;&#39;static, str&gt;,&#xA;    /// The maximum number of requests a Git server can handle simultaneously.&#xA;    /// Default, no limit.&#xA;    #[serde(default)]&#xA;    pub max_reqs:    Option&lt;NonZeroUsize&gt;,&#xA;    /// A timeout for git Git server requests in seconds. If the request&#xA;    /// exceeded the timeout it will aborted.&#xA;    #[serde(default)]&#xA;    pub req_timeout: Option&lt;NonZeroU64&gt;,&#xA;&#xA;    /// Repositories path&#xA;    #[serde(skip, default = &#34;pathes::grasp_repos&#34;)]&#xA;    pub repos_path: PathBuf,&#xA;}&#xA;&#xA;/// Core relay-specific configs.&#xA;#[derive(Debug, Deserialize, Serialize)]&#xA;pub struct CoreConfig {&#xA;    /// The domain of the relay, excluding the protocol. Example: &#39;example.com&#39;&#xA;    /// or &#39;relay.example.net&#39;.&#xA;    #[serde(default)]&#xA;    pub domain:           String, // TODO: Check that it&#39;s without a protocol&#xA;    /// Whether NIP42 authentication is required. Default is `false`.&#xA;    #[serde(default = &#34;defaults::relay::nip42&#34;)]&#xA;    pub nip42:            bool,&#xA;    /// Maximum number of connections allowed. Defaults to no limit.&#xA;    #[serde(default)]&#xA;    pub max_connections:  Option&lt;usize&gt;,&#xA;    /// Minimum Proof of Work difficulty. Defaults to `0`.&#xA;    #[serde(default = &#34;defaults::relay::min_pow&#34;)]&#xA;    pub min_pow:          u8,&#xA;    /// Maximum size of an event in bytes. Default is 150KB.&#xA;    #[serde(default = &#34;defaults::relay::max_event_size&#34;)]&#xA;    pub max_event_size:   NonZeroUsize,&#xA;    /// Limits the maximum number of events a client can fetch from a single&#xA;    /// subscription filter. Defaults `5000`.&#xA;    #[serde(default = &#34;defaults::relay::max_limit&#34;)]&#xA;    pub max_limit:        NonZeroUsize,&#xA;    /// Sets the default number of events returned when no limit is specified in&#xA;    /// a filter. Defaults `500`.&#xA;    #[serde(default = &#34;defaults::relay::default_limit&#34;)]&#xA;    pub default_limit:    NonZeroUsize,&#xA;    /// The maximum allowed length for a subscription ID as a string. Defaults&#xA;    /// `150`.&#xA;    #[serde(default = &#34;defaults::relay::max_subid_length&#34;)]&#xA;    pub max_subid_length: NonZeroUsize,&#xA;    /// List of allowed public keys in hex or `npub` format. Default is empty.&#xA;    #[serde(default, serialize_with = &#34;parsers::pubkeys_ser&#34;)]&#xA;    pub whitelist:        ArcRwVec&lt;PublicKey&gt;,&#xA;    /// List of denied public keys in hex or `npub` format. Default is empty.&#xA;    #[serde(default, serialize_with = &#34;parsers::pubkeys_ser&#34;)]&#xA;    pub blacklist:        ArcRwVec&lt;PublicKey&gt;,&#xA;    /// A list of administrators allowed to use the Relay Management API.&#xA;    #[serde(default, serialize_with = &#34;parsers::pubkeys_ser&#34;)]&#xA;    pub admins:           ArcRwVec&lt;PublicKey&gt;,&#xA;    /// List of event kinds that are permitted&#xA;    #[serde(default, deserialize_with = &#34;parsers::kind_de&#34;)]&#xA;    pub allowed_kinds:    ArcRwVec&lt;Kind&gt;,&#xA;    /// List of event kinds that are rejected&#xA;    #[serde(default, deserialize_with = &#34;parsers::kind_de&#34;)]&#xA;    pub disallowed_kinds: ArcRwVec&lt;Kind&gt;,&#xA;}&#xA;&#xA;/// Configuration for relay components.&#xA;#[derive(Debug, Deserialize, Serialize)]&#xA;pub struct RelayConfig {&#xA;    /// Network-related configs for the relay.&#xA;    #[serde(default)]&#xA;    pub net:       NetworkConfig,&#xA;    /// LMDB database configs for the relay.&#xA;    #[serde(default)]&#xA;    pub lmdb:      LmdbConfig,&#xA;    /// RateLimit configuration.&#xA;    #[serde(default)]&#xA;    pub ratelimit: RatelimitConfig,&#xA;    /// Relay&#39;s NIP-11 metadata&#xA;    #[serde(default)]&#xA;    pub nip11:     Nip11Config,&#xA;    /// Relay&#39;s plugins&#xA;    #[serde(default)]&#xA;    pub plugins:   PluginsConfig,&#xA;    #[serde(default)]&#xA;    pub grasp:     GraspConfig,&#xA;    /// Core relay-specific configs.&#xA;    #[serde(default, flatten)]&#xA;    pub relay:     CoreConfig,&#xA;}&#xA;&#xA;impl RelayConfig {&#xA;    /// Reload the config from the env and config file&#xA;    pub fn reload() -&gt; RelayResult&lt;Self&gt; {&#xA;        let config: RelayConfig = Config::builder()&#xA;            .add_source(&#xA;                FileSource::from(pathes::config_file_path())&#xA;                    .format(FileFormat::Toml)&#xA;                    .required(false),&#xA;            )&#xA;            .add_source(RelayConfig::from_env()?)&#xA;            .build()&#xA;            .unwrap()&#xA;            .try_deserialize()&#xA;            .map_err(|err| RelayError::Config(err.to_string()))?;&#xA;&#xA;        Ok(config.post())&#xA;    }&#xA;&#xA;    /// Post the config&#xA;    fn post(mut self) -&gt; Self {&#xA;        self.nip11.limitation.max_message_length = self.relay.max_event_size.into();&#xA;        self.nip11.limitation.max_limit = self.relay.max_limit.into();&#xA;        self.nip11.limitation.default_limit = self.relay.default_limit.into();&#xA;        self.nip11.limitation.max_subid_length = self.relay.max_subid_length.into();&#xA;        self.nip11.limitation.max_subscriptions = self.relay.max_connections.unwrap_or(usize::MAX);&#xA;        self.nip11.limitation.min_pow_difficulty = self.relay.min_pow;&#xA;        self.nip11.limitation.auth_required = self.relay.nip42;&#xA;        self.nip11.limitation.restricted_writes = self.relay.min_pow &gt; 0&#xA;            || self.relay.nip42&#xA;            || self.nip11.limitation.payment_required&#xA;            || !self.relay.whitelist.is_empty()&#xA;            || !self.relay.allowed_kinds.is_empty();&#xA;&#xA;        self&#xA;    }&#xA;&#xA;    /// Retrieves the database instance based on the configuration.&#xA;    pub async fn get_relay_db(&amp;self) -&gt; RelayResult&lt;Arc&lt;dyn nostr_database::NostrDatabase&gt;&gt; {&#xA;        // TODO: Support sqlite&#xA;        Ok(Arc::new(&#xA;            nostr_lmdb::NostrLmdbBuilder::new(&amp;self.lmdb.dir)&#xA;                .map_size(self.lmdb.map_size)&#xA;                .max_readers(self.lmdb.max_readers)&#xA;                .additional_dbs(self.lmdb.additional_dbs)&#xA;                .build()&#xA;                .await?,&#xA;        ))&#xA;    }&#xA;}&#xA;&#xA;impl Drop for RelayConfig {&#xA;    fn drop(&amp;mut self) {&#xA;        fn inner(value: &amp;RelayConfig) -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {&#xA;            fs::write(pathes::config_file_path(), toml::to_string_pretty(value)?)?;&#xA;            Ok(())&#xA;        }&#xA;&#xA;        tracing::info!(&#34;Writing the configuration to the file&#34;);&#xA;&#xA;        SERIALIZE_TO_TOML.store(true, Ordering::Relaxed);&#xA;        if let Err(err) = inner(self) {&#xA;            tracing::error!(&#34;Failed to write configuration to file: {err}&#34;);&#xA;        }&#xA;        SERIALIZE_TO_TOML.store(false, Ordering::Relaxed);&#xA;    }&#xA;}&#xA;&#xA;impl From&lt;RatelimitConfig&gt; for nostr_relay_builder::builder::RateLimit {&#xA;    fn from(value: RatelimitConfig) -&gt; Self {&#xA;        Self {&#xA;            max_reqs:         value.max_queries,&#xA;            notes_per_minute: value.events_per_minute,&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;/// Returns whether serialization to TOML is enabled.&#xA;fn ser_for_toml&lt;T&gt;(_: T) -&gt; bool {&#xA;    SERIALIZE_TO_TOML.load(Ordering::Relaxed)&#xA;}&#xA;</html></oembed>