From ff4cc47e6023101bf0f4f0417b1f80a84f544f05 Mon Sep 17 00:00:00 2001 From: ztimson Date: Sun, 29 Jan 2023 07:50:58 -0500 Subject: [PATCH] Unknown --- scripts/banner.js | 98 ++++++++--------- scripts/bitburner.js | 34 ++++++ scripts/botnet-manager.js | 23 ++-- scripts/copy.js | 214 ++++++++++++++++++------------------- scripts/find-target.js | 136 +++++++++++------------ scripts/lib/data-file.js | 31 ++++++ scripts/lib/port-helper.js | 42 ++++++++ scripts/rm.js | 78 +++++++------- scripts/server-manager.js | 174 +++++++++++++++--------------- 9 files changed, 473 insertions(+), 357 deletions(-) create mode 100644 scripts/bitburner.js create mode 100644 scripts/lib/data-file.js create mode 100644 scripts/lib/port-helper.js diff --git a/scripts/banner.js b/scripts/banner.js index f65ed7f..3a1c847 100644 --- a/scripts/banner.js +++ b/scripts/banner.js @@ -1,49 +1,49 @@ -import {ArgParser} from "/scripts/lib/arg-parser"; - -/** - * Display an ASCII banner, optionally automatically after reboots. - * - * @param {NS} ns - BitBurner API - */ -export async function main(ns) { - // Setup - ns.disableLog('ALL'); - const argParser = new ArgParser('banner.js', 'Display an ASCII banner.', [ - {name: 'reboot', desc: 'Automatically display after game reboots', flags: ['-r', '--reboot'], default: false} - ]); - const args = argParser.parse(ns.args); - - // Help - if(args['help'] || args['_error'].length) - return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); - - ns.tprint(` - - &&&&&&&& O &&&&&&&& - &&& & && CDDDD &&&&&&&&&& - &&&& &&& && &&& &&&&&&&&&&&&& - &&&&& && && & .&&&. &&&&&&&&&&&&&& - &&&&&&&&&& && && &&&&& &&&&&&&&&&&&&&& - &&&&&&&& && & &&&&& &&&&&&&&&&&&&&&& - &&&&&&&&&& &&& &&&&& &&&&&&&&&&&&&&&& - &&&&&&&&&&&&&&&& *&&&* *&&&&&&&&&&&&&&& - &&&&&&&&&&&&& &&&&&&&&& *&&&&&&* .&& - &&&&&&&&&&& &&& & & &&& &&* .&&& - &&&&&&&&&& & & ,,,,,* .&&&& - &&&&& & & &&&&&&&&&&&&&&&& - && &&&& & & & &&&&&&&&&&&&&& - && &&&&&&& & & & &&&&&* &&&&& - && &&&&&&&& & & &&&&&&&* &&&* - && &&&&&&&&&&& & & &&&& &* - && &&&&&&&&& & & && && - &&& &&&&&& & & && && - &&& &&&& & & && && - &&&& &&& & & && && - &&&&&&& \\&/ && && - &&&&&& V &* &* - - `); - - // Prevent from exiting so the banner will automatically display on startup. - if(args['reboot']) while(true) { await ns.sleep(1000 * 60); } -} +import {ArgParser} from "/scripts/lib/arg-parser"; + +/** + * Display an ASCII banner, optionally automatically after reboots. + * + * @param {NS} ns - BitBurner API + */ +export async function main(ns) { + // Setup + ns.disableLog('ALL'); + const argParser = new ArgParser('banner.js', 'Display an ASCII banner.', [ + {name: 'reboot', desc: 'Automatically display after game reboots', flags: ['-r', '--reboot'], default: false} + ]); + const args = argParser.parse(ns.args); + + // Help + if(args['help'] || args['_error'].length) + return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); + + ns.tprint(` + + &&&&&&&& O &&&&&&&& + &&& & && CDDDD &&&&&&&&&& + &&&& &&& && &&& &&&&&&&&&&&&& + &&&&& && && & .&&&. &&&&&&&&&&&&&& + &&&&&&&&&& && && &&&&& &&&&&&&&&&&&&&& + &&&&&&&& && & &&&&& &&&&&&&&&&&&&&&& + &&&&&&&&&& &&& &&&&& &&&&&&&&&&&&&&&& + &&&&&&&&&&&&&&&& *&&&* *&&&&&&&&&&&&&&& + &&&&&&&&&&&&& &&&&&&&&& *&&&&&&* .&& + &&&&&&&&&&& &&& & & &&& &&* .&&& + &&&&&&&&&& & & ,,,,,* .&&&& + &&&&& & & &&&&&&&&&&&&&&&& + && &&&& & & & &&&&&&&&&&&&&& + && &&&&&&& & & & &&&&&* &&&&& + && &&&&&&&& & & &&&&&&&* &&&* + && &&&&&&&&&&& & & &&&& &* + && &&&&&&&&& & & && && + &&& &&&&&& & & && && + &&& &&&& & & && && + &&&& &&& & & && && + &&&&&&& \\&/ && && + &&&&&& V &* &* + + `); + + // Prevent from exiting so the banner will automatically display on startup. + if(args['reboot']) while(true) { await ns.sleep(1000 * 60); } +} diff --git a/scripts/bitburner.js b/scripts/bitburner.js new file mode 100644 index 0000000..f55e7cd --- /dev/null +++ b/scripts/bitburner.js @@ -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); + } +} diff --git a/scripts/botnet-manager.js b/scripts/botnet-manager.js index c464ccc..9234298 100644 --- a/scripts/botnet-manager.js +++ b/scripts/botnet-manager.js @@ -1,16 +1,20 @@ import {ArgParser} from '/scripts/lib/arg-parser'; +import {Config} from '/scripts/lib/data-file'; import {Logger} from '/scripts/lib/logger'; import {copyWithDependencies} from '/scripts/copy'; +const configPath = '/etc/botnet.txt'; +const port = 1; + class Manager { running; workers = []; - constructor(ns, device, port, config = '/conf/botnet.txt') { + async constructor(ns, hostname) { ns.disableLog('ALL'); this.ns = ns; - this.config = config; - this.device = device; + this.config = new Config(configPath); + this.hostname = hostname; this.logger = new Logger(this.ns, [ () => `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` @@ -124,7 +128,8 @@ class Manager { export async function main(ns) { // Setup 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.', [ new ArgParser('copy', 'Copy file & dependencies to botnet', [ {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', [ {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', [ {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}, @@ -154,9 +160,9 @@ export async function main(ns) { // Run command if(args['_command'] == 'start') { // Start botnet manager - ns.tprint(`Starting ${hostname} as botnet manager`); - ns.tprint(`Connect more nodes with: run botnet-manager.js join [SERVER]`); - await new Manager(ns, hostname, portNum).start(); + 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(); } else if(args['_command'] == 'copy') { // Issue copy command await ns.writePort(portNum, JSON.stringify({ command: 'copy', @@ -176,6 +182,9 @@ export async function main(ns) { command: 'leave', 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 await ns.writePort(portNum, JSON.stringify({ command: 'run', diff --git a/scripts/copy.js b/scripts/copy.js index 88dd4ef..59c7b54 100644 --- a/scripts/copy.js +++ b/scripts/copy.js @@ -1,107 +1,107 @@ -import {ArgParser} from '/scripts/lib/arg-parser'; -import {maxThreads, progressBar} from '/scripts/lib/utils'; - -/** - * Copy a file & it's dependencies to a server. - * - * @param {NS} ns - BitBurner API - * @param {string} src - File to scan & copy - * @param {string} server - Device to copy files to - * @returns {Promise} - Array of copied files - */ -export async function copyWithDependencies(ns, src, server) { - const queue = [src], found = [src]; - while(queue.length) { - const file = queue.splice(0, 1)[0]; - const imports = new RegExp(/from ["']\.?(\/.+)["']/g); - const script = await ns.read(file); - let match; - while((match = imports.exec(script)) != null) { - const path = `${match[1]}.js`; - if(!found.includes(path)) found.push(path); - queue.push(path); - } - } - await ns.scp(found, server); - return found.reverse(); -} - -/** - * Copy a file & it's dependencies to a server. - * - * @param {NS} ns - BitBurner API - */ -export async function main(ns) { - // Setup - ns.disableLog('ALL'); - const argParser = new ArgParser('copy.js', 'Copy a file & it\'s dependencies to a server.', [ - {name: 'file', desc: 'File to copy'}, - {name: 'server', desc: 'Server to copy file(s) to'}, - {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: 'execute', desc: 'Start script after copying', flags: ['-e', '--execute'], 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}, - ]); - const args = argParser.parse(ns.args); - - // Help - if(args['help'] || args['_error'].length) - return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); - - // Banner - if(!args['quite']) { - ns.tprint('==================================================='); - ns.tprint(`Copying: ${args['server']}`); - ns.tprint('==================================================='); - ns.tprint(''); - ns.tprint('Copying Files:'); - await ns.sleep(500); - } - - // Copy files & create download bar - if(args['noDeps']) { - await ns.scp(args['file'], args['server']); - if(!args['quite']) await progressBar(ns, args['file']); - } else { - const files = await copyWithDependencies(ns, args['file'], args['server']); - if(!args['quite']) { - for(let file of files) { - await progressBar(ns, file); - } - } - } - - // Run the script if requested - if(args['execute']) { - const threads = args['cpu'] || maxThreads(ns, args['file'], args['server']) || 1; - if(!args['quite']) { - ns.tprint(''); - ns.tprint(`Executing with ${threads} thread${threads > 1 ? 's' : ''}...`); - await ns.sleep(500); - } - ns.killall(args['server']); - const pid = ns.exec(args['file'], args['server'], threads, ...args['args']); - if(!args['quite']) { - ns.tprint(!!pid ? 'Done!' : 'Failed to start'); - ns.tprint(''); - } - } - - // Done message - if(!args['quite']) { - ns.tprint(''); - ns.tprint('Done!'); - ns.tprint(''); - } -} - -/** - * BitBurner autocomplete. - * - * @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information - * @returns {string[]} - Pool of autocomplete options - */ -export function autocomplete(data) { - return [...data.servers, ...data.scripts]; -} +import {ArgParser} from '/scripts/lib/arg-parser'; +import {maxThreads, progressBar} from '/scripts/lib/utils'; + +/** + * Copy a file & it's dependencies to a server. + * + * @param {NS} ns - BitBurner API + * @param {string} src - File to scan & copy + * @param {string} server - Device to copy files to + * @returns {Promise} - Array of copied files + */ +export async function copyWithDependencies(ns, src, server) { + const queue = [src], found = [src]; + while(queue.length) { + const file = queue.splice(0, 1)[0]; + const imports = new RegExp(/from ["']\.?(\/.+)["']/g); + const script = await ns.read(file); + let match; + while((match = imports.exec(script)) != null) { + const path = `${match[1]}.js`; + if(!found.includes(path)) found.push(path); + queue.push(path); + } + } + await ns.scp(found, server); + return found.reverse(); +} + +/** + * Copy a file & it's dependencies to a server. + * + * @param {NS} ns - BitBurner API + */ +export async function main(ns) { + // Setup + ns.disableLog('ALL'); + const argParser = new ArgParser('copy.js', 'Copy a file & it\'s dependencies to a server.', [ + {name: 'file', desc: 'File to copy'}, + {name: 'server', desc: 'Server to copy file(s) to'}, + {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: 'execute', desc: 'Start script after copying', flags: ['-e', '--execute'], 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}, + ]); + const args = argParser.parse(ns.args); + + // Help + if(args['help'] || args['_error'].length) + return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); + + // Banner + if(!args['quite']) { + ns.tprint('==================================================='); + ns.tprint(`Copying: ${args['server']}`); + ns.tprint('==================================================='); + ns.tprint(''); + ns.tprint('Copying Files:'); + await ns.sleep(500); + } + + // Copy files & create download bar + if(args['noDeps']) { + await ns.scp(args['file'], args['server']); + if(!args['quite']) await progressBar(ns, args['file']); + } else { + const files = await copyWithDependencies(ns, args['file'], args['server']); + if(!args['quite']) { + for(let file of files) { + await progressBar(ns, file); + } + } + } + + // Run the script if requested + if(args['execute']) { + const threads = args['cpu'] || maxThreads(ns, args['file'], args['server']) || 1; + if(!args['quite']) { + ns.tprint(''); + ns.tprint(`Executing with ${threads} thread${threads > 1 ? 's' : ''}...`); + await ns.sleep(500); + } + ns.killall(args['server']); + const pid = ns.exec(args['file'], args['server'], threads, ...args['args']); + if(!args['quite']) { + ns.tprint(!!pid ? 'Done!' : 'Failed to start'); + ns.tprint(''); + } + } + + // Done message + if(!args['quite']) { + ns.tprint(''); + ns.tprint('Done!'); + ns.tprint(''); + } +} + +/** + * BitBurner autocomplete. + * + * @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information + * @returns {string[]} - Pool of autocomplete options + */ +export function autocomplete(data) { + return [...data.servers, ...data.scripts]; +} diff --git a/scripts/find-target.js b/scripts/find-target.js index c11a5ae..f083b75 100644 --- a/scripts/find-target.js +++ b/scripts/find-target.js @@ -1,68 +1,68 @@ -import {ArgParser} from '/scripts/lib/arg-parser'; -import {toCurrency} from '/scripts/lib/utils'; -import {scanNetwork} from '/scripts/crawler'; - -/** - * Sort array of servers based on the potential return/yield. - * - * @param {NS} ns - BitBurner API - * @param {string[]} servers - List of servers to sort based on yield - * @returns {[string, number][]} - Sorted list of servers & their potential yield per minute - */ -export function bestTarget(ns, servers) { - 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; - return 0; - }); -} - -/** - * Calculate the average return per minute when hacking a server. - * - * **Disclaimer:** Does not take into account security or weaken time. - * - * @param {NS} ns - BitBurner API - * @param {string} server - Server to calculate yield for - * @returns {number} - $/minute - */ -export function serverYield(ns, server) { - return (ns.hackAnalyze(server) * ns.getServerMaxMoney(server)) - * ((60 / (ns.getHackTime(server) / 1000)) * ns.hackAnalyzeChance(server)); -} - -/** - * Scan the network for the best server(s) to hack. - * - * @param ns {NS} - BitBurner API - * @returns {*} - */ -export function main(ns) { - // Setup - ns.disableLog('ALL'); - 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: '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: 'verbose', desc: 'Display the estimated income per minute per core', flags: ['-v', '--verbose'], default: false}, - ]); - const args = argParser.parse(ns.args); - - // Help - if(args['help'] || args['_error'].length) - return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); - - // Banner - ns.tprint('==================================================='); - ns.tprint(`Finding Targets:`); - ns.tprint('==================================================='); - - // Search & display results - const [servers, ignore] = scanNetwork(ns); - bestTarget(ns, servers).map(s => [...s, ns.hasRootAccess(s[0])]) - .filter(s => (!args['rooted'] || s[2]) || (!args['notRooted'] || !s[2])) - .filter((s, i) => i < args['count']) - .map(s => `${s[0]}${args['verbose'] ? ` (~${toCurrency(s[1])}/min)` : ''}`) - .forEach((s, i) => ns.tprint(`${i + 1}) ${s}`)); - ns.tprint(''); -} +import {ArgParser} from '/scripts/lib/arg-parser'; +import {toCurrency} from '/scripts/lib/utils'; +import {scanNetwork} from '/scripts/crawler'; + +/** + * Sort array of servers based on the potential return/yield. + * + * @param {NS} ns - BitBurner API + * @param {string[]} servers - List of servers to sort based on yield + * @returns {[string, number][]} - Sorted list of servers & their potential yield per minute + */ +export function bestTarget(ns, servers) { + 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; + return 0; + }); +} + +/** + * Calculate the average return per minute when hacking a server. + * + * **Disclaimer:** Does not take into account security or weaken time. + * + * @param {NS} ns - BitBurner API + * @param {string} server - Server to calculate yield for + * @returns {number} - $/minute + */ +export function serverYield(ns, server) { + return (ns.hackAnalyze(server) * ns.getServerMaxMoney(server)) + * ((60 / (ns.getHackTime(server) / 1000)) * ns.hackAnalyzeChance(server)); +} + +/** + * Scan the network for the best server(s) to hack. + * + * @param ns {NS} - BitBurner API + * @returns {*} + */ +export function main(ns) { + // Setup + ns.disableLog('ALL'); + 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: '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: 'verbose', desc: 'Display the estimated income per minute per core', flags: ['-v', '--verbose'], default: false}, + ]); + const args = argParser.parse(ns.args); + + // Help + if(args['help'] || args['_error'].length) + return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); + + // Banner + ns.tprint('==================================================='); + ns.tprint(`Finding Targets:`); + ns.tprint('==================================================='); + + // Search & display results + const [servers, ignore] = scanNetwork(ns); + bestTarget(ns, servers).map(s => [...s, ns.hasRootAccess(s[0])]) + .filter(s => (!args['rooted'] || s[2]) || (!args['notRooted'] || !s[2])) + .filter((s, i) => i < args['count']) + .map(s => `${s[0]}${args['verbose'] ? ` (~${toCurrency(s[1])}/min)` : ''}`) + .forEach((s, i) => ns.tprint(`${i + 1}) ${s}`)); + ns.tprint(''); +} diff --git a/scripts/lib/data-file.js b/scripts/lib/data-file.js new file mode 100644 index 0000000..527d32a --- /dev/null +++ b/scripts/lib/data-file.js @@ -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} - 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} - Save complete + */ + async save(values) { + await this.ns.write(this.path, JSON.stringify(values), 'w'); + } +} diff --git a/scripts/lib/port-helper.js b/scripts/lib/port-helper.js new file mode 100644 index 0000000..566a9f6 --- /dev/null +++ b/scripts/lib/port-helper.js @@ -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]; } +} diff --git a/scripts/rm.js b/scripts/rm.js index e56d6a5..eff51c9 100644 --- a/scripts/rm.js +++ b/scripts/rm.js @@ -1,39 +1,39 @@ -import {ArgParser} from '/scripts/lib/arg-parser'; - -/** - * Recursively delete files inside a path. Equivalent to the Unix "rm -r". - * @param {NS} ns - BitBurner API - */ -export function main(ns) { - // Setup - ns.disableLog('ALL'); - const argParser = new ArgParser('rm.js', 'Recursively delete files inside a directory', [ - {name: 'path', desc: 'Path to recursively search'}, - {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: 'recursive', desc: 'Delete everything inside directory', flags: ['-r', '--recursive'], default: true} - ]); - const args = argParser.parse(ns.args); - - // Help - if(args['help'] || args['_error'].length) - return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); - - // Run - ns.ls(args['server'], args['path']) - .filter(f => new RegExp(/\.(exe|lit|msg)$/g).test(f) ? args['force'] : true) - .forEach(f => ns.rm(f, args['server'])); -} - -/** - * BitBurner autocomplete. - * - * @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information - * @returns {string[]} - Pool of autocomplete options - */ -export function autocomplete(data) { - return [...data.txts, ...data.scripts] - .map(file => file.split('/').slice(0, -1).join('/')) - .filter((path, i, arr) => arr.indexOf(path) == i) - .concat(data.txts, data.scripts); -} +import {ArgParser} from '/scripts/lib/arg-parser'; + +/** + * Recursively delete files inside a path. Equivalent to the Unix "rm -r". + * @param {NS} ns - BitBurner API + */ +export function main(ns) { + // Setup + ns.disableLog('ALL'); + const argParser = new ArgParser('rm.js', 'Recursively delete files inside a directory', [ + {name: 'path', desc: 'Path to recursively search'}, + {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: 'recursive', desc: 'Delete everything inside directory', flags: ['-r', '--recursive'], default: true} + ]); + const args = argParser.parse(ns.args); + + // Help + if(args['help'] || args['_error'].length) + return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); + + // Run + ns.ls(args['server'], args['path']) + .filter(f => new RegExp(/\.(exe|lit|msg)$/g).test(f) ? args['force'] : true) + .forEach(f => ns.rm(f, args['server'])); +} + +/** + * BitBurner autocomplete. + * + * @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information + * @returns {string[]} - Pool of autocomplete options + */ +export function autocomplete(data) { + return [...data.txts, ...data.scripts] + .map(file => file.split('/').slice(0, -1).join('/')) + .filter((path, i, arr) => arr.indexOf(path) == i) + .concat(data.txts, data.scripts); +} diff --git a/scripts/server-manager.js b/scripts/server-manager.js index e817e52..1f68c54 100644 --- a/scripts/server-manager.js +++ b/scripts/server-manager.js @@ -1,87 +1,87 @@ -import {ArgParser} from '/scripts/lib/arg-parser'; -import {Logger} from '/scripts/lib/logger'; -import {maxThreads, toCurrency} from '/scripts/lib/utils'; -import {copyWithDependencies} from "/scripts/copy"; - -/** - * Automate the buying & upgrading of servers. - * - * @param {NS} ns - BitBurner API - */ -export async function main(ns) { - // Setup - ns.disableLog('ALL'); - let servers = ns.getPurchasedServers(); - const logger = new Logger(ns, [ - () => `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.', [ - {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: '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: '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: '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 serverPrefix = 'botnet_' - const maxRam = ns.getPurchasedServerMaxRam(); - const minRamCost = ns.getPurchasedServerCost(args['ram']); - - async function startScript(server) { - await copyWithDependencies(ns, args['script'], server); - const threads = args['cpu'] || maxThreads(ns, args['script'], server) || 1; - const pid = ns.exec(args['script'], server, threads, ...args['args']); - logger.log(`Starting "${args['script']}" with ${threads} thread${threads > 1 ? 's' : ''}`); - logger[pid == -1 ? 'warn' : 'log'](pid == -1 ? 'Done!' : 'Failed to start'); - } - - // Help - if(args['help'] || args['_error'].length) - return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); - - // Main loop - // noinspection InfiniteLoopJS - while(true) { - servers = ns.getPurchasedServers(); - const balance = ns.getServerMoneyAvailable('home'); - // Purchase new server if we can afford it - if(servers.length < args['limit'] && balance - minRamCost > args['balance']) { - logger.log(`Buying server (${args['ram']} GB): ${toCurrency(minRamCost)}`); - ns.purchaseServer(`${serverPrefix}${servers.length}`, args['ram']); - - // Run the script if requested - if(args['script']) await startScript(`${serverPrefix}${servers.length}`); - } else { // Check for upgrades - let upgrades = servers.map(server => { - // 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); - if(ram > maxRam) ram = null; - return { - server, - ram, - cost: ram ? ns.getPurchasedServerCost(ram) : null - } - }); - upgrades = upgrades.sort((a, b) => { // Sort by price - if(a.cost < b.cost) return 1; - if(a.cost < b.cost) return -1; - return 0; - }); - - // Do the cheapest upgrade if we can afford it - const upgrade = upgrades[0]; - if(upgrade && !!upgrade.ram && balance - upgrade.cost > args['balance']) { - logger.log(`Upgrading ${upgrade.server}: ${upgrade.ram} GB / ${toCurrency(upgrade.cost)}`); - ns.killall(upgrade.server); - ns.deleteServer(upgrade.server); - ns.purchaseServer(upgrade.server, upgrade.ram); - - // Run the script if requested - if(args['script']) await startScript(upgrade.server); - } - } - await ns.sleep(args['sleep'] * 1000); - } -} +import {ArgParser} from '/scripts/lib/arg-parser'; +import {Logger} from '/scripts/lib/logger'; +import {maxThreads, toCurrency} from '/scripts/lib/utils'; +import {copyWithDependencies} from "/scripts/copy"; + +/** + * Automate the buying & upgrading of servers. + * + * @param {NS} ns - BitBurner API + */ +export async function main(ns) { + // Setup + ns.disableLog('ALL'); + let servers = ns.getPurchasedServers(); + const logger = new Logger(ns, [ + () => `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.', [ + {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: '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: '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: '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 serverPrefix = 'botnet_' + const maxRam = ns.getPurchasedServerMaxRam(); + const minRamCost = ns.getPurchasedServerCost(args['ram']); + + async function startScript(server) { + await copyWithDependencies(ns, args['script'], server); + const threads = args['cpu'] || maxThreads(ns, args['script'], server) || 1; + const pid = ns.exec(args['script'], server, threads, ...args['args']); + logger.log(`Starting "${args['script']}" with ${threads} thread${threads > 1 ? 's' : ''}`); + logger[pid == -1 ? 'warn' : 'log'](pid == -1 ? 'Done!' : 'Failed to start'); + } + + // Help + if(args['help'] || args['_error'].length) + return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command'])); + + // Main loop + // noinspection InfiniteLoopJS + while(true) { + servers = ns.getPurchasedServers(); + const balance = ns.getServerMoneyAvailable('home'); + // Purchase new server if we can afford it + if(servers.length < args['limit'] && balance - minRamCost > args['balance']) { + logger.log(`Buying server (${args['ram']} GB): ${toCurrency(minRamCost)}`); + ns.purchaseServer(`${serverPrefix}${servers.length}`, args['ram']); + + // Run the script if requested + if(args['script']) await startScript(`${serverPrefix}${servers.length}`); + } else { // Check for upgrades + let upgrades = servers.map(server => { + // 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); + if(ram > maxRam) ram = null; + return { + server, + ram, + cost: ram ? ns.getPurchasedServerCost(ram) : null + } + }); + upgrades = upgrades.sort((a, b) => { // Sort by price + if(a.cost < b.cost) return 1; + if(a.cost < b.cost) return -1; + return 0; + }); + + // Do the cheapest upgrade if we can afford it + const upgrade = upgrades[0]; + if(upgrade && !!upgrade.ram && balance - upgrade.cost > args['balance']) { + logger.log(`Upgrading ${upgrade.server}: ${upgrade.ram} GB / ${toCurrency(upgrade.cost)}`); + ns.killall(upgrade.server); + ns.deleteServer(upgrade.server); + ns.purchaseServer(upgrade.server, upgrade.ram); + + // Run the script if requested + if(args['script']) await startScript(upgrade.server); + } + } + await ns.sleep(args['sleep'] * 1000); + } +}