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
/in/
/out/
# Transient session data — not code
/tmp/
/logs/
# Python

View file

@ -15,8 +15,9 @@ from pathlib import Path
from dataclasses import dataclass
from typing import Optional
STAGING_IN = Path("/mnt/converter/in")
STAGING_OUT = Path("/mnt/converter/out")
STAGING_ROOT = Path("/mnt/converter/tmp")
STAGING_IN = STAGING_ROOT / "in"
STAGING_OUT = STAGING_ROOT / "out"
logger = logging.getLogger("backend.converter")
@ -44,7 +45,10 @@ class DiskInfo:
def extract_if_needed(filename: str) -> 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
if not filepath.exists():
raise FileNotFoundError(f"Source not found: {filepath}")

View file

@ -61,6 +61,7 @@ for d in \
"$CONVERTER_ROOT/in" \
"$CONVERTER_ROOT/out" \
"$CONVERTER_ROOT/logs" \
"$CONVERTER_ROOT/tmp" \
"$CONVERTER_ROOT/backend" \
"$CONVERTER_ROOT/frontend" \
"$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")
STAGING_IN = Path("/mnt/converter/in")
STAGING_OUT = Path("/mnt/converter/out")
STAGING_ROOT = Path("/mnt/converter/tmp")
STAGING_IN = STAGING_ROOT / "in"
STAGING_OUT = STAGING_ROOT / "out"
def _staging_in(session_id: str = "") -> Path:
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")
_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:
"""Session-aware staging directory."""
return STAGING / session_id if session_id else STAGING
"""Session-aware staging directory. Returns /mnt/converter/tmp/{guid}/ or /mnt/converter/tmp/"""
return STAGING_ROOT / session_id if session_id else STAGING_ROOT
def _get_session_id(request: Request) -> str:
"""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
show progress, then call /session/analyze separately."""
staging = _staging(session_id)
staging.mkdir(parents=True, exist_ok=True)
sdir = _staging(session_id) / "in"
sdir.mkdir(parents=True, exist_ok=True)
err = _validate_vmid(vmid)
if err:
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)
filename = source_file.filename
dest = staging / filename
dest = sdir / filename
content_length = request.headers.get("content-length")
if content_length:
@ -217,11 +217,11 @@ def session_upload(
return JSONResponse({"phase": "error", "error": "No URL provided."}, status_code=400)
filename = Path(url).name or f"download_{uuid.uuid4().hex[:8]}"
dest = staging / filename
dest = sdir / filename
_active_downloads.pop(filename, None)
usage = shutil.disk_usage(staging)
usage = shutil.disk_usage(_staging(session_id))
free_gb = usage.free / (1024**3)
if free_gb < 50:
logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
@ -279,9 +279,9 @@ async def upload_raw(request: Request):
if err:
return JSONResponse({"phase": "error", "error": err}, status_code=400)
staging = _staging(session_id)
staging.mkdir(parents=True, exist_ok=True)
dest = staging / filename
sdir = _staging(session_id) / "in"
sdir.mkdir(parents=True, exist_ok=True)
dest = sdir / filename
content_length = request.headers.get("content-length")
if content_length:
estimated_gb = int(content_length) / (1024**3)
@ -322,7 +322,7 @@ async def session_progress(filename: str):
if not info:
# Check if file exists on disk (download already completed in a
# previous session, or it was an upload)
dest = STAGING / filename
dest = _staging("") / "in" / filename
if dest.exists():
return JSONResponse({
"phase": "complete",
@ -572,9 +572,9 @@ def scp_start(
if not filename:
return JSONResponse({"phase": "error", "error": "Invalid remote path."}, status_code=400)
session_dir = STAGING / session_id if session_id else STAGING
session_dir.mkdir(parents=True, exist_ok=True)
dest = session_dir / filename
sdir = _staging(session_id) / "in"
sdir.mkdir(parents=True, exist_ok=True)
dest = sdir / filename
# Check disk space
usage = shutil.disk_usage(STAGING)
@ -620,7 +620,7 @@ def scp_progress(session_id: str, filename: str):
key = f"{session_id}/{filename}"
info = _active_scp.get(key)
if not info:
dest = STAGING / session_id / filename
dest = _staging(session_id) / "in" / filename
if dest.exists():
return JSONResponse({
"phase": "complete",