├── README.md ├── backend ├── .gitignore ├── Dockerfile ├── bin │ └── scheduler ├── index.js ├── lib │ ├── builder.js │ ├── jenkins_xml.js │ └── xml_encode.js ├── package-lock.json ├── package.json └── templates │ ├── commands-linux.txt │ ├── commands-macos.txt │ ├── commands-solaris.txt │ ├── commands-windows.txt │ ├── jenkins.sh │ ├── job-linux.xml │ ├── job-macos.xml │ ├── job-solaris.xml │ ├── job-ssh-publish.txt │ ├── job-windows.xml │ └── job.xml ├── config.env ├── cron ├── .gitignore ├── Dockerfile ├── README.md ├── bin │ └── scheduler ├── index.js ├── package-lock.json └── package.json ├── dev ├── rhub └── stack-custom.yml ├── digitalocean ├── .gitignore ├── rhub └── stack-custom.yml ├── frontend ├── .dockerignore ├── .gitignore ├── CHECKS ├── Dockerfile ├── README.md ├── app.js ├── bin │ └── www ├── data │ ├── email-inlined.html │ ├── email.html │ ├── email.txt │ └── styles.css ├── lib │ ├── auth-ok.js │ ├── check-token.js │ ├── create-job.js │ ├── email-notification.js │ ├── filter-log.js │ ├── get-image.js │ ├── get-package-data.js │ ├── get-pkg-from-filename.js │ ├── get-user.js │ ├── get-version-from-filename.js │ ├── job-to-db.js │ ├── mail-token.js │ ├── mail-verification-code.js │ ├── parse-rhub-log.js │ ├── queue-job.js │ ├── queue-this.js │ ├── re-status.js │ ├── send-email-mailgun.js │ ├── send-email-smtp.js │ ├── send-email.js │ ├── update-log.js │ └── urls.js ├── package-lock.json ├── package.json ├── public │ ├── .well-known │ │ └── acme-challenge │ │ │ ├── bQS91jPgESpdE1y_YFko_5yLMgn8KGEdpGcEuDIE6Ng │ │ │ ├── qZ8CC8EFZACYXcGVFpHxR4-w-iQWorI2vnx30rx_AD4 │ │ │ └── wyl61bg_fpnNdT2MAzovERs1ltM1FK0ySKh_t2Vl0DE │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── data │ │ └── platforms.json │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── javascripts │ │ └── vendor │ │ │ ├── bootstrap.min.js.gz │ │ │ ├── jquery-1.11.2.min.js │ │ │ └── modernizr-2.8.3-respond-1.4.2.min.js │ ├── logo.png │ ├── mstile-150x150.png │ ├── rhub-header.png │ ├── rhub-square-white.png │ ├── safari-pinned-tab.svg │ ├── site.webmanifest │ └── stylesheets │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── logstyle.css │ │ ├── style.css │ │ └── vendor │ │ ├── bootstrap-theme.min.css.gz │ │ └── bootstrap.min.css.gz ├── routes │ ├── api.js │ ├── build.js │ ├── check.js │ ├── index.js │ ├── job.js │ ├── login.js │ └── status.js └── views │ ├── about.hjs │ ├── aboutdiv.hjs │ ├── aboutdiv.md │ ├── adv.hjs │ ├── badpackage.hjs │ ├── error.hjs │ ├── footer.hjs │ ├── header.hjs │ ├── ie7notice.hjs │ ├── index.hjs │ ├── layout.hjs │ ├── navbar.hjs │ ├── ok.hjs │ ├── simple.hjs │ ├── status.hjs │ ├── terms.hjs │ └── verify.hjs ├── jenkins.pass ├── jenkins ├── Dockerfile ├── config.groovy ├── default-user.groovy ├── plugins.txt └── scripts │ ├── windows-2008-pre-setup.ps1 │ └── windows-2008-setup.ps1 ├── linux-builder ├── Dockerfile └── run.sh ├── nginx ├── Dockerfile ├── entrypoint.sh ├── nginx.conf.https.in ├── nginx.conf.in └── wait-for ├── rhub ├── seed ├── Dockerfile ├── app.json └── logdb.sh ├── stack.yml └── web.pass /README.md: -------------------------------------------------------------------------------- 1 | 2 | # R-hub Server development version 3 | 4 | This repository contains a Docker Compose project, that describes 5 | most of R-hub's services. 6 | 7 | Note that the master branch a development version. For deployment, 8 | please see [Releases](../../releases) for the latest released version. 9 | 10 | ## License 11 | 12 | MIT @ R Consortium 13 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM node:8 3 | 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are 8 | # copied where available (npm@5+) 9 | 10 | COPY package*json ./ 11 | 12 | RUN npm install 13 | 14 | ## Include the app's source code 15 | COPY . . 16 | 17 | CMD [ "npm", "start" ] 18 | -------------------------------------------------------------------------------- /backend/bin/scheduler: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | 5 | var jenkins_url = process.env.JENKINS_URL; 6 | try { 7 | var jenkins_pass = fs.readFileSync("/run/secrets/jenkins.pass", 'utf8') 8 | .trim(); 9 | jenkins_url = jenkins_url.replace("", jenkins_pass); 10 | } catch (e) { 11 | console.log("No jenkins.pass secret, JENKINS_URL is used as is"); 12 | } 13 | process.env.JENKINS_URL = jenkins_url; 14 | 15 | var scheduler = require('../index'); 16 | scheduler('job') 17 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug'); 2 | var builder = require('./lib/builder'); 3 | var amqp = require('amqplib'); 4 | 5 | var broker_url = process.env.RABBITMQ_URL; 6 | 7 | function run(q) { 8 | 9 | return amqp.connect(broker_url).then(function(conn) { 10 | process.once('SIGINT', function() { conn.close(); }); 11 | return conn.createChannel().then(function(ch) { 12 | var ok = ch.assertQueue(q, {durable: true}); 13 | ok = ok.then(function() { ch.prefetch(1); }); 14 | ok = ok.then(function() { 15 | ch.consume(q, doWork, {noAck: false}); 16 | }); 17 | return ok; 18 | 19 | function doWork(msg) { 20 | var msg_obj = JSON.parse(msg.content.toString()); 21 | console.log("STARTED: " + msg_obj); 22 | 23 | builder(msg_obj, function(error) { 24 | if (!error) { 25 | console.log("DONE: " + msg_obj.package); 26 | ch.ack(msg); 27 | } else { 28 | console.log("ERROR: " + msg_obj.package); 29 | } 30 | }) 31 | } 32 | }) 33 | }) 34 | } 35 | 36 | function run_robust(q) { 37 | var q2 = q; 38 | 39 | function run_loop() { 40 | run(q2).catch(function(err) { 41 | console.log(err); 42 | console.log("Waiting 1 sec and trying again"); 43 | setTimeout(run_loop, 1000); 44 | }); 45 | } 46 | 47 | run_loop(); 48 | } 49 | 50 | module.exports = run_robust; 51 | -------------------------------------------------------------------------------- /backend/lib/builder.js: -------------------------------------------------------------------------------- 1 | var jenkins_url = process.env.JENKINS_URL || 2 | 'http://jenkins.rhub.me'; 3 | var jenkins = require('jenkins'); 4 | var jenkins_xml = require('../lib/jenkins_xml'); 5 | var quote = require('shell-quote').quote; 6 | 7 | function builder(job, callback) { 8 | 9 | var conn = jenkins({ baseUrl: jenkins_url, crumbIssuer: true }); 10 | add_pkg(conn, job, function(err) { 11 | if (err) { callback(err); return; } 12 | callback(null); 13 | }) 14 | } 15 | 16 | function add_pkg(conn, job, callback) { 17 | 18 | add_jenkins_job(conn, job, function(err) { 19 | if (err) { console.log(err); callback(err); return; } 20 | 21 | build_jenkins_job(conn, job, function(err) { 22 | if (err) { console.log(err); callback(err); return; } 23 | callback(null); 24 | }) 25 | }) 26 | } 27 | 28 | function add_jenkins_job(conn, job, callback) { 29 | var job_name = jenkins_job_name(job); 30 | jenkins_xml(job, function(err, job_xml) { 31 | if (err) { callback(err); return; } 32 | conn.job.create( 33 | 'Jobs/' + job_name, 34 | job_xml, 35 | function(err) { 36 | if (err) { console.log(err); callback(err); return; } 37 | callback(null); 38 | } 39 | ) 40 | }) 41 | } 42 | 43 | function build_jenkins_job(conn, job, callback) { 44 | var job_name = jenkins_job_name(job); 45 | var env_vars = flatten_env_vars(job.envVars, job.ostype !== "Linux"); 46 | var parameters = { 47 | 'package': job.package, 48 | 'filename': job.filename, 49 | 'url': job.url, 50 | 'image': job.image || "", 51 | 'checkArgs': job.checkArgs || "", 52 | 'envVars': env_vars || "", 53 | 'rversion': job.rversion || "r-release", 54 | 'startPingUrl': job.builder + '/build/in-progress/' + job_name, 55 | 'endPingUrl': job.builder + '/build', 56 | 'build': (job.options.build || false) + '', 57 | 'pkgname': job.pkg + '' 58 | }; 59 | 60 | conn.job.build( 61 | 'Jobs/' + job_name, 62 | { 'parameters': parameters }, 63 | function(err) { 64 | if (err) { console.log(err); callback(err); return; } 65 | callback(null) 66 | } 67 | ) 68 | } 69 | 70 | function flatten_env_vars(x, should_quote) { 71 | if (x === null || x === undefined || x === "") return(null); 72 | return Object.keys(x) 73 | .map(function(key) { 74 | var k = String(key); 75 | var v = String(x[key]); 76 | if (should_quote) { 77 | return quote([k]) + "=" + quote([v]); 78 | } else { 79 | return k + "=" + v; 80 | } 81 | }) 82 | .join("\n"); 83 | } 84 | 85 | function jenkins_job_name(job) { 86 | return job.buildId; 87 | } 88 | 89 | module.exports = builder; 90 | -------------------------------------------------------------------------------- /backend/lib/jenkins_xml.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var mustache = require('mustache'); 3 | var xml_encode = require('../lib/xml_encode'); 4 | 5 | var artifacts = process.env.RHUB_ARTIFACTS || "ssh"; 6 | 7 | function jenkins_xml(job, callback) { 8 | 9 | // For the transition, we don't have ostype 10 | var os = job.ostype || 'Linux'; 11 | os = os.toLowerCase(); 12 | var template = './templates/job-' + os + '.xml'; 13 | 14 | fs.readFile( 15 | template, 16 | 'utf8', 17 | function(err, template) { 18 | if (err) { console.log(err); callback(err); return; } 19 | 20 | fs.readFile( 21 | './templates/commands-' + os + '.txt', 22 | 'utf8', 23 | function(err, command) { 24 | if (err) { console.log(err); callback(err); return; } 25 | var labels = job.platforminfo['node-labels'] || []; 26 | labels.push('swarm'); 27 | labels = labels.join(' && '); 28 | var data = { 'commands': xml_encode(command), 29 | 'email': xml_encode(job.email), 30 | 'labels': xml_encode(labels) }; 31 | 32 | if (artifacts == "ssh") { 33 | fs.readFile('./templates/job-ssh-publish.txt', 34 | 'utf8', 35 | function(err, ssh) { 36 | if (err) { 37 | console.log(err); 38 | return callback(err); 39 | } 40 | data['ssh-publish'] = xml_encode(ssh); 41 | var res = mustache.render(template, data); 42 | callback(null, res); 43 | }) 44 | } else { 45 | data['ssh-publish'] = ''; 46 | var res = mustache.render(template, data); 47 | callback(null, res); 48 | } 49 | } 50 | ) 51 | } 52 | ) 53 | } 54 | 55 | module.exports = jenkins_xml; 56 | -------------------------------------------------------------------------------- /backend/lib/xml_encode.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(str) { 3 | if (typeof str === 'string' || str instanceof String) { 4 | return str 5 | .replace(/&/g, '&') 6 | .replace(//g, '>') 8 | .replace(/"/g, '"') 9 | .replace(/'/g, '''); 10 | } else { 11 | return str; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rhub-backend", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "amqplib": { 8 | "version": "0.5.2", 9 | "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.2.tgz", 10 | "integrity": "sha512-l9mCs6LbydtHqRniRwYkKdqxVa6XMz3Vw1fh+2gJaaVgTM6Jk3o8RccAKWKtlhT1US5sWrFh+KKxsVUALURSIA==", 11 | "requires": { 12 | "bitsyntax": "~0.0.4", 13 | "bluebird": "^3.4.6", 14 | "buffer-more-ints": "0.0.2", 15 | "readable-stream": "1.x >=1.1.9", 16 | "safe-buffer": "^5.0.1" 17 | } 18 | }, 19 | "array-filter": { 20 | "version": "0.0.1", 21 | "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", 22 | "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=" 23 | }, 24 | "array-map": { 25 | "version": "0.0.0", 26 | "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", 27 | "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=" 28 | }, 29 | "array-reduce": { 30 | "version": "0.0.0", 31 | "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", 32 | "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=" 33 | }, 34 | "bitsyntax": { 35 | "version": "0.0.4", 36 | "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz", 37 | "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=", 38 | "requires": { 39 | "buffer-more-ints": "0.0.2" 40 | } 41 | }, 42 | "bluebird": { 43 | "version": "3.5.2", 44 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", 45 | "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==" 46 | }, 47 | "buffer-more-ints": { 48 | "version": "0.0.2", 49 | "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", 50 | "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=" 51 | }, 52 | "core-util-is": { 53 | "version": "1.0.2", 54 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 55 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 56 | }, 57 | "debug": { 58 | "version": "4.1.0", 59 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", 60 | "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", 61 | "requires": { 62 | "ms": "^2.1.1" 63 | } 64 | }, 65 | "inherits": { 66 | "version": "2.0.3", 67 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 68 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 69 | }, 70 | "isarray": { 71 | "version": "0.0.1", 72 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 73 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 74 | }, 75 | "jenkins": { 76 | "version": "0.20.1", 77 | "resolved": "https://registry.npmjs.org/jenkins/-/jenkins-0.20.1.tgz", 78 | "integrity": "sha512-4vpnBYIyy995FaReWP3LAGaVsQgV9WayI1pjEHbF+oIM/nV5DyGSwa/xIojZ7U+ECk8ZcXsCJDOhuJQvCr0AUA==", 79 | "requires": { 80 | "papi": "^0.26.0" 81 | } 82 | }, 83 | "jsonify": { 84 | "version": "0.0.0", 85 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 86 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" 87 | }, 88 | "ms": { 89 | "version": "2.1.1", 90 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 91 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 92 | }, 93 | "mustache": { 94 | "version": "2.3.2", 95 | "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", 96 | "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==" 97 | }, 98 | "papi": { 99 | "version": "0.26.0", 100 | "resolved": "https://registry.npmjs.org/papi/-/papi-0.26.0.tgz", 101 | "integrity": "sha1-1hNqFJIHXrwmRSvY4J5EmYDEfdM=" 102 | }, 103 | "readable-stream": { 104 | "version": "1.1.14", 105 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 106 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 107 | "requires": { 108 | "core-util-is": "~1.0.0", 109 | "inherits": "~2.0.1", 110 | "isarray": "0.0.1", 111 | "string_decoder": "~0.10.x" 112 | } 113 | }, 114 | "safe-buffer": { 115 | "version": "5.1.2", 116 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 117 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 118 | }, 119 | "shell-quote": { 120 | "version": "1.6.1", 121 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", 122 | "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", 123 | "requires": { 124 | "array-filter": "~0.0.0", 125 | "array-map": "~0.0.0", 126 | "array-reduce": "~0.0.0", 127 | "jsonify": "~0.0.0" 128 | } 129 | }, 130 | "string_decoder": { 131 | "version": "0.10.31", 132 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 133 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rhub-backend", 3 | "version": "1.0.0", 4 | "description": "The back-end of r-hub. Picks up submissions from the queue and adds the builds to Jenkins.", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "start": "node ./bin/scheduler" 10 | }, 11 | "engines": { 12 | "node": "8.12.x" 13 | }, 14 | "dependencies": { 15 | "amqplib": "^0.5.2", 16 | "debug": "^4.1.0", 17 | "jenkins": "^0.20.1", 18 | "mustache": "^2.2.1", 19 | "shell-quote": "^1.6.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/templates/commands-linux.txt: -------------------------------------------------------------------------------- 1 | 2 | # Notify the frontend that we are starting the build 3 | wget ${startPingUrl}/$(date -u +%Y-%m-%dT%H:%M:%SZ) || true 4 | 5 | # Get the platform, the R package uses this to determine 6 | # the packages needed 7 | export RHUB_PLATFORM=$(docker run --user docker \ 8 | --rm rhub/${image} \ 9 | bash -c 'echo $RHUB_PLATFORM') 10 | 11 | # Look up system requirements 12 | # wget https://raw.githubusercontent.com/MangoTheCat/remotes/r-hub/install-github.R 13 | # R -e "source(\"install-github.R\")\$value(\"r-hub/sysreqs\")" 14 | 15 | echo ">>>>>==================== Downloading and unpacking package file" 16 | 17 | wget -O "$package" "$url" 18 | DESC=$(tar tzf "$package" | grep "^[^/]*/DESCRIPTION$") 19 | tar xzf "$package" "$DESC" 20 | 21 | echo ">>>>>==================== Querying system requirements" 22 | 23 | sysreqs=$(Rscript -e "library(sysreqs); cat(sysreq_commands(\"$DESC\"))") 24 | rm "$DESC" 25 | 26 | echo ">>>>>==================== Installing system requirements" 27 | 28 | # Install them, if there is anything to install 29 | if [ ! -z "${sysreqs}" ]; then 30 | echo "${sysreqs}" > sysreqs.sh 31 | docker create --user root --name ${JOB_BASE_NAME}-1 \ 32 | rhub/${image} bash /root/sysreqs.sh 33 | # Copy over sysreqs.sh script 34 | docker cp sysreqs.sh "${JOB_BASE_NAME}-1:/root/sysreqs.sh" 35 | # Start it 36 | docker start -i -a ${JOB_BASE_NAME}-1 37 | # Save the container as an image 38 | newimage=$(docker commit ${JOB_BASE_NAME}-1) 39 | else 40 | # If there is nothing to install we just use the stock image 41 | newimage=rhub/${image} 42 | fi 43 | 44 | if [ -z "$RHUB_CRAN_MIRROR" ]; then 45 | RHUB_CRAN_MIRROR=http://jenkins.iotwjus4p5merbek114fbzbqud.dx.internal.cloudapp.net/ 46 | fi 47 | 48 | # Inject env vars into container 49 | 50 | env=$(tempfile 2>/dev/null || mktemp) 51 | echo url=$url >> $env 52 | echo package=$package >> $env 53 | echo checkArgs=$checkArgs >> $env 54 | echo build=$build >> $env 55 | echo R_REMOTES_STANDALONE=true >> $env 56 | echo R_REMOTES_NO_ERRORS_FROM_WARNINGS=true >> $env 57 | echo TZ=Europe/London >> $env 58 | echo RHUB_CRAN_MIRROR="$RHUB_CRAN_MIRROR" >> $env 59 | echo "$envVars" >> $env 60 | 61 | # Run the check in the new image 62 | 63 | echo ">>>>>==================== Starting Docker container" 64 | 65 | cat >build.sh <<'EOF' 66 | ## The default might not be the home directory, but / 67 | cd ~ 68 | 69 | ## Configure R, local package library, and also CRAN and BioConductor 70 | export PATH=$(ls /opt/R-* -d)/bin:$PATH 71 | if [[ -z "$RBINARY" ]]; then RBINARY="R"; fi 72 | export R_LIBS=~/R 73 | mkdir -p ~/R 74 | echo "options(repos = c(CRAN = \"$RHUB_CRAN_MIRROR\"))" >> ~/.Rprofile 75 | $RBINARY -e "source('https://bioconductor.org/biocLite.R')" 76 | echo "options(repos = BiocInstaller::biocinstallRepos())" >> ~/.Rprofile 77 | echo "unloadNamespace('BiocInstaller')" >> ~/.Rprofile 78 | 79 | cp "/tmp/${package}" . 80 | 81 | if [[ "${build}" == "true" ]]; then 82 | echo ">>>>>==================== Running R CMD build" 83 | mkdir build 84 | cd build 85 | tar xzf ../"${package}" 86 | pkgname=$(ls | head -1 | sed 's/\///') 87 | $RBINARY CMD build ${pkgname} 88 | package=$(ls *.tar.gz | head -1) 89 | cp "${package}" .. 90 | cd .. 91 | fi 92 | 93 | ## We put it here, so the main process can pick it up 94 | mkdir -p /tmp/output 95 | cp "${package}" /tmp/output/ 96 | echo "${package}" > /tmp/output/filename 97 | 98 | echo ">>>>>==================== Querying package dependencies" 99 | 100 | ## Download the single file install script from mangothecat/remotes 101 | ## We cannot do this from R, because some R versions do not support 102 | ## HTTPS. Then we install a proper 'remotes' package with it. 103 | curl -O https://raw.githubusercontent.com/MangoTheCat/remotes/r-hub/install-github.R 104 | xvfb-run --server-args="-screen 0 1024x768x24" $RBINARY -e "source(\"install-github.R\")\$value(\"r-lib/remotes@r-hub\")" 105 | 106 | echo ">>>>>==================== Installing package dependencies" 107 | 108 | ## Print configuration information for compilers 109 | echo $PATH 110 | $RBINARY CMD config CC 111 | `$RBINARY CMD config CC` --version 112 | $RBINARY CMD config CXX 113 | `$RBINARY CMD config CXX` --version 114 | 115 | echo Pandoc: 116 | which pandoc 117 | ls -l `which pandoc` 118 | ls -l `which pandoc-citeproc` 119 | 120 | ## Install the package, so its dependencies will be installed 121 | ## This is a temporary solution, until remotes::install_deps works on a 122 | ## package bundle 123 | xvfb-run --server-args="-screen 0 1024x768x24" $RBINARY -e "remotes::install_local(\"$package\", dependencies = TRUE, INSTALL_opts = \"--build\")" 124 | 125 | ## If installation fails, then we do not run the check at all 126 | pkgname=$(echo $package | sed 's/_.*$//') 127 | if ! $RBINARY -q -e "library($pkgname)"; then exit 1; fi 128 | 129 | echo ">>>>>==================== Running R CMD check" 130 | 131 | ## We only override this if it was not set by the user 132 | if [ -z "${_R_CHECK_FORCE_SUGGESTS_}" ]; then 133 | export _R_CHECK_FORCE_SUGGESTS_=false 134 | fi 135 | 136 | if [ -z "$RHUB_CHECK_COMMAND" ]; then 137 | RHUB_CHECK_COMMAND="$RBINARY CMD check $checkArgs" 138 | fi 139 | 140 | echo About to run xvfb-run $RHUB_CHECK_COMMAND $package 141 | xvfb-run --server-args="-screen 0 1024x768x24" $RHUB_CHECK_COMMAND "$package" 142 | 143 | echo ">>>>>==================== Done with R CMD check" 144 | 145 | pkgname=$(echo $package | sed 's/_.*$//') 146 | mkdir -p $pkgname.Rcheck || true 147 | mv *.tar.gz $pkgname.Rcheck/ || true 148 | EOF 149 | 150 | docker create -i --user docker --env-file $env \ 151 | --name ${JOB_BASE_NAME}-2 $newimage /bin/bash -l /tmp/build.sh 152 | docker cp build.sh "${JOB_BASE_NAME}-2:/tmp/build.sh" 153 | docker cp "${package}" "${JOB_BASE_NAME}-2:/tmp/${package}" 154 | docker start -i -a ${JOB_BASE_NAME}-2 155 | 156 | echo ">>>>>==================== Saving artifacts" 157 | 158 | # Save the artifacts 159 | rm -rf ${JOB_BASE_NAME} 160 | mkdir -p ${JOB_BASE_NAME} 161 | 162 | docker cp "${JOB_BASE_NAME}-2:/tmp/output/filename" . || true 163 | package=$(cat filename | head -1) 164 | docker cp "${JOB_BASE_NAME}-2:/tmp/${package}" \ 165 | ${JOB_BASE_NAME}/ || true 166 | pkgname=$(echo $package | sed 's/_.*$//') 167 | 168 | docker cp "${JOB_BASE_NAME}-2:/home/docker/${pkgname}.Rcheck" \ 169 | ${JOB_BASE_NAME}/ || true 170 | mv ${JOB_BASE_NAME}/${pkgname}.Rcheck/*.tar.gz \ 171 | ${JOB_BASE_NAME}/ || true 172 | 173 | if [ "x$RHUB_ARTIFACTS" = "xlocal" ]; then 174 | cp -r ${JOB_BASE_NAME} /artifacts/ 175 | fi 176 | 177 | # Destroy the new containers and the images 178 | # Only if we needed system installs, but not the stock image 179 | docker rm ${JOB_BASE_NAME}-1 || true 180 | docker rm ${JOB_BASE_NAME}-2 || true 181 | docker rm ${JOB_BASE_NAME}-3 || true 182 | if ! echo $newimage | grep -q 'rhub'; then 183 | docker rmi $newimage || true 184 | fi 185 | -------------------------------------------------------------------------------- /backend/templates/commands-macos.txt: -------------------------------------------------------------------------------- 1 | 2 | # Notify the frontend that we are starting the build 3 | 4 | curl ${startPingUrl}/$(date -u +%Y-%m-%dT%H:%M:%SZ) || true 5 | 6 | cp ~/macoscheck/*.sh . 7 | 8 | ./run.sh "${package}" "${JOB_BASE_NAME}" "${url}" "${rversion}" "${checkArgs}" "${envVars}" "${build}" "${pkgname}" 9 | -------------------------------------------------------------------------------- /backend/templates/commands-solaris.txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Notify the frontend that we are starting the build 4 | 5 | /opt/csw/bin/curl ${startPingUrl}/$(date -u +%Y-%m-%dT%H:%M:%SZ) || true 6 | 7 | cp /export/home/solarischeck/*.sh . 8 | 9 | ./run.sh "${package}" "${JOB_BASE_NAME}" "${url}" "${rversion}" "${checkArgs}" "${envVars}" "${build}" "${pkgname}" 10 | -------------------------------------------------------------------------------- /backend/templates/commands-windows.txt: -------------------------------------------------------------------------------- 1 | 2 | # Notify the frontend that we are starting the build 3 | 4 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 5 | 6 | $d = (get-date).ToUniversalTime().toString('s') 7 | $u = ( $env:startPingUrl + '/' + $d ) 8 | Invoke-WebRequest $u 9 | 10 | cp \Users\rhub\Documents\run.ps1 .\run.ps1 11 | cp \Users\rhub\Documents\slave.ps1 .\slave.ps1 12 | 13 | .\run.ps1 -verbose $env:package $env:JOB_BASE_NAME $env:url $env:rversion $env:checkArgs $env:envVars $env:build $env:pkgname 14 | -------------------------------------------------------------------------------- /backend/templates/jenkins.sh: -------------------------------------------------------------------------------- 1 | job-linux.xml -------------------------------------------------------------------------------- /backend/templates/job-linux.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | 9 | 10 | package 11 | 12 | 13 | 14 | 15 | filename 16 | 17 | 18 | 19 | 20 | url 21 | 22 | 23 | 24 | 25 | image 26 | 27 | 28 | 29 | 30 | checkArgs 31 | 32 | 33 | 34 | 35 | envVars 36 | 37 | 38 | 39 | 40 | startPingUrl 41 | 42 | 43 | 44 | 45 | endPingUrl 46 | 47 | 48 | 49 | 50 | pre-check.json 51 | 52 | 53 | 54 | post-check.json 55 | 56 | 57 | 58 | build 59 | Whether to run R CMD build. 60 | false 61 | 62 | 63 | pkgname 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {{{ labels }}} 72 | false 73 | false 74 | false 75 | false 76 | 77 | false 78 | 79 | 80 | {{{ commands }}} 81 | 82 | 83 | {{{ ssh-publish }}} 84 | 85 | 86 | 87 | 96 | false 97 | 98 | 0 99 | false 100 | 101 | 102 | 103 | false 104 | false 105 | true 106 | true 107 | true 108 | true 109 | true 110 | true 111 | false 112 | 113 | false 114 | 115 | 116 | 117 | 118 | 119 | 600 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /backend/templates/job-macos.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | 9 | 10 | package 11 | 12 | 13 | 14 | 15 | filename 16 | 17 | 18 | 19 | 20 | url 21 | 22 | 23 | 24 | 25 | image 26 | 27 | 28 | 29 | 30 | checkArgs 31 | 32 | 33 | 34 | 35 | envVars 36 | 37 | 38 | 39 | 40 | startPingUrl 41 | 42 | 43 | 44 | 45 | endPingUrl 46 | 47 | 48 | 49 | 50 | pre-check.json 51 | 52 | 53 | 54 | post-check.json 55 | 56 | 57 | 58 | build 59 | Whether to run R CMD build. 60 | false 61 | 62 | 63 | pkgname 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {{{ labels }}} 72 | false 73 | false 74 | false 75 | false 76 | 77 | false 78 | 79 | 80 | {{{ commands }}} 81 | 82 | 83 | {{{ ssh-publish }}} 84 | 85 | 86 | 87 | 96 | false 97 | 98 | 0 99 | false 100 | 101 | 102 | 103 | false 104 | false 105 | true 106 | true 107 | true 108 | true 109 | true 110 | true 111 | false 112 | 113 | false 114 | 115 | 116 | 117 | 118 | 119 | 600 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /backend/templates/job-solaris.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | 9 | 10 | package 11 | 12 | 13 | 14 | 15 | filename 16 | 17 | 18 | 19 | 20 | url 21 | 22 | 23 | 24 | 25 | image 26 | 27 | 28 | 29 | 30 | checkArgs 31 | 32 | 33 | 34 | 35 | envVars 36 | 37 | 38 | 39 | 40 | startPingUrl 41 | 42 | 43 | 44 | 45 | endPingUrl 46 | 47 | 48 | 49 | 50 | pre-check.json 51 | 52 | 53 | 54 | post-check.json 55 | 56 | 57 | 58 | build 59 | Whether to run R CMD build. 60 | false 61 | 62 | 63 | pkgname 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {{{ labels }}} 72 | false 73 | false 74 | false 75 | false 76 | 77 | false 78 | 79 | 80 | {{{ commands }}} 81 | 82 | 83 | {{{ ssh-publish }}} 84 | 85 | 86 | 87 | 96 | false 97 | 98 | 0 99 | false 100 | 101 | 102 | 103 | false 104 | false 105 | true 106 | true 107 | true 108 | true 109 | true 110 | true 111 | false 112 | 113 | false 114 | 115 | 116 | 117 | 118 | 119 | 600 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /backend/templates/job-ssh-publish.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | SSH: 4 | 5 | 6 | 7 | files 8 | false 9 | 10 | 11 | ${JOB_BASE_NAME} 12 | ${JOB_BASE_NAME}/**/00install.out,${JOB_BASE_NAME}/**/00check.log,${JOB_BASE_NAME}/**/tests*/**/*.Rout*,${JOB_BASE_NAME}/*.tar.gz,${JOB_BASE_NAME}/*.tgz,${JOB_BASE_NAME}/*.zip 13 | 14 | ${JOB_BASE_NAME}/ 15 | false 16 | false 17 | false 18 | false 19 | false 20 | [, ]+ 21 | 22 | 120000 23 | false 24 | 25 | 26 | false 27 | false 28 | 29 | 30 | false 31 | false 32 | false 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /backend/templates/job-windows.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | 9 | 10 | package 11 | 12 | 13 | 14 | 15 | filename 16 | 17 | 18 | 19 | 20 | url 21 | 22 | 23 | 24 | 25 | image 26 | 27 | 28 | 29 | 30 | checkArgs 31 | 32 | 33 | 34 | 35 | envVars 36 | 37 | 38 | 39 | 40 | rversion 41 | 42 | r-release 43 | 44 | 45 | startPingUrl 46 | 47 | 48 | 49 | 50 | endPingUrl 51 | 52 | 53 | 54 | 55 | pre-check.json 56 | 57 | 58 | 59 | post-check.json 60 | 61 | 62 | 63 | build 64 | Whether to run R CMD build. 65 | false 66 | 67 | 68 | pkgname 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {{{ labels }}} 77 | false 78 | false 79 | false 80 | false 81 | 82 | false 83 | 84 | 85 | {{{ commands }}} 86 | 87 | 88 | {{{ ssh-publish }}} 89 | 90 | 91 | 92 | 101 | false 102 | 103 | 0 104 | false 105 | 106 | 107 | 108 | false 109 | false 110 | true 111 | true 112 | true 113 | true 114 | true 115 | true 116 | false 117 | 118 | false 119 | 120 | 121 | 122 | 123 | 124 | 1800 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | WINDOWS R-DEVEL 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /backend/templates/job.xml: -------------------------------------------------------------------------------- 1 | job-linux.xml -------------------------------------------------------------------------------- /config.env: -------------------------------------------------------------------------------- 1 | 2 | # ------------------------------------------------------------------ 3 | # You can safely leave these untouched 4 | # ------------------------------------------------------------------ 5 | 6 | RHUB_VERSION="0.10" 7 | RABBITMQ_URL="amqp://guest:guest@queue:5672" 8 | REDIS_URL="redis://redis:6379/0" 9 | REDIS_EMAIL_URL="redis://redis:6379/1" 10 | LOGDB_URL="http://logdb:5984/logs" 11 | RHUB_CRAN_MIRROR="https://cloud.r-project.org" 12 | RHUB_ARTIFACTS="local" 13 | JENKINS_ROOT_URL="http://localhost:8080/jenkins" 14 | JENKINS_USER="admin" 15 | 16 | # ------------------------------------------------------------------ 17 | # HTTPS. You'll need to create the 'nginx.crt' and 'nginx.key' 18 | # secrets to make this work. Then set it to "true". 19 | # ------------------------------------------------------------------ 20 | 21 | RHUB_HTTPS="false" 22 | 23 | # ------------------------------------------------------------------ 24 | # GitHub auth. You can leave these untouched for deploying on 127.0.0.1, 25 | # but you'll probably need to update them for a non-local 26 | # deployment. See the digitalocean folder for an example. 27 | # ------------------------------------------------------------------ 28 | 29 | # Create a GitHub app at https://github.com/settings/developers or 30 | # https://github.com/organizations//settings/applications 31 | # if you use a GitHub organization 32 | 33 | GITHUB_CLIENT_ID="d0b3649d316fca3ebc92" 34 | GITHUB_CLIENT_SECRET="68679254184d595b85b1f60772abc93a7f822bbf" 35 | 36 | # You need to set these to the host name of your server 37 | 38 | RHUB_BUILDER_EXTERNAL_URL="http://127.0.0.1" 39 | RHUB_ARTIFACTS_URL="http://127.0.0.1/artifacts/" 40 | 41 | # ------------------------------------------------------------------ 42 | # Sending emails. You'll need to update this to be able to send 43 | # notification emails. 44 | # ------------------------------------------------------------------ 45 | 46 | RHUB_EMAIL_FROM='"R-hub builder" ' 47 | 48 | # This must be smtp or mailgun 49 | RHUB_EMAIL_MODE="${RHUB_EMAIL_MODE:-smtp}" 50 | 51 | # Send via mailgun HTTP API 52 | MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-<-- put-mailgun-domain-here -->}" 53 | MAILGUN_API_KEY="${MAILGUN_API_KEY:-<-- put-mailgun-api-key-here -->}" 54 | 55 | # Send via a generic SMTP server 56 | # Here is an example that works with mailgun: 57 | # RHUB_EMAIL_MODE=smtp 58 | # RHUB_SMTP_SERVER=smtp.mailgun.org 59 | # RHUB_SMTP_USERNAME=postmaster@rhub.io 60 | # RHUB_SMTP_PASSWORD=<-- smtp-password --> 61 | # RHUB_SMTP_PORT=465 62 | # RHUB_SMTP_TLS_REQUIRED=true 63 | 64 | RHUB_SMTP_SERVER="<-- smtp-server -->" 65 | RHUB_SMTP_USERNAME="<-- smtp-username -->" 66 | RHUB_SMTP_PASSWORD="<-- smtp-password -->" 67 | RHUB_SMTP_PORT="25" 68 | RHUB_SMTP_TLS_REQUIRED="true" 69 | -------------------------------------------------------------------------------- /cron/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /cron/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM node:8 3 | 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are 8 | # copied where available (npm@5+) 9 | 10 | COPY package*json ./ 11 | 12 | RUN npm install 13 | 14 | ## Include the app's source code 15 | COPY . . 16 | 17 | CMD [ "npm", "start" ] 18 | -------------------------------------------------------------------------------- /cron/README.md: -------------------------------------------------------------------------------- 1 | # rhub-cron 2 | -------------------------------------------------------------------------------- /cron/bin/scheduler: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var jobs = require('../index'); 3 | -------------------------------------------------------------------------------- /cron/index.js: -------------------------------------------------------------------------------- 1 | var CronJob = require('cron').CronJob; 2 | var jenkins = require('jenkins'); 3 | var async = require('async'); 4 | var fs = require('fs'); 5 | 6 | var jenkins_url = process.env.JENKINS_URL; 7 | 8 | try { 9 | var jenkins_pass = fs.readFileSync("/run/secrets/jenkins.pass", 'utf8') 10 | .trim(); 11 | jenkins_url = jenkins_url.replace("", jenkins_pass); 12 | } catch (e) { 13 | console.log("No jenkins.pass secret, JENKINS_URL is used as is"); 14 | } 15 | 16 | // Time limit to delete job 17 | var TIME_LIMIT = 1000 /* ms */ * 60 /* s */ * 60 /* min */ * 24 * 3; 18 | // How often to run the job reaper, once an hour, at **:42:42 19 | var CRON_JOB_REAPER = '48 45 * * * *'; 20 | 21 | var job = new CronJob(CRON_JOB_REAPER, function() { 22 | 23 | console.log("Running Jenkins job reaper"); 24 | var jen = jenkins({ baseUrl: jenkins_url, crumbIssuer: true }); 25 | jen.job.list(function(err, data) { 26 | if (err) { console.log('Cannot get Jenkins job list'); return; } 27 | async.eachLimit( 28 | data, 29 | 3, 30 | function(job, cb) { delete_if_old(jen, job, cb); } 31 | ); 32 | }); 33 | 34 | }, null, true, 'America/New_York'); 35 | 36 | function delete_if_old(jen, job, callback) { 37 | jen.job.get(job.name, function(err, data) { 38 | if (err) { 39 | console.log('Cannot get Jenkins job ' + job.name); 40 | return callback(null); 41 | } 42 | 43 | if (data.actions) { 44 | for (i = 0; i < data.actions.length; i++) { 45 | var def = data.actions[i].parameterDefinitions; 46 | if (def === undefined) continue; 47 | for (j = 0; j < def.length; j++) { 48 | if (def[j].name == 'keep') { 49 | if (def[j].defaultParameterValue.value) { 50 | console.log('Keeping ' + job.name); 51 | return callback(null); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | // No builds (yet?) 59 | if (! data.lastBuild || ! data.lastBuild.number) { 60 | return callback(null); 61 | } 62 | 63 | jen.build.get(job.name, data.lastBuild.number, function(err, data) { 64 | if (err) { 65 | console.log('Cannot get Jenkins build ' + job.name + ' ' + 66 | data.lastBuild.number); 67 | return callback(null); 68 | } 69 | var diff = new Date() - new Date(data.timestamp); 70 | // about three days 71 | if (diff > TIME_LIMIT) { 72 | return delete_job(jen, job, callback); 73 | } 74 | return callback(null); 75 | }); 76 | }); 77 | } 78 | 79 | function delete_job(jen, job, callback) { 80 | jen.job.destroy(job.name, function(err) { 81 | if (err) { 82 | console.log('Cannot delete Jenkins job ' + job.name); 83 | } else { 84 | console.log('Deleted Jenkins job ' + job.name); 85 | } 86 | return callback(null); 87 | }); 88 | } 89 | 90 | module.exports = job; 91 | -------------------------------------------------------------------------------- /cron/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rhub-cron", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async": { 8 | "version": "2.6.1", 9 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", 10 | "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", 11 | "requires": { 12 | "lodash": "^4.17.10" 13 | } 14 | }, 15 | "cron": { 16 | "version": "1.4.1", 17 | "resolved": "https://registry.npmjs.org/cron/-/cron-1.4.1.tgz", 18 | "integrity": "sha512-HlglwQUNh6bhgfoDR6aEzyHN2T4bc0XhxJxkNPp+Ry7lK7Noby94pHcngYf634+MtxplwZm8okFgNe+R9PGDjg==", 19 | "requires": { 20 | "moment-timezone": "^0.5.x" 21 | } 22 | }, 23 | "jenkins": { 24 | "version": "0.19.0", 25 | "resolved": "https://registry.npmjs.org/jenkins/-/jenkins-0.19.0.tgz", 26 | "integrity": "sha1-kir5yYtz39coyskviti+4rkc1/I=", 27 | "requires": { 28 | "papi": "^0.26.0" 29 | } 30 | }, 31 | "lodash": { 32 | "version": "4.17.11", 33 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 34 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" 35 | }, 36 | "moment": { 37 | "version": "2.22.2", 38 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", 39 | "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" 40 | }, 41 | "moment-timezone": { 42 | "version": "0.5.21", 43 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz", 44 | "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==", 45 | "requires": { 46 | "moment": ">= 2.9.0" 47 | } 48 | }, 49 | "papi": { 50 | "version": "0.26.0", 51 | "resolved": "https://registry.npmjs.org/papi/-/papi-0.26.0.tgz", 52 | "integrity": "sha1-1hNqFJIHXrwmRSvY4J5EmYDEfdM=" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rhub-cron", 3 | "version": "1.0.0", 4 | "description": "Scheduler for periodic JS tasks on r-hub", 5 | "scripts": { 6 | "start": "node ./bin/scheduler", 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/r-hub/rhub-cron.git" 12 | }, 13 | "keywords": [ 14 | "r-hub" 15 | ], 16 | "author": "Gabor Csardi", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/r-hub/rhub-cron/issues" 20 | }, 21 | "homepage": "https://github.com/r-hub/rhub-cron#readme", 22 | "engines": { 23 | "node": "8.12.x" 24 | }, 25 | "dependencies": { 26 | "async": "^2.0.0-rc.3", 27 | "cron": "^1.1.0", 28 | "jenkins": "^0.19.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dev/rhub: -------------------------------------------------------------------------------- 1 | ../rhub -------------------------------------------------------------------------------- /dev/stack-custom.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | # Changes compared to base project: 4 | # - want to have direct access to services, for debugging 5 | 6 | services: 7 | frontend: 8 | ports: 9 | - "3000:3000" 10 | 11 | redis: 12 | ports: 13 | - "3001:6379" 14 | 15 | logdb: 16 | ports: 17 | - "3002:5984" 18 | 19 | queue: 20 | ports: 21 | - "3003:5672" 22 | 23 | jenkins: 24 | ports: 25 | - "8080:8080" 26 | -------------------------------------------------------------------------------- /digitalocean/.gitignore: -------------------------------------------------------------------------------- 1 | /secrets 2 | -------------------------------------------------------------------------------- /digitalocean/rhub: -------------------------------------------------------------------------------- 1 | ../rhub -------------------------------------------------------------------------------- /digitalocean/stack-custom.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | services: 4 | 5 | frontend: 6 | environment: 7 | - RHUB_BUILDER_EXTERNAL_URL=http://test.rhub.io 8 | - RHUB_ARTIFACTS_URL=http://test.rhub.io/artifacts/ 9 | - RHUB_EMAIL_MODE=mailgun 10 | - MAILGUN_DOMAIN=${MAILGUN_DOMAIN:?rhub.io} 11 | - MAILGUN_API_KEY=${MAILGUN_API_KEY:?Set MAILGUN_API_KEY and MAILGUN_DOMAIN} 12 | - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:?Need to set GITHUB_CLIENT_ID} 13 | - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET?:Set GITHUB_CLIENT_SECRET} 14 | 15 | nginx: 16 | environment: 17 | - RHUB_HTTPS=${RHUB_HTTPS} 18 | secrets: 19 | - nginx.crt 20 | - nginx.key 21 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_nodules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.env 3 | .Rproj.user 4 | rhub-frontend.Rproj 5 | -------------------------------------------------------------------------------- /frontend/CHECKS: -------------------------------------------------------------------------------- 1 | /check I am alive 2 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM node:8 3 | 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are 8 | # copied where available (npm@5+) 9 | 10 | COPY package*json ./ 11 | 12 | RUN npm install 13 | 14 | ## Include the app's source code 15 | COPY . . 16 | 17 | EXPOSE 3000 18 | 19 | CMD [ "npm", "start" ] 20 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | 2 | Source for the frontend of R-hub builder 3 | ======================================== 4 | 5 | # Notes on how to run the web app 6 | 7 | ## Requirements 8 | 9 | You need a local Redis server. E.g. on macOS: 10 | ``` 11 | brew install redis 12 | ``` 13 | 14 | On Windows install Redis from 15 | https://github.com/MicrosoftArchive/redis/releases 16 | 17 | On Linux your distribution probably contains Redis. If not, e.g. on Ubuntu run 18 | 19 | ``` 20 | sudo install redis-server 21 | ``` 22 | 23 | ## Install dependencies 24 | 25 | ``` 26 | npm install 27 | npm install -g supervisor 28 | ``` 29 | 30 | On Ubuntu you might have to run some of these commands as root i.e. putting `sudo` before them. 31 | 32 | `supervisor` is not strictly required, but it is nice, because it 33 | automatically reloads the app if the source files change. 34 | 35 | ## Run the app 36 | 37 | Start Redis, and in another terminal start the app: 38 | 39 | ``` 40 | redis-server 41 | ## In another terminal 42 | supervisor bin/www 43 | ## Or 44 | bin/www 45 | ``` 46 | 47 | Browse http://localhost:3000/ 48 | -------------------------------------------------------------------------------- /frontend/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var gzipStatic = require('connect-gzip-static'); 8 | var session = require('express-session'); 9 | var RedisStore = require('connect-redis')(session); 10 | var passport = require('passport'); 11 | var GitHubStrategy = require('passport-github').Strategy; 12 | var uuid = require('uuid'); 13 | 14 | var routes = require('./routes/index'); 15 | var job = require('./routes/job'); 16 | var login = require('./routes/login'); 17 | var dokkucheck = require('./routes/check'); 18 | var status = require('./routes/status'); 19 | var api = require('./routes/api'); 20 | var build = require('./routes/build'); 21 | 22 | require('dotenv').config(); 23 | 24 | var GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || 'foo'; 25 | var GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET || 'bar'; 26 | var RHUB_BUILDER_URL = process.env.RHUB_BUILDER_URL || 27 | 'http://127.0.0.1:3000'; 28 | var RHUB_BUILDER_EXTERNAL_URL = process.env.RHUB_BUILDER_EXTERNAL_URL || 29 | RHUB_BUILDER_URL; 30 | var REDIS_URL = process.env.REDIS_URL || 31 | 'redis://redis:6379/0'; 32 | 33 | passport.use( 34 | new GitHubStrategy( 35 | { 36 | clientID: GITHUB_CLIENT_ID, 37 | clientSecret: GITHUB_CLIENT_SECRET, 38 | callbackURL: RHUB_BUILDER_EXTERNAL_URL + 39 | '/login/github/callback' 40 | }, 41 | function(accessToken, refreshToken, profile, cb) { 42 | return cb(null, 'github:' + JSON.stringify(profile.emails)); 43 | } 44 | ) 45 | ); 46 | 47 | passport.serializeUser(function(user, cb) { 48 | cb(null, user); 49 | }); 50 | 51 | passport.deserializeUser(function(obj, cb) { 52 | cb(null, obj); 53 | }); 54 | 55 | var app = express(); 56 | 57 | // view engine setup 58 | app.set('views', path.join(__dirname, 'views')); 59 | app.engine('hjs', require('hogan-express')); 60 | app.set('view engine', 'hjs'); 61 | app.set('partials', { 62 | 'layout': 'layout', 63 | 'header': 'header', 64 | 'navbar': 'navbar', 65 | 'ie7notice': 'ie7notice', 66 | 'footer': 'footer', 67 | 'simple': 'simple', 68 | 'adv': 'adv', 69 | 'aboutdiv': 'aboutdiv' 70 | }) 71 | 72 | // uncomment after placing your favicon in /public 73 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 74 | app.use(logger('dev')); 75 | app.use(bodyParser.json({ limit: '100mb'})); 76 | app.use(bodyParser.urlencoded({ extended: false })); 77 | app.use(cookieParser()); 78 | app.use(gzipStatic(path.join(__dirname, 'public'))); 79 | 80 | app.use(session({ 81 | secret: 'r-hub magic going on', 82 | resave: true, 83 | saveUninitialized: true, 84 | genid: function(req) { return uuid.v4(); }, 85 | name: "r-hub frontend", 86 | cookie: { 87 | maxAge: 36000000, 88 | httpOnly: false 89 | }, 90 | store: new RedisStore({ 91 | url: REDIS_URL 92 | }) 93 | })); 94 | 95 | app.use(function (req, res, next) { 96 | if (!req.session) { 97 | return next(new Error('oh no')) // handle error 98 | } 99 | next() // otherwise continue 100 | }) 101 | 102 | app.use(passport.initialize()); 103 | app.use(passport.session()); 104 | 105 | app.use('/', routes); 106 | app.use('/job', job); 107 | 108 | app.use('/', login); 109 | app.use('/status', status); 110 | 111 | // The JSON API 112 | app.use('/api', api); 113 | 114 | app.use('/build', build); 115 | 116 | app.use('/file', express.static('uploads')); 117 | 118 | app.use('/check', dokkucheck); 119 | 120 | // catch 404 and forward to error handler 121 | app.use(function(req, res, next) { 122 | var err = new Error('Not Found'); 123 | err.status = 404; 124 | next(err); 125 | }); 126 | 127 | // error handlers 128 | 129 | // development error handler 130 | // will print stacktrace 131 | if (app.get('env') === 'development') { 132 | app.use(function(err, req, res, next) { 133 | res.status(err.status || 500); 134 | res.render('error', { 135 | message: err.message, 136 | error: err 137 | }); 138 | }); 139 | } 140 | 141 | // production error handler 142 | // no stacktraces leaked to user 143 | app.use(function(err, req, res, next) { 144 | res.status(err.status || 500); 145 | res.render('error', { 146 | message: err.message, 147 | error: {} 148 | }); 149 | }); 150 | 151 | 152 | module.exports = app; 153 | -------------------------------------------------------------------------------- /frontend/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Get the secrets from Docker Swarm 5 | */ 6 | var fs = require('fs'); 7 | 8 | var jenkins_url = process.env.JENKINS_URL; 9 | try { 10 | var jenkins_pass = fs.readFileSync("/run/secrets/jenkins.pass", 'utf8') 11 | .trim(); 12 | jenkins_url = jenkins_url.replace("", jenkins_pass); 13 | } catch (e) { 14 | console.log("No jenkins.pass secret, JENKINS_URL is used as is"); 15 | } 16 | process.env.JENKINS_URL = jenkins_url; 17 | 18 | /** 19 | * Module dependencies. 20 | */ 21 | 22 | var app = require('../app'); 23 | var debug = require('debug')('rhub-frontend:server'); 24 | var http = require('http'); 25 | 26 | /** 27 | * Get port from environment and store in Express. 28 | */ 29 | 30 | var port = normalizePort(process.env.PORT || '3000'); 31 | app.set('port', port); 32 | 33 | /** 34 | * Create HTTP server. 35 | */ 36 | 37 | var server = http.createServer(app); 38 | 39 | /** 40 | * Listen on provided port, on all network interfaces. 41 | */ 42 | 43 | server.listen(port); 44 | server.on('error', onError); 45 | server.on('listening', onListening); 46 | 47 | /** 48 | * Normalize a port into a number, string, or false. 49 | */ 50 | 51 | function normalizePort(val) { 52 | var port = parseInt(val, 10); 53 | 54 | if (isNaN(port)) { 55 | // named pipe 56 | return val; 57 | } 58 | 59 | if (port >= 0) { 60 | // port number 61 | return port; 62 | } 63 | 64 | return false; 65 | } 66 | 67 | /** 68 | * Event listener for HTTP server "error" event. 69 | */ 70 | 71 | function onError(error) { 72 | if (error.syscall !== 'listen') { 73 | throw error; 74 | } 75 | 76 | var bind = typeof port === 'string' 77 | ? 'Pipe ' + port 78 | : 'Port ' + port; 79 | 80 | // handle specific listen errors with friendly messages 81 | switch (error.code) { 82 | case 'EACCES': 83 | console.error(bind + ' requires elevated privileges'); 84 | process.exit(1); 85 | break; 86 | case 'EADDRINUSE': 87 | console.error(bind + ' is already in use'); 88 | process.exit(1); 89 | break; 90 | default: 91 | throw error; 92 | } 93 | } 94 | 95 | /** 96 | * Event listener for HTTP server "listening" event. 97 | */ 98 | 99 | function onListening() { 100 | var addr = server.address(); 101 | var bind = typeof addr === 'string' 102 | ? 'pipe ' + addr 103 | : 'port ' + addr.port; 104 | debug('Listening on ' + bind); 105 | } 106 | -------------------------------------------------------------------------------- /frontend/data/email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 107 | 108 | 109 |
16 |
17 | 18 | 19 | {{#bgIsRed}} 20 | 21 | {{/bgIsRed}} 22 | {{#bgIsOrange}} 23 | 24 | {{/bgIsOrange}} 25 | {{#bgIsGreen}} 26 | 27 | {{/bgIsGreen}} 28 | 29 | 30 | 97 | 98 |
{{ header }}{{ header }}{{ header }}
31 | 32 | 33 | 53 | 54 | 55 | 77 | 78 | 79 | 85 | 86 | 87 | 94 | 95 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
Build ID:{{ id }}
Platform:{{ platform }}
Submitted:{{ submitted }}
Build time:{{ buildTime }}
52 |
56 | {{# anyErrors }} 57 |

ERRORS:

58 | {{# errors }} 59 |

* {{.}}

60 | {{/ errors}} 61 | {{/ anyErrors }} 62 | 63 | {{# anyWarnings }} 64 |

WARNINGS:

65 | {{# warnings }} 66 |

* {{.}}

67 | {{/ warnings }} 68 | {{/ anyWarnings }} 69 | 70 | {{# anyNotes }} 71 |

NOTES:

72 | {{# notes }} 73 |

* {{.}}

74 | {{/ notes }} 75 | {{/ anyNotes }} 76 |
80 | See the full build log: 81 | HTML, 82 | text, 83 | artifacts. 84 |
88 | Have questions, suggestions or want to report a bug? 89 | Please file an issue ticket at 90 | 91 | GitHub. 92 | Thank You for using the R-hub builder. 93 |
96 |
99 |
106 |
110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /frontend/data/email.txt: -------------------------------------------------------------------------------- 1 | 2 | {{{ title }}} 3 | 4 | Build ID: {{{ id }}} 5 | Platform: {{{ platform }}} 6 | Submitted: {{{ submitted }}} 7 | Build time: {{{ buildTime }}} 8 | 9 | {{# anyErrors }} 10 | ERRORS: 11 | ------- 12 | {{# errors }} 13 | * {{{.}}} 14 | {{/ errors}} 15 | {{/ anyErrors }} 16 | 17 | {{# anyWarnings }} 18 | WARNINGS: 19 | --------- 20 | {{# warnings }} 21 | * {{{.}}} 22 | {{/ warnings }} 23 | {{/ anyWarnings }} 24 | 25 | {{# anyNotes }} 26 | NOTES: 27 | ------ 28 | {{# notes }} 29 | * {{{.}}} 30 | {{/ notes }} 31 | {{/ anyNotes }} 32 | 33 | See the full build log: 34 | HTML: {{{ logHtml }}} 35 | Text: {{{ logText }}} 36 | Artifacts: {{{ artifactLink }}} 37 | 38 | Have questions, suggestions or want to report a bug? 39 | Please file an issue ticket at GitHub at 40 | https://github.com/r-hub/rhub/issues 41 | 42 | Thank You for using the R-hub builder. 43 | 44 | (c) 2016 The R Consortium 45 | -------------------------------------------------------------------------------- /frontend/data/styles.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------- 2 | GLOBAL 3 | A very basic CSS reset 4 | ------------------------------------- */ 5 | * { 6 | margin: 0; 7 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 8 | box-sizing: border-box; 9 | font-size: 14px; 10 | } 11 | 12 | img { 13 | max-width: 100%; 14 | } 15 | 16 | body { 17 | -webkit-font-smoothing: antialiased; 18 | -webkit-text-size-adjust: none; 19 | width: 100% !important; 20 | height: 100%; 21 | line-height: 1.6em; 22 | /* 1.6em * 14px = 22.4px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */ 23 | /*line-height: 22px;*/ 24 | } 25 | 26 | /* Let's make sure all tables have defaults */ 27 | table td { 28 | vertical-align: top; 29 | } 30 | 31 | /* ------------------------------------- 32 | BODY & CONTAINER 33 | ------------------------------------- */ 34 | body { 35 | background-color: #f6f6f6; 36 | } 37 | 38 | .body-wrap { 39 | background-color: #f6f6f6; 40 | width: 100%; 41 | } 42 | 43 | .container { 44 | display: block !important; 45 | max-width: 600px !important; 46 | margin: 0 auto !important; 47 | /* makes it centered */ 48 | clear: both !important; 49 | } 50 | 51 | .content { 52 | max-width: 600px; 53 | margin: 0 auto; 54 | display: block; 55 | padding: 20px; 56 | } 57 | 58 | /* ------------------------------------- 59 | HEADER, FOOTER, MAIN 60 | ------------------------------------- */ 61 | .main { 62 | background-color: #fff; 63 | border: 1px solid #e9e9e9; 64 | border-radius: 3px; 65 | } 66 | 67 | .content-wrap { 68 | padding: 20px; 69 | } 70 | 71 | .content-block { 72 | padding: 0 0 20px; 73 | } 74 | 75 | .header { 76 | width: 100%; 77 | margin-bottom: 20px; 78 | } 79 | 80 | .footer { 81 | width: 100%; 82 | clear: both; 83 | color: #999; 84 | padding: 20px; 85 | } 86 | .footer p, .footer a, .footer td { 87 | color: #999; 88 | font-size: 12px; 89 | } 90 | 91 | /* ------------------------------------- 92 | TYPOGRAPHY 93 | ------------------------------------- */ 94 | h1, h2, h3 { 95 | font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 96 | color: #000; 97 | margin: 40px 0 0; 98 | line-height: 1.2em; 99 | font-weight: 400; 100 | } 101 | 102 | h1 { 103 | font-size: 32px; 104 | font-weight: 500; 105 | /* 1.2em * 32px = 38.4px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */ 106 | /*line-height: 38px;*/ 107 | } 108 | 109 | h2 { 110 | font-size: 24px; 111 | /* 1.2em * 24px = 28.8px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */ 112 | /*line-height: 29px;*/ 113 | } 114 | 115 | h3 { 116 | font-size: 18px; 117 | /* 1.2em * 18px = 21.6px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */ 118 | /*line-height: 22px;*/ 119 | } 120 | 121 | h4 { 122 | font-size: 14px; 123 | font-weight: 600; 124 | } 125 | 126 | p, ul, ol { 127 | margin-bottom: 10px; 128 | font-weight: normal; 129 | } 130 | p li, ul li, ol li { 131 | margin-left: 5px; 132 | list-style-position: inside; 133 | } 134 | 135 | /* ------------------------------------- 136 | LINKS & BUTTONS 137 | ------------------------------------- */ 138 | a { 139 | color: #348eda; 140 | text-decoration: underline; 141 | } 142 | 143 | .btn-primary { 144 | text-decoration: none; 145 | color: #FFF; 146 | background-color: #348eda; 147 | border: solid #348eda; 148 | border-width: 10px 20px; 149 | line-height: 2em; 150 | /* 2em * 14px = 28px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */ 151 | /*line-height: 28px;*/ 152 | font-weight: bold; 153 | text-align: center; 154 | cursor: pointer; 155 | display: inline-block; 156 | border-radius: 5px; 157 | text-transform: capitalize; 158 | } 159 | 160 | /* ------------------------------------- 161 | OTHER STYLES THAT MIGHT BE USEFUL 162 | ------------------------------------- */ 163 | .last { 164 | margin-bottom: 0; 165 | } 166 | 167 | .first { 168 | margin-top: 0; 169 | } 170 | 171 | .aligncenter { 172 | text-align: center; 173 | } 174 | 175 | .alignright { 176 | text-align: right; 177 | } 178 | 179 | .alignleft { 180 | text-align: left; 181 | } 182 | 183 | .clear { 184 | clear: both; 185 | } 186 | 187 | /* ------------------------------------- 188 | ALERTS 189 | Change the class depending on warning email, good email or bad email 190 | ------------------------------------- */ 191 | .alert { 192 | font-size: 24px; 193 | color: #fff; 194 | font-weight: 500; 195 | padding: 20px; 196 | text-align: center; 197 | border-radius: 3px 3px 0 0; 198 | } 199 | .alert a { 200 | color: #fff; 201 | text-decoration: none; 202 | font-weight: 500; 203 | font-size: 16px; 204 | } 205 | .alert.alert-warning { 206 | background-color: #FF9F00; 207 | } 208 | .alert.alert-bad { 209 | background-color: #D0021B; 210 | } 211 | .alert.alert-good { 212 | background-color: #68B90F; 213 | } 214 | p.code pre { 215 | margin-top: 5px; 216 | padding: 5px; 217 | background: #eee; 218 | font-size: 12px; 219 | font-family: Menlo,Monaco,Consolas,"Courier New",monospace; 220 | line-height: 120%; 221 | white-space: pre; 222 | } 223 | 224 | /* ------------------------------------- 225 | INVOICE 226 | Styles for the billing table 227 | ------------------------------------- */ 228 | .invoice { 229 | margin: 40px auto; 230 | text-align: left; 231 | width: 80%; 232 | } 233 | .invoice td { 234 | padding: 5px 0; 235 | } 236 | .invoice .invoice-items { 237 | width: 100%; 238 | } 239 | .invoice .invoice-items td { 240 | border-top: #eee 1px solid; 241 | } 242 | .invoice .invoice-items .total td { 243 | border-top: 2px solid #333; 244 | border-bottom: 2px solid #333; 245 | font-weight: 700; 246 | } 247 | 248 | /* ------------------------------------- 249 | RESPONSIVE AND MOBILE FRIENDLY STYLES 250 | ------------------------------------- */ 251 | @media only screen and (max-width: 640px) { 252 | body { 253 | padding: 0 !important; 254 | } 255 | 256 | h1, h2, h3, h4 { 257 | font-weight: 800 !important; 258 | margin: 20px 0 5px !important; 259 | } 260 | 261 | h1 { 262 | font-size: 22px !important; 263 | } 264 | 265 | h2 { 266 | font-size: 18px !important; 267 | } 268 | 269 | h3 { 270 | font-size: 16px !important; 271 | } 272 | 273 | .container { 274 | padding: 0 !important; 275 | width: 100% !important; 276 | } 277 | 278 | .content { 279 | padding: 0 !important; 280 | } 281 | 282 | .content-wrap { 283 | padding: 10px !important; 284 | } 285 | 286 | .invoice { 287 | width: 100% !important; 288 | } 289 | } 290 | 291 | /*# sourceMappingURL=styles.css.map */ 292 | -------------------------------------------------------------------------------- /frontend/lib/auth-ok.js: -------------------------------------------------------------------------------- 1 | 2 | var get_user = require('../lib/get-user'); 3 | 4 | function auth_ok(req, job) { 5 | if (!req.isAuthenticated()) { return false; } 6 | 7 | var user = get_user(req); 8 | if (!user) { return false; } 9 | 10 | return user.user == job.email; 11 | } 12 | 13 | module.exports = auth_ok; 14 | -------------------------------------------------------------------------------- /frontend/lib/check-token.js: -------------------------------------------------------------------------------- 1 | 2 | function check_token(client, req, res, callback) { 3 | 4 | var token = (req.headers.authorization || '').split(' '); 5 | 6 | if (! req.params.email || token.length != 2 || token[0] != 'token' || 7 | token[1] == '') { 8 | res.set('Content-Type', 'application/json; charset=utf-8') 9 | .status(400) 10 | .end(JSON.stringify({ 11 | "result": "error", 12 | "message": "Authorization failed, email or token is missing." })); 13 | callback('No email or token'); 14 | return; 15 | } 16 | 17 | client.get(req.params.email, function(err, dbtoken) { 18 | if (err || token[1] != dbtoken) { 19 | res.set('Content-Type', 'application/json; charset=utf-8') 20 | .status(401) 21 | .end(JSON.stringify({ 22 | "result": "error", 23 | "message": "Email address not validated" 24 | })); 25 | callback('Email not validated'); 26 | return; 27 | } 28 | callback(null, req.params.email); 29 | return; 30 | }); 31 | } 32 | 33 | module.exports = check_token; 34 | -------------------------------------------------------------------------------- /frontend/lib/create-job.js: -------------------------------------------------------------------------------- 1 | 2 | var get_package_data = require('../lib/get-package-data'); 3 | var get_image = require('../lib/get-image'); 4 | var r = require('rhub-node'); 5 | 6 | function create_job(req, callback) { 7 | 8 | var baseurl = process.env.RHUB_BUILDER_URL || 9 | req.protocol + '://' + req.get('host'); 10 | var url = baseurl + "/file/" + req.file.filename; 11 | var logUrl = '/status/log/' + req.file.originalname + '-' + 12 | req.file.filename; 13 | 14 | var re_filename = new RegExp( 15 | '^' + 16 | r.valid_package_name + 17 | '_' + 18 | r.valid_package_version + 19 | '[.]tar[.]gz$'); 20 | 21 | if (! req.body['build-package'] && 22 | ! re_filename.test(req.file.originalname)) { 23 | return callback( 24 | "This does not look like an R package. " + 25 | "Did you build it using 'R CMD build'?" 26 | ) 27 | } 28 | 29 | var job = { 30 | 'buildId': req.file.originalname + '-' + req.file.filename, 31 | 'package': req.file.originalname, 32 | 'filename': req.file.filename, 33 | 'url': url, 34 | 'size': req.file.size, 35 | 'email': null, 36 | 'logUrl': logUrl, 37 | 'submitted': new Date().toISOString(), 38 | 'builder': baseurl 39 | }; 40 | 41 | // Get the image 42 | get_image(req.body.platform, function(err, platform) { 43 | if (err) { return callback(err); } 44 | job.platform = platform.name; 45 | job.image = platform["docker-image"]; 46 | job.ostype = platform["os-type"]; 47 | job.rversion = platform.rversion; 48 | job.platforminfo = platform; 49 | 50 | // Build options 51 | job.options = { }; 52 | job.options.build = !! req.body['build-package']; 53 | 54 | // Fill in the maintainer, package name and version 55 | var filename = __dirname + '/../uploads/' + job.filename; 56 | get_package_data(filename, function(err, data) { 57 | if (err) { return callback(err); } 58 | job.email = data.maint; 59 | job.pkg = data.Package; 60 | job.version = data.Version; 61 | if (req.body['alternative-email']) { 62 | job.email = req.body['alternative-email']; 63 | } 64 | if (!job.email) { return(callback("Cannot find 'Maintainer'")); } 65 | if (!job.pkg) { return(callback("Cannot find package name")); } 66 | if (!job.version) { return(callback("Cannot find package version")); } 67 | callback(null, job); 68 | }) 69 | }); 70 | } 71 | 72 | module.exports = create_job; 73 | -------------------------------------------------------------------------------- /frontend/lib/email-notification.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var mustache = require('mustache'); 4 | var pretty_ms = require('pretty-ms'); 5 | var send_email = require('../lib/send-email'); 6 | 7 | var RHUB_BUILDER_URL = process.env.RHUB_BUILDER_URL || 8 | 'http://127.0.0.1:3000'; 9 | var RHUB_BUILDER_EXTERNAL_URL = process.env.RHUB_BUILDER_EXTERNAL_URL || 10 | RHUB_BUILDER_URL; 11 | var RHUB_ARTIFACTS_URL = process.env.RHUB_ARTIFACTS_URL || 12 | 'https://artifacts.r-hub.io/'; 13 | var RHUB_EMAIL_FROM = process.env.RHUB_EMAIL_FROM || 14 | '"r-hub builder" '; 15 | 16 | function email_notification(build, callback) { 17 | 18 | fs.readFile( 19 | './data/email.txt', 20 | 'utf8', 21 | function(err, text_body) { 22 | if (err) { return callback(err); } 23 | 24 | fs.readFile( 25 | './data/email-inlined.html', 26 | 'utf8', 27 | function(err, html_body) { 28 | if (err) { return callback(err); } 29 | 30 | email_with_template( 31 | build, 32 | text_body, 33 | html_body, 34 | callback 35 | ); 36 | } 37 | ); 38 | 39 | } 40 | ); 41 | } 42 | 43 | function email_with_template(build, text_body, html_body, callback) { 44 | 45 | var email = build.email; 46 | var subject = build.package + ' ' + build.version + ': ' + 47 | build.status.toUpperCase(); 48 | 49 | var backgrounds = { 50 | 'ok': '#68B90F', 51 | 'note': '#FF9F00', 52 | 'warning': '#FF9F00', 53 | 'error': '#D0021B', 54 | 'preperror': '#D0021B' 55 | }; 56 | 57 | var submitted = pretty_ms( 58 | new Date() - new Date(build.submitted), 59 | { verbose: true } 60 | ) + ' ago'; 61 | 62 | var lcstatus = build.status.toLowerCase(); 63 | 64 | var dict = { 65 | 'id': build.id, 66 | 'title': subject, 67 | 'header': subject, 68 | 'result': build.result.status, 69 | 'buildTime': pretty_ms(build.build_time, { verbose: true }), 70 | 'submitted': submitted, 71 | 'logHtml': RHUB_BUILDER_EXTERNAL_URL + '/status/' + build.id, 72 | 'logText': RHUB_BUILDER_EXTERNAL_URL + '/status/original/' + build.id, 73 | 'artifactLink': RHUB_ARTIFACTS_URL + '/' + build.id, 74 | 'anyErrors': build.result.errors.length > 0, 75 | 'errors': build.result.errors, 76 | 'anyWarnings': build.result.warnings.length > 0, 77 | 'warnings': build.result.warnings, 78 | 'anyNotes': build.result.notes.length > 0, 79 | 'notes': build.result.notes, 80 | 'platform': build.platform.description, 81 | 'bgIsRed': lcstatus == 'error' || lcstatus == 'preperror' || 82 | lcstatus == "aborted", 83 | 'bgIsOrange': lcstatus == 'warning' || lcstatus == 'note', 84 | 'bgIsGreen': lcstatus == 'ok' 85 | }; 86 | 87 | var mail = { 88 | from: RHUB_EMAIL_FROM, 89 | to: email, 90 | subject: subject, 91 | text: mustache.render(text_body, dict), 92 | html: mustache.render(html_body, dict) 93 | }; 94 | 95 | send_email(mail, function(error, info) { 96 | if (error) { 97 | console.log(error); 98 | return callback(error); 99 | } 100 | console.log('Message sent: ' + info.response); 101 | callback(null, info.response); 102 | }); 103 | } 104 | 105 | module.exports = email_notification; 106 | -------------------------------------------------------------------------------- /frontend/lib/filter-log.js: -------------------------------------------------------------------------------- 1 | 2 | var stream = require('stream'); 3 | var util = require('util'); 4 | var left_pad = require('left-pad'); 5 | 6 | // node v0.10+ use native Transform, else polyfill 7 | var Transform = stream.Transform || 8 | require('readable-stream').Transform; 9 | 10 | function RHubLogFilter(options) { 11 | // allow use without new 12 | if (!(this instanceof RHubLogFilter)) { 13 | return new RHubLogFilter(options); 14 | } 15 | 16 | // init Transform 17 | Transform.call(this, options); 18 | } 19 | util.inherits(RHubLogFilter, Transform); 20 | 21 | RHubLogFilter.prototype._transform = function (chunk, enc, cb) { 22 | 23 | // Line numbers 24 | if (!this.line_number) this.line_number = 1; 25 | var line = this.line_number; 26 | this.line_number += 1; 27 | var sp_line = left_pad(line, 4); 28 | 29 | chunk = chunk.toString(); 30 | 31 | // Are we printing input or output? 32 | if (!this.output_mode) { 33 | this.output_mode = 'header'; 34 | this.push( 35 | '

Preparing

' + 36 | '
' 37 | ); 38 | } 39 | 40 | if (/echo >>>>>=====/.test(chunk)) { 41 | return cb(); 42 | 43 | } else if (/^>>>>>=====/.test(chunk)) { 44 | chunk = chunk.replace(/^>>>>>=====* ?/, ''); 45 | if (this.output_mode != 'header') { this.push('
'); } 46 | chunk = '

' + chunk + 47 | '

'; 48 | this.output_mode = 'header'; 49 | 50 | } else { 51 | if (/^\++R-HUB-R-HUB-R-HUB/.test(chunk)) { 52 | chunk = chunk.replace(/^\++R-HUB-R-HUB-R-HUB/, ""); 53 | if (this.output_mode != 'input') { 54 | if (this.output_mode == 'output') this.push('
'); 55 | this.push('
'); 56 | this.output_mode = 'input'; 57 | } 58 | 59 | } else { 60 | chunk = '#> ' + chunk; 61 | if (this.output_mode != 'output') { 62 | if (this.output_mode == 'input') this.push('
'); 63 | this.push('
'); 64 | this.output_mode = 'output'; 65 | } 66 | } 67 | 68 | chunk = '

' + 69 | '' + 70 | '' + 71 | '' + sp_line + '' + 72 | chunk + '

'; 73 | } 74 | 75 | this.push(chunk + "\n"); 76 | cb(); 77 | }; 78 | 79 | function SimpleLogFilter(options) { 80 | // allow use without new 81 | if (!(this instanceof SimpleLogFilter)) { 82 | return new SimpleLogFilter(options); 83 | } 84 | 85 | // init Transform 86 | Transform.call(this, options); 87 | } 88 | util.inherits(SimpleLogFilter, Transform); 89 | 90 | SimpleLogFilter.prototype._transform = function (chunk, enc, cb) { 91 | 92 | chunk = chunk.toString(); 93 | 94 | if (/echo >>>>>=====/.test(chunk)) { 95 | return cb(); 96 | 97 | } else if (/^>>>>>=====/.test(chunk)) { 98 | chunk = chunk.replace(/^>>>>>=====* ?/, ''); 99 | 100 | } else { 101 | if (/^\++R-HUB-R-HUB-R-HUB/.test(chunk)) { 102 | chunk = chunk.replace(/^\++R-HUB-R-HUB-R-HUB/, ""); 103 | 104 | } else { 105 | chunk = '#> ' + chunk; 106 | } 107 | } 108 | 109 | this.push(chunk + "\n"); 110 | cb(); 111 | }; 112 | 113 | module.exports = RHubLogFilter; 114 | module.exports.SimpleLogFilter = SimpleLogFilter; 115 | -------------------------------------------------------------------------------- /frontend/lib/get-image.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | function get_image(platform, callback) { 4 | fs.readFile( 5 | './public/data/platforms.json', 6 | 'utf8', 7 | function(err, json) { 8 | if (err) { return callback(err); } 9 | 10 | var platforms = JSON.parse(json); 11 | 12 | // Default platform, this should be ideally declared in 13 | // platforms.json. Anyway. 14 | if (platform === null || platform === undefined) { 15 | return callback( 16 | null, 17 | platforms[0] 18 | ); 19 | } 20 | 21 | var image = null; 22 | for (i = 0; i < platforms.length; i++) { 23 | if (platforms[i].name === platform) { 24 | return callback( 25 | null, 26 | platforms[i] 27 | ); 28 | } 29 | } 30 | callback('Unknown platform'); 31 | } 32 | ); 33 | } 34 | 35 | module.exports = get_image; 36 | -------------------------------------------------------------------------------- /frontend/lib/get-package-data.js: -------------------------------------------------------------------------------- 1 | var tar = require('tar-stream'); 2 | var gunzip = require('gunzip-maybe'); 3 | var fs = require('fs'); 4 | var desc = require('rdesc-parser'); 5 | 6 | function get_package_data(tarball, callback) { 7 | 8 | var extract = tar.extract(); 9 | var done = false; 10 | 11 | extract.on('entry', function(header, stream, tarcb) { 12 | if (!done && header.name.match(/^[^\/]+\/DESCRIPTION$/)) { 13 | done = true; 14 | stream.setEncoding('utf8'); 15 | desc(stream, function(err, description) { 16 | if (err) { return callback(err); } 17 | var maint = description.Maintainer; 18 | if (!!maint) { 19 | description.maint = maint.replace(/^.*<(.*)>.*$/, "$1"); 20 | } 21 | 22 | callback(null, description); 23 | extract.destroy(); 24 | }) 25 | } else { 26 | tarcb() 27 | } 28 | 29 | stream.resume(); 30 | }); 31 | 32 | extract.on('finish', function() { 33 | if (!done) { callback('No DESCRIPTION file'); } 34 | }) 35 | 36 | extract.on('error', function() { 37 | callback('Cannot get DESCRIPTION data, not an R package?'); 38 | extract.destroy(); 39 | }) 40 | 41 | fs.createReadStream(tarball) 42 | .pipe(gunzip()) 43 | .pipe(extract); 44 | } 45 | 46 | module.exports = get_package_data; 47 | -------------------------------------------------------------------------------- /frontend/lib/get-pkg-from-filename.js: -------------------------------------------------------------------------------- 1 | 2 | var r = require('rhub-node'); 3 | 4 | function get_pkg_from_filename(filename) { 5 | var re = new RegExp('^(' + r.valid_package_name + ')_'); 6 | var match = re.exec(filename); 7 | if (match) { return match[1]; } else { return null; } 8 | } 9 | 10 | module.exports = get_pkg_from_filename; 11 | -------------------------------------------------------------------------------- /frontend/lib/get-user.js: -------------------------------------------------------------------------------- 1 | 2 | function get_user(req) { 3 | try { 4 | if (! req.isAuthenticated()) { return null; } 5 | 6 | var user = req.session.passport.user; 7 | 8 | if (user.startsWith('github:')) { 9 | user = user.replace(/^github:/, ''); 10 | user = JSON.parse(user); 11 | return { 'via': 'GitHub', 'user': user[0].value }; 12 | 13 | } else if (user.startsWith('email:')) { 14 | user = user.replace(/^email:/, ''); 15 | return { 'via': 'Email verification', 'user': user }; 16 | 17 | } else { 18 | // Unknown login type, how did this happen? 19 | return null; 20 | } 21 | } 22 | catch(err) { 23 | return null; 24 | } 25 | } 26 | 27 | module.exports = get_user; 28 | -------------------------------------------------------------------------------- /frontend/lib/get-version-from-filename.js: -------------------------------------------------------------------------------- 1 | 2 | var r = require('rhub-node'); 3 | 4 | function get_version_from_filename(filename) { 5 | var re = new RegExp( 6 | '^' + r.valid_package_name + 7 | '_(' + r.valid_package_version + ')[.]tar[.]gz$' 8 | ); 9 | var match = re.exec(filename); 10 | if (match) { return match[1]; } else { return null; } 11 | } 12 | 13 | module.exports = get_version_from_filename; 14 | -------------------------------------------------------------------------------- /frontend/lib/job-to-db.js: -------------------------------------------------------------------------------- 1 | 2 | var urls = require('../lib/urls.js'); 3 | var got = require('got'); 4 | var url = require('url'); 5 | 6 | function job_to_db(job, callback) { 7 | var doc = { 8 | id: job.buildId, 9 | group: job.group, 10 | email: job.email, 11 | package: job.pkg, 12 | version: job.version, 13 | submitted: job.submitted, 14 | platform: job.platforminfo, 15 | scripts: job.scripts || null, 16 | checkArgs: job.checkArgs, 17 | envVars: job.envVars, 18 | 19 | // to be updated 20 | status: 'created', 21 | 22 | // to be filled later 23 | started: null, 24 | build_time: null, 25 | builder_machine: null, 26 | 27 | // these are parsed from the output logs 28 | result: null, 29 | check_output: null, 30 | preperror_log: null 31 | }; 32 | 33 | var fullurl = urls.logdb + '/' + doc.id; 34 | var _url = url.parse(fullurl); 35 | var dburl = _url.protocol + '//' + _url.host + _url.path; 36 | 37 | got.put( 38 | dburl, 39 | { body: JSON.stringify(doc), auth: _url.auth }, 40 | function(err, reponse) { 41 | callback(err); 42 | }); 43 | } 44 | 45 | module.exports = job_to_db; 46 | -------------------------------------------------------------------------------- /frontend/lib/mail-token.js: -------------------------------------------------------------------------------- 1 | 2 | var multiline = require('multiline'); 3 | var send_email = require('../lib/send-email'); 4 | 5 | var text_body = multiline(function() { /* 6 | Dear R package developer! 7 | 8 | This is your verification code for your R-hub check submission: 9 | 10 | ${code} 11 | 12 | If you haven't submitted anything to R-hub, please ignore this email. 13 | 14 | Have questions, suggestions or want to report a bug? 15 | Please file an issue ticket at GitHub at 16 | https://github.com/r-hub/rhub/issues Thank You for using the R-hub builder. 17 | 18 | Sincerely, 19 | The R-hub team 20 | 21 | */ }); 22 | 23 | function mail_token(email, token, callback) { 24 | 25 | var mail = { 26 | from: '"R-hub builder" ', 27 | to: email, 28 | subject: 'R-hub check email validation', 29 | text: text_body.replace("${code}", token) 30 | }; 31 | 32 | send_email(mail, function(error, info) { 33 | if (error) { 34 | console.log(error); 35 | return callback(error); 36 | } 37 | console.log('Message sent: ' + info.response); 38 | callback(null, info.response); 39 | }); 40 | } 41 | 42 | module.exports = mail_token; 43 | -------------------------------------------------------------------------------- /frontend/lib/mail-verification-code.js: -------------------------------------------------------------------------------- 1 | 2 | var multiline = require('multiline'); 3 | var uuid = require('uuid'); 4 | var urls = require('../lib/urls'); 5 | var send_email = require('../lib/send-email'); 6 | 7 | var redis = require('redis'); 8 | var cli_client = null; 9 | 10 | var text_body = multiline(function() { /* 11 | Dear R package developer! 12 | 13 | This is your verification code for your R-hub builder upload: ${code} 14 | 15 | If you haven't uploaded anything to R-hub, please ignore this email. 16 | 17 | Have questions, suggestions or want to report a bug? 18 | Please file an issue ticket at GitHub at 19 | https://github.com/r-hub/rhub/issues Thank You for using the R-hub builder. 20 | 21 | Sincerely, 22 | The R-hub team 23 | 24 | */ }); 25 | 26 | function mail_verification_code(req, callback) { 27 | 28 | var code = uuid.v4().substring(0, 6); 29 | 30 | if (cli_client === null) { 31 | cli_client = redis.createClient(urls.validemail_url); 32 | } 33 | 34 | cli_client.set(req.session.job.email + '-pending', code, function(err) { 35 | if (err) { 36 | // Could not add code to CLI validation DB, but nevertheless 37 | // we continue, because it will still work with the web app 38 | console.log("cannot add validation code to CLI app DB") 39 | } 40 | 41 | req.session.verification = code; 42 | 43 | var mail = { 44 | from: '"R-hub builder" ', 45 | to: req.session.job.email, 46 | subject: 'R-hub builder verification', 47 | text: text_body.replace("${code}", code) 48 | }; 49 | 50 | send_email(mail, function(error, info) { 51 | if (error) { 52 | console.log(error); 53 | return callback(error); 54 | } 55 | console.log('Message sent: ' + info.response); 56 | callback(null, info.response); 57 | }); 58 | }) 59 | } 60 | 61 | module.exports = mail_verification_code; 62 | -------------------------------------------------------------------------------- /frontend/lib/parse-rhub-log.js: -------------------------------------------------------------------------------- 1 | 2 | // Parse a full R-hub log. 3 | // Returns two things: 4 | // * result: the check result, a JSON dict with entries: 5 | // - status (preperror, error, warning, note, ok) 6 | // - notes 7 | // - warnings 8 | // - errors 9 | // * check_output: the output of R CMD check. This is null if the build 10 | // fails before getting to runing R CMD check. 11 | // * preperror_log: the last 100 lines of the build, if preperror 12 | 13 | function parse_rhub_log(parser, log) { 14 | 15 | if (!parser || parser == "rcmdcheck") { 16 | return parse_rcmdcheck_log(log); 17 | } else if (parser == "sanitizers") { 18 | return parse_sanitizers_log(log); 19 | } else if (parser == "rchk") { 20 | return parse_rchk_log(log); 21 | } else { 22 | 23 | var last_lines = log.split(/\n/) 24 | .slice(-100) 25 | .join('\n'); 26 | 27 | return { 28 | 'result': { 29 | 'status': 'parseerror', 30 | 'notes': [], 31 | 'warnings': [], 32 | 'errors': [] }, 33 | 'check_output': log, 34 | 'preperror_log': last_lines 35 | }; 36 | } 37 | } 38 | 39 | function parse_preperror_log(log) { 40 | var last_lines = log.split(/\n/) 41 | .slice(-100) 42 | .join('\n'); 43 | 44 | return { 45 | result: { 46 | 'status': 'preperror', 47 | 'notes': [], 48 | 'warnings': [], 49 | 'errors': [] }, 50 | check_output: null, 51 | preperror_log: last_lines 52 | }; 53 | } 54 | 55 | // R CMD check logs ------------------------------------------------------ 56 | 57 | function parse_rcmdcheck_log(log) { 58 | 59 | var check_start_regex = />>>>>======* Running R CMD check/; 60 | var check_done_regex = /\n[*] DONE[\n ]/; 61 | 62 | // If we don't have this in the log, then we never got to checking 63 | if (check_start_regex.test(log) && check_done_regex.test(log)) { 64 | 65 | // Not sure why it would appear multiple times, but we handle 66 | // it nervertheless 67 | var checklog = log.replace(/\r\n/g, '\n') 68 | .split(check_start_regex) 69 | .slice(1) 70 | .join('\n'); 71 | 72 | return parse_rcmdcheck_log2(checklog); 73 | 74 | } else { 75 | return parse_preperror_log(log); 76 | } 77 | } 78 | 79 | function parse_rcmdcheck_log2(log) { 80 | 81 | var last_lines = log.split(/\n/) 82 | .slice(-100) 83 | .join('\n'); 84 | 85 | // Drop stuff after the final DONE 86 | var mylog = log.replace(/\n[*] DONE\n\n?(.|\n)*$/, '\n* DONE\n\n'); 87 | 88 | var pieces = mylog 89 | .replace(/^NOTE: There was .*\n$/, "") 90 | .replace(/^WARNING: There was .*\n$/, "") 91 | .split("\n* "); 92 | 93 | function filter(pattern) { 94 | var re = new RegExp(pattern); 95 | return pieces.filter( 96 | function(x) { return re.test(x); } 97 | ); 98 | } 99 | 100 | var errors = filter(' ERROR(\n|$)'); 101 | var warnings = filter(' WARNING(\n|$)'); 102 | var notes = filter(' NOTE(\n|$)'); 103 | var result; 104 | 105 | if (errors.length) { 106 | result = 'error' 107 | } else if (warnings.length) { 108 | result = 'warning' 109 | } else if (notes.length) { 110 | result = 'note' 111 | } else { 112 | result = 'ok' 113 | } 114 | 115 | return { 116 | 'result': { 117 | 'status': result, 118 | 'notes': notes, 119 | 'warnings': warnings, 120 | 'errors': errors }, 121 | 'check_output': mylog, 122 | 'preperror_log': last_lines 123 | }; 124 | } 125 | 126 | // Saniters log ---------------------------------------------------------- 127 | 128 | function parse_sanitizers_log(log) { 129 | 130 | var check_start_regex = />>>>>======* Running R CMD check/; 131 | var check_done_regex = />>>>>======* Done with R CMD check/; 132 | 133 | if (check_start_regex.test(log) && check_done_regex.test(log)) { 134 | var mylog = log.replace(/\r\n/g, '\n') 135 | .split(check_start_regex) 136 | .slice(1) 137 | .join('\n') 138 | .split(check_done_regex)[0]; 139 | 140 | return parse_sanitizers_log2(mylog); 141 | } else { 142 | return parse_preperror_log(log); 143 | } 144 | } 145 | 146 | function parse_sanitizers_log2(log) { 147 | 148 | var last_lines = log.split(/\n/) 149 | .slice(-100) 150 | .join('\n'); 151 | var status; 152 | 153 | if (/runtime error:/.test(log)) { 154 | return { 155 | 'result': { 156 | 'status': 'error', 157 | 'notes': [], 158 | 'warnings': [], 159 | 'errors': log }, 160 | 'check_output': log, 161 | 'preperror_log': last_lines 162 | }; 163 | 164 | } else { 165 | return { 166 | 'result': { 167 | 'status': 'ok', 168 | 'notes': [], 169 | 'warnings': [], 170 | 'errors': [] }, 171 | 'check_output': log, 172 | 'preperror_log': null 173 | }; 174 | } 175 | } 176 | 177 | // rchk log -------------------------------------------------------------- 178 | 179 | function parse_rchk_log(log) { 180 | 181 | var check_start_regex = />>>>>======* Running R CMD check/; 182 | var check_done_regex = />>>>>======* Done with R CMD check/; 183 | 184 | if (check_start_regex.test(log) && check_done_regex.test(log)) { 185 | var mylog = log.replace(/\r\n/g, '\n') 186 | .split(check_start_regex) 187 | .slice(1) 188 | .join('\n') 189 | .split(check_done_regex)[0]; 190 | 191 | return parse_rchk_log2(mylog); 192 | } else { 193 | return parse_preperror_log(log); 194 | } 195 | } 196 | 197 | function parse_rchk_log2(log) { 198 | 199 | var last_lines = log.split(/\n/) 200 | .slice(-100) 201 | .join('\n'); 202 | var status; 203 | 204 | if (/error/i.test(log) || /warning/i.test(log) || 205 | /\n\nFunction/.test(log)) { 206 | return { 207 | 'result': { 208 | 'status': 'error', 209 | 'notes': [], 210 | 'warnings': [], 211 | 'errors': log }, 212 | 'check_output': log, 213 | 'preperror_log': last_lines 214 | }; 215 | 216 | } else { 217 | return { 218 | 'result': { 219 | 'status': 'ok', 220 | 'notes': [], 221 | 'warnings': [], 222 | 'errors': [] }, 223 | 'check_output': log, 224 | 'preperror_log': null 225 | }; 226 | } 227 | } 228 | 229 | module.exports = parse_rhub_log; 230 | -------------------------------------------------------------------------------- /frontend/lib/queue-job.js: -------------------------------------------------------------------------------- 1 | var queue_this = require('../lib/queue-this'); 2 | var job_to_db = require('../lib/job-to-db'); 3 | 4 | function queue_job(job) { 5 | job_to_db(job, function(err) { 6 | // Report error, but continue, anyway. We'll try to add it later 7 | if (err) { console.log("Cannot add new job to DB") } 8 | 9 | // Add it to the queue as well 10 | queue_this('job', job ); 11 | }); 12 | } 13 | 14 | module.exports = queue_job; 15 | -------------------------------------------------------------------------------- /frontend/lib/queue-this.js: -------------------------------------------------------------------------------- 1 | var amqp = require('amqplib'); 2 | var when = require('when'); 3 | 4 | var broker_url = process.env.RABBITMQ_URL || 5 | 'amqp://q.rhub.me:5672/rhub'; 6 | 7 | function queue_this(q, item) { 8 | 9 | amqp.connect(broker_url).then(function(conn) { 10 | return when(conn.createChannel().then(function(ch) { 11 | var ok = ch.assertQueue(q, { durable: true }); 12 | 13 | item.added_at = new Date().toISOString(); 14 | 15 | return ok.then(function() { 16 | var msg = JSON.stringify(item); 17 | ch.sendToQueue(q, new Buffer(msg), { deliveryMode: true }); 18 | return ch.close(); 19 | }); 20 | })).ensure(function() { conn.close(); }); 21 | }).then(null, console.warn); 22 | } 23 | 24 | module.exports = queue_this; 25 | -------------------------------------------------------------------------------- /frontend/lib/re-status.js: -------------------------------------------------------------------------------- 1 | 2 | var r = require('rhub-node'); 3 | 4 | var re_status = 5 | '(' + 6 | '(' + '[-a-zA-Z0-9\._]+' + ')' + 7 | '[.]tar[.]gz' + '-' + 8 | '([a-zA-Z0-9]+)' + 9 | ')'; 10 | 11 | module.exports = re_status; 12 | -------------------------------------------------------------------------------- /frontend/lib/send-email-mailgun.js: -------------------------------------------------------------------------------- 1 | 2 | var urls = require('../lib/urls'); 3 | 4 | function send_email_mailgun(mail, callback) { 5 | var mailgun = require('mailgun-js')( 6 | { apiKey: urls.mailgun_api_key, domain: urls.mailgun_domain }); 7 | mailgun.messages().send(mail, callback); 8 | } 9 | 10 | module.exports = send_email_mailgun; 11 | -------------------------------------------------------------------------------- /frontend/lib/send-email-smtp.js: -------------------------------------------------------------------------------- 1 | 2 | var urls = require('../lib/urls'); 3 | var nodemailer = require('nodemailer'); 4 | 5 | function send_email_smtp(mail, callback) { 6 | if (! urls.smtp_server) { return callback("No mail server"); } 7 | 8 | var config = { 9 | host: urls.smtp_server, 10 | port: urls.smtp_port, 11 | secure: urls.smtp_tls_required 12 | } 13 | 14 | if (!! urls.smtp_username || !! urls.smtp_password) { 15 | config.auth = { 16 | user: urls.smtp_username || '', 17 | pass: urls.smtp_password || '' }; 18 | } 19 | 20 | var transporter = nodemailer.createTransport(config); 21 | transporter.sendMail(mail, callback); 22 | } 23 | 24 | module.exports = send_email_smtp; 25 | -------------------------------------------------------------------------------- /frontend/lib/send-email.js: -------------------------------------------------------------------------------- 1 | 2 | var urls = require('../lib/urls'); 3 | 4 | function send_email(mail, callback) { 5 | if (urls.email_mode === 'mailgun') { 6 | var send_email_mailgun = require('../lib/send-email-mailgun'); 7 | send_email_mailgun(mail, callback); 8 | } else { 9 | var send_email_smtp = require('../lib/send-email-smtp'); 10 | send_email_smtp(mail, callback); 11 | } 12 | } 13 | 14 | module.exports = send_email; 15 | -------------------------------------------------------------------------------- /frontend/lib/update-log.js: -------------------------------------------------------------------------------- 1 | 2 | var got = require('got'); 3 | var urls = require('../lib/urls'); 4 | var jenkins = require('jenkins')( { 5 | baseUrl: urls.jenkins, crumbIssuer: true }); 6 | var parse_rhub_log = require('../lib/parse-rhub-log'); 7 | 8 | function update_log(id, state, time, body, callback) { 9 | // Need to get the build metadata and the output from Jenkins 10 | jenkins.build.get(id, 'lastBuild', function(err, data) { 11 | if (err) { return callback("Cannot find Jenkins job"); } 12 | 13 | // We cannot use data.duration, because at this point the build 14 | // is still running, and it is set to 0 15 | body.build_time = new Date() - new Date(body.started); 16 | body.builder_machine = data.builtOn; 17 | 18 | jenkins.build.log('Jobs/' + id, 'lastBuild', function(err, log) { 19 | if (err) { return callback("Cannot get Jenkins log"); } 20 | var parsed = parse_rhub_log(body.platform["output-parser"], log); 21 | body.result = parsed.result; 22 | body.check_output = parsed.check_output; 23 | body.preperror_log = parsed.preperror_log; 24 | 25 | if (data.result == "ABORTED") { 26 | body.status = "aborted"; 27 | body.result.status = "aborted"; // for consistency 28 | } else { 29 | body.status = parsed.result.status; 30 | } 31 | 32 | callback(null, body); 33 | }); 34 | }); 35 | } 36 | 37 | module.exports = update_log; 38 | -------------------------------------------------------------------------------- /frontend/lib/urls.js: -------------------------------------------------------------------------------- 1 | 2 | require('dotenv').config(); 3 | 4 | var urls = { 5 | 'jenkins': process.env.JENKINS_URL || 'http://jenkins.url', 6 | 'validemail_url': 7 | process.env.DOKKU_REDIS_GREEN_URL || 8 | process.env.DOKKU_REDIS_PURPLE_URL || 9 | process.env.REDIS_EMAIL_URL, 10 | 'logdb': process.env.LOGDB_URL || 'http://logdb.url', 11 | 12 | // Email settings 13 | 'email_mode': process.env.RHUB_EMAIL_MODE || 'mailgun', 14 | 'mailgun_api_key': process.env.MAILGUN_API_KEY || 'key', 15 | 'mailgun_domain': process.env.MAILGUN_DOMAIN || 'rhub.io', 16 | 'smtp_server': process.env.RHUB_SMTP_SERVER, 17 | 'smtp_username': process.env.RHUB_SMTP_USERNAME, 18 | 'smtp_password': process.env.RHUB_SMTP_PASSWORD, 19 | 'smtp_port': process.env.RHUB_SMTP_PORT || 25, 20 | // TLS required by default, set to 'false' (w/o quotes) to disable 21 | 'smtp_tls_required': ! (process.env.RHUB_SMTP_TLS_REQUIRED === 'false') 22 | }; 23 | 24 | module.exports = urls; 25 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rhub-frontend", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "engines": { 9 | "node": "8.12.x" 10 | }, 11 | "dependencies": { 12 | "amqplib": "^0.4.0", 13 | "array-flatten": "^2.1.1", 14 | "async": "^2.1.2", 15 | "body-parser": "^1.18.3", 16 | "byline": "^4.2.1", 17 | "connect-gzip-static": "^1.0.0", 18 | "connect-redis": "^3.0.2", 19 | "cookie-parser": "~1.3.5", 20 | "debug": "^4.0.0", 21 | "dotenv": "^5.0.1", 22 | "express": "^4.16.4", 23 | "express-session": "^1.13.0", 24 | "got": "^5.6.0", 25 | "gunzip-maybe": "^1.3.1", 26 | "hogan-express": "^0.5.2", 27 | "hogan.js": "^3.0.2", 28 | "is-array": "^1.0.1", 29 | "jenkins": "^0.19.0", 30 | "jenkins-log-stream": "^2.0.0", 31 | "left-pad": "^1.1.0", 32 | "mailgun-js": "^0.20.0", 33 | "morgan": "^1.9.1", 34 | "multer": "^1.1.0", 35 | "multiline": "^1.0.2", 36 | "mustache": "^2.2.1", 37 | "nano": "^6.2.0", 38 | "nodemailer": "^4.6.8", 39 | "passport": "^0.3.2", 40 | "passport-github": "^1.1.0", 41 | "pretty-ms": "^2.1.0", 42 | "rdesc-parser": "^3.0.1", 43 | "redis": "^2.6.2", 44 | "rhub-node": "^1.1.0", 45 | "serve-favicon": "^2.5.0", 46 | "tar-stream": "^1.3.2", 47 | "url": "^0.11.0", 48 | "uuid": "^2.0.2", 49 | "when": "^3.7.5" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/public/.well-known/acme-challenge/bQS91jPgESpdE1y_YFko_5yLMgn8KGEdpGcEuDIE6Ng: -------------------------------------------------------------------------------- 1 | bQS91jPgESpdE1y_YFko_5yLMgn8KGEdpGcEuDIE6Ng.Bw0n2-AR3k8OMKN0b_LhuUVpJlkG2O-O4mLtsuCdkno 2 | -------------------------------------------------------------------------------- /frontend/public/.well-known/acme-challenge/qZ8CC8EFZACYXcGVFpHxR4-w-iQWorI2vnx30rx_AD4: -------------------------------------------------------------------------------- 1 | qZ8CC8EFZACYXcGVFpHxR4-w-iQWorI2vnx30rx_AD4.Bw0n2-AR3k8OMKN0b_LhuUVpJlkG2O-O4mLtsuCdkno 2 | -------------------------------------------------------------------------------- /frontend/public/.well-known/acme-challenge/wyl61bg_fpnNdT2MAzovERs1ltM1FK0ySKh_t2Vl0DE: -------------------------------------------------------------------------------- 1 | wyl61bg_fpnNdT2MAzovERs1ltM1FK0ySKh_t2Vl0DE.Bw0n2-AR3k8OMKN0b_LhuUVpJlkG2O-O4mLtsuCdkno 2 | -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/public/data/platforms.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "debian-gcc-devel", 4 | "description": "Debian Linux, R-devel, GCC", 5 | "cran-name": "r-devel-linux-x86_64-debian-gcc", 6 | "rversion": "r-devel", 7 | "os-type": "Linux", 8 | "cpu-type": "x86_64", 9 | "os-info": "Debian GNU/Linux testing", 10 | "compilers": "gcc version 8.3.0 (Debian 8.3.0-5)", 11 | "docker-image": "debian-gcc-devel", 12 | "sysreqs-platform": "linux-x86_64-debian-gcc", 13 | "categories": ["Linux"], 14 | "node-labels": ["linux"], 15 | "queue": "linux" 16 | }, 17 | 18 | { 19 | "name": "debian-gcc-devel-nold", 20 | "description": "Debian Linux, R-devel, GCC, no long double", 21 | "cran-name": null, 22 | "rversion": "r-devel", 23 | "os-type": "Linux", 24 | "cpu-type": "x86_64", 25 | "os-info": "Debian GNU/Linux testing", 26 | "compilers": "gcc version 8.3.0 (Debian 8.3.0-5)", 27 | "docker-image": "debian-gcc-devel-nold", 28 | "sysreqs-platform": "linux-x86_64-debian-gcc", 29 | "categories": ["Linux"], 30 | "node-labels": ["linux"], 31 | "queue": "linux" 32 | }, 33 | 34 | { 35 | "name": "debian-gcc-release", 36 | "description": "Debian Linux, R-release, GCC", 37 | "cran-name": "r-release-linux-x86_64", 38 | "rversion": "r-release", 39 | "os-type": "Linux", 40 | "cpu-type": "x86_64", 41 | "os-info": "Debian GNU/Linux testing", 42 | "compilers": "gcc version 8.3.0 (Debian 8.3.0-5)", 43 | "docker-image": "debian-gcc-release", 44 | "sysreqs-platform": "linux-x86_64-debian-gcc", 45 | "categories": ["Linux"], 46 | "node-labels": ["linux"], 47 | "queue": "linux" 48 | }, 49 | 50 | { 51 | "name": "debian-gcc-patched", 52 | "description": "Debian Linux, R-patched, GCC", 53 | "cran-name": "r-patched-linux-x86_64", 54 | "rversion": "r-patched", 55 | "os-type": "Linux", 56 | "cpu-type": "x86_64", 57 | "os-info": "Debian GNU/Linux testing", 58 | "compilers": "gcc version 8.3.0 (Debian 8.3.0-5)", 59 | "docker-image": "debian-gcc-patched", 60 | "sysreqs-platform": "linux-x86_64-debian-gcc", 61 | "categories": ["Linux"], 62 | "node-labels": ["linux"], 63 | "queue": "linux" 64 | }, 65 | 66 | { 67 | "name": "debian-clang-devel", 68 | "description": "Debian Linux, R-devel, clang, ISO-8859-15 locale", 69 | "cran-name": "r-devel-linux-x86_64-debian-clang", 70 | "rversion": "r-devel", 71 | "os-type": "Linux", 72 | "cpu-type": "x86_64", 73 | "os-info": "Debian GNU/Linux testing", 74 | "compilers": "clang version 7.0.1-8 (tags/RELEASE_701/final)", 75 | "docker-image": "debian-clang-devel", 76 | "sysreqs-platform": "linux-x86_64-debian-clang", 77 | "categories": ["Linux"], 78 | "node-labels": ["linux"], 79 | "queue": "linux" 80 | }, 81 | 82 | { 83 | "name": "fedora-gcc-devel", 84 | "description": "Fedora Linux, R-devel, GCC", 85 | "cran-name": "r-devel-linux-x86_64-fedora-gcc", 86 | "rversion": "r-devel", 87 | "os-type": "Linux", 88 | "cpu-type": "x86_64", 89 | "os-info": "Fedora 24", 90 | "compilers": "GCC 6.1.1", 91 | "docker-image": "fedora-gcc-devel", 92 | "sysreqs-platform": "linux-x86_64-fedora-gcc", 93 | "categories": ["Linux"], 94 | "node-labels": ["linux"], 95 | "queue": "linux" 96 | }, 97 | 98 | { 99 | "name": "fedora-clang-devel", 100 | "description": "Fedora Linux, R-devel, clang, gfortran", 101 | "cran-name": "r-devel-linux-x86_64-fedora-clang", 102 | "rversion": "r-devel", 103 | "os-type": "Linux", 104 | "cpu-type": "x86_64", 105 | "os-info": "Fedora 24", 106 | "compilers": "clang version 3.8.0; GNU Fortran 6.1.1", 107 | "docker-image": "fedora-clang-devel", 108 | "sysreqs-platform": "linux-x86_64-fedora-clang", 109 | "categories": ["Linux"], 110 | "node-labels": ["linux"], 111 | "queue": "linux" 112 | }, 113 | 114 | { 115 | "name": "ubuntu-gcc-devel", 116 | "description": "Ubuntu Linux 16.04 LTS, R-devel, GCC", 117 | "cran-name": null, 118 | "rversion": "r-devel", 119 | "os-type": "Linux", 120 | "cpu-type": "x86_64", 121 | "os-info": "Ubuntu 16.04 LTS", 122 | "compilers": "GCC 5.3.1", 123 | "docker-image": "ubuntu-gcc-devel", 124 | "sysreqs-platform": "linux-x86_64-ubuntu-gcc", 125 | "categories": ["Linux"], 126 | "node-labels": ["linux"], 127 | "queue": "linux" 128 | }, 129 | 130 | { 131 | "name": "ubuntu-gcc-release", 132 | "description": "Ubuntu Linux 16.04 LTS, R-release, GCC", 133 | "cran-name": null, 134 | "rversion": "r-release", 135 | "os-type": "Linux", 136 | "cpu-type": "x86_64", 137 | "os-info": "Ubuntu 16.04 LTS", 138 | "compilers": "GCC 5.3.1", 139 | "docker-image": "ubuntu-gcc-release", 140 | "sysreqs-platform": "linux-x86_64-ubuntu-gcc", 141 | "categories": ["Linux"], 142 | "node-labels": ["linux"], 143 | "queue": "linux" 144 | }, 145 | 146 | { 147 | "name": "linux-x86_64-centos6-epel", 148 | "description": "CentOS 6, stock R from EPEL", 149 | "cran-name": null, 150 | "rversion": "r-release", 151 | "os-type": "Linux", 152 | "cpu-type": "x86_64", 153 | "os-info": "CentOS 6", 154 | "compilers": "GCC 4.4.x", 155 | "docker-image": "centos6-epel", 156 | "sysreqs-platform": "linux-x86_64-centos6-epel", 157 | "categories": ["Linux"], 158 | "node-labels": ["linux"], 159 | "queue": "linux" 160 | }, 161 | 162 | { 163 | "name": "linux-x86_64-centos6-epel-rdt", 164 | "description": "CentOS 6 with Redhat Developer Toolset, R from EPEL", 165 | "cran-name": null, 166 | "rversion": "r-release", 167 | "os-type": "Linux", 168 | "cpu-type": "x86_64", 169 | "os-info": "CentOS 6", 170 | "compilers": "GCC 5.2.1", 171 | "docker-image": "centos6-epel-rdt", 172 | "sysreqs-platform": "linux-x86_64-centos6-epel", 173 | "categories": ["Linux"], 174 | "node-labels": ["linux"], 175 | "queue": "linux" 176 | }, 177 | 178 | { 179 | "name": "linux-x86_64-rocker-gcc-san", 180 | "description": "Debian Linux, R-devel, GCC ASAN/UBSAN", 181 | "cran-name": null, 182 | "rversion": "r-devel", 183 | "os-type": "Linux", 184 | "cpu-type": "x86_64", 185 | "os-info": "Debian GNU/Linux testing", 186 | "compilers": "GCC 5.4.0 (Debian 5.4.0-4)", 187 | "docker-image": "rocker-gcc-san", 188 | "sysreqs-platform": "linux-x86_64-debian-gcc", 189 | "output-parser": "sanitizers", 190 | "categories": ["Checks for compiled code"], 191 | "node-labels": ["linux"], 192 | "queue": "linux" 193 | }, 194 | 195 | { 196 | "name": "windows-x86_64-oldrel", 197 | "description": "Windows Server 2008 R2 SP1, R-oldrel, 32/64 bit", 198 | "cran-name": "r-oldrel-windows-ix86+x86_64", 199 | "rversion": "r-oldrel", 200 | "os-type": "Windows", 201 | "cpu-type": "x86_64", 202 | "os-info": "Windows Server 2008 R2 SP1", 203 | "compilers": "GCC 4.9.3, Rtools 3.5", 204 | "docker-image": null, 205 | "sysreqs-platform": "windows-2008", 206 | "categories": ["Windows"], 207 | "node-labels": ["windows", "rtools3"], 208 | "queue": "rtools3" 209 | }, 210 | 211 | { 212 | "name": "windows-x86_64-release", 213 | "description": "Windows Server 2008 R2 SP1, R-release, 32/64 bit", 214 | "cran-name": "r-release-windows-ix86+x86_64", 215 | "rversion": "r-release", 216 | "os-type": "Windows", 217 | "cpu-type": "x86_64", 218 | "os-info": "Windows Server 2008 R2 SP1", 219 | "compilers": "GCC 8.3.0, Rtools 4.0", 220 | "docker-image": null, 221 | "sysreqs-platform": "windows-2008", 222 | "categories": ["Windows"], 223 | "node-labels": ["windows", "rtools4"], 224 | "queue": "rtools4" 225 | }, 226 | 227 | { 228 | "name": "windows-x86_64-patched", 229 | "description": "Windows Server 2008 R2 SP1, R-patched, 32/64 bit", 230 | "cran-name": null, 231 | "rversion": "r-patched", 232 | "os-type": "Windows", 233 | "cpu-type": "x86_64", 234 | "os-info": "Windows Server 2008 R2 SP1", 235 | "compilers": "GCC 8.3.0, Rtools 4.0", 236 | "docker-image": null, 237 | "sysreqs-platform": "windows-2008", 238 | "categories": ["Windows"], 239 | "node-labels": ["windows", "rtools4"], 240 | "queue": "rtools4" 241 | }, 242 | 243 | { 244 | "name": "windows-x86_64-devel", 245 | "description": "Windows Server 2008 R2 SP1, R-devel, 32/64 bit", 246 | "cran-name": "r-devel-windows-ix86+x86_64", 247 | "rversion": "r-devel", 248 | "os-type": "Windows", 249 | "cpu-type": "x86_64", 250 | "os-info": "Windows Server 2008 R2 SP1", 251 | "compilers": "GCC 8.3.0, Rtools 4.0", 252 | "docker-image": null, 253 | "sysreqs-platform": "windows-2008", 254 | "categories": ["Windows"], 255 | "node-labels": ["windows", "rtools4"], 256 | "queue": "rtools4" 257 | }, 258 | 259 | { 260 | "name": "macos-elcapitan-release", 261 | "description": "macOS 10.11 El Capitan, R-release (experimental)", 262 | "cran-name": "r-release-osx-x86_64", 263 | "rversion": "r-release", 264 | "os-type": "macOS", 265 | "macos-version": "elcapitan", 266 | "cpu-type": "x86_64", 267 | "os-info": "Mac OS X 10.11.6 15G1217", 268 | "compilers": "Apple LLVM version 8.0 (clang-800.0.42.1); GNU Fortran 4.2.3", 269 | "docker-image": null, 270 | "sysreqs-platform": "osx-x86_64-clang", 271 | "categories": ["macOS"], 272 | "node-labels": ["macos", "elcapitan", "r-release"], 273 | "queue": "elcapitan" 274 | }, 275 | 276 | { 277 | "name": "ubuntu-rchk", 278 | "description": "Ubuntu Linux 16.04 LTS, R-devel with rchk", 279 | "cran-name": null, 280 | "rversion": "r-devel", 281 | "os-type": "Linux", 282 | "cpu-type": "x86_64", 283 | "os-info": "Ubuntu 16.04 LTS", 284 | "compilers": "clang 3.8.0-2ubuntu4", 285 | "docker-image": "ubuntu-rchk", 286 | "sysreqs-platform": "linux-x86_64-ubuntu-gcc", 287 | "output-parser": "rchk", 288 | "categories": ["Checks for compiled code"], 289 | "node-labels": ["linux"], 290 | "queue": "linux" 291 | }, 292 | ] 293 | -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/javascripts/vendor/bootstrap.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/javascripts/vendor/bootstrap.min.js.gz -------------------------------------------------------------------------------- /frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/logo.png -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/public/rhub-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/rhub-header.png -------------------------------------------------------------------------------- /frontend/public/rhub-square-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/rhub-square-white.png -------------------------------------------------------------------------------- /frontend/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /frontend/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/public/stylesheets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/stylesheets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /frontend/public/stylesheets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/stylesheets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /frontend/public/stylesheets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/stylesheets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /frontend/public/stylesheets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/stylesheets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /frontend/public/stylesheets/logstyle.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/stylesheets/logstyle.css -------------------------------------------------------------------------------- /frontend/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | 10 | .btn-file { 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | .btn-file input[type=file] { 15 | position: absolute; 16 | top: 0; 17 | right: 0; 18 | min-width: 100%; 19 | min-height: 100%; 20 | font-size: 100px; 21 | text-align: right; 22 | filter: alpha(opacity=0); 23 | opacity: 0; 24 | outline: none; 25 | background: white; 26 | cursor: inherit; 27 | display: block; 28 | } 29 | 30 | input[readonly] { 31 | background-color: white !important; 32 | cursor: text !important; 33 | } 34 | 35 | .terms { 36 | font-size: 140%; 37 | } 38 | 39 | .content { 40 | margin-top: 30px; 41 | } 42 | 43 | .fluidIframe { 44 | position: relative; 45 | /* proportion value to aspect ratio 16:9 (9 / 16 = 0.5625 or 56.25%) */ 46 | padding-bottom: 56.25%; 47 | padding-top: 30px; 48 | height: 0; 49 | overflow: hidden; 50 | } 51 | 52 | .fluidIframe iframe { 53 | position: absolute; 54 | top: 0; 55 | left: 0; 56 | width: 100%; 57 | height: 100%; 58 | border: 0; 59 | } 60 | 61 | 62 | .log-block { 63 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 64 | padding-left: 0px; 65 | line-height: 130%; 66 | } 67 | 68 | .log-h1 { 69 | font-weight: bold; 70 | font-size: 120%; 71 | margin-top: 20px; 72 | margin-bottom: 10px; 73 | } 74 | 75 | .log-output { 76 | color: #aaa; 77 | padding-top: 10px; 78 | padding-bottom: 10px; 79 | margin-bottom: 10px; 80 | } 81 | 82 | .log-input { 83 | margin-bottom: 10px; 84 | color: #006; 85 | } 86 | 87 | .log-lineno { 88 | color: rgba(0,0,0,0.4); 89 | white-space: pre; 90 | min-width: 30px; 91 | padding-right: 10px; 92 | padding-left: 0px; 93 | text-decoration: none; 94 | } 95 | 96 | .log-lineno:hover { 97 | color: rgba(0,0,0,0.7); 98 | } 99 | 100 | .log-line { 101 | margin-top: 0px; 102 | margin-bottom: 0px; 103 | padding-left: 58px; 104 | text-indent: -58px; 105 | } 106 | 107 | .verify-gh { 108 | text-align: center; 109 | } 110 | 111 | .huge-gh { 112 | font-size: 196px; 113 | } 114 | 115 | .large-gh { 116 | font-size: 64px; 117 | } 118 | 119 | .well-verify-1 { 120 | min-height: 150px; 121 | } 122 | 123 | .well-verify-2 { 124 | min-height: 300px; 125 | } 126 | 127 | .vcenter { 128 | display: table-cell; 129 | vertical-align: middle; 130 | } 131 | 132 | .vcenter-outer { 133 | display: table; 134 | } 135 | -------------------------------------------------------------------------------- /frontend/public/stylesheets/vendor/bootstrap-theme.min.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/stylesheets/vendor/bootstrap-theme.min.css.gz -------------------------------------------------------------------------------- /frontend/public/stylesheets/vendor/bootstrap.min.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-hub/rhub-server/9fd222423803e87de80fb964a796d62f514a7769/frontend/public/stylesheets/vendor/bootstrap.min.css.gz -------------------------------------------------------------------------------- /frontend/routes/build.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require('express'); 3 | var router = express.Router(); 4 | var re_status = require('../lib/re-status'); 5 | var urls = require('../lib/urls'); 6 | var db = require('nano')(urls.logdb); 7 | var update_log = require('../lib/update-log'); 8 | var email_notification = require('../lib/email-notification'); 9 | 10 | function keep(x) { return '(' + x + ')'; } 11 | 12 | var re_state = '[-A-Za-z]+'; 13 | var re_time = '[-a-zA-Z0-9:\\.]+'; 14 | var re = '^/' + keep(re_state) + '/' + re_status + '/' + 15 | keep(re_time) + '$'; 16 | 17 | // The job reports its state here. E.g. 18 | // /in-progress/gpg_0.1.tar.gz-<...>/2016-10-10T02:59:00Z 19 | // /success/igraph-1.0.1.tar.gz-<...>/2016-10-10T07:38:39Z 20 | // Possible state values: 21 | // created, in-progress, success, failure, aborted, unstable, not_built 22 | // These are the Jenkins build status values, except for in-progress 23 | // and created. 24 | // 25 | // This should be ideally a POST request, but it is just easier to make 26 | // a GET from the post-build Groovy script. 27 | 28 | router.get(new RegExp(re), function(req, res) { 29 | var state = req.params[0]; 30 | var id = req.params[1]; // re_status is captured already 31 | var time = req.params[4]; 32 | 33 | function handle_error(err) { 34 | if (err) { 35 | error(500, "Cannot update build " + id); 36 | } else { 37 | ok(id + " updated"); 38 | } 39 | } 40 | 41 | function error(status, message) { 42 | var msg = { "status": "error", "message": message }; 43 | res.set(status) 44 | .end(JSON.stringify(msg)); 45 | } 46 | 47 | function ok(message) { 48 | res.set(200) 49 | .end(JSON.stringify({ "status": "ok" })); 50 | } 51 | 52 | res.set('Content-Type', 'application/json'); 53 | 54 | db.get(id, function(err, body) { 55 | if (err) { return error(res, 404, "Cannot find build " + id); } 56 | 57 | // We handle the simple 'start' update here 58 | if (state == "in-progress") { 59 | body.status = state; 60 | // This is to use the same machine's clock 61 | body.started = new Date(); 62 | db.insert(body, function(err) { 63 | return handle_error(err); 64 | }); 65 | 66 | // More complicated updates need getting the Jenkins job and log, 67 | // we handle these separately 68 | } else { 69 | update_log(id, state, time, body, function(err, body2) { 70 | if (err) { return handle_error(err); } 71 | db.insert(body2, function(err) { 72 | if (err) { return handle_error(err); } 73 | email_notification(body2, function(err) { 74 | return handle_error(err); 75 | }); 76 | }); 77 | }); 78 | } 79 | }); 80 | }); 81 | 82 | module.exports = router; 83 | -------------------------------------------------------------------------------- /frontend/routes/check.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | router.get('/', function(req, res) { 5 | res.set('Content-Type', 'text/plain') 6 | .send('I am alive') 7 | .end(); 8 | }) 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /frontend/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var get_user = require('../lib/get-user'); 4 | var fs = require('fs'); 5 | var flatten = require('array-flatten'); 6 | 7 | /* GET home page. */ 8 | router.get('/', function(req, res, next) { 9 | res.render('index', { 'advanced': false, 'user': get_user(req) }); 10 | }); 11 | 12 | router.get('/advanced', function(req, res, next) { 13 | fs.readFile( 14 | './public/data/platforms.json', 15 | 'utf8', 16 | function(err, json) { 17 | if (err) { console.log(err); throw(err); } 18 | var platforms = JSON.parse(json); 19 | var user = null; 20 | 21 | platform_groups = get_platform_groups(platforms); 22 | 23 | res.render('index', { 24 | 'advanced': true, 25 | platforms: platforms, 26 | groups: platform_groups, 27 | user: get_user(req) 28 | }); 29 | } 30 | ); 31 | }); 32 | 33 | router.get('/about.html', function(req, res) { 34 | res.render('about', { 'user': get_user(req) }); 35 | }); 36 | 37 | router.get('/terms.html', function(req, res) { 38 | res.render('terms', { 'user': get_user(req) }); 39 | }); 40 | 41 | function unique(value, index, self) { 42 | return self.indexOf(value) === index; 43 | } 44 | 45 | function get_platform_groups(platforms) { 46 | var groups = platforms 47 | .map(function(x) { return x.categories; }); 48 | // This just happens to be OK 49 | var groupnames = flatten(groups).sort().reverse(); 50 | 51 | return groupnames.filter(unique) 52 | .map(function(g) { 53 | return { "name": g, 54 | "value": platforms.filter(function(p) { 55 | return p.categories.indexOf(g) != -1 56 | }) 57 | }; 58 | }); 59 | } 60 | 61 | module.exports = router; 62 | -------------------------------------------------------------------------------- /frontend/routes/job.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var multer = require('multer'); 4 | var rhub = require('rhub-node'); 5 | var create_job = require('../lib/create-job'); 6 | var queue_job = require('../lib/queue-job'); 7 | var get_user = require('../lib/get-user'); 8 | var auth_ok = require('../lib/auth-ok'); 9 | var uploader = multer({ 10 | dest: __dirname + '/../uploads/' 11 | }) 12 | 13 | // POST request uploads the package 14 | // 15 | // If the session is authenticated, and the email 16 | // addresses match, we queue the job. 17 | // 18 | // Otherwise we store the job in the session and 19 | // go to authenticate. The authentication 20 | // will call back to /job again, but with a GET request. 21 | // The job data is still in the session. If the emails 22 | // match we queue the job, otherwise an error page is 23 | // returned. 24 | 25 | router.post( 26 | '/', 27 | uploader.single('package'), 28 | function(req, res, next) { 29 | create_job(req, function(err, job) { 30 | if (err) { 31 | res.render( 32 | "badpackage", 33 | { 'error': err, user: get_user(req) } 34 | ); 35 | } else { 36 | if (auth_ok(req, job)) { 37 | queue_job(job); 38 | job.user = get_user(req); 39 | res.redirect("/status/" + job.buildId); 40 | } else { 41 | req.session.job = job; 42 | req.session.job.user = get_user(req) 43 | res.render('verify', req.session.job); 44 | } 45 | } 46 | }) 47 | } 48 | ); 49 | 50 | router.get( 51 | '/', 52 | function(req, res, next) { 53 | if (auth_ok(req, req.session.job)) { 54 | queue_job(req.session.job); 55 | req.session.job.user = get_user(req); 56 | res.redirect("/status/" + req.session.job.buildId); 57 | } else { 58 | res.render( 59 | 'badpackage', 60 | { 'error': 'cannot verify email address', 61 | 'package': req.session.job['package'], 62 | 'job': req.session.job, 63 | 'user': get_user(req) 64 | } 65 | ); 66 | } 67 | } 68 | ); 69 | 70 | module.exports = router; 71 | -------------------------------------------------------------------------------- /frontend/routes/login.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require('express'); 3 | var router = express.Router(); 4 | var passport = require('passport'); 5 | var mail_verification_code = require('../lib/mail-verification-code'); 6 | var get_user = require('../lib/get-user'); 7 | 8 | var queue_job = require('../lib/queue-job'); 9 | 10 | router.get('/login', function(req, res) { 11 | req.session.job.user = get_user(req); 12 | res.render("verify", req.session.job); 13 | }); 14 | 15 | router.get('/login/github', passport.authenticate('github')); 16 | 17 | router.get( 18 | '/login/github/callback', 19 | function(req, res, next) { 20 | passport.authenticate( 21 | 'github', 22 | { successReturnToOrRedirect: '/job', failureRedirect: '/job' } 23 | )(req, res, next); 24 | } 25 | ); 26 | 27 | router.get( 28 | '/logout', 29 | function(req, res) { 30 | req.logout(); 31 | res.redirect('/'); 32 | } 33 | ); 34 | 35 | router.get( 36 | '/login/sendcode', 37 | function(req, res) { 38 | if (!req.session || !req.session.job) { 39 | return res.status(404) 40 | .send("No package upload?"); 41 | } 42 | mail_verification_code(req, function(err, done) { 43 | if (err) { 44 | res.status(404) 45 | .send("Cannot send email"); 46 | } else { 47 | res.send("OK"); 48 | } 49 | }) 50 | } 51 | ); 52 | 53 | router.post( 54 | '/login/submitcode', 55 | function(req, res) { 56 | if (!req.session || !req.session.job) { res.redirect('/'); } 57 | var submitted = req.body.code; 58 | if (submitted == req.session.verification) { 59 | queue_job(req.session.job); 60 | if (!req.session.passport) { req.session.passport = { }; } 61 | req.session.passport.user = 'email:' + req.session.job.email; 62 | req.session.job.user = get_user(req); 63 | res.redirect("/status/" + req.session.job.buildId); 64 | delete req.session.job; 65 | } else { 66 | res.render('verify', req.session.job); 67 | } 68 | } 69 | ); 70 | 71 | module.exports = router; 72 | -------------------------------------------------------------------------------- /frontend/routes/status.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require('express'); 3 | var router = express.Router(); 4 | var JenkinsLogStream = require('jenkins-log-stream'); 5 | var get_user = require('../lib/get-user'); 6 | var byline = require('byline'); 7 | var LogFilter = require('../lib/filter-log'); 8 | var SimpleLogFilter = require('../lib/filter-log').SimpleLogFilter; 9 | var re_status = require('../lib/re-status'); 10 | const prettyMs = require('pretty-ms'); 11 | var urls = require('../lib/urls'); 12 | 13 | // This is the main page. The actual log will be in an IFrame 14 | 15 | router.get(new RegExp('^/' + re_status + '$'), function(req, res) { 16 | var name = req.params[0]; 17 | 18 | var iframeUrl = req.originalUrl.replace('/status', '/status/log'); 19 | 20 | res.render( 21 | 'status', 22 | { 'buildId': name, 23 | 'user': get_user(req), 24 | 'logUrl': iframeUrl 25 | } 26 | ); 27 | }); 28 | 29 | function stream(log, res, filter) { 30 | var log_by_line = byline(log); 31 | if (filter === undefined) { filter = new LogFilter(); } 32 | 33 | var st = byline(log); 34 | 35 | if (filter !== null) { st = st.pipe(filter); } 36 | 37 | st.pipe(res) 38 | .on("error", function(e) { res.set(404).end("No such job"); }); 39 | } 40 | 41 | router.get(new RegExp('^/log/' + re_status + '$'), function(req, res) { 42 | var name = req.params[0]; 43 | var log = new JenkinsLogStream({ 44 | 'baseUrl': urls.jenkins, 45 | 'job': 'Jobs/job/' + name 46 | }); 47 | 48 | res.header("Content-Type", "text/html; charset=utf-8") 49 | .write( 50 | "" + 51 | "" + 52 | "" + 53 | "" 54 | ); 55 | 56 | stream(log, res); 57 | }); 58 | 59 | router.get(new RegExp('^/embedded/' + re_status + '$'), function(req, res) { 60 | var name = req.params[0]; 61 | var log = new JenkinsLogStream({ 62 | 'baseUrl': urls.jenkins, 63 | 'job': 'Jobs/job/' + name 64 | }); 65 | 66 | res.header("Content-Type", "text/html; charset=utf-8") 67 | 68 | stream(log, res); 69 | }); 70 | 71 | router.get(new RegExp('^/raw/' + re_status + '$'), function(req, res) { 72 | var name = req.params[0]; 73 | var log = new JenkinsLogStream({ 74 | 'baseUrl': urls.jenkins, 75 | 'job': 'Jobs/job/' + name 76 | }); 77 | var simpleLogFilter = new SimpleLogFilter(); 78 | 79 | res.header("Content-Type", "text/plain") 80 | stream(log, res, simpleLogFilter); 81 | }); 82 | 83 | router.get(new RegExp('^/original/' + re_status + '$'), function(req, res) { 84 | var name = req.params[0]; 85 | var log = new JenkinsLogStream({ 86 | 'baseUrl': urls.jenkins, 87 | 'job': 'Jobs/job/' + name 88 | }); 89 | 90 | res.header("Content-Type", "text/plain") 91 | stream(log, res, null); 92 | }); 93 | 94 | router.get(new RegExp('^/code/' + re_status + '$'), function(req, res) { 95 | var name = req.params[0]; 96 | 97 | var jenkins_url = urls.jenkins; 98 | var jenkins = require('jenkins'); 99 | var conn = jenkins({ baseUrl: jenkins_url, crumbIssuer: true }); 100 | 101 | var info = { 102 | 'status': 'preparing', 103 | 'submitted': 'moments ago', 104 | 'duration': '...' 105 | }; 106 | 107 | conn.build.get(name, 1, function(err, data) { 108 | res.set('Content-Type', 'application/json'); 109 | if (err) { 110 | res.status(404) 111 | .end(JSON.stringify({ 112 | "result": "error", 113 | "message": "Job not found" 114 | })); 115 | 116 | } else { 117 | info.duration = prettyMs(data.duration, { verbose: true }); 118 | if (data.building) { 119 | info.status = 'in progress' 120 | } else if (data.result == 'SUCCESS') { 121 | info.status = 'success' 122 | } else { 123 | info.status = 'error' 124 | } 125 | res.end(JSON.stringify(info)); 126 | } 127 | }); 128 | }); 129 | 130 | module.exports = router; 131 | -------------------------------------------------------------------------------- /frontend/views/about.hjs: -------------------------------------------------------------------------------- 1 | {{< layout }}{{$ content }} 2 | 3 |
4 |
5 | 8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | {{/ content }}{{/ layout }} 18 | -------------------------------------------------------------------------------- /frontend/views/aboutdiv.hjs: -------------------------------------------------------------------------------- 1 | 7 |

About the R-hub builder

8 |

The R-hub builder offers free R CMD Check as a service on different platforms. It is a project supported by the R Consortium.

9 |

Why use the R-hub builder?

10 |

The builder allows you to check your R package on several platforms, and R versions.

11 |

Moreover, as a side-effect it also allows you to build your R package, i.e. its binaries and the binaries for all (source) dependencies, on several platforms, and R versions.

12 |

How to use the R-hub builder?

13 |

You can use the R-hub builder either

14 | 22 |

You can see a live demo of both the website frontend and of the rhub R package in this video.

23 |

Please note that you can only verify your email address from GitHub if the address associated to your GitHub account listed as maintainer address in the DESCRIPTION of the package.

24 |

Website or package interface to the R-hub builder?

25 |

Advantages of using the rhub package over the website are that it allows your not leaving R, and that the R package offers shortcut functions such as rhub::check_for_cran(), as well as the listing of your recent and current builds, by email address or package.

26 |

A bug, question or feature request?

27 |

Your contributions are welcome. R-hub is developed entirely in the open in its own GitHub organization.

28 |

You can post your bug report/feature request/question belongs file as an issue in the repo of the R package.

29 |

You can also send an email to admin@r-hub.io.

30 | 31 | -------------------------------------------------------------------------------- /frontend/views/aboutdiv.md: -------------------------------------------------------------------------------- 1 | 7 | # About the R-hub builder 8 | 9 | The R-hub builder offers free R CMD Check as a service on different platforms. It is a project supported by the R Consortium. 10 | 11 | ## Why use the R-hub builder? 12 | 13 | The builder allows you to check your R package on several platforms, and R versions. 14 | 15 | Moreover, as a side-effect it also allows you to _build_ your R package, i.e. its binaries and the binaries for all (source) dependencies, on several platforms, and R versions. 16 | 17 | ## How to use the R-hub builder? 18 | 19 | You can use the R-hub builder either 20 | 21 | * via the website (where you are now!), from the [main page](https://builder.r-hub.io/) or [the advanced page](https://builder.r-hub.io/advanced). 22 | 23 | * via its API, in particular by using the [`rhub` package](https://r-hub.github.io/rhub/). 24 | 25 | You can see a live demo of both the website frontend and of the `rhub` R package in [this video](https://www.r-consortium.org/events/2016/10/11/r-hub-public-beta). 26 | 27 | Please note that you can only verify your email address from GitHub if the address associated to your GitHub account listed as maintainer address in the DESCRIPTION of the package. 28 | 29 | ### Website or package interface to the R-hub builder? 30 | 31 | Advantages of using the `rhub` package over the website are that it allows your not leaving R, and that the R package offers [shortcut functions](https://r-hub.github.io/rhub/reference/index.html#section-check-shortcuts) such as [`rhub::check_for_cran()`](https://r-hub.github.io/rhub/reference/check_for_cran.html), as well as the [listing of your recent and current builds](https://r-hub.github.io/rhub/reference/index.html#section-check-management), by email address or package. 32 | 33 | ## A bug, question or feature request? 34 | 35 | Your contributions are welcome. R-hub is developed entirely in the open [in its own GitHub organization](https://github.com/r-hub). 36 | 37 | You can post your bug report/feature request/question belongs file as an issue in [the repo of the R package](https://github.com/r-hub/rhub). 38 | 39 | You can also send an email to admin@r-hub.io. -------------------------------------------------------------------------------- /frontend/views/adv.hjs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

The R-hub builder

5 |
6 | 7 |
8 |
10 | 11 |
12 |
13 |

Select the R package to upload

14 |
15 |
16 | 17 | 18 | Select R package… 19 | 20 | 21 | 22 |
23 |
24 |
25 | 26 |
27 |

Alternative email address

28 |

29 | Help on email address 30 |

31 |
32 | 35 |
36 |
37 | 38 |
39 |

Check options

40 |

41 | For more options use the rhub R package 42 |

43 |
44 | 46 | 48 |
49 |

50 |
51 | 52 | 55 | 56 |
57 | 58 |
59 |

Select platform(s) to build on

60 |

61 | Help on platform choice 62 |

63 |
64 | {{# groups }} 65 |

{{ name }}

66 | {{# value }} 67 |
68 | 71 |
72 | {{/ value }} 73 | {{/ groups }} 74 |
75 |
76 | 77 |
78 | 79 |
80 | -------------------------------------------------------------------------------- /frontend/views/badpackage.hjs: -------------------------------------------------------------------------------- 1 | {{< layout}}{{$ content }} 2 |

3 | Thank you for submitting package {{ package }}. 4 | Unfortunately something went wrong. Please check that you uploaded a proper 5 | R package. 6 |

7 |

Error:

8 |
 9 | {{{ error }}}
10 | 
11 |

12 | {{/ content }}{{/ layout }} 13 | -------------------------------------------------------------------------------- /frontend/views/error.hjs: -------------------------------------------------------------------------------- 1 | {{< layout }}{{$ content }} 2 |

{{ message }}

3 |

{{ error.status }}

4 |
{{ error.stack }}
5 | {{/ content }}{{/ layout }} 6 | -------------------------------------------------------------------------------- /frontend/views/footer.hjs: -------------------------------------------------------------------------------- 1 | 2 |
3 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/views/header.hjs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | R-hub package builder 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{ title }} {{^ title }} R-hub builder {{/ title }} 38 | -------------------------------------------------------------------------------- /frontend/views/ie7notice.hjs: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /frontend/views/index.hjs: -------------------------------------------------------------------------------- 1 | {{< layout }}{{$ content }} 2 | 15 | {{/ content }}{{/ layout }} 16 | 17 | 19 | 20 | {{$ scripts }} 21 | 47 | {{/ scripts }} 48 | -------------------------------------------------------------------------------- /frontend/views/layout.hjs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{> header }}{{$ extraheader }}{{/ extraheader }} 4 | 5 | {{> ie7notice }} 6 | {{> navbar }} 7 |
{{$ content }}{{/ content }} 8 | {{> footer }} 9 | {{$ scripts }}{{/ scripts }} 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/views/navbar.hjs: -------------------------------------------------------------------------------- 1 | 2 | 47 | -------------------------------------------------------------------------------- /frontend/views/ok.hjs: -------------------------------------------------------------------------------- 1 | {{< layout}}{{$ content }} 2 | Thank you for submitting package {{ package }}. 3 | Once the package is checked, a notification email will be sent 4 | to the address of the maintainer: {{ email }}. 5 | {{/ content }}{{/ layout }} 6 | -------------------------------------------------------------------------------- /frontend/views/simple.hjs: -------------------------------------------------------------------------------- 1 |
2 |

The R-hub builder

3 |
4 | 5 | 10 | 11 |
13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 | 27 | 28 | 29 | 30 | 35 | 36 |
37 | 38 |
39 | 40 |

41 | 42 | 43 |

44 | 45 |
46 | 47 |
48 | Advanced 51 |
52 | 53 |
54 | 55 |
56 | -------------------------------------------------------------------------------- /frontend/views/status.hjs: -------------------------------------------------------------------------------- 1 | 2 | {{< layout}}{{$ content }} 3 | 4 |
5 | 6 |
7 |
8 |

9 | Status for build 10 | {{ buildId }} 11 |

12 |
13 |
14 |
15 | 16 | Build id: ...
17 | Package: ...
18 | Version: ...
19 | Email: ...
20 | Status: ...
21 | Platform: ...
22 | Submitted: ...
23 | Started: hang tight
24 | Duration: ???
25 |
26 |
27 |
28 | Waiting for the build to start... 29 |
30 |
31 |
32 | 33 |
34 | 35 | 58 | 59 | 119 | 120 | 126 | 127 | {{/ content }}{{/ layout }} 128 | -------------------------------------------------------------------------------- /frontend/views/terms.hjs: -------------------------------------------------------------------------------- 1 | {{< layout }}{{$ content }} 2 | 3 |
4 |
5 | 8 |
9 |
10 | 11 |

Introduction

12 | 13 |

The R-hub builder service is provided free of charge by the 14 | R consortium.

15 | 16 |

By uploading your R package, you agree to these terms and 17 | conditions.

18 | 19 |

Whereas there are currently no limits for R package 20 | submissions, we reserve the right to introduce rate limits 21 | for the number of packages uploaded per time period, 22 | in the future.

23 | 24 |

Cookies

25 | 26 |

This web site uses cookies to carry out the email address 27 | check, and to maintain a user session.

28 | 29 |

User tracking and data

30 | 31 |

We do not store any data about our users, apart from the 32 | session information. The session information is deleted 33 | once the user logs out.

34 | 35 |

We do not share the uploaded packages with anyone, and 36 | only store them temporarily, until the build and check 37 | process is completed.

38 | 39 |

We store the check results (the console output) for a 40 | short period of time, currently three days, for debugging 41 | purposes. We do not give out the check output to anyone, 42 | except for the package maintainer, as declared in the 43 | DESCRIPTION file of the package.

44 | 45 |

Disclaimer

46 | 47 |

You use this service at your own risk.

48 | 49 |

We are not responsible for the code your R package runs 50 | during the package build and check.

51 | 52 |
53 |
54 |
55 |
56 | 57 | {{/ content }}{{/ layout }} 58 | -------------------------------------------------------------------------------- /frontend/views/verify.hjs: -------------------------------------------------------------------------------- 1 | 2 | {{< layout}} 3 | 4 | {{$ extraheader }} 5 | 7 | {{/ extraheader }} 8 | 9 | {{$ content }} 10 | 11 | 18 | 19 |
20 | 21 |
22 |
23 |

24 | 25 | Package: {{ package }} 26 |

27 |
28 |
29 | 30 |
31 |
32 |

33 | 34 | Maintainer: {{ email }} 35 |

36 |
37 |
38 | 39 |
40 | 41 |
42 | 43 |
44 |

45 | 46 |

47 | 54 |
55 | 56 |
57 |
58 |

59 | – OR – 60 |

61 |
62 |
63 | 64 |
65 |

66 | 67 |

68 |
69 |
70 | 75 |
76 | 78 |
79 |
80 | 81 |
82 |
83 |
84 | 85 |
86 | 87 |
88 | 89 | 96 | 97 | {{/content}} 98 | 99 | {{$ scripts }} 100 | 102 | 103 | 119 | {{/ scripts }} 120 | 121 | {{/ layout }} 122 | -------------------------------------------------------------------------------- /jenkins.pass: -------------------------------------------------------------------------------- 1 | 44186ecbfec14 2 | -------------------------------------------------------------------------------- /jenkins/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM jenkins/jenkins:2.161 3 | 4 | ## Azure CLI, this is not always needed, but it does not hurt now... 5 | 6 | USER root 7 | 8 | RUN apt-get update 9 | RUN apt-get install -y apt-transport-https lsb-release \ 10 | software-properties-common dirmngr 11 | RUN echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | \ 12 | tee /etc/apt/sources.list.d/azure-cli.list 13 | RUN apt-key --keyring /etc/apt/trusted.gpg.d/Microsoft.gpg adv \ 14 | --keyserver packages.microsoft.com \ 15 | --recv-keys BC528686B50D79E339D3721CEB3E94ADBE1229CF 16 | RUN apt-get update && apt-get install -y azure-cli 17 | 18 | USER jenkins 19 | 20 | COPY plugins.txt /usr/share/jenkins/ref/plugins.txt 21 | 22 | RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt 23 | 24 | # Jenkins is fully configured, no configure wizard, please 25 | ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false 26 | RUN echo 2.146 > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state 27 | RUN echo 2.146 > /usr/share/jenkins/ref/jenkins.install.InstallUtil.lastExecVersion 28 | 29 | COPY default-user.groovy /usr/share/jenkins/ref/init.groovy.d/ 30 | COPY config.groovy /usr/share/jenkins/ref/init.groovy.d/ 31 | 32 | -------------------------------------------------------------------------------- /jenkins/config.groovy: -------------------------------------------------------------------------------- 1 | 2 | import jenkins.model.Jenkins 3 | import jenkins.model.JenkinsLocationConfiguration 4 | import jenkins.security.s2m.* 5 | 6 | def env = System.getenv() 7 | 8 | // Disable CLI remoting 9 | Jenkins.instance.getDescriptor("jenkins.CLI").get().setEnabled(false) 10 | 11 | // Set Jenkins root url 12 | jlc = JenkinsLocationConfiguration.get() 13 | jlc.setUrl(env.JENKINS_ROOT_URL) 14 | jlc.save() 15 | 16 | // Turn on agent to master security subsystem 17 | Jenkins.instance.injector.getInstance(AdminWhitelistRule.class) 18 | .setMasterKillSwitch(false); 19 | Jenkins.instance.save() 20 | 21 | // Enable CSRF Protection 22 | import hudson.security.csrf.DefaultCrumbIssuer 23 | import jenkins.model.Jenkins 24 | 25 | def instance = Jenkins.instance 26 | instance.setCrumbIssuer(new DefaultCrumbIssuer(true)) 27 | instance.save() 28 | 29 | // Quiet period 30 | instance = Jenkins.getInstance() 31 | instance.setQuietPeriod(0) 32 | 33 | // Env vars 34 | import hudson.EnvVars; 35 | import hudson.slaves.EnvironmentVariablesNodeProperty; 36 | import hudson.slaves.NodeProperty; 37 | import hudson.slaves.NodePropertyDescriptor; 38 | import hudson.util.DescribableList; 39 | import jenkins.model.Jenkins; 40 | 41 | public createGlobalEnvironmentVariables(String key, String value) { 42 | 43 | Jenkins instance = Jenkins.getInstance(); 44 | 45 | DescribableList, NodePropertyDescriptor> globalNodeProperties = 46 | instance.getGlobalNodeProperties(); 47 | List envVarsNodePropertyList = 48 | globalNodeProperties.getAll(EnvironmentVariablesNodeProperty.class); 49 | 50 | EnvironmentVariablesNodeProperty newEnvVarsNodeProperty = null; 51 | EnvVars envVars = null; 52 | 53 | if (envVarsNodePropertyList == null || envVarsNodePropertyList.size() == 0) { 54 | newEnvVarsNodeProperty = 55 | new hudson.slaves.EnvironmentVariablesNodeProperty(); 56 | globalNodeProperties.add(newEnvVarsNodeProperty); 57 | envVars = newEnvVarsNodeProperty.getEnvVars(); 58 | } else { 59 | envVars = envVarsNodePropertyList.get(0).getEnvVars(); 60 | } 61 | envVars.put(key, value) 62 | instance.save() 63 | } 64 | 65 | createGlobalEnvironmentVariables('PS4','+R-HUB-R-HUB-R-HUB') 66 | 67 | // Create Folders 68 | def jobs_str = ''' 69 | 70 | User submitted builds. 71 | 72 | 73 | 74 | 75 | 76 | All 77 | false 78 | false 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | false 87 | 88 | 89 | 90 | 91 | ''' 92 | def jobs_stream = new ByteArrayInputStream(jobs_str.getBytes()) 93 | Jenkins.instance.createProjectFromXML('Jobs', jobs_stream) 94 | 95 | def manage_str = ''' 96 | 97 | Internal jobs for R-hub. 98 | 99 | 100 | 101 | 102 | 103 | All 104 | false 105 | false 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | false 114 | 115 | 116 | 117 | 118 | ''' 119 | def manage_stream = new ByteArrayInputStream(manage_str.getBytes()) 120 | Jenkins.instance.createProjectFromXML('Management', manage_stream) 121 | -------------------------------------------------------------------------------- /jenkins/default-user.groovy: -------------------------------------------------------------------------------- 1 | 2 | import jenkins.model.* 3 | import hudson.security.* 4 | 5 | def env = System.getenv() 6 | def pass = new File("/run/secrets/jenkins.pass").text.trim() 7 | 8 | def jenkins = Jenkins.getInstance() 9 | jenkins.setSecurityRealm(new HudsonPrivateSecurityRealm(false)) 10 | jenkins.setAuthorizationStrategy(new GlobalMatrixAuthorizationStrategy()) 11 | 12 | def user = jenkins.getSecurityRealm().createAccount(env.JENKINS_USER, pass); 13 | user.save() 14 | 15 | jenkins.getAuthorizationStrategy().add(Jenkins.ADMINISTER, env.JENKINS_USER) 16 | jenkins.save() 17 | -------------------------------------------------------------------------------- /jenkins/plugins.txt: -------------------------------------------------------------------------------- 1 | elastic-axis:1.2 2 | groovy-postbuild:2.4.3 3 | matrix-auth:2.3 4 | publish-over-ssh:1.20.1 5 | swarm:3.15 6 | build-timeout:1.19 7 | cloudbees-folder:6.7 8 | ws-cleanup:0.37 9 | -------------------------------------------------------------------------------- /jenkins/scripts/windows-2008-pre-setup.ps1: -------------------------------------------------------------------------------- 1 | 2 | ## This must be run first, and then the machine must be rebooted 3 | ## to make WMF5.1 available. 4 | 5 | $LocalTempDir = $env:TEMP; 6 | $url = "https://go.microsoft.com/fwlink/?linkid=839523" 7 | $dest = "$LocalTempDir\wmf51.zip" 8 | 9 | (new-object System.Net.WebClient).DownloadFile($url, $dest) 10 | 11 | function Expand-ZIPFile { 12 | [CmdletBinding()] 13 | param ( 14 | [Parameter(Position=0, Mandatory=1)] 15 | [string]$file, 16 | [Parameter(Position=1, Mandatory=1)] 17 | [string]$destination 18 | ) 19 | 20 | $shell = new-object -com shell.application 21 | $zip = $shell.NameSpace($file) 22 | foreach($item in $zip.items()) 23 | { 24 | $shell.Namespace($destination).copyhere($item) 25 | } 26 | } 27 | 28 | $xdir = "$LocalTempDir\wmf51" 29 | mkdir "$xdir" 30 | Expand-ZIPFile "$dest" "$xdir" 31 | 32 | powershell.exe -ExecutionPolicy Bypass -Command "$xdir\Install-WMF5.1.ps1 -AcceptEula" 33 | -------------------------------------------------------------------------------- /jenkins/scripts/windows-2008-setup.ps1: -------------------------------------------------------------------------------- 1 | 2 | ## Otherwise PowerShell defaults to older TLS, which is not supported 3 | ## by many web sites any more 4 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 5 | 6 | $LocalTempDir = $env:TEMP 7 | 8 | Function Download { 9 | [CmdletBinding()] 10 | param ( 11 | [Parameter(Position=0, Mandatory=1)] 12 | [string]$Url, 13 | [Parameter(Position=1, Mandatory=1)] 14 | [string]$Dest 15 | ) 16 | 17 | (new-object System.Net.WebClient).DownloadFile($Url, $Dest) 18 | } 19 | 20 | Function Set-Timezone { 21 | tzutil /g 22 | tzutil /s "GMT Standard Time" 23 | tzutil /g 24 | } 25 | 26 | Function Install-Git { 27 | $giturl = "https://github.com/git-for-windows/git/releases/download/v2.20.1.windows.1/Git-2.20.1-64-bit.exe" 28 | $gitfile = "$LocalTempDir\git-install.exe" 29 | 30 | Download "$giturl" "$gitfile" 31 | 32 | start-process "$gitfile" -argumentlist ` 33 | "/VERYSILENT","/NORESTART","/NOCANCEL","/SP-","/CLOSEAPPLICATIONS","/RESTARTAPPLICATIONS","/COMPONENTS=`"icons,ext\reg\shellhere,assoc,assoc_sh`"" -wait 34 | 35 | $env:path = "C:\Program Files\Git\bin;$env:path" 36 | } 37 | 38 | Function Install-Java { 39 | $javaurl = "https://javadl.oracle.com/webapps/download/AutoDL?BundleId=236886_42970487e3af4f5aa5bca3f542482c60" 40 | $javafile = "$LocalTempDir\java-install.exe" 41 | 42 | Download "$javaurl" "$javafile" 43 | 44 | start-process "$javafile" -argumentlist "/s" -wait 45 | } 46 | 47 | Function Install-Chrome { 48 | $ChromeInstaller = "ChromeInstaller.exe"; 49 | Download 'http://dl.google.com/chrome/install/375.126/chrome_installer.exe' ` 50 | "$LocalTempDir\$ChromeInstaller" 51 | & "$LocalTempDir\$ChromeInstaller" /silent /install; 52 | $Process2Monitor = "ChromeInstaller"; 53 | Do { 54 | $ProcessesFound = Get-Process | 55 | ?{$Process2Monitor -contains $_.Name} | 56 | Select-Object -ExpandProperty Name; 57 | If ($ProcessesFound) { 58 | "Still running: $($ProcessesFound -join ', ')" | Write-Host; 59 | Start-Sleep -Seconds 2 60 | } else { 61 | rm "$LocalTempDir\$ChromeInstaller" -ErrorAction SilentlyContinue -Verbose 62 | } 63 | } Until (!$ProcessesFound) 64 | } 65 | 66 | Function Updates-Off { 67 | & reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU /v AUOptions /t REG_DWORD /d 3 68 | } 69 | 70 | Function Install-R { 71 | $CRAN = "https://cloud.r-project.org" 72 | $DevelUrl = $CRAN + "/bin/windows/base/R-devel-win.exe" 73 | 74 | $Version = $(ConvertFrom-JSON $(Invoke-WebRequest "http://rversions.r-pkg.org/r-release-win").Content).version 75 | If ($Version -eq "3.2.4") { 76 | $Version = "3.2.4revised" 77 | } 78 | 79 | $Oldrel = $(ConvertFrom-JSON $(Invoke-WebRequest "http://rversions.r-pkg.org/r-oldrel").Content).version 80 | 81 | $ReleaseUrl = $CRAN + "/bin/windows/base/R-" + $Version + "-win.exe" 82 | $PatchedUrl = $CRAN + "/bin/windows/base/R-" + $Version + ` 83 | "patched-win.exe" 84 | $OldrelUrl = $CRAN + "/bin/windows/base/old/" + $Oldrel + ` 85 | "/R-" + $Oldrel + "-win.exe" 86 | 87 | $DevelFile = "$LocalTempDir\R-devel-win.exe" 88 | $ReleaseFile = "$LocalTempDir\R-release-win.exe" 89 | $PatchedFile = "$LocalTempDir\R-patched-win.exe" 90 | $OldrelFile = "$LocalTempDir\R-oldrel-win.exe" 91 | 92 | Download "$DevelUrl" "$DevelFile" 93 | Download "$ReleaseUrl" "$ReleaseFile" 94 | Download "$PatchedUrl" "$PatchedFile" 95 | Download "$OldrelUrl" "$OldrelFile" 96 | 97 | Start-Process -FilePath "$ReleaseFile" -ArgumentList "/VERYSILENT" -NoNewWindow -Wait 98 | Start-Process -FilePath "$OldrelFile" -ArgumentList "/VERYSILENT" -NoNewWindow -Wait 99 | Start-Process -FilePath "$PatchedFile" -ArgumentList "/VERYSILENT" -NoNewWindow -Wait 100 | Start-Process -FilePath "$DevelFile" -ArgumentList "/VERYSILENT" -NoNewWindow -Wait 101 | } 102 | 103 | Function Install-7zip { 104 | $zipurl = "https://www.7-zip.org/a/7z1900-x64.msi" 105 | $zipfile = "$LocalTempDir\7zip.msi" 106 | Download "$zipurl" "$zipfile" 107 | 108 | msiexec /i "$zipfile" /q INSTALLDIR="C:\Program Files\7-Zip" 109 | 110 | $env:path = "C:\Program Files\7-Zip;$env:path" 111 | } 112 | 113 | Function Install-Rtools { 114 | $rtoolsver = $(Invoke-WebRequest ($CRAN + "/bin/windows/Rtools/VERSION.txt")).Content.Split(' ')[2].Split('.')[0..1] -Join '' 115 | $rtoolsurl = $CRAN + "/bin/windows/Rtools/Rtools$rtoolsver.exe" 116 | 117 | $rtoolsfile = "$LocalTempDir\Rtools.exe" 118 | Download "$rtoolsurl" "$rtoolsfile" 119 | 120 | Start-Process -FilePath "$rtoolsfile" -ArgumentList /VERYSILENT -NoNewWindow -Wait 121 | } 122 | 123 | Function Install-Latex { 124 | $latexurl = "https://miktex.org/download/ctan/systems/win32/miktex/setup/windows-x64/miktexsetup-2.9.6942-x64.zip" 125 | $latexfile = "$LocalTempDir\miktex.xip" 126 | Download $latexurl $latexfile 127 | $xdir = "$LocalTempDir\miktex" 128 | mkdir "$xdir" -force 129 | $repo = "https://ctan.math.illinois.edu/systems/win32/miktex/tm/packages/" 130 | & 7z x "$latexfile" -o"$xdir" 131 | & "$xdir\miktexsetup.exe" --quiet --package-set=complete ` 132 | "--remote-package-repository=$repo" download 133 | & "$xdir\miktexsetup.exe" --quiet --package-set=complete install 134 | } 135 | 136 | Function Install-Pandoc { 137 | $url1 = "https://files.r-hub.io/pandoc/windows/pandoc-1.19.2.1.zip" 138 | $url2 = "https://files.r-hub.io/pandoc/windows/pandoc-citeproc-0.10.4.zip" 139 | $file1 = "$LocalTempDir\pandoc.zip" 140 | $file2 = "$LocalTempDir\pandoc-citeproc.zip" 141 | 142 | Download "$url1" "$file1" 143 | Download "$url2" "$file2" 144 | 145 | $xdir = "$LocalTempDir\pandoc" 146 | mkdir "$xdir" -force 147 | & 7z x "$file1" -aoa -o"$xdir" 148 | & 7z x "$file2" -aoa -o"$xdir" 149 | 150 | cp "$xdir\*.exe" c:\windows\ 151 | } 152 | 153 | Function Install-Aspell { 154 | $spellurl = "http://ftp.gnu.org/gnu/aspell/w32/Aspell-0-50-3-3-Setup.exe" 155 | $spellfile = "$LocalTempDir\aspell-setup.exe" 156 | 157 | Download "$spellurl" "$spellfile" 158 | 159 | & "$spellfile" /VERYSILENT /NORESTART /NOCANCEL 160 | } 161 | 162 | Function Install-Jags { 163 | $jagsurl = "https://files.r-hub.io/jags/jags-4.3.0.zip" 164 | $jagsfile = "$LocalTempDir\jags-4.3.0.zip" 165 | 166 | Download "$jagsurl" "$jagsfile" 167 | 168 | $xdir = "c:/R/jags" 169 | mkdir "$xdir" -force 170 | & 7z x "$jagsfile" -o"$xdir" 171 | 172 | $mv = "c:\R\Makevars.win" 173 | $mv64 = "c:\R\Makevars.win64" 174 | "JAGS_ROOT=c:/R/jags/JAGS-4.3.0" | AC "$mv" 175 | "JAGS_ROOT=c:/R/jags/JAGS-4.3.0" | AC "$mv64" 176 | 177 | SETX JAGS_HOME "c:/R/jags/JAGS-4.3.0" /M 178 | } 179 | 180 | Function Get-Jenkins { 181 | $swarmversion = "3.9" 182 | $swarmurl = "https://repo.jenkins-ci.org/releases/org/jenkins-ci/plugins/swarm-client/$swarmversion/swarm-client-$swarmversion.jar" 183 | $swarmfile = "c:\Users\rhub\jenkins-swarm-client.jar" 184 | 185 | Download "$swarmurl" "$swarmfile" 186 | } 187 | 188 | Function Get-LocalSoft { 189 | $localurl = "https://github.com/rwinlib/local330/archive/master.zip" 190 | $localfile = "$LocalTempDir\local330.zip" 191 | 192 | Download "$localurl" "$localfile" 193 | 194 | mkdir -force c:\R 195 | ## We need the WMF5.1 update for this, powershell 2.0 does not have it 196 | expand-archive -path "$localfile" -destinationpath "c:\R" 197 | mv "c:\R\local330-master" "c:\R\local330" 198 | 199 | $glpk32url = "https://www.stats.ox.ac.uk/pub/Rtools/goodies/multilib/glpk32.zip" 200 | $glpk64url = "https://www.stats.ox.ac.uk/pub/Rtools/goodies/multilib/glpk64.zip" 201 | $glpk32file = "$LocalTempDir\glpk32.zip" 202 | $glpk64file = "$LocalTempDir\glpk64.zip" 203 | 204 | Download "$glpk32url" "$glpk32file" 205 | Download "$glpk64url" "$glpk64file" 206 | 207 | expand-archive -path "$glpk32file" -destinationpath "c:\R\glpk32" 208 | expand-archive -path "$glpk64file" -destinationpath "c:\R\glpk64" 209 | 210 | $sym32url = "https://www.stats.ox.ac.uk/pub/Rtools/goodies/multilib/symphony32.zip" 211 | $sym64url = "https://www.stats.ox.ac.uk/pub/Rtools/goodies/multilib/symphony64.zip" 212 | $sym32file = "$LocalTempDir\sym32.zip" 213 | $sym64file = "$LocalTempDir\sym64.zip" 214 | 215 | Download "$sym32url" "$sym32file" 216 | Download "$sym64url" "$sym64file" 217 | 218 | expand-archive -path "$sym32file" -destinationpath "c:\R\symphony32" 219 | expand-archive -path "$sym64file" -destinationpath "c:\R\symphony64" 220 | 221 | $mv = "c:\R\Makevars.win" 222 | "LIB_XML=c:/R/local330" | AC "$mv" 223 | "LIB_GSL=c:/R/local330" | AC "$mv" 224 | "GLPK_HOME=c:/R/glpk32" | AC "$mv" 225 | "SYMPHONY_HOME=c:/R/symphony32" | AC "$mv" 226 | 227 | $mv64 = "c:\R\Makevars.win64" 228 | "LIB_XML=c:/R/local330" | AC "$mv64" 229 | "LIB_GSL=c:/R/local330" | AC "$mv64" 230 | "GLPK_HOME=c:/R/glpk64" | AC "$mv64" 231 | "SYMPHONY_HOME=c:/R/symphony64" | AC "$mv64" 232 | 233 | SETX LOCAL_SOFT c:/R/local330 /M 234 | SETX R_MAKEVARS_WIN "$mv" /M 235 | SETX R_MAKEVARS_WIN64 "$mv64" /M 236 | } 237 | 238 | Function Get-Scripts { 239 | git clone https://github.com/r-hub/wincheck.git c:\users\rhub\wincheck 240 | cp c:\users\rhub\wincheck\*.ps1 c:\users\rhub\documents\ 241 | } 242 | 243 | Function Install-Perl { 244 | $perlurl = "http://strawberryperl.com/download/5.28.1.1/strawberry-perl-5.28.1.1-64bit.msi" 245 | $perlfile = "$LocalTempDir\strawberry-perl-5.28.1.1-64bit.msi" 246 | 247 | Download "$perlurl" "$perlfile" 248 | 249 | start-process msiexec -ArgumentList "/I $perlfile /passive" -wait 250 | } 251 | 252 | Function Install-Rtools40 { 253 | # Experimental Rtools40 installation 254 | $rtoolsurl = "https://dl.bintray.com/rtools/installer/rtools40-x86_64.exe" 255 | $rtoolsfile = "$LocalTempDir\rtools40-x86_64.exe" 256 | Download "$rtoolsurl" "$rtoolsfile" 257 | Start-Process -FilePath "$rtoolsfile" -ArgumentList /VERYSILENT -NoNewWindow -Wait 258 | 259 | # Rtools40 path is hardcoded in R-testing for now 260 | # setx path "$c:\rtools40\usr\bin" 261 | 262 | # Special build of R for Rtools40 263 | $TestingUrl = "https://dl.bintray.com/rtools/installer/R-testing-win.exe" 264 | $TestingFile = "$LocalTempDir\R-testing-win.exe" 265 | Download "$TestingUrl" "$TestingFile" 266 | Start-Process -FilePath "$TestingFile" -ArgumentList "/VERYSILENT" -NoNewWindow -Wait 267 | } 268 | 269 | Function Update-Rtools40 { 270 | & C:\rtools40\usr\bin\pacman --noconfirm -Syyu 271 | & C:\rtools40\usr\bin\bash.exe --login -c ` 272 | 'pacman -S --needed --noconfirm $(pacman -Slq | grep mingw-w64-)' 273 | } 274 | 275 | 276 | Updates-Off 277 | Set-Timezone 278 | 279 | Install-Git 280 | Install-Java 281 | Install-Chrome 282 | Install-7zip 283 | Install-R 284 | Install-Rtools 285 | Install-Latex 286 | Install-Pandoc 287 | Install-Aspell 288 | Install-Perl 289 | 290 | Get-Jenkins 291 | Get-LocalSoft 292 | Get-Scripts 293 | 294 | Install-Jags 295 | -------------------------------------------------------------------------------- /linux-builder/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM ubuntu:18.04 3 | 4 | ENV DEBIAN_FRONTEND noninteractive 5 | 6 | # en_US locale 7 | RUN apt-get update && \ 8 | apt-get install -y locales && \ 9 | localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 10 | ENV LANG en_US.utf8 11 | 12 | # R 13 | 14 | RUN echo "deb https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/" \ 15 | > /etc/apt/sources.list.d/cran.list 16 | 17 | RUN apt-get install -yy default-jre-headless gnupg curl wget 18 | 19 | RUN apt-key adv --keyserver keyserver.ubuntu.com \ 20 | --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9 21 | 22 | RUN apt-get update && apt-get install -yy \ 23 | r-base-core r-base-dev 24 | 25 | RUN useradd jenkins && \ 26 | mkdir /home/jenkins && \ 27 | chown jenkins:jenkins /home/jenkins && \ 28 | addgroup jenkins staff 29 | 30 | RUN apt-get install -yy docker.io 31 | RUN addgroup jenkins docker 32 | 33 | RUN apt-get clean 34 | 35 | ENV SWARM_CLIENT_VERSION 3.9 36 | 37 | RUN curl -s https://repo.jenkins-ci.org/releases/org/jenkins-ci/plugins/swarm-client/${SWARM_CLIENT_VERSION}/swarm-client-${SWARM_CLIENT_VERSION}.jar -o /home/jenkins/swarm-client-${SWARM_CLIENT_VERSION}.jar 38 | 39 | # Somewhat surprisingly, this persist after mounting the socket, so 40 | # the jenkins user will have access to Docker 41 | RUN touch /var/run/docker.sock && \ 42 | chown root:docker /var/run/docker.sock 43 | 44 | RUN mkdir /artifacts && \ 45 | chown jenkins:jenkins /artifacts 46 | 47 | COPY run.sh /home/jenkins/run.sh 48 | RUN chmod +x /home/jenkins/run.sh 49 | 50 | RUN rm -rf /var/lib/apt/lists/* 51 | 52 | USER jenkins 53 | 54 | # Get R packages we need 55 | 56 | RUN R -q -e 'source("https://install-github.me/r-hub/sysreqs")' 57 | 58 | # Need to switch back to root, because we need to change the group of 59 | # the docker socket, to be able to start sibling containers 60 | # run.sh will do su to start the jenkins swarm client as non-root 61 | 62 | USER root 63 | 64 | CMD [ "/home/jenkins/run.sh" ] 65 | -------------------------------------------------------------------------------- /linux-builder/run.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | chgrp docker /var/run/docker.sock 4 | 5 | export JENKINS_PASSWORD="$(cat /run/secrets/jenkins.pass)" 6 | 7 | cd ~jenkins 8 | 9 | cat >jenkins.sh < /etc/nginx/nginx.conf 8 | else 9 | echo "Installing HTTP nginx config" 10 | cat /etc/nginx/nginx.conf.in | envsubst > /etc/nginx/nginx.conf 11 | fi 12 | 13 | wait-for frontend:3000 -- sleep 0 14 | wait-for jenkins:8080 -- nginx -g "daemon off;" 15 | -------------------------------------------------------------------------------- /nginx/nginx.conf.https.in: -------------------------------------------------------------------------------- 1 | 2 | user nginx; 3 | 4 | events { 5 | worker_connections 768; 6 | } 7 | 8 | http { 9 | 10 | ssl_session_cache shared:SSL:10m; 11 | ssl_session_timeout 10m; 12 | 13 | server { 14 | listen 80; 15 | access_log off; 16 | return 301 https://${DOLLAR}host${DOLLAR}request_uri; 17 | } 18 | 19 | server { 20 | listen 443 ssl; 21 | keepalive_timeout 70; 22 | 23 | ssl_certificate /run/secrets/nginx.crt; 24 | ssl_certificate_key /run/secrets/nginx.key; 25 | ssl_protocols TLSv1.1 TLSv1.2; 26 | 27 | location ~ ^/artifacts/?${DOLLAR} { 28 | root /; 29 | autoindex off; 30 | } 31 | 32 | location ~ /artifacts/.* { 33 | root /; 34 | autoindex on; 35 | sendfile on; 36 | sendfile_max_chunk 1m; 37 | } 38 | 39 | location /jenkins/ { 40 | sendfile off; 41 | 42 | proxy_pass http://jenkins:8080; 43 | proxy_redirect http://jenkins:8080 ${DOLLAR}scheme://${DOLLAR}host:${DOLLAR}server_port/jenkins; 44 | 45 | proxy_connect_timeout 90; 46 | proxy_send_timeout 90; 47 | proxy_read_timeout 90; 48 | proxy_buffering off; 49 | 50 | proxy_set_header X-Forwarded-Host ${DOLLAR}host:${DOLLAR}server_port; 51 | proxy_set_header X-Forwarded-Server ${DOLLAR}host; 52 | proxy_set_header X-Forwarded-For ${DOLLAR}proxy_add_x_forwarded_for; 53 | proxy_set_header X-Forwarded-Proto ${DOLLAR}scheme; 54 | proxy_set_header X-Real-IP ${DOLLAR}remote_addr; 55 | proxy_request_buffering off; 56 | proxy_set_header Connection ""; 57 | } 58 | 59 | location / { 60 | proxy_pass http://frontend:3000; 61 | proxy_set_header Host ${DOLLAR}host; 62 | proxy_set_header X-Real-IP ${DOLLAR}remote_addr; 63 | proxy_set_header X-Forwarded-For ${DOLLAR}proxy_add_x_forwarded_for; 64 | } 65 | } 66 | 67 | include /etc/nginx/conf.d/*.conf; 68 | } 69 | -------------------------------------------------------------------------------- /nginx/nginx.conf.in: -------------------------------------------------------------------------------- 1 | 2 | user nginx; 3 | 4 | events { 5 | worker_connections 768; 6 | } 7 | 8 | http { 9 | server { 10 | listen 80; 11 | client_max_body_size 200M; 12 | 13 | location ~ ^/artifacts/?${DOLLAR} { 14 | root /; 15 | autoindex off; 16 | } 17 | 18 | location ~ /artifacts/.* { 19 | root /; 20 | autoindex on; 21 | sendfile on; 22 | sendfile_max_chunk 1m; 23 | } 24 | 25 | location /jenkins/ { 26 | sendfile off; 27 | 28 | proxy_pass http://jenkins:8080; 29 | proxy_redirect http://jenkins:8080 ${DOLLAR}scheme://${DOLLAR}host:${DOLLAR}server_port/jenkins; 30 | 31 | proxy_connect_timeout 90; 32 | proxy_send_timeout 90; 33 | proxy_read_timeout 90; 34 | proxy_buffering off; 35 | 36 | proxy_set_header X-Forwarded-Host ${DOLLAR}host:${DOLLAR}server_port; 37 | proxy_set_header X-Forwarded-Server ${DOLLAR}host; 38 | proxy_set_header X-Forwarded-For ${DOLLAR}proxy_add_x_forwarded_for; 39 | proxy_set_header X-Forwarded-Proto ${DOLLAR}scheme; 40 | proxy_set_header X-Real-IP ${DOLLAR}remote_addr; 41 | proxy_request_buffering off; 42 | proxy_set_header Connection ""; 43 | } 44 | 45 | location / { 46 | proxy_pass http://frontend:3000; 47 | proxy_set_header Host ${DOLLAR}host; 48 | proxy_set_header X-Real-IP ${DOLLAR}remote_addr; 49 | proxy_set_header X-Forwarded-For ${DOLLAR}proxy_add_x_forwarded_for; 50 | } 51 | } 52 | 53 | include /etc/nginx/conf.d/*.conf; 54 | } 55 | -------------------------------------------------------------------------------- /nginx/wait-for: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TIMEOUT=15 4 | QUIET=0 5 | 6 | echoerr() { 7 | if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi 8 | } 9 | 10 | usage() { 11 | exitcode="$1" 12 | cat << USAGE >&2 13 | Usage: 14 | $cmdname host:port [-t timeout] [-- command args] 15 | -q | --quiet Do not output any status messages 16 | -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout 17 | -- COMMAND ARGS Execute command with args after the test finishes 18 | USAGE 19 | exit "$exitcode" 20 | } 21 | 22 | wait_for() { 23 | for i in `seq $TIMEOUT` ; do 24 | nc -z "$HOST" "$PORT" > /dev/null 2>&1 25 | 26 | result=$? 27 | if [ $result -eq 0 ] ; then 28 | if [ $# -gt 0 ] ; then 29 | exec "$@" 30 | fi 31 | exit 0 32 | fi 33 | sleep 1 34 | done 35 | echo "Operation timed out" >&2 36 | exit 1 37 | } 38 | 39 | while [ $# -gt 0 ] 40 | do 41 | case "$1" in 42 | *:* ) 43 | HOST=$(printf "%s\n" "$1"| cut -d : -f 1) 44 | PORT=$(printf "%s\n" "$1"| cut -d : -f 2) 45 | shift 1 46 | ;; 47 | -q | --quiet) 48 | QUIET=1 49 | shift 1 50 | ;; 51 | -t) 52 | TIMEOUT="$2" 53 | if [ "$TIMEOUT" = "" ]; then break; fi 54 | shift 2 55 | ;; 56 | --timeout=*) 57 | TIMEOUT="${1#*=}" 58 | shift 1 59 | ;; 60 | --) 61 | shift 62 | break 63 | ;; 64 | --help) 65 | usage 0 66 | ;; 67 | *) 68 | echoerr "Unknown argument: $1" 69 | usage 1 70 | ;; 71 | esac 72 | done 73 | 74 | if [ "$HOST" = "" -o "$PORT" = "" ]; then 75 | echoerr "Error: you need to provide a host and port to test." 76 | usage 2 77 | fi 78 | 79 | wait_for "$@" 80 | -------------------------------------------------------------------------------- /rhub: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [[ ! -z "$DOCKER_MACHINE_NAME" ]]; then 4 | echo "RHUB> Active Docker Machine: $DOCKER_MACHINE_NAME" 5 | else 6 | echo "RHUB> No active Docker Machine" 7 | fi 8 | 9 | if [[ -f stack.yml ]]; then 10 | echo "RHUB> Setting up base project" 11 | 12 | set -a 13 | . ./config.env 14 | set +a 15 | 16 | export COMPOSE_FILE=stack.yml 17 | export COMPOSE_ARG="-c stack.yml" 18 | 19 | else 20 | echo "RHUB> Setting up $(basename $(pwd)) project" 21 | 22 | set -a 23 | . ../config.env 24 | if [[ -f config-custom.env ]]; then . ./config-custom.env; fi 25 | set +a 26 | 27 | if [[ ! -f stack-custom.yml ]]; then 28 | export COMPOSE_FILE="../stack.yml" 29 | export COMPOSE_ARG="-c ../stack.yml" 30 | else 31 | export COMPOSE_PATH_SEPARATOR=":" 32 | export COMPOSE_FILE="../stack.yml:stack-custom.yml" 33 | export COMPOSE_ARG="-c ../stack.yml -c stack-custom.yml" 34 | fi 35 | fi 36 | 37 | if [[ "$1" == "docker" && "$2" == "stack" && "$3" == "deploy" ]]; then 38 | shift 3 39 | echo "RHUB> Calling docker stack deploy $COMPOSE_ARG $@" 40 | echo 41 | docker stack deploy $COMPOSE_ARG "$@" 42 | else 43 | echo "RHUB> Calling $@" 44 | echo 45 | "$@" 46 | fi 47 | -------------------------------------------------------------------------------- /seed/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM alpine:3.8 3 | 4 | RUN apk add --no-cache curl bash jq 5 | 6 | COPY . /seed 7 | 8 | CMD [ "echo" "Need to define entrypoint" ] 9 | -------------------------------------------------------------------------------- /seed/app.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "views": { 4 | "email": { 5 | "map": "function (doc) { \n\temit([doc.email, doc.submitted], doc);\n\t}" 6 | }, 7 | "emailpackage": { 8 | "map": "function (doc) {\n\temit([doc.email, doc.package, doc.submitted],doc)\n}\n" 9 | }, 10 | "stats": { 11 | "map": "function (doc) {\n emit(\n doc.submitted,\n { email: doc.email,\n package: doc.package,\n platform: doc.platform.name,\n status: doc.status,\n submitted: doc.submitted,\n started: doc.started,\n build_time: doc.build_time\n });\n}\n" 12 | }, 13 | "group": { 14 | "map": "function(doc) {\n if (!!doc.group) {\n emit([doc.email, doc.submitted, doc.group], doc.id );\n } else {\n emit([doc.email, doc.submitted, doc.id], doc.id);\n }\n}\n", 15 | "reduce": "function(keys, vals, re) {\n var a;\n\n var key = keys[0][2];\n for (a = 0; a < keys.length; a++)\n if (keys[a][2] !== key)\n return null;\n\n var result = [];\n for (a = 0; a < vals.length; a++)\n if (vals[a]) {\n result.push(vals[a]);\n }\n return result;\n}\n\n}\n" 16 | }, 17 | "grouppackage": { 18 | "map": "function(doc) {\n if (!!doc.group) {\n emit([doc.email, doc.package, doc.submitted, doc.group], doc.id );\n } else {\n emit([doc.email, doc.package, doc.submitted, doc.id], doc.id);\n }\n}\n", 19 | "reduce": "function(keys, vals, re) {\n var a;\n\n var key = keys[0][3];\n for (a = 0; a < keys.length; a++)\n if (keys[a][3] !== key)\n return null;\n\n var result = [];\n for (a = 0; a < vals.length; a++)\n if (vals[a]) {\n result.push(vals[a]);\n }\n return result;\n}\n" 20 | }, 21 | }, 22 | "rewrites": [ 23 | { 24 | "from": "/-/email/:email", 25 | "to": "_view/email", 26 | "query": { 27 | "start_key": [ 28 | ":email", 29 | {} 30 | ], 31 | "end_key": [ 32 | ":email" 33 | ], 34 | "descending": "true" 35 | } 36 | }, 37 | { 38 | "from": "/-/package/:email/:package", 39 | "to": "_view/emailpackage", 40 | "query": { 41 | "start_key": [ 42 | ":email", 43 | ":package", 44 | {} 45 | ], 46 | "end_key": [ 47 | ":email", 48 | ":package" 49 | ], 50 | "descending": "true" 51 | } 52 | }, 53 | { 54 | "from": "/-/group/email/:email", 55 | "to": "_view/group", 56 | "query": { 57 | "start_key": [ 58 | ":email", 59 | { 60 | } 61 | ], 62 | "end_key": [ 63 | ":email" 64 | ], 65 | "descending": "true", 66 | "group": "true" 67 | } 68 | }, 69 | { 70 | "from": "/-/group/package/:email/:package", 71 | "to": "_view/grouppackage", 72 | "query": { 73 | "start_key": [ 74 | ":email", 75 | ":package", 76 | { 77 | } 78 | ], 79 | "end_key": [ 80 | ":email", 81 | ":package" 82 | ], 83 | "descending": "true", 84 | "group": "true" 85 | } 86 | } 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /seed/logdb.sh: -------------------------------------------------------------------------------- 1 | 2 | echo DB is at $LOGDB_URL 3 | 4 | # The last part is the DB, so we remove that 5 | BASEURL="${LOGDB_URL%/*}" 6 | 7 | # Need to wait for the DB to be ready 8 | while true 9 | do 10 | echo "waiting for DB" 11 | if curl -s "$BASEURL"; then break; fi 12 | sleep 1 13 | done 14 | 15 | # Try creating a _users DB, to avoid error messages 16 | curl -s -X PUT "$BASEURL/_users" || true 17 | 18 | if curl -s -o /dev/null "$LOGDB_URL"; then 19 | echo Creating /logs DB 20 | curl -s -X PUT "$LOGDB_URL" 21 | else 22 | echo /logs DB already exists 23 | fi 24 | 25 | echo Adding/updating design document 26 | REV=$(curl -s "$LOGDB_URL"/_design/app | jq -r ._rev) 27 | if [[ "$REV" != "null" ]]; then 28 | curl --silent --request DELETE "$LOGDB_URL/_design/app?rev=$REV" 29 | fi 30 | curl --silent --header "Content-Type: application/json" \ 31 | --request PUT --data @/seed/app.json \ 32 | "$LOGDB_URL/_design/app" 33 | -------------------------------------------------------------------------------- /stack.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | volumes: 4 | uploads-data: 5 | session-data: 6 | log-data: 7 | queue-data: 8 | jenkins-data: 9 | artifacts-data: 10 | 11 | secrets: 12 | nginx.crt: 13 | external: true 14 | nginx.key: 15 | external: true 16 | jenkins.pass: 17 | file: ./jenkins.pass 18 | web.pass: 19 | file: ./web.pass 20 | 21 | services: 22 | 23 | frontend: 24 | build: ./frontend 25 | image: "rhub/frontend:${RHUB_VERSION}" 26 | environment: 27 | - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} 28 | - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} 29 | - JENKINS_URL=http://${JENKINS_USER}:@jenkins:8080/jenkins 30 | - LOGDB_URL=${LOGDB_URL} 31 | - MAILGUN_DOMAIN=${MAILGUN_DOMAIN} 32 | - MAILGUN_API_KEY=${MAILGUN_API_KEY} 33 | - RABBITMQ_URL=${RABBITMQ_URL} 34 | - REDIS_URL=${REDIS_URL} 35 | - REDIS_EMAIL_URL=${REDIS_EMAIL_URL} 36 | - RHUB_ARTIFACTS_URL=${RHUB_ARTIFACTS_URL} 37 | - RHUB_BUILDER_EXTERNAL_URL=${RHUB_BUILDER_EXTERNAL_URL} 38 | - RHUB_BUILDER_URL=http://frontend:3000 39 | - RHUB_EMAIL_FROM=${RHUB_EMAIL_FROM} 40 | - RHUB_EMAIL_MODE=${RHUB_EMAIL_MODE} 41 | - RHUB_SMTP_SERVER=${RHUB_SMTP_SERVER} 42 | - RHUB_SMTP_USERNAME=${RHUB_SMTP_USERNAME} 43 | - RHUB_SMTP_PASSWORD=${RHUB_SMTP_PASSWORD} 44 | - RHUB_SMTP_PORT=${RHUB_SMTP_PORT} 45 | - RHUB_SMTP_TLS_REQUIRED=${RHUB_SMTP_TLS_REQUIRED} 46 | volumes: 47 | - uploads-data:/usr/src/app/uploads 48 | depends_on: 49 | - redis 50 | - queue 51 | - logdb 52 | - jenkins 53 | secrets: 54 | - jenkins.pass 55 | 56 | backend: 57 | build: ./backend 58 | image: "rhub/backend:${RHUB_VERSION}" 59 | environment: 60 | - RABBITMQ_URL=${RABBITMQ_URL} 61 | - JENKINS_URL=http://${JENKINS_USER}:@jenkins:8080/jenkins 62 | - RHUB_ARTIFACTS=${RHUB_ARTIFACTS} 63 | depends_on: 64 | - queue 65 | - jenkins 66 | secrets: 67 | - jenkins.pass 68 | 69 | redis: 70 | image: "redis:4.0.11-alpine" 71 | volumes: 72 | - session-data:/data 73 | 74 | queue: 75 | image: "rabbitmq:3.7.8-alpine" 76 | volumes: 77 | - queue-data:/var/lib/rabbitmq 78 | 79 | logdb: 80 | image: "couchdb:2.2" 81 | volumes: 82 | - log-data:/opt/couchdb/data 83 | 84 | logdb-seed: 85 | build: ./seed 86 | image: "rhub/seed:${RHUB_VERSION}" 87 | environment: 88 | - LOGDB_URL=${LOGDB_URL} 89 | entrypoint: 90 | - bash 91 | - -c 92 | - /seed/logdb.sh 93 | depends_on: 94 | - logdb 95 | deploy: 96 | restart_policy: 97 | condition: on-failure 98 | 99 | jenkins: 100 | build: ./jenkins 101 | image: "rhub/jenkins:${RHUB_VERSION}" 102 | volumes: 103 | - jenkins-data:/var/jenkins_home 104 | environment: 105 | - JENKINS_ROOT_URL=${JENKINS_ROOT_URL} 106 | - JENKINS_USER=${JENKINS_USER} 107 | - JENKINS_OPTS="--prefix=/jenkins" 108 | secrets: 109 | - jenkins.pass 110 | ports: 111 | - 50000:50000 112 | 113 | linux-builder: 114 | hostname: linux-builder 115 | build: ./linux-builder 116 | image: "rhub/linux-builder:${RHUB_VERSION}" 117 | volumes: 118 | - /var/run/docker.sock:/var/run/docker.sock 119 | - artifacts-data:/artifacts 120 | environment: 121 | - JENKINS_URL=http://jenkins:8080/jenkins 122 | - JENKINS_USER=${JENKINS_USER} 123 | - RHUB_EXECUTORS=4 124 | - JENKINS_LABELS=swarm linux 125 | - RHUB_CRAN_MIRROR=${RHUB_CRAN_MIRROR} 126 | - RHUB_ARTIFACTS=${RHUB_ARTIFACTS} 127 | - RHUB_BUILDER_URL=http://frontend:3000 128 | secrets: 129 | - jenkins.pass 130 | 131 | cron: 132 | build: ./cron 133 | image: "rhub/cron:${RHUB_VERSION}" 134 | environment: 135 | - JENKINS_URL=http://${JENKINS_USER}:@jenkins:8080/jenkins 136 | secrets: 137 | - jenkins.pass 138 | 139 | nginx: 140 | build: ./nginx 141 | image: "rhub/nginx:${RHUB_VERSION}" 142 | volumes: 143 | - artifacts-data:/artifacts:ro 144 | ports: 145 | - "80:80" 146 | - "443:443" 147 | depends_on: 148 | - frontend 149 | - jenkins 150 | entrypoint: 151 | - sh 152 | - -c 153 | - /entrypoint.sh 154 | -------------------------------------------------------------------------------- /web.pass: -------------------------------------------------------------------------------- 1 | no 2 | --------------------------------------------------------------------------------