- Add embedded Svelte admin web UI for process monitoring and control
- Implement NIP-98 HTTP authentication middleware for secure API access
- Add binary update/rollback system with versioned symlinks
- Add admin API endpoints: status, config, binaries, update, restart, rollback
- Update CI workflow to build all binaries for AMD64 and ARM64 architectures
- Add launcher-web Makefile target for building admin UI separately
Files modified:
- .gitea/workflows/go.yml: Build all binaries and launcher admin UI
- Makefile: Add launcher-web and orly-launcher-no-web targets
- cmd/orly-launcher/auth.go: NIP-98 authentication middleware
- cmd/orly-launcher/config.go: Admin UI configuration (port, owners)
- cmd/orly-launcher/main.go: Start admin server, updated help text
- cmd/orly-launcher/server.go: Admin HTTP server with API endpoints
- cmd/orly-launcher/supervisor.go: GetProcessStatuses, RestartAll methods
- cmd/orly-launcher/updater.go: Binary version management with symlinks
- cmd/orly-launcher/web.go: Embedded admin UI serving
- cmd/orly-launcher/web/: Svelte admin UI (dashboard, config, update pages)
- pkg/version/version: Bump to v0.55.11
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Add O(1) direct lookup for parameterized replaceable events (kinds 30000-39999)
- Reduces query time from 10+ seconds to <1ms for kind+author+d-tag queries
- Optimizes WouldReplaceEvent() for faster event replacement checks
Fixes#29
## Changes
- `pkg/database/query-addressable.go` - New file with fast path lookup logic
- `pkg/database/query-addressable_test.go` - Tests for the optimization
- `pkg/database/save-event.go` - Writes AddressableEvent index, optimizes WouldReplaceEvent
- `pkg/database/query-events.go` - Integrates fast path at start of QueryEventsWithOptions
- `pkg/database/get-indexes-for-event.go` - Minor refactor (variable rename)
## Test plan
- [x] All existing database tests pass
- [x] New tests added for addressable event queries
- [x] Tests cover: valid/invalid query patterns, not found case, event replacement
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: woikos <woikos@users.noreply.github.com>
Reviewed-on: https://git.nostrdev.com/mleku/next.orly.dev/pulls/30
Use eventenvelope.NewResultWith() to properly format EVENT messages
with subscription ID, matching the expected format: ["EVENT", sub_id, event]
Previously SendEvent was sending just the raw event which caused JSON
parsing errors on the receiving side.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add have_ids, need_ids, complete fields to NegOpenResponse proto
- Fix ID encoding in server: decode hex strings to binary before sending
- Fix event sending order: send NEG-MSG response before EVENT envelopes
- Client now waits for expected events after reconciliation completes
- Store received events from peer in sync client message loop
This enables bidirectional event sync via NIP-77 negentropy. The server
can now push events it has that the client needs, in addition to the
client pushing events to the server.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Implement sendEventsForIDs to actually send events via EVENT envelopes
- Previously this was a TODO stub that only logged "would send"
- Server now fetches and sends identified "have" events to clients
Files modified:
- pkg/version/version: Bump to v0.55.6
- app/handle-negentropy.go: Implement sendEventsForIDs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add MaxEvents config option to negentropy manager
- Increase default sync limit from 100,000 to 1,000,000 events
- Ensures full database sync for relays with 200k+ events
Files modified:
- pkg/sync/negentropy/manager.go: Add MaxEvents config, use configurable limit
- pkg/sync/negentropy/server/service.go: Increase hardcoded limit to 1M
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds ORLY_SYNC_NEGENTROPY_FILTER env var for specifying a JSON-encoded
nostr filter for selective sync. The filter is used both when building
local storage for comparison and in the NEG-OPEN message sent to peers.
Example usage:
ORLY_SYNC_NEGENTROPY_FILTER='{"kinds":[1,6,7,30023]}'
ORLY_SYNC_NEGENTROPY_FILTER='{"authors":["abc123..."],"since":1700000000}'
This allows syncing only specific event types between relays instead of
the full event set.
Files modified:
- cmd/orly-sync-negentropy/config.go: Add FilterJSON config and parsing
- cmd/orly-sync-negentropy/main.go: Pass filter to manager config
- pkg/sync/negentropy/manager.go: Add Filter to Config, use in buildStorage and NEG-OPEN
- pkg/version/version: Bump to v0.55.3
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ACL: Mark service ready immediately after gRPC server starts
- ACL: Run Configure() in background goroutine (follow list loading)
- Launcher: Actually call gRPC Ready() endpoint instead of just TCP port check
- Launcher: Increase ACL ready timeout from 30s to 120s as fallback
This fixes the issue where relay would timeout waiting for ACL because
the launcher only checked if the TCP port was open, but the ACL service's
Ready() was returning false until Configure() completed (which takes
minutes for large follow lists).
Now ACL marks itself ready immediately so the relay can start while
follow lists continue loading in the background.
Files modified:
- cmd/orly-acl/main.go: Restructure startup to be async
- cmd/orly-launcher/config.go: Increase ACL timeout to 120s
- cmd/orly-launcher/supervisor.go: Add gRPC Ready() check
- pkg/version/version: Bump to v0.55.2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove debug 100 iteration limit from GetSerialsByRange
The limit was a debug safety check that should have been removed.
This was capping all range queries to 100 results, causing negentropy
sync to only see 100 events instead of all events.
- Fix ACL service startup to listen immediately before Configure
The ACL server now starts the gRPC listener immediately after database
ready, then runs Configure in the background. This prevents the
launcher from timing out while waiting for ACL to load follow lists.
- Add SetReady method to ACL service for proper ready signaling
Files modified:
- pkg/database/get-serials-by-range.go: Remove debug iteration limit
- cmd/orly-acl/main.go: Start gRPC server before Configure
- cmd/orly-acl/service.go: Add ready field and SetReady method
- pkg/version/version: Bump to v0.55.1
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add four independent gRPC sync service binaries:
- orly-sync-distributed: Serial-based peer-to-peer sync
- orly-sync-cluster: Cluster replication with persistent state
- orly-sync-relaygroup: Relay group config discovery (Kind 39105)
- orly-sync-negentropy: NIP-77 efficient set reconciliation
- Implement NIP-77 negentropy protocol for relay-to-relay sync
- Add push-based event transfer for negentropy sync
- Add client-facing NIP-77 WebSocket support (NEG-OPEN/MSG/CLOSE)
- Add deployment scripts with symlink-based versioning
- Add Makefile targets for quick deployment and rollback
- Refactor existing sync code into modular service packages
- Add proto definitions for all sync services
- Integrate negentropy service into orly-launcher supervisor
- Advertise NIP-77 support in relay info document
Files modified:
- Makefile: Add sync service build targets and deployment commands
- README.md: Document NIP-77 support
- app/config/config.go: Add negentropy configuration options
- app/handle-message.go: Route NEG-* envelopes to negentropy handler
- app/handle-negentropy.go: New file for NIP-77 WebSocket handling
- app/handle-relayinfo.go: Advertise NIP-77 support
- cmd/orly-launcher/: Add negentropy service management
- cmd/orly-sync-*/: New service binaries
- main.go: Add negentropy gRPC client initialization
- pkg/proto/orlysync/: New proto definitions and generated code
- pkg/sync/*/: Refactored sync packages with gRPC services
- scripts/deploy-orly.sh: New symlink-based deployment script
- scripts/build-and-deploy.sh: New build and deploy workflow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change Follows, Managed, and Curating ACL structs to use database.Database
interface instead of concrete *database.D type
- Add type assertions for ManagedACL and CuratingACL initialization since
they require Badger-specific APIs
- Log warning when running managed/curating ACL with non-Badger backend
- Update managed_minimal_test.go to use new field name
Files modified:
- pkg/acl/follows.go: Use db database.Database field, replace f.D with f.db
- pkg/acl/managed.go: Use db database.Database with type assertion for ManagedACL
- pkg/acl/curating.go: Use db database.Database with type assertion for CuratingACL
- pkg/acl/managed_minimal_test.go: Update test to use db field
- pkg/version/version: Bump to v0.53.1
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add query semaphore limiting concurrent database queries to 3
- Reuse single iterator in FetchEventsBySerials instead of one per serial
- Disable expensive e-tag fallback lookup in deletion processing
- Add empty ID validation in access tracker loop
- Reduce log spam from GetSerialById empty ID errors
These changes reduce memory usage from ~5GB to ~150MB under load by
limiting concurrent Badger iterators which consume significant memory.
Files modified:
- pkg/database/database.go: Add query semaphore with acquire/release
- pkg/database/fetch-events-by-serials.go: Reuse iterator for sev lookups
- pkg/database/query-events.go: Add semaphore, disable e-tag fallback
- pkg/database/get-serial-by-id.go: Don't log empty ID errors
- app/handle-req.go: Validate event ID before GetSerialById
- pkg/version/version: Bump to v0.52.17
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previous: 10MB read + 10MB write = 20MB per connection
New: 64KB read + 64KB write = 128KB per connection
With 100 connections:
- Before: 2GB just for buffers
- After: 12.5MB for buffers
The read limit (SetReadLimit) still allows 10MB messages, but the
internal buffers don't need to be that large.
Files modified:
- app/handle-websocket.go: Reduce buffer sizes
- pkg/version/version: Bump to v0.52.12
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ORLY_QUERY_RESULT_LIMIT config (default 256)
- Enforce limit on each REQ filter before database query
- Caps client limit if it exceeds server limit
- Applies server limit if client sends no limit
This prevents unbounded queries from loading millions of events
into memory and causing OOM kills.
Files modified:
- app/config/config.go: Add QueryResultLimit config
- app/handle-req.go: Enforce limit on each filter
- pkg/version/version: Bump to v0.52.11
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ORLY_MAX_CONN_PER_IP config option (default 25, hard limit 40)
- Track connections per IP in Server struct
- Reject new connections with HTTP 429 when limit exceeded
- Properly decrement count when connections close
This prevents a single IP from opening unlimited connections and
exhausting server memory/goroutines.
Files modified:
- app/config/config.go: Add MaxConnectionsPerIP config
- app/server.go: Add connPerIP tracking map
- app/main.go: Initialize connPerIP map
- app/handle-websocket.go: Implement per-IP limit check
- pkg/version/version: Bump to v0.52.10
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Disable goroutine that deleted expired events during query processing
- This was causing "assignment to entry in nil map" Badger panics under load
- Expired events will remain until proper background cleanup is implemented
- Added TODO comments explaining the issue and fix needed
Files modified:
- pkg/database/query-events.go: Disable inline expired event deletion
- pkg/version/version: Bump to v0.52.9
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change ORLY_GC_ENABLED default from true to false
- Mark GC as EXPERIMENTAL in config usage string
- Add detailed TODOs documenting Badger race condition issues
- GC triggers "assignment to entry in nil map" panics under concurrent load
Files modified:
- app/config/config.go: Default GC to false, add TODO comments
- pkg/storage/gc.go: Add detailed implementation TODOs
- pkg/version/version: Bump to v0.52.8
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change default event query window from 30 days to 5 years in web UI
- Remove 6-month fallback retry logic (no longer needed with larger window)
- Simplifies fetchAllEvents function in nostr.js
Files modified:
- app/web/src/nostr.js: Extended time window from 30 days to 5 years
- app/web/dist/bundle.js: Rebuilt with changes
- pkg/version/version: Bump to v0.52.7
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add port 7777 to list of known relay ports in config.js
- Prevents false-positive standalone mode detection
Files modified:
- app/web/src/config.js: Add 7777 to relay port list
- pkg/version/version: Bump to v0.52.6
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add post-query filtering to return only latest version per (pubkey, kind, d-tag)
- Delete older versions on save for kinds 30000-39999 in Neo4j backend
- QueryAllVersions bypasses filtering for recovery UI compatibility
- Badger continues to keep old versions (filtered at query time)
Files modified:
- pkg/neo4j/query-events.go: Add replaceable event filtering logic
- pkg/neo4j/save-event.go: Add deleteOlderParameterizedReplaceable helper
- pkg/version/version: Bump to v0.52.5
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add standalone mode for dashboard to connect to any ORLY relay
- Implement relay switcher dropdown in header for standalone mode
- Add mobile drawer sidebar at 640px breakpoint with hamburger menu
- Add dedicated fallback pool for profile/relay list/contact list fetches
- Fix relay URL display to show host instead of NIP-11 name
- Add filter validation and defensive checks in event fetching
- Auto-expand search window from 30 days to 6 months on few results
- Add CORS support for API endpoints with configurable origins
- Fetch user relay list (NIP-65 kind 10002) and contact list on login
- Fix light mode user name color visibility in header
Files modified:
- app/config/config.go: Add CORS configuration options
- app/server.go: Add CORS middleware for API endpoints
- app/handle-relayinfo.go: Include CORS in relay info
- app/web/src/config.js: New config module for standalone mode
- app/web/src/stores.js: Add relay URL and standalone mode stores
- app/web/src/nostr.js: Add fallback pool, filter validation, relay/contact fetch
- app/web/src/Header.svelte: Relay dropdown, mobile menu, static indicator
- app/web/src/Sidebar.svelte: Mobile drawer mode with overlay
- app/web/src/App.svelte: Relay switching, mobile menu state, NIP-65 fetch
- app/web/src/api.js: Use configurable base URL
- app/web/src/constants.js: Add fallback relays, dynamic relay URLs
- cmd/dashboard-server/main.go: New standalone dashboard server
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change .kind-number and .delete-target chips to use white text
- Improves contrast on colored backgrounds (cyan/red) in light mode
Files modified:
- app/web/src/EventsView.svelte: Fixed chip text colors
- app/web/dist/: Rebuilt web UI bundle
- pkg/version/version: Bump to v0.52.1
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Improved Cashu token handling and validation
- Better error messages for token verification
- Version bump to v0.51.0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add geteventsforpubkey API method for viewing user events with pagination
- Add deleteeventsforpubkey API method to purge blacklisted user events
- Add clickable user detail view in curation UI showing all events
- Add event content expansion/truncation for long content
- Add kind name display for common Nostr event types
- Implement safety check requiring blacklist before event deletion
Files modified:
- app/handle-nip86-curating.go: Add event fetch/delete handlers
- pkg/database/curating-acl.go: Add GetEventsForPubkey, DeleteEventsForPubkey
- app/web/src/CurationView.svelte: Add user detail view with event listing
- pkg/version/version: Bump to v0.50.0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix countEventsForPubkey to use SHA256 hash of pubkey (first 8 bytes)
matching the PubHash type used in the Pubkey index
- Fix UI to use event_count field instead of total_events
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ScanAllPubkeys method to scan SerialPubkey index for all pubkeys
- Count events for each pubkey using the Pubkey index
- Store event counts in CURATING_ACL_EVENT_COUNT_ prefix
- Add NIP-86 "scanpubkeys" API endpoint to trigger the scan
This allows the curation UI to show all existing users in the unclassified
list, even if they had events before curating mode was enabled.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change OPTIONAL MATCH to EXISTS subquery for tag filtering in Neo4j
- OPTIONAL MATCH returned rows even when tags didn't match (NULL values)
- EXISTS subquery correctly requires matching tags to exist
- Strip "#" prefix from filter tag types before matching
- Filters use "#d", "#p", "#e" but events store tags without prefix
- Add trace-level logging for Neo4j query debugging
- Add comprehensive tests for Neo4j query builder
- Clean up temporary debug logging from handle-req.go
Files modified:
- pkg/neo4j/query-events.go: Fix tag filtering with EXISTS subquery
- pkg/neo4j/query-events_test.go: Add query builder tests
- app/handle-req.go: Remove debug logging
- pkg/version/version: Bump to v0.49.2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix zero-value timestamp filter bug: since/until with value 0 were
being added as WHERE clauses, causing queries to match no events
- Fix event parsing: use direct slice assignment instead of copy() on
nil slices for ID, Pubkey, and Sig fields
Files modified:
- pkg/neo4j/query-events.go: Fix buildCypherQuery and parseEventsFromResult
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix fetchEvents() discarding IndexedDB cached events instead of merging with relay results
- Add mergeAndDeduplicateEvents() helper to combine and dedupe events by ID
- Add ORLY_BLOSSOM_ENABLED config option to disable Blossom server
- Make fetch-kinds.js fall back to existing eventKinds.js when network unavailable
Files modified:
- app/web/src/nostr.js: Fix event caching, add merge helper
- app/web/scripts/fetch-kinds.js: Add fallback for network failures
- app/config/config.go: Add BlossomEnabled config field
- app/main.go: Check BlossomEnabled before initializing Blossom server
- pkg/version/version: Bump to v0.48.13
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove createDefaultProfile() function from nostr.js that auto-created
placeholder profiles for new users - profiles should not be auto-generated
- Add auth-required configuration caution section to CLAUDE.md documenting
risks of enabling NIP-42 auth on production relays
Files modified:
- CLAUDE.md: Added auth-required configuration section
- app/web/src/nostr.js: Removed createDefaultProfile and auto-profile logic
- app/web/dist/bundle.js: Rebuilt with changes
- pkg/version/version: v0.48.11
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add progressive throttle feature for follows ACL mode, allowing
non-followed users to write with increasing delay instead of blocking
- Delay increases linearly per event (default 200ms) and decays at 1:1
ratio with elapsed time, capping at configurable max (default 60s)
- Track both IP and pubkey independently to prevent evasion
- Add periodic cleanup to remove fully-decayed throttle entries
- Fix BBolt serial resolver to return proper errors when buckets or
entries are not found
Files modified:
- app/config/config.go: Add ORLY_FOLLOWS_THROTTLE_* env vars
- app/handle-event.go: Apply throttle delay before event processing
- app/listener.go: Add getFollowsThrottleDelay helper method
- pkg/acl/follows.go: Integrate throttle with follows ACL
- pkg/acl/follows_throttle.go: New progressive throttle implementation
- pkg/bbolt/save-event.go: Return errors from serial lookups
- pkg/version/version: Bump to v0.48.10
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add sanity bounds to prevent memory exhaustion when decoding corrupt
events with garbage varint values. Previously, corrupt data could cause
massive allocations (e.g., make([]byte, 2^60)) leading to OOM crashes.
- Add MaxTagsPerEvent (10000), MaxTagElements (100), MaxContentLength (10MB),
MaxTagElementLength (1MB) limits
- Return sentinel errors for corrupt data instead of logging
- Silently skip corrupt events (caller handles gracefully)
This fixes crash loops on archive.orly.dev where OOM during writes
left corrupt events in bbolt database.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add README.md table of contents for easier navigation
- Add Curation ACL documentation section to README.md
- Create detailed Curation Mode Guide (docs/CURATION_MODE_GUIDE.md)
- Fix OOM during BBolt index building by closing temp file before build
- Add GC calls before index building to reclaim batch buffer memory
- Improve import-export.go with processJSONLEventsReturningCount
- Add policy-aware import path for sync operations
Files modified:
- README.md: Added TOC and curation ACL documentation
- docs/CURATION_MODE_GUIDE.md: New comprehensive curation mode guide
- pkg/bbolt/import-export.go: Memory-safe import with deferred cleanup
- pkg/bbolt/import-minimal.go: Added GC before index build
- pkg/version/version: Bump to v0.48.8
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Process events in 200k chunks instead of loading all at once
- Write indexes to disk after each chunk, then free memory
- Call debug.FreeOSMemory() between chunks to release memory to OS
- Memory usage now ~150-200MB per chunk instead of 5GB+
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add import-export.go with full JSONL import support for bbolt
- Remove Import/Export/ImportEventsFromReader stubs from stubs.go
- Includes batched write flush after import completion
- Progress logging every 5 seconds during import
Files modified:
- pkg/bbolt/import-export.go: New file with import functionality
- pkg/bbolt/stubs.go: Remove implemented stubs
- pkg/version/version: v0.48.0 -> v0.48.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>