Created
June 10, 2023 01:34
-
-
Save foxt/86e47b1f591234fa0556ba64e9223b6c to your computer and use it in GitHub Desktop.
SSH-like command to connect to a Proxmox node
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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