Add builtin domains to configuration
This commit is contained in:
parent
03428cef02
commit
4483185e75
@ -5,7 +5,7 @@ domain:
|
|||||||
service:
|
service:
|
||||||
passphrase: foobar
|
passphrase: foobar
|
||||||
dns_records:
|
dns_records:
|
||||||
- type: A
|
- kind: A
|
||||||
addr: 127.0.0.1
|
addr: 127.0.0.1
|
||||||
- type: AAAA
|
- kind: AAAA
|
||||||
addr: ::1
|
addr: ::1
|
||||||
|
19
README.md
19
README.md
@ -53,6 +53,18 @@ domain:
|
|||||||
# renewed.
|
# renewed.
|
||||||
#contact_email: REQUIRED if service.http.https_addr is set
|
#contact_email: REQUIRED if service.http.https_addr is set
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# it unless the `public` key is set to true.
|
||||||
|
#builtins:
|
||||||
|
|
||||||
|
# An example built-in domain backed by a git repo.
|
||||||
|
#example.com:
|
||||||
|
# kind: git
|
||||||
|
# url: "https://somewhere.com/some/repo.git"
|
||||||
|
# branch: main
|
||||||
|
# public: false
|
||||||
|
|
||||||
service:
|
service:
|
||||||
|
|
||||||
# Passphrase which must be given by users who are configuring new domains via
|
# Passphrase which must be given by users who are configuring new domains via
|
||||||
@ -66,14 +78,14 @@ service:
|
|||||||
# A CNAME record with the primary_domain of this server is automatically
|
# A CNAME record with the primary_domain of this server is automatically
|
||||||
# included.
|
# included.
|
||||||
dns_records:
|
dns_records:
|
||||||
#- type: A
|
#- kind: A
|
||||||
# addr: 127.0.0.1
|
# addr: 127.0.0.1
|
||||||
|
|
||||||
#- type: AAAA
|
#- kind: AAAA
|
||||||
# addr: ::1
|
# addr: ::1
|
||||||
|
|
||||||
# NOTE that the name given here must resolve to the Domani server.
|
# NOTE that the name given here must resolve to the Domani server.
|
||||||
#- type: CNAME
|
#- kind: CNAME
|
||||||
# name: domain.com
|
# name: domain.com
|
||||||
|
|
||||||
# The domain name which will be used to serve the web interface of Domani. If
|
# The domain name which will be used to serve the web interface of Domani. If
|
||||||
@ -126,5 +138,6 @@ Within the shell which opens you can do `cargo run` to start a local instance.
|
|||||||
* Support for more backends than just git repositories, including:
|
* Support for more backends than just git repositories, including:
|
||||||
* IPFS/IPNS
|
* IPFS/IPNS
|
||||||
* Alternative URLs (reverse proxy)
|
* Alternative URLs (reverse proxy)
|
||||||
|
* Small static files (e.g. for well-knowns)
|
||||||
* Google Drive
|
* Google Drive
|
||||||
* Dropbox
|
* Dropbox
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use std::{net, path, str::FromStr};
|
use std::{collections, net, path, str::FromStr};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::domain;
|
||||||
|
|
||||||
fn default_resolver_addr() -> net::SocketAddr {
|
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()
|
||||||
}
|
}
|
||||||
@ -25,10 +27,19 @@ pub struct ConfigACME {
|
|||||||
pub contact_email: String,
|
pub contact_email: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct BuiltinDomain {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub domain: domain::Domain,
|
||||||
|
|
||||||
|
pub public: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub store_dir_path: path::PathBuf,
|
pub store_dir_path: path::PathBuf,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dns: ConfigDNS,
|
pub dns: ConfigDNS,
|
||||||
pub acme: Option<ConfigACME>,
|
pub acme: Option<ConfigACME>,
|
||||||
|
pub builtins: collections::HashMap<domain::Name, BuiltinDomain>,
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,9 @@ impl From<store::GetError> for SyncError {
|
|||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum SyncWithConfigError {
|
pub enum SyncWithConfigError {
|
||||||
|
#[error("cannot call SyncWithConfig on builtin domain")]
|
||||||
|
BuiltinDomain,
|
||||||
|
|
||||||
#[error("invalid url")]
|
#[error("invalid url")]
|
||||||
InvalidURL,
|
InvalidURL,
|
||||||
|
|
||||||
@ -127,6 +130,7 @@ impl From<checker::CheckDomainError> for SyncWithConfigError {
|
|||||||
impl From<store::SetError> for SyncWithConfigError {
|
impl From<store::SetError> for SyncWithConfigError {
|
||||||
fn from(e: store::SetError) -> SyncWithConfigError {
|
fn from(e: store::SetError) -> SyncWithConfigError {
|
||||||
match e {
|
match e {
|
||||||
|
store::SetError::BuiltinDomain => SyncWithConfigError::BuiltinDomain,
|
||||||
store::SetError::Unexpected(e) => SyncWithConfigError::Unexpected(e),
|
store::SetError::Unexpected(e) => SyncWithConfigError::Unexpected(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{cmp, fmt};
|
use std::{cmp, fmt, hash};
|
||||||
|
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use trust_dns_client::rr as trust_dns_rr;
|
use trust_dns_client::rr as trust_dns_rr;
|
||||||
@ -7,7 +7,7 @@ use trust_dns_client::rr as trust_dns_rr;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
/// Validated representation of a domain name
|
/// Validated representation of a domain name
|
||||||
pub struct Name {
|
pub struct Name {
|
||||||
inner: trust_dns_rr::Name,
|
rr: trust_dns_rr::Name,
|
||||||
utf8_str: String,
|
utf8_str: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ impl Name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_rr(&self) -> &trust_dns_rr::Name {
|
pub fn as_rr(&self) -> &trust_dns_rr::Name {
|
||||||
&self.inner
|
&self.rr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,13 +36,21 @@ impl FromStr for Name {
|
|||||||
|
|
||||||
n.set_fqdn(true);
|
n.set_fqdn(true);
|
||||||
|
|
||||||
Ok(Name { inner: n, utf8_str })
|
Ok(Name { rr: n, utf8_str })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl cmp::PartialEq for Name {
|
impl cmp::PartialEq for Name {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.inner == other.inner
|
self.rr == other.rr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::Eq for Name {}
|
||||||
|
|
||||||
|
impl hash::Hash for Name {
|
||||||
|
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.rr.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
use std::path;
|
use std::{collections, fs, io, path, str::FromStr};
|
||||||
use std::str::FromStr;
|
|
||||||
use std::{fs, io};
|
|
||||||
|
|
||||||
use crate::domain;
|
use crate::domain;
|
||||||
use crate::error::unexpected::{self, Intoable, Mappable};
|
use crate::error::unexpected::{self, Intoable, Mappable};
|
||||||
@ -16,6 +14,9 @@ pub enum GetError {
|
|||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum SetError {
|
pub enum SetError {
|
||||||
|
#[error("cannot call set on builtin domain")]
|
||||||
|
BuiltinDomain,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] unexpected::Error),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
@ -96,6 +97,51 @@ impl Store for FSStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct StoreWithBuiltin<S: Store> {
|
||||||
|
inner: S,
|
||||||
|
domains: collections::HashMap<domain::Name, domain::config::BuiltinDomain>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Store> StoreWithBuiltin<S> {
|
||||||
|
pub fn new(
|
||||||
|
inner: S,
|
||||||
|
builtin_domains: collections::HashMap<domain::Name, domain::config::BuiltinDomain>,
|
||||||
|
) -> StoreWithBuiltin<S> {
|
||||||
|
StoreWithBuiltin {
|
||||||
|
inner,
|
||||||
|
domains: builtin_domains,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Store> Store for StoreWithBuiltin<S> {
|
||||||
|
fn get(&self, domain: &domain::Name) -> Result<domain::Domain, GetError> {
|
||||||
|
if let Some(domain) = self.domains.get(domain) {
|
||||||
|
return Ok(domain.domain.clone());
|
||||||
|
}
|
||||||
|
self.inner.get(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&self, domain: &domain::Name, config: &domain::Domain) -> Result<(), SetError> {
|
||||||
|
if self.domains.get(domain).is_some() {
|
||||||
|
return Err(SetError::BuiltinDomain);
|
||||||
|
}
|
||||||
|
self.inner.set(domain, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
||||||
|
let inner_domains = self.inner.all_domains()?;
|
||||||
|
let mut domains: Vec<domain::Name> = self
|
||||||
|
.domains
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, v)| v.public)
|
||||||
|
.map(|(k, _)| k.clone())
|
||||||
|
.collect();
|
||||||
|
domains.extend(inner_domains);
|
||||||
|
Ok(domains)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Store, *};
|
use super::{Store, *};
|
||||||
|
@ -89,10 +89,13 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.expect("domain checker initialization failed");
|
.expect("domain checker initialization failed");
|
||||||
|
|
||||||
let domain_config_store =
|
let domain_store =
|
||||||
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
|
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
|
||||||
.expect("domain config store initialization failed");
|
.expect("domain config store initialization failed");
|
||||||
|
|
||||||
|
let domain_store =
|
||||||
|
domani::domain::store::StoreWithBuiltin::new(domain_store, config.domain.builtins);
|
||||||
|
|
||||||
let domain_acme_manager = if config.service.http.https_addr.is_some() {
|
let domain_acme_manager = if config.service.http.https_addr.is_some() {
|
||||||
let acme_config = config
|
let acme_config = config
|
||||||
.domain
|
.domain
|
||||||
@ -121,7 +124,7 @@ async fn main() {
|
|||||||
let domain_manager = domani::domain::manager::ManagerImpl::new(
|
let domain_manager = domani::domain::manager::ManagerImpl::new(
|
||||||
&mut task_stack,
|
&mut task_stack,
|
||||||
origin_store,
|
origin_store,
|
||||||
domain_config_store,
|
domain_store,
|
||||||
domain_checker,
|
domain_checker,
|
||||||
domain_acme_manager,
|
domain_acme_manager,
|
||||||
);
|
);
|
||||||
|
@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "kind")]
|
||||||
/// A unique description of an origin, from where a domain might be served.
|
/// A unique description of an origin, from where a domain might be served.
|
||||||
pub enum Descr {
|
pub enum Descr {
|
||||||
Git { url: String, branch_name: String },
|
Git { url: String, branch_name: String },
|
||||||
|
@ -10,7 +10,7 @@ fn default_primary_domain() -> domain::Name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "kind")]
|
||||||
pub enum ConfigDNSRecord {
|
pub enum ConfigDNSRecord {
|
||||||
A { addr: net::Ipv4Addr },
|
A { addr: net::Ipv4Addr },
|
||||||
AAAA { addr: net::Ipv6Addr },
|
AAAA { addr: net::Ipv6Addr },
|
||||||
|
@ -302,6 +302,7 @@ impl<'svc> Service {
|
|||||||
|
|
||||||
let error_msg = match sync_result {
|
let error_msg = match sync_result {
|
||||||
Ok(_) => None,
|
Ok(_) => None,
|
||||||
|
Err(domain::manager::SyncWithConfigError::BuiltinDomain) => Some("This domain is not able to be configured, please contact the server administrator.".to_string()),
|
||||||
Err(domain::manager::SyncWithConfigError::InvalidURL) => Some("Fetching the git repository failed, please double check that you input the correct URL.".to_string()),
|
Err(domain::manager::SyncWithConfigError::InvalidURL) => Some("Fetching the git repository failed, please double check that you input the correct URL.".to_string()),
|
||||||
Err(domain::manager::SyncWithConfigError::InvalidBranchName) => Some("The git repository does not have a branch of the given name, please double check that you input the correct name.".to_string()),
|
Err(domain::manager::SyncWithConfigError::InvalidBranchName) => Some("The git repository does not have a branch of the given name, please double check that you input the correct name.".to_string()),
|
||||||
Err(domain::manager::SyncWithConfigError::AlreadyInProgress) => Some("The configuration of your domain is still in progress, please refresh in a few minutes.".to_string()),
|
Err(domain::manager::SyncWithConfigError::AlreadyInProgress) => Some("The configuration of your domain is still in progress, please refresh in a few minutes.".to_string()),
|
||||||
|
Loading…
Reference in New Issue
Block a user