From 4a84d95d1b4dcbfb6410b85bd24ef5221165a5e1 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Fri, 26 Jun 2026 15:20:22 +0800 Subject: [PATCH] Fix deploy: use base64 for server.py, simple restart --- scripts/deploy_cmd_agent.sh | 214 ++---------------------------------- 1 file changed, 7 insertions(+), 207 deletions(-) diff --git a/scripts/deploy_cmd_agent.sh b/scripts/deploy_cmd_agent.sh index 0e8c8bf72..62dea8459 100644 --- a/scripts/deploy_cmd_agent.sh +++ b/scripts/deploy_cmd_agent.sh @@ -1,214 +1,14 @@ #!/bin/bash set -e -echo "[1/5] Creating agent directory..." +echo 'Deploying CMD Agent...' mkdir -p /opt/xiaoxia-cmd-agent - -echo "[2/5] Writing server.py..." -cat > /opt/xiaoxia-cmd-agent/server.py << 'SERVEREOF' -#!/usr/bin/env python3 -import http.server, subprocess, json, os, sys, secrets -from urllib.parse import urlparse, parse_qs - -LISTEN_PORT = 18888 -AUTH_TOKEN = "xsa-" + secrets.token_hex(16) -LOG_FILE = "/var/log/xiaoxia-cmd-agent.log" - -class Handler(http.server.BaseHTTPRequestHandler): - def log_message(self, fmt, *args): - try: - with open(LOG_FILE, "a") as f: - f.write("%s - %s\n" % (self.log_date_time_string(), fmt % args)) - except: pass - - def check_auth(self): - t = self.headers.get("Authorization", "") - if t != AUTH_TOKEN: - self._j({"error": "unauthorized"}, 401); return False - return True - - def _j(self, data, code=200): - body = json.dumps(data, ensure_ascii=False).encode() - self.send_response(code) - self.send_header("Content-Type", "application/json; charset=utf-8") - self.send_header("Content-Length", str(len(body))) - self.end_headers() - self.wfile.write(body) - - def do_GET(self): - if not self.check_auth(): return - if self.path.startswith("/download"): - self._download(); return - if self.path == "/status": - self._j({"status":"ok","hostname":os.uname().nodename,"port":LISTEN_PORT}) - else: - self._j({"error":"use /status or /download?path=xxx"}, 404) - - def do_POST(self): - if not self.check_auth(): return - try: length = int(self.headers.get("Content-Length", 0)) - except: self._j({"error":"bad content-length"},400); return - if self.path == "/exec": self._exec(length) - elif self.path == "/list": self._list(length) - elif self.path == "/upload": self._upload(length) - elif self.path == "/mkdir": self._mkdir(length) - else: self._j({"error":"unknown"}, 404) - - def do_DELETE(self): - if not self.check_auth(): return - try: length = int(self.headers.get("Content-Length", 0)) - except: self._j({"error":"bad"},400); return - body = json.loads(self.rfile.read(length)) if length > 0 else {} - fp = body.get("path","") - if not fp: self._j({"error":"path required"},400); return - try: - import shutil - if os.path.isdir(fp): shutil.rmtree(fp) - else: os.remove(fp) - self._j({"ok":True,"deleted":fp}) - except Exception as e: self._j({"error":str(e)},500) - - def _exec(self, length): - try: data = json.loads(self.rfile.read(length)) if length>0 else {} - except: self._j({"error":"invalid json"},400); return - cmd = data.get("command","") - timeout = min(data.get("timeout",30), 120) - if not cmd: self._j({"error":"command required"},400); return - try: - r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) - self._j({"exit_code":r.returncode,"stdout":r.stdout[-100000:] if r.stdout else "","stderr":r.stderr[-10000:] if r.stderr else ""}) - except subprocess.TimeoutExpired: self._j({"error":f"timeout {timeout}s"},408) - except Exception as e: self._j({"error":str(e)},500) - - def _list(self, length): - try: data = json.loads(self.rfile.read(length)) if length>0 else {} - except: self._j({"error":"invalid json"},400); return - path = data.get("path",".") - try: - entries = [] - for name in sorted(os.listdir(path)): - fp2 = os.path.join(path, name) - try: - st = os.stat(fp2) - entries.append({"name":name,"is_dir":os.path.isdir(fp2),"size":st.st_size,"mtime":st.st_mtime}) - except: entries.append({"name":name,"is_dir":False,"size":0,"mtime":0}) - self._j({"path":path,"entries":entries}) - except Exception as e: self._j({"error":str(e)},500) - - def _upload(self, length): - ct = self.headers.get("Content-Type","") - if "multipart/form-data" not in ct: - self._j({"error":"multipart required"},400); return - try: - boundary = ct.split("boundary=")[1].strip() - body = self.rfile.read(length) - parts = body.split(b"--" + boundary.encode()) - fname = fdata = None - for part in parts: - if b"filename=" in part: - h, _, content = part.partition(b"\r\n\r\n") - for line in h.decode(errors="replace").split("\r\n"): - if 'filename="' in line: fname = line.split('filename="')[1].split('"')[0] - fdata = content.rstrip(b"\r\n") - if fname is None or fdata is None: - self._j({"error":"no file"},400); return - dest_dir = "/tmp" - dest = os.path.join(dest_dir, fname) - os.makedirs(dest_dir, exist_ok=True) - with open(dest,"wb") as f: f.write(fdata) - self._j({"ok":True,"path":dest,"size":len(fdata)}) - except Exception as e: self._j({"error":str(e)},500) - - def _mkdir(self, length): - try: data = json.loads(self.rfile.read(length)) if length>0 else {} - except: self._j({"error":"invalid json"},400); return - path = data.get("path","") - if not path: self._j({"error":"path required"},400); return - try: os.makedirs(path, exist_ok=True); self._j({"ok":True,"created":path}) - except Exception as e: self._j({"error":str(e)},500) - - def _download(self): - if not self.check_auth(): return - qs = parse_qs(urlparse(self.path).query) - fp = qs.get("path",[""])[0] - if not fp or not os.path.isfile(fp): - self._j({"error":"not found"},404); return - try: - with open(fp,"rb") as f: data = f.read() - self.send_response(200) - self.send_header("Content-Type","application/octet-stream") - self.send_header("Content-Length",str(len(data))) - self.end_headers() - self.wfile.write(data) - except Exception as e: self._j({"error":str(e)},500) - -def main(): - global AUTH_TOKEN - installed_flag = "/etc/xiaoxia-cmd-agent.installed" - if not os.path.exists(installed_flag): - with open("/etc/xiaoxia-cmd-agent.token","w") as f: f.write(AUTH_TOKEN) - os.chmod("/etc/xiaoxia-cmd-agent.token", 0o600) - svc = "[Unit]\nDescription=Xiaoxia Command Agent\nAfter=network.target\n[Service]\nType=simple\nExecStart=/usr/bin/python3 /opt/xiaoxia-cmd-agent/server.py\nRestart=always\nRestartSec=5\n[Install]\nWantedBy=multi-user.target\n" - with open("/etc/systemd/system/xiaoxia-cmd-agent.service","w") as f: f.write(svc) - os.system("systemctl daemon-reload && systemctl enable xiaoxia-cmd-agent") - with open(installed_flag,"w") as f: f.write("1") - print(f"Service installed. TOKEN: {AUTH_TOKEN}") - else: - with open("/etc/xiaoxia-cmd-agent.token") as f: - AUTH_TOKEN = f.read().strip() - print(f"Using existing token: {AUTH_TOKEN}") - server = http.server.HTTPServer(("127.0.0.1", LISTEN_PORT), Handler) - print(f"Listening: 127.0.0.1:{LISTEN_PORT}") - server.serve_forever() - -if __name__ == "__main__": - main() - -SERVEREOF - -echo "[3/5] Starting agent service..." -python3 /opt/xiaoxia-cmd-agent/server.py & +echo 'IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwppbXBvcnQgaHR0cC5zZXJ2ZXIsIHN1YnByb2Nlc3MsIGpzb24sIG9zLCBzeXMsIHNlY3JldHMKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlLCBwYXJzZV9xcwoKTElTVEVOX1BPUlQgPSAxODg4OApBVVRIX1RPS0VOID0gInhzYS0iICsgc2VjcmV0cy50b2tlbl9oZXgoMTYpCkxPR19GSUxFID0gIi92YXIvbG9nL3hpYW94aWEtY21kLWFnZW50LmxvZyIKCmNsYXNzIEhhbmRsZXIoaHR0cC5zZXJ2ZXIuQmFzZUhUVFBSZXF1ZXN0SGFuZGxlcik6CiAgICBkZWYgbG9nX21lc3NhZ2Uoc2VsZiwgZm10LCAqYXJncyk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICB3aXRoIG9wZW4oTE9HX0ZJTEUsICJhIikgYXMgZjoKICAgICAgICAgICAgICAgIGYud3JpdGUoIiVzIC0gJXNcbiIgJSAoc2VsZi5sb2dfZGF0ZV90aW1lX3N0cmluZygpLCBmbXQgJSBhcmdzKSkKICAgICAgICBleGNlcHQ6IHBhc3MKCiAgICBkZWYgY2hlY2tfYXV0aChzZWxmKToKICAgICAgICB0ID0gc2VsZi5oZWFkZXJzLmdldCgiQXV0aG9yaXphdGlvbiIsICIiKQogICAgICAgIGlmIHQgIT0gQVVUSF9UT0tFTjoKICAgICAgICAgICAgc2VsZi5faih7ImVycm9yIjogInVuYXV0aG9yaXplZCJ9LCA0MDEpOyByZXR1cm4gRmFsc2UKICAgICAgICByZXR1cm4gVHJ1ZQoKICAgIGRlZiBfaihzZWxmLCBkYXRhLCBjb2RlPTIwMCk6CiAgICAgICAgYm9keSA9IGpzb24uZHVtcHMoZGF0YSwgZW5zdXJlX2FzY2lpPUZhbHNlKS5lbmNvZGUoKQogICAgICAgIHNlbGYuc2VuZF9yZXNwb25zZShjb2RlKQogICAgICAgIHNlbGYuc2VuZF9oZWFkZXIoIkNvbnRlbnQtVHlwZSIsICJhcHBsaWNhdGlvbi9qc29uOyBjaGFyc2V0PXV0Zi04IikKICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCJDb250ZW50LUxlbmd0aCIsIHN0cihsZW4oYm9keSkpKQogICAgICAgIHNlbGYuZW5kX2hlYWRlcnMoKQogICAgICAgIHNlbGYud2ZpbGUud3JpdGUoYm9keSkKCiAgICBkZWYgZG9fR0VUKHNlbGYpOgogICAgICAgIGlmIG5vdCBzZWxmLmNoZWNrX2F1dGgoKTogcmV0dXJuCiAgICAgICAgaWYgc2VsZi5wYXRoLnN0YXJ0c3dpdGgoIi9kb3dubG9hZCIpOgogICAgICAgICAgICBzZWxmLl9kb3dubG9hZCgpOyByZXR1cm4KICAgICAgICBpZiBzZWxmLnBhdGggPT0gIi9zdGF0dXMiOgogICAgICAgICAgICBzZWxmLl9qKHsic3RhdHVzIjoib2siLCJob3N0bmFtZSI6b3MudW5hbWUoKS5ub2RlbmFtZSwicG9ydCI6TElTVEVOX1BPUlR9KQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNlbGYuX2ooeyJlcnJvciI6InVzZSAvc3RhdHVzIG9yIC9kb3dubG9hZD9wYXRoPXh4eCJ9LCA0MDQpCgogICAgZGVmIGRvX1BPU1Qoc2VsZik6CiAgICAgICAgaWYgbm90IHNlbGYuY2hlY2tfYXV0aCgpOiByZXR1cm4KICAgICAgICB0cnk6IGxlbmd0aCA9IGludChzZWxmLmhlYWRlcnMuZ2V0KCJDb250ZW50LUxlbmd0aCIsIDApKQogICAgICAgIGV4Y2VwdDogc2VsZi5faih7ImVycm9yIjoiYmFkIGNvbnRlbnQtbGVuZ3RoIn0sNDAwKTsgcmV0dXJuCiAgICAgICAgaWYgc2VsZi5wYXRoID09ICIvZXhlYyI6IHNlbGYuX2V4ZWMobGVuZ3RoKQogICAgICAgIGVsaWYgc2VsZi5wYXRoID09ICIvbGlzdCI6IHNlbGYuX2xpc3QobGVuZ3RoKQogICAgICAgIGVsaWYgc2VsZi5wYXRoID09ICIvdXBsb2FkIjogc2VsZi5fdXBsb2FkKGxlbmd0aCkKICAgICAgICBlbGlmIHNlbGYucGF0aCA9PSAiL21rZGlyIjogc2VsZi5fbWtkaXIobGVuZ3RoKQogICAgICAgIGVsc2U6IHNlbGYuX2ooeyJlcnJvciI6InVua25vd24ifSwgNDA0KQoKICAgIGRlZiBkb19ERUxFVEUoc2VsZik6CiAgICAgICAgaWYgbm90IHNlbGYuY2hlY2tfYXV0aCgpOiByZXR1cm4KICAgICAgICB0cnk6IGxlbmd0aCA9IGludChzZWxmLmhlYWRlcnMuZ2V0KCJDb250ZW50LUxlbmd0aCIsIDApKQogICAgICAgIGV4Y2VwdDogc2VsZi5faih7ImVycm9yIjoiYmFkIn0sNDAwKTsgcmV0dXJuCiAgICAgICAgYm9keSA9IGpzb24ubG9hZHMoc2VsZi5yZmlsZS5yZWFkKGxlbmd0aCkpIGlmIGxlbmd0aCA+IDAgZWxzZSB7fQogICAgICAgIGZwID0gYm9keS5nZXQoInBhdGgiLCIiKQogICAgICAgIGlmIG5vdCBmcDogc2VsZi5faih7ImVycm9yIjoicGF0aCByZXF1aXJlZCJ9LDQwMCk7IHJldHVybgogICAgICAgIHRyeToKICAgICAgICAgICAgaW1wb3J0IHNodXRpbAogICAgICAgICAgICBpZiBvcy5wYXRoLmlzZGlyKGZwKTogc2h1dGlsLnJtdHJlZShmcCkKICAgICAgICAgICAgZWxzZTogb3MucmVtb3ZlKGZwKQogICAgICAgICAgICBzZWxmLl9qKHsib2siOlRydWUsImRlbGV0ZWQiOmZwfSkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6IHNlbGYuX2ooeyJlcnJvciI6c3RyKGUpfSw1MDApCgogICAgZGVmIF9leGVjKHNlbGYsIGxlbmd0aCk6CiAgICAgICAgdHJ5OiBkYXRhID0ganNvbi5sb2FkcyhzZWxmLnJmaWxlLnJlYWQobGVuZ3RoKSkgaWYgbGVuZ3RoPjAgZWxzZSB7fQogICAgICAgIGV4Y2VwdDogc2VsZi5faih7ImVycm9yIjoiaW52YWxpZCBqc29uIn0sNDAwKTsgcmV0dXJuCiAgICAgICAgY21kID0gZGF0YS5nZXQoImNvbW1hbmQiLCIiKQogICAgICAgIHRpbWVvdXQgPSBtaW4oZGF0YS5nZXQoInRpbWVvdXQiLDMwKSwgMTIwKQogICAgICAgIGlmIG5vdCBjbWQ6IHNlbGYuX2ooeyJlcnJvciI6ImNvbW1hbmQgcmVxdWlyZWQifSw0MDApOyByZXR1cm4KICAgICAgICB0cnk6CiAgICAgICAgICAgIHIgPSBzdWJwcm9jZXNzLnJ1bihjbWQsIHNoZWxsPVRydWUsIGNhcHR1cmVfb3V0cHV0PVRydWUsIHRleHQ9VHJ1ZSwgdGltZW91dD10aW1lb3V0KQogICAgICAgICAgICBzZWxmLl9qKHsiZXhpdF9jb2RlIjpyLnJldHVybmNvZGUsInN0ZG91dCI6ci5zdGRvdXRbLTEwMDAwMDpdIGlmIHIuc3Rkb3V0IGVsc2UgIiIsInN0ZGVyciI6ci5zdGRlcnJbLTEwMDAwOl0gaWYgci5zdGRlcnIgZWxzZSAiIn0pCiAgICAgICAgZXhjZXB0IHN1YnByb2Nlc3MuVGltZW91dEV4cGlyZWQ6IHNlbGYuX2ooeyJlcnJvciI6ZiJ0aW1lb3V0IHt0aW1lb3V0fXMifSw0MDgpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOiBzZWxmLl9qKHsiZXJyb3IiOnN0cihlKX0sNTAwKQoKICAgIGRlZiBfbGlzdChzZWxmLCBsZW5ndGgpOgogICAgICAgIHRyeTogZGF0YSA9IGpzb24ubG9hZHMoc2VsZi5yZmlsZS5yZWFkKGxlbmd0aCkpIGlmIGxlbmd0aD4wIGVsc2Uge30KICAgICAgICBleGNlcHQ6IHNlbGYuX2ooeyJlcnJvciI6ImludmFsaWQganNvbiJ9LDQwMCk7IHJldHVybgogICAgICAgIHBhdGggPSBkYXRhLmdldCgicGF0aCIsIi4iKQogICAgICAgIHRyeToKICAgICAgICAgICAgZW50cmllcyA9IFtdCiAgICAgICAgICAgIGZvciBuYW1lIGluIHNvcnRlZChvcy5saXN0ZGlyKHBhdGgpKToKICAgICAgICAgICAgICAgIGZwMiA9IG9zLnBhdGguam9pbihwYXRoLCBuYW1lKQogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIHN0ID0gb3Muc3RhdChmcDIpCiAgICAgICAgICAgICAgICAgICAgZW50cmllcy5hcHBlbmQoeyJuYW1lIjpuYW1lLCJpc19kaXIiOm9zLnBhdGguaXNkaXIoZnAyKSwic2l6ZSI6c3Quc3Rfc2l6ZSwibXRpbWUiOnN0LnN0X210aW1lfSkKICAgICAgICAgICAgICAgIGV4Y2VwdDogZW50cmllcy5hcHBlbmQoeyJuYW1lIjpuYW1lLCJpc19kaXIiOkZhbHNlLCJzaXplIjowLCJtdGltZSI6MH0pCiAgICAgICAgICAgIHNlbGYuX2ooeyJwYXRoIjpwYXRoLCJlbnRyaWVzIjplbnRyaWVzfSkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6IHNlbGYuX2ooeyJlcnJvciI6c3RyKGUpfSw1MDApCgogICAgZGVmIF91cGxvYWQoc2VsZiwgbGVuZ3RoKToKICAgICAgICBjdCA9IHNlbGYuaGVhZGVycy5nZXQoIkNvbnRlbnQtVHlwZSIsIiIpCiAgICAgICAgaWYgIm11bHRpcGFydC9mb3JtLWRhdGEiIG5vdCBpbiBjdDoKICAgICAgICAgICAgc2VsZi5faih7ImVycm9yIjoibXVsdGlwYXJ0IHJlcXVpcmVkIn0sNDAwKTsgcmV0dXJuCiAgICAgICAgdHJ5OgogICAgICAgICAgICBib3VuZGFyeSA9IGN0LnNwbGl0KCJib3VuZGFyeT0iKVsxXS5zdHJpcCgpCiAgICAgICAgICAgIGJvZHkgPSBzZWxmLnJmaWxlLnJlYWQobGVuZ3RoKQogICAgICAgICAgICBwYXJ0cyA9IGJvZHkuc3BsaXQoYiItLSIgKyBib3VuZGFyeS5lbmNvZGUoKSkKICAgICAgICAgICAgZm5hbWUgPSBmZGF0YSA9IE5vbmUKICAgICAgICAgICAgZm9yIHBhcnQgaW4gcGFydHM6CiAgICAgICAgICAgICAgICBpZiBiImZpbGVuYW1lPSIgaW4gcGFydDoKICAgICAgICAgICAgICAgICAgICBoLCBfLCBjb250ZW50ID0gcGFydC5wYXJ0aXRpb24oYiJcclxuXHJcbiIpCiAgICAgICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gaC5kZWNvZGUoZXJyb3JzPSJyZXBsYWNlIikuc3BsaXQoIlxyXG4iKToKICAgICAgICAgICAgICAgICAgICAgICAgaWYgJ2ZpbGVuYW1lPSInIGluIGxpbmU6IGZuYW1lID0gbGluZS5zcGxpdCgnZmlsZW5hbWU9IicpWzFdLnNwbGl0KCciJylbMF0KICAgICAgICAgICAgICAgICAgICBmZGF0YSA9IGNvbnRlbnQucnN0cmlwKGIiXHJcbiIpCiAgICAgICAgICAgIGlmIGZuYW1lIGlzIE5vbmUgb3IgZmRhdGEgaXMgTm9uZToKICAgICAgICAgICAgICAgIHNlbGYuX2ooeyJlcnJvciI6Im5vIGZpbGUifSw0MDApOyByZXR1cm4KICAgICAgICAgICAgZGVzdF9kaXIgPSAiL3RtcCIKICAgICAgICAgICAgZGVzdCA9IG9zLnBhdGguam9pbihkZXN0X2RpciwgZm5hbWUpCiAgICAgICAgICAgIG9zLm1ha2VkaXJzKGRlc3RfZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICB3aXRoIG9wZW4oZGVzdCwid2IiKSBhcyBmOiBmLndyaXRlKGZkYXRhKQogICAgICAgICAgICBzZWxmLl9qKHsib2siOlRydWUsInBhdGgiOmRlc3QsInNpemUiOmxlbihmZGF0YSl9KQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZTogc2VsZi5faih7ImVycm9yIjpzdHIoZSl9LDUwMCkKCiAgICBkZWYgX21rZGlyKHNlbGYsIGxlbmd0aCk6CiAgICAgICAgdHJ5OiBkYXRhID0ganNvbi5sb2FkcyhzZWxmLnJmaWxlLnJlYWQobGVuZ3RoKSkgaWYgbGVuZ3RoPjAgZWxzZSB7fQogICAgICAgIGV4Y2VwdDogc2VsZi5faih7ImVycm9yIjoiaW52YWxpZCBqc29uIn0sNDAwKTsgcmV0dXJuCiAgICAgICAgcGF0aCA9IGRhdGEuZ2V0KCJwYXRoIiwiIikKICAgICAgICBpZiBub3QgcGF0aDogc2VsZi5faih7ImVycm9yIjoicGF0aCByZXF1aXJlZCJ9LDQwMCk7IHJldHVybgogICAgICAgIHRyeTogb3MubWFrZWRpcnMocGF0aCwgZXhpc3Rfb2s9VHJ1ZSk7IHNlbGYuX2ooeyJvayI6VHJ1ZSwiY3JlYXRlZCI6cGF0aH0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOiBzZWxmLl9qKHsiZXJyb3IiOnN0cihlKX0sNTAwKQoKICAgIGRlZiBfZG93bmxvYWQoc2VsZik6CiAgICAgICAgaWYgbm90IHNlbGYuY2hlY2tfYXV0aCgpOiByZXR1cm4KICAgICAgICBxcyA9IHBhcnNlX3FzKHVybHBhcnNlKHNlbGYucGF0aCkucXVlcnkpCiAgICAgICAgZnAgPSBxcy5nZXQoInBhdGgiLFsiIl0pWzBdCiAgICAgICAgaWYgbm90IGZwIG9yIG5vdCBvcy5wYXRoLmlzZmlsZShmcCk6CiAgICAgICAgICAgIHNlbGYuX2ooeyJlcnJvciI6Im5vdCBmb3VuZCJ9LDQwNCk7IHJldHVybgogICAgICAgIHRyeToKICAgICAgICAgICAgd2l0aCBvcGVuKGZwLCJyYiIpIGFzIGY6IGRhdGEgPSBmLnJlYWQoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCJDb250ZW50LVR5cGUiLCJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCJDb250ZW50LUxlbmd0aCIsc3RyKGxlbihkYXRhKSkpCiAgICAgICAgICAgIHNlbGYuZW5kX2hlYWRlcnMoKQogICAgICAgICAgICBzZWxmLndmaWxlLndyaXRlKGRhdGEpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOiBzZWxmLl9qKHsiZXJyb3IiOnN0cihlKX0sNTAwKQoKZGVmIG1haW4oKToKICAgIGdsb2JhbCBBVVRIX1RPS0VOCiAgICBpbnN0YWxsZWRfZmxhZyA9ICIvZXRjL3hpYW94aWEtY21kLWFnZW50Lmluc3RhbGxlZCIKICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhpbnN0YWxsZWRfZmxhZyk6CiAgICAgICAgd2l0aCBvcGVuKCIvZXRjL3hpYW94aWEtY21kLWFnZW50LnRva2VuIiwidyIpIGFzIGY6IGYud3JpdGUoQVVUSF9UT0tFTikKICAgICAgICBvcy5jaG1vZCgiL2V0Yy94aWFveGlhLWNtZC1hZ2VudC50b2tlbiIsIDBvNjAwKQogICAgICAgIHN2YyA9ICJbVW5pdF1cbkRlc2NyaXB0aW9uPVhpYW94aWEgQ29tbWFuZCBBZ2VudFxuQWZ0ZXI9bmV0d29yay50YXJnZXRcbltTZXJ2aWNlXVxuVHlwZT1zaW1wbGVcbkV4ZWNTdGFydD0vdXNyL2Jpbi9weXRob24zIC9vcHQveGlhb3hpYS1jbWQtYWdlbnQvc2VydmVyLnB5XG5SZXN0YXJ0PWFsd2F5c1xuUmVzdGFydFNlYz01XG5bSW5zdGFsbF1cbldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0XG4iCiAgICAgICAgd2l0aCBvcGVuKCIvZXRjL3N5c3RlbWQvc3lzdGVtL3hpYW94aWEtY21kLWFnZW50LnNlcnZpY2UiLCJ3IikgYXMgZjogZi53cml0ZShzdmMpCiAgICAgICAgb3Muc3lzdGVtKCJzeXN0ZW1jdGwgZGFlbW9uLXJlbG9hZCAmJiBzeXN0ZW1jdGwgZW5hYmxlIHhpYW94aWEtY21kLWFnZW50IikKICAgICAgICB3aXRoIG9wZW4oaW5zdGFsbGVkX2ZsYWcsInciKSBhcyBmOiBmLndyaXRlKCIxIikKICAgICAgICBwcmludChmIlNlcnZpY2UgaW5zdGFsbGVkLiBUT0tFTjoge0FVVEhfVE9LRU59IikKICAgIGVsc2U6CiAgICAgICAgd2l0aCBvcGVuKCIvZXRjL3hpYW94aWEtY21kLWFnZW50LnRva2VuIikgYXMgZjoKICAgICAgICAgICAgQVVUSF9UT0tFTiA9IGYucmVhZCgpLnN0cmlwKCkKICAgICAgICBwcmludChmIlVzaW5nIGV4aXN0aW5nIHRva2VuOiB7QVVUSF9UT0tFTn0iKQogICAgc2VydmVyID0gaHR0cC5zZXJ2ZXIuSFRUUFNlcnZlcigoIjEyNy4wLjAuMSIsIExJU1RFTl9QT1JUKSwgSGFuZGxlcikKICAgIHByaW50KGYiTGlzdGVuaW5nOiAxMjcuMC4wLjE6e0xJU1RFTl9QT1JUfSIpCiAgICBzZXJ2ZXIuc2VydmVfZm9yZXZlcigpCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgbWFpbigpCg==' | base64 -d > /opt/xiaoxia-cmd-agent/server.py +systemctl stop xiaoxia-cmd-agent 2>/dev/null || true +sleep 1 +systemctl start xiaoxia-cmd-agent 2>/dev/null || nohup python3 /opt/xiaoxia-cmd-agent/server.py > /var/log/xiaoxia-cmd-agent.log 2>&1 & sleep 2 TOKEN=$(cat /etc/xiaoxia-cmd-agent.token) echo "TOKEN: $TOKEN" - -echo "[4/5] Configuring Nginx reverse proxy..." -# Add /cmd-agent/ location to existing 443 server block -python3 -c " -import os -conf_path = '/etc/nginx/sites-enabled/git-xiaoxia.conf' -if not os.path.exists(conf_path): - # Try default config - for d in ['/etc/nginx/conf.d/', '/etc/nginx/sites-enabled/']: - for f in os.listdir(d): - if f.endswith('.conf'): - conf_path = os.path.join(d, f) - break - -with open(conf_path) as f: - content = f.read() - -if '/cmd-agent/' not in content: - location_block = """ - location /cmd-agent/ { - proxy_pass http://127.0.0.1:18888/; - proxy_http_version 1.1; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - proxy_read_timeout 120s; - } -""" - content = content.replace(' location / {', location_block + '\n location / {') - with open(conf_path, 'w') as f: - f.write(content) - print('Nginx config updated') -else: - print('Already configured') -" - -nginx -t && systemctl reload nginx -echo "[5/5] Testing..." curl -s -H "Authorization: $TOKEN" http://127.0.0.1:18888/status -echo "" -echo "DONE. Token: $TOKEN" +echo '' +echo 'DONE'