2026-09-06 12:55:06 +02:00
2026-09-04 11:39:55 +02:00
2026-09-04 11:39:55 +02:00
2026-09-06 12:41:17 +02:00
2026-09-06 12:41:17 +02:00
2026-09-06 12:41:17 +02:00
2026-09-06 12:41:17 +02:00
2026-09-06 12:55:06 +02:00
2026-09-04 15:19:54 +02:00
2026-09-04 11:39:55 +02:00
2026-09-04 11:39:55 +02:00
2026-09-04 11:39:55 +02:00
2026-09-06 12:55:06 +02:00
2026-09-06 12:55:06 +02:00
2026-09-04 11:39:55 +02:00
2026-09-04 11:39:55 +02:00
2026-09-04 11:39:55 +02:00
2026-09-05 20:10:04 +02:00
2026-09-06 12:55:06 +02:00
2026-09-04 11:39:55 +02:00
2026-09-06 12:41:17 +02:00
2026-09-06 12:41:17 +02:00
2026-09-06 12:41:17 +02:00
2026-09-04 15:19:54 +02:00
2026-09-05 20:10:04 +02:00
2026-09-04 11:39:55 +02:00
2026-09-06 12:55:06 +02:00
2026-09-04 11:39:55 +02:00

gvm — VMware command line helper

A small command line tool for the VMware vCenters: list the virtual machines of all of them at once, take and remove snapshots, look at what the ESXi hosts are doing, and mail the vCenter event log.

gvm                           # interactive list of every machine, everywhere
gvm vm                        # the same thing, spelled out
gvm vm -l                     # the same as plain output
gvm vm -l -m web              # only those whose name matches "web"
gvm -v v108 snap -l myvm      # the snapshots of myvm on v108
gvm -v v108 snap -n myvm      # take one
gvm -v v108 power -s myvm     # ask its guest to shut down
gvm host                      # cpu, memory and machine counts per host
gvm log -l                    # the last hour of events
gvm config                    # what gvm made of ~/.gvmrc

Configuration

Everything gvm knows about the world is in ~/.gvmrc; there are no built-in servers and no built-in credentials. On first run gvm writes an annotated template there and says so — fill in the passwords and it works. gvmrc.example is the same file with the comments spelled out.

default = v308

vcenter.v308.url        = https://v308.fhi.mpg.de/
vcenter.v308.user       = administrator@v308.fhi.mpg.de
vcenter.v308.password   = ...
vcenter.v308.datacenter = PPB
vcenter.v308.insecure   = true

-v <name> picks a server by its full name; an unknown name is an error rather than a silent fallback to the first one in the list. Every setting has an environment spelling that wins over the file — GVM_VCENTER_V308_PASSWORD, GVM_MAILTO, GVM_DEFAULT and so on — which is how to run gvm from cron without the password living in a file.

The file holds passwords, so gvm creates it mode 0600 and complains when it finds it readable by others.

Commands

command what it does
(nothing) browse the machines interactively (see below)
vm the same, spelled out
vm -l [-m <re>] print them instead; all vCenters unless -v names one
snap -l <vm> list a machine's snapshots
snap -n <vm> take a snapshot, name printed
snap -r <vm> -s <snap> remove one snapshot
snap --revert <vm> -s <snap> put the machine back to that snapshot
snap --removeall <vm> remove all of them
power -o <vm> power on
power -s <vm> ask the guest to shut down (needs VMware Tools)
power -b <vm> ask the guest to reboot (needs VMware Tools)
power --off <vm> power off at the hypervisor — hard
power --reset <vm> reset at the hypervisor — hard
host [-t] per-host cpu, memory, machine counts; -t also posts them
host -c just the machine counts
log -l [-m] [-t <min>] the event log, optionally by mail, default 60 minutes
config the effective configuration, passwords not shown

The interactive list

gvm on its own — or gvm vm, or gvm vm -i — asks every configured vCenter at once and puts the machines of all of them into one full-screen list, sorted by name. -v <name> narrows it to one server, and gvm -h still prints the help, as does anything gvm does not recognise:

type            narrow the list — the text is matched against the whole
                line, so a name, an address, a host or "off" all work, and
                the hit is picked out in the row
↑ ↓ PgUp PgDn   move, Home/End for the ends
enter           the machine's parameters: power, host, guest and tools,
                cpu and memory in use, uptime, storage, guest filesystems,
                network adapters, snapshots, uuid and moref
↑ ↓ in there    scroll the sheet, esc/enter back to the list

The sheet is one line per thing worth knowing, values that belong together joined with a middle dot and no section headings — an ordinary machine fits a 24-row terminal whole, and the identity numbers and the annotation at the bottom are the only part anyone scrolls for. A value too long for the width is carried onto a continuation line under its own label rather than cut off at the edge, including one long word such as a datastore path, so a narrow terminal loses nothing. The machine's name, vCenter, datacenter and host are the title. ^r ask the servers again esc clear the filter, or leave when there is none ^c leave

Nothing acts on a machine from the table. Everything that changes one lives in the machine's own sheet, which ⏎ opens — a row of a table of two hundred machines is something the eye runs past, not something anyone has read. In the sheet:

^a              the action menu (see below)
^s              take a snapshot: a name, then a confirmation

Pressing either in the table says so rather than doing nothing visible.

^s takes two steps.

First the name. The field starts empty and its hint says which random name Enter alone would use — the same kind snap -n gives — so ^s enter y is the quick path and typing something that will still mean something in three weeks is the deliberate one. ← → Home End Backspace Delete edit it, Esc abandons the whole thing, and 80 characters is where vSphere stops.

Then the question, naming the snapshot, the machine and the vCenter. It takes nothing but y: Enter finishes a name, it never takes a snapshot. Afterwards the name is on the status line, and a sheet that is open jumps to its snapshot section so the new one is there to see.

The action menu

In a machine's sheet, ^a opens the menu for it. Above the choices it repeats the few lines of the sheet the choice depends on — state, guest, hostname, address — taken from the sheet itself, so the two cannot word the same fact differently. On a terminal too short for both, those lines go and the choices stay. Everything that changes a machine lives there and nowhere else — the list is arrowed through and its filter swallows every ordinary letter, so a hotkey that powered a machine off would sit one fumbled control key away from an outage, and the sheet has to be opened first anyway.

n  take a snapshot                     o  power on
r  revert to a snapshot ...            s  shut down the guest
d  remove a snapshot ...               b  reboot the guest
D  remove ALL snapshots                S  power off  (hard)
                                       B  reset      (hard)

Lowercase asks the guest, uppercase acts at the hypervisor: the violent variant always needs the shift key. What cannot be done right now is greyed out with the reason next to it — "no VMware Tools", "already running" — rather than left out, and picking it anyway spells the reason out instead of running it.

Snapshots are drawn as the tree they are — which state descends from which is the whole point of a snapshot list — and the one the machine is running from says so:

snapshots   base            (01.09.2026 02:00)
            ├─ after-patch  (03.09.2026 09:12)
            │  └─ hotfix    (03.09.2026 16:40)
            └─ before-boot  (05.09.2026 07:00)  ← current

The same drawing appears in the picker below and in gvm snap -l; there is one function that draws it, so the three cannot drift apart.

r and d open a list of the machine's snapshots. What is chosen there is carried on by its vSphere reference, not by its name: two snapshots of one machine may share a name, and a rollback point is not something to identify by a string that is not unique.

Then comes a full page: the machine, the vCenter, the datacenter, the host, the state, and what the operation costs — and it asks for YES, in capitals. Not y, not yes, not Enter. A word that needs the shift key cannot be given by a hand resting on Enter, and in the list the machine is whatever the cursor happens to be on, so the page above the prompt is the part that matters.

Powering on is the single exception, and ends at one y: it destroys nothing.

Reverting asks vCenter not to start the machine again afterwards, so a machine whose snapshot was taken while it ran does not come back up with a rewound disk while you are still reading the message. Afterwards gvm reads the power state back and reports what it actually is rather than what it asked for.

After an operation the machine's row is read again, so the list shows what happened rather than what the last sweep found. For a power on or off that means waiting for vCenter's own view to catch up first: the task finishes a moment before the property collector agrees, and a row re-read in between would still show the old state. Opening the menu re-reads too, which is what picks up a guest that has finished shutting down in its own time — ^r reloads everything.

Nothing else here writes: no key changes a setting, and there is no way to delete a machine.

Everything but the snapshot tree comes out of the one inventory sweep the list makes at the start; the snapshots of a machine are fetched when its sheet is opened.

It needs a terminal, and says so before it connects to anything — in a pipe or under cron, use gvm vm -l.

The dangerous half

snap --revert, snap --removeall, power --off and power --reset stop or rewind a running machine, and the first of them destroys data outright. Four things hold for all of them:

  • Graceful and hard are separate commands. power -s asks the guest's operating system to shut down; power --off cuts the power at the hypervisor. gvm never turns the first into the second because VMware Tools did not answer — it refuses, and names the hard variant so you choose it deliberately.
  • The destructive options have no short letter. --revert, --removeall, --off and --reset have to be spelled out; only the harmless ones (-o, -s, -b, -l, -n) are one keystroke.
  • Nothing impossible is sent. A machine that is already off is not shut down again, and a graceful operation on a machine without Tools is refused before anything reaches vCenter. -y does not override this.
  • They ask first, and fail closed. Each prints what will happen, to which machine, on which vCenter, and asks — defaulting to no. -y answers in advance, which is what cron needs. Without a terminal and without -y the command refuses with exit status 1 rather than doing nothing quietly: a script that gets "nothing done" and exit 0 would believe the machine was stopped.

One power operation per command line; two is a mistake, not a sequence, and gvm says so instead of guessing.

Colours

The palette is mwxcol, copied into colors.go — that repo's mwxcol.go is the source of truth, so a change there is a change here. Everything gvm paints goes through that one file: the P/PF helpers in tools.go through the C* functions, the full-screen list through the escape sequences at the bottom of it.

The frame follows the mapping mwxcol's own fzf theme uses, job for job: the selected row on a darker surface with a violet pointer, the filter's hits in pink, counts in green, questions in yellow, errors in red, headers and the help line in dark.

Inside the table and the sheet every colour is a role, not a decoration:

white the machine's own name, and its guest
violet which vCenter — a kind of thing
green / dark / yellow / red powered on / off / suspended / anything else
blue addresses, paths and dates
orange sizes and counts
pink names a person gave: snapshots
grey present but seldom read: host, guest os, uuids

A machine that is off is dark rather than red — being switched off is not a fault. Two places lift that tone to grey: the selected row, where dark would sit on the darker surface, and every value on the sheet, where the labels beside it are dark themselves. Lifted, it is still visibly quieter than the rest of the palette, so what was dimmed stays dimmed.

Colours are written as true colour (24 bit). In a pipe the C* functions leave them out; the full-screen list needs a terminal anyway.

Updating

gvm updates itself from the releases of the Gitea instance named at the top of selfupdate.go:

gvm --check-update      # look, change nothing
gvm --update            # fetch and replace the running binary
gvm --version

Once a day an ordinary run looks in the background and, when there is something newer, prints one line about it. It costs nothing in the foreground, says nothing when gvm is not on a terminal, and GVM_NO_UPDATE_CHECK=1 turns it off. The downloaded binary is run once with --version before it replaces the running one, so a truncated or wrong-platform download cannot install itself.

Building

./build.sh                        # all platforms into ./bin
PLATFORMS="linux/amd64" ./build.sh
VERSION=1.1.0 ./build.sh          # set the version instead of bumping it
go test ./...                     # incl. tests against govmomi's simulator

Every run bumps the patch level in version.txt and injects it into the binaries. The files in ./bin are named the way --update expects them in a release: gvm-<goos>-<goarch> on a release tagged with the bare version number.

The automatic bump only ever touches the last number, so VERSION= is how a major or minor step is made — no number of builds reaches 1.0.0 from 0.x. It is checked to be MAJOR.MINOR.PATCH before anything is built: that number ends up in the binary, in version.txt and on the release tag, and --update compares versions number by number, so anything else would compare as older than everything and quietly stop updates.

Tests

go test ./... runs without touching any real vCenter. The list, the filter, the parameter sheet and the palette are checked on synthetic machines, and everything that talks to a server — logging in, the inventory sweep, the snapshot round trip, ^s with its name field and its confirmation, the whole power and revert half with its confirmations declined and then given, the error paths — runs against govmomi's own simulator, started inside the test process (see sim_test.go). The configured vCenters are production; no test goes near them, and none of them reads ~/.gvmrc.

The checks that stand between a keystroke and a machine are tested on their own as well: the whole power matrix (state × VMware Tools × operation), that a refused operation sends nothing, that an unavailable menu entry does not run when it is picked anyway, that only the exact machine name passes the confirmation, and that removing one of two identically named snapshots removes the one that was picked.

S
Description
No description provided
Readme
2.5 MiB
1.3.10
Latest
2026-09-16 14:57:39 +02:00
Languages
Go 99.6%
Shell 0.4%