Improve environment variable handling and reading (#5389)
* wip: better env var reading * move most env vars to env.rs * migrate more env vars * more migration * more migrations * More migration * 🦀 dotenvy is gone (almost) * 🦀 dotenvy is gone 🦀 * Fix mural source account env var handling * Remove defaults from admin key vars * dummy commit to update github pr * fix ci
This commit is contained in:
280
apps/labrinth/src/env.rs
Normal file
280
apps/labrinth/src/env.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
use std::{any::type_name, convert::Infallible, str::FromStr, sync::LazyLock};
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use eyre::{Context, eyre};
|
||||
use rust_decimal::Decimal;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
macro_rules! vars {
|
||||
(
|
||||
$(
|
||||
$field:ident: $ty:ty $(= $default:expr)?;
|
||||
)*
|
||||
) => {
|
||||
#[derive(Debug)]
|
||||
#[allow(
|
||||
non_snake_case,
|
||||
reason = "environment variables are UPPER_SNAKE_CASE",
|
||||
)]
|
||||
pub struct EnvVars {
|
||||
$(
|
||||
pub $field: $ty,
|
||||
)*
|
||||
}
|
||||
|
||||
impl EnvVars {
|
||||
pub fn from_env() -> eyre::Result<Self> {
|
||||
let mut err = eyre!("failed to read environment variables");
|
||||
|
||||
$(
|
||||
#[expect(
|
||||
non_snake_case,
|
||||
reason = "environment variables are UPPER_SNAKE_CASE",
|
||||
)]
|
||||
#[allow(
|
||||
unused_assignments,
|
||||
unused_mut,
|
||||
reason = "`default` is not used if there is no default",
|
||||
)]
|
||||
let $field: Option<$ty> = {
|
||||
let mut default = None::<$ty>;
|
||||
$( default = Some({ $default }.into()); )?
|
||||
|
||||
match parse_value::<$ty>(stringify!($field), default) {
|
||||
Ok(value) => Some(value),
|
||||
Err(source) => {
|
||||
err = err.wrap_err(eyre!("{source:#}"));
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
)*
|
||||
|
||||
Ok(EnvVars {
|
||||
$(
|
||||
$field: match $field {
|
||||
Some(value) => value,
|
||||
None => return Err(err),
|
||||
},
|
||||
)*
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub static ENV: LazyLock<EnvVars> = LazyLock::new(|| {
|
||||
EnvVars::from_env().unwrap_or_else(|err| panic!("{err:?}"))
|
||||
});
|
||||
|
||||
fn parse_value<T>(key: &str, default: Option<T>) -> eyre::Result<T>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
match (dotenvy::var(key), default) {
|
||||
(Ok(value), _) => value.parse::<T>().wrap_err_with(|| {
|
||||
eyre!("`{key}` is not a valid `{}`", type_name::<T>())
|
||||
}),
|
||||
(Err(_), Some(default)) => Ok(default),
|
||||
(Err(_), None) => Err(eyre!("`{key}` missing")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() -> eyre::Result<()> {
|
||||
EnvVars::from_env()?;
|
||||
LazyLock::force(&ENV);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Deref, DerefMut,
|
||||
)]
|
||||
pub struct Json<T: DeserializeOwned>(pub T);
|
||||
|
||||
impl<T: DeserializeOwned> FromStr for Json<T> {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_str(s).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deref, DerefMut,
|
||||
)]
|
||||
pub struct StringCsv(pub Vec<String>);
|
||||
|
||||
impl FromStr for StringCsv {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let v = s
|
||||
.split(',')
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Self(v))
|
||||
}
|
||||
}
|
||||
|
||||
vars! {
|
||||
SENTRY_ENVIRONMENT: String;
|
||||
SENTRY_TRACES_SAMPLE_RATE: f32;
|
||||
SITE_URL: String;
|
||||
CDN_URL: String;
|
||||
LABRINTH_ADMIN_KEY: String;
|
||||
LABRINTH_MEDAL_KEY: String;
|
||||
LABRINTH_EXTERNAL_NOTIFICATION_KEY: String;
|
||||
RATE_LIMIT_IGNORE_KEY: String;
|
||||
DATABASE_URL: String;
|
||||
MEILISEARCH_READ_ADDR: String;
|
||||
MEILISEARCH_WRITE_ADDRS: StringCsv;
|
||||
MEILISEARCH_KEY: String;
|
||||
REDIS_URL: String;
|
||||
BIND_ADDR: String;
|
||||
SELF_ADDR: String;
|
||||
|
||||
LOCAL_INDEX_INTERVAL: u64;
|
||||
VERSION_INDEX_INTERVAL: u64;
|
||||
|
||||
WHITELISTED_MODPACK_DOMAINS: Json<Vec<String>>;
|
||||
ALLOWED_CALLBACK_URLS: Json<Vec<String>>;
|
||||
ANALYTICS_ALLOWED_ORIGINS: Json<Vec<String>>;
|
||||
|
||||
// storage
|
||||
STORAGE_BACKEND: crate::file_hosting::FileHostKind;
|
||||
|
||||
// s3
|
||||
S3_PUBLIC_BUCKET_NAME: String = "";
|
||||
S3_PUBLIC_USES_PATH_STYLE_BUCKET: bool = false;
|
||||
S3_PUBLIC_REGION: String = "";
|
||||
S3_PUBLIC_URL: String = "";
|
||||
S3_PUBLIC_ACCESS_TOKEN: String = "";
|
||||
S3_PUBLIC_SECRET: String = "";
|
||||
|
||||
S3_PRIVATE_BUCKET_NAME: String = "";
|
||||
S3_PRIVATE_USES_PATH_STYLE_BUCKET: bool = false;
|
||||
S3_PRIVATE_REGION: String = "";
|
||||
S3_PRIVATE_URL: String = "";
|
||||
S3_PRIVATE_ACCESS_TOKEN: String = "";
|
||||
S3_PRIVATE_SECRET: String = "";
|
||||
|
||||
// local
|
||||
MOCK_FILE_PATH: String = "";
|
||||
|
||||
GITHUB_CLIENT_ID: String;
|
||||
GITHUB_CLIENT_SECRET: String;
|
||||
GITLAB_CLIENT_ID: String;
|
||||
GITLAB_CLIENT_SECRET: String;
|
||||
DISCORD_CLIENT_ID: String;
|
||||
DISCORD_CLIENT_SECRET: String;
|
||||
MICROSOFT_CLIENT_ID: String;
|
||||
MICROSOFT_CLIENT_SECRET: String;
|
||||
GOOGLE_CLIENT_ID: String;
|
||||
GOOGLE_CLIENT_SECRET: String;
|
||||
STEAM_API_KEY: String;
|
||||
|
||||
TREMENDOUS_API_URL: String;
|
||||
TREMENDOUS_API_KEY: String;
|
||||
TREMENDOUS_PRIVATE_KEY: String;
|
||||
|
||||
PAYPAL_API_URL: String;
|
||||
PAYPAL_WEBHOOK_ID: String;
|
||||
PAYPAL_CLIENT_ID: String;
|
||||
PAYPAL_CLIENT_SECRET: String;
|
||||
PAYPAL_NVP_USERNAME: String;
|
||||
PAYPAL_NVP_PASSWORD: String;
|
||||
PAYPAL_NVP_SIGNATURE: String;
|
||||
|
||||
PAYPAL_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||
BREX_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||
TREMENDOUS_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||
MURAL_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||
|
||||
HCAPTCHA_SECRET: String;
|
||||
|
||||
SMTP_USERNAME: String;
|
||||
SMTP_PASSWORD: String;
|
||||
SMTP_HOST: String;
|
||||
SMTP_PORT: u16;
|
||||
SMTP_TLS: String;
|
||||
SMTP_FROM_NAME: String;
|
||||
SMTP_FROM_ADDRESS: String;
|
||||
|
||||
SITE_VERIFY_EMAIL_PATH: String;
|
||||
SITE_RESET_PASSWORD_PATH: String;
|
||||
SITE_BILLING_PATH: String;
|
||||
|
||||
SENDY_URL: String;
|
||||
SENDY_LIST_ID: String;
|
||||
SENDY_API_KEY: String;
|
||||
|
||||
CLICKHOUSE_REPLICATED: bool;
|
||||
CLICKHOUSE_URL: String;
|
||||
CLICKHOUSE_USER: String;
|
||||
CLICKHOUSE_PASSWORD: String;
|
||||
CLICKHOUSE_DATABASE: String;
|
||||
|
||||
FLAME_ANVIL_URL: String;
|
||||
|
||||
GOTENBERG_URL: String;
|
||||
GOTENBERG_CALLBACK_BASE: String;
|
||||
GOTENBERG_TIMEOUT: u64;
|
||||
|
||||
STRIPE_API_KEY: String;
|
||||
STRIPE_WEBHOOK_SECRET: String;
|
||||
|
||||
ADITUDE_API_KEY: String;
|
||||
|
||||
PYRO_API_KEY: String;
|
||||
|
||||
BREX_API_URL: String;
|
||||
BREX_API_KEY: String;
|
||||
|
||||
DELPHI_URL: String;
|
||||
|
||||
AVALARA_1099_API_URL: String;
|
||||
AVALARA_1099_API_KEY: String;
|
||||
AVALARA_1099_API_TEAM_ID: String;
|
||||
AVALARA_1099_COMPANY_ID: String;
|
||||
|
||||
ANROK_API_URL: String;
|
||||
ANROK_API_KEY: String;
|
||||
|
||||
COMPLIANCE_PAYOUT_THRESHOLD: String;
|
||||
|
||||
PAYOUT_ALERT_SLACK_WEBHOOK: String;
|
||||
CLOUDFLARE_INTEGRATION: bool = false;
|
||||
|
||||
ARCHON_URL: String;
|
||||
|
||||
MURALPAY_API_URL: String;
|
||||
MURALPAY_API_KEY: String;
|
||||
MURALPAY_TRANSFER_API_KEY: String;
|
||||
MURALPAY_SOURCE_ACCOUNT_ID: muralpay::AccountId = muralpay::AccountId(uuid::Uuid::nil());
|
||||
|
||||
DEFAULT_AFFILIATE_REVENUE_SPLIT: Decimal;
|
||||
|
||||
DATABASE_ACQUIRE_TIMEOUT_MS: u64 = 30000u64;
|
||||
DATABASE_MIN_CONNECTIONS: u32 = 0u32;
|
||||
DATABASE_MAX_CONNECTIONS: u32 = 16u32;
|
||||
READONLY_DATABASE_URL: String = "";
|
||||
READONLY_DATABASE_MIN_CONNECTIONS: u32 = 0u32;
|
||||
READONLY_DATABASE_MAX_CONNECTIONS: u32 = 1u32;
|
||||
|
||||
REDIS_WAIT_TIMEOUT_MS: u64 = 15000u64;
|
||||
REDIS_MAX_CONNECTIONS: u32 = 10000u32;
|
||||
REDIS_MIN_CONNECTIONS: usize = 0usize;
|
||||
|
||||
SEARCH_OPERATION_TIMEOUT: u64 = 300000u64;
|
||||
|
||||
SMTP_REPLY_TO_NAME: String = "";
|
||||
SMTP_REPLY_TO_ADDRESS: String = "";
|
||||
|
||||
PUBLIC_DISCORD_WEBHOOK: String = "";
|
||||
MODERATION_SLACK_WEBHOOK: String = "";
|
||||
DELPHI_SLACK_WEBHOOK: String = "";
|
||||
|
||||
TREMENDOUS_CAMPAIGN_ID: String = "";
|
||||
}
|
||||
Reference in New Issue
Block a user