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;
# eachCrossSystem [system] ({buildSystem, targetSystem, isDefault }: ...)
# eachCrossSystem [system] (buildSystem: targetSystem: ...)
#
# Returns an attrset with a key "$buildSystem.cross-$targetSystem" for
# every combination of the elements of the array of system strings. The
@ -72,20 +72,13 @@
pkgs = mkPkgs buildSystem null;
crosses = builtins.foldl'
(inner: targetSystem: inner // {
"cross-${targetSystem}" = callback {
inherit buildSystem targetSystem;
isDefault = false;
};
"cross-${targetSystem}" = callback buildSystem targetSystem;
})
{}
{ default = callback buildSystem buildSystem; }
supportedSystems;
in
crosses // (rec {
default = callback {
inherit buildSystem;
targetSystem = buildSystem;
isDefault = true;
};
default = callback buildSystem buildSystem;
release = let
bins = pkgs.symlinkJoin {
name = "${default.name}-all-bins";
@ -171,7 +164,7 @@
packages = eachCrossSystem
(builtins.attrNames buildTargets)
({ buildSystem, targetSystem, isDefault }: let
(buildSystem: targetSystem: let
pkgs = mkPkgs buildSystem null;
toolchain = mkToolchain buildSystem targetSystem;
naersk-lib = pkgs.callPackage naersk {
@ -184,7 +177,7 @@
strictDeps = true;
doCheck = false;
release = true;
postInstall = if isDefault then "" else ''
postInstall = ''
cd "$out"/bin
for f in "$(ls)"; do
if ext="$(echo "$f" | grep -oP '\.[a-z]+$')"; then
@ -200,7 +193,7 @@
devShells = eachCrossSystem
(builtins.attrNames buildTargets)
({ buildSystem, targetSystem, isDefault }: let
(buildSystem: targetSystem: let
pkgs = mkPkgs buildSystem null;
toolchain = mkToolchain buildSystem targetSystem;
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)]
pub enum SyncWithSettingsError {
@ -94,7 +125,7 @@ pub trait Manager: Sync + Send {
fn get_file(
&self,
settings: &domain::Settings,
domain: &domain::Name,
path: &str,
) -> Result<util::BoxByteStream, GetFileError>;
@ -394,12 +425,34 @@ impl Manager for ManagerImpl {
fn get_file(
&self,
settings: &domain::Settings,
domain: &domain::Name,
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);
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(

View File

@ -176,6 +176,8 @@ 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 {
@ -184,6 +186,7 @@ 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,13 +6,14 @@ pub use config::*;
use crate::error::unexpected::{self, Mappable};
use crate::{domain, service, task_stack, util};
use std::sync;
use std::{collections, 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)]
@ -30,6 +31,7 @@ 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,
@ -38,6 +40,7 @@ impl Service {
domain_manager,
cert_resolver: sync::Arc::from(cert_resolver),
config,
proxied,
});
task_stack.push_spawn(|canceller| listen(service.clone(), canceller));
service
@ -71,11 +74,7 @@ impl Service {
Ok(())
}
async fn serve_conn<IO>(
&self,
settings: &domain::Settings,
conn: IO,
) -> Result<(), HandleConnError>
async fn serve_conn<IO>(&self, domain: &domain::Name, conn: IO) -> Result<(), HandleConnError>
where
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");
use domain::manager::GetFileError;
let f = match self.domain_manager.get_file(settings, &path) {
let f = match self.domain_manager.get_file(domain, &path) {
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?)
}
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()),
Err(domain::manager::GetFileError::Unexpected(e)) => return Err(e.into()),
};
let content_type = service::guess_mime(&path);
@ -165,14 +156,8 @@ impl Service {
))
})?;
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 the domain should be proxied, then proxy it
if let Some(config) = self.proxied.get(&domain) {
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)
@ -181,26 +166,8 @@ impl Service {
}
}
// Terminate TLS stream
let conn = start.into_stream(tls_config).await.or_unexpected()?;
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
self.serve_conn(&domain, 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::{future, net, sync};
use std::{collections, future, net, sync};
use crate::error::unexpected::{self, Mappable};
use crate::{domain, service, task_stack};
@ -50,6 +50,8 @@ 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 {
@ -58,6 +60,8 @@ 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,
@ -67,6 +71,8 @@ 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));
@ -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");
use domain::manager::GetFileError;
match self.domain_manager.get_file(&settings, &path) {
match self.domain_manager.get_file(&domain, &path) {
Ok(f) => self.serve(200, &path, Body::wrap_stream(f)),
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)) => {
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)) => {
self.internal_error(format!("failed to fetch file {path}: {e}").as_str())
}
}
@ -543,56 +546,41 @@ impl Service {
// everything else must use https if possible.
let https_upgradable = self.https_enabled() && !req_is_https;
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(),
)
}
if let Some(config) = self.proxied_domains.get(&domain) {
if config.http_url.is_none() {
return self.render_error_page(404, "Domain not found");
}
};
self.serve_origin(settings, req).await
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
}
}

View File

@ -14,6 +14,12 @@ 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();
@ -30,7 +36,7 @@ pub async fn listen_http(
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 graceful = server.with_graceful_shutdown(async {
@ -47,6 +53,12 @@ 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();
@ -85,7 +97,7 @@ pub async fn listen_https(
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);