2021-01-02 23:11:18 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate tracing;
|
|
|
|
|
2022-03-22 00:14:14 +00:00
|
|
|
use axum::{
|
|
|
|
body,
|
|
|
|
extract::Extension,
|
2022-03-22 12:32:30 +00:00
|
|
|
http::header::{self, HeaderValue, CONTENT_TYPE},
|
2022-03-22 00:14:14 +00:00
|
|
|
response::{Html, Response},
|
2022-03-22 12:32:30 +00:00
|
|
|
routing::{get, get_service},
|
2022-03-22 00:14:14 +00:00
|
|
|
Router,
|
|
|
|
};
|
2020-09-19 15:33:46 +00:00
|
|
|
use color_eyre::eyre::Result;
|
2022-03-22 00:14:14 +00:00
|
|
|
use hyper::StatusCode;
|
2020-07-16 19:32:30 +00:00
|
|
|
use prometheus::{Encoder, TextEncoder};
|
2022-03-22 12:32:30 +00:00
|
|
|
use sdnotify::SdNotify;
|
2022-03-22 00:14:14 +00:00
|
|
|
use std::{
|
2022-03-22 12:32:30 +00:00
|
|
|
env, io,
|
2022-03-22 00:14:14 +00:00
|
|
|
net::{IpAddr, SocketAddr},
|
|
|
|
str::FromStr,
|
|
|
|
sync::Arc,
|
|
|
|
};
|
2021-04-02 02:30:45 +00:00
|
|
|
use tokio::net::UnixListener;
|
2022-03-22 00:14:14 +00:00
|
|
|
use tower_http::{
|
|
|
|
services::{ServeDir, ServeFile},
|
|
|
|
set_header::SetResponseHeaderLayer,
|
|
|
|
trace::TraceLayer,
|
|
|
|
};
|
2020-07-16 19:32:30 +00:00
|
|
|
|
|
|
|
pub mod app;
|
|
|
|
pub mod handlers;
|
|
|
|
pub mod post;
|
|
|
|
pub mod signalboost;
|
|
|
|
|
2022-03-22 00:14:14 +00:00
|
|
|
mod domainsocket;
|
|
|
|
use domainsocket::*;
|
2020-07-16 19:32:30 +00:00
|
|
|
|
2022-03-22 12:32:30 +00:00
|
|
|
use crate::app::poke;
|
|
|
|
|
2020-07-16 19:32:30 +00:00
|
|
|
const APPLICATION_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
|
|
|
|
2022-03-22 00:14:14 +00:00
|
|
|
async fn healthcheck() -> &'static str {
|
|
|
|
"OK"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn cache_header(_: &Response) -> Option<header::HeaderValue> {
|
|
|
|
Some(header::HeaderValue::from_static(
|
|
|
|
"public, max-age=3600, stale-if-error=60",
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn webmention_header(_: &Response) -> Option<HeaderValue> {
|
|
|
|
Some(header::HeaderValue::from_static(
|
|
|
|
r#"<https://mi.within.website/api/webmention/accept>; rel="webmention""#,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clacks_header(_: &Response) -> Option<HeaderValue> {
|
|
|
|
Some(HeaderValue::from_static("Ashlynn"))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn hacker_header(_: &Response) -> Option<HeaderValue> {
|
|
|
|
Some(header::HeaderValue::from_static(
|
|
|
|
"If you are reading this, check out /signalboost to find people for your team",
|
|
|
|
))
|
2020-07-16 19:32:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> Result<()> {
|
2020-09-19 15:33:46 +00:00
|
|
|
color_eyre::install()?;
|
2020-07-16 19:32:30 +00:00
|
|
|
let _ = kankyo::init();
|
2020-10-02 22:36:57 +00:00
|
|
|
tracing_subscriber::fmt::init();
|
2021-01-02 23:11:18 +00:00
|
|
|
info!("starting up commit {}", env!("GITHUB_SHA"));
|
2020-07-16 19:32:30 +00:00
|
|
|
|
2020-11-18 17:18:24 +00:00
|
|
|
let state = Arc::new(
|
|
|
|
app::init(
|
2022-03-22 00:14:14 +00:00
|
|
|
env::var("CONFIG_FNAME")
|
2020-11-18 17:18:24 +00:00
|
|
|
.unwrap_or("./config.dhall".into())
|
|
|
|
.as_str()
|
|
|
|
.into(),
|
|
|
|
)
|
|
|
|
.await?,
|
|
|
|
);
|
2020-07-16 19:32:30 +00:00
|
|
|
|
2022-03-22 00:14:14 +00:00
|
|
|
let middleware = tower::ServiceBuilder::new()
|
|
|
|
.layer(TraceLayer::new_for_http())
|
|
|
|
.layer(Extension(state.clone()))
|
|
|
|
.layer(SetResponseHeaderLayer::overriding(
|
|
|
|
header::CACHE_CONTROL,
|
|
|
|
cache_header,
|
|
|
|
))
|
|
|
|
.layer(SetResponseHeaderLayer::appending(
|
|
|
|
header::LINK,
|
|
|
|
webmention_header,
|
|
|
|
))
|
|
|
|
.layer(SetResponseHeaderLayer::appending(
|
|
|
|
header::HeaderName::from_static("x-clacks-overhead"),
|
|
|
|
clacks_header,
|
|
|
|
))
|
|
|
|
.layer(SetResponseHeaderLayer::overriding(
|
|
|
|
header::HeaderName::from_static("x-hacker"),
|
|
|
|
hacker_header,
|
|
|
|
));
|
2020-07-16 19:32:30 +00:00
|
|
|
|
2022-03-22 00:14:14 +00:00
|
|
|
let app = Router::new()
|
|
|
|
// meta
|
|
|
|
.route("/.within/health", get(healthcheck))
|
|
|
|
.route(
|
|
|
|
"/.within/website.within.xesite/new_post",
|
|
|
|
get(handlers::feeds::new_post),
|
2021-01-15 03:36:34 +00:00
|
|
|
)
|
2022-03-22 00:14:14 +00:00
|
|
|
.route("/jsonfeed", get(go_vanity))
|
|
|
|
.route("/metrics", get(metrics))
|
|
|
|
.route(
|
|
|
|
"/sw.js",
|
2022-03-22 12:32:30 +00:00
|
|
|
get_service(ServeFile::new("./static/js/sw.js")).handle_error(
|
|
|
|
|err: io::Error| async move {
|
2022-03-22 00:14:14 +00:00
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
format!("unhandled internal server error: {}", err),
|
|
|
|
)
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/.well-known/assetlinks.json",
|
2022-03-22 12:32:30 +00:00
|
|
|
get_service(ServeFile::new("./static/assetlinks.json")).handle_error(
|
|
|
|
|err: io::Error| async move {
|
2022-03-22 00:14:14 +00:00
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
format!("unhandled internal server error: {}", err),
|
|
|
|
)
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/robots.txt",
|
2022-03-22 12:32:30 +00:00
|
|
|
get_service(ServeFile::new("./static/robots.txt")).handle_error(
|
|
|
|
|err: io::Error| async move {
|
2022-03-22 00:14:14 +00:00
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
format!("unhandled internal server error: {}", err),
|
|
|
|
)
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/favicon.ico",
|
2022-03-22 12:32:30 +00:00
|
|
|
get_service(ServeFile::new("./static/favicon/favicon.ico")).handle_error(
|
|
|
|
|err: io::Error| async move {
|
2022-03-22 00:14:14 +00:00
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
format!("unhandled internal server error: {}", err),
|
|
|
|
)
|
2022-03-22 12:32:30 +00:00
|
|
|
},
|
|
|
|
),
|
2022-03-22 00:14:14 +00:00
|
|
|
)
|
|
|
|
// static pages
|
|
|
|
.route("/", get(handlers::index))
|
|
|
|
.route("/contact", get(handlers::contact))
|
|
|
|
.route("/feeds", get(handlers::feeds))
|
|
|
|
.route("/resume", get(handlers::resume))
|
|
|
|
.route("/patrons", get(handlers::patrons))
|
|
|
|
.route("/signalboost", get(handlers::signalboost))
|
|
|
|
// feeds
|
|
|
|
.route("/blog.json", get(handlers::feeds::jsonfeed))
|
|
|
|
.route("/blog.atom", get(handlers::feeds::atom))
|
|
|
|
.route("/blog.rss", get(handlers::feeds::rss))
|
|
|
|
// blog
|
|
|
|
.route("/blog", get(handlers::blog::index))
|
|
|
|
.route("/blog/", get(handlers::blog::index))
|
|
|
|
.route("/blog/:name", get(handlers::blog::post_view))
|
|
|
|
.route("/blog/series", get(handlers::blog::series))
|
|
|
|
.route("/blog/series/:series", get(handlers::blog::series_view))
|
|
|
|
// gallery
|
|
|
|
.route("/gallery", get(handlers::gallery::index))
|
|
|
|
.route("/gallery/", get(handlers::gallery::index))
|
|
|
|
.route("/gallery/:name", get(handlers::gallery::post_view))
|
|
|
|
// talks
|
|
|
|
.route("/talks", get(handlers::talks::index))
|
|
|
|
.route("/talks/", get(handlers::talks::index))
|
|
|
|
.route("/talks/:name", get(handlers::talks::post_view))
|
|
|
|
// junk google wants
|
|
|
|
.route("/sitemap.xml", get(handlers::feeds::sitemap))
|
|
|
|
// static files
|
|
|
|
.nest(
|
|
|
|
"/css",
|
2022-03-22 12:32:30 +00:00
|
|
|
get_service(ServeDir::new("./css")).handle_error(|err: io::Error| async move {
|
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
format!("unhandled internal server error: {}", err),
|
|
|
|
)
|
|
|
|
}),
|
2022-03-22 00:14:14 +00:00
|
|
|
)
|
|
|
|
.nest(
|
|
|
|
"/static",
|
2022-03-22 12:32:30 +00:00
|
|
|
get_service(ServeDir::new("./static")).handle_error(|err: io::Error| async move {
|
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
format!("unhandled internal server error: {}", err),
|
|
|
|
)
|
|
|
|
}),
|
2022-03-22 00:14:14 +00:00
|
|
|
)
|
|
|
|
.layer(middleware);
|
2020-07-16 19:32:30 +00:00
|
|
|
|
2021-03-07 19:38:10 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
{
|
2022-03-22 12:32:30 +00:00
|
|
|
match SdNotify::from_env() {
|
2021-03-07 19:38:10 +00:00
|
|
|
Ok(ref mut n) => {
|
|
|
|
// shitty heuristic for detecting if we're running in prod
|
|
|
|
tokio::spawn(async {
|
2022-03-22 12:32:30 +00:00
|
|
|
if let Err(why) = poke::the_cloud().await {
|
2021-03-07 19:38:10 +00:00
|
|
|
error!("Unable to poke the cloud: {}", why);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
n.notify_ready().map_err(|why| {
|
|
|
|
error!("can't signal readiness to systemd: {}", why);
|
2021-01-17 02:38:22 +00:00
|
|
|
why
|
|
|
|
})?;
|
2021-03-07 19:38:10 +00:00
|
|
|
n.set_status(format!("hosting {} posts", state.clone().everything.len()))
|
|
|
|
.map_err(|why| {
|
|
|
|
error!("can't signal status to systemd: {}", why);
|
|
|
|
why
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
Err(why) => error!("not running under systemd with Type=notify: {}", why),
|
2021-01-17 02:38:22 +00:00
|
|
|
}
|
2021-03-07 19:38:10 +00:00
|
|
|
}
|
2021-01-17 02:38:22 +00:00
|
|
|
|
2021-04-02 02:30:45 +00:00
|
|
|
match std::env::var("SOCKPATH") {
|
|
|
|
Ok(sockpath) => {
|
2022-03-22 00:26:02 +00:00
|
|
|
let _ = std::fs::remove_file(&sockpath);
|
2022-03-22 00:14:14 +00:00
|
|
|
let uds = UnixListener::bind(&sockpath)?;
|
|
|
|
axum::Server::builder(ServerAccept { uds })
|
|
|
|
.serve(app.into_make_service_with_connect_info::<UdsConnectInfo, _>())
|
|
|
|
.await?;
|
2021-04-02 02:30:45 +00:00
|
|
|
}
|
|
|
|
Err(_) => {
|
2022-03-22 00:14:14 +00:00
|
|
|
let addr: SocketAddr = (
|
|
|
|
IpAddr::from_str(&env::var("HOST").unwrap_or("::".into()))?,
|
|
|
|
env::var("PORT").unwrap_or("3030".into()).parse::<u16>()?,
|
|
|
|
)
|
|
|
|
.into();
|
|
|
|
info!("listening on {}", addr);
|
|
|
|
axum::Server::bind(&addr)
|
|
|
|
.serve(app.into_make_service())
|
|
|
|
.await?;
|
2021-04-02 02:30:45 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-22 00:14:14 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn metrics() -> Response {
|
|
|
|
let encoder = TextEncoder::new();
|
|
|
|
let metric_families = prometheus::gather();
|
|
|
|
let mut buffer = vec![];
|
|
|
|
encoder.encode(&metric_families, &mut buffer).unwrap();
|
|
|
|
Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.header(CONTENT_TYPE, encoder.format_type())
|
|
|
|
.body(body::boxed(body::Full::from(buffer)))
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn go_vanity() -> Html<Vec<u8>> {
|
|
|
|
let mut buffer: Vec<u8> = vec![];
|
|
|
|
templates::gitea_html(
|
|
|
|
&mut buffer,
|
|
|
|
"christine.website/jsonfeed",
|
|
|
|
"https://christine.website/metrics",
|
|
|
|
"master",
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
Html(buffer)
|
2020-07-16 19:32:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|