Compare commits

..

No commits in common. "6a611c371ce330fc8469858895494374c07a4635" and "4009f0da48ceb59090a218379343bc9fffdf299f" have entirely different histories.

6 changed files with 145 additions and 129 deletions

View File

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

View File

@ -30,7 +30,38 @@ impl From<store::GetError> for GetSettingsError {
} }
} }
pub type GetFileError = origin::GetFileError; #[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),
}
}
}
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum SyncWithSettingsError { pub enum SyncWithSettingsError {
@ -94,7 +125,7 @@ pub trait Manager: Sync + Send {
fn get_file( fn get_file(
&self, &self,
settings: &domain::Settings, domain: &domain::Name,
path: &str, path: &str,
) -> Result<util::BoxByteStream, GetFileError>; ) -> Result<util::BoxByteStream, GetFileError>;
@ -394,12 +425,34 @@ impl Manager for ManagerImpl {
fn get_file( fn get_file(
&self, &self,
settings: &domain::Settings, domain: &domain::Name,
path: &str, path: &str,
) -> Result<util::BoxByteStream, GetFileError> { ) -> 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 path = settings.process_path(path);
self.origin_store
.get_file(&settings.origin_descr, path.as_ref()) let f = self
.origin_store
.get_file(&settings.origin_descr, path.as_ref())?;
Ok(f)
} }
fn sync_with_settings( fn sync_with_settings(

View File

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

View File

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

View File

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

View File

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