commit 69c92aa4d44638a35cc6e623756394d542f73b5e
parent 5b006af60a2b97c35436c3815968d2d8c114c027
Author: triesap <tyson@radroots.org>
Date: Sun, 15 Feb 2026 18:13:41 +0000
app: use shared runtime and presence bootstrap primitives
- replace local cli flags with shared runtime service cli args
- flatten shared nostr service config into radrootsd configuration
- switch startup presence publishing to shared nostr bootstrap helper
- route nip89 identifier and extra tags through shared service config fields
Diffstat:
3 files changed, 33 insertions(+), 104 deletions(-)
diff --git a/src/app/cli.rs b/src/app/cli.rs
@@ -1,6 +1,5 @@
-use std::path::PathBuf;
-
-use clap::{command, Parser, ValueHint};
+use clap::Parser;
+use radroots_runtime::RadrootsServiceCliArgs;
#[derive(Parser, Debug, Clone)]
#[command(
@@ -9,27 +8,6 @@ use clap::{command, Parser, ValueHint};
version = env!("CARGO_PKG_VERSION")
)]
pub struct Args {
- #[arg(
- long,
- value_name = "PATH",
- value_hint = ValueHint::FilePath,
- default_value = "config.toml",
- help = "Path to the daemon configuration file (defaults to config.toml)"
- )]
- pub config: PathBuf,
-
- #[arg(
- long,
- value_name = "PATH",
- value_hint = ValueHint::FilePath,
- help = "Path to the daemon identity file (json, txt, or raw 32-byte key; defaults to identity.json)",
- )]
- pub identity: Option<PathBuf>,
-
- #[arg(
- long,
- action = clap::ArgAction::SetTrue,
- help = "Allow generating a new identity file if missing; if not set and identity file is absent, the daemon will fail"
- )]
- pub allow_generate_identity: bool,
+ #[command(flatten)]
+ pub service: RadrootsServiceCliArgs,
}
diff --git a/src/app/config.rs b/src/app/config.rs
@@ -1,4 +1,5 @@
use radroots_nostr::prelude::RadrootsNostrMetadata;
+use radroots_runtime::RadrootsNostrServiceConfig;
use serde::{Deserialize, Serialize};
fn default_rpc_addr() -> String {
@@ -33,10 +34,6 @@ fn default_nip46_perms() -> Vec<String> {
Vec::new()
}
-fn default_nip46_nip89_extra_tags() -> Vec<Vec<String>> {
- Vec::new()
-}
-
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Nip46Config {
#[serde(default = "default_nip46_session_ttl_secs")]
@@ -45,10 +42,6 @@ pub struct Nip46Config {
pub perms: Vec<String>,
#[serde(default)]
pub nostrconnect_url: Option<String>,
- #[serde(default)]
- pub nip89_identifier: Option<String>,
- #[serde(default = "default_nip46_nip89_extra_tags")]
- pub nip89_extra_tags: Vec<Vec<String>>,
}
impl Default for Nip46Config {
@@ -57,8 +50,6 @@ impl Default for Nip46Config {
session_ttl_secs: default_nip46_session_ttl_secs(),
perms: default_nip46_perms(),
nostrconnect_url: None,
- nip89_identifier: None,
- nip89_extra_tags: default_nip46_nip89_extra_tags(),
}
}
}
@@ -97,22 +88,19 @@ impl Default for RpcConfig {
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Configuration {
- pub logs_dir: String,
+ #[serde(flatten)]
+ pub service: RadrootsNostrServiceConfig,
#[serde(default)]
pub rpc: RpcConfig,
#[serde(default)]
pub rpc_addr: Option<String>,
#[serde(default)]
- pub relays: Vec<String>,
- #[serde(default)]
pub nip46: Nip46Config,
}
impl Configuration {
pub fn rpc_addr(&self) -> &str {
- self.rpc_addr
- .as_deref()
- .unwrap_or(self.rpc.addr.as_str())
+ self.rpc_addr.as_deref().unwrap_or(self.rpc.addr.as_str())
}
}
diff --git a/src/app/runtime.rs b/src/app/runtime.rs
@@ -1,5 +1,6 @@
use anyhow::{Context, Result};
use radroots_identity::RadrootsIdentity;
+use std::time::Duration;
use tracing::info;
use crate::app::{cli, config};
@@ -7,22 +8,16 @@ use crate::core::Radrootsd;
use crate::transport::jsonrpc;
use crate::transport::nostr::listener::spawn_nip46_listener;
use radroots_events::profile::RadrootsProfileType;
-use radroots_events_codec::profile::encode::profile_type_tags;
use radroots_nostr::prelude::{
- radroots_nostr_build_metadata_event,
- radroots_nostr_publish_application_handler,
- radroots_nostr_publish_identity_profile_with_type,
- RadrootsNostrApplicationHandlerSpec,
- RadrootsNostrKind,
- RadrootsNostrTag,
- RadrootsNostrTagKind,
+ RadrootsNostrApplicationHandlerSpec, RadrootsNostrKind,
+ radroots_nostr_bootstrap_service_presence,
};
pub async fn run() -> Result<()> {
let (args, settings): (cli::Args, config::Settings) =
radroots_runtime::parse_and_load_path_with_init(
- |a: &cli::Args| Some(a.config.as_path()),
- |cfg: &config::Settings| cfg.config.logs_dir.as_str(),
+ |a: &cli::Args| Some(a.service.config.as_path()),
+ |cfg: &config::Settings| cfg.config.service.logs_dir.as_str(),
None,
)
.context("load configuration")?;
@@ -30,8 +25,8 @@ pub async fn run() -> Result<()> {
info!("Starting radrootsd");
let identity = RadrootsIdentity::load_or_generate(
- args.identity.as_ref(),
- args.allow_generate_identity,
+ args.service.identity.as_ref(),
+ args.service.allow_generate_identity,
)?;
let keys = identity.keys().clone();
let radrootsd = Radrootsd::new(
@@ -40,72 +35,40 @@ pub async fn run() -> Result<()> {
settings.config.nip46.clone(),
);
- for relay in settings.config.relays.iter() {
+ for relay in settings.config.service.relays.iter() {
radrootsd.client.add_relay(relay).await?;
}
- if !settings.config.relays.is_empty() {
+ if !settings.config.service.relays.is_empty() {
let client = radrootsd.client.clone();
let md = settings.metadata.clone();
let identity = identity.clone();
let nip46_config = settings.config.nip46.clone();
- let relays = settings.config.relays.clone();
- let has_metadata = serde_json::to_value(&md)
- .ok()
- .and_then(|v| v.as_object().cloned())
- .map(|o| !o.is_empty())
- .unwrap_or(false);
+ let service_cfg = settings.config.service.clone();
tokio::spawn(async move {
- client.connect().await;
- let profile_published =
- match radroots_nostr_publish_identity_profile_with_type(
- &client,
- &identity,
- Some(RadrootsProfileType::Radrootsd),
- )
- .await
- {
- Ok(Some(_)) => true,
- Ok(None) => false,
- Err(e) => {
- tracing::warn!("Failed to publish identity profile: {e}");
- false
- }
- };
- if has_metadata && !profile_published {
- let mut tags = Vec::new();
- for mut tag in profile_type_tags(RadrootsProfileType::Radrootsd) {
- if tag.is_empty() {
- continue;
- }
- let key = tag.remove(0);
- tags.push(RadrootsNostrTag::custom(
- RadrootsNostrTagKind::Custom(key.into()),
- tag,
- ));
- }
- let builder = radroots_nostr_build_metadata_event(&md).tags(tags);
- if let Err(e) = client.send_event_builder(builder).await {
- tracing::warn!("Failed to publish metadata on startup: {e}");
- } else {
- tracing::info!("Published metadata on startup");
- }
- }
-
let nip46_kind = RadrootsNostrKind::NostrConnect.as_u16() as u32;
let handler_spec = RadrootsNostrApplicationHandlerSpec {
kinds: vec![nip46_kind],
- identifier: nip46_config.nip89_identifier.clone(),
+ identifier: service_cfg.nip89_identifier.clone(),
metadata: Some(md.clone()),
- extra_tags: nip46_config.nip89_extra_tags.clone(),
- relays,
+ extra_tags: service_cfg.nip89_extra_tags.clone(),
+ relays: service_cfg.relays.clone(),
nostrconnect_url: nip46_config.nostrconnect_url.clone(),
};
- if let Err(e) = radroots_nostr_publish_application_handler(&client, &handler_spec).await {
- tracing::warn!("Failed to publish NIP-89 announcement: {e}");
+ if let Err(e) = radroots_nostr_bootstrap_service_presence(
+ &client,
+ &identity,
+ Some(RadrootsProfileType::Radrootsd),
+ &md,
+ &handler_spec,
+ Duration::from_secs(5),
+ )
+ .await
+ {
+ tracing::warn!("Failed to publish service presence on startup: {e}");
} else {
- tracing::info!("Published NIP-89 announcement");
+ tracing::info!("Published service presence on startup");
}
});