#!/bin/bash # -*- mode: sh; fill-column: 75; tab-width: 8; coding: utf-8-unix -*- # from https://github.com/earlruby/create-vm/ [ -f /usr/local/bin/usr_local_tput.bash ] && \ . /usr/local/bin/usr_local_tput.bash || { DBUG() { echo DEBUG $* ; } INFO() { echo INFO $* ; } WARN() { echo WARN $* ; } ERROR() { echo ERROR $* ; } } prog=`basename $0 .bash` PREFIX=/usr/local ROLE=toxcore export PATH=$PATH:$PREFIX/bin have_genisoimage=true # create-vm - Quickly create guest VMs using cloud image files and cloud-init. # Copyright 2018-2023 Earl C. Ruby III # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # Set BOX_NBD_OVERLAY_DIR environment variable to override default storage location for VMs HOSTNAME= IMG_FQN= AUTH_KEYS_FQN= RAM=2048 VCPUS=1 STORAGE=20 BRIDGE=virbr1 MAC='52:54:00:1d:9c:6f' VERBOSE= PASS= OSINFO=gentoo password=ansible OUTDIR=${BOX_NBD_OVERLAY_DIRs:-"${HOME}/vms/virsh"} 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) -i Full path and name of the base .img file to use (required) -k Full path and name of the ansible user's public key file (required) -d Output directory for the overlay qcow2 and related files -r RAM in MB (defaults to ${RAM}) -c Number of VCPUs (defaults to ${VCPUS}) -s Amount of storage to allocate in GB (defaults to ${STORAGE}) -b Bridge interface to use (defaults to ${BRIDGE}) -m MAC address to use (default is to use a randomly-generated MAC) -o OSINFO name like win11, win10, fedora32, gentoo, ububtu20 -p ansible users plaintext password -v Verbose EOF } while getopts "h:n:i:k:r:c:s:b:m:o:p:d:v" option; do case "${option}" in h) usage exit 0 ;; n) HOSTNAME=${OPTARG};; i) IMG_FQN=${OPTARG};; k) AUTH_KEYS_FQN=${OPTARG};; r) RAM=${OPTARG};; c) VCPUS=${OPTARG};; s) STORAGE=${OPTARG};; b) BRIDGE=${OPTARG};; m) MAC=${OPTARG};; p) PASS=${OPTARG};; o) password=${OPTARG};; d) OUTDIR=${OPTARG}; BOX_NBD_OVERLAY_DIR=${OUTDIR};; v) VERBOSE=1;; *) ERROR unhandled option "${option}" ${OPTARG} usage exit 1 ;; esac done if [[ -z $HOSTNAME ]]; then ERROR "Host name is required" usage exit 1 fi if [[ -z $IMG_FQN ]]; then ERROR "Base cloud image file name is required" usage exit 1 fi if [[ -z $BOX_NBD_OVERLAY_DIR ]]; then ERROR "Output image directory is required BOX_NBD_OVERLAY_DIR" usage exit 1 fi if [[ -z $AUTH_KEYS_FQN ]]; then ERROR "ansible public key file $AUTH_KEYS_FQN not found" usage exit 1 fi if ! [[ -f $IMG_FQN ]]; then ERROR "$IMG_FQN file not found" usage exit 1 fi if [[ -n $VERBOSE ]]; then INFO "Building ${HOSTNAME} in $BOX_NBD_OVERLAY_DIR" set -xv fi mkdir -p "$BOX_NBD_OVERLAY_DIR"/{images,xml,init,base} || exit 2 echo "Creating a qcow2 image file ${BOX_NBD_OVERLAY_DIR}/images/${HOSTNAME}.img that uses the cloud image file ${IMG_FQN} as its base" INFO qemu-img create -b "${IMG_FQN}" -f qcow2 -F qcow2 \ "${BOX_NBD_OVERLAY_DIR}/images/${HOSTNAME}.img" "${STORAGE}G" qemu-img create -b "${IMG_FQN}" -f qcow2 -F qcow2 \ "${BOX_NBD_OVERLAY_DIR}/images/${HOSTNAME}.img" "${STORAGE}G" || \ exit 3 echo "Creating meta-data file $BOX_NBD_OVERLAY_DIR/init/meta-data" cat > "$BOX_NBD_OVERLAY_DIR/init/meta-data" << EOF instance-id: ${HOSTNAME} local-hostname: ${HOSTNAME} EOF # echo "Creating meta-data file $BOX_NBD_OVERLAY_DIR/init/meta-data.json" # cat > "$BOX_NBD_OVERLAY_DIR/init/meta-data.json" << EOF cat > /dev/null << EOF { "admin_pass": "root", "availability_zone": "nova", "hostname": "test.novalocal", "launch_index": 0, "name": "gentoo6", "meta": { "role": "webservers", "essential": "false" }, "public_keys": { "mykey": " ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDRCJCQ1UD9QslWDSw5Pwsvba0Wsf1pO4how5BtNaZn0xLZpTq2nqFEJshUkd/zCWF7DWyhmNphQ8c+U+wcmdNVcg2pI1kPxq0VZzBfZ7cDwhjgeLsIvTXvU+HVRtsXh4c5FlUXpRjf/x+a3vqFRvNsRd1DE+5ZqQHbOVbnsStk3PZppaByMg+AZZMx56OUk2pZCgvpCwj6LIixqwuxNKPxmJf45RyOsPUXwCwkq9UD4me5jksTPPkt3oeUWw1ZSSF8F/141moWsGxSnd5NxCbPUWGoRfYcHc865E70nN4WrZkM7RFI/s5mvQtuj8dRL67JUEwvdvEDO0EBz21FV/iOracXd2omlTUSK+wYrWGtiwQwEgr4r5bimxDKy9L8UlaJZ+ONhLTP8ecTHYkaU1C75sLX9ZYd5YtqjiNGsNF+wdW6WrXrQiWeyrGK7ZwbA7lagSxIa7yeqnKDjdkcJvQXCYGLM9AMBKWeJaOpwqZ+dOunMDLd5VZrDCU2lpCSJ1M=" }, "uuid": "83679162-1378-4288-a2d4-70e13ec132aa" } EOF # password=`openssl passwd -1 -stdin <<< $password` echo "Creating user-data file $BOX_NBD_OVERLAY_DIR/init/user-data" # https://techglimpse.com/nova-boot-instance-with-password/ cat > "$BOX_NBD_OVERLAY_DIR/init/user-data" << EOF #cloud-config # password: ansible # chpasswd: { expire: False } ssh_pwauth: true runcmd: - "rc-update add qemu-guest-agent" - "chmod 755 /etc/init.d/qemu-guest-agent" - "/etc/init.d/qemu-guest-agent start" - "echo /etc/init.d/qemu-guest-agent start >> /etc/rc.local" users: - default - name: ansible sudo: ["ALL=(ALL) NOPASSWD:ALL"] groups: - wheel - adm shell: /bin/bash plain_text_password: "$password" chpasswd: { expire: False } homedir: /home/ansible ssh_pwauth: true ssh_authorized_keys: - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDRCJCQ1UD9QslWDSw5Pwsvba0Wsf1pO4how5BtNaZn0xLZpTq2nqFEJshUkd/zCWF7DWyhmNphQ8c+U+wcmdNVcg2pI1kPxq0VZzBfZ7cDwhjgeLsIvTXvU+HVRtsXh4c5FlUXpRjf/x+a3vqFRvNsRd1DE+5ZqQHbOVbnsStk3PZppaByMg+AZZMx56OUk2pZCgvpCwj6LIixqwuxNKPxmJf45RyOsPUXwCwkq9UD4me5jksTPPkt3oeUWw1ZSSF8F/141moWsGxSnd5NxCbPUWGoRfYcHc865E70nN4WrZkM7RFI/s5mvQtuj8dRL67JUEwvdvEDO0EBz21FV/iOracXd2omlTUSK+wYrWGtiwQwEgr4r5bimxDKy9L8UlaJZ+ONhLTP8ecTHYkaU1C75sLX9ZYd5YtqjiNGsNF+wdW6WrXrQiWeyrGK7ZwbA7lagSxIa7yeqnKDjdkcJvQXCYGLM9AMBKWeJaOpwqZ+dOunMDLd5VZrDCU2lpCSJ1M=" EOF echo "Adding keys from the public key file $AUTH_KEYS_FQN to the user-data file" while IFS= read -r key; do echo " - $key" >> "$BOX_NBD_OVERLAY_DIR/init/user-data" done < <(grep -v '^ *#' < "$AUTH_KEYS_FQN") VM_IMAGE_DIR="$BOX_NBD_OVERLAY_DIR" #old . /usr/local/bin/toxcore_create-ga.sh || exit 4 cat > "$BOX_NBD_OVERLAY_DIR/init/user-data" << \EOF #!/bin/bash # typically only executes on first boot echo "############# user_data executing ##############" #grep gentoo /etc/shadow sed -e 's/#-:ALL:ALL/+:gentoo:ALL/' -i /etc/security/access.conf PW=`echo $PASS | openssl passwd -1 --stdin ` grep -q ^gentoo /etc/passwd || \ useradd --gid 4 --uid 1000 --home-dir /home/gentoo \ --comment Gentoo --password "$PW" \ -G adm,wheel --shell /bin/bash gentoo usermod --password "$PW" -G adm,wheel gentoo # root usermod --password '$1$1Ho4y/W8$5VymfKWWAhLxwkkPZiWTZ1' root # unlock account passwd -u gentoo passwd -u root sed -e 's/# %wheel /%wheel /' -i /etc/sudoers sed -e 's/PasswordAuthentication no/PasswordAuthentication yes/' -i /etc//ssh/sshd_config sed -e 's/PermitRootLogin.*/PermitRootLogin yes/' -i /etc//ssh/sshd_config grep net.ipv4.ip_forward=1 /etc/sysctl.conf || \ echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf cd /etc/init.d [ -e net.eth0 ] || ln -s net.lo net.eth0 for elt in i o linuxPen19 ; do grep -q $elt /etc/fstab && continue echo "$elt /mnt/$elt virtiofs defaults 0 0" >> /etc/fstab done #grep gentoo /etc/shadow EOF echo "Generating the cidata ISO file $BOX_NBD_OVERLAY_DIR/images/${HOSTNAME}-cidata.iso" ( cd "$BOX_NBD_OVERLAY_DIR/init/" genisoimage \ -output "$BOX_NBD_OVERLAY_DIR/images/${HOSTNAME}-cidata.img" \ -volid cidata \ -rational-rock \ -joliet \ -input-charset utf-8 \ user-data meta-data ) || exit 5 MACCMD= if [[ -n $MAC ]]; then MACCMD="--mac=${MAC}" fi [ -f ${BOX_NBD_OVERLAY_DIR}/images/${HOSTNAME}.img ] || exit 5 [ -f $BOX_NBD_OVERLAY_DIR/images/${HOSTNAME}-cidata.img ] || exit 6 # libvirt.libvirtError: /usr/lib/qemu/qemu-bridge-helper --use-vnet --br=-c --fd=31: failed to communicate with bridge helper: stderr=failed to parse default acl file `/etc/qemu/bridge.conf' if [ ! -f "/etc/qemu/bridge.conf" ] ; then echo allow $BRIDGE >> "/etc/qemu/bridge.conf" elif ! grep $BRIDGE "/etc/qemu/bridge.conf" ; then echo allow $BRIDGE >> "/etc/qemu/bridge.conf" fi if [ $BRIDGE = virbr0 ] ; then network=default # 192.168.122.248/24 elif [ $BRIDGE = virbr1 ] ; then network=Whonix-External else WARN unrecognized $BRIDGE fi if [ "$network" != '' ] ; then virsh net-list | grep -q $network || \ virsh net-start $network else network=default fi file=/etc/libvirt/qemu/networks/$network.xml if [ ! -f $file ] ; then WARN no network file $file elif ! grep ' in network file' $file fi declare -a LARGS LARGS=( --name="${HOSTNAME}" \ --osinfo "$OSINFO" \ --import \ --disk "path=${BOX_NBD_OVERLAY_DIR}/images/${HOSTNAME}.img,format=qcow2" \ --disk "path=$BOX_NBD_OVERLAY_DIR/images/${HOSTNAME}-cidata.img,device=cdrom" \ --ram="${RAM}" \ --vcpus="${VCPUS}" \ --autostart \ --hvm \ --arch x86_64 \ --accelerate \ --check-cpu \ --force \ --watchdog=default \ --channel type=spicevmc,target.type=virtio,target.name=com.redhat.spice.0 \ --channel type=unix,target.type=virtio,target.name=org.qemu.guest_agent.0 \ --rng /dev/urandom \ --os-variant detect=on,name=$OSINFO \ --noautoconsole \ ) # not type=qemu-vdagent NETWORK="--network network=$network,model=virtio" if [ -n "$NETWORK" ] ; then LARGS+=( $NETWORK \ ) fi LARGS+=( # --graphics spice,listen=socket \ --boot init=/sbin/init --console pty --video vga --memorybacking source.type=memfd,access.mode=shared --filesystem /,/mnt/linuxPen19 \ ) INFO virt-install "${LARGS[@]}" # squelch warnings python3.sh `which virt-install` "${LARGS[@]}" || exit 7 # --debug #? --shmem name=shmem_server,type="memfd",mode="shared" # --shmem name=shmem0 ivshmem device is not supported with this QEMU binary # was --graphics vnc,listen=0.0.0.0 # --osinfo "$OSINFO" \ # Make a backup of the VM's XML definition file virsh dumpxml "${HOSTNAME}" > "${BOX_NBD_OVERLAY_DIR}/xml/${HOSTNAME}.xml" || exit 8 INFO wrote xml `ls -l ${BOX_NBD_OVERLAY_DIR}/xml/${HOSTNAME}.xml` if [ -n "$VERBOSE" ]; then set +xv fi # problems: type=qemu-vdagent unix unix=on # problems: type="spicevmc # ERROR Unknown --channel options: ['unix'] cp "${BOX_NBD_OVERLAY_DIR}/xml/${HOSTNAME}.xml" \ "${BOX_NBD_OVERLAY_DIR}/xml/${HOSTNAME}.xml".new cat > /tmp/ga.works <
EOF cat > /tmp/sp.works <
EOF # Show running VMs virsh list | grep "${HOSTNAME}" && INFO "${HOSTNAME}" || { ERROR "${HOSTNAME}" ; exit 9$? ; } # use the following passwordless demonstration key for testing or # replace with your own key pair # # -----BEGIN OPENSSH PRIVATE KEY----- # b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn # NhAAAAAwEAAQAAAYEA0QiQkNVA/ULJVg0sOT8LL22tFrH9aTuIaMOQbTWmZ9MS2aU6tp6h # RCbIVJHf8wlhew1soZjaYUPHPlPsHJnTVXINqSNZD8atFWcwX2e3A8IY4Hi7CL0171Ph1U # bbF4eHORZVF6UY3/8fmt76hUbzbEXdQxPuWakB2zlW57ErZNz2aaWgcjIPgGWTMeejlJNq # WQoL6QsI+iyIsasLsTSj8ZiX+OUcjrD1F8AsJKvVA+JnuY5LEzz5Ld6HlFsNWUkhfBf9eN # ZqFrBsUp3eTcQmz1FhqEX2HB3POuRO9JzeFq2ZDO0RSP7OZr0Lbo/HUS+uyVBML3bxAztB # Ac9tRVf4jq2nF3dqJpU1EivsGK1hrYsEMBIK+K+W4psQysvS/FJWiWfjjYS0z/HnEx2JGl # NQu+bC1/WWHeWLao4jRrDRfsHVulq160Ilnsqxiu2cGwO5WoEsSGu8nqpyg43ZHCb0FwmB # izPQDASlniWjqcKmfnTrpzAy3eVWawwlNpaQkidTAAAFgGKSj8diko/HAAAAB3NzaC1yc2 # EAAAGBANEIkJDVQP1CyVYNLDk/Cy9trRax/Wk7iGjDkG01pmfTEtmlOraeoUQmyFSR3/MJ # YXsNbKGY2mFDxz5T7ByZ01VyDakjWQ/GrRVnMF9ntwPCGOB4uwi9Ne9T4dVG2xeHhzkWVR # elGN//H5re+oVG82xF3UMT7lmpAds5VuexK2Tc9mmloHIyD4BlkzHno5STalkKC+kLCPos # iLGrC7E0o/GYl/jlHI6w9RfALCSr1QPiZ7mOSxM8+S3eh5RbDVlJIXwX/XjWahawbFKd3k # 3EJs9RYahF9hwdzzrkTvSc3hatmQztEUj+zma9C26Px1EvrslQTC928QM7QQHPbUVX+I6t # pxd3aiaVNRIr7BitYa2LBDASCvivluKbEMrL0vxSVoln442EtM/x5xMdiRpTULvmwtf1lh # 3li2qOI0aw0X7B1bpatetCJZ7KsYrtnBsDuVqBLEhrvJ6qcoON2Rwm9BcJgYsz0AwEpZ4l # o6nCpn5066cwMt3lVmsMJTaWkJInUwAAAAMBAAEAAAGAEuz77Hu9EEZyujLOdTnAW9afRv # XDOZA6pS7yWEufjw5CSlMLwisR83yww09t1QWyvhRqEyYmvOBecsXgaSUtnYfftWz44apy # /gQYvMVELGKaJAC/q7vjMpGyrxUPkyLMhckALU2KYgV+/rj/j6pBMeVlchmk3pikYrffUX # JDY990WVO194Dm0buLRzJvfMKYF2BcfF4TvarjOXWAxSuR8www050oJ8HdKahW7Cm5S0po # FRnNXFGMnLA62vN00vJW8V7j7vui9ukBbhjRWaJuY5rdG/UYmzAe4wvdIEnpk9xIn6JGCp # FRYTRn7lTh5+/QlQ6FXRP8Ir1vXZFnhKzl0K8Vqh2sf4M79MsIUGAqGxg9xdhjIa5dmgp8 # N18IEDoNEVKUbKuKe/Z5yf8Z9tmexfH1YttjmXMOojBvUHIjRS5hdI9NxnPGRLY2kjAzcm # gV9Rv3vtdF/+zalk3fAVLeK8hXK+di/7XTvYpfJ2EZBWiNrTeagfNNGiYydsQy3zjZAAAA # wBNRak7UrqnIHMZn7pkCTgceb1MfByaFtlNzd+Obah54HYIQj5WdZTBAITReMZNt9S5NAR # M8sQB8UoZPaVSC3ppILIOfLhs6KYj6RrGdiYwyIhMPJ5kRWF8xGCLUX5CjwH2EOq7XhIWt # MwEFtd/gF2Du7HUNFPsZGnzJ3e7pDKDnE7w2khZ8CIpTFgD769uBYGAtk45QYTDo5JroVM # ZPDq08Gb/RhIgJLmIpMwyreVpLLLe8SwoMJJ+rihmnJZxO8gAAAMEA0lhiKezeTshht4xu # rWc0NxxD84a29gSGfTphDPOrlKSEYbkSXhjqCsAZHd8S8kMr3iF6poOk3IWSvFJ6mbd3ie # qdRTgXH9Thwk4KgpjUhNsQuYRHBbI59Mo+BxSI1B1qzmJSGdmCBL54wwzZmFKDQPQKPxiL # n0Mlc7GooiDMjT1tbuW/O1EL5EqTRqwgWPTKhBA6r4PnGF150hZRIMooZkD2zX6b1sGojk # QpvKkEykTwnKCzF5TXO8+wJ3qbcEo9AAAAwQD+Z0r68c2YMNpsmyj3ZKtZNPSvJNcLmyD/ # lWoNJq3djJN4s2JbK8l5ARUdW3xSFEDI9yx/wpfsXoaqWnygP3PoFw2CM4i0EiJiyvrLFU # r3JLfDUFRy3EJ24RsqbigmEsgQOzTl3xfzeFPfxFoOhokSvTG88PQji1AYHz5kA7p6Zfaz # Ok11rJYIe7+e9B0lhku0AFwGyqlWQmS/MhIpnjHIk5tP4heHGSmzKQWJDbTskNWd6aq1G7 # 6HWfDpX4HgoM8AAAALaG9sbWFuYkBhcmM= # -----END OPENSSH PRIVATE KEY----- #