feat(config): derive MusicBrainz/AcoustID User-Agent from app name+version
Docker Build & Publish / build (push) Successful in 1m8s
Docker Build & Publish / push (push) Failing after 6s
Docker Build & Publish / Prune old image versions (push) Has been skipped

Replace the placeholder MUSICBRAINZ_USER_AGENT env var with
MUSICBRAINZ_OWNER_EMAIL. The User-Agent ("MCMA/<version> ( <contact> )")
is now composed from the fixed app name, the installed package version,
and the operator's contact email — falling back to the project URL when
no email is configured. Also use the same version for the FastAPI app.
This commit is contained in:
Senko-san
2026-06-11 00:39:24 +03:00
parent 356cd00772
commit c7e078d758
3 changed files with 33 additions and 4 deletions
+2 -1
View File
@@ -37,5 +37,6 @@ MAX_PARALLEL_DOWNLOADS=2
# external services (all optional — backend degrades gracefully if unset) # external services (all optional — backend degrades gracefully if unset)
# ML_SERVICE_URL=http://ml:9000 # ML_SERVICE_URL=http://ml:9000
# ACOUSTID_API_KEY= # ACOUSTID_API_KEY=
MUSICBRAINZ_USER_AGENT=mcma-backend/0.1.0 ( https://github.com/your/repo ) # Sent to MusicBrainz/AcoustID as part of the User-Agent (MCMA/<version> ( <email> )).
# MUSICBRAINZ_OWNER_EMAIL=you@example.com
# YOUTUBE_COOKIES_PATH=/data/cookies.txt # YOUTUBE_COOKIES_PATH=/data/cookies.txt
+29 -1
View File
@@ -5,12 +5,25 @@ development). Access the cached singleton via :func:`get_settings`.
""" """
from functools import lru_cache from functools import lru_cache
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path from pathlib import Path
from typing import Literal from typing import Literal
from pydantic import Field, SecretStr, field_validator from pydantic import Field, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
# App identity for outbound API calls (e.g. the MusicBrainz/AcoustID
# User-Agent). Name is fixed; version comes from the installed package.
APP_NAME = "MCMA"
_PROJECT_URL = "https://git.ollyhearn.ru/olly/mcma-backend"
def app_version() -> str:
try:
return version("mcma-backend")
except PackageNotFoundError:
return "0.0.0"
class Settings(BaseSettings): class Settings(BaseSettings):
model_config = SettingsConfigDict( model_config = SettingsConfigDict(
@@ -77,7 +90,12 @@ class Settings(BaseSettings):
ml_service_url: str | None = None ml_service_url: str | None = None
acoustid_api_key: SecretStr | None = None acoustid_api_key: SecretStr | None = None
acoustid_api_url: str = "https://api.acoustid.org/v2/lookup" acoustid_api_url: str = "https://api.acoustid.org/v2/lookup"
musicbrainz_user_agent: str = "mcma-backend/0.1.0 ( https://github.com/your/repo )" # MusicBrainz/AcoustID require a meaningful User-Agent identifying the
# application and a way to contact its maintainer (see
# https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting). Self-hosted
# deployments should set their own contact email; see
# ``musicbrainz_user_agent`` below for how it's used.
musicbrainz_owner_email: str | None = None
youtube_cookies_path: Path | None = None youtube_cookies_path: Path | None = None
# -- enrichment ------------------------------------------------------- # -- enrichment -------------------------------------------------------
@@ -96,6 +114,16 @@ class Settings(BaseSettings):
def is_prod(self) -> bool: def is_prod(self) -> bool:
return self.environment == "prod" return self.environment == "prod"
@property
def musicbrainz_user_agent(self) -> str:
"""User-Agent sent to MusicBrainz/AcoustID: ``MCMA/<version> ( <contact> )``.
Falls back to the project URL if the deployment hasn't set
``musicbrainz_owner_email``.
"""
contact = self.musicbrainz_owner_email or _PROJECT_URL
return f"{APP_NAME}/{app_version()} ( {contact} )"
@lru_cache @lru_cache
def get_settings() -> Settings: def get_settings() -> Settings:
+2 -2
View File
@@ -10,7 +10,7 @@ from app.api.health import router as health_router
from app.api.middleware import CorrelationIdMiddleware from app.api.middleware import CorrelationIdMiddleware
from app.api.rest import subsonic_router from app.api.rest import subsonic_router
from app.api.v1 import api_v1_router from app.api.v1 import api_v1_router
from app.core.config import get_settings from app.core.config import app_version, get_settings
from app.core.logging import configure_logging, get_logger from app.core.logging import configure_logging, get_logger
from app.infrastructure.cache import close_redis from app.infrastructure.cache import close_redis
from app.infrastructure.db import dispose_engine from app.infrastructure.db import dispose_engine
@@ -34,7 +34,7 @@ def create_app() -> FastAPI:
app = FastAPI( app = FastAPI(
title="mcma-backend", title="mcma-backend",
version="0.1.0", version=app_version(),
summary="Self-hosted, offline-first music service.", summary="Self-hosted, offline-first music service.",
lifespan=lifespan, lifespan=lifespan,
) )