feat: add pve lxc xtermjs console

This commit is contained in:
2024-11-06 13:49:41 +00:00
parent b9d879330a
commit bde42ca729
7 changed files with 289 additions and 28 deletions
+127
View File
@@ -0,0 +1,127 @@
package lib
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
)
type PVEServer struct {
HostName string
Port int
Username string
Password string
}
type PVERequestInit struct {
Body map[string]string
Ticket string
CSRF string
}
func fetch(method string, url string, cfg *PVERequestInit) ([]byte, error) {
var body io.Reader
if cfg.Body != nil {
json, _ := json.Marshal(cfg.Body)
body = bytes.NewBuffer(json)
}
req, _ := http.NewRequest(method, url, body)
if cfg.Ticket != "" {
req.Header.Add("Cookie", "PVEAuthCookie="+cfg.Ticket)
}
if cfg.CSRF != "" {
req.Header.Add("CSRFPreventionToken", cfg.CSRF)
}
if body != nil {
req.Header.Add("Content-Type", "application/json")
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Transport: tr,
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("request failed with status code %d", resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
type PVEAccessTicket struct {
CSRFPreventionToken string `json:"CSRFPreventionToken"`
Ticket string `json:"ticket"`
Username string `json:"username"`
}
func (pve *PVEServer) GetAccessTicket() (*PVEAccessTicket, error) {
url := fmt.Sprintf("https://%s:%d/api2/json/access/ticket", pve.HostName, pve.Port)
body, err := fetch("POST", url, &PVERequestInit{Body: map[string]string{
"username": pve.Username,
"password": pve.Password,
}})
if err != nil {
return nil, err
}
var res struct {
Data PVEAccessTicket `json:"data"`
}
if err := json.Unmarshal(body, &res); err != nil {
return nil, err
}
return &res.Data, nil
}
type PVEInstance struct {
Type string
Node string
VMID string
}
type PVEVNCTicketData struct {
Port string `json:"port"`
User string `json:"user"`
Ticket string `json:"ticket"`
CERT string `json:"cert"`
Upid string `json:"upid"`
}
func (pve *PVEServer) GetVNCTicket(access *PVEAccessTicket, instance *PVEInstance, isVNC bool) (*PVEVNCTicketData, error) {
proxyType := "termproxy"
if isVNC {
proxyType = "vncproxy"
}
url := fmt.Sprintf("https://%s:%d/api2/json/nodes/%s/%s/%s/%s",
pve.HostName, pve.Port, instance.Node, instance.Type, instance.VMID, proxyType)
body, err := fetch("POST", url, &PVERequestInit{Ticket: access.Ticket, CSRF: access.CSRFPreventionToken})
if err != nil {
return nil, err
}
var res struct {
Data PVEVNCTicketData `json:"data"`
}
if err := json.Unmarshal(body, &res); err != nil {
return nil, err
}
return &res.Data, nil
}
+96
View File
@@ -0,0 +1,96 @@
package lib
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"net/url"
"strconv"
"strings"
fastWs "github.com/fasthttp/websocket"
"github.com/gofiber/contrib/websocket"
)
type PVEConfig struct {
HostName string
User string
Password string
Port int
PrivateKey string
PrivateKeyPassphrase string
}
func (pve *PVEServer) NewTerminalSession(c *websocket.Conn, access *PVEAccessTicket, instance *PVEInstance, ticket *PVEVNCTicketData) error {
url := fmt.Sprintf("wss://%s:%d/api2/json/nodes/%s/%s/%s/vncwebsocket?port=%s&vncticket=%s",
pve.HostName, pve.Port, instance.Node, instance.Type, instance.VMID, ticket.Port, url.QueryEscape(ticket.Ticket))
headers := http.Header{}
headers.Add("Authorization", "PVEAPIToken="+access.Username)
headers.Add("Cookie", "PVEAuthCookie="+access.Ticket)
dialer := fastWs.Dialer{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
ws, _, err := dialer.Dial(url, headers)
if err != nil {
log.Println("Error connecting to Proxmox WebSocket:", err)
return err
}
defer ws.Close()
// Send first ticket line
ws.WriteMessage(fastWs.TextMessage, []byte(fmt.Sprintf("%s:%s\n", access.Username, access.Ticket)))
// https://github.com/proxmox/pve-xtermjs/blob/master/README
go func() {
for {
t, msg, err := c.ReadMessage()
if err != nil {
log.Println("Error reading from client:", err)
break
}
if strings.HasPrefix(string(msg), "\x01") {
parts := strings.Split(string(msg[1:]), ",")
if len(parts) == 2 {
width, _ := strconv.Atoi(parts[0])
height, _ := strconv.Atoi(parts[1])
ws.WriteMessage(fastWs.TextMessage, []byte(fmt.Sprintf("1:%d:%d:", width, height)))
}
continue
}
msg = []byte(fmt.Sprintf("0:%d:%s\n", len(msg), string(msg)))
err = ws.WriteMessage(t, msg)
if err != nil {
log.Println("Error writing to Proxmox:", err)
break
}
}
}()
for {
t, msg, err := ws.ReadMessage()
if err != nil {
log.Println("Error reading from Proxmox:", err)
break
}
if string(msg) == "OK" {
continue
}
err = c.WriteMessage(t, msg)
if err != nil {
log.Println("Error writing to client:", err)
break
}
}
return nil
}
@@ -145,9 +145,10 @@ func NewSSHWebsocketSession(c *websocket.Conn, cfg *SSHConfig) error {
height, _ := strconv.Atoi(parts[1])
session.WindowChange(height, width)
}
} else {
stdinPipe.Write(msg)
continue
}
stdinPipe.Write(msg)
}
}()