Files
deploy.stack/dbSer/couchdb/generate_config.py

305 lines
9.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Obsidian LiveSync CouchDB 配置生成器
此脚本用于生成运行 CouchDB 所需的配置文件特别针对 Obsidian LiveSync 插件进行了优化配置
"""
import os
import sys
import re
import getpass
import platform
from typing import Dict, Any, Optional, Tuple
from pathlib import Path
def get_current_user_ids() -> Tuple[str, str]:
"""
获取当前用户的 UID GID
Windows 上返回默认值 Linux/macOS 上返回实际值
"""
if platform.system() in ['Linux', 'Darwin']: # Linux 或 macOS
try:
return str(os.getuid()), str(os.getgid())
except AttributeError:
pass
# Windows 或其他系统返回默认值
return "99", "100"
# 常量定义
DEFAULT_CONFIG = {
"couchdb_user": "obsidian_user",
"host_port": "5984",
"volume_name": "couchdb_data",
"config_host_path": "./etc/couchdb/local.d",
"single_node": "true",
"cors_origins": "app://obsidian.md,capacitor://localhost,http://localhost",
"puid": get_current_user_ids()[0], # 自动获取当前用户的 UID
"pgid": get_current_user_ids()[1], # 自动获取当前用户的 GID
"tz": "Asia/Shanghai"
}
COMMON_TIMEZONES = {
"1": "Asia/Shanghai",
"2": "Asia/Singapore",
"3": "America/New_York",
"4": "Europe/London",
"5": "Asia/Tokyo"
}
def validate_port(port: str) -> bool:
"""验证端口号是否有效。"""
try:
port_num = int(port)
return 1 <= port_num <= 65535
except ValueError:
return False
def validate_username(username: str) -> bool:
"""验证用户名是否有效。"""
return bool(re.match(r'^[a-zA-Z0-9_-]{3,32}$', username))
def validate_password(password: str) -> bool:
"""验证密码是否满足最低安全要求。"""
if len(password) < 8:
return False
if not re.search(r'[A-Z]', password):
return False
if not re.search(r'[a-z]', password):
return False
if not re.search(r'[0-9]', password):
return False
return True
def get_input_with_default(prompt: str, default: str, validator: Optional[callable] = None) -> str:
"""获取用户输入,支持默认值和验证。"""
while True:
value = input(f"{prompt} [{default}]").strip()
if not value:
value = default
if validator and not validator(value):
print(f"输入无效,请重试。")
continue
return value
def get_password() -> str:
"""安全地获取密码输入并进行确认。"""
while True:
password = getpass.getpass("请输入密码:").strip()
if not validate_password(password):
print("密码必须至少8个字符并包含大写字母、小写字母和数字。")
continue
confirm = getpass.getpass("请确认密码:").strip()
if password == confirm:
return password
print("两次输入的密码不一致,请重试。")
def get_timezone() -> str:
"""获取用户选择的时区。"""
print("\n可用的时区:")
for key, value in COMMON_TIMEZONES.items():
print(f" {key}. {value}")
print(" (或输入自定义时区名称,例如:'Asia/Kuala_Lumpur'")
while True:
choice = input(f"选择时区 [{DEFAULT_CONFIG['tz']}]").strip()
if not choice:
return DEFAULT_CONFIG['tz']
if choice in COMMON_TIMEZONES:
return COMMON_TIMEZONES[choice]
# 基本验证自定义时区
if '/' in choice and all(part.isalnum() or part in '_-' for part in choice.split('/')):
return choice
print("时区格式无效,请重试。")
def generate_docker_compose(config: Dict[str, Any]) -> str:
"""生成 docker-compose.yml 内容。"""
return f"""# 为 Obsidian LiveSync 生成的 docker-compose.yml
# 使用最新的 Docker Compose 规范
services:
couchdb-obsidian-livesync:
container_name: couchdb-obsidian-livesync
image: couchdb:3.3.3
environment:
PUID: {config['puid']}
PGID: {config['pgid']}
UMASK: 0022
TZ: {config['tz']}
COUCHDB_USER: {config['couchdb_user']}
COUCHDB_PASSWORD: {config['couchdb_password']}
volumes:
- {config['volume_name']}:/opt/couchdb/data
- {config['config_host_path']}:/opt/couchdb/etc/local.d
ports:
- "{config['host_port']}:5984"
restart: unless-stopped
networks:
- couchdb_network
volumes:
{config['volume_name']}:
networks:
couchdb_network:
driver: bridge
"""
def generate_couchdb_config(config: Dict[str, Any]) -> str:
"""生成 CouchDB local.ini 内容。"""
return f"""[couchdb]
single_node={config['single_node']}
max_document_size = 50000000
[chttpd]
require_valid_user = true
max_http_request_size = 4294967296
[chttpd_auth]
require_valid_user = true
authentication_redirect = /_utils/session.html
[httpd]
WWW-Authenticate = Basic realm="couchdb"
enable_cors = true
[cors]
origins = {config['cors_origins']}
credentials = true
headers = accept, authorization, content-type, origin, referer
methods = GET, PUT, POST, HEAD, DELETE
max_age = 3600
"""
def save_config_files(config: Dict[str, Any]) -> None:
"""保存所有配置文件。"""
# 创建配置目录
config_dir = Path(config['config_host_path'])
config_dir.mkdir(parents=True, exist_ok=True)
# 保存 docker-compose.yml
with open('docker-compose.yml', 'w') as f:
f.write(generate_docker_compose(config))
print(f"\n✓ 已生成 docker-compose.yml")
# 保存 CouchDB 配置
config_path = config_dir / "my_obsidian_config.ini"
with open(config_path, 'w') as f:
f.write(generate_couchdb_config(config))
print(f"✓ 已生成 {config_path}")
def main():
"""主函数,运行配置生成器。"""
print("=== Obsidian LiveSync CouchDB 配置生成器 ===")
print("此脚本将帮助您设置用于 Obsidian LiveSync 的 CouchDB。")
print("随时可以按 Ctrl+C 退出。\n")
try:
config = DEFAULT_CONFIG.copy()
# 获取用户名
config['couchdb_user'] = get_input_with_default(
"请输入 CouchDB 管理员用户名",
DEFAULT_CONFIG['couchdb_user'],
validate_username
)
# 获取密码
print("\n请输入 CouchDB 管理员密码必须至少8个字符包含大写字母、小写字母和数字")
config['couchdb_password'] = get_password()
# 获取时区
config['tz'] = get_timezone()
# 获取端口
config['host_port'] = get_input_with_default(
"请输入 CouchDB 主机端口",
DEFAULT_CONFIG['host_port'],
validate_port
)
# 获取卷名称
config['volume_name'] = get_input_with_default(
"请输入 CouchDB 数据卷名称",
DEFAULT_CONFIG['volume_name']
)
# 获取配置路径
config['config_host_path'] = get_input_with_default(
"请输入配置文件目录路径",
DEFAULT_CONFIG['config_host_path']
)
# 获取单节点模式
single_node = get_input_with_default(
"是否使用单节点模式true/false",
DEFAULT_CONFIG['single_node']
).lower()
config['single_node'] = "true" if single_node in ["true", "t", "yes", "y"] else "false"
# 获取 CORS 源
config['cors_origins'] = get_input_with_default(
"请输入允许的 CORS 源(用逗号分隔)",
DEFAULT_CONFIG['cors_origins']
)
# 获取 PUID/PGID
current_uid, current_gid = get_current_user_ids()
if platform.system() in ['Linux', 'Darwin']:
print(f"\n检测到当前用户 IDUID={current_uid}, GID={current_gid}")
print("这些值将用于设置容器内的文件权限。")
use_current = get_input_with_default(
"是否使用当前用户的 UID/GID推荐",
"yes"
).lower() in ["yes", "y", "true", "t"]
if use_current:
config['puid'] = current_uid
config['pgid'] = current_gid
else:
print("\n请输入自定义的 PUID/PGID")
config['puid'] = get_input_with_default(
"请输入 PUID",
current_uid,
lambda x: x.isdigit()
)
config['pgid'] = get_input_with_default(
"请输入 PGID",
current_gid,
lambda x: x.isdigit()
)
else:
print("\n当前系统不支持自动获取用户 ID将使用默认值99/100")
print("如果需要自定义,请手动输入。")
config['puid'] = get_input_with_default(
"请输入 PUID",
DEFAULT_CONFIG['puid'],
lambda x: x.isdigit()
)
config['pgid'] = get_input_with_default(
"请输入 PGID",
DEFAULT_CONFIG['pgid'],
lambda x: x.isdigit()
)
# 保存所有配置文件
save_config_files(config)
print("\n=== 配置完成 ===")
print(f"✓ CouchDB 将在 http://localhost:{config['host_port']}/_utils/ 访问")
print(f"✓ 用户名:{config['couchdb_user']}")
print(f"✓ 文件权限UID={config['puid']}, GID={config['pgid']}")
print("\n要启动 CouchDB请运行")
print(" docker-compose up -d")
print("\n要查看日志,请运行:")
print(" docker-compose logs -f")
except KeyboardInterrupt:
print("\n\n配置已取消。")
sys.exit(1)
except Exception as e:
print(f"\n错误:{str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()