Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 2ca99eb

Browse files
committed
feat(driver-vnc): create vnc driver
Create a vnc driver for jumpstarter that opens a tunnel to connect vnc clients locally (and opens a browser directly to that tunnel). If a VNC server is running in the remote machine it will allow sharing the screen. Co-Authored-By: Claude noreply@anthropic.com Signed-off-by: Albert Esteve <aesteve@redhat.com>
1 parent fa0a3b5 commit 2ca99eb

10 files changed

Lines changed: 249 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../../packages/jumpstarter-driver-vnc/README.md
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Vnc Driver
2+
3+
`jumpstarter-driver-vnc` provides functionality for interacting with VNC servers. It allows you to create a secure, tunneled VNC session in your browser.
4+
5+
## Installation
6+
7+
```shell
8+
pip3 install --extra-index-url https://pkg.jumpstarter.dev/simple/ jumpstarter-driver-vnc
9+
```
10+
11+
## Configuration
12+
13+
The VNC driver is a composite driver that requires a TCP child driver to establish the underlying network connection. The TCP driver should be configured to point to the VNC server's host and port, which is often `127.0.0.1` from the perspective of the Jumpstarter server.
14+
15+
Example `exporter.yaml` configuration:
16+
17+
```yaml
18+
export:
19+
vnc:
20+
type: jumpstarter_driver_vnc.driver.Vnc
21+
children:
22+
tcp:
23+
type: jumpstarter_driver_network.driver.TcpNetwork
24+
config:
25+
host: "127.0.0.1"
26+
port: 5901 # Default VNC port for display :1
27+
```
28+
29+
## API Reference
30+
31+
The client class for this driver is `jumpstarter_driver_vnc.client.VNClient`.
32+
33+
### `vnc.session()`
34+
35+
This asynchronous context manager establishes a connection to the remote VNC server and provides a local web server to view the session.
36+
37+
**Usage:**
38+
39+
```python
40+
async with vnc.session() as novnc_adapter:
41+
print(f"VNC session available at: {novnc_adapter.url}")
42+
# The session remains open until the context block is exited.
43+
await novnc_adapter.wait()
44+
```
45+
46+
### CLI: `j vnc session`
47+
48+
This driver provides a convenient CLI command within the `jmp shell`.
49+
50+
**Usage:**
51+
52+
```bash
53+
# This will start the local server and print the URL.
54+
# It will remain running until you press Ctrl+C.
55+
j vnc session
56+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: jumpstarter.dev/v1alpha1
2+
kind: ExporterConfig
3+
metadata:
4+
namespace: default
5+
name: demo
6+
endpoint: grpc.jumpstarter.192.168.0.203.nip.io:8082
7+
token: "<token>"
8+
export:
9+
vnc:
10+
type: jumpstarter_driver_vnc.driver.Vnc
11+
children:
12+
tcp:
13+
type: jumpstarter_driver_network.driver.TcpNetwork
14+
config:
15+
host: "127.0.0.1"
16+
port: 5900 # Default VNC port
17+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .client import VNClient
2+
3+
VNClient = VNClient
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import webbrowser
2+
from dataclasses import dataclass
3+
4+
import click
5+
from jumpstarter_driver_composite.client import CompositeClient
6+
from jumpstarter_driver_network.adapters import NovncAdapter
7+
8+
from jumpstarter.client.decorators import driver_click_group
9+
10+
11+
@dataclass(kw_only=True)
12+
class VNClient(CompositeClient):
13+
"""Client for interacting with a VNC server."""
14+
15+
def session(self, open_browser: bool = True) -> str:
16+
"""
17+
Starts a noVNC session in the browser.
18+
19+
param open_browser:
20+
type: bool
21+
default: True
22+
help: Whether to open the browser automatically.
23+
24+
return:
25+
type: str
26+
description: The URL to the noVNC session.
27+
"""
28+
with NovncAdapter(client=self.tcp) as url:
29+
if open_browser:
30+
webbrowser.open(url)
31+
return url
32+
33+
def cli(self) -> click.Command:
34+
"""
35+
Return a click command handler for this driver.
36+
37+
Returns:
38+
The click command handler.
39+
"""
40+
41+
@driver_click_group(self)
42+
def vnc():
43+
"""Opens a noVNC session in the browser."""
44+
45+
@vnc.command()
46+
@click.option(
47+
"--no-browser",
48+
is_flag=True,
49+
help="Do not open the browser automatically, just print the URL.",
50+
)
51+
def session(no_browser: bool):
52+
"""Opens a noVNC session in the browser."""
53+
url = self.session(open_browser=not no_browser)
54+
if no_browser:
55+
click.echo(f"noVNC session available at: {url}")
56+
57+
return vnc
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from dataclasses import dataclass
2+
3+
from jumpstarter.common.exceptions import ConfigurationError
4+
from jumpstarter.driver import Driver
5+
6+
7+
@dataclass(kw_only=True)
8+
class Vnc(Driver):
9+
"""Provides access to a VNC server."""
10+
11+
def __post_init__(self):
12+
if hasattr(super(), "__post_init__"):
13+
super().__post_init__()
14+
15+
if "tcp" not in self.children:
16+
raise ConfigurationError("A tcp child is required for Vnc")
17+
18+
@classmethod
19+
def client(cls) -> str:
20+
return "jumpstarter_driver_vnc.client.VNClient"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from jumpstarter_driver_composite.client import CompositeClient
2+
from jumpstarter_driver_network.driver import TcpNetwork
3+
4+
from .driver import Vnc
5+
from jumpstarter.common.utils import serve
6+
7+
8+
def test_vnc_driver_is_composite():
9+
"""Test that the Vnc driver is a composite driver and requires a tcp child."""
10+
instance = Vnc(
11+
children={"tcp": TcpNetwork(host="127.0.0.1", port=5900)},
12+
)
13+
14+
with serve(instance) as client:
15+
assert isinstance(client, CompositeClient)
16+
assert client.tcp
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[project]
2+
name = "jumpstarter-driver-vnc"
3+
dynamic = ["version", "urls"]
4+
description = "Jumpstarter driver for VNC"
5+
readme = "README.md"
6+
license = "Apache-2.0"
7+
authors = [
8+
{ name = "Albert Esteve", email = "aesteve@redhat.com" }
9+
]
10+
requires-python = ">=3.11"
11+
dependencies = [
12+
"anyio>=4.10.0",
13+
"jumpstarter",
14+
"jumpstarter-driver-composite",
15+
"jumpstarter-driver-network",
16+
"click",
17+
]
18+
19+
[tool.hatch.version]
20+
source = "vcs"
21+
raw-options = { 'root' = '../../'}
22+
23+
[tool.hatch.metadata.hooks.vcs.urls]
24+
Homepage = "https://jumpstarter.dev"
25+
source_archive = "https://github.com/jumpstarter-dev/repo/archive/{commit_hash}.zip"
26+
27+
[tool.pytest.ini_options]
28+
addopts = "--cov --cov-report=html --cov-report=xml"
29+
log_cli = true
30+
log_cli_level = "INFO"
31+
testpaths = ["jumpstarter_driver_vnc"]
32+
asyncio_mode = "auto"
33+
34+
[build-system]
35+
requires = ["hatchling", "hatch-vcs", "hatch-pin-jumpstarter"]
36+
build-backend = "hatchling.build"
37+
38+
[tool.hatch.build.hooks.pin_jumpstarter]
39+
name = "pin_jumpstarter"
40+
41+
[dependency-groups]
42+
dev = [
43+
"pytest-cov>=6.0.0",
44+
"pytest>=8.3.3",
45+
]

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jumpstarter-driver-uboot = { workspace = true }
3535
jumpstarter-driver-iscsi = { workspace = true }
3636
jumpstarter-driver-ustreamer = { workspace = true }
3737
jumpstarter-driver-yepkit = { workspace = true }
38+
jumpstarter-driver-vnc = { workspace = true }
3839
jumpstarter-imagehash = { workspace = true }
3940
jumpstarter-kubernetes = { workspace = true }
4041
jumpstarter-protocol = { workspace = true }

uv.lock

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)