@ -48,6 +48,8 @@ See [docs/IPC_SYNC_SERVICES.md](./docs/IPC_SYNC_SERVICES.md) for detailed API do
@@ -48,6 +48,8 @@ See [docs/IPC_SYNC_SERVICES.md](./docs/IPC_SYNC_SERVICES.md) for detailed API do
- [Building with Web UI](#building-with-web-ui)
- [Core Features](#core-features)
- [Web UI](#web-ui)
- [Event Import](#event-import)
- [Event Streaming and Synchronization](#event-streaming-and-synchronization)
@ -122,7 +124,7 @@ ORLY is a standard Go application that can be built using the Go toolchain.
@@ -122,7 +124,7 @@ ORLY is a standard Go application that can be built using the Go toolchain.
- Go 1.25.3 or later
- Git
- For web UI: [Bun](https://bun.sh/) JavaScript runtime
- For web UI: Node.js and npm (JavaScript runtime and package manager)
### Basic Build
@ -141,8 +143,8 @@ To build with the embedded web interface:
@@ -141,8 +143,8 @@ To build with the embedded web interface:
```bash
# Build the Svelte web application
cd app/web
bun install
bun run build
npm install
npm run build
# Build the Go binary from project root
cd ../../
@ -156,7 +158,7 @@ The recommended way to build and embed the web UI is using the provided script:
@@ -156,7 +158,7 @@ The recommended way to build and embed the web UI is using the provided script:
```
This script will:
- Build the Svelte app in `app/web` to `app/web/dist` using Bun (preferred) or fall back to npm/yarn/pnpm
- Build the Svelte app in `app/web` to `app/web/dist` using npm (or fall back to yarn/pnpm if npm is not available)
- Run `go install` from the repository root so the binary picks up the new embedded assets
- Automatically detect and use the best available JavaScript package manager
@ -167,8 +169,8 @@ For manual builds, you can also use:
@@ -167,8 +169,8 @@ For manual builds, you can also use:
# build.sh
echo "Building Svelte app..."
cd app/web
bun install
bun run build
npm install
npm run build
echo "Building Go binary..."
cd ../../
@ -210,8 +212,8 @@ For development with hot-reloading, ORLY can proxy web requests to a local dev s
@@ -210,8 +212,8 @@ For development with hot-reloading, ORLY can proxy web requests to a local dev s
```bash
cd app/web
bun install
bun run dev
npm install
npm run dev
```
Note the port sirv is listening on (e.g., `http://localhost:8080`).
@ -244,7 +246,7 @@ Example with the relay on port 3334 and sirv on port 8080:
@@ -244,7 +246,7 @@ Example with the relay on port 3334 and sirv on port 8080:
If you only want to disable the embedded web UI (without proxying to a dev server), just set `ORLY_WEB_DISABLE=true` without setting `ORLY_WEB_DEV_PROXY_URL`. The relay will return 404 for web UI requests while still handling WebSocket and API requests.
### Event Import
ORLY provides flexible event import capabilities for bulk data migration and synchronization. The import system supports both single files and entire directories, with efficient disk-based buffering for large datasets.
#### Features
- **Large File Support**: Handles multi-gigabyte files efficiently with disk buffering
- **Folder Import**: Import entire directories of JSONL files in one operation
- **Localhost Authentication**: Automatic authentication for localhost requests using `$NOSTR_PRIVATE_KEY`
- **Progress Tracking**: Real-time progress logging for long-running imports
- **Error Handling**: Continues processing even if individual events fail
#### HTTP API Import
Import events via the `/api/import` endpoint:
**Single File Upload:**
```bash
# Using multipart form data
curl -X POST http://localhost:3334/api/import \
-H "Authorization: Nostr <base64_nip98_auth>" \
-F "file=@events.jsonl"
# Using raw body
curl -X POST http://localhost:3334/api/import \
-H "Authorization: Nostr <base64_nip98_auth>" \
-H "Content-Type: application/x-ndjson" \
--data-binary @events.jsonl
```
**Folder Import:**
Import all `.jsonl` files from a directory:
```bash
curl -X POST "http://localhost:3334/api/import?folder=../scripts/exports" \
-H "Authorization: Nostr <base64_nip98_auth>"
```
The folder import will:
- Process all `.jsonl` files in the specified directory
- Return a summary with results for each file
- Continue processing even if individual files fail
**Localhost Authentication:**
For localhost requests, you can use the `$NOSTR_PRIVATE_KEY` environment variable instead of NIP-98 authentication:
**Security:** Localhost authentication only works for requests from `127.0.0.1`, `::1`, or `localhost`. Folder imports are restricted to allowed directories (e.g., `../scripts/exports`) for security.
#### Command-Line Import
For direct database access, use the `orly db import` command:
```bash
# Import a single file directly to the database
orly db import events.jsonl
# Or specify with flag
orly db import --file /path/to/large-export.jsonl
```
This bypasses the HTTP API and is faster for very large imports when you have direct database access.
#### Import Format
Events must be in JSONL (JSON Lines) format - one JSON event per line:
ORLY includes powerful event streaming capabilities to forward events from a local relay to remote relays, supporting both standard Nostr protocol and efficient negentropy-based synchronization.
#### Features
- **WebSocket Streaming**: Stream events to any Nostr-compatible relay
- **Negentropy Support**: Efficient set reconciliation using NIP-77 protocol
- **Batch Processing**: Processes events in batches for optimal performance
- **Progress Tracking**: Real-time logging of streaming progress
curl -X POST "http://localhost:3334/api/stream-to-relay?target=wss://remote-relay.com"
```
**How It Works:**
1. The endpoint accepts the request and returns immediately with a status message
2. Streaming happens asynchronously in the background
3. All events from the local database are queried and streamed to the target relay
4. Events are sent in batches using standard Nostr EVENT messages
5. Progress is logged to the relay's log output
**Example Workflow:**
```bash
# 1. Import events to local relay
curl -X POST http://localhost:3334/api/import \
-F "file=@book-events.jsonl"
# 2. Stream events to remote relay
curl -X POST "http://localhost:3334/api/stream-to-relay?target=wss://orly-relay.imwald.eu" \
-H "Authorization: Nostr <base64_nip98_auth>"
```
#### Negentropy Synchronization
For more efficient synchronization, you can use negentropy (NIP-77) set reconciliation:
```bash
curl -X POST "http://localhost:3334/api/stream-to-relay?target=wss://remote-relay.com&use_negentropy=true" \
-H "Authorization: Nostr <base64_nip98_auth>"
```
**Note:** Full negentropy support requires the target relay to be configured as a peer in the sync manager. For ad-hoc streaming, use the standard EVENT streaming method.
#### Use Cases
- **Data Migration**: Move events from one relay to another
- **Backup Synchronization**: Keep multiple relays in sync
- **Local to Remote**: Transfer events from a local development relay to production
- **Relay Consolidation**: Merge events from multiple sources
### Sprocket Event Processing
ORLY includes a powerful sprocket system for external event processing scripts. Sprocket scripts enable custom filtering, validation, and processing logic for Nostr events before storage.
log.Printf("Connected to remote relay: %s",targetURL)
// Query all events from local database
f:=filter.New()// Empty filter = all events
events,err:=s.DB.QueryEvents(ctx,f)
iferr!=nil{
returnfmt.Errorf("failed to query events: %w",err)
}
// Stream events in batches to avoid memory exhaustion
// Query events in chunks using timestamp-based pagination
constbatchLimit=1000// Query 1000 events at a time
varsince*timestamp.T
vartotalSentint64
for{
// Create filter for this batch
f:=filter.New()
ifsince!=nil{
f.Since=since
}
limit:=uint(batchLimit)
f.Limit=&limit
log.Printf("Streaming %d events to %s",len(events),targetURL)
// Query batch of events
events,err:=s.DB.QueryEvents(ctx,f)
iferr!=nil{
returnfmt.Errorf("failed to query events: %w",err)
}
// Stream events in batches
batchSize:=100
sent:=0
fori:=0;i<len(events);i+=batchSize{
end:=i+batchSize
ifend>len(events){
end=len(events)
iflen(events)==0{
// No more events
break
}
forj:=i;j<end;j++{
ev:=events[j]
// Stream this batch - ensure all events are freed
processedCount:=0
fori,ev:=rangeevents{
ifev==nil{
continue
}
// Capture timestamp before freeing
createdAt:=ev.CreatedAt
// Send EVENT message: ["EVENT", event]
eventMsg:=[]interface{}{"EVENT",ev}
iferr:=conn.WriteJSON(eventMsg);err!=nil{
// Free this event and all remaining events in the batch
ev.Free()
forj:=i+1;j<len(events);j++{
ifevents[j]!=nil{
events[j].Free()
}
}
returnfmt.Errorf("failed to send event: %w",err)
}
sent++
ifsent%1000==0{
log.Printf("Streamed %d/%d events to %s",sent,len(events),targetURL)
// Free the event to return it to the pool
ev.Free()
processedCount++
totalSent++
iftotalSent%1000==0{
log.Printf("Streamed %d events to %s",totalSent,targetURL)
}
// Update since timestamp for next batch (use created_at + 1 to avoid duplicates)
since=timestamp.FromUnix(createdAt+1)
}
// If we got fewer events than the limit, we've reached the end
iflen(events)<batchLimit{
break
}
// Small delay between batches to avoid overwhelming the remote relay
time.Sleep(100*time.Millisecond)
}
log.Printf("Successfully streamed %d events to %s",sent,targetURL)
log.Printf("Successfully streamed %d events to %s",totalSent,targetURL)
returnnil
}
@ -1492,7 +1538,7 @@ func (s *Server) streamWithNegentropy(ctx context.Context, targetURL string) (in
@@ -1492,7 +1538,7 @@ func (s *Server) streamWithNegentropy(ctx context.Context, targetURL string) (in
// 2. Creating negentropy instance
// 3. Performing NIP-77 protocol exchange
// 4. Fetching and pushing events
// This is a placeholder - the sync manager's negentropy functionality
// should be used via the existing sync infrastructure
return0,fmt.Errorf("negentropy sync via HTTP endpoint not yet fully implemented. Use the sync manager's peer configuration or implement direct negentropy protocol")