diff --git a/Cargo.lock b/Cargo.lock index 6c5adba31..8dff7b271 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1000,6 +1000,28 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31ad93652f40969dead8d4bf897a41e9462095152eb21c56e5830537e41179dd" +[[package]] +name = "doku" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b44778199365a299b026bf1c16d80579adaeba3c484244e1561f3a93de43451a" +dependencies = [ + "doku-derive", + "serde", +] + +[[package]] +name = "doku-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949dc0bf36de2fe276d42c79ff81cf4326c45b98b091c0edec75e857b5775626" +dependencies = [ + "darling 0.13.0", + "proc-macro2 1.0.29", + "quote 1.0.10", + "syn 1.0.80", +] + [[package]] name = "either" version = "1.6.1" @@ -1924,6 +1946,7 @@ dependencies = [ "clokwerk", "diesel", "diesel_migrations", + "doku", "env_logger", "http-signature-normalization-actix", "lemmy_api", @@ -1960,6 +1983,7 @@ dependencies = [ "comrak", "deser-hjson", "diesel", + "doku", "futures", "http", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 199c732ee..8fe071a83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ activitystreams = "0.7.0-alpha.11" actix-rt = { version = "2.2.0", default-features = false } serde_json = { version = "1.0.68", features = ["preserve_order"] } clokwerk = "0.3.5" +doku = "0.10.1" [dev-dependencies.cargo-husky] version = "1.5.0" diff --git a/config/config.hjson b/config/config.hjson index 11b64b204..252fca250 100644 --- a/config/config.hjson +++ b/config/config.hjson @@ -1,95 +1,5 @@ +# See the documentation for available config fields and descriptions: +# https://join-lemmy.org/docs/en/administration/configuration.html { -# # optional: parameters for automatic configuration of new instance (only used at first start) -# setup: { -# # username for the admin user -# admin_username: "" -# # password for the admin user -# admin_password: "" -# # optional: email for the admin user (can be omitted and set later through the website) -# admin_email: "" -# # name of the site (can be changed later) -# site_name: "" -# } - # settings related to the postgresql database - database: { - # username to connect to postgres - user: "lemmy" - # password to connect to postgres - password: "password" - # host where postgres is running - host: "localhost" - # port where postgres can be accessed - port: 5432 - # name of the postgres database for lemmy - database: "lemmy" - # maximum number of active sql connections - pool_size: 5 - } - # the domain name of your instance (eg "lemmy.ml") hostname: lemmy-alpha - # address where lemmy should listen for incoming requests - bind: "0.0.0.0" - # port where lemmy should listen for incoming requests - port: 8536 - # whether tls is required for activitypub. only disable this for debugging, never for producion. - tls_enabled: true - # address where pictrs is available - pictrs_url: "http://pictrs:8080" - # maximum length of local community and user names - actor_name_max_length: 20 - # rate limits for various user actions, by user ip - rate_limit: { - # maximum number of messages created in interval - message: 180 - # interval length for message limit - message_per_second: 60 - # maximum number of posts created in interval - post: 6 - # interval length for post limit - post_per_second: 600 - # maximum number of registrations in interval - register: 3 - # interval length for registration limit - register_per_second: 3600 - # maximum number of image uploads in interval - image: 6 - # interval length for image uploads - image_per_second: 3600 - } - # settings related to activitypub federation - federation: { - # whether to enable activitypub federation. - enabled: false - # Allows and blocks are described here: - # https://join-lemmy.org/docs/en/federation/administration.html#instance-allowlist-and-blocklist - # - # list of instances with which federation is allowed - # allowed_instances: ["instance1.tld","instance2.tld"] - # instances which we never federate anything with (but previously federated objects are unaffected) - # blocked_instances: [] - # If true, only federate with instances on the allowlist and block everything else. If false, - # use allowlist only for remote communities, and posts/comments in local communities. - # strict_allowlist: true - } - captcha: { - enabled: true - difficulty: medium # Can be easy, medium, or hard - } -# # email sending configuration -# email: { -# # hostname and port of the smtp server -# smtp_server: "" -# # login name for smtp server -# smtp_login: "" -# # password to login to the smtp server -# smtp_password: "" -# # address to send emails from, eg "noreply@your-instance.com" -# smtp_from_address: "" -# # whether or not smtp connections should use tls -# use_tls: true -# } - # additional_slurs: - # ''' - # (\bThis\b)|(\bis\b)|(\bsample\b) - # ''' } diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index f8e7804ac..9671b885c 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -39,3 +39,4 @@ deser-hjson = "1.0.2" smart-default = "0.6.0" webpage = { version = "1.3.0", default-features = false, features = ["serde"] } jsonwebtoken = "7.2.0" +doku = "0.10.1" diff --git a/crates/utils/src/settings/mod.rs b/crates/utils/src/settings/mod.rs index 7e320701d..6678b91f5 100644 --- a/crates/utils/src/settings/mod.rs +++ b/crates/utils/src/settings/mod.rs @@ -11,6 +11,16 @@ static DEFAULT_CONFIG_FILE: &str = "config/config.hjson"; lazy_static! { static ref SETTINGS: RwLock = RwLock::new(Settings::init().expect("Failed to load settings file")); + static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!( + "^group:([a-z0-9_]{{3,}})@{}$", + Settings::get().hostname + )) + .expect("compile webfinger regex"); + static ref WEBFINGER_USER_REGEX: Regex = Regex::new(&format!( + "^acct:([a-z0-9_]{{3,}})@{}$", + Settings::get().hostname + )) + .expect("compile webfinger regex"); } impl Settings { @@ -21,22 +31,12 @@ impl Settings { /// Warning: Only call this once. pub fn init() -> Result { // Read the config file - let mut config = from_str::(&Self::read_config_file()?)?; + let config = from_str::(&Self::read_config_file()?)?; if config.hostname == "unset" { return Err(anyhow!("Hostname variable is not set!").into()); } - // Initialize the regexes - config.webfinger_community_regex = Some( - Regex::new(&format!("^group:([a-z0-9_]{{3,}})@{}$", config.hostname)) - .expect("compile webfinger regex"), - ); - config.webfinger_username_regex = Some( - Regex::new(&format!("^acct:([a-z0-9_]{{3,}})@{}$", config.hostname)) - .expect("compile webfinger regex"), - ); - Ok(config) } @@ -106,17 +106,11 @@ impl Settings { } pub fn webfinger_community_regex(&self) -> Regex { - self - .webfinger_community_regex - .to_owned() - .expect("compile webfinger regex") + WEBFINGER_COMMUNITY_REGEX.to_owned() } pub fn webfinger_username_regex(&self) -> Regex { - self - .webfinger_username_regex - .to_owned() - .expect("compile webfinger regex") + WEBFINGER_USER_REGEX.to_owned() } pub fn slur_regex(&self) -> Regex { diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index 293349348..4ec17906b 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -1,118 +1,168 @@ -use regex::Regex; -use serde::Deserialize; +use doku::Document; +use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr}; -#[derive(Debug, Deserialize, Clone, SmartDefault)] +#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] #[serde(default)] pub struct Settings { + /// settings related to the postgresql database #[serde(default)] pub database: DatabaseConfig, #[default(Some(RateLimitConfig::default()))] + /// rate limits for various user actions, by user ip pub rate_limit: Option, + /// Settings related to activitypub federation #[default(FederationConfig::default())] pub federation: FederationConfig, #[default(CaptchaConfig::default())] pub captcha: CaptchaConfig, + /// Email sending configuration. All options except login/password are mandatory #[default(None)] pub email: Option, + /// Parameters for automatic configuration of new instance (only used at first start) #[default(None)] pub setup: Option, + /// the domain name of your instance (mandatory) #[default("unset")] + #[doku(example = "example.com")] pub hostname: String, + /// Address where lemmy should listen for incoming requests #[default(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))] + #[doku(as = "String")] pub bind: IpAddr, + /// Port where lemmy should listen for incoming requests #[default(8536)] pub port: u16, + /// Whether the site is available over TLS. Needs to be true for federation to work. #[default(true)] pub tls_enabled: bool, + /// Address where pictrs is available (for image hosting) #[default(None)] + #[doku(example = "http://localhost:8080")] pub pictrs_url: Option, + /// Regex for slurs which are prohibited. Example: `(\bThis\b)|(\bis\b)|(\bsample\b)` #[default(None)] pub additional_slurs: Option, + /// Maximum length of local community and user names #[default(20)] pub actor_name_max_length: usize, - #[default(None)] - #[serde(skip)] - pub webfinger_community_regex: Option, - #[default(None)] - #[serde(skip)] - pub webfinger_username_regex: Option, } -#[derive(Debug, Deserialize, Clone, SmartDefault)] +#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] #[serde(default)] pub struct CaptchaConfig { + /// Whether captcha is required for signup #[default(false)] pub enabled: bool, + /// Can be easy, medium, or hard #[default("medium")] pub difficulty: String, } -#[derive(Debug, Deserialize, Clone, SmartDefault)] +#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] #[serde(default)] pub struct DatabaseConfig { + /// Username to connect to postgres #[default("lemmy")] pub(super) user: String, + /// Password to connect to postgres #[default("password")] pub password: String, #[default("localhost")] + /// Host where postgres is running pub host: String, + /// Port where postgres can be accessed #[default(5432)] pub(super) port: i32, + /// Name of the postgres database for lemmy #[default("lemmy")] pub(super) database: String, + /// Maximum number of active sql connections #[default(5)] pub pool_size: u32, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Document)] pub struct EmailConfig { + /// Hostname and port of the smtp server + #[doku(example = "localhost:25")] pub smtp_server: String, + /// Login name for smtp server pub smtp_login: Option, + /// Password to login to the smtp server pub smtp_password: Option, + #[doku(example = "noreply@example.com")] + /// Address to send emails from, eg "noreply@your-instance.com" pub smtp_from_address: String, + /// Whether or not smtp connections should use tls pub use_tls: bool, } -#[derive(Debug, Deserialize, Clone, SmartDefault)] +#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] #[serde(default)] pub struct FederationConfig { + /// Whether to enable activitypub federation. #[default(false)] pub enabled: bool, + /// Allows and blocks are described here: + /// https://join-lemmy.org/docs/en/federation/administration.html///instance-allowlist-and-blocklist + /// + /// list of instances with which federation is allowed #[default(None)] + #[doku(example = "instance1.tld")] + #[doku(example = "instance2.tld")] pub allowed_instances: Option>, + /// Instances which we never federate anything with (but previously federated objects are unaffected) #[default(None)] pub blocked_instances: Option>, + /// If true, only federate with instances on the allowlist and block everything else. If false, + /// use allowlist only for remote communities, and posts/comments in local communities + /// (meaning remote communities will show content from arbitrary instances). #[default(true)] pub strict_allowlist: bool, } -#[derive(Debug, Deserialize, Clone, SmartDefault)] +#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] #[serde(default)] pub struct RateLimitConfig { + /// Maximum number of messages created in interval #[default(180)] pub message: i32, + /// Interval length for message limit, in seconds #[default(60)] pub message_per_second: i32, + /// Maximum number of posts created in interval #[default(6)] pub post: i32, + /// Interval length for post limit, in seconds #[default(600)] pub post_per_second: i32, + /// Maximum number of registrations in interval #[default(3)] pub register: i32, + /// Interval length for registration limit, in seconds #[default(3600)] pub register_per_second: i32, + /// Maximum number of image uploads in interval #[default(6)] pub image: i32, + /// Interval length for image uploads, in seconds #[default(3600)] pub image_per_second: i32, } -#[derive(Debug, Deserialize, Clone, SmartDefault)] +#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] pub struct SetupConfig { + /// Username for the admin user + #[doku(example = "admin")] pub admin_username: String, + /// Password for the admin user + #[doku(example = "my_passwd")] pub admin_password: String, + /// Name of the site (can be changed later) + #[doku(example = "My Lemmy Instance")] pub site_name: String, + /// Email for the admin user (optional, can be omitted and set later through the website) #[default(None)] pub admin_email: Option, #[default(None)] diff --git a/src/main.rs b/src/main.rs index 7d49e32ba..483b17415 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use diesel::{ r2d2::{ConnectionManager, Pool}, PgConnection, }; +use doku::json::{AutoComments, Formatting}; use lemmy_api::match_websocket_operation; use lemmy_api_common::blocking; use lemmy_api_crud::match_websocket_operation_crud; @@ -23,13 +24,23 @@ use lemmy_utils::{ }; use lemmy_websocket::{chat_server::ChatServer, LemmyContext}; use reqwest::Client; -use std::{sync::Arc, thread}; +use std::{env, sync::Arc, thread}; use tokio::sync::Mutex; embed_migrations!(); #[actix_web::main] async fn main() -> Result<(), LemmyError> { + let args: Vec = env::args().collect(); + if args.len() == 2 && args[1] == "--print-config-docs" { + let fmt = Formatting { + auto_comments: AutoComments::none(), + ..Default::default() + }; + println!("{}", doku::to_json_fmt_val(&fmt, &Settings::default())); + return Ok(()); + } + env_logger::init(); let settings = Settings::init().expect("Couldn't initialize settings.");