basiliskiivm/basiliskiivm

517 lines
12 KiB
Bash
Executable File

#!/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 <morgant@makkintosshu.com>
# 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] <command>"
echo
echo "Options:"
echo " -h, --help : print these usage instructions"
echo " -V, --version : print version information"
echo
echo "Commands:"
echo " info <vm> : print basic configuration info for a .BasiliskIIVM"
echo " list [<path>] : list all .BasiliskIIVM in path (or none for default directory)"
echo " package : package the current BasiliskII configuration into a .BasiliskIIVM"
echo " snapshot <vm> : create a snapshot of the current state of disks in the .BasiliskIIVM"
echo " snapshots <vm>: list all snapshots in a .BasiliskIIVM"
echo " start <vm> : start a BasiliskII instance from a .BasiliskIIVM"
echo " status <vm> : get the status of a .BasiliskIIVM"
echo " stop <vm> : 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 "$@"