upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/probe.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-26 15:42:09 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-26 15:42:09 +0000
commit9d86cf15f0275ffeee4519bd054e3b61dc8992ac (patch)
tree65b5d5ffb2a11b5ecd05d01e63fb5a4a0f8b6e06 /grasp-audit/src/probe.rs
parenta2ecfc5a63311570f0f90c7ee40117e289639cb8 (diff)
chore: apply cargo fmt and fix clippy warnings
Fix pre-existing clippy lints: - &PathBuf -> &Path in audit_cleanup.rs - too_many_arguments on process_newly_available_git_data, process_purgatory_announcements, and HttpService::new - clone_on_copy for PublicKey (Copy type) in purgatory cleanup loop
Diffstat (limited to 'grasp-audit/src/probe.rs')
-rw-r--r--grasp-audit/src/probe.rs190
1 files changed, 121 insertions, 69 deletions
diff --git a/grasp-audit/src/probe.rs b/grasp-audit/src/probe.rs
index ecbbcc9..8dad1d4 100644
--- a/grasp-audit/src/probe.rs
+++ b/grasp-audit/src/probe.rs
@@ -167,10 +167,7 @@ fn now_iso8601() -> String {
167 let mo = if mp < 10 { mp + 3 } else { mp - 9 }; // month [1, 12] 167 let mo = if mp < 10 { mp + 3 } else { mp - 9 }; // month [1, 12]
168 let yr = if mo <= 2 { y + 1 } else { y }; 168 let yr = if mo <= 2 { y + 1 } else { y };
169 169
170 format!( 170 format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", yr, mo, d, h, m, s)
171 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
172 yr, mo, d, h, m, s
173 )
174} 171}
175 172
176// ============================================================ 173// ============================================================
@@ -246,8 +243,7 @@ pub async fn run_probe(
246 error: Some(error_msg), 243 error: Some(error_msg),
247 }); 244 });
248 // Skip all subsequent checks 245 // Skip all subsequent checks
249 let already: std::collections::HashSet<&str> = 246 let already: std::collections::HashSet<&str> = $checks.iter().map(|c| c.name).collect();
250 $checks.iter().map(|c| c.name).collect();
251 for name in ALL_CHECK_NAMES { 247 for name in ALL_CHECK_NAMES {
252 if !already.contains(name) { 248 if !already.contains(name) {
253 $checks.push(skipped(name, "overall timeout")); 249 $checks.push(skipped(name, "overall timeout"));
@@ -303,8 +299,8 @@ pub async fn run_probe(
303 let clone_url = format!("{}/{}/{}.git", http_base, npub, repo_id); 299 let clone_url = format!("{}/{}/{}.git", http_base, npub, repo_id);
304 300
305 // Create temp dir for local repo 301 // Create temp dir for local repo
306 let local_repo_path = std::env::temp_dir() 302 let local_repo_path =
307 .join(format!("grasp-probe-{}", uuid::Uuid::new_v4())); 303 std::env::temp_dir().join(format!("grasp-probe-{}", uuid::Uuid::new_v4()));
308 304
309 // Initialise local repo (offline) 305 // Initialise local repo (offline)
310 let init_result = init_local_repo(&local_repo_path, &clone_url); 306 let init_result = init_local_repo(&local_repo_path, &clone_url);
@@ -368,7 +364,14 @@ pub async fn run_probe(
368 // Step 1: connect_websocket 364 // Step 1: connect_websocket
369 // ============================================================ 365 // ============================================================
370 if Instant::now() >= deadline { 366 if Instant::now() >= deadline {
371 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "connect_websocket"); 367 deadline_return!(
368 relay_url,
369 timestamp,
370 total_start,
371 overall_secs,
372 checks,
373 "connect_websocket"
374 );
372 } 375 }
373 let step1_start = Instant::now(); 376 let step1_start = Instant::now();
374 let client_result = tokio::time::timeout( 377 let client_result = tokio::time::timeout(
@@ -426,12 +429,21 @@ pub async fn run_probe(
426 // ============================================================ 429 // ============================================================
427 { 430 {
428 if Instant::now() >= deadline { 431 if Instant::now() >= deadline {
429 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "nip11_fetch"); 432 deadline_return!(
433 relay_url,
434 timestamp,
435 total_start,
436 overall_secs,
437 checks,
438 "nip11_fetch"
439 );
430 } 440 }
431 let step2_start = Instant::now(); 441 let step2_start = Instant::now();
432 let http_client = reqwest::Client::new(); 442 let http_client = reqwest::Client::new();
433 let nip11_result = tokio::time::timeout( 443 let nip11_result = tokio::time::timeout(
434 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(timeout_secs)), 444 deadline
445 .saturating_duration_since(Instant::now())
446 .min(Duration::from_secs(timeout_secs)),
435 http_client 447 http_client
436 .get(&http_base) 448 .get(&http_base)
437 .header("Accept", "application/nostr+json") 449 .header("Accept", "application/nostr+json")
@@ -443,24 +455,20 @@ pub async fn run_probe(
443 455
444 match nip11_result { 456 match nip11_result {
445 Ok(Ok(resp)) if resp.status().is_success() => { 457 Ok(Ok(resp)) if resp.status().is_success() => {
446 let detail = resp 458 let detail = resp.json::<serde_json::Value>().await.ok().map(|v| {
447 .json::<serde_json::Value>() 459 let name = v.get("name").and_then(|n| n.as_str()).unwrap_or("unknown");
448 .await 460 // software is typically a repo URL; take the last path segment
449 .ok() 461 let software = v
450 .map(|v| { 462 .get("software")
451 let name = v.get("name").and_then(|n| n.as_str()).unwrap_or("unknown"); 463 .and_then(|s| s.as_str())
452 // software is typically a repo URL; take the last path segment 464 .map(|s| s.trim_end_matches('/').rsplit('/').next().unwrap_or(s))
453 let software = v 465 .unwrap_or("unknown");
454 .get("software") 466 let version = v
455 .and_then(|s| s.as_str()) 467 .get("version")
456 .map(|s| s.trim_end_matches('/').rsplit('/').next().unwrap_or(s)) 468 .and_then(|ver| ver.as_str())
457 .unwrap_or("unknown"); 469 .unwrap_or("unknown");
458 let version = v 470 format!("{} ({} v{})", name, software, version)
459 .get("version") 471 });
460 .and_then(|ver| ver.as_str())
461 .unwrap_or("unknown");
462 format!("{} ({} v{})", name, software, version)
463 });
464 checks.push(ProbeCheck { 472 checks.push(ProbeCheck {
465 name: "nip11_fetch", 473 name: "nip11_fetch",
466 passed: true, 474 passed: true,
@@ -509,7 +517,14 @@ pub async fn run_probe(
509 let mut write_succeeded = false; 517 let mut write_succeeded = false;
510 518
511 if Instant::now() >= deadline { 519 if Instant::now() >= deadline {
512 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "publish_events"); 520 deadline_return!(
521 relay_url,
522 timestamp,
523 total_start,
524 overall_secs,
525 checks,
526 "publish_events"
527 );
513 } 528 }
514 529
515 if read_only { 530 if read_only {
@@ -558,10 +573,7 @@ pub async fn run_probe(
558 error: Some(e.to_string()), 573 error: Some(e.to_string()),
559 }); 574 });
560 // Skip steps 4 and 5; step 6 will use fallback 575 // Skip steps 4 and 5; step 6 will use fallback
561 checks.push(skipped( 576 checks.push(skipped("git_repo_initialised", "publish_events failed"));
562 "git_repo_initialised",
563 "publish_events failed",
564 ));
565 checks.push(skipped("git_push", "publish_events failed")); 577 checks.push(skipped("git_push", "publish_events failed"));
566 } 578 }
567 } 579 }
@@ -571,7 +583,14 @@ pub async fn run_probe(
571 // ============================================================ 583 // ============================================================
572 if write_succeeded { 584 if write_succeeded {
573 if Instant::now() >= deadline { 585 if Instant::now() >= deadline {
574 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "git_repo_initialised"); 586 deadline_return!(
587 relay_url,
588 timestamp,
589 total_start,
590 overall_secs,
591 checks,
592 "git_repo_initialised"
593 );
575 } 594 }
576 let step4_start = Instant::now(); 595 let step4_start = Instant::now();
577 let poll_url = format!("{}/info/refs?service=git-upload-pack", clone_url); 596 let poll_url = format!("{}/info/refs?service=git-upload-pack", clone_url);
@@ -624,7 +643,14 @@ pub async fn run_probe(
624 // ============================================================ 643 // ============================================================
625 if write_succeeded { 644 if write_succeeded {
626 if Instant::now() >= deadline { 645 if Instant::now() >= deadline {
627 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "git_push"); 646 deadline_return!(
647 relay_url,
648 timestamp,
649 total_start,
650 overall_secs,
651 checks,
652 "git_push"
653 );
628 } 654 }
629 let step5_start = Instant::now(); 655 let step5_start = Instant::now();
630 let push_result = try_push(&local_repo_path); 656 let push_result = try_push(&local_repo_path);
@@ -690,8 +716,14 @@ pub async fn run_probe(
690 continue; 716 continue;
691 } 717 }
692 let mut parts = content.splitn(2, ' '); 718 let mut parts = content.splitn(2, ' ');
693 let hash = match parts.next() { Some(h) if h.len() == 40 => h, _ => continue }; 719 let hash = match parts.next() {
694 let refname = match parts.next() { Some(r) => r.trim(), None => continue }; 720 Some(h) if h.len() == 40 => h,
721 _ => continue,
722 };
723 let refname = match parts.next() {
724 Some(r) => r.trim(),
725 None => continue,
726 };
695 // Skip refs/nostr/* — only branches (refs/heads/*) and tags (refs/tags/*) 727 // Skip refs/nostr/* — only branches (refs/heads/*) and tags (refs/tags/*)
696 if refname.starts_with("refs/nostr/") { 728 if refname.starts_with("refs/nostr/") {
697 continue; 729 continue;
@@ -705,14 +737,23 @@ pub async fn run_probe(
705 // ---- Write path ---- 737 // ---- Write path ----
706 // Step 6a: git_fetch_refs — just verify the endpoint returns 200 738 // Step 6a: git_fetch_refs — just verify the endpoint returns 200
707 if Instant::now() >= deadline { 739 if Instant::now() >= deadline {
708 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "git_fetch_refs"); 740 deadline_return!(
741 relay_url,
742 timestamp,
743 total_start,
744 overall_secs,
745 checks,
746 "git_fetch_refs"
747 );
709 } 748 }
710 let refs_url = format!("{}/info/refs?service=git-upload-pack", clone_url); 749 let refs_url = format!("{}/info/refs?service=git-upload-pack", clone_url);
711 let http_client = reqwest::Client::new(); 750 let http_client = reqwest::Client::new();
712 751
713 let step6_start = Instant::now(); 752 let step6_start = Instant::now();
714 let refs_result = tokio::time::timeout( 753 let refs_result = tokio::time::timeout(
715 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(timeout_secs)), 754 deadline
755 .saturating_duration_since(Instant::now())
756 .min(Duration::from_secs(timeout_secs)),
716 http_client.get(&refs_url).send(), 757 http_client.get(&refs_url).send(),
717 ) 758 )
718 .await; 759 .await;
@@ -833,14 +874,23 @@ pub async fn run_probe(
833 874
834 // In read-only mode: first check that at least one announcement exists 875 // In read-only mode: first check that at least one announcement exists
835 if Instant::now() >= deadline { 876 if Instant::now() >= deadline {
836 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "serves_latest_announcement"); 877 deadline_return!(
878 relay_url,
879 timestamp,
880 total_start,
881 overall_secs,
882 checks,
883 "serves_latest_announcement"
884 );
837 } 885 }
838 let filter = Filter::new().kind(Kind::GitRepoAnnouncement).limit(1); 886 let filter = Filter::new().kind(Kind::GitRepoAnnouncement).limit(1);
839 let existing = client 887 let existing = client
840 .client() 888 .client()
841 .fetch_events( 889 .fetch_events(
842 filter, 890 filter,
843 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(5)), 891 deadline
892 .saturating_duration_since(Instant::now())
893 .min(Duration::from_secs(5)),
844 ) 894 )
845 .await 895 .await
846 .unwrap_or_default(); 896 .unwrap_or_default();
@@ -909,18 +959,25 @@ pub async fn run_probe(
909 .find(|t| t.kind() == TagKind::custom("clone")) 959 .find(|t| t.kind() == TagKind::custom("clone"))
910 .and_then(|t| t.content()) 960 .and_then(|t| t.content())
911 .map(|s| s.to_string()) 961 .map(|s| s.to_string())
912 .unwrap_or_else(|| { 962 .unwrap_or_else(|| format!("{}/{}/{}.git", http_base, ann_npub, ann_id));
913 format!("{}/{}/{}.git", http_base, ann_npub, ann_id)
914 });
915 963
916 if Instant::now() >= deadline { 964 if Instant::now() >= deadline {
917 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "git_fetch_refs"); 965 deadline_return!(
966 relay_url,
967 timestamp,
968 total_start,
969 overall_secs,
970 checks,
971 "git_fetch_refs"
972 );
918 } 973 }
919 let step6_start = Instant::now(); 974 let step6_start = Instant::now();
920 let refs_url = format!("{}/info/refs?service=git-upload-pack", fetch_url); 975 let refs_url = format!("{}/info/refs?service=git-upload-pack", fetch_url);
921 let http_client = reqwest::Client::new(); 976 let http_client = reqwest::Client::new();
922 let refs_result = tokio::time::timeout( 977 let refs_result = tokio::time::timeout(
923 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(timeout_secs)), 978 deadline
979 .saturating_duration_since(Instant::now())
980 .min(Duration::from_secs(timeout_secs)),
924 http_client.get(&refs_url).send(), 981 http_client.get(&refs_url).send(),
925 ) 982 )
926 .await; 983 .await;
@@ -935,7 +992,7 @@ pub async fn run_probe(
935 passed: true, 992 passed: true,
936 skipped: false, 993 skipped: false,
937 duration_ms: step6_ms, 994 duration_ms: step6_ms,
938 detail: None, 995 detail: None,
939 error: None, 996 error: None,
940 }); 997 });
941 Some(body) 998 Some(body)
@@ -946,7 +1003,7 @@ pub async fn run_probe(
946 passed: false, 1003 passed: false,
947 skipped: false, 1004 skipped: false,
948 duration_ms: step6_ms, 1005 duration_ms: step6_ms,
949 detail: None, 1006 detail: None,
950 error: Some(format!("HTTP {}", resp.status())), 1007 error: Some(format!("HTTP {}", resp.status())),
951 }); 1008 });
952 None 1009 None
@@ -957,7 +1014,7 @@ pub async fn run_probe(
957 passed: false, 1014 passed: false,
958 skipped: false, 1015 skipped: false,
959 duration_ms: step6_ms, 1016 duration_ms: step6_ms,
960 detail: None, 1017 detail: None,
961 error: Some(e.to_string()), 1018 error: Some(e.to_string()),
962 }); 1019 });
963 None 1020 None
@@ -968,7 +1025,7 @@ pub async fn run_probe(
968 passed: false, 1025 passed: false,
969 skipped: false, 1026 skipped: false,
970 duration_ms: step6_ms, 1027 duration_ms: step6_ms,
971 detail: None, 1028 detail: None,
972 error: Some("timeout".to_string()), 1029 error: Some("timeout".to_string()),
973 }); 1030 });
974 None 1031 None
@@ -981,10 +1038,7 @@ pub async fn run_probe(
981 // including recursive maintainer chains), then compare against git refs. 1038 // including recursive maintainer chains), then compare against git refs.
982 match refs_body_fallback { 1039 match refs_body_fallback {
983 None => { 1040 None => {
984 checks.push(skipped( 1041 checks.push(skipped("git_refs_match_state", "git_fetch_refs failed"));
985 "git_refs_match_state",
986 "git_fetch_refs failed",
987 ));
988 } 1042 }
989 Some(body) => { 1043 Some(body) => {
990 let fetched_refs = parse_refs(&body); 1044 let fetched_refs = parse_refs(&body);
@@ -992,19 +1046,19 @@ pub async fn run_probe(
992 // Fetch all state events for this repo_id from the relay. 1046 // Fetch all state events for this repo_id from the relay.
993 // The relay only serves authorized state events (owner + full 1047 // The relay only serves authorized state events (owner + full
994 // recursive maintainer chain already resolved by the relay). 1048 // recursive maintainer chain already resolved by the relay).
995 let state_filter = Filter::new() 1049 let state_filter = Filter::new().kind(Kind::RepoState).custom_tag(
996 .kind(Kind::RepoState) 1050 nostr_sdk::prelude::SingleLetterTag::lowercase(
997 .custom_tag( 1051 nostr_sdk::prelude::Alphabet::D,
998 nostr_sdk::prelude::SingleLetterTag::lowercase( 1052 ),
999 nostr_sdk::prelude::Alphabet::D, 1053 ann_id.clone(),
1000 ), 1054 );
1001 ann_id.clone(),
1002 );
1003 let state_events = client 1055 let state_events = client
1004 .client() 1056 .client()
1005 .fetch_events( 1057 .fetch_events(
1006 state_filter, 1058 state_filter,
1007 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(5)), 1059 deadline
1060 .saturating_duration_since(Instant::now())
1061 .min(Duration::from_secs(5)),
1008 ) 1062 )
1009 .await 1063 .await
1010 .unwrap_or_default(); 1064 .unwrap_or_default();
@@ -1046,7 +1100,8 @@ pub async fn run_probe(
1046 Some(h) => h.to_string(), 1100 Some(h) => h.to_string(),
1047 None => continue, 1101 None => continue,
1048 }; 1102 };
1049 let prev_ts = latest_ts.get(kind_str.as_ref()).copied().unwrap_or(0); 1103 let prev_ts =
1104 latest_ts.get(kind_str.as_ref()).copied().unwrap_or(0);
1050 if ts >= prev_ts { 1105 if ts >= prev_ts {
1051 expected.insert(kind_str.to_string(), hash); 1106 expected.insert(kind_str.to_string(), hash);
1052 latest_ts.insert(kind_str.to_string(), ts); 1107 latest_ts.insert(kind_str.to_string(), ts);
@@ -1103,10 +1158,7 @@ pub async fn run_probe(
1103 detail: None, 1158 detail: None,
1104 error: Some("no repositories found on relay".to_string()), 1159 error: Some("no repositories found on relay".to_string()),
1105 }); 1160 });
1106 checks.push(skipped( 1161 checks.push(skipped("git_refs_match_state", "no announcement found"));
1107 "git_refs_match_state",
1108 "no announcement found",
1109 ));
1110 } 1162 }
1111 } 1163 }
1112 } 1164 }