2024-12-16 01:49:04 +03:00
|
|
|
#![feature(let_chains, pattern, path_add_extension)]
|
2024-04-18 04:05:38 +03:00
|
|
|
|
2024-05-08 23:03:10 +03:00
|
|
|
mod app;
|
2024-04-18 04:05:38 +03:00
|
|
|
mod config;
|
|
|
|
mod error;
|
2024-08-13 15:53:18 +03:00
|
|
|
mod helpers;
|
2024-04-18 04:05:38 +03:00
|
|
|
mod markdown_render;
|
2024-06-13 23:43:03 +03:00
|
|
|
mod platform;
|
2024-04-18 04:05:38 +03:00
|
|
|
mod post;
|
2024-04-20 23:02:23 +03:00
|
|
|
mod ranged_i128_visitor;
|
2024-08-13 16:06:33 +03:00
|
|
|
mod serve_dir_included;
|
2024-04-30 11:41:35 +03:00
|
|
|
mod systemtime_as_secs;
|
2024-08-13 15:53:18 +03:00
|
|
|
mod templates;
|
2024-04-18 04:05:38 +03:00
|
|
|
|
|
|
|
use std::future::IntoFuture;
|
|
|
|
use std::net::SocketAddr;
|
|
|
|
use std::process::exit;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
use color_eyre::eyre::{self, Context};
|
2024-12-16 01:49:04 +03:00
|
|
|
use config::Engine;
|
2024-04-18 04:05:38 +03:00
|
|
|
use tokio::net::TcpListener;
|
2024-08-13 15:53:18 +03:00
|
|
|
use tokio::sync::RwLock;
|
2024-04-18 04:05:38 +03:00
|
|
|
use tokio::task::JoinSet;
|
2024-08-13 15:53:18 +03:00
|
|
|
use tokio::time::Instant;
|
2024-04-30 11:41:35 +03:00
|
|
|
use tokio::{select, signal};
|
2024-04-18 04:05:38 +03:00
|
|
|
use tokio_util::sync::CancellationToken;
|
|
|
|
use tracing::level_filters::LevelFilter;
|
2024-08-13 15:53:18 +03:00
|
|
|
use tracing::{debug, error, info, info_span, warn, Instrument};
|
2024-05-08 23:03:10 +03:00
|
|
|
use tracing_subscriber::layer::SubscriberExt;
|
|
|
|
use tracing_subscriber::{util::SubscriberInitExt, EnvFilter};
|
2024-04-18 04:05:38 +03:00
|
|
|
|
2024-05-08 23:03:10 +03:00
|
|
|
use crate::app::AppState;
|
2024-12-15 23:06:58 +03:00
|
|
|
use crate::post::cache::{load_cache, CacheGuard, CACHE_VERSION};
|
2024-12-16 01:49:04 +03:00
|
|
|
use crate::post::{Blag, MarkdownPosts, PostManager};
|
2024-08-13 15:53:18 +03:00
|
|
|
use crate::templates::new_registry;
|
|
|
|
use crate::templates::watcher::watch_templates;
|
2024-04-18 04:05:38 +03:00
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> eyre::Result<()> {
|
|
|
|
color_eyre::install()?;
|
2024-08-13 15:53:18 +03:00
|
|
|
let reg = tracing_subscriber::registry();
|
|
|
|
#[cfg(feature = "tokio-console")]
|
2024-12-16 01:49:04 +03:00
|
|
|
let reg = reg.with(console_subscriber::spawn());
|
2024-08-13 15:53:18 +03:00
|
|
|
#[cfg(not(feature = "tokio-console"))]
|
|
|
|
let reg = reg.with(
|
|
|
|
EnvFilter::builder()
|
|
|
|
.with_default_directive(LevelFilter::INFO.into())
|
|
|
|
.from_env_lossy(),
|
|
|
|
);
|
|
|
|
reg.with(tracing_subscriber::fmt::layer()).init();
|
2024-04-18 04:05:38 +03:00
|
|
|
|
2024-05-08 23:03:10 +03:00
|
|
|
let config = Arc::new(
|
|
|
|
config::load()
|
|
|
|
.await
|
|
|
|
.context("couldn't load configuration")?,
|
|
|
|
);
|
2024-04-18 04:05:38 +03:00
|
|
|
|
2024-05-01 18:25:01 +03:00
|
|
|
let socket_addr = SocketAddr::new(config.http.host, config.http.port);
|
|
|
|
|
2024-04-18 04:05:38 +03:00
|
|
|
let mut tasks = JoinSet::new();
|
2024-04-30 11:41:35 +03:00
|
|
|
let cancellation_token = CancellationToken::new();
|
2024-04-18 04:05:38 +03:00
|
|
|
|
2024-08-13 15:53:18 +03:00
|
|
|
let start = Instant::now();
|
|
|
|
// NOTE: use tokio::task::spawn_blocking if this ever turns into a concurrent task
|
2024-08-13 17:02:33 +03:00
|
|
|
let mut reg = new_registry(&config.dirs.custom_templates)
|
|
|
|
.context("failed to create handlebars registry")?;
|
2024-08-13 15:53:18 +03:00
|
|
|
reg.register_helper("date", Box::new(helpers::date));
|
|
|
|
reg.register_helper("duration", Box::new(helpers::duration));
|
|
|
|
debug!(duration = ?start.elapsed(), "registered all templates");
|
|
|
|
|
2024-12-15 23:14:21 +03:00
|
|
|
let registry = Arc::new(RwLock::new(reg));
|
2024-08-13 15:53:18 +03:00
|
|
|
|
|
|
|
debug!("setting up watcher");
|
2024-12-02 20:07:54 +03:00
|
|
|
let watcher_token = cancellation_token.child_token();
|
2024-08-13 15:53:18 +03:00
|
|
|
tasks.spawn(
|
2024-08-13 17:02:33 +03:00
|
|
|
watch_templates(
|
|
|
|
config.dirs.custom_templates.clone(),
|
|
|
|
watcher_token.clone(),
|
2024-12-15 23:14:21 +03:00
|
|
|
registry.clone(),
|
2024-08-13 17:02:33 +03:00
|
|
|
)
|
|
|
|
.instrument(info_span!("custom_template_watcher")),
|
2024-08-13 15:53:18 +03:00
|
|
|
);
|
|
|
|
|
2024-12-16 14:58:02 +03:00
|
|
|
let cache = if config.cache.enable {
|
|
|
|
if config.cache.persistence && tokio::fs::try_exists(&config.cache.file).await? {
|
|
|
|
info!("loading cache from file");
|
|
|
|
let mut cache = load_cache(&config.cache).await.unwrap_or_else(|err| {
|
|
|
|
error!("failed to load cache: {}", err);
|
|
|
|
info!("using empty cache");
|
|
|
|
Default::default()
|
|
|
|
});
|
|
|
|
|
|
|
|
if cache.version() < CACHE_VERSION {
|
|
|
|
warn!("cache version changed, clearing cache");
|
|
|
|
cache = Default::default();
|
|
|
|
};
|
2024-12-15 23:06:58 +03:00
|
|
|
|
2024-12-16 14:58:02 +03:00
|
|
|
Some(cache)
|
|
|
|
} else {
|
|
|
|
Some(Default::default())
|
2024-12-15 23:06:58 +03:00
|
|
|
}
|
2024-12-16 14:58:02 +03:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
.map(|cache| CacheGuard::new(cache, config.cache.clone()))
|
|
|
|
.map(Arc::new);
|
|
|
|
|
|
|
|
let posts: Arc<dyn PostManager + Send + Sync> = match config.engine {
|
|
|
|
Engine::Markdown => Arc::new(MarkdownPosts::new(Arc::clone(&config), cache.clone()).await?),
|
2024-12-16 01:49:04 +03:00
|
|
|
Engine::Blag => Arc::new(Blag::new(
|
|
|
|
config.dirs.posts.clone().into(),
|
2024-12-16 02:56:43 +03:00
|
|
|
config.blag.bin.clone().into(),
|
2024-12-16 14:58:02 +03:00
|
|
|
cache.clone(),
|
2024-12-16 01:49:04 +03:00
|
|
|
)),
|
|
|
|
};
|
2024-12-02 20:07:54 +03:00
|
|
|
|
2024-05-08 23:03:10 +03:00
|
|
|
if config.cache.enable && config.cache.cleanup {
|
2024-08-13 15:53:18 +03:00
|
|
|
if let Some(millis) = config.cache.cleanup_interval {
|
2024-05-09 11:30:18 +03:00
|
|
|
let posts = Arc::clone(&posts);
|
2024-04-30 11:41:35 +03:00
|
|
|
let token = cancellation_token.child_token();
|
|
|
|
debug!("setting up cleanup task");
|
|
|
|
tasks.spawn(async move {
|
2024-08-13 15:53:18 +03:00
|
|
|
let mut interval = tokio::time::interval(Duration::from_millis(millis));
|
2024-04-30 11:41:35 +03:00
|
|
|
loop {
|
|
|
|
select! {
|
2024-08-13 15:53:18 +03:00
|
|
|
_ = token.cancelled() => break Ok(()),
|
2024-04-30 11:41:35 +03:00
|
|
|
_ = interval.tick() => {
|
2024-05-09 11:30:18 +03:00
|
|
|
posts.cleanup().await
|
2024-04-30 11:41:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2024-05-09 11:30:18 +03:00
|
|
|
posts.cleanup().await;
|
2024-04-30 11:41:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-02 20:07:54 +03:00
|
|
|
let state = AppState {
|
|
|
|
config: Arc::clone(&config),
|
2024-12-15 23:14:21 +03:00
|
|
|
posts,
|
|
|
|
templates: registry,
|
2024-12-02 20:07:54 +03:00
|
|
|
};
|
2024-05-14 10:11:41 +03:00
|
|
|
let app = app::new(&config).with_state(state.clone());
|
2024-04-18 04:05:38 +03:00
|
|
|
|
2024-05-01 18:25:01 +03:00
|
|
|
let listener = TcpListener::bind(socket_addr)
|
2024-04-18 04:05:38 +03:00
|
|
|
.await
|
2024-05-01 18:25:01 +03:00
|
|
|
.with_context(|| format!("couldn't listen on {}", socket_addr))?;
|
2024-04-18 04:05:38 +03:00
|
|
|
let local_addr = listener
|
|
|
|
.local_addr()
|
2024-04-18 19:17:33 +03:00
|
|
|
.context("couldn't get socket address")?;
|
2024-04-18 04:05:38 +03:00
|
|
|
info!("listening on http://{}", local_addr);
|
|
|
|
|
|
|
|
let sigint = signal::ctrl_c();
|
2024-06-13 23:43:03 +03:00
|
|
|
let sigterm = platform::sigterm();
|
2024-04-18 04:05:38 +03:00
|
|
|
|
2024-04-30 11:41:35 +03:00
|
|
|
let axum_token = cancellation_token.child_token();
|
2024-04-18 04:05:38 +03:00
|
|
|
|
|
|
|
let mut server = axum::serve(
|
|
|
|
listener,
|
|
|
|
app.into_make_service_with_connect_info::<SocketAddr>(),
|
|
|
|
)
|
|
|
|
.with_graceful_shutdown(async move { axum_token.cancelled().await })
|
|
|
|
.into_future();
|
|
|
|
|
|
|
|
tokio::select! {
|
|
|
|
result = &mut server => {
|
2024-04-18 19:17:33 +03:00
|
|
|
result.context("failed to serve app")?;
|
2024-04-18 04:05:38 +03:00
|
|
|
},
|
|
|
|
_ = sigint => {
|
|
|
|
info!("received SIGINT, exiting gracefully");
|
|
|
|
},
|
|
|
|
_ = sigterm => {
|
|
|
|
info!("received SIGTERM, exiting gracefully");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let cleanup = async move {
|
|
|
|
// stop tasks
|
2024-04-30 11:41:35 +03:00
|
|
|
cancellation_token.cancel();
|
2024-04-18 19:17:33 +03:00
|
|
|
server.await.context("failed to serve app")?;
|
2024-04-18 04:05:38 +03:00
|
|
|
while let Some(task) = tasks.join_next().await {
|
2024-08-13 15:53:18 +03:00
|
|
|
let res = task.context("failed to join task")?;
|
|
|
|
if let Err(err) = res {
|
|
|
|
error!("task failed with error: {err}");
|
|
|
|
}
|
2024-04-18 04:05:38 +03:00
|
|
|
}
|
|
|
|
|
2024-05-08 23:03:10 +03:00
|
|
|
drop(state);
|
2024-04-18 04:05:38 +03:00
|
|
|
Ok::<(), color_eyre::Report>(())
|
|
|
|
};
|
|
|
|
|
|
|
|
let sigint = signal::ctrl_c();
|
2024-06-13 23:43:03 +03:00
|
|
|
let sigterm = platform::sigterm();
|
2024-04-18 04:05:38 +03:00
|
|
|
|
|
|
|
tokio::select! {
|
|
|
|
result = cleanup => {
|
2024-04-18 19:17:33 +03:00
|
|
|
result.context("cleanup failed, oh well")?;
|
2024-04-18 04:05:38 +03:00
|
|
|
},
|
|
|
|
_ = sigint => {
|
|
|
|
warn!("received second signal, exiting");
|
|
|
|
exit(1);
|
|
|
|
},
|
|
|
|
_ = sigterm => {
|
|
|
|
warn!("received second signal, exiting");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|