fix(deploy): 保留旧部署鉴权与命名卷配置

This commit is contained in:
2026-10-03 07:58:35 +08:00
parent e29d203bb7
commit 00aaad6374
2 changed files with 25 additions and 6 deletions
+16 -4
View File
@@ -99,23 +99,24 @@ def merge(base, local, remote, path=()):
return clone(local) return clone(local)
def delta(remote, merged): def delta(remote, merged, path=()):
if remote == merged: return MISSING if remote == merged: return MISSING
if isinstance(remote, dict) and isinstance(merged, dict): if isinstance(remote, dict) and isinstance(merged, dict):
result = {} result = {}
for key in merged: 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 if value is not MISSING: result[key] = value
# Removal cannot be safely represented by an ordinary Compose override. # Removal cannot be safely represented by an ordinary Compose override.
if set(remote) - set(merged): 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 result
return copy.deepcopy(merged) return copy.deepcopy(merged)
def serialize(value, key=''): def serialize(value, key=''):
if isinstance(value, dict): 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 {k: serialize(v, k) for k, v in value.items()}
return value 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())) local = canonical(yaml.safe_load((root / 'docker-compose.yml').read_text()))
remote_text = git('show', upstream + ':docker-compose.yml') remote_text = git('show', upstream + ':docker-compose.yml')
remote = canonical(yaml.safe_load(remote_text)) 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) merged = merge(base, local, remote)
override = delta(remote, merged) override = delta(remote, merged)
with tempfile.NamedTemporaryFile(mode='w', dir=root, prefix='.update-compose-', suffix='.json', delete=False) as candidate: with tempfile.NamedTemporaryFile(mode='w', dir=root, prefix='.update-compose-', suffix='.json', delete=False) as candidate:
+9 -2
View File
@@ -20,7 +20,7 @@ with tempfile.TemporaryDirectory(prefix='easyai-legacy-update-') as directory:
base = Path(directory) base = Path(directory)
root = base / 'easyai' root = base / 'easyai'
root.mkdir() 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 / '.gitignore').write_text('.env\ndata/\n')
(root / '.env').write_text('VERSION=old\nCONFIG_JWT_SECRET=keep-this-private-key\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: 旧部署基准']]: 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} - MODEL_CONVERSION_WORKER_TOKEN=${MODEL_CONVERSION_WORKER_TOKEN}
watchtower: watchtower:
image: watcher:new image: watcher:new
environment:
- WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_KEY}
easyai-updater: easyai-updater:
image: updater:new image: updater:new
environment: environment:
@@ -55,6 +57,8 @@ with tempfile.TemporaryDirectory(prefix='easyai-legacy-update-') as directory:
image: worker:new image: worker:new
networks: networks:
easyai: {} easyai: {}
volumes:
accepted-data: {}
''') ''')
(seed / 'scripts').mkdir() (seed / 'scripts').mkdir()
(seed / 'scripts/init-security-env.sh').write_text('# modern security initializer\n') (seed / 'scripts/init-security-env.sh').write_text('# modern security initializer\n')
@@ -74,7 +78,8 @@ networks:
(root / 'scripts').mkdir() (root / 'scripts').mkdir()
(root / 'scripts/init-security-env.sh').write_text('# local legacy initializer\n') (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') (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 = base / 'bin'
bins.mkdir() bins.mkdir()
docker = bins / 'docker' 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']['networks']['easyai']['ipv4_address']=='172.21.0.31'
assert config['services']['easyai-updater']['environment']['UPDATE_MANIFEST_FILE']=='/state/custom.json' assert config['services']['easyai-updater']['environment']['UPDATE_MANIFEST_FILE']=='/state/custom.json'
assert 'model-conversion-worker' in config['services'] 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) repeat=run(['python3',str(source/'scripts/prepare-update-repository.py'),str(root)],root,env)
assert repeat.returncode==0 assert repeat.returncode==0
assert len(list((root/'data/deployment-backups').glob('legacy-update-*')))==1 assert len(list((root/'data/deployment-backups').glob('legacy-update-*')))==1