diff --git a/backend/app/main.py b/backend/app/main.py index 93c09fb..542e3ff 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -103,20 +103,31 @@ def health(): STATIC_DIR = Path("/app/static") +class NoCacheStaticFiles(StaticFiles): + """webpack outputs a fixed 'bundle.js' filename (no content hash), so + browsers can keep serving a stale cached copy after a rebuild unless + forced to revalidate on every request.""" + + def file_response(self, *args, **kwargs): + response = super().file_response(*args, **kwargs) + response.headers["Cache-Control"] = "no-cache" + return response + + # Serve static assets - mount subdirectories if they exist if STATIC_DIR.exists(): # React-style assets folder if (STATIC_DIR / "assets").exists(): app.mount("/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="assets") - # miniPaint dist folder (webpack bundle) + # miniPaint dist folder (webpack bundle) - no-cache so code updates are picked up immediately if (STATIC_DIR / "dist").exists(): - app.mount("/dist", StaticFiles(directory=STATIC_DIR / "dist"), name="dist") + app.mount("/dist", NoCacheStaticFiles(directory=STATIC_DIR / "dist"), name="dist") # miniPaint images folder if (STATIC_DIR / "images").exists(): app.mount("/images", StaticFiles(directory=STATIC_DIR / "images"), name="images") - # miniPaint CSS folder + # miniPaint CSS folder - no-cache, same reasoning as /dist if (STATIC_DIR / "src").exists(): - app.mount("/src", StaticFiles(directory=STATIC_DIR / "src"), name="src") + app.mount("/src", NoCacheStaticFiles(directory=STATIC_DIR / "src"), name="src") @app.get("/", response_class=HTMLResponse) @@ -124,7 +135,7 @@ async def serve_spa(): """Serve miniPaint index.html""" index_path = STATIC_DIR / "index.html" if index_path.exists(): - return FileResponse(index_path) + return FileResponse(index_path, headers={"Cache-Control": "no-cache"}) return HTMLResponse("

Frontend not built. Run npm build in frontend/

") @@ -146,6 +157,6 @@ async def serve_spa_routes(request: Request, full_path: str): # Otherwise serve index.html index_path = STATIC_DIR / "index.html" if index_path.exists(): - return FileResponse(index_path) + return FileResponse(index_path, headers={"Cache-Control": "no-cache"}) return HTMLResponse("

Frontend not built

", status_code=404) diff --git a/backend/app/routers/tools.py b/backend/app/routers/tools.py index 4a0cbce..244b1d0 100644 --- a/backend/app/routers/tools.py +++ b/backend/app/routers/tools.py @@ -369,7 +369,10 @@ async def _remove_background_ben2(img: Image.Image) -> bytes: _ben2_model.to(device).eval() print("BEN2_Base model loaded") - result = _ben2_model.inference(img.convert('RGB'), refine_foreground=False) + # refine_foreground=True runs BEN2's extra foreground-color refinement pass + # (slower, but recovers fine/semi-transparent edge detail instead of a hard + # cutout — matters for things like lace, light rays, or fine text borders). + result = _ben2_model.inference(img.convert('RGB'), refine_foreground=True) buffer = BytesIO() result.save(buffer, format='PNG') diff --git a/bring-up-local-gpu.sh b/bring-up-local-gpu.sh index cbbdf2e..1b08344 100755 --- a/bring-up-local-gpu.sh +++ b/bring-up-local-gpu.sh @@ -19,6 +19,15 @@ set -euo pipefail +cd "$(dirname "$0")" + +# Pre-create ./data as the current (non-root) user. Otherwise, on a fresh +# checkout, Docker's daemon (root) auto-creates these bind-mount sources on +# the first 'up' — leaving them root-owned and blocking this same user from +# later writing to them without sudo (e.g. ./prefetch-models.sh). No-op if +# they already exist, regardless of current ownership. +mkdir -p data/models data/hf_cache data/projects data/patches + if [ $# -eq 0 ]; then exec docker compose -f docker-compose.gpu.yml up -d --build else diff --git a/install-local-gpu.sh b/install-local-gpu.sh index 78b6c9b..938509d 100755 --- a/install-local-gpu.sh +++ b/install-local-gpu.sh @@ -80,6 +80,18 @@ fi echo "" +# ── 0.5. Pre-create ./data with correct ownership ──────────────────────────── +# Docker's daemon (always root) auto-creates bind-mount source directories on +# the first 'compose up' if they don't exist yet — leaving ./data root-owned +# and blocking the invoking user from later running ./prefetch-models.sh +# without a manual 'sudo chown'. Create it now, owned by the real (non-root) +# user, so that problem has no chance to happen on a fresh checkout. +REPO_DIR="$(cd "$(dirname "$0")" && pwd)" +mkdir -p "$REPO_DIR"/data/{models,hf_cache,projects,patches} +chown -R "${SUDO_UID:-$(id -u)}:${SUDO_GID:-$(id -g)}" "$REPO_DIR/data" +echo "✓ ./data prepared (writable without sudo)" +echo "" + # ── 1. NVIDIA container toolkit ────────────────────────────────────────────── if command -v nvidia-ctk &>/dev/null; then echo "✓ nvidia-container-toolkit already installed — skipping" diff --git a/prefetch-models.sh b/prefetch-models.sh index dc02fd3..5aa7ccc 100755 --- a/prefetch-models.sh +++ b/prefetch-models.sh @@ -28,16 +28,31 @@ fi # mkdir -p succeeds silently on an already-existing directory even when we # can't write into it, so actually test writability rather than trusting that. +check_writable() { + mkdir -p "$1" 2>/dev/null + touch "$1/.write_test" 2>/dev/null && rm -f "$1/.write_test" +} + +NEED_CHOWN=0 for d in data/models data/hf_cache; do - mkdir -p "$d" 2>/dev/null - if ! { touch "$d/.write_test" 2>/dev/null && rm -f "$d/.write_test"; }; then - echo "✗ No write permission in ./$d" >&2 - echo " This usually means Docker created ./data as root on a previous run." >&2 - echo " Fix permanently (the container runs as root and will still work fine):" >&2 + check_writable "$d" || NEED_CHOWN=1 +done + +if [ "$NEED_CHOWN" -eq 1 ]; then + echo "⚠ ./data isn't writable by $(id -un) — this usually means Docker created it as root on a previous run." + echo " Fixing ownership (the container runs as root and will still work fine afterward):" + echo " sudo chown -R $(id -u):$(id -g) ./data" + if ! sudo chown -R "$(id -u):$(id -g)" ./data; then + echo "✗ Could not fix ownership automatically (sudo failed or unavailable)." >&2 + echo " Run this manually, then re-run this script:" >&2 echo " sudo chown -R \$(id -u):\$(id -g) ./data" >&2 exit 1 fi -done + for d in data/models data/hf_cache; do + check_writable "$d" || { echo "✗ Still no write permission in ./$d after chown." >&2; exit 1; } + done + echo "✓ Fixed." +fi PREFETCH_SDXL=0 if [ "${1:-}" = "--sdxl" ]; then