Split monorepo into separate units for future independent repositories: - media-server/: Standalone FastAPI server with own README, requirements, config example, and CLAUDE.md - haos-integration/: HACS-ready Home Assistant integration with hacs.json, own README, and CLAUDE.md Both components now have their own .gitignore files and can be easily extracted into separate repositories. Also adds custom icon support for scripts configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
143 lines
4.7 KiB
Python
143 lines
4.7 KiB
Python
"""Configuration management for the media server."""
|
|
|
|
import os
|
|
import secrets
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import yaml
|
|
from pydantic import BaseModel, Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class ScriptConfig(BaseModel):
|
|
"""Configuration for a custom script."""
|
|
|
|
command: str = Field(..., description="Command or script to execute")
|
|
label: Optional[str] = Field(default=None, description="User-friendly display label")
|
|
description: str = Field(default="", description="Script description")
|
|
icon: Optional[str] = Field(default=None, description="Custom icon (e.g., 'mdi:power')")
|
|
timeout: int = Field(default=30, description="Execution timeout in seconds", ge=1, le=300)
|
|
working_dir: Optional[str] = Field(default=None, description="Working directory")
|
|
shell: bool = Field(default=True, description="Run command in shell")
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Application settings loaded from environment or config file."""
|
|
|
|
model_config = SettingsConfigDict(
|
|
env_prefix="MEDIA_SERVER_",
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
)
|
|
|
|
# Server settings
|
|
host: str = Field(default="0.0.0.0", description="Server bind address")
|
|
port: int = Field(default=8765, description="Server port")
|
|
|
|
# Authentication
|
|
api_token: str = Field(
|
|
default_factory=lambda: secrets.token_urlsafe(32),
|
|
description="API authentication token",
|
|
)
|
|
|
|
# Media controller settings
|
|
poll_interval: float = Field(
|
|
default=1.0, description="Media status poll interval in seconds"
|
|
)
|
|
|
|
# Logging
|
|
log_level: str = Field(default="INFO", description="Logging level")
|
|
|
|
# Custom scripts (loaded separately from YAML)
|
|
scripts: dict[str, ScriptConfig] = Field(
|
|
default_factory=dict,
|
|
description="Custom scripts that can be executed via API",
|
|
)
|
|
|
|
@classmethod
|
|
def load_from_yaml(cls, path: Optional[Path] = None) -> "Settings":
|
|
"""Load settings from a YAML configuration file."""
|
|
if path is None:
|
|
# Look for config in standard locations
|
|
search_paths = [
|
|
Path("config.yaml"),
|
|
Path("config.yml"),
|
|
]
|
|
|
|
# Add platform-specific config directory
|
|
if os.name == "nt": # Windows
|
|
appdata = os.environ.get("APPDATA", "")
|
|
if appdata:
|
|
search_paths.append(Path(appdata) / "media-server" / "config.yaml")
|
|
else: # Linux/Unix/macOS
|
|
search_paths.append(Path.home() / ".config" / "media-server" / "config.yaml")
|
|
search_paths.append(Path("/etc/media-server/config.yaml"))
|
|
|
|
for search_path in search_paths:
|
|
if search_path.exists():
|
|
path = search_path
|
|
break
|
|
|
|
if path and path.exists():
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
config_data = yaml.safe_load(f) or {}
|
|
return cls(**config_data)
|
|
|
|
return cls()
|
|
|
|
|
|
def get_config_dir() -> Path:
|
|
"""Get the configuration directory path."""
|
|
if os.name == "nt": # Windows
|
|
config_dir = Path(os.environ.get("APPDATA", "")) / "media-server"
|
|
else: # Linux/Unix
|
|
config_dir = Path.home() / ".config" / "media-server"
|
|
|
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
return config_dir
|
|
|
|
|
|
def generate_default_config(path: Optional[Path] = None) -> Path:
|
|
"""Generate a default configuration file with a new API token."""
|
|
if path is None:
|
|
path = get_config_dir() / "config.yaml"
|
|
|
|
config = {
|
|
"host": "0.0.0.0",
|
|
"port": 8765,
|
|
"api_token": secrets.token_urlsafe(32),
|
|
"poll_interval": 1.0,
|
|
"log_level": "INFO",
|
|
"scripts": {
|
|
"example_script": {
|
|
"command": "echo Hello from Media Server!",
|
|
"description": "Example script - echoes a message",
|
|
"timeout": 10,
|
|
"shell": True,
|
|
},
|
|
# Add your custom scripts here:
|
|
# "shutdown": {
|
|
# "command": "shutdown /s /t 60",
|
|
# "description": "Shutdown computer in 60 seconds",
|
|
# "timeout": 5,
|
|
# },
|
|
# "lock_screen": {
|
|
# "command": "rundll32.exe user32.dll,LockWorkStation",
|
|
# "description": "Lock the workstation",
|
|
# "timeout": 5,
|
|
# },
|
|
},
|
|
}
|
|
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(path, "w", encoding="utf-8") as f:
|
|
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
|
|
return path
|
|
|
|
|
|
# Global settings instance
|
|
settings = Settings.load_from_yaml()
|