mod auth; mod container; mod node; mod package; mod peers; use crate::auth::AuthManager; use crate::config::Config; use crate::container::DevContainerOrchestrator; use crate::port_allocator::PortAllocator; use crate::state::StateManager; use anyhow::{Context, Result}; use hyper::{Request, Response, StatusCode}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use tracing::{debug, error}; #[derive(Debug, Deserialize)] struct RpcRequest { method: String, params: Option, } #[derive(Debug, Serialize)] struct RpcResponse { result: Option, error: Option, } #[derive(Debug, Serialize)] struct RpcError { code: i32, message: String, data: Option, } /// Default dev password when no user is set up (matches mock-backend). pub(crate) const DEV_DEFAULT_PASSWORD: &str = "password123"; pub struct RpcHandler { config: Config, auth_manager: AuthManager, orchestrator: Option>, state_manager: Arc, port_allocator: Arc>, } impl RpcHandler { pub async fn new(config: Config, state_manager: Arc) -> Result { let auth_manager = AuthManager::new(config.data_dir.clone()); let orchestrator = if config.dev_mode { Some(Arc::new( DevContainerOrchestrator::new(config.clone()).await?, )) } else { None }; let port_allocator = Arc::new(Mutex::new(PortAllocator::new(&config.data_dir)?)); Ok(Self { config, auth_manager, orchestrator, state_manager, port_allocator, }) } pub async fn handle( &self, req: Request, ) -> Result> { // Read request body let (_, body) = req.into_parts(); let body_bytes = hyper::body::to_bytes(body).await .context("Failed to read body")?; let rpc_req: RpcRequest = serde_json::from_slice(&body_bytes) .context("Invalid RPC request")?; debug!("RPC method: {}", rpc_req.method); // Route to handler let result = match rpc_req.method.as_str() { "echo" => self.handle_echo(rpc_req.params).await, "server.echo" => self.handle_echo(rpc_req.params).await, "auth.login" => self.handle_auth_login(rpc_req.params).await, "auth.logout" => self.handle_auth_logout().await, "auth.changePassword" => self.handle_auth_change_password(rpc_req.params).await, "auth.onboardingComplete" => self.handle_auth_onboarding_complete().await, "auth.isOnboardingComplete" => self.handle_auth_is_onboarding_complete().await, "auth.resetOnboarding" => self.handle_auth_reset_onboarding().await, // Container orchestration (for Archipelago-managed containers) "container-install" => self.handle_container_install(rpc_req.params).await, "container-start" => self.handle_container_start(rpc_req.params).await, "container-stop" => self.handle_container_stop(rpc_req.params).await, "container-remove" => self.handle_container_remove(rpc_req.params).await, "container-list" => self.handle_container_list().await, "container-status" => self.handle_container_status(rpc_req.params).await, "container-logs" => self.handle_container_logs(rpc_req.params).await, "container-health" => self.handle_container_health(rpc_req.params).await, // Package management (for docker-compose apps) "package.install" => self.handle_package_install(rpc_req.params).await, "package.start" => self.handle_package_start(rpc_req.params).await, "package.stop" => self.handle_package_stop(rpc_req.params).await, "package.restart" => self.handle_package_restart(rpc_req.params).await, "package.uninstall" => self.handle_package_uninstall(rpc_req.params).await, // Bundled app management (for pre-loaded container images) "bundled-app-start" => self.handle_bundled_app_start(rpc_req.params).await, "bundled-app-stop" => self.handle_bundled_app_stop(rpc_req.params).await, // Node identity and P2P peers "node-add-peer" => self.handle_node_add_peer(rpc_req.params).await, "node-list-peers" => self.handle_node_list_peers().await, "node-remove-peer" => self.handle_node_remove_peer(rpc_req.params).await, "node-send-message" => self.handle_node_send_message(rpc_req.params).await, "node-check-peer" => self.handle_node_check_peer(rpc_req.params).await, "node-messages-received" => self.handle_node_messages_received().await, "node-nostr-discover" => self.handle_node_nostr_discover().await, "node.did" => self.handle_node_did().await, "node.signChallenge" => self.handle_node_sign_challenge(rpc_req.params).await, "node.createBackup" => self.handle_node_create_backup(rpc_req.params).await, "node.tor-address" => self.handle_node_tor_address().await, "node.nostr-publish" => self.handle_node_nostr_publish().await, "node.nostr-pubkey" => self.handle_node_nostr_pubkey().await, "node-nostr-verify-revoked" => self.handle_node_nostr_verify_revoked().await, _ => { Err(anyhow::anyhow!("Unknown method: {}", rpc_req.method)) } }; // Build response let rpc_resp = match result { Ok(data) => RpcResponse { result: Some(data), error: None, }, Err(e) => { error!("RPC error: {}", e); RpcResponse { result: None, error: Some(RpcError { code: -1, message: e.to_string(), data: None, }), } } }; let body = serde_json::to_vec(&rpc_resp) .context("Failed to serialize response")?; Ok(Response::builder() .status(StatusCode::OK) .header("Content-Type", "application/json") .body(hyper::Body::from(body)) .unwrap()) } async fn handle_echo(&self, params: Option) -> Result { if let Some(p) = params { if let Some(msg) = p.get("message").and_then(|v| v.as_str()) { return Ok(serde_json::json!({ "message": msg })); } } Ok(serde_json::json!({ "message": "Hello from Archipelago!" })) } }