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";
/**
* 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); }
}

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 {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',

View File

@ -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<string[]>} - 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<string[]>} - 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];
}

View File

@ -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('');
}

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';
/**
* 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);
}

View File

@ -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);
}
}