2020-06-17 16:57:28 +02:00
use crate ::search ::TokenizerKind as SearchTokenizer ;
2022-01-04 11:40:24 +01:00
use crate ::signups ::Strategy as SignupStrategy ;
2022-01-03 09:20:57 +01:00
use crate ::smtp ::{ SMTP_PORT , SUBMISSIONS_PORT , SUBMISSION_PORT } ;
2019-03-21 10:30:33 +01:00
use rocket ::config ::Limits ;
2019-03-22 19:51:36 +01:00
use rocket ::Config as RocketConfig ;
2021-01-11 21:27:52 +01:00
use std ::collections ::HashSet ;
2019-03-22 19:51:36 +01:00
use std ::env ::{ self , var } ;
2019-03-21 10:30:33 +01:00
2023-05-12 12:28:00 +02:00
#[ cfg(feature = " s3 " ) ]
use s3 ::{ Bucket , Region , creds ::Credentials } ;
2022-11-13 11:18:13 +01:00
2019-03-21 10:30:33 +01:00
#[ cfg(not(test)) ]
2019-03-22 19:51:36 +01:00
const DB_NAME : & str = " plume " ;
2019-03-21 10:30:33 +01:00
#[ cfg(test) ]
const DB_NAME : & str = " plume_tests " ;
pub struct Config {
pub base_url : String ,
pub database_url : String ,
2019-03-21 11:51:41 +01:00
pub db_name : & 'static str ,
2020-05-06 19:27:59 +02:00
pub db_max_size : Option < u32 > ,
pub db_min_idle : Option < u32 > ,
2022-01-04 11:40:24 +01:00
pub signup : SignupStrategy ,
2019-03-21 10:30:33 +01:00
pub search_index : String ,
2020-06-17 16:57:28 +02:00
pub search_tokenizers : SearchTokenizerConfig ,
2021-11-27 23:53:13 +01:00
pub rocket : Result < RocketConfig , InvalidRocketConfig > ,
2019-03-21 11:51:41 +01:00
pub logo : LogoConfig ,
2019-08-21 00:42:04 +02:00
pub default_theme : String ,
2019-10-28 22:28:28 +01:00
pub media_directory : String ,
2022-01-03 07:50:04 +01:00
pub mail : Option < MailConfig > ,
2020-10-04 12:18:22 +02:00
pub ldap : Option < LdapConfig > ,
2021-01-11 21:27:52 +01:00
pub proxy : Option < ProxyConfig > ,
2022-11-13 11:18:13 +01:00
pub s3 : Option < S3Config > ,
2021-01-11 21:27:52 +01:00
}
2022-11-13 11:18:13 +01:00
2021-01-11 21:27:52 +01:00
impl Config {
pub fn proxy ( & self ) -> Option < & reqwest ::Proxy > {
self . proxy . as_ref ( ) . map ( | p | & p . proxy )
}
2019-03-21 10:30:33 +01:00
}
2022-11-13 11:18:13 +01:00
fn string_to_bool ( val : & str , name : & str ) -> bool {
match val {
" 1 " | " true " | " TRUE " = > true ,
" 0 " | " false " | " FALSE " = > false ,
_ = > panic! ( " Invalid configuration: {} is not boolean " , name ) ,
}
}
2019-03-22 19:51:36 +01:00
#[ derive(Debug, Clone) ]
2021-11-27 23:53:13 +01:00
pub enum InvalidRocketConfig {
Env ,
Address ,
SecretKey ,
2019-03-21 10:30:33 +01:00
}
2021-11-27 23:53:13 +01:00
fn get_rocket_config ( ) -> Result < RocketConfig , InvalidRocketConfig > {
let mut c = RocketConfig ::active ( ) . map_err ( | _ | InvalidRocketConfig ::Env ) ? ;
2019-03-21 10:30:33 +01:00
let address = var ( " ROCKET_ADDRESS " ) . unwrap_or_else ( | _ | " localhost " . to_owned ( ) ) ;
2019-03-22 19:51:36 +01:00
let port = var ( " ROCKET_PORT " )
. ok ( )
. map ( | s | s . parse ::< u16 > ( ) . unwrap ( ) )
. unwrap_or ( 7878 ) ;
2021-11-27 23:53:13 +01:00
let secret_key = var ( " ROCKET_SECRET_KEY " ) . map_err ( | _ | InvalidRocketConfig ::SecretKey ) ? ;
2019-03-22 19:51:36 +01:00
let form_size = var ( " FORM_SIZE " )
2019-04-19 13:00:39 +02:00
. unwrap_or_else ( | _ | " 128 " . to_owned ( ) )
2019-03-22 19:51:36 +01:00
. parse ::< u64 > ( )
. unwrap ( ) ;
let activity_size = var ( " ACTIVITY_SIZE " )
. unwrap_or_else ( | _ | " 1024 " . to_owned ( ) )
. parse ::< u64 > ( )
. unwrap ( ) ;
c . set_address ( address )
2021-11-27 23:53:13 +01:00
. map_err ( | _ | InvalidRocketConfig ::Address ) ? ;
2019-03-21 10:30:33 +01:00
c . set_port ( port ) ;
2019-03-22 19:51:36 +01:00
c . set_secret_key ( secret_key )
2021-11-27 23:53:13 +01:00
. map_err ( | _ | InvalidRocketConfig ::SecretKey ) ? ;
2019-03-21 10:30:33 +01:00
2019-03-22 19:51:36 +01:00
c . set_limits (
Limits ::new ( )
. limit ( " forms " , form_size * 1024 )
. limit ( " json " , activity_size * 1024 ) ,
) ;
2019-03-21 10:30:33 +01:00
Ok ( c )
}
2019-03-21 11:51:41 +01:00
pub struct LogoConfig {
pub main : String ,
pub favicon : String ,
2019-03-26 12:45:17 +01:00
pub other : Vec < Icon > , //url, size, type
2019-03-21 11:51:41 +01:00
}
#[ derive(Serialize) ]
pub struct Icon {
pub src : String ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub sizes : Option < String > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
#[ serde(rename = " type " ) ]
pub image_type : Option < String > ,
}
impl Icon {
pub fn with_prefix ( & self , prefix : & str ) -> Icon {
Icon {
src : format ! ( " {}/{} " , prefix , self . src ) ,
sizes : self . sizes . clone ( ) ,
image_type : self . image_type . clone ( ) ,
}
}
}
impl Default for LogoConfig {
fn default ( ) -> Self {
let to_icon = | ( src , sizes , image_type ) : & ( & str , Option < & str > , Option < & str > ) | Icon {
src : str ::to_owned ( src ) ,
sizes : sizes . map ( str ::to_owned ) ,
2019-03-26 12:45:17 +01:00
image_type : image_type . map ( str ::to_owned ) ,
2019-03-21 11:51:41 +01:00
} ;
let icons = [
(
" icons/trwnh/feather/plumeFeather48.png " ,
Some ( " 48x48 " ) ,
Some ( " image/png " ) ,
) ,
(
" icons/trwnh/feather/plumeFeather72.png " ,
Some ( " 72x72 " ) ,
Some ( " image/png " ) ,
) ,
(
" icons/trwnh/feather/plumeFeather96.png " ,
Some ( " 96x96 " ) ,
Some ( " image/png " ) ,
) ,
(
" icons/trwnh/feather/plumeFeather144.png " ,
Some ( " 144x144 " ) ,
Some ( " image/png " ) ,
) ,
(
" icons/trwnh/feather/plumeFeather160.png " ,
Some ( " 160x160 " ) ,
Some ( " image/png " ) ,
) ,
(
" icons/trwnh/feather/plumeFeather192.png " ,
Some ( " 192x192 " ) ,
Some ( " image/png " ) ,
) ,
(
" icons/trwnh/feather/plumeFeather256.png " ,
Some ( " 256x256 " ) ,
Some ( " image/png " ) ,
) ,
(
" icons/trwnh/feather/plumeFeather512.png " ,
Some ( " 512x512 " ) ,
Some ( " image/png " ) ,
) ,
2019-03-26 12:45:17 +01:00
( " icons/trwnh/feather/plumeFeather.svg " , None , None ) ,
]
. iter ( )
. map ( to_icon )
. collect ( ) ;
2019-03-21 11:51:41 +01:00
let custom_main = var ( " PLUME_LOGO " ) . ok ( ) ;
2019-03-26 12:45:17 +01:00
let custom_favicon = var ( " PLUME_LOGO_FAVICON " )
. ok ( )
. or_else ( | | custom_main . clone ( ) ) ;
2019-03-21 11:51:41 +01:00
let other = if let Some ( main ) = custom_main . clone ( ) {
2021-11-27 23:53:13 +01:00
let ext = | path : & str | match path . rsplit_once ( '.' ) . map ( | x | x . 1 ) {
2019-03-21 11:51:41 +01:00
Some ( " png " ) = > Some ( " image/png " . to_owned ( ) ) ,
2019-03-26 12:45:17 +01:00
Some ( " jpg " ) | Some ( " jpeg " ) = > Some ( " image/jpeg " . to_owned ( ) ) ,
2019-03-21 11:51:41 +01:00
Some ( " svg " ) = > Some ( " image/svg+xml " . to_owned ( ) ) ,
Some ( " webp " ) = > Some ( " image/webp " . to_owned ( ) ) ,
_ = > None ,
} ;
let mut custom_icons = env ::vars ( )
2019-03-26 12:45:17 +01:00
. filter_map ( | ( var , val ) | {
2021-03-27 19:46:37 +01:00
var . strip_prefix ( " PLUME_LOGO_ " )
. map ( | size | ( size . to_owned ( ) , val ) )
2019-03-26 12:45:17 +01:00
} )
. filter_map ( | ( var , val ) | var . parse ::< u64 > ( ) . ok ( ) . map ( | var | ( var , val ) ) )
. map ( | ( dim , src ) | Icon {
2019-03-21 11:51:41 +01:00
image_type : ext ( & src ) ,
src ,
sizes : Some ( format! ( " {} x {} " , dim , dim ) ) ,
} )
. collect ::< Vec < _ > > ( ) ;
custom_icons . push ( Icon {
image_type : ext ( & main ) ,
src : main ,
sizes : None ,
} ) ;
custom_icons
} else {
icons
} ;
LogoConfig {
2019-03-26 12:45:17 +01:00
main : custom_main
. unwrap_or_else ( | | " icons/trwnh/feather/plumeFeather256.png " . to_owned ( ) ) ,
favicon : custom_favicon . unwrap_or_else ( | | {
" icons/trwnh/feather-filled/plumeFeatherFilled64.png " . to_owned ( )
} ) ,
2019-03-21 11:51:41 +01:00
other ,
}
}
}
2020-06-17 16:57:28 +02:00
pub struct SearchTokenizerConfig {
pub tag_tokenizer : SearchTokenizer ,
pub content_tokenizer : SearchTokenizer ,
pub property_tokenizer : SearchTokenizer ,
}
impl SearchTokenizerConfig {
pub fn init ( ) -> Self {
use SearchTokenizer ::* ;
match var ( " SEARCH_LANG " ) . ok ( ) . as_deref ( ) {
Some ( " ja " ) = > {
#[ cfg(not(feature = " search-lindera " )) ]
panic! ( " You need build Plume with search-lindera feature, or execute it with SEARCH_TAG_TOKENIZER=ngram and SEARCH_CONTENT_TOKENIZER=ngram to enable Japanese search feature " ) ;
#[ cfg(feature = " search-lindera " ) ]
Self {
tag_tokenizer : Self ::determine_tokenizer ( " SEARCH_TAG_TOKENIZER " , Lindera ) ,
content_tokenizer : Self ::determine_tokenizer (
" SEARCH_CONTENT_TOKENIZER " ,
Lindera ,
) ,
property_tokenizer : Ngram ,
}
}
_ = > Self {
tag_tokenizer : Self ::determine_tokenizer ( " SEARCH_TAG_TOKENIZER " , Whitespace ) ,
content_tokenizer : Self ::determine_tokenizer ( " SEARCH_CONTENT_TOKENIZER " , Simple ) ,
property_tokenizer : Ngram ,
} ,
}
}
fn determine_tokenizer ( var_name : & str , default : SearchTokenizer ) -> SearchTokenizer {
use SearchTokenizer ::* ;
match var ( var_name ) . ok ( ) . as_deref ( ) {
Some ( " simple " ) = > Simple ,
Some ( " ngram " ) = > Ngram ,
Some ( " whitespace " ) = > Whitespace ,
Some ( " lindera " ) = > {
#[ cfg(not(feature = " search-lindera " )) ]
panic! ( " You need build Plume with search-lindera feature to use Lindera tokenizer " ) ;
#[ cfg(feature = " search-lindera " ) ]
Lindera
}
_ = > default ,
}
}
}
2022-01-03 07:50:04 +01:00
pub struct MailConfig {
pub server : String ,
2022-01-03 09:20:57 +01:00
pub port : u16 ,
2022-01-03 07:50:04 +01:00
pub helo_name : String ,
pub username : String ,
pub password : String ,
}
fn get_mail_config ( ) -> Option < MailConfig > {
Some ( MailConfig {
server : env ::var ( " MAIL_SERVER " ) . ok ( ) ? ,
2022-01-03 09:20:57 +01:00
port : env ::var ( " MAIL_PORT " ) . map_or ( SUBMISSIONS_PORT , | port | match port . as_str ( ) {
" smtp " = > SMTP_PORT ,
" submissions " = > SUBMISSIONS_PORT ,
" submission " = > SUBMISSION_PORT ,
number = > number
. parse ( )
. expect ( r # "MAIL_PORT must be "smtp", "submissions", "submission" or an integer."# ) ,
} ) ,
2022-01-03 07:50:04 +01:00
helo_name : env ::var ( " MAIL_HELO_NAME " ) . unwrap_or_else ( | _ | " localhost " . to_owned ( ) ) ,
username : env ::var ( " MAIL_USER " ) . ok ( ) ? ,
password : env ::var ( " MAIL_PASSWORD " ) . ok ( ) ? ,
} )
}
2020-10-04 12:18:22 +02:00
pub struct LdapConfig {
pub addr : String ,
pub base_dn : String ,
pub tls : bool ,
pub user_name_attr : String ,
pub mail_attr : String ,
}
fn get_ldap_config ( ) -> Option < LdapConfig > {
let addr = var ( " LDAP_ADDR " ) . ok ( ) ;
let base_dn = var ( " LDAP_BASE_DN " ) . ok ( ) ;
2020-10-08 20:24:03 +02:00
match ( addr , base_dn ) {
( Some ( addr ) , Some ( base_dn ) ) = > {
let tls = var ( " LDAP_TLS " ) . unwrap_or_else ( | _ | " false " . to_owned ( ) ) ;
2022-11-13 11:18:13 +01:00
let tls = string_to_bool ( & tls , " LDAP_TLS " ) ;
2020-10-08 20:24:03 +02:00
let user_name_attr = var ( " LDAP_USER_NAME_ATTR " ) . unwrap_or_else ( | _ | " cn " . to_owned ( ) ) ;
let mail_attr = var ( " LDAP_USER_MAIL_ATTR " ) . unwrap_or_else ( | _ | " mail " . to_owned ( ) ) ;
Some ( LdapConfig {
addr ,
base_dn ,
tls ,
user_name_attr ,
mail_attr ,
} )
}
( None , None ) = > None ,
( _ , _ ) = > {
panic! ( " Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set " )
}
2020-10-04 12:18:22 +02:00
}
}
2021-01-11 21:27:52 +01:00
pub struct ProxyConfig {
pub url : reqwest ::Url ,
pub only_domains : Option < HashSet < String > > ,
pub proxy : reqwest ::Proxy ,
}
fn get_proxy_config ( ) -> Option < ProxyConfig > {
let url : reqwest ::Url = var ( " PROXY_URL " ) . ok ( ) ? . parse ( ) . expect ( " Invalid PROXY_URL " ) ;
let proxy_url = url . clone ( ) ;
let only_domains : Option < HashSet < String > > = var ( " PROXY_DOMAINS " )
. ok ( )
2021-01-15 15:01:37 +01:00
. map ( | ods | ods . split ( ',' ) . map ( str ::to_owned ) . collect ( ) ) ;
2021-01-11 21:27:52 +01:00
let proxy = if let Some ( ref only_domains ) = only_domains {
let only_domains = only_domains . clone ( ) ;
reqwest ::Proxy ::custom ( move | url | {
if let Some ( domain ) = url . domain ( ) {
if only_domains . contains ( domain )
| | only_domains
. iter ( )
2021-01-15 15:17:00 +01:00
. any ( | target | domain . ends_with ( & format! ( " . {} " , target ) ) )
2021-01-11 21:27:52 +01:00
{
Some ( proxy_url . clone ( ) )
} else {
None
}
} else {
None
}
} )
} else {
2021-01-15 17:13:45 +01:00
reqwest ::Proxy ::all ( proxy_url ) . expect ( " Invalid PROXY_URL " )
2021-01-11 21:27:52 +01:00
} ;
Some ( ProxyConfig {
url ,
only_domains ,
proxy ,
} )
}
2022-11-13 11:18:13 +01:00
pub struct S3Config {
pub bucket : String ,
pub access_key_id : String ,
pub access_key_secret : String ,
// region? If not set, default to us-east-1
pub region : String ,
// hostname for s3. If not set, default to $region.amazonaws.com
pub hostname : String ,
// may be useful when using self hosted s3. Won't work with recent AWS buckets
pub path_style : bool ,
2023-05-15 12:35:39 +02:00
// http or https
2022-11-13 11:18:13 +01:00
pub protocol : String ,
// download directly from s3 to user, wihout going through Plume. Require public read on bucket
pub direct_download : bool ,
2023-05-12 15:40:36 +02:00
// use this hostname for downloads, can be used with caching proxy in front of s3 (expected to
// be reachable through https)
2022-11-13 11:18:13 +01:00
pub alias : Option < String > ,
}
impl S3Config {
2023-05-12 12:28:00 +02:00
#[ cfg(feature = " s3 " ) ]
2022-11-13 11:18:13 +01:00
pub fn get_bucket ( & self ) -> Bucket {
let region = Region ::Custom {
region : self . region . clone ( ) ,
endpoint : format ! ( " {}://{} " , self . protocol , self . hostname ) ,
} ;
let credentials = Credentials {
access_key : Some ( self . access_key_id . clone ( ) ) ,
secret_key : Some ( self . access_key_secret . clone ( ) ) ,
security_token : None ,
session_token : None ,
2023-05-12 12:12:32 +02:00
expiration : None ,
2022-11-13 11:18:13 +01:00
} ;
2023-05-12 12:12:32 +02:00
let bucket = Bucket ::new ( & self . bucket , region , credentials ) . unwrap ( ) ;
2022-11-13 11:18:13 +01:00
if self . path_style {
2023-05-12 12:12:32 +02:00
bucket . with_path_style ( )
2022-11-13 11:18:13 +01:00
} else {
2023-05-12 12:12:32 +02:00
bucket
}
2022-11-13 11:18:13 +01:00
}
}
fn get_s3_config ( ) -> Option < S3Config > {
let bucket = var ( " S3_BUCKET " ) . ok ( ) ;
let access_key_id = var ( " AWS_ACCESS_KEY_ID " ) . ok ( ) ;
let access_key_secret = var ( " AWS_SECRET_ACCESS_KEY " ) . ok ( ) ;
if bucket . is_none ( ) & & access_key_id . is_none ( ) & & access_key_secret . is_none ( ) {
return None ;
}
2023-05-12 12:28:00 +02:00
#[ cfg(not(feature = " s3 " )) ]
panic! ( " S3 support is not enabled in this build " ) ;
2022-11-13 11:18:13 +01:00
2023-05-12 12:28:00 +02:00
#[ cfg(feature = " s3 " ) ]
{
if bucket . is_none ( ) | | access_key_id . is_none ( ) | | access_key_secret . is_none ( ) {
panic! ( " Invalid S3 configuration: some required values are set, but not others " ) ;
}
let bucket = bucket . unwrap ( ) ;
let access_key_id = access_key_id . unwrap ( ) ;
let access_key_secret = access_key_secret . unwrap ( ) ;
2022-11-13 11:18:13 +01:00
2023-05-12 12:28:00 +02:00
let region = var ( " S3_REGION " ) . unwrap_or_else ( | _ | " us-east-1 " . to_owned ( ) ) ;
let hostname = var ( " S3_HOSTNAME " ) . unwrap_or_else ( | _ | format! ( " {} .amazonaws.com " , region ) ) ;
let protocol = var ( " S3_PROTOCOL " ) . unwrap_or_else ( | _ | " https " . to_owned ( ) ) ;
if protocol ! = " http " & & protocol ! = " https " {
panic! ( " Invalid S3 configuration: invalid protocol {} " , protocol ) ;
}
let path_style = var ( " S3_PATH_STYLE " ) . unwrap_or_else ( | _ | " false " . to_owned ( ) ) ;
let path_style = string_to_bool ( & path_style , " S3_PATH_STYLE " ) ;
let direct_download = var ( " S3_DIRECT_DOWNLOAD " ) . unwrap_or_else ( | _ | " false " . to_owned ( ) ) ;
let direct_download = string_to_bool ( & direct_download , " S3_DIRECT_DOWNLOAD " ) ;
let alias = var ( " S3_ALIAS_HOST " ) . ok ( ) ;
2023-05-12 15:40:36 +02:00
if direct_download & & protocol = = " http " & & alias . is_none ( ) {
panic! ( " S3 direct download is disabled because bucket is accessed through plain HTTP. Use HTTPS or set an alias hostname (S3_ALIAS_HOST). " ) ;
}
2023-05-12 12:28:00 +02:00
Some ( S3Config {
bucket ,
access_key_id ,
access_key_secret ,
region ,
hostname ,
protocol ,
path_style ,
direct_download ,
alias ,
} )
}
2022-11-13 11:18:13 +01:00
}
2019-03-21 10:30:33 +01:00
lazy_static! {
pub static ref CONFIG : Config = Config {
base_url : var ( " BASE_URL " ) . unwrap_or_else ( | _ | format! (
2019-03-22 19:51:36 +01:00
" 127.0.0.1:{} " ,
2019-04-06 17:41:57 +02:00
var ( " ROCKET_PORT " ) . unwrap_or_else ( | _ | " 7878 " . to_owned ( ) )
2019-03-22 19:51:36 +01:00
) ) ,
2019-03-21 10:30:33 +01:00
db_name : DB_NAME ,
2020-05-06 19:27:59 +02:00
db_max_size : var ( " DB_MAX_SIZE " ) . map_or ( None , | s | Some (
s . parse ::< u32 > ( )
. expect ( " Couldn't parse DB_MAX_SIZE into u32 " )
) ) ,
db_min_idle : var ( " DB_MIN_IDLE " ) . map_or ( None , | s | Some (
s . parse ::< u32 > ( )
. expect ( " Couldn't parse DB_MIN_IDLE into u32 " )
) ) ,
2022-01-04 11:40:24 +01:00
signup : var ( " SIGNUP " ) . map_or ( SignupStrategy ::default ( ) , | s | s . parse ( ) . unwrap ( ) ) ,
2019-03-21 10:30:33 +01:00
#[ cfg(feature = " postgres " ) ]
2019-03-22 19:51:36 +01:00
database_url : var ( " DATABASE_URL " )
. unwrap_or_else ( | _ | format! ( " postgres://plume:plume@localhost/ {} " , DB_NAME ) ) ,
2019-03-21 10:30:33 +01:00
#[ cfg(feature = " sqlite " ) ]
2019-03-22 19:51:36 +01:00
database_url : var ( " DATABASE_URL " ) . unwrap_or_else ( | _ | format! ( " {} .sqlite " , DB_NAME ) ) ,
2019-03-21 10:30:33 +01:00
search_index : var ( " SEARCH_INDEX " ) . unwrap_or_else ( | _ | " search_index " . to_owned ( ) ) ,
2020-06-17 16:57:28 +02:00
search_tokenizers : SearchTokenizerConfig ::init ( ) ,
2019-03-21 11:51:41 +01:00
rocket : get_rocket_config ( ) ,
logo : LogoConfig ::default ( ) ,
2019-08-21 00:42:04 +02:00
default_theme : var ( " DEFAULT_THEME " ) . unwrap_or_else ( | _ | " default-light " . to_owned ( ) ) ,
2019-10-28 22:28:28 +01:00
media_directory : var ( " MEDIA_UPLOAD_DIRECTORY " )
. unwrap_or_else ( | _ | " static/media " . to_owned ( ) ) ,
2022-01-03 07:50:04 +01:00
mail : get_mail_config ( ) ,
2020-10-04 12:18:22 +02:00
ldap : get_ldap_config ( ) ,
2021-01-11 21:27:52 +01:00
proxy : get_proxy_config ( ) ,
2022-11-13 11:18:13 +01:00
s3 : get_s3_config ( ) ,
2019-03-21 10:30:33 +01:00
} ;
}