"termcolor",
]
-[[package]]
-name = "envy"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "event-listener"
version = "2.5.1"
"comrak",
"deser-hjson",
"diesel",
- "envy",
"futures",
"http",
"itertools",
"lazy_static",
"lettre",
"log",
- "merge",
"openssl",
"percent-encoding",
"rand 0.8.4",
"reqwest",
"serde",
"serde_json",
+ "smart-default",
"strum",
"strum_macros",
"thiserror",
"autocfg",
]
-[[package]]
-name = "merge"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
-dependencies = [
- "merge_derive",
- "num-traits",
-]
-
-[[package]]
-name = "merge_derive"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
-dependencies = [
- "proc-macro-error",
- "proc-macro2 1.0.27",
- "quote 1.0.9",
- "syn 1.0.73",
-]
-
[[package]]
name = "migrations_internals"
version = "1.4.1"
"vcpkg",
]
-[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2 1.0.27",
- "quote 1.0.9",
- "syn 1.0.73",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-dependencies = [
- "proc-macro2 1.0.27",
- "quote 1.0.9",
- "version_check",
-]
-
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+[[package]]
+name = "smart-default"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
+dependencies = [
+ "proc-macro2 1.0.27",
+ "quote 1.0.9",
+ "syn 1.0.73",
+]
+
[[package]]
name = "socket2"
version = "0.4.0"
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<Self::Response, LemmyError> {
- let captcha_settings = Settings::get().captcha();
+ let captcha_settings = Settings::get().captcha;
if !captcha_settings.enabled {
return Ok(GetCaptchaResponse { ok: None });
let subject = &format!(
"{} - {} {}",
subject_text,
- Settings::get().hostname(),
+ Settings::get().hostname,
local_user_view.person.name,
);
let html = &format!(
pub async fn build_federated_instances(
pool: &DbPool,
) -> Result<Option<FederatedInstances>, LemmyError> {
- if Settings::get().federation().enabled {
+ if Settings::get().federation.enabled {
let distinct_communities = blocking(pool, move |conn| {
Community::distinct_federated_communities(conn)
})
.await??;
- let allowed = Settings::get().get_allowed_instances();
- let blocked = Settings::get().get_blocked_instances();
+ let allowed = Settings::get().federation.allowed_instances;
+ let blocked = Settings::get().federation.blocked_instances;
let mut linked = distinct_communities
.iter()
}
if let Some(blocked) = blocked.as_ref() {
- linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
+ linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname));
}
// Sort and remove dupes
// Fetch Iframely and pictrs cached image
let data_url = data.url.as_ref();
- let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
- fetch_iframely_and_pictrs_data(context.client(), data_url).await;
+ let (iframely_response, pictrs_thumbnail) =
+ fetch_iframely_and_pictrs_data(context.client(), data_url).await?;
+ let (embed_title, embed_description, embed_html) = iframely_response
+ .map(|u| (u.title, u.description, u.html))
+ .unwrap_or((None, None, None));
let post_form = PostForm {
name: data.name.trim().to_owned(),
community_id: data.community_id,
creator_id: local_user_view.person.id,
nsfw: data.nsfw,
- embed_title: iframely_title,
- embed_description: iframely_description,
- embed_html: iframely_html,
+ embed_title,
+ embed_description,
+ embed_html,
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
..PostForm::default()
};
// Fetch Iframely and Pictrs cached image
let data_url = data.url.as_ref();
- let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
- fetch_iframely_and_pictrs_data(context.client(), data_url).await;
+ let (iframely_response, pictrs_thumbnail) =
+ fetch_iframely_and_pictrs_data(context.client(), data_url).await?;
+ let (embed_title, embed_description, embed_html) = iframely_response
+ .map(|u| (u.title, u.description, u.html))
+ .unwrap_or((None, None, None));
let post_form = PostForm {
creator_id: orig_post.creator_id.to_owned(),
body: data.body.to_owned(),
nsfw: data.nsfw,
updated: Some(naive_now()),
- embed_title: iframely_title,
- embed_description: iframely_description,
- embed_html: iframely_html,
+ embed_title,
+ embed_description,
+ embed_html,
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
..PostForm::default()
};
Ok(site_view) => Some(site_view),
// If the site isn't created yet, check the setup
Err(_) => {
- if let Some(setup) = Settings::get().setup().as_ref() {
+ if let Some(setup) = Settings::get().setup.as_ref() {
let register = Register {
username: setup.admin_username.to_owned(),
email: setup.admin_email.to_owned(),
.await??;
// If its not the admin, check the captcha
- if !no_admins && Settings::get().captcha().enabled {
+ if !no_admins && Settings::get().captcha.enabled {
let check = context
.chat_server()
.send(CheckCaptcha {
.iter()
.flatten()
.unique()
- .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname()))
+ .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname))
.filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
.map(|inbox| inbox.to_owned())
.collect(),
.iter()
.flatten()
.unique()
- .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname()))
+ .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname))
.filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
.map(|inbox| inbox.to_owned())
.collect();
where
T: Serialize,
{
- if !Settings::get().federation().enabled || inboxes.is_empty() {
+ if !Settings::get().federation.enabled || inboxes.is_empty() {
return Ok(());
}
Kind: Serialize,
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
{
- if !Settings::get().federation().enabled || inboxes.is_empty() {
+ if !Settings::get().federation.enabled || inboxes.is_empty() {
return Ok(());
}
.domain()
.context(location_info!())?;
- if activity_domain == Settings::get().hostname() {
+ if activity_domain == Settings::get().hostname {
return Err(
anyhow!(
"Error: received activity which was sent by local instance: {:?}",
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
pub fn config(cfg: &mut web::ServiceConfig) {
- if Settings::get().federation().enabled {
- println!("federation enabled, host is {}", Settings::get().hostname());
+ if Settings::get().federation.enabled {
+ println!("federation enabled, host is {}", Settings::get().hostname);
let digest_verifier = VerifyDigest::new(Sha256::new());
let header_guard_accept = guard::Any(guard::Header("Accept", APUB_JSON_CONTENT_TYPE))
let domain = apub_id.domain().context(location_info!())?.to_string();
let local_instance = settings.get_hostname_without_port()?;
- if !settings.federation().enabled {
+ if !settings.federation.enabled {
return if domain == local_instance {
Ok(())
} else {
// TODO: might be good to put the part above in one method, and below in another
// (which only gets called in apub::objects)
// -> no that doesnt make sense, we still need the code below for blocklist and strict allowlist
- if let Some(blocked) = Settings::get().get_blocked_instances() {
+ if let Some(blocked) = Settings::get().federation.blocked_instances {
if blocked.contains(&domain) {
return Err(anyhow!("{} is in federation blocklist", domain).into());
}
}
- if let Some(mut allowed) = Settings::get().get_allowed_instances() {
+ if let Some(mut allowed) = Settings::get().federation.allowed_instances {
// Only check allowlist if this is a community, or strict allowlist is enabled.
- let strict_allowlist = Settings::get()
- .federation()
- .strict_allowlist
- .unwrap_or(true);
+ let strict_allowlist = Settings::get().federation.strict_allowlist;
if use_strict_allowlist || strict_allowlist {
// need to allow this explicitly because apub receive might contain objects from our local
// instance.
let domain = object_id.domain().context(location_info!())?;
// if its a local object, return it directly from the database
- if Settings::get().hostname() == domain {
+ if Settings::get().hostname == domain {
let object = blocking(context.pool(), move |conn| {
To::read_from_apub_id(conn, &object_id.into())
})
) -> Result<DbPerson, LemmyError> {
let person_id = person.id_unchecked().context(location_info!())?.to_owned();
let domain = person_id.domain().context(location_info!())?;
- if domain == Settings::get().hostname() {
+ if domain == Settings::get().hostname {
let person = blocking(context.pool(), move |conn| {
DbPerson::read_from_apub_id(conn, &person_id.into())
})
let community = extract_community(&page.to, context, request_counter).await?;
let thumbnail_url: Option<Url> = page.image.clone().map(|i| i.url);
- let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
- if let Some(url) = &page.url {
- fetch_iframely_and_pictrs_data(context.client(), Some(url)).await
- } else {
- (None, None, None, thumbnail_url)
- };
+ let (iframely_response, pictrs_thumbnail) = if let Some(url) = &page.url {
+ fetch_iframely_and_pictrs_data(context.client(), Some(url)).await?
+ } else {
+ (None, thumbnail_url)
+ };
+ let (embed_title, embed_description, embed_html) = iframely_response
+ .map(|u| (u.title, u.description, u.html))
+ .unwrap_or((None, None, None));
let body_slurs_removed = page.source.as_ref().map(|s| remove_slurs(&s.content));
let form = PostForm {
deleted: None,
nsfw: page.sensitive,
stickied: page.stickied,
- embed_title: iframely_title,
- embed_description: iframely_description,
- embed_html: iframely_html,
+ embed_title,
+ embed_description,
+ embed_html,
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
ap_id: Some(page.id.clone().into()),
local: Some(false),
use actix_web::{body::BodyStream, http::StatusCode, web::Data, *};
+use anyhow::anyhow;
use awc::Client;
-use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings};
+use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings, LemmyError};
use serde::{Deserialize, Serialize};
use std::time::Duration;
return Ok(HttpResponse::Unauthorized().finish());
};
- let mut client_req = client.request_from(
- format!("{}/image", Settings::get().pictrs_url()),
- req.head(),
- );
+ let mut client_req = client.request_from(format!("{}/image", pictrs_url()?), req.head());
if let Some(addr) = req.head().peer_addr {
client_req = client_req.insert_header(("X-Forwarded-For", addr.to_string()))
// If there are no query params, the URL is original
let url = if params.format.is_none() && params.thumbnail.is_none() {
- format!("{}/image/original/{}", Settings::get().pictrs_url(), name,)
+ format!("{}/image/original/{}", pictrs_url()?, name,)
} else {
// Use jpg as a default when none is given
let format = params.format.unwrap_or_else(|| "jpg".to_string());
- let mut url = format!(
- "{}/image/process.{}?src={}",
- Settings::get().pictrs_url(),
- format,
- name,
- );
+ let mut url = format!("{}/image/process.{}?src={}", pictrs_url()?, format, name,);
if let Some(size) = params.thumbnail {
url = format!("{}&thumbnail={}", url, size,);
) -> Result<HttpResponse, Error> {
let (token, file) = components.into_inner();
- let url = format!(
- "{}/image/delete/{}/{}",
- Settings::get().pictrs_url(),
- &token,
- &file
- );
+ let url = format!("{}/image/delete/{}/{}", pictrs_url()?, &token, &file);
let mut client_req = client.request_from(url, req.head());
Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
}
+
+fn pictrs_url() -> Result<String, LemmyError> {
+ Settings::get()
+ .pictrs_url
+ .ok_or_else(|| anyhow!("images_disabled").into())
+}
.await?
.map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?;
- let protocols = if Settings::get().federation().enabled {
+ let protocols = if Settings::get().federation.enabled {
vec!["activitypub".to_string()]
} else {
vec![]
}
pub fn config(cfg: &mut web::ServiceConfig) {
- if Settings::get().federation().enabled {
+ if Settings::get().federation.enabled {
cfg.route(
".well-known/webfinger",
web::get().to(get_webfinger_response),
http = "0.2.4"
jsonwebtoken = "7.2.0"
deser-hjson = "1.0.1"
-merge = "0.1.0"
-envy = "0.4.2"
+smart-default = "0.6.0"
};
decode::<Claims>(
jwt,
- &DecodingKey::from_secret(Settings::get().jwt_secret().as_ref()),
+ &DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
&v,
)
}
pub fn jwt(local_user_id: i32) -> Result<Jwt, jsonwebtoken::errors::Error> {
let my_claims = Claims {
sub: local_user_id,
- iss: Settings::get().hostname(),
+ iss: Settings::get().hostname,
iat: Utc::now().timestamp(),
};
encode(
&Header::default(),
&my_claims,
- &EncodingKey::from_secret(Settings::get().jwt_secret().as_ref()),
+ &EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
)
}
}
to_username: &str,
html: &str,
) -> Result<(), String> {
- let email_config = Settings::get().email().ok_or("no_email_setup")?;
- let domain = Settings::get().hostname();
+ let email_config = Settings::get().email.ok_or("no_email_setup")?;
+ let domain = Settings::get().hostname;
let (smtp_server, smtp_port) = {
let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
extern crate lazy_static;
#[macro_use]
extern crate strum_macros;
+#[macro_use]
+extern crate smart_default;
pub mod apub;
pub mod claims;
lazy_static! {
pub static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!(
"^group:([a-z0-9_]{{3,}})@{}$",
- Settings::get().hostname()
+ Settings::get().hostname
))
.expect("compile webfinger regex");
pub static ref WEBFINGER_USERNAME_REGEX: Regex = Regex::new(&format!(
"^acct:([a-z0-9_]{{3,}})@{}$",
- Settings::get().hostname()
+ Settings::get().hostname
))
.expect("compile webfinger regex");
}
{
// Does not need to be blocking because the RwLock in settings never held across await points,
// and the operation here locks only long enough to clone
- let rate_limit: RateLimitConfig = Settings::get().rate_limit();
+ let rate_limit: RateLimitConfig = Settings::get()
+ .rate_limit
+ .unwrap_or_else(RateLimitConfig::default);
// before
{
}
#[derive(Deserialize, Debug)]
-pub(crate) struct IframelyResponse {
- title: Option<String>,
- description: Option<String>,
+pub struct IframelyResponse {
+ pub title: Option<String>,
+ pub description: Option<String>,
thumbnail_url: Option<Url>,
- html: Option<String>,
+ pub html: Option<String>,
}
pub(crate) async fn fetch_iframely(
client: &Client,
url: &Url,
) -> Result<IframelyResponse, LemmyError> {
- let fetch_url = format!("{}/oembed?url={}", Settings::get().iframely_url(), url);
+ if let Some(iframely_url) = Settings::get().iframely_url {
+ let fetch_url = format!("{}/oembed?url={}", iframely_url, url);
- let response = retry(|| client.get(&fetch_url).send()).await?;
+ let response = retry(|| client.get(&fetch_url).send()).await?;
- let res: IframelyResponse = response
- .json()
- .await
- .map_err(|e| RecvError(e.to_string()))?;
- Ok(res)
+ let res: IframelyResponse = response
+ .json()
+ .await
+ .map_err(|e| RecvError(e.to_string()))?;
+ Ok(res)
+ } else {
+ Err(anyhow!("Missing Iframely URL in config.").into())
+ }
}
#[derive(Deserialize, Debug, Clone)]
pub(crate) async fn fetch_pictrs(
client: &Client,
image_url: &Url,
-) -> Result<PictrsResponse, LemmyError> {
- is_image_content_type(client, image_url).await?;
-
- let fetch_url = format!(
- "{}/image/download?url={}",
- Settings::get().pictrs_url(),
- utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
- );
-
- let response = retry(|| client.get(&fetch_url).send()).await?;
-
- let response: PictrsResponse = response
- .json()
- .await
- .map_err(|e| RecvError(e.to_string()))?;
-
- if response.msg == "ok" {
- Ok(response)
+) -> Result<Option<PictrsResponse>, LemmyError> {
+ if let Some(pictrs_url) = Settings::get().pictrs_url {
+ is_image_content_type(client, image_url).await?;
+
+ let fetch_url = format!(
+ "{}/image/download?url={}",
+ pictrs_url,
+ utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
+ );
+
+ let response = retry(|| client.get(&fetch_url).send()).await?;
+
+ let response: PictrsResponse = response
+ .json()
+ .await
+ .map_err(|e| RecvError(e.to_string()))?;
+
+ if response.msg == "ok" {
+ Ok(Some(response))
+ } else {
+ Err(anyhow!("{}", &response.msg).into())
+ }
} else {
- Err(anyhow!("{}", &response.msg).into())
+ Ok(None)
}
}
pub async fn fetch_iframely_and_pictrs_data(
client: &Client,
url: Option<&Url>,
-) -> (Option<String>, Option<String>, Option<String>, Option<Url>) {
+) -> Result<(Option<IframelyResponse>, Option<Url>), LemmyError> {
match &url {
Some(url) => {
// Fetch iframely data
- let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
- match fetch_iframely(client, url).await {
- Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
- Err(e) => {
- error!("iframely err: {}", e);
- (None, None, None, None)
- }
- };
+ let iframely_res_option = fetch_iframely(client, url).await.ok();
// Fetch pictrs thumbnail
- let pictrs_hash = match iframely_thumbnail_url {
- Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
- Ok(res) => Some(res.files[0].file.to_owned()),
- Err(e) => {
- error!("pictrs err: {}", e);
- None
- }
- },
- // Try to generate a small thumbnail if iframely is not supported
- None => match fetch_pictrs(client, url).await {
- Ok(res) => Some(res.files[0].file.to_owned()),
- Err(e) => {
- error!("pictrs err: {}", e);
- None
- }
+ let pictrs_hash = match &iframely_res_option {
+ Some(iframely_res) => match &iframely_res.thumbnail_url {
+ Some(iframely_thumbnail_url) => fetch_pictrs(client, iframely_thumbnail_url)
+ .await?
+ .map(|r| r.files[0].file.to_owned()),
+ // Try to generate a small thumbnail if iframely is not supported
+ None => fetch_pictrs(client, url)
+ .await?
+ .map(|r| r.files[0].file.to_owned()),
},
+ None => fetch_pictrs(client, url)
+ .await?
+ .map(|r| r.files[0].file.to_owned()),
};
// The full urls are necessary for federation
- let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
- let url = Url::parse(&format!(
- "{}/pictrs/image/{}",
- Settings::get().get_protocol_and_hostname(),
- pictrs_hash
- ));
- match url {
- Ok(parsed_url) => Some(parsed_url),
- Err(e) => {
- // This really shouldn't happen unless the settings or hash are malformed
- error!("Unexpected error constructing pictrs thumbnail URL: {}", e);
- None
- }
- }
- } else {
- None
- };
-
- (
- iframely_title,
- iframely_description,
- iframely_html,
- pictrs_thumbnail,
- )
+ let pictrs_thumbnail = pictrs_hash
+ .map(|p| {
+ Url::parse(&format!(
+ "{}/pictrs/image/{}",
+ Settings::get().get_protocol_and_hostname(),
+ p
+ ))
+ .ok()
+ })
+ .flatten();
+
+ Ok((iframely_res_option, pictrs_thumbnail))
}
- None => (None, None, None, None),
+ None => Ok((None, None)),
}
}
+++ /dev/null
-use crate::settings::{CaptchaConfig, DatabaseConfig, FederationConfig, RateLimitConfig, Settings};
-use std::net::{IpAddr, Ipv4Addr};
-
-impl Default for Settings {
- fn default() -> Self {
- Self {
- database: Some(DatabaseConfig::default()),
- rate_limit: Some(RateLimitConfig::default()),
- federation: Some(FederationConfig::default()),
- captcha: Some(CaptchaConfig::default()),
- email: None,
- setup: None,
- hostname: None,
- bind: Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))),
- port: Some(8536),
- tls_enabled: Some(true),
- jwt_secret: Some("changeme".into()),
- pictrs_url: Some("http://pictrs:8080".into()),
- iframely_url: Some("http://iframely".into()),
- additional_slurs: None,
- actor_name_max_length: Some(20),
- }
- }
-}
-
-impl Default for DatabaseConfig {
- fn default() -> Self {
- Self {
- user: Some("lemmy".to_string()),
- password: "password".into(),
- host: "localhost".into(),
- port: Some(5432),
- database: Some("lemmy".to_string()),
- pool_size: Some(5),
- }
- }
-}
-
-impl Default for CaptchaConfig {
- fn default() -> Self {
- Self {
- enabled: true,
- difficulty: "medium".into(),
- }
- }
-}
-
-impl Default for FederationConfig {
- fn default() -> Self {
- Self {
- enabled: false,
- allowed_instances: None,
- blocked_instances: None,
- strict_allowlist: Some(true),
- }
- }
-}
-
-impl Default for RateLimitConfig {
- fn default() -> Self {
- Self {
- message: 180,
- message_per_second: 60,
- post: 6,
- post_per_second: 600,
- register: 3,
- register_per_second: 3600,
- image: 6,
- image_per_second: 3600,
- }
- }
-}
-use crate::{
- location_info,
- settings::structs::{
- CaptchaConfig,
- DatabaseConfig,
- EmailConfig,
- FederationConfig,
- RateLimitConfig,
- Settings,
- SetupConfig,
- },
- LemmyError,
-};
+use crate::{location_info, settings::structs::Settings, LemmyError};
use anyhow::{anyhow, Context};
use deser_hjson::from_str;
-use merge::Merge;
-use std::{env, fs, io::Error, net::IpAddr, sync::RwLock};
+use std::{env, fs, io::Error, sync::RwLock};
-pub mod defaults;
pub mod structs;
static CONFIG_FILE: &str = "config/config.hjson";
impl Settings {
/// Reads config from configuration file.
- /// Then values from the environment (with prefix LEMMY) are added to the config.
- /// And then default values are merged into config.
- /// Defaults are controlled by Default trait implemntation for Settings structs.
///
/// Note: The env var `LEMMY_DATABASE_URL` is parsed in
/// `lemmy_db_queries/src/lib.rs::get_database_url_from_env()`
fn init() -> Result<Self, LemmyError> {
// Read the config file
- let mut custom_config = from_str::<Settings>(&Self::read_config_file()?)?;
+ let config = from_str::<Settings>(&Self::read_config_file()?)?;
- // Merge with env vars
- custom_config.merge(envy::prefixed("LEMMY_").from_env::<Settings>()?);
-
- // Merge with default
- custom_config.merge(Settings::default());
-
- if custom_config.hostname == Settings::default().hostname {
+ if config.hostname == "unset" {
return Err(anyhow!("Hostname variable is not set!").into());
}
- Ok(custom_config)
+ Ok(config)
}
/// Returns the config as a struct.
}
pub fn get_database_url(&self) -> String {
- let conf = self.database();
+ let conf = &self.database;
format!(
"postgres://{}:{}@{}:{}/{}",
- conf.user(),
- conf.password,
- conf.host,
- conf.port(),
- conf.database(),
+ conf.user, conf.password, conf.host, conf.port, conf.database,
)
}
fs::read_to_string(Self::get_config_location())
}
- pub fn get_allowed_instances(&self) -> Option<Vec<String>> {
- self.federation().allowed_instances
- }
-
- pub fn get_blocked_instances(&self) -> Option<Vec<String>> {
- self.federation().blocked_instances
- }
-
/// Returns either "http" or "https", depending on tls_enabled setting
pub fn get_protocol_string(&self) -> &'static str {
- if let Some(tls_enabled) = self.tls_enabled {
- if tls_enabled {
- "https"
- } else {
- "http"
- }
+ if self.tls_enabled {
+ "https"
} else {
"http"
}
/// Returns something like `http://localhost` or `https://lemmy.ml`,
/// with the correct protocol and hostname.
pub fn get_protocol_and_hostname(&self) -> String {
- format!("{}://{}", self.get_protocol_string(), self.hostname())
+ format!("{}://{}", self.get_protocol_string(), self.hostname)
}
/// When running the federation test setup in `api_tests/` or `docker/federation`, the `hostname`
pub fn get_hostname_without_port(&self) -> Result<String, anyhow::Error> {
Ok(
self
- .hostname()
+ .hostname
.split(':')
.collect::<Vec<&str>>()
.first()
Ok(Self::read_config_file()?)
}
-
- pub fn hostname(&self) -> String {
- self.hostname.to_owned().expect("No hostname given")
- }
- pub fn bind(&self) -> IpAddr {
- self.bind.expect("return bind address")
- }
- pub fn port(&self) -> u16 {
- self
- .port
- .unwrap_or_else(|| Settings::default().port.expect("missing port"))
- }
- pub fn tls_enabled(&self) -> bool {
- self.tls_enabled.unwrap_or_else(|| {
- Settings::default()
- .tls_enabled
- .expect("missing tls_enabled")
- })
- }
- pub fn jwt_secret(&self) -> String {
- self
- .jwt_secret
- .to_owned()
- .unwrap_or_else(|| Settings::default().jwt_secret.expect("missing jwt_secret"))
- }
- pub fn pictrs_url(&self) -> String {
- self
- .pictrs_url
- .to_owned()
- .unwrap_or_else(|| Settings::default().pictrs_url.expect("missing pictrs_url"))
- }
- pub fn iframely_url(&self) -> String {
- self.iframely_url.to_owned().unwrap_or_else(|| {
- Settings::default()
- .iframely_url
- .expect("missing iframely_url")
- })
- }
- pub fn actor_name_max_length(&self) -> usize {
- self.actor_name_max_length.unwrap_or_else(|| {
- Settings::default()
- .actor_name_max_length
- .expect("missing actor name length")
- })
- }
- pub fn database(&self) -> DatabaseConfig {
- self.database.to_owned().unwrap_or_default()
- }
- pub fn rate_limit(&self) -> RateLimitConfig {
- self.rate_limit.to_owned().unwrap_or_default()
- }
- pub fn federation(&self) -> FederationConfig {
- self.federation.to_owned().unwrap_or_default()
- }
- pub fn captcha(&self) -> CaptchaConfig {
- self.captcha.to_owned().unwrap_or_default()
- }
- pub fn email(&self) -> Option<EmailConfig> {
- self.email.to_owned()
- }
- pub fn setup(&self) -> Option<SetupConfig> {
- self.setup.to_owned()
- }
}
-use merge::Merge;
use serde::Deserialize;
-use std::net::IpAddr;
+use std::net::{IpAddr, Ipv4Addr};
-#[derive(Debug, Deserialize, Clone, Merge)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
pub struct Settings {
- pub(crate) database: Option<DatabaseConfig>,
- pub(crate) rate_limit: Option<RateLimitConfig>,
- pub(crate) federation: Option<FederationConfig>,
- pub(crate) hostname: Option<String>,
- pub(crate) bind: Option<IpAddr>,
- pub(crate) port: Option<u16>,
- pub(crate) tls_enabled: Option<bool>,
- pub(crate) jwt_secret: Option<String>,
- pub(crate) pictrs_url: Option<String>,
- pub(crate) iframely_url: Option<String>,
- pub(crate) captcha: Option<CaptchaConfig>,
- pub(crate) email: Option<EmailConfig>,
- pub(crate) setup: Option<SetupConfig>,
- pub(crate) additional_slurs: Option<String>,
- pub(crate) actor_name_max_length: Option<usize>,
+ #[serde(default)]
+ pub database: DatabaseConfig,
+ #[default(Some(RateLimitConfig::default()))]
+ pub rate_limit: Option<RateLimitConfig>,
+ #[default(FederationConfig::default())]
+ pub federation: FederationConfig,
+ #[default(CaptchaConfig::default())]
+ pub captcha: CaptchaConfig,
+ #[default(None)]
+ pub email: Option<EmailConfig>,
+ #[default(None)]
+ pub setup: Option<SetupConfig>,
+ #[default("unset")]
+ pub hostname: String,
+ #[default(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))]
+ pub bind: IpAddr,
+ #[default(8536)]
+ pub port: u16,
+ #[default(true)]
+ pub tls_enabled: bool,
+ #[default("changeme")]
+ pub jwt_secret: String,
+ #[default(None)]
+ pub pictrs_url: Option<String>,
+ #[default(None)]
+ pub iframely_url: Option<String>,
+ #[default(None)]
+ pub additional_slurs: Option<String>,
+ #[default(20)]
+ pub actor_name_max_length: usize,
}
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
pub struct CaptchaConfig {
+ #[default(false)]
pub enabled: bool,
+ #[default("medium")]
pub difficulty: String,
}
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
pub struct DatabaseConfig {
- pub(super) user: Option<String>,
+ #[default("lemmy")]
+ pub(super) user: String,
+ #[default("password")]
pub password: String,
+ #[default("localhost")]
pub host: String,
- pub(super) port: Option<i32>,
- pub(super) database: Option<String>,
- pub(super) pool_size: Option<u32>,
-}
-
-impl DatabaseConfig {
- pub fn user(&self) -> String {
- self
- .user
- .to_owned()
- .unwrap_or_else(|| DatabaseConfig::default().user.expect("missing user"))
- }
- pub fn port(&self) -> i32 {
- self
- .port
- .unwrap_or_else(|| DatabaseConfig::default().port.expect("missing port"))
- }
- pub fn database(&self) -> String {
- self.database.to_owned().unwrap_or_else(|| {
- DatabaseConfig::default()
- .database
- .expect("missing database")
- })
- }
- pub fn pool_size(&self) -> u32 {
- self.pool_size.unwrap_or_else(|| {
- DatabaseConfig::default()
- .pool_size
- .expect("missing pool_size")
- })
- }
+ #[default(5432)]
+ pub(super) port: i32,
+ #[default("lemmy")]
+ pub(super) database: String,
+ #[default(5)]
+ pub pool_size: u32,
}
#[derive(Debug, Deserialize, Clone)]
pub use_tls: bool,
}
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
pub struct FederationConfig {
+ #[default(false)]
pub enabled: bool,
+ #[default(None)]
pub allowed_instances: Option<Vec<String>>,
+ #[default(None)]
pub blocked_instances: Option<Vec<String>>,
- pub strict_allowlist: Option<bool>,
+ #[default(true)]
+ pub strict_allowlist: bool,
}
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
pub struct RateLimitConfig {
+ #[default(180)]
pub message: i32,
+ #[default(60)]
pub message_per_second: i32,
+ #[default(6)]
pub post: i32,
+ #[default(600)]
pub post_per_second: i32,
+ #[default(3)]
pub register: i32,
+ #[default(3600)]
pub register_per_second: i32,
+ #[default(6)]
pub image: i32,
+ #[default(3600)]
pub image_per_second: i32,
}
impl MentionData {
pub fn is_local(&self) -> bool {
- Settings::get().hostname().eq(&self.domain)
+ Settings::get().hostname.eq(&self.domain)
}
pub fn full_name(&self) -> String {
format!("@{}@{}", &self.name, &self.domain)
}
pub fn is_valid_actor_name(name: &str) -> bool {
- name.chars().count() <= Settings::get().actor_name_max_length()
+ name.chars().count() <= Settings::get().actor_name_max_length
&& VALID_ACTOR_NAME_REGEX.is_match(name)
}
!name.starts_with('@')
&& !name.starts_with('\u{200b}')
&& name.chars().count() >= 3
- && name.chars().count() <= Settings::get().actor_name_max_length()
+ && name.chars().count() <= Settings::get().actor_name_max_length
}
pub fn is_valid_matrix_id(matrix_id: &str) -> bool {
};
let manager = ConnectionManager::<PgConnection>::new(&db_url);
let pool = Pool::builder()
- .max_size(settings.database().pool_size())
+ .max_size(settings.database.pool_size)
.build(manager)
.unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
println!(
"Starting http server at {}:{}",
- settings.bind(),
- settings.port()
+ settings.bind, settings.port
);
let activity_queue = create_activity_queue();
.configure(nodeinfo::config)
.configure(webfinger::config)
})
- .bind((settings.bind(), settings.port()))?
+ .bind((settings.bind, settings.port))?
.run()
.await?;