Initial commit: StackPilot Phase 1 (Core)
Self-hosted Docker Compose manager. - Backend: FastAPI + docker-py + SQLite (JWT auth, file-first stacks, lifecycle, live status, WebSocket logs, docker-run converter, audit log) - Frontend: React + Vite + Tailwind (login/setup, dashboard, stacks, stack detail, Monaco editor, dark/light theme) - Deployment: docker-compose.yml, Dockerfiles, nginx reverse proxy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""Application settings, loaded from environment variables."""
|
||||
from __future__ import annotations
|
||||
|
||||
import secrets
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic import field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||
|
||||
# Paths
|
||||
STACKS_DIR: str = "/opt/stackpilot/stacks"
|
||||
DATA_DIR: str = "/opt/stackpilot/data"
|
||||
|
||||
# Security
|
||||
SECRET_KEY: str = "" # Auto-generated if empty (dev only); set in prod.
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
|
||||
REFRESH_TOKEN_EXPIRE_DAYS: int = 30
|
||||
|
||||
# Update checker
|
||||
UPDATE_CHECK_INTERVAL_MINUTES: int = 60
|
||||
|
||||
# Notifications (webhook URLs)
|
||||
NOTIFY_WEBHOOKS: list[str] = []
|
||||
|
||||
# Docker
|
||||
DOCKER_SOCKET: str = "/var/run/docker.sock"
|
||||
HOST_PROC_PATH: str = "/host_proc"
|
||||
|
||||
# Host browser sandbox roots
|
||||
ALLOWED_BROWSE_ROOTS: list[str] = ["/", "/mnt", "/media", "/srv", "/opt"]
|
||||
HOST_ROOT_PREFIX: str = "" # e.g. "/host_root" when host / is bind-mounted
|
||||
|
||||
# CORS
|
||||
CORS_ORIGINS: list[str] = ["http://localhost:5009", "http://localhost:5173"]
|
||||
|
||||
# Server
|
||||
PORT: int = 5008
|
||||
|
||||
@field_validator("SECRET_KEY", mode="after")
|
||||
@classmethod
|
||||
def _ensure_secret(cls, v: str) -> str:
|
||||
return v or secrets.token_urlsafe(48)
|
||||
|
||||
@field_validator(
|
||||
"NOTIFY_WEBHOOKS", "ALLOWED_BROWSE_ROOTS", "CORS_ORIGINS", mode="before"
|
||||
)
|
||||
@classmethod
|
||||
def _split_csv(cls, v):
|
||||
if isinstance(v, str):
|
||||
v = v.strip()
|
||||
if not v:
|
||||
return []
|
||||
if v.startswith("["): # JSON list
|
||||
return v
|
||||
return [item.strip() for item in v.split(",") if item.strip()]
|
||||
return v
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
Reference in New Issue
Block a user