This commit is contained in:
Zakary Timson 2023-01-29 07:50:58 -05:00
parent c386cc7391
commit ff4cc47e60
9 changed files with 473 additions and 357 deletions

View File

@ -1,49 +1,49 @@
import {ArgParser} from "/scripts/lib/arg-parser"; import {ArgParser} from "/scripts/lib/arg-parser";
/** /**
* Display an ASCII banner, optionally automatically after reboots. * Display an ASCII banner, optionally automatically after reboots.
* *
* @param {NS} ns - BitBurner API * @param {NS} ns - BitBurner API
*/ */
export async function main(ns) { export async function main(ns) {
// Setup // Setup
ns.disableLog('ALL'); ns.disableLog('ALL');
const argParser = new ArgParser('banner.js', 'Display an ASCII banner.', [ const argParser = new ArgParser('banner.js', 'Display an ASCII banner.', [
{name: 'reboot', desc: 'Automatically display after game reboots', flags: ['-r', '--reboot'], default: false} {name: 'reboot', desc: 'Automatically display after game reboots', flags: ['-r', '--reboot'], default: false}
]); ]);
const args = argParser.parse(ns.args); const args = argParser.parse(ns.args);
// Help // Help
if(args['help'] || args['_error'].length) if(args['help'] || args['_error'].length)
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
ns.tprint(` ns.tprint(`
&&&&&&&& O &&&&&&&& &&&&&&&& O &&&&&&&&
&&& & && CDDDD &&&&&&&&&& &&& & && CDDDD &&&&&&&&&&
&&&& &&& && &&& &&&&&&&&&&&&& &&&& &&& && &&& &&&&&&&&&&&&&
&&&&& && && & .&&&. &&&&&&&&&&&&&& &&&&& && && & .&&&. &&&&&&&&&&&&&&
&&&&&&&&&& && && &&&&& &&&&&&&&&&&&&&& &&&&&&&&&& && && &&&&& &&&&&&&&&&&&&&&
&&&&&&&& && & &&&&& &&&&&&&&&&&&&&&& &&&&&&&& && & &&&&& &&&&&&&&&&&&&&&&
&&&&&&&&&& &&& &&&&& &&&&&&&&&&&&&&&& &&&&&&&&&& &&& &&&&& &&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&& *&&&* *&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& *&&&* *&&&&&&&&&&&&&&&
&&&&&&&&&&&&& &&&&&&&&& *&&&&&&* .&& &&&&&&&&&&&&& &&&&&&&&& *&&&&&&* .&&
&&&&&&&&&&& &&& & & &&& &&* .&&& &&&&&&&&&&& &&& & & &&& &&* .&&&
&&&&&&&&&& & & ,,,,,* .&&&& &&&&&&&&&& & & ,,,,,* .&&&&
&&&&& & & &&&&&&&&&&&&&&&& &&&&& & & &&&&&&&&&&&&&&&&
&& &&&& & & & &&&&&&&&&&&&&& && &&&& & & & &&&&&&&&&&&&&&
&& &&&&&&& & & & &&&&&* &&&&& && &&&&&&& & & & &&&&&* &&&&&
&& &&&&&&&& & & &&&&&&&* &&&* && &&&&&&&& & & &&&&&&&* &&&*
&& &&&&&&&&&&& & & &&&& &* && &&&&&&&&&&& & & &&&& &*
&& &&&&&&&&& & & && && && &&&&&&&&& & & && &&
&&& &&&&&& & & && && &&& &&&&&& & & && &&
&&& &&&& & & && && &&& &&&& & & && &&
&&&& &&& & & && && &&&& &&& & & && &&
&&&&&&& \\&/ && && &&&&&&& \\&/ && &&
&&&&&& V &* &* &&&&&& V &* &*
`); `);
// Prevent from exiting so the banner will automatically display on startup. // Prevent from exiting so the banner will automatically display on startup.
if(args['reboot']) while(true) { await ns.sleep(1000 * 60); } if(args['reboot']) while(true) { await ns.sleep(1000 * 60); }
} }

34
scripts/bitburner.js Normal file
View File

@ -0,0 +1,34 @@
/**
* Automatically complete the current BitNode.
*
* @param {NS} ns - BitBurner API
*/
export function main(ns) {
let modules = [
'auto-root',
'auto-hack',
'botnet-manager',
'hacknet-manager',
'server-manager'
];
// Banner
ns.run('/scripts/banner.js', 1, '-r');
ns.tprint(`Starting BitBurner with ${modules.length} enabled: `);
ns.tprint(modules.join(', '));
// botnet-manager
// hacknet-manager
ns.run('/scripts/hacknet-manager', 1, '-a');
// server-manager
ns.run('/scripts/server-manager', 1, '');
while(true) {
// auto-hack
ns.sleep(1000);
}
}

View File

@ -1,16 +1,20 @@
import {ArgParser} from '/scripts/lib/arg-parser'; import {ArgParser} from '/scripts/lib/arg-parser';
import {Config} from '/scripts/lib/data-file';
import {Logger} from '/scripts/lib/logger'; import {Logger} from '/scripts/lib/logger';
import {copyWithDependencies} from '/scripts/copy'; import {copyWithDependencies} from '/scripts/copy';
const configPath = '/etc/botnet.txt';
const port = 1;
class Manager { class Manager {
running; running;
workers = []; workers = [];
constructor(ns, device, port, config = '/conf/botnet.txt') { async constructor(ns, hostname) {
ns.disableLog('ALL'); ns.disableLog('ALL');
this.ns = ns; this.ns = ns;
this.config = config; this.config = new Config(configPath);
this.device = device; this.hostname = hostname;
this.logger = new Logger(this.ns, [ this.logger = new Logger(this.ns, [
() => `Botnet: ${device}`, () => `Botnet: ${device}`,
() => `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` () => `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`
@ -124,7 +128,8 @@ class Manager {
export async function main(ns) { export async function main(ns) {
// Setup // Setup
ns.disableLog('ALL'); ns.disableLog('ALL');
const hostname = ns.getHostname(), portNum = 1; const config = await new Config(ns, configPath).load();
const hostname = ns.getHostname();
const argParser = new ArgParser('botnet-manager.js', 'Connect & manage a network of servers to launch distributed attacks.', [ 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', [ new ArgParser('copy', 'Copy file & dependencies to botnet', [
{name: 'file', desc: 'File to copy', default: false}, {name: 'file', desc: 'File to copy', default: false},
@ -139,6 +144,7 @@ export async function main(ns) {
new ArgParser('leave', 'Disconnect worker node from swarm', [ new ArgParser('leave', 'Disconnect worker node from swarm', [
{name: 'device', desc: 'Device to disconnect, defaults to the current machine', optional: true, default: hostname} {name: 'device', desc: 'Device to disconnect, defaults to the current machine', optional: true, default: hostname}
]), ]),
new ArgParser('list', 'List connected worker nodes'),
new ArgParser('run', 'Copy & run script on the botnet', [ new ArgParser('run', 'Copy & run script on the botnet', [
{name: 'script', desc: 'Script to copy & execute', type: 'string'}, {name: 'script', desc: 'Script to copy & execute', type: 'string'},
{name: 'args', desc: 'Arguments for script. Forward the current target with: {{TARGET}}', optional: true, extras: true}, {name: 'args', desc: 'Arguments for script. Forward the current target with: {{TARGET}}', optional: true, extras: true},
@ -154,9 +160,9 @@ export async function main(ns) {
// Run command // Run command
if(args['_command'] == 'start') { // Start botnet manager if(args['_command'] == 'start') { // Start botnet manager
ns.tprint(`Starting ${hostname} as botnet manager`); ns.tprint(`Starting botnet controller on: ${hostname}`);
ns.tprint(`Connect more nodes with: run botnet-manager.js join [SERVER]`); ns.tprint(`Connect workers to botnet with: run botnet-manager.js join [SERVER]`);
await new Manager(ns, hostname, portNum).start(); await new Manager(ns, hostname).start();
} else if(args['_command'] == 'copy') { // Issue copy command } else if(args['_command'] == 'copy') { // Issue copy command
await ns.writePort(portNum, JSON.stringify({ await ns.writePort(portNum, JSON.stringify({
command: 'copy', command: 'copy',
@ -176,6 +182,9 @@ export async function main(ns) {
command: 'leave', command: 'leave',
value: args['device'] value: args['device']
})); }));
} else if(args['_command'] == 'list') {
ns.tprint('Botnet workers:');
ns.tprint(config['workers'].map(worker => worker.hostname).join(', '));
} else if(args['_command'] == 'run') { // Issue run command } else if(args['_command'] == 'run') { // Issue run command
await ns.writePort(portNum, JSON.stringify({ await ns.writePort(portNum, JSON.stringify({
command: 'run', command: 'run',

View File

@ -1,107 +1,107 @@
import {ArgParser} from '/scripts/lib/arg-parser'; import {ArgParser} from '/scripts/lib/arg-parser';
import {maxThreads, progressBar} from '/scripts/lib/utils'; import {maxThreads, progressBar} from '/scripts/lib/utils';
/** /**
* Copy a file & it's dependencies to a server. * Copy a file & it's dependencies to a server.
* *
* @param {NS} ns - BitBurner API * @param {NS} ns - BitBurner API
* @param {string} src - File to scan & copy * @param {string} src - File to scan & copy
* @param {string} server - Device to copy files to * @param {string} server - Device to copy files to
* @returns {Promise<string[]>} - Array of copied files * @returns {Promise<string[]>} - Array of copied files
*/ */
export async function copyWithDependencies(ns, src, server) { export async function copyWithDependencies(ns, src, server) {
const queue = [src], found = [src]; const queue = [src], found = [src];
while(queue.length) { while(queue.length) {
const file = queue.splice(0, 1)[0]; const file = queue.splice(0, 1)[0];
const imports = new RegExp(/from ["']\.?(\/.+)["']/g); const imports = new RegExp(/from ["']\.?(\/.+)["']/g);
const script = await ns.read(file); const script = await ns.read(file);
let match; let match;
while((match = imports.exec(script)) != null) { while((match = imports.exec(script)) != null) {
const path = `${match[1]}.js`; const path = `${match[1]}.js`;
if(!found.includes(path)) found.push(path); if(!found.includes(path)) found.push(path);
queue.push(path); queue.push(path);
} }
} }
await ns.scp(found, server); await ns.scp(found, server);
return found.reverse(); return found.reverse();
} }
/** /**
* Copy a file & it's dependencies to a server. * Copy a file & it's dependencies to a server.
* *
* @param {NS} ns - BitBurner API * @param {NS} ns - BitBurner API
*/ */
export async function main(ns) { export async function main(ns) {
// Setup // Setup
ns.disableLog('ALL'); ns.disableLog('ALL');
const argParser = new ArgParser('copy.js', 'Copy a file & it\'s dependencies to a server.', [ const argParser = new ArgParser('copy.js', 'Copy a file & it\'s dependencies to a server.', [
{name: 'file', desc: 'File to copy'}, {name: 'file', desc: 'File to copy'},
{name: 'server', desc: 'Server to copy file(s) to'}, {name: 'server', desc: 'Server to copy file(s) to'},
{name: 'args', desc: 'Arguments to start file/script with', optional: true, extras: true}, {name: 'args', desc: 'Arguments to start file/script with', optional: true, extras: true},
{name: 'cpu', desc: 'Number of CPU threads to start script with, will use maximum if not specified', flags: ['-c', '--cpu']}, {name: 'cpu', desc: 'Number of CPU threads to start script with, will use maximum if not specified', flags: ['-c', '--cpu']},
{name: 'execute', desc: 'Start script after copying', flags: ['-e', '--execute'], default: false}, {name: 'execute', desc: 'Start script after copying', flags: ['-e', '--execute'], default: false},
{name: 'noDeps', desc: 'Skip copying dependencies', flags: ['-n', '--no-deps'], default: false}, {name: 'noDeps', desc: 'Skip copying dependencies', flags: ['-n', '--no-deps'], default: false},
{name: 'quite', desc: 'Suppress program output', flags: ['-q', '--quite'], default: false}, {name: 'quite', desc: 'Suppress program output', flags: ['-q', '--quite'], default: false},
]); ]);
const args = argParser.parse(ns.args); const args = argParser.parse(ns.args);
// Help // Help
if(args['help'] || args['_error'].length) if(args['help'] || args['_error'].length)
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
// Banner // Banner
if(!args['quite']) { if(!args['quite']) {
ns.tprint('==================================================='); ns.tprint('===================================================');
ns.tprint(`Copying: ${args['server']}`); ns.tprint(`Copying: ${args['server']}`);
ns.tprint('==================================================='); ns.tprint('===================================================');
ns.tprint(''); ns.tprint('');
ns.tprint('Copying Files:'); ns.tprint('Copying Files:');
await ns.sleep(500); await ns.sleep(500);
} }
// Copy files & create download bar // Copy files & create download bar
if(args['noDeps']) { if(args['noDeps']) {
await ns.scp(args['file'], args['server']); await ns.scp(args['file'], args['server']);
if(!args['quite']) await progressBar(ns, args['file']); if(!args['quite']) await progressBar(ns, args['file']);
} else { } else {
const files = await copyWithDependencies(ns, args['file'], args['server']); const files = await copyWithDependencies(ns, args['file'], args['server']);
if(!args['quite']) { if(!args['quite']) {
for(let file of files) { for(let file of files) {
await progressBar(ns, file); await progressBar(ns, file);
} }
} }
} }
// Run the script if requested // Run the script if requested
if(args['execute']) { if(args['execute']) {
const threads = args['cpu'] || maxThreads(ns, args['file'], args['server']) || 1; const threads = args['cpu'] || maxThreads(ns, args['file'], args['server']) || 1;
if(!args['quite']) { if(!args['quite']) {
ns.tprint(''); ns.tprint('');
ns.tprint(`Executing with ${threads} thread${threads > 1 ? 's' : ''}...`); ns.tprint(`Executing with ${threads} thread${threads > 1 ? 's' : ''}...`);
await ns.sleep(500); await ns.sleep(500);
} }
ns.killall(args['server']); ns.killall(args['server']);
const pid = ns.exec(args['file'], args['server'], threads, ...args['args']); const pid = ns.exec(args['file'], args['server'], threads, ...args['args']);
if(!args['quite']) { if(!args['quite']) {
ns.tprint(!!pid ? 'Done!' : 'Failed to start'); ns.tprint(!!pid ? 'Done!' : 'Failed to start');
ns.tprint(''); ns.tprint('');
} }
} }
// Done message // Done message
if(!args['quite']) { if(!args['quite']) {
ns.tprint(''); ns.tprint('');
ns.tprint('Done!'); ns.tprint('Done!');
ns.tprint(''); ns.tprint('');
} }
} }
/** /**
* BitBurner autocomplete. * BitBurner autocomplete.
* *
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information * @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
* @returns {string[]} - Pool of autocomplete options * @returns {string[]} - Pool of autocomplete options
*/ */
export function autocomplete(data) { export function autocomplete(data) {
return [...data.servers, ...data.scripts]; return [...data.servers, ...data.scripts];
} }

View File

@ -1,68 +1,68 @@
import {ArgParser} from '/scripts/lib/arg-parser'; import {ArgParser} from '/scripts/lib/arg-parser';
import {toCurrency} from '/scripts/lib/utils'; import {toCurrency} from '/scripts/lib/utils';
import {scanNetwork} from '/scripts/crawler'; import {scanNetwork} from '/scripts/crawler';
/** /**
* Sort array of servers based on the potential return/yield. * Sort array of servers based on the potential return/yield.
* *
* @param {NS} ns - BitBurner API * @param {NS} ns - BitBurner API
* @param {string[]} servers - List of servers to sort based on yield * @param {string[]} servers - List of servers to sort based on yield
* @returns {[string, number][]} - Sorted list of servers & their potential yield per minute * @returns {[string, number][]} - Sorted list of servers & their potential yield per minute
*/ */
export function bestTarget(ns, servers) { export function bestTarget(ns, servers) {
return servers.map(s => [s, serverYield(ns, s)]).sort((a, b) => { return servers.map(s => [s, serverYield(ns, s)]).sort((a, b) => {
if(a[1] < b[1]) return 1; if(a[1] < b[1]) return 1;
if(a[1] > b[1]) return -1; if(a[1] > b[1]) return -1;
return 0; return 0;
}); });
} }
/** /**
* Calculate the average return per minute when hacking a server. * Calculate the average return per minute when hacking a server.
* *
* **Disclaimer:** Does not take into account security or weaken time. * **Disclaimer:** Does not take into account security or weaken time.
* *
* @param {NS} ns - BitBurner API * @param {NS} ns - BitBurner API
* @param {string} server - Server to calculate yield for * @param {string} server - Server to calculate yield for
* @returns {number} - $/minute * @returns {number} - $/minute
*/ */
export function serverYield(ns, server) { export function serverYield(ns, server) {
return (ns.hackAnalyze(server) * ns.getServerMaxMoney(server)) return (ns.hackAnalyze(server) * ns.getServerMaxMoney(server))
* ((60 / (ns.getHackTime(server) / 1000)) * ns.hackAnalyzeChance(server)); * ((60 / (ns.getHackTime(server) / 1000)) * ns.hackAnalyzeChance(server));
} }
/** /**
* Scan the network for the best server(s) to hack. * Scan the network for the best server(s) to hack.
* *
* @param ns {NS} - BitBurner API * @param ns {NS} - BitBurner API
* @returns {*} * @returns {*}
*/ */
export function main(ns) { export function main(ns) {
// Setup // Setup
ns.disableLog('ALL'); ns.disableLog('ALL');
const argParser = new ArgParser('find-target.js', 'Scan the network for the best servers(s) to hack.',[ const argParser = new ArgParser('find-target.js', 'Scan the network for the best servers(s) to hack.',[
{name: 'count', desc: 'Number of servers to return', flags: ['-c', '--count'], default: Infinity}, {name: 'count', desc: 'Number of servers to return', flags: ['-c', '--count'], default: Infinity},
{name: 'rooted', desc: 'Only servers that have been rooted', flags: ['-r', '--rooted'], default: false}, {name: 'rooted', desc: 'Only servers that have been rooted', flags: ['-r', '--rooted'], default: false},
{name: 'notRooted', desc: 'Only servers that have not been rooted', flags: ['-n', '--not-rooted'], default: false}, {name: 'notRooted', desc: 'Only servers that have not been rooted', flags: ['-n', '--not-rooted'], default: false},
{name: 'verbose', desc: 'Display the estimated income per minute per core', flags: ['-v', '--verbose'], default: false}, {name: 'verbose', desc: 'Display the estimated income per minute per core', flags: ['-v', '--verbose'], default: false},
]); ]);
const args = argParser.parse(ns.args); const args = argParser.parse(ns.args);
// Help // Help
if(args['help'] || args['_error'].length) if(args['help'] || args['_error'].length)
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
// Banner // Banner
ns.tprint('==================================================='); ns.tprint('===================================================');
ns.tprint(`Finding Targets:`); ns.tprint(`Finding Targets:`);
ns.tprint('==================================================='); ns.tprint('===================================================');
// Search & display results // Search & display results
const [servers, ignore] = scanNetwork(ns); const [servers, ignore] = scanNetwork(ns);
bestTarget(ns, servers).map(s => [...s, ns.hasRootAccess(s[0])]) bestTarget(ns, servers).map(s => [...s, ns.hasRootAccess(s[0])])
.filter(s => (!args['rooted'] || s[2]) || (!args['notRooted'] || !s[2])) .filter(s => (!args['rooted'] || s[2]) || (!args['notRooted'] || !s[2]))
.filter((s, i) => i < args['count']) .filter((s, i) => i < args['count'])
.map(s => `${s[0]}${args['verbose'] ? ` (~${toCurrency(s[1])}/min)` : ''}`) .map(s => `${s[0]}${args['verbose'] ? ` (~${toCurrency(s[1])}/min)` : ''}`)
.forEach((s, i) => ns.tprint(`${i + 1}) ${s}`)); .forEach((s, i) => ns.tprint(`${i + 1}) ${s}`));
ns.tprint(''); ns.tprint('');
} }

31
scripts/lib/data-file.js Normal file
View File

@ -0,0 +1,31 @@
export class DataFile {
/**
* Read & write data to a JSON file.
*
* @param {NS} ns - Bitburner API
* @param path - Path to config file
*/
constructor(ns, path) {
this.ns = ns;
this.path = path;
}
/**
* Load data file
*
* @returns {Promise<any>} - Saved data
*/
async load() {
return JSON.parse(await this.ns.read(this.path) || 'null');
}
/**
* Save data to file
*
* @param values - Data to save
* @returns {Promise<void>} - Save complete
*/
async save(values) {
await this.ns.write(this.path, JSON.stringify(values), 'w');
}
}

View File

@ -0,0 +1,42 @@
export class PortHelper {
/**
*
* @param ns
* @param port
* @param host
*/
constructor(ns, port, host) {
this.ns = ns;
this.host = host;
this.portNum = port;
this.port = ns.getPortHandle(port);
this.callbacks = {};
}
check() {
const pending = [];
while(!this.port.empty()) pending.push(this.port.read());
pending.filter(p => {
try {
const payload = JSON.parse(p);
if(this.callbacks[payload.subject]) return !this.callbacks[payload.subject](payload.value);
if(this.callbacks['*']) return !this.callbacks['*'](payload.value);
return true;
} catch {
return true;
}
}).forEach(p => this.port.write(p));
}
subscribe(subject, callback) { if(typeof callback == 'function') this.callbacks[subject] = callback; }
send(subject, value) {
this.ns.writePort(this.portNum, JSON.stringify({
from: this.host,
subject,
value
}));
}
unsubscribe(subject) { delete this.callbacks[subject]; }
}

View File

@ -1,39 +1,39 @@
import {ArgParser} from '/scripts/lib/arg-parser'; import {ArgParser} from '/scripts/lib/arg-parser';
/** /**
* Recursively delete files inside a path. Equivalent to the Unix "rm -r". * Recursively delete files inside a path. Equivalent to the Unix "rm -r".
* @param {NS} ns - BitBurner API * @param {NS} ns - BitBurner API
*/ */
export function main(ns) { export function main(ns) {
// Setup // Setup
ns.disableLog('ALL'); ns.disableLog('ALL');
const argParser = new ArgParser('rm.js', 'Recursively delete files inside a directory', [ const argParser = new ArgParser('rm.js', 'Recursively delete files inside a directory', [
{name: 'path', desc: 'Path to recursively search'}, {name: 'path', desc: 'Path to recursively search'},
{name: 'server', desc: 'Run on remote server', optional: true, default: ns.getHostname()}, {name: 'server', desc: 'Run on remote server', optional: true, default: ns.getHostname()},
{name: 'force', desc: 'Remove game files (.exe, .lit, .msg)', flags: ['-f', '--force'], default: false}, {name: 'force', desc: 'Remove game files (.exe, .lit, .msg)', flags: ['-f', '--force'], default: false},
{name: 'recursive', desc: 'Delete everything inside directory', flags: ['-r', '--recursive'], default: true} {name: 'recursive', desc: 'Delete everything inside directory', flags: ['-r', '--recursive'], default: true}
]); ]);
const args = argParser.parse(ns.args); const args = argParser.parse(ns.args);
// Help // Help
if(args['help'] || args['_error'].length) if(args['help'] || args['_error'].length)
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
// Run // Run
ns.ls(args['server'], args['path']) ns.ls(args['server'], args['path'])
.filter(f => new RegExp(/\.(exe|lit|msg)$/g).test(f) ? args['force'] : true) .filter(f => new RegExp(/\.(exe|lit|msg)$/g).test(f) ? args['force'] : true)
.forEach(f => ns.rm(f, args['server'])); .forEach(f => ns.rm(f, args['server']));
} }
/** /**
* BitBurner autocomplete. * BitBurner autocomplete.
* *
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information * @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
* @returns {string[]} - Pool of autocomplete options * @returns {string[]} - Pool of autocomplete options
*/ */
export function autocomplete(data) { export function autocomplete(data) {
return [...data.txts, ...data.scripts] return [...data.txts, ...data.scripts]
.map(file => file.split('/').slice(0, -1).join('/')) .map(file => file.split('/').slice(0, -1).join('/'))
.filter((path, i, arr) => arr.indexOf(path) == i) .filter((path, i, arr) => arr.indexOf(path) == i)
.concat(data.txts, data.scripts); .concat(data.txts, data.scripts);
} }

View File

@ -1,87 +1,87 @@
import {ArgParser} from '/scripts/lib/arg-parser'; import {ArgParser} from '/scripts/lib/arg-parser';
import {Logger} from '/scripts/lib/logger'; import {Logger} from '/scripts/lib/logger';
import {maxThreads, toCurrency} from '/scripts/lib/utils'; import {maxThreads, toCurrency} from '/scripts/lib/utils';
import {copyWithDependencies} from "/scripts/copy"; import {copyWithDependencies} from "/scripts/copy";
/** /**
* Automate the buying & upgrading of servers. * Automate the buying & upgrading of servers.
* *
* @param {NS} ns - BitBurner API * @param {NS} ns - BitBurner API
*/ */
export async function main(ns) { export async function main(ns) {
// Setup // Setup
ns.disableLog('ALL'); ns.disableLog('ALL');
let servers = ns.getPurchasedServers(); let servers = ns.getPurchasedServers();
const logger = new Logger(ns, [ const logger = new Logger(ns, [
() => `Server Manager: ${servers.length}` () => `Server Manager: ${servers.length}`
]); ]);
const argParser = new ArgParser('server-manager.js', 'Automate the buying & upgrading of servers. Automatically starts script after purchase. Tail for live updates.', [ const argParser = new ArgParser('server-manager.js', 'Automate the buying & upgrading of servers. Automatically starts script after purchase. Tail for live updates.', [
{name: 'script', desc: 'Script to copy & execute', optional: true}, {name: 'script', desc: 'Script to copy & execute', optional: true},
{name: 'args', desc: 'Arguments for script. Forward the discovered server with: {{SERVER}}', optional: true, extras: true}, {name: 'args', desc: 'Arguments for script. Forward the discovered server with: {{SERVER}}', optional: true, extras: true},
{name: 'balance', desc: 'Prevent spending bellow point', flags: ['-b', '--balance'], default: 0}, {name: 'balance', desc: 'Prevent spending bellow point', flags: ['-b', '--balance'], default: 0},
{name: 'cpu', desc: 'Number of CPU threads to start script with, will use maximum if not specified', flags: ['-c', '--cpu'], default: false}, {name: 'cpu', desc: 'Number of CPU threads to start script with, will use maximum if not specified', flags: ['-c', '--cpu'], default: false},
{name: 'limit', desc: 'Limit the number of servers that can be purchased, defaults to 25', flags: ['-l', '--limit'], default: 25}, {name: 'limit', desc: 'Limit the number of servers that can be purchased, defaults to 25', flags: ['-l', '--limit'], default: 25},
{name: 'ram', desc: 'Amount of RAM to purchase new servers with, defaults to 8 GB', flags: ['-r', '--ram'], default: 8}, {name: 'ram', desc: 'Amount of RAM to purchase new servers with, defaults to 8 GB', flags: ['-r', '--ram'], default: 8},
{name: 'sleep', desc: 'Amount of time to wait between purchases, defaults to 1 (second)', flags: ['-s', '--sleep'], default: 1} {name: 'sleep', desc: 'Amount of time to wait between purchases, defaults to 1 (second)', flags: ['-s', '--sleep'], default: 1}
]); ]);
const args = argParser.parse(ns.args); const args = argParser.parse(ns.args);
const serverPrefix = 'botnet_' const serverPrefix = 'botnet_'
const maxRam = ns.getPurchasedServerMaxRam(); const maxRam = ns.getPurchasedServerMaxRam();
const minRamCost = ns.getPurchasedServerCost(args['ram']); const minRamCost = ns.getPurchasedServerCost(args['ram']);
async function startScript(server) { async function startScript(server) {
await copyWithDependencies(ns, args['script'], server); await copyWithDependencies(ns, args['script'], server);
const threads = args['cpu'] || maxThreads(ns, args['script'], server) || 1; const threads = args['cpu'] || maxThreads(ns, args['script'], server) || 1;
const pid = ns.exec(args['script'], server, threads, ...args['args']); const pid = ns.exec(args['script'], server, threads, ...args['args']);
logger.log(`Starting "${args['script']}" with ${threads} thread${threads > 1 ? 's' : ''}`); logger.log(`Starting "${args['script']}" with ${threads} thread${threads > 1 ? 's' : ''}`);
logger[pid == -1 ? 'warn' : 'log'](pid == -1 ? 'Done!' : 'Failed to start'); logger[pid == -1 ? 'warn' : 'log'](pid == -1 ? 'Done!' : 'Failed to start');
} }
// Help // Help
if(args['help'] || args['_error'].length) if(args['help'] || args['_error'].length)
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
// Main loop // Main loop
// noinspection InfiniteLoopJS // noinspection InfiniteLoopJS
while(true) { while(true) {
servers = ns.getPurchasedServers(); servers = ns.getPurchasedServers();
const balance = ns.getServerMoneyAvailable('home'); const balance = ns.getServerMoneyAvailable('home');
// Purchase new server if we can afford it // Purchase new server if we can afford it
if(servers.length < args['limit'] && balance - minRamCost > args['balance']) { if(servers.length < args['limit'] && balance - minRamCost > args['balance']) {
logger.log(`Buying server (${args['ram']} GB): ${toCurrency(minRamCost)}`); logger.log(`Buying server (${args['ram']} GB): ${toCurrency(minRamCost)}`);
ns.purchaseServer(`${serverPrefix}${servers.length}`, args['ram']); ns.purchaseServer(`${serverPrefix}${servers.length}`, args['ram']);
// Run the script if requested // Run the script if requested
if(args['script']) await startScript(`${serverPrefix}${servers.length}`); if(args['script']) await startScript(`${serverPrefix}${servers.length}`);
} else { // Check for upgrades } else { // Check for upgrades
let upgrades = servers.map(server => { let upgrades = servers.map(server => {
// Calculate next RAM upgrades (must be a power of two: 2, 4, 8, 16, 32...) // Calculate next RAM upgrades (must be a power of two: 2, 4, 8, 16, 32...)
let ram = Math.pow(2, Math.log2(ns.getServerMaxRam(server)) + 1); let ram = Math.pow(2, Math.log2(ns.getServerMaxRam(server)) + 1);
if(ram > maxRam) ram = null; if(ram > maxRam) ram = null;
return { return {
server, server,
ram, ram,
cost: ram ? ns.getPurchasedServerCost(ram) : null cost: ram ? ns.getPurchasedServerCost(ram) : null
} }
}); });
upgrades = upgrades.sort((a, b) => { // Sort by price upgrades = upgrades.sort((a, b) => { // Sort by price
if(a.cost < b.cost) return 1; if(a.cost < b.cost) return 1;
if(a.cost < b.cost) return -1; if(a.cost < b.cost) return -1;
return 0; return 0;
}); });
// Do the cheapest upgrade if we can afford it // Do the cheapest upgrade if we can afford it
const upgrade = upgrades[0]; const upgrade = upgrades[0];
if(upgrade && !!upgrade.ram && balance - upgrade.cost > args['balance']) { if(upgrade && !!upgrade.ram && balance - upgrade.cost > args['balance']) {
logger.log(`Upgrading ${upgrade.server}: ${upgrade.ram} GB / ${toCurrency(upgrade.cost)}`); logger.log(`Upgrading ${upgrade.server}: ${upgrade.ram} GB / ${toCurrency(upgrade.cost)}`);
ns.killall(upgrade.server); ns.killall(upgrade.server);
ns.deleteServer(upgrade.server); ns.deleteServer(upgrade.server);
ns.purchaseServer(upgrade.server, upgrade.ram); ns.purchaseServer(upgrade.server, upgrade.ram);
// Run the script if requested // Run the script if requested
if(args['script']) await startScript(upgrade.server); if(args['script']) await startScript(upgrade.server);
} }
} }
await ns.sleep(args['sleep'] * 1000); await ns.sleep(args['sleep'] * 1000);
} }
} }