26 KiB
Ginxsom Management System Design
Executive Summary
This document outlines the design for a secure management interface for ginxsom (Blossom media storage server) based on c-relay's proven admin system architecture. The design uses Kind 23456/23457 events with NIP-44 encryption over WebSocket for real-time admin operations.
1. System Architecture
1.1 High-Level Overview
graph TB
Admin[Admin Client] -->|WebSocket| WS[WebSocket Handler]
WS -->|Kind 23456| Auth[Admin Authorization]
Auth -->|Decrypt NIP-44| Decrypt[Command Decryption]
Decrypt -->|Parse JSON Array| Router[Command Router]
Router -->|Route by Command Type| Handlers[Unified Handlers]
Handlers -->|Execute| DB[(Database)]
Handlers -->|Execute| FS[File System]
Handlers -->|Generate Response| Encrypt[NIP-44 Encryption]
Encrypt -->|Kind 23457| WS
WS -->|WebSocket| Admin
style Admin fill:#e1f5ff
style Auth fill:#fff3cd
style Handlers fill:#d4edda
style DB fill:#f8d7da
1.2 Component Architecture
graph LR
subgraph "Admin Interface"
CLI[CLI Tool]
Web[Web Dashboard]
end
subgraph "ginxsom FastCGI Process"
WS[WebSocket Endpoint]
Auth[Authorization Layer]
Router[Command Router]
subgraph "Unified Handlers"
BlobH[Blob Handler]
StorageH[Storage Handler]
ConfigH[Config Handler]
StatsH[Stats Handler]
SystemH[System Handler]
end
DB[(SQLite Database)]
Storage[Blob Storage]
end
CLI -->|WebSocket| WS
Web -->|WebSocket| WS
WS --> Auth
Auth --> Router
Router --> BlobH
Router --> StorageH
Router --> ConfigH
Router --> StatsH
Router --> SystemH
BlobH --> DB
BlobH --> Storage
StorageH --> Storage
ConfigH --> DB
StatsH --> DB
SystemH --> DB
style Auth fill:#fff3cd
style Router fill:#d4edda
1.3 Data Flow for Admin Commands
sequenceDiagram
participant Admin
participant WebSocket
participant Auth
participant Handler
participant Database
Admin->>WebSocket: Kind 23456 Event (NIP-44 encrypted)
WebSocket->>Auth: Verify admin signature
Auth->>Auth: Check pubkey matches admin_pubkey
Auth->>Auth: Verify event signature
Auth->>WebSocket: Authorization OK
WebSocket->>Handler: Decrypt & parse command array
Handler->>Handler: Validate command structure
Handler->>Database: Execute operation
Database-->>Handler: Result
Handler->>Handler: Build response JSON
Handler->>WebSocket: Encrypt response (NIP-44)
WebSocket->>Admin: Kind 23457 Event (encrypted response)
1.4 Integration with Existing Ginxsom
graph TB
subgraph "Existing Ginxsom"
Main[main.c]
BUD04[bud04.c - Mirror]
BUD06[bud06.c - Requirements]
BUD08[bud08.c - NIP-94]
BUD09[bud09.c - Report]
AdminAPI[admin_api.c - Basic Admin]
Validator[request_validator.c]
end
subgraph "New Management System"
AdminWS[admin_websocket.c]
AdminAuth[admin_auth.c]
AdminHandlers[admin_handlers.c]
AdminConfig[admin_config.c]
end
Main -->|Initialize| AdminWS
AdminWS -->|Use| AdminAuth
AdminWS -->|Route to| AdminHandlers
AdminHandlers -->|Query| BUD04
AdminHandlers -->|Query| BUD06
AdminHandlers -->|Query| BUD08
AdminHandlers -->|Query| BUD09
AdminHandlers -->|Update| AdminConfig
AdminAuth -->|Use| Validator
style AdminWS fill:#d4edda
style AdminAuth fill:#fff3cd
style AdminHandlers fill:#e1f5ff
2. Database Schema
2.1 Core Tables
Following c-relay's minimal approach, we need only two tables for key management:
relay_seckey Table
-- Stores relay's private key (used for signing Kind 23457 responses)
CREATE TABLE relay_seckey (
private_key_hex TEXT NOT NULL CHECK (length(private_key_hex) = 64),
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);
Note: This table stores the relay's private key as plain hex (no encryption). The key is used to:
- Sign Kind 23457 response events
- Encrypt responses using NIP-44 (shared secret with admin pubkey)
config Table (Extended)
-- Existing config table, add admin_pubkey entry
INSERT INTO config (key, value, data_type, description, category, requires_restart)
VALUES (
'admin_pubkey',
'<64-char-hex-pubkey>',
'string',
'Public key of authorized admin (hex format)',
'security',
0
);
Note: Admin public key is stored in the config table, not a separate table. Admin private key is NEVER stored anywhere.
2.2 Schema Comparison with c-relay
| c-relay | ginxsom | Purpose |
|---|---|---|
relay_seckey (private_key_hex, created_at) |
relay_seckey (private_key_hex, created_at) |
Relay private key storage |
config table entry for admin_pubkey |
config table entry for admin_pubkey |
Admin authorization |
| No audit log | No audit log | Keep it simple |
| No processed events tracking | No processed events tracking | Stateless processing |
2.3 Key Storage Strategy
Relay Private Key:
- Stored in
relay_seckeytable as plain 64-character hex - Generated on first startup or provided via
--relay-privkeyCLI option - Used for signing Kind 23457 responses and NIP-44 encryption
- Never exposed via API
Admin Public Key:
- Stored in
configtable as plain 64-character hex - Generated on first startup or provided via
--admin-pubkeyCLI option - Used to verify Kind 23456 command signatures
- Can be queried via admin API
Admin Private Key:
- NEVER stored anywhere in the system
- Kept only by the admin in their client/tool
- Used to sign Kind 23456 commands and decrypt Kind 23457 responses
3. API Design
3.1 Command Structure
Following c-relay's pattern, all commands use JSON array format:
["command_name", {"param1": "value1", "param2": "value2"}]
3.2 Event Structure
Kind 23456 - Admin Command Event
{
"kind": 23456,
"pubkey": "<admin-pubkey-hex>",
"created_at": 1234567890,
"tags": [
["p", "<relay-pubkey-hex>"]
],
"content": "<nip44-encrypted-command-array>",
"sig": "<signature>"
}
Content (decrypted):
["blob_list", {"limit": 100, "offset": 0}]
Kind 23457 - Admin Response Event
{
"kind": 23457,
"pubkey": "<relay-pubkey-hex>",
"created_at": 1234567890,
"tags": [
["p", "<admin-pubkey-hex>"],
["e", "<original-command-event-id>"]
],
"content": "<nip44-encrypted-response>",
"sig": "<signature>"
}
Content (decrypted):
{
"success": true,
"data": {
"blobs": [
{"sha256": "abc123...", "size": 1024, "type": "image/png"},
{"sha256": "def456...", "size": 2048, "type": "video/mp4"}
],
"total": 2
}
}
3.3 Command Categories
Blob Operations
blob_list- List blobs with paginationblob_info- Get detailed blob informationblob_delete- Delete blob(s)blob_mirror- Mirror blob from another server
Storage Management
storage_stats- Get storage usage statisticsstorage_quota- Get/set storage quotasstorage_cleanup- Clean up orphaned files
Configuration
config_get- Get configuration value(s)config_set- Set configuration value(s)config_list- List all configurationauth_rules_list- List authentication rulesauth_rules_add- Add authentication ruleauth_rules_remove- Remove authentication rule
Statistics
stats_uploads- Upload statisticsstats_bandwidth- Bandwidth usagestats_storage- Storage usage over timestats_users- User activity statistics
System
system_info- Get system informationsystem_restart- Restart server (graceful)system_backup- Trigger database backupsystem_restore- Restore from backup
3.4 Command Examples
Example 1: List Blobs
// Command (Kind 23456 content, decrypted)
["blob_list", {
"limit": 50,
"offset": 0,
"type": "image/*",
"sort": "created_at",
"order": "desc"
}]
// Response (Kind 23457 content, decrypted)
{
"success": true,
"data": {
"blobs": [
{
"sha256": "abc123...",
"size": 102400,
"type": "image/png",
"created": 1234567890,
"url": "https://blossom.example.com/abc123.png"
}
],
"total": 150,
"limit": 50,
"offset": 0
}
}
Example 2: Delete Blob
// Command
["blob_delete", {
"sha256": "abc123...",
"confirm": true
}]
// Response
{
"success": true,
"data": {
"deleted": true,
"sha256": "abc123...",
"freed_bytes": 102400
}
}
Example 3: Get Storage Stats
// Command
["storage_stats", {}]
// Response
{
"success": true,
"data": {
"total_blobs": 1500,
"total_bytes": 5368709120,
"total_bytes_human": "5.0 GB",
"disk_usage": {
"used": 5368709120,
"available": 94631291904,
"total": 100000000000,
"percent": 5.4
},
"by_type": {
"image/png": {"count": 500, "bytes": 2147483648},
"image/jpeg": {"count": 300, "bytes": 1610612736},
"video/mp4": {"count": 200, "bytes": 1610612736}
}
}
}
Example 4: Set Configuration
// Command
["config_set", {
"max_upload_size": 10485760,
"allowed_mime_types": ["image/*", "video/mp4"]
}]
// Response
{
"success": true,
"data": {
"updated": ["max_upload_size", "allowed_mime_types"],
"requires_restart": false
}
}
3.5 Error Handling
All errors follow consistent format:
{
"success": false,
"error": {
"code": "BLOB_NOT_FOUND",
"message": "Blob with hash abc123... not found",
"details": {
"sha256": "abc123..."
}
}
}
Error Codes:
UNAUTHORIZED- Invalid admin signatureINVALID_COMMAND- Unknown command or malformed structureINVALID_PARAMS- Missing or invalid parametersBLOB_NOT_FOUND- Requested blob doesn't existSTORAGE_FULL- Storage quota exceededDATABASE_ERROR- Database operation failedSYSTEM_ERROR- Internal server error
4. File Structure
4.1 New Files to Create
src/
├── admin_websocket.c # WebSocket endpoint for admin commands
├── admin_websocket.h # WebSocket handler declarations
├── admin_auth.c # Admin authorization (adapted from c-relay)
├── admin_auth.h # Authorization function declarations
├── admin_handlers.c # Unified command handlers
├── admin_handlers.h # Handler function declarations
├── admin_config.c # Configuration management
├── admin_config.h # Config function declarations
└── admin_keys.c # Key generation and storage
admin_keys.h # Key management declarations
include/
└── admin_system.h # Public admin system interface
4.2 Files to Adapt from c-relay
| c-relay File | Purpose | Adaptation for ginxsom |
|---|---|---|
dm_admin.c |
Admin event processing | → admin_websocket.c (WebSocket instead of DM) |
api.c (lines 768-838) |
NIP-44 encryption/response | → admin_handlers.c (response generation) |
config.c (lines 500-583) |
Key storage/retrieval | → admin_keys.c (relay key management) |
main.c (lines 1389-1556) |
CLI argument parsing | → main.c (add admin CLI options) |
4.3 Integration with Existing Files
src/main.c:
- Add CLI options:
--admin-pubkey,--relay-privkey - Initialize admin WebSocket endpoint
- Generate keys on first startup
src/admin_api.c (existing):
- Keep existing basic admin API
- Add WebSocket admin endpoint
- Route Kind 23456 events to new handlers
db/schema.sql:
- Add
relay_seckeytable - Add
admin_pubkeyto config table
5. Implementation Plan
5.1 Phase 1: Foundation (Week 1)
Goal: Set up key management and database schema
Tasks:
- Create
relay_seckeytable in schema - Add
admin_pubkeyto config table - Implement
admin_keys.c:generate_relay_keypair()generate_admin_keypair()store_relay_private_key()load_relay_private_key()get_admin_pubkey()
- Update
main.c:- Add CLI options (
--admin-pubkey,--relay-privkey) - Generate keys on first startup
- Print keys once (like c-relay)
- Add CLI options (
- Test key generation and storage
Deliverables:
- Working key generation
- Keys stored in database
- CLI options functional
5.2 Phase 2: Authorization (Week 2)
Goal: Implement admin event authorization
Tasks:
- Create
admin_auth.c(adapted from c-relay's authorization):verify_admin_event()- Check Kind 23456 signaturecheck_admin_pubkey()- Verify against stored admin_pubkeyverify_relay_target()- Check 'p' tag matches relay pubkey
- Add NIP-44 crypto functions (use existing nostr_core_lib):
decrypt_admin_command()- Decrypt Kind 23456 contentencrypt_admin_response()- Encrypt Kind 23457 content
- Test authorization flow
- Test encryption/decryption
Deliverables:
- Working authorization layer
- NIP-44 encryption functional
- Unit tests for auth
5.3 Phase 3: WebSocket Endpoint (Week 3)
Goal: Create WebSocket handler for admin commands
Tasks:
- Create
admin_websocket.c:- WebSocket endpoint at
/adminor similar - Receive Kind 23456 events
- Route to authorization layer
- Parse command array from decrypted content
- Route to appropriate handler
- Build Kind 23457 response
- Send encrypted response
- WebSocket endpoint at
- Integrate with existing FastCGI WebSocket handling
- Add connection management
- Test WebSocket communication
Deliverables:
- Working WebSocket endpoint
- Event routing functional
- Response generation working
5.4 Phase 4: Command Handlers (Week 4-5)
Goal: Implement unified command handlers
Tasks:
- Create
admin_handlers.cwith unified handler pattern:handle_blob_command()- Blob operationshandle_storage_command()- Storage managementhandle_config_command()- Configurationhandle_stats_command()- Statisticshandle_system_command()- System operations
- Implement each command:
- Blob: list, info, delete, mirror
- Storage: stats, quota, cleanup
- Config: get, set, list, auth_rules
- Stats: uploads, bandwidth, storage, users
- System: info, restart, backup, restore
- Add validation for each command
- Test each command individually
Deliverables:
- All commands implemented
- Validation working
- Integration tests passing
5.5 Phase 5: Testing & Documentation (Week 6)
Goal: Comprehensive testing and documentation
Tasks:
- Create test suite:
- Unit tests for each handler
- Integration tests for full flow
- Security tests for authorization
- Performance tests for WebSocket
- Create admin CLI tool (simple Node.js/Python script):
- Generate Kind 23456 events
- Send via WebSocket
- Decrypt Kind 23457 responses
- Pretty-print results
- Write documentation:
- Admin API reference
- CLI tool usage guide
- Security best practices
- Troubleshooting guide
- Create example scripts
Deliverables:
- Complete test suite
- Working CLI tool
- Full documentation
- Example scripts
5.6 Phase 6: Web Dashboard (Optional, Week 7-8)
Goal: Create web-based admin interface
Tasks:
- Design web UI (React/Vue/Svelte)
- Implement WebSocket client
- Create command forms
- Add real-time updates
- Deploy dashboard
Deliverables:
- Working web dashboard
- User documentation
- Deployment guide
6. Security Considerations
6.1 Key Security
Relay Private Key:
- Stored in database as plain hex (following c-relay pattern)
- Never exposed via API
- Used only for signing responses
- Backed up with database
Admin Private Key:
- NEVER stored on server
- Kept only by admin
- Used to sign commands
- Should be stored securely by admin (password manager, hardware key, etc.)
Admin Public Key:
- Stored in config table
- Used for authorization
- Can be rotated by updating config
6.2 Authorization Flow
- Receive Kind 23456 event
- Verify event signature (nostr_verify_event_signature)
- Check pubkey matches admin_pubkey from config
- Verify 'p' tag targets this relay
- Decrypt content using NIP-44
- Parse and validate command
- Execute command
- Encrypt response using NIP-44
- Sign Kind 23457 response
- Send response
6.3 Attack Mitigation
Replay Attacks:
- Check event timestamp (reject old events)
- Optional: Track processed event IDs (if needed)
Unauthorized Access:
- Strict pubkey verification
- Signature validation
- Relay targeting check
Command Injection:
- Validate all command parameters
- Use parameterized SQL queries
- Sanitize file paths
DoS Protection:
- Rate limit admin commands
- Timeout long-running operations
- Limit response sizes
7. Command Line Interface
7.1 CLI Options (Following c-relay Pattern)
ginxsom [OPTIONS]
Options:
-h, --help Show help message
-v, --version Show version information
-p, --port PORT Override server port
--strict-port Fail if exact port unavailable
-a, --admin-pubkey KEY Override admin public key (hex or npub)
-r, --relay-privkey KEY Override relay private key (hex or nsec)
--debug-level=N Set debug level (0-5)
Examples:
ginxsom # Start server (auto-generate keys on first run)
ginxsom -p 8080 # Start on port 8080
ginxsom -a <npub> # Set admin pubkey
ginxsom -r <nsec> # Set relay privkey
ginxsom --debug-level=3 # Enable info-level debugging
7.2 First Startup Behavior
On first startup (no database exists):
- Generate relay keypair
- Generate admin keypair
- Print keys ONCE to console:
=== Ginxsom First Startup ===
Relay Keys (for server):
Public Key (npub): npub1...
Private Key (nsec): nsec1...
Admin Keys (for you):
Public Key (npub): npub1...
Private Key (nsec): nsec1...
IMPORTANT: Save these keys securely!
The admin private key will NOT be shown again.
The relay private key is stored in the database.
Database created: <relay-pubkey>.db
- Store relay private key in database
- Store admin public key in config
- Start server
7.3 Subsequent Startups
On subsequent startups:
- Find existing database file
- Load relay private key from database
- Load admin public key from config
- Apply CLI overrides if provided
- Start server
8. Comparison with c-relay
8.1 Similarities
| Feature | c-relay | ginxsom |
|---|---|---|
| Event Types | Kind 23456/23457 | Kind 23456/23457 |
| Encryption | NIP-44 | NIP-44 |
| Command Format | JSON arrays | JSON arrays |
| Key Storage | relay_seckey table | relay_seckey table |
| Admin Auth | config table | config table |
| CLI Options | --admin-pubkey, --relay-privkey | --admin-pubkey, --relay-privkey |
| Response Format | Encrypted JSON | Encrypted JSON |
8.2 Differences
| Aspect | c-relay | ginxsom |
|---|---|---|
| Transport | WebSocket (Nostr relay) | WebSocket (FastCGI) |
| Commands | Relay-specific (auth, config, stats) | Blossom-specific (blob, storage, mirror) |
| Database | SQLite (events) | SQLite (blobs + metadata) |
| File Storage | N/A | Blob storage on disk |
| Integration | Standalone relay | FastCGI + nginx |
8.3 Architectural Decisions
Why follow c-relay's pattern?
- Proven in production
- Simple and secure
- No complex key management
- Minimal database schema
- Easy to understand and maintain
What we're NOT doing (from initial design):
- ❌ NIP-17 gift wrap (too complex)
- ❌ Separate admin_keys table (use config)
- ❌ Audit log table (keep it simple)
- ❌ Processed events tracking (stateless)
- ❌ Key encryption before storage (plain hex)
- ❌ Migration strategy (new project)
9. Testing Strategy
9.1 Unit Tests
admin_keys.c:
- Key generation produces valid keys
- Keys can be stored and retrieved
- Invalid keys are rejected
admin_auth.c:
- Valid admin events pass authorization
- Invalid signatures are rejected
- Wrong pubkeys are rejected
- Expired events are rejected
admin_handlers.c:
- Each command handler works correctly
- Invalid parameters are rejected
- Error responses are properly formatted
9.2 Integration Tests
Full Flow:
- Generate admin keypair
- Create Kind 23456 command
- Send via WebSocket
- Verify authorization
- Execute command
- Receive Kind 23457 response
- Decrypt and verify response
Security Tests:
- Unauthorized pubkey rejected
- Invalid signature rejected
- Replay attack prevented
- Command injection prevented
9.3 Performance Tests
- WebSocket connection handling
- Command processing latency
- Concurrent admin operations
- Large response handling
10. Future Enhancements
10.1 Short Term
- Command History: Track admin commands for audit
- Multi-Admin Support: Multiple authorized admin pubkeys
- Role-Based Access: Different permission levels
- Batch Operations: Execute multiple commands in one request
10.2 Long Term
- Web Dashboard: Full-featured web UI
- Monitoring Integration: Prometheus/Grafana metrics
- Backup Automation: Scheduled backups
- Replication: Multi-server blob replication
- Advanced Analytics: Usage patterns, trends, predictions
11. References
11.1 Nostr NIPs
- NIP-01: Basic protocol flow
- NIP-04: Encrypted Direct Messages (deprecated, but reference)
- NIP-19: bech32-encoded entities (npub, nsec)
- NIP-44: Versioned Encryption (used for admin commands)
11.2 Blossom Specifications
- BUD-01: Blob Upload/Download
- BUD-02: Blob Descriptor
- BUD-04: Mirroring
- BUD-06: Upload Requirements
- BUD-08: NIP-94 Integration
- BUD-09: Blob Reporting
11.3 c-relay Source Files
c-relay/src/dm_admin.c- Admin event processingc-relay/src/api.c- NIP-44 encryptionc-relay/src/config.c- Key storagec-relay/src/main.c- CLI optionsc-relay/src/sql_schema.h- Database schema
12. Appendix
12.1 Example Admin CLI Tool (Python)
#!/usr/bin/env python3
"""
Ginxsom Admin CLI Tool
Sends admin commands to ginxsom server via WebSocket
"""
import asyncio
import websockets
import json
from nostr_sdk import Keys, Event, EventBuilder, Kind
class GinxsomAdmin:
def __init__(self, server_url, admin_nsec, relay_npub):
self.server_url = server_url
self.admin_keys = Keys.parse(admin_nsec)
self.relay_pubkey = Keys.parse(relay_npub).public_key()
async def send_command(self, command, params):
"""Send admin command and wait for response"""
# Build command array
command_array = [command, params]
# Encrypt with NIP-44
encrypted = self.admin_keys.nip44_encrypt(
self.relay_pubkey,
json.dumps(command_array)
)
# Build Kind 23456 event
event = EventBuilder(
Kind(23456),
encrypted,
[["p", str(self.relay_pubkey)]]
).to_event(self.admin_keys)
# Send via WebSocket
async with websockets.connect(self.server_url) as ws:
await ws.send(json.dumps(event.as_json()))
# Wait for Kind 23457 response
response = await ws.recv()
response_event = Event.from_json(response)
# Decrypt response
decrypted = self.admin_keys.nip44_decrypt(
self.relay_pubkey,
response_event.content()
)
return json.loads(decrypted)
# Usage
async def main():
admin = GinxsomAdmin(
"ws://localhost:8080/admin",
"nsec1...", # Admin private key
"npub1..." # Relay public key
)
# List blobs
result = await admin.send_command("blob_list", {
"limit": 10,
"offset": 0
})
print(json.dumps(result, indent=2))
if __name__ == "__main__":
asyncio.run(main())
12.2 Database Schema SQL
-- Add to db/schema.sql
-- Relay Private Key Storage
CREATE TABLE relay_seckey (
private_key_hex TEXT NOT NULL CHECK (length(private_key_hex) = 64),
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);
-- Admin Public Key (add to config table)
INSERT INTO config (key, value, data_type, description, category, requires_restart)
VALUES (
'admin_pubkey',
'', -- Set during first startup
'string',
'Public key of authorized admin (64-char hex)',
'security',
0
);
-- Relay Public Key (add to config table)
INSERT INTO config (key, value, data_type, description, category, requires_restart)
VALUES (
'relay_pubkey',
'', -- Set during first startup
'string',
'Public key of this relay (64-char hex)',
'server',
0
);
12.3 Makefile Updates
# Add to Makefile
# Admin system objects
ADMIN_OBJS = build/admin_websocket.o \
build/admin_auth.o \
build/admin_handlers.o \
build/admin_config.o \
build/admin_keys.o
# Update main target
build/ginxsom-fcgi: $(OBJS) $(ADMIN_OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Admin system rules
build/admin_websocket.o: src/admin_websocket.c
$(CC) $(CFLAGS) -c $< -o $@
build/admin_auth.o: src/admin_auth.c
$(CC) $(CFLAGS) -c $< -o $@
build/admin_handlers.o: src/admin_handlers.c
$(CC) $(CFLAGS) -c $< -o $@
build/admin_config.o: src/admin_config.c
$(CC) $(CFLAGS) -c $< -o $@
build/admin_keys.o: src/admin_keys.c
$(CC) $(CFLAGS) -c $< -o $@
Document Version: 2.0
Last Updated: 2025-01-16
Status: Ready for Implementation