import paramiko client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect('152.136.232.83', port=22, username='root', password='Jianhua105', timeout=10) def run(cmd): print(f"\n>>> {cmd[:120]}") stdin, stdout, stderr = client.exec_command(cmd, timeout=20) out = stdout.read().decode('utf-8', errors='replace').strip() err = stderr.read().decode('utf-8', errors='replace').strip() if out: print(out[-600:]) if err: print(f"ERR: {err[-200:]}") return out # Backup run("cp /www/wwwroot/ipzhinengti/server.js /www/wwwroot/ipzhinengti/server.js.bak.update") run("cp /www/wwwroot/ipzhinengti/public/admin.html /www/wwwroot/ipzhinengti/public/admin.html.bak.update") # === Patch 1: server.js - add UPDATE_COS keys to publicKeys and compute update_url === patch_server = r'''const fs = require('fs'); const filePath = '/www/wwwroot/ipzhinengti/server.js'; let content = fs.readFileSync(filePath, 'utf-8'); if (content.includes('UPDATE_COS_BUCKET')) { console.log('SKIP: already patched'); process.exit(0); } // 1. Add UPDATE_COS keys to publicKeys array const marker1 = " 'cloud_beauty_enabled'"; const replacement1 = marker1 + ",\n 'UPDATE_COS_BUCKET',\n 'UPDATE_COS_REGION',\n 'UPDATE_COS_PATH'"; content = content.replace(marker1, replacement1); // 2. Replace the res.json to add update_url computation const marker2 = "res.json({ success: true, config })"; const replacement2 = `const cosBucket = config.UPDATE_COS_BUCKET || '' const cosRegion = config.UPDATE_COS_REGION || '' const updatePath = config.UPDATE_COS_PATH || 'updates' let updateUrl = '' if (cosBucket && cosRegion) { updateUrl = 'https://' + cosBucket + '.cos.' + cosRegion + '.myqcloud.com/' + updatePath } res.json({ success: true, config, update_url: updateUrl })`; content = content.replace(marker2, replacement2); fs.writeFileSync(filePath, content, 'utf-8'); console.log('OK: patched server.js'); ''' sftp = client.open_sftp() with sftp.open('/tmp/patch_server.js', 'w') as f: f.write(patch_server) sftp.close() run("node /tmp/patch_server.js") run("node --check /www/wwwroot/ipzhinengti/server.js 2>&1") # === Patch 2: admin.html - add UPDATE_COS config section === patch_admin = r'''const fs = require('fs'); const filePath = '/www/wwwroot/ipzhinengti/public/admin.html'; let content = fs.readFileSync(filePath, 'utf-8'); if (content.includes('UPDATE_COS_BUCKET')) { console.log('SKIP: already patched'); process.exit(0); } // 1. Add to CONFIG_KEYS array const keysMarker = "'cloud_beauty_enabled'"; content = content.replace(keysMarker, keysMarker + ", 'UPDATE_COS_BUCKET', 'UPDATE_COS_REGION', 'UPDATE_COS_PATH'"); // 2. Add save values const saveMarker = "cloud_beauty_enabled: document.getElementById('cfg_cloud_beauty_enabled').checked ? 'true' : 'false',"; const saveReplacement = saveMarker + "\n UPDATE_COS_BUCKET: document.getElementById('cfg_UPDATE_COS_BUCKET').value,\n UPDATE_COS_REGION: document.getElementById('cfg_UPDATE_COS_REGION').value,\n UPDATE_COS_PATH: document.getElementById('cfg_UPDATE_COS_PATH').value,"; content = content.replace(saveMarker, saveReplacement); // 3. Add HTML section after cloud_beauty section const htmlMarker = "document.getElementById('cfg_cloud_beauty_enabled').checked = config.cloud_beauty_enabled === 'true';"; const htmlSection = htmlMarker + ` document.getElementById('cfg_UPDATE_COS_BUCKET').value = config.UPDATE_COS_BUCKET || ''; document.getElementById('cfg_UPDATE_COS_REGION').value = config.UPDATE_COS_REGION || ''; document.getElementById('cfg_UPDATE_COS_PATH').value = config.UPDATE_COS_PATH || '';`; content = content.replace(htmlMarker, htmlSection); // 4. Add form HTML - find the closing tag after cloud_beauty section const formMarker = ""; const formSection = formMarker + `