Skip to content

d3bvstack/vmctl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vmctl

One-command Debian VM lifecycle: preseed → install → launch.

License Shell Platform


Table of Contents


Overview

vmctl is a single Bash entrypoint that drives the full Debian VM lifecycle under QEMU/KVM — from first configuration to a running, SSH-accessible machine.

  Wizard ──► Preseed ──► Download ──► Inject  ──► Install ──► Launch
     │           │            │           │            │           │
  collect     generate     fetch       prepend     unattended  boot from
  config      .cfg file    netboot     preseed     d-i run     disk image
  options     + hash pwd   kernel &    to initrd
                           initrd.gz

  Input: interactive prompts  |  --yes  |  --config FILE  |  CLI flags

The wizard lets you choose the Debian release, configure multiple port-forward rules, and set all preseed options. After installation a .vmcfg sidecar is written next to the disk image so you can re-launch any previously built VM with ./vmctl.sh --launch and pick from a menu.

Config priority (lowest → highest): hardcoded defaults → ~/.vmctlrc--config FILE → CLI flags


Prerequisites

Tool Package (Debian/Ubuntu) Purpose
qemu-system-x86_64 qemu-system-x86 VM emulation — KVM required
qemu-img qemu-utils qcow2 disk image creation
cpio cpio Preseed injection into initrd
curl curl Installer download + Docker install
awk gawk Preseed template substitution
openssl openssl SHA-512 password hashing
sha256sum coreutils Installer integrity verification
nc netcat-openbsd SSH port polling in verify_deploy.sh
sudo apt-get install \
  qemu-system-x86 qemu-utils cpio curl gawk openssl netcat-openbsd

Quick Start

# Interactive full run — wizard + install
./vmctl.sh

# Fully non-interactive, all defaults accepted
./vmctl.sh --yes

# Load a config file, prompt only for missing values
./vmctl.sh --config my.cfg

# Generate a preseed file only, no VM created
./vmctl.sh --preseed-only

# Use an existing preseed, skip the wizard
./vmctl.sh --deploy-only preseeds/debian-vm.cfg

# Boot a specific disk image directly
./vmctl.sh --launch .vms/debian-vm.qcow2

# Interactive VM picker — choose from all saved VMs
./vmctl.sh --launch

# Full run with SSH readiness polling after install
./vmctl.sh --yes --wait-ssh

# Dry run — print QEMU commands without executing
./vmctl.sh --yes --dry-run

Configuration

Config file example

Create a file with KEY=value lines and pass it with --config FILE, or save it as ~/.vmctlrc to load it automatically on every run.

# ~/.vmctlrc  (or any file passed with --config)

# VM identity
VM_NAME="myvm"
DEBIAN_VERSION="bookworm"

# Guest identity (preseed)
HOSTNAME="myhost"
DOMAIN="home.lan"
USERNAME="alice"
FULLNAME="Alice Example"
PASSWORD="secret"          # plain text — hashed to SHA-512 before embedding
SUDO_NOPASSWD="y"

# Resources
RAM="4096"
VCPUS="4"
DISK_SIZE="30"

# Networking — multiple port forwards, comma-separated
PORTS="2222:22,8080:80,9090:9090"
NET_MODE="user"

# Localization (preseed)
LOCALE="en_US.UTF-8"
KEYMAP="us"
TIMEZONE="America/New_York"

# Features
INSTALL_DOCKER="y"
ADD_DOCKER_GROUP="y"
CLEANUP_LEVEL="standard"
POWEROFF="true"

# VirtFS shared folder (optional)
SHARE_NAME="hostshare"
SHARE_GUEST_PATH="/home/alice/share"
SHARE_PATH="/home/alice/projects"

VM / runtime keys

Key Default CLI flag Description
VM_NAME debian-vm --name VM name and disk image prefix
RAM 2048 --ram Memory in MB
VCPUS 2 --cpus Virtual CPU count
DISK_SIZE 20 --disk-size Disk image size in GB
DEBIAN_VERSION stable --version Debian release (e.g. bookworm)
DISPLAY_MODE nographic --display nographic | gtk | sdl
NET_MODE user --net-mode user | tap | bridge
PORTS (empty) --ports Port map HOST:GUEST,...
TAP_IF tap0 --tap-if TAP interface (tap mode)
BRIDGE_NAME br0 --bridge Bridge name (bridge mode)
MAC_ADDR (empty) --mac MAC address (optional)
CACHE_DIR .cache/ --cachedir Installer file cache directory
VM_DIR .vms/ --vmdir qcow2 disk images directory
SHARE_PATH ./ --share-path Host directory for VirtFS share

Preseed / guest keys

Key Default CLI flag Description
HOSTNAME debian-vm --hostname Guest hostname
DOMAIN local --domain Guest domain
USERNAME user --username Primary user account name
FULLNAME Debian User --fullname User display name
PASSWORD changeme --password Plain text password (hashed to SHA-512)
SUDO_NOPASSWD n --sudo-nopasswd y to enable passwordless sudo
UIDVAL caller's UID --uid Guest user UID
GIDVAL caller's GID --gid Guest user GID
LOCALE en_US.UTF-8 --locale System locale
KEYMAP us --keymap Keyboard layout
TIMEZONE UTC --timezone System timezone
DISK /dev/vda --disk-target Installer target disk
PROXY (empty) --proxy APT proxy URL
EXTRA_PACKAGES (empty) --extra-packages Space-separated extra packages
INSTALL_DOCKER n --docker y to install Docker
ADD_DOCKER_GROUP n --add-docker-group y to add user to docker group (requires Docker)
SHARE_NAME (empty) --share-name VirtFS mount tag
SHARE_GUEST_PATH (empty) --share-guest-path Mount point inside guest
POST (empty) --post Post-install shell command
POWEROFF true --poweroff Power off guest after install
CLEANUP_LEVEL standard --cleanup-level none | standard | aggressive

Operational Modes

Mode Trigger What happens
Full (default) Wizard → preseed → cache → disk → install → [boot]
Preseed only --preseed-only Wizard → write preseed file → exit
Deploy only --deploy-only PRESEED Skip wizard; use supplied preseed → install
Launch --launch DISK Boot a specific disk image directly
Launch picker --launch Menu of saved VMs; picks one and boots it
Dry run --dry-run Print QEMU commands without executing
Wait for SSH --wait-ssh After install, poll SSH port until open (up to 5 min)

Interactive Wizard

The wizard runs 10 sections in order and skips any section whose value was already supplied via --config or a CLI flag. --yes bypasses all prompts and accepts defaults.

Section Topic Key variables set
0 Debian release DEBIAN_VERSION
1 Identity HOSTNAME, DOMAIN, USERNAME, FULLNAME, PASSWORD, SUDO_NOPASSWD
2 Access SSH key, UIDVAL, GIDVAL
3 Localization LOCALE, KEYMAP, TIMEZONE
4 Storage DISK
5 Network (APT) PROXY
6 Packages EXTRA_PACKAGES
7 Features INSTALL_DOCKER, ADD_DOCKER_GROUP, SHARE_NAME, SHARE_GUEST_PATH
8 Post-install POST, POWEROFF
9 Optimization CLEANUP_LEVEL

Debian release selection

The wizard presents a numbered menu. You can pick a number or type any codename directly:

=== 0. Debian Release ===
  [1] stable     (current stable release — default)
  [2] bookworm   (Debian 12)
  [3] bullseye   (Debian 11)
  [4] buster     (Debian 10)
  [5] trixie     (testing)
  [6] sid        (unstable)

Selection or custom codename [stable]:

The chosen version determines the netboot installer URL and the SHA256SUMS verification URL used to validate the downloaded kernel and initrd.

Port forwarding

Port forwards are collected one at a time in a loop. The current list is shown after each addition so you can verify what has been configured. Leave the prompt empty to finish.

=== Port Forwarding ===
  No mappings configured yet.
  Add mapping HOST:GUEST (e.g. 2222:22), or press Enter to finish: 2222:22

  Current mappings:
    2222:22
  Add mapping HOST:GUEST (e.g. 2222:22), or press Enter to finish: 8080:80

  Current mappings:
    2222:22
    8080:80
  Add mapping HOST:GUEST (e.g. 2222:22), or press Enter to finish:
  Configured: 2222:22,8080:80

Passing --ports "2222:22,8080:80" on the CLI skips the interactive loop entirely. Any number of HOST:GUEST pairs are accepted in both styles.


VM Config Files

After the disk image is created (before the install starts), vmctl writes a <VM_NAME>.vmcfg sidecar alongside the .qcow2 in $VM_DIR (default .vms/). Because it is written before installation begins, the sidecar persists even if an install fails.

# .vms/myvm.vmcfg  (auto-generated)
VM_NAME="myvm"
DISK_IMG="/data/vmctl/.vms/myvm.qcow2"
RAM="4096"
VCPUS="4"
PORTS="2222:22,8080:80"
DISPLAY_MODE="nographic"
NET_MODE="user"
TAP_IF="tap0"
BRIDGE_NAME="br0"
MAC_ADDR=""
SHARE_NAME=""
SHARE_PATH="/data/vmctl"
VIRTFS_ARGS=""
DEBIAN_VERSION="bookworm"
CREATED_AT="2026-03-02 14:30:00"

Running ./vmctl.sh --launch without a disk path scans .vms/*.vmcfg, filters to those whose DISK_IMG still exists, and presents a menu:

=== Available VMs ===
  [1] myvm      RAM: 4096M  Ports: 2222:22,8080:80  Debian: bookworm
      Disk   : /data/vmctl/.vms/myvm.qcow2
      Created: 2026-03-02 14:30:00

  [2] testvm    RAM: 2048M  Ports: (none)            Debian: stable
      Disk   : /data/vmctl/.vms/testvm.qcow2
      Created: 2026-03-02 09:15:00

Select VM [1]:

The selected config is sourced into the shell and all settings — RAM, ports, VirtFS, display mode, network mode — are restored exactly as they were during installation.


Project Structure

vmctl/
  vmctl.sh                    ← single entrypoint
  lib/
    common.sh                 ← logging, colors, validators, port collector
    config.sh                 ← defaults, CLI parsing, save_vm_config(), select_vm()
    preseed.sh                ← wizard, preseed generation, injection, poweroff detection
    cache.sh                  ← installer download + SHA256 integrity check
    qemu.sh                   ← QEMU command builder, disk creation, run phases
  scripts/
    replace_placeholders.awk  ← awk-based preseed template substitution engine
  templates/
    preseed_template.cfg      ← Debian preseed template with placeholders
  preseeds/                   ← generated preseed files (*.cfg)
  .vms/                       ← qcow2 disk images + VM config sidecars (*.vmcfg)
  .cache/                     ← downloaded linux + initrd.gz (per Debian version)
  logs/                       ← per-run log files + latest.log symlink
  tools/
    verify_deploy.sh          ← SSH readiness checker (used by --wait-ssh)

License

Zero Clause BSD — do whatever you want, no conditions.

About

One-command Debian VM deployment.

Topics

Resources

License

Stars

Watchers

Forks

Contributors