mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-15 12:12:01 +08:00
refactor: 删除部分多余的代码和注释
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
//! Extension process manager
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
@@ -12,25 +10,18 @@ use tokio::sync::RwLock;
|
||||
use super::types::*;
|
||||
use crate::events::EventBus;
|
||||
|
||||
/// Maximum number of log lines to keep per extension
|
||||
const LOG_BUFFER_SIZE: usize = 200;
|
||||
|
||||
/// Number of log lines to buffer before flushing to shared storage
|
||||
const LOG_BATCH_SIZE: usize = 16;
|
||||
|
||||
/// Unix socket path for ttyd
|
||||
pub const TTYD_SOCKET_PATH: &str = "/var/run/one-kvm/ttyd.sock";
|
||||
|
||||
/// Extension process with log buffer
|
||||
struct ExtensionProcess {
|
||||
child: Child,
|
||||
logs: Arc<RwLock<VecDeque<String>>>,
|
||||
}
|
||||
|
||||
/// Extension manager handles lifecycle of external processes
|
||||
pub struct ExtensionManager {
|
||||
processes: RwLock<HashMap<ExtensionId, ExtensionProcess>>,
|
||||
/// Cached availability status (checked once at startup)
|
||||
availability: HashMap<ExtensionId, bool>,
|
||||
event_bus: RwLock<Option<Arc<EventBus>>>,
|
||||
}
|
||||
@@ -42,9 +33,7 @@ impl Default for ExtensionManager {
|
||||
}
|
||||
|
||||
impl ExtensionManager {
|
||||
/// Create a new extension manager with cached availability
|
||||
pub fn new() -> Self {
|
||||
// Check availability once at startup
|
||||
let availability = ExtensionId::all()
|
||||
.iter()
|
||||
.map(|id| (*id, Path::new(id.binary_path()).exists()))
|
||||
@@ -57,7 +46,6 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set event bus for ttyd status notifications.
|
||||
pub async fn set_event_bus(&self, event_bus: Arc<EventBus>) {
|
||||
*self.event_bus.write().await = Some(event_bus);
|
||||
}
|
||||
@@ -72,12 +60,10 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the binary for an extension is available (cached)
|
||||
pub fn check_available(&self, id: ExtensionId) -> bool {
|
||||
*self.availability.get(&id).unwrap_or(&false)
|
||||
}
|
||||
|
||||
/// Get the current status of an extension
|
||||
pub async fn status(&self, id: ExtensionId) -> ExtensionStatus {
|
||||
if !self.check_available(id) {
|
||||
return ExtensionStatus::Unavailable;
|
||||
@@ -117,20 +103,13 @@ impl ExtensionManager {
|
||||
ExtensionStatus::Stopped
|
||||
}
|
||||
|
||||
/// Start an extension with the given configuration
|
||||
pub async fn start(&self, id: ExtensionId, config: &ExtensionsConfig) -> Result<(), String> {
|
||||
if !self.check_available(id) {
|
||||
return Err(format!(
|
||||
"{} not found at {}",
|
||||
id.display_name(),
|
||||
id.binary_path()
|
||||
));
|
||||
return Err(format!("{} not found at {}", id, id.binary_path()));
|
||||
}
|
||||
|
||||
// Stop existing process first
|
||||
self.stop(id).await.ok();
|
||||
|
||||
// Build command arguments
|
||||
let args = self.build_args(id, config).await?;
|
||||
|
||||
tracing::info!(
|
||||
@@ -146,11 +125,10 @@ impl ExtensionManager {
|
||||
.stderr(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to start {}: {}", id.display_name(), e))?;
|
||||
.map_err(|e| format!("Failed to start {}: {}", id, e))?;
|
||||
|
||||
let logs = Arc::new(RwLock::new(VecDeque::with_capacity(LOG_BUFFER_SIZE)));
|
||||
|
||||
// Spawn log collector for stdout
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
let logs_clone = logs.clone();
|
||||
let id_clone = id;
|
||||
@@ -159,7 +137,6 @@ impl ExtensionManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Spawn log collector for stderr
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let logs_clone = logs.clone();
|
||||
let id_clone = id;
|
||||
@@ -179,7 +156,6 @@ impl ExtensionManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stop an extension
|
||||
pub async fn stop(&self, id: ExtensionId) -> Result<(), String> {
|
||||
let mut processes = self.processes.write().await;
|
||||
if let Some(mut proc) = processes.remove(&id) {
|
||||
@@ -193,7 +169,6 @@ impl ExtensionManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get recent logs for an extension
|
||||
pub async fn logs(&self, id: ExtensionId, lines: usize) -> Vec<String> {
|
||||
let processes = self.processes.read().await;
|
||||
if let Some(proc) = processes.get(&id) {
|
||||
@@ -205,7 +180,6 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect logs from a stream with batched writes to reduce lock contention
|
||||
async fn collect_logs<R: tokio::io::AsyncRead + Unpin>(
|
||||
id: ExtensionId,
|
||||
reader: R,
|
||||
@@ -221,13 +195,11 @@ impl ExtensionManager {
|
||||
tracing::debug!("[{}] {}", id, line);
|
||||
local_buffer.push(line);
|
||||
|
||||
// Flush when batch is full
|
||||
if local_buffer.len() >= LOG_BATCH_SIZE {
|
||||
Self::flush_logs(&logs, &mut local_buffer).await;
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
// Stream ended, flush remaining logs
|
||||
if !local_buffer.is_empty() {
|
||||
Self::flush_logs(&logs, &mut local_buffer).await;
|
||||
}
|
||||
@@ -241,7 +213,6 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush buffered logs to shared storage
|
||||
async fn flush_logs(logs: &RwLock<VecDeque<String>>, buffer: &mut Vec<String>) {
|
||||
let mut logs = logs.write().await;
|
||||
for line in buffer.drain(..) {
|
||||
@@ -252,7 +223,6 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build command arguments for an extension
|
||||
async fn build_args(
|
||||
&self,
|
||||
id: ExtensionId,
|
||||
@@ -262,18 +232,16 @@ impl ExtensionManager {
|
||||
ExtensionId::Ttyd => {
|
||||
let c = &config.ttyd;
|
||||
|
||||
// Prepare socket directory and clean up old socket (async)
|
||||
Self::prepare_ttyd_socket().await?;
|
||||
|
||||
let mut args = vec![
|
||||
"-i".to_string(),
|
||||
TTYD_SOCKET_PATH.to_string(), // Unix socket
|
||||
TTYD_SOCKET_PATH.to_string(),
|
||||
"-b".to_string(),
|
||||
"/api/terminal".to_string(), // Base path for reverse proxy
|
||||
"-W".to_string(), // Writable (allow input)
|
||||
"/api/terminal".to_string(),
|
||||
"-W".to_string(),
|
||||
];
|
||||
|
||||
// Add shell as last argument
|
||||
args.push(c.shell.clone());
|
||||
Ok(args)
|
||||
}
|
||||
@@ -289,15 +257,12 @@ impl ExtensionManager {
|
||||
|
||||
let mut args = Vec::new();
|
||||
|
||||
// Add TLS flag
|
||||
if c.tls {
|
||||
args.push("--tls=true".to_string());
|
||||
}
|
||||
|
||||
// Server address (validated non-empty above)
|
||||
args.extend(["-addr".to_string(), c.addr.trim().to_string()]);
|
||||
|
||||
// Add client key
|
||||
args.extend(["-key".to_string(), c.key.clone()]);
|
||||
|
||||
Ok(args)
|
||||
@@ -316,24 +281,19 @@ impl ExtensionManager {
|
||||
c.network_secret.clone(),
|
||||
];
|
||||
|
||||
// Add peer URLs
|
||||
for peer in &c.peer_urls {
|
||||
if !peer.is_empty() {
|
||||
args.extend(["--peers".to_string(), peer.clone()]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add virtual IP: use -d for DHCP if empty, or -i for specific IP
|
||||
if let Some(ref ip) = c.virtual_ip {
|
||||
if !ip.is_empty() {
|
||||
// Use specific IP with -i (must include CIDR, e.g., 10.0.0.1/24)
|
||||
args.extend(["-i".to_string(), ip.clone()]);
|
||||
} else {
|
||||
// Empty string means use DHCP
|
||||
args.push("-d".to_string());
|
||||
}
|
||||
} else {
|
||||
// None means use DHCP
|
||||
args.push("-d".to_string());
|
||||
}
|
||||
|
||||
@@ -342,11 +302,9 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare ttyd socket directory and clean up old socket file
|
||||
async fn prepare_ttyd_socket() -> Result<(), String> {
|
||||
let socket_path = Path::new(TTYD_SOCKET_PATH);
|
||||
|
||||
// Ensure socket directory exists
|
||||
if let Some(socket_dir) = socket_path.parent() {
|
||||
if !socket_dir.exists() {
|
||||
tokio::fs::create_dir_all(socket_dir)
|
||||
@@ -355,7 +313,6 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove old socket file if exists
|
||||
if tokio::fs::try_exists(TTYD_SOCKET_PATH)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
@@ -368,9 +325,7 @@ impl ExtensionManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Health check - restart crashed processes that should be running
|
||||
pub async fn health_check(&self, config: &ExtensionsConfig) {
|
||||
// Collect extensions that need restart check
|
||||
let checks: Vec<_> = ExtensionId::all()
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
@@ -393,7 +348,6 @@ impl ExtensionManager {
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Check which ones need restart (single read lock)
|
||||
let needs_restart: Vec<_> = {
|
||||
let processes = self.processes.read().await;
|
||||
checks
|
||||
@@ -408,7 +362,6 @@ impl ExtensionManager {
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Restart all crashed extensions in parallel
|
||||
let restart_futures: Vec<_> = needs_restart
|
||||
.into_iter()
|
||||
.map(|id| async move {
|
||||
@@ -422,14 +375,12 @@ impl ExtensionManager {
|
||||
futures::future::join_all(restart_futures).await;
|
||||
}
|
||||
|
||||
/// Start all enabled extensions in parallel
|
||||
pub async fn start_enabled(&self, config: &ExtensionsConfig) {
|
||||
use futures::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
let mut start_futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + '_>>> = Vec::new();
|
||||
|
||||
// Collect enabled extensions
|
||||
if config.ttyd.enabled && self.check_available(ExtensionId::Ttyd) {
|
||||
start_futures.push(Box::pin(async {
|
||||
if let Err(e) = self.start(ExtensionId::Ttyd, config).await {
|
||||
@@ -461,11 +412,9 @@ impl ExtensionManager {
|
||||
}));
|
||||
}
|
||||
|
||||
// Start all in parallel
|
||||
futures::future::join_all(start_futures).await;
|
||||
}
|
||||
|
||||
/// Stop all running extensions in parallel
|
||||
pub async fn stop_all(&self) {
|
||||
let stop_futures: Vec<_> = ExtensionId::all().iter().map(|id| self.stop(*id)).collect();
|
||||
futures::future::join_all(stop_futures).await;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Extensions module - manage external processes like ttyd, gostc, easytier
|
||||
|
||||
mod manager;
|
||||
mod types;
|
||||
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
//! Extension types and configurations
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
/// Extension identifier (fixed set of supported extensions)
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ExtensionId {
|
||||
/// Web terminal (ttyd)
|
||||
Ttyd,
|
||||
/// NAT traversal client (gostc)
|
||||
Gostc,
|
||||
/// P2P VPN (easytier)
|
||||
Easytier,
|
||||
}
|
||||
|
||||
impl ExtensionId {
|
||||
/// Get the binary path for this extension
|
||||
pub fn binary_path(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Ttyd => "/usr/bin/ttyd",
|
||||
@@ -26,16 +19,6 @@ impl ExtensionId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the display name for this extension
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Ttyd => "Web Terminal",
|
||||
Self::Gostc => "GOSTC Tunnel",
|
||||
Self::Easytier => "EasyTier VPN",
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all extension IDs
|
||||
pub fn all() -> &'static [ExtensionId] {
|
||||
&[Self::Ttyd, Self::Gostc, Self::Easytier]
|
||||
}
|
||||
@@ -64,25 +47,14 @@ impl std::str::FromStr for ExtensionId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension running status
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "state", content = "data", rename_all = "lowercase")]
|
||||
pub enum ExtensionStatus {
|
||||
/// Binary not found at expected path
|
||||
Unavailable,
|
||||
/// Extension is stopped
|
||||
Stopped,
|
||||
/// Extension is running
|
||||
Running {
|
||||
/// Process ID
|
||||
pid: u32,
|
||||
},
|
||||
/// Extension failed to start
|
||||
Failed {
|
||||
/// Error message
|
||||
error: String,
|
||||
},
|
||||
Running { pid: u32 },
|
||||
Failed { error: String },
|
||||
}
|
||||
|
||||
impl ExtensionStatus {
|
||||
@@ -91,16 +63,11 @@ impl ExtensionStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// ttyd configuration (Web Terminal)
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct TtydConfig {
|
||||
/// Enable auto-start
|
||||
pub enabled: bool,
|
||||
/// Port to listen on
|
||||
pub port: u16,
|
||||
/// Shell to execute
|
||||
pub shell: String,
|
||||
}
|
||||
|
||||
@@ -108,25 +75,19 @@ impl Default for TtydConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
port: 7681,
|
||||
shell: "/bin/bash".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// gostc configuration (NAT traversal based on FRP)
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct GostcConfig {
|
||||
/// Enable auto-start
|
||||
pub enabled: bool,
|
||||
/// Server address (hostname or IP)
|
||||
pub addr: String,
|
||||
/// Client key from GOSTC management panel
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
pub key: String,
|
||||
/// Enable TLS
|
||||
pub tls: bool,
|
||||
}
|
||||
|
||||
@@ -141,28 +102,21 @@ impl Default for GostcConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// EasyTier configuration (P2P VPN)
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[derive(Default)]
|
||||
pub struct EasytierConfig {
|
||||
/// Enable auto-start
|
||||
pub enabled: bool,
|
||||
/// Network name
|
||||
pub network_name: String,
|
||||
/// Network secret/password
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
pub network_secret: String,
|
||||
/// Peer node URLs
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub peer_urls: Vec<String>,
|
||||
/// Virtual IP address (optional, auto-assigned if not set)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub virtual_ip: Option<String>,
|
||||
}
|
||||
|
||||
/// Combined extensions configuration
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
@@ -172,53 +126,37 @@ pub struct ExtensionsConfig {
|
||||
pub easytier: EasytierConfig,
|
||||
}
|
||||
|
||||
/// Extension info with status and config
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExtensionInfo {
|
||||
/// Whether binary exists
|
||||
pub available: bool,
|
||||
/// Current status
|
||||
pub status: ExtensionStatus,
|
||||
}
|
||||
|
||||
/// ttyd extension info
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TtydInfo {
|
||||
/// Whether binary exists
|
||||
pub available: bool,
|
||||
/// Current status
|
||||
pub status: ExtensionStatus,
|
||||
/// Configuration
|
||||
pub config: TtydConfig,
|
||||
}
|
||||
|
||||
/// gostc extension info
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GostcInfo {
|
||||
/// Whether binary exists
|
||||
pub available: bool,
|
||||
/// Current status
|
||||
pub status: ExtensionStatus,
|
||||
/// Configuration
|
||||
pub config: GostcConfig,
|
||||
}
|
||||
|
||||
/// easytier extension info
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EasytierInfo {
|
||||
/// Whether binary exists
|
||||
pub available: bool,
|
||||
/// Current status
|
||||
pub status: ExtensionStatus,
|
||||
/// Configuration
|
||||
pub config: EasytierConfig,
|
||||
}
|
||||
|
||||
/// All extensions status response
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExtensionsStatus {
|
||||
@@ -227,7 +165,6 @@ pub struct ExtensionsStatus {
|
||||
pub easytier: EasytierInfo,
|
||||
}
|
||||
|
||||
/// Extension logs response
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExtensionLogs {
|
||||
|
||||
Reference in New Issue
Block a user