diff --git a/env/nginx_bind_le/Cargo.lock b/env/nginx_bind_le/Cargo.lock
index 7fc32e6..639164e 100644
--- a/env/nginx_bind_le/Cargo.lock
+++ b/env/nginx_bind_le/Cargo.lock
@@ -417,7 +417,10 @@ name = "nginx_bind_le"
version = "0.1.0"
dependencies = [
"async-trait",
+ "lazy_static",
"libconductor",
+ "libconfig",
+ "rust-embed",
"serde",
"serde_json",
"tera",
@@ -634,6 +637,40 @@ version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+[[package]]
+name = "rust-embed"
+version = "6.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "6.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "7.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
[[package]]
name = "ryu"
version = "1.0.11"
@@ -697,6 +734,17 @@ dependencies = [
"digest",
]
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
@@ -801,6 +849,7 @@ dependencies = [
"autocfg",
"bytes",
"libc",
+ "memchr",
"mio",
"num_cpus",
"pin-project-lite",
diff --git a/env/nginx_bind_le/Cargo.toml b/env/nginx_bind_le/Cargo.toml
index f76f95c..a41b1fb 100644
--- a/env/nginx_bind_le/Cargo.toml
+++ b/env/nginx_bind_le/Cargo.toml
@@ -2,6 +2,7 @@
name = "nginx_bind_le"
version = "0.1.0"
edition = "2021"
+include = ["/templates"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -9,8 +10,11 @@ edition = "2021"
serde = { version = "1", features=["derive"]}
serde_json = { version ="1", features = ["raw_value"]}
async-trait = "0.1.57"
-tokio = { version = "1.23.0", features = ["process"] }
+tokio = { version = "1.23.0", features = ["process", "fs", "io-util"] }
tera = "1.17.1"
+rust-embed = "6.4.2"
+lazy_static = "1.4.0"
+libconfig = { version = "0.1.0", git = "https://git.batsense.net/librepages/libconfig" }
[dependencies.libconductor]
path = "../libconductor"
diff --git a/env/nginx_bind_le/src/lib.rs b/env/nginx_bind_le/src/lib.rs
index 0a4201f..3547538 100644
--- a/env/nginx_bind_le/src/lib.rs
+++ b/env/nginx_bind_le/src/lib.rs
@@ -20,6 +20,9 @@ use tokio::process::Command;
use libconductor::*;
mod nginx;
+mod templates;
+
+use nginx::Nginx;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NginxBindLEConductor;
@@ -33,10 +36,17 @@ impl Conductor for NginxBindLEConductor {
EventType::NewSite {
hostname,
path,
- branch,
- } => unimplemented!(),
- EventType::Config { data } => unimplemented!(),
- EventType::DeleteSite { hostname } => unimplemented!(),
+ branch: _branch,
+ } => {
+ Nginx::new_site(&hostname, &path, None).await.unwrap();
+ }
+ EventType::Config { hostname, data } => {
+ unimplemented!();
+ // Nginx::new_site(&hostname, &path, Some(data)).await.unwrap();
+ }
+ EventType::DeleteSite { hostname } => {
+ Nginx::rm_site(&hostname).await.unwrap();
+ }
};
}
fn name(&self) -> &'static str {
@@ -44,7 +54,7 @@ impl Conductor for NginxBindLEConductor {
}
async fn health(&self) -> bool {
- nginx::Nginx::status().await
+ nginx::Nginx::env_exists() && nginx::Nginx::status().await
}
}
#[cfg(test)]
@@ -54,8 +64,26 @@ mod tests {
#[tokio::test]
async fn all_good() {
+ const HOSTNAME: &str = "lab.batsense.net";
let c = NginxBindLEConductor {};
assert_eq!(c.name(), CONDUCTOR_NAME);
assert!(c.health().await);
+ if Nginx::site_exists(HOSTNAME) {
+ c.process(EventType::DeleteSite {
+ hostname: HOSTNAME.into(),
+ })
+ .await;
+ }
+
+ c.process(EventType::NewSite {
+ hostname: HOSTNAME.into(),
+ branch: "librepages".into(),
+ path: "/var/www/website/".into(),
+ })
+ .await;
+ c.process(EventType::DeleteSite {
+ hostname: HOSTNAME.into(),
+ })
+ .await;
}
}
diff --git a/env/nginx_bind_le/src/nginx.rs b/env/nginx_bind_le/src/nginx.rs
index 4682722..2ba062b 100644
--- a/env/nginx_bind_le/src/nginx.rs
+++ b/env/nginx_bind_le/src/nginx.rs
@@ -14,11 +14,30 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
+use std::cell::RefCell;
+use std::error::Error;
+use std::path::{Path, PathBuf};
+
+use tera::*;
+use tokio::fs;
+use tokio::io::AsyncWriteExt;
use tokio::process::Command;
+use crate::templates::*;
+
pub struct Nginx;
impl Nginx {
+ pub async fn reload() -> MyResult<()> {
+ Command::new("sudo")
+ .arg("nginx")
+ .arg("-s")
+ .arg("reload")
+ .spawn()?
+ .wait()
+ .await?;
+ Ok(())
+ }
pub async fn status() -> bool {
async fn run_async_cmd(cmd: &mut Command) -> bool {
if let Ok(mut child) = cmd.spawn() {
@@ -30,4 +49,84 @@ impl Nginx {
}
run_async_cmd(Command::new("sudo").arg("nginx").arg("-t")).await
}
+
+ pub async fn new_site(
+ hostname: &str,
+ path: &str,
+ config: Option,
+ ) -> MyResult<()> {
+ let config = CreateSite::new(hostname, path, config);
+ let contents = config.render();
+ let staging = Self::get_staging(hostname);
+ let prod = Self::get_prod(hostname);
+
+ let mut file = fs::File::create(&staging).await?;
+ file.write_all(contents.as_bytes()).await?;
+ file.sync_all().await?;
+ fs::symlink(&staging, &prod).await?;
+ Self::reload().await
+ }
+
+ fn get_staging(hostname: &str) -> PathBuf {
+ Path::new(NGINX_STAGING_CONFIG_PATH).join(hostname)
+ }
+ fn get_prod(hostname: &str) -> PathBuf {
+ Path::new(NGINX_PRODUCTION_CONFIG_PATH).join(hostname)
+ }
+
+ pub fn site_exists(hostname: &str) -> bool {
+ Self::get_prod(hostname).exists()
+ }
+
+ pub async fn rm_site(hostname: &str) -> MyResult<()> {
+ let staging = Self::get_staging(hostname);
+ let prod = Self::get_prod(hostname);
+
+ fs::remove_file(&prod).await?;
+ fs::remove_file(&staging).await?;
+ Self::reload().await
+ }
+
+ pub fn env_exists() -> bool {
+ let prod = Path::new(NGINX_PRODUCTION_CONFIG_PATH);
+ let staging = Path::new(NGINX_STAGING_CONFIG_PATH);
+ prod.exists() && prod.is_dir() && staging.exists() && staging.is_dir()
+ }
+}
+
+pub struct CreateSite {
+ ctx: RefCell,
+}
+
+pub const CREATE_SITE: TemplateFile = TemplateFile::new("create_site", "nginx/create-site.j2");
+pub const CREATE_SITE_FRAGMENT: TemplateFile =
+ TemplateFile::new("new_site_frag", "nginx/_new_site.fragement.j2");
+
+pub const HOSTNAME_KEY: &str = "hostname";
+pub const DOMAINS_KEY: &str = "domains";
+pub const PATH_KEY: &str = "path";
+pub const REDIRECTS_KEY: &str = "redirects";
+
+pub const NGINX_STAGING_CONFIG_PATH: &str = "/etc/librepages/nginx/sites-available/";
+pub const NGINX_PRODUCTION_CONFIG_PATH: &str = "/etc/librepages/nginx/sites-enabled/";
+
+type MyResult = std::result::Result>;
+
+impl CreateSite {
+ fn new(hostname: &str, path: &str, config: Option) -> Self {
+ let ctx = RefCell::new(context());
+ ctx.borrow_mut().insert(HOSTNAME_KEY, hostname);
+ ctx.borrow_mut().insert(PATH_KEY, path);
+ if let Some(config) = config {
+ ctx.borrow_mut().insert(REDIRECTS_KEY, &config.redirects);
+ ctx.borrow_mut().insert(DOMAINS_KEY, &config.domains);
+ }
+ Self { ctx }
+ }
+
+ fn render(&self) -> String {
+ TEMPLATES
+ .render(CREATE_SITE.name, &self.ctx.borrow())
+ .unwrap()
+ }
}