From 7fd2cc3002ea5fdd044fcfd3ad69978f2adc3cba Mon Sep 17 00:00:00 2001 From: ABHAY PANDEY Date: Thu, 16 Jul 2026 09:59:47 +0530 Subject: [PATCH 1/2] feat(nip66): add relay monitor settings schema and defaults Signed-off-by: ABHAY PANDEY --- .changeset/nip66-settings-foundation.md | 5 +++ .env.example | 3 ++ CONFIGURATION.md | 20 +++++++++++ resources/default-settings.yaml | 18 ++++++++++ src/@types/settings.ts | 44 +++++++++++++++++++++++++ test/unit/utils/settings.spec.ts | 37 +++++++++++++++++++++ 6 files changed, 127 insertions(+) create mode 100644 .changeset/nip66-settings-foundation.md 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 66c82e1d..e41892e8 100644 --- a/.env.example +++ b/.env.example @@ -40,6 +40,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) --- +# 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 5091ea2f..2726114c 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -179,6 +179,16 @@ The settings below are listed in alphabetical order by name. Please keep this ta | nip05.verifyExpiration | Time in milliseconds before a successful NIP-05 verification expires and needs re-checking. Defaults to 604800000 (1 week). | | nip05.verifyUpdateFrequency | Minimum interval in milliseconds between re-verification attempts for a given author. Defaults to 86400000 (24 hours). | | nip45.enabled | Enable or disable NIP-45 COUNT handling. Defaults to true. | +| nip66.dnsCacheTtlSeconds | DNS cache TTL in seconds for repeated probe lookups of the same hostname. Defaults to 300. | +| nip66.enabled | Enable NIP-66 relay monitoring worker and event publishing. Defaults to false. | +| nip66.monitorPrivateKey | Hex-encoded private key for the monitor identity that signs kind 30166/10166 events. Prefer `MONITOR_PRIVATE_KEY` env when set. | +| nip66.monitorPubkey | Hex-encoded public key for the monitor identity. Optional when `monitorPrivateKey` is configured. | +| nip66.probeIntervalSeconds | Seconds between scheduled relay probe runs. Defaults to 3600. | +| nip66.targets | Public WebSocket URLs to probe (for example `wss://relay.example.com`). When empty, defaults to `info.relay_url`. | +| 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) | @@ -212,6 +222,16 @@ The settings below are listed in alphabetical order by name. Please keep this ta | nip05.mode | NIP-05 verification mode: `enabled` requires verification, `passive` verifies without blocking, `disabled` does nothing. Defaults to `disabled`. | | nip05.verifyExpiration | Time in milliseconds before a successful NIP-05 verification expires and needs re-checking. Defaults to 604800000 (1 week). | | nip05.verifyUpdateFrequency | Minimum interval in milliseconds between re-verification attempts for a given author. Defaults to 86400000 (24 hours). | +| nip66.dnsCacheTtlSeconds | DNS cache TTL in seconds for repeated probe lookups of the same hostname. Defaults to 300. | +| nip66.enabled | Enable NIP-66 relay monitoring worker and event publishing. Defaults to false. | +| nip66.monitorPrivateKey | Hex-encoded private key for the monitor identity that signs kind 30166/10166 events. Prefer `MONITOR_PRIVATE_KEY` env when set. | +| nip66.monitorPubkey | Hex-encoded public key for the monitor identity. Optional when `monitorPrivateKey` is configured. | +| nip66.probeIntervalSeconds | Seconds between scheduled relay probe runs. Defaults to 3600. | +| nip66.targets | Public WebSocket URLs to probe (for example `wss://relay.example.com`). When empty, defaults to `info.relay_url`. | +| 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 00aeba35..abb8af82 100755 --- a/resources/default-settings.yaml +++ b/resources/default-settings.yaml @@ -62,6 +62,24 @@ nip05: domainBlacklist: [] nip45: enabled: true +nip66: + # NIP-66 relay liveness monitoring. Disabled by default. + # When enabled, a dedicated worker probes public relay URLs and can publish + # kind 30166/10166 events using the configured monitor identity. + enabled: false + # Seconds between scheduled probe runs. + 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 for signing NIP-66 events. Prefer MONITOR_PRIVATE_KEY env. + # 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 12136a19..d2c5ccf0 100644 --- a/src/@types/settings.ts +++ b/src/@types/settings.ts @@ -252,6 +252,49 @@ export interface Nip45Settings { enabled?: boolean } +export interface Nip66ProbeTimeouts { + dnsMs: number + tlsMs: number + wsRttMs: number + nip11Ms: number +} + +export interface Nip66Settings { + /** + * Enable the relay monitor worker and NIP-66 event publishing. + * Defaults to false. + */ + enabled: boolean + /** + * Interval in seconds between probe runs. + * Defaults to 3600. + */ + probeIntervalSeconds: number + /** + * Per-check probe timeouts in milliseconds. + */ + timeouts: Nip66ProbeTimeouts + /** + * Public relay WebSocket URLs to probe (for example wss://relay.example.com). + * When empty, the worker uses info.relay_url. + */ + targets: string[] + /** + * Hex-encoded private key for the monitor identity that signs kind 30166/10166 events. + * Prefer MONITOR_PRIVATE_KEY env when set; settings value is a fallback. + */ + monitorPrivateKey?: Secret + /** + * Hex-encoded public key for the monitor identity. Optional when monitorPrivateKey is set. + */ + monitorPubkey?: Pubkey + /** + * DNS cache TTL in seconds for repeated probes of the same hostname. + * Defaults to 300. + */ + dnsCacheTtlSeconds: number +} + export interface Nip05Settings { mode: Nip05Mode /** @@ -317,5 +360,6 @@ export interface Settings { nip05?: Nip05Settings nip43?: Nip43Settings nip45?: Nip45Settings + 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( From 4c4032dc756d18b6dd481bf5f3917ee38fb7914c Mon Sep 17 00:00:00 2001 From: ABHAY PANDEY Date: Thu, 16 Jul 2026 10:40:07 +0530 Subject: [PATCH 2/2] docs(nip66): clarify settings are config-only until monitor worker lands --- .env.example | 2 +- CONFIGURATION.md | 25 ++++++------------------- resources/default-settings.yaml | 8 ++++---- src/@types/settings.ts | 18 +++++++++++------- 4 files changed, 22 insertions(+), 31 deletions(-) diff --git a/.env.example b/.env.example index 530a4f3a..647e4378 100644 --- a/.env.example +++ b/.env.example @@ -45,7 +45,7 @@ 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) --- +# --- NIP-66 MONITOR IDENTITY (Reserved; not used yet) --- # MONITOR_PRIVATE_KEY=your_hex_monitor_private_key # --- PAYMENTS (Only if enabled in settings.yaml) --- diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 399c4db5..1602373c 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -182,12 +182,12 @@ 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. Defaults to 300. | -| nip66.enabled | Enable NIP-66 relay monitoring worker and event publishing. Defaults to false. | -| nip66.monitorPrivateKey | Hex-encoded private key for the monitor identity that signs kind 30166/10166 events. Prefer `MONITOR_PRIVATE_KEY` env when set. | -| nip66.monitorPubkey | Hex-encoded public key for the monitor identity. Optional when `monitorPrivateKey` is configured. | -| nip66.probeIntervalSeconds | Seconds between scheduled relay probe runs. Defaults to 3600. | -| nip66.targets | Public WebSocket URLs to probe (for example `wss://relay.example.com`). When empty, defaults to `info.relay_url`. | +| 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. | @@ -225,19 +225,6 @@ The settings below are listed in alphabetical order by name. Please keep this ta | nip05.mode | NIP-05 verification mode: `enabled` requires verification, `passive` verifies without blocking, `disabled` does nothing. Defaults to `disabled`. | | nip05.verifyExpiration | Time in milliseconds before a successful NIP-05 verification expires and needs re-checking. Defaults to 604800000 (1 week). | | nip05.verifyUpdateFrequency | Minimum interval in milliseconds between re-verification attempts for a given author. Defaults to 86400000 (24 hours). | -| 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. Defaults to 300. | -| nip66.enabled | Enable NIP-66 relay monitoring worker and event publishing. Defaults to false. | -| nip66.monitorPrivateKey | Hex-encoded private key for the monitor identity that signs kind 30166/10166 events. Prefer `MONITOR_PRIVATE_KEY` env when set. | -| nip66.monitorPubkey | Hex-encoded public key for the monitor identity. Optional when `monitorPrivateKey` is configured. | -| nip66.probeIntervalSeconds | Seconds between scheduled relay probe runs. Defaults to 3600. | -| nip66.targets | Public WebSocket URLs to probe (for example `wss://relay.example.com`). When empty, defaults to `info.relay_url`. | -| 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 a18f7d95..0a42c6cc 100755 --- a/resources/default-settings.yaml +++ b/resources/default-settings.yaml @@ -69,10 +69,10 @@ nip50: maxQueryLength: 256 nip66: # NIP-66 relay liveness monitoring. Disabled by default. - # When enabled, a dedicated worker probes public relay URLs and can publish - # kind 30166/10166 events using the configured monitor identity. + # 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. + # Seconds between scheduled probe runs (reserved for a future monitor worker). probeIntervalSeconds: 3600 timeouts: dnsMs: 10000 @@ -81,7 +81,7 @@ nip66: nip11Ms: 10000 # Public WebSocket URLs to probe. Empty list defaults to info.relay_url. targets: [] - # Optional monitor identity for signing NIP-66 events. Prefer MONITOR_PRIVATE_KEY env. + # 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 diff --git a/src/@types/settings.ts b/src/@types/settings.ts index 0e8b8162..51e4cd78 100644 --- a/src/@types/settings.ts +++ b/src/@types/settings.ts @@ -267,36 +267,40 @@ export interface Nip66ProbeTimeouts { export interface Nip66Settings { /** - * Enable the relay monitor worker and NIP-66 event publishing. + * 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. - * Defaults to 3600. + * 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, the worker uses info.relay_url. + * When empty, a future worker will use info.relay_url. */ targets: string[] /** - * Hex-encoded private key for the monitor identity that signs kind 30166/10166 events. - * Prefer MONITOR_PRIVATE_KEY env when set; settings value is a fallback. + * 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. + * 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. - * Defaults to 300. + * Reserved for a future monitor worker. Defaults to 300. */ dnsCacheTtlSeconds: number }