forked from DevOps/deploy.stack
feat(couchdb): 添加 CouchDB 配置文件和生成脚本
- 添加 CouchDB 的 Docker 配置文件和环境变量配置 - 添加用于生成 Obsidian LiveSync 配置的 Python 脚本
This commit is contained in:
2
dbSer/couchdb/env.cfg
Normal file
2
dbSer/couchdb/env.cfg
Normal file
@@ -0,0 +1,2 @@
|
||||
IMAGE_TAG=couchdb:3.5
|
||||
|
||||
38
dbSer/couchdb/etc/couchdb/local.d/local.ini
Normal file
38
dbSer/couchdb/etc/couchdb/local.d/local.ini
Normal file
@@ -0,0 +1,38 @@
|
||||
# CouchDB 配置文件
|
||||
# 此文件包含 Obsidian LiveSync 所需的 CouchDB 配置
|
||||
|
||||
[couchdb]
|
||||
# 单节点模式设置
|
||||
single_node=true
|
||||
# 最大文档大小(字节)
|
||||
max_document_size = 50000000
|
||||
|
||||
[chttpd]
|
||||
# 要求有效用户认证
|
||||
require_valid_user = true
|
||||
# 最大 HTTP 请求大小(字节)
|
||||
max_http_request_size = 4294967296
|
||||
|
||||
[chttpd_auth]
|
||||
# 要求有效用户认证
|
||||
require_valid_user = true
|
||||
# 认证重定向页面
|
||||
authentication_redirect = /_utils/session.html
|
||||
|
||||
[httpd]
|
||||
# 基本认证领域
|
||||
WWW-Authenticate = Basic realm="couchdb"
|
||||
# 启用 CORS
|
||||
enable_cors = true
|
||||
|
||||
[cors]
|
||||
# 允许的源(用逗号分隔)
|
||||
origins = app://obsidian.md,capacitor://localhost,http://localhost
|
||||
# 允许发送认证信息
|
||||
credentials = true
|
||||
# 允许的请求头
|
||||
headers = accept, authorization, content-type, origin, referer
|
||||
# 允许的 HTTP 方法
|
||||
methods = GET, PUT, POST, HEAD, DELETE
|
||||
# CORS 预检请求缓存时间(秒)
|
||||
max_age = 3600
|
||||
305
dbSer/couchdb/generate_config.py
Normal file
305
dbSer/couchdb/generate_config.py
Normal file
@@ -0,0 +1,305 @@
|
||||
#!/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()
|
||||
17
dbSer/couchdb/stack.yml
Normal file
17
dbSer/couchdb/stack.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
couchdb:
|
||||
image: ${IMAGE_TAG:-couchdb:3.5}
|
||||
container_name: couchdb
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 5984:5984
|
||||
volumes:
|
||||
- ./etc/couchdb/local.d:/opt/couchdb/etc/local.d
|
||||
- ./couchdb:/opt/couchdb/data
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
PUID: 99
|
||||
PGID: 100
|
||||
UMASK: 0022
|
||||
COUCHDB_USER: your_custom_user
|
||||
COUCHDB_PASSWORD: your_strong_password
|
||||
Reference in New Issue
Block a user