Class: Pvectl::Services::Console

Inherits:
Object
  • Object
show all
Defined in:
lib/pvectl/services/console.rb,
sig/pvectl/services/console.rbs

Overview

Orchestrates an interactive console session to a VM or container.

Handles: authentication (session ticket), termproxy setup, WebSocket URL construction, and terminal session lifecycle.

Examples:

Basic usage

service = Console.new
service.run(resource: vm, resource_path: "qemu/100", server: "https://pve1:8006",
            username: "root@pam", password: "secret", verify_ssl: true)

Defined Under Namespace

Classes: AuthenticationError, ResourceNotRunningError

Instance Method Summary collapse

Instance Method Details

#authenticate_with_client(client, username, password) ⇒ Hash

Authenticates using a provided REST client and returns session data.

Parameters:

  • clean REST client

  • Proxmox username

  • password

Returns:

  • { ticket:, csrf_token: }

Raises:

  • on failure



118
119
120
121
122
123
124
125
126
127
128
# File 'lib/pvectl/services/console.rb', line 118

def authenticate_with_client(client, username, password)
  response = client["access/ticket"].post(username: username, password: password)
  data = JSON.parse(response.body, symbolize_names: true)[:data]

  {
    ticket: data[:ticket],
    csrf_token: data[:CSRFPreventionToken]
  }
rescue StandardError => e
  raise AuthenticationError, "Authentication failed: #{e.message}"
end

#build_websocket_url(server:, node:, resource_path:, port:, ticket:) ⇒ String

Builds the WebSocket URL for vncwebsocket endpoint.

Parameters:

  • Proxmox server URL (e.g. "https://pve1:8006")

  • node name

  • resource path (e.g. "qemu/100" or "lxc/200")

  • termproxy port

  • PVEVNC ticket

Returns:

  • full WebSocket URL



34
35
36
37
38
39
40
41
42
43
# File 'lib/pvectl/services/console.rb', line 34

def build_websocket_url(server:, node:, resource_path:, port:, ticket:)
  uri = URI.parse(server)
  scheme = uri.scheme == "https" ? "wss" : "ws"
  host = uri.host
  ws_port = uri.port || 8006
  encoded_ticket = URI.encode_www_form_component(ticket)

  "#{scheme}://#{host}:#{ws_port}/api2/json/nodes/#{node}/#{resource_path}/vncwebsocket" \
    "?port=#{port}&vncticket=#{encoded_ticket}"
end

#open_termproxy(client, auth, node:, resource_path:) ⇒ Hash

Opens a termproxy session using session auth credentials.

Parameters:

  • REST client

  • session auth with :ticket and :csrf_token

  • Proxmox node name

  • e.g. "qemu/100" or "lxc/200"

Returns:

  • { port:, ticket:, user: }



137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/pvectl/services/console.rb', line 137

def open_termproxy(client, auth, node:, resource_path:)
  endpoint = "nodes/#{node}/#{resource_path}/termproxy"
  response = client[endpoint].post(
    {},
    cookies: { PVEAuthCookie: auth[:ticket] },
    CSRFPreventionToken: auth[:csrf_token]
  )
  data = JSON.parse(response.body, symbolize_names: true)[:data]

  { port: data[:port], ticket: data[:ticket], user: data[:user] }
rescue StandardError => e
  raise AuthenticationError, "Termproxy failed: #{e.message}"
end

#run(resource:, resource_path:, server:, username:, password:, verify_ssl:) ⇒ void

This method returns an undefined value.

Runs a console session end-to-end.

All API calls (authenticate, termproxy) use the same session-based REST client to ensure PVEVNC ticket and PVEAuthCookie share the same identity. Using a token-based connection for termproxy would generate a ticket bound to the token identity (e.g., "root@pam!pvectl"), which is rejected by the WebSocket endpoint expecting a session cookie for "root@pam".

Parameters:

  • target resource

  • API path segment ("qemu/vmid" or "lxc/ctid")

  • Proxmox server URL

  • username for auth

  • password for auth

  • SSL verification flag



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/pvectl/services/console.rb', line 73

def run(resource:, resource_path:, server:, username:, password:, verify_ssl:)
  validate_resource_running!(resource)

  # All operations use one session-authenticated REST client
  api_url = "#{server}/api2/json/"
  session_client = RestClient::Resource.new(api_url, verify_ssl: verify_ssl)

  # 1. Authenticate
  auth = authenticate_with_client(session_client, username, password)

  # 2. Open termproxy (using session ticket, not API token)
  termproxy_data = open_termproxy(
    session_client, auth,
    node: resource.node, resource_path: resource_path
  )

  # 3. Build websocket URL
  url = build_websocket_url(
    server: server,
    node: resource.node,
    resource_path: resource_path,
    port: termproxy_data[:port],
    ticket: termproxy_data[:ticket]
  )

  # 4. Run terminal session
  session = Pvectl::Console::TerminalSession.new(
    url: url,
    cookie: "PVEAuthCookie=#{auth[:ticket]}",
    user: termproxy_data[:user],
    ticket: termproxy_data[:ticket],
    verify_ssl: verify_ssl
  )
  session.run
end

#validate_resource_running!(resource) ⇒ void

This method returns an undefined value.

Validates that the resource is in a running state.

Parameters:

  • resource to check

Raises:

  • if resource is not running



50
51
52
53
54
55
# File 'lib/pvectl/services/console.rb', line 50

def validate_resource_running!(resource)
  return if resource.status == "running"

  raise ResourceNotRunningError,
        "Resource #{resource.vmid} is not running (status: #{resource.status})"
end