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
This commit is contained in:
Eric Helgeson 2021-02-01 12:24:59 -06:00
parent 943a9ab537
commit 0b78f09f8d
9 changed files with 216 additions and 27 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
venv venv
*.pyc *.pyc
core core
.idea/
.DS_Store
*.swp

View File

@ -28,7 +28,7 @@ LIDO_DRIVER=~/RASCSI/lido-driver.img
function initialChecks() { function initialChecks() {
currentUser=$(whoami) currentUser=$(whoami)
if [ "pi" != $currentUser ]; then if [ "pi" != "$currentUser" ]; then
echo "You must use 'pi' user (current: $currentUser)" echo "You must use 'pi' user (current: $currentUser)"
exit 1 exit 1
fi fi
@ -40,10 +40,14 @@ function initialChecks() {
fi 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 # install all dependency packages for RaSCSI Service
# compile and install RaSCSI Service # compile and install RaSCSI Service
function installRaScsi() { function installRaScsi() {
installPackages
sudo apt-get update && sudo apt-get install --yes git libspdlog-dev sudo apt-get update && sudo apt-get install --yes git libspdlog-dev
cd ~/RASCSI/src/raspberrypi cd ~/RASCSI/src/raspberrypi
@ -69,7 +73,7 @@ www-data ALL=NOPASSWD: /sbin/shutdown, /sbin/reboot
function stopOldWebInterface() { function stopOldWebInterface() {
APACHE_STATUS=$(sudo systemctl status apache2 &> /dev/null; echo $?) 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..." echo "Stopping old Apache2 RaSCSI Web..."
sudo systemctl disable apache2 sudo systemctl disable apache2
sudo systemctl stop apache2 sudo systemctl stop apache2
@ -79,7 +83,7 @@ function stopOldWebInterface() {
# install everything required to run an HTTP server (Nginx + Python Flask App) # install everything required to run an HTTP server (Nginx + Python Flask App)
function installRaScsiWebInterface() { function installRaScsiWebInterface() {
stopOldWebInterface 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/nginx-default.conf /etc/nginx/sites-available/default
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
@ -100,11 +104,15 @@ function installRaScsiWebInterface() {
function updateRaScsiGit() { function updateRaScsiGit() {
cd ~/RASCSI cd ~/RASCSI
git pull git fetch
git stash
git rebase origin/master
git stash apply
} }
function updateRaScsi() { function updateRaScsi() {
updateRaScsiGit updateRaScsiGit
installPackages
sudo systemctl stop rascsi sudo systemctl stop rascsi
cd ~/RASCSI/src/raspberrypi cd ~/RASCSI/src/raspberrypi
@ -241,8 +249,8 @@ function showMenu() {
choice=-1 choice=-1
until [ $choice -ge "0" ] && [ $choice -le "7" ]; do until [ $choice -ge "0" ] && [ $choice -le "7" ]; do
echo "Enter your choice (0-7) or CTRL-C to exit" echo -n "Enter your choice (0-7) or CTRL-C to exit: "
read choice read -r choice
done done

View File

@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
set -e # Fail if any command fails
sudo brctl addbr rascsi_bridge sudo brctl addbr rascsi_bridge
sudo brctl addif rascsi_bridge eth0 sudo brctl addif rascsi_bridge eth0
sudo ip link set dev rascsi_bridge up sudo ip link set dev rascsi_bridge up

View File

@ -64,7 +64,7 @@ def download_file_to_iso(scsi_id, url):
) )
if iso_proc.returncode != 0: if iso_proc.returncode != 0:
return iso_proc return iso_proc
return attach_image(scsi_id, iso_filename, "cd") return attach_image(scsi_id, iso_filename, "SCCD")
def download_image(url): def download_image(url):

12
src/web/mock/bin/brctl Normal file
View File

@ -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

12
src/web/mock/bin/ip Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Mock responses to rascsi-web
case $1 in
"link")
echo "link here"
;;
**)
echo "default"
;;
esac

View File

@ -37,6 +37,15 @@ def list_files():
return files_list 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): def get_valid_scsi_ids(devices):
invalid_list = EXCLUDE_SCSI_IDS.copy() invalid_list = EXCLUDE_SCSI_IDS.copy()
for device in devices: for device in devices:
@ -55,12 +64,16 @@ def get_type(scsi_id):
return list_devices()[int(scsi_id)]["type"] return list_devices()[int(scsi_id)]["type"]
def attach_image(scsi_id, image, type): def attach_image(scsi_id, image, device_type):
if type == "cd" and get_type(scsi_id) == "SCCD": if device_type == "SCCD" and get_type(scsi_id) == "SCCD":
return insert(scsi_id, image) return insert(scsi_id, image)
elif device_type == "SCDP":
attach_daynaport(scsi_id)
else: else:
if device_type == "SCCD":
device_type = "cd"
return subprocess.run( 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, 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) 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): def disconnect_by_id(scsi_id):
return subprocess.run( return subprocess.run(
["rasctl", "-c", "disconnect", "-i", scsi_id], capture_output=True ["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): def rascsi_service(action):
# start/stop/restart # start/stop/restart
return ( return (

View File

@ -17,10 +17,26 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h2>Current RaSCSI Configuration</h2> <h2>Current RaSCSI Configuration</h2>
<form action="/config/load" method="post">
<select name="name" >
{% for config in config_files %}
<option value="{{config}}">{{config.replace(".csv", '')}}</option>
{% endfor %}
</select>
<input type="submit" value="Load" />
</form>
<form action="/config/save" method="post">
<input name="name" placeholder="default">
<input type="submit" value="Save" />
</form>
<form action="/scsi/detach_all" method="post" onsubmit="return confirm('Detach all SCSI Devices?')">
<input type="submit" value="Detach All" />
</form>
<table cellpadding="3" border="black"> <table cellpadding="3" border="black">
<tbody> <tbody>
<tr> <tr>
@ -103,7 +119,35 @@
</table> </table>
<hr/> <hr/>
<h2>Attach Ethernet Adapter</h2>
<p>Emulates a SCSI DaynaPORT Ethernet Adapter. Host drivers required.</p>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/daynaport/attach" method="post">
<select name="scsi_id">
{% for id in scsi_ids %}
<option value="{{id}}">{{id}}</option>
{% endfor %}
</select>
<input type="submit" value="Attach" />
</form>
</td>
</tr>
<tr style="border: none">
<td style="border: none; vertical-align:top;">
{% if bridge_configured %}
<small>Bridge is currently configured!</small>
{% else %}
<form action="/daynaport/setup" method="post">
<input type="submit" value="Create Bridge" />
</form>
{% endif %}
</td>
</tr>
</table>
<hr/>
<h2>Upload File</h2> <h2>Upload File</h2>
<p>Uploads file to <tt>{{base_dir}}</tt>. Max file size is set to {{max_file_size / 1024 /1024 }}MB</p> <p>Uploads file to <tt>{{base_dir}}</tt>. Max file size is set to {{max_file_size / 1024 /1024 }}MB</p>
<table style="border: none"> <table style="border: none">
@ -183,7 +227,6 @@
</table> </table>
<hr/> <hr/>
<h2>Raspberry Pi Operations</h2> <h2>Raspberry Pi Operations</h2>
<table style="border: none"> <table style="border: none">
<tr style="border: none"> <tr style="border: none">

View File

@ -1,5 +1,3 @@
import os
from flask import Flask, render_template, request, flash, url_for, redirect, send_file from flask import Flask, render_template, request, flash, url_for, redirect, send_file
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@ -19,6 +17,11 @@ from ractl_cmds import (
detach_by_id, detach_by_id,
eject_by_id, eject_by_id,
get_valid_scsi_ids, get_valid_scsi_ids,
attach_daynaport,
is_bridge_setup,
daynaport_setup_bridge,
list_config_files,
detach_all,
) )
from settings import * from settings import *
@ -31,9 +34,11 @@ def index():
scsi_ids = get_valid_scsi_ids(devices) scsi_ids = get_valid_scsi_ids(devices)
return render_template( return render_template(
"index.html", "index.html",
bridge_configured=is_bridge_setup("eth0"),
devices=devices, devices=devices,
active=is_active(), active=is_active(),
files=list_files(), files=list_files(),
config_files=list_config_files(),
base_dir=base_dir, base_dir=base_dir,
scsi_ids=scsi_ids, scsi_ids=scsi_ids,
max_file_size=MAX_FILE_SIZE, 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") @app.route("/logs")
def logs(): def logs():
import subprocess import subprocess
@ -52,7 +88,36 @@ def logs():
headers = {"content-type": "text/plain"} headers = {"content-type": "text/plain"}
return process.stdout.decode("utf-8"), 200, headers return process.stdout.decode("utf-8"), 200, headers
else: 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.stdout.decode("utf-8"), "stdout")
flash(process.stderr.decode("utf-8"), "stderr") flash(process.stderr.decode("utf-8"), "stderr")
return redirect(url_for("index")) return redirect(url_for("index"))
@ -69,31 +134,36 @@ def attach():
elif file_name.lower().endswith(".hda"): elif file_name.lower().endswith(".hda"):
image_type = "hd" image_type = "hd"
else: 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")) return redirect(url_for("index"))
process = attach_image(scsi_id, file_name, image_type) process = attach_image(scsi_id, file_name, image_type)
if process.returncode == 0: 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")) return redirect(url_for("index"))
else: else:
flash( flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error")
u"Failed to attach " + file_name + " to scsi id " + scsi_id + "!", "error"
)
flash(process.stdout.decode("utf-8"), "stdout") flash(process.stdout.decode("utf-8"), "stdout")
flash(process.stderr.decode("utf-8"), "stderr") flash(process.stderr.decode("utf-8"), "stderr")
return redirect(url_for("index")) 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"]) @app.route("/scsi/detach", methods=["POST"])
def detach(): def detach():
scsi_id = request.form.get("scsi_id") scsi_id = request.form.get("scsi_id")
process = detach_by_id(scsi_id) process = detach_by_id(scsi_id)
if process.returncode == 0: if process.returncode == 0:
flash("Detached scsi id " + scsi_id + "!") flash("Detached SCSI id " + scsi_id + "!")
return redirect(url_for("index")) return redirect(url_for("index"))
else: 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.stdout, "stdout")
flash(process.stderr, "stderr") flash(process.stderr, "stderr")
return redirect(url_for("index")) return redirect(url_for("index"))
@ -107,7 +177,7 @@ def eject():
flash("Ejected scsi id " + scsi_id + "!") flash("Ejected scsi id " + scsi_id + "!")
return redirect(url_for("index")) return redirect(url_for("index"))
else: 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.stdout, "stdout")
flash(process.stderr, "stderr") flash(process.stderr, "stderr")
return redirect(url_for("index")) return redirect(url_for("index"))
@ -143,7 +213,7 @@ def download_file():
flash("File Downloaded") flash("File Downloaded")
return redirect(url_for("index")) return redirect(url_for("index"))
else: else:
flash(u"Failed to download file", "error") flash("Failed to download file", "error")
flash(process.stdout, "stdout") flash(process.stdout, "stdout")
flash(process.stderr, "stderr") flash(process.stderr, "stderr")
return redirect(url_for("index")) return redirect(url_for("index"))
@ -181,7 +251,7 @@ def create_file():
flash("Drive created") flash("Drive created")
return redirect(url_for("index")) return redirect(url_for("index"))
else: else:
flash(u"Failed to create file", "error") flash("Failed to create file", "error")
flash(process.stdout, "stdout") flash(process.stdout, "stdout")
flash(process.stderr, "stderr") flash(process.stderr, "stderr")
return redirect(url_for("index")) return redirect(url_for("index"))
@ -200,7 +270,7 @@ def delete():
flash("File " + image + " deleted") flash("File " + image + " deleted")
return redirect(url_for("index")) return redirect(url_for("index"))
else: else:
flash(u"Failed to Delete " + image, "error") flash("Failed to Delete " + image, "error")
return redirect(url_for("index")) return redirect(url_for("index"))
@ -212,7 +282,7 @@ def unzip():
flash("Unzipped file " + image) flash("Unzipped file " + image)
return redirect(url_for("index")) return redirect(url_for("index"))
else: else:
flash(u"Failed to unzip " + image, "error") flash("Failed to unzip " + image, "error")
return redirect(url_for("index")) return redirect(url_for("index"))