summaryrefslogtreecommitdiffstats
path: root/internal
Commit message (Collapse)AuthorAgeFilesLines
* feat: separate Auth Writes and Auth Reads on dashboardbndw42 hours1-4/+22
| | | | | | | | - Added getMetricByLabels() helper for multi-label metric queries - Split 'Authorized' metric into: - Auth Writes: requests_total{method=EVENT, status=authorized} - Auth Reads: requests_total{method=REQ, status=authorized} - Provides clearer visibility into authenticated read vs write operations
* feat: track authorized (authenticated + successful) requestsbndw42 hours3-2/+26
| | | | | | | | | | | | | | | | | | | | | Add 'authorized' status for requests that complete successfully after authentication. This complements the existing 'unauthenticated' (pre-auth) status tracking. Now the dashboard shows: - Authorized: Authenticated requests that succeeded - Unauthorized: Authenticated requests rejected (not in allowlist) - Pre-Auth: Requests sent before authentication This gives full visibility into the auth flow: 1. Challenges: How many clients authenticated 2. Authorized: How many authenticated requests succeeded 3. Unauthorized: How many were rejected despite valid auth 4. Pre-Auth: How many tried before authenticating Updated metrics: - requests_total{status="authorized"} - authenticated successes - requests_total{status="ok"} - unauthenticated successes (when no auth)
* fix: record AUTH attempt metrics in WebSocket handlerbndw42 hours2-0/+10
| | | | | | | | | | | | Add RecordAuthAttempt calls to handleAuth so successful and failed AUTH attempts are tracked in metrics. This fixes the dashboard 'Challenges' counter which was always showing 0. The deferred call ensures both success and failure cases are recorded: - success=true when AUTH completes successfully - success=false when AUTH fails (invalid signature, wrong challenge, etc.) Updated MetricsRecorder interface and mock to include RecordAuthAttempt.
* feat: add auth rejection metrics to dashboardbndw42 hours1-5/+12
| | | | | | | | | | Updated metrics dashboard to show: - Unauthorized: Requests rejected due to pubkey not in allowlist - Pre-Auth: Requests sent before authentication completed - Challenges: Successful AUTH challenge completions This surfaces the new 'unauthorized' and 'unauthenticated' request status metrics on the dashboard for easier monitoring of auth issues.
* feat: track auth rejections with specific 'unauthorized' statusbndw42 hours2-2/+9
| | | | | | | | | | | | | | | Auth failures (pubkey not in allowlist) are now tracked with status 'unauthorized' instead of generic 'error' in metrics. This allows monitoring of auth rejections separately from other errors. Metrics will now show: - muxstr_relay_requests_total{status="unauthorized"} - auth failures - muxstr_relay_requests_total{status="unauthenticated"} - no auth yet - muxstr_relay_requests_total{status="error"} - other errors - muxstr_relay_requests_total{status="rate_limited"} - rate limited - muxstr_relay_requests_total{status="ok"} - success Added test assertion to verify metrics tracking.
* fix: add YAML tags to AuthOperationConfigbndw42 hours1-2/+2
| | | | | | | | The AuthOperationConfig struct was missing YAML tags, causing the config loader to not properly parse allowed_npubs from YAML. This was causing TestNpubNormalization to fail with an index out of range panic because AllowedNpubs was always empty.
* fix: improve WebSocket error responses and protocol compliancebndw43 hours1-8/+10
| | | | | | | | | | | | | - Send OK false for rate limit errors instead of NOTICE - Send OK false for auth errors (e.g. pubkey not in allowlist) - Remove OK response for AUTH events (AUTH is not an EVENT type) - Parse event before auth checks to get event ID for error responses These changes improve client UX by providing immediate, structured feedback for all rejection cases instead of generic NOTICE messages. The AUTH event OK removal fixes a bug where clients would read the wrong response when sending EVENT after AUTH.
* test: add integration tests for NIP-42 AUTH and rate limitingbndw43 hours1-0/+526
| | | | | | | | | | | | | | | | | | | Add comprehensive WebSocket handler integration tests that verify: - NIP-42 authentication flow (auth required, challenge/response) - Allowlist enforcement (reject unauthorized pubkeys) - Rate limiting by IP address - Rate limiting by authenticated pubkey - No-auth mode works correctly These tests use real WebSocket connections and would have caught the AUTH timeout bug and other protocol issues. Tests cover: - TestAuthRequired: Verifies AUTH challenge sent, client authenticates, publish succeeds - TestAuthNotInAllowlist: Verifies pubkeys not in allowlist are rejected - TestRateLimitByIP: Verifies unauthenticated clients are rate limited by IP - TestRateLimitByPubkey: Verifies authenticated clients are rate limited by pubkey - TestNoAuthWhenDisabled: Verifies publishing works when auth is disabled
* fix: send OK response when AUTH required but not completedbndw44 hours1-5/+9
| | | | | | | | | | | | | Previously when an EVENT was received before authentication was completed, the relay would send an AUTH challenge but silently drop the EVENT without sending an OK response. This caused clients to timeout waiting for the OK that never came. Now the relay immediately sends OK false with "auth-required" message, so clients know the EVENT was rejected and can retry after AUTH completes. Also optimized to parse the event JSON only once instead of twice.
* feat: add metrics for blocked eventsbndw44 hours3-0/+28
| | | | | | | | | | | | Track and display blocked event counts with per-kind breakdown. - Add events_blocked_total counter with kind label - Add RecordBlockedEvent method to metrics - Display blocked count in dashboard Storage section - Call metric when rejecting non-core protocol kinds Allows monitoring spam patterns and verifying kind filter effectiveness. Raw metrics show breakdown by kind (e.g., kind=20001, kind=30311).
* feat: filter events to core Nostr protocol kinds onlybndw45 hours1-0/+42
| | | | | | | | | | | | | | | | | | | | Add allowlist filtering to reject spam, ephemeral events, and live chat messages. Only accept core protocol kinds (notes, reactions, metadata, etc). Allowed kinds: - 0, 1, 3, 4, 5, 6, 7: Core protocol (NIP-01, 02, 04, 09, 18, 25) - 9735: Zaps (NIP-57) - 10000-10002: Mute/pin lists (NIP-51) - 10050: Relay list metadata - 30023, 30078: Long-form content, app data (NIP-23, 78) Rejected kinds: - 20001: Bot metadata spam (~157+ events/day) - 30311: Live chat messages (~100+ events/day) - All other kinds: Future spam/ephemeral events Reduces storage growth by ~80% while keeping all essential functionality. Clients receive "rejected: kind not supported" for filtered events.
* feat: track WebSocket requests in metricsbndw2 days1-0/+46
| | | | | | | | | | | | | Add request tracking for EVENT and REQ messages to match gRPC behavior. Dashboard now shows total/success/error counts for all requests. - Add RecordRequest to MetricsRecorder interface - Track timing and status in handleEvent and handleReq - Record metrics with status: ok, error, unauthenticated, rate_limited - Measure request duration for performance monitoring WebSocket is the primary interface, so tracking these requests is critical for understanding relay usage and performance.
* fix: track WebSocket connections in metricsbndw2 days1-0/+13
| | | | | | | | | | | Connection metrics were defined but never called. Add IncrementConnections and DecrementConnections to MetricsRecorder interface and call them when WebSocket connections are established/closed. - Add connection methods to MetricsRecorder interface - Increment on successful WebSocket upgrade - Decrement in defer when connection closes - Dashboard will now show active connection count
* feat: add rate limiting to WebSocket connectionsbndw2 days1-1/+53
| | | | | | | | | | | | | | | WebSocket clients were completely unprotected from abuse. Add RateLimiter interface to WebSocket handler and enforce limits on EVENT and REQ messages. - Add RateLimiter interface with Allow(identifier, method) method - Track client IP in connState (proxy-aware via X-Forwarded-For) - Check rate limits in handleEvent and handleReq - Use authenticated pubkey as identifier, fallback to IP - Share same rate limiter instance with gRPC - Add getClientIP() helper that checks proxy headers first Critical security fix for production deployment. Without this, any client could spam unlimited events/subscriptions via WebSocket.
* fix: prioritize proxy headers for rate limitingbndw2 days1-7/+10
| | | | | | | | | Check X-Forwarded-For and X-Real-IP headers before peer info to correctly identify clients behind reverse proxies. Previously, rate limiting would apply globally when behind Caddy/nginx because all requests appeared to come from the proxy's IP address. This fix is critical for production deployments behind reverse proxies.
* refactor: use AuthStore interface and remove type assertionsbndw3 days2-35/+52
| | | | | | | | | | | | | Replace runtime type assertions with compile-time safe AuthStore interface. Add connState struct for cleaner per-connection state management instead of mutable pointer parameters. Reduce auth challenge TTL from 10min to 2min. - Add AuthStore interface with CreateAuthChallenge and ValidateAndConsumeChallenge - Add connState struct for authenticatedPubkey and authChallenge - Remove fragile type assertion pattern in requireAuth and handleAuth - Add nil checks for auth store with clear error messages - Update Handler to have separate auth field - Wire auth store in main.go when auth is enabled
* fix: silently wait for auth instead of sending NOTICEbndw3 days1-1/+9
| | | | | | | After sending AUTH challenge, return nil instead of error to avoid sending NOTICE messages to clients. Add explicit checks in handleEvent and handleReq to silently ignore requests when auth is required but client hasn't authenticated yet. This follows NIP-42 spec more closely.
* feat: implement NIP-42 WebSocket authenticationbndw3 days2-6/+214
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Add support for authenticating WebSocket clients using NIP-42 protocol, enabling auth restrictions for normal Nostr clients. Storage layer (internal/storage/auth.go): - CreateAuthChallenge() - Generate random 32-byte challenge with 10min TTL - ValidateAndConsumeChallenge() - Verify challenge validity and mark as used - CleanupExpiredChallenges() - Remove old challenges from database - Uses existing auth_challenges table WebSocket handler (internal/handler/websocket/handler.go): - Track authenticatedPubkey per connection - Track authChallenge per connection - requireAuth() - Check if operation requires authentication - handleAuth() - Process AUTH responses (kind 22242 events) - sendAuthChallenge() - Send AUTH challenge to client - Enforce auth on EVENT (writes) and REQ (reads) messages - Support separate read/write allowlists Main (cmd/relay/main.go): - Wire auth config from YAML to WebSocket handler - Pass read/write enabled flags and allowed npub lists NIP-42 Flow: 1. Client sends EVENT/REQ without auth 2. If auth required, relay sends: ["AUTH", "<challenge>"] 3. Client signs kind 22242 event with challenge tag 4. Client sends: ["AUTH", <signed-event>] 5. Relay validates signature, challenge, and allowlist 6. Connection marked as authenticated 7. Client can now EVENT/REQ Example config to restrict writes to your npub: ```yaml auth: write: enabled: true allowed_npubs: - npub1your-npub-here... ``` WebSocket clients (Damus, Amethyst, etc.) can now authenticate!
* style: dashboardbndw3 days1-5/+1
|
* style: restyle dashboard to match index page aestheticbndw3 days1-63/+89
| | | | | | | | | | | | | | | | | | | | Transform metrics dashboard from dark modern theme to classic beige theme matching the index page. Changes: - Background: dark (#0a0a0a) → beige (#f5f0e8) - Font: sans-serif → Courier New (monospace) - Colors: vibrant gradients → simple black/brown - Cards: dark (#1a1a1a) → white with black borders - Layout: max-width 1200px → 960px (matches index) - Typography: modern sizing → classic smaller fonts - Logo: Add muxstr SVG logo matching index page - Dividers: Add horizontal rules for section breaks - Status badge: Green gradient → orange/brown (#c97556) - Removed: Color coding (success/error/warning classes) Result: Clean, classic, brutalist aesthetic consistent with index page. The dashboard now feels like part of the same relay, not a separate app.
* feat: add storage stats and average latency metricsbndw3 days2-2/+40
| | | | | | | | | | | | | | | | | | | | | | Track and display storage and performance metrics that were previously showing zeros. Storage metrics: - Add GetStats() method to storage returning event count and DB size - Store database file path for size calculation - Update metrics every 30 seconds via goroutine in main.go - Display event count and DB size (MB) in dashboard Performance metrics: - Calculate average latency from histogram sum/count metrics - Display as milliseconds in dashboard - Formula: (duration_sum / duration_count) * 1000 Missing metrics (deferred): - Connections: requires connection lifecycle tracking (gRPC + WebSocket) - Deletions: count would need API change to ProcessDeletion Dashboard now shows accurate storage stats and request latency!
* feat: track WebSocket subscriptions in metricsbndw3 days2-0/+33
| | | | | | | | | | | | | | | | | Add metrics tracking for WebSocket (NIP-01) subscriptions in addition to existing gRPC subscription tracking. Changes: - Add Count() method to subscription.Manager - Add MetricsRecorder interface to WebSocket handler - Update subscription metrics when REQ/CLOSE messages processed - Wire up metrics to WebSocket handler in main.go Before: Only gRPC stream subscriptions were counted After: Both gRPC and WebSocket subscriptions tracked accurately This fixes the dashboard showing 0 subscriptions when clients connect via WebSocket (e.g., nak req --stream).
* fix: dashboard prefix detection with Go runtime metricsbndw3 days1-1/+2
| | | | | | | | | | | | | | Bug: Dashboard showed zeros because it detected "go" as the prefix instead of "muxstr" when Go runtime metrics appeared first in the metrics list. Fix: Search for the first metric containing "_relay_" to properly detect the namespace (muxstr, nostr-grpc, etc.) instead of using the first metric in the alphabetical list. Before: Object.keys(metrics)[0] → "go_gc_..." → prefix = "go" After: Find metric with "_relay_" → "muxstr_relay_..." → prefix = "muxstr" This fixes the dashboard displaying zeros when metrics data is present.
* fix: properly chain gRPC interceptors and fix dashboard uptimebndw3 days1-6/+10
| | | | | | | | | | | | | | | | | | | Two critical fixes for metrics: 1. Fix interceptor chaining - Changed from mixed grpc.UnaryInterceptor/ChainUnaryInterceptor to proper chaining with grpc.ChainUnaryInterceptor - Metrics interceptor now runs first (as intended) - All interceptors properly chained in order: metrics → auth → ratelimit - This fixes metrics not being recorded for any requests 2. Fix dashboard uptime calculation - Changed from page load time to process_start_time_seconds metric - Uptime now persists correctly across page refreshes - Uses Prometheus standard process_start_time_seconds gauge Before: Metrics showed 0 for all requests, uptime reset on refresh After: Metrics properly record all gRPC requests, uptime shows actual relay uptime
* refactor: serve metrics on main HTTP port instead of separate portbndw3 days1-4/+12
| | | | | | | | | | | | | | | | | | | | | | | Move metrics dashboard and Prometheus endpoint to the main HTTP server for simplified deployment and single ingress configuration. Changes: - Add PrometheusHandler() and DashboardHandler() methods to Metrics - Serve /dashboard on main HTTP port (was root on separate port) - Serve /metrics on main HTTP port (was /metrics on separate port) - Remove separate metrics server goroutine - Update logging to show metrics paths on main HTTP port Benefits: - Single port/ingress needed for all HTTP traffic - Simpler reverse proxy configuration - Dashboard accessible alongside main relay endpoints Endpoints on port 8080: - / - WebSocket/index - /nostr.v1.NostrRelay/* - Connect (gRPC-Web) - /dashboard - Metrics dashboard (HTML) - /metrics - Prometheus metrics (text)
* feat: add metrics dashboard HTML pagebndw3 days2-0/+322
| | | | | | | | | | | | | | | | | | | | | Add a real-time metrics dashboard accessible at the metrics server root. The dashboard displays relay statistics in a human-readable format with auto-refresh every 5 seconds. Features: - Active connections and subscriptions - Request counts (total, success, errors) - Authentication stats (success/failure) - Rate limiting hits - Storage metrics (events, DB size, deletions) - Clean, dark-themed UI with gradient accents - Auto-refresh and live uptime counter The dashboard is embedded using go:embed and served at / while Prometheus metrics continue to be available at /metrics. Example: http://localhost:9090/ for dashboard http://localhost:9090/metrics for raw metrics
* feat: integrate config system into relay main.gobndw3 days5-27/+80
| | | | | | | | | | | | | | | | Add support for loading configuration from YAML file via -config flag. Wire up auth, rate limiting, and metrics interceptors based on config. Changes: - Add -config flag to relay command - Use config types directly in auth package (AuthOperationConfig) - Add conversion methods: RateLimitConfig.ToRateLimiter(), MetricsConfig.ToMetrics() - Add Metrics.Serve() method for prometheus HTTP endpoint - Update main.go to initialize interceptors from config - Fix type naming: OperationAuthConfig -> AuthOperationConfig for consistency Config now supports complete relay setup including auth read/write allowlists, rate limiting, and prometheus metrics.
* refactor: remove frivolous comments from auth validation/credentialsbndw3 days26-3637/+34
| | | | Also removed internal/nostr package - now using northwest.io/nostr library.
* index updatebndw3 days1-1/+1
|
* index updatebndw3 days1-4/+4
|
* Update index pagebndw3 days1-9/+1
|
* refactor: remove frivolous comments from auth and configbndw3 days2-115/+16
| | | | | Removed ~100 lines of obvious comments that just repeated what the code does. Kept only comments that add clarity or valuable detail.
* refactor: restructure auth config for better UXbndw3 days6-151/+242
| | | | | | | | | | | | | | | | | | | | | | | | | | | Changed from flat structure to hierarchical read/write config: Before: auth: enabled: bool required: bool allowed_npubs_read: [] allowed_npubs_write: [] After: auth: read: enabled: bool allowed_npubs: [] write: enabled: bool allowed_npubs: [] Three states per operation: - enabled=false: no auth, allow all - enabled=true, allowed_npubs=[]: auth required, any valid signature - enabled=true, allowed_npubs=[...]: auth required, whitelist only Much clearer semantics and easier to reason about.
* refactor: simplify isWriteMethod to only check actual API methodsbndw3 days2-33/+7
| | | | | Replace pattern-matching with explicit checks for PublishEvent/PublishBatch. API is small and well-defined - no need for extensible pattern matching.
* feat: add separate read/write allowlists for granular access controlbndw3 days6-67/+263
| | | | | | | | | | | | | | - Split allowed_npubs into allowed_npubs_read and allowed_npubs_write - Write operations: Publish, Delete, Create, Update, Insert, Remove, Set, Put - Read operations: everything else (Query, Subscribe, Get, List, etc.) - Auth interceptor checks appropriate list based on method type - Enables common patterns: - Public relay: only some can write, everyone can read - Private relay: restricted read and write - Open relay: everyone can read and write - Updated config, docs, and comprehensive tests Use cases: "only some can write, everyone can read"
* feat: rename allowed_pubkeys to allowed_npubs with normalizationbndw3 days5-14/+159
| | | | | | | | | | | - Config now accepts npub format only (human-readable) - Automatically converts npubs to hex pubkeys at load time - Updated InterceptorOptions.AllowedPubkeys -> AllowedNpubs - Added validation to reject hex format in config (npub only) - Updated documentation to clarify npub-only config - Added comprehensive tests for npub normalization Config is for humans (npub), internal code uses hex pubkeys.
* fix: optimize SQLite connection pooling for single-writer architecturebndw3 days4-22/+14
| | | | | | | | | | | | | | | | | | | | | Remove misleading max_connections config option and properly configure SQLite connection pooling in the storage layer. Changes: - Set MaxOpenConns(1) for optimal SQLite performance - Set MaxIdleConns(1) to keep connection alive - Set ConnMaxLifetime(0) to never close connection - Remove max_connections and max_lifetime from DatabaseConfig - Update docs to clarify SQLite's single-writer architecture Rationale: SQLite is an embedded database with a single-writer lock. Multiple connections cause lock contention and reduce performance. WAL mode allows concurrent reads from the same connection, making connection pooling unnecessary and counterproductive. This change makes the configuration clearer and ensures optimal SQLite performance by using a single long-lived connection.
* feat: add Prometheus metrics and YAML config file supportbndw3 days6-0/+1677
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ## Metrics Package Comprehensive Prometheus metrics for production observability: Metrics tracked: - Request rate, latency, size per method (histograms) - Active connections and subscriptions (gauges) - Auth success/failure rates (counters) - Rate limit hits (counters) - Storage stats (event count, DB size) - Standard Go runtime metrics Features: - Automatic gRPC instrumentation via interceptors - Low overhead (~300-500ns per request) - Standard Prometheus client - HTTP /metrics endpoint - Grafana dashboard examples ## Config Package YAML configuration file support with environment overrides: Configuration sections: - Server (addresses, timeouts, public URL) - Database (path, connections, lifetime) - Auth (enabled, required, timestamp window, allowed pubkeys) - Rate limiting (per-method and per-user limits) - Metrics (endpoint, namespace) - Logging (level, format, output) - Storage (compaction, retention) Features: - YAML file loading - Environment variable overrides (MUXSTR_<SECTION>_<KEY>) - Sensible defaults - Validation on load - Duration and list parsing - Save/export configuration Both packages include comprehensive README with examples, best practices, and usage patterns. Config tests verify YAML parsing, env overrides, validation, and round-trip serialization.
* feat: implement per-user rate limiting with token bucket algorithmbndw3 days5-0/+1361
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Add comprehensive rate limiting package that works seamlessly with NIP-98 authentication. Features: - Token bucket algorithm (allows bursts, smooth average rate) - Per-pubkey limits for authenticated users - Per-IP limits for unauthenticated users (fallback) - Method-specific overrides (e.g., stricter for PublishEvent) - Per-user custom limits (VIP/admin tiers) - Standard gRPC interceptors (chain after auth) - Automatic cleanup of idle limiters - Statistics tracking (allowed/denied/denial rate) Configuration options: - Default rate limits and burst sizes - Method-specific overrides - User-specific overrides (with method overrides) - Skip methods (health checks, public endpoints) - Skip users (admins, monitoring) - Configurable cleanup intervals Performance: - In-memory (200 bytes per user) - O(1) lookups with sync.RWMutex - ~85ns per rate limit check - Periodic cleanup to free memory Returns gRPC ResourceExhausted (HTTP 429) when limits exceeded. Includes comprehensive tests, benchmarks, and detailed README with usage examples, configuration reference, and security considerations.
* docs: clarify NIP-98 relationship to NIP-42 and write access controlbndw3 days1-4/+60
| | | | | | | | | Explain that the gRPC NIP-98 implementation is effectively NIP-42 for reads (same pattern: authenticate once, stream many events) and adds standardized relay access control for writes (beyond event.sig). Add comparison table showing functional equivalence for streaming reads and the additional benefits for write access control.
* feat: implement NIP-98 HTTP auth for gRPCbndw3 days5-0/+951
| | | | | | | | | | | | | | | | | | | | | | | | | Add comprehensive NIP-98 authentication support following the standard gRPC credentials.PerRPCCredentials pattern. Client-side: - NostrCredentials implements PerRPCCredentials interface - Automatically signs each request with kind 27235 event - Drop-in replacement for OAuth2/JWT in gRPC clients Server-side: - Unary and stream interceptors for validation - Extracts and validates NIP-98 events from Authorization headers - Configurable options (timestamp window, whitelists, skip methods) - Adds authenticated pubkey to request context Security features: - Replay protection via timestamp validation - Optional payload hash verification - Signature verification using schnorr - TLS requirement option Includes comprehensive test coverage and detailed README with usage examples and security considerations.
* refactor: simplify footer text on index pagebndw3 days1-1/+1
|
* refactor: remove PGP signature block from index pagebndw3 days1-8/+0
|
* feat: add muxstr SVG logo to index pagebndw3 days1-11/+16
| | | | | Replace ASCII art banner with SVG logo featuring the muxstr branding. Update page title and background color to match brand identity.
* refactor: rename project from nostr-grpc to muxstrbndw3 days19-33/+33
| | | | | | Update module path from northwest.io/nostr-grpc to northwest.io/muxstr. This includes updating all Go imports, protobuf definitions, generated files, and documentation.
* fix: allow WebSocket upgrades to bypass HTML indexbndw3 days1-2/+3
| | | | | | | | | | WebSocket connections start as GET requests with 'Upgrade: websocket' header. The handler was serving HTML for ALL GET requests, preventing WebSocket upgrades from ever happening. Fix by checking for Upgrade header and only serving HTML/NIP-11 for non-WebSocket GET requests. Now WebSocket connections return status 101 (Switching Protocols) instead of 200 (OK).
* fix: remove duplicate protocol prefix in WebSocket URLbndw3 days1-1/+1
| | | | | | | | | | | The template was hardcoding 'ws://' prefix, but when using --public-url we were already passing 'wss://'. This caused the URL to display as 'ws://wss://domain/'. Fix by: - Removing 'ws://' prefix from template - Always including protocol in the variable (ws:// or wss://) - Also add http:// prefix for local development consistency
* redesign: early web minimalism + cypherpunkbndw3 days1-408/+115
| | | | | | | | | | | | | | | | | | | | | | | | | | | Complete aesthetic reversal - from cyber-brutalist to minimal: AESTHETIC: 1995 web document meets cypherpunks mailing list - Courier New system font (no web fonts) - Black on white for readability - Semantic HTML with minimal CSS - PGP signature blocks (cypherpunk heritage) - Classic underlined blue links - Horizontal rules for section breaks - Looks like a .txt file rendered as HTML PHILOSOPHY: Throwback to when the web was just documents. No animations, no grids, no gradients. Just semantic HTML, monospace type, and information. Fast loading, accessible, timeless. CYPHERPUNK TOUCHES: - PGP signature blocks - Hash fingerprints - Technical language - Cryptographic references - Feels like reading crypto mailing list archives Zero frameworks, zero build tools, maximum signal.
* redesign: cyber-brutalist terminal aestheticbndw3 days1-101/+367
| | | | | | | | | | | | | | | | | | | | | | | | | | Complete visual overhaul with bold conceptual direction: AESTHETIC: Cyber-brutalist terminal interface - JetBrains Mono monospace throughout - Deep black (#0a0e14) with cyan/green accents - ASCII art Nostr logo with glitch animation - Animated grid background (scrolling terminal feel) - Terminal-style status bar with pulse indicators - Protocol cards with scanning line effects - Information-dense but organized layout MOTION & EFFECTS: - Glitching ASCII logo animation - Scanning line on protocol cards - Pulsing status indicators - Animated grid background - Staggered fade-in on page load - Hover effects with glow DIFFERENTIATION: Feels like SSH into a relay node. Unapologetically technical, embracing Nostr's decentralized, cypherpunk ethos. Zero generic design patterns - full commitment to terminal aesthetic.
* feat: add HTML index page for browser viewingbndw3 days2-4/+218
| | | | | | | | | | Add a beautiful HTML landing page when visiting relay in browser: - Shows all three protocol endpoints (gRPC, Connect, WebSocket) - Lists supported NIPs (01, 09, 11) - Displays relay features and info - Responsive design with gradient styling - Serves on GET requests (regular Accept header) - NIP-11 still served for Accept: application/nostr+json