/* * Copyright (C) 2022 Aravinth Manivannan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * 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() { if let Ok(res) = child.wait().await { return res.success(); } } false } 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() } }