10 KiB
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
withMultiCircleServerConfig
- 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
- Simplified Deployment: Single command, single port, multiple circles
- Resource Efficiency: Shared server infrastructure
- Clear Separation: Per-circle authentication and worker routing
- Scalable: Easy to add/remove circles via CLI
- 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
- ✅ Remove JSON config functionality - Completely replaced with CLI args
- ✅ Accept list of circle public keys - Via
-k
flags or comma-separated - ✅ Single port for multiple circles - One server handles all circles
- ✅ Path-based routing -
wss://127.0.0.1:port/circle_pk
- ✅ Per-circle authentication - Auth against specific circle's public key
- ✅ Route to appropriate worker - Based on circle public key from URL
- ✅ Use hex format everywhere - Consistent secp256k1 hex encoding
- ✅ HTTP 404 for invalid circles - Before WebSocket upgrade