补齐三页查询/开关/授权/数量布局,并附上线上 static-override 与 jar 热补部署脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
157 lines
6.1 KiB
Python
157 lines
6.1 KiB
Python
#!/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()
|