# 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 ```sql -- 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) ```sql -- 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_seckey` table as plain 64-character hex - Generated on first startup or provided via `--relay-privkey` CLI option - Used for signing Kind 23457 responses and NIP-44 encryption - Never exposed via API **Admin Public Key**: - Stored in `config` table as plain 64-character hex - Generated on first startup or provided via `--admin-pubkey` CLI 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: ```json ["command_name", {"param1": "value1", "param2": "value2"}] ``` ### 3.2 Event Structure #### Kind 23456 - Admin Command Event ```json { "kind": 23456, "pubkey": "", "created_at": 1234567890, "tags": [ ["p", ""] ], "content": "", "sig": "" } ``` **Content (decrypted)**: ```json ["blob_list", {"limit": 100, "offset": 0}] ``` #### Kind 23457 - Admin Response Event ```json { "kind": 23457, "pubkey": "", "created_at": 1234567890, "tags": [ ["p", ""], ["e", ""] ], "content": "", "sig": "" } ``` **Content (decrypted)**: ```json { "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 pagination - `blob_info` - Get detailed blob information - `blob_delete` - Delete blob(s) - `blob_mirror` - Mirror blob from another server #### Storage Management - `storage_stats` - Get storage usage statistics - `storage_quota` - Get/set storage quotas - `storage_cleanup` - Clean up orphaned files #### Configuration - `config_get` - Get configuration value(s) - `config_set` - Set configuration value(s) - `config_list` - List all configuration - `auth_rules_list` - List authentication rules - `auth_rules_add` - Add authentication rule - `auth_rules_remove` - Remove authentication rule #### Statistics - `stats_uploads` - Upload statistics - `stats_bandwidth` - Bandwidth usage - `stats_storage` - Storage usage over time - `stats_users` - User activity statistics #### System - `system_info` - Get system information - `system_restart` - Restart server (graceful) - `system_backup` - Trigger database backup - `system_restore` - Restore from backup ### 3.4 Command Examples #### Example 1: List Blobs ```json // 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 ```json // Command ["blob_delete", { "sha256": "abc123...", "confirm": true }] // Response { "success": true, "data": { "deleted": true, "sha256": "abc123...", "freed_bytes": 102400 } } ``` #### Example 3: Get Storage Stats ```json // 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 ```json // 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: ```json { "success": false, "error": { "code": "BLOB_NOT_FOUND", "message": "Blob with hash abc123... not found", "details": { "sha256": "abc123..." } } } ``` **Error Codes**: - `UNAUTHORIZED` - Invalid admin signature - `INVALID_COMMAND` - Unknown command or malformed structure - `INVALID_PARAMS` - Missing or invalid parameters - `BLOB_NOT_FOUND` - Requested blob doesn't exist - `STORAGE_FULL` - Storage quota exceeded - `DATABASE_ERROR` - Database operation failed - `SYSTEM_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_seckey` table - Add `admin_pubkey` to config table ## 5. Implementation Plan ### 5.1 Phase 1: Foundation (Week 1) **Goal**: Set up key management and database schema **Tasks**: 1. Create `relay_seckey` table in schema 2. Add `admin_pubkey` to config table 3. Implement `admin_keys.c`: - `generate_relay_keypair()` - `generate_admin_keypair()` - `store_relay_private_key()` - `load_relay_private_key()` - `get_admin_pubkey()` 4. Update `main.c`: - Add CLI options (`--admin-pubkey`, `--relay-privkey`) - Generate keys on first startup - Print keys once (like c-relay) 5. 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**: 1. Create `admin_auth.c` (adapted from c-relay's authorization): - `verify_admin_event()` - Check Kind 23456 signature - `check_admin_pubkey()` - Verify against stored admin_pubkey - `verify_relay_target()` - Check 'p' tag matches relay pubkey 2. Add NIP-44 crypto functions (use existing nostr_core_lib): - `decrypt_admin_command()` - Decrypt Kind 23456 content - `encrypt_admin_response()` - Encrypt Kind 23457 content 3. Test authorization flow 4. 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**: 1. Create `admin_websocket.c`: - WebSocket endpoint at `/admin` or 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 2. Integrate with existing FastCGI WebSocket handling 3. Add connection management 4. 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**: 1. Create `admin_handlers.c` with unified handler pattern: - `handle_blob_command()` - Blob operations - `handle_storage_command()` - Storage management - `handle_config_command()` - Configuration - `handle_stats_command()` - Statistics - `handle_system_command()` - System operations 2. 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 3. Add validation for each command 4. 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**: 1. Create test suite: - Unit tests for each handler - Integration tests for full flow - Security tests for authorization - Performance tests for WebSocket 2. Create admin CLI tool (simple Node.js/Python script): - Generate Kind 23456 events - Send via WebSocket - Decrypt Kind 23457 responses - Pretty-print results 3. Write documentation: - Admin API reference - CLI tool usage guide - Security best practices - Troubleshooting guide 4. 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**: 1. Design web UI (React/Vue/Svelte) 2. Implement WebSocket client 3. Create command forms 4. Add real-time updates 5. 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 1. Receive Kind 23456 event 2. Verify event signature (nostr_verify_event_signature) 3. Check pubkey matches admin_pubkey from config 4. Verify 'p' tag targets this relay 5. Decrypt content using NIP-44 6. Parse and validate command 7. Execute command 8. Encrypt response using NIP-44 9. Sign Kind 23457 response 10. 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) ```bash 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 # Set admin pubkey ginxsom -r # Set relay privkey ginxsom --debug-level=3 # Enable info-level debugging ``` ### 7.2 First Startup Behavior On first startup (no database exists): 1. Generate relay keypair 2. Generate admin keypair 3. 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: .db ``` 4. Store relay private key in database 5. Store admin public key in config 6. Start server ### 7.3 Subsequent Startups On subsequent startups: 1. Find existing database file 2. Load relay private key from database 3. Load admin public key from config 4. Apply CLI overrides if provided 5. 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?** 1. Proven in production 2. Simple and secure 3. No complex key management 4. Minimal database schema 5. Easy to understand and maintain **What we're NOT doing (from initial design)**: 1. ❌ NIP-17 gift wrap (too complex) 2. ❌ Separate admin_keys table (use config) 3. ❌ Audit log table (keep it simple) 4. ❌ Processed events tracking (stateless) 5. ❌ Key encryption before storage (plain hex) 6. ❌ 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**: 1. Generate admin keypair 2. Create Kind 23456 command 3. Send via WebSocket 4. Verify authorization 5. Execute command 6. Receive Kind 23457 response 7. 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 1. **Command History**: Track admin commands for audit 2. **Multi-Admin Support**: Multiple authorized admin pubkeys 3. **Role-Based Access**: Different permission levels 4. **Batch Operations**: Execute multiple commands in one request ### 10.2 Long Term 1. **Web Dashboard**: Full-featured web UI 2. **Monitoring Integration**: Prometheus/Grafana metrics 3. **Backup Automation**: Scheduled backups 4. **Replication**: Multi-server blob replication 5. **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 processing - `c-relay/src/api.c` - NIP-44 encryption - `c-relay/src/config.c` - Key storage - `c-relay/src/main.c` - CLI options - `c-relay/src/sql_schema.h` - Database schema ## 12. Appendix ### 12.1 Example Admin CLI Tool (Python) ```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 ```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 ```makefile # 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