bitburner/scripts/botnet-manager.js

196 lines
8.3 KiB
JavaScript
Raw Normal View History

2022-04-20 11:32:10 -04:00
import {ArgParser} from '/scripts/lib/arg-parser';
2023-01-29 07:50:58 -05:00
import {Config} from '/scripts/lib/data-file';
2022-03-19 11:19:23 -04:00
import {Logger} from '/scripts/lib/logger';
2022-04-20 11:32:10 -04:00
import {copyWithDependencies} from '/scripts/copy';
2022-03-19 11:19:23 -04:00
2023-01-29 07:50:58 -05:00
const configPath = '/etc/botnet.txt';
const port = 1;
2022-03-19 11:19:23 -04:00
class Manager {
running;
workers = [];
2023-01-29 07:50:58 -05:00
async constructor(ns, hostname) {
2022-04-02 12:08:40 -04:00
ns.disableLog('ALL');
2022-03-19 11:19:23 -04:00
this.ns = ns;
2023-01-29 07:50:58 -05:00
this.config = new Config(configPath);
this.hostname = hostname;
2022-03-19 11:19:23 -04:00
this.logger = new Logger(this.ns, [
2022-05-02 12:23:35 -04:00
() => `Botnet: ${device}`,
2022-03-19 11:19:23 -04:00
() => `Workers: ${this.workers.length}\tCores: ${this.workers.reduce((acc, w) => acc + w.cpuCores, 0)}\tRAM: ${this.workers.reduce((acc, w) => acc + w.maxRam, 0)} GB`
]);
2022-03-24 00:34:09 -04:00
// Port communication
this.rx = () => {
let payload = ns.readPort(port);
return payload == 'NULL PORT DATA' ? null : payload;
}
2022-05-02 12:23:35 -04:00
this.tx = (payload) => ns.writePort(port + 10, JSON.stringify(payload));
2022-03-19 11:19:23 -04:00
}
2022-03-24 00:34:09 -04:00
isCommand(payload) { return ['copy', 'join', 'kill', 'leave', 'run'].includes(payload['command']); }
2022-03-19 11:19:23 -04:00
async load() {
const state = JSON.parse(await this.ns.read(this.config) || 'null');
if(state) {
this.running = state.running;
this.workers = state.workers;
2022-03-24 00:34:09 -04:00
if(this.running) await this.runCommand(this.running);
2022-03-19 11:19:23 -04:00
}
}
2022-03-24 00:34:09 -04:00
async runCommand(request = null) {
try {
if (request['command'] == 'copy') {
this.logger.log(`Copying: ${request['value']}`);
await this.workerExec(async w => await copyWithDependencies(ns, request['value'], w));
} else if (request['command'] == 'join') {
if(request['value'] == this.device) throw 'Cannot connect manager as a worker';
const exists = this.workers.findIndex(w => w.hostname == request['value']);
if(exists != -1) this.workers.splice(exists, 1);
this.workers.push(this.ns.getServer(request['value']));
this.logger.log(`${exists != -1 ? 'Reconnected' : 'Connected'}: ${request['value']}`);
if(this.running) await this.runCommand({
...this.running,
device: request['value']
});
} else if (request['command'] == 'kill') {
this.logger.log('Killing scripts');
await this.workerExec(w => this.ns.killall(w.hostname));
this.running = null;
} else if (request['command'] == 'leave') {
this.logger.log(`Disconnecting: ${request['value']}`);
const worker = this.workers.splice(this.workers.findIndex(w => w.hostname == request['value']), 1);
this.ns.killall(worker.hostname);
} else if (request['command'] == 'run') {
await this.runCommand('copy', {value: request['value']});
await this.runCommand('kill');
const run = (w) => {
const threads = ~~(w.maxRam / this.ns.getScriptRam(request['value'], this.ns.getHostname())) || 1;
this.ns.exec(request['value'], w, threads, ...(request['args'] || []));
}
if (request['device']) {
const w = this.workers.find(w => w.hostname == request['device']);
if (w) run(w);
} else {
this.logger.log(`Starting script: ${request['value']}`);
await this.workerExec(run);
this.running = request;
}
2022-03-19 11:19:23 -04:00
}
2022-03-24 00:34:09 -04:00
} catch (e) {
this.logger.error(`${request['command']} - ${e}`);
2022-03-19 11:19:23 -04:00
}
}
async save() {
await this.ns.write(this.config, JSON.stringify({
running: this.running,
workers: this.workers
}), 'w');
}
2022-03-24 00:34:09 -04:00
async start() {
if(this.config) await this.load();
let checkTick = 3600, runCheck = false;
for(let tick = 1; true; tick = tick > 3600 ? 1 : tick + 1) {
2022-03-19 11:19:23 -04:00
await this.ns.sleep(1000);
2022-03-24 00:34:09 -04:00
this.logger.log();
if(tick == checkTick) runCheck = true;
let req = this.rx();
2022-03-19 11:19:23 -04:00
2022-03-24 00:34:09 -04:00
// Check if we are idle
if(!req) {
// Check if we need to update the running command while we are idle
if(runCheck && this.running['update']) {
runCheck = false;
await this.runCommand(this.running['command'], this.running);
}
2022-03-19 11:19:23 -04:00
continue;
}
// Run command
2022-03-24 00:34:09 -04:00
if((req = JSON.parse(req)) && this.isCommand(req)) {
await this.runCommand(req);
2022-03-19 11:19:23 -04:00
await this.save();
} else { // Invalid command
2022-03-24 00:34:09 -04:00
this.logger.log(`Unknown command: ${JSON.stringify(req)}`);
2022-03-19 11:19:23 -04:00
}
}
}
async workerExec(fn) { for(let w of this.workers) await fn(w); }
}
/**
* @param ns {NS} - BitBurner API
*/
export async function main(ns) {
// Setup
ns.disableLog('ALL');
2023-01-29 07:50:58 -05:00
const config = await new Config(ns, configPath).load();
const hostname = ns.getHostname();
2022-05-02 12:23:35 -04:00
const argParser = new ArgParser('botnet-manager.js', 'Connect & manage a network of servers to launch distributed attacks.', [
new ArgParser('copy', 'Copy file & dependencies to botnet', [
2022-03-22 22:31:30 -04:00
{name: 'file', desc: 'File to copy', default: false},
2022-05-02 12:23:35 -04:00
{name: 'control', desc: 'Copy to master server', flags: ['-c', '--control'], default: false},
2022-03-22 22:31:30 -04:00
{name: 'noDeps', desc: 'Skip copying dependencies', flags: ['-d', '--no-deps'], default: false},
2022-05-02 12:23:35 -04:00
{name: 'slave', desc: 'Copy to slave servers', flags: ['-s', '--slave'], default: false},
2022-03-19 11:19:23 -04:00
]),
2022-05-02 12:23:35 -04:00
new ArgParser('join', 'Connect server as a botnet slave', [
2022-03-22 22:31:30 -04:00
{name: 'device', desc: 'Device to connect, defaults to the current machine', optional: true, default: hostname}
2022-03-19 11:19:23 -04:00
]),
2022-05-02 12:23:35 -04:00
new ArgParser('kill', 'Kill any scripts running on the botnet'),
2022-04-20 11:32:10 -04:00
new ArgParser('leave', 'Disconnect worker node from swarm', [
2022-03-22 22:31:30 -04:00
{name: 'device', desc: 'Device to disconnect, defaults to the current machine', optional: true, default: hostname}
2022-03-19 11:19:23 -04:00
]),
2023-01-29 07:50:58 -05:00
new ArgParser('list', 'List connected worker nodes'),
2022-05-02 12:23:35 -04:00
new ArgParser('run', 'Copy & run script on the botnet', [
2022-03-19 11:19:23 -04:00
{name: 'script', desc: 'Script to copy & execute', type: 'string'},
2022-03-22 22:31:30 -04:00
{name: 'args', desc: 'Arguments for script. Forward the current target with: {{TARGET}}', optional: true, extras: true},
2022-03-19 11:19:23 -04:00
]),
2022-05-02 12:23:35 -04:00
new ArgParser('start', 'Start this server as the botnet manager'),
2022-03-22 22:31:30 -04:00
{name: 'silent', desc: 'Suppress program output', flags: ['-s', '--silent'], default: false},
2022-03-19 11:19:23 -04:00
]);
2022-03-22 22:31:30 -04:00
const args = argParser.parse(ns.args);
2022-03-19 11:19:23 -04:00
2022-03-22 22:31:30 -04:00
// Help
2022-05-02 12:23:35 -04:00
if(args['help'] || args['_error'].length || !args['_command'])
2022-04-20 11:32:10 -04:00
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
2022-03-22 22:31:30 -04:00
2022-03-24 00:34:09 -04:00
// Run command
2022-03-22 22:31:30 -04:00
if(args['_command'] == 'start') { // Start botnet manager
2023-01-29 07:50:58 -05:00
ns.tprint(`Starting botnet controller on: ${hostname}`);
ns.tprint(`Connect workers to botnet with: run botnet-manager.js join [SERVER]`);
await new Manager(ns, hostname).start();
2022-03-22 22:31:30 -04:00
} else if(args['_command'] == 'copy') { // Issue copy command
2022-03-24 00:34:09 -04:00
await ns.writePort(portNum, JSON.stringify({
2022-03-22 22:31:30 -04:00
command: 'copy',
2022-03-24 00:34:09 -04:00
value: args['file']
2022-03-22 22:31:30 -04:00
}));
} else if(args['_command'] == 'join') { // Issue join command
2022-03-24 00:34:09 -04:00
await ns.writePort(portNum, JSON.stringify({
2022-03-22 22:31:30 -04:00
command: 'join',
2022-03-24 00:34:09 -04:00
value: args['device']
2022-03-22 22:31:30 -04:00
}));
} else if(args['_command'] == 'kill') { // Issue kill command
2022-03-24 00:34:09 -04:00
await ns.writePort(portNum, JSON.stringify({
2022-03-22 22:31:30 -04:00
command: 'kill'
}));
} else if(args['_command'] == 'leave') { // Issue leave command
2022-03-24 00:34:09 -04:00
await ns.writePort(portNum, JSON.stringify({
2022-03-22 22:31:30 -04:00
command: 'leave',
2022-03-24 00:34:09 -04:00
value: args['device']
2022-03-22 22:31:30 -04:00
}));
2023-01-29 07:50:58 -05:00
} else if(args['_command'] == 'list') {
ns.tprint('Botnet workers:');
ns.tprint(config['workers'].map(worker => worker.hostname).join(', '));
2022-03-22 22:31:30 -04:00
} else if(args['_command'] == 'run') { // Issue run command
2022-03-24 00:34:09 -04:00
await ns.writePort(portNum, JSON.stringify({
2022-03-22 22:31:30 -04:00
command: 'run',
2022-03-24 00:34:09 -04:00
value: args['script'],
args: args['args']
2022-03-22 22:31:30 -04:00
}));
2022-03-19 11:19:23 -04:00
}
}