diff --git a/app/routers/auth.py b/app/routers/auth.py index c94e96e..3dc57b8 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -55,7 +55,7 @@ async def auth_callback(request: Request): return RedirectResponse(url="/") -@router.get("/logout") +@router.post("/logout") async def logout(request: Request): request.session.clear() end_session_endpoint = None diff --git a/app/routers/jobs.py b/app/routers/jobs.py index 4486541..e022849 100644 --- a/app/routers/jobs.py +++ b/app/routers/jobs.py @@ -99,7 +99,14 @@ async def view_log(run_id: int, user: dict = Depends(require_auth), db=Depends(g raise HTTPException(404, "no such run/log") from pathlib import Path - path = Path(run.log_path) + from app.settings import settings + + # Containment check: only ever serve files from under the logs dir. Paths + # are app-generated so this is defense in depth against a malformed row. + logs_dir = settings.logs_dir.resolve() + path = Path(run.log_path).resolve() + if logs_dir != path and logs_dir not in path.parents: + raise HTTPException(404, "no such run/log") if not path.exists(): raise HTTPException(404, "log file no longer exists") return path.read_text(errors="replace") diff --git a/app/services/pipeline_runner.py b/app/services/pipeline_runner.py index a595f46..d2b108e 100644 --- a/app/services/pipeline_runner.py +++ b/app/services/pipeline_runner.py @@ -42,6 +42,7 @@ def _subprocess_env() -> dict: env["MUSIC_DATA_DIR"] = str(settings.music_data_dir) env["PIPELINE_DIR"] = str(settings.pipeline_dir) env["BEETSDIR"] = str(settings.beets_dir) + env["MIN_ARTIST_DIRS"] = str(settings.min_artist_dirs) return env diff --git a/app/settings.py b/app/settings.py index 956e9ca..e2cbf17 100644 --- a/app/settings.py +++ b/app/settings.py @@ -48,6 +48,12 @@ class Settings(BaseSettings): validation_alias=AliasChoices("TZ", "ALEMBIC_TIMEZONE", "TIMEZONE"), ) + # Share-health gate: the Navidrome scan and Bandcamp sync abort if the + # library has fewer than this many artist folders, to catch a half-mounted + # or empty share before it does damage. The default suits a large library; + # set it lower (via env MIN_ARTIST_DIRS) if yours is small. + min_artist_dirs: int = 500 + @property def beets_dir(self) -> Path: return self.alembic_config_dir / "beets" diff --git a/app/static/style.css b/app/static/style.css index a2cb705..c16bc83 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -219,11 +219,21 @@ a:hover { color: var(--iris-pink); text-decoration: underline; } background: var(--status-success); box-shadow: 0 0 8px var(--status-success); } -.user-chip a.logout { +.user-chip a.logout, +.user-chip button.logout { color: var(--muted-2); font-size: 0.95rem; } -.user-chip a.logout:hover { color: var(--status-danger); text-decoration: none; } +.user-chip button.logout { + background: none; + border: none; + padding: 0; + cursor: pointer; + font-family: inherit; + line-height: 1; +} +.user-chip a.logout:hover, +.user-chip button.logout:hover { color: var(--status-danger); text-decoration: none; } /* ---- layout ---- */ diff --git a/app/templates/base.html b/app/templates/base.html index 7a1fd99..1f88845 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -51,7 +51,9 @@