use sos_logs::Logger;
use sos_server::{LogConfig, Result};
#[tokio::main]
async fn main() -> Result<()> {
if let Err(e) = cli::run().await {
sos_cli_helpers::messages::fail(e.to_string());
}
Ok(())
}
fn init_default_subscriber() {
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("RUST_LOG").unwrap_or_else(|_| "sos=info".into()),
))
.with(tracing_subscriber::fmt::layer().without_time())
.init();
}
fn init_server_logs(config: &LogConfig) -> Logger {
if !config.directory.exists() {
std::fs::create_dir(&config.directory)
.expect("create logs directory");
}
let logger =
Logger::new_dir(config.directory.clone(), config.name.clone());
logger
.init_subscriber(Some(config.level.clone()))
.expect("initialize tracing subscriber");
logger
}
mod cli {
use crate::{init_default_subscriber, init_server_logs, Result};
use clap::{CommandFactory, Parser, Subcommand};
use sos_cli_helpers::CommandTree;
use sos_server::ServerConfig;
use std::{net::SocketAddr, path::PathBuf};
#[derive(Parser, Debug)]
#[clap(name = "sos-server", author, version, about, long_about = None)]
pub struct SosServer {
#[clap(subcommand)]
cmd: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Init {
#[clap(short, long)]
path: Option<PathBuf>,
#[clap(short, long)]
bind: Option<SocketAddr>,
#[clap(short, long)]
cache: Option<PathBuf>,
#[clap(short, long)]
domains: Vec<String>,
#[clap(short, long)]
email: Vec<String>,
#[clap(long)]
production: bool,
config: PathBuf,
},
Start {
#[clap(short, long)]
bind: Option<SocketAddr>,
config: PathBuf,
},
}
pub async fn run() -> Result<()> {
if std::env::var("SOS_CLI_JSON").ok().is_some() {
let cmd = SosServer::command();
let tree: CommandTree = (&cmd).into();
serde_json::to_writer_pretty(std::io::stdout(), &tree)?;
std::process::exit(0);
}
let args = SosServer::parse();
match args.cmd {
Command::Init {
config,
path,
bind,
cache,
domains,
email,
production,
} => {
init_default_subscriber();
service::init(
config, path, bind, cache, domains, email, production,
)
.await?;
}
Command::Start { bind, config } => {
let mut config = ServerConfig::load(&config).await?;
if let Some(addr) = bind {
config.set_bind_address(addr);
}
let _logger = init_server_logs(&config.log);
service::start(config).await?;
}
}
Ok(())
}
mod service {
use axum_server::Handle;
use sos_server::{
Error, Result, Server, ServerConfig, SslConfig, State,
StorageConfig,
};
use sos_vfs as vfs;
use std::{net::SocketAddr, path::PathBuf, sync::Arc};
use tokio::sync::RwLock;
pub async fn init(
output: PathBuf,
mut path: Option<PathBuf>,
bind: Option<SocketAddr>,
cache: Option<PathBuf>,
domains: Vec<String>,
email: Vec<String>,
production: bool,
) -> Result<()> {
if vfs::try_exists(&output).await? {
return Err(Error::FileExists(output));
}
let mut config: ServerConfig = Default::default();
if let Some(path) = path.take() {
config.storage = StorageConfig {
path,
database: None,
database_uri: None,
};
}
if let Some(addr) = bind {
config.set_bind_address(addr);
}
#[cfg(feature = "acme")]
if let (Some(cache), false) = (cache, domains.is_empty()) {
config.net.ssl =
Some(SslConfig::Acme(sos_server::AcmeConfig {
cache,
domains,
email,
production,
}))
}
let content = toml::to_string_pretty(&config)?;
vfs::write(output, content.as_bytes()).await?;
Ok(())
}
pub async fn start(config: ServerConfig) -> Result<()> {
let backend = config.backend().await?;
#[cfg(feature = "audit")]
{
let provider = sos_backend::audit::new_fs_provider(
backend.paths().audit_file().to_owned(),
);
sos_backend::audit::init_providers(vec![provider]);
}
let state = Arc::new(RwLock::new(State::new(config)));
let handle = Handle::new();
let server = Server::new().await?;
server
.start(state, Arc::new(RwLock::new(backend)), handle)
.await?;
Ok(())
}
}
}