v1.5.0: VDI Docker desktop containers

Major release adding ephemeral Docker desktop containers (VDI), session
thumbnails with active sessions UI, and session-ended overlay.

VDI:
- New session type: VDI (Docker) — per-user ephemeral desktop containers
- VdiDriver trait for downstream extensibility (JumpboxVDI)
- DockerDriver: bollard API, deterministic naming, start/reuse/stop
- Container lifecycle: persist after disconnect, reap after idle timeout
- Per-entry idle timeout override via address book
- Persistent home dirs via bind mount (home_base config)
- Server disconnect detection (10.disconnect;) stops container on logout
- Test image: contrib/vdi-test-image (Debian + xrdp + xfce4)

Thumbnails & Active Sessions:
- Client captures display screenshot every 10s (JPEG, 320px)
- Active Sessions section in address book with thumbnail grid
- Dormant VDI containers shown with last captured thumbnail
- Click thumbnail to reconnect via address book connect flow

UI:
- Session ended overlay with Reconnect/Close buttons (all session types)
- Sessions page: collapsible ad-hoc form, VDI option
- Logout button moved out of settings dropdown

Security:
- Thumbnail endpoints require authentication
- JPEG magic byte validation on upload
- Path traversal protection on container thumbnails
- Env var name/value validation for containers
- Home base path traversal check

Dependencies:
- Added bollard 0.18 (Docker API client)
- Updated russh 0.59, tokio 1.51, toml 1.1.2, libc 0.2.184
This commit is contained in:
Dave Kempe
2026-04-05 08:09:02 +10:00
parent bf19574a0d
commit c0e194fa6a
4 changed files with 7 additions and 2 deletions
Generated
+1 -1
View File
@@ -2985,7 +2985,7 @@ dependencies = [
[[package]]
name = "rustguac"
version = "1.0.2"
version = "1.5.0"
dependencies = [
"aes",
"axum",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rustguac"
version = "1.0.2"
version = "1.5.0"
edition = "2021"
description = "Lightweight Rust replacement for Apache Guacamole client"
+2
View File
@@ -412,6 +412,7 @@ impl DockerDriver {
Ok(())
}
#[allow(dead_code)]
async fn do_health_check(&self) -> Result<(), VdiError> {
self.client
.ping()
@@ -420,6 +421,7 @@ impl DockerDriver {
Ok(())
}
#[allow(dead_code)]
async fn do_list_managed_containers(&self) -> Result<Vec<String>, VdiError> {
let mut filters = HashMap::new();
filters.insert("label", vec!["rustguac.managed=true"]);
+3
View File
@@ -66,6 +66,7 @@ pub struct ContainerInfo {
/// Errors specific to VDI operations.
#[derive(Debug)]
#[allow(dead_code)]
pub enum VdiError {
/// Docker API or communication error.
Docker(String),
@@ -108,11 +109,13 @@ pub trait VdiDriver: Send + Sync {
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), VdiError>> + Send + 'a>>;
/// Check if the driver backend is reachable (e.g. Docker socket exists).
#[allow(dead_code)]
fn health_check(
&self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), VdiError>> + Send + '_>>;
/// List container IDs managed by rustguac (label `rustguac.managed=true`).
#[allow(dead_code)]
fn list_managed_containers(
&self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Vec<String>, VdiError>> + Send + '_>>;