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:
+17
-6
@@ -103,20 +103,31 @@ def health():
|
|||||||
STATIC_DIR = Path("/app/static")
|
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
|
# Serve static assets - mount subdirectories if they exist
|
||||||
if STATIC_DIR.exists():
|
if STATIC_DIR.exists():
|
||||||
# React-style assets folder
|
# React-style assets folder
|
||||||
if (STATIC_DIR / "assets").exists():
|
if (STATIC_DIR / "assets").exists():
|
||||||
app.mount("/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="assets")
|
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():
|
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
|
# miniPaint images folder
|
||||||
if (STATIC_DIR / "images").exists():
|
if (STATIC_DIR / "images").exists():
|
||||||
app.mount("/images", StaticFiles(directory=STATIC_DIR / "images"), name="images")
|
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():
|
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)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
@@ -124,7 +135,7 @@ async def serve_spa():
|
|||||||
"""Serve miniPaint index.html"""
|
"""Serve miniPaint index.html"""
|
||||||
index_path = STATIC_DIR / "index.html"
|
index_path = STATIC_DIR / "index.html"
|
||||||
if index_path.exists():
|
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>")
|
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
|
# Otherwise serve index.html
|
||||||
index_path = STATIC_DIR / "index.html"
|
index_path = STATIC_DIR / "index.html"
|
||||||
if index_path.exists():
|
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)
|
return HTMLResponse("<h1>Frontend not built</h1>", status_code=404)
|
||||||
|
|||||||
@@ -369,7 +369,10 @@ async def _remove_background_ben2(img: Image.Image) -> bytes:
|
|||||||
_ben2_model.to(device).eval()
|
_ben2_model.to(device).eval()
|
||||||
print("BEN2_Base model loaded")
|
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()
|
buffer = BytesIO()
|
||||||
result.save(buffer, format='PNG')
|
result.save(buffer, format='PNG')
|
||||||
|
|||||||
@@ -19,6 +19,15 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
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
|
if [ $# -eq 0 ]; then
|
||||||
exec docker compose -f docker-compose.gpu.yml up -d --build
|
exec docker compose -f docker-compose.gpu.yml up -d --build
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -80,6 +80,18 @@ fi
|
|||||||
|
|
||||||
echo ""
|
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 ──────────────────────────────────────────────
|
# ── 1. NVIDIA container toolkit ──────────────────────────────────────────────
|
||||||
if command -v nvidia-ctk &>/dev/null; then
|
if command -v nvidia-ctk &>/dev/null; then
|
||||||
echo "✓ nvidia-container-toolkit already installed — skipping"
|
echo "✓ nvidia-container-toolkit already installed — skipping"
|
||||||
|
|||||||
+21
-6
@@ -28,16 +28,31 @@ fi
|
|||||||
|
|
||||||
# mkdir -p succeeds silently on an already-existing directory even when we
|
# 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.
|
# 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
|
for d in data/models data/hf_cache; do
|
||||||
mkdir -p "$d" 2>/dev/null
|
check_writable "$d" || NEED_CHOWN=1
|
||||||
if ! { touch "$d/.write_test" 2>/dev/null && rm -f "$d/.write_test"; }; then
|
done
|
||||||
echo "✗ No write permission in ./$d" >&2
|
|
||||||
echo " This usually means Docker created ./data as root on a previous run." >&2
|
if [ "$NEED_CHOWN" -eq 1 ]; then
|
||||||
echo " Fix permanently (the container runs as root and will still work fine):" >&2
|
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
|
echo " sudo chown -R \$(id -u):\$(id -g) ./data" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
PREFETCH_SDXL=0
|
||||||
if [ "${1:-}" = "--sdxl" ]; then
|
if [ "${1:-}" = "--sdxl" ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user