config.py
python
| 1 | """ |
| 2 | Muse Configuration |
| 3 | |
| 4 | Environment-based configuration for the Muse service. |
| 5 | """ |
| 6 | from __future__ import annotations |
| 7 | |
| 8 | import logging |
| 9 | from functools import lru_cache |
| 10 | |
| 11 | from pydantic import model_validator |
| 12 | from pydantic_settings import BaseSettings, SettingsConfigDict |
| 13 | |
| 14 | |
| 15 | def _app_version_from_package() -> str: |
| 16 | """Read version from the single source of truth (pyproject.toml via protocol.version).""" |
| 17 | from musehub.protocol.version import MUSE_VERSION |
| 18 | return MUSE_VERSION |
| 19 | |
| 20 | |
| 21 | class Settings(BaseSettings): |
| 22 | """Application settings loaded from environment variables.""" |
| 23 | |
| 24 | # Service Info |
| 25 | app_name: str = "Muse" |
| 26 | app_version: str = _app_version_from_package() |
| 27 | debug: bool = False |
| 28 | |
| 29 | # Server Configuration |
| 30 | host: str = "0.0.0.0" |
| 31 | port: int = 10001 |
| 32 | |
| 33 | # Database Configuration |
| 34 | # PostgreSQL: postgresql+asyncpg://user:pass@localhost:5432/muse |
| 35 | # SQLite (dev): sqlite+aiosqlite:///./muse.db |
| 36 | database_url: str | None = None |
| 37 | db_password: str | None = None |
| 38 | |
| 39 | # CORS Settings (fail closed: no default origins) |
| 40 | # Set CORS_ORIGINS (JSON array) in .env. Local dev: ["http://localhost:10003", "muse://"]. |
| 41 | # Production: exact origins only. Never use "*" in production. |
| 42 | cors_origins: list[str] = [] |
| 43 | |
| 44 | @model_validator(mode="after") |
| 45 | def _warn_cors_wildcard_in_production(self) -> "Settings": |
| 46 | """Warn when CORS allows all origins in non-debug (production) mode.""" |
| 47 | if not self.debug and self.cors_origins and "*" in self.cors_origins: |
| 48 | logging.getLogger(__name__).warning( |
| 49 | "CORS allows all origins (*) with DEBUG=false. " |
| 50 | "Set CORS_ORIGINS to exact origins in production." |
| 51 | ) |
| 52 | return self |
| 53 | |
| 54 | # Access Token Settings |
| 55 | # Generate secret with: openssl rand -hex 32 |
| 56 | access_token_secret: str | None = None |
| 57 | access_token_algorithm: str = "HS256" |
| 58 | # In-memory revocation cache TTL (seconds). Reduces DB hits; revocation visible within this window. |
| 59 | token_revocation_cache_ttl_seconds: int = 60 |
| 60 | |
| 61 | # AWS S3 Asset Delivery (drum kits, GM soundfont) |
| 62 | # Region MUST match the bucket's region (S3 returns 301 if URL uses wrong region). |
| 63 | aws_region: str = "eu-west-1" |
| 64 | aws_s3_asset_bucket: str | None = None |
| 65 | aws_cloudfront_domain: str | None = None |
| 66 | presign_expiry_seconds: int = 1800 # 30-min default for presigned download URLs |
| 67 | |
| 68 | # Asset endpoint rate limits (UUID-only auth, no JWT) |
| 69 | asset_rate_limit_per_device: str = "30/minute" |
| 70 | asset_rate_limit_per_ip: str = "120/minute" |
| 71 | |
| 72 | # Stdio MCP server: proxy DAW tools to Muse backend |
| 73 | muse_mcp_url: str | None = None |
| 74 | mcp_token: str | None = None |
| 75 | |
| 76 | # Muse Hub object storage — binary artifacts (MIDI, MP3, WebP) stored as flat files |
| 77 | # under <musehub_objects_dir>/<repo_id>/<object_id>. Mount on a persistent volume in prod. |
| 78 | musehub_objects_dir: str = "/data/musehub/objects" |
| 79 | |
| 80 | # Webhook secret encryption key — AES-256 (Fernet) key for encrypting webhook signing |
| 81 | # secrets at rest. Generate with: |
| 82 | # python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" |
| 83 | webhook_secret_key: str | None = None |
| 84 | |
| 85 | model_config = SettingsConfigDict( |
| 86 | env_file=".env", |
| 87 | env_file_encoding="utf-8", |
| 88 | ) |
| 89 | |
| 90 | |
| 91 | @lru_cache() |
| 92 | def get_settings() -> Settings: |
| 93 | """Get cached settings instance.""" |
| 94 | return Settings() |
| 95 | |
| 96 | |
| 97 | # Convenience access |
| 98 | settings = get_settings() |