refactor: move staging to /mnt/converter/tmp/{guid}/in + out

This commit is contained in:
Claus Lohmar 2026-07-23 10:07:57 +00:00
parent fc380f7d08
commit a28e2af674
5 changed files with 30 additions and 25 deletions

5
.gitignore vendored
View file

@ -1,6 +1,5 @@
# Staging data — not code # Transient session data — not code
/in/ /tmp/
/out/
/logs/ /logs/
# Python # Python

View file

@ -15,8 +15,9 @@ from pathlib import Path
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional from typing import Optional
STAGING_IN = Path("/mnt/converter/in") STAGING_ROOT = Path("/mnt/converter/tmp")
STAGING_OUT = Path("/mnt/converter/out") STAGING_IN = STAGING_ROOT / "in"
STAGING_OUT = STAGING_ROOT / "out"
logger = logging.getLogger("backend.converter") logger = logging.getLogger("backend.converter")
@ -44,7 +45,10 @@ class DiskInfo:
def extract_if_needed(filename: str) -> Path: def extract_if_needed(filename: str) -> Path:
"""Extract an archive into STAGING_IN if it's compressed. Returns the path """Extract an archive into STAGING_IN if it's compressed. Returns the path
to the extracted directory (or the original file if not compressed).""" to the extracted directory (or the original file if not compressed).
Accepts both absolute paths and paths relative to STAGING_IN."""
filepath = Path(filename)
if not filepath.is_absolute():
filepath = STAGING_IN / filename filepath = STAGING_IN / filename
if not filepath.exists(): if not filepath.exists():
raise FileNotFoundError(f"Source not found: {filepath}") raise FileNotFoundError(f"Source not found: {filepath}")

View file

@ -61,6 +61,7 @@ for d in \
"$CONVERTER_ROOT/in" \ "$CONVERTER_ROOT/in" \
"$CONVERTER_ROOT/out" \ "$CONVERTER_ROOT/out" \
"$CONVERTER_ROOT/logs" \ "$CONVERTER_ROOT/logs" \
"$CONVERTER_ROOT/tmp" \
"$CONVERTER_ROOT/backend" \ "$CONVERTER_ROOT/backend" \
"$CONVERTER_ROOT/frontend" \ "$CONVERTER_ROOT/frontend" \
"$CONVERTER_ROOT/frontend/templates" \ "$CONVERTER_ROOT/frontend/templates" \

View file

@ -30,8 +30,9 @@ from converter import extract_if_needed, discover_disk, detect_efi
logger = logging.getLogger("backend.provisioner") logger = logging.getLogger("backend.provisioner")
STAGING_IN = Path("/mnt/converter/in") STAGING_ROOT = Path("/mnt/converter/tmp")
STAGING_OUT = Path("/mnt/converter/out") STAGING_IN = STAGING_ROOT / "in"
STAGING_OUT = STAGING_ROOT / "out"
def _staging_in(session_id: str = "") -> Path: def _staging_in(session_id: str = "") -> Path:
return STAGING_IN / session_id if session_id else STAGING_IN return STAGING_IN / session_id if session_id else STAGING_IN

View file

@ -61,11 +61,11 @@ BASE = Path(__file__).parent
app.mount("/static", StaticFiles(directory=str(BASE / "static")), name="static") app.mount("/static", StaticFiles(directory=str(BASE / "static")), name="static")
_jinja = Environment(loader=FileSystemLoader(str(BASE / "templates")), autoescape=True) _jinja = Environment(loader=FileSystemLoader(str(BASE / "templates")), autoescape=True)
STAGING = Path("/mnt/converter/in") STAGING_ROOT = Path("/mnt/converter/tmp")
def _staging(session_id: str = "") -> Path: def _staging(session_id: str = "") -> Path:
"""Session-aware staging directory.""" """Session-aware staging directory. Returns /mnt/converter/tmp/{guid}/ or /mnt/converter/tmp/"""
return STAGING / session_id if session_id else STAGING return STAGING_ROOT / session_id if session_id else STAGING_ROOT
def _get_session_id(request: Request) -> str: def _get_session_id(request: Request) -> str:
"""Extract session ID from cookie or query param.""" """Extract session ID from cookie or query param."""
@ -156,8 +156,8 @@ def session_upload(
): ):
"""Phase 1 — acquire the source file. Returns JSON so the frontend can """Phase 1 — acquire the source file. Returns JSON so the frontend can
show progress, then call /session/analyze separately.""" show progress, then call /session/analyze separately."""
staging = _staging(session_id) sdir = _staging(session_id) / "in"
staging.mkdir(parents=True, exist_ok=True) sdir.mkdir(parents=True, exist_ok=True)
err = _validate_vmid(vmid) err = _validate_vmid(vmid)
if err: if err:
return JSONResponse({"phase": "error", "error": err}, status_code=400) return JSONResponse({"phase": "error", "error": err}, status_code=400)
@ -170,7 +170,7 @@ def session_upload(
return JSONResponse({"phase": "error", "error": "No file uploaded."}, status_code=400) return JSONResponse({"phase": "error", "error": "No file uploaded."}, status_code=400)
filename = source_file.filename filename = source_file.filename
dest = staging / filename dest = sdir / filename
content_length = request.headers.get("content-length") content_length = request.headers.get("content-length")
if content_length: if content_length:
@ -217,11 +217,11 @@ def session_upload(
return JSONResponse({"phase": "error", "error": "No URL provided."}, status_code=400) return JSONResponse({"phase": "error", "error": "No URL provided."}, status_code=400)
filename = Path(url).name or f"download_{uuid.uuid4().hex[:8]}" filename = Path(url).name or f"download_{uuid.uuid4().hex[:8]}"
dest = staging / filename dest = sdir / filename
_active_downloads.pop(filename, None) _active_downloads.pop(filename, None)
usage = shutil.disk_usage(staging) usage = shutil.disk_usage(_staging(session_id))
free_gb = usage.free / (1024**3) free_gb = usage.free / (1024**3)
if free_gb < 50: if free_gb < 50:
logger.warning("Low disk: %.1f GB free — download may fail", free_gb) logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
@ -279,9 +279,9 @@ async def upload_raw(request: Request):
if err: if err:
return JSONResponse({"phase": "error", "error": err}, status_code=400) return JSONResponse({"phase": "error", "error": err}, status_code=400)
staging = _staging(session_id) sdir = _staging(session_id) / "in"
staging.mkdir(parents=True, exist_ok=True) sdir.mkdir(parents=True, exist_ok=True)
dest = staging / filename dest = sdir / filename
content_length = request.headers.get("content-length") content_length = request.headers.get("content-length")
if content_length: if content_length:
estimated_gb = int(content_length) / (1024**3) estimated_gb = int(content_length) / (1024**3)
@ -322,7 +322,7 @@ async def session_progress(filename: str):
if not info: if not info:
# Check if file exists on disk (download already completed in a # Check if file exists on disk (download already completed in a
# previous session, or it was an upload) # previous session, or it was an upload)
dest = STAGING / filename dest = _staging("") / "in" / filename
if dest.exists(): if dest.exists():
return JSONResponse({ return JSONResponse({
"phase": "complete", "phase": "complete",
@ -572,9 +572,9 @@ def scp_start(
if not filename: if not filename:
return JSONResponse({"phase": "error", "error": "Invalid remote path."}, status_code=400) return JSONResponse({"phase": "error", "error": "Invalid remote path."}, status_code=400)
session_dir = STAGING / session_id if session_id else STAGING sdir = _staging(session_id) / "in"
session_dir.mkdir(parents=True, exist_ok=True) sdir.mkdir(parents=True, exist_ok=True)
dest = session_dir / filename dest = sdir / filename
# Check disk space # Check disk space
usage = shutil.disk_usage(STAGING) usage = shutil.disk_usage(STAGING)
@ -620,7 +620,7 @@ def scp_progress(session_id: str, filename: str):
key = f"{session_id}/{filename}" key = f"{session_id}/{filename}"
info = _active_scp.get(key) info = _active_scp.get(key)
if not info: if not info:
dest = STAGING / session_id / filename dest = _staging(session_id) / "in" / filename
if dest.exists(): if dest.exists():
return JSONResponse({ return JSONResponse({
"phase": "complete", "phase": "complete",