diff --git a/README.md b/README.md index c92ebf0..ab34e0e 100644 --- a/README.md +++ b/README.md @@ -113,3 +113,15 @@ VM 'ubuntuTest' removed successfully. ## TODO - FreeBSD support is still in progress +- Check if used commands are available +./vm_create.sh: línea 52: mkpasswd: orden no encontrada +./vm_create.sh: línea 259: virt-install: orden no encontrada +./vm_create.sh: línea 261: virsh: orden no encontrada +qemu-img wget curl mkpass arp +sudo apt install --no-install-recommends qemu-system libvirt-clients libvirt-daemon-system whois virtinst net-tools +sudo chmod 750 /home/victor +sudo usermod -a -G libvirt $(whoami) +sudo usermod --append --groups earl libvirt-qemu + +- Refactoring variables, functions and scripts calls for legibility and maintenance +- add script for create default files (network, variables etc) diff --git a/env_scripts/common.sh b/env_scripts/common.sh new file mode 100644 index 0000000..9421094 --- /dev/null +++ b/env_scripts/common.sh @@ -0,0 +1,16 @@ +#!/bin/env bash +LIBVIRT_NET_MODEL="virtio" +LIBVIRT_NET_OPTION="network=$VM_NETWORK,model=$LIBVIRT_NET_MODEL" +OS_JSON_FILE="files/os_options.json" +#VM_BASE_DIR=${VM_BASE_DIR:-"${HOME}/.local/share/libvirt"} +#VM_BASE_DIR=${VM_BASE_DIR:-"${HOME}/var/lib/libvirt"} +VM_BASE_DIR="${HOME}/vms" +VM_BASE_IMAGES="base" +VM_DISK_EXTENSION="img" +VM_USERNAME="user" + +VM_IMAGE_PATH="${VM_BASE_DIR}/images/$1.img" +CI_IMAGE_PATH="${VM_BASE_DIR}/images/$1-cidata.iso" +VM_NETWORK="vmnetwork" +REPO_BRANCH="main" +REPO_SOURCE="https://raw.githubusercontent.com/vgenguita/kvm-cloudimage/refs/heads/${REPO_BRANCH}/env_scripts/" diff --git a/env_scripts/functions.sh b/env_scripts/functions.sh new file mode 100644 index 0000000..46d2918 --- /dev/null +++ b/env_scripts/functions.sh @@ -0,0 +1,279 @@ +#!/bin/env bash + +# Functions + +check_host_os() +{ + local HOST_OS=$(cat /etc/os-release | grep -v VERSION_ID |grep "ID=" | awk -F'=' '{print $2}') + if [ $HOST_OS == "debian" ]; then + source env_scripts/older_os.sh + else + source env_scripts/newer_os.sh + fi +} + +show_vm_menu() +{ + # Show dinamic menu + echo "Select VM OS:" + for entry in $(jq -r '.os_variants[] | @base64' "$OS_JSON_FILE"); do + decoded=$(echo "$entry" | base64 --decode) + id=$(echo "$decoded" | jq -r .id) + name=$(echo "$decoded" | jq -r .name) + echo "$id. $name" + done + + # ID_MAX calculation + ID_MAX=$(jq -r '[.os_variants[].id] | max' "$OS_JSON_FILE") + + # Read input + read -r -p "Enter your choice [1-${ID_MAX}]: " answer + if ! [[ "$answer" =~ ^[0-9]+$ ]] || (( answer < 1 || answer > ID_MAX )); then + echo "Invalid option. Please enter a number between 1 and ${ID_MAX}." + exit 1 + fi + + selected=$(jq -r ".os_variants[] | select(.id == $answer)" "$OS_JSON_FILE") + + if [ -z "$selected" ]; then + echo "Invalid option." + exit 1 + fi + + # Asignar variables + VM_OS_VARIANT=$(echo "$selected" | jq -r .variant) + VM_BASE_IMAGE_URL=$(echo "$selected" | jq -r .url) + VM_BASE_IMAGE=$(echo "$selected" | jq -r .origin_image_name) + VM_BOOT_TYPE=$(echo "$selected" | jq -r .boot_type) + VM_CHECKSUMS_URL=$(echo "$selected" | jq -r .md5sum) +} + +compare_checksum() +{ + CHECKSUM_TMP_FOLDER=$(mktemp) + curl -s -o "${CHECKSUM_TMP_FOLDER}" "${VM_CHECKSUMS_URL}" + VM_BASE_IMAGE_CHECKSUM=$(grep "$VM_BASE_IMAGE_NAME.${VM_BASE_IMAGE_EXTENSION}" "${CHECKSUM_TMP_FOLDER}" | awk '{print $1}') + if [[ "${VM_CHECKSUMS_URL}" == *"SHA256SUMS"* ]]; then + HASH_CMD="sha256sum" + elif [[ "${VM_CHECKSUMS_URL}" == *"SHA512SUMS"* ]]; then + HASH_CMD="sha512sum" + else + echo "ERROR: Unknown checksum type in URL: $CHECKSUM_URL" + exit 1 + fi + BASE_FILE_CHECKSUM=$(${HASH_CMD} ${VM_BASE_IMAGE_LOCATION} | awk '{print $1}') + if [ "${BASE_FILE_CHECKSUM}" = "${VM_BASE_IMAGE_CHECKSUM}" ]; then + echo "Checksum OK: ${BASE_FILE_CHECKSUM}" + else + echo "ERROR: MD5 checksum does NOT match!" + echo "Expected: ${VM_BASE_IMAGE_CHECKSUM}" + echo "Got: ${BASE_FILE_CHECKSUM}" + exit 1 + fi +} +## List Installed VMS +vm_list() +{ + virsh list +} + +vm_net_get_mac() +{ + local VM=$1 + MAC_VM=$(virsh domiflist "$VM" | awk '{ print $5 }' | tail -2 | head -1) + echo $MAC_VM +} +## Get VM ip (only on NAT) +vm_net_get_ip() +{ + local VM="$1" + # Obtener la dirección MAC de la interfaz de red + MAC_VM=$(vm_net_get_mac $VM) + if [[ -z "$MAC_VM" ]]; then + echo "Error: No se pudo encontrar la dirección MAC para '$VM'" + return 1 + fi + # Obtener la dirección IP a partir de la dirección MAC + VM_IP_ADDRESS=$(arp -a | grep "$MAC_VM" | awk '{ print $2 }' | sed 's/[()]//g') + if [[ -z "$VM_IP_ADDRESS" ]]; then + echo "Error: No se pudo encontrar la dirección IP para la dirección MAC '$MAC_VM'" + return 1 + fi + echo "$VM_IP_ADDRESS" +} + +vm_net_create_netplan() +{ + local VM=$1 + local MAV_VM=$2 +cat < "$VM_BASE_DIR/init/${VM}-netplan" +# This file is generated from information provided by the datasource. Changes +# to it will not persist across an instance reboot. To disable cloud-init's +# network configuration capabilities, write a file +# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: +# network: {config: disabled} +network: + ethernets: + enp1s0: + addresses: + - ${IP}/24 + nameservers: + addresses: + - 208.67.222.222 + - 208.67.220.220 + match: + macaddress: ${MAC_VM} + set-name: enp1s0 + version: 2 +EOF +} + +vm_net_bridge_set_ip() +{ + local VM="$1" + local IP="$2" + MAC_VM=$(vm_net_get_mac "$VM") + CURRENT_IP=$(vm_net_get_ip "$VM") + define_netplan + # Obtener la dirección IP de la máquina virtual + scp -i ${VM_BASE_DIR}/ssh/${VM} \ + -r $VM_BASE_DIR/init/${VM}-netplan \ + ${VM_USERNAME}@${CURRENT_IP}:50-cloud-init.yaml + ssh -i ${VM_BASE_DIR}/ssh/${VM} -l${VM_USERNAME} ${CURRENT_IP} "bash -s" -- < ../vm_example_scripts/apply_netplan.sh +} + +vm_net_set_bridge_mode() +{ + if [[ -n $VM_BRIDGE_INT ]]; then + LIBVIRT_NET_OPTION="model=virtio,bridge=${VM_BRIDGE_INT}" + fi +} +## Connect to an existent VM using ssh +vm_connect() +{ + local VM=$1 + local VM_IP=$(vm_net_get_ip "$VM") + ssh -i ${VM_BASE_DIR}/ssh/${VM} -l${VM_USERNAME} ${VM_IP} +} + +## Delete VM +vm_delete () +{ + local VM=$1 + echo "VM: $VM" + if [[ -f "$VM_IMAGE_PATH" ]]; then + # Safely remove the VM with confirmation + read -p "Are you sure you want to remove the VM '$VM' (y/N)? " confirm + if [[ "$confirm" =~ ^[Yy]$ ]]; then + # Attempt to stop the VM before deleting + virsh destroy "$VM" 2>/dev/null || true + # Delete VM definition and associated images + virsh undefine "$VM" 2>/dev/null || true + rm -fv "$VM_IMAGE_PATH" "$CI_IMAGE_PATH" + rm ${VM_BASE_DIR}/xml/$1.xml + rm ${VM_BASE_DIR}/ssh/$1 + rm ${VM_BASE_DIR}/ssh/$1.pub + rm ${VM_BASE_DIR}/init/$1-user-data + rm ${VM_BASE_DIR}/init/$1-meta-data + else + echo "VM removal cancelled." + fi + else + # Handle case where VM image is not found + echo "Cannot find VM image file '$VM_IMAGE_PATH'. No action taken." + fi +} +vm_download_base_image() +{ + VM_BASE_IMAGE_NAME=${VM_BASE_IMAGE%%.*} + VM_BASE_IMAGE_EXTENSION=${VM_BASE_IMAGE#*.} + VM_BASE_IMAGE_LOCATION="${VM_BASE_DIR}/${VM_BASE_IMAGES}/${VM_BASE_IMAGE_NAME}.${VM_BASE_IMAGE_EXTENSION}" + if ! test -f "${VM_BASE_IMAGE_LOCATION}"; then + wget -O "${VM_BASE_IMAGE_LOCATION}" ${VM_BASE_IMAGE_URL} + fi +} + +vm_create_guest_image() +{ + echo "Creating a qcow2 image file ${VM_BASE_DIR}/images/${VM_HOSTNAME}.${VM_DISK_EXTENSION} that uses the cloud image file ${VM_BASE_IMAGE_LOCATION} as its base" + if ! test -f "${VM_BASE_DIR}/images/${VM_HOSTNAME}.${VM_DISK_EXTENSION}"; then + qemu-img convert \ + -O qcow2 \ + "${VM_BASE_IMAGE_LOCATION}" \ + "${VM_BASE_DIR}/images/${VM_HOSTNAME}.${VM_DISK_EXTENSION}" + qemu-img resize \ + "${VM_BASE_DIR}/images/${VM_HOSTNAME}.${VM_DISK_EXTENSION}" \ + "${VM_DISK_SIZE}G" + sudo chown -R $USER:libvirt-qemu "${VM_BASE_DIR}/images/${VM_HOSTNAME}.${VM_DISK_EXTENSION}" + else + echo "${VM_BASE_DIR}/images/${VM_HOSTNAME}.${VM_DISK_EXTENSION} already exists. Delete VM with "delete" option" + exit 1 + fi +} + +vm_generate_ssh_hey() +{ + ssh-keygen -t rsa -b 4096 -N '' -f "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}" + chmod 600 ${VM_BASE_DIR}/ssh/${VM_HOSTNAME}.pub + SSH_PUB_KEY=$(cat "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}.pub") + #ssh-keygen -y -f "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}" > "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}".pub.txt + #SSH_PUB_KEY=$(cat "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}".pub.txt) + #rm "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}".pub.txt +} + +vm_gen_linux_user_data() +{ +VM_USER_PASS=$(tr -dc A-Za-z0-9 "$VM_BASE_DIR/init/${VM_HOSTNAME}-user-data" +#cloud-config +hostname: ${VM_HOSTNAME} +# manage_etc_hosts: false +ssh_pwauth: true +disable_root: true +users: +- name: ${VM_USERNAME} + hashed_passwd: ${VM_USER_PASS_HASH} + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + lock-passwd: false + ssh_authorized_keys: + - ${SSH_PUB_KEY} +EOF +} + +vm_gen_user_data() +{ + VM_USER_PASS=$(tr -dc A-Za-z0-9 "${VM_BASE_DIR}/xml/${VM_HOSTNAME}.xml" + +} diff --git a/files/meta-data b/files/meta-data new file mode 100644 index 0000000..555891f --- /dev/null +++ b/files/meta-data @@ -0,0 +1,2 @@ +instance-id: __VMname__ +local-hostname: __VMname__ diff --git a/files/network.xml b/files/network.xml new file mode 100644 index 0000000..2b0079c --- /dev/null +++ b/files/network.xml @@ -0,0 +1,14 @@ + + YOURNETWORK + + + + + + + + + + + + diff --git a/files/os_options.json b/files/os_options.json new file mode 100644 index 0000000..e51178a --- /dev/null +++ b/files/os_options.json @@ -0,0 +1,68 @@ +{ + "os_variants": [ + { + "id": 1, + "name": "Debian 12", + "variant": "$GUEST_OS_TYPE_DEBIAN", + "url": "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2", + "origin_image_name": "debian-12-generic-amd64.qcow2", + "md5sum": "https://cdimage.debian.org/images/cloud/bookworm/latest/SHA512SUMS", + "boot_type": "bios" + }, + { + "id": 2, + "name": "Ubuntu 20.04 server", + "variant": "ubuntu20.04", + "url": "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img", + "origin_image_name": "focal-server-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/focal/current/SHA256SUMS", + "boot_type": "bios" + + }, + { + "id": 3, + "name": "Ubuntu 20.04 minimal", + "variant": "ubuntu20.04", + "url": "https://cloud-images.ubuntu.com/minimal/releases/focal/release/ubuntu-20.04-minimal-cloudimg-amd64.img", + "origin_image_name": "ubuntu-20.04-minimal-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/minimal/releases/focal/release/SHA256SUMS", + "boot_type": "bios" + }, + { + "id": 4, + "name": "Ubuntu 22.04 server", + "variant": "ubuntu22.04", + "url": "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img", + "origin_image_name": "jammy-server-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/jammy/current/SHA256SUMS", + "boot_type": "uefi" + }, + { + "id": 5, + "name": "Ubuntu 22.04 minimal", + "variant": "ubuntu22.04", + "url": "https://cloud-images.ubuntu.com/minimal/releases/jammy/release/ubuntu-22.04-minimal-cloudimg-amd64.img", + "origin_image_name": "ubuntu-22.04-minimal-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/minimal/releases/jammy/release/SHA256SUMS", + "boot_type": "uefi" + }, + { + "id": 6, + "name": "Ubuntu 24.04 server", + "variant": "ubuntu22.04", + "url": "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img", + "origin_image_name": "noble-server-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/noble/current/SHA256SUMS", + "boot_type": "uefi" + }, + { + "id": 7, + "name": "Ubuntu 24.04 minimal", + "variant": "ubuntu22.04", + "url": "https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-24.04-minimal-cloudimg-amd64.img", + "origin_image_name": "ubuntu-24.04-minimal-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/minimal/releases/noble/release/SHA256SUMS", + "boot_type": "uefi" + } + ] +} diff --git a/files/user-data b/files/user-data new file mode 100644 index 0000000..7e7b119 --- /dev/null +++ b/files/user-data @@ -0,0 +1,12 @@ +ssh_pwauth: true +disable_root: true +users: +- name: user + ssh_authorized_keys: + - __SSH_KEY__ + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + groups: sudo + shell: /bin/bash + hashed_passwd: __USER_PASSWORD__ + lock-passwd: false + diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..9eec904 --- /dev/null +++ b/install.sh @@ -0,0 +1,13 @@ +#!/bin/env bash +#Define variable names on env_scripts/common.sh +#VM_NETWORK= +#VM_BASE_DIR= +#Install dependencies - TODO +source variables/common.sh + +mkdir -p "${VM_BASE_DIR}"/{images,xml,init,base,ssh} +cp files/network.xml ${VM_BASE_DIR}/xml/network.xml +sed -i "s/YOURNETWORK/${VM_NETWORK}/g" ${VM_BASE_DIR}/xml/network.xml +virsh net-define ${VM_BASE_DIR}/xml/network.xml +virsh net-autostart ${VM_NETWORK} +virsh net-start ${VM_NETWORK} \ No newline at end of file diff --git a/kvm_cloudimage.sh b/kvm_cloudimage.sh deleted file mode 100644 index e69de29..0000000 diff --git a/os_option_test.json b/os_option_test.json new file mode 100644 index 0000000..e51178a --- /dev/null +++ b/os_option_test.json @@ -0,0 +1,68 @@ +{ + "os_variants": [ + { + "id": 1, + "name": "Debian 12", + "variant": "$GUEST_OS_TYPE_DEBIAN", + "url": "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2", + "origin_image_name": "debian-12-generic-amd64.qcow2", + "md5sum": "https://cdimage.debian.org/images/cloud/bookworm/latest/SHA512SUMS", + "boot_type": "bios" + }, + { + "id": 2, + "name": "Ubuntu 20.04 server", + "variant": "ubuntu20.04", + "url": "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img", + "origin_image_name": "focal-server-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/focal/current/SHA256SUMS", + "boot_type": "bios" + + }, + { + "id": 3, + "name": "Ubuntu 20.04 minimal", + "variant": "ubuntu20.04", + "url": "https://cloud-images.ubuntu.com/minimal/releases/focal/release/ubuntu-20.04-minimal-cloudimg-amd64.img", + "origin_image_name": "ubuntu-20.04-minimal-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/minimal/releases/focal/release/SHA256SUMS", + "boot_type": "bios" + }, + { + "id": 4, + "name": "Ubuntu 22.04 server", + "variant": "ubuntu22.04", + "url": "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img", + "origin_image_name": "jammy-server-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/jammy/current/SHA256SUMS", + "boot_type": "uefi" + }, + { + "id": 5, + "name": "Ubuntu 22.04 minimal", + "variant": "ubuntu22.04", + "url": "https://cloud-images.ubuntu.com/minimal/releases/jammy/release/ubuntu-22.04-minimal-cloudimg-amd64.img", + "origin_image_name": "ubuntu-22.04-minimal-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/minimal/releases/jammy/release/SHA256SUMS", + "boot_type": "uefi" + }, + { + "id": 6, + "name": "Ubuntu 24.04 server", + "variant": "ubuntu22.04", + "url": "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img", + "origin_image_name": "noble-server-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/noble/current/SHA256SUMS", + "boot_type": "uefi" + }, + { + "id": 7, + "name": "Ubuntu 24.04 minimal", + "variant": "ubuntu22.04", + "url": "https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-24.04-minimal-cloudimg-amd64.img", + "origin_image_name": "ubuntu-24.04-minimal-cloudimg-amd64.img", + "md5sum": "https://cloud-images.ubuntu.com/minimal/releases/noble/release/SHA256SUMS", + "boot_type": "uefi" + } + ] +} diff --git a/vm_connect.sh b/vm_connect.sh deleted file mode 100755 index fd7e872..0000000 --- a/vm_connect.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/env bash -#Variables -VM="$1" -VM_BASE_DIR=${VM_BASE_DIR:-"${HOME}/vms"} -VM_USER="user" -#Functions -usage() { - cat << EOF -USO: $0 VM - -Este script conecta por ssh con la maquina virtual seleccionada. -EOF -} - -connect_vm() { - local VM_IP=$(./vm_get_ip.sh ${VM}) - ssh -i ${VM_BASE_DIR}/ssh/${VM} -l${VM_USER} ${VM_IP} -} -if [[ -z "$VM" ]]; then - usage - exit 1 -fi -connect_vm \ No newline at end of file diff --git a/vm_create.sh b/vm_create.sh deleted file mode 100755 index 2b06b8e..0000000 --- a/vm_create.sh +++ /dev/null @@ -1,289 +0,0 @@ -#!/bin/env bash -VM_HOSTNAME= -VM_BASE_DIR=${VM_BASE_DIR:-"${HOME}/vms"} -VM_DISK_SIZE=20 -VM_DISK_FORMAT=qcow2 -VM_MEM_SIZE=2048 -VM_VCPUS=2 -VM_BASE_IMAGE= -VM_OS_VARIANT= -VM_USERNAME="user" -VM_BRIDGE_INT= -VM_NET_USED="PU-internalTests" -LIBVIRT_NET_OPTION="network=$VM_NET_USED,model=e1000" -HAT_STREAM="stable" -# Functions -usage() -{ -cat << EOF -usage: $0 options - -Quickly create guest VMs using cloud image files and cloud-init. - -OPTIONS: - -h Show this message - -n Host name (required) - -b bridge interface name (bridge network is used) - -r RAM in MB (defaults to ${VM_MEM_SIZE}) - -c Number of VCPUs (defaults to ${VM_VCPUS}) - -s Amount of storage to allocate in GB (defaults to ${VM_DISK_SIZE}) - -v Verbose -EOF -} - -HOST_OS=$(cat /etc/os-release | grep -v VERSION_ID |grep "ID=" | awk -F'=' '{print $2}') -if [ "$HOST_OS" == "debian" ]; then - source env_scripts/older_os.sh -else - source env_scripts/newer_os.sh -fi - -download_base_image() -{ -if ! test -f "${VM_BASE_DIR}/base/$VM_OS_VARIANT.qcow2"; then - if [[ "$VM_OS_VARIANT" == "freebsd14.2" ]]; then - VM_DISK_FORMAT="qcow2.xz" - cd ${VM_BASE_DIR}/base/ - wget -v -O "${VM_BASE_DIR}/base/$VM_OS_VARIANT.${VM_DISK_FORMAT}" ${VM_BASE_IMAGE} - xz -d $VM_OS_VARIANT.${VM_DISK_FORMAT} - mv $VM_OS_VARIANT.${VM_DISK_FORMAT} $VM_OS_VARIANT.img - cd - - elif [[ "$VM_OS_VARIANT" == "fedora-coreos-stable" ]]; then - podman run --pull=always --rm \ - -v /dev:/dev -v /run/udev:/run/udev \ - -v $VM_BASE_DIR/base:/data -w /data \ - quay.io/coreos/coreos-installer:release \ - download -s ${HAT_STREAM} -p qemu -f qcow2.xz --decompress -C . - mv $VM_BASE_DIR/base/fedora-coreos-*.qcow2 $VM_BASE_DIR/base/"$VM_OS_VARIANT".qcow2 - else - wget -v -O "${VM_BASE_DIR}/base/$VM_OS_VARIANT.${VM_DISK_FORMAT}" ${VM_BASE_IMAGE} - fi -fi -} - -gen_coreos_data() -{ -cat < "$VM_BASE_DIR/init/${VM_HOSTNAME}-user-data.bu" -variant: fcos -version: 1.6.0 -passwd: - users: - - name: core - ssh_authorized_keys: - - ${SSH_PUB_KEY} -EOF -} - -gen_linux_user_data() -{ -VM_USER_PASS=$(tr -dc A-Za-z0-9 "$VM_BASE_DIR/init/${VM_HOSTNAME}-user-data" -#cloud-config -hostname: ${VM_HOSTNAME} -# manage_etc_hosts: false -ssh_pwauth: true -disable_root: true -users: -- name: ${VM_USERNAME} - hashed_passwd: ${VM_USER_PASS_HASH} - sudo: ALL=(ALL) NOPASSWD:ALL - shell: /bin/bash - lock-passwd: false - ssh_authorized_keys: - - ${SSH_PUB_KEY} -EOF -} - -gen_freebsd_user_data() -{ -#VM_ROOT_PASS=$(tr -dc A-Za-z0-9 "$VM_BASE_DIR/init/${VM_HOSTNAME}-user-data" -#cloud-config -users: - - name: root - lock_passwd: false - hashed_passwd: ${VM_ROOT_PASS} - - name: ${VM_USERNAME} - ssh_authorized_keys: - - ssh-rsa ${SSH_PUB_KEY} - groups: wheel - ssh_pwauth: true - hashed_passwd: ${VM_USER_PASS_HASH} -write_files: - - path: /usr/local/etc/sudoers - content: | - %wheel ALL=(ALL) NOPASSWD: ALL - append: true -EOF - -} - - -while getopts "h:n:net:b:r:c:s:v" option; do - case "${option}" - in - h) - usage - exit 0 - ;; - n) VM_HOSTNAME=${OPTARG};; - b) VM_BRIDGE_INT=${OPTARG};; - r) VM_MEM_SIZE=${OPTARG};; - c) VM_VCPUS=${OPTARG};; - s) VM_DISK_SIZE=${OPTARG};; - v) VERBOSE=1;; - *) - usage - exit 1 - ;; - esac -done - -if [[ -z $VM_HOSTNAME ]]; then - echo "ERROR: Host name is required" - usage - exit 1 -fi - -if [[ -n $VERBOSE ]]; then - echo "Building ${VM_HOSTNAME} in $VM_IMAGE_DIR" - set -xv -fi - -if [[ -n $VM_BRIDGE_INT ]]; then - LIBVIRT_NET_OPTION="model=virtio,bridge=${VM_BRIDGE_INT}" -fi - -mkdir -p "$VM_BASE_DIR"/{images,xml,init,base,ssh} - -## VM Base image -if [ -n "$VM_BASE_IMAGE" ] && [ -f "$VM_BASE_IMAGE" ]; then -download_base_image -else - while true; do - read -r -p $'Select VM OS:\n 1.Debian12\n 2.Ubuntu 20.04\n 3.Ubuntu 22.04\n 4.Ubuntu 24.04 \n 5.FreeBSD 14\n 6.Alpine Linux\n 7.Fedora CoreOS' -n1 answer - case $answer in - [1]* ) VM_OS_VARIANT='debian11' - VM_BASE_IMAGE='https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2' - break;; - [2]* ) VM_OS_VARIANT='ubuntu20.04' - VM_BASE_IMAGE='https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img' - break;; - [3]* ) VM_OS_VARIANT='ubuntu22.04' - VM_BASE_IMAGE='https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img' - break;; - [4]* ) VM_OS_VARIANT='ubuntu24.04' - VM_BASE_IMAGE='https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img' - break;; - [5]* ) VM_OS_VARIANT='freebsd14.2' - VM_BASE_IMAGE='https://download.freebsd.org/ftp/releases/VM-IMAGES/14.2-RELEASE/amd64/Latest/FreeBSD-14.2-RELEASE-amd64-BASIC-CLOUDINIT.ufs.qcow2.xz' - break;; - [6]* ) VM_OS_VARIANT='alpinelinux3.20' - VM_BASE_IMAGE='https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/cloud/generic_alpine-3.21.2-x86_64-bios-tiny-r0.qcow2"' - break;; - [7]* ) VM_OS_VARIANT='fedora-coreos-stable' - #VM_BASE_IMAGE='https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/cloud/generic_alpine-3.21.2-x86_64-bios-tiny-r0.qcow2"' - break;; - * ) echo "Please answer 1,2,3,4,5,6,7.";; - esac - done - download_base_image -fi - - -echo "Creating a qcow2 image file ${VM_BASE_DIR}/images/${VM_HOSTNAME}.img that uses the cloud image file ${VM_BASE_DIR}/base/$VM_OS_VARIANT.${VM_DISK_FORMAT} as its base" -if ! test -f "${VM_BASE_DIR}/images/${VM_HOSTNAME}.img"; then - qemu-img create -b "${VM_BASE_DIR}/base/${VM_OS_VARIANT}.qcow2" -f qcow2 -F qcow2 "${VM_BASE_DIR}/images/${VM_HOSTNAME}.img" "${VM_DISK_SIZE}G" -else - echo "El fichero ${VM_BASE_DIR}/images/${VM_HOSTNAME}.img ya existe" - exit 1 -fi - - -# VM ssh keys gen -if [ -f "${VM_BASE_IMAGE}/ssh/${VM_HOSTNAME}" ]; then - echo "Ya existe una clave ssh para la maquina ${VM_HOSTNAME}" -else - ssh-keygen -t rsa -b 4096 -N '' -f "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}" - chmod 600 ${VM_BASE_DIR}/ssh/${VM_HOSTNAME}.pub - ssh-keygen -y -f "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}" > "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}".pub.txt - SSH_PUB_KEY=$(cat "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}".pub.txt) - rm "${VM_BASE_DIR}/ssh/${VM_HOSTNAME}".pub.txt -fi -#cloud-init VM meta-data -cat > "$VM_BASE_DIR/init/${VM_HOSTNAME}-meta-data" << EOF -instance-id: ${VM_HOSTNAME} -local-hostname: ${VM_HOSTNAME} -EOF -#cloud-init VM user-data -if [[ "$VM_OS_VARIANT" == "freebsd14.2" ]]; then - gen_freebsd_user_data - # genisoimage \ - # -output ${VM_BASE_DIR}/images/${VM_HOSTNAME}-cidata.iso \ - # -V cidata -r \ - # -J ${VM_BASE_DIR}/init/${VM_HOSTNAME}-user-data ${VM_BASE_DIR}/init/${VM_HOSTNAME}-meta-data - # virt-install \ - # --name ${VM_HOSTNAME} \ - # --memory ${VM_MEM_SIZE} \ - # --vcpus="${VM_VCPUS}" \ - # --os-variant=${VM_OS_VARIANT} \ - # --disk ${VM_BASE_DIR}/images/${VM_HOSTNAME}.img,device=disk,bus=virtio \ - # --disk path=${VM_BASE_DIR}/images/${VM_HOSTNAME}-cidata.iso,device=cdrom \ - # --network ${LIBVIRT_NET_OPTION} \ - # --autostart \ - # --import --noautoconsole \ - # --cloud-init root-password-generate=on,user-data=${VM_BASE_DIR}/init/${VM_HOSTNAME}-user-data -elif [[ "$VM_OS_VARIANT" == "fedora-coreos-stable" ]]; then - gen_coreos_data -else - gen_linux_user_data -fi -if [[ "$VM_OS_VARIANT" == "fedora-coreos-stable" ]]; then - IGNITION_CONFIG="${VM_BASE_DIR}/init/${VM_HOSTNAME}.ign" - BUTANE_CONFIG="$VM_BASE_DIR/init/${VM_HOSTNAME}-user-data.bu" - IGNITION_DEVICE_ARG=(--qemu-commandline="-fw_cfg name=opt/com.coreos/config,file=${IGNITION_CONFIG}") - #Generate ignition config - podman run --interactive --rm quay.io/coreos/butane:release \ - --pretty --strict < ${BUTANE_CONFIG} > ${IGNITION_CONFIG} - chown ${USERNAME}:libvirt-qemu /home/victor/vms/init/* - #Install - virt-install \ - --connect="qemu:///system" \ - --name ${VM_HOSTNAME} \ - --memory ${VM_MEM_SIZE} \ - --vcpus="${VM_VCPUS}" \ - --os-variant=${VM_OS_VARIANT} \ - --disk ${VM_BASE_DIR}/images/${VM_HOSTNAME}.img,device=disk,bus=virtio \ - --autostart \ - --import --noautoconsole \ - --network ${LIBVIRT_NET_OPTION} "${IGNITION_DEVICE_ARG[@]}" - #https://unix.stackexchange.com/questions/578086/virt-install-error-cant-load-ignit -else - virt-install \ - --name ${VM_HOSTNAME} \ - --memory ${VM_MEM_SIZE} \ - --vcpus="${VM_VCPUS}" \ - --os-variant=${VM_OS_VARIANT} \ - --disk ${VM_BASE_DIR}/images/${VM_HOSTNAME}.img,device=disk,bus=virtio \ - --network ${LIBVIRT_NET_OPTION} \ - --autostart \ - --import --noautoconsole \ - --cloud-init root-password-generate=on,user-data=${VM_BASE_DIR}/init/${VM_HOSTNAME}-user-data -# cloud-localds \ -# ${VM_BASE_DIR}/images/${VM_HOSTNAME}.iso \ -# ${VM_BASE_DIR}/init/${VM_HOSTNAME}-user-data -fi -virsh dumpxml "${VM_HOSTNAME}" > "${VM_BASE_DIR}/xml/${VM_HOSTNAME}.xml" - -if [ -n $VERBOSE ]; then - set +xv -fi -# Show running VMs -virsh list diff --git a/vm_delete.sh b/vm_delete.sh deleted file mode 100755 index fec4d1c..0000000 --- a/vm_delete.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/env bash - -# Function for usage message -usage() { - cat << EOF -Usage: $0 vm_name - -This script removes a virtual machine managed by virsh. - -EOF -} -VM_BASE_DIR="${VM_BASE_DIR:-${HOME}/vms}" -VM_IMAGE_PATH="${VM_BASE_DIR}/images/$1.img" -CI_IMAGE_PATH="${VM_BASE_DIR}/images/$1-cidata.iso" - -# Validate VM name argument -if [[ -z "$1" ]]; then - usage - exit 1 -fi - -# Check if VM exists -if [[ -f "$VM_IMAGE_PATH" ]]; then - # Safely remove the VM with confirmation - read -p "Are you sure you want to remove the VM '$1' (y/N)? " confirm - if [[ "$confirm" =~ ^[Yy]$ ]]; then - # Attempt to stop the VM before deleting - virsh destroy "$1" 2>/dev/null || true - # Delete VM definition and associated images - virsh undefine "$1" 2>/dev/null || true - rm -fv "$VM_IMAGE_PATH" "$CI_IMAGE_PATH" - rm ${VM_BASE_DIR}/xml/$1.xml - rm ${VM_BASE_DIR}/ssh/$1* - rm ${VM_BASE_DIR}/init/$1-user-data - rm ${VM_BASE_DIR}/init/$1-meta-data - else - echo "VM removal cancelled." - fi -else - # Handle case where VM image is not found - echo "Cannot find VM image file '$VM_IMAGE_PATH'. No action taken." -fi \ No newline at end of file diff --git a/vm_get_ip.sh b/vm_get_ip.sh deleted file mode 100755 index eec5cf7..0000000 --- a/vm_get_ip.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/env bash -usage() { - cat << EOF -USO: $0 VM - -Este script recupera la dirección IP de una máquina virtual administrada por virsh. - -EOF -} - -# Función para obtener la dirección IP de la máquina virtual -get_vm_ip_address() { - local VM="$1" - - # Obtener la dirección MAC de la interfaz de red - MAC_VM=$(virsh domiflist "$VM" | awk '{ print $5 }' | tail -2 | head -1) - if [[ -z "$MAC_VM" ]]; then - echo "Error: No se pudo encontrar la dirección MAC para '$VM'" - return 1 - fi - - # Obtener la dirección IP a partir de la dirección MAC - VM_IP_ADDRESS=$(arp -a | grep "$MAC_VM" | awk '{ print $2 }' | sed 's/[()]//g') - if [[ -z "$VM_IP_ADDRESS" ]]; then - echo "Error: No se pudo encontrar la dirección IP para la dirección MAC '$MAC_VM'" - return 1 - fi - - echo "$VM_IP_ADDRESS" -} - -# Obtener el nombre del host de la máquina virtual -VM="$1" - -if [[ -z "$VM" ]]; then - usage - exit 1 -fi - -# Obtener la dirección IP de la máquina virtual -get_vm_ip_address "$VM" diff --git a/vm_install.sh b/vm_install.sh deleted file mode 100755 index cdca0cc..0000000 --- a/vm_install.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/env bash -VM_BASE_DIR=${VM_BASE_DIR:-"${HOME}/vms"} -VM_USER="user" -VM=$1 -VM_IP='' -SCRIPT='' -VM_DISTRO='' -VM_VERSION='' -usage() { - cat << EOF -USO: $0 VM - -Este script instala algunos programas en la VM seleccionada - -EOF -} - -# Función para obtener la dirección IP de la máquina virtual -get_vm_ip_address() { - local VM="$1" - - # Obtener la dirección MAC de la interfaz de red - MAC_VM=$(virsh domiflist "$VM" | awk '{ print $5 }' | tail -2 | head -1) - if [[ -z "$MAC_VM" ]]; then - echo "Error: No se pudo encontrar la dirección MAC para '$VM'" - return 1 - fi - - # Obtener la dirección IP a partir de la dirección MAC - VM_IP_ADDRESS=$(arp -a | grep "$MAC_VM" | awk '{ print $2 }' | sed 's/[()]//g') - if [[ -z "$VM_IP_ADDRESS" ]]; then - echo "Error: No se pudo encontrar la dirección IP para la dirección MAC '$MAC_VM'" - return 1 - fi - - echo "$VM_IP_ADDRESS" -} - -obtener_info_vm() { - # Obtener el ID del sistema operativo - # Obtener el ID del sistema operativo - OS_ID=$(grep -o 'id="[^"]*"' "$1" | tr -d '"' | awk '{print $1}') - - # Eliminar el protocolo y el dominio del ID - OS_ID=$(echo "$OS_ID" | cut -d '/' -f 2-) - echo $OS_ID - # Convertir la URL a un nombre de distribución y versión - VM_DISTRO=$(echo "$OS_ID" | awk -F '/' '{print $3}') - VM_VERSION=$(echo "$OS_ID" | awk -F '/' '{print $4}') - -} - -# Obtener el nombre del host de la máquina virtual -VM="$1" - -if [[ -z "$VM" ]]; then - usage - exit 1 -fi - -# Obtener la dirección IP de la máquina virtual -VM_IP=$(get_vm_ip_address "$VM") -obtener_info_vm ${VM_BASE_DIR}/xml/${VM}.xml -while true; do - read -r -p $'Select software to install:\n 1.Docker\n 2.Gitlab CE\n 3.Gitlab runner\n 4.Kubernetes Single cluster\n' -n1 answer - case $answer in - [1]* ) - if [[ "$VM_DISTRO" == "debian" ]]; then - SCRIPT='../vm_example_scripts/docker_debian.sh' - elif [[ "$VM_DISTRO" == "ubuntu" ]]; then - SCRIPT='../vm_example_scripts/docker_ubuntu.sh' - fi - break;; - [2]* ) SCRIPT='../vm_example_scripts/gitlab_ce.sh' - break;; - [3]* ) SCRIPT='../vm_example_scripts/gitlab_runner.sh' - break;; - [4]* ) cd ../vm_example_scripts/ - ./k8s.sh $VM - break;; - * ) echo "Please answer 1,2,3 or 4.";; - esac -done -if [[ -z "$SCRIPT" ]]; then - exit 0 -else - ssh -i ${VM_BASE_DIR}/ssh/${VM} -l${VM_USER} ${VM_IP} "bash -s" -- < ${SCRIPT} -fi diff --git a/vm_list.sh b/vm_list.sh deleted file mode 100755 index 842ea73..0000000 --- a/vm_list.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/env bash -virsh list \ No newline at end of file diff --git a/vm_manage.sh b/vm_manage.sh new file mode 100755 index 0000000..fcf464b --- /dev/null +++ b/vm_manage.sh @@ -0,0 +1,143 @@ +#!/bin/env bash + +source env_scripts/common.sh +source env_scripts/functions.sh +# Default values for VM creation parameters +VM_MEM_SIZE=1024 +VM_VCPUS=1 +VM_DISK_SIZE=10 + +# Function to display usage message +usage() { + echo "Usage: $0 create -n NAME [-b BRIDGE] [-r RAM] [-c VCPUS] [-s DISK] [-v]" + echo " $0 delete -n NAME" + echo " $0 info -n NAME" + echo " $0 connect -n NAME" + echo " $0 list" + echo "" + echo "Actions:" + echo " create Create a new virtual machine" + echo " delete Delete a virtual machine" + echo " list List all defined virtual machines" + echo " info Show information about a virtual machine" + echo " connect Connect to the console of a virtual machine" + echo "" + echo "Options for 'create':" + echo " -h Show this help message" + echo " -n NAME Host name (required)" + echo " -b BRIDGE Bridge interface name" + echo " -r RAM RAM in MB (default: ${VM_MEM_SIZE})" + echo " -c VCPUS Number of VCPUs (default: ${VM_VCPUS})" + echo " -s DISK Disk size in GB (default: ${VM_DISK_SIZE})" + echo " -v Verbose mode" + exit 1 +} + +# Check if at least one argument is provided +if [ $# -eq 0 ]; then + usage +fi + +ACTION="$1" +shift + +case "${ACTION}" in + create) + # Parse options for create command + VERBOSE=false + NAME_SET=false + + while getopts ":hn:b:r:c:s:v" opt; do + case "${opt}" in + h) + usage + ;; + n) + VM_HOSTNAME="${OPTARG}" + NAME_SET=true + ;; + b) + BRIDGE_INTERFACE="${OPTARG}" + ;; + r) + VM_MEM_SIZE="${OPTARG}" + ;; + c) + VM_VCPUS="${OPTARG}" + ;; + s) + VM_DISK_SIZE="${OPTARG}" + ;; + v) + VERBOSE=true + ;; + \?) + echo "Invalid option: -${OPTARG}" >&2 + usage + ;; + :) + echo "Option -${OPTARG} requires an argument." >&2 + usage + ;; + esac + done + + # Check that required parameter (-n) was provided + if ! ${NAME_SET}; then + echo "Error: The -n option is required for create action." >&2 + usage + fi + source env_scripts/common.sh + #Check network type + vm_net_set_bridge_mode + #Check host os for guest debian type + check_host_os + #Read os_options.json and generate guests menu + #Select guest + show_vm_menu + #Download cloud image + vm_download_base_image + #Compare hashes + compare_checksum + #Create guest image + vm_create_guest_image + #Generate ssh key + vm_generate_ssh_hey + #Generate meta-data file for VM + vm_gen_meta_data + #Generate user-data file for VM + vm_gen_linux_user_data + #Install VM + vm_guest_install + ;; + + delete|info|connect) + # These actions require a NAME directly as first argument after ACTION + if [ $# -ne 1 ]; then + echo "Error: ${ACTION} requires a VM name as argument." >&2 + usage + fi + VM_HOSTNAME="$1" + source env_scripts/common.sh + echo "Action: ${ACTION}" + echo "VM Name: ${VM_HOSTNAME}" + if [[ "${ACTION}" == 'delete' ]]; then + vm_delete ${VM_HOSTNAME} + elif [[ "${ACTION}" == 'info' ]]; then + vm_net_get_ip ${VM_HOSTNAME} + elif [[ "${ACTION}" == 'connect' ]]; then + vm_connect ${VM_HOSTNAME} + fi + ;; + + list) + #echo "Action: list" + vm_list + ;; + + *) + echo "Unknown action: ${ACTION}" >&2 + usage + ;; +esac +exit 0 \ No newline at end of file diff --git a/vm_set_ip.sh b/vm_set_ip.sh deleted file mode 100755 index afc2821..0000000 --- a/vm_set_ip.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/env bash - -VM_BASE_DIR=${VM_BASE_DIR:-"${HOME}/vms"} -VM_USER="user" -MAC_VM= -usage() { - cat << EOF -USO: $0 VM - -Este script setea la dirección IP de una máquina virtual administrada por virsh. - -EOF -} - -# Función para obtener la dirección IP de la máquina virtual -get_vm_ip_address() { - local VM="$1" - - # Obtener la dirección MAC de la interfaz de red - MAC_VM=$(virsh domiflist "$VM" | awk '{ print $5 }' | tail -2 | head -1) - if [[ -z "$MAC_VM" ]]; then - echo "Error: No se pudo encontrar la dirección MAC para '$VM'" - return 1 - fi - - # Obtener la dirección IP a partir de la dirección MAC - VM_IP_ADDRESS=$(arp -a | grep "$MAC_VM" | awk '{ print $2 }' | sed 's/[()]//g') - if [[ -z "$VM_IP_ADDRESS" ]]; then - echo "Error: No se pudo encontrar la dirección IP para la dirección MAC '$MAC_VM'" - return 1 - fi - - echo "$VM_IP_ADDRESS" -} - - -define_netplan() -{ -cat < "$VM_BASE_DIR/init/${VM}-netplan" -# This file is generated from information provided by the datasource. Changes -# to it will not persist across an instance reboot. To disable cloud-init's -# network configuration capabilities, write a file -# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: -# network: {config: disabled} -network: - ethernets: - enp1s0: - addresses: - - ${IP}/24 - nameservers: - addresses: - - 208.67.222.222 - - 208.67.220.220 - match: - macaddress: ${MAC_VM} - set-name: enp1s0 - version: 2 -EOF -} -# Obtener el nombre del host de la máquina virtual -VM="$1" -IP="$2" -if [[ -z "$VM" ]]; then - usage - exit 1 -fi - -if [[ -z "$IP" ]]; then - usage - exit 1 -fi -MAC_VM=$(virsh domiflist "$VM" | awk '{ print $5 }' | tail -2 | head -1) -CURRENT_IP=$(get_vm_ip_address "$VM") -define_netplan -# Obtener la dirección IP de la máquina virtual -scp -i ${VM_BASE_DIR}/ssh/${VM} \ - -r $VM_BASE_DIR/init/${VM}-netplan \ - ${VM_USER}@${CURRENT_IP}:50-cloud-init.yaml -ssh -i ${VM_BASE_DIR}/ssh/${VM} -l${VM_USER} ${CURRENT_IP} "bash -s" -- < ../vm_example_scripts/apply_netplan.sh -