134 lines
3.5 KiB
Rust
134 lines
3.5 KiB
Rust
use std::env;
|
|
use std::fs::File;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::{
|
|
mpsc::{channel, Receiver, Sender},
|
|
Mutex,
|
|
};
|
|
use std::thread;
|
|
|
|
use anyhow::Context;
|
|
use clap::{crate_version, App, Arg};
|
|
use log::{error, info};
|
|
use rocket::{http::Status, post, routes, State};
|
|
|
|
mod gitea;
|
|
use gitea::GiteaWebHook;
|
|
|
|
mod job;
|
|
use job::Job;
|
|
|
|
mod settings;
|
|
use settings::GlobalSettings;
|
|
|
|
mod signature;
|
|
use signature::SignedJson;
|
|
|
|
struct JobSender(Mutex<Sender<Job>>);
|
|
struct Secret(String);
|
|
|
|
#[post("/", data = "<payload>")]
|
|
fn gitea_webhook(
|
|
payload: SignedJson<GiteaWebHook>,
|
|
sender: &State<JobSender>,
|
|
config: &State<GlobalSettings>,
|
|
) -> Status {
|
|
if config
|
|
.blacklist
|
|
.iter()
|
|
.any(|re| re.is_match(&payload.repository.full_name))
|
|
{
|
|
info!(
|
|
"Ignoring webhook for repo {} which is blacklisted",
|
|
payload.repository.full_name
|
|
);
|
|
return Status::Ok;
|
|
}
|
|
|
|
{
|
|
let sender = sender.0.lock().unwrap();
|
|
let repo = &payload.repository;
|
|
sender.send(Job::new(repo.clone())).unwrap();
|
|
}
|
|
|
|
Status::Ok
|
|
}
|
|
|
|
fn repo_updater(rx: Receiver<Job>, homedir: PathBuf, config: GlobalSettings) {
|
|
loop {
|
|
let mut job = rx.recv().unwrap();
|
|
|
|
if let Err(err) = job.run(&homedir, &config) {
|
|
error!("couldn't process job: {}", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_config(home: &Path, flags: &clap::ArgMatches) -> anyhow::Result<GlobalSettings> {
|
|
// prioritize CLI flag, then env var
|
|
let config_path = flags.value_of("config").map(PathBuf::from);
|
|
let config_path = config_path.or_else(|| env::var("LOHR_CONFIG").map(PathBuf::from).ok());
|
|
|
|
let file = match config_path {
|
|
Some(config_path) => File::open(&config_path).with_context(|| {
|
|
format!(
|
|
"could not open provided configuration file at {}",
|
|
config_path.display()
|
|
)
|
|
})?,
|
|
None => {
|
|
// check if file exists in lohr home
|
|
let config_path = home.join("lohr-config.yaml");
|
|
if !config_path.is_file() {
|
|
return Ok(Default::default());
|
|
}
|
|
|
|
File::open(config_path).context("failed to open configuration file in LOHR_HOME")?
|
|
}
|
|
};
|
|
|
|
serde_yaml::from_reader(file).context("could not parse configuration file")
|
|
}
|
|
|
|
#[rocket::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let matches = App::new("lohr")
|
|
.version(crate_version!())
|
|
.about("Git mirroring daemon")
|
|
.arg(
|
|
Arg::with_name("config")
|
|
.short("c")
|
|
.long("config")
|
|
.value_name("FILE")
|
|
.help("Use a custom config file")
|
|
.takes_value(true),
|
|
)
|
|
.get_matches();
|
|
|
|
let (sender, receiver) = channel();
|
|
|
|
let homedir = env::var("LOHR_HOME").unwrap_or_else(|_| "./".to_string());
|
|
let homedir: PathBuf = homedir.into();
|
|
let homedir = homedir.canonicalize().expect("LOHR_HOME isn't valid!");
|
|
|
|
let secret = env::var("LOHR_SECRET")
|
|
.expect("please provide a secret, otherwise anyone can send you a malicious webhook");
|
|
|
|
let config = parse_config(&homedir, &matches)?;
|
|
let config_state = config.clone();
|
|
|
|
thread::spawn(move || {
|
|
repo_updater(receiver, homedir, config);
|
|
});
|
|
|
|
rocket::build()
|
|
.mount("/", routes![gitea_webhook])
|
|
.manage(JobSender(Mutex::new(sender)))
|
|
.manage(Secret(secret))
|
|
.manage(config_state)
|
|
.launch()
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|