diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76ac2f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.venv/ +__pycache__/ +*.pyc + +# Real credentials must never be committed — use the .example files +archinstall/user_credentials.json diff --git a/archinstall/user_credentials.json b/archinstall/user_credentials.example.json similarity index 100% rename from archinstall/user_credentials.json rename to archinstall/user_credentials.example.json diff --git a/driveval/dependancies.txt b/driveval/dependancies.txt deleted file mode 100644 index c7b8ca6..0000000 --- a/driveval/dependancies.txt +++ /dev/null @@ -1,2 +0,0 @@ -pip install psutil -sudo apt-get install smartmontools \ No newline at end of file diff --git a/driveval/main.py b/driveval/main.py deleted file mode 100644 index 382fd95..0000000 --- a/driveval/main.py +++ /dev/null @@ -1,146 +0,0 @@ -import psutil -import subprocess - -def get_drive_health(device): - """ - Get the health of a storage device using smartctl. - Returns a score based on the drive's SMART health status. - """ - try: - result = subprocess.run(['smartctl', '-H', device], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output = result.stdout.decode() - if "PASSED" in output: - return 10 # Healthy drive - elif "FAILED" in output: - return 0 # Failed drive - else: - return 5 # Unknown or problematic drive - except Exception as e: - print(f"Error checking SMART status for {device}: {e}") - return 5 # Default score for uncheckable devices - -def get_drive_type(device): - """ - Determine if a device is an SSD or HDD based on its device type. - """ - if 'NVME' in device: - return 15 # SSDs are optimal - elif 'SSD' in device: - return 10 # SSDs are optimal - else: - return 5 # HDDs are less optimal for boot drives - -def get_drive_size(device): - """ - Get size of a block device using lsblk (works for disks). - Always returns an integer score. - """ - try: - result = subprocess.run( - ["lsblk", "-dn", "-o", "SIZE", device], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - check=True - ) - - size_str = result.stdout.strip().upper() - - if not size_str: - return 5 # fallback - - size_str = size_str.replace(",", ".") - - # Convert to GB - if size_str.endswith("T"): - size_gb = float(size_str[:-1]) * 1024 - elif size_str.endswith("G"): - size_gb = float(size_str[:-1]) - elif size_str.endswith("M"): - size_gb = float(size_str[:-1]) / 1024 - else: - return 5 # unknown format - - if size_gb < 128: - return 5 - elif size_gb < 512: - return 7 - else: - return 10 - - except Exception as e: - print(f"Error getting size for {device}: {e}") - return 5 # ALWAYS return something - -def get_device_score(device): - """ - Calculate a suitability score for each drive based on: - - Type (SSD/HDD) - - Health (SMART status) - - Size (GB) - """ - score = 0 - - # Type - score += get_drive_type(device) - - # Health - score += get_drive_health(device) - - # Size - score += get_drive_size(device) - - return score - - -def list_storage_devices(): - """ - List all physical storage devices (e.g. /dev/sda, /dev/nvme0n1) - and return them with their computed scores. - - Compatible with the existing scoring pipeline. - """ - devices = [] - - try: - result = subprocess.run( - ["lsblk", "-dn", "-o", "NAME"], # -d = disks only, no partitions - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - check=True - ) - - for line in result.stdout.strip().split("\n"): - if not line: - continue - - device = f"/dev/{line.strip()}" - score = get_device_score(device) # <-- reuses your existing logic - devices.append((device, score)) - - except subprocess.CalledProcessError as e: - print(f"Error listing devices: {e}") - - return devices - -def main(): - devices = list_storage_devices() - print(devices) - - if not devices: - print("No storage devices found.") - return - - print(f"\n{'Device':<20} {'Score'}") - print("-" * 30) - - for device, score in devices: - print(f"{device:<20} {score}") - - # Find the highest scoring drive - best_device = max(devices, key=lambda x: x[1]) - print(f"\nBest drive for boot: {best_device[0]} with score: {best_device[1]}") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/driveval/requirements.txt b/driveval/requirements.txt deleted file mode 100644 index 0b574b5..0000000 --- a/driveval/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -psutil \ No newline at end of file diff --git a/webinstaller/.gitignore b/webinstaller/.gitignore deleted file mode 100644 index 4362d8b..0000000 --- a/webinstaller/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.venv/ -*__pycache__* diff --git a/webinstaller/app.py b/webinstaller/app.py index 5cd5cc9..8cc5d9e 100644 --- a/webinstaller/app.py +++ b/webinstaller/app.py @@ -1,5 +1,5 @@ from flask import Flask, render_template, request, redirect, url_for -from hardware import get_hardware_info +from drives import list_scored_devices app = Flask(__name__) @@ -11,22 +11,22 @@ settings = { "password2": "", "backend": False, "backend_adress": "127.0.0.1", - "language" + "language": "en", # devices - "boot_drive_uuid": "1" + "boot_drive_uuid": "", } + @app.route("/") def home(): return "Hello World" + @app.route("/install/step1", methods=["GET", "POST"]) def install_step_1(): if request.method == "POST": settings["hostname"] = request.form["hostname"] - return redirect(url_for("install_step_2")) - return render_template("install/step1.html") @@ -34,15 +34,12 @@ def install_step_1(): def install_step_2(): if request.method == "POST": settings["boot_drive_uuid"] = request.form["boot_drive_uuid"] - return redirect(url_for("install_overview")) - - return render_template("install/step2.html", storage=get_hardware_info("storage")) + return render_template("install/step2.html", drives=list_scored_devices()) @app.route("/install/overview") def install_overview(): - return render_template("install/overview.html", settings=settings) diff --git a/webinstaller/drives.py b/webinstaller/drives.py new file mode 100644 index 0000000..c3e0556 --- /dev/null +++ b/webinstaller/drives.py @@ -0,0 +1,104 @@ +import subprocess + + +def get_drive_health(device): + try: + result = subprocess.run( + ["smartctl", "-H", device], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + output = result.stdout.decode() + if "PASSED" in output: + return 10 + elif "FAILED" in output: + return 0 + return 5 + except Exception as e: + print(f"Error checking SMART status for {device}: {e}") + return 5 + + +def get_drive_type_score(device): + name = device.lower() + if "nvme" in name: + return 15 + if "ssd" in name: + return 10 + return 5 + + +def parse_size_gb(size_str): + size_str = size_str.strip().upper().replace(",", ".") + if not size_str: + return None + if size_str.endswith("T"): + return float(size_str[:-1]) * 1024 + if size_str.endswith("G"): + return float(size_str[:-1]) + if size_str.endswith("M"): + return float(size_str[:-1]) / 1024 + return None + + +def get_size_score(size_gb): + if size_gb is None: + return 5 + if size_gb < 128: + return 5 + if size_gb < 512: + return 7 + return 10 + + +def score_device(device, size_gb): + return get_drive_type_score(device) + get_drive_health(device) + get_size_score(size_gb) + + +def list_scored_devices(): + """Return [{name, size, score}, ...] for all physical disks, highest score first.""" + devices = [] + try: + result = subprocess.run( + ["lsblk", "-dn", "-o", "NAME,SIZE"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + ) + for line in result.stdout.strip().split("\n"): + if not line: + continue + parts = line.split() + if len(parts) < 2: + continue + name, size = parts[0], parts[1] + device = f"/dev/{name}" + devices.append( + { + "name": device, + "size": size, + "score": score_device(device, parse_size_gb(size)), + } + ) + except subprocess.CalledProcessError as e: + print(f"Error listing devices: {e}") + + devices.sort(key=lambda d: d["score"], reverse=True) + return devices + + +def main(): + devices = list_scored_devices() + if not devices: + print("No storage devices found.") + return + print(f"\n{'Device':<20} {'Size':<10} {'Score'}") + print("-" * 40) + for d in devices: + print(f"{d['name']:<20} {d['size']:<10} {d['score']}") + print(f"\nBest drive for boot: {devices[0]['name']} (score {devices[0]['score']})") + + +if __name__ == "__main__": + main() diff --git a/webinstaller/hardware.py b/webinstaller/hardware.py deleted file mode 100644 index cebb3dd..0000000 --- a/webinstaller/hardware.py +++ /dev/null @@ -1,23 +0,0 @@ -from os import popen -import json - -class HardwareDevice: - def __init__(self, hw_path, device, device_class, description): - self.hw_path = hw_path - self.device = device - self.device_class = device_class - self.description = description - - def __str__(self): - return f"{self.description}@{self.device}" - -def get_hardware_info(hw_type: str): - hardware_read_process = popen(f"lshw -json -c {hw_type}") - hardware = json.loads(hardware_read_process.read()) - hardware_read_process.close() - for hw in hardware: - print(hw["description"]) - return hardware - - -get_hardware_info(hw_type="storage") \ No newline at end of file diff --git a/webinstaller/requirements.txt b/webinstaller/requirements.txt new file mode 100644 index 0000000..7e10602 --- /dev/null +++ b/webinstaller/requirements.txt @@ -0,0 +1 @@ +flask diff --git a/webinstaller/templates/install/step2.html b/webinstaller/templates/install/step2.html index 383ff6e..b81d852 100644 --- a/webinstaller/templates/install/step2.html +++ b/webinstaller/templates/install/step2.html @@ -5,11 +5,14 @@
{{ h }}
- {% endfor %} -