Fix stale UI cache, BEN2 edge quality, and fresh-install permissions

- Serve /dist and /src with Cache-Control: no-cache so browsers always
  revalidate the webpack bundle (it has a fixed filename with no content
  hash, so a stale browser cache was hiding the new Remove Background
  model dropdown after the rebuild).
- Enable BEN2's refine_foreground pass by default - it's the model's own
  documented option for preserving fine/semi-transparent edge detail
  (text borders, light rays) instead of a harder cutout, at a small
  speed cost.
- Make install-local-gpu.sh and bring-up-local-gpu.sh pre-create ./data
  as the invoking non-root user before Docker ever runs, so its daemon
  never gets a chance to auto-create those bind-mount dirs as root.
- Make prefetch-models.sh self-heal a root-owned ./data automatically
  (sudo chown) instead of erroring out with a manual fix-it command.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ro4PwQKvSc3CH19LSN21Ht
This commit is contained in:
Claude
2026-06-18 17:09:23 +00:00
parent eca834a7f0
commit b4a790473c
5 changed files with 63 additions and 13 deletions
+17 -6
View File
@@ -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("<h1>Frontend not built. Run npm build in frontend/</h1>")
@@ -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("<h1>Frontend not built</h1>", status_code=404)
+4 -1
View File
@@ -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')
+9
View File
@@ -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
+12
View File
@@ -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"
+21 -6
View File
@@ -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