diff --git a/ansible/roles/bhyve/files/bhyve_netgraph_bridge.bash b/ansible/roles/bhyve/files/bhyve_netgraph_bridge.bash index 5ad4270..09645d6 100644 --- a/ansible/roles/bhyve/files/bhyve_netgraph_bridge.bash +++ b/ansible/roles/bhyve/files/bhyve_netgraph_bridge.bash @@ -217,7 +217,7 @@ EOF mkpeer ${host_interface_name}: bridge ether link0 name ${host_interface_name}:ether $bridge_name EOF - ifconfig $(ngctl msg "${host_interface_name}:" getifname | grep Args | cut -d '"' -f 2) name "${host_interface_name}" "$ip_range" up + ifconfig "$(ngctl msg "${host_interface_name}:" getifname | grep Args | cut -d '"' -f 2)" name "${host_interface_name}" "$ip_range" up fi } diff --git a/ansible/roles/bhyve/files/bhyverc.bash b/ansible/roles/bhyve/files/bhyverc.bash new file mode 100644 index 0000000..afaed5e --- /dev/null +++ b/ansible/roles/bhyve/files/bhyverc.bash @@ -0,0 +1,455 @@ +#!/usr/bin/env bash +# +set -euo pipefail +IFS=$'\n\t' +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Share a host directory to the guest via 9pfs. +# +# Inside the VM run: +# mount -t virtfs -o trans=virtio sharename /some/vm/path +# mount -t 9p -o cache=mmap -o msize=512000 sharename /mnt/9p +# mount -t 9p -o trans=virtio,cache=mmap,msize=512000 sharename /path/to/mountpoint +# bhyve_options="-s 28,virtio-9p,sharename=/" + +# Enable Sound +# bhyve_options="-s 16,hda,play=/dev/dsp,rec=/dev/dsp" + +# Example usage: +# +# doas bhyverc create-disk zdata/vm/poudriere /vm/poudriere 10 +# doas bhyverc start poudriere zdata/vm/poudriere /vm/poudriere /vm/iso/FreeBSD-13.2-RELEASE-amd64-bootonly.iso +# doas bhyverc start poudriere zdata/vm/poudriere /vm/poudriere + + +: ${VERBOSE:="NO"} # or YES +if [ "$VERBOSE" = "YES" ]; then + set -x +fi + +: ${CPU_CORES:="1"} +: ${MEMORY:="1G"} +: ${NETWORK:="NAT"} # or RAW or BOTH +: ${IP_RANGE:="10.215.1.1/24"} # Ignored for RAW networks +: ${INTERFACE_NAME:="jail_nat"} # or the external interface like lagg0 for RAW networks +: ${BRIDGE_NAME:="bridge_$INTERFACE_NAME"} # or bridge_raw for RAW networks +: ${VNC_ENABLE:="NO"} +: ${VNC_LISTEN:="127.0.0.1:5900"} +: ${VNC_WIDTH:="1920"} +: ${VNC_HEIGHT:="1080"} +: "${CD:=}" + +: ${SHUTDOWN_TIMEOUT:="600"} # 10 minutes + + + +############## Setup ######################### + + +function die { + local status_code="$1" + shift + (>&2 echo "${@}") + exit "$status_code" +} + +function log { + (>&2 echo "${@}") +} + +############## Program ######################### + +function main { + local cmd + cmd=$1 + shift + if [ "$cmd" = "start" ]; then + init + start "${@}" + elif [ "$cmd" = "stop" ]; then + init + stop "${@}" + elif [ "$cmd" = "status" ]; then + init + status "${@}" + elif [ "$cmd" = "console" ]; then + init + console "${@}" + elif [ "$cmd" = "_start_body" ]; then + init + start_body "${@}" + elif [ "$cmd" = "create-disk" ]; then + create_disk "${@}" + else + (>&2 echo "Unknown command: $cmd") + exit 1 + fi +} + +function start { + local num_vms="$#" + if [ "$num_vms" -eq 0 ]; then + log "No VMs specified." + return 0 + fi + + while [ "$#" -gt 0 ]; do + local name="$1" + shift 1 + log "Starting VM $name." + start_one "$name" + [ "$#" -eq 0 ] || sleep 5 + done +} + +function start_one { + local name="$1" + local tmux_name="$name" + /usr/local/bin/tmux new-session -d -s "$tmux_name" "$0" "_start_body" "$name" + # /usr/local/bin/tmux new-session -d -s "$tmux_name" "/usr/bin/env VNC_ENABLE=NO VNC_LISTEN=0.0.0.0:5900 /usr/local/bin/bash /home/talexander/launch_opnsense.bash" +} + +function launch_pidfile { + local pidfile="$1" + shift 1 + mkdir -p "$(dirname "$pidfile")" + cat > "${pidfile}" <<< "$$" + set -x + exec "${@}" +} +export -f launch_pidfile + +function stop { + local num_vms="$#" + if [ "$num_vms" -eq 0 ]; then + log "No VMs specified." + return 0 + fi + + while [ "$#" -gt 0 ]; do + local name="$1" + shift 1 + log "Stopping VM $name." + stop_one "$name" + [ "$#" -eq 0 ] || sleep 5 + done +} + +function stop_one { + local name="$1" + local pidfile="/run/bhyverc/${name}/pid" + + if [ ! -e "$pidfile" ]; then + log "Pid file $pidfile does not exist." + return 0 + fi + + local bhyve_pid + bhyve_pid=$(cat "$pidfile") + + if ps -p "$bhyve_pid" >/dev/null; then + # Send ACPI shutdown command + log "Sending ACPI shutdown to ${name}:${bhyve_pid}." + kill -SIGTERM "$bhyve_pid" + fi + + local timeout_start timeout_end + timeout_start=$(date +%s) + while ps -p "$bhyve_pid" >/dev/null; do + timeout_end=$(date +%s) + if [ $((timeout_end-timeout_start)) -ge "$SHUTDOWN_TIMEOUT" ]; then + log "${name}:${bhyve_pid} took more than $SHUTDOWN_TIMEOUT seconds to shut down. Hard powering down." + break + fi + + log "Waiting for ${name}:${bhyve_pid} to exit." + sleep 2 + done + + bhyvectl "--vm=$name" --destroy || true + + local timeout_start timeout_end + timeout_start=$(date +%s) + while ps -p "$bhyve_pid" >/dev/null; do + timeout_end=$(date +%s) + if [ $((timeout_end-timeout_start)) -ge "$SHUTDOWN_TIMEOUT" ]; then + log "${name}:${bhyve_pid} took more than $SHUTDOWN_TIMEOUT seconds to hard power down. Giving up." + break + fi + + log "Waiting for ${name}:${bhyve_pid} to hard power down." + sleep 2 + done + + rm -f "$pidfile" + + log "Finished stopping $name." +} + +function status { + local num_vms="$#" + + if [ "$num_vms" -gt 0 ]; then + for name in "$@"; do + status_one "$name" + done + else + log "No VMs specified." + fi +} + +function status_one { + local name="$1" + local pidfile="/run/bhyverc/${name}/pid" + + if [ ! -e "$pidfile" ]; then + log "$name is not running." + return 0 + fi + + local bhyve_pid + bhyve_pid=$(cat "$pidfile") + + if ! ps -p "$bhyve_pid" >/dev/null; then + log "$name is not running." + return 0 + fi + + log "$name is running as pid $bhyve_pid." +} + +function console { + local num_vms="$#" + + if [ "$num_vms" -gt 0 ]; then + for name in "$@"; do + log "Attaching to console of VM $name." + console_one "$name" + done + else + log "No VMs specified." + fi +} + +function console_one { + local name="$1" + local tmux_name="$name" + exec tmux a -t "$tmux_name" +} + +function init { + mkdir -p /run/bhyverc +} + +############## Bhyve ########################### + +function create_disk { + local zfs_path="$1" + local mount_path="$2" + local gigabytes="$3" + zfs create -o "mountpoint=$mount_path" "$zfs_path" + cp /usr/local/share/edk2-bhyve/BHYVE_UEFI_VARS.fd "${mount_path}/" + tee "${mount_path}/settings" <&2 echo "No available links on bridge $bridge_name") + exit 1 + fi + done +} + +function assert_bridge { + local host_interface_name="$1" + local bridge_name="$2" + local ip_range="$3" + + if ! ng_exists "${bridge_name}:"; then + ngctl -d -f - </dev/null 2>&1 +} + +function calculate_mac_address { + local name="$1" + local source + source=$(md5 -r -s "$name" | awk '{print $1}') + echo "06:${source:0:2}:${source:2:2}:${source:4:2}:${source:6:2}:${source:8:2}" +} + +function find_available_port { + local start_port="$1" + local port="$start_port" + while true; do + sockstat -P tcp -p 443 + port=$((port + 1)) + done +} + +function ngctlcat { + if [ "$VERBOSE" = "YES" ]; then + tee /dev/tty | ngctl -d -f - + else + ngctl -d -f - + fi +} + +main "${@}" diff --git a/ansible/roles/bhyve/files/bhyverc.sh b/ansible/roles/bhyve/files/bhyverc.sh new file mode 100644 index 0000000..7d2bda8 --- /dev/null +++ b/ansible/roles/bhyve/files/bhyverc.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# REQUIRE: LOGIN FILESYSTEMS +# PROVIDE: bhyverc +# KEYWORD: shutdown + +. /etc/rc.subr +name=bhyverc +rcvar=${name}_enable +start_cmd="${name}_start" +stop_cmd="${name}_stop" +status_cmd="${name}_status" +console_cmd="${name}_console" +extra_commands="console" +load_rc_config $name + +bhyverc_start() { + export PATH="$PATH:/usr/local/bin" + exec /usr/local/bin/bhyverc start "${@}" +} + +bhyverc_status() { + export PATH="$PATH:/usr/local/bin" + exec /usr/local/bin/bhyverc status "${@}" +} + +bhyverc_stop() { + export PATH="$PATH:/usr/local/bin" + exec /usr/local/bin/bhyverc stop "${@}" +} + +bhyverc_console() { + export PATH="$PATH:/usr/local/bin" + exec /usr/local/bin/bhyverc console "${@}" +} + +run_rc_command "$@" diff --git a/ansible/roles/bhyve/tasks/freebsd.yaml b/ansible/roles/bhyve/tasks/freebsd.yaml index 363475c..77b4ee8 100644 --- a/ansible/roles/bhyve/tasks/freebsd.yaml +++ b/ansible/roles/bhyve/tasks/freebsd.yaml @@ -22,6 +22,25 @@ loop: - src: bhyve_netgraph_bridge.bash dest: /usr/local/bin/bhyve_netgraph_bridge + - src: bhyverc.bash + dest: /usr/local/bin/bhyverc + +- name: Install rc script + copy: + src: "files/{{ item.src }}" + dest: "/usr/local/etc/rc.d/{{ item.dest|default(item.src) }}" + owner: root + group: wheel + mode: 0755 + loop: + - src: bhyverc.sh + dest: bhyverc + +- name: Enable bhyverc + community.general.sysrc: + name: bhyverc_enable + value: "YES" + path: /etc/rc.conf.d/bhyverc - name: Create zfs dataset zfs: