1use super::backend::Backend;
3use super::{Error, Result};
4use colored::Colorize;
5use serde::{Deserialize, Serialize};
6use sos_backend::BackendTarget;
7use sos_core::{AccountId, Paths};
8use sos_database::{migrations::migrate_client, open_file};
9use sos_vfs as vfs;
10use std::{
11 collections::HashSet,
12 net::{IpAddr, Ipv4Addr, SocketAddr},
13 path::{Path, PathBuf},
14};
15use url::Url;
16
17#[derive(Default, Debug, Serialize, Deserialize)]
19#[serde(default)]
20pub struct ServerConfig {
21 pub storage: StorageConfig,
23
24 pub log: LogConfig,
26
27 pub access: Option<AccessControlConfig>,
29
30 pub net: NetworkConfig,
32
33 #[serde(skip)]
36 file: Option<PathBuf>,
37}
38
39#[derive(Debug, Default, Clone, Serialize, Deserialize)]
44pub struct AccessControlConfig {
45 pub allow: Option<HashSet<AccountId>>,
47 pub deny: Option<HashSet<AccountId>>,
49}
50
51impl AccessControlConfig {
52 pub fn is_allowed_access(&self, account_id: &AccountId) -> bool {
55 let has_definitions = self.allow.is_some() || self.deny.is_some();
56 if has_definitions {
57 match (&self.deny, &self.allow) {
58 (Some(deny), None) => {
59 if deny.iter().any(|a| a == account_id) {
60 return false;
61 }
62 true
63 }
64 (None, Some(allow)) => {
65 if allow.iter().any(|a| a == account_id) {
66 return true;
67 }
68 false
69 }
70 (Some(deny), Some(allow)) => {
71 if allow.iter().any(|a| a == account_id) {
72 return true;
73 }
74 if deny.iter().any(|a| a == account_id) {
75 return false;
76 }
77 false
78 }
79 _ => true,
80 }
81 } else {
82 true
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct LogConfig {
90 pub directory: PathBuf,
92 pub name: String,
94 pub level: String,
96}
97
98impl Default for LogConfig {
99 fn default() -> Self {
100 Self {
101 directory: PathBuf::from("logs"),
102 name: "sos-server.log".to_string(),
103 level: "sos_server=info".to_string(),
104 }
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(default)]
111pub struct NetworkConfig {
112 pub bind: SocketAddr,
114
115 pub ssl: Option<SslConfig>,
117
118 pub cors: Option<CorsConfig>,
120}
121
122impl Default for NetworkConfig {
123 fn default() -> Self {
124 Self {
125 bind: SocketAddr::new(
126 IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
127 5053,
128 ),
129 ssl: Default::default(),
130 cors: None,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "lowercase", untagged)]
138pub enum SslConfig {
139 Tls(TlsConfig),
141 #[cfg(feature = "acme")]
143 Acme(AcmeConfig),
144}
145
146#[derive(Debug, Default, Clone, Serialize, Deserialize)]
148pub struct TlsConfig {
149 pub cert: PathBuf,
151 pub key: PathBuf,
153}
154
155#[cfg(feature = "acme")]
157#[derive(Debug, Default, Clone, Serialize, Deserialize)]
158pub struct AcmeConfig {
159 pub cache: PathBuf,
161 pub domains: Vec<String>,
163 pub email: Vec<String>,
165 pub production: bool,
167}
168
169#[derive(Debug, Clone, Default, Serialize, Deserialize)]
171pub struct CorsConfig {
172 pub origins: Vec<Url>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct StorageConfig {
179 pub path: PathBuf,
181
182 #[serde(skip_serializing_if = "Option::is_none")]
187 pub database: Option<String>,
188
189 #[serde(skip)]
191 pub database_uri: Option<UriOrPath>,
192}
193
194#[derive(Debug, Clone)]
196pub enum UriOrPath {
197 Uri(http::Uri),
199 Path(PathBuf),
201}
202
203impl UriOrPath {
204 pub fn as_uri_string(&self) -> String {
206 match self {
207 UriOrPath::Uri(uri) => uri.to_string(),
208 UriOrPath::Path(path) => format!("file:{}", path.display()),
209 }
210 }
211}
212
213impl StorageConfig {
214 #[doc(hidden)]
216 fn set_database_uri(
217 &mut self,
218 db: &str,
219 base_dir: impl AsRef<Path>,
220 ) -> Result<()> {
221 let uri = if db.starts_with("file:") {
222 UriOrPath::Uri(db.parse()?)
223 } else {
224 let path = PathBuf::from(db);
225 if path.is_relative() {
226 let path = base_dir.as_ref().join(path);
227 if !path.exists() {
228 std::fs::File::create(&path)?;
229 }
230 UriOrPath::Path(path.canonicalize()?)
231 } else {
232 UriOrPath::Path(path)
233 }
234 };
235
236 self.database_uri = Some(uri);
237 Ok(())
238 }
239}
240
241impl Default for StorageConfig {
242 fn default() -> Self {
243 Self {
244 path: PathBuf::from("."),
245 database: None,
246 database_uri: None,
247 }
248 }
249}
250
251impl ServerConfig {
252 pub async fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
254 if !vfs::try_exists(path.as_ref()).await? {
255 return Err(Error::NotFile(path.as_ref().to_path_buf()));
256 }
257
258 let contents = vfs::read_to_string(path.as_ref()).await?;
259 let mut config: ServerConfig = toml::from_str(&contents)?;
260 config.file = Some(path.as_ref().canonicalize()?);
261
262 let dir = config.directory();
263
264 if config.log.directory.is_relative() {
265 config.log.directory = dir.join(&config.log.directory);
266 if !config.log.directory.exists() {
267 vfs::create_dir_all(&config.log.directory).await?;
268 }
269 config.log.directory = config.log.directory.canonicalize()?;
270 }
271
272 if let Some(SslConfig::Tls(tls)) = &mut config.net.ssl {
273 if tls.cert.is_relative() {
274 tls.cert = dir.join(&tls.cert);
275 }
276 if tls.key.is_relative() {
277 tls.key = dir.join(&tls.key);
278 }
279
280 tls.cert = tls.cert.canonicalize()?;
281 tls.key = tls.key.canonicalize()?;
282 }
283
284 if let Some(db) = &config.storage.database.clone() {
285 config.storage.set_database_uri(db, config.directory())?;
286 }
287
288 Ok(config)
289 }
290
291 pub fn set_bind_address(&mut self, addr: SocketAddr) {
293 self.net.bind = addr;
294 }
295
296 pub fn bind_address(&self) -> &SocketAddr {
298 &self.net.bind
299 }
300
301 fn directory(&self) -> PathBuf {
303 self.file
304 .as_ref()
305 .unwrap()
306 .parent()
307 .map(|p| p.to_path_buf())
308 .unwrap()
309 }
310
311 pub async fn backend(&self) -> Result<Backend> {
313 let dir = self.directory();
315
316 let path = &self.storage.path;
317 let path = if path.is_relative() {
318 dir.join(path)
319 } else {
320 path.to_owned()
321 };
322 let path = path.canonicalize()?;
323
324 let paths = Paths::new_server(&path);
325
326 let target = if let Some(uri) = &self.storage.database_uri {
327 tracing::debug!(
328 database_uri = % uri.as_uri_string(),
329 "server::db",
330 );
331 let mut client = open_file(uri.as_uri_string()).await?;
332 tracing::debug!("server::db::migrate",);
333 let report = migrate_client(&mut client).await?;
334 for migration in report.applied_migrations() {
335 tracing::debug!(
336 name = %migration.name(),
337 version = %migration.version(),
338 "server::db::migration",);
339
340 println!(
341 "Migration {} {}",
342 migration.name().green(),
343 format!("v{}", migration.version()).green(),
344 );
345 }
346 BackendTarget::Database(paths.clone(), client)
347 } else {
348 BackendTarget::FileSystem(paths.clone())
349 };
350
351 let mut backend = Backend::new(paths, target);
352 backend.load_accounts().await?;
353 Ok(backend)
354 }
355}