From 00aaad63744fbe2e8befa38eb7afbb4c545cd99c Mon Sep 17 00:00:00 2001 From: wangbo Date: Sat, 3 Oct 2026 07:58:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20=E4=BF=9D=E7=95=99=E6=97=A7?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E9=89=B4=E6=9D=83=E4=B8=8E=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=B7=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/prepare-update-repository.py | 20 ++++++++++++++++---- scripts/test-legacy-deployment-update.py | 11 +++++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/scripts/prepare-update-repository.py b/scripts/prepare-update-repository.py index 0ad1009..c1393d6 100644 --- a/scripts/prepare-update-repository.py +++ b/scripts/prepare-update-repository.py @@ -99,23 +99,24 @@ def merge(base, local, remote, path=()): return clone(local) -def delta(remote, merged): +def delta(remote, merged, path=()): if remote == merged: return MISSING if isinstance(remote, dict) and isinstance(merged, dict): result = {} for key in merged: - value = delta(remote.get(key, MISSING), merged[key]) + value = delta(remote.get(key, MISSING), merged[key], path + (key,)) if value is not MISSING: result[key] = value # Removal cannot be safely represented by an ordinary Compose override. if set(remote) - set(merged): - sys.exit('❌ 本地配置删除了新版字段,无法安全合并;尚未修改配置或容器') + fields = ', '.join('.'.join(path + (key,)) for key in sorted(set(remote) - set(merged))) + sys.exit('❌ 本地配置删除了新版字段,无法安全合并;尚未修改配置或容器: ' + fields) return result return copy.deepcopy(merged) def serialize(value, key=''): if isinstance(value, dict): - if key == 'volumes': return list(value.values()) + if key == 'volumes' and value and all(isinstance(v, str) for v in value.values()): return list(value.values()) return {k: serialize(v, k) for k, v in value.items()} return value @@ -124,6 +125,17 @@ base = canonical(yaml.safe_load(git('show', 'HEAD:docker-compose.yml'))) local = canonical(yaml.safe_load((root / 'docker-compose.yml').read_text())) remote_text = git('show', upstream + ':docker-compose.yml') remote = canonical(yaml.safe_load(remote_text)) +# Early managed deployments removed Watchtower's old token while disabling its +# HTTP trigger. Current deployments require a shared internal token; restore the +# upstream reference, whose value is initialized by update.sh, rather than +# preserving an unauthenticated monitor. Other removals still stop adoption. +monitor = local.get('services', {}).get('watchtower', {}).get('environment', {}) +old_monitor = base.get('services', {}).get('watchtower', {}).get('environment', {}) +new_monitor = remote.get('services', {}).get('watchtower', {}).get('environment', {}) +token_key = 'WATCHTOWER_HTTP_API_TOKEN' +if token_key in old_monitor and token_key not in monitor and token_key in new_monitor: + monitor = local['services']['watchtower'].setdefault('environment', {}) + monitor[token_key] = new_monitor[token_key] merged = merge(base, local, remote) override = delta(remote, merged) with tempfile.NamedTemporaryFile(mode='w', dir=root, prefix='.update-compose-', suffix='.json', delete=False) as candidate: diff --git a/scripts/test-legacy-deployment-update.py b/scripts/test-legacy-deployment-update.py index 2185225..64d5fc7 100644 --- a/scripts/test-legacy-deployment-update.py +++ b/scripts/test-legacy-deployment-update.py @@ -20,7 +20,7 @@ with tempfile.TemporaryDirectory(prefix='easyai-legacy-update-') as directory: base = Path(directory) root = base / 'easyai' root.mkdir() - (root / 'docker-compose.yml').write_text('services:\n easyai-server:\n image: server:old\n environment:\n - EXISTING=keep\n watchtower:\n image: watcher:old\n') + (root / 'docker-compose.yml').write_text('services:\n easyai-server:\n image: server:old\n environment:\n - EXISTING=keep\n watchtower:\n image: watcher:old\n environment:\n - WATCHTOWER_HTTP_API_TOKEN=${LEGACY_TOKEN}\n') (root / '.gitignore').write_text('.env\ndata/\n') (root / '.env').write_text('VERSION=old\nCONFIG_JWT_SECRET=keep-this-private-key\n') for args in [['git','init','-q'], ['git','config','user.name','Update test'], ['git','config','user.email','test@example.invalid'], ['git','add','.'], ['git','commit','-qm','test: 旧部署基准']]: @@ -41,6 +41,8 @@ with tempfile.TemporaryDirectory(prefix='easyai-legacy-update-') as directory: - MODEL_CONVERSION_WORKER_TOKEN=${MODEL_CONVERSION_WORKER_TOKEN} watchtower: image: watcher:new + environment: + - WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_KEY} easyai-updater: image: updater:new environment: @@ -55,6 +57,8 @@ with tempfile.TemporaryDirectory(prefix='easyai-legacy-update-') as directory: image: worker:new networks: easyai: {} +volumes: + accepted-data: {} ''') (seed / 'scripts').mkdir() (seed / 'scripts/init-security-env.sh').write_text('# modern security initializer\n') @@ -74,7 +78,8 @@ networks: (root / 'scripts').mkdir() (root / 'scripts/init-security-env.sh').write_text('# local legacy initializer\n') (root / 'demo.example.conf').write_text('# custom nginx source; active copy elsewhere\n') - original = (root / 'docker-compose.yml').read_bytes() + original = (root / 'docker-compose.yml').read_bytes().replace(b' environment:\n - WATCHTOWER_HTTP_API_TOKEN=${LEGACY_TOKEN}\n', b'') + (root / 'docker-compose.yml').write_bytes(original) bins = base / 'bin' bins.mkdir() docker = bins / 'docker' @@ -110,6 +115,8 @@ networks: assert config['services']['easyai-updater']['networks']['easyai']['ipv4_address']=='172.21.0.31' assert config['services']['easyai-updater']['environment']['UPDATE_MANIFEST_FILE']=='/state/custom.json' assert 'model-conversion-worker' in config['services'] + assert 'accepted-data' in config['volumes'] + assert 'WATCHTOWER_HTTP_API_TOKEN' in config['services']['watchtower']['environment'] repeat=run(['python3',str(source/'scripts/prepare-update-repository.py'),str(root)],root,env) assert repeat.returncode==0 assert len(list((root/'data/deployment-backups').glob('legacy-update-*')))==1