Implement comprehensive configuration management system with: **Core Components:** - config/config.schema.yaml: Configuration metadata (single source of truth) - scripts/lib/generate_from_schema.py: Schema → local.yaml generator - scripts/lib/generate_env.py: local.yaml → .env converter - scripts/setup.sh: One-click configuration initialization **Key Features:** - Deep merge logic preserves existing values - Auto-generation of secrets (32-char random strings) - Type inference for configuration values - Nested YAML → flat environment variables - Git-safe: local.yaml and .env excluded from version control **Configuration Coverage:** - Trilingual entity extractor (Chinese/English/Swedish) - LightRAG API, database, vector DB settings - LLM provider configuration - Entity/relation extraction settings - Security and performance tuning **Documentation:** - docs/ConfigurationGuide-zh.md: Complete usage guide with examples **Usage:** ```bash ./scripts/setup.sh # Generate config/local.yaml and .env ``` This enables centralized configuration management with automatic secret generation and safe handling of sensitive data.
206 lines
5.3 KiB
Python
Executable file
206 lines
5.3 KiB
Python
Executable file
#!/usr/bin/env python3
|
||
"""
|
||
环境变量生成器 - 将 YAML 配置转换为 .env 格式
|
||
|
||
从 config/local.yaml 读取配置,生成 .env 文件。
|
||
支持嵌套 YAML 扁平化为环境变量。
|
||
"""
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
from typing import Any, Dict, List
|
||
import yaml
|
||
|
||
|
||
def flatten_dict(data: Dict, parent_key: str = '', sep: str = '_') -> Dict[str, Any]:
|
||
"""
|
||
扁平化嵌套字典
|
||
|
||
Args:
|
||
data: 嵌套字典
|
||
parent_key: 父级键名
|
||
sep: 分隔符
|
||
|
||
Returns:
|
||
扁平化后的字典
|
||
|
||
Example:
|
||
{'trilingual': {'enabled': True}} -> {'TRILINGUAL_ENABLED': True}
|
||
"""
|
||
items = []
|
||
|
||
for key, value in data.items():
|
||
# 转换为大写并组合键名
|
||
new_key = f"{parent_key}{sep}{key}".upper() if parent_key else key.upper()
|
||
|
||
if isinstance(value, dict):
|
||
# 递归处理嵌套字典
|
||
items.extend(flatten_dict(value, new_key, sep=sep).items())
|
||
else:
|
||
items.append((new_key, value))
|
||
|
||
return dict(items)
|
||
|
||
|
||
def format_env_value(value: Any) -> str:
|
||
"""
|
||
格式化环境变量值
|
||
|
||
Args:
|
||
value: 原始值
|
||
|
||
Returns:
|
||
格式化后的字符串
|
||
|
||
Example:
|
||
True -> 'true'
|
||
123 -> '123'
|
||
'hello world' -> 'hello world'
|
||
"""
|
||
if isinstance(value, bool):
|
||
return 'true' if value else 'false'
|
||
elif isinstance(value, (int, float)):
|
||
return str(value)
|
||
elif isinstance(value, str):
|
||
# 如果字符串包含空格或特殊字符,添加引号
|
||
if ' ' in value or any(c in value for c in ['#', '$', '&', '|', ';']):
|
||
# 转义内部引号
|
||
escaped = value.replace('"', '\\"')
|
||
return f'"{escaped}"'
|
||
return value
|
||
elif value is None:
|
||
return ''
|
||
else:
|
||
return str(value)
|
||
|
||
|
||
def load_config(config_path: Path) -> Dict:
|
||
"""
|
||
加载 YAML 配置
|
||
|
||
Args:
|
||
config_path: 配置文件路径
|
||
|
||
Returns:
|
||
配置字典
|
||
"""
|
||
if not config_path.exists():
|
||
raise FileNotFoundError(f"配置文件不存在: {config_path}")
|
||
|
||
with open(config_path, 'r', encoding='utf-8') as f:
|
||
config = yaml.safe_load(f)
|
||
|
||
return config if config else {}
|
||
|
||
|
||
def generate_env_content(config: Dict) -> str:
|
||
"""
|
||
生成 .env 文件内容
|
||
|
||
Args:
|
||
config: 配置字典
|
||
|
||
Returns:
|
||
.env 格式的字符串
|
||
"""
|
||
# 扁平化配置
|
||
flat_config = flatten_dict(config)
|
||
|
||
# 按键名排序
|
||
sorted_items = sorted(flat_config.items())
|
||
|
||
# 生成 .env 内容
|
||
lines = [
|
||
"# LightRAG 环境变量配置",
|
||
"# 此文件由 scripts/setup.sh 自动生成,请勿手动编辑",
|
||
"# 修改 config/local.yaml 后重新运行 ./scripts/setup.sh 更新此文件",
|
||
"",
|
||
]
|
||
|
||
current_section = None
|
||
|
||
for key, value in sorted_items:
|
||
# 提取顶级 section(第一个下划线之前的部分)
|
||
section = key.split('_')[0]
|
||
|
||
# 如果切换到新 section,添加分隔注释
|
||
if section != current_section:
|
||
if current_section is not None:
|
||
lines.append("") # 添加空行分隔
|
||
lines.append(f"# {section}")
|
||
current_section = section
|
||
|
||
# 添加键值对
|
||
formatted_value = format_env_value(value)
|
||
lines.append(f"{key}={formatted_value}")
|
||
|
||
return '\n'.join(lines) + '\n'
|
||
|
||
|
||
def save_env_file(content: str, env_path: Path) -> None:
|
||
"""
|
||
保存 .env 文件
|
||
|
||
Args:
|
||
content: .env 文件内容
|
||
env_path: 输出文件路径
|
||
"""
|
||
with open(env_path, 'w', encoding='utf-8') as f:
|
||
f.write(content)
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
# 获取项目根目录
|
||
project_root = Path(__file__).parent.parent.parent
|
||
|
||
# 文件路径
|
||
config_path = project_root / 'config' / 'local.yaml'
|
||
env_path = project_root / '.env'
|
||
|
||
print("=" * 70)
|
||
print(" 环境变量生成器")
|
||
print("=" * 70)
|
||
print()
|
||
|
||
try:
|
||
# 加载配置
|
||
print(f"📖 读取配置: {config_path.relative_to(project_root)}")
|
||
config = load_config(config_path)
|
||
print(f" 找到 {len(config)} 个顶级配置节")
|
||
|
||
# 生成 .env 内容
|
||
print(f"\n⚙️ 生成环境变量...")
|
||
env_content = generate_env_content(config)
|
||
|
||
# 统计生成的环境变量数量
|
||
env_count = len([line for line in env_content.split('\n') if '=' in line])
|
||
print(f" 生成 {env_count} 个环境变量")
|
||
|
||
# 保存 .env 文件
|
||
print(f"\n💾 保存文件: {env_path.relative_to(project_root)}")
|
||
save_env_file(env_content, env_path)
|
||
|
||
print()
|
||
print("=" * 70)
|
||
print(" ✅ 环境变量生成成功")
|
||
print("=" * 70)
|
||
print()
|
||
print(f"输出文件: {env_path}")
|
||
print()
|
||
print("提示:")
|
||
print(" - .env 文件已添加到 .gitignore,不会提交到 Git")
|
||
print(" - 修改 config/local.yaml 后重新运行此脚本更新 .env")
|
||
print(" - 环境变量命名规则: 嵌套路径转大写并用下划线连接")
|
||
print(" 例如: trilingual.chinese.enabled -> TRILINGUAL_CHINESE_ENABLED")
|
||
print()
|
||
|
||
except Exception as e:
|
||
print(f"\n❌ 错误: {e}", file=sys.stderr)
|
||
import traceback
|
||
traceback.print_exc()
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|