forked from alemi/upub
feat: server config file and default generator
This commit is contained in:
parent
ef97000c23
commit
15746c699f
6 changed files with 125 additions and 20 deletions
|
@ -24,6 +24,8 @@ chrono = { version = "0.4", features = ["serde"] }
|
|||
uuid = { version = "1.8", features = ["v4"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_default = "0.1"
|
||||
serde-inline-default = "0.2"
|
||||
mdhtml = { path = "mdhtml", features = ["markdown"] }
|
||||
jrd = "0.1"
|
||||
tracing = "0.1"
|
||||
|
@ -44,6 +46,7 @@ sea-orm-migration = { version = "0.12", optional = true }
|
|||
mastodon-async-entities = { version = "1.1.0", optional = true }
|
||||
time = { version = "0.3", features = ["serde"], optional = true }
|
||||
async-recursion = "1.1"
|
||||
toml = "0.8.12"
|
||||
|
||||
[features]
|
||||
default = ["migrations", "cli"]
|
||||
|
|
74
src/config.rs
Normal file
74
src/config.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
|
||||
|
||||
#[serde_inline_default::serde_inline_default]
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, serde_default::DefaultFromSerde)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
pub instance: InstanceConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub datasource: DatasourceConfig,
|
||||
|
||||
// TODO should i move app keys here?
|
||||
}
|
||||
|
||||
#[serde_inline_default::serde_inline_default]
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, serde_default::DefaultFromSerde)]
|
||||
pub struct DatasourceConfig {
|
||||
#[serde_inline_default("sqlite://./upub.db".into())]
|
||||
pub connection_string: String,
|
||||
|
||||
#[serde_inline_default(4)]
|
||||
pub max_connections: u32,
|
||||
|
||||
#[serde_inline_default(1)]
|
||||
pub min_connections: u32,
|
||||
|
||||
#[serde_inline_default(300u64)]
|
||||
pub connect_timeout_seconds: u64,
|
||||
|
||||
#[serde_inline_default(300u64)]
|
||||
pub acquire_timeout_seconds: u64,
|
||||
|
||||
#[serde_inline_default(1u64)]
|
||||
pub slow_query_warn_seconds: u64,
|
||||
|
||||
#[serde_inline_default(true)]
|
||||
pub slow_query_warn_enable: bool,
|
||||
}
|
||||
|
||||
#[serde_inline_default::serde_inline_default]
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, serde_default::DefaultFromSerde)]
|
||||
pub struct InstanceConfig {
|
||||
#[serde_inline_default("μpub".into())]
|
||||
pub name: String,
|
||||
|
||||
#[serde_inline_default("micro social network, federated".into())]
|
||||
pub description: String,
|
||||
|
||||
#[serde_inline_default("upub.social".into())]
|
||||
pub domain: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub contact: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub frontend: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
impl Config {
|
||||
pub fn load(path: Option<std::path::PathBuf>) -> Self {
|
||||
let Some(cfg_path) = path else { return Config::default() };
|
||||
match std::fs::read_to_string(cfg_path) {
|
||||
Ok(x) => match toml::from_str(&x) {
|
||||
Ok(cfg) => return cfg,
|
||||
Err(e) => tracing::error!("failed parsing config file: {e}"),
|
||||
},
|
||||
Err(e) => tracing::error!("failed reading config file: {e}"),
|
||||
}
|
||||
Config::default()
|
||||
}
|
||||
}
|
45
src/main.rs
45
src/main.rs
|
@ -3,7 +3,6 @@ mod model;
|
|||
mod routes;
|
||||
|
||||
mod errors;
|
||||
|
||||
mod config;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
|
@ -14,6 +13,8 @@ mod migrations;
|
|||
|
||||
#[cfg(feature = "migrations")]
|
||||
use sea_orm_migration::MigratorTrait;
|
||||
use std::path::PathBuf;
|
||||
use config::Config;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use sea_orm::{ConnectOptions, Database};
|
||||
|
@ -30,13 +31,17 @@ struct Args {
|
|||
/// command to run
|
||||
command: Mode,
|
||||
|
||||
#[arg(short = 'd', long = "db", default_value = "sqlite://./upub.db")]
|
||||
/// database connection uri
|
||||
database: String,
|
||||
/// path to config file, leave empty to not use any
|
||||
#[arg(short, long)]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
#[arg(short = 'D', long, default_value = "http://localhost:3000")]
|
||||
/// instance base domain, for AP ids
|
||||
domain: String,
|
||||
#[arg(long = "db")]
|
||||
/// database connection uri, overrides config value
|
||||
database: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
/// instance base domain, for AP ids, overrides config value
|
||||
domain: Option<String>,
|
||||
|
||||
#[arg(long, default_value_t=false)]
|
||||
/// run with debug level tracing
|
||||
|
@ -48,6 +53,9 @@ enum Mode {
|
|||
/// run fediverse server
|
||||
Serve,
|
||||
|
||||
/// print current or default configuration
|
||||
Config,
|
||||
|
||||
#[cfg(feature = "migrations")]
|
||||
/// apply database migrations
|
||||
Migrate,
|
||||
|
@ -71,11 +79,24 @@ async fn main() {
|
|||
.with_max_level(if args.debug { tracing::Level::DEBUG } else { tracing::Level::INFO })
|
||||
.init();
|
||||
|
||||
let config = Config::load(args.config);
|
||||
|
||||
let database = args.database.unwrap_or(config.datasource.connection_string.clone());
|
||||
let domain = args.domain.unwrap_or(config.instance.domain.clone());
|
||||
|
||||
// TODO can i do connectoptions.into() or .connect() and skip these ugly bindings?
|
||||
let mut opts = ConnectOptions::new(&args.database);
|
||||
let mut opts = ConnectOptions::new(&database);
|
||||
|
||||
opts
|
||||
.sqlx_logging_level(tracing::log::LevelFilter::Debug);
|
||||
.sqlx_logging_level(tracing::log::LevelFilter::Debug)
|
||||
.max_connections(config.datasource.max_connections)
|
||||
.min_connections(config.datasource.min_connections)
|
||||
.acquire_timeout(std::time::Duration::from_secs(config.datasource.acquire_timeout_seconds))
|
||||
.connect_timeout(std::time::Duration::from_secs(config.datasource.connect_timeout_seconds))
|
||||
.sqlx_slow_statements_logging_settings(
|
||||
if config.datasource.slow_query_warn_enable { tracing::log::LevelFilter::Warn } else { tracing::log::LevelFilter::Off },
|
||||
std::time::Duration::from_secs(config.datasource.slow_query_warn_seconds)
|
||||
);
|
||||
|
||||
let db = Database::connect(opts)
|
||||
.await.expect("error connecting to db");
|
||||
|
@ -88,11 +109,13 @@ async fn main() {
|
|||
|
||||
#[cfg(feature = "cli")]
|
||||
Mode::Cli { command } =>
|
||||
cli::run(command, db, args.domain)
|
||||
cli::run(command, db, domain, config)
|
||||
.await.expect("failed running cli task"),
|
||||
|
||||
Mode::Config => println!("{}", toml::to_string_pretty(&config).expect("failed serializing config")),
|
||||
|
||||
Mode::Serve => {
|
||||
let ctx = server::Context::new(db, args.domain)
|
||||
let ctx = server::Context::new(db, domain, config)
|
||||
.await.expect("failed creating server context");
|
||||
|
||||
use routes::activitypub::ActivityPubRouter;
|
||||
|
|
|
@ -22,8 +22,8 @@ pub async fn view(
|
|||
serde_json::Value::new_object()
|
||||
.set_id(Some(&url!(ctx, "")))
|
||||
.set_actor_type(Some(apb::ActorType::Application))
|
||||
.set_name(Some("μpub"))
|
||||
.set_summary(Some("micro social network, federated"))
|
||||
.set_name(Some(&ctx.cfg().instance.name))
|
||||
.set_summary(Some(&ctx.cfg().instance.description))
|
||||
.set_inbox(apb::Node::link(url!(ctx, "/inbox")))
|
||||
.set_outbox(apb::Node::link(url!(ctx, "/outbox")))
|
||||
.set_published(Some(ctx.app().created))
|
||||
|
|
|
@ -8,9 +8,9 @@ pub async fn get(
|
|||
) -> crate::Result<Json<mastodon_async_entities::instance::Instance>> {
|
||||
Ok(Json(mastodon_async_entities::instance::Instance {
|
||||
uri: ctx.domain().to_string(),
|
||||
title: "μpub".to_string(),
|
||||
description: "micro social network, federated".to_string(),
|
||||
email: "me@alemi.dev".to_string(),
|
||||
title: ctx.cfg().instance.name.clone(),
|
||||
description: ctx.cfg().instance.description.clone(),
|
||||
email: ctx.cfg().instance.contact.as_deref().unwrap_or_default().to_string(),
|
||||
version: crate::VERSION.to_string(),
|
||||
urls: None,
|
||||
stats: None,
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{collections::BTreeSet, sync::Arc};
|
|||
use openssl::rsa::Rsa;
|
||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
||||
|
||||
use crate::{model, server::fetcher::Fetcher};
|
||||
use crate::{config::Config, model, server::fetcher::Fetcher};
|
||||
|
||||
use super::dispatcher::Dispatcher;
|
||||
|
||||
|
@ -12,6 +12,7 @@ use super::dispatcher::Dispatcher;
|
|||
pub struct Context(Arc<ContextInner>);
|
||||
struct ContextInner {
|
||||
db: DatabaseConnection,
|
||||
config: Config,
|
||||
domain: String,
|
||||
protocol: String,
|
||||
dispatcher: Dispatcher,
|
||||
|
@ -30,7 +31,7 @@ macro_rules! url {
|
|||
impl Context {
|
||||
|
||||
// TODO slim constructor down, maybe make a builder?
|
||||
pub async fn new(db: DatabaseConnection, mut domain: String) -> crate::Result<Self> {
|
||||
pub async fn new(db: DatabaseConnection, mut domain: String, config: Config) -> crate::Result<Self> {
|
||||
let protocol = if domain.starts_with("http://")
|
||||
{ "http://" } else { "https://" }.to_string();
|
||||
if domain.ends_with('/') {
|
||||
|
@ -71,7 +72,7 @@ impl Context {
|
|||
.await?;
|
||||
|
||||
Ok(Context(Arc::new(ContextInner {
|
||||
db, domain, protocol, app, dispatcher,
|
||||
db, domain, protocol, app, dispatcher, config,
|
||||
relays: BTreeSet::from_iter(relays.into_iter()),
|
||||
})))
|
||||
}
|
||||
|
@ -84,6 +85,10 @@ impl Context {
|
|||
&self.0.db
|
||||
}
|
||||
|
||||
pub fn cfg(&self) -> &Config {
|
||||
&self.0.config
|
||||
}
|
||||
|
||||
pub fn domain(&self) -> &str {
|
||||
&self.0.domain
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue