Etappe 1: Container-Infrastruktur + udev-Erkennung

- Docker Compose mit 5 Containern (api, worker, ui, postgres, redis)
- Basis-Dockerfiles für FastAPI, Celery, React/Vite, PostgreSQL, Redis
- udev-Regel + Python-Daemon für Disc-Einwurf-Erkennung
- Device-Resolver (UUID/Serial → /dev/disc/<uuid>)
- Celery-Worker mit Dummy-Job-Task (Disc-Erkennung)
- .env.example mit Umgebungsvariablen
- .gitignore für Docker, .env, node_modules
- README, SAVEPOINT, ROADMAP aktualisiert
This commit is contained in:
Hitonabi
2026-07-21 14:59:09 +02:00
parent cbe7d2b9c3
commit 5d23b1e6c8
22 changed files with 607 additions and 34 deletions
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""
udev-Daemon für Rippy
Erkennt Disc-Einwurf/-auswurf und erstellt Celery-Jobs.
"""
import os
import sys
import subprocess
import redis
import json
from pathlib import Path
from time import sleep
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
DISC_DEVICE_PATH = "/dev/disc"
# Redis verbinden
def get_redis_client():
try:
return redis.from_url(REDIS_URL)
except Exception as e:
print(f"Fehler beim Verbinden mit Redis: {e}")
sys.exit(1)
def get_device_info(device_name: str) -> dict:
"""Ermittle Device-Info via udevadm."""
try:
result = subprocess.run(
["udevadm", "info", "-q", "property", "-n", f"/dev/{device_name}"],
capture_output=True,
text=True,
timeout=5
)
info = {}
for line in result.stdout.strip().split('\n'):
if '=' in line:
key, value = line.split('=', 1)
info[key] = value
return info
except Exception as e:
print(f"Fehler beim Auslesen von Device {device_name}: {e}")
return {}
def create_device_symlink(device_name: str, info: dict) -> str:
"""Erstelle Symlink mit UUID/Serial-Nummer."""
serial = info.get("ID_SERIAL", "unknown")
model = info.get("ID_MODEL", "unknown")
symlink_name = f"{model}_{serial}".replace(" ", "_")
symlink_path = f"{DISC_DEVICE_PATH}/{symlink_name}"
os.makedirs(DISC_DEVICE_PATH, exist_ok=True)
# Alten Symlink entfernen falls vorhanden
if os.path.exists(symlink_path):
os.unlink(symlink_path)
# Neuen Symlink erstellen
os.symlink(f"/dev/{device_name}", symlink_path)
return symlink_path
def delete_device_symlink(device_name: str):
"""Lösche Symlink beim Auswurf."""
symlink_path = f"{DISC_DEVICE_PATH}/{device_name}"
if os.path.exists(symlink_path):
os.unlink(symlink_path)
def detect_disc_type(device_path: str) -> str:
"""Erkenne Disc-Typ (CD/DVD/Blu-ray)."""
try:
result = subprocess.run(
["isoinfo", "-d", "-i", device_path],
capture_output=True,
text=True,
timeout=10
)
output = result.stdout.lower()
if "rock ridge" in output or "joliet" in output:
if "blu-ray" in output:
return "bluray"
return "dvd"
elif "cda" in output:
return "cd"
return "unknown"
except Exception:
return "unknown"
def create_job(device_path: str, disc_type: str):
"""Erstelle Celery-Job für Disc-Erkennung."""
try:
client = get_redis_client()
job_data = {
"device_path": device_path,
"disc_type": disc_type,
"timestamp": str(int(subprocess.check_output(["date", "+%s"]).decode().strip())),
"status": "pending"
}
client.lpush("rippy:jobs", json.dumps(job_data))
print(f"Job erstellt: {disc_type} -> {device_path}")
except Exception as e:
print(f"Fehler beim Erstellen des Jobs: {e}")
def handle_add(device_name: str):
"""Handle Disc-Einwurf."""
print(f"Disc-Einwurf erkannt: {device_name}")
info = get_device_info(device_name)
if not info:
print("Konnte Device-Info nicht auslesen")
return
symlink_path = create_device_symlink(device_name, info)
print(f"Symlink erstellt: {symlink_path}")
disc_type = detect_disc_type(symlink_path)
print(f"Disc-Typ erkannt: {disc_type}")
create_job(symlink_path, disc_type)
def handle_remove(device_name: str):
"""Handle Disc-Auswurf."""
print(f"Disc-Auswurf erkannt: {device_name}")
delete_device_symlink(device_name)
print(f"Symlink gelöscht: /dev/disc/{device_name}")
def main():
"""Hauptfunktion."""
if len(sys.argv) < 3:
print("Usage: udev_daemon.py <action> <device>")
sys.exit(1)
action = sys.argv[1]
device = sys.argv[2]
if action == "add":
handle_add(device)
elif action == "remove":
handle_remove(device)
else:
print(f"Unbekannte Aktion: {action}")
if __name__ == "__main__":
main()