Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .changeset/sunset-static-mirroring-worker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"nostream": minor
---

chore: sunset the StaticMirroringWorker

The static mirroring worker is removed, together with the `mirroring.static` settings and the mirror health table and `/admin/ops/mirrors` route of the admin console. A `mirroring.static` that is still configured is ignored and logs a warning at start-up. To sync events with another relay, use NIP-77 (Negentropy), for example with `strfry sync`.
15 changes: 3 additions & 12 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ The settings below are listed in alphabetical order by name. Please keep this ta
| limits.event.content[].maxLength | Maximum length of `content`. Defaults to 1 MB. Disabled when set to zero. |
| limits.event.createdAt.maxNegativeDelta | Maximum number of seconds an event's `created_at` can be in the past. Defaults to zero. Disabled when set to zero. |
| limits.event.createdAt.maxPositiveDelta | Maximum number of seconds an event's `created_at` can be in the future. Defaults to 900 (15 minutes). Disabled when set to zero. |
| limits.event.eventId.minLeadingZeroBits | Leading zero bits required on every incoming event for proof of work. Defaults to zero. Disabled when set to zero. Ignored on the client path while `limits.event.pow.enabled` is true (mirrored events from `static-mirroring-worker.ts` still enforce this static value). |
| limits.event.eventId.minLeadingZeroBits | Leading zero bits required on every incoming event for proof of work. Defaults to zero. Disabled when set to zero. Ignored on the client path while `limits.event.pow.enabled` is true. |
| limits.event.kind.blacklist | List of event kinds to always reject. Leave empty to allow any. |
| limits.event.kind.whitelist | List of event kinds to always allow. Leave empty to allow any. |
| limits.event.pow.ceilingBits | Maximum adaptive PoW difficulty, reached at approximately 2x `targetEventsPerSecond` and beyond. |
Expand Down Expand Up @@ -194,11 +194,7 @@ The settings below are listed in alphabetical order by name. Please keep this ta
| limits.client.subscription.maxFilters | Maximum number of filters per subscription. Defaults to 10. Disabled when set to zero. |
| limits.message.rateLimits[].period | Rate limiting period in milliseconds. For `sliding_window`: the time window. For `ewma`: the half-life of the decay function. |
| limits.message.rateLimits[].rate | Maximum number of messages during period. |
| mirroring.static[].address | Address of mirrored relay. (e.g. ws://100.100.100.100:8008) |
| mirroring.static[].filters | Subscription filters used to mirror. |
| mirroring.static[].limits.event | Event limit overrides for this mirror. See configurations under limits.event. |
| mirroring.static[].secret | Secret to pass to relays. Nostream relays only. Optional. |
| mirroring.static[].skipAdmissionCheck | Disable the admission fee check for events coming from this mirror. |
| mirroring.static | Removed: the static mirroring worker no longer exists, so this setting is ignored and logs a warning at start-up. To sync events with another relay, use NIP-77 (Negentropy) from a client that speaks it, for example `strfry sync`, against a relay with `nip77.enabled` set. |
| network.maxPayloadSize | Maximum number of bytes accepted per WebSocket frame |
| network.remoteIpHeader | HTTP header from proxy containing IP address from client. |
| nip05.domainBlacklist | List of domains blocked from NIP-05 verification. Authors with NIP-05 at these domains will be rejected. |
Expand Down Expand Up @@ -226,7 +222,7 @@ The settings below are listed in alphabetical order by name. Please keep this ta
| 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. When true, starts a `relay-monitor` cluster worker that probes targets on an interval and stores the latest snapshot in Redis. Defaults to false. |
| 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`. Addresses from `mirroring.static[]` are probed for the admin snapshot but are not published as kind `30166` discovery events. |
| nip66.targets | Public WebSocket URLs to probe (for example `wss://relay.example.com`). When empty, defaults to `info.relay_url`. |
| nip66.geohash | Optional NIP-52 geohash describing this monitor's network vantage. Published on kind `10166` monitor announcements when set. |
| nip66.historyRetentionRuns | Maximum probe run snapshots kept for the admin Network Health history API and timeline. Defaults to 168. |
| nip66.historyRetentionHours | Optional maximum age in hours for retained probe history. When unset, history is trimmed by run count only. |
Expand Down Expand Up @@ -265,11 +261,6 @@ The settings below are listed in alphabetical order by name. Please keep this ta
| limits.admissionCheck.rateLimits[].rate | Maximum number of admission checks during period. |
| limits.admissionCheck.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
| limits.rateLimiter.strategy | Rate limiting strategy. Either `ewma` or `sliding_window`. Defaults to `ewma`. When using `ewma`, the `period` field in each rate limit serves as the half-life for the exponential decay function. Note: when switching from `sliding_window` to `ewma`, consider increasing `rate` values slightly as EWMA penalizes bursty behavior more aggressively. |
| mirroring.static[].address | Address of mirrored relay. (e.g. ws://100.100.100.100:8008) |
| mirroring.static[].filters | Subscription filters used to mirror. |
| mirroring.static[].limits.event | Event limit overrides for this mirror. See configurations under limits.event. |
| mirroring.static[].secret | Secret to pass to relays. Nostream relays only. Optional. |
| mirroring.static[].skipAdmissionCheck | Disable the admission fee check for events coming from this mirror. |
| network.maxPayloadSize | Maximum number of bytes accepted per WebSocket frame |
| network.remoteIpHeader | HTTP header from proxy containing IP address from client. |
| network.trustedProxies | Optional allow-list of proxy IPs allowed to set `network.remoteIpHeader`; otherwise socket remote IP is used. |
Expand Down
50 changes: 4 additions & 46 deletions resources/admin/assets/admin-ops.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
const config = window.__ADMIN_DASHBOARD__ || { pathPrefix: '' }
const adminBase = `${config.pathPrefix || ''}/admin`

const mirrorsBody = document.getElementById('ops-mirrors-body')
if (!mirrorsBody) {
const workersBody = document.getElementById('ops-workers-body')
if (!workersBody) {
return
}

const elements = {
mirrorsSync: document.getElementById('ops-mirrors-sync'),
mirrorsBody,
workersSync: document.getElementById('ops-workers-sync'),
workersBody: document.getElementById('ops-workers-body'),
workersBody,
nip05Error: document.getElementById('ops-nip05-error'),
nip05Domain: document.getElementById('ops-nip05-domain'),
nip05Verified: document.getElementById('ops-nip05-verified'),
Expand Down Expand Up @@ -80,34 +78,6 @@
return response
}

const renderMirrors = (mirrors) => {
if (!Array.isArray(mirrors) || !mirrors.length) {
elements.mirrorsBody.innerHTML =
'<tr><td colspan="6" class="admin-muted small">No mirrors configured.</td></tr>'
return
}

elements.mirrorsBody.innerHTML = mirrors
.map((mirror) => {
const heartbeat = mirror.heartbeat
const state = heartbeat?.connectionState ?? 'unknown'
const reconnects = heartbeat?.reconnectCount ?? 0
const lastEvent = formatTime(heartbeat?.lastEventAt)
const errorNote = heartbeat?.lastError
? `<span class="admin-muted small d-block">${escapeHtml(heartbeat.lastError)}</span>`
: ''
return `<tr>
<td>${escapeHtml(mirror.index)}</td>
<td><code class="small">${escapeHtml(mirror.targetUrl)}</code>${errorNote}</td>
<td>${escapeHtml(state)}</td>
<td>${escapeHtml(reconnects)}</td>
<td class="small">${escapeHtml(lastEvent)}</td>
<td>${signalBadge(mirror.stale)}</td>
</tr>`
})
.join('')
}

const renderWorkers = (workers) => {
if (!Array.isArray(workers) || !workers.length) {
elements.workersBody.innerHTML =
Expand Down Expand Up @@ -222,18 +192,6 @@
elements.storageError.classList.remove('d-none')
}

const loadMirrors = async () => {
const response = await adminFetch('/ops/mirrors')
if (!response.ok) {
throw new Error('Failed to load mirrors')
}
const payload = await response.json()
renderMirrors(payload.mirrors)
if (elements.mirrorsSync) {
elements.mirrorsSync.innerHTML = `<span class="prompt-char">&gt;</span> mirrors: updated ${formatTime(new Date().toISOString())}`
}
}

const loadWorkers = async () => {
const response = await adminFetch('/ops/workers')
if (!response.ok) {
Expand Down Expand Up @@ -327,7 +285,7 @@

const refreshAll = async () => {
try {
await Promise.all([loadMirrors(), loadWorkers(), loadNip05()])
await Promise.all([loadWorkers(), loadNip05()])
} catch {
showNip05Error('Unable to refresh operations data.')
}
Expand Down
1 change: 0 additions & 1 deletion resources/admin/assets/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
'probe.failed': true,
'probe.recovered': true,
'ssl.expiring': true,
'mirror.disconnected': true,
})

const newTargetId = () => {
Expand Down
31 changes: 1 addition & 30 deletions resources/admin/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -204,29 +204,6 @@ <h2 class="section-title">Charts</h2>
</div>

<div id="operations-view" class="dashboard-view d-none">
<section class="dashboard-section">
<h2 class="section-title">Mirror health</h2>
<p id="ops-mirrors-sync" class="sync-line mb-2"><span class="prompt-char">&gt;</span> mirrors: not loaded</p>
<div class="table-responsive panel-card">
<table class="table table-sm mb-0" aria-describedby="ops-mirrors-caption">
<caption id="ops-mirrors-caption" class="visually-hidden">Static mirror connection status</caption>
<thead>
<tr>
<th scope="col">Index</th>
<th scope="col">Target</th>
<th scope="col">State</th>
<th scope="col">Reconnects</th>
<th scope="col">Last event</th>
<th scope="col">Signal</th>
</tr>
</thead>
<tbody id="ops-mirrors-body">
<tr><td colspan="6" class="admin-muted small">No mirrors configured.</td></tr>
</tbody>
</table>
</div>
</section>

<section class="dashboard-section">
<h2 class="section-title">Worker ops</h2>
<p id="ops-workers-sync" class="sync-line mb-2"><span class="prompt-char">&gt;</span> workers: not loaded</p>
Expand Down Expand Up @@ -433,12 +410,6 @@ <h2 class="section-title">Operator notifications</h2>
<label class="form-check-label small" for="notifications-event-ssl-expiring">SSL expiring</label>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="form-check">
<input id="notifications-event-mirror-disconnected" class="form-check-input" type="checkbox" data-event-key="mirror.disconnected">
<label class="form-check-label small" for="notifications-event-mirror-disconnected">Mirror disconnected</label>
</div>
</div>
</div>
</fieldset>
</div>
Expand Down Expand Up @@ -511,7 +482,7 @@ <h3 class="section-title h6">Delivery history</h3>
<div id="trust-view" class="dashboard-view d-none">
<section class="dashboard-section">
<h2 class="section-title">Diagnostics export</h2>
<p class="admin-muted small">Download a redacted support bundle: version, health, settings, workers, mirrors, and latest probe snapshot.</p>
<p class="admin-muted small">Download a redacted support bundle: version, health, settings, workers, and latest probe snapshot.</p>
<div id="trust-error" class="alert alert-danger d-none small" role="alert"></div>
<div class="d-flex flex-wrap gap-2 mb-2">
<button id="trust-diagnostics-download" type="button" class="btn btn-console btn-sm">Download JSON</button>
Expand Down
3 changes: 0 additions & 3 deletions resources/default-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,6 @@ workers:
eventStore:
# Only postgres for now. Changing it needs a restart.
backend: postgres
mirroring:
static: []
dvm:
Comment thread
greptile-apps[bot] marked this conversation as resolved.
workers: []
limits:
Expand Down Expand Up @@ -387,7 +385,6 @@ admin:
probe.failed: true
probe.recovered: true
ssl.expiring: true
mirror.disconnected: true
retry:
maxAttempts: 5
baseDelayMs: 1000
Expand Down
23 changes: 0 additions & 23 deletions src/@types/admin-ops.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
export type AdminOpsConnectionState = 'connecting' | 'open' | 'closed'

export interface AdminOpsMirrorHeartbeat {
index: string
targetUrl: string
connectionState: AdminOpsConnectionState
reconnectCount: number
lastError?: string
lastEventAt?: string
updatedAt: string
}

export interface AdminOpsWorkerHeartbeat {
type: string
index?: string
Expand All @@ -19,24 +7,13 @@ export interface AdminOpsWorkerHeartbeat {
updatedAt: string
}

export interface AdminOpsConfiguredMirror {
index: string
targetUrl: string
enabled: boolean
}

export interface AdminOpsConfiguredWorker {
type: string
index?: string
enabled: boolean
label: string
}

export interface AdminOpsMirrorRow extends AdminOpsConfiguredMirror {
heartbeat?: AdminOpsMirrorHeartbeat
stale: boolean
}

export interface AdminOpsWorkerRow extends AdminOpsConfiguredWorker {
heartbeat?: AdminOpsWorkerHeartbeat
monitored: boolean
Expand Down
2 changes: 0 additions & 2 deletions src/@types/operator-notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export enum OperatorNotificationEventType {
PROBE_FAILED = 'probe.failed',
PROBE_RECOVERED = 'probe.recovered',
SSL_EXPIRING = 'ssl.expiring',
MIRROR_DISCONNECTED = 'mirror.disconnected',
}

export type OperatorNotificationChannelType = 'http' | 'discord' | 'slack' | 'telegram'
Expand All @@ -32,7 +31,6 @@ export interface OperatorNotificationEventToggles {
'probe.failed'?: boolean
'probe.recovered'?: boolean
'ssl.expiring'?: boolean
'mirror.disconnected'?: boolean
}

export interface OperatorNotificationRetrySettings {
Expand Down
15 changes: 4 additions & 11 deletions src/@types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,18 +300,11 @@ export interface Remote {
secret: Secret
}

export interface Mirror {
address: string
filters?: SubscriptionFilter[]
secret?: Secret
limits?: {
event?: EventLimits
}
skipAdmissionCheck?: boolean
}

/**
* @deprecated The static mirroring worker was removed. Setting `mirroring.static` only logs a warning at start-up.
*/
export interface Mirroring {
static?: Mirror[]
static?: unknown[]
}

export interface DvmWorker {
Expand Down
15 changes: 5 additions & 10 deletions src/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,11 @@ export class App implements IRunnable {
})

logCentered('1 maintenance worker started', width)
const mirrors = settings?.mirroring?.static

if (Array.isArray(mirrors) && mirrors.length) {
for (let i = 0; i < mirrors.length; i++) {
createWorker({
WORKER_TYPE: 'static-mirroring',
MIRROR_INDEX: i.toString(),
})
}
logCentered(`${mirrors.length} static-mirroring worker started`, width)
if (Array.isArray(settings?.mirroring?.static) && settings.mirroring.static.length) {
logger.warn(
'mirroring.static is no longer supported and is ignored; sync with another relay over NIP-77 (negentropy) instead',
)
logCentered('mirroring.static is no longer supported (ignored)', width)
}

if (settings.nip66?.enabled) {
Expand Down
Loading
Loading