From 0b78f09f8dee79c75d705d034551d85f4642107f Mon Sep 17 00:00:00 2001 From: Eric Helgeson Date: Mon, 1 Feb 2021 12:24:59 -0600 Subject: [PATCH] Add DaynaPORT config to web Add bridge-utils Minor fixes to easyinstall Can now save config, detach all devices Bridge script error out if any command fails --- .gitignore | 3 + easyinstall.sh | 20 +++++-- src/raspberrypi/setup_bridge.sh | 1 + src/web/file_cmds.py | 2 +- src/web/mock/bin/brctl | 12 ++++ src/web/mock/bin/ip | 12 ++++ src/web/ractl_cmds.py | 46 ++++++++++++++- src/web/templates/index.html | 47 ++++++++++++++- src/web/web.py | 100 +++++++++++++++++++++++++++----- 9 files changed, 216 insertions(+), 27 deletions(-) create mode 100644 src/web/mock/bin/brctl create mode 100644 src/web/mock/bin/ip diff --git a/.gitignore b/.gitignore index 995e1e8a..7b05f121 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ venv *.pyc core +.idea/ +.DS_Store +*.swp diff --git a/easyinstall.sh b/easyinstall.sh index a3701188..811adda9 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -28,7 +28,7 @@ LIDO_DRIVER=~/RASCSI/lido-driver.img function initialChecks() { currentUser=$(whoami) - if [ "pi" != $currentUser ]; then + if [ "pi" != "$currentUser" ]; then echo "You must use 'pi' user (current: $currentUser)" exit 1 fi @@ -40,10 +40,14 @@ function initialChecks() { fi } +function installPackages() { + sudo apt-get update && sudo apt install git libspdlog-dev genisoimage python3 python3-venv nginx bridge-utils -y +} # install all dependency packages for RaSCSI Service # compile and install RaSCSI Service function installRaScsi() { + installPackages sudo apt-get update && sudo apt-get install --yes git libspdlog-dev cd ~/RASCSI/src/raspberrypi @@ -69,7 +73,7 @@ www-data ALL=NOPASSWD: /sbin/shutdown, /sbin/reboot function stopOldWebInterface() { APACHE_STATUS=$(sudo systemctl status apache2 &> /dev/null; echo $?) - if [ $APACHE_STATUS -eq 0 ] ; then + if [ "$APACHE_STATUS" -eq 0 ] ; then echo "Stopping old Apache2 RaSCSI Web..." sudo systemctl disable apache2 sudo systemctl stop apache2 @@ -79,7 +83,7 @@ function stopOldWebInterface() { # install everything required to run an HTTP server (Nginx + Python Flask App) function installRaScsiWebInterface() { stopOldWebInterface - sudo apt install genisoimage python3 python3-venv nginx -y + installPackages sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html @@ -100,11 +104,15 @@ function installRaScsiWebInterface() { function updateRaScsiGit() { cd ~/RASCSI - git pull + git fetch + git stash + git rebase origin/master + git stash apply } function updateRaScsi() { updateRaScsiGit + installPackages sudo systemctl stop rascsi cd ~/RASCSI/src/raspberrypi @@ -241,8 +249,8 @@ function showMenu() { choice=-1 until [ $choice -ge "0" ] && [ $choice -le "7" ]; do - echo "Enter your choice (0-7) or CTRL-C to exit" - read choice + echo -n "Enter your choice (0-7) or CTRL-C to exit: " + read -r choice done diff --git a/src/raspberrypi/setup_bridge.sh b/src/raspberrypi/setup_bridge.sh index d81e7358..936a4944 100755 --- a/src/raspberrypi/setup_bridge.sh +++ b/src/raspberrypi/setup_bridge.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e # Fail if any command fails sudo brctl addbr rascsi_bridge sudo brctl addif rascsi_bridge eth0 sudo ip link set dev rascsi_bridge up diff --git a/src/web/file_cmds.py b/src/web/file_cmds.py index 58512ed0..c424f82f 100644 --- a/src/web/file_cmds.py +++ b/src/web/file_cmds.py @@ -64,7 +64,7 @@ def download_file_to_iso(scsi_id, url): ) if iso_proc.returncode != 0: return iso_proc - return attach_image(scsi_id, iso_filename, "cd") + return attach_image(scsi_id, iso_filename, "SCCD") def download_image(url): diff --git a/src/web/mock/bin/brctl b/src/web/mock/bin/brctl new file mode 100644 index 00000000..176fe42d --- /dev/null +++ b/src/web/mock/bin/brctl @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# Mock responses to rascsi-web +case $1 in + "show") + echo "rascsi_bridge 8000.dca632b05dd1 no eth0" + ;; + + **) + echo "default" + ;; +esac diff --git a/src/web/mock/bin/ip b/src/web/mock/bin/ip new file mode 100644 index 00000000..259dc4b6 --- /dev/null +++ b/src/web/mock/bin/ip @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# Mock responses to rascsi-web +case $1 in + "link") + echo "link here" + ;; + + **) + echo "default" + ;; +esac diff --git a/src/web/ractl_cmds.py b/src/web/ractl_cmds.py index 60205903..fbc9cef5 100644 --- a/src/web/ractl_cmds.py +++ b/src/web/ractl_cmds.py @@ -37,6 +37,15 @@ def list_files(): return files_list +def list_config_files(): + files_list = [] + for root, dirs, files in os.walk(base_dir): + for file in files: + if file.endswith(".csv"): + files_list.append(file) + return files_list + + def get_valid_scsi_ids(devices): invalid_list = EXCLUDE_SCSI_IDS.copy() for device in devices: @@ -55,12 +64,16 @@ def get_type(scsi_id): return list_devices()[int(scsi_id)]["type"] -def attach_image(scsi_id, image, type): - if type == "cd" and get_type(scsi_id) == "SCCD": +def attach_image(scsi_id, image, device_type): + if device_type == "SCCD" and get_type(scsi_id) == "SCCD": return insert(scsi_id, image) + elif device_type == "SCDP": + attach_daynaport(scsi_id) else: + if device_type == "SCCD": + device_type = "cd" return subprocess.run( - ["rasctl", "-c", "attach", "-t", type, "-i", scsi_id, "-f", image], + ["rasctl", "-c", "attach", "-t", device_type, "-i", scsi_id, "-f", image], capture_output=True, ) @@ -69,6 +82,11 @@ def detach_by_id(scsi_id): return subprocess.run(["rasctl", "-c" "detach", "-i", scsi_id], capture_output=True) +def detach_all(): + for scsi_id in range(0, 7): + subprocess.run(["rasctl", "-c" "detach", "-i", str(scsi_id)]) + + def disconnect_by_id(scsi_id): return subprocess.run( ["rasctl", "-c", "disconnect", "-i", scsi_id], capture_output=True @@ -85,6 +103,28 @@ def insert(scsi_id, image): ) +def attach_daynaport(scsi_id): + return subprocess.run( + ["rasctl", "-i", scsi_id, "-c", "attach", "-t", "daynaport"], + capture_output=True, + ) + + +def is_bridge_setup(interface): + process = subprocess.run(["brctl", "show"], capture_output=True) + output = process.stdout.decode("utf-8") + if "rascsi_bridge" in output and interface in output: + return True + return False + + +def daynaport_setup_bridge(interface): + return subprocess.run( + [f"{base_dir}../RASCSI/src/raspberrypi/setup_bridge.sh", interface], + capture_output=True, + ) + + def rascsi_service(action): # start/stop/restart return ( diff --git a/src/web/templates/index.html b/src/web/templates/index.html index a11fab57..d7bbccdd 100644 --- a/src/web/templates/index.html +++ b/src/web/templates/index.html @@ -17,10 +17,26 @@ - {% endblock %} +{% endblock %} {% block content %}

Current RaSCSI Configuration

+
+ + +
+
+ + +
+
+ +
+ @@ -103,7 +119,35 @@

+

Attach Ethernet Adapter

+

Emulates a SCSI DaynaPORT Ethernet Adapter. Host drivers required.

+ + + + + + + +
+
+ + +
+
+ {% if bridge_configured %} + Bridge is currently configured! + {% else %} +
+ +
+ {% endif %} +
+

Upload File

Uploads file to {{base_dir}}. Max file size is set to {{max_file_size / 1024 /1024 }}MB

@@ -183,7 +227,6 @@

-

Raspberry Pi Operations

diff --git a/src/web/web.py b/src/web/web.py index 0fe3a20f..2268d43d 100644 --- a/src/web/web.py +++ b/src/web/web.py @@ -1,5 +1,3 @@ -import os - from flask import Flask, render_template, request, flash, url_for, redirect, send_file from werkzeug.utils import secure_filename @@ -19,6 +17,11 @@ from ractl_cmds import ( detach_by_id, eject_by_id, get_valid_scsi_ids, + attach_daynaport, + is_bridge_setup, + daynaport_setup_bridge, + list_config_files, + detach_all, ) from settings import * @@ -31,9 +34,11 @@ def index(): scsi_ids = get_valid_scsi_ids(devices) return render_template( "index.html", + bridge_configured=is_bridge_setup("eth0"), devices=devices, active=is_active(), files=list_files(), + config_files=list_config_files(), base_dir=base_dir, scsi_ids=scsi_ids, max_file_size=MAX_FILE_SIZE, @@ -41,6 +46,37 @@ def 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}.csv" + import csv + + with open(file_name, "w") as csv_file: + writer = csv.writer(csv_file) + for device in list_devices(): + if device["type"] is not "-": + writer.writerow(device.values()) + flash(f"Saved config to {file_name}!") + return redirect(url_for("index")) + + +@app.route("/config/load", methods=["POST"]) +def config_load(): + file_name = request.form.get("name") or "default.csv" + file_name = f"{base_dir}{file_name}" + detach_all() + import csv + + with open(file_name) as csv_file: + config_reader = csv.reader(csv_file) + for row in config_reader: + image_name = row[3].replace("(WRITEPROTECT)", "") + attach_image(row[0], image_name, row[2]) + flash(f"Loaded config from {file_name}!") + return redirect(url_for("index")) + + @app.route("/logs") def logs(): import subprocess @@ -52,7 +88,36 @@ def logs(): headers = {"content-type": "text/plain"} return process.stdout.decode("utf-8"), 200, headers else: - flash(u"Failed to get logs") + flash("Failed to get logs") + flash(process.stdout.decode("utf-8"), "stdout") + flash(process.stderr.decode("utf-8"), "stderr") + return redirect(url_for("index")) + + +@app.route("/daynaport/attach", methods=["POST"]) +def daynaport_attach(): + scsi_id = request.form.get("scsi_id") + process = attach_daynaport(scsi_id) + if process.returncode == 0: + flash(f"Attached DaynaPORT to SCSI id {scsi_id}!") + return redirect(url_for("index")) + else: + flash(f"Failed to attach DaynaPORT to SCSI id {scsi_id}!", "error") + flash(process.stdout.decode("utf-8"), "stdout") + flash(process.stderr.decode("utf-8"), "stderr") + return redirect(url_for("index")) + + +@app.route("/daynaport/setup", methods=["POST"]) +def daynaport_setup(): + # Future use for wifi + interface = request.form.get("interface") or "eth0" + process = daynaport_setup_bridge(interface) + if process.returncode == 0: + flash(f"Configured DaynaPORT bridge on {interface}!") + return redirect(url_for("index")) + else: + flash(f"Failed to configure DaynaPORT bridge on {interface}!", "error") flash(process.stdout.decode("utf-8"), "stdout") flash(process.stderr.decode("utf-8"), "stderr") return redirect(url_for("index")) @@ -69,31 +134,36 @@ def attach(): elif file_name.lower().endswith(".hda"): image_type = "hd" else: - flash(u"Unknown file type. Valid files are .iso, .hda, .cdr", "error") + flash("Unknown file type. Valid files are .iso, .hda, .cdr", "error") return redirect(url_for("index")) process = attach_image(scsi_id, file_name, image_type) if process.returncode == 0: - flash("Attached " + file_name + " to scsi id " + scsi_id + "!") + flash(f"Attached {file_name} to SCSI id {scsi_id}!") return redirect(url_for("index")) else: - flash( - u"Failed to attach " + file_name + " to scsi id " + scsi_id + "!", "error" - ) + flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error") flash(process.stdout.decode("utf-8"), "stdout") flash(process.stderr.decode("utf-8"), "stderr") return redirect(url_for("index")) +@app.route("/scsi/detach_all", methods=["POST"]) +def detach_all_devices(): + detach_all() + flash("Detached all SCSI devices!") + return redirect(url_for("index")) + + @app.route("/scsi/detach", methods=["POST"]) def detach(): scsi_id = request.form.get("scsi_id") process = detach_by_id(scsi_id) if process.returncode == 0: - flash("Detached scsi id " + scsi_id + "!") + flash("Detached SCSI id " + scsi_id + "!") return redirect(url_for("index")) else: - flash(u"Failed to detach scsi id " + scsi_id + "!", "error") + flash("Failed to detach SCSI id " + scsi_id + "!", "error") flash(process.stdout, "stdout") flash(process.stderr, "stderr") return redirect(url_for("index")) @@ -107,7 +177,7 @@ def eject(): flash("Ejected scsi id " + scsi_id + "!") return redirect(url_for("index")) else: - flash(u"Failed to eject scsi id " + scsi_id + "!", "error") + flash("Failed to eject SCSI id " + scsi_id + "!", "error") flash(process.stdout, "stdout") flash(process.stderr, "stderr") return redirect(url_for("index")) @@ -143,7 +213,7 @@ def download_file(): flash("File Downloaded") return redirect(url_for("index")) else: - flash(u"Failed to download file", "error") + flash("Failed to download file", "error") flash(process.stdout, "stdout") flash(process.stderr, "stderr") return redirect(url_for("index")) @@ -181,7 +251,7 @@ def create_file(): flash("Drive created") return redirect(url_for("index")) else: - flash(u"Failed to create file", "error") + flash("Failed to create file", "error") flash(process.stdout, "stdout") flash(process.stderr, "stderr") return redirect(url_for("index")) @@ -200,7 +270,7 @@ def delete(): flash("File " + image + " deleted") return redirect(url_for("index")) else: - flash(u"Failed to Delete " + image, "error") + flash("Failed to Delete " + image, "error") return redirect(url_for("index")) @@ -212,7 +282,7 @@ def unzip(): flash("Unzipped file " + image) return redirect(url_for("index")) else: - flash(u"Failed to unzip " + image, "error") + flash("Failed to unzip " + image, "error") return redirect(url_for("index"))