Compare commits

...

2 Commits

Author SHA1 Message Date
6a611c371c Retrieve domain settings within each service, rather than in domain manager
This allows for fewer dependencies when initializing the services, and
more precise handling of the different cases.
2024-01-15 18:07:49 +01:00
43502f82f9 Remove platform suffix from binary produced by default flake target 2024-01-15 17:07:55 +01:00
6 changed files with 129 additions and 145 deletions

View File

@ -57,7 +57,7 @@
{}
supportedSystems;
# eachCrossSystem [system] (buildSystem: targetSystem: ...)
# eachCrossSystem [system] ({buildSystem, targetSystem, isDefault }: ...)
#
# Returns an attrset with a key "$buildSystem.cross-$targetSystem" for
# every combination of the elements of the array of system strings. The
@ -72,13 +72,20 @@
pkgs = mkPkgs buildSystem null;
crosses = builtins.foldl'
(inner: targetSystem: inner // {
"cross-${targetSystem}" = callback buildSystem targetSystem;
"cross-${targetSystem}" = callback {
inherit buildSystem targetSystem;
isDefault = false;
};
})
{ default = callback buildSystem buildSystem; }
{}
supportedSystems;
in
crosses // (rec {
default = callback buildSystem buildSystem;
default = callback {
inherit buildSystem;
targetSystem = buildSystem;
isDefault = true;
};
release = let
bins = pkgs.symlinkJoin {
name = "${default.name}-all-bins";
@ -164,7 +171,7 @@
packages = eachCrossSystem
(builtins.attrNames buildTargets)
(buildSystem: targetSystem: let
({ buildSystem, targetSystem, isDefault }: let
pkgs = mkPkgs buildSystem null;
toolchain = mkToolchain buildSystem targetSystem;
naersk-lib = pkgs.callPackage naersk {
@ -177,7 +184,7 @@
strictDeps = true;
doCheck = false;
release = true;
postInstall = ''
postInstall = if isDefault then "" else ''
cd "$out"/bin
for f in "$(ls)"; do
if ext="$(echo "$f" | grep -oP '\.[a-z]+$')"; then
@ -193,7 +200,7 @@
devShells = eachCrossSystem
(builtins.attrNames buildTargets)
(buildSystem: targetSystem: let
({ buildSystem, targetSystem, isDefault }: let
pkgs = mkPkgs buildSystem null;
toolchain = mkToolchain buildSystem targetSystem;
in

View File

@ -30,38 +30,7 @@ impl From<store::GetError> for GetSettingsError {
}
}
#[derive(thiserror::Error, Debug)]
pub enum GetFileError {
#[error("domain not found")]
DomainNotFound,
#[error("file not found")]
FileNotFound,
#[error(transparent)]
Unexpected(#[from] unexpected::Error),
}
impl From<GetSettingsError> for GetFileError {
fn from(e: GetSettingsError) -> Self {
match e {
GetSettingsError::NotFound => Self::DomainNotFound,
GetSettingsError::Unexpected(e) => Self::Unexpected(e),
}
}
}
impl From<origin::GetFileError> for GetFileError {
fn from(e: origin::GetFileError) -> Self {
match e {
origin::GetFileError::DescrNotSynced => {
Self::Unexpected(unexpected::Error::from("origin descr not synced"))
}
origin::GetFileError::FileNotFound => Self::FileNotFound,
origin::GetFileError::Unexpected(e) => Self::Unexpected(e),
}
}
}
pub type GetFileError = origin::GetFileError;
#[derive(thiserror::Error, Debug)]
pub enum SyncWithSettingsError {
@ -125,7 +94,7 @@ pub trait Manager: Sync + Send {
fn get_file(
&self,
domain: &domain::Name,
settings: &domain::Settings,
path: &str,
) -> Result<util::BoxByteStream, GetFileError>;
@ -425,34 +394,12 @@ impl Manager for ManagerImpl {
fn get_file(
&self,
domain: &domain::Name,
settings: &domain::Settings,
path: &str,
) -> Result<util::BoxByteStream, GetFileError> {
let settings = match self.get_settings(domain)? {
GetSettingsResult::Stored(settings) => settings,
GetSettingsResult::Builtin(config) => config.settings,
GetSettingsResult::Proxied(_) => {
return Err(
unexpected::Error::from("can't call get_file on proxied domain").into(),
);
}
GetSettingsResult::Interface => {
return Err(
unexpected::Error::from("can't call get_file on interface domain").into(),
);
}
GetSettingsResult::External(_) => {
return Err(GetFileError::DomainNotFound);
}
};
let path = settings.process_path(path);
let f = self
.origin_store
.get_file(&settings.origin_descr, path.as_ref())?;
Ok(f)
self.origin_store
.get_file(&settings.origin_descr, path.as_ref())
}
fn sync_with_settings(

View File

@ -176,8 +176,6 @@ async fn main() {
domain_manager.clone(),
domani::domain::manager::HttpsCertResolver::from(domain_manager.clone()),
config.service.clone(),
config.domain.proxied_domains.clone(),
config.domain.interface_domain.clone(),
);
if gemini_enabled {
@ -186,7 +184,6 @@ async fn main() {
domain_manager.clone(),
domani::domain::manager::GeminiCertResolver::from(domain_manager.clone()),
config.service.gemini.clone(),
config.domain.proxied_domains.clone(),
);
}

View File

@ -6,14 +6,13 @@ pub use config::*;
use crate::error::unexpected::{self, Mappable};
use crate::{domain, service, task_stack, util};
use std::{collections, sync};
use std::sync;
use tokio_util::sync::CancellationToken;
pub struct Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
config: Config,
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
}
#[derive(thiserror::Error, Debug)]
@ -31,7 +30,6 @@ impl Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: CertResolver,
config: Config,
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
) -> sync::Arc<Service>
where
CertResolver: rustls::server::ResolvesServerCert + 'static,
@ -40,7 +38,6 @@ impl Service {
domain_manager,
cert_resolver: sync::Arc::from(cert_resolver),
config,
proxied,
});
task_stack.push_spawn(|canceller| listen(service.clone(), canceller));
service
@ -74,7 +71,11 @@ impl Service {
Ok(())
}
async fn serve_conn<IO>(&self, domain: &domain::Name, conn: IO) -> Result<(), HandleConnError>
async fn serve_conn<IO>(
&self,
settings: &domain::Settings,
conn: IO,
) -> Result<(), HandleConnError>
where
IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
{
@ -96,15 +97,23 @@ impl Service {
let path = service::append_index_to_path(req.path(), "index.gmi");
let f = match self.domain_manager.get_file(domain, &path) {
use domain::manager::GetFileError;
let f = match self.domain_manager.get_file(settings, &path) {
Ok(f) => f,
Err(domain::manager::GetFileError::DomainNotFound) => {
return Err(unexpected::Error::from("domain not found when serving file").into())
}
Err(domain::manager::GetFileError::FileNotFound) => {
Err(GetFileError::FileNotFound) => {
return Ok(self.respond_conn(w, "51", "File not found", None).await?)
}
Err(domain::manager::GetFileError::Unexpected(e)) => return Err(e.into()),
Err(GetFileError::DescrNotSynced) => {
return Err(unexpected::Error::from(
format!(
"Backend for {:?} has not yet been synced",
settings.origin_descr
)
.as_str(),
)
.into())
}
Err(GetFileError::Unexpected(e)) => return Err(e.into()),
};
let content_type = service::guess_mime(&path);
@ -156,8 +165,14 @@ impl Service {
))
})?;
// If the domain should be proxied, then proxy it
if let Some(config) = self.proxied.get(&domain) {
use domain::manager::{GetSettingsError, GetSettingsResult};
let get_settings_res = self.domain_manager.get_settings(&domain);
// If the domain should be proxied, then proxy it. This case gets checked before
// any of the others because the others require terminating the TLS stream to be
// handled.
if let Ok(GetSettingsResult::Proxied(ref config)) = get_settings_res {
if let Some(ref gemini_url) = config.gemini_url {
let prefixed_conn = proxy::teed_io_to_prefixed(start.into_inner());
self.proxy_conn(gemini_url.addr.as_str(), prefixed_conn)
@ -166,8 +181,26 @@ impl Service {
}
}
// Terminate TLS stream
let conn = start.into_stream(tls_config).await.or_unexpected()?;
self.serve_conn(&domain, conn).await
let settings = match get_settings_res {
Ok(GetSettingsResult::Stored(settings)) => settings,
Ok(GetSettingsResult::Builtin(config)) => config.settings,
Ok(GetSettingsResult::Proxied(_)) => {
panic!("proxied case already handled")
}
Ok(GetSettingsResult::Interface)
| Ok(GetSettingsResult::External(_))
| Err(GetSettingsError::NotFound) => {
return Ok(self
.respond_conn(conn, "51", "File not found", None)
.await?)
}
Err(GetSettingsError::Unexpected(e)) => return Err(e.into()),
};
self.serve_conn(&settings, conn).await
}
Err(err) => {
return Err(unexpected::Error::from(

View File

@ -10,7 +10,7 @@ use http::request::Parts;
use hyper::{Body, Method, Request, Response};
use serde::{Deserialize, Serialize};
use std::{collections, future, net, sync};
use std::{future, net, sync};
use crate::error::unexpected::{self, Mappable};
use crate::{domain, service, task_stack};
@ -50,8 +50,6 @@ pub struct Service {
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
handlebars: handlebars::Handlebars<'static>,
config: service::Config,
proxied_domains: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
interface_domain: Option<domain::Name>,
}
impl Service {
@ -60,8 +58,6 @@ impl Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: CertResolver,
config: service::Config,
proxied_domains: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
interface_domain: Option<domain::Name>,
) -> sync::Arc<Service>
where
CertResolver: rustls::server::ResolvesServerCert + 'static,
@ -71,8 +67,6 @@ impl Service {
cert_resolver: sync::Arc::from(cert_resolver),
handlebars: tpl::get(),
config,
proxied_domains,
interface_domain,
});
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
@ -208,18 +202,21 @@ impl Service {
)
}
async fn serve_origin(&self, domain: domain::Name, req: Request<Body>) -> Response<Body> {
async fn serve_origin(&self, settings: domain::Settings, req: Request<Body>) -> Response<Body> {
let path = service::append_index_to_path(req.uri().path(), "index.html");
match self.domain_manager.get_file(&domain, &path) {
use domain::manager::GetFileError;
match self.domain_manager.get_file(&settings, &path) {
Ok(f) => self.serve(200, &path, Body::wrap_stream(f)),
Err(domain::manager::GetFileError::DomainNotFound) => {
self.render_error_page(404, "Unknown domain name")
}
Err(domain::manager::GetFileError::FileNotFound) => {
self.render_error_page(404, "File not found")
}
Err(domain::manager::GetFileError::Unexpected(e)) => {
Err(GetFileError::FileNotFound) => self.render_error_page(404, "File not found"),
Err(GetFileError::DescrNotSynced) => self.internal_error(
format!(
"Backend for {:?} has not yet been synced",
settings.origin_descr
)
.as_str(),
),
Err(GetFileError::Unexpected(e)) => {
self.internal_error(format!("failed to fetch file {path}: {e}").as_str())
}
}
@ -546,41 +543,56 @@ impl Service {
// everything else must use https if possible.
let https_upgradable = self.https_enabled() && !req_is_https;
if let Some(config) = self.proxied_domains.get(&domain) {
if config.http_url.is_none() {
return self.render_error_page(404, "Domain not found");
let settings = {
use domain::manager::{GetSettingsError, GetSettingsResult};
match self.domain_manager.get_settings(&domain) {
Ok(GetSettingsResult::Stored(settings)) => settings,
Ok(GetSettingsResult::Builtin(config)) => config.settings,
Ok(GetSettingsResult::Proxied(config)) => {
if config.http_url.is_none() {
return self.render_error_page(404, "Domain not found");
} else if https_upgradable && !config.https_disabled {
return self.https_redirect(domain, req);
}
let http_url = config.http_url.as_ref().unwrap();
return service::http::proxy::serve_http_request(
http_url.original_url.as_str(),
&config.http_request_headers.0,
client_ip,
req,
req_is_https,
)
.await
.unwrap_or_else(|e| {
self.internal_error(
format!("serving {domain} via proxy {}: {e}", http_url.original_url)
.as_str(),
)
});
}
Ok(GetSettingsResult::Interface) => {
if https_upgradable {
return self.https_redirect(domain, req);
}
return self.serve_interface(req).await;
}
Ok(GetSettingsResult::External(_)) => {
return self.render_error_page(404, "Unknown domain name")
}
Err(GetSettingsError::NotFound) => {
return self.render_error_page(404, "Unknown domain name")
}
Err(GetSettingsError::Unexpected(e)) => {
return self.internal_error(
format!("failed to fetch settings for domain {domain}: {e}").as_str(),
)
}
}
};
let http_url = config.http_url.as_ref().unwrap();
if https_upgradable && !config.https_disabled {
return self.https_redirect(domain, req);
}
return service::http::proxy::serve_http_request(
http_url.original_url.as_str(),
&config.http_request_headers.0,
client_ip,
req,
req_is_https,
)
.await
.unwrap_or_else(|e| {
self.internal_error(
format!("serving {domain} via proxy {}: {e}", http_url.original_url).as_str(),
)
});
}
if https_upgradable {
return self.https_redirect(domain, req);
}
if Some(&domain) == self.interface_domain.as_ref() {
return self.serve_interface(req).await;
}
self.serve_origin(domain, req).await
self.serve_origin(settings, req).await
}
}

View File

@ -14,12 +14,6 @@ pub async fn listen_http(
) -> Result<(), unexpected::Error> {
let addr = service.config.http.http_addr;
// only used for logging
let listen_host = service
.interface_domain
.clone()
.map_or(addr.ip().to_string(), |ref d| d.as_str().to_string());
let make_service = hyper::service::make_service_fn(move |conn: &AddrStream| {
let service = service.clone();
let client_ip = conn.remote_addr().ip();
@ -36,7 +30,7 @@ pub async fn listen_http(
async move { Ok::<_, convert::Infallible>(hyper_service) }
});
log::info!("Listening on http://{}:{}", listen_host, addr.port(),);
log::info!("Listening on http://{}", &addr);
let server = hyper::Server::bind(&addr).serve(make_service);
let graceful = server.with_graceful_shutdown(async {
@ -53,12 +47,6 @@ pub async fn listen_https(
let cert_resolver = service.cert_resolver.clone();
let addr = service.config.http.https_addr.unwrap();
// only used for logging
let listen_host = service
.interface_domain
.clone()
.map_or(addr.ip().to_string(), |ref d| d.as_str().to_string());
let make_service = hyper::service::make_service_fn(move |conn: &TlsStream<AddrStream>| {
let service = service.clone();
let client_ip = conn.get_ref().0.remote_addr().ip();
@ -97,7 +85,7 @@ pub async fn listen_https(
let incoming = hyper::server::accept::from_stream(incoming);
log::info!("Listening on https://{}:{}", listen_host, addr.port());
log::info!("Listening on https://{}", addr);
let server = hyper::Server::builder(incoming).serve(make_service);