mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-17 17:41:28 +03:00
remove terminal.md
This commit is contained in:
521
terminal.md
521
terminal.md
@@ -1,521 +0,0 @@
|
|||||||
# RustDesk Terminal Service Implementation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The RustDesk terminal service provides remote terminal/shell access with support for multiple concurrent terminal sessions per connection. It features persistence support, allowing terminal sessions to survive connection drops and be resumed later.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Client-Side (Flutter)
|
|
||||||
|
|
||||||
#### Terminal Connection Management
|
|
||||||
- **TerminalConnectionManager** (`flutter/lib/desktop/pages/terminal_connection_manager.dart`)
|
|
||||||
- Manages one FFI instance per peer (shared across all terminal tabs)
|
|
||||||
- Tracks persistence settings per peer
|
|
||||||
- Handles connection reference counting
|
|
||||||
|
|
||||||
#### Terminal Models
|
|
||||||
- **TerminalModel** (`flutter/lib/models/terminal_model.dart`)
|
|
||||||
- One instance per terminal tab
|
|
||||||
- Handles terminal I/O and display using xterm package
|
|
||||||
- Manages terminal state (opened, size, buffer)
|
|
||||||
|
|
||||||
#### UI Components
|
|
||||||
- **TerminalTabPage** (`flutter/lib/desktop/pages/terminal_tab_page.dart`)
|
|
||||||
- Manages multiple terminal tabs
|
|
||||||
- Right-click menu for persistence toggle
|
|
||||||
- Keyboard shortcuts (Cmd/Ctrl+Shift+T for new terminal)
|
|
||||||
|
|
||||||
### Server-Side (Rust)
|
|
||||||
|
|
||||||
#### Terminal Service Structure
|
|
||||||
```rust
|
|
||||||
TerminalService {
|
|
||||||
conn_id: i32,
|
|
||||||
service_id: String, // "tmp_{uuid}" or "persist_{uuid}"
|
|
||||||
persist: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
PersistentTerminalService {
|
|
||||||
service_id: String,
|
|
||||||
sessions: HashMap<i32, TerminalSession>, // terminal_id -> session
|
|
||||||
next_terminal_id: i32,
|
|
||||||
created_at: Instant,
|
|
||||||
last_activity: Instant,
|
|
||||||
}
|
|
||||||
|
|
||||||
TerminalSession {
|
|
||||||
terminal_id: i32,
|
|
||||||
pty_pair: PtyPair,
|
|
||||||
child: Box<dyn Child>,
|
|
||||||
writer: Box<dyn Write>,
|
|
||||||
reader: Box<dyn Read>,
|
|
||||||
output_buffer: OutputBuffer, // For reconnection
|
|
||||||
rows: u16,
|
|
||||||
cols: u16,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Message Protocol
|
|
||||||
|
|
||||||
### Client → Server Messages
|
|
||||||
|
|
||||||
1. **Open Terminal**
|
|
||||||
```protobuf
|
|
||||||
TerminalAction {
|
|
||||||
open: OpenTerminal {
|
|
||||||
terminal_id: i32,
|
|
||||||
rows: u32,
|
|
||||||
cols: u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Send Input**
|
|
||||||
```protobuf
|
|
||||||
TerminalAction {
|
|
||||||
data: TerminalData {
|
|
||||||
terminal_id: i32,
|
|
||||||
data: bytes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Resize Terminal**
|
|
||||||
```protobuf
|
|
||||||
TerminalAction {
|
|
||||||
resize: ResizeTerminal {
|
|
||||||
terminal_id: i32,
|
|
||||||
rows: u32,
|
|
||||||
cols: u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Close Terminal**
|
|
||||||
```protobuf
|
|
||||||
TerminalAction {
|
|
||||||
close: CloseTerminal {
|
|
||||||
terminal_id: i32,
|
|
||||||
force: bool,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Server → Client Messages
|
|
||||||
|
|
||||||
1. **Terminal Opened**
|
|
||||||
```protobuf
|
|
||||||
TerminalResponse {
|
|
||||||
opened: TerminalOpened {
|
|
||||||
terminal_id: i32,
|
|
||||||
success: bool,
|
|
||||||
message: string,
|
|
||||||
pid: u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Terminal Output**
|
|
||||||
```protobuf
|
|
||||||
TerminalResponse {
|
|
||||||
data: TerminalData {
|
|
||||||
terminal_id: i32,
|
|
||||||
data: bytes, // Base64 encoded in Flutter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Terminal Closed**
|
|
||||||
```protobuf
|
|
||||||
TerminalResponse {
|
|
||||||
closed: TerminalClosed {
|
|
||||||
terminal_id: i32,
|
|
||||||
exit_code: i32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Persistence Design
|
|
||||||
|
|
||||||
### Service ID Convention
|
|
||||||
- **Temporary**: `"tmp_{uuid}"` - Cleaned up after idle timeout
|
|
||||||
- **Persistent**: `"persist_{uuid}"` - Survives disconnections
|
|
||||||
|
|
||||||
### Persistence Flow
|
|
||||||
1. User right-clicks terminal tab → "Enable terminal persistence"
|
|
||||||
2. Client stores persistence preference in `TerminalConnectionManager`
|
|
||||||
3. New terminals created with appropriate service ID prefix
|
|
||||||
4. Service ID saved for future reconnection (TODO: implement storage)
|
|
||||||
|
|
||||||
### Cleanup Rules
|
|
||||||
- **Temporary services (`tmp_`)**:
|
|
||||||
- Removed after 1 hour idle time
|
|
||||||
- Immediately removed when service loop exits
|
|
||||||
|
|
||||||
- **Persistent services**:
|
|
||||||
- Removed after 2 hours idle time IF empty
|
|
||||||
- Survive connection drops
|
|
||||||
- Can be reconnected using saved service ID
|
|
||||||
|
|
||||||
### Cleanup Implementation
|
|
||||||
|
|
||||||
#### 1. **Automatic Background Cleanup**
|
|
||||||
```rust
|
|
||||||
// Runs every 5 minutes
|
|
||||||
fn ensure_cleanup_task() {
|
|
||||||
tokio::spawn(async {
|
|
||||||
let mut interval = tokio::time::interval(Duration::from_secs(300));
|
|
||||||
loop {
|
|
||||||
interval.tick().await;
|
|
||||||
cleanup_inactive_services();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. **Cleanup Logic**
|
|
||||||
```rust
|
|
||||||
fn cleanup_inactive_services() {
|
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
for (service_id, service) in services.iter() {
|
|
||||||
// Temporary services: clean up after 1 hour idle
|
|
||||||
if service_id.starts_with("tmp_") &&
|
|
||||||
now.duration_since(svc.last_activity) > SERVICE_IDLE_TIMEOUT {
|
|
||||||
to_remove.push(service_id);
|
|
||||||
}
|
|
||||||
// Persistent services: clean up after 2 hours IF empty
|
|
||||||
else if !service_id.starts_with("tmp_") &&
|
|
||||||
svc.sessions.is_empty() &&
|
|
||||||
now.duration_since(svc.last_activity) > SERVICE_IDLE_TIMEOUT * 2 {
|
|
||||||
to_remove.push(service_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. **Service Loop Exit Cleanup**
|
|
||||||
```rust
|
|
||||||
fn run(sp: EmptyExtraFieldService, _conn_id: i32, service_id: String) {
|
|
||||||
// Service loop
|
|
||||||
while sp.ok() {
|
|
||||||
// Read and send terminal outputs...
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up temporary services immediately on exit
|
|
||||||
if service_id.starts_with("tmp_") {
|
|
||||||
remove_service(&service_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. **Session Cleanup Within Service**
|
|
||||||
When a terminal is closed:
|
|
||||||
- PTY process is terminated
|
|
||||||
- Terminal session removed from service's HashMap
|
|
||||||
- Resources (file descriptors, buffers) are freed
|
|
||||||
- Service continues running for other terminals
|
|
||||||
|
|
||||||
#### 5. **Connection Drop Behavior**
|
|
||||||
```rust
|
|
||||||
impl Drop for Connection {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.terminal {
|
|
||||||
// Unsubscribe from terminal service
|
|
||||||
server.subscribe(&service_name, self.inner.clone(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Connection unsubscribes from service
|
|
||||||
- Service loop continues if other subscribers exist
|
|
||||||
- If no subscribers remain, `sp.ok()` returns false → service loop exits
|
|
||||||
|
|
||||||
#### 6. **Activity Tracking**
|
|
||||||
`last_activity` is updated when:
|
|
||||||
- New terminal opened
|
|
||||||
- Input sent to terminal
|
|
||||||
- Terminal resized
|
|
||||||
- Output read from terminal
|
|
||||||
- Any terminal operation occurs
|
|
||||||
|
|
||||||
#### 7. **Two-Phase Cleanup Process**
|
|
||||||
```rust
|
|
||||||
// Collect services to remove (while holding lock)
|
|
||||||
let mut to_remove = Vec::new();
|
|
||||||
for (id, service) in services.iter() {
|
|
||||||
if should_remove(service) {
|
|
||||||
to_remove.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove services (after releasing lock)
|
|
||||||
drop(services);
|
|
||||||
for id in to_remove {
|
|
||||||
remove_service(&id);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
This prevents deadlock when removing services.
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### Multiple Terminals per Connection
|
|
||||||
- Single FFI connection shared by all terminal tabs
|
|
||||||
- Each terminal has unique ID within the service
|
|
||||||
- Independent PTY sessions per terminal
|
|
||||||
|
|
||||||
### Output Buffering
|
|
||||||
- Last 1MB of output buffered per terminal
|
|
||||||
- Allows showing recent history on reconnection
|
|
||||||
- Ring buffer with line-based storage
|
|
||||||
|
|
||||||
### Cross-Platform Support
|
|
||||||
- **Unix/Linux/macOS**: Uses default shell from `$SHELL` or `/bin/bash`
|
|
||||||
- **Windows**: Uses `%COMSPEC%` or `cmd.exe`
|
|
||||||
- PTY implementation via `portable_pty` crate
|
|
||||||
|
|
||||||
### Non-Blocking I/O
|
|
||||||
- PTY readers set to non-blocking mode (Unix)
|
|
||||||
- Output polled at ~33fps for responsive display
|
|
||||||
- Prevents blocking when no data available
|
|
||||||
|
|
||||||
## Current Limitations
|
|
||||||
|
|
||||||
1. **Service ID Storage**: Client doesn't persist service IDs yet
|
|
||||||
2. **Reconnection UI**: No UI to recover previous sessions
|
|
||||||
3. **Authentication**: No per-service authentication for reconnection
|
|
||||||
4. **Resource Limits**: No configurable limits on terminals per service
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
1. **Proper Reconnection Flow**:
|
|
||||||
- Store service IDs in peer config
|
|
||||||
- UI to list and recover previous sessions
|
|
||||||
- Show buffered output on reconnection
|
|
||||||
|
|
||||||
2. **Security**:
|
|
||||||
- Authentication token for service recovery
|
|
||||||
- Encryption of buffered output
|
|
||||||
- Access control per terminal
|
|
||||||
|
|
||||||
3. **Advanced Features**:
|
|
||||||
- Terminal sharing between users
|
|
||||||
- Session recording/playback
|
|
||||||
- File transfer via terminal
|
|
||||||
- Custom shell/command configuration
|
|
||||||
|
|
||||||
## Code Locations
|
|
||||||
|
|
||||||
- **Server Implementation**: `src/server/terminal_service.rs`
|
|
||||||
- **Connection Handler**: `src/server/connection.rs` (handle_terminal_action)
|
|
||||||
- **Client Interface**: `src/ui_session_interface.rs` (terminal methods)
|
|
||||||
- **Flutter FFI**: `src/flutter_ffi.rs` (session_open_terminal, etc.)
|
|
||||||
- **Flutter Models**: `flutter/lib/models/terminal_model.dart`
|
|
||||||
- **Flutter UI**: `flutter/lib/desktop/pages/terminal_*.dart`
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
1. **Start Terminal Session**:
|
|
||||||
- Click terminal icon or use Ctrl/Cmd+Shift+T
|
|
||||||
- Terminal opens with default shell
|
|
||||||
|
|
||||||
2. **Enable Persistence**:
|
|
||||||
- Right-click any terminal tab
|
|
||||||
- Select "Enable terminal persistence"
|
|
||||||
- All terminals for that peer become persistent
|
|
||||||
|
|
||||||
3. **Multiple Terminals**:
|
|
||||||
- Click "+" button or Ctrl/Cmd+Shift+T
|
|
||||||
- Each terminal is independent
|
|
||||||
|
|
||||||
4. **Reconnection** (TODO):
|
|
||||||
- Connect to same peer
|
|
||||||
- Previous terminals automatically restored
|
|
||||||
- Recent output displayed
|
|
||||||
|
|
||||||
## Implementation Issues & TODOs
|
|
||||||
|
|
||||||
### Critical Missing Features
|
|
||||||
|
|
||||||
1. **Service ID Storage & Recovery**
|
|
||||||
- Need to store service_id in peer config when persistence enabled
|
|
||||||
- Pass service_id in LoginRequest for reconnection
|
|
||||||
- Handle service_id in server login flow
|
|
||||||
- Return terminal list in LoginResponse
|
|
||||||
|
|
||||||
2. **Protocol Extensions Needed**
|
|
||||||
```protobuf
|
|
||||||
// In LoginRequest
|
|
||||||
message Terminal {
|
|
||||||
string service_id = 1; // For reconnection
|
|
||||||
bool persistent = 2; // Request persistence
|
|
||||||
}
|
|
||||||
|
|
||||||
// In LoginResponse
|
|
||||||
message TerminalServiceInfo {
|
|
||||||
string service_id = 1;
|
|
||||||
repeated TerminalSessionInfo sessions = 2;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Terminal Recovery Flow**
|
|
||||||
- Add RecoverTerminal action to restore specific terminal
|
|
||||||
- Send buffered output on reconnection
|
|
||||||
- Handle terminal size on recovery
|
|
||||||
- UI to show available terminals
|
|
||||||
|
|
||||||
### Current Design Issues
|
|
||||||
|
|
||||||
1. **Service Pattern Mismatch**
|
|
||||||
- Terminal service forced into broadcast service pattern
|
|
||||||
- Should be direct connection resource, not shared service
|
|
||||||
- Complex routing through service registry unnecessary
|
|
||||||
|
|
||||||
2. **Global State Management**
|
|
||||||
- TERMINAL_SERVICES static HashMap may cause issues
|
|
||||||
- No proper service discovery mechanism
|
|
||||||
- Cleanup task is global, not per-connection
|
|
||||||
|
|
||||||
3. **Resource Limits Missing**
|
|
||||||
- No limit on terminals per service
|
|
||||||
- No limit on buffer size per terminal
|
|
||||||
- No limit on total services
|
|
||||||
- Could lead to resource exhaustion
|
|
||||||
|
|
||||||
4. **Security Concerns**
|
|
||||||
- No authentication for service recovery
|
|
||||||
- Service IDs are predictable (just UUID)
|
|
||||||
- No encryption of buffered terminal output
|
|
||||||
- No access control between users
|
|
||||||
|
|
||||||
### Performance Optimizations Needed
|
|
||||||
|
|
||||||
1. **Output Reading**
|
|
||||||
- Currently polls at 33fps regardless of activity
|
|
||||||
- Should use event-driven I/O (epoll/kqueue)
|
|
||||||
- Batch small outputs to reduce messages
|
|
||||||
|
|
||||||
2. **Buffer Management**
|
|
||||||
- Ring buffer could be more efficient
|
|
||||||
- Consider compression for stored output
|
|
||||||
- Implement smart truncation (keep last N complete lines)
|
|
||||||
|
|
||||||
3. **Message Overhead**
|
|
||||||
- Each output chunk creates new protobuf message
|
|
||||||
- Could batch multiple terminal outputs
|
|
||||||
- Consider streaming protocol for continuous output
|
|
||||||
|
|
||||||
### Platform-Specific Issues
|
|
||||||
|
|
||||||
1. **Windows**
|
|
||||||
- ConPTY support needs testing
|
|
||||||
- Non-blocking I/O handled differently
|
|
||||||
- Shell detection could be improved
|
|
||||||
|
|
||||||
2. **Mobile (Android/iOS)**
|
|
||||||
- Terminal feature disabled by conditional compilation
|
|
||||||
- Need to evaluate mobile terminal support
|
|
||||||
- Touch keyboard integration needed
|
|
||||||
|
|
||||||
### Testing Requirements
|
|
||||||
|
|
||||||
1. **Unit Tests Needed**
|
|
||||||
- Terminal service lifecycle
|
|
||||||
- Cleanup logic edge cases
|
|
||||||
- Buffer management
|
|
||||||
- Message serialization
|
|
||||||
|
|
||||||
2. **Integration Tests**
|
|
||||||
- Multi-terminal scenarios
|
|
||||||
- Reconnection flows
|
|
||||||
- Cleanup timing
|
|
||||||
- Resource limits
|
|
||||||
|
|
||||||
3. **Stress Tests**
|
|
||||||
- Many terminals per connection
|
|
||||||
- Large output volumes
|
|
||||||
- Rapid connect/disconnect
|
|
||||||
- Long-running sessions
|
|
||||||
|
|
||||||
### Alternative Designs to Consider
|
|
||||||
|
|
||||||
1. **Direct Terminal Management**
|
|
||||||
```rust
|
|
||||||
// In Connection struct
|
|
||||||
terminals: HashMap<i32, TerminalSession>,
|
|
||||||
|
|
||||||
// No service pattern, direct management
|
|
||||||
async fn handle_terminal_action(&mut self, action) {
|
|
||||||
match action {
|
|
||||||
Open => self.open_terminal(),
|
|
||||||
Data => self.terminal_input(),
|
|
||||||
// etc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Actor-Based Design**
|
|
||||||
- Each terminal as an actor
|
|
||||||
- Message passing for I/O
|
|
||||||
- Better isolation and error handling
|
|
||||||
|
|
||||||
3. **Session Manager Service**
|
|
||||||
- One global terminal manager
|
|
||||||
- Connections request terminals from manager
|
|
||||||
- Cleaner separation of concerns
|
|
||||||
|
|
||||||
### Documentation Gaps
|
|
||||||
|
|
||||||
1. **API Documentation**
|
|
||||||
- Document all public methods
|
|
||||||
- Add examples for common operations
|
|
||||||
- Document error conditions
|
|
||||||
|
|
||||||
2. **Configuration**
|
|
||||||
- Document all timeouts and limits
|
|
||||||
- How to configure shell/terminal
|
|
||||||
- Platform-specific settings
|
|
||||||
|
|
||||||
3. **Troubleshooting Guide**
|
|
||||||
- Common issues and solutions
|
|
||||||
- Debug logging interpretation
|
|
||||||
- Performance tuning
|
|
||||||
|
|
||||||
### Future Feature Ideas
|
|
||||||
|
|
||||||
1. **Advanced Terminal Features**
|
|
||||||
- Terminal sharing (multiple users, one terminal)
|
|
||||||
- Session recording and playback
|
|
||||||
- File transfer through terminal (zmodem)
|
|
||||||
- Custom color schemes
|
|
||||||
- Font configuration
|
|
||||||
|
|
||||||
2. **Integration Features**
|
|
||||||
- SSH key forwarding
|
|
||||||
- Environment variable injection
|
|
||||||
- Working directory synchronization
|
|
||||||
- Shell integration (prompt markers, etc)
|
|
||||||
|
|
||||||
3. **Management Features**
|
|
||||||
- Terminal session monitoring
|
|
||||||
- Usage statistics
|
|
||||||
- Audit logging
|
|
||||||
- Rate limiting
|
|
||||||
|
|
||||||
### Refactoring Suggestions
|
|
||||||
|
|
||||||
1. **Separate Concerns**
|
|
||||||
- Split terminal_service.rs into multiple files
|
|
||||||
- Separate PTY management from service logic
|
|
||||||
- Extract buffer management to own module
|
|
||||||
|
|
||||||
2. **Improve Error Handling**
|
|
||||||
- Use proper error types, not strings
|
|
||||||
- Add error recovery mechanisms
|
|
||||||
- Better error reporting to client
|
|
||||||
|
|
||||||
3. **Configuration Management**
|
|
||||||
- Make timeouts configurable
|
|
||||||
- Add feature flags for experimental features
|
|
||||||
- Environment-based configuration
|
|
||||||
Reference in New Issue
Block a user