upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/audit_cleanup.rs
blob: b976b1ffd60d4aa2409c79a4c3c166bb3e73130d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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);
        }
    }
}