letsbe-sysadmin/app/config.py

114 lines
4.5 KiB
Python

"""Agent configuration via environment variables."""
import socket
from functools import lru_cache
from typing import Optional
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from app import __version__
class Settings(BaseSettings):
"""Agent settings loaded from environment variables.
All settings are frozen after initialization to prevent runtime mutation.
"""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
frozen=True, # Prevent runtime mutation
)
# Agent identity
agent_version: str = Field(default=__version__, description="Agent version for API headers")
hostname: str = Field(default_factory=socket.gethostname, description="Agent hostname")
agent_id: Optional[str] = Field(default=None, description="Assigned by orchestrator after registration")
# New secure registration (recommended)
registration_token: Optional[str] = Field(
default=None,
description="Registration token from orchestrator. Required for first-time registration."
)
# Agent credentials (set after registration, persisted to disk)
agent_secret: Optional[str] = Field(
default=None,
description="Agent secret for authentication. Set after registration."
)
# Tenant assignment (derived from registration token, or can be set directly for legacy)
tenant_id: Optional[str] = Field(
default=None,
description="Tenant UUID this agent belongs to. Set after registration."
)
# Orchestrator connection
# Default URL is for Docker-based dev where orchestrator runs on the host.
# When running directly on a Linux tenant server, set ORCHESTRATOR_URL to
# the orchestrator's public URL (e.g., "https://orchestrator.letsbe.io").
orchestrator_url: str = Field(
default="http://host.docker.internal:8000",
description="Orchestrator API base URL"
)
# Legacy auth (deprecated - use registration_token + agent_secret instead)
agent_token: Optional[str] = Field(
default=None,
description="[DEPRECATED] Legacy authentication token. Use agent_secret instead."
)
# Timing intervals (seconds)
heartbeat_interval: int = Field(default=30, ge=5, le=300, description="Heartbeat interval")
poll_interval: int = Field(default=5, ge=1, le=60, description="Task polling interval")
# Logging
log_level: str = Field(default="INFO", description="Log level (DEBUG, INFO, WARNING, ERROR)")
log_json: bool = Field(default=True, description="Output logs as JSON")
# Resilience
max_concurrent_tasks: int = Field(default=3, ge=1, le=10, description="Max concurrent task executions")
backoff_base: float = Field(default=1.0, ge=0.1, le=10.0, description="Base backoff time in seconds")
backoff_max: float = Field(default=60.0, ge=10.0, le=300.0, description="Max backoff time in seconds")
circuit_breaker_threshold: int = Field(default=5, ge=1, le=20, description="Consecutive failures to trip breaker")
circuit_breaker_cooldown: int = Field(default=300, ge=30, le=900, description="Cooldown period in seconds")
# Security - File operations
allowed_file_root: str = Field(default="/opt/letsbe", description="Root directory for file operations")
allowed_env_root: str = Field(default="/opt/letsbe/env", description="Root directory for ENV file operations")
max_file_size: int = Field(default=10 * 1024 * 1024, description="Max file size in bytes (default 10MB)")
# Security - Shell operations
shell_timeout: int = Field(default=60, ge=5, le=600, description="Default shell command timeout")
# Security - Docker operations
allowed_compose_paths: list[str] = Field(
default=["/opt/letsbe", "/home/letsbe"],
description="Allowed directories for compose files"
)
allowed_stacks_root: str = Field(
default="/opt/letsbe/stacks",
description="Root directory for Docker stack operations"
)
# Local persistence
pending_results_path: str = Field(
default="~/.letsbe-agent/pending_results.json",
description="Path for buffering unsent task results"
)
credentials_path: str = Field(
default="~/.letsbe-agent/credentials.json",
description="Path for persisting agent credentials after registration"
)
@lru_cache
def get_settings() -> Settings:
"""Get cached settings instance.
Settings are loaded once and cached for the lifetime of the process.
"""
return Settings()