Compare commits
2 Commits
0b02400f4e
...
6cabb9b742
Author | SHA1 | Date | |
---|---|---|---|
6cabb9b742 | |||
0b5b2cb3f3 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
.cargo
|
.cargo
|
||||||
/result
|
/result
|
||||||
config.yml
|
config-dev.yml
|
||||||
|
138
README.md
138
README.md
@ -33,140 +33,8 @@ A statically compiled binary will be placed in the `result` directory.
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Domani is configured via a YAML file whose path is given on the command-line.
|
Domani is configured via a YAML file whose path is given on the command-line.
|
||||||
The format of the YAML file, along with all default values, is as follows:
|
The format of the YAML file, along with all default values, can be found in the
|
||||||
|
`config.yml` file in this repo.
|
||||||
```yaml
|
|
||||||
origin:
|
|
||||||
|
|
||||||
# Path under which all origin data (i.e. git repositories, file caches,
|
|
||||||
# etc...) will be stored.
|
|
||||||
#
|
|
||||||
# This should be different than any other store_dir_paths.
|
|
||||||
#store_dir_path: REQUIRED
|
|
||||||
|
|
||||||
domain:
|
|
||||||
|
|
||||||
# Path under which all domain data (i.e. domains configured by users, HTTPS
|
|
||||||
# certificates, etc...) will be stored.
|
|
||||||
#
|
|
||||||
# This should be different than any other store_dir_paths.
|
|
||||||
#store_dir_path: REQUIRED
|
|
||||||
|
|
||||||
#dns:
|
|
||||||
|
|
||||||
# Address of DNS resolver to use.
|
|
||||||
#resolver_addr: "1.1.1.1:53"
|
|
||||||
|
|
||||||
#acme:
|
|
||||||
|
|
||||||
# Contact email to use when creating HTTPS certificates using LetsEncrypt.
|
|
||||||
# This email will be used for notifying you if certificates are not being
|
|
||||||
# renewed.
|
|
||||||
#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
|
|
||||||
# are not able to be configured via the web interface, and will be hidden from
|
|
||||||
# it unless the `public` key is set to true.
|
|
||||||
#builtin_domains:
|
|
||||||
|
|
||||||
# An example built-in domain backed by a git repo.
|
|
||||||
#git.example.com:
|
|
||||||
#kind: git
|
|
||||||
#url: "https://somewhere.com/some/repo.git"
|
|
||||||
#branch_name: main
|
|
||||||
|
|
||||||
# If true then the built-in will be included in the web interface's
|
|
||||||
# domain list, but will not be configurable in the web interface
|
|
||||||
#public: false
|
|
||||||
|
|
||||||
#proxied_domains:
|
|
||||||
|
|
||||||
# An example proxied domain backed by an gemini and HTTP reverse-proxies to
|
|
||||||
# other backends.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
# protocol.
|
|
||||||
#
|
|
||||||
# HTTP requests to the backing service will automatically have
|
|
||||||
# X-Forwarded-For and (if HTTPS) X-Forwarded-Proto headers added to them.
|
|
||||||
#
|
|
||||||
# Proxies are currently limited in the following ways:
|
|
||||||
# * http_url must be to an http endpoint (not https)
|
|
||||||
# * dns.resolver_addr is ignored and the system-wide dns is used
|
|
||||||
#
|
|
||||||
#example.com:
|
|
||||||
|
|
||||||
#http_url: "http://some.other.service.com"
|
|
||||||
#gemini_url: "gemini://some.other.service.com"
|
|
||||||
|
|
||||||
# Extra headers to add to proxied requests
|
|
||||||
#http_request_headers:
|
|
||||||
# - name: Host
|
|
||||||
# value: "yet.another.service.com"
|
|
||||||
# - name: X-HEADER-TO-DELETE
|
|
||||||
# value: ""
|
|
||||||
|
|
||||||
# Set to true to prevent the domain from being served over https, even if
|
|
||||||
# http_url is set.
|
|
||||||
#https_disabled: false
|
|
||||||
|
|
||||||
# External domains will have a TLS key/cert generated and signed for them, but
|
|
||||||
# which will not be served by domani itself. The key/cert files will be placed
|
|
||||||
# in the configured paths.
|
|
||||||
#
|
|
||||||
# HTTPS must be enabled for external_domains to be used.
|
|
||||||
#external_domains:
|
|
||||||
#example.com
|
|
||||||
# tls_key_path: /dir/path/key.pem
|
|
||||||
# tls_cert_path: /dir/path/cert.pem
|
|
||||||
|
|
||||||
service:
|
|
||||||
|
|
||||||
# Passphrase which must be given by users who are configuring new domains via
|
|
||||||
# the web interface.
|
|
||||||
#passphrase: REQUIRED
|
|
||||||
|
|
||||||
# DNS records which users must add to their domain's DNS so that
|
|
||||||
# Domani can serve the domains. All records given must route to this Domani
|
|
||||||
# instance.
|
|
||||||
#
|
|
||||||
# A CNAME record with the interface_domain of this server is automatically
|
|
||||||
# included, if it's not null itself.
|
|
||||||
#dns_records:
|
|
||||||
#- kind: A
|
|
||||||
# addr: 127.0.0.1
|
|
||||||
|
|
||||||
#- kind: AAAA
|
|
||||||
# addr: ::1
|
|
||||||
|
|
||||||
# NOTE that the name given here must resolve to the Domani server.
|
|
||||||
#- kind: CNAME
|
|
||||||
# name: domain.com
|
|
||||||
|
|
||||||
#http:
|
|
||||||
|
|
||||||
# The address to listen for HTTP requests on. This must use port 80 if
|
|
||||||
# https_addr is set.
|
|
||||||
#http_addr: "[::]:3080"
|
|
||||||
|
|
||||||
# The address to listen for HTTPS requests on. Defaults to not having HTTP
|
|
||||||
# enabled. You can enable HTTPS by setting this to "[::]:443".
|
|
||||||
#https_addr: null
|
|
||||||
|
|
||||||
#gemini:
|
|
||||||
|
|
||||||
# The address to listen for gemini requests on. Set this to null to disable
|
|
||||||
# gemini support.
|
|
||||||
#gemini_addr: "[::]:3965"
|
|
||||||
```
|
|
||||||
|
|
||||||
The YAML config file can be passed to the Domani process via the `--config-path`
|
The YAML config file can be passed to the Domani process via the `--config-path`
|
||||||
CLI parameter:
|
CLI parameter:
|
||||||
@ -193,7 +61,7 @@ In order to open a shell with all necessary tooling (expected rust toolchain
|
|||||||
versions, etc...) simply do:
|
versions, etc...) simply do:
|
||||||
|
|
||||||
```
|
```
|
||||||
cp config.yml.tpl config.yml
|
cp config-dev.yml.tpl config-dev.yml
|
||||||
nix develop
|
nix develop
|
||||||
```
|
```
|
||||||
|
|
||||||
|
130
config.yml
Normal file
130
config.yml
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
origin:
|
||||||
|
|
||||||
|
# Path under which all origin data (i.e. git repositories, file caches,
|
||||||
|
# etc...) will be stored.
|
||||||
|
#
|
||||||
|
# This should be different than any other store_dir_paths.
|
||||||
|
#store_dir_path: REQUIRED
|
||||||
|
|
||||||
|
domain:
|
||||||
|
|
||||||
|
# Path under which all domain data (i.e. domains configured by users, HTTPS
|
||||||
|
# certificates, etc...) will be stored.
|
||||||
|
#
|
||||||
|
# This should be different than any other store_dir_paths.
|
||||||
|
#store_dir_path: REQUIRED
|
||||||
|
|
||||||
|
#dns:
|
||||||
|
|
||||||
|
# Address of DNS resolver to use.
|
||||||
|
#resolver_addr: "1.1.1.1:53"
|
||||||
|
|
||||||
|
#acme:
|
||||||
|
|
||||||
|
# Contact email to use when creating HTTPS certificates using LetsEncrypt.
|
||||||
|
# This email will be used for notifying you if certificates are not being
|
||||||
|
# renewed.
|
||||||
|
#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
|
||||||
|
# are not able to be configured via the web interface, and will be hidden from
|
||||||
|
# it unless the `public` key is set to true.
|
||||||
|
#builtin_domains:
|
||||||
|
|
||||||
|
# An example built-in domain backed by a git repo.
|
||||||
|
#git.example.com:
|
||||||
|
#kind: git
|
||||||
|
#url: "https://somewhere.com/some/repo.git"
|
||||||
|
#branch_name: main
|
||||||
|
|
||||||
|
# If true then the built-in will be included in the web interface's
|
||||||
|
# domain list, but will not be configurable in the web interface
|
||||||
|
#public: false
|
||||||
|
|
||||||
|
#proxied_domains:
|
||||||
|
|
||||||
|
# An example proxied domain backed by an gemini and HTTP reverse-proxies to
|
||||||
|
# other backends.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# protocol.
|
||||||
|
#
|
||||||
|
# HTTP requests to the backing service will automatically have
|
||||||
|
# X-Forwarded-For and (if HTTPS) X-Forwarded-Proto headers added to them.
|
||||||
|
#
|
||||||
|
# Proxies are currently limited in the following ways:
|
||||||
|
# * http_url must be to an http endpoint (not https)
|
||||||
|
# * dns.resolver_addr is ignored and the system-wide dns is used
|
||||||
|
#
|
||||||
|
#example.com:
|
||||||
|
|
||||||
|
#http_url: "http://some.other.service.com"
|
||||||
|
#gemini_url: "gemini://some.other.service.com"
|
||||||
|
|
||||||
|
# Extra headers to add/remove to proxied requests
|
||||||
|
#http_request_headers:
|
||||||
|
# - name: Host
|
||||||
|
# value: "yet.another.service.com"
|
||||||
|
# - name: X-HEADER-TO-DELETE
|
||||||
|
# value: ""
|
||||||
|
|
||||||
|
# Set to true to prevent the domain from being served over https.
|
||||||
|
#https_disabled: false
|
||||||
|
|
||||||
|
# External domains will have a TLS key/cert generated and signed for them, but
|
||||||
|
# which will not be served by domani itself. The key/cert files will be placed
|
||||||
|
# in the configured paths.
|
||||||
|
#
|
||||||
|
# HTTPS must be enabled for external_domains to be used.
|
||||||
|
#external_domains:
|
||||||
|
#example.com
|
||||||
|
# tls_key_path: /dir/path/key.pem
|
||||||
|
# tls_cert_path: /dir/path/cert.pem
|
||||||
|
|
||||||
|
service:
|
||||||
|
|
||||||
|
# Passphrase which must be given by users who are configuring new domains via
|
||||||
|
# the web interface.
|
||||||
|
#passphrase: REQUIRED
|
||||||
|
|
||||||
|
# DNS records which users must add to their domain's DNS so that
|
||||||
|
# Domani can serve the domains. All records given must route to this Domani
|
||||||
|
# instance.
|
||||||
|
#
|
||||||
|
# A CNAME record with the interface_domain of this server is automatically
|
||||||
|
# included, if it's not null itself.
|
||||||
|
#dns_records:
|
||||||
|
#- kind: A
|
||||||
|
# addr: 127.0.0.1
|
||||||
|
|
||||||
|
#- kind: AAAA
|
||||||
|
# addr: ::1
|
||||||
|
|
||||||
|
# NOTE that the name given here must resolve to the Domani server.
|
||||||
|
#- kind: CNAME
|
||||||
|
# name: domain.com
|
||||||
|
|
||||||
|
#http:
|
||||||
|
|
||||||
|
# The address to listen for HTTP requests on. This must use port 80 if
|
||||||
|
# https_addr is set.
|
||||||
|
#http_addr: "[::]:3080"
|
||||||
|
|
||||||
|
# The address to listen for HTTPS requests on. Defaults to not having HTTP
|
||||||
|
# enabled. You can enable HTTPS by setting this to "[::]:443".
|
||||||
|
#https_addr: null
|
||||||
|
|
||||||
|
#gemini:
|
||||||
|
|
||||||
|
# The address to listen for gemini requests on. Set this to null to disable
|
||||||
|
# gemini support.
|
||||||
|
#gemini_addr: "[::]:3965"
|
||||||
|
|
@ -51,7 +51,7 @@
|
|||||||
export CARGO_HOME=$(pwd)/.cargo
|
export CARGO_HOME=$(pwd)/.cargo
|
||||||
|
|
||||||
if [ -f "config.yml" ]; then
|
if [ -f "config.yml" ]; then
|
||||||
export DOMANI_CONFIG_PATH=config.yml
|
export DOMANI_CONFIG_PATH=config-dev.yml
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
} // opensslEnv);
|
} // opensslEnv);
|
||||||
|
@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use std::{collections, future, net, sync};
|
use std::{collections, future, net, sync};
|
||||||
|
|
||||||
use crate::error::unexpected;
|
use crate::error::unexpected::{self, Mappable};
|
||||||
use crate::{domain, service, task_stack};
|
use crate::{domain, service, task_stack};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@ -157,6 +157,39 @@ impl Service {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn https_redirect(&self, domain: domain::Name, req: Request<Body>) -> Response<Body> {
|
||||||
|
let https_addr = self.config.http.https_addr.unwrap();
|
||||||
|
|
||||||
|
(|| {
|
||||||
|
let mut uri_parts = http::uri::Parts::default();
|
||||||
|
uri_parts.scheme = Some(http::uri::Scheme::HTTPS);
|
||||||
|
uri_parts.authority = Some(
|
||||||
|
http::uri::Authority::from_maybe_shared(format!(
|
||||||
|
"{}:{}",
|
||||||
|
&domain,
|
||||||
|
https_addr.port()
|
||||||
|
))
|
||||||
|
.or_unexpected_while("constructing authority")?,
|
||||||
|
);
|
||||||
|
uri_parts.path_and_query = req.uri().path_and_query().cloned();
|
||||||
|
|
||||||
|
let uri: http::uri::Uri = uri_parts
|
||||||
|
.try_into()
|
||||||
|
.or_unexpected_while("constructing new URI")?;
|
||||||
|
|
||||||
|
Response::builder()
|
||||||
|
.status(http::status::StatusCode::PERMANENT_REDIRECT)
|
||||||
|
.header("Location", uri.to_string())
|
||||||
|
.body(Body::empty())
|
||||||
|
.or_unexpected_while("building redirect")
|
||||||
|
})()
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
self.internal_error(
|
||||||
|
format!("failed to redirect from {} to https: {}", req.uri(), err).as_str(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn render_page<T>(&self, name: &str, data: T) -> Response<Body>
|
fn render_page<T>(&self, name: &str, data: T) -> Response<Body>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
@ -504,34 +537,23 @@ impl Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If HTTPS is enabled then only .well-known endpoints are allowed over HTTP (because they
|
// We only allow HTTP requests when HTTPS is enabled in specific cases:
|
||||||
// require it). Otherwise we redirect all HTTP requests to HTTPS.
|
// - /.well-known urls
|
||||||
if self.config.http.https_addr.is_some() && !req_is_https {
|
// - proxied domains with https_disabled set on them
|
||||||
let https_addr = self.config.http.https_addr.unwrap();
|
// everything else must use https if possible.
|
||||||
|
let https_upgradable = self.config.http.https_addr.is_some() && !req_is_https;
|
||||||
let mut uri_parts = http::uri::Parts::default();
|
|
||||||
uri_parts.scheme = Some(http::uri::Scheme::HTTPS);
|
|
||||||
uri_parts.authority = Some(
|
|
||||||
http::uri::Authority::from_maybe_shared(format!(
|
|
||||||
"{}:{}",
|
|
||||||
&domain,
|
|
||||||
https_addr.port()
|
|
||||||
))
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
uri_parts.path_and_query = req.uri().path_and_query().cloned();
|
|
||||||
|
|
||||||
let uri: http::uri::Uri = uri_parts.try_into().unwrap();
|
|
||||||
|
|
||||||
return Response::builder()
|
|
||||||
.status(http::status::StatusCode::PERMANENT_REDIRECT)
|
|
||||||
.header("Location", uri.to_string())
|
|
||||||
.body(Body::empty())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(config) = self.proxied_domains.get(&domain) {
|
if let Some(config) = self.proxied_domains.get(&domain) {
|
||||||
if let Some(ref http_url) = config.http_url {
|
if config.http_url.is_none() {
|
||||||
|
return self.render_error_page(404, "Domain not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
@ -542,13 +564,13 @@ 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(),
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
return self.render_error_page(404, "Domain not found");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if https_upgradable {
|
||||||
|
return self.https_redirect(domain, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if Some(&domain) == self.interface_domain.as_ref() {
|
if Some(&domain) == self.interface_domain.as_ref() {
|
||||||
|
Loading…
Reference in New Issue
Block a user