mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-29 00:51:53 +08:00
feat: 添加 RustDesk 协议支持和项目文档
- 新增 RustDesk 模块,支持与 RustDesk 客户端连接 - 实现会合服务器协议和 P2P 连接 - 支持 NaCl 加密和密钥交换 - 添加视频帧和 HID 事件适配器 - 添加 Protobuf 协议定义 (message.proto, rendezvous.proto) - 新增完整项目文档 - 各功能模块文档 (video, hid, msd, otg, webrtc 等) - hwcodec 和 RustDesk 协议技术报告 - 系统架构和技术栈文档 - 更新 Web 前端 RustDesk 配置界面和 API
This commit is contained in:
@@ -360,3 +360,62 @@ pub async fn apply_audio_config(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 应用 RustDesk 配置变更
|
||||
pub async fn apply_rustdesk_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &crate::rustdesk::config::RustDeskConfig,
|
||||
new_config: &crate::rustdesk::config::RustDeskConfig,
|
||||
) -> Result<()> {
|
||||
tracing::info!("Applying RustDesk config changes...");
|
||||
|
||||
let mut rustdesk_guard = state.rustdesk.write().await;
|
||||
|
||||
// Check if service needs to be stopped
|
||||
if old_config.enabled && !new_config.enabled {
|
||||
// Disable service
|
||||
if let Some(ref service) = *rustdesk_guard {
|
||||
if let Err(e) = service.stop().await {
|
||||
tracing::error!("Failed to stop RustDesk service: {}", e);
|
||||
}
|
||||
tracing::info!("RustDesk service stopped");
|
||||
}
|
||||
*rustdesk_guard = None;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Check if service needs to be started or restarted
|
||||
if new_config.enabled {
|
||||
let need_restart = old_config.rendezvous_server != new_config.rendezvous_server
|
||||
|| old_config.device_id != new_config.device_id
|
||||
|| old_config.device_password != new_config.device_password;
|
||||
|
||||
if rustdesk_guard.is_none() {
|
||||
// Create new service
|
||||
tracing::info!("Initializing RustDesk service...");
|
||||
let service = crate::rustdesk::RustDeskService::new(
|
||||
new_config.clone(),
|
||||
state.stream_manager.clone(),
|
||||
state.hid.clone(),
|
||||
state.audio.clone(),
|
||||
);
|
||||
if let Err(e) = service.start().await {
|
||||
tracing::error!("Failed to start RustDesk service: {}", e);
|
||||
} else {
|
||||
tracing::info!("RustDesk service started with ID: {}", new_config.device_id);
|
||||
}
|
||||
*rustdesk_guard = Some(std::sync::Arc::new(service));
|
||||
} else if need_restart {
|
||||
// Restart existing service with new config
|
||||
if let Some(ref service) = *rustdesk_guard {
|
||||
if let Err(e) = service.restart(new_config.clone()).await {
|
||||
tracing::error!("Failed to restart RustDesk service: {}", e);
|
||||
} else {
|
||||
tracing::info!("RustDesk service restarted with ID: {}", new_config.device_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
//! - PATCH /api/config/atx - 更新 ATX 配置
|
||||
//! - GET /api/config/audio - 获取音频配置
|
||||
//! - PATCH /api/config/audio - 更新音频配置
|
||||
//! - GET /api/config/rustdesk - 获取 RustDesk 配置
|
||||
//! - PATCH /api/config/rustdesk - 更新 RustDesk 配置
|
||||
|
||||
mod apply;
|
||||
mod types;
|
||||
@@ -23,6 +25,7 @@ mod hid;
|
||||
mod msd;
|
||||
mod atx;
|
||||
mod audio;
|
||||
mod rustdesk;
|
||||
|
||||
// 导出 handler 函数
|
||||
pub use video::{get_video_config, update_video_config};
|
||||
@@ -31,6 +34,10 @@ pub use hid::{get_hid_config, update_hid_config};
|
||||
pub use msd::{get_msd_config, update_msd_config};
|
||||
pub use atx::{get_atx_config, update_atx_config};
|
||||
pub use audio::{get_audio_config, update_audio_config};
|
||||
pub use rustdesk::{
|
||||
get_rustdesk_config, get_rustdesk_status, update_rustdesk_config,
|
||||
regenerate_device_id, regenerate_device_password, get_device_password,
|
||||
};
|
||||
|
||||
// 保留全局配置查询(向后兼容)
|
||||
use axum::{extract::State, Json};
|
||||
|
||||
142
src/web/handlers/config/rustdesk.rs
Normal file
142
src/web/handlers/config/rustdesk.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
//! RustDesk 配置 Handler
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::rustdesk::config::RustDeskConfig;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_rustdesk_config;
|
||||
use super::types::RustDeskConfigUpdate;
|
||||
|
||||
/// RustDesk 配置响应(隐藏敏感信息)
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct RustDeskConfigResponse {
|
||||
pub enabled: bool,
|
||||
pub rendezvous_server: String,
|
||||
pub relay_server: Option<String>,
|
||||
pub device_id: String,
|
||||
/// 是否已设置密码
|
||||
pub has_password: bool,
|
||||
/// 是否已设置密钥对
|
||||
pub has_keypair: bool,
|
||||
}
|
||||
|
||||
impl From<&RustDeskConfig> for RustDeskConfigResponse {
|
||||
fn from(config: &RustDeskConfig) -> Self {
|
||||
Self {
|
||||
enabled: config.enabled,
|
||||
rendezvous_server: config.rendezvous_server.clone(),
|
||||
relay_server: config.relay_server.clone(),
|
||||
device_id: config.device_id.clone(),
|
||||
has_password: !config.device_password.is_empty(),
|
||||
has_keypair: config.public_key.is_some() && config.private_key.is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RustDesk 状态响应
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct RustDeskStatusResponse {
|
||||
pub config: RustDeskConfigResponse,
|
||||
pub service_status: String,
|
||||
pub rendezvous_status: Option<String>,
|
||||
}
|
||||
|
||||
/// 获取 RustDesk 配置
|
||||
pub async fn get_rustdesk_config(State(state): State<Arc<AppState>>) -> Json<RustDeskConfigResponse> {
|
||||
Json(RustDeskConfigResponse::from(&state.config.get().rustdesk))
|
||||
}
|
||||
|
||||
/// 获取 RustDesk 完整状态(配置 + 服务状态)
|
||||
pub async fn get_rustdesk_status(State(state): State<Arc<AppState>>) -> Json<RustDeskStatusResponse> {
|
||||
let config = state.config.get().rustdesk.clone();
|
||||
|
||||
// 获取服务状态
|
||||
let (service_status, rendezvous_status) = {
|
||||
let guard = state.rustdesk.read().await;
|
||||
if let Some(ref service) = *guard {
|
||||
let status = format!("{}", service.status());
|
||||
let rv_status = service.rendezvous_status().map(|s| format!("{}", s));
|
||||
(status, rv_status)
|
||||
} else {
|
||||
("not_initialized".to_string(), None)
|
||||
}
|
||||
};
|
||||
|
||||
Json(RustDeskStatusResponse {
|
||||
config: RustDeskConfigResponse::from(&config),
|
||||
service_status,
|
||||
rendezvous_status,
|
||||
})
|
||||
}
|
||||
|
||||
/// 更新 RustDesk 配置
|
||||
pub async fn update_rustdesk_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<RustDeskConfigUpdate>,
|
||||
) -> Result<Json<RustDeskConfigResponse>> {
|
||||
// 1. 验证请求
|
||||
req.validate()?;
|
||||
|
||||
// 2. 获取旧配置
|
||||
let old_config = state.config.get().rustdesk.clone();
|
||||
|
||||
// 3. 应用更新到配置存储
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
req.apply_to(&mut config.rustdesk);
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 4. 获取新配置
|
||||
let new_config = state.config.get().rustdesk.clone();
|
||||
|
||||
// 5. 应用到子系统(热重载)
|
||||
if let Err(e) = apply_rustdesk_config(&state, &old_config, &new_config).await {
|
||||
tracing::error!("Failed to apply RustDesk config: {}", e);
|
||||
}
|
||||
|
||||
Ok(Json(RustDeskConfigResponse::from(&new_config)))
|
||||
}
|
||||
|
||||
/// 重新生成设备 ID
|
||||
pub async fn regenerate_device_id(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<RustDeskConfigResponse>> {
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
config.rustdesk.device_id = RustDeskConfig::generate_device_id();
|
||||
})
|
||||
.await?;
|
||||
|
||||
let new_config = state.config.get().rustdesk.clone();
|
||||
Ok(Json(RustDeskConfigResponse::from(&new_config)))
|
||||
}
|
||||
|
||||
/// 重新生成设备密码
|
||||
pub async fn regenerate_device_password(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<RustDeskConfigResponse>> {
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
config.rustdesk.device_password = RustDeskConfig::generate_password();
|
||||
})
|
||||
.await?;
|
||||
|
||||
let new_config = state.config.get().rustdesk.clone();
|
||||
Ok(Json(RustDeskConfigResponse::from(&new_config)))
|
||||
}
|
||||
|
||||
/// 获取设备密码(管理员专用)
|
||||
pub async fn get_device_password(State(state): State<Arc<AppState>>) -> Json<serde_json::Value> {
|
||||
let config = state.config.get().rustdesk.clone();
|
||||
Json(serde_json::json!({
|
||||
"device_id": config.device_id,
|
||||
"device_password": config.device_password
|
||||
}))
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use serde::Deserialize;
|
||||
use typeshare::typeshare;
|
||||
use crate::config::*;
|
||||
use crate::error::AppError;
|
||||
use crate::rustdesk::config::RustDeskConfig;
|
||||
|
||||
// ===== Video Config =====
|
||||
#[typeshare]
|
||||
@@ -394,3 +395,60 @@ impl AudioConfigUpdate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== RustDesk Config =====
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RustDeskConfigUpdate {
|
||||
pub enabled: Option<bool>,
|
||||
pub rendezvous_server: Option<String>,
|
||||
pub relay_server: Option<String>,
|
||||
pub device_password: Option<String>,
|
||||
}
|
||||
|
||||
impl RustDeskConfigUpdate {
|
||||
pub fn validate(&self) -> crate::error::Result<()> {
|
||||
// Validate rendezvous server format (should be host:port)
|
||||
if let Some(ref server) = self.rendezvous_server {
|
||||
if !server.is_empty() && !server.contains(':') {
|
||||
return Err(AppError::BadRequest(
|
||||
"Rendezvous server must be in format 'host:port' (e.g., rs.example.com:21116)".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
// Validate relay server format if provided
|
||||
if let Some(ref server) = self.relay_server {
|
||||
if !server.is_empty() && !server.contains(':') {
|
||||
return Err(AppError::BadRequest(
|
||||
"Relay server must be in format 'host:port' (e.g., rs.example.com:21117)".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
// Validate password (minimum 6 characters if provided)
|
||||
if let Some(ref password) = self.device_password {
|
||||
if !password.is_empty() && password.len() < 6 {
|
||||
return Err(AppError::BadRequest(
|
||||
"Device password must be at least 6 characters".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_to(&self, config: &mut RustDeskConfig) {
|
||||
if let Some(enabled) = self.enabled {
|
||||
config.enabled = enabled;
|
||||
}
|
||||
if let Some(ref server) = self.rendezvous_server {
|
||||
config.rendezvous_server = server.clone();
|
||||
}
|
||||
if let Some(ref server) = self.relay_server {
|
||||
config.relay_server = if server.is_empty() { None } else { Some(server.clone()) };
|
||||
}
|
||||
if let Some(ref password) = self.device_password {
|
||||
if !password.is_empty() {
|
||||
config.device_password = password.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user