#!/usr/bin/env python3 """Restore prod jar backup, then jar-uf only media-pull classes from patched jar.""" from __future__ import annotations import os import time from pathlib import Path import paramiko HOST = "47.236.66.9" USER = "root" PEM = Path(os.path.expandvars(r"%USERPROFILE%\.ssh\wyrs_shanghai_deploy.pem")) LOCAL_PATCHED = Path(r"F:\0000wyrs\Aliyun\target\remote-control-1.0.0.media-pull.jar") REMOTE_JAR = "/opt/wyrs-admin/remote-control-1.0.0.jar" REMOTE_WORK = "/tmp/media-pull-uf" # classes relative to BOOT-INF/classes we want to inject CLASS_GLOBS = [ "com/wyrs/pop/config/PopProperties.class", "com/wyrs/pop/entity/PopUserMedia.class", "com/wyrs/pop/repository/PopUserMediaRepository.class", "com/wyrs/pop/dto/PopMediaPullDtos*.class", "com/wyrs/pop/dto/PopSyncDtos*.class", "com/wyrs/pop/store/PopMediaPullFileStore*.class", "com/wyrs/pop/service/PopMediaPullService*.class", "com/wyrs/pop/service/PopSyncService.class", "com/wyrs/pop/controller/PopAdminMediaPullController.class", "com/wyrs/pop/controller/PopClientMediaPullController.class", "com/wyrs/pop/controller/PopClientOpsController.class", "com/wyrs/pop/controller/PopClientSyncController.class", "com/wyrs/pop/controller/PopAdminController.class", ] def load_key(pem: Path): for loader in ( paramiko.Ed25519Key.from_private_key_file, paramiko.RSAKey.from_private_key_file, paramiko.ECDSAKey.from_private_key_file, ): try: return loader(str(pem)) except Exception: continue raise SystemExit(f"cannot load {pem}") def run(client, cmd, timeout=180): print("$", cmd[:300]) _, stdout, stderr = client.exec_command(cmd, timeout=timeout) out = stdout.read().decode("utf-8", "replace") err = stderr.read().decode("utf-8", "replace") code = stdout.channel.recv_exit_status() if out.strip(): print(out[:2500]) if err.strip(): print("[stderr]", err[:1500]) return code, out, err def main(): client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(HOST, username=USER, pkey=load_key(PEM), timeout=30) sftp = client.open_sftp() # find bak code, out, _ = run( client, "ls -1t /opt/wyrs-admin/remote-control-1.0.0.jar.bak.pullpages.* 2>/dev/null | head -1", ) bak = out.strip() if not bak: raise SystemExit("no bak.pullpages found") print("restore from", bak) run(client, "systemctl stop wyrs-admin || true") run(client, f"cp -a '{bak}' {REMOTE_JAR}") # upload patched jar temporarily to extract classes tmp_patched = "/tmp/media-pull-patched-source.jar" print("upload patched jar for class extract...") sftp.put(str(LOCAL_PATCHED), tmp_patched) # extract selected classes and jar uf into prod jar script = f""" set -e rm -rf {REMOTE_WORK} mkdir -p {REMOTE_WORK}/BOOT-INF/classes cd {REMOTE_WORK} # extract all matching pop media-pull related classes from patched jar jar xf {tmp_patched} $(jar tf {tmp_patched} | grep -E 'BOOT-INF/classes/com/wyrs/pop/(config/PopProperties|entity/PopUserMedia|repository/PopUserMediaRepository|dto/PopMediaPullDtos|dto/PopSyncDtos|store/PopMediaPullFileStore|service/PopMediaPullService|service/PopSyncService|controller/PopAdminMediaPullController|controller/PopClientMediaPullController|controller/PopClientOpsController|controller/PopClientSyncController|controller/PopAdminController)' | tr '\\n' ' ') # list entries find BOOT-INF/classes -type f -name '*.class' | sort | tee /tmp/media-pull-entries.txt | wc -l # update prod jar jar uf {REMOTE_JAR} $(cat /tmp/media-pull-entries.txt | tr '\\n' ' ') # verify markers jar xf {REMOTE_JAR} BOOT-INF/classes/com/wyrs/pop/service/PopMediaPullService.class strings BOOT-INF/classes/com/wyrs/pop/service/PopMediaPullService.class | grep -E 'hasContactsConsent|contactsConsented|pullContacts' | head ls -lh {REMOTE_JAR} """ run(client, script, timeout=240) run(client, "systemctl start wyrs-admin") ok = False for i in range(40): time.sleep(3) code, out, _ = run( client, "curl -sS -m 3 -o /dev/null -w '%{http_code}' http://127.0.0.1:8090/api/admin/auth/login || true", timeout=20, ) if out.strip() not in ("", "000"): print("UP", out.strip(), "attempt", i) ok = True break if not ok: run(client, "journalctl -u wyrs-admin -n 60 --no-pager | tail -60") raise SystemExit("service failed to come up") # API verify verify = r''' python3 - <<'PY' import json, urllib.request env={} for line in open('/etc/wyrs-admin/app.env',encoding='utf-8',errors='replace'): line=line.strip() if not line or line.startswith('#') or '=' not in line: continue k,v=line.split('=',1); env[k.strip()]=v.strip().strip('"').strip("'") password=env.get('WYRS_ADMIN_PASSWORD') or '' body=json.dumps({'username':'admin','password':password}).encode() req=urllib.request.Request('http://127.0.0.1:8090/api/admin/auth/login',data=body,headers={'Content-Type':'application/json'},method='POST') with urllib.request.urlopen(req,timeout=20) as resp: token=json.loads(resp.read().decode())['data']['token'] for url in [ 'http://127.0.0.1:8090/api/admin/spark/media-pull?userId=1017', 'http://127.0.0.1:8090/api/admin/spark/media-pull?email=spark.test.01@wyrs.test.local', ]: req=urllib.request.Request(url,headers={'Authorization':'Bearer '+token}) with urllib.request.urlopen(req,timeout=20) as resp: print('GET', url, resp.read().decode()[:500]) req=urllib.request.Request( 'http://127.0.0.1:8090/api/admin/spark/media-pull?email=spark.test.01@wyrs.test.local', data=json.dumps({'pullContacts':False,'pullDocuments':False,'count':25}).encode(), headers={'Authorization':'Bearer '+token,'Content-Type':'application/json'}, method='PUT') with urllib.request.urlopen(req,timeout=20) as resp: print('PUT', resp.read().decode()[:600]) PY''' run(client, verify, timeout=60) sftp.close() client.close() print("RECOVER+HOTPATCH DONE") if __name__ == "__main__": main()