ci/woodpecker/push/tests Pipeline was successful
limit tool result to open router tool response (with truncated info for ai) Hard kill if graceful shutdown is too long
158 lines
4.1 KiB
Rust
158 lines
4.1 KiB
Rust
use std::time::Duration;
|
|
|
|
use crate::{
|
|
bot::Bot,
|
|
gitea::{GiteaAPI, WebhookType},
|
|
open_router::OpenRouterClient,
|
|
sandbox::SandboxConfig,
|
|
state::AppState,
|
|
};
|
|
|
|
use dotenvy::dotenv;
|
|
use tokio::signal::unix::{SignalKind, signal};
|
|
use tokio_util::sync::CancellationToken;
|
|
use tracing::{info, warn};
|
|
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
|
|
|
mod api;
|
|
mod bot;
|
|
mod bot_actions;
|
|
mod consts;
|
|
mod env;
|
|
mod errors;
|
|
mod gitea;
|
|
mod metrics;
|
|
mod open_router;
|
|
mod sandbox;
|
|
mod state;
|
|
mod text;
|
|
|
|
/// Délai laissé aux reviews en cours et aux serveurs pour s'arrêter proprement.
|
|
///
|
|
/// Sans lui, une review prise dans une sandbox récalcitrante garderait le processus
|
|
/// en vie jusqu'à ce que le superviseur le tue.
|
|
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(60);
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
dotenv().ok();
|
|
|
|
tracing_subscriber::registry()
|
|
.with(fmt::layer())
|
|
.with(
|
|
EnvFilter::try_from_default_env() // lit RUST_LOG depuis l'env
|
|
.unwrap_or_else(|_| EnvFilter::new("info")),
|
|
)
|
|
.init();
|
|
|
|
let _sentry_guard = if let Ok(sentry_dsn) = env::try_get_env("SENTRY_DSN") {
|
|
info!("Initialize sentry");
|
|
|
|
Some(sentry::init(
|
|
sentry::ClientOptions::new()
|
|
.dsn(&sentry_dsn)
|
|
.maybe_release(sentry::release_name!())
|
|
.send_default_pii(true),
|
|
))
|
|
} else {
|
|
warn!("SENTRY_DSN not set, sentry will not be initialized");
|
|
None
|
|
};
|
|
|
|
tokio::runtime::Runtime::new()?.block_on(run())
|
|
}
|
|
|
|
async fn run() -> anyhow::Result<()> {
|
|
let config = env::load_config()?;
|
|
|
|
if let Some(metric_bind_addr) = &config.metrics_bind_addr {
|
|
metrics::install(metric_bind_addr)?;
|
|
}
|
|
|
|
let gitea_api = GiteaAPI::new(&config.gitea_url, &config.gitea_token, config.gitea_timeout)?;
|
|
let gitea_user = gitea_api.get_authorized_user().await?;
|
|
|
|
info!(
|
|
port = config.http_port,
|
|
model = %config.open_router_model,
|
|
gitea_url = %config.gitea_url,
|
|
bot_name = %gitea_user.login,
|
|
"Starting Herald"
|
|
);
|
|
|
|
let open_router_client = OpenRouterClient::new(
|
|
&config.open_router_api_key,
|
|
&config.open_router_model,
|
|
config.open_router_timeout,
|
|
)?;
|
|
|
|
let shutdown = CancellationToken::new();
|
|
|
|
let sandbox = SandboxConfig {
|
|
runtime: devcontainer_rs::ContainerRuntime::connect()?,
|
|
max_iterations: config.sandbox_max_iterations,
|
|
};
|
|
|
|
if !sandbox.runtime.available().await {
|
|
warn!(
|
|
endpoint = sandbox.runtime.endpoint(),
|
|
"Container daemon is not reachable, every review will fail"
|
|
);
|
|
}
|
|
|
|
let bot = Bot::new(
|
|
gitea_user.login,
|
|
gitea_api,
|
|
open_router_client,
|
|
config.bot_max_concurrent,
|
|
config.open_router_model.clone(),
|
|
sandbox,
|
|
);
|
|
|
|
let (tx, rx) = tokio::sync::mpsc::channel::<WebhookType>(config.bot_max_concurrent * 2);
|
|
let app_state = AppState {
|
|
bot_tx: tx,
|
|
bot: bot.clone(),
|
|
config,
|
|
};
|
|
|
|
let signal = async {
|
|
let mut sigterm = signal(SignalKind::terminate())?;
|
|
let mut sigint = signal(SignalKind::interrupt())?;
|
|
tokio::select! {
|
|
_ = sigterm.recv() => info!("Received SIGTERM"),
|
|
_ = sigint.recv() => info!("Received SIGINT"),
|
|
}
|
|
|
|
info!("Shutting down...");
|
|
shutdown.cancel();
|
|
anyhow::Ok(())
|
|
};
|
|
|
|
let shutdown_deadline = async {
|
|
shutdown.cancelled().await;
|
|
tokio::time::sleep(SHUTDOWN_TIMEOUT).await;
|
|
};
|
|
|
|
tokio::select! {
|
|
result = async {
|
|
tokio::try_join!(
|
|
bot.start(rx, shutdown.clone()),
|
|
api::start(app_state, shutdown.clone()),
|
|
signal
|
|
)
|
|
} => {
|
|
result?;
|
|
}
|
|
() = shutdown_deadline => {
|
|
warn!(
|
|
timeout = ?SHUTDOWN_TIMEOUT,
|
|
"Shutdown did not finish in time, exiting anyway"
|
|
);
|
|
}
|
|
}
|
|
|
|
info!("Shutdown complete");
|
|
|
|
Ok(())
|
|
}
|