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 060b4b69..647e4378 100644 --- a/.env.example +++ b/.env.example @@ -45,6 +45,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 (Reserved; not used yet) --- +# 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 1b4fa612..1602373c 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -182,6 +182,16 @@ 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.monitorPrivateKey | Hex-encoded private key for the monitor identity that will sign kind 30166/10166 events. Reserved for a future monitor worker. | +| nip66.monitorPubkey | Hex-encoded public key for the monitor identity. Optional when `monitorPrivateKey` is configured. Reserved for a future monitor worker. | +| 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 300e2f38..0a42c6cc 100755 --- a/resources/default-settings.yaml +++ b/resources/default-settings.yaml @@ -67,6 +67,24 @@ 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: [] + # Optional monitor identity (reserved for a future monitor worker). + # monitorPrivateKey: replace-with-monitor-private-key-in-hex + # monitorPubkey: replace-with-monitor-pubkey-in-hex + 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 ce111cfe..51e4cd78 100644 --- a/src/@types/settings.ts +++ b/src/@types/settings.ts @@ -258,6 +258,53 @@ 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[] + /** + * Hex-encoded private key for the monitor identity that will sign kind 30166/10166 events. + * Reserved for a future monitor worker. + */ + monitorPrivateKey?: Secret + /** + * Hex-encoded public key for the monitor identity. + * Optional when monitorPrivateKey is set. Reserved for a future monitor worker. + */ + monitorPubkey?: Pubkey + /** + * 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 /** @@ -324,5 +371,6 @@ export interface Settings { nip43?: Nip43Settings nip45?: Nip45Settings nip50?: Nip50Settings + nip66?: Nip66Settings wot?: WoTSettings } diff --git a/test/unit/utils/settings.spec.ts b/test/unit/utils/settings.spec.ts index dff99e6a..87a1eb76 100644 --- a/test/unit/utils/settings.spec.ts +++ b/test/unit/utils/settings.spec.ts @@ -261,6 +261,43 @@ 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'], + monitorPubkey: '22e804d26ed16b68db5259e78449e96dab5d464c8f470bda3eb1a70467f2c793', + }, + } + 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?.monitorPubkey).to.equal( + '22e804d26ed16b68db5259e78449e96dab5d464c8f470bda3eb1a70467f2c793', + ) + 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(