Skip to content

feat(server): allow forwarding VNC port locally - #1515

Open
phm07 wants to merge 2 commits into
mainfrom
server-vnc
Open

phm07 wants to merge 2 commits into
mainfrom
server-vnc

Conversation

@phm07

@phm07 phm07 commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

This PR adds a new hcloud server vnc command that requests a web console and then forwards the port locally. This makes it possible to use any VNC client.

@phm07
phm07 requested a review from a team as a code owner October 15, 2026 12:16
@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 28.57143% with 65 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.13%. Comparing base (06903a4) to head (7ef434f).

Files with missing lines Patch % Lines
internal/cmd/server/vnc.go 20.00% 44 Missing ⚠️
internal/cmd/util/vncutil/websocket.go 38.23% 21 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1515      +/-   ##
==========================================
- Coverage   79.61%   79.13%   -0.48%     
==========================================
  Files         305      307       +2     
  Lines        9790     9864      +74     
==========================================
+ Hits         7794     7806      +12     
- Misses       1996     2058      +62     
Flag Coverage Δ
e2e 54.85% <28.57%> (-0.31%) ⬇️
unit 73.78% <13.18%> (-0.44%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread internal/cmd/server/vnc.go Resolved
@phm07
phm07 force-pushed the server-screenshot branch 2 times, most recently from 200459d to 3e78fe2 Compare November 12, 2026 12:57
Base automatically changed from server-screenshot to main November 12, 2026 12:59

@jooola jooola left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, we still need a few extra things and fix the few nits.

var VNCCmd = base.Cmd{
BaseCobraCommand: func(client hcapi2.Client) *cobra.Command {
cmd := &cobra.Command{
Use: "vnc-port-foward [options] <server>",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Use: "vnc-port-foward [options] <server>",
Use: "vnc-port-forward [options] <server>",

cmd.Flags().IntP("port", "p", 5900, "Local port to listen on (0 picks a random free port)")
return cmd
},
Experimental: base.ExperimentalWrapper("The vnc-port-foward command", "experimental", ""),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Experimental: base.ExperimentalWrapper("The vnc-port-foward command", "experimental", ""),
Experimental: base.ExperimentalWrapper("The vnc-port-forward command", "experimental", ""),

return fmt.Errorf("invalid port: %d", port)
}

server, _, err := s.Client().Server().Get(s, idOrName)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check if the server is running and return a nice error message? What happens if the server is off or rebuilding?

return err
}

cmd.Printf("Accepted connection from %s\n", conn.RemoteAddr())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be at the top of the function?

I understand this log message as: We accepted the connection of a VNC client on port 5900, and then start the process of request the console URL, and opening the websocket connection.

Comment on lines +102 to +104
if err := s.WaitForActions(ctx, cmd, result.Action); err != nil {
return err
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should only log a simple "waiting" message, instead of the full blown waiting with prorgess bar. WDYT?

@@ -0,0 +1,115 @@
package server

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename this file to vnc_port_foward.go ?

Could we also have some test for this command?

"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var VNCCmd = base.Cmd{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var VNCCmd = base.Cmd{
var VNCPortForwardCmd = base.Cmd{

Comment on lines +42 to +69
func ProxyWebsocket(ctx context.Context, conn net.Conn, ws *websocket.Conn) error {
closeAll := func() {
_ = conn.Close()
_ = ws.Close()
}
stop := context.AfterFunc(ctx, closeAll)
defer stop()

errs := make(chan error, 2)
go func() {
_, err := io.Copy(ws, conn)
errs <- err
}()
go func() {
_, err := io.Copy(conn, ws)
errs <- err
}()

// close both connections if one side errors
err := <-errs
closeAll()
<-errs

if ctx.Err() != nil || errors.Is(err, net.ErrClosed) {
return nil
}
return err
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe some debug logs could be useful, in case something is not working as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants