basics and hardware eval
This commit is contained in:
parent
7f0b099ef3
commit
bf26fc881a
13 changed files with 330 additions and 0 deletions
55
archinstall/user_configuration.json
Normal file
55
archinstall/user_configuration.json
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"archinstall-language": "English",
|
||||
"timezone": "Europe/Berlin",
|
||||
"ntp": true,
|
||||
|
||||
"bootloader": "Systemd-boot",
|
||||
|
||||
"disk_config": {
|
||||
"config_type": "use_entire_disk",
|
||||
"device": "/dev/sda",
|
||||
"filesystem": "ext4"
|
||||
},
|
||||
|
||||
"hostname": "arch-server",
|
||||
|
||||
"kernels": ["linux"],
|
||||
|
||||
"packages": [
|
||||
"docker",
|
||||
"docker-compose",
|
||||
"vim",
|
||||
"git",
|
||||
"htop",
|
||||
"curl"
|
||||
],
|
||||
|
||||
"profile": {
|
||||
"type": "server"
|
||||
},
|
||||
|
||||
"services": [
|
||||
"docker"
|
||||
],
|
||||
|
||||
"network_config": {
|
||||
"type": "iso"
|
||||
},
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "server",
|
||||
"sudo": true,
|
||||
"groups": ["docker"]
|
||||
}
|
||||
],
|
||||
|
||||
"ssh": true,
|
||||
|
||||
"audio_config": null,
|
||||
|
||||
"locale_config": {
|
||||
"locale": "en_US.UTF-8",
|
||||
"keyboard_layout": "us"
|
||||
}
|
||||
}
|
||||
9
archinstall/user_credentials.json
Normal file
9
archinstall/user_credentials.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"root_password": "CHANGE_ME",
|
||||
"users": [
|
||||
{
|
||||
"username": "server",
|
||||
"password": "CHANGE_ME"
|
||||
}
|
||||
]
|
||||
}
|
||||
2
driveval/dependancies.txt
Normal file
2
driveval/dependancies.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pip install psutil
|
||||
sudo apt-get install smartmontools
|
||||
146
driveval/main.py
Normal file
146
driveval/main.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
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()
|
||||
1
driveval/requirements.txt
Normal file
1
driveval/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
psutil
|
||||
2
webinstaller/.gitignore
vendored
Normal file
2
webinstaller/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*.venv/
|
||||
*__pycache__*
|
||||
50
webinstaller/app.py
Normal file
50
webinstaller/app.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from flask import Flask, render_template, request, redirect, url_for
|
||||
from hardware import get_hardware_info
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
settings = {
|
||||
# Step 1
|
||||
"hostname": "furtka",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"password2": "",
|
||||
"backend": False,
|
||||
"backend_adress": "127.0.0.1",
|
||||
"language"
|
||||
# devices
|
||||
"boot_drive_uuid": "1"
|
||||
}
|
||||
|
||||
@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")
|
||||
|
||||
|
||||
@app.route("/install/step2", methods=["GET", "POST"])
|
||||
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"))
|
||||
|
||||
|
||||
@app.route("/install/overview")
|
||||
def install_overview():
|
||||
|
||||
return render_template("install/overview.html", settings=settings)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True, port=5000)
|
||||
23
webinstaller/hardware.py
Normal file
23
webinstaller/hardware.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
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")
|
||||
0
webinstaller/static/style.css
Normal file
0
webinstaller/static/style.css
Normal file
0
webinstaller/templates/base.html
Normal file
0
webinstaller/templates/base.html
Normal file
13
webinstaller/templates/install/overview.html
Normal file
13
webinstaller/templates/install/overview.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Furtka Install</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Overview</h1>
|
||||
<h2>Results:</h2>
|
||||
{% for k, s in settings.items() %}
|
||||
<p>{{k}}: {{s}}</p>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
13
webinstaller/templates/install/step1.html
Normal file
13
webinstaller/templates/install/step1.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Furtka Install</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Step 1</h1>
|
||||
<form method="post" action="{{ url_for('install_step_1') }}">
|
||||
<p>Hostname: <input type="text" name="hostname" required /></p>
|
||||
<input type="submit" value="Next" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
16
webinstaller/templates/install/step2.html
Normal file
16
webinstaller/templates/install/step2.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Furtka Install</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Step 2 - Choose Boot Drive</h1>
|
||||
{% for h in storage %}
|
||||
<p>{{ h }}</p>
|
||||
{% endfor %}
|
||||
<form method="post", action="{{ url_for('install_step_2') }}">
|
||||
<p>Boot Drive: <input type="text" name="boot_drive_uuid" required /></p>
|
||||
<input type="submit" value="Go to Overview" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue