Compare commits

..

No commits in common. "92f7d3d52a7e289429adba718061f838a879aa62" and "083976935c214341f0de117f29bcf71c21a67daf" have entirely different histories.

7 changed files with 57 additions and 84 deletions

View File

@ -64,17 +64,10 @@ domain:
# renewed. # renewed.
#contact_email: REQUIRED if service.http.https_addr is set #contact_email: REQUIRED if service.http.https_addr is set
# The domain name which will be used to serve the web interface of Domani. If
# service.http.https_addr is enabled then an HTTPS certificate for this domain
# will be retrieved automatically.
#
# This can be set to null to disable the web interface entirely.
#interface_domain: "localhost"
# builtins are domains whose configuration is built into domani. These domains # builtins are domains whose configuration is built into domani. These domains
# are not able to be configured via the web interface, and will be hidden from # are not able to be configured via the web interface, and will be hidden from
# it unless the `public` key is set to true. # it unless the `public` key is set to true.
#builtin_domains: #builtins:
# An example built-in domain backed by a git repo. # An example built-in domain backed by a git repo.
#git.example.com: #git.example.com:
@ -86,14 +79,13 @@ domain:
# domain list, but will not be configurable in the web interface # domain list, but will not be configurable in the web interface
#public: false #public: false
#proxied_domains: #proxied:
# An example proxied domain backed by an gemini and HTTP reverse-proxies to # An example built-in domain backed by an gemini and HTTP reverse-proxies to
# other backends. # other backends.
# #
# HTTP requests will be proxied to http_url, and gemini requests will be # HTTP requests will be proxied to http_url, and gemini requests will be
# proxied to gemini_url. Either can be null to disable serving on that # proxied to gemini_url. Either can be null.
# protocol.
# #
# HTTP requests to the backing service will automatically have # HTTP requests to the backing service will automatically have
# X-Forwarded-For and (if HTTPS) X-Forwarded-Proto headers added to them. # X-Forwarded-For and (if HTTPS) X-Forwarded-Proto headers added to them.
@ -114,8 +106,7 @@ domain:
# - name: X-HEADER-TO-DELETE # - name: X-HEADER-TO-DELETE
# value: "" # value: ""
# Set to true to prevent the domain from being served over https, even if # Set to true to prevent the domain from being served over https.
# http_url is set.
#https_disabled: false #https_disabled: false
service: service:
@ -141,6 +132,13 @@ service:
#- kind: CNAME #- kind: CNAME
# name: domain.com # name: domain.com
# The domain name which will be used to serve the web interface of Domani. If
# service.http.https_addr is enabled then an HTTPS certificate for this domain
# will be retrieved automatically.
#
# This can be set to null to disable the web interface entirely.
#interface_domain: "localhost"
#http: #http:
# The address to listen for HTTP requests on. This must use port 80 if # The address to listen for HTTP requests on. This must use port 80 if

View File

@ -9,11 +9,7 @@ fn default_resolver_addr() -> net::SocketAddr {
net::SocketAddr::from_str("1.1.1.1:53").unwrap() net::SocketAddr::from_str("1.1.1.1:53").unwrap()
} }
fn default_interface_domain() -> Option<domain::Name> { #[derive(Deserialize, Serialize)]
Some(domain::Name::from_str("localhost").unwrap())
}
#[derive(Clone, Deserialize, Serialize)]
pub struct ConfigDNS { pub struct ConfigDNS {
#[serde(default = "default_resolver_addr")] #[serde(default = "default_resolver_addr")]
pub resolver_addr: net::SocketAddr, pub resolver_addr: net::SocketAddr,
@ -27,7 +23,7 @@ impl Default for ConfigDNS {
} }
} }
#[derive(Clone, Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct ConfigACME { pub struct ConfigACME {
pub contact_email: String, pub contact_email: String,
} }
@ -58,7 +54,7 @@ pub struct ConfigProxiedDomain {
pub https_disabled: bool, pub https_disabled: bool,
} }
#[derive(Clone, Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Config { pub struct Config {
pub store_dir_path: path::PathBuf, pub store_dir_path: path::PathBuf,
#[serde(default)] #[serde(default)]
@ -66,11 +62,8 @@ pub struct Config {
pub acme: Option<ConfigACME>, pub acme: Option<ConfigACME>,
#[serde(default)] #[serde(default)]
pub builtin_domains: collections::HashMap<domain::Name, ConfigBuiltinDomain>, pub builtins: collections::HashMap<domain::Name, ConfigBuiltinDomain>,
#[serde(default)] #[serde(default)]
pub proxied_domains: collections::HashMap<domain::Name, ConfigProxiedDomain>, pub proxied: collections::HashMap<domain::Name, ConfigProxiedDomain>,
#[serde(default = "default_interface_domain")]
pub interface_domain: Option<domain::Name>,
} }

View File

@ -1,15 +1,14 @@
use crate::domain::{self, acme, checker, gemini, store, tls}; use crate::domain::{self, acme, checker, config, gemini, store, tls};
use crate::error::unexpected::{self, Mappable}; use crate::error::unexpected::{self, Mappable};
use crate::{origin, task_stack, util}; use crate::{origin, task_stack, util};
use std::sync; use std::{collections, sync};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
pub enum GetSettingsResult { pub enum GetSettingsResult {
Stored(domain::Settings), Stored(domain::Settings),
Builtin(domain::config::ConfigBuiltinDomain), Builtin(domain::config::ConfigBuiltinDomain),
Proxied(domain::config::ConfigProxiedDomain), Proxied(domain::config::ConfigProxiedDomain),
Interface,
} }
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -154,7 +153,8 @@ pub struct ManagerImpl {
domain_checker: checker::DNSChecker, domain_checker: checker::DNSChecker,
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>, acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
gemini_store: Option<Box<dyn gemini::Store + Send + Sync>>, gemini_store: Option<Box<dyn gemini::Store + Send + Sync>>,
config: domain::Config, builtins: collections::HashMap<domain::Name, config::ConfigBuiltinDomain>,
proxied: collections::HashMap<domain::Name, config::ConfigProxiedDomain>,
} }
impl ManagerImpl { impl ManagerImpl {
@ -170,7 +170,8 @@ impl ManagerImpl {
domain_checker: checker::DNSChecker, domain_checker: checker::DNSChecker,
acme_manager: Option<AcmeManager>, acme_manager: Option<AcmeManager>,
gemini_store: Option<GeminiStore>, gemini_store: Option<GeminiStore>,
config: domain::Config, builtins: collections::HashMap<domain::Name, config::ConfigBuiltinDomain>,
proxied: collections::HashMap<domain::Name, config::ConfigProxiedDomain>,
) -> sync::Arc<Self> { ) -> sync::Arc<Self> {
let manager = sync::Arc::new(ManagerImpl { let manager = sync::Arc::new(ManagerImpl {
origin_store: Box::from(origin_store), origin_store: Box::from(origin_store),
@ -179,7 +180,8 @@ impl ManagerImpl {
acme_manager: acme_manager acme_manager: acme_manager
.map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>), .map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>),
gemini_store: gemini_store.map(|m| Box::new(m) as Box<dyn gemini::Store + Send + Sync>), gemini_store: gemini_store.map(|m| Box::new(m) as Box<dyn gemini::Store + Send + Sync>),
config, builtins,
proxied,
}); });
task_stack.push_spawn(|canceller| { task_stack.push_spawn(|canceller| {
@ -245,8 +247,6 @@ impl ManagerImpl {
// A proxied domain never needs gemini certs, since gemini requests will be // A proxied domain never needs gemini certs, since gemini requests will be
// transparently proxied to the backing server anyway. // transparently proxied to the backing server anyway.
GetSettingsResult::Proxied(config) => (None, !config.https_disabled, false), GetSettingsResult::Proxied(config) => (None, !config.https_disabled, false),
GetSettingsResult::Interface => (None, true, false),
}; };
if let Some(settings) = settings { if let Some(settings) = settings {
@ -289,15 +289,11 @@ impl ManagerImpl {
impl Manager for ManagerImpl { impl Manager for ManagerImpl {
fn get_settings(&self, domain: &domain::Name) -> Result<GetSettingsResult, GetSettingsError> { fn get_settings(&self, domain: &domain::Name) -> Result<GetSettingsResult, GetSettingsError> {
if Some(domain) == self.config.interface_domain.as_ref() { if let Some(config) = self.builtins.get(domain) {
return Ok(GetSettingsResult::Interface);
}
if let Some(config) = self.config.builtin_domains.get(domain) {
return Ok(GetSettingsResult::Builtin(config.clone())); return Ok(GetSettingsResult::Builtin(config.clone()));
} }
if let Some(config) = self.config.proxied_domains.get(domain) { if let Some(config) = self.proxied.get(domain) {
return Ok(GetSettingsResult::Proxied(config.clone())); return Ok(GetSettingsResult::Proxied(config.clone()));
} }
@ -317,11 +313,6 @@ impl Manager for ManagerImpl {
unexpected::Error::from("can't call get_file on proxied domain").into(), 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(),
);
}
}; };
let path = settings.process_path(path); let path = settings.process_path(path);
@ -339,11 +330,7 @@ impl Manager for ManagerImpl {
settings: domain::Settings, settings: domain::Settings,
) -> util::BoxFuture<'mgr, Result<(), SyncWithSettingsError>> { ) -> util::BoxFuture<'mgr, Result<(), SyncWithSettingsError>> {
Box::pin(async move { Box::pin(async move {
let is_interface = Some(&domain) == self.config.interface_domain.as_ref(); if self.builtins.contains_key(&domain) || self.proxied.contains_key(&domain) {
let is_builtin = self.config.builtin_domains.contains_key(&domain);
let is_proxied = self.config.proxied_domains.contains_key(&domain);
if is_interface || is_builtin || is_proxied {
return Err(SyncWithSettingsError::NotModifiable); return Err(SyncWithSettingsError::NotModifiable);
} }
@ -397,8 +384,7 @@ impl Manager for ManagerImpl {
}) })
.collect(); .collect();
self.config self.builtins
.builtin_domains
.iter() .iter()
.map(|(domain, config)| ManagedDomain { .map(|(domain, config)| ManagedDomain {
domain: domain.clone(), domain: domain.clone(),
@ -406,8 +392,7 @@ impl Manager for ManagerImpl {
}) })
.collect_into(&mut res); .collect_into(&mut res);
self.config self.proxied
.proxied_domains
.iter() .iter()
.map(|(domain, _)| ManagedDomain { .map(|(domain, _)| ManagedDomain {
domain: domain.clone(), domain: domain.clone(),
@ -415,13 +400,6 @@ impl Manager for ManagerImpl {
}) })
.collect_into(&mut res); .collect_into(&mut res);
if let Some(ref interface_domain) = self.config.interface_domain {
res.push(ManagedDomain {
domain: interface_domain.clone(),
public: false,
})
}
Ok(res) Ok(res)
} }
} }

View File

@ -60,7 +60,7 @@ async fn main() {
// Since the interface domain _must_ point to the service (otherwise it wouldn't work) it's // Since the interface domain _must_ point to the service (otherwise it wouldn't work) it's
// reasonable to assume that a CNAME on any domain would suffice to point that domain to // reasonable to assume that a CNAME on any domain would suffice to point that domain to
// the service. // the service.
if let Some(ref interface_domain) = config.domain.interface_domain { if let Some(ref interface_domain) = config.service.interface_domain {
let interface_cname = domani::service::ConfigDNSRecord::CNAME { let interface_cname = domani::service::ConfigDNSRecord::CNAME {
name: interface_domain.clone(), name: interface_domain.clone(),
}; };
@ -106,7 +106,6 @@ async fn main() {
let acme_config = config let acme_config = config
.domain .domain
.acme .acme
.clone()
.expect("acme configuration must be set if https is enabled"); .expect("acme configuration must be set if https is enabled");
let domain_acme_store = let domain_acme_store =
@ -144,7 +143,8 @@ async fn main() {
domain_checker, domain_checker,
domain_acme_manager, domain_acme_manager,
domain_gemini_store, domain_gemini_store,
config.domain.clone(), config.domain.builtins.clone(),
config.domain.proxied.clone(),
); );
let _ = domani::service::http::Service::new( let _ = domani::service::http::Service::new(
@ -152,8 +152,7 @@ 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.proxied.clone(),
config.domain.interface_domain.clone(),
); );
if gemini_enabled { if gemini_enabled {
@ -162,7 +161,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(), config.domain.proxied.clone(),
); );
} }

View File

@ -1,6 +1,10 @@
use crate::{domain, service}; use crate::{domain, service};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net; use std::{net, str::FromStr};
fn default_interface_domain() -> Option<domain::Name> {
Some(domain::Name::from_str("localhost").unwrap())
}
#[derive(Serialize, Deserialize, Clone, PartialEq)] #[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "kind")] #[serde(tag = "kind")]
@ -27,6 +31,9 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub dns_records: Vec<ConfigDNSRecord>, pub dns_records: Vec<ConfigDNSRecord>,
#[serde(default = "default_interface_domain")]
pub interface_domain: Option<domain::Name>,
#[serde(default)] #[serde(default)]
pub http: service::http::Config, pub http: service::http::Config,

View File

@ -15,6 +15,14 @@ use std::{collections, future, net, sync};
use crate::error::unexpected; use crate::error::unexpected;
use crate::{domain, service, task_stack}; use crate::{domain, service, task_stack};
pub struct Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
handlebars: handlebars::Handlebars<'static>,
config: service::Config,
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
}
#[derive(Serialize)] #[derive(Serialize)]
struct BasePresenter<'a, T> { struct BasePresenter<'a, T> {
page_name: &'a str, page_name: &'a str,
@ -44,23 +52,13 @@ struct DomainSyncArgs {
url_encoded_domain_settings: util::UrlEncodedDomainSettings, url_encoded_domain_settings: util::UrlEncodedDomainSettings,
} }
pub struct Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
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 { impl Service {
pub fn new<CertResolver>( pub fn new<CertResolver>(
task_stack: &mut task_stack::TaskStack<unexpected::Error>, task_stack: &mut task_stack::TaskStack<unexpected::Error>,
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>, proxied: 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,
@ -72,8 +70,7 @@ 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, proxied,
interface_domain,
}); });
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller)); task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
@ -227,7 +224,6 @@ impl Service {
config.public.then(|| config.settings) config.public.then(|| config.settings)
} }
Ok(domain::manager::GetSettingsResult::Proxied(_)) => None, Ok(domain::manager::GetSettingsResult::Proxied(_)) => None,
Ok(domain::manager::GetSettingsResult::Interface) => None,
Err(domain::manager::GetSettingsError::NotFound) => None, Err(domain::manager::GetSettingsError::NotFound) => None,
Err(domain::manager::GetSettingsError::Unexpected(e)) => { Err(domain::manager::GetSettingsError::Unexpected(e)) => {
return self.internal_error( return self.internal_error(
@ -461,7 +457,7 @@ impl Service {
} }
} }
if let Some(config) = self.proxied_domains.get(&domain) { if let Some(config) = self.proxied.get(&domain) {
if let Some(ref http_url) = config.http_url { if let Some(ref http_url) = config.http_url {
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(),
@ -482,7 +478,7 @@ impl Service {
} }
} }
if Some(&domain) == self.interface_domain.as_ref() { if Some(&domain) == self.config.interface_domain.as_ref() {
return self.serve_interface(req).await; return self.serve_interface(req).await;
} }

View File

@ -16,6 +16,7 @@ pub async fn listen_http(
// only used for logging // only used for logging
let listen_host = service let listen_host = service
.config
.interface_domain .interface_domain
.clone() .clone()
.map_or(addr.ip().to_string(), |ref d| d.as_str().to_string()); .map_or(addr.ip().to_string(), |ref d| d.as_str().to_string());
@ -55,6 +56,7 @@ pub async fn listen_https(
// only used for logging // only used for logging
let listen_host = service let listen_host = service
.config
.interface_domain .interface_domain
.clone() .clone()
.map_or(addr.ip().to_string(), |ref d| d.as_str().to_string()); .map_or(addr.ip().to_string(), |ref d| d.as_str().to_string());