Skip to content

Commit e294a71

Browse files
authored
feat(nip66): add relay monitor settings schema and defaults (#689)
1 parent f92eabe commit e294a71

7 files changed

Lines changed: 104 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"nostream": minor
3+
---
4+
5+
Add NIP-66 relay monitor settings foundation with defaults for probe interval, timeouts, targets, monitor identity, and DNS cache TTL.

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ WORKER_COUNT=2 # Defaults to CPU count. Use 1 or 2 for local testing.
5555
# --- RELAY PRIVATE KEY (Optional) ---
5656
# RELAY_PRIVATE_KEY=your_hex_private_key
5757

58+
# --- NIP-66 MONITOR IDENTITY (Optional; reserved for future event publisher) ---
59+
# MONITOR_PRIVATE_KEY=your_hex_monitor_private_key
60+
5861
# --- PAYMENTS (Only if enabled in settings.yaml) ---
5962
# ZEBEDEE_API_KEY=
6063
# NODELESS_API_KEY=

CONFIGURATION.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ The following environment variables can be set:
6161
| ADMIN_DEPENDENCY_PING_TIMEOUT_MS | Timeout for admin DB/Redis dependency pings (ms) | 3000 |
6262
| GRAFANA_URL | Grafana base URL for admin dashboard embeds | http://127.0.0.1:7777 |
6363
| NOSTR_CONFIG_DIR | Configuration directory | <project_root>/.nostr/ |
64+
| 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. | |
6465
| DEBUG | Debugging filter | |
6566
| ZEBEDEE_API_KEY | Zebedee Project API Key | |
6667
| 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
194195
| 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. |
195196
| 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('<your_language>', event_content));` — otherwise the planner cannot use the index and queries fall back to sequential scans. |
196197
| nip50.maxQueryLength | Maximum length of the search query string. Queries exceeding this are truncated. Defaults to 256. |
198+
| nip66.dnsCacheTtlSeconds | DNS cache TTL in seconds for repeated probe lookups of the same hostname. Reserved for a future monitor worker. Defaults to 300. |
199+
| 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. |
200+
| nip66.probeIntervalSeconds | Seconds between scheduled relay probe runs. Reserved for a future monitor worker. Defaults to 3600. |
201+
| 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. |
202+
| nip66.timeouts.dnsMs | DNS probe timeout in milliseconds. Defaults to 10000. |
203+
| nip66.timeouts.nip11Ms | NIP-11 fetch timeout in milliseconds. Defaults to 10000. |
204+
| nip66.timeouts.tlsMs | TLS probe timeout in milliseconds. Defaults to 10000. |
205+
| nip66.timeouts.wsRttMs | WebSocket open RTT probe timeout in milliseconds. Defaults to 10000. |
197206
| paymentProcessors.lnbits.baseURL | Base URL of your Lnbits instance. |
198207
| paymentProcessors.lnbits.callbackBaseURL | Public-facing Nostream's Lnbits Callback URL. (e.g. https://relay.your-domain.com/callbacks/lnbits) |
199208
| 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) |

resources/default-settings.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,21 @@ nip50:
8080
# 'simple' (no stemming) or a language name like 'english', 'spanish'
8181
language: simple
8282
maxQueryLength: 256
83+
nip66:
84+
# NIP-66 relay liveness monitoring. Disabled by default.
85+
# Settings only in this release (no monitor worker yet); enabling is currently a no-op.
86+
# Future versions may probe public relay URLs and publish kind 30166/10166 events.
87+
enabled: false
88+
# Seconds between scheduled probe runs (reserved for a future monitor worker).
89+
probeIntervalSeconds: 3600
90+
timeouts:
91+
dnsMs: 10000
92+
tlsMs: 10000
93+
wsRttMs: 10000
94+
nip11Ms: 10000
95+
# Public WebSocket URLs to probe. Empty list defaults to info.relay_url.
96+
targets: []
97+
dnsCacheTtlSeconds: 300
8398
wot:
8499
# Web of Trust filtering. When enabled, only events from pubkeys within
85100
# the relay owner's 2-hop follow graph are accepted.

src/@types/settings.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,43 @@ export interface Nip50Settings {
273273
maxQueryLength?: number
274274
}
275275

276+
export interface Nip66ProbeTimeouts {
277+
dnsMs: number
278+
tlsMs: number
279+
wsRttMs: number
280+
nip11Ms: number
281+
}
282+
283+
export interface Nip66Settings {
284+
/**
285+
* Enable NIP-66 relay monitoring configuration.
286+
* Note: this release only defines settings (no monitor worker yet), so
287+
* enabling is currently a no-op.
288+
* Defaults to false.
289+
*/
290+
enabled: boolean
291+
/**
292+
* Interval in seconds between probe runs.
293+
* Reserved for a future monitor worker. Defaults to 3600.
294+
*/
295+
probeIntervalSeconds: number
296+
/**
297+
* Per-check probe timeouts in milliseconds.
298+
* Reserved for a future monitor worker.
299+
*/
300+
timeouts: Nip66ProbeTimeouts
301+
/**
302+
* Public relay WebSocket URLs to probe (for example wss://relay.example.com).
303+
* When empty, a future worker will use info.relay_url.
304+
*/
305+
targets: string[]
306+
/**
307+
* DNS cache TTL in seconds for repeated probes of the same hostname.
308+
* Reserved for a future monitor worker. Defaults to 300.
309+
*/
310+
dnsCacheTtlSeconds: number
311+
}
312+
276313
export interface Nip05Settings {
277314
mode: Nip05Mode
278315
/**
@@ -351,5 +388,6 @@ export interface Settings {
351388
nip43?: Nip43Settings
352389
nip45?: Nip45Settings
353390
nip50?: Nip50Settings
391+
nip66?: Nip66Settings
354392
wot?: WoTSettings
355393
}

src/cli/utils/env-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const SUPPORTED_ENV_KEYS = new Set([
1919
'SECRET',
2020
'RELAY_PORT',
2121
'RELAY_PRIVATE_KEY',
22+
'MONITOR_PRIVATE_KEY',
2223
'WORKER_COUNT',
2324
'DB_URI',
2425
'DB_HOST',

test/unit/utils/settings.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,39 @@ describe('SettingsStatic', () => {
261261
})
262262
})
263263

264+
describe('NIP-66 settings defaults', () => {
265+
it('default-settings.yaml contains a nip66 block with safe defaults', () => {
266+
const defaults = SettingsStatic.loadAndParseYamlFile(SettingsStatic.getDefaultSettingsFilePath())
267+
268+
expect(defaults).to.have.nested.property('nip66.enabled', false)
269+
expect(defaults).to.have.nested.property('nip66.probeIntervalSeconds', 3600)
270+
expect(defaults).to.have.nested.property('nip66.timeouts.dnsMs', 10_000)
271+
expect(defaults).to.have.nested.property('nip66.timeouts.tlsMs', 10_000)
272+
expect(defaults).to.have.nested.property('nip66.timeouts.wsRttMs', 10_000)
273+
expect(defaults).to.have.nested.property('nip66.timeouts.nip11Ms', 10_000)
274+
expect(defaults).to.have.nested.property('nip66.targets').that.deep.equals([])
275+
expect(defaults).to.have.nested.property('nip66.dnsCacheTtlSeconds', 300)
276+
})
277+
278+
it('user config nip66 block overrides defaults', () => {
279+
const defaults = SettingsStatic.loadAndParseYamlFile(SettingsStatic.getDefaultSettingsFilePath())
280+
const userConfig = {
281+
nip66: {
282+
enabled: true,
283+
probeIntervalSeconds: 900,
284+
targets: ['wss://relay.example.com'],
285+
},
286+
}
287+
const merged = mergeDeepRight(defaults, userConfig) as Settings
288+
289+
expect(merged.nip66?.enabled).to.equal(true)
290+
expect(merged.nip66?.probeIntervalSeconds).to.equal(900)
291+
expect(merged.nip66?.targets).to.deep.equal(['wss://relay.example.com'])
292+
expect(merged.nip66?.timeouts?.dnsMs).to.equal(10_000)
293+
expect(merged.nip66?.dnsCacheTtlSeconds).to.equal(300)
294+
})
295+
})
296+
264297
describe('WoT settings defaults', () => {
265298
it('default-settings.yaml contains a wot block with enabled: false', () => {
266299
const defaults = SettingsStatic.loadAndParseYamlFile(

0 commit comments

Comments
 (0)