diff --git a/python/proxy/raproxy_plugins/automount.ini b/python/proxy/raproxy_plugins/automount.ini new file mode 100644 index 00000000..0d8587a7 --- /dev/null +++ b/python/proxy/raproxy_plugins/automount.ini @@ -0,0 +1,6 @@ +automount_directory=/home/pi/automount +use_sudo=True +sudo=/usr/bin/sudo +mount=/usr/bin/mount +umount=/usr/bin/umount +automount_extension=.automount \ No newline at end of file diff --git a/python/proxy/raproxy_plugins/automount.py b/python/proxy/raproxy_plugins/automount.py new file mode 100644 index 00000000..5d5d98c1 --- /dev/null +++ b/python/proxy/raproxy_plugins/automount.py @@ -0,0 +1,120 @@ +from yapsy.IPlugin import IPlugin +from pathlib import Path +import configparser +import os +import subprocess + + +class RaProxyAutomountPlugin(IPlugin): + def detach(self, scsi_id, type, path): + file = os.path.basename(str(path)) + automount_path = Path(self.config['main']['automount_directory']).joinpath(file) + automount_image_config_file = str(path) + self.config['main']['automount_extension'] + if not Path(automount_image_config_file).is_file(): + print("RASCSI proxy automount image config file [" + automount_image_config_file + "] not found. Skipping " + "mount action.") + return + automount_image_config = configparser.ConfigParser() + with open(automount_image_config_file) as stream: + automount_image_config.read_string("[main]\n" + stream.read()) + + if not automount_image_config.has_option("main","mount_options"): + print("RASCSI proxy automount image config file is invalid and/or does not have a valid key [" + "mount_options]. Skipping mount action.") + return + + if not automount_path.exists(): + try: + automount_path.mkdir(parents=True, exist_ok=True) + except Exception as e: + print(e) + + try: + if self.config['main']['use_sudo']: + command = [self.config['main']['sudo'],self.config['main']['mount'], path, str(f'{automount_path}/')] +# print(command) + return_code = subprocess.call(command) + else: + command = [self.config['main']['mount'], path, str(f'{automount_path}/')] +# print(command) + return_code = subprocess.call(command) +# print("return code: " + str(return_code)) + if return_code != 0: + raise Exception('mounting ' + str(path) + ' failed on detach command. Unknown state!') + print("RASCSI proxy automount detach/eject action: image [" + str(path) + "] has been mounted to [" + str(f'{automount_path}/') + "].") + except Exception as e: + print(e) + + def attach(self, scsi_id, type, path): + file = os.path.basename(str(path)) + automount_path = Path(self.config['main']['automount_directory']).joinpath(file) + if not automount_path.exists(): + print("RASCSI proxy automount image mount directory [" + str(automount_path) + "] not found. Probably not " + "mounted. Skipping unmount" + " action.") + return + + if len(os.listdir(str(automount_path)) ) > 0: + try: + if self.config['main']['use_sudo']: + command = [self.config['main']['sudo'],self.config['main']['umount'],'-l', str(f'{automount_path}/')] +# print(command) + return_code = subprocess.call(command) + else: + command = [self.config['main']['umount'],'-l', str(f'{automount_path}/')] +# print(command) + return_code = subprocess.call(command) +# print("return code: " + str(return_code)) + if return_code != 0: + raise Exception('Umounting ' + str(path) + ' failed on attach command. Unknown state!') + + print("RASCSI proxy automount attach/insert action: image [" + str(path) + "] has been unmounted from [" + str(f'{automount_path}/') + "].") + + except Exception as e: + print(e) + + try: + automount_path.rmdir() + except Exception as e: + print(e) + + def init_hook(self): + print("RASCSI proxy automount plugin: init") + + self.config = configparser.ConfigParser() + + with open('./raproxy_plugins/automount.ini') as stream: + self.config.read_string("[main]\n" + stream.read()) + + path = Path(self.config['main']['automount_directory']) + if not path.exists(): + path.mkdir(parents=True, exist_ok=True) + + def detach_hook(self, scsi_id, type, path): + self.detach(scsi_id, type, path) +# print("automount path: " + str(path)) +# print("detaching from scsi id: " + str(scsi_id)) +# print("detaching file type : " + str(type)) +# print("detaching file : " + str(path)) + + def eject_hook(self, scsi_id, type, path): + self.detach(scsi_id, type, path) +# print("automount path: " + str(path)) +# print("ejecting from scsi id : " + str(scsi_id)) +# print("ejecting file type : " + str(type)) +# print("ejecting file : " + str(path)) + + def attach_hook(self, scsi_id, type, path): + self.attach(scsi_id, type, path) +# print("automount path: " + str(path)) +# print("attaching on scsi id : " + str(scsi_id)) +# print("attaching file type : " + str(type)) +# print("attaching file : " + str(path)) + + def insert_hook(self, scsi_id, type, path): + self.attach(scsi_id, type, path) +# print("automount path: " + str(path)) +# print("inserting on scsi id : " + str(scsi_id)) +# print("inserting file type : " + str(type)) +# print("inserting file : " + str(path)) + diff --git a/python/proxy/raproxy_plugins/automount.yapsy-plugin b/python/proxy/raproxy_plugins/automount.yapsy-plugin new file mode 100644 index 00000000..510a048f --- /dev/null +++ b/python/proxy/raproxy_plugins/automount.yapsy-plugin @@ -0,0 +1,10 @@ +[Core] +Name = RASCSI proxy automount plugin +Module = automount + +[Documentation] +Author = RASCSI Contributors +Version = 0.1 +Website = https://github.com/akuker/RASCSI +Description = RASCSI proxy automount plugin + diff --git a/python/proxy/raproxy_plugins/echo.py b/python/proxy/raproxy_plugins/echo.py new file mode 100644 index 00000000..08aec7ac --- /dev/null +++ b/python/proxy/raproxy_plugins/echo.py @@ -0,0 +1,22 @@ +from yapsy.IPlugin import IPlugin + + +class RaProxyEchoPlugin(IPlugin): + def init_hook(self): + print("RASCSI proxy echo plugin: init") + + def detach_hook(self, scsi_id, type, path): + print("detaching from scsi id: " + str(scsi_id)) + print("detaching file type : " + str(type)) + print("detaching file : " + str(path)) + + def eject_hook(self, scsi_id, type, path): + print("ejecting from scsi id : " + str(scsi_id)) + print("ejecting file type : " + str(type)) + print("ejecting file : " + str(path)) + + def attach_hook(self, scsi_id, type, path): + print("attaching on scsi id : " + str(scsi_id)) + print("attaching file type : " + str(type)) + print("attaching file : " + str(path)) + diff --git a/python/proxy/raproxy_plugins/echo.yapsy-plugin.disabled b/python/proxy/raproxy_plugins/echo.yapsy-plugin.disabled new file mode 100644 index 00000000..8b920d6a --- /dev/null +++ b/python/proxy/raproxy_plugins/echo.yapsy-plugin.disabled @@ -0,0 +1,10 @@ +[Core] +Name = RASCSI proxy echo plugin +Module = echo + +[Documentation] +Author = RASCSI Contributors +Version = 0.1 +Website = https://github.com/akuker/RASCSI +Description = RASCSI proxy echo plugin + diff --git a/python/proxy/raproxy_plugins/raproxy_attach.sh b/python/proxy/raproxy_plugins/raproxy_attach.sh new file mode 100644 index 00000000..9c7060a4 --- /dev/null +++ b/python/proxy/raproxy_plugins/raproxy_attach.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +scsi_id=$1 +type=$2 +path=$3 + +echo "shell script: attaching from scsi id: " ${scsi_id} +echo "shell script: attaching file type : " ${type} +echo "shell script: attaching file : " ${path} + +exit 0 diff --git a/python/proxy/raproxy_plugins/raproxy_detach.sh b/python/proxy/raproxy_plugins/raproxy_detach.sh new file mode 100644 index 00000000..79f61b22 --- /dev/null +++ b/python/proxy/raproxy_plugins/raproxy_detach.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +scsi_id=$1 +type=$2 +path=$3 + +echo "shell script: detaching from scsi id: " ${scsi_id} +echo "shell script: detaching file type : " ${type} +echo "shell script: detaching file : " ${path} + +exit 0 diff --git a/python/proxy/raproxy_plugins/raproxy_eject.sh b/python/proxy/raproxy_plugins/raproxy_eject.sh new file mode 100644 index 00000000..d0de2f87 --- /dev/null +++ b/python/proxy/raproxy_plugins/raproxy_eject.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +scsi_id=$1 +type=$2 +path=$3 + +echo "shell script: ejecting from scsi id : " ${scsi_id} +echo "shell script: ejecting file type : " ${type} +echo "shell script: ejecting file : " ${path} + +exit 0 diff --git a/python/proxy/raproxy_plugins/raproxy_init.sh b/python/proxy/raproxy_plugins/raproxy_init.sh new file mode 100644 index 00000000..8b1f57c1 --- /dev/null +++ b/python/proxy/raproxy_plugins/raproxy_init.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +scsi_id=$1 +type=$2 +path=$3 + +echo "RASCSI proxy shellscript plugin: init" + +exit 0 diff --git a/python/proxy/raproxy_plugins/shellscript.py b/python/proxy/raproxy_plugins/shellscript.py new file mode 100644 index 00000000..eefeb941 --- /dev/null +++ b/python/proxy/raproxy_plugins/shellscript.py @@ -0,0 +1,17 @@ +from yapsy.IPlugin import IPlugin +import subprocess + + +class RaProxyShellscriptPlugin(IPlugin): + def init_hook(self): + subprocess.call(['./raproxy_plugins/raproxy_init.sh']) + + def detach_hook(self, scsi_id, type, path): + subprocess.call(['./raproxy_plugins/raproxy_detach.sh',str(scsi_id),str(type),str(path)]) + + def eject_hook(self, scsi_id, type, path): + subprocess.call(['./raproxy_plugins/raproxy_eject.sh',str(scsi_id),str(type),str(path)]) + + def attach_hook(self, scsi_id, type, path): + subprocess.call(['./raproxy_plugins/raproxy_attach.sh',str(scsi_id),str(type),str(path)]) + diff --git a/python/proxy/raproxy_plugins/shellscript.yapsy-plugin.disabled b/python/proxy/raproxy_plugins/shellscript.yapsy-plugin.disabled new file mode 100644 index 00000000..df631576 --- /dev/null +++ b/python/proxy/raproxy_plugins/shellscript.yapsy-plugin.disabled @@ -0,0 +1,10 @@ +[Core] +Name = RASCSI Proxy shell script plugin +Module = shellscript + +[Documentation] +Author = RASCSI Contributors +Version = 0.1 +Website = https://github.com/akuker/RASCSI +Description = RASCSI Proxy Shellscript Plugin + diff --git a/python/proxy/rascsi_proxy.py b/python/proxy/rascsi_proxy.py new file mode 100644 index 00000000..677bbbb5 --- /dev/null +++ b/python/proxy/rascsi_proxy.py @@ -0,0 +1,224 @@ +#! /usr/bin/python + +import rascsi_interface_pb2 as proto +import socket +import socket_cmds +import time +from struct import pack, unpack +from yapsy.PluginManager import PluginManager + +######################### + +def send_pb_command(payload, host='localhost', port=6868): + """ + Takes a (str) containing a serialized protobuf as argument. + Establishes a socket connection with RaSCSI. + """ + + counter = 0 + tries = 20 + error_msg = "" + + import socket + while counter < tries: + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect((host, port)) + return socket_cmds.send_over_socket(sock, payload) + except socket.error as error: + counter += 1 + logging.warning("The RaSCSI service is not responding - attempt %s/%s", + str(counter), str(tries)) + error_msg = str(error) + sleep(0.2) + + logging.error(error_msg) + + # After failing all attempts, throw a 404 error + abort(404, _( + u"The RaSCSI Web Interface failed to connect to RaSCSI at %(host)s:%(port)s " + u"with error: %(error_msg)s. The RaSCSI process is not running or may have crashed.", + host=host, port=port, error_msg=error_msg, + ) + ) + +######################### + +def get_image_files_info(host='localhost',port=6868): + """ + Sends a DEFAULT_IMAGE_FILES_INFO command to the server. + Returns a dict with: + - (bool) status + - (str) images_dir, path to images dir + - (list) of (str) image_files + - (int) scan_depth, the current scan depth + """ + command = proto.PbCommand() + command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO + + data = send_pb_command(command.SerializeToString(),host,port) + result = proto.PbResult() + result.ParseFromString(data) + images_dir = result.image_files_info.default_image_folder + image_files = result.image_files_info.image_files + scan_depth = result.image_files_info.depth + return { + "status": result.status, + "images_dir": images_dir, + "image_files": image_files, + "scan_depth": scan_depth, + } + +######################### + +def socket_read_n(conn, n): + """ Read exactly n bytes from the socket. + Raise RuntimeError if the connection closed before + n bytes were read. + """ + buf = bytearray(b'') + while n > 0: + data = conn.recv(n) + if data == '': + raise RuntimeError('unexpected connection close') + buf.extend(data) + n -= len(data) + return bytes(buf) + +######################### + +def deserialize_message(conn, message): + # read the header with the size of the protobuf data + buf = socket_read_n(conn, 4) + size = (int(buf[3]) << 24) + (int(buf[2]) << 16) + (int(buf[1]) << 8) + int(buf[0]) + + buf = socket_read_n(conn, size) + + message.ParseFromString(buf) + +######################### + +def serialize_message(conn, data): + conn.sendall(pack(" /dev/null ; then + echo "python3 could not be found" + echo "Run 'sudo apt install python3' to fix." + ERROR=1 +fi +if ! python3 -m venv --help &> /dev/null ; then + echo "venv could not be found" + echo "Run 'sudo apt install python3-venv' to fix." + ERROR=1 +fi +if [ $ERROR = 1 ] ; then + echo + echo "Fix errors and re-run ./start.sh" + exit 1 +fi + +# Test for two known broken venv states +if test -e venv; then + GOOD_VENV=true + ! test -e venv/bin/activate && GOOD_VENV=false + pip3 list 1> /dev/null + test $? -eq 1 && GOOD_VENV=false + if ! "$GOOD_VENV"; then + echo "Deleting bad python venv" + sudo rm -rf venv + fi +fi + +# Create the venv if it doesn't exist +if ! test -e venv; then + echo "Creating python venv for RaSCSI proxy" + python3 -m venv venv + echo "Activating venv" + source venv/bin/activate + echo "Installing requirements.txt" + pip3 install wheel + pip3 install -r requirements.txt + git rev-parse HEAD > current +fi + +source venv/bin/activate + +# Detect if someone updates the git repo - we need to re-run pip3 install. +set +e +git rev-parse --is-inside-work-tree &> /dev/null +if [[ $? -eq 0 ]]; then + set -e + if ! test -e current; then + git rev-parse > current + elif [ "$(cat current)" != "$(git rev-parse HEAD)" ]; then + echo "New version detected, updating libraries from requirements.txt" + pip3 install -r requirements.txt + git rev-parse HEAD > current + fi +else + echo "Warning: Not running from a valid git repository. Will not be able to update the code." +fi +set -e + +# parse arguments +while [ "$1" != "" ]; do + PARAM=$(echo "$1" | awk -F= '{print $1}') + VALUE=$(echo "$1" | awk -F= '{print $2}') + case $PARAM in + -p | --port) + PORT="--port $VALUE" + ;; + *) + echo "ERROR: unknown parameter \"$PARAM\"" + exit 1 + ;; + esac + shift +done + +echo "Starting RaSCSI proxy..." +python3 rascsi_proxy.py ${PORT}