diff --git a/.changeset/nip66-settings-foundation.md b/.changeset/nip66-settings-foundation.md new file mode 100644 index 00000000..6500f52d --- /dev/null +++ b/.changeset/nip66-settings-foundation.md @@ -0,0 +1,5 @@ +--- +"nostream": minor +--- + +Add NIP-66 relay monitor settings foundation with defaults for probe interval, timeouts, targets, monitor identity, and DNS cache TTL. diff --git a/.env.example b/.env.example index 70162578..64191de5 100644 --- a/.env.example +++ b/.env.example @@ -55,6 +55,9 @@ WORKER_COUNT=2 # Defaults to CPU count. Use 1 or 2 for local testing. # --- RELAY PRIVATE KEY (Optional) --- # RELAY_PRIVATE_KEY=your_hex_private_key +# --- NIP-66 MONITOR IDENTITY (Optional; reserved for future event publisher) --- +# MONITOR_PRIVATE_KEY=your_hex_monitor_private_key + # --- PAYMENTS (Only if enabled in settings.yaml) --- # ZEBEDEE_API_KEY= # NODELESS_API_KEY= diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 3e5ff311..4ffe7209 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -61,6 +61,7 @@ The following environment variables can be set: | ADMIN_DEPENDENCY_PING_TIMEOUT_MS | Timeout for admin DB/Redis dependency pings (ms) | 3000 | | GRAFANA_URL | Grafana base URL for admin dashboard embeds | http://127.0.0.1:7777 | | NOSTR_CONFIG_DIR | Configuration directory | /.nostr/ | +| MONITOR_PRIVATE_KEY | Hex-encoded private key for the NIP-66 monitor identity that will sign kind 30166/10166 events. Configure via environment variable, not settings.yaml. | | | DEBUG | Debugging filter | | | ZEBEDEE_API_KEY | Zebedee Project API Key | | | NWC_URL | NWC connection URL (`nostr+walletconnect://...`) | | @@ -194,6 +195,14 @@ The settings below are listed in alphabetical order by name. Please keep this ta | nip50.enabled | Enable or disable NIP-50 full-text search. Defaults to false. When enabled, clients can include a `search` field in REQ filters to perform text queries against event content. Requires the GIN full-text index migration. | | nip50.language | PostgreSQL text-search configuration name. Defaults to `simple` (language-agnostic tokenization). Set to `english`, `spanish`, etc. for stemming support. See [PostgreSQL text search configurations](https://www.postgresql.org/docs/current/textsearch-configuration.html). **Note:** The GIN index migration is built with the `simple` configuration. If you change this value, you must manually rebuild the index: `DROP INDEX CONCURRENTLY events_content_fts_idx; CREATE INDEX CONCURRENTLY events_content_fts_idx ON events USING gin (to_tsvector('', event_content));` — otherwise the planner cannot use the index and queries fall back to sequential scans. | | nip50.maxQueryLength | Maximum length of the search query string. Queries exceeding this are truncated. Defaults to 256. | +| nip66.dnsCacheTtlSeconds | DNS cache TTL in seconds for repeated probe lookups of the same hostname. Reserved for a future monitor worker. Defaults to 300. | +| nip66.enabled | Enable NIP-66 relay monitoring configuration. **Note:** this release only defines settings (no monitor worker yet); enabling is currently a no-op. Defaults to false. | +| nip66.probeIntervalSeconds | Seconds between scheduled relay probe runs. Reserved for a future monitor worker. Defaults to 3600. | +| nip66.targets | Public WebSocket URLs to probe (for example `wss://relay.example.com`). When empty, defaults to `info.relay_url`. Reserved for a future monitor worker. | +| nip66.timeouts.dnsMs | DNS probe timeout in milliseconds. Defaults to 10000. | +| nip66.timeouts.nip11Ms | NIP-11 fetch timeout in milliseconds. Defaults to 10000. | +| nip66.timeouts.tlsMs | TLS probe timeout in milliseconds. Defaults to 10000. | +| nip66.timeouts.wsRttMs | WebSocket open RTT probe timeout in milliseconds. Defaults to 10000. | | paymentProcessors.lnbits.baseURL | Base URL of your Lnbits instance. | | paymentProcessors.lnbits.callbackBaseURL | Public-facing Nostream's Lnbits Callback URL. (e.g. https://relay.your-domain.com/callbacks/lnbits) | | paymentProcessors.lnurl.invoiceURL | [LUD-06 Pay Request](https://github.com/lnurl/luds/blob/luds/06.md) provider URL. (e.g. https://getalby.com/lnurlp/your-username) | diff --git a/resources/default-settings.yaml b/resources/default-settings.yaml index 9eba3b9f..7e04e9ba 100755 --- a/resources/default-settings.yaml +++ b/resources/default-settings.yaml @@ -80,6 +80,21 @@ nip50: # 'simple' (no stemming) or a language name like 'english', 'spanish' language: simple maxQueryLength: 256 +nip66: + # NIP-66 relay liveness monitoring. Disabled by default. + # Settings only in this release (no monitor worker yet); enabling is currently a no-op. + # Future versions may probe public relay URLs and publish kind 30166/10166 events. + enabled: false + # Seconds between scheduled probe runs (reserved for a future monitor worker). + probeIntervalSeconds: 3600 + timeouts: + dnsMs: 10000 + tlsMs: 10000 + wsRttMs: 10000 + nip11Ms: 10000 + # Public WebSocket URLs to probe. Empty list defaults to info.relay_url. + targets: [] + dnsCacheTtlSeconds: 300 wot: # Web of Trust filtering. When enabled, only events from pubkeys within # the relay owner's 2-hop follow graph are accepted. diff --git a/src/@types/settings.ts b/src/@types/settings.ts index 05fa0a8d..8d503b43 100644 --- a/src/@types/settings.ts +++ b/src/@types/settings.ts @@ -273,6 +273,43 @@ export interface Nip50Settings { maxQueryLength?: number } +export interface Nip66ProbeTimeouts { + dnsMs: number + tlsMs: number + wsRttMs: number + nip11Ms: number +} + +export interface Nip66Settings { + /** + * Enable NIP-66 relay monitoring configuration. + * Note: this release only defines settings (no monitor worker yet), so + * enabling is currently a no-op. + * Defaults to false. + */ + enabled: boolean + /** + * Interval in seconds between probe runs. + * Reserved for a future monitor worker. Defaults to 3600. + */ + probeIntervalSeconds: number + /** + * Per-check probe timeouts in milliseconds. + * Reserved for a future monitor worker. + */ + timeouts: Nip66ProbeTimeouts + /** + * Public relay WebSocket URLs to probe (for example wss://relay.example.com). + * When empty, a future worker will use info.relay_url. + */ + targets: string[] + /** + * DNS cache TTL in seconds for repeated probes of the same hostname. + * Reserved for a future monitor worker. Defaults to 300. + */ + dnsCacheTtlSeconds: number +} + export interface Nip05Settings { mode: Nip05Mode /** @@ -351,5 +388,6 @@ export interface Settings { nip43?: Nip43Settings nip45?: Nip45Settings nip50?: Nip50Settings + nip66?: Nip66Settings wot?: WoTSettings } diff --git a/src/cli/utils/env-config.ts b/src/cli/utils/env-config.ts index fb4704c7..bc9817e1 100644 --- a/src/cli/utils/env-config.ts +++ b/src/cli/utils/env-config.ts @@ -19,6 +19,7 @@ const SUPPORTED_ENV_KEYS = new Set([ 'SECRET', 'RELAY_PORT', 'RELAY_PRIVATE_KEY', + 'MONITOR_PRIVATE_KEY', 'WORKER_COUNT', 'DB_URI', 'DB_HOST', diff --git a/test/unit/utils/settings.spec.ts b/test/unit/utils/settings.spec.ts index dff99e6a..57e5f9e2 100644 --- a/test/unit/utils/settings.spec.ts +++ b/test/unit/utils/settings.spec.ts @@ -261,6 +261,39 @@ describe('SettingsStatic', () => { }) }) + describe('NIP-66 settings defaults', () => { + it('default-settings.yaml contains a nip66 block with safe defaults', () => { + const defaults = SettingsStatic.loadAndParseYamlFile(SettingsStatic.getDefaultSettingsFilePath()) + + expect(defaults).to.have.nested.property('nip66.enabled', false) + expect(defaults).to.have.nested.property('nip66.probeIntervalSeconds', 3600) + expect(defaults).to.have.nested.property('nip66.timeouts.dnsMs', 10_000) + expect(defaults).to.have.nested.property('nip66.timeouts.tlsMs', 10_000) + expect(defaults).to.have.nested.property('nip66.timeouts.wsRttMs', 10_000) + expect(defaults).to.have.nested.property('nip66.timeouts.nip11Ms', 10_000) + expect(defaults).to.have.nested.property('nip66.targets').that.deep.equals([]) + expect(defaults).to.have.nested.property('nip66.dnsCacheTtlSeconds', 300) + }) + + it('user config nip66 block overrides defaults', () => { + const defaults = SettingsStatic.loadAndParseYamlFile(SettingsStatic.getDefaultSettingsFilePath()) + const userConfig = { + nip66: { + enabled: true, + probeIntervalSeconds: 900, + targets: ['wss://relay.example.com'], + }, + } + const merged = mergeDeepRight(defaults, userConfig) as Settings + + expect(merged.nip66?.enabled).to.equal(true) + expect(merged.nip66?.probeIntervalSeconds).to.equal(900) + expect(merged.nip66?.targets).to.deep.equal(['wss://relay.example.com']) + expect(merged.nip66?.timeouts?.dnsMs).to.equal(10_000) + expect(merged.nip66?.dnsCacheTtlSeconds).to.equal(300) + }) + }) + describe('WoT settings defaults', () => { it('default-settings.yaml contains a wot block with enabled: false', () => { const defaults = SettingsStatic.loadAndParseYamlFile(