# Multi-Circle Single-Server Implementation Plan ## Overview Transform the current launcher from a JSON-config based multi-server system to a command-line driven single-server system that handles multiple circles via path-based routing. ## Architecture Changes ### Current Architecture: ``` Launcher → circles.json → Multiple Servers (one per circle, different ports) ``` ### New Architecture: ``` Launcher → CLI args → Single Server (one port, path-based routing) ``` ## Phase 1: Update Launcher Interface ### 1.1 New Command Line Interface ```bash # New launcher usage cargo run --bin launcher -- --port 8080 --public-keys "pk1,pk2,pk3" # or with multiple -k flags cargo run --bin launcher -- --port 8080 -k "pk1" -k "pk2" -k "pk3" ``` ### 1.2 Updated Args Structure Replace current `Args` struct with: ```rust #[derive(Parser, Debug, Clone)] #[command(author, version, about, long_about = None)] pub struct Args { /// Port for the WebSocket server #[arg(short, long, default_value = "8080")] pub port: u16, /// Circle public keys (hex format, can be specified multiple times) #[arg(short = 'k', long = "public-key")] pub public_keys: Vec, /// Redis URL #[arg(long, default_value = "redis://127.0.0.1:6379")] pub redis_url: String, /// Enable authentication #[arg(long)] pub enable_auth: bool, /// Enable debug mode #[arg(short, long)] pub debug: bool, /// Verbosity level #[arg(short, long, action = clap::ArgAction::Count)] pub verbose: u8, } ``` ### 1.3 Remove Obsolete Code - Remove `CircleConfig` struct - Remove `SimpleArgs` struct - Remove `run_simple_launcher` function - Remove JSON parsing logic from `main.rs` - Remove `simple.rs` binary entirely ## Phase 2: Multi-Circle Server Architecture ### 2.1 New Server Configuration Update `ServerConfig` to support multiple circles: ```rust #[derive(Clone)] pub struct MultiCircleServerConfig { pub host: String, pub port: u16, pub redis_url: String, pub circle_public_keys: Vec, // List of allowed circles pub enable_auth: bool, pub enable_tls: bool, pub cert_path: Option, pub key_path: Option, } ``` ### 2.2 Path-Based Routing Update WebSocket routing from `/ws` to `/{circle_pk}`: ```rust // New route pattern in spawn_circle_server .route("/{circle_pk}", web::get().to(ws_handler)) // Updated handler signature async fn ws_handler( req: HttpRequest, stream: web::Payload, path: web::Path, // circle_pk from URL server_config: web::Data, ) -> Result ``` ### 2.3 Circle Validation Add validation logic in `ws_handler`: ```rust let circle_pk = path.into_inner(); // Validate circle_pk is in allowed list if !server_config.circle_public_keys.contains(&circle_pk) { return Ok(HttpResponse::NotFound() .json(json!({ "error": "Circle not found", "message": format!("Circle '{}' is not available on this server", circle_pk) }))); } ``` ## Phase 3: Per-Circle Authentication ### 3.1 Update CircleWs Actor Modify `CircleWs` to work with specific circle from URL: ```rust struct CircleWs { circle_public_key: String, // From URL path redis_url: String, nonce_store: HashMap, auth_enabled: bool, authenticated_pubkey: Option, } impl CircleWs { fn new_for_circle( circle_public_key: String, redis_url: String, auth_enabled: bool, ) -> Self { Self { circle_public_key, redis_url, nonce_store: HashMap::new(), auth_enabled, authenticated_pubkey: None, } } } ``` ### 3.2 Circle-Specific Authentication Update authentication logic in `handle_authenticate`: - Authentication challenges are specific to the circle from URL path - Signature verification uses the circle's public key context - Each circle maintains separate authentication state ## Phase 4: Worker Communication Updates ### 4.1 Update Launcher Worker Spawning Modify `setup_and_spawn_circles` to: - Accept list of public keys instead of CircleConfig - Spawn one worker per public key - Launch single server instance with all public keys ### 4.2 Play Request Routing Update `handle_play` to route to correct worker: ```rust // Use circle_public_key from URL path for worker routing rhai_dispatcher .new_play_request() .recipient_id(&self.circle_public_key) // From URL path .script_path(&script_content) .timeout(TASK_TIMEOUT_DURATION) .await_response() .await ``` ## Phase 5: Updated Launcher Logic ### 5.1 New Setup Function Replace `setup_and_spawn_circles` with: ```rust pub async fn setup_multi_circle_server( public_keys: Vec, port: u16, redis_url: String, enable_auth: bool, ) -> Result<(Vec>, ServerHandle), Box> ``` ### 5.2 Single Server Spawn Launch one server instance that handles all circles: ```rust let server_config = MultiCircleServerConfig { host: "127.0.0.1".to_string(), port, redis_url: redis_url.clone(), circle_public_keys: public_keys.clone(), enable_auth, enable_tls: false, cert_path: None, key_path: None, }; let (server_task, server_handle) = spawn_multi_circle_server(server_config)?; ``` ### 5.3 Worker Spawning Per Circle Spawn one worker per public key: ```rust let mut worker_handles = Vec::new(); for public_key in &public_keys { let worker_handle = spawn_rhai_worker( public_key.clone(), public_key.clone(), engine.clone(), redis_url.clone(), worker_shutdown_rx, preserve_tasks, ); worker_handles.push(worker_handle); } ``` ## Phase 6: File Structure Changes ### 6.1 Remove Files - Delete `src/launcher/src/cmd/simple.rs` - Remove simple binary from `Cargo.toml` ### 6.2 Update Main Binary Update `src/launcher/src/cmd/main.rs` to use new CLI args instead of JSON config ### 6.3 Update Library Exports Remove exports for: - `SimpleArgs` - `run_simple_launcher` - `CircleConfig` ## Architecture Diagram ```mermaid graph TB CLI[Launcher CLI
--port 8080
-k pk1 -k pk2 -k pk3
--enable-auth] CLI --> L[Launcher Process] L --> W1[Worker: pk1
Redis Queue: rhai_tasks:pk1] L --> W2[Worker: pk2
Redis Queue: rhai_tasks:pk2] L --> W3[Worker: pk3
Redis Queue: rhai_tasks:pk3] L --> S[Single Server Instance
Port 8080
MultiCircleServerConfig] C1[Client 1] --> |wss://127.0.0.1:8080/pk1| S C2[Client 2] --> |wss://127.0.0.1:8080/pk2| S C3[Client 3] --> |wss://127.0.0.1:8080/pk3| S S --> |Validate pk1 in allowed list| V1[Circle Validation] S --> |Validate pk2 in allowed list| V2[Circle Validation] S --> |Validate pk3 in allowed list| V3[Circle Validation] V1 --> |Create CircleWs for pk1| WS1[CircleWs Actor: pk1] V2 --> |Create CircleWs for pk2| WS2[CircleWs Actor: pk2] V3 --> |Create CircleWs for pk3| WS3[CircleWs Actor: pk3] WS1 --> |Route play requests| W1 WS2 --> |Route play requests| W2 WS3 --> |Route play requests| W3 subgraph "Redis Queues" R1[rhai_tasks:pk1] R2[rhai_tasks:pk2] R3[rhai_tasks:pk3] end W1 <--> R1 W2 <--> R2 W3 <--> R3 subgraph "Authentication Per Circle" A1[Auth State: pk1] A2[Auth State: pk2] A3[Auth State: pk3] end WS1 <--> A1 WS2 <--> A2 WS3 <--> A3 ``` ## Implementation Steps Summary ### Step 1: Update Launcher Args - Replace `Args` struct with new CLI interface - Remove JSON config dependencies - Add public key validation ### Step 2: Create Multi-Circle Server Config - Replace `ServerConfig` with `MultiCircleServerConfig` - Support list of circle public keys - Maintain TLS and auth options ### Step 3: Implement Path-Based Routing - Update WebSocket route from `/ws` to `/{circle_pk}` - Add circle validation before WebSocket upgrade - Return HTTP 404 for invalid circles ### Step 4: Update CircleWs Actor - Extract circle public key from URL path - Implement per-circle authentication - Route to correct worker based on circle ### Step 5: Update Launcher Logic - Remove JSON parsing - Spawn single server with multiple circles - Spawn one worker per circle public key ### Step 6: Clean Up Code - Remove `SimpleArgs`, `CircleConfig`, `run_simple_launcher` - Delete simple binary - Update exports and documentation ## Benefits of This Architecture 1. **Simplified Deployment**: Single command, single port, multiple circles 2. **Resource Efficiency**: Shared server infrastructure 3. **Clear Separation**: Per-circle authentication and worker routing 4. **Scalable**: Easy to add/remove circles via CLI 5. **Maintainable**: Less complex than multi-server approach ## Example Usage ```bash # Launch server with 3 circles on port 8080 with auth enabled cargo run --bin launcher -- \ --port 8080 \ --enable-auth \ -k "02a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab" \ -k "03b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789abc1" \ -k "02c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789abcd12" # Clients connect to specific circles: # wss://127.0.0.1:8080/02a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab # wss://127.0.0.1:8080/03b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789abc1 # wss://127.0.0.1:8080/02c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789abcd12 ``` ## Key Requirements Satisfied 1. ✅ **Remove JSON config functionality** - Completely replaced with CLI args 2. ✅ **Accept list of circle public keys** - Via `-k` flags or comma-separated 3. ✅ **Single port for multiple circles** - One server handles all circles 4. ✅ **Path-based routing** - `wss://127.0.0.1:port/circle_pk` 5. ✅ **Per-circle authentication** - Auth against specific circle's public key 6. ✅ **Route to appropriate worker** - Based on circle public key from URL 7. ✅ **Use hex format everywhere** - Consistent secp256k1 hex encoding 8. ✅ **HTTP 404 for invalid circles** - Before WebSocket upgrade