upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr_mirror.rs
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-26 16:11:05 +0530
committerYour Name <you@example.com>2026-05-26 16:11:05 +0530
commit8816a192c95cf539b65975469a2d61aed46f0414 (patch)
tree7590318244a56fabbfa6919ef6d0fab5be529134 /src/nostr_mirror.rs
feat: initial implementation of grasp-mirror daemon
GRASP mirror daemon that discovers repos from watched npubs and mirrors git data + Nostr events across all known GRASP servers for redundancy. Features: - Configurable npub watch list via .env (MIRROR_NPUBS) - TOML config for GRASP server list, index relays, storage paths - NIP-11 verification of GRASP servers on startup - Discovery of repos via kind:30617 announcements on index relays - Git mirroring (bare clone + push --mirror) to missing GRASP servers - Nostr event forwarding to all GRASP server embedded relays - SQLite state tracking for sync status and event dedup - Optional signing key for updating announcements with new clone URLs - CLI subcommands: daemon, status, verify, mirror-once Architecture: config.rs - TOML + .env config loading db.rs - SQLite state tracking health.rs - NIP-11 GRASP server verification discovery.rs - Relay subscription, kind:30617 parsing git_mirror.rs - Bare clone + push to GRASP servers nostr_mirror.rs - Event forwarding to all GRASP relays signing.rs - Optional announcement updates main.rs - CLI entry point, daemon loop
Diffstat (limited to 'src/nostr_mirror.rs')
-rw-r--r--src/nostr_mirror.rs139
1 files changed, 139 insertions, 0 deletions
diff --git a/src/nostr_mirror.rs b/src/nostr_mirror.rs
new file mode 100644
index 0000000..76f66d0
--- /dev/null
+++ b/src/nostr_mirror.rs
@@ -0,0 +1,139 @@
1use crate::db::MirrorDb;
2use crate::discovery::DiscoveredRepo;
3use crate::health::GraspServer;
4use anyhow::Result;
5use nostr::Kind;
6use nostr_sdk::prelude::*;
7
8pub struct NostrMirror {
9 client: nostr_sdk::Client,
10}
11
12impl NostrMirror {
13 pub fn new(client: nostr_sdk::Client) -> Self {
14 Self { client }
15 }
16
17 pub async fn forward_events_to_servers(
18 &self,
19 db: &MirrorDb,
20 events: &[Event],
21 servers: &[GraspServer],
22 ) -> Result<()> {
23 for event in events {
24 if db.have_seen_event(&event.id.to_hex()).await? {
25 continue;
26 }
27
28 for server in servers {
29 if !server.is_grasp_server() {
30 continue;
31 }
32
33 tracing::debug!(
34 event_id = %event.id.to_hex(),
35 kind = event.kind.as_u16(),
36 server = %server.domain,
37 "forwarding event"
38 );
39
40 let url: RelayUrl = RelayUrl::parse(&server.relay_url)?;
41 let urls = vec![url];
42
43 match self.client.send_event_to(urls, event.clone()).await {
44 Ok(_) => {
45 tracing::debug!(
46 event_id = %event.id.to_hex(),
47 server = %server.domain,
48 "event forwarded"
49 );
50 }
51 Err(e) => {
52 tracing::warn!(
53 event_id = %event.id.to_hex(),
54 server = %server.domain,
55 error = %e,
56 "failed to forward event"
57 );
58 }
59 }
60 }
61
62 let _ = db.record_event(&event.id.to_hex()).await;
63 }
64
65 Ok(())
66 }
67
68 pub async fn forward_repo_events(
69 &self,
70 db: &MirrorDb,
71 repo: &DiscoveredRepo,
72 servers: &[GraspServer],
73 ) -> Result<()> {
74 let filters = vec![
75 Filter::new()
76 .kind(Kind::Custom(30617))
77 .author(repo.pubkey)
78 .identifier(&repo.identifier),
79 Filter::new()
80 .kind(Kind::Custom(30618))
81 .author(repo.pubkey)
82 .identifier(&repo.identifier),
83 ];
84
85 let mut all_events = Vec::new();
86 for filter in filters {
87 let events = self
88 .client
89 .fetch_events(filter, std::time::Duration::from_secs(15))
90 .await?;
91 all_events.extend(events);
92 }
93
94 if all_events.is_empty() {
95 tracing::debug!(identifier = %repo.identifier, "no events to forward");
96 return Ok(());
97 }
98
99 tracing::info!(
100 identifier = %repo.identifier,
101 count = all_events.len(),
102 "forwarding repo events"
103 );
104
105 self.forward_events_to_servers(db, &all_events, servers).await
106 }
107
108 pub async fn sync_all_events(
109 &self,
110 db: &MirrorDb,
111 npubs: &[PublicKey],
112 servers: &[GraspServer],
113 ) -> Result<()> {
114 let git_kinds = [
115 Kind::Custom(30617),
116 Kind::Custom(30618),
117 Kind::Custom(1631),
118 Kind::Custom(1642),
119 Kind::EventDeletion,
120 ];
121
122 let mut all_events = Vec::new();
123
124 for pk in npubs {
125 for kind in &git_kinds {
126 let filter = Filter::new().kind(*kind).author(*pk).limit(100);
127 let events = self
128 .client
129 .fetch_events(filter, std::time::Duration::from_secs(30))
130 .await?;
131 all_events.extend(events);
132 }
133 }
134
135 tracing::info!(count = all_events.len(), "fetched events for forwarding");
136
137 self.forward_events_to_servers(db, &all_events, servers).await
138 }
139}