diff --git a/README.md b/README.md index 15fd464..f34cc6e 100644 --- a/README.md +++ b/README.md @@ -106,22 +106,41 @@ The container is locked down, so there is no networking, limited memory and CPU 0. Install Docker 18+ 0. Install Node 10+ 0. Run `npm i` -0. Fill out `config.json` - - `owner` - The owner(s) of the bot. - Use an array for multiple owners. - - `token` - The bot token. - - `prefix` - The prefix for commands. - - `codePrefix` - The prefix for code evaluation. - - `languages` Languages to use. - The language names here are different from the user-facing ones. - Check the filenames in `src/languages/` for the language names. - Change to `null` to enable all languages. - - `memory` Max memory usage of a container. - - `cpu` Max CPU usage of a container. - - `timeout` Time limit for code in milliseconds. - - `prepare` Whether to start containers on setup. - Setting to true will speed up the first eval, but that language might not be used. - - `parallel` Whether to build images and container in parallel. - Faster, but will take more resources. - - `concurrent` Number of code evaluations per language than can run at a time. +0. Fill out `config.json` as described in the configuration section below 0. Run `node .` + +## Configuration + +### Bot + +- `owner` - The owner(s) of the bot. + Use an array for multiple owners. +- `token` - The bot token. +- `prefix` - The prefix for commands. +- `codePrefix` - The prefix for code evaluation. + +### Setup + +- `languages` Languages to use. + The language names here are different from the user-facing ones. + Check the filenames in `src/languages/` for the language names. + Change to `null` to enable all languages. +- `prepare` Whether to start containers on setup. + Setting to true will speed up the first eval, but that language might not be used. +- `parallel` Whether to build images in parallel. + Will also setup containers in parallel if `prepare` is set. + Faster, but will take more resources. +- `cleanup` Interval in minutes to occasionally kill all containers. + Set to `0` to disable. + +### Compilers + +For each of these options, you can use either the expected value to set it for all compilers or an object with compiler names to the expected values. +If using an object, you can set the default with the `default` key. +The compiler names are the folder names under `docker/`. + +- `memory` Max memory usage of a container. +- `cpu` Max CPU usage of a container. +- `timeout` Time limit for code in milliseconds. +- `concurrent` Number of code evaluations than can run at a time per container. + The more that can run, the more resources a container would need. diff --git a/config.example.json b/config.example.json index 3ec0007..d9f7b57 100644 --- a/config.example.json +++ b/config.example.json @@ -8,10 +8,11 @@ "python", "javascript" ], + "prepare": false, + "parallel": false, + "cleanup": 60, "memory": "256m", "cpus": "0.25", "timeout": 10000, - "prepare": false, - "parallel": false, "concurrent": 10 } diff --git a/src/struct/LanguageHandler.js b/src/struct/LanguageHandler.js index efc6661..a26eebe 100644 --- a/src/struct/LanguageHandler.js +++ b/src/struct/LanguageHandler.js @@ -65,6 +65,10 @@ class LanguageHandler extends AkairoHandler { await this.buildImage(dockerID); } } + + if (this.client.config.cleanup > 0) { + setInterval(() => this.cleanup().catch(() => null), this.client.config.cleanup * 60 * 1000); + } } async buildImage(dockerID) { @@ -72,7 +76,8 @@ class LanguageHandler extends AkairoHandler { await util.promisify(childProcess.exec)(`docker build -t "1computer1/comp_iler:${dockerID}" ${folder}`); // eslint-disable-next-line no-console console.log(`Built image 1computer1/comp_iler:${dockerID}.`); - this.queues.set(dockerID, new Queue(10)); + const concurrent = this.getCompilerConfig(dockerID, 'concurrent', 'number'); + this.queues.set(dockerID, new Queue(concurrent)); if (this.client.config.prepare) { await this.setupContainer(dockerID); } @@ -83,11 +88,13 @@ class LanguageHandler extends AkairoHandler { return this.containers.get(dockerID); } + const cpus = this.getCompilerConfig(dockerID, 'cpus', 'string'); + const memory = this.getCompilerConfig(dockerID, 'memory', 'string'); const name = `comp_iler-${dockerID}-${Date.now()}`; const proc = childProcess.spawn('docker', [ 'run', '--rm', `--name=${name}`, '-u1000', '-w/tmp/', '-dt', - '--net=none', `--cpus=${this.client.config.cpus}`, - `-m=${this.client.config.memory}`, `--memory-swap=${this.client.config.memory}`, + '--net=none', `--cpus=${cpus}`, + `-m=${memory}`, `--memory-swap=${memory}`, `1computer1/comp_iler:${dockerID}`, '/bin/sh' ]); @@ -120,8 +127,9 @@ class LanguageHandler extends AkairoHandler { name, '/bin/sh', '/var/run/run.sh', code ]); + const timeout = this.getCompilerConfig(dockerID, 'timeout', 'number'); try { - const result = await this.handleSpawn(proc, true); + const result = await this.handleSpawn(proc, timeout); return result; } catch (err) { this.containers.delete(dockerID); @@ -131,12 +139,12 @@ class LanguageHandler extends AkairoHandler { }); } - handleSpawn(proc, withTimeout = false) { + handleSpawn(proc, timeout = null) { return new Promise((resolve, reject) => { - if (withTimeout) { + if (timeout !== null) { setTimeout(() => { reject(new Error('Timed out')); - }, this.client.config.timeout); + }, timeout); } let data = ''; @@ -177,6 +185,15 @@ class LanguageHandler extends AkairoHandler { cleanup() { return Promise.all(this.containers.map(({ name }) => this.kill(name))); } + + getCompilerConfig(dockerID, key, type) { + const o = this.client.config[key]; + return typeof o === type + ? o + : o[dockerID] !== null + ? o[dockerID] + : o.default; + } } module.exports = LanguageHandler;