sscdc/src/caddy.rs
Moritz Ruth 5b6d2c6cf7
Some checks failed
Build / build (push) Failing after 48s
commit 03
2025-02-28 21:40:45 +01:00

181 lines
6.6 KiB
Rust

use async_std::os::unix::net::UnixStream;
use camino::{Utf8Path, Utf8PathBuf};
use color_eyre::Result;
use color_eyre::eyre::{OptionExt, eyre};
use http_client::http_types::{Method, StatusCode, Url};
use http_client::{Request, Response};
use log::debug;
use serde_json::json;
use std::process::Stdio;
use std::str::FromStr;
use std::time::Duration;
use async_std::io::WriteExt;
use async_std::process::Command;
pub struct CaddyController {
api_socket_path: Utf8PathBuf,
admin_api_socket_path: Utf8PathBuf,
}
impl CaddyController {
pub async fn upsert_site_configuration(&mut self, domain: &String, content_path: &Utf8Path) -> Result<()> {
let configuration_object = json!({
"@id": domain,
"match": [{ "host": [domain] }],
"handle": [{
"handler": "subroute",
"routes": [
{
"group": "1",
"match": [{ "path": ["/_sscdc/*"] }],
"handle": [
{
"handler": "rewrite",
"strip_path_prefix": "/_sscdc"
},
{
"handler": "reverse_proxy",
"upstreams": [{ "dial": format!("unix/{}", &self.api_socket_path) }],
}
]
},
{
"group": "1",
"handle": [
{ "handler": "vars", "root": content_path.to_string() },
{
"handler": "file_server",
"precompressed": {
"br": {},
"gzip": {}
},
"precompressed_order": ["br", "gzip"]
}
]
}
]
}],
"terminal": true
});
let mut request = Request::post(Url::from_str(&format!("http://localhost/id/{}", domain))?);
request.insert_header("Content-Type", "application/json");
request.set_body(configuration_object.to_string());
let mut response = request_uds(&self.admin_api_socket_path, request.clone()).await.unwrap();
match response.status() {
StatusCode::Ok => {
debug!("Caddy configuration for {domain} was updated.")
}
StatusCode::NotFound => {
// The site does not yet exist.
*request.url_mut() = Url::from_str("http://localhost/config/apps/http/servers/srv0/routes")?;
let mut response = request_uds(&self.admin_api_socket_path, request).await.unwrap();
let status = response.status();
if !status.is_success() {
return Err(eyre!(
"The configuration update request to Caddy failed with status code {}:\n{}",
status,
response.body_string().await.unwrap()
));
}
debug!("Caddy configuration for {domain} was created.")
}
c => {
return Err(eyre!(
"The configuration update request to Caddy failed with status code {}:\n{}",
c,
response.body_string().await.unwrap()
));
}
}
Ok(())
}
}
fn get_initial_caddy_configuration_object(admin_api_socket_path: &Utf8Path, api_socket_path: &Utf8Path) -> serde_json::Value {
json!({
"admin": {
"listen": format!("unix/{}", admin_api_socket_path),
"enforce_origin": false,
"origins": ["localhost"],
"config": { "persist": false },
},
"logging": {
"sink": {
"writer": { "output": "discard" }
},
"logs": {
"": {
"writer": { "output": "discard" }
}
}
},
"storage": {
"module": "file_system",
"root": "/tmp/sscdc-caddy"
},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [":80"],
"routes": [{
"match": [{ "path": ["/_sscdc/*"] }],
"handle": [
{
"handler": "rewrite",
"strip_path_prefix": "/_sscdc"
},
{
"handler": "reverse_proxy",
"upstreams": [{ "dial": format!("unix/{}", api_socket_path) }],
}
]
}],
"automatic_https": { "disable": true }
}
}
}
}
})
}
async fn request_uds(path: &Utf8Path, request: Request) -> http_client::http_types::Result<Response> {
let stream = UnixStream::connect(path.as_std_path()).await?;
async_h1::connect(stream, request).await
}
pub async fn start_caddy(api_socket_path: &Utf8Path, sockets_directory_path: &Utf8Path) -> Result<CaddyController> {
let caddy_admin_socket_path = sockets_directory_path.join("caddy-admin-api.sock");
// Stop not properly cleaned up proxy.
let _ = request_uds(&caddy_admin_socket_path, Request::new(Method::Post, "http://localhost/stop")).await;
let caddy_path = option_env!("CADDY_PATH").unwrap_or("caddy");
debug!("Caddy path: {}", caddy_path);
// Spawn a new proxy.
let process = Command::new(caddy_path).args(&["run", "--config", "-"]).stdin(Stdio::piped()).spawn()?;
let initial_configuration_object = get_initial_caddy_configuration_object(&caddy_admin_socket_path, api_socket_path);
let initial_configuration_string = initial_configuration_object.to_string();
debug!("Initial caddy configuration: {}", initial_configuration_string);
process
.stdin
.ok_or_eyre("The Caddy process STDIN is not open.")?
.write_all(initial_configuration_string.as_ref())
.await?;
async_std::task::sleep(Duration::from_secs(1)).await;
Ok(CaddyController {
api_socket_path: api_socket_path.to_path_buf(),
admin_api_socket_path: caddy_admin_socket_path,
})
}