feat: implement Dockerfile and service scripts for yanpm-agent

This commit is contained in:
GW_MC
2025-12-21 17:51:43 +08:00
parent 4ca59d2bb6
commit 7781878c2d
4 changed files with 142 additions and 19 deletions

View File

@@ -5,7 +5,7 @@ mod routes;
use axum::routing::get;
use axum::{Router, routing::post};
use clap::Parser;
use clap::{Arg, Command};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::sync::Arc;
@@ -15,33 +15,89 @@ 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,
}
const SOCK_ARG: &str = "sock";
const NGINX_CONFIG_DIR_ARG: &str = "nginx_config_dir";
const SOCK_ENV: &str = "YANPM_AGENT_SOCK";
const NGINX_CONFIG_DIR_ENV: &str = "YANPM_NGINX_CONFIG_DIR";
const SOCK_DEFAULT: &str = "./yanpm-agent.sock";
const NGINX_CONFIG_DIR_DEFAULT: &str = "/etc/nginx/conf.d";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let args = Args::parse();
let sock = args.sock;
let args = Command::new("yanpm-agent")
.arg(
Arg::new("sock")
.short('s')
.long("sock")
.value_name("SOCK_PATH")
.help("Unix socket path to bind the agent daemon to")
.required(false),
)
.arg(
Arg::new("nginx_config_dir")
.short('d')
.long("nginx-config-dir")
.value_name("NGINX_CONFIG_DIR")
.help("Directory where generated nginx config files will be written")
.required(false),
)
.about("YANPM Agent Daemon")
.get_matches();
let subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.with_level(true)
.with_timer(tracing_subscriber::fmt::time::SystemTime)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Failed to set global default subscriber");
let sock = args
.get_one::<String>(SOCK_ARG)
.cloned()
.unwrap_or_else(|| std::env::var(SOCK_ENV).unwrap_or_else(|_| SOCK_DEFAULT.to_string()));
let nginx_config_dir = args
.get_one::<String>(NGINX_CONFIG_DIR_ARG)
.cloned()
.unwrap_or_else(|| {
std::env::var(NGINX_CONFIG_DIR_ENV)
.unwrap_or_else(|_| NGINX_CONFIG_DIR_DEFAULT.to_string())
});
let path = PathBuf::from(&sock);
if let Some(dir) = path.parent() {
tokio::fs::create_dir_all(dir).await?;
tokio::fs::create_dir_all(dir).await.unwrap_or_else(|err| {
error!(
"Warning: failed to create socket directory {}: {}",
dir.display(),
err
)
});
// permissive; set tighter perms in production via image/build steps
tokio::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o770)).await?;
tokio::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o770))
.await
.unwrap_or_else(|err| {
error!(
"Warning: failed to set permissions on socket directory {}: {}",
dir.display(),
err
)
});
}
// 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?;
tokio::fs::remove_file(&path).await.unwrap_or_else(|err| {
error!(
"Warning: failed to remove existing socket file {}: {}",
path.display(),
err
)
});
} else {
return Err(
format!("Socket path {} exists and is not a socket", path.display()).into(),
@@ -49,11 +105,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(e.into()),
Err(e) => {
return Err(format!("Failed to stat socket path {}: {}", path.display(), e).into());
}
}
// bind using tokio's UnixListener (avoids converting a blocking std listener)
let listener = UnixListener::bind(&path)?;
let listener = UnixListener::bind(&path).expect("Failed to bind to unix socket");
// set socket perms to 0660 (best-effort)
if let Err(err) =
tokio::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o660)).await
@@ -72,12 +130,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.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?);
.with_state(NginxService::new(scheduler.clone(), PathBuf::from(nginx_config_dir)).await?);
scheduler.start().await?;
info!("Starting yanpm-daemon on unix socket: {}", sock);
axum::serve::serve(listener, app).await?;
axum::serve::serve(listener, app)
.await
.expect("Failed to start axum server");
Ok(())
}