Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
templates/cloud-config.yml
templates/network-config.yml
templates/launch-vm.ini
templates/*lab*
templates/*local*
Expand Down
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# **virt-install-cloud**

Bash scripts and templates to **create, deploy, and remove cloud-init-enabled virtual machines**
using `virt-install`. It has been tested on **Ubuntu 26.04 LTS** and works
using `virt-install`. It has been tested on **Ubuntu 24.04 LTS** and **Gentoo** and works
on other recent Ubuntu/Debian systems with a proper libvirt setup.

---
Expand All @@ -15,15 +15,16 @@ sudo apt update
sudo apt install libvirt-daemon-system libvirt-clients qemu-kvm qemu-utils virtinst wget
```

For non-root usage, add your user to the `libvirt` and `kvm` groups:
For non-root usage, add your user to the `libvirt`, `qemu`, and `kvm` groups:

```bash
sudo usermod -aG libvirt,kvm $USER
sudo usermod -aG libvirt,kvm,qemu $USER
```

### **Required Tools (Local or Remote)**

These commands must be available on the host where you run the scripts (local system or the remote hypervisor when using `LIBVIRT_DEFAULT_URI`):
These commands must be available on the host where you run the scripts
(local system or the remote hypervisor when using `LIBVIRT_DEFAULT_URI`):

- `virsh`
- `virt-install`
Expand All @@ -38,19 +39,22 @@ These commands must be available on the host where you run the scripts (local sy
Example templates are provided in the `templates` directory:

- `cloud-config.yml-example`
- `network-config.yml-example`
- `launch-vm.ini-example`

To set up your configuration:

```bash
cp templates/cloud-config.yml-example templates/cloud-config.yml
cp templates/network-config.yml-example templates/network-config.yml
cp templates/launch-vm.ini-example templates/launch-vm.ini
```

Edit:

- **`launch-vm.ini`** to configure network, CPUs, RAM, storage pool, etc.
- **`cloud-config.yml`** to define the default user, password, and SSH key.
- **`network-config.yml`** to use anything besides default DHCP.

### **Generate a password hash**

Expand Down Expand Up @@ -78,6 +82,12 @@ VMPOOL=vm-pool
CONSOLE="pty,target_type=virtio"
```

> [!IMPORTANT]
> The OS distro names are only as current as the version of `osinfo-db`
> installed in your environment. If the name is not found, eg, `rocky10`,
> then you need to edit the template file and change the OSVARIANT to
> `unknown` in order to install the desired OS version.

---

## **Storage Pool Setup**
Expand Down Expand Up @@ -192,14 +202,16 @@ export LAUNCH_VM_INI="/path/to/custom-launch-vm.ini"

> Uses `/path/to/custom-launch-vm.ini` instead of `templates/launch-vm.ini`.

### **Use a custom cloud-init file**
### **Use a custom cloud-init or network config file**

```bash
export CLOUD_CONFIG="/custom/path/cloud-config.yml"
export NET_CONFIG="/custom/path/network-config.yml"
./launch-vm.sh -d ubuntu22.04 -n test-vm
```

> Uses `/custom/path/cloud-config.yml` instead of `templates/cloud-config.yml`.
> Uses `/custom/path/cloud-config.yml` instead of `templates/cloud-config.yml`
> and/or `/custom/path/network-config.yml` instead of `templates/network-config.yml`.

### **Use a specific Python interpreter for `virt-install`**

Expand Down
48 changes: 46 additions & 2 deletions bin/launch-vm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,12 @@ create-extra-volume() {
}

vm-setup() {
NET_ARG=""
CLOUD_CONFIG_FILE="${CLOUD_CONFIG:-${LVTEMPLATES}/cloud-config.yml}"
NET_CONFIG_FILE="${NET_CONFIG:-${LVTEMPLATES}/network-config.yml}"
if [[ -f "${NET_CONFIG_FILE}" ]]; then
NET_ARG=",network-config=${NET_CONFIG_FILE}"
fi

if [[ ! -f "${CLOUD_CONFIG_FILE}" ]]; then
echo "Error: Cloud-init config file '${CLOUD_CONFIG_FILE}' not found."
Expand Down Expand Up @@ -301,13 +306,51 @@ EOF
--virt-type kvm \
--import \
--cloud-init "user-data=${CLOUD_CONFIG_FILE},meta-data=${META_DATA_FILE}" \
--wait \
--noautoconsole \
"${console_arg[@]}" \
--video none \
--graphics=spice,port=-1,listen=localhost \
--qemu-commandline="-smbios type=1,serial=ds=nocloud;h=${VMNAME}.${DOMAIN}"
}

get-vminfo() {
IP=${IP:-}
if [ -f "${NET_CONFIG_FILE}" ] && [ -z "${IP}" ] ; then
echo "Detected NET_CONFIG_FILE in use, aborting VM IP loop."
exit 0
fi
timeout=60 # seconds
if [[ ! -n "$IP" ]]; then
echo "Waiting for $VMNAME IP address..."
for ((i = 0; i < timeout; i++)); do
DOM=$(virsh -q domifaddr "$VMNAME")
read -ra arr <<<"$DOM"
if [[ -n "${arr[@]}" ]]; then
IP="${arr[3]%/*}"
fi

if [[ -n "$IP" ]]; then
break
fi
sleep 1
done
fi

if [[ -n "$IP" ]]; then
echo ""
echo "SSH to ${VMNAME}:"
echo " ssh ${IP}"
echo " ssh ubuntu@${IP}"
echo ""
echo "Checking for ${IP} in known_hosts file"
grep -q ${IP} ${HOME}/.ssh/known_hosts &&
echo "Found entry for ${IP}. Removing" &&
(sed --in-place "/^${IP}/d" ~/.ssh/known_hosts) ||
echo "No entries found for ${IP}"
else
echo "Timed out waiting for DHCP lease"
fi
}

# -------------------------------------------------------------------------
# Main Execution Flow
# -------------------------------------------------------------------------
Expand All @@ -320,3 +363,4 @@ clone-base
resize-clone
create-extra-volume
vm-setup
get-vminfo
12 changes: 12 additions & 0 deletions templates/network-config.yml-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
network:
version: 2
ethernets:
ens3:
dhcp4: yes
dhcp6: no
#addresses: [192.168.122.21/24]
#nameservers:
# addresses: [192.168.122.1]
#routes:
#- to: 0.0.0.0/0
# via: 192.168.122.1
8 changes: 3 additions & 5 deletions templates/rockylinux10.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
URL=https://dl.rockylinux.org/pub/rocky/10/images/x86_64/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2
SOURCE=source-rockylinux10.img
# libosinfo (osinfo-db 0.20250606) has no rocky10 entry yet; the nearest known
# variant is used only to pick sensible default device models.
# Bump this to rocky10 once `osinfo-query os` lists it.
OSVARIANT=rocky9
SOURCE=source-rocky10.img
#OSVARIANT=rocky10
OSVARIANT=rocky-unknown
CONSOLE="pty,target_type=virtio"
2 changes: 1 addition & 1 deletion templates/rockylinux9.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
URL=https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
SOURCE=source-rockylinux9.img
SOURCE=source-rocky9.img
OSVARIANT=rocky9
CONSOLE="pty,target_type=virtio"
6 changes: 2 additions & 4 deletions templates/ubuntu26.04.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
URL=https://cloud-images.ubuntu.com/resolute/current/resolute-server-cloudimg-amd64.img
SOURCE=source-ubuntu26.04.img
# libosinfo (osinfo-db 0.20250606) has no ubuntu26.04 entry yet; the nearest
# known variant is used only to pick sensible default device models.
# Bump this to ubuntu26.04 once `osinfo-query os` lists it.
OSVARIANT=ubuntu25.10
#OSVARIANT=ubuntu26.04
OSVARIANT=unknown
CONSOLE="pty,target_type=virtio"