Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nip66-settings-foundation.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
10 changes: 10 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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('<your_language>', 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) |
Expand Down
18 changes: 18 additions & 0 deletions resources/default-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
48 changes: 48 additions & 0 deletions src/@types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
/**
Expand Down Expand Up @@ -324,5 +371,6 @@ export interface Settings {
nip43?: Nip43Settings
nip45?: Nip45Settings
nip50?: Nip50Settings
nip66?: Nip66Settings
wot?: WoTSettings
}
37 changes: 37 additions & 0 deletions test/unit/utils/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading