Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5a4c22c
use default import for dompurify instead of namespace import
KTibow May 10, 2026
0afb6e3
Add RelayMon trusted relay assertion publishing (#923)
dskvr Jun 12, 2026
238d016
Allow RelayMon TRA publication during warmup (#925)
dskvr Jun 13, 2026
1419099
fix(relaymon): move TRA per-check DB work off the check hot path (#926)
dskvr Jun 14, 2026
62b71ab
ci: unified Docker image CD (dev + tagged, Docker Hub + GHCR) (#928)
dskvr Jun 16, 2026
9948640
rstate: publish trusted relay assertions from computed state (#922)
dskvr Jun 16, 2026
875adc3
fix(relaymon): publish each relay deletion once, not on every boot (#…
dskvr Jun 16, 2026
72149bf
chore(trawler): remove committed merge-conflict markers from CHANGELO…
dskvr Jul 8, 2026
563bab9
fix build failures (#937)
dskvr Jul 8, 2026
2365b2f
chore(deps-dev): bump http-proxy-middleware from 3.0.5 to 3.0.7 (#933)
dependabot[bot] Jul 8, 2026
f0d7e5a
chore(deps): bump dompurify from 3.4.2 to 3.4.11 (#934)
dependabot[bot] Jul 8, 2026
124ac2a
chore(deps): bump js-yaml from 4.1.1 to 4.2.0 (#935)
dependabot[bot] Jul 8, 2026
89d1136
fix(gui/security): close remaining XSS vectors (#938)
dskvr Jul 8, 2026
30c4643
chore(deps-dev): bump vitest from 3.1.1 to 3.2.6 in /libraries/audito…
dependabot[bot] Jul 8, 2026
8c1ad69
chore(deps-dev): bump vitest from 3.1.2 to 3.2.6 in /libraries/schema…
dependabot[bot] Jul 8, 2026
c0da427
chore(deps): bump ws from 8.18.0 to 8.20.1 in /libraries/nocap/adapte…
dependabot[bot] Jul 8, 2026
e4a23de
chore(deps): bump ws from 8.17.1 to 8.20.1 in /libraries/nocap (#911)
dependabot[bot] Jul 8, 2026
c1e9e67
chore(deps): bump ws from 8.18.0 to 8.20.1 (#912)
dependabot[bot] Jul 8, 2026
8c1802e
chore(deps-dev): bump @sveltejs/kit from 2.59.0 to 2.60.1 (#910)
dependabot[bot] Jul 8, 2026
d26acd1
chore(deps-dev): bump svelte from 5.53.5 to 5.55.7 (#909)
dependabot[bot] Jul 8, 2026
4bf5759
chore(deps-dev): bump vitest from 0.34.6 to 3.2.6 in /libraries/nocap…
dependabot[bot] Jul 8, 2026
a09d28d
chore(deps-dev): bump vitest from 0.34.6 to 4.1.0 in /libraries/nocap…
dependabot[bot] Jul 8, 2026
e8a2fb8
chore(deps-dev): bump esbuild from 0.25.12 to 0.28.1 (#924)
dependabot[bot] Jul 8, 2026
c98d886
add hasLimitations table formatter (#907)
KTibow Jul 8, 2026
f42e7d6
chore(deps-dev): bump vite from 5.4.21 to 6.4.2 (#881)
dependabot[bot] Jul 8, 2026
316689e
Merge remote-tracking branch 'origin/pr/908' into HEAD
dskvr Jul 8, 2026
a31523c
Preserve DOMPurify sanitize calls after default-import repair
dskvr Jul 8, 2026
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
122 changes: 122 additions & 0 deletions .github/workflows/docker-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: Docker Images

# Unified Docker image CD for the matrix packages that produce images
# (rstate, trawler, relaymon — nocapd is deprecated and intentionally omitted).
#
# Two paths, both publishing to Docker Hub (nostrwatch/<app>) AND GHCR
# (ghcr.io/sandwichfarm/<app>):
#
# dev — on every push to the integration branch (`next`): rolling
# :dev and :dev-<sha> images for ALL matrix packages.
# tagged — on a release tag `<package>@v<version>` (the format produced by
# the changesets release in publish-package.yml): :<version> for
# that package, plus :latest when it is the highest non-prerelease
# version.
#
# Both paths are runnable manually via workflow_dispatch. The decision of what
# to build and which tags to push lives in scripts/ci/plan-docker-images.mjs,
# which is dependency-free and locally runnable for verification.

on:
push:
branches: [next]
tags:
- '*@v*'
- '**@v*'
workflow_dispatch:
inputs:
mode:
description: 'Build path: dev (rolling :dev images) or tagged (:<version> [+ :latest])'
type: choice
options: [dev, tagged]
default: dev
package:
description: 'Which image(s) to build'
type: choice
options: [all, rstate, trawler, relaymon]
default: all
version:
description: 'tagged mode: version to publish (defaults to each package version file)'
type: string
required: false
update_latest:
description: 'tagged mode: move :latest? auto = only when highest non-prerelease'
type: choice
options: [auto, 'true', 'false']
default: auto

concurrency:
group: docker-images-${{ github.ref }}-${{ github.event.inputs.package || 'auto' }}
cancel-in-progress: false

permissions:
contents: read
packages: write

jobs:
prepare:
name: Plan images
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.plan.outputs.matrix }}
any: ${{ steps.plan.outputs.any }}
steps:
- uses: actions/checkout@v4
with:
# Need full tag history so the planner can decide whether a tagged
# build is the highest version and should move :latest.
fetch-depth: 0

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Plan docker images
id: plan
env:
GH_EVENT_NAME: ${{ github.event_name }}
GH_REF_TYPE: ${{ github.ref_type }}
GH_REF_NAME: ${{ github.ref_name }}
GH_SHA: ${{ github.sha }}
IN_MODE: ${{ github.event.inputs.mode }}
IN_PACKAGE: ${{ github.event.inputs.package }}
IN_VERSION: ${{ github.event.inputs.version }}
IN_UPDATE_LATEST: ${{ github.event.inputs.update_latest }}
run: node scripts/ci/plan-docker-images.mjs

build:
name: ${{ matrix.name }} (${{ matrix.mode }})
needs: prepare
if: needs.prepare.outputs.any == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build & push ${{ matrix.name }}
uses: docker/build-push-action@v6
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
push: true
platforms: linux/amd64
provenance: false
tags: ${{ matrix.tags }}
51 changes: 0 additions & 51 deletions .github/workflows/docker-relaymon.yml

This file was deleted.

51 changes: 0 additions & 51 deletions .github/workflows/docker-trawler.yml

This file was deleted.

2 changes: 2 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
^/architecture
^/getting-started
^/packages
# Local planning artifacts — intentionally absent from CI checkouts and package docs
^file://.*/\.planning/
# localhost dev server — not available in CI
^http://localhost
11 changes: 8 additions & 3 deletions apps/docker-stacks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ A collection of Docker Compose configurations for deploying nostr-watch services

### How It Works

nostr-watch monitors nostr relays and publishes the results as nostr events ([NIP-66](https://github.com/nostr-protocol/nips/blob/master/66.md)). There are two core services:
nostr-watch monitors nostr relays and publishes the results as nostr events ([NIP-66](https://github.com/nostr-protocol/nips/blob/master/66.md)). RelayMon can also publish opt-in Trusted Relay Assertions (kind `30385`) using the draft TRA NIP shape from [`Letdown2491/trustedrelays`](https://github.com/Letdown2491/trustedrelays). There are two core services:

- **RelayMon** — Continuously checks relays for connectivity (WebSocket open, read, NIP-11 info, DNS) and publishes the results. Supports clearnet, Tor, and I2P networks.
- **RelayMon** — Continuously checks relays for connectivity (WebSocket open, read, NIP-11 info, DNS), publishes the results, and can optionally publish Trusted Relay Assertions. Supports clearnet, Tor, and I2P networks.
- **Trawler** — Crawls nostr relay lists to discover new relays and writes them to a SQLite database. When paired with RelayMon, trawler feeds discovered relays into the monitor automatically.

Each stack composes these services (and optional network proxies) into a ready-to-run deployment. You configure two things per service:
Expand All @@ -40,6 +40,10 @@ Each stack composes these services (and optional network proxies) into a ready-t

Each stack directory contains its own README with detailed setup instructions.

## Installation

Each stack is installed from its own directory by copying the example environment and configuration files, filling in local values, and starting Docker Compose. Use the Quick Start below for the clearnet RelayMon stack, or open the target stack README for stack-specific variables.

## Quick Start

```sh
Expand Down Expand Up @@ -100,11 +104,12 @@ Controls application behavior — monitor identity, seed sources, check types, a
Key sections:

- **`monitor`** — Your monitor's identity: slug, name, owner pubkey, and announcement relays
- **`publisher`** — Which relays receive check result events (Kind 1066, Kind 20166)
- **`publisher`** — Which relays receive check result events (Kind 1066, Kind 20166) and default TRA events
- **`relaymon.networks`** — Which networks to monitor (`clearnet`, `tor`, `i2pd`)
- **`relaymon.seed`** — How the relay list is populated (`events`, `config`, `db`)
- **`relaymon.checks`** — Which checks to run (`open`, `read`, `info`, `dns`) and their intervals/timeouts
- **`relaymon.retry`** — Backoff strategy for failed relay connections
- **`relaymon.trustedRelayAssertions`** — Optional kind `30385` Trusted Relay Assertion publishing controls

### Docker Compose Environment (set in `docker-compose.yaml`)

Expand Down
3 changes: 2 additions & 1 deletion apps/docker-stacks/relaymon-clearnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Runs RelayMon in clearnet mode — direct connections to relays with no proxy or

## How It Works

RelayMon connects directly to nostr relays over the public internet, runs connectivity checks (WebSocket open, read, NIP-11 info, DNS), and publishes the results as nostr events. This is the simplest stack — a single container with no network routing.
RelayMon connects directly to nostr relays over the public internet, runs connectivity checks (WebSocket open, read, NIP-11 info, DNS), and publishes the results as nostr events. It can also publish optional Trusted Relay Assertions (kind `30385`) when `relaymon.trustedRelayAssertions.enabled` is set. This is the simplest stack — a single container with no network routing.

## Setup

Expand Down Expand Up @@ -50,3 +50,4 @@ See `config.yaml.example` for a fully commented template. Key settings to custom
- **`monitor.owner`** — Your nostr public key (hex format)
- **`relaymon.seed`** — Where to get the list of relays to monitor
- **`relaymon.checks`** — Which checks to run and how often
- **`relaymon.trustedRelayAssertions`** — Optional kind `30385` assertion publishing controls
15 changes: 15 additions & 0 deletions apps/docker-stacks/relaymon-clearnet/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ relaymon:
read: 5000 # Read timeout (ms)
max: 200 # Max concurrent checks

# Optional: publish Trusted Relay Assertions (Kind 30385)
# trustedRelayAssertions:
# enabled: false
# relays: [] # Defaults to publisher.relays when empty
# min_observations: 10
# material_change_threshold: 3
# refresh_interval: 1h
# history_retention: 30d
# max_observations_per_relay: 1000
# publish_unreachable: true
# publish_blocked: false
# algorithm:
# version: relaymon-local-v2
# url: https://github.com/Letdown2491/trustedrelays/blob/main/ALGORITHM.md

# Optional: Health monitoring
# health:
# enabled: true
Expand Down
4 changes: 3 additions & 1 deletion apps/docker-stacks/relaymon-multinet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Runs RelayMon in multinet mode with Tor and I2P proxy support via [hedproxy](htt

This stack runs three containers:

- **relaymon** — The relay monitor, running in `multinet` mode. hedproxy runs inside this container and automatically routes connections based on the relay URL scheme:
- **relaymon** — The relay monitor, running in `multinet` mode. It publishes NIP-66 results and can optionally publish Trusted Relay Assertions (kind `30385`). hedproxy runs inside this container and automatically routes connections based on the relay URL scheme:
- `wss://` / `ws://` — direct clearnet connection
- `.onion` URLs — routed through the Tor SOCKS5 proxy
- `.i2p` URLs — routed through the I2P HTTP proxy
Expand Down Expand Up @@ -70,6 +70,8 @@ relaymon:
- i2pd
```

Set `relaymon.trustedRelayAssertions.enabled` to `true` to publish optional kind `30385` trust assertions.

### Proxy Configuration (`config/`)

The proxy config files are pre-configured for the default setup:
Expand Down
15 changes: 15 additions & 0 deletions apps/docker-stacks/relaymon-multinet/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,18 @@ relaymon:
open: 30000
read: 5000
max: 200

# Optional: publish Trusted Relay Assertions (Kind 30385)
# trustedRelayAssertions:
# enabled: false
# relays: []
# min_observations: 10
# material_change_threshold: 3
# refresh_interval: 1h
# history_retention: 30d
# max_observations_per_relay: 1000
# publish_unreachable: true
# publish_blocked: false
# algorithm:
# version: relaymon-local-v2
# url: https://github.com/Letdown2491/trustedrelays/blob/main/ALGORITHM.md
4 changes: 2 additions & 2 deletions apps/docker-stacks/relaymon-vpn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Runs RelayMon in clearnet mode, with all traffic routed through a VPN via [Gluet

## How It Works

This stack adds a Gluetun VPN container in front of RelayMon. RelayMon uses Gluetun's network (`network_mode: container:gluetun`), so all outbound traffic — relay connections, DNS lookups, event publishing — goes through the VPN tunnel. The relay monitoring behavior is identical to the clearnet stack, just routed differently.
This stack adds a Gluetun VPN container in front of RelayMon. RelayMon uses Gluetun's network (`network_mode: container:gluetun`), so all outbound traffic — relay connections, DNS lookups, NIP-66 publishing, and optional Trusted Relay Assertion publishing — goes through the VPN tunnel. The relay monitoring behavior is identical to the clearnet stack, just routed differently.

## Setup

Expand Down Expand Up @@ -56,4 +56,4 @@ To change the VPN provider, also update `VPN_SERVICE_PROVIDER` in `docker-compos

### `config.yaml`

See `config.yaml.example` for a fully commented template. Configuration is the same as the clearnet stack.
See `config.yaml.example` for a fully commented template. Configuration is the same as the clearnet stack. Set `relaymon.trustedRelayAssertions.enabled` to `true` to publish optional kind `30385` trust assertions.
15 changes: 15 additions & 0 deletions apps/docker-stacks/relaymon-vpn/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,18 @@ relaymon:
open: 30000
read: 5000
max: 200

# Optional: publish Trusted Relay Assertions (Kind 30385)
# trustedRelayAssertions:
# enabled: false
# relays: []
# min_observations: 10
# material_change_threshold: 3
# refresh_interval: 1h
# history_retention: 30d
# max_observations_per_relay: 1000
# publish_unreachable: true
# publish_blocked: false
# algorithm:
# version: relaymon-local-v2
# url: https://github.com/Letdown2491/trustedrelays/blob/main/ALGORITHM.md
3 changes: 2 additions & 1 deletion apps/docker-stacks/trawler-relaymon-clearnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Runs both Trawler and RelayMon in clearnet mode with separate databases.
This stack combines two services:

- **Trawler** — Crawls nostr relay lists (Kind 10002 events) to discover new relay URLs and writes them to its own SQLite database (`trawler.db`)
- **RelayMon** — Maintains its own database (`relaymon.db`) for check state, and seeds its relay list by reading from trawler's database as a read-only source
- **RelayMon** — Maintains its own database (`relaymon.db`) for check state, seeds its relay list by reading from trawler's database as a read-only source, and can optionally publish Trusted Relay Assertions (kind `30385`)

Both services mount the same `./data` volume so RelayMon can read trawler's database, but each service writes to its own database file to avoid SQLite locking conflicts. Trawler continuously discovers new relays, and RelayMon picks them up on its next seed cycle via the `db` seed source.

Expand Down Expand Up @@ -62,6 +62,7 @@ See the `.example` files for fully commented templates. Key points:
- Trawler writes to `/opt/data/trawler.db`
- RelayMon writes to `/opt/data/relaymon.db`
- RelayMon's `seed.options.db.path` points to `/opt/data/trawler.db` (read-only seed source)
- Set `relaymon.trustedRelayAssertions.enabled` to `true` to publish optional kind `30385` trust assertions

### Database Architecture

Expand Down
Loading
Loading