- Add explicit .env loading in config.py for Windows compatibility - Add backend directory to sys.path in main.py to fix module imports - Add start.bat and start-full.bat for Windows startup - Add frontend/package-lock.json for dependency tracking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
78 lines
2.1 KiB
Python
78 lines
2.1 KiB
Python
import base64
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
from pydantic import field_validator
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
# Load .env file explicitly - Windows compatibility
|
|
# This ensures environment variables are loaded before Settings instantiation
|
|
_env_path = Path(__file__).parent.parent.parent / '.env'
|
|
if _env_path.exists():
|
|
from dotenv import load_dotenv
|
|
load_dotenv(_env_path, override=True)
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""应用配置,从环境变量加载."""
|
|
|
|
# 安全配置
|
|
encrypt_key: str # AES-256 密钥 (base64)
|
|
api_token: str # API 认证 Token
|
|
|
|
# 路径配置
|
|
data_dir: Path = Path('./data')
|
|
|
|
# 服务器配置
|
|
host: str = '0.0.0.0'
|
|
port: int = 8000
|
|
|
|
model_config = SettingsConfigDict(
|
|
env_prefix='GM_',
|
|
env_file='.env',
|
|
env_file_encoding='utf-8',
|
|
)
|
|
|
|
@field_validator('encrypt_key')
|
|
@classmethod
|
|
def validate_encrypt_key(cls, v: str) -> str:
|
|
"""验证 encrypt_key 是否为有效的 base64 格式且长度足够."""
|
|
# Check if valid base64
|
|
try:
|
|
decoded = base64.b64decode(v, validate=True)
|
|
except Exception:
|
|
raise ValueError('encrypt_key must be valid base64 string')
|
|
|
|
# Check length (AES-256 requires 32 bytes)
|
|
if len(decoded) < 32:
|
|
raise ValueError('encrypt_key must decode to at least 32 bytes for AES-256')
|
|
|
|
return v
|
|
|
|
@property
|
|
def db_path(self) -> Path:
|
|
"""SQLite 数据库路径."""
|
|
return self.data_dir / 'git_manager.db'
|
|
|
|
@property
|
|
def ssh_keys_dir(self) -> Path:
|
|
"""SSH 密钥存储目录."""
|
|
return self.data_dir / 'ssh_keys'
|
|
|
|
@property
|
|
def repos_dir(self) -> Path:
|
|
"""仓库镜像存储目录."""
|
|
return self.data_dir / 'repos'
|
|
|
|
|
|
# Lazy initialization - settings is loaded on first access
|
|
_settings: Optional[Settings] = None
|
|
|
|
|
|
def get_settings() -> Settings:
|
|
"""获取全局配置实例(懒加载)."""
|
|
global _settings
|
|
if _settings is None:
|
|
_settings = Settings()
|
|
return _settings
|