Files
MemoryBear/sandbox/app/config.py

131 lines
4.0 KiB
Python

"""Configuration management"""
import os
from typing import List, Optional
from pydantic import BaseModel, Field
import yaml
DEFAULT_PYTHON_LIB_REQUIREMENTS_AMD = [
"/usr/local/lib/python3.12",
"/usr/lib/python3",
"/usr/lib/x86_64-linux-gnu",
"/etc/ssl/certs/ca-certificates.crt",
"/etc/nsswitch.conf",
"/etc/hosts",
"/etc/resolv.conf",
"/etc/localtime",
"/usr/share/zoneinfo",
"/etc/timezone",
]
DEFAULT_NODEJS_LIB_REQUIREMENTS = [
"/etc/ssl/certs/ca-certificates.crt",
"/etc/nsswitch.conf",
"/etc/resolv.conf",
"/etc/hosts",
]
class AppConfig(BaseModel):
"""Application configuration"""
port: int = 8194
debug: bool = True
key: str = "redbear-sandbox"
class ProxyConfig(BaseModel):
"""Proxy configuration"""
socks5: str = ""
http: str = ""
https: str = ""
class Config(BaseModel):
"""Global configuration"""
app: AppConfig = Field(default_factory=AppConfig)
max_workers: int = 4
max_requests: int = 50
worker_timeout: int = 30
enable_network: bool = True
enable_preload: bool = False
python_path: str = ""
python_lib_paths: list = Field(default=DEFAULT_PYTHON_LIB_REQUIREMENTS_AMD)
python_deps_update_interval: str = "30m"
nodejs_path: str = ""
nodejs_lib_paths: list = Field(default=DEFAULT_NODEJS_LIB_REQUIREMENTS)
allowed_syscalls: List[int] = Field(default_factory=list)
proxy: ProxyConfig = Field(default_factory=ProxyConfig)
sandbox_user: str = "sandbox"
sandbox_uid: int = 65537
sandbox_gid: int = 0
def set_sandbox_gid(self, gid: int):
"""Update sandbox GID dynamically"""
self.sandbox_gid = gid
def override_with_env(self):
"""Override configuration with environment variables"""
env_map = {
"DEBUG": ("app.debug", lambda v: v.lower() in ("true", "1", "yes")),
"MAX_WORKERS": ("max_workers", int),
"MAX_REQUESTS": ("max_requests", int),
"SANDBOX_PORT": ("app.port", int),
"WORKER_TIMEOUT": ("worker_timeout", int),
"API_KEY": ("app.key", str),
"NODEJS_PATH": ("nodejs_path", str),
"ENABLE_NETWORK": ("enable_network", lambda v: v.lower() in ("true", "1", "yes")),
"ENABLE_PRELOAD": ("enable_preload", lambda v: v.lower() in ("true", "1", "yes")),
"ALLOWED_SYSCALLS": ("allowed_syscalls", lambda v: [int(x) for x in v.split(",")]),
"SOCKS5_PROXY": ("proxy.socks5", str),
"HTTP_PROXY": ("proxy.http", str),
"HTTPS_PROXY": ("proxy.https", str),
"PYTHON_PATH": ("python_path", str),
"PYTHON_LIB_PATH": ("python_lib_paths", lambda v: v.split(",")),
"PYTHON_DEPS_UPDATE_INTERVAL": ("python_deps_update_interval", str),
"NODEJS_LIB_PATH": ("nodejs_lib_paths", lambda v: v.split(",")),
}
for env_var, (attr_path, cast) in env_map.items():
value = os.getenv(env_var)
if value is not None:
# Support nested attributes like 'app.debug'
parts = attr_path.split(".")
obj = self
for part in parts[:-1]:
obj = getattr(obj, part)
setattr(obj, parts[-1], cast(value))
# Global configuration instance
_config: Optional[Config] = None
def load_config(config_path: str = "config.yaml") -> Config:
"""Load configuration from YAML file and override with env variables"""
global _config
if os.path.exists(config_path):
with open(config_path, 'r') as f:
data = yaml.safe_load(f) or {}
_config = Config(**data)
else:
_config = Config()
# Override from environment
_config.override_with_env()
return _config
config_path = os.getenv("CONFIG_PATH", "config.yaml")
load_config(config_path)
def get_config() -> Config:
"""Get global configuration"""
if _config is None:
raise RuntimeError("Configuration not loaded. Call load_config() first.")
return _config