110 lines
4.1 KiB
Rust
110 lines
4.1 KiB
Rust
use std::path::Path;
|
|
use std::sync::Arc;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use tokio::sync::Mutex;
|
|
use tracing::error;
|
|
|
|
use crate::commands::write_config::INTERNAL_CONFIG_FOLDER_NAME;
|
|
use crate::commands::{run::run_cmd, validate::ValidateCommand};
|
|
|
|
pub struct ReloadCommand {
|
|
is_reloading: Mutex<bool>,
|
|
}
|
|
|
|
struct ReloadResetGuard<'a> {
|
|
guard: tokio::sync::MutexGuard<'a, bool>,
|
|
}
|
|
|
|
impl<'a> Drop for ReloadResetGuard<'a> {
|
|
fn drop(&mut self) {
|
|
*self.guard = false;
|
|
}
|
|
}
|
|
|
|
impl Default for ReloadCommand {
|
|
fn default() -> Self {
|
|
Self {
|
|
is_reloading: Mutex::new(false),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ReloadCommand {
|
|
pub async fn validate_and_reload(
|
|
&self,
|
|
config_name: &str,
|
|
timestamp: u64,
|
|
validate_cmd: Arc<ValidateCommand>,
|
|
) -> Result<(i32, String), Box<dyn std::error::Error + Send + Sync>> {
|
|
// ensure the written fragment exists
|
|
validate_cmd.validate(config_name, timestamp).await?;
|
|
|
|
// Now atomically swap the YANPM.conf symlink to point to the new fragment
|
|
// so nginx -t validates the composed main config. If validation fails,
|
|
// attempt to restore the previous symlink.
|
|
let filename = crate::commands::run::to_file_name(config_name, timestamp)?;
|
|
let nginx_dir = validate_cmd.nginx_config_dir();
|
|
let symlink_path = nginx_dir.join("YANPM.conf");
|
|
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
|
let tmp_name = format!("YANPM.conf.tmp.{}.{}", std::process::id(), now);
|
|
let tmp_path = nginx_dir.join(&tmp_name);
|
|
|
|
// prepare relative target: INTERNAL_CONFIG_FOLDER_NAME/<filename>
|
|
let rel_target = Path::new(INTERNAL_CONFIG_FOLDER_NAME).join(&filename);
|
|
|
|
// read previous target if exists
|
|
let previous_target = std::fs::read_link(&symlink_path).ok();
|
|
|
|
// Acquire reload guard before mutating the symlink to avoid races
|
|
let reloading_lock = self.is_reloading.lock().await;
|
|
if *reloading_lock {
|
|
return Err("Reload already in progress".into());
|
|
}
|
|
// set flag to true and ensure it is reset on drop
|
|
let mut mut_guard = reloading_lock;
|
|
*mut_guard = true;
|
|
let _reset_guard = ReloadResetGuard { guard: mut_guard };
|
|
|
|
// create temporary symlink and atomically rename into place
|
|
std::os::unix::fs::symlink(&rel_target, &tmp_path)?;
|
|
tokio::fs::rename(&tmp_path, &symlink_path).await?;
|
|
|
|
// validate composed main config now that symlink points to new fragment
|
|
if let Err(e) = validate_cmd.validate_all().await {
|
|
// restore previous symlink state while still holding the guard
|
|
if let Some(prev) = previous_target {
|
|
let restore_tmp =
|
|
nginx_dir.join(format!("YANPM.conf.restore.{}.{}", std::process::id(), now));
|
|
std::os::unix::fs::symlink(&prev, &restore_tmp)?;
|
|
if let Err(err) = tokio::fs::rename(&restore_tmp, &symlink_path).await {
|
|
error!(
|
|
"Failed to restore previous YANPM.conf symlink after validation error: {}",
|
|
err
|
|
);
|
|
}
|
|
} else if let Err(err) = tokio::fs::remove_file(&symlink_path).await {
|
|
error!(
|
|
"Failed to remove YANPM.conf symlink after validation error: {}",
|
|
err
|
|
);
|
|
}
|
|
return Err(e);
|
|
}
|
|
|
|
// reload the running nginx master process (no -c) so it reloads its configured main config
|
|
// Prefer the restricted sudo wrapper if available, fall back to direct nginx reload.
|
|
// TODO: allow configuring the path to the wrapper
|
|
match run_cmd("sudo", &["-n", "/usr/local/sbin/yanpm-nginx-reload"], 10).await {
|
|
Ok(res) => Ok(res),
|
|
Err(e) => {
|
|
error!(
|
|
"sudo reload wrapper failed, falling back to direct nginx reload: {}",
|
|
e
|
|
);
|
|
run_cmd("nginx", &["-s", "reload"], 10).await
|
|
}
|
|
}
|
|
}
|
|
}
|