circles/docs/aidocs/IMPLEMENTATION_PLAN.md
2025-07-09 23:39:48 +02:00

10 KiB

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

# 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:

#[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<String>,

    /// 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:

#[derive(Clone)]
pub struct MultiCircleServerConfig {
    pub host: String,
    pub port: u16,
    pub redis_url: String,
    pub circle_public_keys: Vec<String>, // List of allowed circles
    pub enable_auth: bool,
    pub enable_tls: bool,
    pub cert_path: Option<String>,
    pub key_path: Option<String>,
}

2.2 Path-Based Routing

Update WebSocket routing from /ws to /{circle_pk}:

// 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<String>, // circle_pk from URL
    server_config: web::Data<MultiCircleServerConfig>,
) -> Result<HttpResponse, Error>

2.3 Circle Validation

Add validation logic in ws_handler:

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:

struct CircleWs {
    circle_public_key: String, // From URL path
    redis_url: String,
    nonce_store: HashMap<String, NonceResponse>,
    auth_enabled: bool,
    authenticated_pubkey: Option<String>,
}

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:

// 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:

pub async fn setup_multi_circle_server(
    public_keys: Vec<String>,
    port: u16,
    redis_url: String,
    enable_auth: bool,
) -> Result<(Vec<JoinHandle<_>>, ServerHandle), Box<dyn std::error::Error>>

5.2 Single Server Spawn

Launch one server instance that handles all circles:

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:

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

graph TB
    CLI[Launcher CLI<br/>--port 8080<br/>-k pk1 -k pk2 -k pk3<br/>--enable-auth]
    
    CLI --> L[Launcher Process]
    
    L --> W1[Worker: pk1<br/>Redis Queue: rhai_tasks:pk1]
    L --> W2[Worker: pk2<br/>Redis Queue: rhai_tasks:pk2] 
    L --> W3[Worker: pk3<br/>Redis Queue: rhai_tasks:pk3]
    
    L --> S[Single Server Instance<br/>Port 8080<br/>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

# 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