Fix local relay auth and community persistence

Signed-off-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
This commit is contained in:
npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je
2026-08-10 15:30:41 -04:00
committed by Brother Darryl
parent 7c73a9522e
commit 89e0350bbf
5 changed files with 79 additions and 2 deletions
+21
View File
@@ -1439,6 +1439,27 @@ impl Db {
Ok(())
}
/// Rebind the durable loopback community to this single-node process's
/// current ephemeral authority while preserving its stable UUID and data.
/// SQLite-only: production deployments never rewrite tenant host mappings.
pub async fn rebind_single_node_community_host(
&self,
normalized_host: &str,
owner_pubkey: &str,
) -> Result<Option<CommunityRecord>> {
if matches!(&self.backend, DbBackend::SQLite(_)) {
return sqlite::rebind_single_node_community_host(
self.sqlite_pool()?,
normalized_host,
owner_pubkey,
)
.await;
}
Err(DbError::UnsupportedBackend(
"single-node community rebind requires SQLite",
))
}
/// Ensure a configured community host exists and return its row.
///
/// This is the startup/config seeding path for N=1 deployments. Migrations
+8 -1
View File
@@ -1231,7 +1231,14 @@ async fn run_single_node(config: Config, tracer_init: telemetry::TracerInit) ->
"Cannot derive community host from BUZZ_RELAY_URL"
));
}
let community = db.ensure_configured_community(&host).await?.id;
let community = if let Some(owner) = config.relay_owner_pubkey.as_deref() {
match db.rebind_single_node_community_host(&host, owner).await? {
Some(record) => record.id,
None => db.ensure_configured_community(&host).await?.id,
}
} else {
db.ensure_configured_community(&host).await?.id
};
if let Some(owner) = config.relay_owner_pubkey.as_deref() {
db.bootstrap_owner(community, owner).await?;
}
+40
View File
@@ -18,6 +18,46 @@ use crate::{
util::now_iso,
};
pub(super) fn backfill_missing_agent_auth_tags(
app: &AppHandle,
state: &AppState,
) -> Result<usize, String> {
let owner_keys = state.signing_keys()?;
let compat_owner = nostr::Keys::parse(&owner_keys.secret_key().to_secret_hex())
.map_err(|e| format!("failed to bridge owner keys: {e}"))?;
let _store_guard = state
.managed_agents_store_lock
.lock()
.map_err(|e| e.to_string())?;
let mut records = load_managed_agents(app)?;
let mut changed = 0;
for record in &mut records {
if record.auth_tag.is_some()
|| record
.pubkey
.eq_ignore_ascii_case(&owner_keys.public_key().to_hex())
{
continue;
}
let agent = nostr::PublicKey::from_hex(&record.pubkey)
.map_err(|e| format!("invalid managed agent pubkey {}: {e}", record.pubkey))?;
record.auth_tag = Some(
buzz_sdk_pkg::nip_oa::compute_auth_tag(&compat_owner, &agent, "").map_err(|e| {
format!(
"failed to compute NIP-OA auth tag for {}: {e}",
record.pubkey
)
})?,
);
record.updated_at = now_iso();
changed += 1;
}
if changed > 0 {
save_managed_agents(app, &records)?;
}
Ok(changed)
}
/// Read the workspace owner pubkey without holding the lock. Used to populate `BUZZ_ACP_AGENT_OWNER`
/// as a fallback for legacy agent records that have no NIP-OA `auth_tag`.
pub(super) fn workspace_owner_hex(state: &AppState) -> Result<String, String> {
+8 -1
View File
@@ -145,7 +145,8 @@ pub async fn apply_workspace(
None => None,
};
if crate::local_relay::is_local_relay_url(&relay_url) {
let is_local_workspace = crate::local_relay::is_local_relay_url(&relay_url);
if is_local_workspace {
// Only the UI-created local sentinel can launch the bundled relay.
// An arbitrary loopback URL remains a normal remote community.
let keys = parsed_keys.clone().unwrap_or(state.signing_keys()?);
@@ -228,6 +229,12 @@ pub async fn apply_workspace(
}
try_regenerate_nest(&app);
if is_local_workspace {
let changed = crate::commands::agents::backfill_missing_agent_auth_tags(&app, &state)?;
if changed > 0 {
eprintln!("buzz-desktop: backfilled NIP-OA auth tags for {changed} local agent(s)");
}
}
Ok::<(), String>(())
})
+2
View File
@@ -53,6 +53,7 @@ impl LocalRelayConfig {
("BUZZ_LOCAL_DB", sqlite_url(&db_path)),
("BUZZ_LOCAL_MEDIA_DIR", media_dir.display().to_string()),
("BUZZ_REQUIRE_RELAY_MEMBERSHIP", "true".to_string()),
("BUZZ_ALLOW_NIP_OA_AUTH", "true".to_string()),
("RELAY_OWNER_PUBKEY", self.owner_pubkey.clone()),
("BUZZ_RELAY_PRIVATE_KEY", self.relay_private_key.clone()),
]
@@ -369,6 +370,7 @@ mod tests {
env.get("BUZZ_REQUIRE_RELAY_MEMBERSHIP"),
Some(&"true".to_string())
);
assert_eq!(env.get("BUZZ_ALLOW_NIP_OA_AUTH"), Some(&"true".to_string()));
assert_eq!(env.get("RELAY_OWNER_PUBKEY"), Some(&"owner".to_string()));
assert_eq!(
env.get("BUZZ_RELAY_PRIVATE_KEY"),