[TUTORIAL] API automation, Power ON/OFF vm and else.

Veeh

Well-Known Member
Jul 2, 2017
67
13
48
37
Dear Proxmox community,

I have been working on this for the last few days. And It might help some of you so I decided to share it.
My home setup has VM, that are not on all the time. I was using WOL + python script previously to power on VM.
With the upgrade to proxmox7, I was not working anymore, and instead of installing python2 lib, I preferred to embrace the API way.

So there it is.
The 2 following scripts, (Powershell and Bash) are sending a request to get a ticket.
then they are reusing the data from the ticket to do whatever you put in the endpoint!

Enjoy.

PowerShell:
Code:
##################################
### API AUTOMATION
# for the proxmox community
# By veeh, enjoy
#
# Requirement: not working with Powershell5.1, I did this with powershell7.

# Host info
$pve = "HOST_FQDN_OR_IP"
$node = "HOSTID"
$port = ":8006"
$vmid = "VMID"

# API info
$apiu = "user@realm"
$apip = "USER_PASSWORD"
$url_base = "https://$pve$port/api2/json"
# this is where you put what ever you want do
# https://pve.proxmox.com/pve-docs/api-viewer/
$url_end = "/nodes/$node/qemu/$vmid/status/start"
$urlqr = $url_base + $url_end
$urltk = "$url_base/access/ticket"

# Ticket creation
$ticket = curl --insecure --data "username=$apiu&password=$apip" $urltk

# Parse the ticket data to grab the cookie and token
$Array = $ticket.Split('"')
# Grab the cookie
$linecookie = $Array | select-string ticket
$linecookienb = $linecookie.LineNumber + 1
$cookie = $Array | select -Index $linecookienb
# Grab the token
$linetoken = $Array | select-string CSRFP
$linetokennb = $linetoken.LineNumber + 1
$token = $Array | select -Index $linetokennb

# proxmox api query
$headersps = @{
    "cookie" = "PVEAuthCookie=$cookie"
    "CSRFPreventionToken" = "$token"
}
Invoke-RestMethod -SkipCertificateCheck -Uri $urlqr -Method Post -Headers $headersps

Bash:
Code:
#!/bin/bash
##################################
### API AUTOMATION
# for the proxmox community
# By veeh, enjoy

#Host info
pve="HOST_FQDN_OR_IP"
node="HOSTID"
port=":8006"
vmid="VMID"

#API info
apiu="user@realm"
apip="USER_PASSWORD"
url_base="https://$pve$port/api2/json"
# this is where you put what ever you want do
# https://pve.proxmox.com/pve-docs/api-viewer/
url_end="nodes/$node/qemu/$vmid/status/start"
urlqr="$url_base/$url_end"
urltk="$url_base/access/ticket"

ticket=`curl --insecure --data "username=$apiu&password=$apip" $urltk`

# Grab cookie and token from the ticket data
cookieid=`echo $ticket | tr -t '"' '\n' | grep "PVE:$apiu"`
cookie="PVEAuthCookie=$cookieid"
ticketid=`echo $cookie | awk -F ':' '{ print $3 }'`
tokenid=`echo $ticket | tr -t '"' '\n' | grep $ticketid | grep -v PVE`
token="CSRFPreventionToken:$tokenid"

#proxmox api query
curl --insecure --cookie $cookie --header $token -X POST "$urlqr"
 
Last edited:
Hello,

I am not a expert so please could you explain to me a bit more about this script?

The bash code should be running from the device which will send the wake up command to the VM?

Which data should I change from your bash ?

Thank you
 
Hi,

This script is using the API system available with proxmox 6.7 if I remember correctly (Available in version 7 that's for sure).

What you need to feed is the host info and API credentials.
pve="HOST_FQDN_OR_IP"
node="HOSTID"
port=":8006"
vmid="VMID"
apiu="user@realm"
apip="USER_PASSWORD"

First, you need to create an account to use the API system.
Datacenter > Permissions > API Tokens
then add your user. this is your API credentials.
https://pve.proxmox.com/wiki/Proxmox_VE_API

The script is creating a ticket to run an action.
The action is defined by $url_end
url_end="nodes/$node/qemu/$vmid/status/stop" (in this case that action will stop the VM $vmid)

If your API user has the proper permission you can run whatever you want as long as it is listed here:
https://pve.proxmox.com/pve-docs/api-viewer/index.html
another example: nodes/{node}/status/reboot (this will reboot your host)

the base URL for your API is always the same https://HOSTIP:8006/api2/json
It is what comes after that will tell the system what it has to do.

The last bit will basically grab in the ticket what is required to be able to trigger the API action.

In summary, this is how it works. Your API user allows you to get a ticket, this ticket will have the necessary information to trigger the API action requested as long as your user has the right permissions.

You can run this from any device. I use it on my laptop to wake up/reboot/shut down VM on my workstation.
But you can also run this from your cellphone. With an app sending HTTP POST requests.
It's a little bit more complicated because you need a token. But it's working fine.
 
Hi
This is the proxmox hypervisor name. In the screenshot attached that would be pxmx or qxmx.
 

Attachments

  • Screenshot from 2022-12-13 08-32-59.png
    Screenshot from 2022-12-13 08-32-59.png
    6.1 KB · Views: 52
Last edited:
  • Like
Reactions: Kosh
No,

If you look at the script below,
$url_base = "https://$pve$port/api2/json"
[...]
$url_end = "/nodes/$node/qemu/$vmid/status/start"

$node is used in the api call, that's why it's just the name of your hypervisor.
$pve is used in the url and you need the hypervisor fqdn or ip.

You could have something like this:
node=NODENAME
pve=$node.yourdomain.loc

But make sure to define node before pve in that case.
 
  • Like
Reactions: Kosh
No,

If you look at the script below,
$url_base = "https://$pve$port/api2/json"
[...]
$url_end = "/nodes/$node/qemu/$vmid/status/start"

$node is used in the api call, that's why it's just the name of your hypervisor.
$pve is used in the url and you need the hypervisor fqdn or ip.

You could have something like this:
node=NODENAME
pve=$node.yourdomain.loc

But make sure to define node before pve in that case.


thanks for your clarification
unfortunately it is not yet possible to enable vm through your script
so vm starts
pvesh create /nodes/cloud-p013/qemu/137/status/start
1671008925445.png

PVE 7.2
 
Last edited:
It's working fine for me.

You can look in your syslog. You should see the api logging attemps. In my case my user is 'api@pve'
Dec 14 13:00:13 qxmx pvedaemon[2121831]: <root@pam> successful auth for user 'api@pve'
Dec 14 13:00:13 qxmx pvedaemon[2121831]: <api@pve> starting task UPID:qxmx:0011B691:05F3A38C:639A1D3D:qmstart:107:api@pve:

If you see the successful auth but not the action, I suggest you look for either the api user permissions.
Or a typo in the api call.

I remember having issues with the api user permission when I set this up. By default, the role you assign to your user will be the one for the api.
But you can also have separate permission.
 
Last edited:
You were right !! There was a mistake in the bash script.
In the cookieid variable. I forgot to replace the user field by $apiu. It was set to api@pve.
And off course it worked for me because that's the user I used...
I fixed it.
Try again
 
  • Like
Reactions: Kosh
You were right !! There was a mistake in the bash script.
In the cookieid variable. I forgot to replace the user field by $apiu. It was set to api@pve.
And off course it worked for me because that's the user I used...
I fixed it.
Try again
Hi!
yes it worked! thank you so much
Do you know if it is possible to do so that you can do restarts and so on without being tied to a specific node?
I have a large number of servers and the machine often migrates, which means that I have to edit your script manually every time when the machine with vmid 100 moved from node1 to node2/3/4....

anyway, thank you for your time!
 
I don't know the api that much but, if you check the api viewer
https://pve.proxmox.com/pve-docs/api-viewer/
You have to specify the node name. I don't think you could use a wildcard.

They might be a way to get the VM list for each node with the API. But it is just a guess
Then from the list you can run a loop to check if the vmid you are looking for is there or go to the next node.

Because you can do all that with ssh
Something like this:
Code:
#!/bin/bash
VMID=$1
hypvsr="node1 node2 node3 node4"

for i in $hypvsr;
do
    j=$(ssh $i "sudo qm list | grep $VMID")
    
    if [ ! -z "$j" ];
    then
        node=$i
        break
    fi
done

rest of the script

But you need to have a user with sudo and also no password prompt. And most of all it defeats the purpose of using the api because you could run anything from there.
 
No idea sorry Domenico.
I have not set up anything with TFA, so I would not know. maybe someone else could help you out.
Or if you use separate permission for the API token, it way me be possible to bypass the TFA. I'm just guessing at that point.
 
I've solved in another thread with the help of Folke (Proxmox Staff member):

# notice the -r in the jq command to prevent the result from being enclosed in quotation marks and escaped curl -f -s -S -k --data-urlencode "username=root@pam" --data-urlencode "password=$PASSWORD" "https://cl1:8006/api2/json/access/ticket"|jq -r '.data.ticket' > cookie curl --silent --insecure --data-urlencode "username=root@pam" --data-urlencode "password=totp:$(oathtool -b --totp "$TOTP_SECRET")" --data-urlencode "tfa-challenge=$(<cookie)" https://cl1:8006/api2/json/access/ticket

with these two line I've managed to get a valid ticket doing two factor autetication....

Hope this will help other people......
 
Or if you use separate permission for the API token, it way me be possible to bypass the TFA. I'm just guessing at that point.
API tokens do not require TFA. Their purpose is to give limited access to the API, that can be used with scripts and other applications, without having to do multiple requests to obtain a ticket. TFA in scripts doesn't really make sense, since it would just be another secret lying next to the token.
Since an API token poses as a 'bypass' to TFA, it's strongly recommended to actually limit its scope to what the script needs.
 
So I'm in 8.1.4. I have copied your script to a rpi and changed the variables. I created a user I added a api token. The new user has admin permissions. When I run the script and check the syslogs I see "authentication failure; rhost=::ffff:10.20.20.166 user=AGVuser@pve msg=invalid credentials" Is the password the actual user password are is it the one time token you are given when you create the token? I just want this to start the VM when it runs

Code:
#!/bin/bash
##################################
### API AUTOMATION
# for the proxmox community
# By veeh, enjoy

#Host info
pve="10.20.20.20"
node="pvetemp"
port=":8006"
vmid="301"

#API info
apiu="VMuser@pve"
apip="USERSloginPASSWORD"
url_base="https://$pve$port/api2/json"
# this is where you put what ever you want do
# https://pve.proxmox.com/pve-docs/api-viewer/
url_end="nodes/$node/qemu/$vmid/status/start"
urlqr="$url_base/$url_end"
urltk="$url_base/access/ticket"

ticket=`curl --insecure --data "username=$apiu&password=$apip" $urltk`

# Grab cookie and token from the ticket data
cookieid=`echo $ticket | tr -t '"' '\n' | grep "PVE:$apiu"`
cookie="PVEAuthCookie=$cookieid"
ticketid=`echo $cookie | awk -F ':' '{ print $3 }'`
tokenid=`echo $ticket | tr -t '"' '\n' | grep $ticketid | grep -v PVE`
token="CSRFPreventionToken:$tokenid"

#proxmox api query
curl --insecure --cookie $cookie --header $token -X POST "$urlqr"
 
I just tried it on my infra I'm running 8.1.4 as well, and it worked.

This script is not based on the api token secret. You need to enter the user password in the apip variable
And there will be a first api call to obtain a ticket and cookie, which will then be used to log in and generate the action.

apiu is the user@realm and apip is the password of this user. (Same password used for the GUI)
If your user is based on an LDAP directory for instance your login should look like user@domain.loc.
 
I was finally able to get this to work. I tweaked it a little bit to match the formatting from this guide for creating a thin client. I also added it to the .bash_profile in the pi so that when you start the pi it check to see if the vm is running first, if not it starts or resumes the VM then enter the virtual desktop. Makes for a nice Thin client and it lets you free up resources on the server if it is not in uses and able to restart the VM right from the PI.

the .bash_profile looks like this It only runs these commands if the terminal start from the display and not form a ssh connection
[[ -z $DISPLAY && $XDG_VTNR -eq 1 ]] && (sh /home/pi/vmstart.sh && startx --)

I saved this script as vmstart.sh in the home directory and it looks like below. One thing to note with this is I did not have to create a API token. I only had to create a User and give that user the PVEVMuser permissions

Bash:
#!/bin/bash



echo "$PROXY is reachable!"

# Set auth options
PASSWORD='Users_login_password'
USERNAME='VMuser@pve'

# Set VM ID
VMID="VMID"

# Set Node
# This must either be a DNS address or name of the node in the cluster
NODE="HOSTID"
# Proxy equals node if node is a DNS address
# Otherwise, you need to set the IP address of the node here
PROXY="HOST_FQDN_OR_IP"

#The rest of the script from Proxmox
NODE="${NODE%%\.*}"

# Function to check if the IP is reachable
check_ip() {
    ping -c 1 $1 > /dev/null 2>&1
}

echo "Waiting for $PROXY to become reachable..."

# Loop until the IP is reachable
while ! check_ip $PROXY; do
    sleep 1
done

# gets auth thicket form the proxmox server
ticket="$(curl -f -s -S -k --data-urlencode "username=$USERNAME" --data-urlencode "password=$PASSWORD" "https://$PROXY:8006/api2/json/access/ticket")"

echo "AUTH OK"

# Grab cookie and token from the ticket data
cookieid=`echo $ticket | tr -t '"' '\n' | grep "PVE:$USERNAME"`
cookie="PVEAuthCookie=$cookieid"
ticketid=`echo $cookie | awk -F ':' '{ print $3 }'`
tokenid=`echo $ticket | tr -t '"' '\n' | grep $ticketid | grep -v PVE`
token="CSRFPreventionToken:$tokenid"

#proxmox api query
status='Unknown'
while [ $status != "qmpstatus:running" ]
do
        statusreturn=$(curl --insecure --cookie $cookie --header $token -X GET  https://$PROXY:8006/api2/json/nodes/$NODE/qemu/$VMID/status/current)
        #echo $statusreturn
        status=`echo $statusreturn | tr -t ',' '\n' | grep 'qmpstatus'`
        #echo $status
        status=`echo $status | tr -d '"' `
        echo $status
        if [ $status = "qmpstatus:running" ]; then
                echo "VM running"
        elif [ $status = "qmpstatus:stopped" ]; then
                echo "Starting VM"
                curl  --insecure --cookie $cookie --header $token -X POST https://$PROXY:8006/api2/json/nodes/$NODE/qemu/$VMID/status/start
        elif [ $status = "qmpstatus:suspended" ]; then
                echo " Restarting VM"
                curl  --insecure --cookie $cookie --header $token -X POST https://$PROXY:8006/api2/json/nodes/$NODE/qemu/$VMID/status/stop
                sleep 5
                curl  --insecure --cookie $cookie --header $token -X POST https://$PROXY:8006/api2/json/nodes/$NODE/qemu/$VMID/status/start
        elif [ $status = "qmpstatus:paused" ]; then
                echo " Resuming VM"
                curl  --insecure --cookie $cookie --header $token -X POST https://$PROXY:8006/api2/json/nodes/$NODE/qemu/$VMID/status/resume
        else
                echo "Unknown status:" $status
        fi
done
 
Last edited:
  • Like
Reactions: Veeh and gfngfn256

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!