Skip to content

Instantly share code, notes, and snippets.

@foxt
Created June 10, 2023 01:34
Show Gist options
  • Save foxt/86e47b1f591234fa0556ba64e9223b6c to your computer and use it in GitHub Desktop.
Save foxt/86e47b1f591234fa0556ba64e9223b6c to your computer and use it in GitHub Desktop.
SSH-like command to connect to a Proxmox node
const ws = require("ws")
var user,host,node;
var connString = process.argv[2]
try {
user = connString.split("@")[0]
host = connString.split("@")[1].split("!")[0]
node = connString.split("@")[1].split("!")[1]
if (!user || !host || !node) throw new Error("Invalid connection string")
} catch(e) {
console.error("Invalid connection string, for example root@pve.foxt.dev:8006!cana")
process.exit(1)
}
// this is a hack to hide the password from the command line
require("child_process").execSync("stty -echo", {stdio: "inherit"})
// Read the password from stdin
process.stdout.write("Password: ")
process.stdin.resume()
process.stdin.setEncoding("utf8")
process.stdin.on("data", async (data) => {
password = data.toString().trim()
process.stdin.removeAllListeners("data")
process.stdin.pause()
process.stdin.setRawMode(false)
process.stdout.write("\n")
// allow self-signed certs
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
// Authenticate to Proxmox
const ticketReq = await fetch(`https://${host}/api2/json/access/ticket`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: user,
password: password,
realm: "pam"
})
})
if (!ticketReq.ok) return console.error("Failed to get ticket" + ticketReq.status)
const auth = (await ticketReq.json()).data
// Get a terminal session
const wsReq = await fetch(`https://${host}/api2/json/nodes/${node}/termproxy`, {
method: "POST",
headers: {
"Cookie": `PVEAuthCookie=${auth.ticket};`,
"CSRFPreventionToken": auth.CSRFPreventionToken
},
});
if (!wsReq.ok) return console.error("Failed to get websocket" + wsReq.status)
const wsData = await wsReq.json()
// Connect to the websocket
const wsClient = new ws(`wss://${host}/api2/json/nodes/${node}/vncwebsocket?port=${wsData.data.port}&vncticket=${encodeURIComponent(wsData.data.ticket)}`,{
headers: {
"Cookie": `PVEAuthCookie=${auth.ticket};`,
}
});
// Send the auth token (again) to the websocket on connect
wsClient.on("open", () => {
console.log("Connected to websocket")
wsClient.send(wsData.data.user + ":" +wsData.data.ticket);
wsClient.send("\n");
// Send the current size to the websocket
const {rows, columns} = process.stdout
wsClient.send("1:" + columns + ":" + rows + ":")
process.stdin.resume()
})
// Write data from the websocket to stdout
wsClient.on("message", (data) => {
process.stdout.write(data)
})
// Switch to raw mode so w recieve unbuffered input including keys like backspace
process.stdin.setRawMode(true)
// Write data from stdin to the websocket
// Format: 0:LENGTH:DATA
process.stdin.on("data", (data) => {
wsClient.send("0:" + data.length + ":" + data.toString())
})
// On resize, send the new size to the websocket
// Format: 1:COLS:ROWS:
process.on("SIGWINCH", () => {
const {rows, columns} = process.stdout
wsClient.send("1:" + columns + ":" + rows + ":")
})
wsClient.on("close", () => {
console.log("Websocket closed")
process.exit(0)
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment