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_API_URL=http://127.0.0.1:3334
ZOOID_API_SECRET=
RELAY_DOMAIN=spaces.coracle.social
LIVEKIT_URL=
LIVEKIT_API_KEY=
+1
View File
@@ -38,6 +38,7 @@ Environment variables:
| `ADMINS` | Comma-separated admin pubkeys (hex) | _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_SECRET` | Nostr secret key used for authentication of requests to the zooid API | _required_ |
| `RELAY_DOMAIN` | Base domain appended to relay subdomains | empty |
| `LIVEKIT_URL` | LiveKit URL sent to zooid when relay livekit is enabled | _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)`
- 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.
- 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 nostr_sdk::prelude::*;
use tokio::sync::Mutex;
use crate::repo::Repo;
@@ -10,6 +11,7 @@ pub struct Infra {
livekit_url: String,
livekit_api_key: String,
livekit_api_secret: String,
api_secret: String,
repo: Repo,
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_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 api_secret = std::env::var("ZOOID_API_SECRET").unwrap_or_default();
Self {
api_url,
relay_domain,
livekit_url,
livekit_api_key,
livekit_api_secret,
api_secret,
repo,
last_activity_at: std::sync::Arc::new(Mutex::new(0)),
}
@@ -67,7 +71,10 @@ impl Infra {
let is_new = relay.synced == 0;
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) => {
tracing::warn!(relay = %relay.id, error = %e, "relay sync failed");
self.repo.fail_relay_sync(&relay, e.to_string()).await?;
@@ -81,6 +88,13 @@ impl Infra {
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<()> {
let client = reqwest::Client::new();
let base = self.api_url.trim_end_matches('/');
@@ -91,12 +105,12 @@ impl Infra {
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 {
serde_json::json!({
"enabled": true,
"url": self.livekit_url,
"server_url": self.livekit_url,
"api_key": self.livekit_api_key,
"api_secret": self.livekit_api_secret,
})
@@ -113,6 +127,7 @@ impl Infra {
"name": relay.info_name,
"icon": relay.info_icon,
"description": relay.info_description,
"pubkey": relay.tenant,
},
"policy": {
"public_join": relay.policy_public_join == 1,
@@ -123,21 +138,26 @@ impl Infra {
"blossom": { "enabled": relay.blossom_enabled == 1 },
"livekit": livekit,
"push": { "enabled": relay.push_enabled == 1 },
"roles": [
{ "name": "admin", "permissions": ["read", "write", "admin"] },
{ "name": "member", "permissions": ["read", "write"] },
{ "name": "guest", "permissions": ["read"] },
],
"roles": {
"admin": { "can_manage": true, "can_invite": true },
"member": { "can_invite": true },
},
});
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 {
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() {
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(())
}
+7 -1
View File
@@ -313,10 +313,16 @@ impl Repo {
}
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 = ?")
.bind(relay_id)
.execute(&self.pool)
.execute(&mut *tx)
.await?;
Self::insert_activity(&mut tx, "mark_relay_synced", "relay", relay_id).await?;
tx.commit().await?;
Ok(())
}
+1
View File
@@ -14,6 +14,7 @@ const ACTIVITY_LABELS: Record<string, string> = {
mark_invoice_attempted: "Invoice payment attempted",
mark_invoice_sent: "Invoice sent",
mark_invoice_closed: "Invoice closed",
mark_relay_synced: "Relay synchronized",
}
function formatDate(ts: number) {