upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/signing.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/signing.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/signing.rs')
-rw-r--r--src/signing.rs76
1 files changed, 76 insertions, 0 deletions
diff --git a/src/signing.rs b/src/signing.rs
new file mode 100644
index 0000000..7ec796a
--- /dev/null
+++ b/src/signing.rs
@@ -0,0 +1,76 @@
1use crate::discovery::DiscoveredRepo;
2use crate::health::GraspServer;
3use anyhow::{Context, Result};
4use nostr_sdk::prelude::*;
5
6pub struct OptionalSigner {
7 keys: Option<Keys>,
8}
9
10impl OptionalSigner {
11 pub fn none() -> Self {
12 Self { keys: None }
13 }
14
15 pub fn from_nsec(nsec: &str) -> Result<Self> {
16 let keys = Keys::parse(nsec).context("failed to parse nsec")?;
17 Ok(Self { keys: Some(keys) })
18 }
19
20 pub fn is_available(&self) -> bool {
21 self.keys.is_some()
22 }
23
24 pub async fn build_updated_announcement(
25 &self,
26 original: &DiscoveredRepo,
27 additional_servers: &[GraspServer],
28 ) -> Result<Option<Event>> {
29 let keys = match &self.keys {
30 Some(k) => k,
31 None => {
32 tracing::debug!("no signer available, skipping announcement update");
33 return Ok(None);
34 }
35 };
36
37 if additional_servers.is_empty() {
38 return Ok(None);
39 }
40
41 let pk_hex = original.pubkey.to_hex();
42
43 let mut new_clone_urls: Vec<String> = original.clone_urls.clone();
44 for server in additional_servers {
45 let url = server.clone_url(&pk_hex, &original.identifier);
46 if !new_clone_urls.contains(&url) {
47 new_clone_urls.push(url);
48 }
49 }
50
51 let mut tags: Vec<Tag> = vec![
52 Tag::custom(TagKind::Custom("d".into()), [&original.identifier]),
53 ];
54
55 for url in &new_clone_urls {
56 tags.push(Tag::custom(TagKind::Custom("clone".into()), [url.as_str()]));
57 }
58 for url in &original.relay_urls {
59 tags.push(Tag::custom(
60 TagKind::Custom("relays".into()),
61 [url.as_str()],
62 ));
63 }
64
65 let builder = EventBuilder::new(Kind::Custom(30617), "").tags(tags);
66 let event = builder.sign_with_keys(keys)?;
67
68 tracing::info!(
69 identifier = %original.identifier,
70 added = additional_servers.len(),
71 "built updated announcement with additional clone URLs"
72 );
73
74 Ok(Some(event))
75 }
76}