Skip to content

Commit 5491b37

Browse files
authored
Merge pull request #1004 from afbjorklund/vnc
Add VNC video display including password
2 parents eb49205 + 8f88336 commit 5491b37

File tree

13 files changed

+196
-4
lines changed

13 files changed

+196
-4
lines changed

docs/experimental.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ The following features are experimental and subject to change:
55
- `mountType: 9p`
66
- `vmType: vz` and relevant configurations (`mountType: virtiofs`, `rosetta`, `[]networks.vzNAT`)
77
- `arch: riscv64`
8+
- `video.display: vnc` and relevant configuration (`video.vnc.display`)

docs/internal.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ SSH:
5656
- `ssh.sock`: SSH control master socket
5757
- `ssh.config`: SSH config file for `ssh -F`. Not consumed by Lima itself.
5858

59+
VNC:
60+
- `vncdisplay`: VNC display host/port
61+
- `vncpassword`: VNC display password
62+
5963
Guest agent:
6064
- `ga.sock`: Forwarded to `/run/lima-guestagent.sock` in the guest, via SSH
6165

examples/default.yaml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,21 @@ firmware:
245245
legacyBIOS: null
246246

247247
video:
248-
# QEMU display, e.g., "none", "cocoa", "sdl", "gtk", "default".
248+
# QEMU display, e.g., "none", "cocoa", "sdl", "gtk", "vnc", "default".
249249
# Choosing "none" will hide the video output, and not show any window.
250+
# Choosing "vnc" will use a network server, and not show any window.
250251
# Choosing "default" will pick the first available of: gtk, sdl, cocoa.
251-
# As of QEMU v6.2, enabling this is known to have negative impact
252+
# As of QEMU v6.2, enabling anything but none or vnc is known to have negative impact
252253
# on performance on macOS hosts: https://gitlab.com/qemu-project/qemu/-/issues/334
253254
# 🟢 Builtin default: "none"
254255
display: null
256+
# VNC (Virtual Network Computing) is a platform-independent graphical
257+
# desktop-sharing system that uses the Remote Frame Buffer protocol (RFB)
258+
vnc:
259+
# VNC display, e.g.,"to=L", "host:d", "unix:path", "none"
260+
# By convention the TCP port is 5900+d, connections from any host.
261+
# 🟢 Builtin default: "127.0.0.1:0,to=9"
262+
display: null
255263

256264
# The instance can get routable IP addresses from the vmnet framework using
257265
# https://github.com/lima-vm/socket_vmnet.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ require (
2828
github.com/norouter/norouter v0.6.3
2929
github.com/nxadm/tail v1.4.8
3030
github.com/opencontainers/go-digest v1.0.0
31+
github.com/sethvargo/go-password v0.2.0
3132
github.com/sirupsen/logrus v1.9.0
3233
github.com/spf13/cobra v1.6.1
3334
github.com/xorcare/pointer v1.2.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
496496
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
497497
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
498498
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
499+
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
500+
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
499501
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
500502
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
501503
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=

pkg/driver/driver.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ type Driver interface {
1515
Start(_ context.Context) (chan error, error)
1616

1717
Stop(_ context.Context) error
18+
19+
ChangeDisplayPassword(_ context.Context, password string) error
20+
21+
GetDisplayConnection(_ context.Context) (string, error)
1822
}
1923

2024
type BaseDriver struct {
@@ -39,3 +43,11 @@ func (d *BaseDriver) Start(_ context.Context) (chan error, error) {
3943
func (d *BaseDriver) Stop(_ context.Context) error {
4044
return nil
4145
}
46+
47+
func (d *BaseDriver) ChangeDisplayPassword(_ context.Context, password string) error {
48+
return nil
49+
}
50+
51+
func (d *BaseDriver) GetDisplayConnection(_ context.Context) (string, error) {
52+
return "", nil
53+
}

pkg/hostagent/hostagent.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/lima-vm/lima/pkg/store"
3333
"github.com/lima-vm/lima/pkg/store/filenames"
3434
"github.com/lima-vm/sshocker/pkg/ssh"
35+
"github.com/sethvargo/go-password/password"
3536
"github.com/sirupsen/logrus"
3637
)
3738

@@ -251,6 +252,11 @@ func (a *HostAgent) emitEvent(_ context.Context, ev events.Event) {
251252
}
252253
}
253254

255+
func generatePassword(length int) (string, error) {
256+
// avoid any special symbols, to make it easier to copy/paste
257+
return password.Generate(length, length/4, 0, false, false)
258+
}
259+
254260
func (a *HostAgent) Run(ctx context.Context) error {
255261
defer func() {
256262
exitingEv := events.Event{
@@ -285,6 +291,50 @@ func (a *HostAgent) Run(ctx context.Context) error {
285291
return err
286292
}
287293

294+
if *a.y.Video.Display == "vnc" {
295+
vncdisplay, vncoptions, _ := strings.Cut(*a.y.Video.VNC.Display, ",")
296+
vnchost, vncnum, err := net.SplitHostPort(vncdisplay)
297+
if err != nil {
298+
return err
299+
}
300+
n, err := strconv.Atoi(vncnum)
301+
if err != nil {
302+
return err
303+
}
304+
vncport := strconv.Itoa(5900 + n)
305+
vncpwdfile := filepath.Join(a.instDir, filenames.VNCPasswordFile)
306+
vncpasswd, err := generatePassword(8)
307+
if err != nil {
308+
return err
309+
}
310+
if err := a.driver.ChangeDisplayPassword(ctx, vncpasswd); err != nil {
311+
return err
312+
}
313+
if err := os.WriteFile(vncpwdfile, []byte(vncpasswd), 0600); err != nil {
314+
return err
315+
}
316+
if strings.Contains(vncoptions, "to=") {
317+
vncport, err = a.driver.GetDisplayConnection(ctx)
318+
if err != nil {
319+
return err
320+
}
321+
p, err := strconv.Atoi(vncport)
322+
if err != nil {
323+
return err
324+
}
325+
vncnum = strconv.Itoa(p - 5900)
326+
vncdisplay = net.JoinHostPort(vnchost, vncnum)
327+
}
328+
vncfile := filepath.Join(a.instDir, filenames.VNCDisplayFile)
329+
if err := os.WriteFile(vncfile, []byte(vncdisplay), 0600); err != nil {
330+
return err
331+
}
332+
vncurl := "vnc://" + net.JoinHostPort(vnchost, vncport)
333+
logrus.Infof("VNC server running at %s <%s>", vncdisplay, vncurl)
334+
logrus.Infof("VNC Display: `%s`", vncfile)
335+
logrus.Infof("VNC Password: `%s`", vncpwdfile)
336+
}
337+
288338
stBase := events.Status{
289339
SSHLocalPort: a.sshLocalPort,
290340
}

pkg/limayaml/defaults.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
186186
y.Video.Display = pointer.String("none")
187187
}
188188

189+
if y.Video.VNC.Display == nil {
190+
y.Video.VNC.Display = d.Video.VNC.Display
191+
}
192+
if o.Video.VNC.Display != nil {
193+
y.Video.VNC.Display = o.Video.VNC.Display
194+
}
195+
if y.Video.VNC.Display == nil || *y.Video.VNC.Display == "" {
196+
y.Video.VNC.Display = pointer.String("127.0.0.1:0,to=9")
197+
}
198+
189199
if y.Firmware.LegacyBIOS == nil {
190200
y.Firmware.LegacyBIOS = d.Firmware.LegacyBIOS
191201
}

pkg/limayaml/defaults_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ func TestFillDefault(t *testing.T) {
7272
},
7373
Video: Video{
7474
Display: pointer.String("none"),
75+
VNC: VNCOptions{
76+
Display: pointer.String("127.0.0.1:0,to=9"),
77+
},
7578
},
7679
HostResolver: HostResolver{
7780
Enabled: pointer.Bool(true),
@@ -262,6 +265,9 @@ func TestFillDefault(t *testing.T) {
262265
},
263266
Video: Video{
264267
Display: pointer.String("cocoa"),
268+
VNC: VNCOptions{
269+
Display: pointer.String("none"),
270+
},
265271
},
266272
HostResolver: HostResolver{
267273
Enabled: pointer.Bool(false),
@@ -418,6 +424,9 @@ func TestFillDefault(t *testing.T) {
418424
},
419425
Video: Video{
420426
Display: pointer.String("cocoa"),
427+
VNC: VNCOptions{
428+
Display: pointer.String("none"),
429+
},
421430
},
422431
HostResolver: HostResolver{
423432
Enabled: pointer.Bool(false),

pkg/limayaml/limayaml.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,14 @@ type Firmware struct {
122122
LegacyBIOS *bool `yaml:"legacyBIOS,omitempty" json:"legacyBIOS,omitempty"`
123123
}
124124

125+
type VNCOptions struct {
126+
Display *string `yaml:"display,omitempty" json:"display,omitempty"`
127+
}
128+
125129
type Video struct {
126130
// Display is a QEMU display string
127-
Display *string `yaml:"display,omitempty" json:"display,omitempty"`
131+
Display *string `yaml:"display,omitempty" json:"display,omitempty"`
132+
VNC VNCOptions `yaml:"vnc" json:"vnc"`
128133
}
129134

130135
type ProvisionMode = string

0 commit comments

Comments
 (0)