Table of Contents
Overview
BashVM is an interactive, text-menu wrapper around the
virsh (libvirt) command-line tools. Instead of typing long
virsh, virt-clone, ufw, and
iptables invocations, you pick a numbered item from a menu and
answer a the prompts.
- Main menu — top level, 11 functional areas plus
qto exit. - Sub-menus — one per functional area (VMs, storage, networks, snapshots, …).
- Prompts — short questions that collect names, sizes, IPs, MAC addresses, etc.
Running the Script
On a fresh host, run the 12.1 Installer first:
chmod +x bashvm-installer.sh
sudo ./bashvm-installer.sh
Once the installer finishes, run the main script:
# make it executable, then run as root
chmod +x bashvm.sh
sudo ./bashvm.sh
On launch you are shown the main menu:
============================ Main Menu ============================
1. Virtual Machines 2. Storage Pools 3. Networks
4. Snapshots 5. Edit Properties 6. Firewall Settings
7. Port Forwarding 8. VNC / Console Access 9. System Monitor
10. VM Monitor 11. SQLite Editor q. Exit
Enter your choice:
Type the number (or letter) of the area you want and press Enter.
The helper scripts (e.g. the 1.1 Create a new / existing VM option) are expected to live in the
same working directory as the main script or somewhere on your PATH.
Main Menu Reference
| Key | Section | Purpose |
|---|---|---|
| 1 | Virtual Machines | Show, start, reboot, shut down, clone, rename, delete, console into VMs |
| 2 | Storage Pools | Manage libvirt storage pools and volumes; download ISOs |
| 3 | Networks | Create NAT / macvtap / routed networks; DHCP reservations |
| 4 | Snapshots | List, create, delete, revert VM snapshots |
| 5 | Edit Properties | Open the raw XML editor for VMs, pools, networks, snapshots |
| 6 | Firewall Settings | ufw status, allow/deny ports and ranges, delete rules |
| 7 | Port Forwarding | NAT port-forward rules via the libvirt qemu hook |
| 8 | VNC / Console Access | Show assigned VNC ports; add/remove VNC with password |
| 9 | System Monitor | Launches btop |
| 10 | VM Monitor | Per-VM vCPU, memory, disk and network usage |
| 11 | SQLite Editor | Edit the BashVM database |
| q | — | Exit the script |
1. Virtual Machines
============================== Manage Virtual Machine ==============================
s. Show all virtual machines 1. Show more details of a VM 2. Start a VM
3. Reboot a VM 4. Shutdown a VM (graceful) 5. Shutdown a VM (force)
6. Enable autostart of a VM 7. Disable autostart of a VM 8. Create a new / existing VM
9. Undefine a VM 10. Create a new VM (Automated) 11. Console into a VM
12. Change resources of a VM 13. Delete a VM 14. Clone a VM
15. Clone multiple VM's 16. Rename a VM q. Back to main menu
| Key | Action | Underlying command / script | Prompts |
|---|---|---|---|
| s | List all VMs, running and shut off | virsh list --all |
none |
| 1 | Show detailed info + block-device info for one VM | virsh dominfo, virsh domblkinfo --all --human |
VM name |
| 2 | Start a VM | virsh start |
VM name |
| 3 | Reboot a running VM (ACPI) | virsh reboot |
VM name |
| 4 | Graceful shutdown (ACPI signal to guest) | virsh shutdown |
VM name |
| 5 | Forced shutdown — pulls the virtual power cord | virsh destroy |
VM name |
| 6 | Enable autostart at host boot | virsh autostart |
VM name |
| 7 | Disable autostart | virsh autostart --disable |
VM name |
| 8 | Guided creation of a new VM (or import an existing disk) | 1.1 Create a new / existing VM | Name, memory, vCPUs, ISO choice, disk, network, MAC |
| 9 | Undefine (unregister) a VM. Refuses if the VM is running; destroys it first if shut off. | virsh destroy, virsh undefine |
VM name |
| 10 | Automated cloud-image VM creation with DHCP reservation and port forwarding | 1.2 Create a new VM (Automated) | Hostname, memory, vCPUs, disk, user, password, OS |
| 11 | Attach to the VM's serial console (exit with Ctrl+]) | virsh console |
VM name |
| 12 | Change vCPU, memory, disk, and network attachments | 1.3 Change resources of a VM | Sub-menu — see script reference |
| 13 | Delete a VM, its disks, snapshots, DHCP reservation, DB entry, and forwarded ports | 1.4 Delete a VM | VM name, type DELETE to confirm |
| 14 | Clone a single VM to a new name, auto-generating disk names | virt-clone --original NAME --name NEW --auto-clone |
Source VM, new VM name |
| 15 | Clone one source VM multiple times; names are auto-assigned by virt-clone | virt-clone --original NAME --auto-clone in a loop |
Source VM, number of clones |
| 16 | Rename an existing VM definition | virsh domrename |
Current name, new name |
| q | Return to main menu | — | none |
Example — start a VM and confirm
Enter your choice: 2
Enter the name of the virtual machine to start: web-server
Domain 'web-server' started
Example — clone a VM
Enter your choice: 14
Enter the name of the VM to clone (e.g., test-vm): template-debian
Enter the name for the new cloned VM: web-server
Allocating 'template-debian-clone.qcow2' [100%]
Clone 'web-server' created successfully.
1.1 Create a new / existing VM (menu 1 → 8)
Interactive VM creation from an installation ISO, or import of an existing disk.
Builds a complete libvirt XML definition with Q35 machine type, host-passthrough CPU,
virtio disk, and a VNC listener on 0.0.0.0, then defines the domain.
Prompts
| Prompt | Notes |
|---|---|
| Name for the new / existing VM | Used for the XML filename in /etc/libvirt/qemu/ |
| Memory in MB | e.g. 1024 for 1 GiB |
| Number of vCPUs | e.g. 2 |
| ISO choice |
If iso.bashvm.com is reachable, you get five curated ISOs:
Debian 13.2, Ubuntu 24.04, AlmaLinux 10.1, OpenMediaVault 7.4, TrueNAS SCALE 25.10.
Press Enter with no choice to use your own ISO or none.
|
| Windows based OS? (y/n) | Only asked when using your own ISO. Switches disk bus to SATA and NIC model to e1000. |
| Pool containing the ISO | Only asked when using your own ISO. Lists volumes in the pool first, then asks for the full ISO path. |
| Create a new disk volume? (y/n) |
If yes: volume name, capacity (e.g. 10GB), pool — runs
virsh vol-create-as … --format qcow2.If no: lists pool volumes and asks for a full disk path. |
| Network name | Default default |
| MAC address | Leave empty to auto-generate a random 52:54:xx:xx:xx:xx MAC. |
What it does
- Downloads the selected ISO into the pool with resumable
wget -c(saved as.partthen renamed on completion). - Generates a random UUID via
/proc/sys/kernel/random/uuid. - Writes XML to
/etc/libvirt/qemu/<name>.xmland runsvirsh define. - VNC is pre-configured with
autoport='yes'on0.0.0.0and no password.
1.2 Create a new VM (Automated) (menu 1 → 10)
Fully automated VM deployment using a cloud image and
cloud-init. In one pass it downloads the base image, writes a
cloud-init user-data file, runs virt-install, waits for the VM's MAC,
reserves a DHCP IP, and installs port-forwarding rules into the qemu hook.
Prompts
| Prompt | Notes |
|---|---|
| Hostname of the VM | Used for the domain name and cloud-init hostname |
| Memory in MB | e.g. 1024 |
| Number of vCPUs | e.g. 2 |
| Disk space in GB | e.g. 50 |
| Username for the new VM | Created with passwordless sudo |
| Password for the new VM | Stored as plain_text_passwd in cloud-init and printed at the end |
| OS choice |
1 = Debian 13, 2 = Ubuntu 24.04,
3 = AlmaLinux 10, 4 = openSUSE Leap 15.6
|
What it does
- Writes
bashvm-cloudinit.yamlwith the chosen user, password, SSH password auth,qemu-guest-agentpackage, and aruncmd: shutdown -P 0so the VM powers off after first boot (ready for the hook). - Moves the cloud-init file to
/var/lib/libvirt/images. - Downloads the cloud image to
/var/lib/libvirt/imagesif not already present. - Ensures the
defaultnetwork is active and autostarted. - Runs
virt-install --disk=size=…,backing_store=… --cloud-init user-data=… --network bridge=virbr0 --noautoconsole. - Waits for a virtio MAC to appear, then generates a unique IPv4 in
192.168.122.x(excluding.0,.1,.255) and a port range starting at49152, stored inbashvm.db. - Adds a DHCP reservation to the
defaultnetwork. - Appends iptables DNAT rules between
#vm_name#and###vm_name###markers in/etc/libvirt/hooks/qemu(SSH plus the port range, TCP and UDP). - Restarts
libvirtdif it was the only running VM (so the hook loads cleanly). - Prints the assigned IPv4, SSH port, port range, username, and password.
1.3 Change resources of a VM (menu 1 → 12)
A second-level menu for adjusting an existing VM's vCPU count, memory, disk size, and network attachments. Most operations require the VM to be shut off.
=================================== Manage Resources ===================================
s. Show resources of a VM 1. Add disk space to a VM 2. Shrink disk space of a VM
3. Change the number of vcpu in a VM 4. Change the memory of a VM 5. Attach a disk to a VM
6. Detach a disk from a VM 7. Attach Network to a VM 8. Detach Network from a VM
q. Back to main menu
| Key | Action | Command / behaviour |
|---|---|---|
| s | Show resources | virsh dominfo, virsh domfsinfo, virsh domblkinfo --all --human |
| 1 | Grow a disk | virsh vol-resize <vm>.qcow2 <size> --pool <pool> — requires VM shut off. Uses the VM name + .qcow2 as the volume name. |
| 2 | Shrink a disk | Same as above with --shrink. Destructive — ensure the filesystem inside is small enough first. |
| 3 | Change vCPU count | Sets maximum then current count with setvcpus --config. Requires VM shut off. |
| 4 | Change memory | setmaxmem --current then setmem --current. Requires VM shut off. |
| 5 | Attach a disk | virsh attach-disk <vm> <path> <target> --current --subdriver qcow2 |
| 6 | Detach a disk | virsh detach-disk <vm> <path> --current |
| 7 | Attach a network | Builds a temporary interface XML (e1000 model, type='network') and calls virsh attach-device --config. |
| 8 | Detach a network | Same temporary XML, but with virsh detach-device --config. |
| q | Return to VM menu | Note: returns to the main menu in the current script flow. |
1.4 Delete a VM (menu 1 → 13)
Deletes a VM and everything attached to it: DHCP reservation, CD-ROM attachment, all storage volumes, snapshot metadata, SQLite port-forwarding record, and the iptables block in the qemu hook.
Prompts
- VM name — must be shut off; the script aborts if state is
running. - Confirmation — you must type
DELETE(all caps). Anything else cancels.
What it does
- Refuses to run if the VM is running.
- Removes the DHCP reservation by matching the VM's MAC and IP in
virsh net-dumpxml. - Detaches any CD-ROM device (looks for
cdromfollowed bysource filein the XML). - Runs
virsh undefine <vm> --remove-all-storage --snapshots-metadata— this deletes the qcow2 disks. - Removes the block between
#<vm>#and###<vm>###in/etc/libvirt/hooks/qemu. - Deletes the VM's row from the network's table in
bashvm.dbif present.
2. Storage Pools
============================== Manage Storage Pool ==============================
s. Show all storage pools 1. Show all volumes in a pool 2. Activate a storage pool
3. Deactivate a storage pool 4. Create a storage pool 5. Delete a storage pool
6. Create a storage volume 7. Delete a storage volume 8. Clone a storage volume
9. Download a iso / file q. Back to main menu
| Key | Action | Notes |
|---|---|---|
| s | virsh pool-list --details | Shows state, autostart, persistence, capacity, allocation, available. |
| 1 | List volumes in a pool | virsh vol-list --pool <pool>. Pool must exist. |
| 2 | Activate (start) a pool | virsh pool-start |
| 3 | Deactivate (destroy) a pool | virsh pool-destroy — the definition is kept. |
| 4 | Create a new pool | Prompts for name, type (dir, logical, fs, …) and target path/source. Runs pool-define-as, pool-start, pool-autostart. |
| 5 | Delete a pool | pool-destroy then pool-undefine. Aborts if the pool does not exist. |
| 6 | Create a qcow2 volume | Prompts for pool, volume name and capacity (e.g. 10G). The .qcow2 extension is appended automatically. |
| 7 | Delete a volume | virsh vol-delete --pool <pool> <name>.qcow2 |
| 8 | Clone a volume | virsh vol-clone <src>.qcow2 <dst>.qcow2 --pool <pool> |
| 9 | Download an ISO/file into a pool | Prompts for URL and pool; derives the filename from the URL; skips if the file already exists; wget --tries=2 --timeout=10. |
| q | Return to main menu |
Example — create a 20 GB volume in the default pool
Enter your choice: 6
Enter the name of the storage pool to use [default]:
Enter the name of the new storage volume (e.g., new-vm): web-server
Enter the size of the volume (e.g., 10G): 20G
Volume 'web-server.qcow2' created
Example — download an ISO
Enter your choice: 9
Enter the full URL to download from (e.g., https://example/image.iso): https://example.org/debian-12.iso
Enter the storage pool name that the file will download to [default]:
...wget output...
You may need to stop and start the pool to see the new image within bashvm
You can check to see if the file is there at /var/lib/libvirt/images
3. Networks
============================================== Manage Network ==============================================
s. Show all networks 1. Show more details of a network 2. Start a network
3. Stop a network 4. Create a NAT network 5. Create a macvtap network
6. Create a routed IPv4 network 7. Create a routed IPv6 network 8. Delete a network
9. Add a IPv4 reservation 10. Remove a IPv4 reservation 11. Add DHCPv6 to a NAT network
12. Add a IPv6 reservation 13. Edit a network q. Back to main menu
| Key | Action | Prompts / Behaviour |
|---|---|---|
| s | virsh net-list --all | Lists every defined network with state and autostart. |
| 1 | Network info + current DHCP leases | net-info, net-dhcp-leases |
| 2 | Start a network and mark it autostart | net-start, net-autostart |
| 3 | Stop a network | net-destroy |
| 4 | Create an isolated NAT network | Name, bridge name, gateway IP, netmask, DHCP start, DHCP end. XML written to /etc/libvirt/qemu/networks/<name>.xml, then defined, started and autostarted. |
| 5 | Create a macvtap (bridge-forward) network | Network name + physical interface (e.g. eth0). VMs appear directly on the physical LAN. |
| 6 | Create a routed IPv4 network | Name, bridge, gateway IPv4, netmask. No NAT, no DHCP — static configuration only. Also attempts to enable net.ipv4.ip_forward. |
| 7 | Create a routed IPv6 network | 3.3 Create a routed IPv6 network |
| 8 | Delete a network (optionally drop its SQLite table) | 3.1 Delete a network |
| 9 | Add a static DHCPv4 reservation | VM name, MAC, IP, network. Uses virsh net-update … add ip-dhcp-host … --live --config. |
| 10 | Remove a DHCPv4 reservation | Looks up MAC and IP from the network XML by VM name, then deletes the host entry. |
| 11 | Add DHCPv6 service to a NAT network | 3.2 Add DHCPv6 to a NAT network |
| 12 | Add a DHCPv6 reservation | Desired IPv6 address, network name, then the VM's DUID (listed with net-dhcp-leases). |
| 13 | Edit a network's XML | virsh net-edit <name> |
| q | Return to main menu |
Example — create a NAT network
Enter your choice: 4
Enter the new network name (e.g., natbr0): natbr0
Enter the virtual bridge name (e.g., natbr0): natbr0
Enter the new gateway ip address (e.g., 192.168.100.1): 192.168.100.1
Enter the new subnet mask (e.g., 255.255.255.0): 255.255.255.0
Enter the starting ip for the DHCP range (e.g., 192.168.100.2): 192.168.100.2
Enter the ending ip for the DHCP range (e.g., 192.168.100.254): 192.168.100.254
Network natbr0 defined from /etc/libvirt/qemu/networks/natbr0.xml
Network natbr0 started
Network natbr0 marked as autostart
Example — reserve an IP for a VM
Enter your choice: 9
Enter the virtual machines name: web-server
Enter the virtual machines mac address: 52:54:00:aa:bb:cc
Enter the new ip address for the virtual machine: 192.168.100.50
Enter the network name [default]:
Setting DHCP reservation...
You may need to start / stop the vm for the changes to take effect
3.1 Delete a network (menu 3 → 8)
Deletes a libvirt network and, optionally, its associated table in
bashvm.db.
Prompts
- Network name to delete.
- Delete the SQLite table and data? (y/n) — accepts
y,ye, oryes(case-insensitive).
What it does
- If confirmed, drops the table
<network>_tablefrombashvm.db(only if it exists). - Runs
virsh net-destroy <network>thenvirsh net-undefine <network>.
3.2 Add DHCPv6 to a NAT network (menu 3 → 11)
Adds DHCPv6 service to an existing NAT network. The script refuses to run on non-NAT networks.
Prompts
| Prompt | Notes |
|---|---|
| Network name | Default default. Must be a NAT network. |
| Main interface name | e.g. eth0 — the host's uplink for ndppd proxying |
| IPv6 gateway address | e.g. fd00::1 or a globally routed prefix |
| Prefix length | e.g. 64 |
| Starting range | e.g. fd00::2 |
| Ending range | e.g. fd00::300 |
What it does
- Aborts if
virsh net-dumpxmldoes not contain<forward mode='nat'>. - Aborts if the network XML already contains
ipv6— you must remove the existing DHCPv6 config first. - Adds
net.ipv6.conf.<iface>.accept_ra = 2to/etc/sysctl.confif not present, runssysctl -p, and creates a systemd unitbashvm-ipv6-accept-ra.serviceto re-apply on boot. - Strips the closing
</ip>and</network>tags from the network XML withsedand appends a new IPv6<ip>block with a<dhcp>range. - Stops, redefines, starts, and autostarts the network.
- Writes
/etc/ndppd.confwith a proxy rule for the configured prefix and enables/restartsndppd.
3.3 Create a routed IPv6 network (menu 3 → 7)
Creates a routed (non-NAT, no DHCP) IPv6 network. Useful for static IPv6 addressing on guests where you control the upstream routing.
Prompts
| Prompt | Notes |
|---|---|
| New network name | Must not already exist — the script validates with virsh net-info |
| Main physical interface | e.g. eth0 — used for accept_ra and ndppd |
| IPv6 network address | e.g. 2001:db8:: — this address is the gateway for guests |
| IPv6 prefix length | e.g. 64 |
What it does
- Aborts if a network with that name already exists.
- Adds
net.ipv6.conf.<iface>.accept_ra = 2to/etc/sysctl.confif not already present, applies it, and creates the samebashvm-ipv6-accept-ra.servicepersistence unit. - Generates a network XML with
<forward mode='route'/>and a random bridge name in the formvirbrNNN(three random digits). - Defines, starts, and autostarts the network.
- Writes
/etc/ndppd.confwith a proxy rule for the chosen prefix and enables/restartsndppd.
4. Snapshots
========================= Manage Snapshot =========================
s. Show all snapshots of a VM 1. Create a snapshot 2. Delete a snapshot
3. Revert to a snapshot q. Back to main menu
| Key | Action | Command |
|---|---|---|
| s | List snapshots for a VM | virsh snapshot-list <vm> |
| 1 | Create a named snapshot | virsh snapshot-create-as <vm> <name> |
| 2 | Delete a snapshot | virsh snapshot-delete <vm> <name> |
| 3 | Revert the VM to a snapshot | virsh snapshot-revert <vm> <name> |
| q | Return to main menu |
Example
Enter your choice: 1
Enter the name of the virtual machine: web-server
Name Creation Time State
------------------------------------------------------------
pre-upgrade 2025-01-14 09:12:03 +0000 shutoff
Enter the name for the new snapshot: post-upgrade
Domain snapshot post-upgrade created
5. Edit Properties
====================== Edit Properties ======================
1. Edit a VM 2. Edit a storage pool 3. Edit a network
4. Edit a snapshot q. Back to main menu
| Key | Target | Command | Prompts |
|---|---|---|---|
| 1 | VM definition | virsh edit <vm> | VM name |
| 2 | Storage pool definition | virsh pool-edit <pool> | Pool name (default: default) |
| 3 | Network definition | virsh net-edit <net> | Network name (default: default) |
| 4 | Snapshot metadata | virsh snapshot-edit --snapshotname <s> --domain <vm> | VM name, snapshot name |
| q | Return to main menu |
6. Firewall Settings
===================== Firewall Settings =====================
s. Show ufw status 1. Show listening ports 2. Allow port range
3. Deny port range 4. Allow single port 5. Deny single port
6. Delete a rule 7. Enable and reload ufw 8. Disable and reset ufw
q. Back to main menu
| Key | Action | Command | Prompts |
|---|---|---|---|
| s | Numbered ufw rule list | ufw status numbered | none |
| 1 | Listening TCP/UDP sockets | netstat -l | grep "tcp\|udp" | none |
| 2 | Allow a port range on TCP and UDP | ufw allow START:END/tcp + /udp, then ufw reload | Start port, end port |
| 3 | Deny a port range on TCP and UDP | ufw deny START:END/tcp + /udp, then ufw reload | Start port, end port |
| 4 | Allow a single port | ufw allow <port>, ufw reload | Port |
| 5 | Deny a single port | ufw deny <port>, ufw reload | Port |
| 6 | Delete a rule by number | ufw delete <n>, ufw reload | Rule number (see s first) |
| 7 | Enable the firewall and reload rules | ufw enable, ufw reload | none |
| 8 | Disable the firewall and wipe all rules | ufw disable, ufw reset | Confirmation (ufw asks) |
| q | Return to main menu |
Example — open a port range for a web stack
Enter your choice: 2
Enter starting port: 8000
Enter ending port: 8100
Rules updated
Rules updated (v6)
Firewall reloaded
7. Port Forwarding
==================================== Manage Port Forwarding ====================================
s. Show port forwarding rules 1. List DHCP leases from a network 2. Add port forwarding to a VM
3. Remove port forwarding from a VM 4. Edit port forwarding rule file q. Back to main menu
Port forwarding for VMs behind a NAT network is implemented with the libvirt
qemu hook at /etc/libvirt/hooks/qemu. When a VM starts,
the hook adds iptables DNAT rules; when it stops, they are removed.
Forwarding records are also stored in the SQLite database bashvm.db.
| Key | Action | Details |
|---|---|---|
| s | Show current NAT rules | Runs iptables -t nat -L -n -v and then 7.3 Show port forwarding rules to dump all tables in bashvm.db. |
| 1 | List DHCP leases on a network | virsh net-dhcp-leases <network> — use this to find the VM's IP before forwarding to it. |
| 2 | Add a forwarding rule to a VM | 7.1 Add port forwarding to a VM |
| 3 | Remove a forwarding rule from a VM | 7.2 Remove port forwarding from a VM |
| 4 | Edit the hook script directly | Opens /etc/libvirt/hooks/qemu in nano (falls back to vim). |
| q | Return to main menu |
Typical port-forwarding workflow
- 1 — list DHCP leases to confirm the guest's IP address.
- 2 — add the forwarding rule (host port → guest IP:guest port).
- Restart the VM so the
qemuhook runs. - s — verify the DNAT rule appears in the NAT table.
- 3 — remove the rule when it is no longer needed.
7.1 Add port forwarding to a VM (menu 7 → 2)
Adds a new port-forwarding rule set for a VM behind a NAT network. The first port in the range is reserved for SSH (forwarded to guest port 22); the remaining ports are forwarded 1:1 to the same guest port numbers.
Prompts
| Prompt | Validation |
|---|---|
| VM name | Must be non-empty |
| IP of the VM | Must match x.x.x.x |
| Start port range | Numeric; becomes the SSH port |
| Ending port | Numeric, ≥ start port |
What it does
- Resolves the VM's network and bridge name from
virsh dumpxml/virsh net-dumpxml(defaults todefault/virbr0). - Validates that the IP, start port, and end port are well-formed and ordered.
- Creates
bashvm.dband the network's table if missing. - Rejects the request if the VM name or IP already exists in the table, or if any of the three ports is not strictly greater than the highest port already recorded.
- Inserts
(vm_name, ipv4, ssh_port, start_port, end_port)into the table. - Appends a block to
/etc/libvirt/hooks/qemubetween#<vm>#and###<vm>###markers, containingiptablesdelete rules for thestopped/reconnectbranch and insert rules for thestart/reconnectbranch. Both TCP and UDP are covered for SSH and every port in the range. - Makes the hook executable and restarts
libvirtdif it was the only running VM. - Prints the SSH port and the forwarded port range.
7.2 Remove port forwarding from a VM (menu 7 → 3)
Removes a VM's port-forwarding rule set from both bashvm.db and
/etc/libvirt/hooks/qemu.
Prompts
- VM name — must be non-empty.
What it does
- Resolves the network and bridge (defaults
default/virbr0). - Aborts if
bashvm.dbdoes not exist. - Aborts if the VM has no row in the network's table.
- Aborts if the hook file contains no reference to the VM.
- Deletes the row from the table.
- Removes the block between
#<vm>#and###<vm>###from the hook.
7.3 Show port forwarding rules (menu 7 → s)
Dumps every table in bashvm.db with column headers. Used by the
port-forwarding menu to give a human-readable view of all reserved ports.
What it does
- Lists all tables from
sqlite_master. - For each table, prints the table name and then
SELECT *with-column -headerformatting.
table: default_table
vm_name ipv4 ssh_port start_port end_port
----------- --------------- -------- ---------- --------
web-server 192.168.122.50 49152 49153 49172
db-server 192.168.122.51 49173 49174 49193
8. VNC / Console Access
======================== VNC / Console Access ========================
s. Show assigned VNC ports 1. Add VNC port with password 2. Remove VNC port
3. Console into a vm q. Back to main menu
| Key | Action | Details |
|---|---|---|
| s | Show VNC port per VM | Iterates over all VMs, reports not running for stopped domains, otherwise maps the VNC display number N to TCP port 5900 + N, or shows none if VNC is not configured. |
| 1 | Add VNC access with a password | Prompts for VM name and password, then rewrites the VM XML to inject a full device block: QEMU guest agent channel, USB tablet, PS/2 mouse and keyboard, <graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0' passwd='…'>, ICH9 sound, QXL video, virtio balloon and virtio RNG. Redefines the domain. |
| 2 | Remove VNC access | Rewrites the XML with the same device block but without the <graphics> element, removing the VNC listener. |
| 3 | Open a serial console | virsh console <vm> — exit with Ctrl+]. |
| q | Return to main menu |
Example — check ports
Enter your choice: s
VM: web-server, VNC Port: 5901
db-server is not running (state: shut off)
VM: test-vm, VNC Port: none
Connecting
# from your workstation
vncviewer 192.0.2.10:5901
9. System Monitor
Selecting 9 immediately launches btop — a
full-screen terminal resource monitor showing CPU, memory, disks, network, and
processes for the host.
Enter your choice: 9
# btop opens full screen; press q to quit and return to the BashVM main menu
10. VM Monitor
==================================== VM Monitor ====================================
s. Show all virtual machines 1. VCPU usage of a VM 2. Memory usage of a VM
3. Disk usage of a VM 4. Network usage of a VM 5. All usage metrics of a VM
q. Back to main menu
| Key | Metric | Method |
|---|---|---|
| s | VM inventory | virsh list --all |
| 1 | Per-vCPU utilisation | Reads vcpu.N.time from virsh domstats, sleeps 1 s, reads again, divides the delta by the elapsed time, and prints a percentage for each vCPU. |
| 2 | Memory utilisation | Ratio of balloon.rss to balloon.rss + balloon.available from virsh domstats. |
| 3 | Disk utilisation of the guest filesystem | virsh guestinfo fields fs.0.used / fs.0.total. |
| 4 | Network throughput | Finds the VM's vNIC with virsh domiflist, then ifstat -i <iface> 1 1. |
| 5 | Combined metrics dashboard | 10.1 All usage metrics of a VM — runs all of the above in one pass. |
| q | Return to main menu |
Example — per-vCPU usage
Enter your choice: 1
Enter the name of the VM: web-server
CPU 0 Usage: 12.43%
CPU 1 Usage: 3.07%
10.1 All usage metrics of a VM (menu 10 → 5)
Combined per-VM metrics dashboard. Equivalent to running options 1–4 of the VM Monitor menu in sequence for a single VM.
Prompts
- VM name — validated with
virsh dominfo; invalid names exit silently.
What it reports
- Per-vCPU utilisation — loops from vCPU 0 to
count-1, sleeping 1 s betweendomstatsreads and normalising by10⁹ ns. - Memory usage —
balloon.rss / (balloon.rss + balloon.available) × 100. - Disk usage —
fs.0.used / fs.0.total × 100fromvirsh guestinfo. - Network throughput —
ifstat -i <vNIC> 1 1.
Enter the VM name: web-server
CPU 0 Usage: 12.43%
CPU 1 Usage: 3.07%
Memory Usage: 41.20%
Disk Usage: 58.71%
eth0
KB/s in KB/s out
0.00 0.00
11. SQLite Editor
The SQLite Editor provides menu-driven access to the per-network tables in
bashvm.db (typically holding VM name, IPv4 address, SSH port, and a port
range used for port-forwarding bookkeeping). The main script's option
11 simply launches this editor.
Enter your choice: 11
# The SQLite editor takes over; quit it to return to the main menu
Startup prompts
- Network name — validated against
virsh net-list --all. Repeats until a real network is entered. Defaults todefault.
The chosen network's table is created if it does not already exist.
Editor menu
============================ SQLite Editor Menu ============================
s. View all entries 1. Add new VM entry 2. Delete VM entry
3. Update VM entry 4. Delete current table q. Exit
| Key | Action | Details |
|---|---|---|
| s | View all entries | Column-aligned table with headers, ordered by vm_name. |
| 1 | Add VM entry | Prompts for VM name (non-empty), IPv4 (validated x.x.x.x), SSH port, start port, end port. Ports accept blank for NULL; non-numeric input is rejected. |
| 2 | Delete VM entry | Confirms the row exists before deleting. |
| 3 | Update VM entry | Choose field 1–4 (IPv4, SSH port, start port, end port); validators re-run for the new value. |
| 4 | Delete current table | Drops the whole table. Requires a y/yes confirmation. Cannot be undone. |
| q | Exit | Returns to the main menu. |
12. Installation & Uninstallation
Two standalone scripts handle first-time setup and complete removal of the packages
used by BashVM. They are not invoked from the main menu; run them directly
from the shell before the first bashvm.sh launch (installer) or when
decommissioning the host (uninstaller).
12.1 Installer (run first)
A fully automated setup script for Debian/Ubuntu hosts. Installs every package BashVM needs, initializes the default storage pool and network, and configures VNC over TLS. It does not prompt for input.
Running
chmod +x bashvm-installer.sh
sudo ./bashvm-installer.sh
Packages installed
qemu-kvm,libvirt-daemon-system,libvirt-clients,qemu-utilsbridge-utils,net-tools,dnsmasq(only if not already present)virt-manager,cloud-initufw,btop,bc,ifstat,ndppd,sqlite3
What it does
- Installs every package with
apt install -yand exits on the first failure. - Starts and enables
libvirtdif it is not already active. - Defines, starts, and autostarts the
defaultstorage pool at/var/lib/libvirt/imagesif missing or inactive. - Defines, starts, and autostarts the
defaultnetwork if missing or inactive. - Generates a TLS CA and server certificate in
/etc/pki/libvirt-vnc:ca-cert.pem,ca-key.pem(CA)server-cert.pem,server-key.pem(server, with all detected IPv4/IPv6 addresses in the SAN)- 10-year validity, private keys chmod 600
- Backs up
/etc/libvirt/qemu.confto a timestamped.bakfile, then sets:vnc_tls = 1vnc_tls_x509_cert_dir = "/etc/pki/libvirt-vnc"
- Restarts
libvirtdif no VMs are currently running. If VMs are running, it prints the restart command and a warning instead. - Prints the final hint: "If no errors above then you can run bashvm with
bash bashvm.sh".
12.2 Uninstaller (destructive)
Removes every package that the installer would have installed. It does not touch
user data (VM disks, bashvm.db, network XML, or the qemu hook) — those
are left for you to remove manually.
Running
chmod +x bashvm-uninstaller.sh
sudo ./bashvm-uninstaller.sh
Prompts
- The script prints a warning that it will remove BashVM's packages.
- You must type
remove(case-insensitive) to proceed, orcancelto exit. Any other input re-prompts.
What it does
- Removes each package with
apt remove -yand exits on the first failure:qemu-kvm,libvirt-daemon-system,libvirt-clients,bridge-utils,qemu-utils,virt-manager,cloud-init,net-tools,ufw,btop,bc,ifstat,ndppd,sqlite3. - Runs
apt autoremove -yto clean up now-orphaned dependencies. - Prints a completion message.
Common Workflows
First-time host setup
- 12.1 — run the installer to install packages and configure VNC TLS.
- Confirm
systemctl status libvirtdis active. - 1 → 1 — list VMs (should be empty).
- 2 → s — confirm the
defaultpool is active. - 3 → s — confirm the
defaultnetwork is active.
Create a new VM from scratch (ISO install)
- 2 → 9 — download the installation ISO into the
defaultpool (or let 1.1 Create a new / existing VM do it). - 2 → 6 — create a qcow2 volume for the VM's disk.
- 1 → 8 — create the VM definition interactively.
- 1 → 11 — console in to run the installer, or use VNC (menu 8).
- 1 → 6 — enable autostart so it comes back after a host reboot.
Deploy a cloud-image VM in one step
- 1 → 10 — 1.2 Create a new VM (Automated) handles download, cloud-init, DHCP reservation, and port forwarding.
- Note the printed IPv4, SSH port, username, and password.
- 1 → 11 — console in to verify the guest is healthy.
- 7 → s — confirm the forwarding rules landed in the hook.
Duplicate an existing VM
- 1 → 4 — gracefully shut down the source VM (important:
virt-cloneneeds a consistent disk). - 1 → 14 — clone to a new name (or 1 → 15 for several copies).
- 1 → 2 — start the clone.
- 8 → 1 — add a VNC password if you need graphical access.
Expose a guest web server to the LAN
- 3 → 4 — make sure the VM sits on a NAT network with a known subnet.
- 3 → 9 — reserve a fixed IP for the VM's MAC address.
- 7 → 1 — confirm the guest picked up the reserved lease.
- 7 → 2 — forward host port 8080 to
<guest-ip>:80. - 6 → 4 — allow port 8080 through ufw.
- 7 → s — verify the DNAT rule is present.
Roll back a risky change
- 4 → 1 — snapshot the VM before the change.
- Apply the change.
- 4 → 3 — revert to the snapshot if something breaks.
- 4 → 2 — clean up the snapshot when you are happy.
Add DHCPv6 to an existing NAT network
- Back up the network XML:
cp /etc/libvirt/qemu/networks/default.xml ~/default.xml.bak. - 3 → 11 — run 3.2 Add DHCPv6 to a NAT network and supply the interface, gateway, prefix, and range.
- Verify with
virsh net-dumpxml default | grep ipv6. - Restart guest VMs so they pick up a DHCPv6 address.
Delete a VM completely
- 1 → 4 — graceful shutdown (or 1 → 5 to force).
- 1 → 13 — 1.4 Delete a VM; type
DELETEto confirm. - Check the network no longer has a reservation: 3 → 1.
- Check the hook no longer has a block: 7 → 4 and search for the VM name.
Decommission a host
- Back up
bashvm.db, network XML, and the qemu hook. - 12.2 — run the uninstaller; type
removeto confirm. - Manually remove user data left behind (VM disks,
/etc/pki/libvirt-vnc, etc.).
Troubleshooting & Notes
| Symptom | Likely cause | Resolution |
|---|---|---|
command not found: bashvm-*.sh |
Helper scripts are not in the working directory or on PATH. |
Run the main script from the directory containing all helper scripts, or add that directory to PATH. |
error: failed to connect to the hypervisor |
libvirtd not running, or the script is not run as root / not in the libvirt group. |
Run 12.1 Installer, or sudo systemctl enable --now libvirtd, then run as root. |
No pool with this name was found |
The pool name typed does not exist. | Press s in the storage menu to see exact pool names; remember the prompt defaults to default on an empty line. |
Please shutdown the vm before running this again |
Undefine (1 → 9) or delete (1 → 13) was attempted against a running VM. | Shut it down with 1 → 4 (graceful) or 1 → 5 (force), then retry. |
| Disk usage shows errors or blank values | virsh guestinfo returns nothing. |
Install and start qemu-guest-agent inside the guest. |
| Downloaded ISO not visible in BashVM | Pool metadata not refreshed. | Deactivate and reactivate the pool (2 → 3 then 2 → 2), then list volumes with 2 → 1. |
| Port forward has no effect | qemu hook missing, not executable, or the VM was not restarted. |
Check /etc/libvirt/hooks/qemu (7 → 4), chmod +x it, restart the VM, verify with 7 → s. |
ssh_port, start_port and end_port must all be higher… |
New rule set overlaps or undercuts an existing range in bashvm.db. |
Use higher port numbers, or inspect/clean the table via menu 11. |
The vm name 'x' already exists in the database |
A port-forwarding record already exists for that VM. | Remove it with 7 → 3 first, or choose a different VM name. |
This function only works on NAT networks currently |
3.2 Add DHCPv6 to a NAT network was run against a routed or macvtap network. | Create or select a NAT network first (menu 3 → 4). |
| VNC TLS connection fails after installer | The VNC client is not configured for TLS, or does not trust the generated CA. | Configure the client to use TLS and import /etc/pki/libvirt-vnc/ca-cert.pem. Existing VMs need a stop/start before the new qemu.conf settings apply. |
| VM will not start after editing XML | Invalid device addresses or duplicate MACs introduced via menu 5. | Restore from a virsh dumpxml backup or re-run virsh edit to correct the offending element. |
Invalid choice. Please enter a valid option. |
Typo or a key that does not exist in the current menu. | Re-enter the key exactly as listed; q always leaves the current menu. |
General notes
- No confirmation prompts on most destructive actions. Options such as 1 → 5 (force shutdown), 2 → 5 (delete pool), 3 → 8 (delete network), 4 → 2 (delete snapshot), and 6 → 8 (ufw reset) act immediately. Only 1 → 13 (1.4 Delete a VM) requires typing
DELETE, and 12.2 Uninstaller requires typingremove. - Default values. Empty input at a
[default]prompt meansdefault. - Restart expectations. DHCP reservation changes, network edits, VNC/device XML changes, and port-forwarding rules usually require a VM stop and start rather than a reboot or hot-plug.
- Database file.
bashvm.dbis created relative to the current working directory. Run all scripts from the same directory, or the port-forwarding scripts will see an empty database. - Logging. The scripts do not log to a file; errors come from the underlying
virsh/ufw/iptables/sqlite3output as it happens. Scroll back in your terminal to review it. - Editor. Menu 5 and 7 → 4 invoke an editor via
$EDITORor explicitnano/vim. SetEDITORif you prefer something else. - Systemd units.
bashvm-ipv6-accept-ra.serviceis created by the DHCPv6 and routed-IPv6 options to re-applyaccept_ra = 2on boot. Remove it withsystemctl disable --now bashvm-ipv6-accept-ra.serviceif you no longer use IPv6. - Root required. All scripts that touch libvirt, iptables, ufw, or systemd unit files must be run as root.