forked from DevOps/deploy.stack
305 lines
9.8 KiB
Python
305 lines
9.8 KiB
Python
#!/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检测到当前用户 ID:UID={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() |