Compare commits

..

No commits in common. "8727a545c635ed7ac174cf2c089a7e34b5a412ef" and "91420c39d7ebf8fe65cfac04634c46d2d35b8bdd" have entirely different histories.

28 changed files with 570 additions and 1084 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
data/

238
README.MD
View File

@ -1,238 +0,0 @@
<h1 align="center">
<a href="https://github.com/engineer-man/piston"><img src="docs/images/icon_circle.svg" width="25" height="25" alt="engineer-man piston"></a>
Piston
</h1>
<h3 align="center">A high performance general purpose code execution engine.</h3>
<br>
<p align="center">
<a href="https://github.com/engineer-man/piston/commits/master">
<img src="https://img.shields.io/github/last-commit/engineer-man/piston.svg?style=for-the-badge&logo=github&logoColor=white"
alt="GitHub last commit">
<a href="https://github.com/engineer-man/piston/issues">
<img src="https://img.shields.io/github/issues/engineer-man/piston.svg?style=for-the-badge&logo=github&logoColor=white"
alt="GitHub issues">
<a href="https://github.com/engineer-man/piston/pulls">
<img src="https://img.shields.io/github/issues-pr-raw/engineer-man/piston.svg?style=for-the-badge&logo=github&logoColor=white"
alt="GitHub pull requests">
</p>
---
<h4 align="center">
<a href="#About">About</a> •
<a href="#Public-API">Public API</a> •
<a href="#Getting-Started">Getting Started</a> •
<a href="#Usage">Usage</a> •
<a href="#Supported-Languages">Supported Languages</a> •
<a href="#Principle-of-Operation">Principles</a> •
<a href="#Security">Security</a> •
<a href="#License">License</a>
</h4>
---
<br>
# About
<h4>
Piston is a high performance general purpose code execution engine. It excels at running untrusted and
possibly malicious code without fear from any harmful effects.
</h4>
<br>
It's used in numerous places including:
* [EMKC Challenges](https://emkc.org/challenges),
* [EMKC Weekly Contests](https://emkc.org/contests),
* [Engineer Man Discord Server](https://discord.gg/engineerman),
* [I Run Code (Discord Bot)](https://github.com/engineer-man/piston-bot) bot as well as 1300+ other servers
and 100+ direct integrations.
To get it in your own server, go here: https://emkc.org/run.
<br>
# Public API
- Requires no installation and you can use it immediately.
- Reference the Versions/Execute sections below to learn about the request and response formats.
<br>
When using the public Piston API, use the base URL:
```
https://emkc.org/api/v1/piston
```
#### GET
```
https://emkc.org/api/v1/piston/versions
```
#### POST
```
https://emkc.org/api/v1/piston/execute
```
> Important Note: The Piston API is rate limited to 5 requests per second. If you have a need for more requests than that
and it's for a good cause, please reach out to me (EngineerMan#0001) on [Discord](https://discord.gg/engineerman)
so we can discuss potentially getting you an unlimited key.
<br>
# Getting Started
### Host System Package Dependencies
- Docker
- Docker Compose
- Node JS
#### After system dependencies are installed, clone this repository:
```sh
# clone and enter repo
git clone https://github.com/engineer-man/piston
```
#### Installation
- docker-compose up
#### CLI Usage
- `cli/execute [language] [file path] [args]`
<br>
# Usage
### CLI
```sh
lxc/execute [language] [file path] [args]
```
### API
To use the API, it must first be started. Please note that if root is required to access
LXC then the API must also be running as root. To start the API, run the following:
```
cd api
./start
```
For your own local installation, the API is available at:
```
http://127.0.0.1:2000
```
#### Versions Endpoint
`GET /versions`
This endpoint will return the supported languages along with the current version and aliases. To execute
code for a particular language using the `/execute` endpoint, either the name or one of the aliases must
be provided.
```json
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"name": "awk",
"aliases": ["awk"],
"version": "1.3.3"
},
{
"name": "bash",
"aliases": ["bash"],
"version": "4.4.20"
},
{
"name": "c",
"aliases": ["c"],
"version": "7.5.0"
}
]
```
#### Execute Endpoint
`POST /execute`
This endpoint requests execution of some arbitrary code.
- `language` (**required**) The language to use for execution, must be a string and supported by Piston (see list below).
- `source` (**required**) The source code to execute, must be a string.
- `stdin` (*optional*) The text to pass as stdin to the program. Must be a string or left out of the request.
- `args` (*optional*) The arguments to pass to the program. Must be an array or left out of the request.
```json
{
"language": "js",
"source": "console.log(process.argv)",
"stdin": "",
"args": [
"1",
"2",
"3"
]
}
```
A typical response upon successful execution will contain the `language`, `version`, `output` which
is a combination of both `stdout` and `stderr` but in chronological order according to program output,
as well as separate `stdout` and `stderr`.
```json
HTTP/1.1 200 OK
Content-Type: application/json
{
"ran": true,
"language": "js",
"version": "12.13.0",
"output": "[ '/usr/bin/node',\n '/tmp/code.code',\n '1',\n '2',\n '3' ]",
"stdout": "[ '/usr/bin/node',\n '/tmp/code.code',\n '1',\n '2',\n '3' ]",
"stderr": ""
}
```
If a problem exists with the request, a `400` status code is returned and the reason in the `message` key.
```json
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"message": "Supplied language is not supported by Piston"
}
```
<br>
# Supported Languages
`python`,
<br>
<!--
# Principle of Operation
Piston utilizes LXC as the primary mechanism for sandboxing. There is a small API written in Node which takes
in execution requests and executes them in the container. High level, the API writes
a temporary source and args file to `/tmp` and that gets mounted read-only along with the execution scripts into the container.
The source file is either ran or compiled and ran (in the case of languages like c, c++, c#, go, etc.).
<br>
<!--
# Security
LXC provides a great deal of security out of the box in that it's separate from the system.
Piston takes additional steps to make it resistant to
various privilege escalation, denial-of-service, and resource saturation threats. These steps include:
- Disabling outgoing network interaction
- Capping max processes at 64 (resists `:(){ :|: &}:;`, `while True: os.fork()`, etc.)
- Capping max files at 2048 (resists various file based attacks)
- Mounting all resources read-only (resists `sudo rm -rf --no-preserve-root /`)
- Cleaning up all temp space after each execution (resists out of drive space attacks)
- Running as a variety of unprivileged users
- Capping runtime execution at 3 seconds
- Capping stdout to 65536 characters (resists yes/no bombs and runaway output)
- SIGKILLing misbehaving code
-->
<br>
<!-- Someone please do this -->
# License
Piston is licensed under the MIT license.

View File

@ -8,7 +8,6 @@
"snakecasejs"
],
"extends": "eslint:recommended",
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 12
},
@ -28,11 +27,11 @@
],
"quotes": [
"error",
"single"
"double"
],
"semi": [
"error",
"always"
"never"
],
"no-unused-vars": ["error", { "argsIgnorePattern": "^_"}],
"snakecasejs/snakecasejs": "warn"

View File

@ -1,13 +1,5 @@
FROM node:15.8.0-alpine3.13
RUN apk add --no-cache gnupg tar bash coreutils shadow
RUN for i in $(seq 1000 1500); do \
groupadd -g $i runner$i && \
useradd -M runner$i -g $i -u $i && \
echo "runner$i soft nproc 64" >> /etc/security/limits.conf && \
echo "runner$i hard nproc 64" >> /etc/security/limits.conf && \
echo "runner$i soft nofile 2048" >> /etc/security/limits.conf && \
echo "runner$i hard nofile 2048" >> /etc/security/limits.conf ;\
done
RUN apk add --no-cache gnupg tar bash coreutils
ENV NODE_ENV=production
WORKDIR /piston_api

View File

@ -9,16 +9,14 @@
"is-docker": "^2.1.1",
"js-yaml": "^4.0.0",
"logplease": "^1.2.15",
"nocamel": "HexF/nocamel#patch-1",
"nocamel": "*",
"node-fetch": "^2.6.1",
"semver": "^7.3.4",
"uuid": "^8.3.2",
"yargs": "^16.2.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.20.0",
"eslint-plugin-snakecasejs": "^2.2.0"
},
"license": "MIT"
}
}

View File

@ -1,55 +1,54 @@
const globals = require('./globals');
const logger = require('logplease').create('cache');
const fs = require('fs/promises'),
fss = require('fs'),
path = require('path');
const globals = require("./globals")
const logger = require("logplease").create("cache")
const fs = require("fs"), path = require("path")
const util = require("util")
const cache = new Map();
const cache = new Map()
module.exports = {
cache_key: (context, key) => Buffer.from(`${context}-${key}`).toString('base64'),
cache_key: (context, key) => Buffer.from(`${context}-${key}`).toString("base64"),
has(key){
return cache.has(key) && cache.get(key).expiry > Date.now();
return cache.has(key) && cache.get(key).expiry > Date.now()
},
async get(key, callback, ttl=globals.cache_ttl){
logger.debug('get:', key);
logger.debug("get:", key)
if(module.exports.has(key)){
logger.debug('hit:',key);
return cache.get(key).data;
logger.debug("hit:",key)
return cache.get(key).data
}
logger.debug('miss:', key);
var data = await callback();
cache.set(key, {data, expiry: Date.now() + ttl});
return data;
logger.debug("miss:", key)
var data = await callback()
cache.set(key, {data, expiry: Date.now() + ttl})
return data
},
async flush(cache_dir){
logger.info('Flushing cache');
logger.info("Flushing cache")
cache.forEach((v,k)=>{
var file_path = path.join(cache_dir, k);
var file_path = path.join(cache_dir, k)
if(v.expiry < Date.now()){
//remove from cache
cache.delete(k);
cache.delete(k)
fs.stat(file_path, (err, stats)=>{
if(err) return; //ignore - probably hasn't been flushed yet
if(err) return //ignore - probably hasn't been flushed yet
if(stats.is_file())
fs.rm(file_path, (err)=>{
if(err) logger.warn(`Couldn't clean up on-disk cache file ${k}`);
});
});
if(err) logger.warn(`Couldn't clean up on-disk cache file ${k}`)
})
})
}else{
//flush to disk
fs.write_file(file_path, JSON.stringify(v),()=>{});
fs.write_file(file_path, JSON.stringify(v),()=>{})
}
});
})
},
async load(cache_dir){
return fs.readdir(cache_dir)
return util.promisify(fs.readdir)(cache_dir)
.then(files => Promise.all(files.map(
async file => {
cache.set(file, JSON.parse(fss.read_file_sync(path.join(cache_dir,file)).toString()));
cache.set(file, JSON.parse(fs.read_file_sync(path.join(cache_dir,file)).toString()))
}
)));
)))
}
};
}

View File

@ -1,9 +1,9 @@
const fss = require('fs');
const yargs = require('yargs');
const hide_bin = require('yargs/helpers').hideBin; //eslint-disable-line snakecasejs/snakecasejs
const Logger = require('logplease');
const logger = Logger.create('config');
const yaml = require('js-yaml');
const fs = require("fs")
const yargs = require("yargs")
const hide_bin = require("yargs/helpers").hideBin //eslint-disable-line snakecasejs/snakecasejs
const Logger = require("logplease")
const logger = Logger.create("config")
const yaml = require("js-yaml")
const header = `#
# ____ _ _
@ -16,151 +16,151 @@ const header = `#
# github.com/engineer-man/piston
#
`;
`
const argv = yargs(hide_bin(process.argv))
.usage('Usage: $0 -c [config]')
.demandOption('c') //eslint-disable-line snakecasejs/snakecasejs
.option('config', {
alias: 'c',
describe: 'config file to load from',
default: '/piston/config.yaml'
.usage("Usage: $0 -c [config]")
.demandOption("c") //eslint-disable-line snakecasejs/snakecasejs
.option("config", {
alias: "c",
describe: "config file to load from",
default: "/piston/config.yaml"
})
.option('make-config', {
alias: 'm',
type: 'boolean',
describe: 'create config file and populate defaults if it does not already exist'
}).argv;
.option("make-config", {
alias: "m",
type: "boolean",
describe: "create config file and populate defaults if it does not already exist"
}).argv
const options = [
{
key: 'log_level',
desc: 'Level of data to log',
default: 'INFO',
key: "log_level",
desc: "Level of data to log",
default: "INFO",
/* eslint-disable snakecasejs/snakecasejs */
options: Object.values(Logger.LogLevels),
validators: [x=>Object.values(Logger.LogLevels).includes(x) || `Log level ${x} does not exist`]
/* eslint-enable snakecasejs/snakecasejs */
},
{
key: 'bind_address',
desc: 'Address to bind REST API on\nThank @Bones for the number',
default: '0.0.0.0:6969',
key: "bind_address",
desc: "Address to bind REST API on\nThank @Bones for the number",
default: "0.0.0.0:6969",
validators: []
},
{
key: 'data_directory',
desc: 'Absolute path to store all piston related data at',
default: '/piston',
validators: [x=> fss.exists_sync(x) || `Directory ${x} does not exist`]
key: "data_directory",
desc: "Absolute path to store all piston related data at",
default: "/piston",
validators: [x=> fs.exists_sync(x) || `Directory ${x} does not exist`]
},
{
key: 'cache_ttl',
desc: 'Time in milliseconds to keep data in cache for at a maximum',
key: "cache_ttl",
desc: "Time in milliseconds to keep data in cache for at a maximum",
default: 60 * 60 * 1000,
validators: []
},
{
key: 'cache_flush_time',
desc: 'Interval in milliseconds to flush cache to disk at',
key: "cache_flush_time",
desc: "Interval in milliseconds to flush cache to disk at",
default: 90 * 60 * 1000, //90 minutes
validators: []
},
{
key: 'state_flush_time',
desc: 'Interval in milliseconds to flush state to disk at',
key: "state_flush_time",
desc: "Interval in milliseconds to flush state to disk at",
default: 5000, // 5 seconds (file is tiny)
validators: []
},
{
key: 'runner_uid_min',
desc: 'Minimum uid to use for runner',
key: "runner_uid_min",
desc: "Minimum uid to use for runner",
default: 1000,
validators: []
},
{
key: 'runner_uid_max',
desc: 'Maximum uid to use for runner',
key: "runner_uid_max",
desc: "Maximum uid to use for runner",
default: 1500,
validators: []
},
{
key: 'runner_gid_min',
desc: 'Minimum gid to use for runner',
key: "runner_gid_min",
desc: "Minimum gid to use for runner",
default: 1000,
validators: []
},
{
key: 'runner_gid_max',
desc: 'Maximum gid to use for runner',
key: "runner_gid_max",
desc: "Maximum gid to use for runner",
default: 1500,
validators: []
}
];
]
const default_config = [
...header.split('\n'),
...header.split("\n"),
...options.map(option => `
${[
...option.desc.split('\n'),
option.options?('Options: ' + option.options.join(', ')):''
].filter(x=>x.length>0).map(x=>`# ${x}`).join('\n')}
...option.desc.split("\n"),
option.options?("Options: " + option.options.join(", ")):""
].filter(x=>x.length>0).map(x=>`# ${x}`).join("\n")}
${option.key}: ${option.default}
`)].join('\n');
`)].join("\n")
logger.info(`Loading Configuration from ${argv.config}`);
!!argv['make-config'] && logger.debug('Make configuration flag is set');
logger.info(`Loading Configuration from ${argv.config}`)
!!argv["make-config"] && logger.debug("Make configuration flag is set")
if(!!argv['make-config'] && !fss.exists_sync(argv.config)){
logger.info('Writing default configuration...');
if(!!argv["make-config"] && !fs.exists_sync(argv.config)){
logger.info("Writing default configuration...")
try {
fss.write_file_sync(argv.config, default_config);
fs.write_file_sync(argv.config, default_config)
} catch (err) {
logger.error('Error writing default configuration:', err.message);
process.exit(1);
logger.error("Error writing default configuration:", err.message)
process.exit(1)
}
}
var config = {};
logger.debug('Reading config file');
var config = {}
logger.debug("Reading config file")
try{
const cfg_content = fss.read_file_sync(argv.config);
const cfg_content = fs.read_file_sync(argv.config)
try{
config = yaml.load(cfg_content);
config = yaml.load(cfg_content)
}catch(err){
logger.error('Error parsing configuration file:', err.message);
process.exit(1);
logger.error("Error parsing configuration file:", err.message)
process.exit(1)
}
}catch(err){
logger.error('Error reading configuration from disk:', err.message);
process.exit(1);
logger.error("Error reading configuration from disk:", err.message)
process.exit(1)
}
logger.debug('Validating config entries');
var errored=false;
logger.debug("Validating config entries")
var errored=false
options.forEach(opt => {
logger.debug('Checking key',opt.key);
var cfg_val = config[opt.key];
logger.debug("Checking key",opt.key)
var cfg_val = config[opt.key]
if(cfg_val == undefined){
errored = true;
logger.error(`Config key ${opt.key} does not exist on currently loaded configuration`);
return;
errored = true
logger.error(`Config key ${opt.key} does not exist on currently loaded configuration`)
return
}
opt.validators.forEach(validator => {
var response = validator(cfg_val);
var response = validator(cfg_val)
if(response !== true){
errored = true;
logger.error(`Config key ${opt.key} failed validation:`, response);
return;
errored = true
logger.error(`Config key ${opt.key} failed validation:`, response)
return
}
});
});
})
})
if(errored) process.exit(1);
if(errored) process.exit(1)
logger.info('Configuration successfully loaded');
logger.info("Configuration successfully loaded")
module.exports = config;
module.exports = config

View File

@ -1,146 +1,146 @@
const logger = require('logplease').create('executor/job');
const { v4: uuidv4 } = require('uuid');
const cp = require('child_process');
const path = require('path');
const config = require('../config');
const globals = require('../globals');
const fs = require('fs/promises');
const logger = require("logplease").create("executor/job")
const { v4: uuidv4 } = require("uuid")
const cp = require("child_process")
const path = require("path")
const config = require("../config");
const globals = require("../globals");
const fs = require("fs");
const util = require("util");
const job_states = {
READY: Symbol('Ready to be primed'),
PRIMED: Symbol('Primed and ready for execution'),
EXECUTED: Symbol('Executed and ready for cleanup')
};
READY: Symbol("Ready to be primed"),
PRIMED: Symbol("Primed and ready for execution"),
EXECUTED: Symbol("Executed and ready for cleanup")
}
var uid=0;
var gid=0;
class Job {
constructor(runtime, files, args, stdin, timeouts, main){
this.uuid = uuidv4();
this.runtime = runtime;
this.files = files;
this.args = args;
this.stdin = stdin;
this.timeouts = timeouts;
this.main = main;
this.uuid = uuidv4()
this.runtime = runtime
this.files = files
this.args = args
this.stdin = stdin
this.timeouts = timeouts
this.main = main
if(!this.files.map(f=>f.name).includes(this.main))
throw new Error(`Main file "${this.main}" will not be written to disk`);
if(!Object.keys(this.files).includes(this.main))
throw new Error(`Main file "${this.main}" will not be written to disk`)
this.uid = config.runner_uid_min + uid;
this.gid = config.runner_gid_min + gid;
uid++;
gid++;
uid++
gid++
uid %= (config.runner_uid_max - config.runner_uid_min) + 1;
gid %= (config.runner_gid_max - config.runner_gid_min) + 1;
uid %= (config.runner_uid_max - config.runner_uid_min) + 1
gid %= (config.runner_gid_max - config.runner_gid_min) + 1
this.state = job_states.READY;
this.dir = path.join(config.data_directory, globals.data_directories.jobs, this.uuid);
}
async prime(){
logger.info(`Priming job uuid=${this.uuid}`);
logger.info(`Priming job uuid=${this.uuid}`)
logger.debug('Writing files to job cache');
logger.debug("Writing files to job cache")
await fs.mkdir(this.dir, {mode:0o700});
await util.promisify(fs.mkdir)(this.dir, {mode:0o700})
const files = this.files.map(({name: file_name, content}) => {
return fs.write_file(path.join(this.dir, file_name), content);
});
const files = Object.keys(this.files).map(fileName => {
var content = this.files[fileName];
return util.promisify(fs.writeFile)(path.join(this.dir, fileName), content)
})
await Promise.all(files);
await Promise.all(files)
logger.debug(`Transfering ownership uid=${this.uid} gid=${this.gid}`);
await fs.chown(this.dir, this.uid, this.gid);
logger.debug(`Transfering ownership uid=${this.uid} gid=${this.gid}`)
await util.promisify(fs.chown)(this.dir, this.uid, this.gid)
const chowns = this.files.map(({name:file_name}) => {
return fs.chown(path.join(this.dir, file_name), this.uid, this.gid);
});
const chowns = Object.keys(this.files).map(fileName => {
return util.promisify(fs.chown)(path.join(this.dir, fileName), this.uid, this.gid)
})
await Promise.all(chowns);
await Promise.all(chowns)
this.state = job_states.PRIMED;
logger.debug('Primed job');
logger.debug("Primed job")
}
async execute(){
if(this.state != job_states.PRIMED) throw new Error('Job must be in primed state, current state: ' + this.state.toString());
logger.info(`Executing job uuid=${this.uuid} uid=${this.uid} gid=${this.gid} runtime=${this.runtime.toString()}`);
logger.debug('Compiling');
if(this.state != job_states.PRIMED) throw new Error("Job must be in primed state, current state: " + this.state.toString())
logger.info(`Executing job uuid=${this.uuid} uid=${this.uid} gid=${this.gid} runtime=${this.runtime.toString()}`)
logger.debug(`Compiling`)
const compile = this.runtime.compiled && await new Promise((resolve, reject) => {
var stdout = '';
var stderr = '';
const proc = cp.spawn('unshare', ['-n', 'bash', path.join(this.runtime.pkgdir, 'compile'),this.main, ...this.files] ,{
var stderr, stdout = "";
const proc = cp.spawn(this.runtime.pkgdir, [this.main, ...this.args] ,{
env: this.runtime.env_vars,
stdio: ['pipe', 'pipe', 'pipe'],
cwd: this.dir,
uid: this.uid,
gid: this.gid
});
})
const kill_timeout = setTimeout(proc.kill, this.timeouts.compile, 'SIGKILL');
const killTimeout = setTimeout(proc.kill, this.timeouts.compile, "SIGKILL")
proc.stderr.on('data', d=>stderr += d);
proc.stdout.on('data', d=>stdout += d);
proc.stderr.on('data', d=>stderr += d)
proc.stdout.on('data', d=>stdout += d)
proc.on('exit', (code, signal)=>{
clearTimeout(kill_timeout);
resolve({stdout, stderr, code, signal});
});
clearTimeout(killTimeout);
resolve({stdout, stderr, code, signal})
})
proc.on('error', (err) => {
clearTimeout(kill_timeout);
reject({error: err, stdout, stderr});
});
});
proc.on('error', (code, signal) => {
clearTimeout(killTimeout);
reject({stdout, stderr, code, signal})
})
})
logger.debug('Running');
logger.debug("Running")
const run = await new Promise((resolve, reject) => {
var stdout = '';
var stderr = '';
const proc = cp.spawn('unshare', ['-n', 'bash', path.join(this.runtime.pkgdir, 'run'),this.main, ...this.args] ,{
var stderr, stdout = "";
const proc = cp.spawn('bash', [path.join(this.runtime.pkgdir, "run"), this.main, ...this.args] ,{
env: this.runtime.env_vars,
stdio: ['pipe', 'pipe', 'pipe'],
cwd: this.dir,
uid: this.uid,
gid: this.gid
});
})
const kill_timeout = setTimeout(proc.kill, this.timeouts.run, 'SIGKILL');
const killTimeout = setTimeout(proc.kill, this.timeouts.run, "SIGKILL")
proc.stderr.on('data', d=>stderr += d);
proc.stdout.on('data', d=>stdout += d);
proc.stderr.on('data', d=>stderr += d)
proc.stdout.on('data', d=>stdout += d)
proc.on('exit', (code, signal)=>{
clearTimeout(kill_timeout);
resolve({stdout, stderr, code, signal});
});
clearTimeout(killTimeout);
resolve({stdout, stderr, code, signal})
})
proc.on('error', (err) => {
clearTimeout(kill_timeout);
reject({error: err, stdout, stderr});
});
});
proc.on('error', (code, signal) => {
clearTimeout(killTimeout);
reject({stdout, stderr, code, signal})
})
})
this.state = job_states.EXECUTED;
return {
compile,
run
};
}
}
async cleanup(){
logger.info(`Cleaning up job uuid=${this.uuid}`);
await fs.rm(this.dir, {recursive: true, force: true});
logger.info(`Cleaning up job uuid=${this.uuid}`)
await util.promisify(fs.rm)(this.dir, {recursive: true, force: true})
}
}
module.exports = {Job};
module.exports = {Job}

View File

@ -1,34 +1,34 @@
// {"language":"python","version":"3.9.1","files":{"code.py":"print('hello world')"},"args":[],"stdin":"","compile_timeout":10, "run_timeout":3, "main": "code.py"}
// {"success":true, "run":{"stdout":"hello world", "stderr":"", "error_code":0},"compile":{"stdout":"","stderr":"","error_code":0}}
const { get_latest_runtime_matching_language_version } = require('../runtime');
const { Job } = require('./job');
const { get_latest_runtime_matching_language_version } = require("../runtime");
const { Job } = require("./job");
module.exports = {
async run_job(req, res){
// POST /jobs
var errored = false;
['language', 'version',
'files', 'main',
'args', 'stdin',
'compile_timeout', 'run_timeout',
["language", "version",
"files", "main",
"args", "stdin",
"compile_timeout", "run_timeout",
].forEach(key => {
if(req.body[key] == undefined) errored = errored || res.json_error(`${key} is required`, 400);
});
if(req.body[key] == undefined) errored = errored || res.json_error(`${key} is required`, 400)
})
if(errored) return errored;
const runtime = get_latest_runtime_matching_language_version(req.body.language, req.body.version);
if(runtime == undefined) return res.json_error(`${req.body.language}-${req.body.version} runtime is unknown`, 400);
if(runtime == undefined) return res.json_error(`${req.body.language}-${req.body.version} runtime is unknown`, 400)
const job = new Job(runtime, req.body.files, req.body.args, req.body.stdin, {run: req.body.run_timeout, compile: req.body.compile_timeout}, req.body.main);
await job.prime();
const job = new Job(runtime, req.body.files, req.body.args, req.body.stdin, {run: req.body.run_timeout, compile: req.body.compile_timeout}, req.body.main)
await job.prime()
const result = await job.execute();
res.json_success(result);
const result = await job.execute()
res.json_success(result)
await job.cleanup();
await job.cleanup()
}
};
}

View File

@ -1,25 +1,25 @@
// Globals are things the user shouldn't change in config, but is good to not use inline constants for
const is_docker = require('is-docker');
const fss = require('fs');
const platform = `${is_docker() ? 'docker' : 'baremetal'}-${
fss.read_file_sync('/etc/os-release')
const is_docker = require("is-docker")
const fs = require("fs")
const platform = `${is_docker() ? "docker" : "baremetal"}-${
fs.read_file_sync("/etc/os-release")
.toString()
.split('\n')
.find(x=>x.startsWith('ID'))
.replace('ID=','')
}`;
.split("\n")
.find(x=>x.startsWith("ID"))
.replace("ID=","")
}`
module.exports = {
data_directories: {
cache: 'cache',
packages: 'packages',
runtimes: 'runtimes',
jobs: 'jobs'
cache: "cache",
packages: "packages",
runtimes: "runtimes",
jobs: "jobs"
},
data_files:{
state: 'state.json'
state: "state.json"
},
version: require('../package.json').version,
version: require("../package.json").version,
platform,
pkg_installed_file: '.ppman-installed' //Used as indication for if a package was installed
};
pkg_installed_file: ".ppman-installed" //Used as indication for if a package was installed
}

View File

@ -1,33 +1,34 @@
const fs = require('fs/promises'),
path= require('path'),
fetch = require('node-fetch'),
urlp = require('url');
const fs = require("fs"),
path= require("path"),
util = require("util"),
fetch = require("node-fetch"),
urlp = require("url")
module.exports = {
async buffer_from_url(url){
async buffer_from_u_r_l(url){
if(!(url instanceof URL))
url = new URL(url);
if(url.protocol == 'file:'){
url = new URL(url)
if(url.protocol == "file:"){
//eslint-disable-next-line snakecasejs/snakecasejs
return await fs.read_file(urlp.fileURLToPath(url));
return await util.promisify(fs.read_file)(urlp.fileURLToPath(url))
}else{
return await fetch({
url: url.toString()
});
})
}
},
add_url_base_if_required(url, base){
try{
return new URL(url);
return new URL(url)
}catch{
//Assume this is a file name
return new URL(url, base + '/');
return new URL(url, base + "/")
}
},
url_basename(url){
return path.basename(url.pathname);
return path.basename(url.pathname)
},
};
}

View File

@ -1,105 +1,106 @@
#!/usr/bin/env node
require('nocamel');
const Logger = require('logplease');
const express = require('express');
const globals = require('./globals');
const config = require('./config');
const cache = require('./cache');
const state = require('./state');
const path = require('path');
const fs = require('fs/promises');
const fss = require('fs');
const body_parser = require('body-parser');
const runtime = require('./runtime');
require("nocamel")
const Logger = require("logplease")
const express = require("express")
const globals = require("./globals")
const config = require("./config")
const cache = require("./cache")
const state = require("./state")
const path = require("path")
const fs = require("fs")
const util = require("util")
const body_parser = require("body-parser")
const runtime = require("./runtime")
const logger = Logger.create('index');
const logger = Logger.create("index")
const app = express();
(async () => {
logger.info('Setting loglevel to',config.log_level);
Logger.setLogLevel(config.log_level); //eslint-disable-line snakecasejs/snakecasejs
logger.info("Setting loglevel to",config.log_level)
Logger.setLogLevel(config.log_level) //eslint-disable-line snakecasejs/snakecasejs
logger.debug('Ensuring data directories exist');
logger.debug("Ensuring data directories exist")
Object.values(globals.data_directories).forEach(dir => {
var data_path = path.join(config.data_directory, dir);
logger.debug(`Ensuring ${data_path} exists`);
if(!fss.exists_sync(data_path)){
logger.info(`${data_path} does not exist.. Creating..`);
var data_path = path.join(config.data_directory, dir)
logger.debug(`Ensuring ${data_path} exists`)
if(!fs.exists_sync(data_path)){
logger.info(`${data_path} does not exist.. Creating..`)
try{
fss.mkdir_sync(data_path);
fs.mkdir_sync(data_path)
}catch(err){
logger.error(`Failed to create ${data_path}: `, err.message);
logger.error(`Failed to create ${data_path}: `, err.message)
}
}
});
})
logger.info('Loading state');
await state.load(path.join(config.data_directory,globals.data_files.state));
logger.info("Loading state")
await state.load(path.join(config.data_directory,globals.data_files.state))
logger.info('Loading cache');
await cache.load(path.join(config.data_directory,globals.data_directories.cache));
logger.info("Loading cache")
await cache.load(path.join(config.data_directory,globals.data_directories.cache))
logger.info('Loading packages');
const pkgdir = path.join(config.data_directory,globals.data_directories.packages);
await fs.readdir(pkgdir)
logger.info("Loading packages")
const pkgdir = path.join(config.data_directory,globals.data_directories.packages)
await util.promisify(fs.readdir)(pkgdir)
.then(langs => Promise.all(
langs.map(lang=>
fs.readdir(path.join(pkgdir,lang))
util.promisify(fs.readdir)(path.join(pkgdir,lang))
.then(x=>x.map(y=>path.join(pkgdir, lang, y)))
)))
.then(pkgs=>pkgs.flat().filter(pkg=>fss.exists_sync(path.join(pkg, globals.pkg_installed_file))))
.then(pkgs=>pkgs.forEach(pkg => new runtime.Runtime(pkg)));
//eslint-disable-next-line snakecasejs/snakecasejs
.then(pkgs=>pkgs.flat().filter(pkg=>fs.existsSync(path.join(pkg, globals.pkg_installed_file))))
.then(pkgs=>pkgs.forEach(pkg => new runtime.Runtime(pkg)))
logger.info('Starting API Server');
logger.info("Starting API Server")
logger.debug('Constructing Express App');
logger.debug("Constructing Express App")
logger.debug('Registering middleware');
logger.debug("Registering middleware")
app.use(body_parser.urlencoded({extended: true}));
app.use(body_parser.json());
app.use(body_parser.urlencoded({extended: true}))
app.use(body_parser.json())
logger.debug('Registering custom message wrappers');
logger.debug("Registering custom message wrappers")
express.response.json_error = function(message, code) {
this.status(code);
return this.json({success: false, message, code});
};
this.status(code)
return this.json({success: false, message, code})
}
express.response.json_success = function(obj) {
return this.json({success: true, data: obj});
};
return this.json({success: true, data: obj})
}
logger.debug('Registering Routes');
logger.debug("Registering Routes")
const ppman_routes = require('./ppman/routes');
const ppman_routes = require("./ppman/routes")
app.get ('/repos', ppman_routes.repo_list);
app.post ('/repos', ppman_routes.repo_add);
app.get ('/repos/:repo_slug', ppman_routes.repo_info);
app.get ('/repos/:repo_slug/packages', ppman_routes.repo_packages);
app.get ('/repos/:repo_slug/packages/:language/:version', ppman_routes.package_info);
app.post ('/repos/:repo_slug/packages/:language/:version', ppman_routes.package_install);
app.delete('/repos/:repo_slug/packages/:language/:version', ppman_routes.package_uninstall); //TODO
app.get ("/repos", ppman_routes.repo_list)
app.post ("/repos", ppman_routes.repo_add)
app.get ("/repos/:repo_slug", ppman_routes.repo_info)
app.get ("/repos/:repo_slug/packages", ppman_routes.repo_packages)
app.get ("/repos/:repo_slug/packages/:language/:version", ppman_routes.package_info)
app.post ("/repos/:repo_slug/packages/:language/:version", ppman_routes.package_install)
app.delete("/repos/:repo_slug/packages/:language/:version", ppman_routes.package_uninstall) //TODO
const executor_routes = require('./executor/routes');
app.post ('/jobs', executor_routes.run_job);
const executor_routes = require('./executor/routes')
app.post ("/jobs", executor_routes.run_job)
logger.debug('Calling app.listen');
const [address,port] = config.bind_address.split(':');
logger.debug("Calling app.listen")
const [address,port] = config.bind_address.split(":")
app.listen(port, address, ()=>{
logger.info('API server started on', config.bind_address);
});
logger.info("API server started on", config.bind_address)
})
logger.debug('Setting up flush timers');
setInterval(cache.flush,config.cache_flush_time,path.join(config.data_directory,globals.data_directories.cache));
setInterval(state.save,config.state_flush_time,path.join(config.data_directory,globals.data_files.state));
})();
logger.debug("Setting up flush timers")
setInterval(cache.flush,config.cache_flush_time,path.join(config.data_directory,globals.data_directories.cache))
setInterval(state.save,config.state_flush_time,path.join(config.data_directory,globals.data_files.state))
})()

View File

@ -1,172 +1,170 @@
const logger = require('logplease').create('ppman/package');
const semver = require('semver');
const config = require('../config');
const globals = require('../globals');
const helpers = require('../helpers');
const path = require('path');
const fs = require('fs/promises');
const fss = require('fs');
const cp = require('child_process');
const crypto = require('crypto');
const runtime = require('../runtime');
const logger = require("logplease").create("ppman/package")
const semver = require("semver")
const config = require("../config")
const globals = require("../globals")
const helpers = require("../helpers")
const path = require("path")
const fs = require("fs")
const util = require("util")
const cp = require("child_process")
const crypto = require("crypto")
const runtime = require("../runtime")
class Package {
constructor(repo, {author, language, version, checksums, dependencies, size, buildfile, download, signature}){
this.author = author;
this.language = language;
this.version = semver.parse(version);
this.checksums = checksums;
this.dependencies = dependencies;
this.size = size;
this.buildfile = buildfile;
this.download = download;
this.signature = signature;
this.author = author
this.language = language
this.version = semver.parse(version)
this.checksums = checksums
this.dependencies = dependencies
this.size = size
this.buildfile = buildfile
this.download = download
this.signature = signature
this.repo = repo;
this.repo = repo
}
get installed(){
return fss.exists_sync(path.join(this.install_path, globals.pkg_installed_file));
return fs.exists_sync(path.join(this.install_path, globals.pkg_installed_file))
}
get download_url(){
return helpers.add_url_base_if_required(this.download, this.repo.base_u_r_l);
return helpers.add_url_base_if_required(this.download, this.repo.base_u_r_l)
}
get install_path(){
return path.join(config.data_directory,
globals.data_directories.packages,
this.language,
this.version.raw);
this.version.raw)
}
async install(){
if(this.installed) throw new Error('Already installed');
logger.info(`Installing ${this.language}-${this.version.raw}`);
if(this.installed) throw new Error("Already installed")
logger.info(`Installing ${this.language}-${this.version.raw}`)
if(fss.exists_sync(this.install_path)){
logger.warn(`${this.language}-${this.version.raw} has residual files. Removing them.`);
await fs.rm(this.install_path, {recursive: true, force: true});
if(fs.exists_sync(this.install_path)){
logger.warn(`${this.language}-${this.version.raw} has residual files. Removing them.`)
await util.promisify(fs.rm)(this.install_path, {recursive: true, force: true})
}
logger.debug(`Making directory ${this.install_path}`);
await fs.mkdir(this.install_path, {recursive: true});
logger.debug(`Making directory ${this.install_path}`)
await util.promisify(fs.mkdir)(this.install_path, {recursive: true})
logger.debug(`Downloading package from ${this.download_url} in to ${this.install_path}`);
const pkgfile = helpers.url_basename(this.download_url);
const pkgpath = path.join(this.install_path, pkgfile);
await helpers.buffer_from_url(this.download_url)
.then(buf=> fs.write_file(pkgpath, buf));
logger.debug(`Downloading package from ${this.download_url} in to ${this.install_path}`)
const pkgfile = helpers.url_basename(this.download_url)
const pkgpath = path.join(this.install_path, pkgfile)
await helpers.buffer_from_u_r_l(this.download_url)
.then(buf=> util.promisify(fs.write_file)(pkgpath, buf))
logger.debug('Validating checksums');
logger.debug("Validating checksums")
Object.keys(this.checksums).forEach(algo => {
var val = this.checksums[algo];
logger.debug(`Assert ${algo}(${pkgpath}) == ${val}`);
var val = this.checksums[algo]
logger.debug(`Assert ${algo}(${pkgpath}) == ${val}`)
var cs = crypto.create_hash(algo)
.update(fss.read_file_sync(pkgpath))
.digest('hex');
if(cs != val) throw new Error(`Checksum miss-match want: ${val} got: ${cs}`);
});
.update(fs.read_file_sync(pkgpath))
.digest("hex")
if(cs != val) throw new Error(`Checksum miss-match want: ${val} got: ${cs}`)
})
await this.repo.import_keys();
await this.repo.importKeys()
logger.debug('Validating signatutes');
if(this.signature != '')
logger.debug("Validating signatutes")
await new Promise((resolve,reject)=>{
const gpgspawn = cp.spawn('gpg', ['--verify', '-', pkgpath], {
stdio: ['pipe', 'ignore', 'ignore']
});
const gpgspawn = cp.spawn("gpg", ["--verify", "-", pkgpath], {
stdio: ["pipe", "ignore", "ignore"]
})
gpgspawn.once('exit', (code, _) => {
if(code == 0) resolve();
else reject(new Error('Invalid signature'));
});
gpgspawn.once("exit", (code, _) => {
if(code == 0) resolve()
else reject(new Error("Invalid signature"))
})
gpgspawn.once('error', reject);
gpgspawn.once("error", reject)
gpgspawn.stdin.write(this.signature);
gpgspawn.stdin.end();
gpgspawn.stdin.write(this.signature)
gpgspawn.stdin.end()
});
else
logger.warn('Package does not contain a signature - allowing install, but proceed with caution');
})
logger.debug(`Extracting package files from archive ${pkgfile} in to ${this.install_path}`);
logger.debug(`Extracting package files from archive ${pkgfile} in to ${this.install_path}`)
await new Promise((resolve, reject)=>{
const proc = cp.exec(`bash -c 'cd "${this.install_path}" && tar xzf ${pkgfile}'`);
proc.once('exit', (code,_)=>{
if(code == 0) resolve();
else reject(new Error('Failed to extract package'));
});
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
const proc = cp.exec(`bash -c 'cd "${this.install_path}" && tar xzf ${pkgfile}'`)
proc.once("exit", (code,_)=>{
if(code == 0) resolve()
else reject(new Error("Failed to extract package"))
})
proc.stdout.pipe(process.stdout)
proc.stderr.pipe(process.stderr)
proc.once('error', reject);
});
proc.once("error", reject)
})
logger.debug('Ensuring binary files exist for package');
const pkgbin = path.join(this.install_path, `${this.language}-${this.version.raw}`);
logger.debug("Ensuring binary files exist for package")
const pkgbin = path.join(this.install_path, `${this.language}-${this.version.raw}`)
try{
const pkgbinstat = await fs.stat(pkgbin);
const pkgbinstat = await util.promisify(fs.stat)(pkgbin)
//eslint-disable-next-line snakecasejs/snakecasejs
if(!pkgbinstat.isDirectory()) throw new Error();
if(!pkgbinstat.isDirectory()) throw new Error()
}catch(err){
throw new Error(`Invalid package: could not find ${this.language}-${this.version.raw}/ contained within package files`);
throw new Error(`Invalid package: could not find ${this.language}-${this.version.raw}/ contained within package files`)
}
logger.debug('Symlinking into runtimes');
await fs.symlink(
logger.debug("Symlinking into runtimes")
await util.promisify(fs.symlink)(
pkgbin,
path.join(config.data_directory,
globals.data_directories.runtimes,
`${this.language}-${this.version.raw}`)
).catch((err)=>err); //catch
).catch((err)=>err) //catch
logger.debug('Registering runtime');
const pkgruntime = new runtime.Runtime(this.install_path);
logger.debug("Registering runtime")
const pkgruntime = new runtime.Runtime(this.install_path)
logger.debug('Caching environment');
const required_pkgs = [pkgruntime, ...pkgruntime.get_all_dependencies()];
logger.debug("Caching environment")
const required_pkgs = [pkgruntime, ...pkgruntime.get_all_dependencies()]
const get_env_command = [...required_pkgs.map(p=>`cd "${p.runtime_dir}"; source environment; `),
'env' ].join(' ');
"env" ].join(" ")
const envout = await new Promise((resolve, reject)=>{
var stdout = '';
const proc = cp.spawn('env',['-i','bash','-c',`${get_env_command}`], {
stdio: ['ignore', 'pipe', 'pipe']});
proc.once('exit', (code,_)=>{
if(code == 0) resolve(stdout);
else reject(new Error('Failed to cache environment'));
});
var stdout = ""
const proc = cp.spawn("env",["-i","bash","-c",`${get_env_command}`], {
stdio: ["ignore", "pipe", "pipe"]})
proc.once("exit", (code,_)=>{
if(code == 0) resolve(stdout)
else reject(new Error("Failed to cache environment"))
})
proc.stdout.on('data', (data)=>{
stdout += data;
});
proc.stdout.on("data", (data)=>{
stdout += data
})
proc.once('error', reject);
});
proc.once("error", reject)
})
const filtered_env = envout.split('\n')
.filter(l=>!['PWD','OLDPWD','_', 'SHLVL'].includes(l.split('=',2)[0]))
.join('\n');
const filtered_env = envout.split("\n")
.filter(l=>!["PWD","OLDPWD","_", "SHLVL"].includes(l.split("=",2)[0]))
.join("\n")
await fs.write_file(path.join(this.install_path, '.env'), filtered_env);
await util.promisify(fs.write_file)(path.join(this.install_path, ".env"), filtered_env)
logger.debug('Writing installed state to disk');
await fs.write_file(path.join(this.install_path, globals.pkg_installed_file), Date.now().toString());
logger.debug("Writing installed state to disk")
await util.promisify(fs.write_file)(path.join(this.install_path, globals.pkg_installed_file), Date.now().toString())
logger.info(`Installed ${this.language}-${this.version.raw}`);
logger.info(`Installed ${this.language}-${this.version.raw}`)
return {
language: this.language,
version: this.version.raw
};
}
}
}
module.exports = {Package};
module.exports = {Package}

View File

@ -1,66 +1,66 @@
const logger = require('logplease').create('ppman/repo');
const cache = require('../cache');
const CACHE_CONTEXT = 'repo';
const logger = require("logplease").create("ppman/repo")
const cache = require("../cache")
const CACHE_CONTEXT = "repo"
const cp = require('child_process');
const yaml = require('js-yaml');
const { Package } = require('./package');
const helpers = require('../helpers');
const cp = require("child_process")
const yaml = require("js-yaml")
const { Package } = require("./package")
const helpers = require("../helpers")
class Repository {
constructor(slug, url){
this.slug = slug;
this.url = new URL(url);
this.keys = [];
this.packages = [];
this.base_u_r_l='';
logger.debug(`Created repo slug=${this.slug} url=${this.url}`);
this.slug = slug
this.url = new URL(url)
this.keys = []
this.packages = []
this.base_u_r_l=""
logger.debug(`Created repo slug=${this.slug} url=${this.url}`)
}
get cache_key(){
return cache.cache_key(CACHE_CONTEXT, this.slug);
return cache.cache_key(CACHE_CONTEXT, this.slug)
}
async load(){
try{
var index = await cache.get(this.cache_key,async ()=>{
return helpers.buffer_from_url(this.url);
});
return helpers.buffer_from_u_r_l(this.url)
})
var repo = yaml.load(index);
if(repo.schema != 'ppman-repo-1'){
throw new Error('YAML Schema unknown');
var repo = yaml.load(index)
if(repo.schema != "ppman-repo-1"){
throw new Error("YAML Schema unknown")
}
this.keys = repo.keys;
this.packages = repo.packages.map(pkg => new Package(this, pkg));
this.base_u_r_l = repo.baseurl;
this.keys = repo.keys
this.packages = repo.packages.map(pkg => new Package(this, pkg))
this.base_u_r_l = repo.baseurl
}catch(err){
logger.error(`Failed to load repository ${this.slug}:`,err.message);
logger.error(`Failed to load repository ${this.slug}:`,err.message)
}
}
async import_keys(){
async importKeys(){
await this.load();
logger.info(`Importing keys for repo ${this.slug}`);
logger.info(`Importing keys for repo ${this.slug}`)
await new Promise((resolve,reject)=>{
const gpgspawn = cp.spawn('gpg', ['--receive-keys', this.keys], {
stdio: ['ignore', 'ignore', 'ignore']
});
const gpgspawn = cp.spawn("gpg", ['--receive-keys', this.keys], {
stdio: ["ignore", "ignore", "ignore"]
})
gpgspawn.once('exit', (code, _) => {
if(code == 0) resolve();
else reject(new Error('Failed to import keys'));
});
gpgspawn.once("exit", (code, _) => {
if(code == 0) resolve()
else reject(new Error("Failed to import keys"))
})
gpgspawn.once('error', reject);
gpgspawn.once("error", reject)
});
})
}
}
module.exports = {Repository};
module.exports = {Repository}

View File

@ -1,82 +1,82 @@
const repos = new Map();
const state = require('../state');
const logger = require('logplease').create('ppman/routes');
const {Repository} = require('./repo');
const semver = require('semver');
const repos = new Map()
const state = require("../state")
const logger = require("logplease").create("ppman/routes")
const {Repository} = require("./repo")
const semver = require("semver")
async function get_or_construct_repo(slug){
if(repos.has(slug))return repos.get(slug);
if(state.state.get('repositories').has(slug)){
const repo_url = state.state.get('repositories').get(slug);
const repo = new Repository(slug, repo_url);
await repo.load();
repos.set(slug, repo);
return repo;
if(repos.has(slug))return repos.get(slug)
if(state.state.get("repositories").has(slug)){
const repo_url = state.state.get("repositories").get(slug)
const repo = new Repository(slug, repo_url)
await repo.load()
repos.set(slug, repo)
return repo
}
logger.warn(`Requested repo ${slug} does not exist`);
return null;
logger.warn(`Requested repo ${slug} does not exist`)
return null
}
async function get_package(repo, lang, version){
var candidates = repo.packages.filter(
pkg => pkg.language == lang && semver.satisfies(pkg.version, version)
);
return candidates.sort((a,b)=>semver.rcompare(a.version,b.version))[0] || null;
)
return candidates.sort((a,b)=>semver.rcompare(a.version,b.version))[0] || null
}
module.exports = {
async repo_list(req,res){
// GET /repos
logger.debug('Request for repoList');
logger.debug("Request for repoList")
res.json_success({
repos: (await Promise.all(
[...state.state.get('repositories').keys()].map( async slug => await get_or_construct_repo(slug))
[...state.state.get("repositories").keys()].map( async slug => await get_or_construct_repo(slug))
)).map(repo=>({
slug: repo.slug,
url: repo.url,
packages: repo.packages.length
}))
});
})
},
async repo_add(req, res){
// POST /repos
logger.debug(`Request for repoAdd slug=${req.body.slug} url=${req.body.url}`);
logger.debug(`Request for repoAdd slug=${req.body.slug} url=${req.body.url}`)
if(!req.body.slug)
return res.json_error('slug is missing from request body', 400);
return res.json_error("slug is missing from request body", 400)
if(!req.body.url)
return res.json_error('url is missing from request body', 400);
return res.json_error("url is missing from request body", 400)
const repo_state = state.state.get('repositories');
const repo_state = state.state.get("repositories")
if(repo_state.has(req.body.slug)) return res.json_error(`repository ${req.body.slug} already exists`, 409);
if(repo_state.has(req.body.slug)) return res.json_error(`repository ${req.body.slug} already exists`, 409)
repo_state.set(req.body.slug, req.body.url);
logger.info(`Repository ${req.body.slug} added url=${req.body.url}`);
repo_state.set(req.body.slug, req.body.url)
logger.info(`Repository ${req.body.slug} added url=${req.body.url}`)
return res.json_success(req.body.slug);
return res.json_success(req.body.slug)
},
async repo_info(req, res){
// GET /repos/:slug
logger.debug(`Request for repoInfo for ${req.params.repo_slug}`);
const repo = await get_or_construct_repo(req.params.repo_slug);
logger.debug(`Request for repoInfo for ${req.params.repo_slug}`)
const repo = await get_or_construct_repo(req.params.repo_slug)
if(repo == null) return res.json_error(`Requested repo ${req.params.repo_slug} does not exist`, 404);
if(repo == null) return res.json_error(`Requested repo ${req.params.repo_slug} does not exist`, 404)
res.json_success({
slug: repo.slug,
url: repo.url,
packages: repo.packages.length
});
})
},
async repo_packages(req, res){
// GET /repos/:slug/packages
logger.debug('Request to repoPackages');
logger.debug("Request to repoPackages")
const repo = await get_or_construct_repo(req.params.repo_slug);
if(repo == null) return res.json_error(`Requested repo ${req.params.repo_slug} does not exist`, 404);
const repo = await get_or_construct_repo(req.params.repo_slug)
if(repo == null) return res.json_error(`Requested repo ${req.params.repo_slug} does not exist`, 404)
res.json_success({
packages: repo.packages.map(pkg=>({
@ -84,46 +84,46 @@ module.exports = {
language_version: pkg.version.raw,
installed: pkg.installed
}))
});
})
},
async package_info(req, res){
// GET /repos/:slug/packages/:language/:version
logger.debug('Request to packageInfo');
logger.debug("Request to packageInfo")
const repo = await get_or_construct_repo(req.params.repo_slug);
if(repo == null) return res.json_error(`Requested repo ${req.params.repo_slug} does not exist`, 404);
const repo = await get_or_construct_repo(req.params.repo_slug)
if(repo == null) return res.json_error(`Requested repo ${req.params.repo_slug} does not exist`, 404)
const pkg = await get_package(repo, req.params.language, req.params.version);
if(pkg == null) return res.json_error(`Requested package ${req.params.language}-${req.params.version} does not exist`, 404);
const package = await get_package(repo, req.params.language, req.params.version)
if(package == null) return res.json_error(`Requested package ${req.params.language}-${req.params.version} does not exist`, 404)
res.json_success({
language: pkg.language,
language_version: pkg.version.raw,
author: pkg.author,
buildfile: pkg.buildfile,
size: pkg.size,
dependencies: pkg.dependencies,
installed: pkg.installed
});
language: package.language,
language_version: package.version.raw,
author: package.author,
buildfile: package.buildfile,
size: package.size,
dependencies: package.dependencies,
installed: package.installed
})
},
async package_install(req,res){
// POST /repos/:slug/packages/:language/:version
logger.debug('Request to packageInstall');
logger.debug("Request to packageInstall")
const repo = await get_or_construct_repo(req.params.repo_slug);
if(repo == null) return res.json_error(`Requested repo ${req.params.repo_slug} does not exist`, 404);
const repo = await get_or_construct_repo(req.params.repo_slug)
if(repo == null) return res.json_error(`Requested repo ${req.params.repo_slug} does not exist`, 404)
const pkg = await get_package(repo, req.params.language, req.params.version);
if(pkg == null) return res.json_error(`Requested package ${req.params.language}-${req.params.version} does not exist`, 404);
const package = await get_package(repo, req.params.language, req.params.version)
if(package == null) return res.json_error(`Requested package ${req.params.language}-${req.params.version} does not exist`, 404)
try{
const response = await pkg.install();
return res.json_success(response);
const response = await package.install()
return res.json_success(response)
}catch(err){
logger.error(`Error while installing package ${pkg.language}-${pkg.version}:`, err.message);
res.json_error(err.message,500);
logger.error(`Error while installing package ${package.language}-${package.version}:`, err.message)
res.json_error(err.message,500)
}
@ -131,6 +131,6 @@ module.exports = {
async package_uninstall(req,res){
// DELETE /repos/:slug/packages/:language/:version
res.json(req.body); //TODO
res.json(req.body) //TODO
}
};
}

View File

@ -1,86 +1,85 @@
const logger = require('logplease').create('runtime');
const semver = require('semver');
const config = require('./config');
const globals = require('./globals');
const fss = require('fs');
const path = require('path');
const logger = require("logplease").create("runtime")
const semver = require("semver")
const config = require("./config")
const globals = require("./globals")
const fs = require("fs")
const path = require("path")
const runtimes = [];
const runtimes = []
class Runtime {
#env_vars
#compiled
constructor(package_dir){
const {language, version, author, dependencies, build_platform} = JSON.parse(
fss.read_file_sync(path.join(package_dir, 'pkg-info.json'))
);
fs.read_file_sync(path.join(package_dir, "pkg-info.json"))
)
this.pkgdir = package_dir;
this.language = language;
this.version = semver.parse(version);
this.author = author;
this.dependencies = dependencies;
this.pkgdir = package_dir
this.language = language
this.version = semver.parse(version)
this.author = author
this.dependencies = dependencies
if(build_platform != globals.platform){
logger.warn(`Package ${language}-${version} was built for platform ${build_platform}, but our platform is ${globals.platform}`);
logger.warn(`Package ${language}-${version} was built for platform ${build_platform}, but our platform is ${globals.platform}`)
}
logger.debug(`Package ${language}-${version} was loaded`);
runtimes.push(this);
logger.debug(`Package ${language}-${version} was loaded`)
runtimes.push(this)
}
get env_file_path(){
return path.join(this.runtime_dir, 'environment');
return path.join(this.runtime_dir, "environment")
}
get runtime_dir(){
return path.join(config.data_directory,globals.data_directories.runtimes, this.toString());
return path.join(config.data_directory,globals.data_directories.runtimes, this.toString())
}
get_all_dependencies(){
const res = [];
const res = []
Object.keys(this.dependencies).forEach(dep => {
const selector = this.dependencies[dep];
const lang = module.exports.get_latest_runtime_matching_language_version(dep, selector);
res.push(lang);
res.concat(lang.get_all_dependencies(lang));
});
return res;
const selector = this.dependencies[dep]
const lang = module.exports.get_latest_runtime_matching_language_version(dep, selector)
res.push(lang)
res.concat(lang.get_all_dependencies(lang))
})
return res
}
get compile(){
if(this.#compiled === undefined) this.#compiled = fss.exists_sync(path.join(this.pkgdir, 'compile'));
return this.#compiled;
if(this.#compiled === undefined) this.#compiled = fs.existsSync(path.join(this.pkgdir, "compile"))
return this.#compiled
}
get env_vars(){
if(!this.#env_vars){
const env_file = path.join(this.pkgdir, '.env');
const env_content = fss.read_file_sync(env_file).toString();
this.#env_vars = {};
const env_file = path.join(this.pkgdir, ".env")
const env_content = fs.read_file_sync(env_file).toString()
this.#env_vars = {}
env_content
.trim()
.split('\n')
.map(line => line.split('=',2))
.split("\n")
.map(line => line.split("=",2))
.forEach(([key,val]) => {
this.#env_vars[key.trim()] = val.trim();
});
this.#env_vars[key] = val
})
}
return this.#env_vars;
return this.#env_vars
}
toString(){
return `${this.language}-${this.version.raw}`;
return `${this.language}-${this.version.raw}`
}
}
module.exports = runtimes;
module.exports.Runtime = Runtime;
module.exports = runtimes
module.exports.Runtime = Runtime
module.exports.get_runtimes_matching_language_version = function(lang, ver){
return runtimes.filter(rt => rt.language == lang && semver.satisfies(rt.version, ver));
};
return runtimes.filter(rt => rt.language == lang && semver.satisfies(rt.version, ver))
}
module.exports.get_latest_runtime_matching_language_version = function(lang, ver){
return module.exports.get_runtimes_matching_language_version(lang, ver)
.sort((a,b) => semver.rcompare(a.version, b.version))[0];
};
.sort((a,b) => semver.rcompare(a.version, b.version))[0]
}

View File

@ -1,45 +1,45 @@
const fs = require('fs/promises');
const fss = require('fs');
const fs = require("fs")
const util = require("util")
const logger = require('logplease').create('state');
const state = new Map();
const logger = require("logplease").create("state")
const state = new Map()
function replacer(key, value) {
if(value instanceof Map) {
return {
data_type: 'Map',
data_type: "Map",
value: Array.from(value.entries()),
};
}
} else {
return value;
return value
}
}
function reviver(key, value) {
if(typeof value === 'object' && value !== null) {
if (value.data_type === 'Map') {
return new Map(value.value);
if(typeof value === "object" && value !== null) {
if (value.data_type === "Map") {
return new Map(value.value)
}
}
return value;
return value
}
module.exports = {
state,
async load(data_file){
if(fss.exists_sync(data_file)){
logger.info('Loading state from file');
var content = await fs.read_file(data_file);
if(fs.exists_sync(data_file)){
logger.info("Loading state from file")
var content = await util.promisify(fs.read_file)(data_file)
var obj = JSON.parse(content.toString(), reviver);
[...obj.keys()].forEach(k => state.set(k, obj.get(k)));
[...obj.keys()].forEach(k => state.set(k, obj.get(k)))
}else{
logger.info('Creating new statefile');
state.set('repositories', new Map().set('offical', 'https://repo.pistonee.org/index.yaml'));
logger.info("Creating new statefile")
state.set("repositories", new Map().set("offical", "https://repo.pistonee.org/index.yaml"))
}
},
async save(data_file){
logger.info('Saving state to disk');
await fs.write_file(data_file, JSON.stringify(state, replacer));
logger.info("Saving state to disk")
await util.promisify(fs.write_file)(data_file, JSON.stringify(state, replacer))
}
};
}

View File

@ -9,51 +9,12 @@
dependencies:
"@babel/highlight" "^7.10.4"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
dependencies:
"@babel/highlight" "^7.12.13"
"@babel/generator@^7.12.17":
version "7.12.17"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.17.tgz#9ef1dd792d778b32284411df63f4f668a9957287"
integrity sha512-DSA7ruZrY4WI8VxuS1jWSRezFnghEoYEFrZcw9BizQRmOZiUsiHl59+qEARGPqPikwA/GPTyRCi7isuCK/oyqg==
dependencies:
"@babel/types" "^7.12.17"
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/helper-function-name@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a"
integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==
dependencies:
"@babel/helper-get-function-arity" "^7.12.13"
"@babel/template" "^7.12.13"
"@babel/types" "^7.12.13"
"@babel/helper-get-function-arity@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583"
integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==
dependencies:
"@babel/types" "^7.12.13"
"@babel/helper-split-export-declaration@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05"
integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==
dependencies:
"@babel/types" "^7.12.13"
"@babel/helper-validator-identifier@^7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13":
"@babel/highlight@^7.10.4":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c"
integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==
@ -62,44 +23,6 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.12.13", "@babel/parser@^7.12.17", "@babel/parser@^7.7.0":
version "7.12.17"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.17.tgz#bc85d2d47db38094e5bb268fc761716e7d693848"
integrity sha512-r1yKkiUTYMQ8LiEI0UcQx5ETw5dpTLn9wijn9hk6KkTtOK95FndDN10M+8/s6k/Ymlbivw0Av9q4SlgF80PtHg==
"@babel/template@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==
dependencies:
"@babel/code-frame" "^7.12.13"
"@babel/parser" "^7.12.13"
"@babel/types" "^7.12.13"
"@babel/traverse@^7.7.0":
version "7.12.17"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.17.tgz#40ec8c7ffb502c4e54c7f95492dc11b88d718619"
integrity sha512-LGkTqDqdiwC6Q7fWSwQoas/oyiEYw6Hqjve5KOSykXkmFJFqzvGMb9niaUEag3Rlve492Mkye3gLw9FTv94fdQ==
dependencies:
"@babel/code-frame" "^7.12.13"
"@babel/generator" "^7.12.17"
"@babel/helper-function-name" "^7.12.13"
"@babel/helper-split-export-declaration" "^7.12.13"
"@babel/parser" "^7.12.17"
"@babel/types" "^7.12.17"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.19"
"@babel/types@^7.12.13", "@babel/types@^7.12.17", "@babel/types@^7.7.0":
version "7.12.17"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.17.tgz#9d711eb807e0934c90b8b1ca0eb1f7230d150963"
integrity sha512-tNMDjcv/4DIcHxErTgwB9q2ZcYyN0sUfgGKUK/mm1FJK7Wz+KstoEekxrl/tBiNDgLK1HGi+sppj1An/1DR4fQ==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@eslint/eslintrc@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318"
@ -200,18 +123,6 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
babel-eslint@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.7.0"
"@babel/traverse" "^7.7.0"
"@babel/types" "^7.7.0"
eslint-visitor-keys "^1.0.0"
resolve "^1.12.0"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@ -344,7 +255,7 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
debug@^4.0.1, debug@^4.1.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
@ -430,7 +341,7 @@ eslint-utils@^2.1.0:
dependencies:
eslint-visitor-keys "^1.1.0"
eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
@ -630,11 +541,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@ -664,11 +570,6 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globals@^12.1.0:
version "12.4.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8"
@ -686,13 +587,6 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
@ -763,13 +657,6 @@ ipaddr.js@1.9.1:
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
is-core-module@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==
dependencies:
has "^1.0.3"
is-docker@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
@ -817,11 +704,6 @@ js-yaml@^4.0.0:
dependencies:
argparse "^2.0.1"
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@ -845,11 +727,6 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
lodash@^4.17.19:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lodash@^4.17.20:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
@ -931,9 +808,10 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
nocamel@HexF/nocamel#patch-1:
version "1.1.0"
resolved "https://codeload.github.com/HexF/nocamel/tar.gz/89a5bfbbd07c72c302d968b967d0f4fe54846544"
nocamel@*:
version "1.0.2"
resolved "https://registry.yarnpkg.com/nocamel/-/nocamel-1.0.2.tgz#13ff04ffacd5487ba65555c0dcafcf8c95c918ba"
integrity sha512-CRkRSRLChj+H6e4lHS851QS6YGCoTETnSG/z+XGanxLSsTbBkvEeIWaIYMKzuBznFwWM0YcLGXsFyXg4xWYnWA==
node-fetch@^2.6.1:
version "2.6.1"
@ -988,11 +866,6 @@ path-key@^3.1.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@ -1061,14 +934,6 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.12.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
dependencies:
is-core-module "^2.2.0"
path-parse "^1.0.6"
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@ -1148,11 +1013,6 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
source-map@^0.5.0:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@ -1213,11 +1073,6 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"

View File

@ -1,25 +0,0 @@
version: '3.8'
services:
piston_api:
build: api
restart: always
ports:
- 6969:6969
volumes:
- ./data/piston:/piston
- ./repo:/repo
tmpfs:
- /piston/cache
- /piston/jobs
piston_fs_repo: #Temporary solution until CI works
build: repo
command: >
bash -c '/repo/make.sh &&
curl http://piston_api:6969/repos -XPOST -d "slug=local&url=file:///repo/index.yaml";
echo -e "\nAn error here is fine, it just means its already added it. Perhaps you restarted this container"
'
volumes:
- ./repo:/repo
- ./packages:/packages

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#Circle_1_);}
.st1{fill:#00E300;}
.st2{fill:#FFFFFF;}
</style>
<radialGradient id="Circle_1_" cx="512" cy="512" r="512" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#0B0B0B"/>
<stop offset="1" style="stop-color:#1B1B1B"/>
</radialGradient>
<path id="Circle" class="st0" d="M512,1024L512,1024c282.77,0,512-229.23,512-512v0C1024,229.23,794.77,0,512,0h0
C229.23,0,0,229.23,0,512v0C0,794.77,229.23,1024,512,1024z"/>
<g id="E">
<path class="st1" d="M218,840.5c-4.31,0-8.4-1.92-11.15-5.23c-2.75-3.32-3.89-7.68-3.1-11.92l117-628
c1.28-6.87,7.27-11.84,14.25-11.84h427c3.58,0,7.03,1.32,9.69,3.72l99,89c4.47,4.01,5.99,10.37,3.84,15.97
c-2.15,5.61-7.53,9.31-13.54,9.31H481.02l-28.52,151H541c3.58,0,7.03,1.32,9.69,3.72l99,89c4.47,4.01,5.99,10.37,3.84,15.97
c-2.15,5.61-7.53,9.31-13.54,9.31H430.03l-28.55,152H771c6.37,0,11.99,4.15,13.86,10.24c1.87,6.08-0.45,12.68-5.71,16.26l-131,89
c-2.4,1.63-5.24,2.51-8.15,2.51H218z"/>
<path d="M762,198l99,89H469l-34,180h106l99,89H418l-34,181h387l-131,89H218l117-628H762 M762,169H335
c-13.97,0-25.95,9.96-28.51,23.69l-117,628c-1.58,8.48,0.69,17.21,6.2,23.84C201.2,851.16,209.38,855,218,855h422
c5.81,0,11.49-1.75,16.3-5.01l131-89c10.53-7.16,15.16-20.34,11.42-32.51C794.98,716.3,783.73,708,771,708H418.95l23.1-123H640
c12.01,0,22.78-7.4,27.08-18.62c4.3-11.21,1.24-23.92-7.69-31.95l-99-89c-5.32-4.79-12.23-7.43-19.39-7.43h-71.01l23.04-122H861
c12.01,0,22.78-7.4,27.08-18.62c4.3-11.21,1.24-23.92-7.69-31.95l-99-89C776.06,171.65,769.16,169,762,169L762,169z"/>
</g>
<g id="Highlight">
<polygon id="Top" class="st2" points="640,556 541,467 435,467 469,287 645.78,287 632.79,198 378,198 430.19,556 "/>
<polygon id="Bottom" class="st2" points="456.57,737 469.55,826 640,826 716.83,773.81 711.45,737 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -26,14 +26,11 @@ pkg-info.jq:
$(foreach dep, ${LANG_DEPS}, echo '.dependencies.$(word 1,$(subst =, ,${dep}))="$(word 2,$(subst =, ,${dep}))"' >> pkg-info.jq)
%.asc: %
gpg --detach-sig --armor --batch --output $@ $<
gpg --detach-sig --armor --output $@ $<
%/: %.tgz
tar xzf $<
%/: %.tar.gz
tar xzf $<
.PHONY: clean
clean:
rm -rf $(filter-out Makefile, $(wildcard *))

View File

@ -8,12 +8,12 @@ run:
echo 'python$(shell grep -oP "\d+.\d+"<<<${VERSION}) $$*' > run
${NAME}-${VERSION}/environment:
echo 'export PATH=$$PWD/bin:$$PATH' > $@
echo 'export PATH=$$PWD/${NAME}-${VERSION}/bin:$$PATH' > $@
${NAME}-${VERSION}/: Python-${VERSION}/
cd $< && ./configure --prefix /
$(MAKE) -j$(or ${MAKE_JOBS},64) -C $<
DESTDIR=../$@ $(MAKE) -j$(or ${MAKE_JOBS},64) -C $< altinstall || true
Python-${VERSION}.tgz:
${NAME}-${VERSION}.tgz:
curl "https://www.python.org/ftp/python/${VERSION}/$@" -o $@

View File

@ -15,9 +15,8 @@ cleanup: $(patsubst %,%/cleanup,${VERSIONS})
%/${LANGUAGE}-%.pkg.tar.gz: %/Makefile
$(MAKE) -C $(shell dirname $<)
%/Makefile:
@mkdir -p $(shell dirname $@)
@echo 'VERSION=$(patsubst %/Makefile,%,$@)' > $@
@echo 'NAME=${LANGUAGE}' >> $@
@echo 'NAME=${LANGUAGE}' > $@
@echo 'include ../base.mk' >> $@

3
repo/.gitignore vendored
View File

@ -1,3 +0,0 @@
*.pkg.tar.gz
index.yaml
*.key

View File

@ -1,7 +0,0 @@
FROM alpine:3.13
RUN apk add --no-cache python3 py3-pip gnupg jq zlib zlib-dev cmake cmake-doc extra-cmake-modules extra-cmake-modules-doc build-base gcc abuild binutils binutils-doc gcc-doc yq bash coreutils util-linux pciutils usbutils coreutils binutils findutils grep && \
ln -sf /bin/bash /bin/sh && \
pip3 install 'yq==2.12.0'
CMD [ "bash", "/repo/make.sh" ]

View File

@ -1,7 +0,0 @@
# Piston Filesystem Repo Builder
This is just a simple POC for a repository tool to run locally.
This only demonstrates building an unsigned python-3.9.1 package, however if it finds an `asc` file it will include it as the signature.
Mount this whole directory into `/repo` in your API container if you wish to use it.

View File

@ -1,13 +0,0 @@
#!/bin/bash -e
cd /repo
# Make packages
pushd ../packages/python
make build VERSIONS=3.9.1
popd
# Make repo index
./mkindex.sh

View File

@ -1,26 +0,0 @@
echo "schema: ppman-repo-1" > index.yaml
echo "baseurl: file://$PWD" >> index.yaml
echo "keys: []" >> index.yaml
echo "packages: []" >> index.yaml
yq -yi '.keys[0] = "0x107DA02C7AE97B084746564B9F1FD9D87950DB6F"' index.yaml
i=-1
for pkg in $(find ../packages -type f -name "*.pkg.tar.gz")
do
((i=i+1))
cp $pkg .
PKGFILE=$(basename $pkg)
PKGFILENAME=$(echo $PKGFILE | sed 's/\.pkg\.tar\.gz//g')
PKGNAME=$(echo $PKGFILENAME | grep -oP '^\K.+(?=-)')
PKGVERSION=$(echo $PKGFILENAME | grep -oP '^.+-\K.+')
BUILDFILE=https://github.com/engineer-man/piston/tree/v3/packages/python/
SIZE=$(tar tzvf $PKGFILE | sed 's/ \+/ /g' | cut -f3 -d' ' | sed '2,$s/^/+ /' | paste -sd' ' | bc)
tar xzf $PKGFILE pkg-info.json
yq -yi ".packages[$i] = {} | .packages[$i].signature = \"$(cat ${pkg}.asc)\" | .packages[$i].buildfile = \"$BUILDFILE\" | .packages[$i].size = $SIZE | .packages[$i].download = \"$PKGFILE\" | .packages[$i].dependencies = $(jq .dependencies -r pkg-info.json) | .packages[$i].author = $(jq .author pkg-info.json) | .packages[$i].language =\"$PKGNAME\" | .packages[$i].version = \"$PKGVERSION\" | .packages[$i].checksums = {} | .packages[$i].checksums.sha256 = \"$(sha256sum $PKGFILE | awk '{print $1}')\"" index.yaml
rm pkg-info.json
done