Fix zooid sync

This commit is contained in:
Jon Staab
2026-03-31 11:40:22 -07:00
parent 8018950ba9
commit d1209c635b
6 changed files with 42 additions and 13 deletions
+1
View File
@@ -20,6 +20,7 @@ ROBOT_MESSAGING_RELAYS=auth.nostr1.com,relay.keychat.io,relay.ditto.pub
# Zooid # Zooid
ZOOID_API_URL=http://127.0.0.1:3334 ZOOID_API_URL=http://127.0.0.1:3334
ZOOID_API_SECRET=
RELAY_DOMAIN=spaces.coracle.social RELAY_DOMAIN=spaces.coracle.social
LIVEKIT_URL= LIVEKIT_URL=
LIVEKIT_API_KEY= LIVEKIT_API_KEY=
+1
View File
@@ -38,6 +38,7 @@ Environment variables:
| `ADMINS` | Comma-separated admin pubkeys (hex) | _optional_ | | `ADMINS` | Comma-separated admin pubkeys (hex) | _optional_ |
| `ALLOW_ORIGINS` | Comma-separated CORS origins. If empty, CORS is permissive. | _optional_ | | `ALLOW_ORIGINS` | Comma-separated CORS origins. If empty, CORS is permissive. | _optional_ |
| `ZOOID_API_URL` | Zooid API base URL used by infra worker | _required for infra sync_ | | `ZOOID_API_URL` | Zooid API base URL used by infra worker | _required for infra sync_ |
| `ZOOID_API_SECRET` | Nostr secret key used for authentication of requests to the zooid API | _required_ |
| `RELAY_DOMAIN` | Base domain appended to relay subdomains | empty | | `RELAY_DOMAIN` | Base domain appended to relay subdomains | empty |
| `LIVEKIT_URL` | LiveKit URL sent to zooid when relay livekit is enabled | _optional_ | | `LIVEKIT_URL` | LiveKit URL sent to zooid when relay livekit is enabled | _optional_ |
| `LIVEKIT_API_KEY` | LiveKit API key sent to zooid | _optional_ | | `LIVEKIT_API_KEY` | LiveKit API key sent to zooid | _optional_ |
+1 -1
View File
@@ -28,6 +28,6 @@ Iterates over `repo.list_activity` since last run and does the following:
## `async fn sync_relay(&self, relay: &Relay, is_new: bool)` ## `async fn sync_relay(&self, relay: &Relay, is_new: bool)`
- If `is_new`, sends `POST /relay` to create the relay in zooid. - If `is_new`, sends `POST /relay/:id` to create the relay in zooid.
- Otherwise, sends `PUT /relay/:id` to update it. - Otherwise, sends `PUT /relay/:id` to update it.
- Passes full relay configuration in the body including host, schema, secret, inactive flag, info, policy, groups, management, blossom, livekit, push, and roles. - Passes full relay configuration in the body including host, schema, secret, inactive flag, info, policy, groups, management, blossom, livekit, push, and roles.
+31 -11
View File
@@ -1,4 +1,5 @@
use anyhow::Result; use anyhow::Result;
use nostr_sdk::prelude::*;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::repo::Repo; use crate::repo::Repo;
@@ -10,6 +11,7 @@ pub struct Infra {
livekit_url: String, livekit_url: String,
livekit_api_key: String, livekit_api_key: String,
livekit_api_secret: String, livekit_api_secret: String,
api_secret: String,
repo: Repo, repo: Repo,
last_activity_at: std::sync::Arc<Mutex<i64>>, last_activity_at: std::sync::Arc<Mutex<i64>>,
} }
@@ -21,12 +23,14 @@ impl Infra {
let livekit_url = std::env::var("LIVEKIT_URL").unwrap_or_default(); let livekit_url = std::env::var("LIVEKIT_URL").unwrap_or_default();
let livekit_api_key = std::env::var("LIVEKIT_API_KEY").unwrap_or_default(); let livekit_api_key = std::env::var("LIVEKIT_API_KEY").unwrap_or_default();
let livekit_api_secret = std::env::var("LIVEKIT_API_SECRET").unwrap_or_default(); let livekit_api_secret = std::env::var("LIVEKIT_API_SECRET").unwrap_or_default();
let api_secret = std::env::var("ZOOID_API_SECRET").unwrap_or_default();
Self { Self {
api_url, api_url,
relay_domain, relay_domain,
livekit_url, livekit_url,
livekit_api_key, livekit_api_key,
livekit_api_secret, livekit_api_secret,
api_secret,
repo, repo,
last_activity_at: std::sync::Arc::new(Mutex::new(0)), last_activity_at: std::sync::Arc::new(Mutex::new(0)),
} }
@@ -67,7 +71,10 @@ impl Infra {
let is_new = relay.synced == 0; let is_new = relay.synced == 0;
match self.sync_relay(&relay, is_new).await { match self.sync_relay(&relay, is_new).await {
Ok(()) => self.repo.mark_relay_synced(&relay.id).await?, Ok(()) => {
tracing::info!(relay = %relay.id, "relay sync succeeded");
self.repo.mark_relay_synced(&relay.id).await?
}
Err(e) => { Err(e) => {
tracing::warn!(relay = %relay.id, error = %e, "relay sync failed"); tracing::warn!(relay = %relay.id, error = %e, "relay sync failed");
self.repo.fail_relay_sync(&relay, e.to_string()).await?; self.repo.fail_relay_sync(&relay, e.to_string()).await?;
@@ -81,6 +88,13 @@ impl Infra {
Ok(()) Ok(())
} }
async fn nip98_auth(&self, url: &str, method: HttpMethod) -> Result<String> {
let keys = Keys::parse(&self.api_secret)?;
let server_url = Url::parse(url)?;
let auth = HttpData::new(server_url, method).to_authorization(&keys).await?;
Ok(auth)
}
async fn sync_relay(&self, relay: &crate::models::Relay, is_new: bool) -> Result<()> { async fn sync_relay(&self, relay: &crate::models::Relay, is_new: bool) -> Result<()> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let base = self.api_url.trim_end_matches('/'); let base = self.api_url.trim_end_matches('/');
@@ -91,12 +105,12 @@ impl Infra {
format!("{}.{}", relay.subdomain, self.relay_domain) format!("{}.{}", relay.subdomain, self.relay_domain)
}; };
let secret = uuid::Uuid::new_v4().to_string(); let secret = Keys::generate().secret_key().to_secret_hex();
let livekit = if relay.livekit_enabled == 1 { let livekit = if relay.livekit_enabled == 1 {
serde_json::json!({ serde_json::json!({
"enabled": true, "enabled": true,
"url": self.livekit_url, "server_url": self.livekit_url,
"api_key": self.livekit_api_key, "api_key": self.livekit_api_key,
"api_secret": self.livekit_api_secret, "api_secret": self.livekit_api_secret,
}) })
@@ -113,6 +127,7 @@ impl Infra {
"name": relay.info_name, "name": relay.info_name,
"icon": relay.info_icon, "icon": relay.info_icon,
"description": relay.info_description, "description": relay.info_description,
"pubkey": relay.tenant,
}, },
"policy": { "policy": {
"public_join": relay.policy_public_join == 1, "public_join": relay.policy_public_join == 1,
@@ -123,21 +138,26 @@ impl Infra {
"blossom": { "enabled": relay.blossom_enabled == 1 }, "blossom": { "enabled": relay.blossom_enabled == 1 },
"livekit": livekit, "livekit": livekit,
"push": { "enabled": relay.push_enabled == 1 }, "push": { "enabled": relay.push_enabled == 1 },
"roles": [ "roles": {
{ "name": "admin", "permissions": ["read", "write", "admin"] }, "admin": { "can_manage": true, "can_invite": true },
{ "name": "member", "permissions": ["read", "write"] }, "member": { "can_invite": true },
{ "name": "guest", "permissions": ["read"] }, },
],
}); });
let response = if is_new { let response = if is_new {
client.post(format!("{}/relay", base)).json(&body).send().await? let url = format!("{}/relay/{}", base, relay.id);
let auth = self.nip98_auth(&url, HttpMethod::POST).await?;
client.post(&url).header("Authorization", auth).json(&body).send().await?
} else { } else {
client.put(format!("{}/relay/{}", base, relay.id)).json(&body).send().await? let url = format!("{}/relay/{}", base, relay.id);
let auth = self.nip98_auth(&url, HttpMethod::PUT).await?;
client.put(&url).header("Authorization", auth).json(&body).send().await?
}; };
if !response.status().is_success() { if !response.status().is_success() {
anyhow::bail!("zooid sync returned {}", response.status()) let status = response.status();
let body = response.text().await.unwrap_or_default();
anyhow::bail!("zooid sync returned {}: {}", status, body)
} }
Ok(()) Ok(())
} }
+7 -1
View File
@@ -313,10 +313,16 @@ impl Repo {
} }
pub async fn mark_relay_synced(&self, relay_id: &str) -> Result<()> { pub async fn mark_relay_synced(&self, relay_id: &str) -> Result<()> {
let mut tx = self.pool.begin().await?;
sqlx::query("UPDATE relay SET synced = 1, status = 'active', sync_error = '' WHERE id = ?") sqlx::query("UPDATE relay SET synced = 1, status = 'active', sync_error = '' WHERE id = ?")
.bind(relay_id) .bind(relay_id)
.execute(&self.pool) .execute(&mut *tx)
.await?; .await?;
Self::insert_activity(&mut tx, "mark_relay_synced", "relay", relay_id).await?;
tx.commit().await?;
Ok(()) Ok(())
} }
+1
View File
@@ -14,6 +14,7 @@ const ACTIVITY_LABELS: Record<string, string> = {
mark_invoice_attempted: "Invoice payment attempted", mark_invoice_attempted: "Invoice payment attempted",
mark_invoice_sent: "Invoice sent", mark_invoice_sent: "Invoice sent",
mark_invoice_closed: "Invoice closed", mark_invoice_closed: "Invoice closed",
mark_relay_synced: "Relay synchronized",
} }
function formatDate(ts: number) { function formatDate(ts: number) {