Container console on an external site (via API)

elone

New Member
Jan 13, 2025
2
0
1
Hello!

I’ve done several searches, and I must admit I’m completely lost. I would like to do the following:

  • When I’m on a page of an external website (site.fr), API requests should be sent to my Proxmox.
  • The requests sent should allow me to retrieve the console of a container (or a virtual machine) on site.fr.
I’ve already tried to:

  • Use /vncproxy to get a ticket, and then use /vncwebsockets to obtain a wss link.
  • Attempt to use the root@pam password and simulate a connection to the console URL of a VM (it works, but only if the browser is already logged into Proxmox). As you can imagine, I’m not going to give my users the Proxmox credentials so they can access their machine.
So, my problem is that I can’t understand how the API works, because no matter how many wss links I generate, nothing seems to work.

I’m providing a Python code and an HTML code that you can test on your side, and I would be happy to get your feedback on them.

Thanks in advance!


Python:
sudo apt update && apt upgrade -y
sudo apt install python3-pip

mkdir /var/www
mkdir /var/www/console
mkdir /var/www/console/templates

# On a LXC machine ?
sudo apt install python3-venv
python3 -m venv /var/www/console/venv
source /var/www/console/venv/bin/activate

cd /var/www/console/
pip3 install flask requests


Python:
# server.py
from flask import Flask, render_template, request, jsonify
import requests

app = Flask(__name__)

CONFIG = {
    "base_url": "proxmoxUrl:8006/api2/json",
    "token_id": "username@auth!tokenId",
    "token_secret": "tokenSecret",
    "node": "nodeName"
}

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/get_vnc_ticket', methods=['POST'])
def get_vnc_ticket():
    # Retrieve container ID from the request
    container_id = request.json.get("container_id")
    if not container_id:
        return jsonify({"error": "Container ID is required"}), 400

    # Build the Proxmox API endpoint URL
    url = f"{CONFIG['base_url']}/nodes/{CONFIG['node']}/lxc/{container_id}/vncproxy"

    # Authenticate with Proxmox API and get the VNC ticket
    headers = {
        "Authorization": f"PVEAPIToken={CONFIG['token_id']}={CONFIG['token_secret']}"
    }

    try:
        response = requests.post(url, headers=headers, verify=False)
        response.raise_for_status()
        data = response.json()["data"]
        return jsonify({
            "vncticket": data["ticket"],
            "port": data["port"]
        })
    except requests.exceptions.RequestException as e:
        return jsonify({"error": str(e)}), 500

@app.route('/get_vnc_websocket', methods=['GET'])
def get_vnc_websocket():
    # Retrieve container ID, vncticket, port, and protocol
    container_id = request.args.get("container_id")
    vncticket = request.args.get("vncticket")
    port = request.args.get("port")
    protocol = request.args.get("protocol", "wss")

    print(f"Received parameters - container_id: {container_id}, vncticket: {vncticket}, port: {port}, protocol: {protocol}")

    if not container_id or not vncticket or not port:
        return jsonify({"error": "Container ID, vncticket, and port are required"}), 400

    # Ensure that the protocol is either "wss" or "https" (it was just for try the https://)
    if protocol not in ["wss", "https"]:
        return jsonify({"error": 'Protocol must be either "wss" or "https"'}), 400

    # Build the WebSocket URL (with port first and vncticket second)
    websocket_url = f"{protocol}://{CONFIG['base_url'].split('//')[1].split(':')[0]}:8006/api2/json/nodes/{CONFIG['node']}/lxc/{container_id}/vncwebsocket?port={port}&vncticket={vncticket}"
 
    return jsonify({"websocket_url": websocket_url})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
    # The server will be accessible with the IPv4 given on your machine
    # For example, you can access the server with : 123.123.123.123:5000
    # You can change the host to 127.0.0.1 if you want to try it on your local machine (if you don't have IPv4 or if you want to run it on your computer for example)


HTML:
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Proxmox LXC Console</title>
</head>
<body>
    <h1>Access LXC Console</h1>
    <form id="vnc-form">
        <label for="container_id">Container ID:</label>
        <input type="text" id="container_id" name="container_id" required>
     
        <label for="protocol">Protocol:</label>
        <select id="protocol" name="protocol">
            <option value="wss" selected>WSS</option>
            <option value="https">HTTPS</option>
        </select>
     
        <button type="submit">Get Console</button>
    </form>

    <div id="output"></div>

    <script>
        document.getElementById('vnc-form').addEventListener('submit', async (e) => {
            e.preventDefault();
            const containerId = document.getElementById('container_id').value;
            const protocol = document.getElementById('protocol').value;
         
            try {
                // Request VNC Ticket
                const ticketResponse = await fetch('/get_vnc_ticket', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ container_id: containerId })
                });
                const ticketData = await ticketResponse.json();

                if (ticketResponse.ok) {
                    const wsResponse = await fetch(`/get_vnc_websocket?container_id=${containerId}&vncticket=${encodeURIComponent(ticketData.vncticket)}&port=${ticketData.port}&protocol=${protocol}`);
                    const wsData = await wsResponse.json();

                    if (wsResponse.ok) {
                        document.getElementById('output').innerHTML = `
                            <p>WebSocket URL: <a href="${wsData.websocket_url}" target="_blank">${wsData.websocket_url}</a></p>
                        `;
                    } else {
                        document.getElementById('output').textContent = wsData.error || 'Failed to retrieve WebSocket URL.';
                    }
                } else {
                    document.getElementById('output').textContent = ticketData.error || 'Failed to retrieve VNC Ticket.';
                }
            } catch (error) {
                document.getElementById('output').textContent = error.message;
            }
        });
    </script>
</body>
</html>

PS : The protocol wss or https does not impact the URL. So don't need to search the issue here :)
 
Last edited:

About

The Proxmox community has been around for many years and offers help and support for Proxmox VE, Proxmox Backup Server, and Proxmox Mail Gateway.
We think our community is one of the best thanks to people like you!

Get your subscription!

The Proxmox team works very hard to make sure you are running the best software and getting stable updates and security enhancements, as well as quick enterprise support. Tens of thousands of happy customers have a Proxmox subscription. Get yours easily in our online shop.

Buy now!