Files
andrew 2c86d7973f P2 minor set: password off argv, configurable share gate, POST logout, log path check
- Navidrome password is now passed to curl via stdin (--data-urlencode "p@-")
  in navidrome-scan.sh and pipeline-status.sh, so it no longer appears in
  ps/proc. Verified the query sent is identical and a live scan still triggers.
- MIN_ARTIST_DIRS (the share-health gate) is now a setting, threaded through to
  the pipeline env, so a user with a small library can lower it instead of the
  scan/sync being permanently blocked by the hardcoded 500.
- /auth/logout is now POST-only (with a nav form + aria-label), so a drive-by
  GET can't log the user out; enforced allowed_email already landed separately.
- view_log now confirms the run's log_path resolves under the logs dir before
  serving it (defense in depth).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:50:06 -06:00

82 lines
3.2 KiB
Python

from pathlib import Path
from pydantic import AliasChoices, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="", extra="ignore")
# Mount points — see docker-compose.snippet.yml. Two volumes total:
# everything audio-related under MUSIC_DATA_DIR, everything else
# (app DB, beets config+DB, rendered pipeline credentials, job logs)
# under ALEMBIC_CONFIG_DIR.
music_data_dir: Path = Path("/data/music")
alembic_config_dir: Path = Path("/config")
pipeline_dir: Path = Path("/app/pipeline")
alembic_port: int = 8420
# Fernet key for the `secrets` table, read from a Docker secret file
# (not a plain env var) — see docker-compose.snippet.yml's `secrets:` block.
encryption_key_file: Path = Path("/run/secrets/alembic_master_key")
# Session cookie signing secret for Starlette's SessionMiddleware.
# Left empty by default on purpose: an empty/placeholder value would let
# anyone forge a signed session cookie and skip login entirely. When this
# is unset, resolve_session_secret() generates and persists a random one
# to the config volume instead of falling back to a known constant.
session_secret: str = ""
# Pocket ID OIDC client, registered once in the Pocket ID admin UI
# (redirect URI: https://<alembic-host>/auth/callback).
pocketid_issuer: str = ""
pocketid_client_id: str = ""
pocketid_client_secret: str = ""
# Defense-in-depth: this is a single-user app. If set, only this email
# may complete login even if Pocket ID ever grows a second account.
allowed_email: str = ""
# IANA timezone (e.g. "America/Edmonton") used for all scheduling and for
# times shown in the UI and the status report. Defaults to UTC so a fresh
# install behaves predictably anywhere. Reads the standard Docker `TZ`
# variable, or `ALEMBIC_TIMEZONE` if you'd rather be explicit. An
# unrecognized value falls back to UTC (see scheduler_service).
timezone: str = Field(
default="UTC",
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"
@property
def beets_db_path(self) -> Path:
return self.beets_dir / "data" / "beets_library.db"
@property
def pipeline_config_dir(self) -> Path:
"""Rendered/instance pipeline config: per-playlist .conf files,
credential env files, editable lists — NOT the static code in
pipeline_dir, which is baked into the image."""
return self.alembic_config_dir / "pipeline"
@property
def app_db_path(self) -> Path:
return self.alembic_config_dir / "alembic.db"
@property
def logs_dir(self) -> Path:
return self.alembic_config_dir / "logs"
settings = Settings()