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