From 3f4dc8e1bc07f88c064d4734f5cc3ee87b1497e4 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 24 Feb 2026 12:45:26 +0000 Subject: add background job to clean up grasp-audit test events and git repos Spawns a tokio task that runs every 30 minutes and removes all events tagged 'grasp-audit-test-event' older than 2 hours from the LMDB database, along with their associated bare git repositories on disk. --- src/audit_cleanup.rs | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 9 ++++ 3 files changed, 159 insertions(+) create mode 100644 src/audit_cleanup.rs (limited to 'src') diff --git a/src/audit_cleanup.rs b/src/audit_cleanup.rs new file mode 100644 index 0000000..b976b1f --- /dev/null +++ b/src/audit_cleanup.rs @@ -0,0 +1,149 @@ +//! Audit Event Cleanup +//! +//! Background job that periodically removes grasp-audit test events and their +//! associated git repositories from the relay. +//! +//! The grasp-audit tool tags every event it creates with: +//! `["t", "grasp-audit-test-event"]` +//! +//! This job: +//! 1. Queries kind 30617 (repo announcement) events tagged "grasp-audit-test-event" +//! that are older than `AUDIT_CLEANUP_AGE_SECS` seconds. +//! 2. Deletes the corresponding bare git repositories from disk. +//! 3. Deletes all events tagged "grasp-audit-test-event" older than the threshold +//! from the Nostr database. +//! +//! Runs every `AUDIT_CLEANUP_INTERVAL_SECS` seconds. + +use std::path::PathBuf; +use std::time::Duration; + +use nostr_sdk::prelude::*; +use tracing::{debug, error, info, warn}; + +use crate::nostr::builder::SharedDatabase; +use crate::nostr::events::RepositoryAnnouncement; + +/// How old an audit event must be before it is eligible for deletion (2 hours). +const AUDIT_CLEANUP_AGE_SECS: u64 = 2 * 3600; + +/// How often the cleanup job runs (30 minutes). +const AUDIT_CLEANUP_INTERVAL_SECS: u64 = 30 * 60; + +/// The hashtag used by grasp-audit to mark all test events. +const AUDIT_TEST_EVENT_TAG: &str = "grasp-audit-test-event"; + +/// Run the audit cleanup loop indefinitely. +/// +/// Spawned as a background tokio task in `main.rs`. +pub async fn run_audit_cleanup_loop(database: SharedDatabase, git_data_path: PathBuf) { + // Use an interval that fires immediately on the first tick, then every 30 minutes. + let mut interval = tokio::time::interval(Duration::from_secs(AUDIT_CLEANUP_INTERVAL_SECS)); + loop { + interval.tick().await; + run_audit_cleanup_once(&database, &git_data_path).await; + } +} + +/// Perform a single cleanup pass. +async fn run_audit_cleanup_once(database: &SharedDatabase, git_data_path: &PathBuf) { + let cutoff = Timestamp::from(Timestamp::now().as_secs().saturating_sub(AUDIT_CLEANUP_AGE_SECS)); + + // --- Step 1: Find repo announcements to delete git repos for --- + let repo_filter = Filter::new() + .kind(Kind::GitRepoAnnouncement) + .hashtag(AUDIT_TEST_EVENT_TAG) + .until(cutoff); + + let repo_events = match database.query(repo_filter).await { + Ok(events) => events, + Err(e) => { + error!("audit_cleanup: failed to query repo announcements: {}", e); + return; + } + }; + + let mut repos_deleted = 0usize; + let mut repos_failed = 0usize; + + for event in repo_events.iter() { + match RepositoryAnnouncement::from_event(event.clone()) { + Ok(announcement) => { + let repo_path = git_data_path.join(announcement.repo_path()); + if repo_path.exists() { + match std::fs::remove_dir_all(&repo_path) { + Ok(()) => { + debug!( + "audit_cleanup: deleted git repo {}", + repo_path.display() + ); + repos_deleted += 1; + + // Remove the parent npub directory if it is now empty + if let Some(npub_dir) = repo_path.parent() { + if npub_dir.exists() { + match std::fs::read_dir(npub_dir) { + Ok(mut entries) => { + if entries.next().is_none() { + if let Err(e) = std::fs::remove_dir(npub_dir) { + warn!( + "audit_cleanup: could not remove empty npub dir {}: {}", + npub_dir.display(), + e + ); + } + } + } + Err(e) => { + warn!( + "audit_cleanup: could not read npub dir {}: {}", + npub_dir.display(), + e + ); + } + } + } + } + } + Err(e) => { + warn!( + "audit_cleanup: failed to delete git repo {}: {}", + repo_path.display(), + e + ); + repos_failed += 1; + } + } + } else { + debug!( + "audit_cleanup: git repo already absent: {}", + repo_path.display() + ); + } + } + Err(e) => { + warn!( + "audit_cleanup: could not parse repo announcement {}: {}", + event.id, e + ); + } + } + } + + // --- Step 2: Delete all audit events from the database --- + let all_audit_filter = Filter::new() + .hashtag(AUDIT_TEST_EVENT_TAG) + .until(cutoff); + + match database.delete(all_audit_filter).await { + Ok(()) => { + info!( + "audit_cleanup: deleted audit events older than {}s; git repos deleted={}, failed={}", + AUDIT_CLEANUP_AGE_SECS, repos_deleted, repos_failed + ); + } + Err(e) => { + error!("audit_cleanup: failed to delete audit events from database: {}", e); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 8befd6f..d0e2c2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod audit_cleanup; pub mod config; pub mod git; pub mod http; diff --git a/src/main.rs b/src/main.rs index bf3aefb..12a875c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use tracing::{error, info, warn}; use tracing_subscriber::{EnvFilter, FmtSubscriber}; use ngit_grasp::{ + audit_cleanup, config::{Config, DatabaseBackend}, git, http, metrics::Metrics, @@ -175,6 +176,14 @@ async fn main() -> Result<()> { }); info!("Expired event cleanup task started (24h interval, keeps 7 days)"); + // Spawn audit event cleanup task (30m interval, removes events >2h old) + let audit_db = relay_with_db.database.clone(); + let audit_git_path = PathBuf::from(config.effective_git_data_path()); + tokio::spawn(async move { + audit_cleanup::run_audit_cleanup_loop(audit_db, audit_git_path).await; + }); + info!("Audit event cleanup task started (30m interval, removes events >2h old)"); + // Start purgatory sync loop for background git data fetching // Create naughty list tracker for git remote domains with persistent errors (12h expiration) let git_naughty_list = Arc::new(NaughtyListTracker::with_defaults()); -- cgit v1.2.3