feat: add agent module with Nginx service commands and routes

- Introduced a new agent module with commands for managing Nginx configurations.
- Implemented `NginxService` for handling reload, validation, and configuration writing.
- Added routes for status, validation, and configuration writing using Axum.
- Created necessary command files: `reload.rs`, `run.rs`, `validate.rs`, `write_config.rs`.
- Updated `Cargo.toml` and `Cargo.lock` to include new dependencies.
- Added `.gitignore` for the agent module.
- Updated `justfile` to include OpenAPI generation for the agent.
This commit is contained in:
GW_MC
2025-12-21 15:32:42 +08:00
parent 8334da8cf1
commit 4ca59d2bb6
12 changed files with 1023 additions and 3 deletions

83
apps/agent/src/main.rs Normal file
View File

@@ -0,0 +1,83 @@
#![forbid(unsafe_code)]
mod commands;
mod routes;
use axum::routing::get;
use axum::{Router, routing::post};
use clap::Parser;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::net::UnixListener;
use tracing::{error, info};
use crate::commands::NginxService;
use crate::routes::{status, validate, validate_and_reload, write_config};
#[derive(Parser)]
struct Args {
/// Unix socket path to bind the daemon to
sock: String,
/// Directory where generated nginx config files will be written
#[arg(long, default_value = "/etc/nginx/conf.d")]
nginx_config_dir: PathBuf,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let args = Args::parse();
let sock = args.sock;
let path = PathBuf::from(&sock);
if let Some(dir) = path.parent() {
tokio::fs::create_dir_all(dir).await?;
// permissive; set tighter perms in production via image/build steps
tokio::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o770)).await?;
}
// If an existing path exists at the socket location, ensure it's a socket
match tokio::fs::metadata(&path).await {
Ok(md) => {
use std::os::unix::fs::FileTypeExt;
if md.file_type().is_socket() {
tokio::fs::remove_file(&path).await?;
} else {
return Err(
format!("Socket path {} exists and is not a socket", path.display()).into(),
);
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(e.into()),
}
// bind using tokio's UnixListener (avoids converting a blocking std listener)
let listener = UnixListener::bind(&path)?;
// set socket perms to 0660 (best-effort)
if let Err(err) =
tokio::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o660)).await
{
error!(
"Warning: failed to set permissions on socket {}: {}",
path.display(),
err
);
}
let scheduler = Arc::new(tokio_cron_scheduler::JobScheduler::new().await?);
let app = Router::new()
.route("/status", get(status))
.route("/validate_and_reload", post(validate_and_reload))
.route("/validate", post(validate))
.route("/write_config", post(write_config))
.with_state(NginxService::new(scheduler.clone(), args.nginx_config_dir).await?);
scheduler.start().await?;
info!("Starting yanpm-daemon on unix socket: {}", sock);
axum::serve::serve(listener, app).await?;
Ok(())
}