From 13f8e76ae3c0051ec72a8f4c0b25db89c9e38a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C3=ABl=20Gu=C3=A9neau?= Date: Sun, 22 Dec 2024 20:36:24 +0100 Subject: [PATCH] bundle auxiliary files (templates/css) in the binary --- .gitignore | 3 ++- Cargo.lock | 20 +++++++++++++++++++ Cargo.toml | 1 + README.md | 15 +++++++-------- src/main.rs | 52 +++++++++++++++++++++++++++++++++++++++++++------- src/workers.rs | 18 +++-------------- 6 files changed, 78 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 3568573..21d607f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ classification.json db.json api_token profile.json -_*.json \ No newline at end of file +_*.json +env diff --git a/Cargo.lock b/Cargo.lock index ec2351a..0b84b2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -694,6 +694,7 @@ dependencies = [ "actix-web", "anyhow", "forgejo-api", + "include_dir", "lazy_static", "lettre", "rand", @@ -1260,6 +1261,25 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "2.6.0" diff --git a/Cargo.toml b/Cargo.toml index 0bb0530..0c2eac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ lazy_static = "1" actix-files = "0.6" unicode-segmentation = "1" lettre = { version = "0.11", features = ["builder", "smtp-transport", "rustls-tls"], default-features = false } +include_dir = "0.7.4" [profile.profiling] inherits = "dev" diff --git a/README.md b/README.md index c84ebb1..31bebc2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# spam management for forgejo +# spam accounts management for forgejo ## Usage @@ -14,7 +14,7 @@ ## Configuration Forgery reads the following environment variables: -- `FORGEJO_URL`: url of the forgejo instance +- `FORGEJO_URL`: url of the forgejo instance (e.g. https://git.deuxfleurs.fr) - `FORGEJO_API_TOKEN`: Forgejo API token *granting admin access*. Required. You can generate an API token using the Forgejo web interface in `Settings -> Applications -> Generate New Token`. @@ -22,11 +22,11 @@ Forgery reads the following environment variables: locking accounts) - `ADMIN_CONTACT_EMAIL`: email that can be used to contact admins of your instance (included in the notification email sent when locking accounts) -- `ACTUALLY_BAN_USERS`: define it (e.g. to `true`) to actually lock user - accounts, send notification emails and eventually delete user accounts. If not - defined (the default), no actual action is taken, spammers are only listed in - the database. The variable should be set in production, but probably not for - testing. +- `ACTUALLY_BAN_USERS`: define it to `true` to actually lock user accounts, send + notification emails and eventually delete user accounts. If not defined (the + default) or set to `false`, no actual action is taken: spammers are only + listed in the database. The variable should be set in production, but probably + not for testing. Environment variables that are relevant when `ACTUALLY_BAN_USERS=true`: - `SMTP_ADDRESS`: address of the SMTP relay used to send email notifications @@ -42,4 +42,3 @@ Environment variables that are relevant when `ACTUALLY_BAN_USERS=true`: the email could not be sent…) - add backend to store data on garage instead of local files - improve error handling -- bundle auxiliary files (templates, css) in the binary for easy deployment? diff --git a/src/main.rs b/src/main.rs index 62ae21c..ed0be23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer, Responder}; -use anyhow::Context; +use anyhow::{anyhow, Context}; use forgejo_api::{Auth, Forgejo}; use lazy_static::lazy_static; use rand::prelude::*; @@ -61,9 +61,19 @@ impl Config { .context("reading the ADMIN_CONTACT_EMAIL environment variable")?; let actually_ban = match std::env::var("ACTUALLY_BAN_USERS") { - Ok(_) => ActuallyBan::Yes { - smtp: SmtpConfig::from_env().await?, - }, + Ok(s) => { + if &s == "true" { + ActuallyBan::Yes { + smtp: SmtpConfig::from_env().await?, + } + } else if &s == "false" { + ActuallyBan::No + } else { + return Err(anyhow!( + "ACTUALLY_BAN_USERS: unknown value (expected: true/false)" + )); + } + } Err(_) => ActuallyBan::No, }; @@ -231,10 +241,25 @@ async fn apply_classification( } } +const TEMPLATES_DIR: include_dir::Dir = include_dir::include_dir!("templates"); + lazy_static! { pub static ref TEMPLATES: Tera = { - match Tera::new("templates/**/*.html") { - Ok(t) => t, + let files: Vec<_> = TEMPLATES_DIR + .files() + .into_iter() + .map(|f| { + ( + f.path().to_str().unwrap(), + std::str::from_utf8(f.contents()).unwrap(), + ) + }) + .collect(); + + let mut tera = Tera::default(); + + match tera.add_raw_templates(files) { + Ok(()) => tera, Err(e) => { eprintln!("Parsing error(s): {}", e); ::std::process::exit(1); @@ -421,6 +446,19 @@ async fn classified( HttpResponse::Ok().body(page) } +const STATIC_DIR: include_dir::Dir = include_dir::include_dir!("static"); + +#[get("/static/{filename:.*}")] +async fn static_(req: HttpRequest) -> impl Responder { + eprintln!("GET {}", req.uri()); + + let path: String = req.match_info().query("filename").parse().unwrap(); + match STATIC_DIR.get_file(path) { + None => HttpResponse::NotFound().body("404 Not found"), + Some(page) => HttpResponse::Ok().body(page.contents()), + } +} + #[actix_web::main] async fn main() -> anyhow::Result<()> { eprintln!("Eval templates"); @@ -464,8 +502,8 @@ async fn main() -> anyhow::Result<()> { HttpServer::new(move || { App::new() - .service(actix_files::Files::new("/static/", "./static")) .app_data(st.clone()) + .service(static_) .service(index) .service(classified) .service(post_classified_index) diff --git a/src/workers.rs b/src/workers.rs index 3f3dcf2..cad65a2 100644 --- a/src/workers.rs +++ b/src/workers.rs @@ -83,11 +83,7 @@ pub async fn refresh_user_data( // Worker to delete spam accounts after their grace period expired -async fn try_purge_account( - config: &Config, - forge: &Forgejo, - login: &str, -) -> anyhow::Result<()> { +async fn try_purge_account(config: &Config, forge: &Forgejo, login: &str) -> anyhow::Result<()> { if let ActuallyBan::No = config.actually_ban { eprintln!("[Simulating: delete account of user {login}]"); return Ok(()); @@ -104,11 +100,7 @@ async fn try_purge_account( Ok(()) } -pub async fn purge_spammer_accounts( - config: Arc, - forge: Arc, - db: Arc>, -) { +pub async fn purge_spammer_accounts(config: Arc, forge: Arc, db: Arc>) { loop { let mut classified_users = Vec::new(); { @@ -274,11 +266,7 @@ pub async fn try_lock_and_notify_user( } } -pub async fn lock_and_notify_users( - config: Arc, - forge: Arc, - db: Arc>, -) { +pub async fn lock_and_notify_users(config: Arc, forge: Arc, db: Arc>) { let mut spammers = Vec::new(); { let db = &db.lock().unwrap();