Ditch the Dashboard: Mastering VergeOS with `verge-cli`

What if your entire private cloud was just a curl away?

For most VergeOS administrators, the dashboard is home. It's intuitive, powerful, and visual. But what happens when you need to:

  • Power cycle an entire test lab of 20 VMs with a single command string?
  • Find out which VM is consuming the most resources across your environment?
  • Script a snapshot-before-deploy workflow that even an intern can run safely?

That's where verge-cli becomes your new best friend. It's a single, statically-linked binary that gives you raw, unadulterated access to the entire VergeOS API. No browser, no clicking, just pure command-line power.

πŸ’‘ Why Ditch the Dashboard?

While the VergeOS UI is fantastic, the CLI wins in three key scenarios:

  1. Bulk Operations: Updating settings on 20 VMs takes 30 seconds with a one-liner vs. 15 minutes of manual clicking.
  2. Automation Pipelines: Triggering snapshots before a deployment can be baked directly into your CI/CD jobs.
  3. Speed & Stability: On a spotty cellular connection or when tunneling through SSH, a 50KB JSON response is inherently more reliable than a heavy web interface.

πŸ”Œ Getting Started: Connect to Your System

Before you do anything, you need to authenticate. The most robust way to work is by generating a session token and storing it in a shell variable for reuse.

Step 1: Generate a Session Token

Generate a session token using the /sys/tokens endpoint. Unlike the UI, the API requires a specific JSON payload:

export VG_HOST="192.168.1.138"  # Your VergeOS IP/Hostname

# Note: The JSON body keys are 'login' and 'password'
export YB_TOKEN=$(verge-cli --server=$VG_HOST --tls-skip-verify \
  --post='{"login": "admin", "password": "YourSecurePassword"}' \
  /sys/tokens | jq -r '."$key"')

echo "Token: $YB_TOKEN"

Step 2: Use the Token

Now pass the token and server explicitly to every command:

# This lists your active VMs (ignoring recipe templates)
verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --filter="is_snapshot eq false" /v4/vms

⚠️ Important: Most lab and tenant environments use self-signed certificates. Always include --tls-skip-verify unless you've configured custom CA certificates with --tls-ca-cert.


πŸ“– Reading Data: The GET World

The default action for verge-cli is a GET request. This is your window into the system's state.

List Active VMs

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --filter="is_snapshot eq false" /v4/vms

VMs vs. Recipes (Templates)

VergeOS stores "Recipes" (templates) in the same endpoint as active VMs. You can filter them using the is_snapshot field:

# List ONLY active VMs
verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --filter="is_snapshot eq false" /v4/vms

# List ONLY Recipes/Templates
verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --filter="is_snapshot eq true" /v4/vms

Filter by Name

Use the --filter flag with OData-style syntax:

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --filter="name eq 'web-server-prod'" /v4/vms

Get Specific Fields

Don't need the whole object? Use --fields:

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --fields='name,$key,ram,cpu_cores' /v4/vms

Output:

[
  {"name": "web-server-1", "$key": 12, "ram": 4096, "cpu_cores": 2},
  {"name": "db-server-1", "$key": 15, "ram": 8192, "cpu_cores": 4}
]

The Magic most Field

Too lazy to list every field? VergeOS has you covered. By adding a filter, we can also ensure we only see active VMs and not recipes:

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --filter="is_snapshot eq false" --fields='most' /v4/vms

This returns all commonly useful fields without the internal metadata.

Checking VM Power Status

Here's a gotcha: the /v4/vms endpoint doesn't directly show if a VM is running or stopped. For live power state, you need the /v4/machine_status endpoint.

Step 1: Get the machine IDs

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --filter="is_snapshot eq false" --fields='name,machine' /v4/vms

Step 2: Query machine_status

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --fields='machine,running,status,state' /v4/machine_status

The status field contains human-readable values: running, stopped, hibernating, starting, stopping, etc.

The Power Status One-Liner:

# Join VM names with their power status
VMS=$(verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --filter="is_snapshot eq false" --fields='name,machine' /v4/vms)
STATUS=$(verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --fields='machine,status' /v4/machine_status)

echo "$VMS" | jq -r '.[] | "\(.machine)|\(.name)"' | while IFS='|' read m n; do
  s=$(echo "$STATUS" | jq -r ".[] | select(.machine == $m) | .status")
  echo "$n: $s"
done

Which Node Is My VM On?

In a multi-node cluster, you often need to know which physical host is running each VM. The /v4/machine_status endpoint includes a node field for this:

# Get running VMs and their host nodes
VMS=$(verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --filter="is_snapshot eq false" --fields='name,machine' /v4/vms)
STATUS=$(verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --fields='machine,status,node' /v4/machine_status)
NODES=$(verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --fields='$key,name' /v4/nodes)

echo "$VMS" | jq -r '.[] | "\(.machine)|\(.name)"' | while IFS='|' read m n; do
  m_status=$(echo "$STATUS" | jq -r ".[] | select(.machine == $m)")
  if [ "$(echo "$m_status" | jq -r '.status')" == "running" ]; then
    node_id=$(echo "$m_status" | jq -r '.node')
    node_name=$(echo "$NODES" | jq -r ".[] | select(.\"\$key\" == $node_id) | .name")
    echo "VM: $n βž” Node: $node_name"
  fi
done

✏️ Creating and Modifying: POST and PUT

Here's where the scripting power shines.

Create a New Tag Category

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --post='{"name":"Environment","description":"Classify resources","taggable_vms":true}' \
  /v4/tag_categories

Response:

{"$key": "1", "location": "/v4/tag_categories/1"}

Create a Tag

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --post='{"name":"production","category":1}' /v4/tags

Update a VM's RAM

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --put='{"ram":8192}' /v4/vms/12

This updates VM with $key=12 to 8GB of RAM.


πŸ—‘οΈ Deleting Resources

Careful now. This is the --delete flag.

Delete a Snapshot

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --delete /v4/machine_snapshots/5

Delete a Tag

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --delete /v4/tags/2

⚑ Performing Actions

Some endpoints aren't just dataβ€”they're commands. VergeOS uses a /v4/<resource>/<id>/<action> pattern.

⚠️ Important: Action endpoints use POST, not PUT!

Power On a VM

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --post='{}' /v4/vms/36/poweron

Graceful Shutdown (Guest OS Shutdown)

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --post='{}' /v4/vms/36/poweroff

This sends an ACPI shutdown signal to the guest OS, allowing it to shut down gracefully.

Force Power Off (Hard Stop)

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --post='{}' /v4/vms/36/kill

Use this when the guest OS is unresponsive. It's equivalent to pulling the power cord.

Guest Reboot

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --post='{}' /v4/vms/36/guestreset

Reset (Hard Reboot)

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --post='{}' /v4/vms/36/reset

Take a Snapshot

# Note: You need the VM's 'machine' ID, not its '$key'!
verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --post='{"machine":35,"name":"pre-upgrade-snap"}' \
  /v4/machine_snapshots

πŸ“€ Uploading Files: The Secret Weapon

One of the most powerful (and least documented) features of verge-cli is its ability to upload large files directly to the VergeOS file store.

Upload a QCOW2 Image

verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --upload=/path/to/debian-13-generic-amd64.qcow2 \
  --upload-name="debian-13.qcow2" \
  --threads=16 \
  /v4/files

This uses parallel chunked uploads (16 threads!) to stream the file directly into VergeOS. A 400MB image uploads in under 20 seconds.


⚠️ Gotchas & Tips

1. The $key vs machine Distinction

VMs have two important IDs:

  • $key: The VM's database ID (what you see in URLs like /v4/vms/12)
  • machine: The hypervisor's internal machine ID

When creating snapshots, you need the machine ID, not $key:

# First, get the machine ID
VM_INFO=$(verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN --fields='$key,name,machine' /v4/vms/12)
MACHINE_ID=$(echo $VM_INFO | jq -r '.machine')

# Then create the snapshot
verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --post="{\"machine\":$MACHINE_ID,\"name\":\"my-snapshot\"}" \
  /v4/machine_snapshots

2. Escaping $key in jq

The $key field requires quoting in jq:

# ❌ Wrong
echo $JSON | jq '.[0].$key'

# βœ… Correct
echo $JSON | jq '.[0]."$key"'

3. Recipe VMs Can't Be Snapshotted

If you're bulk-snapshotting VMs and some fail, they're probably recipe templates (read-only base images). These are immutable by design.

4. Write Idempotent Scripts

Always check if a resource exists before creating:

EXISTING=$(verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --filter="name eq 'my-tag'" /v4/tags)
if [ "$(echo "$EXISTING" | jq 'length')" -eq 0 ]; then
  # Create the tag
fi

🧩 Real-World Scripting Examples

Script 1: Bulk Create VMs from a CSV

#!/bin/bash
while IFS=, read -r name cores ram; do
  verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
    --post="{\"name\":\"$name\",\"cpu_cores\":$cores,\"ram\":$ram}" \
    /v4/vms
  echo "Created VM: $name"
done < vms.csv

Script 2: Nightly Snapshot of All Production VMs

#!/bin/bash
DATE=$(date +%Y-%m-%d)
verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
  --filter="is_snapshot eq false" --fields='$key,name,machine' /v4/vms \
  | jq -c '.[]' | while read vm; do
    MACHINE=$(echo $vm | jq -r '.machine')
    NAME=$(echo $vm | jq -r '.name')
    
    # Skip if machine ID is null (recipe templates)
    if [ "$MACHINE" = "null" ]; then
      echo "Skipping $NAME (no machine ID)"
      continue
    fi
    
    verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN \
      --post="{\"machine\":$MACHINE,\"name\":\"nightly-$NAME-$DATE\"}" \
      /v4/machine_snapshots
    echo "Snapshotted: $NAME"
  done

Script 3: Complete Infrastructure Workflow

Here's a production-ready script that discovers VMs, creates tags, takes snapshots, and generates an inventory report:

#!/bin/bash
set -e
CLI="verge-cli --server=$VG_HOST --tls-skip-verify -T $YB_TOKEN"
DATE=$(date +%Y-%m-%d_%H%M)

echo "πŸ“‘ Discovering VMs..."
VMS=$($CLI --filter="is_snapshot eq false" --fields='$key,name,machine' /v4/vms)
echo "   Found $(echo $VMS | jq 'length') VMs"

echo "🏷️ Setting up tags..."
$CLI --post='{"name":"CLI-Managed","taggable_vms":true}' /v4/tag_categories 2>/dev/null || true

echo "πŸ“Έ Taking snapshots..."
echo "$VMS" | jq -c '.[]' | while read vm; do
    MACHINE=$(echo $vm | jq -r '.machine')
    NAME=$(echo $vm | jq -r '.name')
    [ "$MACHINE" != "null" ] && \
      $CLI --post="{\"machine\":$MACHINE,\"name\":\"backup-$NAME-$DATE\"}" \
        /v4/machine_snapshots 2>/dev/null && echo "   βœ“ $NAME"
done

echo "πŸ“Š Generating report..."
echo "{\"vms\":$($CLI --filter=\"is_snapshot eq false\" /v4/vms),\"snapshots\":$($CLI /v4/machine_snapshots)}" \
  > inventory-$DATE.json

echo "βœ… Complete! Report: inventory-$DATE.json"

πŸ” Security Best Practices

  1. Never hardcode tokens in scripts. Use environment variables and pass them as flags:
    verge-cli -T $YB_TOKEN ...
    
  2. Use custom CA certs for production:
    verge-cli --tls-ca-cert=/path/to/ca.pem ...
    
  3. Rotate tokens regularly. Session tokens expire. For long-running automation, consider using the Terraform provider which handles authentication gracefully.

🎯 When to Use verge-cli vs. Terraform

Use Case verge-cli Terraform
Quick one-off queries βœ… ❌
CI/CD pipeline deploys βœ… (scripted) βœ… (preferred)
DR runbooks βœ… ⚠️ (state file complexity)
Drift detection ❌ βœ…
Complex dependency graphs ❌ βœ…
Bulk operations βœ… ⚠️

The Golden Rule: Use verge-cli for imperative tasks (do this now). Use Terraform for declarative state (this is what should exist).


πŸš€ Conclusion

verge-cli is the Swiss Army knife you didn't know you needed. Whether you're automating nightly backups, scripting disaster recovery, or just prototyping API calls before writing Terraform, it puts the full power of VergeOS at your fingertips.

So next time you find yourself clicking through the dashboard for the hundredth time, ask yourself: Could this be a one-liner?

The answer is probably yes.


πŸ“₯ Download & Installation

GitHub Repository: https://github.com/verge-io/verge-cli

verge-cli is a single, statically-linked Go binary. Download the appropriate release for your platform:

# Example: Download and install on Linux
curl -L https://github.com/verge-io/verge-cli/releases/latest/download/verge-cli-linux-amd64 -o verge-cli
chmod +x verge-cli
sudo mv verge-cli /usr/local/bin/

Built with ❀️ for the VergeOS Community.

Read more