diff --git a/easyinstall.sh b/easyinstall.sh index 012d7bf6..547541bc 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -261,7 +261,7 @@ function createDrive() { driveSize=$1 driveName=$2 mkdir -p $VIRTUAL_DRIVER_PATH - drivePath="${VIRTUAL_DRIVER_PATH}/${driveSize}MB.hda" + drivePath="${VIRTUAL_DRIVER_PATH}/${driveSize}MB.hds" if [ ! -f $drivePath ]; then echo "Creating a ${driveSize}MB Drive" @@ -415,19 +415,6 @@ function reserveScsiIds() { function runChoice() { case $1 in - 0) - echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface + 600MB Drive" - stopOldWebInterface - updateRaScsiGit - createImagesDir - installPackages - installRaScsi - installRaScsiWebInterface - createDrive600MB - showRaScsiStatus - showRaScsiWebStatus - echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface + 600MB Drive - Complete!" - ;; 1) echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface" stopOldWebInterface @@ -501,11 +488,11 @@ function showMenu() { echo "" echo "Choose among the following options:" echo "INSTALL/UPDATE RASCSI (${CONNECT_TYPE-FULLSPEC} version)" - echo " 0) install or update RaSCSI Service + web interface + 600MB Drive (recommended)" - echo " 1) install or update RaSCSI Service + web interface" + echo " 1) install or update RaSCSI Service + Web Interface" echo " 2) install or update RaSCSI Service" - echo "CREATE EMPTY DRIVE IMAGE" - echo " 3) 600MB drive (recommended)" + echo "CREATE HFS FORMATTED (MAC) IMAGE WITH LIDO DRIVERS" + echo "** For the Mac Plus, it's better to create an image through the Web Interface **" + echo " 3) 600MB drive (suggested size)" echo " 4) custom drive size (up to 4000MB)" echo "NETWORK ASSISTANT" echo " 5) configure network forwarding over Ethernet (DHCP)" @@ -531,7 +518,7 @@ while [ "$1" != "" ]; do ;; esac case $VALUE in - FULLSPEC | STANDARD | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7) + FULLSPEC | STANDARD | 1 | 2 | 3 | 4 | 5 | 6 | 7) ;; *) echo "ERROR: unknown option \"$VALUE\"" diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp index 06a7c334..d7be00f0 100644 --- a/src/raspberrypi/rascsi.cpp +++ b/src/raspberrypi/rascsi.cpp @@ -32,6 +32,7 @@ #include "spdlog/sinks/stdout_color_sinks.h" #include #include +#include #include #include #include diff --git a/src/web/drive_properties.json b/src/web/drive_properties.json new file mode 100644 index 00000000..b50f3968 --- /dev/null +++ b/src/web/drive_properties.json @@ -0,0 +1,266 @@ +[ +{ + "device_type": "SCHD", + "vendor": "DEC", + "product": "RZ55 (C) DEC", + "revision": "", + "block_size": 512, + "blocks": 660960, + "name": "DEC RZ55", + "file_type": "hds", + "description": "Largest recognized drive on Ultrix 3.0", + "url": "http://lastin.dti.supsi.ch/VET/disks/EK-RZXXD-PS.pdf" +}, +{ + "device_type": "SCHD", + "vendor": "DEC", + "product": "RZ57 (C) DEC", + "revision": "5000", + "block_size": 512, + "blocks": 2050125, + "name": "DEC RZ57", + "file_type": "hds", + "description": "Largest recognized drive on Ultrix 3.1 - 4.3", + "url": "http://lastin.dti.supsi.ch/VET/disks/EK-RZXXD-PS.pdf" +}, +{ + "device_type": "SCHD", + "vendor": "DEC", + "product": "RZ59 (C) DEC", + "revision": "2000", + "block_size": 512, + "blocks": 17755614, + "name": "DEC RZ59", + "file_type": "hds", + "description": "Largest recognized drive on OSF/1 3.x - 5.x", + "url": "" +}, +{ + "device_type": "SCHD", + "vendor": "DEC", + "product": "RZ74 (C) DEC", + "revision": "427H", + "block_size": 512, + "blocks": 6950125, + "name": "DEC RZ74", + "file_type": "hds", + "description": "Largest recognized drive on Ultrix 4.4 - 4.5", + "url": "" +}, +{ + "device_type": "SCHD", + "vendor": "HP", + "product": "A2076", + "revision": "DD24", + "block_size": 512, + "blocks": 2621688, + "name": "HP A2076", + "file_type": "hds", + "description": "Largest recognized drive on HP-UX 8.0", + "url": "http://www.bitsavers.org/pdf/micropolis/105389b_1528_1991.pdf" +}, +{ + "device_type": "SCHD", + "vendor": "HP", + "product": "C3010", + "revision": "6.0", + "block_size": 512, + "blocks": 3905792, + "name": "HP C3010", + "file_type": "hds", + "description": "Largest recognized drive on HP-UX 9.0", + "url": "https://stason.org/TULARC/pc/hard-drives-hdd/hewlett-packard/HP-C3010-001-2003MB-5-25-FH-SCSI2-FAST.html" +}, +{ + "device_type": "SCHD", + "vendor": "MICROP", + "product": "1325", + "revision": "", + "block_size": 512, + "blocks": 270336, + "name": "Micropolis 1325", + "file_type": "hds", + "description": "Largest predefined on SunOS 2; Microp 1325 is actually an ESDI disk on an adapter.", + "url": "https://stason.org/TULARC/pc/hard-drives-hdd/micropolis/1325-69MB-5-25-FH-MFM-ST506.html" +}, +{ + "device_type": "SCHD", + "vendor": "MICROP", + "product": "1588T", + "revision": "", + "block_size": 512, + "blocks": 651840, + "name": "Micropolis 1588-15", + "file_type": "hds", + "description": "Largest predefined on SunOS 3/4 (Sun-3)", + "url": "" +}, +{ + "device_type": "SCHD", + "vendor": "SEAGATE", + "product": "ST32430N SUN2.1G", + "revision": "0444", + "block_size": 512, + "blocks": 4197405, + "name": "Seagate SUN2.1G", + "file_type": "hds", + "description": "Largest predefined for SunOS 4 (Sun-4) and Solaris 2.0-2.3", + "url": "" +}, +{ + "device_type": "SCHD", + "vendor": "SEAGATE", + "product": "ST34371W SUN4.2G", + "revision": "7462", + "block_size": 512, + "blocks": 8380800, + "name": "Seagate SUN4.2G", + "file_type": "hds", + "description": "Recommended for Solaris 2.4+", + "url": "" +}, +{ + "device_type": "SCHD", + "vendor": "SEAGATE", + "product": "ST39173W SUN9.0G", + "revision": "2815", + "block_size": 512, + "blocks": 17689267, + "name": "Seagate SUN9.0G", + "file_type": "hds", + "description": "Recommended for Solaris 2.4+", + "url": "" +}, +{ + "device_type": "SCHD", + "vendor": "SEAGATE", + "product": "ST914603SSUN146G", + "revision": "0B70", + "block_size": 512, + "blocks": 286739329, + "name": "Seagate SUN146G", + "file_type": "hds", + "description": "Recommended for Solaris 2.4+", + "url": "" +}, +{ + "device_type": "SCHD", + "vendor": "QUANTUM", + "product": "FIREBALL540S", + "revision": "", + "block_size": 512, + "blocks": 1065235, + "name": "Quantum Fireball 540S", + "file_type": "hds", + "description": "Recommended for older Macintosh systems. Recognized by Apple HD SC Setup.", + "url": "" +}, +{ + "device_type": "SCHD", + "vendor": "QUANTUM", + "product": "FIREBALL ST4.3S", + "revision": "0F0C", + "block_size": 512, + "blocks": 8471232, + "name": "Quantum Fireball ST4.3S", + "file_type": "hds", + "description": "Recommended for Macintosh System 6 or later. Recognized by Apple HD SC Setup.", + "url": "" +}, +{ + "device_type": "SCRM", + "vendor": "IOMEGA", + "product": "ZIP 100", + "revision": "D.13", + "block_size": 512, + "blocks": 196608, + "name": "Iomega ZIP 100", + "file_type": "hdr", + "description": "Removable Iomega ZIP drive, 100MB capacity", + "url": "https://www.win.tue.nl/~aeb/linux/zip/zip-1.html" +}, +{ + "device_type": "SCRM", + "vendor": "IOMEGA", + "product": "ZIP 250", + "revision": "D.58", + "block_size": 512, + "blocks": 489532, + "name": "Iomega ZIP 250", + "file_type": "hdr", + "description": "Removable Iomega ZIP drive, 250MB capacity", + "url": "https://www.win.tue.nl/~aeb/linux/zip/zip-1.html" +}, +{ + "device_type": "SCRM", + "vendor": "IOMEGA", + "product": "JAZ 2GB", + "revision": "E.16", + "block_size": 512, + "blocks": 3909632, + "name": "Iomega Jaz 2GB", + "file_type": "hdr", + "description": "Removable Iomega Jaz drive, 2GB capacity", + "url": "https://archive.eol.ucar.edu/docs/isf/facilities/iss/manual/creating-data-disks.html" +}, +{ + "device_type": "SCRM", + "vendor": "SYQUEST", + "product": "SQ5110C", + "revision": "4AA0", + "block_size": 512, + "blocks": 173456, + "name": "SyQuest 88MB", + "file_type": "hdr", + "description": "SyQuest removable hard drive cartridges, 88MB capacity", + "url": "" +}, +{ + "device_type": "SCCD", + "vendor": "TOSHIBA", + "product": "CD-ROM XM-3401TA", + "revision": "0283", + "block_size": 512, + "blocks": null, + "name": "Toshiba XM-3401TA", + "file_type": null, + "description": "Boots most SGI, Sun, HP, IBM, DEC etc. Use only with Unix workstations of this vintage.", + "url": "" +}, +{ + "device_type": "SCCD", + "vendor": "SONY", + "product": "CD-ROM CDU-8012", + "revision": "3.1a", + "block_size": 512, + "blocks": null, + "name": "Sony CDU-8012", + "file_type": null, + "description": "Boots Sun-3. Use only with Unix workstations of this vintage.", + "url": "" +}, +{ + "device_type": "SCCD", + "vendor": "HP", + "product": "A1448A", + "revision": "", + "block_size": 512, + "blocks": null, + "name": "HP A1448A", + "file_type": null, + "description": "Recognized by HP-UX. Use only with Unix workstations of this vintage.", + "url": "" +}, +{ + "device_type": "SCCD", + "vendor": "DEC", + "product": "RRD42 (C) DEC", + "revision": "4.5d", + "block_size": 512, + "blocks": null, + "name": "DEC RRD42", + "file_type": null, + "description": "Boots DECstations and VAXstations. Use only with Unix workstations of this vintage.", + "url": "" +} +] diff --git a/src/web/file_cmds.py b/src/web/file_cmds.py index 62a9e64d..51aee9e0 100644 --- a/src/web/file_cmds.py +++ b/src/web/file_cmds.py @@ -1,41 +1,30 @@ import os import subprocess -import time import logging from ractl_cmds import ( attach_image, detach_all, list_devices, + send_pb_command, ) from settings import * +import rascsi_interface_pb2 as proto def list_files(): - from fnmatch import translate - valid_file_types = list(VALID_FILE_SUFFIX) - valid_file_types = ["*." + s for s in valid_file_types] - valid_file_types = r"|".join([translate(x) for x in valid_file_types]) + command = proto.PbCommand() + command.operation = proto.PbOperation.IMAGE_FILES_INFO - from re import match, IGNORECASE + data = send_pb_command(command.SerializeToString()) + result = proto.PbResult() + result.ParseFromString(data) + files = [] + for f in result.image_files_info.image_files: + size_mb = "{:,.1f}".format(f.size / 1024 / 1024) + files.append({"name": f.name, "size": f.size, "size_mb": size_mb}) - files_list = [] - for path, dirs, files in os.walk(base_dir): - # Only list valid file types - files = [f for f in files if match(valid_file_types, f, IGNORECASE)] - files_list.extend( - [ - ( - os.path.join(path, file), - "{:,.1f}".format( - os.path.getsize(os.path.join(path, file)) / float(1 << 20), - ), - os.path.getsize(os.path.join(path, file)), - ) - for file in files - ] - ) - return files_list + return {"status": result.status, "msg": result.msg, "files": files} def list_config_files(): @@ -47,24 +36,30 @@ def list_config_files(): return files_list -def create_new_image(file_name, type, size): - if file_name == "": - file_name = "new_image." + str(int(time.time())) + "." + type - else: - file_name = file_name + "." + type +def create_new_image(file_name, file_type, size): + command = proto.PbCommand() + command.operation = proto.PbOperation.CREATE_IMAGE - return subprocess.run( - ["truncate", "--size", f"{size}m", f"{base_dir}{file_name}"], - capture_output=True, - ) + command.params["file"] = file_name + "." + file_type + command.params["size"] = str(size) + command.params["read_only"] = "false" + + data = send_pb_command(command.SerializeToString()) + result = proto.PbResult() + result.ParseFromString(data) + return {"status": result.status, "msg": result.msg} def delete_file(file_name): - if os.path.exists(file_name): - os.remove(file_name) - return True - else: - return False + command = proto.PbCommand() + command.operation = proto.PbOperation.DELETE_IMAGE + + command.params["file"] = file_name + + data = send_pb_command(command.SerializeToString()) + result = proto.PbResult() + result.ParseFromString(data) + return {"status": result.status, "msg": result.msg} def unzip_file(file_name): @@ -77,6 +72,8 @@ def unzip_file(file_name): def download_file_to_iso(scsi_id, url): import urllib.request + import urllib.error as error + import time file_name = url.split("/")[-1] tmp_ts = int(time.time()) @@ -87,8 +84,10 @@ def download_file_to_iso(scsi_id, url): try: urllib.request.urlretrieve(url, tmp_full_path) + except (error.URLError, error.HTTPError, error.ContentTooShortError) as e: + logging.error(str(e)) + return {"status": False, "msg": str(e)} except: - # TODO: Capture a more descriptive error message return {"status": False, "msg": "Error loading the URL"} # iso_filename = make_cd(tmp_full_path, None, None) # not working yet @@ -102,6 +101,7 @@ def download_file_to_iso(scsi_id, url): def download_image(url): import urllib.request + import urllib.error as error file_name = url.split("/")[-1] full_path = base_dir + file_name @@ -109,16 +109,19 @@ def download_image(url): try: urllib.request.urlretrieve(url, full_path) return {"status": True, "msg": "Downloaded the URL"} + except (error.URLError, error.HTTPError, error.ContentTooShortError) as e: + logging.error(str(e)) + return {"status": False, "msg": str(e)} except: - # TODO: Capture a more descriptive error message return {"status": False, "msg": "Error loading the URL"} def write_config(file_name): from json import dump + file_name = base_dir + file_name try: with open(file_name, "w") as json_file: - devices = list_devices()[0] + devices = list_devices()["device_list"] for device in devices: # Remove keys that we don't want to store in the file del device["status"] @@ -136,7 +139,9 @@ def write_config(file_name): device["params"] = list(device["params"]) dump(devices, json_file, indent=4) return {"status": True, "msg": f"Successfully wrote to file: {file_name}"} - #TODO: more verbose error handling of file system errors + except (IOError, ValueError, EOFError, TypeError) as e: + logging.error(str(e)) + return {"status": False, "msg": str(e)} except: logging.error(f"Could not write to file: {file_name}") return {"status": False, "msg": f"Could not write to file: {file_name}"} @@ -144,31 +149,61 @@ def write_config(file_name): def read_config(file_name): from json import load + file_name = base_dir + file_name try: with open(file_name) as json_file: detach_all() devices = load(json_file) for row in devices: - process = attach_image(row["id"], device_type=row["device_type"], image=row["image"], unit=int(row["un"]), \ + process = attach_image(row["id"], device_type=row["device_type"], \ + image=row["image"], unit=int(row["un"]), \ params=row["params"], vendor=row["vendor"], product=row["product"], \ revision=row["revision"], block_size=row["block_size"]) if process["status"] == True: return {"status": process["status"], "msg": f"Successfully read from file: {file_name}"} else: return {"status": process["status"], "msg": process["msg"]} - #TODO: more verbose error handling of file system errors + except (IOError, ValueError, EOFError, TypeError) as e: + logging.error(str(e)) + return {"status": False, "msg": str(e)} except: logging.error(f"Could not read file: {file_name}") return {"status": False, "msg": f"Could not read file: {file_name}"} -def read_device_config(file_name): +def write_drive_properties(file_name, conf): + """ + Writes a drive property configuration file to the images dir. + Takes file name base (str) and conf (list of dicts) as arguments + """ + from json import dump + try: + with open(base_dir + file_name, "w") as json_file: + dump(conf, json_file, indent=4) + return {"status": True, "msg": f"Successfully wrote to file: {file_name}"} + except (IOError, ValueError, EOFError, TypeError) as e: + logging.error(str(e)) + return {"status": False, "msg": str(e)} + except: + logging.error(f"Could not write to file: {file_name}") + return {"status": False, "msg": f"Could not write to file: {file_name}"} + + + +def read_drive_properties(path_name): + """ + Reads drive properties to any dir. + Either ones deployed to the images dir, or the canonical database. + Takes file path and bas (str) as argument + """ from json import load try: - with open(file_name) as json_file: + with open(path_name) as json_file: conf = load(json_file) - return {"status": True, "msg": f"Read data from file: {file_name}", "conf": conf} - #TODO: more verbose error handling of file system errors + return {"status": True, "msg": f"Read data from file: {path_name}", "conf": conf} + except (IOError, ValueError, EOFError, TypeError) as e: + logging.error(str(e)) + return {"status": False, "msg": str(e)} except: logging.error(f"Could not read file: {file_name}") - return {"status": False, "msg": f"Could not read file: {file_name}"} + return {"status": False, "msg": f"Could not read file: {path_name}"} diff --git a/src/web/pi_cmds.py b/src/web/pi_cmds.py index f5164091..6d865009 100644 --- a/src/web/pi_cmds.py +++ b/src/web/pi_cmds.py @@ -10,15 +10,17 @@ def rascsi_service(action): def reboot_pi(): - return subprocess.run(["sudo", "reboot"]).returncode == 0 + subprocess.Popen(["sudo", "reboot"]) + return True def shutdown_pi(): - return subprocess.run(["sudo", "shutdown", "-h", "now"]).returncode == 0 + subprocess.Popen(["sudo", "shutdown", "-h", "now"]) + return True -def running_version(): - ra_web_version = ( +def running_env(): + ra_git_version = ( subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True) .stdout.decode("utf-8") .strip() @@ -28,13 +30,12 @@ def running_version(): .stdout.decode("utf-8") .strip() ) - return ra_web_version + " " + pi_version + return {"git": ra_git_version, "env": pi_version} def is_bridge_setup(): - from subprocess import run - process = run(["brctl", "show"], capture_output=True) + process = subprocess.run(["brctl", "show"], capture_output=True) output = process.stdout.decode("utf-8") if "rascsi_bridge" in output: return True - return False \ No newline at end of file + return False diff --git a/src/web/ractl_cmds.py b/src/web/ractl_cmds.py index 8284f13b..16d787e2 100644 --- a/src/web/ractl_cmds.py +++ b/src/web/ractl_cmds.py @@ -16,7 +16,19 @@ def get_server_info(): str(result.server_info.patch_version) log_levels = result.server_info.log_levels current_log_level = result.server_info.current_log_level - return {"status": result.status, "version": version, "log_levels": log_levels, "current_log_level": current_log_level} + reserved_ids = list(result.server_info.reserved_ids) + return {"status": result.status, "version": version, "log_levels": log_levels, "current_log_level": current_log_level, "reserved_ids": reserved_ids} + + +def get_network_info(): + command = proto.PbCommand() + command.operation = proto.PbOperation.NETWORK_INTERFACES_INFO + + data = send_pb_command(command.SerializeToString()) + result = proto.PbResult() + result.ParseFromString(data) + ifs = result.network_interfaces_info.name + return {"status": result.status, "ifs": ifs} def validate_scsi_id(scsi_id): @@ -27,18 +39,24 @@ def validate_scsi_id(scsi_id): return {"status": False, "msg": "Invalid SCSI ID. Should be a number between 0-7"} -def get_valid_scsi_ids(devices, invalid_list, occupied_ids): - for device in devices: +def get_valid_scsi_ids(devices, reserved_ids): + occupied_ids = [] + for d in devices: # Make it possible to insert images on top of a # removable media device currently without an image attached - if "No Media" in device["status"]: - occupied_ids.remove(device["id"]) + if d["device_type"] != "-" and "No Media" not in d["status"]: + occupied_ids.append(d["id"]) # Combine lists and remove duplicates - invalid_ids = list(set(invalid_list + occupied_ids)) + invalid_ids = list(set(reserved_ids + occupied_ids)) valid_ids = list(range(8)) for id in invalid_ids: - valid_ids.remove(int(id)) + try: + valid_ids.remove(int(id)) + except: + # May reach this state if the RaSCSI Web UI thinks an ID + # is reserved but RaSCSI has not actually reserved it. + logging.warning(f"SCSI ID {id} flagged as both valid and invalid. Try restarting the RaSCSI Web UI.") valid_ids.reverse() return valid_ids @@ -48,7 +66,7 @@ def get_type(scsi_id): device.id = int(scsi_id) command = proto.PbCommand() - command.operation = proto.PbOperation.DEVICE_INFO + command.operation = proto.PbOperation.DEVICES_INFO command.devices.append(device) data = send_pb_command(command.SerializeToString()) @@ -63,6 +81,17 @@ def get_type(scsi_id): def attach_image(scsi_id, **kwargs): + command = proto.PbCommand() + devices = proto.PbDeviceDefinition() + devices.id = int(scsi_id) + + if "device_type" in kwargs.keys(): + devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"])) + if "unit" in kwargs.keys(): + devices.unit = kwargs["unit"] + if "image" in kwargs.keys(): + if kwargs["image"] not in [None, ""]: + devices.params["file"] = kwargs["image"] # Handling the inserting of media into an attached removable type device currently_attached = get_type(scsi_id)["device_type"] @@ -72,22 +101,13 @@ def attach_image(scsi_id, **kwargs): if currently_attached != device_type: return {"status": False, "msg": f"Cannot insert an image for {device_type} into a {currently_attached} device."} else: - return insert(scsi_id, kwargs.get("image", "")) + command.operation = proto.PbOperation.INSERT # Handling attaching a new device else: - devices = proto.PbDeviceDefinition() - devices.id = int(scsi_id) - if "device_type" in kwargs.keys(): - logging.warning(kwargs["device_type"]) - devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"])) - if "unit" in kwargs.keys(): - devices.unit = kwargs["unit"] - if "image" in kwargs.keys(): - if kwargs["image"] not in [None, ""]: - devices.params.append(kwargs["image"]) - if "params" in kwargs.keys(): - for p in kwargs["params"]: - devices.params.append(p) + command.operation = proto.PbOperation.ATTACH + if "interfaces" in kwargs.keys(): + if kwargs["interfaces"] not in [None, ""]: + devices.params["interfaces"] = kwargs["interfaces"] if "vendor" in kwargs.keys(): if kwargs["vendor"] not in [None, ""]: devices.vendor = kwargs["vendor"] @@ -101,14 +121,12 @@ def attach_image(scsi_id, **kwargs): if kwargs["block_size"] not in [None, ""]: devices.block_size = int(kwargs["block_size"]) - command = proto.PbCommand() - command.operation = proto.PbOperation.ATTACH - command.devices.append(devices) + command.devices.append(devices) - data = send_pb_command(command.SerializeToString()) - result = proto.PbResult() - result.ParseFromString(data) - return {"status": result.status, "msg": result.msg} + data = send_pb_command(command.SerializeToString()) + result = proto.PbResult() + result.ParseFromString(data) + return {"status": result.status, "msg": result.msg} def detach_by_id(scsi_id): @@ -149,40 +167,10 @@ def eject_by_id(scsi_id): return {"status": result.status, "msg": result.msg} -def insert(scsi_id, image): - devices = proto.PbDeviceDefinition() - devices.id = int(scsi_id) - devices.params.append(image) - - command = proto.PbCommand() - command.operation = proto.PbOperation.INSERT - command.devices.append(devices) - - data = send_pb_command(command.SerializeToString()) - result = proto.PbResult() - result.ParseFromString(data) - return {"status": result.status, "msg": result.msg} - - -def attach_daynaport(scsi_id): - devices = proto.PbDeviceDefinition() - devices.id = int(scsi_id) - devices.type = proto.PbDeviceType.SCDP - - command = proto.PbCommand() - command.operation = proto.PbOperation.ATTACH - command.devices.append(devices) - - data = send_pb_command(command.SerializeToString()) - result = proto.PbResult() - result.ParseFromString(data) - return {"status": result.status, "msg": result.msg} - - def list_devices(scsi_id=None): from os import path command = proto.PbCommand() - command.operation = proto.PbOperation.DEVICE_INFO + command.operation = proto.PbOperation.DEVICES_INFO # If method is called with scsi_id parameter, return the info on those devices # Otherwise, return the info on all attached devices @@ -196,8 +184,11 @@ def list_devices(scsi_id=None): result.ParseFromString(data) device_list = [] - occupied_ids = [] n = 0 + + if len(result.device_info.devices) == 0: + return {"status": False, "device_list": []} + while n < len(result.device_info.devices): did = result.device_info.devices[n].id dun = result.device_info.devices[n].unit @@ -228,27 +219,34 @@ def list_devices(scsi_id=None): device_list.append({"id": did, "un": dun, "device_type": dtype, \ "status": ", ".join(dstat_msg), "image": dpath, "file": dfile, "params": dparam,\ "vendor": dven, "product": dprod, "revision": drev, "block_size": dblock}) - occupied_ids.append(did) n += 1 - return device_list, occupied_ids + + return {"status": True, "device_list": device_list} -def sort_and_format_devices(device_list, occupied_ids): +def sort_and_format_devices(devices): + occupied_ids = [] + for d in devices: + occupied_ids.append(d["id"]) + + formatted_devices = devices + # Add padding devices and sort the list for id in range(8): if id not in occupied_ids: - device_list.append({"id": id, "type": "-", \ + formatted_devices.append({"id": id, "device_type": "-", \ "status": "-", "file": "-", "product": "-"}) # Sort list of devices by id - device_list.sort(key=lambda dic: str(dic["id"])) + formatted_devices.sort(key=lambda dic: str(dic["id"])) - return device_list + return formatted_devices def reserve_scsi_ids(reserved_scsi_ids): + '''Sends a command to the server to reserve SCSI IDs. Takes a list of strings as argument.''' command = proto.PbCommand() command.operation = proto.PbOperation.RESERVE - command.params.append(reserved_scsi_ids) + command.params["ids"] = ",".join(reserved_scsi_ids) data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -257,10 +255,10 @@ def reserve_scsi_ids(reserved_scsi_ids): def set_log_level(log_level): - '''Sends a command to the server to change the log level. Takes target log level as an argument''' + '''Sends a command to the server to change the log level. Takes target log level as an argument.''' command = proto.PbCommand() command.operation = proto.PbOperation.LOG_LEVEL - command.params.append(str(log_level)) + command.params["level"] = str(log_level) data = send_pb_command(command.SerializeToString()) result = proto.PbResult() diff --git a/src/web/settings.py b/src/web/settings.py index a91156e7..41da8a2c 100644 --- a/src/web/settings.py +++ b/src/web/settings.py @@ -1,7 +1,9 @@ -from os import getenv +from os import getenv, getcwd base_dir = getenv("BASE_DIR", "/home/pi/images/") -DEFAULT_CONFIG = base_dir + "default.json" +home_dir = getcwd() + +DEFAULT_CONFIG = "default.json" MAX_FILE_SIZE = getenv("MAX_FILE_SIZE", 1024 * 1024 * 1024 * 2) # 2gb HARDDRIVE_FILE_SUFFIX = ("hda", "hdn", "hdi", "nhd", "hdf", "hds") @@ -11,4 +13,9 @@ ARCHIVE_FILE_SUFFIX = ("zip",) VALID_FILE_SUFFIX = HARDDRIVE_FILE_SUFFIX + REMOVABLE_FILE_SUFFIX + \ CDROM_FILE_SUFFIX + ARCHIVE_FILE_SUFFIX +# File containing canonical drive properties +DRIVE_PROPERTIES_FILE = home_dir + "/drive_properties.json" +# File ending used for drive properties files +PROPERTIES_SUFFIX = "properties" + REMOVABLE_DEVICE_TYPES = ("SCCD", "SCRM", "SCMO") diff --git a/src/web/templates/base.html b/src/web/templates/base.html index 5e01ae1a..48af0ab7 100644 --- a/src/web/templates/base.html +++ b/src/web/templates/base.html @@ -24,7 +24,18 @@
- {% block header %}{% endblock %} +Service Running + + + + + + +
+ +

RaSCSI - 68kmla Edition

+
+
{% for category, message in get_flashed_messages(with_categories=true) %} @@ -39,6 +50,9 @@ {% block content %}{% endblock %}
-
\ No newline at end of file + diff --git a/src/web/templates/drives.html b/src/web/templates/drives.html new file mode 100644 index 00000000..e08dc93e --- /dev/null +++ b/src/web/templates/drives.html @@ -0,0 +1,139 @@ +{% extends "base.html" %} + + {% block content %} +

Cancel

+

Disclaimer

+

These device profiles are provided as-is with no guarantee to work on the systems mentioned. You may need appropirate device drivers and/or configuration parameters. If you have improvement suggestions or success stories to share we would love to hear from you, so please connect with us at GitHub or Discord!

+

Hard Drives

+ + + + + + + + + + + {% for hd in hd_conf %} + + + + + + + + {% endfor %} + +
NameSize (MB)DescriptionRef.Action
{{hd.name}}{{hd.size_mb}}{{hd.description}} + {% if hd.url != "" %} + Link + {% else %} + - + {% endif %} + +
+ + + + + + + + + .{{hd.file_type}} + +
+
+ +
+ +

CD-ROM Drives

+

This will create a properties file for the given CD-ROM image. No new image file will be created.

+ + + + + + + + + + {% for cd in cd_conf %} + + + + + + + + {% endfor %} + +
NameSize (MB)DescriptionRef.Action
{{cd.name}}{{cd.size_mb}}{{cd.description}} + {% if cd.url != "" %} + Link + {% else %} + - + {% endif %} + +
+ + + + + + + +
+
+ +
+ +

Removable Drives

+ + + + + + + + + + {% for rm in rm_conf %} + + + + + + + + {% endfor %} + +
NameSize (MB)DescriptionRef.Action
{{rm.name}}{{rm.size_mb}}{{rm.description}} + {% if rm.url != "" %} + Link + {% else %} + - + {% endif %} + +
+ + + + + + + + + .{{rm.file_type}} + +
+
+ +{% endblock %} diff --git a/src/web/templates/index.html b/src/web/templates/index.html index e9c35aca..1281553f 100644 --- a/src/web/templates/index.html +++ b/src/web/templates/index.html @@ -1,24 +1,5 @@ {% extends "base.html" %} -{% block header %} -{% if server_info["status"] == True %} -Service Running -{% else %} -Service Stopped -{% endif %} - - - - - - -
- -

RaSCSI - 68kmla Edition

-
-
-{% endblock %} - {% block content %}

Current RaSCSI Configuration

@@ -55,7 +36,7 @@ {% for device in devices %} - {% if device["id"]|string() not in reserved_scsi_ids %} + {% if device["id"] not in reserved_scsi_ids %} {{device.id}} {{device.device_type}} {{device.status}} @@ -75,8 +56,6 @@ - {% elif device.device_type in ["-"] %} -

-
{% else %}
@@ -112,33 +91,33 @@ {% for file in files %} - {{file[0].replace(base_dir, '')}} + {{file["name"].replace(base_dir, '')}} - - + +
- - + + - {% if not file[0].lower().endswith(archive_file_suffix) %} + {% if not file["name"].lower().endswith(archive_file_suffix) %} {% endif %}
- +
- {% if file[0].lower().endswith(archive_file_suffix) %} + {% if file["name"].lower().endswith(archive_file_suffix) %}
- +
{% endif %} @@ -151,11 +130,22 @@

Attach Ethernet Adapter

-

Emulates a SCSI DaynaPORT Ethernet Adapter. Host drivers required.

+

Emulates a SCSI DaynaPORT Ethernet Adapter. Host drivers required.

+

Leave Interface and Static IP blank for default settings, and input only Interface for DHCP setups.

+ + + + / + +

+

Create Empty Disk Image File

@@ -245,14 +236,13 @@ @@ -264,6 +254,11 @@
+

Create Named Drive

+

Ceate a named disk image with a companion properties file that mimics real-life drives

+ +
+

Logging

@@ -327,9 +322,3 @@
{% endblock %} - -{% block footer %} -
RaSCSI version: {{server_info["version"]}}
-
Server log level: {{server_info["current_log_level"]}}
-
{{version}}
-{% endblock %} diff --git a/src/web/web.py b/src/web/web.py index 3cc88fd2..ad15b7e4 100644 --- a/src/web/web.py +++ b/src/web/web.py @@ -10,12 +10,13 @@ from file_cmds import ( download_image, write_config, read_config, - read_device_config, + write_drive_properties, + read_drive_properties, ) from pi_cmds import ( - shutdown_pi, - reboot_pi, - running_version, + shutdown_pi, + reboot_pi, + running_env, rascsi_service, is_bridge_setup, ) @@ -26,10 +27,10 @@ from ractl_cmds import ( detach_by_id, eject_by_id, get_valid_scsi_ids, - attach_daynaport, detach_all, reserve_scsi_ids, get_server_info, + get_network_info, validate_scsi_id, set_log_level, ) @@ -40,22 +41,30 @@ app = Flask(__name__) @app.route("/") def index(): - reserved_scsi_ids = app.config.get("RESERVED_SCSI_IDS") - unsorted_devices, occupied_ids = list_devices() - devices = sort_and_format_devices(unsorted_devices, occupied_ids) - scsi_ids = get_valid_scsi_ids(devices, list(reserved_scsi_ids), occupied_ids) + server_info = get_server_info() + devices = list_devices() + files=list_files() + config_files=list_config_files() + + sorted_image_files = sorted(files["files"], key = lambda x: x["name"].lower()) + sorted_config_files = sorted(config_files, key = lambda x: x.lower()) + + reserved_scsi_ids = server_info["reserved_ids"] + formatted_devices = sort_and_format_devices(devices["device_list"]) + scsi_ids = get_valid_scsi_ids(devices["device_list"], reserved_scsi_ids) return render_template( "index.html", bridge_configured=is_bridge_setup(), - devices=devices, - files=list_files(), - config_files=list_config_files(), + devices=formatted_devices, + files=sorted_image_files, + config_files=sorted_config_files, base_dir=base_dir, scsi_ids=scsi_ids, - reserved_scsi_ids=[reserved_scsi_ids], + reserved_scsi_ids=reserved_scsi_ids, max_file_size=MAX_FILE_SIZE, - version=running_version(), - server_info=get_server_info(), + running_env=running_env(), + server_info=server_info, + netinfo=get_network_info(), valid_file_suffix=VALID_FILE_SUFFIX, removable_device_types=REMOVABLE_DEVICE_TYPES, harddrive_file_suffix=HARDDRIVE_FILE_SUFFIX, @@ -64,44 +73,165 @@ def index(): archive_file_suffix=ARCHIVE_FILE_SUFFIX, ) + +@app.route("/drive/list", methods=["GET"]) +def drive_list(): + """ + Sets up the data structures and kicks off the rendering of the drive list page + """ + server_info = get_server_info() + + # Reads the canonical drive properties into a dict + # The file resides in the current dir of the web ui process + from pathlib import Path + drive_properties = Path(DRIVE_PROPERTIES_FILE) + if drive_properties.is_file(): + process = read_drive_properties(str(drive_properties)) + if process["status"] == False: + flash(process["msg"], "error") + return redirect(url_for("index")) + conf = process["conf"] + else: + flash("Could not read drive properties from " + str(drive_properties), "error") + return redirect(url_for("index")) + + hd_conf = [] + cd_conf = [] + rm_conf = [] + + for d in conf: + if d["device_type"] == "SCHD": + d["size"] = d["block_size"] * d["blocks"] + d["size_mb"] = "{:,.2f}".format(d["size"] / 1024 / 1024) + hd_conf.append(d) + elif d["device_type"] == "SCCD": + d["size_mb"] = "N/A" + cd_conf.append(d) + elif d["device_type"] == "SCRM": + d["size"] = d["block_size"] * d["blocks"] + d["size_mb"] = "{:,.2f}".format(d["size"] / 1024 / 1024) + rm_conf.append(d) + + files=list_files() + sorted_image_files = sorted(files["files"], key = lambda x: x["name"].lower()) + hd_conf = sorted(hd_conf, key = lambda x: x["name"].lower()) + cd_conf = sorted(cd_conf, key = lambda x: x["name"].lower()) + rm_conf = sorted(rm_conf, key = lambda x: x["name"].lower()) + + return render_template( + "drives.html", + files=sorted_image_files, + base_dir=base_dir, + hd_conf=hd_conf, + cd_conf=cd_conf, + rm_conf=rm_conf, + running_env=running_env(), + server_info=server_info, + cdrom_file_suffix=CDROM_FILE_SUFFIX, + ) + + @app.route('/pwa/') def send_pwa_files(path): return send_from_directory('pwa', path) + +@app.route("/drive/create", methods=["POST"]) +def drive_create(): + vendor = request.form.get("vendor") + product = request.form.get("product") + revision = request.form.get("revision") + blocks = request.form.get("blocks") + block_size = request.form.get("block_size") + size = request.form.get("size") + file_type = request.form.get("file_type") + file_name = request.form.get("file_name") + + # Creating the image file + process = create_new_image(file_name, file_type, size) + if process["status"] == True: + flash(f"Drive image file {file_name}.{file_type} created") + flash(process["msg"]) + else: + flash(f"Failed to create file {file_name}.{file_type}", "error") + flash(process["msg"], "error") + return redirect(url_for("index")) + + # Creating the drive properties file + from pathlib import Path + file_name = str(Path(file_name).stem) + "." + PROPERTIES_SUFFIX + properties = {"vendor": vendor, "product": product, "revision": revision, \ + "blocks": blocks, "block_size": block_size} + process = write_drive_properties(file_name, properties) + if process["status"] == True: + flash(f"Drive properties file {file_name} created") + return redirect(url_for("index")) + else: + flash(f"Failed to create drive properties file {file_name}", "error") + return redirect(url_for("index")) + + +@app.route("/drive/cdrom", methods=["POST"]) +def drive_cdrom(): + vendor = request.form.get("vendor") + product = request.form.get("product") + revision = request.form.get("revision") + block_size = request.form.get("block_size") + file_name = request.form.get("file_name") + + # Creating the drive properties file + from pathlib import Path + file_name = str(Path(file_name).stem) + "." + PROPERTIES_SUFFIX + properties = {"vendor": vendor, "product": product, "revision": revision, "block_size": block_size} + process = write_drive_properties(file_name, properties) + if process["status"] == True: + flash(f"Drive properties file {file_name} created") + return redirect(url_for("index")) + else: + flash(f"Failed to create drive properties file {file_name}", "error") + return redirect(url_for("index")) + + @app.route("/config/save", methods=["POST"]) def config_save(): file_name = request.form.get("name") or "default" - file_name = f"{base_dir}{file_name}.json" + file_name = f"{file_name}.json" process = write_config(file_name) if process["status"] == True: - flash(f"Saved config to {file_name}!") + flash(f"Saved config to {file_name}!") + flash(process["msg"]) return redirect(url_for("index")) else: - flash(f"Failed to saved config to {file_name}!", "error") - flash(f"{process['msg']}", "error") - return redirect(url_for("index")) + flash(f"Failed to saved config to {file_name}!", "error") + flash(process['msg'], "error") + return redirect(url_for("index")) @app.route("/config/load", methods=["POST"]) def config_load(): file_name = request.form.get("name") - file_name = f"{base_dir}{file_name}" if "load" in request.form: process = read_config(file_name) if process["status"] == True: - flash(f"Loaded config from {file_name}!") + flash(f"Loaded config from {file_name}!") + flash(process["msg"]) + return redirect(url_for("index")) else: - flash(f"Failed to load {file_name}!", "error") - flash(f"{process['msg']}", "error") + flash(f"Failed to load {file_name}!", "error") + flash(process['msg'], "error") + return redirect(url_for("index")) elif "delete" in request.form: - if delete_file(file_name): - flash(f"Deleted config {file_name}!") + process = delete_file(file_name) + if process["status"] == True: + flash(f"Deleted config {file_name}!") + flash(process["msg"]) + return redirect(url_for("index")) else: - flash(f"Failed to delete {file_name}!", "error") - - return redirect(url_for("index")) + flash(f"Failed to delete {file_name}!", "error") + flash(process['msg'], "error") + return redirect(url_for("index")) @app.route("/logs/show", methods=["POST"]) @@ -142,13 +272,23 @@ def log_level(): @app.route("/daynaport/attach", methods=["POST"]) def daynaport_attach(): scsi_id = request.form.get("scsi_id") + interface = request.form.get("if") + ip = request.form.get("ip") + mask = request.form.get("mask") + + kwargs = {"device_type": "SCDP"} + if interface != "": + arg = interface + if "" not in (ip, mask): + arg += (":" + ip + "/" + mask) + kwargs["interfaces"] = arg validate = validate_scsi_id(scsi_id) if validate["status"] == False: flash(validate["msg"], "error") return redirect(url_for("index")) - process = attach_daynaport(scsi_id) + process = attach_image(scsi_id, **kwargs) if process["status"] == True: flash(f"Attached DaynaPORT to SCSI id {scsi_id}!") return redirect(url_for("index")) @@ -171,34 +311,38 @@ def attach(): kwargs = {"image": file_name} - # Attempt to load the device config sidecar file: - # same base path but .rascsi instead of the original suffix. - from pathlib import Path - device_config = Path(base_dir + str(Path(file_name).stem) + ".rascsi") - if device_config.is_file(): - process = read_device_config(device_config) - if process["status"] == False: - flash(process["msg"], "error") - return redirect(url_for("index")) - conf = process["conf"] - conf_file_size = conf["blocks"] * conf["block_size"] - if conf_file_size != 0 and conf_file_size > int(file_size): - flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error") - flash(f"The file size {file_size} bytes needs to be at least {conf_file_size} bytes.", "error") - return redirect(url_for("index")) - kwargs["device_type"] = conf["device_type"] - kwargs["vendor"] = conf["vendor"] - kwargs["product"] = conf["product"] - kwargs["revision"] = conf["revision"] - kwargs["block_size"] = conf["block_size"] - # Validate image type by file name suffix as fallback - elif file_name.lower().endswith(CDROM_FILE_SUFFIX): + # Validate image type by file name suffix + if file_name.lower().endswith(CDROM_FILE_SUFFIX): kwargs["device_type"] = "SCCD" elif file_name.lower().endswith(REMOVABLE_FILE_SUFFIX): kwargs["device_type"] = "SCRM" elif file_name.lower().endswith(HARDDRIVE_FILE_SUFFIX): kwargs["device_type"] = "SCHD" + # Attempt to load the device properties file: + # same base path but PROPERTIES_SUFFIX instead of the original suffix. + from pathlib import Path + file_name_base = str(Path(file_name).stem) + drive_properties = Path(base_dir + file_name_base + "." + PROPERTIES_SUFFIX) + if drive_properties.is_file(): + process = read_drive_properties(str(drive_properties)) + if process["status"] == False: + flash(f"Failed to load the device properties file {file_name_base}.{PROPERTIES_SUFFIX}", "error") + flash(process["msg"], "error") + return redirect(url_for("index")) + conf = process["conf"] + # CD-ROM drives have no inherent size, so bypass the size check + if kwargs["device_type"] != "SCCD": + conf_file_size = int(conf["blocks"]) * int(conf["block_size"]) + if conf_file_size != 0 and conf_file_size > int(file_size): + flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error") + flash(f"The file size {file_size} bytes needs to be at least {conf_file_size} bytes.", "error") + return redirect(url_for("index")) + kwargs["vendor"] = conf["vendor"] + kwargs["product"] = conf["product"] + kwargs["revision"] = conf["revision"] + kwargs["block_size"] = conf["block_size"] + process = attach_image(scsi_id, **kwargs) if process["status"] == True: flash(f"Attached {file_name} to SCSI id {scsi_id}!") @@ -249,8 +393,16 @@ def eject(): @app.route("/scsi/info", methods=["POST"]) def device_info(): scsi_id = request.form.get("scsi_id") - # Extracting the 0th dictionary in list index 0 - device = list_devices(scsi_id)[0][0] + + devices = list_devices(scsi_id) + + # First check if any device at all was returned + if devices["status"] == False: + flash(f"No device attached to SCSI id {scsi_id}!", "error") + return redirect(url_for("index")) + # Looking at the first dict in list to get + # the one and only device that should have been returned + device = devices["device_list"][0] if str(device["id"]) == scsi_id: flash("=== DEVICE INFO ===") flash(f"SCSI ID: {device['id']}") @@ -270,31 +422,36 @@ def device_info(): @app.route("/pi/reboot", methods=["POST"]) def restart(): + flash("Restarting the Pi momentarily...") reboot_pi() - flash("Restarting...") return redirect(url_for("index")) @app.route("/rascsi/restart", methods=["POST"]) def rascsi_restart(): + server_info = get_server_info() rascsi_service("restart") flash("Restarting RaSCSI Service...") - reserved_scsi_ids = app.config.get("RESERVED_SCSI_IDS") - if reserved_scsi_ids != "": - reserve_scsi_ids(reserved_scsi_ids) + # Need to turn this into a list of strings from a list of ints + reserve_scsi_ids([str(e) for e in server_info["reserved_ids"]]) return redirect(url_for("index")) @app.route("/pi/shutdown", methods=["POST"]) def shutdown(): + flash("Shutting down the Pi momentarily...") shutdown_pi() - flash("Shutting down...") return redirect(url_for("index")) @app.route("/files/download_to_iso", methods=["POST"]) def download_file(): scsi_id = request.form.get("scsi_id") + validate = validate_scsi_id(scsi_id) + if validate["status"] == False: + flash(validate["msg"], "error") + return redirect(url_for("index")) + url = request.form.get("url") process = download_file_to_iso(scsi_id, url) if process["status"] == True: @@ -348,17 +505,17 @@ def upload_file(filename): @app.route("/files/create", methods=["POST"]) def create_file(): file_name = request.form.get("file_name") - size = request.form.get("size") + size = (int(request.form.get("size")) * 1024 * 1024) file_type = request.form.get("type") process = create_new_image(file_name, file_type, size) - if process.returncode == 0: - flash("Drive created") + if process["status"] == True: + flash(f"Drive image created as {file_name}.{file_type}") + flash(process["msg"]) return redirect(url_for("index")) else: - flash("Failed to create file", "error") - flash(process.stdout, "stdout") - flash(process.stderr, "stderr") + flash(f"Failed to create file {file_name}.{file_type}", "error") + flash(process["msg"], "error") return redirect(url_for("index")) @@ -370,14 +527,35 @@ def download(): @app.route("/files/delete", methods=["POST"]) def delete(): - image = request.form.get("image") - if delete_file(base_dir + image): - flash("File " + image + " deleted") - return redirect(url_for("index")) + file_name = request.form.get("image") + + process = delete_file(file_name) + if process["status"] == True: + flash(f"File {file_name} deleted!") + flash(process["msg"]) else: - flash("Failed to Delete " + image, "error") + flash(f"Failed to delete file {file_name}!", "error") + flash(process["msg"], "error") return redirect(url_for("index")) + # Delete the drive properties file, if it exists + from pathlib import Path + file_name = str(Path(file_name).stem) + "." + PROPERTIES_SUFFIX + file_path = Path(base_dir + file_name) + if file_path.is_file(): + process = delete_file(file_name) + if process["status"] == True: + flash(f"File {file_name} deleted!") + flash(process["msg"]) + return redirect(url_for("index")) + else: + flash(f"Failed to delete file {file_name}!", "error") + flash(process["msg"], "error") + return redirect(url_for("index")) + + return redirect(url_for("index")) + + @app.route("/files/unzip", methods=["POST"]) def unzip(): @@ -402,18 +580,15 @@ if __name__ == "__main__": from sys import argv if len(argv) >= 2: - # Reserved ids format is a string of digits such as '017' - app.config["RESERVED_SCSI_IDS"] = str(argv[1]) # Reserve SCSI IDs on the backend side to prevent use - reserve_scsi_ids(app.config.get("RESERVED_SCSI_IDS")) - else: - app.config["RESERVED_SCSI_IDS"] = "" + # Expecting argv as a string of digits such as '017' + reserve_scsi_ids(list(argv[1])) # Load the default configuration file, if found from pathlib import Path - default_config = Path(DEFAULT_CONFIG) - if default_config.is_file(): - read_config(default_config) + default_config_path = Path(base_dir + DEFAULT_CONFIG) + if default_config_path.is_file(): + read_config(DEFAULT_CONFIG) import bjoern print("Serving rascsi-web...")