From fd0678cfec7fdfce41f74b589fe17419856307d9 Mon Sep 17 00:00:00 2001 From: userAdityaa Date: Wed, 13 May 2026 20:22:11 +0545 Subject: [PATCH] feat(infra): pass Blossom S3 config to Zooid with schema key prefix --- backend/.env.template | 7 ++++ backend/README.md | 5 +++ backend/spec/infra.md | 2 + backend/src/infra.rs | 95 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 108 insertions(+), 1 deletion(-) diff --git a/backend/.env.template b/backend/.env.template index 7a416ff..ea45eef 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -26,6 +26,13 @@ LIVEKIT_URL= LIVEKIT_API_KEY= LIVEKIT_API_SECRET= +# Blossom S3 (optional; when region, bucket, access key, and secret are all set, relays with blossom enabled sync with adapter s3 and key_prefix = relay schema) +BLOSSOM_S3_ENDPOINT= +BLOSSOM_S3_REGION= +BLOSSOM_S3_BUCKET= +BLOSSOM_S3_ACCESS_KEY= +BLOSSOM_S3_SECRET_KEY= + # Billing NWC_URL= # Nostr Wallet Connect URL for generating Lightning invoices ENCRYPTION_SECRET= # Nostr secret key (hex or nsec) used to encrypt tenant NWC URLs at rest diff --git a/backend/README.md b/backend/README.md index cc02aab..7d3236f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -43,6 +43,11 @@ Environment variables: | `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_SECRET` | LiveKit API secret sent to zooid | _optional_ | +| `BLOSSOM_S3_ENDPOINT` | S3-compatible endpoint URL for Blossom; omit for AWS S3 | _optional_ | +| `BLOSSOM_S3_REGION` | S3 region; with bucket, access key, and secret enables S3 for Blossom | _optional_ | +| `BLOSSOM_S3_BUCKET` | S3 bucket name | _optional_ | +| `BLOSSOM_S3_ACCESS_KEY` | S3 access key ID | _optional_ | +| `BLOSSOM_S3_SECRET_KEY` | S3 secret access key | _optional_ | | `NWC_URL` | Platform NWC URL used to generate BOLT11 invoices | _required for invoice generation_ | | `ENCRYPTION_SECRET` | Nostr secret key (hex or nsec) used to encrypt tenant NWC URLs at rest | _required_ | | `STRIPE_SECRET_KEY` | Stripe API secret key used for billing API operations | _required_ | diff --git a/backend/spec/infra.md b/backend/spec/infra.md index 7383ab6..1b833c6 100644 --- a/backend/spec/infra.md +++ b/backend/spec/infra.md @@ -5,6 +5,7 @@ Infra is a service which listens for activity and synchronizes relay updates to Members: - `api_url: String` - the URL of the zooid instance to be managed, from `ZOOID_API_URL` +- `blossom_s3: Option` - shared Blossom S3 settings from `BLOSSOM_S3_*` when region, bucket, access key, and secret are all non-empty after trim - `query: Query` - `command: Command` @@ -36,3 +37,4 @@ Members: - Otherwise, sends `PATCH /relay/:id` to update it. - Includes `secret` only for relay creation (`POST`) so updates do not rotate relay identity. - Passes relay configuration in the body including host, schema, inactive flag, info, policy, groups, management, blossom, livekit, push, and roles. +- When `blossom_s3` is configured and the relay has blossom enabled, the blossom section includes `adapter: "s3"`, S3 fields from the environment, and `s3.key_prefix` set to the relay's `schema`. Otherwise blossom omits S3 (zooid defaults to local storage) or sends `{ "enabled": false }` when blossom is disabled. diff --git a/backend/src/infra.rs b/backend/src/infra.rs index a0f480e..557a48b 100644 --- a/backend/src/infra.rs +++ b/backend/src/infra.rs @@ -10,6 +10,47 @@ const RELAY_SYNC_RETRY_BASE_DELAY_SECS: u64 = 30; const RELAY_SYNC_RETRY_MAX_DELAY_SECS: u64 = 15 * 60; const RELAY_SYNC_RETRY_MAX_ATTEMPTS: usize = 6; +/// Blossom S3 settings from env; relay sync sets `key_prefix` to the relay schema. +#[derive(Clone)] +struct BlossomS3Sync { + endpoint: String, + region: String, + bucket: String, + access_key: String, + secret_key: String, +} + +impl BlossomS3Sync { + fn from_env() -> Option { + let region = std::env::var("BLOSSOM_S3_REGION").unwrap_or_default(); + let bucket = std::env::var("BLOSSOM_S3_BUCKET").unwrap_or_default(); + let access_key = std::env::var("BLOSSOM_S3_ACCESS_KEY").unwrap_or_default(); + let secret_key = std::env::var("BLOSSOM_S3_SECRET_KEY").unwrap_or_default(); + + let region = region.trim().to_string(); + let bucket = bucket.trim().to_string(); + let access_key = access_key.trim().to_string(); + let secret_key = secret_key.trim().to_string(); + + if region.is_empty() || bucket.is_empty() || access_key.is_empty() || secret_key.is_empty() { + return None; + } + + let endpoint = std::env::var("BLOSSOM_S3_ENDPOINT") + .unwrap_or_default() + .trim() + .to_string(); + + Some(Self { + endpoint, + region, + bucket, + access_key, + secret_key, + }) + } +} + #[derive(Clone)] pub struct Infra { api_url: String, @@ -18,6 +59,7 @@ pub struct Infra { livekit_api_key: String, livekit_api_secret: String, api_secret: String, + blossom_s3: Option, query: Query, command: Command, } @@ -30,6 +72,7 @@ impl Infra { 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(); + let blossom_s3 = BlossomS3Sync::from_env(); if api_url.trim().is_empty() { anyhow::bail!("missing ZOOID_API_URL"); @@ -45,6 +88,7 @@ impl Infra { livekit_api_key, livekit_api_secret, api_secret, + blossom_s3, query, command, }) @@ -254,6 +298,7 @@ impl Infra { host, livekit, is_new.then(|| Keys::generate().secret_key().to_secret_hex()), + self.blossom_s3.as_ref(), ); let url = format!("{}/relay/{}", base, relay.id); @@ -323,7 +368,10 @@ fn relay_sync_body( host: String, livekit: serde_json::Value, secret: Option, + blossom_s3: Option<&BlossomS3Sync>, ) -> serde_json::Value { + let blossom = blossom_sync_json(relay, blossom_s3); + let mut body = serde_json::json!({ "host": host, "schema": relay.schema, @@ -341,7 +389,7 @@ fn relay_sync_body( }, "groups": { "enabled": relay.groups_enabled == 1 }, "management": { "enabled": relay.management_enabled == 1 }, - "blossom": { "enabled": relay.blossom_enabled == 1 }, + "blossom": blossom, "livekit": livekit, "push": { "enabled": relay.push_enabled == 1 }, "roles": { @@ -357,6 +405,51 @@ fn relay_sync_body( body } +fn blossom_sync_json(relay: &Relay, blossom_s3: Option<&BlossomS3Sync>) -> serde_json::Value { + let enabled = relay.blossom_enabled == 1; + if !enabled { + return serde_json::json!({ "enabled": false }); + } + + let Some(s3) = blossom_s3 else { + return serde_json::json!({ "enabled": true }); + }; + + let mut s3_obj = serde_json::Map::new(); + if !s3.endpoint.trim().is_empty() { + s3_obj.insert( + "endpoint".to_string(), + serde_json::Value::String(s3.endpoint.clone()), + ); + } + s3_obj.insert( + "region".to_string(), + serde_json::Value::String(s3.region.clone()), + ); + s3_obj.insert( + "bucket".to_string(), + serde_json::Value::String(s3.bucket.clone()), + ); + s3_obj.insert( + "access_key".to_string(), + serde_json::Value::String(s3.access_key.clone()), + ); + s3_obj.insert( + "secret_key".to_string(), + serde_json::Value::String(s3.secret_key.clone()), + ); + s3_obj.insert( + "key_prefix".to_string(), + serde_json::Value::String(relay.schema.clone()), + ); + + serde_json::json!({ + "enabled": true, + "adapter": "s3", + "s3": serde_json::Value::Object(s3_obj), + }) +} + fn should_sync_relay_activity(activity_type: &str) -> bool { matches!( activity_type, -- 2.52.0