整理 NAS 部署目录并归档迁移工具

更新 Compose 和部署更新脚本以使用新的状态、凭据及日历目录布局;新增目录迁移、诊断和历史清理工具及本机演练测试,并记录 NAS 迁移与清理结果。
This commit is contained in:
weiyuqing committed 2026-10-17 22:32:58 +08:00
1 parent 1af63ff83a
commit 6e2a53ad20
14 files changed
+838 -8

No files matched your search

@@ -0,0 +1,90 @@
"""显式运行的本机真实容器演练,全部使用合成配置,不连接 NAS。"""
import ast
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import time
HERE = Path(__file__).resolve().parent
DOCKER = 'C:/Users/35861/AppData/Local/Programs/DockerDesktop/resources/bin/docker.exe'
PROJECT = 'health-directory-local-rebuild'
def main():
if sys.argv[1:] != ['--run']:
raise SystemExit('仅在本机运行时显式指定 --run')
with tempfile.TemporaryDirectory(prefix='health-directory-local-') as directory:
root = Path(directory)
for name in ['state', 'secrets', 'calendar']:
(root/name).mkdir()
(root/'secrets/keyring_master').write_text('synthetic-local-master-value-1234567890123456789', encoding='utf-8')
(root/'.env').write_text('MI_FITNESS_ADMIN_KEY=synthetic-local-admin\nMI_FITNESS_WEB_PASSWORD=synthetic-local-password\n', encoding='utf-8')
base = (HERE.parent/'compose.yml').read_text(encoding='utf-8').replace('name: mi-fitness-health', 'name: '+PROJECT)
over = (HERE.parent/'compose.override.yml').read_text(encoding='utf-8')
base = base.replace('./shared/state:', './state:').replace('./shared/state/data:', './state/data:').replace('./backend/secrets/', './secrets/')
over = over.replace('./web/calendar:', './calendar:')
over = over.replace(" - '127.0.0.1:18322:8322'\n - '192.168.31.201:18322:8322'", " - '127.0.0.1:0:8322'")
over += '\nnetworks:\n default:\n internal: true\n'
def save():
(root/'compose.yml').write_text(base, encoding='utf-8')
(root/'compose.override.yml').write_text(over, encoding='utf-8')
save()
command = [DOCKER, '--context', 'desktop-linux', 'compose', '--project-directory', str(root),
'-p', PROJECT, '-f', str(root/'compose.yml'), '-f', str(root/'compose.override.yml')]
def compose(*args):
return subprocess.check_output([*command, *args], stderr=subprocess.STDOUT)
def inspect():
ids = compose('ps', '-aq').decode().split()
return json.loads(subprocess.check_output([DOCKER, '--context', 'desktop-linux', 'inspect', *ids]))
def ready():
deadline = time.monotonic()+90
while time.monotonic() < deadline:
items = inspect()
if len(items)==3 and all(i['State']['Running'] and i['State'].get('Health',{}).get('Status','healthy')=='healthy' for i in items):
return
time.sleep(1)
raise RuntimeError('本机容器就绪超时')
module = ast.parse((HERE.parent/'query-summary/check-ready.py').read_text(encoding='utf-8'))
probe = next(ast.literal_eval(n.value) for n in module.body if isinstance(n, ast.Assign)
and any(isinstance(t, ast.Name) and t.id=='probe' for t in n.targets))
try:
compose('up','-d','--no-build','--pull','never','backend','web','worker')
ready()
compose('stop','-t','30','worker','web','backend')
for source, target in [('state','shared/state'),('secrets','backend/secrets'),('calendar','web/calendar')]:
destination=root/target
destination.parent.mkdir(exist_ok=True)
(root/source).rename(destination)
base=base.replace('./state:', './shared/state:').replace('./state/data:', './shared/state/data:').replace('./secrets/', './backend/secrets/')
over=over.replace('./calendar:', './web/calendar:')
save()
compose('config','--quiet')
compose('up','-d','--no-build','--pull','never','backend','web','worker')
ready()
print('容器已标记就绪,立即执行实际网页探针:', flush=True)
result=subprocess.run([DOCKER,'--context','desktop-linux','exec','-i',PROJECT+'-web-1','python','-'],
input=probe.encode('utf-8'),capture_output=True)
print(result.stdout.decode('utf-8',errors='replace'), flush=True)
print(result.stderr.decode('utf-8',errors='replace'), flush=True)
if result.returncode:
print('首次探针失败,五秒后重复同一只读探针以区分启动时序。', flush=True)
time.sleep(5)
retry=subprocess.run([DOCKER,'--context','desktop-linux','exec','-i',PROJECT+'-web-1','python','-'],
input=probe.encode('utf-8'),capture_output=True)
print(retry.stdout.decode('utf-8',errors='replace'),flush=True)
print(retry.stderr.decode('utf-8',errors='replace'),flush=True)
if retry.returncode:
raise RuntimeError('本机网页探针持续失败')
print('LOCAL_REAL_REBUILD_AND_WEB_PROBE_OK',flush=True)
except subprocess.CalledProcessError as error:
# 此处仅含本机合成配置,无真实凭据。
print(error.output.decode('utf-8',errors='replace'),flush=True)
raise
finally:
compose('down')
if __name__ == '__main__':
main()