#!/usr/bin/env bash # # basiliskiivm - Package BasiliskII emulator settings & disk images # into a single '.basiliskiivm' directory and run # from there. # # CHANGE LOG: # # v0.1 2016-12-08 - Morgan T. Aldridge # Initial version. # v0.1.1 2017-11-07 - Morgan T. Aldridge # Automatically search for BasiliskII binary on macOS. # v0.1.2 2019-01-28 0 Morgan T. Aldridge # Better *nix compatibility (tested OpenBSD). # # LICENSE: # # Copyright (c) 2016-2017, Morgan T. Aldridge. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # - Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # - Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # info tool="$(basename "$0")" version="0.1.2" copyright="Copyright (c) 2016-2019 Morgan Aldridge" # global variables BASILISKII_BINARY="${BASILISKII_BINARY:=BasiliskII}" BASILISKII_VMS_PATH="${BASILISKII_VMS_PATH:="${HOME}/Documents/BasiliskII"}" BASILISKII_SNAPSHOT_COMPRESSION="${BASILISKII_SNAPSHOT_COMPRESSION:=gzip}" basiliskii_prefs_file=".basilisk_ii_prefs" basiliskii_pid_file=".basiliskii.pid" function in_array() { local found=false local value="$1" shift if [ -z "$value" ]; then $found; fi if [ ${#@} -lt 1 ]; then $found; fi for array_value in "$@"; do if [ "$value" = "$array_value" ]; then found=true; fi done $found } function usage() { echo "Usage: ${tool} [options] " echo echo "Options:" echo " -h, --help : print these usage instructions" echo " -V, --version : print version information" echo echo "Commands:" echo " info : print basic configuration info for a .BasiliskIIVM" echo " list [] : list all .BasiliskIIVM in path (or none for default directory)" echo " package : package the current BasiliskII configuration into a .BasiliskIIVM" echo " snapshot : create a snapshot of the current state of disks in the .BasiliskIIVM" echo " snapshots : list all snapshots in a .BasiliskIIVM" echo " start : start a BasiliskII instance from a .BasiliskIIVM" echo " status : get the status of a .BasiliskIIVM" echo " stop : stop a running .BasiliskIIVM" echo echo "Environment Variables:" echo " BASILISKII_BINARY : path to your BasiliskII binary, if it's not in your search path" echo " BASILISKII_VMS_PATH : path where you store your BasiliskII VMs" echo " BASILISKII_SNAPSHOT_COMPRESSION: method used for snapshot compression ('none', 'gzip')" echo } function version() { echo "${tool} v${version} ${copyright}" } function find_basilisk() { # but don't look! local success=false if [ -x "$BASILISKII_BINARY" ]; then success=true elif which "$BASILISKII_BINARY" >/dev/null; then success=true elif [[ "$OSTYPE" == "darwin"* ]]; then local mac_binary="$(find ~/Applications /Applications -type f -path "*.app/Contents/MacOS*" -name "$BASILISKII_BINARY" | head -n 1)" if [ -n "$mac_binary" ]; then read -p "Found '$mac_binary', would you like to use this binary? [Y/n] " response if [ "$response" = "Y" ]; then BASILISKII_BINARY="$mac_binary" success=true fi fi fi if ! $success; then echo "ERROR! Unable to locate the BasiliskII binary!" fi $success } function vm_pkg_name() { local success=false local vm="$(basename "$1")" if [[ "$vm" =~ ^(.+)\.[Bb]asilisk[Ii]{2}[Vv][Mm]$ ]]; then echo "${BASH_REMATCH[1]}" success=true fi $success } function vm_pkg_config_file() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then local prefs_file="${1}/${basiliskii_prefs_file}" if [ -f "$prefs_file" ]; then echo "$prefs_file" sucess=true fi fi $success } function vm_pkg_config_parse() { local success=false local vm_path="$1" shift local vm="$(vm_pkg_name "$vm_path")" if [ -n "$vm" ]; then local config="$(vm_pkg_config_file "$vm_path")" if [ -z "$config" ]; then echo "Error! The '$vm' BasiliskII VM's config file couldn't be found." else while IFS= read -r line; do if [ -n "$line" ]; then if [[ "$line" =~ ^([A-Za-z0-9]+)\ (.+)$ ]]; then if in_array "${BASH_REMATCH[1]}" "$@"; then echo "$line" fi fi fi done < "$config" success=true fi fi $success } function vm_pkg_disks() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then local disk_paths=() while IFS= read -r line; do if [[ "$line" =~ ^disk\ (.+)$ ]]; then # if [ ! -f "${BASH_REMATCH[1]}" ]; then # echo "Warning: '${vm}' VM disk '${BASH_REMATCH[1]}' does not exist!" # fi disk_paths+=("${BASH_REMATCH[1]}") fi done <<< "$(vm_pkg_config_parse "$1" disk)" for disk in "${disk_paths[@]}"; do echo "$(basename "$disk")" success=true done fi $success } function vm_is_running() { local running=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then local pid_path="${1}/${basiliskii_pid_file}" if [ -f "$pid_path" ]; then if ps -p "$(cat "$pid_path")" > /dev/null 2>&1; then running=true fi fi fi $running } function vm_create_pid_file() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then local pid_path="${1}/${basiliskii_pid_file}" # create the pid file if ! echo "$2" > "$pid_path"; then echo "ERROR! Unable to create the '$vm' BasiliskII VM PID file '$pid_path'." else success=true fi fi $success } function vm_delete_pid_file() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then local pid_path="${1}/${basiliskii_pid_file}" if [ ! -f "$pid_path" ]; then echo "ERROR! The '$vm' BasiliskII VM PID file '$pid_path' doesn't exist, so cannot delete it." else if ! rm "$pid_path"; then echo "ERROR! Unable to delete the '$vm' BasiliskII VM PID file '$pid_path'." else success=true fi fi fi $success } function vm_create_package() { success=false echo "ERROR! This functionality isn't implemented yet." $success } function vm_info() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then local rom_path="" local shared_path="" local disk_paths=() local screen_size="" local ram_size=0 while IFS= read -r line; do if [[ "$line" =~ ^([A-Za-z0-9]+)\ (.+)$ ]]; then case "${BASH_REMATCH[1]}" in "extfs") shared_path="${BASH_REMATCH[2]}" ;; "rom") rom_path="${BASH_REMATCH[2]}" ;; "ramsize") ram_size=$(( ${BASH_REMATCH[2]} / 1024 / 1024 )) ;; "disk") disk_paths+=("${BASH_REMATCH[2]}") ;; "screen") if [[ "${BASH_REMATCH[2]}" =~ ^([A-Za-z]+)/([0-9]+)/([0-9]+)$ ]]; then screen_size="${BASH_REMATCH[2]}x${BASH_REMATCH[3]}" fi ;; esac fi done <<< "$(vm_pkg_config_parse "$1" disk extfs screen rom ramsize)" echo "$(basename "$1"):" echo echo "Path: $(dirname "$1")" echo "ROM: $(basename "${rom_path}")" echo "RAM: ${ram_size}MB" echo "Resolution: ${screen_size}" echo "Shared Folder: ${shared_path}" echo "Disks:" for disk in "${disk_paths[@]}"; do echo " $(basename "$disk")" done success=true fi $success } function vm_status() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then echo -n "'$vm': " if vm_is_running "$1"; then echo "Running" else echo "Stopped" fi success=true fi $success } function vm_start() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then if vm_is_running "$1"; then echo "Error! The '$vm' BasiliskII VM is already running." else local config="$(vm_pkg_config_file "$1")" if [ -z "$config" ]; then echo "Error! The '$vm' BasiliskII VM's config file couldn't be found." else echo "Starting the '$vm' BasiliskII VM..." "$BASILISKII_BINARY" --nogui true --config "$config" >/dev/null & local pid="$!" if [ -z "$pid" -a "$pid" -lt 1 ]; then echo "ERROR! The '$vm' BasiliskII VM didn't start correctly." elif vm_create_pid_file "$1" "$pid"; then success=true fi fi fi fi $success } function vm_stop() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then if ! vm_is_running "$1"; then echo "Error! The '$vm' BasiliskII VM is not running." else echo "Warning! It is not safe to stop the '$vm' BasiliskII VM while it is running." echo "Please choose Special > Shutdown from within the BasiliskII VM instance to shut it down." fi fi $success } function vm_pkg_snapshots() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then local snapshots_path="${1}/Snapshots" if [ -d "$snapshots_path" ]; then while IFS= read -r line; do echo "$(basename "$line")" done <<< "$(find "$snapshots_path" -type d -mindepth 1 -maxdepth 1)" fi success=true fi $success } function vm_pkg_create_snapshot() { local success=false local vm="$(vm_pkg_name "$1")" if [ -n "$vm" ]; then if vm_is_running "$1"; then echo "Error! You cannot create a snapshot while the '${vm}' BasiliskII VM is running. Please stop it and try again." return 1 fi local snapshots_path="${1}/Snapshots" if [ ! -d "$snapshots_path" ]; then if ! mkdir "$snapshots_path"; then echo "Error! Unable to create snapshots directory '${snapshots_path}'." return 1 fi fi local timestamp="$(date +%Y%m%d-%H%M%S)" local snapshot="${snapshots_path}/${timestamp}" if ! mkdir "$snapshot"; then echo "Error! Unable to create snapshot directory '${snapshot}'." else local disks_copy_success=true while IFS= read -r disk; do local src_disk="${1}/${disk}" local dst_disk="${snapshot}/${disk}" local copy_success=true case "$BASILISKII_SNAPSHOT_COMPRESSION" in "gzip") dst_disk="${dst_disk}.gz" if ! gzip -k -c "$src_disk" > "$dst_disk"; then copy_success=false fi ;; *) if ! cp "$src_disk" "$dst_disk"; then copy_success=false fi ;; esac if ! $copy_success; then echo "Error! Unable to copy disk '${src_disk}' to '${dst_disk}'." disks_copy_success=false fi done <<< "$(vm_pkg_disks "$1")" if $disks_copy_success; then echo "Created '${timestamp}' snapshot of '${vm}' BasiliskII VM." success=true else echo "Error! Unable to create '${timestamp}' snapshot of '${vm}' BasiliskII VM." fi fi fi $success } function list_vms() { success=false local vms_path="$1" if [ -z "$vms_path" ]; then vms_path="$BASILISKII_VMS_PATH" fi while IFS= read -r line; do echo "$(basename "$line")" done <<< "$(find "$vms_path" -iname "*.BasiliskIIVM")" success=true $success } function main() { if ! find_basilisk; then echo "Exiting." exit 1 fi case "$1" in "-h" | "--help") usage exit 0 ;; "-V" | "--version") version exit 0 ;; "info") shift vm_info "$1" ;; "list") shift list_vms "$1" ;; "snapshot") shift vm_pkg_create_snapshot "$1" ;; "snapshots") shift vm_pkg_snapshots "$1" ;; "start") shift vm_start "$1" ;; "status") shift vm_status "$1" ;; "stop") shift vm_stop "$1" ;; "package") shift vm_create_package "$1" ;; *) echo "ERROR! Unknown option '$1'. Exiting" exit 1 ;; esac } main "$@"