import pathlib,re,json,secrets,os,subprocess,hashlib root=pathlib.Path('/home/admin/easyai');spec={'watchtowerBlock': ' watchtower:\n image: ${WATCHTOWER_IMAGE:-registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - ${DOCKER_CONFIG_FILE:-/root/.docker/config.json}:/config.json:ro\n # 新安装只负责 easyai-updater 自身;存量环境未配置目标时保持原来的全容器扫描方式。\n command: --debug ${WATCHTOWER_UPDATE_TARGET:-}\n environment:\n - WATCHTOWER_POLL_INTERVAL=${SYSTEM_UPDATER_UPDATE_INTERVAL_SECONDS:-86400}\n - WATCHTOWER_CLEANUP=${WATCHTOWER_CLEANUP:-false}\n - WATCHTOWER_HTTP_API_HOST=0.0.0.0\n - WATCHTOWER_HTTP_API_ENDPOINTS=health,update,metrics,events,history\n - WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_KEY}\n - WATCHTOWER_HTTP_API_EVENTS_TOKEN=${WATCHTOWER_HTTP_API_EVENTS_TOKEN}\n - WATCHTOWER_HTTP_API_PERIODIC_POLLS=${WATCHTOWER_HTTP_API_PERIODIC_POLLS:-false}\n restart: always\n labels:\n - "com.centurylinklabs.watchtower.enable=false"\n# ports:\n# - "${WATCHTOWER_PORT}:8080"\n read_only: true\n networks:\n easyai:\n ipv4_address: 172.21.0.9\n # 日志大小设置,避免日志文件过大\n logging:\n driver: "json-file"\n options:\n max-size: "100m"\n max-file: "10"\n', 'healthcheckBlocks': {'easyai-server': ' healthcheck:\n test: ["CMD-SHELL", "node -e \\"require(\'http\').get(\'http://127.0.0.1:3001/health/update-ready\', (res) => { res.resume(); process.exit(res.statusCode === 200 ? 0 : 1); }).on(\'error\', () => process.exit(1))\\""]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 60s\n', 'easyai-web': ' healthcheck:\n test: ["CMD-SHELL", "node -e \\"require(\'http\').get(\'http://127.0.0.1:3010/health\', (res) => { res.resume(); process.exit(res.statusCode === 200 ? 0 : 1); }).on(\'error\', () => process.exit(1))\\""]\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 30s\n # 资源限制,防止内存溢出\n'}} p=root/'.env';original=p.read_text();rows=original.splitlines();env={line.partition('=')[0]:line.partition('=')[2].strip().strip('"').strip("'") for line in rows if '=' in line and not line.lstrip().startswith('#')} updates={'SYSTEM_UPDATE_MODE':'legacy_watchtower','SYSTEM_UPDATE_DEPLOYMENT_ID':'newfound-101200186210','WATCHTOWER_IMAGE':'registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3','WATCHTOWER_UPDATE_TARGET':'easyai-server easyai-wsgateway easyai-web','WATCHTOWER_HTTP_API_PERIODIC_POLLS':'false','WATCHTOWER_CLEANUP':'false','WATCHTOWER_CORE_UPDATE_ENABLED':'true','WATCHTOWER_API_URL':'http://watchtower:8080'} for key in ['WATCHTOWER_API_KEY','WATCHTOWER_HTTP_API_EVENTS_TOKEN']: updates[key]=env[key] if len(env.get(key,''))>=32 else secrets.token_hex(32) if updates['WATCHTOWER_API_KEY']==updates['WATCHTOWER_HTTP_API_EVENTS_TOKEN']:updates['WATCHTOWER_HTTP_API_EVENTS_TOKEN']=secrets.token_hex(32) if not env.get('COMPOSE_FILE'):updates['COMPOSE_FILE']='docker-compose.yml:docker-compose.ice-v1.yml' added=list(updates) new=[k+'='+updates.pop(k) if k in updates else line for line in rows for k in [line.partition('=')[0]]];new.extend(k+'='+v for k,v in updates.items());stat=p.stat();p.write_text('\n'.join(new)+'\n');p.chmod(0o600);os.chown(p,stat.st_uid,stat.st_gid) p=root/'docker-compose.yml';s=p.read_text();stat=p.stat() def bounds(name): start=s.index(' '+name+':');match=re.search(r'^ [A-Za-z0-9_-]+:',s[start+3:],re.M);return start,start+3+match.start() if match else len(s) a,b=bounds('watchtower');wt=spec['watchtowerBlock'] if not pathlib.Path('/root/.docker/config.json').is_file():wt='\n'.join(line for line in wt.splitlines() if '/config.json:ro' not in line)+'\n' s=s[:a]+wt+s[b:] for name,health in spec['healthcheckBlocks'].items(): a,b=bounds(name);part=s[a:b] if ' healthcheck:' not in part:s=s[:b]+health+s[b:] p.write_text(s);os.chown(p,stat.st_uid,stat.st_gid) backup=root/'data/deployment-backups/online-update-progress-20261002';(backup/'configuration-change.json').write_text(json.dumps({'envKeys':added,'mode':'legacy_watchtower','composeBeforeSha256':hashlib.sha256((backup/'docker-compose.yml').read_bytes()).hexdigest(),'composeAfterSha256':hashlib.sha256(p.read_bytes()).hexdigest(),'iceSha256':hashlib.sha256((root/'docker-compose.ice-v1.yml').read_bytes()).hexdigest()},indent=2));(backup/'configuration-change.json').chmod(0o600) x=json.loads(subprocess.check_output(['docker','compose','config','--format','json'],cwd=root));assert 'easyai-updater' not in x['services'];assert not x['services']['watchtower'].get('ports');assert x['services']['easyai-server']['environment']['SYSTEM_UPDATE_MODE']=='legacy_watchtower';assert len(x['services']['watchtower']['environment']['WATCHTOWER_HTTP_API_TOKEN'])>=32 print(json.dumps({'configured':True,'mode':'legacy_watchtower','updaterContainerRequested':False,'watchtowerPublishedPorts':[],'changedEnvKeys':added}))