├── web ├── images │ └── redports.png ├── assets │ ├── css │ │ ├── images │ │ │ ├── bg.png │ │ │ └── ie │ │ │ │ └── grad0-20.svg │ │ ├── ie9.css │ │ └── ie8.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── sass │ │ ├── libs │ │ │ ├── _vars.scss │ │ │ ├── _functions.scss │ │ │ ├── _mixins.scss │ │ │ └── _skel.scss │ │ ├── ie9.scss │ │ └── ie8.scss │ └── js │ │ ├── jquery.scrolly.min.js │ │ ├── skel-viewport.min.js │ │ ├── main.js │ │ ├── ie │ │ ├── html5shiv.js │ │ └── respond.min.js │ │ ├── skel.min.js │ │ └── util.js ├── composer.json ├── lib │ └── Redports │ │ └── Web │ │ ├── Config.php │ │ └── Session.php ├── templates │ ├── repositories.html │ ├── _header.html │ ├── _footer.html │ └── index.html ├── index.php └── nginx.conf.sample ├── master ├── scripts │ ├── resque │ └── addmachine ├── composer.json ├── lib │ ├── Redports │ │ └── Master │ │ │ ├── Task │ │ │ ├── TaskNotifyEmail.php │ │ │ ├── TaskNotifyIRC.php │ │ │ └── TaskPreparePortstree.php │ │ │ ├── Repositories.php │ │ │ ├── Jails.php │ │ │ ├── Machines.php │ │ │ ├── User.php │ │ │ ├── Config.php │ │ │ ├── Jobgroup.php │ │ │ ├── Machine.php │ │ │ ├── Queue.php │ │ │ ├── Job.php │ │ │ ├── Session.php │ │ │ └── GitHubWebhook.php │ └── functions.php ├── index.php └── composer.lock ├── ports └── ports-mgmt │ └── redports-node │ ├── pkg-plist │ ├── pkg-descr │ ├── files │ ├── redports-node.json.sample.in │ └── redportsnode.in │ ├── distinfo │ └── Makefile ├── node ├── redports-node.json.dist ├── composer.json ├── box.json ├── lib │ └── Redports │ │ └── Node │ │ ├── Command │ │ ├── Command.php │ │ ├── MainCommand.php │ │ ├── UpdateCommand.php │ │ └── SetupCommand.php │ │ ├── Logger │ │ └── Stdout.php │ │ ├── Client │ │ ├── Client.php │ │ └── ConnectionManager.php │ │ ├── Poudriere │ │ ├── Poudriere.php │ │ ├── Portstree.php │ │ └── Jail.php │ │ ├── Config.php │ │ ├── Update │ │ └── UpdateManager.php │ │ └── Process │ │ ├── Child.php │ │ └── ProcessManager.php ├── bin │ └── redports-node ├── Makefile └── composer.lock ├── README.md └── LICENSE /web/images/redports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/redports/HEAD/web/images/redports.png -------------------------------------------------------------------------------- /web/assets/css/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/redports/HEAD/web/assets/css/images/bg.png -------------------------------------------------------------------------------- /web/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/redports/HEAD/web/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /web/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/redports/HEAD/web/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /web/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/redports/HEAD/web/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /web/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/redports/HEAD/web/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /web/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/redports/HEAD/web/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /master/scripts/resque: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export QUEUE="default" 4 | export REDIS_BACKEND="/var/run/redis/redis.sock" 5 | 6 | exec ../vendor/bin/resque 7 | 8 | -------------------------------------------------------------------------------- /ports/ports-mgmt/redports-node/pkg-plist: -------------------------------------------------------------------------------- 1 | bin/redports-node 2 | bin/redports-node.phar 3 | bin/redports-node.phar.pubkey 4 | @sample etc/redports-node.json.sample 5 | -------------------------------------------------------------------------------- /node/redports-node.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "manifest": "https://api.redports.org/downloads/", 3 | "pubkeyhash": "ffb4fcbf9223594ec6ab97724cade73446fbdd4cb91f9bd94b74cc79e5bc4aaf", 4 | "server": "https://api.redports.org/", 5 | "machineid": "", 6 | "secret": "" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /ports/ports-mgmt/redports-node/pkg-descr: -------------------------------------------------------------------------------- 1 | Redports is a fully hosted continuous integration platform for 2 | testing FreeBSD Ports. redports-node is running on each building 3 | machine which asks for new building jobs from the master and 4 | builds them using poudriere. 5 | 6 | WWW: https://freebsd.github.io/redports/ 7 | -------------------------------------------------------------------------------- /ports/ports-mgmt/redports-node/files/redports-node.json.sample.in: -------------------------------------------------------------------------------- 1 | { 2 | "manifest": "https://api.redports.org/downloads/manifest.json", 3 | "pubkeyhash": "d36e912c47725fdbbd1efcc2d4ecaff7d75b428a3bfaa35d0b124fd663d9ef95", 4 | "server": "https://api.redports.org/", 5 | "machineid": "", 6 | "secret": "" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /web/assets/sass/libs/_vars.scss: -------------------------------------------------------------------------------- 1 | // Misc. 2 | $misc: ( 3 | z-index-base: 10000 4 | ); 5 | 6 | // Duration. 7 | $duration: ( 8 | navPanel: 0.5s 9 | ); 10 | 11 | // Size. 12 | $size: ( 13 | navPanel: 275px 14 | ); 15 | 16 | // Font. 17 | $font: ( 18 | ); 19 | 20 | // Palette. 21 | $palette: ( 22 | ); -------------------------------------------------------------------------------- /ports/ports-mgmt/redports-node/distinfo: -------------------------------------------------------------------------------- 1 | SHA256 (redports-node-0.1.2.phar) = d36e912c47725fdbbd1efcc2d4ecaff7d75b428a3bfaa35d0b124fd663d9ef95 2 | SIZE (redports-node-0.1.2.phar) = 269860 3 | SHA256 (redports-node-0.1.2.phar.pubkey) = 73f4bc7fd6b3318f0adbd35fb125984ffa832c93416ff62ed7ae59bb03097b25 4 | SIZE (redports-node-0.1.2.phar.pubkey) = 451 5 | -------------------------------------------------------------------------------- /web/assets/css/images/ie/grad0-20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/assets/css/ie9.css: -------------------------------------------------------------------------------- 1 | /* 2 | Miniport by HTML5 UP 3 | html5up.net | @n33co 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Button */ 8 | 9 | input[type="button"], 10 | input[type="submit"], 11 | input[type="reset"], 12 | button, 13 | .button { 14 | background-image: url("images/ie/grad0-20.svg"); 15 | } 16 | 17 | /* List */ 18 | 19 | ul.social li a:before { 20 | background-image: url("images/ie/grad0-20.svg"); 21 | } -------------------------------------------------------------------------------- /node/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redports/node", 3 | "description": "A fully hosted continuous integration platform for FreeBSD ports", 4 | "homepage": "https://freebsd.github.io/redports/", 5 | "license": "BSD-2-Clause", 6 | "require": { 7 | "apix/log": "1.0.*", 8 | "amercier/cli-helpers": "1.*", 9 | "herrera-io/phar-update": "~1.0", 10 | "php": ">=5.4.0" 11 | }, 12 | "autoload": { 13 | "psr-0": { 14 | "Redports": "lib" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redports/web", 3 | "description": "A fully hosted continuous integration platform for FreeBSD ports", 4 | "homepage": "https://freebsd.github.io/redports/", 5 | "license": "BSD-2-Clause", 6 | "require": { 7 | "slim/slim": "3.*", 8 | "slim/php-view": "~2.0", 9 | "knplabs/github-api": "~1.4", 10 | "lusitanian/oauth": "~0.3", 11 | "php": ">=5.5.0" 12 | }, 13 | "autoload": { 14 | "psr-0": { 15 | "Redports": "lib" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /master/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redports/master", 3 | "description": "A fully hosted continuous integration platform for FreeBSD ports", 4 | "homepage": "https://freebsd.github.io/redports/", 5 | "license": "BSD-2-Clause", 6 | "require": { 7 | "slim/slim": "2.*", 8 | "knplabs/github-api": "~1.4", 9 | "chrisboulton/php-resque": "dev-master as 1.2.x-dev", 10 | "lusitanian/oauth": "~0.3", 11 | "php": ">=5.4.0" 12 | }, 13 | "autoload": { 14 | "psr-0": { 15 | "Redports": "lib" 16 | }, 17 | "files": ["lib/functions.php"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web/assets/sass/ie9.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | /* 5 | Miniport by HTML5 UP 6 | html5up.net | @n33co 7 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 8 | */ 9 | 10 | /* Button */ 11 | 12 | input[type="button"], 13 | input[type="submit"], 14 | input[type="reset"], 15 | button, 16 | .button { 17 | background-image: url('images/ie/grad0-20.svg'); 18 | } 19 | 20 | /* List */ 21 | 22 | ul.social { 23 | li { 24 | a:before { 25 | background-image: url('images/ie/grad0-20.svg'); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /node/box.json: -------------------------------------------------------------------------------- 1 | { 2 | "alias": "redports-node.phar", 3 | "algorithm": "openssl", 4 | "chmod": "0755", 5 | "directories": ["lib"], 6 | "main": "bin/redports-node", 7 | "output": "redports-node-0.1.2.phar", 8 | "stub": true, 9 | "key": "private.key", 10 | "finder": [ 11 | { 12 | "name": [ "*.php", "*.json"], 13 | "exclude": [ 14 | "phpunit", 15 | "phpunit-test-case", 16 | "Tests", 17 | "tests" 18 | ], 19 | "in": "vendor" 20 | } 21 | ], 22 | "replacements": { 23 | "node_version": "0.1.2" 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Command/Command.php: -------------------------------------------------------------------------------- 1 | execute($options, $arguments, $app); 23 | } 24 | 25 | protected function writeln($line) 26 | { 27 | printf("%s\n", $line); 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /web/assets/js/jquery.scrolly.min.js: -------------------------------------------------------------------------------- 1 | /* jquery.scrolly v1.0.0-dev | (c) n33 | n33.co @n33co | MIT */ 2 | (function(e){function u(s,o){var u,a,f;if((u=e(s))[t]==0)return n;a=u[i]()[r];switch(o.anchor){case"middle":f=a-(e(window).height()-u.outerHeight())/2;break;default:case r:f=Math.max(a,0)}return typeof o[i]=="function"?f-=o[i]():f-=o[i],f}var t="length",n=null,r="top",i="offset",s="click.scrolly",o=e(window);e.fn.scrolly=function(i){var o,a,f,l,c=e(this);if(this[t]==0)return c;if(this[t]>1){for(o=0;odestination = $file; 27 | $this->type = static::FILE; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Command/MainCommand.php: -------------------------------------------------------------------------------- 1 | getAllJails() as $jail) { 18 | if ($jail->getQueue() === null) { 19 | $logger->warning('Ignoring Jail '.$jail->getJailname().' because Queue is not defined'); 20 | } else { 21 | $logger->info('Adding Jail '.$jail->getJailname().' to Queue '.$jail->getQueue()); 22 | $pm->addJail($jail); 23 | } 24 | } 25 | 26 | $pm->run(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/assets/sass/libs/_functions.scss: -------------------------------------------------------------------------------- 1 | /// Gets a duration value. 2 | /// @param {string} $keys Key(s). 3 | /// @return {string} Value. 4 | @function _duration($keys...) { 5 | @return val($duration, $keys...); 6 | } 7 | 8 | /// Gets a font value. 9 | /// @param {string} $keys Key(s). 10 | /// @return {string} Value. 11 | @function _font($keys...) { 12 | @return val($font, $keys...); 13 | } 14 | 15 | /// Gets a misc value. 16 | /// @param {string} $keys Key(s). 17 | /// @return {string} Value. 18 | @function _misc($keys...) { 19 | @return val($misc, $keys...); 20 | } 21 | 22 | /// Gets a palette value. 23 | /// @param {string} $keys Key(s). 24 | /// @return {string} Value. 25 | @function _palette($keys...) { 26 | @return val($palette, $keys...); 27 | } 28 | 29 | /// Gets a size value. 30 | /// @param {string} $keys Key(s). 31 | /// @return {string} Value. 32 | @function _size($keys...) { 33 | @return val($size, $keys...); 34 | } -------------------------------------------------------------------------------- /master/scripts/addmachine: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 8 | * @copyright 2015 Bernhard Froehlich 9 | * @license BSD License (2 Clause) 10 | * @link https://freebsd.github.io/redports/ 11 | */ 12 | 13 | require_once __DIR__.'/../vendor/autoload.php'; 14 | 15 | if($argc != 2) 16 | die("Usage: ".basename($argv[0])." \n"); 17 | 18 | $redis = new Redis(); 19 | $redis->pconnect(\Redports\Master\Config::get('datasource')); 20 | 21 | $machines = new \Redports\Master\Machines(); 22 | 23 | if(!$machines->exists($argv[1])) 24 | { 25 | printf("Creating machine.\n"); 26 | $machine = $machines->createMachine($argv[1]); 27 | } 28 | else 29 | { 30 | printf("Machine already exists.\n"); 31 | $machine = $machines->getMachine($argv[1]); 32 | } 33 | 34 | printf("Machine: %s\n", $machine->getName()); 35 | printf("Token: %s\n", $machine->getToken()); 36 | 37 | -------------------------------------------------------------------------------- /ports/ports-mgmt/redports-node/files/redportsnode.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # $FreeBSD$ 4 | # 5 | # PROVIDE: redportsnode 6 | # REQUIRE: DAEMON 7 | # BEFORE: LOGIN 8 | # KEYWORD: shutdown 9 | # 10 | # Add the following line to /etc/rc.conf[.local] to enable redportsnode 11 | # 12 | # redportsnode_enable (bool): Set to "NO" by default. 13 | # Set it to "YES" to enable redportsnode. 14 | 15 | . /etc/rc.subr 16 | 17 | name=redportsnode 18 | rcvar=redportsnode_enable 19 | 20 | command="%%LOCALBASE%%/bin/redports-node" 21 | command_interpreter="php" 22 | pidfile="/var/run/${name}.pid" 23 | 24 | start_cmd="${name}_start" 25 | 26 | redportsnode_start() 27 | { 28 | local pid 29 | 30 | pid=$(check_pidfile $pidfile $command) 31 | 32 | if [ -n "${pid}" ]; then 33 | echo "${name} already running? (pid=${pid})." 34 | return 1 35 | fi 36 | 37 | echo -n "Starting ${name}" 38 | /usr/sbin/daemon -f -p ${pidfile} ${command} 39 | echo '.' 40 | } 41 | 42 | load_rc_config $name 43 | 44 | : ${redportsnode_enable="NO"} 45 | 46 | run_rc_command "$1" 47 | -------------------------------------------------------------------------------- /web/lib/Redports/Web/Config.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Config 15 | { 16 | protected static $settings = array( 17 | 'https_only' => true, 18 | 'github.oauth.key' => '', 19 | 'github.oauth.secret' => '', 20 | 'github.oauth.redirecturl' => 'https://redports.org/login', 21 | 'slimconfig' => array( 22 | 'settings' => array( 23 | 'displayErrorDetails' => true, 24 | ), 25 | ), 26 | ); 27 | 28 | public static function get($property) 29 | { 30 | if (isset(self::$settings[$property])) { 31 | return self::$settings[$property]; 32 | } 33 | 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /node/bin/redports-node: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 8 | * @copyright 2015 Bernhard Froehlich 9 | * @license BSD License (2 Clause) 10 | * @link https://freebsd.github.io/redports/ 11 | */ 12 | 13 | require_once __DIR__.'/../vendor/autoload.php'; 14 | 15 | use Redports\Node\Config; 16 | use Redports\Node\Command\Command; 17 | use Cli\Helpers\DocumentedScript; 18 | use Cli\Helpers\Parameter; 19 | 20 | date_default_timezone_set('UTC'); 21 | 22 | Config::loadFirst(array('/usr/local/etc/redports-node.json')); 23 | 24 | $app = new DocumentedScript(); 25 | $app->setName('Redports Node') 26 | ->setVersion('@node_version@') 27 | ->setDescription('Redports node client') 28 | ->addParameter(new Parameter('u', 'update', Parameter::VALUE_NO_VALUE), 'Update client to latest release') 29 | ->addParameter(new Parameter('s', 'setup', Parameter::VALUE_NO_VALUE), 'Setup poudriere jails and portstrees') 30 | ->setProgram(array(new Command(), 'execute')) 31 | ->start(); 32 | 33 | -------------------------------------------------------------------------------- /web/templates/repositories.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Repository setup

6 |

Specify which GitHub repositories redports should use.

7 |
8 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![StyleCI](https://styleci.io/repos/32724433/shield)](https://styleci.io/repos/32724433) 2 | 3 | # redports 4 | Redports is a continuous integration platform for FreeBSD ports. 5 | 6 | This is a new attempt to fully automate the redports platform and 7 | to minimize setup and administration effort. This is why we are 8 | heavily depending on GitHub for user accounts, repositories and 9 | just integrate into that workflow. 10 | 11 | Want to help? IRC #redports (freenode) 12 | 13 | 14 | # TODO 15 | 16 | This are the major items before we can do a first release. 17 | 18 | node: 19 | - poudriere integration 20 | - business logic for building and talking to master 21 | (node/lib/Redports/Node/Process/Child.php) 22 | 23 | 24 | ## Cleanup after first release 25 | 26 | master: 27 | - Introduce registering user and repository 28 | - GitHub Webhook: verify repository and use data from registered user 29 | - GitHub Status API Integration for build status response 30 | https://github.com/KnpLabs/php-github-api/blob/master/lib/Github/Api/Repository/Statuses.php 31 | 32 | web: 33 | - register new user at master 34 | - register new repository at master 35 | 36 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Task/TaskNotifyEmail.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class TaskNotifyEmail 15 | { 16 | protected $_db; 17 | protected $_headers = array( 18 | 'From: "redports.org" ', 19 | 'Reply-To: "redports.org" ', 20 | 'Content-type: text/plain; charset=iso-8859-1', 21 | ); 22 | 23 | public function __construct() 24 | { 25 | $this->_db = Config::getDatabaseHandle(); 26 | } 27 | 28 | public function perform() 29 | { 30 | $job = new Job($this->args['jobid']); 31 | $jobdata = $job->getJobData(); 32 | 33 | $to = ''; 34 | $subject = 'Build '.$this->args['action']; 35 | $content = 'Build '.$this->args['action']; 36 | 37 | return mail($to, $subject, $content, implode("\r\n", $this->_headers)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/assets/js/skel-viewport.min.js: -------------------------------------------------------------------------------- 1 | /* skel-viewport.js v3.0.0 | (c) n33 | skel.io | MIT licensed */ 2 | !function(e){"use strict";var t={config:{width:"device-width",height:"",scalable:!0,breakpoints:{}},init:function(i){return e.extend(t.config,i),e.addStateHandler("viewport",t.stateHandler),e.attach(e.newAttachment("mv",t.newViewportMeta("initial-scale=1"),1,!0)),"ie"==e.vars.browser&&e.vars.IEVersion>=10&&(e.attach(e.newAttachment("mVie",e.newStyle("@-ms-viewport{width:device-width}"),1,!0)),window.setTimeout(function(){var e=document.getElementsByTagName("body")[0],t=e.style.height;e.style.height="10000px",window.setTimeout(function(){e.style.height=t},250)},250)),e},newViewportMeta:function(e){var t=document.createElement("meta");return t.name="viewport",t.content=e,t},stateHandler:function(){var i,n,a;return n=e.generateStateConfig({width:t.config.width,height:t.config.height,scalable:t.config.scalable},t.config.breakpoints),a=[],a.push("user-scalable="+(n.scalable?"yes":"no")),n.width&&a.push("width="+n.width),n.height&&a.push("height="+n.height),"device-width"==n.width&&a.push("initial-scale=1"),i=e.newAttachment("mv-"+e.stateId,t.newViewportMeta(a.join(",")),1),[i]}};e.viewport=t.init}(skel); 3 | -------------------------------------------------------------------------------- /web/assets/sass/libs/_mixins.scss: -------------------------------------------------------------------------------- 1 | /// Makes an element's :before pseudoelement a FontAwesome icon. 2 | /// @param {string} $content Optional content value to use. 3 | @mixin icon($content: false) { 4 | 5 | text-decoration: none; 6 | 7 | &:before { 8 | 9 | @if $content { 10 | content: $content; 11 | } 12 | 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-font-smoothing: antialiased; 15 | font-family: FontAwesome; 16 | font-style: normal; 17 | font-weight: normal; 18 | text-transform: none !important; 19 | 20 | } 21 | 22 | } 23 | 24 | /// Applies padding to an element, taking the current element-margin value into account. 25 | /// @param {mixed} $tb Top/bottom padding. 26 | /// @param {mixed} $lr Left/right padding. 27 | /// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left) 28 | /// @param {bool} $important If true, adds !important. 29 | @mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) { 30 | 31 | @if $important { 32 | $important: '!important'; 33 | } 34 | 35 | padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max(0.1em, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important}; 36 | 37 | } -------------------------------------------------------------------------------- /master/lib/functions.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2015 Bernhard Froehlich 8 | * @license BSD License (2 Clause) 9 | * 10 | * @link https://freebsd.github.io/redports/ 11 | */ 12 | function isAllowed() 13 | { 14 | $app = \Slim\Slim::getInstance(); 15 | 16 | if (!\Redports\Master\Session::isAuthenticated()) { 17 | $app->halt(403, 'You are not authenticated'); 18 | } 19 | } 20 | 21 | function jsonResponse($code, $data = array()) 22 | { 23 | $app = \Slim\Slim::getInstance(); 24 | $app->response->setStatus($code); 25 | $app->response->headers->set('Content-Type', 'application/json'); 26 | $app->response->write(json_encode($data)); 27 | 28 | if ($code != 200) { 29 | $app->stop(); 30 | } 31 | 32 | return true; 33 | } 34 | 35 | function textResponse($code, $data = '') 36 | { 37 | $app = \Slim\Slim::getInstance(); 38 | $app->response->setStatus($code); 39 | $app->response->headers->set('Content-Type', 'text/plain'); 40 | $app->response->write($data); 41 | 42 | if ($code != 200) { 43 | $app->stop(); 44 | } 45 | 46 | return true; 47 | } 48 | -------------------------------------------------------------------------------- /web/templates/_header.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | redports.org 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 | 33 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Command/UpdateCommand.php: -------------------------------------------------------------------------------- 1 | writeln('Manifest URL not defined'); 17 | 18 | return 1; 19 | } 20 | 21 | $this->writeln('Checking for updates ...'); 22 | 23 | try { 24 | $manager = new UpdateManager(Manifest::loadFile($manifest)); 25 | 26 | if (Config::get('pubkeyhash') !== false) { 27 | $manager->setPublicKeyHash(Config::get('pubkeyhash')); 28 | } 29 | } catch (FileException $e) { 30 | $this->writeln('Unable to search for updates'); 31 | 32 | return 1; 33 | } 34 | 35 | if ($manager->update($app->version, true)) { 36 | $this->writeln('Updated to latest version'); 37 | } else { 38 | $this->writeln('Already up-to-date'); 39 | } 40 | 41 | return 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /web/templates/_footer.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
7 | 11 |
12 |
13 |
14 |
15 | 18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ports/ports-mgmt/redports-node/Makefile: -------------------------------------------------------------------------------- 1 | # Created by: Bernhard Froehlich 2 | # $FreeBSD$ 3 | 4 | PORTNAME= redports-node 5 | PORTVERSION= 0.1.2 6 | CATEGORIES= ports-mgmt 7 | MASTER_SITES= https://api.redports.org/downloads/ 8 | DISTFILES= ${PORTNAME}-${DISTVERSION}.phar ${PORTNAME}-${DISTVERSION}.phar.pubkey 9 | 10 | MAINTAINER= decke@FreeBSD.org 11 | COMMENT= Redports building node 12 | 13 | LICENSE= BSD2CLAUSE 14 | 15 | RUN_DEPENDS= ${LOCALBASE}/share/certs/ca-root-nss.crt:${PORTSDIR}/security/ca_root_nss \ 16 | poudriere:${PORTSDIR}/ports-mgmt/poudriere \ 17 | ${LOCALBASE}/bin/ccache:${PORTSDIR}/devel/ccache 18 | 19 | NO_BUILD= yes 20 | USE_PHP= curl hash json openssl pcntl phar 21 | USE_RC_SUBR= redportsnode 22 | 23 | SUB_FILES= redports-node.json.sample 24 | 25 | do-extract: 26 | ${MKDIR} ${WRKSRC}/ 27 | ${CP} ${_DISTDIR}/redports-node-${DISTVERSION}.phar ${WRKSRC}/redports-node.phar 28 | ${CP} ${_DISTDIR}/redports-node-${DISTVERSION}.phar.pubkey ${WRKSRC}/redports-node.phar.pubkey 29 | 30 | do-install: 31 | ${INSTALL_SCRIPT} ${WRKSRC}/redports-node.phar ${STAGEDIR}${PREFIX}/bin/ 32 | ${LN} -s redports-node.phar ${STAGEDIR}${PREFIX}/bin/redports-node 33 | ${INSTALL_DATA} ${WRKSRC}/redports-node.phar.pubkey ${STAGEDIR}${PREFIX}/bin/ 34 | ${INSTALL_DATA} ${WRKDIR}/${PORTNAME}.json.sample ${STAGEDIR}${PREFIX}/etc/ 35 | 36 | .include 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Bernhard Fröhlich 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Repositories.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Repositories 15 | { 16 | protected $_db; 17 | 18 | public function __construct() 19 | { 20 | $this->_db = Config::getDatabaseHandle(); 21 | } 22 | 23 | public function addRepository($name, $data) 24 | { 25 | if ($this->_db->sAdd('repositories', $name) != 1) { 26 | return false; 27 | } 28 | 29 | $this->_db->set('repositories:'.$name, json_encode($data)); 30 | 31 | return true; 32 | } 33 | 34 | public function getRepository($name) 35 | { 36 | if (($data = $this->_db->get('repositories:'.$name)) === false) { 37 | return false; 38 | } 39 | 40 | return json_decode($data, true); 41 | } 42 | 43 | public function deleteRepository($name) 44 | { 45 | $this->_db->sRemove('repositories', $name); 46 | $this->_db->delete('repositories:'.$name); 47 | 48 | return true; 49 | } 50 | 51 | public function exists($name) 52 | { 53 | return $this->_db->sIsMember('repositories', $name); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Jails.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Jails 15 | { 16 | protected $_db; 17 | 18 | public function __construct() 19 | { 20 | $this->_db = Config::getDatabaseHandle(); 21 | } 22 | 23 | public function addJail($name, $data) 24 | { 25 | if ($this->_db->sAdd('jails', $name) != 1) { 26 | return false; 27 | } 28 | 29 | $this->_db->set('jails:'.$name, json_encode($data)); 30 | 31 | return true; 32 | } 33 | 34 | public function getJail($name) 35 | { 36 | if (($data = $this->_db->get('jails:'.$name)) === false) { 37 | return false; 38 | } 39 | 40 | return json_decode($data, true); 41 | } 42 | 43 | public function getJails() 44 | { 45 | return $this->_db->sMembers('jails'); 46 | } 47 | 48 | public function deleteJail($name) 49 | { 50 | $this->_db->sRemove('jails', $name); 51 | $this->_db->delete('jails:'.$name); 52 | 53 | return true; 54 | } 55 | 56 | public function exists($name) 57 | { 58 | return $this->_db->sIsMember('jails', $name); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Command/SetupCommand.php: -------------------------------------------------------------------------------- 1 | writeln(sprintf('%-25s%-25s', 'POUDRIERE JAIL', 'REDPORTS QUEUE')); 15 | 16 | foreach ($poudriere->getAllJails() as $jail) { 17 | $queue = $jail->getQueue() ? $jail->getQueue() : '-'; 18 | 19 | $this->writeln(sprintf('%-25s%-25s', $jail->getJailname(), $queue)); 20 | } 21 | 22 | return 0; 23 | } 24 | 25 | public function modifyJail($jailname, $queue) 26 | { 27 | $jail = new Jail($jailname); 28 | 29 | if (strlen($queue) < 1 || $queue == 'none') { 30 | $jail->unsetQueue(); 31 | } else { 32 | $jail->setQueue($queue); 33 | } 34 | 35 | return true; 36 | } 37 | 38 | public function execute($options, $arguments) 39 | { 40 | if (count($arguments) < 3) { 41 | $this->showSetup(); 42 | } else { 43 | array_shift($arguments); 44 | array_shift($arguments); 45 | 46 | foreach ($arguments as $tmp) { 47 | list($jail, $queue) = array_merge(explode('=', $tmp, 2), array('none')); 48 | $this->modifyJail($jail, $queue); 49 | } 50 | } 51 | 52 | return 1; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /web/assets/css/ie8.css: -------------------------------------------------------------------------------- 1 | /* 2 | Miniport by HTML5 UP 3 | html5up.net | @n33co 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Basic */ 8 | 9 | form input[type=text], 10 | form input[type=password], 11 | form select, 12 | form textarea { 13 | position: relative; 14 | -ms-behavior: url("assets/js/ie/PIE.htc"); 15 | } 16 | 17 | input[type="button"], 18 | input[type="submit"], 19 | input[type="reset"], 20 | button, 21 | .button { 22 | position: relative; 23 | -ms-behavior: url("assets/js/ie/PIE.htc"); 24 | } 25 | 26 | /* Section/Article */ 27 | 28 | section > .last-child, 29 | article > .last-child, 30 | section.last-child, 31 | article.last-child { 32 | margin-bottom: 0; 33 | } 34 | 35 | /* Box */ 36 | 37 | .box { 38 | border: solid 1px #ddd; 39 | } 40 | 41 | /* List */ 42 | 43 | ul.social li a { 44 | position: relative; 45 | top: 0 !important; 46 | overflow: hidden; 47 | -ms-behavior: url("assets/js/ie/PIE.htc"); 48 | } 49 | 50 | ul.social li a:before { 51 | background: none; 52 | } 53 | 54 | /* Wrappers */ 55 | 56 | .wrapper { 57 | border-top: solid 1px #ddd; 58 | } 59 | 60 | /* Nav */ 61 | 62 | #nav a { 63 | position: relative; 64 | -ms-behavior: url("assets/js/ie/PIE.htc"); 65 | } 66 | 67 | /* Articles */ 68 | 69 | #top .image { 70 | position: relative; 71 | -ms-behavior: url("assets/js/ie/PIE.htc"); 72 | } 73 | 74 | #top .image img { 75 | position: relative; 76 | -ms-behavior: url("assets/js/ie/PIE.htc"); 77 | } -------------------------------------------------------------------------------- /web/assets/js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Miniport by HTML5 UP 3 | html5up.net | @n33co 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | (function($) { 8 | 9 | skel 10 | .breakpoints({ 11 | desktop: '(min-width: 737px)', 12 | tablet: '(min-width: 737px) and (max-width: 1200px)', 13 | mobile: '(max-width: 736px)' 14 | }) 15 | .viewport({ 16 | breakpoints: { 17 | tablet: { 18 | width: 1080 19 | } 20 | } 21 | }); 22 | 23 | $(function() { 24 | 25 | var $window = $(window), 26 | $body = $('body'); 27 | 28 | // Disable animations/transitions until the page has loaded. 29 | $body.addClass('is-loading'); 30 | 31 | $window.on('load', function() { 32 | $body.removeClass('is-loading'); 33 | }); 34 | 35 | // Fix: Placeholder polyfill. 36 | $('form').placeholder(); 37 | 38 | // Prioritize "important" elements on mobile. 39 | skel.on('+mobile -mobile', function() { 40 | $.prioritize( 41 | '.important\\28 mobile\\29', 42 | skel.breakpoint('mobile').active 43 | ); 44 | }); 45 | 46 | // CSS polyfills (IE<9). 47 | if (skel.vars.IEVersion < 9) 48 | $(':last-child').addClass('last-child'); 49 | 50 | // Scrolly. 51 | $window.load(function() { 52 | 53 | var x = parseInt($('.wrapper').first().css('padding-top')) - 15; 54 | 55 | $('#nav a, .scrolly').scrolly({ 56 | speed: 1000, 57 | offset: x 58 | }); 59 | 60 | }); 61 | 62 | }); 63 | 64 | })(jQuery); -------------------------------------------------------------------------------- /node/Makefile: -------------------------------------------------------------------------------- 1 | # redports node Makefile 2 | 3 | VERSION?=0.1.2 4 | DOWNLOADURL="https://api.redports.org/downloads" 5 | DOWNLOADDIR="downloads/" 6 | 7 | all: build 8 | 9 | clean: 10 | rm -f redports-node-*.phar* manifest.json 11 | 12 | box.phar: 13 | fetch -q -o - http://box-project.github.io/box2/installer.php | php 14 | 15 | private.key: box.phar 16 | php box.phar key:create 17 | 18 | public.key: private.key 19 | php box.phar key:extract private.key 20 | 21 | build: box.phar public.key 22 | php -d phar.readonly=0 box.phar build 23 | 24 | release: build 25 | @printf ' {\n' > manifest.json 26 | @printf ' "name": "%s",\n' redports-node-$(VERSION).phar >> manifest.json 27 | @printf ' "sha1": "%s",\n' `sha1 -q redports-node-$(VERSION).phar` >> manifest.json 28 | @printf ' "url": "%s/redports-node-%s.phar",\n' $(DOWNLOADURL) $(VERSION) >> manifest.json 29 | @printf ' "publicKey": "%s/redports-node-%s.phar.pubkey",\n' $(DOWNLOADURL) $(VERSION) >> manifest.json 30 | @printf ' "version": "%s"\n' $(VERSION) >> manifest.json 31 | @printf ' }\n' >> manifest.json 32 | 33 | mv redports-node-$(VERSION).phar $(DOWNLOADDIR) 34 | mv redports-node-$(VERSION).phar.pubkey $(DOWNLOADDIR) 35 | 36 | tail -r $(DOWNLOADDIR)/manifest.json | tail +3 | tail -r > $(DOWNLOADDIR)/manifest.json.new 37 | @printf ' },\n' >> $(DOWNLOADDIR)/manifest.json.new 38 | cat manifest.json >> $(DOWNLOADDIR)/manifest.json.new 39 | @printf ']' >> $(DOWNLOADDIR)/manifest.json.new 40 | mv $(DOWNLOADDIR)/manifest.json.new $(DOWNLOADDIR)/manifest.json 41 | rm manifest.json 42 | 43 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Client/Client.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2015 Bernhard Froehlich 12 | * @license BSD License (2 Clause) 13 | * 14 | * @link https://freebsd.github.io/redports/ 15 | */ 16 | class Client 17 | { 18 | protected $_conn = null; 19 | 20 | public function __construct($server = null) 21 | { 22 | if ($server == null) { 23 | $server = Config::get('server'); 24 | } 25 | 26 | $this->_conn = new ConnectionManager($server); 27 | } 28 | 29 | public function login($machineid = null, $secret = null) 30 | { 31 | if ($machineid == null) { 32 | $machineid = Config::get('machineid'); 33 | } 34 | 35 | if ($secret == null) { 36 | $secret = Config::get('secret'); 37 | } 38 | 39 | return $this->_conn->call('/auth/', 'POST', 'machineid='.urlencode($machineid).'&secret='.urlencode($secret)); 40 | } 41 | 42 | public function listJails() 43 | { 44 | return $this->_conn->call('/jails/'); 45 | } 46 | 47 | public function takeJob($queue, $jail) 48 | { 49 | return $this->_conn->call('/queues/'.urlencode($queue).'/'.urlencode($jail).'/take'); 50 | } 51 | 52 | public function uploadLog($jobid, $file) 53 | { 54 | return $this->_conn->call('/jobs/'.$jobid.'/logfile/'.urlencode(basename($file)), 'PUT', $file); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Machines.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Machines 15 | { 16 | protected $_db; 17 | 18 | public function __construct() 19 | { 20 | $this->_db = Config::getDatabaseHandle(); 21 | } 22 | 23 | public function createMachine($name) 24 | { 25 | if ($this->exists($name)) { 26 | return false; 27 | } 28 | 29 | $machine = new Machine($name); 30 | $machine->save(); 31 | 32 | $this->addMachine($machine->getName()); 33 | 34 | return $machine; 35 | } 36 | 37 | public function addMachine($name) 38 | { 39 | if ($this->_db->sAdd('machines', $name) != 1) { 40 | return false; 41 | } 42 | 43 | return true; 44 | } 45 | 46 | public function getMachine($name) 47 | { 48 | if (!$this->exists($name)) { 49 | return false; 50 | } 51 | 52 | return new Machine($name); 53 | } 54 | 55 | public function deleteMachine($name) 56 | { 57 | $this->_db->sRemove('machines', $name); 58 | $this->_db->delete('machines:'.$name); 59 | 60 | return true; 61 | } 62 | 63 | public function exists($name) 64 | { 65 | return $this->_db->sIsMember('machines', $name); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/User.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2015 Bernhard Froehlich 11 | * @license BSD License (2 Clause) 12 | * 13 | * @link https://freebsd.github.io/redports/ 14 | */ 15 | class User 16 | { 17 | protected $_db; 18 | protected $_name; 19 | protected $_data = array(); 20 | 21 | public function __construct($name) 22 | { 23 | $this->_db = Config::getDatabaseHandle(); 24 | $this->_name = $name; 25 | 26 | $this->_load(); 27 | } 28 | 29 | public function _load() 30 | { 31 | if ($this->_db->exists('user:'.$this->_name)) { 32 | $this->_data = json_decode($this->_db->get('user:'.$this->_name), true); 33 | } 34 | } 35 | 36 | public function save() 37 | { 38 | $this->_db->set('user:'.$this->_name, json_encode($this->_data)); 39 | } 40 | 41 | public function getUsername() 42 | { 43 | return $this->_name; 44 | } 45 | 46 | public function getOAuthToken() 47 | { 48 | return $this->get('token'); 49 | } 50 | 51 | public function get($key, $default = false) 52 | { 53 | if (isset($this->_data[$key])) { 54 | return $this->_data[$key]; 55 | } 56 | 57 | return $default; 58 | } 59 | 60 | public function set($key, $value) 61 | { 62 | $this->_data[$key] = $value; 63 | 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Task/TaskNotifyIRC.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class TaskNotifyIRC 15 | { 16 | protected $_db; 17 | 18 | public function __construct() 19 | { 20 | $this->_db = Config::getDatabaseHandle(); 21 | } 22 | 23 | /** 24 | * args: 25 | * jobid Job ID 26 | * action Action (started, finished, failed). 27 | */ 28 | public function perform() 29 | { 30 | $token = Config::get('ircbridgetoken'); 31 | $job = new Job($this->args['jobid']); 32 | $jobdata = $job->getJobData(); 33 | 34 | $msg = sprintf('[%%02%s%%0f] (%s) - %s - ', $jobdata['jail'], $jobdata['creator'], $jobdata['port']); 35 | 36 | switch ($this->args['action']) { 37 | case 'started': 38 | $msg .= 'started'; 39 | break; 40 | case 'finished': 41 | $msg .= 'finished ('.$jobdata['buildreason'].')'; 42 | break; 43 | case 'failed': 44 | $msg .= 'failed'; 45 | break; 46 | default: 47 | $msg .= 'unknown'; 48 | break; 49 | } 50 | 51 | if (file_get_contents('https://redportsircbot-bluelife.rhcloud.com/?token='.$token.'&msg='.urlencode($msg)) === false) { 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Config.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Config 15 | { 16 | protected static $settings = array( 17 | 'debug' => true, 18 | 'datasource' => '/var/run/redis/redis.sock', 19 | 'logdir' => __DIR__.'/../../../logs', 20 | 'https_only' => true, 21 | 'ircbridgetoken' => '', 22 | 'github.oauth.key' => '', 23 | 'github.oauth.secret' => '', 24 | 'github.oauth.redirecturl' => 'https://api.redports.org/oauth/login', 25 | 'userconfig' => array( 26 | 'jails' => array('10.1-RELEASE-amd64', '10.1-RELEASE-i386'), 27 | 'notify' => 'commit', /* commit, email, none */ 28 | ), 29 | ); 30 | 31 | public static function get($property) 32 | { 33 | if (isset(self::$settings[$property])) { 34 | return self::$settings[$property]; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | public static function getDatabaseHandle() 41 | { 42 | if (isset($GLOBALS['redis'])) { 43 | return $GLOBALS['redis']; 44 | } else { 45 | $GLOBALS['redis'] = new \Redis(); 46 | $GLOBALS['redis']->pconnect(self::get('datasource')); 47 | 48 | return $GLOBALS['redis']; 49 | } 50 | 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /web/assets/sass/ie8.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | /* 5 | Miniport by HTML5 UP 6 | html5up.net | @n33co 7 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 8 | */ 9 | 10 | /* Basic */ 11 | 12 | form { 13 | input[type=text], 14 | input[type=password], 15 | select, 16 | textarea { 17 | position: relative; 18 | -ms-behavior: url('assets/js/ie/PIE.htc'); 19 | } 20 | } 21 | 22 | input[type="button"], 23 | input[type="submit"], 24 | input[type="reset"], 25 | button, 26 | .button { 27 | position: relative; 28 | -ms-behavior: url('assets/js/ie/PIE.htc'); 29 | } 30 | 31 | /* Section/Article */ 32 | 33 | section > .last-child, 34 | article > .last-child, 35 | section.last-child, 36 | article.last-child { 37 | margin-bottom: 0; 38 | } 39 | 40 | /* Box */ 41 | 42 | .box { 43 | border: solid 1px #ddd; 44 | } 45 | 46 | /* List */ 47 | 48 | ul { 49 | &.social { 50 | li { 51 | a { 52 | position: relative; 53 | top: 0 !important; 54 | overflow: hidden; 55 | -ms-behavior: url('assets/js/ie/PIE.htc'); 56 | 57 | &:before { 58 | background: none; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | /* Wrappers */ 66 | 67 | .wrapper { 68 | border-top: solid 1px #ddd; 69 | } 70 | 71 | /* Nav */ 72 | 73 | #nav { 74 | a { 75 | position: relative; 76 | -ms-behavior: url('assets/js/ie/PIE.htc'); 77 | } 78 | } 79 | 80 | /* Articles */ 81 | 82 | #top { 83 | .image { 84 | position: relative; 85 | -ms-behavior: url('assets/js/ie/PIE.htc'); 86 | 87 | img { 88 | position: relative; 89 | -ms-behavior: url('assets/js/ie/PIE.htc'); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /node/lib/Redports/Node/Poudriere/Poudriere.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2015 Bernhard Froehlich 11 | * @license BSD License (2 Clause) 12 | * 13 | * @link https://freebsd.github.io/redports/ 14 | */ 15 | class Poudriere 16 | { 17 | protected $binpath = '/usr/local/bin/poudriere'; 18 | 19 | public function __construct() 20 | { 21 | if (!file_exists($this->binpath)) { 22 | trigger_error('Poudriere binary '.$this->binpath.' does not exist!', E_USER_ERROR); 23 | } 24 | } 25 | 26 | public function getAllJails() 27 | { 28 | $jails = array(); 29 | 30 | exec(sprintf('%s jail -l -q', $this->binpath), $output); 31 | 32 | foreach ($output as $line) { 33 | $parts = preg_split('/\s+/', $line); 34 | 35 | $jails[] = new Jail($parts[0]); 36 | } 37 | 38 | return $jails; 39 | } 40 | 41 | public function getAllPortstrees() 42 | { 43 | $portstrees = array(); 44 | 45 | exec(sprintf('%s ports -l -q', $this->binpath), $output); 46 | 47 | foreach ($output as $line) { 48 | $parts = preg_split('/\s+/', $line); 49 | 50 | $portstrees[] = new Portstree($parts[0]); 51 | } 52 | 53 | return $portstrees; 54 | } 55 | 56 | public function createJail($name, $version, $arch) 57 | { 58 | exec(sprintf('%s jail -c -j %s -v %s -a %s', $name, $version, $arch), $output); 59 | 60 | return $output; 61 | } 62 | 63 | public function createPortstree($name) 64 | { 65 | exec(sprintf('%s ports -c -p %s', $this->binpath, $name), $output); 66 | 67 | return $output; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Jobgroup.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Jobgroup 15 | { 16 | protected $_db; 17 | protected $_groupid; 18 | 19 | public function __construct($groupid) 20 | { 21 | $this->_db = Config::getDatabaseHandle(); 22 | $this->_groupid = $groupid; 23 | } 24 | 25 | public function getJobgroupId() 26 | { 27 | return $this->_groupid; 28 | } 29 | 30 | public function addJob($jobid) 31 | { 32 | if (!$this->_db->exists('jobs:'.$jobid)) { 33 | return false; 34 | } 35 | 36 | if ($this->_db->sAdd('jobgroup:'.$this->getJobgroupId(), $jobid) != 1) { 37 | return false; 38 | } 39 | 40 | return true; 41 | } 42 | 43 | public function countJobs() 44 | { 45 | return $this->_db->sSize('jobgroup:'.$this->getJobgroupId()); 46 | } 47 | 48 | public function getJobs() 49 | { 50 | return $this->_db->sMembers('jobgroup:'.$this->getJobgroupId()); 51 | } 52 | 53 | public function deleteJobgroup() 54 | { 55 | $this->_db->delete('jobgroup:'.$this->getJobgroupId()); 56 | 57 | return true; 58 | } 59 | 60 | public function exists($groupid = null) 61 | { 62 | if (is_null($groupid)) { 63 | $groupid = $this->_groupid; 64 | } 65 | 66 | return $this->_db->exists('jobgroup:'.$groupid); 67 | } 68 | 69 | public function getGroupInfo() 70 | { 71 | if (!$this->exists()) { 72 | return false; 73 | } 74 | 75 | $data = array( 76 | 'groupname' => $this->getJobgroupId(), 77 | 'jobs' => $jobgroup->getJobs(), 78 | ); 79 | 80 | return $data; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Poudriere/Portstree.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Portstree 15 | { 16 | protected $binpath = '/usr/local/bin/poudriere'; 17 | 18 | protected $_portstreename; 19 | protected $_method; 20 | protected $_path; 21 | protected $_updated; 22 | 23 | public function __construct($name) 24 | { 25 | $this->_load($name); 26 | } 27 | 28 | protected function _load($name) 29 | { 30 | exec(sprintf('%s ports -l -q', $this->binpath), $output, $result); 31 | 32 | if ($result != 0) { 33 | return false; 34 | } 35 | 36 | foreach ($output as $line) { 37 | $parts = preg_split('/\s+/', $line); 38 | 39 | if ($parts[0] != $name) { 40 | continue; 41 | } 42 | 43 | $this->_portstreename = $parts[0]; 44 | $this->_method = $parts[1]; 45 | $this->_updated = $parts[2].' '.$parts[3]; 46 | $this->_path = $parts[4]; 47 | 48 | return true; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | public function update() 55 | { 56 | exec(sprintf('%s ports -u -p %s', $this->binpath, $this->portstreename)); 57 | 58 | return true; 59 | } 60 | 61 | public function getPortstreename() 62 | { 63 | return $this->_portstreename; 64 | } 65 | 66 | public function getMethod() 67 | { 68 | return $this->_method; 69 | } 70 | 71 | public function getPath() 72 | { 73 | return $this->_path; 74 | } 75 | 76 | public function getUpdated($raw = false) 77 | { 78 | if ($raw) { 79 | return $this->_updated; 80 | } 81 | 82 | return strtotime($this->_updated); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /web/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 |
11 |

Redports

12 |
13 |

Your fully hosted continuous integration platform for FreeBSD Ports.

14 | Sign up with GitHub 15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 |
24 |

Automate testing your FreeBSD Ports

25 |

For Port Maintainers and FreeBSD Committers

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

GitHub integration

33 |

Builds and integrates into your GitHub workflow. No separate account needed.

34 |
35 |
36 |
37 |
38 | 39 |

Hardware

40 |

We have powerful hardware available that is optimized for that job and runs your builds.

41 |
42 |
43 |
44 |
45 | 46 |

Fully automated

47 |

Testing cannot be easier. You commit. We tell you the build results.

48 |
49 |
50 |
51 |
52 |
53 |

Give it a try and let us show you how to improve your own FreeBSD Ports contributions.

54 |
55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Config.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Config 15 | { 16 | protected static $settings = array( 17 | 'manifest' => false, 18 | 'pubkeyhash' => false, 19 | 'server' => false, 20 | 'machineid' => '', 21 | 'secret' => '', 22 | 'logfile' => '/var/log/redports-node.log', 23 | 'loglevel' => 'info', 24 | ); 25 | 26 | protected static $logger = null; 27 | 28 | public static function loadFirst($files) 29 | { 30 | foreach ($files as $file) { 31 | if (self::load($file) === true) { 32 | return true; 33 | } 34 | } 35 | 36 | return false; 37 | } 38 | 39 | public static function load($file) 40 | { 41 | if (!file_exists($file)) { 42 | return false; 43 | } 44 | 45 | $content = file_get_contents($file); 46 | $json = json_decode($content, true); 47 | if ($json == null) { 48 | return false; 49 | } 50 | 51 | self::$settings = array_merge(self::$settings, $json); 52 | 53 | return true; 54 | } 55 | 56 | public static function get($property) 57 | { 58 | if (isset(self::$settings[$property])) { 59 | return self::$settings[$property]; 60 | } 61 | 62 | return false; 63 | } 64 | 65 | public static function getLogger() 66 | { 67 | if (self::$logger == null) { 68 | $file = new \Apix\Log\Logger\File(self::get('logfile')); 69 | $file->setMinLevel(self::get('loglevel')); 70 | 71 | $stdout = new \Redports\Node\Logger\Stdout(); 72 | $stdout->setMinLevel(self::get('loglevel')); 73 | 74 | self::$logger = new \Apix\Log\Logger(); 75 | self::$logger->add($file); 76 | self::$logger->add($stdout); 77 | } 78 | 79 | return self::$logger; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /web/assets/js/ie/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d 10 | * @copyright 2015 Bernhard Froehlich 11 | * @license BSD License (2 Clause) 12 | * 13 | * @link https://freebsd.github.io/redports/ 14 | */ 15 | class Machine 16 | { 17 | protected $_db; 18 | protected $_name; 19 | protected $_data = array(); 20 | 21 | public function __construct($name) 22 | { 23 | $this->_db = Config::getDatabaseHandle(); 24 | $this->_name = $name; 25 | 26 | $this->_load(); 27 | } 28 | 29 | public function _load() 30 | { 31 | if ($this->_db->exists('machines:'.$this->_name)) { 32 | $this->_data = json_decode($this->_db->get('machines:'.$this->_name), true); 33 | } else { 34 | $this->_data['token'] = $this->generateToken(); 35 | } 36 | } 37 | 38 | public function save() 39 | { 40 | $this->_db->set('machines:'.$this->_name, json_encode($this->_data)); 41 | } 42 | 43 | public function getName() 44 | { 45 | return $this->_name; 46 | } 47 | 48 | public function getToken() 49 | { 50 | return $this->_data['token']; 51 | } 52 | 53 | public function addJob($jobid) 54 | { 55 | if ($this->_db->sAdd('machinejobs:'.$this->_name, $jobid) != 1) { 56 | return false; 57 | } 58 | 59 | return true; 60 | } 61 | 62 | public function releaseJob($jobid) 63 | { 64 | if ($this->_db->sRemove('machinejobs:'.$this->_name, $jobid) != 1) { 65 | return false; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | public function hasJob($jobid) 72 | { 73 | return $this->_db->sIsMember('machinejobs:'.$this->_name, $jobid); 74 | } 75 | 76 | public function getAllJobs() 77 | { 78 | return $this->_db->sMembers('machinejobs:'.$this->_name); 79 | } 80 | 81 | protected function generateToken() 82 | { 83 | $cstrong = true; 84 | $bytes = ''; 85 | 86 | for ($i = 0; $i <= 32; $i++) { 87 | $bytes .= bin2hex(openssl_random_pseudo_bytes(8, $cstrong)); 88 | } 89 | 90 | return hash('sha256', $bytes); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Queue.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Queue 15 | { 16 | protected $_db; 17 | protected $_queue; 18 | protected $_jail; 19 | protected $_queues = array('preparequeue', 'waitqueue', 'runqueue', 'archivequeue'); 20 | 21 | public function __construct($queue, $jail) 22 | { 23 | $this->_db = Config::getDatabaseHandle(); 24 | 25 | if (in_array($queue, $this->_queues)) { 26 | $this->_queue = $queue; 27 | } else { 28 | $this->_queue = $this->_queues[0]; 29 | } 30 | 31 | $jails = new Jails(); 32 | if ($jails->exists($jail)) { 33 | $this->_jail = $jail; 34 | } else { 35 | trigger_error(E_USER_ERROR, 'Jail is unknown'); 36 | } 37 | } 38 | 39 | public function createJob($data, $jobgroup = null) 40 | { 41 | $data['queue'] = $this->_queue; 42 | $data['jail'] = $this->_jail; 43 | 44 | $job = new Job(); 45 | if ($job->setJobData($data) !== true) { 46 | return false; 47 | } 48 | 49 | if ($job->save() !== true) { 50 | return false; 51 | } 52 | 53 | if ($jobgroup == null) { 54 | return true; 55 | } 56 | 57 | return $jobgroup->addJob($job->getJobId()); 58 | } 59 | 60 | public function getFullQueue() 61 | { 62 | return $this->_queue.':'.$this->_jail; 63 | } 64 | 65 | public function getNextJob() 66 | { 67 | $jobid = $this->_db->zRangeByScore($this->getFullQueue(), 68 | '-inf', '+inf', array('limit' => array(0, 1))); 69 | 70 | if (count($jobid) < 1) { 71 | return false; 72 | } 73 | 74 | return new Job($jobid); 75 | } 76 | 77 | public function countJobs() 78 | { 79 | return $this->_db->zSize($this->getFullQueue()); 80 | } 81 | 82 | public function exists() 83 | { 84 | return $this->_db->exists($this->getFullQueue()); 85 | } 86 | 87 | public function getQueueInfo() 88 | { 89 | if (!$this->exists()) { 90 | return false; 91 | } 92 | 93 | $data = array( 94 | 'numjobs' => $this->countJobs(), 95 | 'jail' => $this->_jail, 96 | 'queue' => $this->_queue, 97 | ); 98 | 99 | return $data; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Update/UpdateManager.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class UpdateManager extends Manager 16 | { 17 | /** 18 | * SHA256 hash of public key. 19 | * 20 | * @var string 21 | */ 22 | private $publicKeyHash; 23 | 24 | /** 25 | * Sets the update manifest. 26 | * 27 | * @param Manifest $manifest The manifest. 28 | */ 29 | public function __construct(Manifest $manifest) 30 | { 31 | parent::__construct($manifest); 32 | } 33 | 34 | /** 35 | * Returns the SHA256 hash of the publicKey file. 36 | * 37 | * @return string SHA256 hash. 38 | */ 39 | public function getPublicKeyHash() 40 | { 41 | return $this->publicKeyHash; 42 | } 43 | 44 | /** 45 | * Sets the SHA256 hash of the publicKey file. 46 | * 47 | * @param string $file SHA256 hash for publicKey 48 | * 49 | * @throws Exception\Exception 50 | * @throws InvalidArgumentException If the hash is invalid. 51 | */ 52 | public function setPublicKeyHash($hash) 53 | { 54 | if (strlen($hash) != 64) { 55 | throw InvalidArgumentException::create( 56 | 'The hash has an invalid length' 57 | ); 58 | } 59 | 60 | $this->publicKeyHash = $hash; 61 | } 62 | 63 | /** 64 | * Updates the running Phar if any is available and checks 65 | * the fingerprint of the public key. 66 | * 67 | * @param string|Version $version The current version. 68 | * @param bool $major Lock to current major version? 69 | * @param bool $pre Allow pre-releases? 70 | * 71 | * @return bool TRUE if an update was performed, FALSE if none available. 72 | */ 73 | public function update($version, $major = false, $pre = false) 74 | { 75 | if (false === ($version instanceof Version)) { 76 | $version = Version::create($version); 77 | } 78 | 79 | if (null !== ($update = $this->getManifest()->findRecent( 80 | $version, 81 | $major, 82 | $pre 83 | ))) { 84 | $tmpfile = $update->getFile(); 85 | 86 | if (null !== $this->getPublicKeyHash()) { 87 | if (false === is_file($tmpfile.'.pubkey')) { 88 | echo "ALERT: Update not signed with public key!\n"; 89 | $update->deleteFile(); 90 | 91 | return false; 92 | } 93 | 94 | if (hash_file('sha256', $tmpfile.'.pubkey') !== $this->getPublicKeyHash()) { 95 | echo "ALERT: Public key fingerprint mismatch!!!\n"; 96 | $update->deleteFile(); 97 | 98 | return false; 99 | } 100 | } 101 | 102 | $update->copyTo($this->getRunningFile()); 103 | 104 | return true; 105 | } 106 | 107 | return false; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Task/TaskPreparePortstree.php: -------------------------------------------------------------------------------- 1 | 13 | * @copyright 2015 Bernhard Froehlich 14 | * @license BSD License (2 Clause) 15 | * 16 | * @link https://freebsd.github.io/redports/ 17 | */ 18 | class TaskPreparePortstree 19 | { 20 | protected $_db; 21 | 22 | public function __construct() 23 | { 24 | $this->_db = Config::getDatabaseHandle(); 25 | } 26 | 27 | public function tempdir($prefix) 28 | { 29 | $tmpfile = tempnam(sys_get_temp_dir(), $prefix); 30 | 31 | if (file_exists($tmpfile)) { 32 | unlink($tmpfile); 33 | } 34 | 35 | mkdir($tmpfile); 36 | 37 | if (is_dir($tmpfile)) { 38 | return $tmpfile; 39 | } 40 | 41 | return; 42 | } 43 | 44 | protected function downloadFile($url, $file) 45 | { 46 | $fr = fopen($url, 'r'); 47 | $fw = fopen($file, 'w'); 48 | 49 | if (stream_copy_to_stream($fr, $fw) < 1) { 50 | return; 51 | } 52 | 53 | fclose($fw); 54 | fclose($fr); 55 | 56 | return true; 57 | } 58 | 59 | protected function setArchiveForJobgroup($jobgroupid, $uri) 60 | { 61 | $jobgroup = new Jobgroup($jobgroupid); 62 | 63 | foreach ($jobgroup->getJobs() as $jobid) { 64 | $job = new Job($jobid); 65 | if ($job->set('portsoverlay', $uri) !== true) { 66 | return false; 67 | } 68 | 69 | if ($job->moveToQueue('waitqueue') !== true) { 70 | return false; 71 | } 72 | } 73 | 74 | return true; 75 | } 76 | 77 | public function perform() 78 | { 79 | $targetdir = Config::get('logdir').'/'.$this->args['jobgroup'].'/'; 80 | $targeturi = sprintf('%s/%s/portstree-%s.tar.xz', basename(Config::get('logdir')), $this->args['jobgroup'], $this->args['commit']); 81 | $targetfile = sprintf('%s/%s', $targetdir, basename($targeturi)); 82 | 83 | $tmpdir = $this->tempdir('php-'); 84 | $tmpfile = $tmpdir.'/portstree.tar.gz'; 85 | 86 | if (file_exists($targetfile)) { 87 | return $this->setArchiveForJobgroup($this->args['jobgroup'], $targeturi); 88 | } 89 | 90 | if ($this->downloadFile($this->args['repository'].'/archive/'.$this->args['commit'].'.tar.gz', $tmpfile) !== true) { 91 | return false; 92 | } 93 | 94 | chdir($tmpdir); 95 | 96 | mkdir($tmpdir.'/ports/'); 97 | exec('/usr/bin/tar xf '.$tmpfile.' -C '.$tmpdir.'/ports/ --strip-components 1'); 98 | unlink($tmpfile); 99 | 100 | exec('rm -rf '.$tmpdir.'/ports/Mk'); 101 | 102 | if (!file_exists($targetdir)) { 103 | mkdir($targetdir); 104 | } 105 | 106 | exec('/usr/bin/tar cfJ '.$targetfile.' ports'); 107 | 108 | exec('rm -rf '.$tmpdir); 109 | 110 | if ($this->setArchiveForJobgroup($this->args['jobgroup'], $targeturi) !== true) { 111 | return false; 112 | } 113 | 114 | return true; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /web/lib/Redports/Web/Session.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Session 15 | { 16 | public function __construct() 17 | { 18 | self::initialize(); 19 | 20 | if (isset($_COOKIE['SESSIONID'])) { 21 | session_start(); 22 | 23 | if (isset($_SESSION['loginip']) && $_SESSION['loginip'] != $_SERVER['REMOTE_ADDR']) { 24 | $this->logout(); 25 | } 26 | } 27 | } 28 | 29 | public static function initialize() 30 | { 31 | // Set Redis as session handler 32 | ini_set('session.save_handler', 'redis'); 33 | ini_set('session.save_path', 'unix:///var/run/redis/redis.sock?persistent=1'); 34 | 35 | // Specify hash function used for session ids. Usually does not 36 | // work on FreeBSD unless hash functions are compiled into the binary 37 | //ini_set('session.hash_function', 'sha256'); 38 | ini_set('session.hash_bits_per_character', 5); 39 | ini_set('session.entropy_length', 512); 40 | 41 | // Set session lifetime in redis (8h) 42 | ini_set('session.gc_maxlifetime', 28800); 43 | 44 | // Set cookie lifetime on client 45 | ini_set('session.cookie_lifetime', 0); 46 | 47 | // do not expose Cookie value to JavaScript (enforced by browser) 48 | ini_set('session.cookie_httponly', 1); 49 | 50 | if (Config::get('https_only') === true) { 51 | // only send cookie over https 52 | ini_set('session.cookie_secure', 1); 53 | } 54 | 55 | // prevent caching by sending no-cache header 56 | session_cache_limiter('nocache'); 57 | 58 | // rename session 59 | session_name('SESSIONID'); 60 | } 61 | 62 | public static function getSessionId() 63 | { 64 | return session_id(); 65 | } 66 | 67 | public static function login($username) 68 | { 69 | if (session_id() === '') { 70 | session_start(); 71 | } 72 | 73 | /* login assumed to be successfull */ 74 | $_SESSION['authenticated'] = true; 75 | $_SESSION['username'] = $username; 76 | $_SESSION['loginip'] = $_SERVER['REMOTE_ADDR']; 77 | $_SESSION['useragent'] = $_SERVER['HTTP_USER_AGENT']; 78 | 79 | return true; 80 | } 81 | 82 | public static function getUsername() 83 | { 84 | if (isset($_SESSION['username'])) { 85 | return $_SESSION['username']; 86 | } 87 | 88 | return false; 89 | } 90 | 91 | public static function isAuthenticated() 92 | { 93 | return isset($_SESSION['authenticated']); 94 | } 95 | 96 | public static function logout() 97 | { 98 | $_SESSION = array(); 99 | 100 | /* also destroy session cookie on client */ 101 | $params = session_get_cookie_params(); 102 | setcookie(session_name(), '', time() - 42000, 103 | $params['path'], $params['domain'], 104 | $params['secure'], $params['httponly'] 105 | ); 106 | 107 | session_destroy(); 108 | 109 | return true; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Process/Child.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2015 Bernhard Froehlich 12 | * @license BSD License (2 Clause) 13 | * 14 | * @link https://freebsd.github.io/redports/ 15 | */ 16 | class Child 17 | { 18 | protected $_client; 19 | protected $_jail; 20 | protected $_log; 21 | 22 | protected $_job; 23 | 24 | public function __construct($client, $jail) 25 | { 26 | $this->_client = $client; 27 | $this->_jail = $jail; 28 | $this->_log = Config::getLogger(); 29 | } 30 | 31 | public function updatePortstree() 32 | { 33 | $portstree = $this->_jail->getPortstree(); 34 | 35 | if (time() - $portstree->getUpdated() > 60 * 60 * 24) { 36 | $this->_log->info('Updating portstree for '.$portstree->getPortstreename()); 37 | $portstree->update(); 38 | } 39 | 40 | return true; 41 | } 42 | 43 | public function getNextJob() 44 | { 45 | $res = $this->_client->takeJob('waitqueue', $this->_jail->getQueue()); 46 | 47 | if ($res['http_code'] == 204) { 48 | $this->_log->info('No job in queue'); 49 | 50 | return false; 51 | } 52 | 53 | if ($res['http_code'] != 200) { 54 | $this->_log->error('Got invalid response from server', $res); 55 | 56 | return false; 57 | } 58 | 59 | $this->_log->info('Got new Job for '.$this->_jail->getJailname()); 60 | 61 | $this->_job = json_decode($res['body'], true); 62 | 63 | return true; 64 | } 65 | 66 | protected function downloadFile($url, $file) 67 | { 68 | $fr = fopen($url, 'r'); 69 | $fw = fopen($file, 'w'); 70 | 71 | if (stream_copy_to_stream($fr, $fw) < 1) { 72 | return false; 73 | } 74 | 75 | fclose($fw); 76 | fclose($fr); 77 | 78 | return true; 79 | } 80 | 81 | public function preparePortstree() 82 | { 83 | /* TODO: zfs snapshot */ 84 | 85 | $overlay = $this->_jail->getFilesystem().'/portsoverlay.tar.gz'; 86 | if(!$this->downloadFile($this->_job['portsoverlay'], $overlay)) 87 | return false; 88 | 89 | /* TODO: apply overlay */ 90 | 91 | return false; 92 | } 93 | 94 | public function bulkBuild() 95 | { 96 | /* TODO: poudriere bulk */ 97 | return false; 98 | } 99 | 100 | public function uploadBuild() 101 | { 102 | /* TODO: get buildresult, upload logfile, finish job */ 103 | return false; 104 | } 105 | 106 | public function cleanupBuild() 107 | { 108 | /* TODO: zfs rollback, remove tainted packages */ 109 | return false; 110 | } 111 | 112 | public function run() 113 | { 114 | if (!$this->updatePortstree()) { 115 | return false; 116 | } 117 | 118 | if (!$this->getNextJob()) { 119 | return false; 120 | } 121 | 122 | if (!$this->preparePortstree()) { 123 | return false; 124 | } 125 | 126 | $this->bulkBuild() && $this->uploadBuild(); 127 | 128 | if (!$this->cleanupBuild()) { 129 | return false; 130 | } 131 | 132 | return true; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Client/ConnectionManager.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2015 Bernhard Froehlich 11 | * @license BSD License (2 Clause) 12 | * 13 | * @link https://freebsd.github.io/redports/ 14 | */ 15 | class ConnectionManager 16 | { 17 | const USERAGENT = 'redports-node/@node_version@'; 18 | const CONNECTTIMEOUT = 3; 19 | const TIMEOUT = 30; 20 | 21 | protected $_server = null; 22 | protected $_handle = null; 23 | protected $_verifypeer = true; 24 | 25 | public function __construct($server) 26 | { 27 | if (!function_exists('curl_init')) { 28 | trigger_error('curl extension not loaded!', E_USER_ERROR); 29 | } 30 | 31 | $this->_handle = curl_init(); 32 | $this->_setServer($server); 33 | } 34 | 35 | public function __destruct() 36 | { 37 | if ($this->_handle != null) { 38 | curl_close($this->_handle); 39 | $this->_handle = null; 40 | } 41 | } 42 | 43 | protected function _setServer($server) 44 | { 45 | $this->_server = rtrim($server, '/'); 46 | $this->_verifypeer = (substr($server, 0, 5) == 'https'); 47 | 48 | return true; 49 | } 50 | 51 | public function getServer() 52 | { 53 | return $this->_server; 54 | } 55 | 56 | public function call($uri, $method = 'GET', $content = null) 57 | { 58 | $url = $this->getServer().'/'.ltrim($uri, '/'); 59 | 60 | curl_reset($this->_handle); 61 | curl_setopt($this->_handle, CURLOPT_RETURNTRANSFER, 1); 62 | curl_setopt($this->_handle, CURLOPT_HEADER, 1); 63 | curl_setopt($this->_handle, CURLOPT_URL, $url); 64 | curl_setopt($this->_handle, CURLOPT_USERAGENT, self::USERAGENT); 65 | curl_setopt($this->_handle, CURLOPT_CONNECTTIMEOUT, self::CONNECTTIMEOUT); 66 | curl_setopt($this->_handle, CURLOPT_TIMEOUT, self::TIMEOUT); 67 | curl_setopt($this->_handle, CURLOPT_SSL_VERIFYPEER, $this->_verifypeer); 68 | curl_setopt($this->_handle, CURLOPT_COOKIEFILE, ''); 69 | curl_setopt($this->_handle, CURLINFO_HEADER_OUT, true); 70 | 71 | if ($method == 'GET') { 72 | curl_setopt($this->_handle, CURLOPT_HTTPGET, 1); 73 | } elseif ($method == 'POST') { 74 | curl_setopt($this->_handle, CURLOPT_POST, 1); 75 | curl_setopt($this->_handle, CURLOPT_POSTFIELDS, $content); 76 | } elseif ($method == 'PUT') { 77 | curl_setopt($this->_handle, CURLOPT_PUT, 1); 78 | curl_setopt($this->_handle, CURLOPT_INFILE, $content); 79 | curl_setopt($this->_handle, CURLOPT_INFILESIZE, filesize($content)); 80 | } else { 81 | return false; 82 | } 83 | 84 | $res = curl_exec($this->_handle); 85 | if ($res === false) { 86 | return false; 87 | } 88 | 89 | $data = array( 90 | 'url' => curl_getinfo($this->_handle, CURLINFO_EFFECTIVE_URL), 91 | 'http_code' => curl_getinfo($this->_handle, CURLINFO_HTTP_CODE), 92 | 'total_time' => curl_getinfo($this->_handle, CURLINFO_TOTAL_TIME), 93 | 'request_header' => explode("\r\n", curl_getinfo($this->_handle, CURLINFO_HEADER_OUT)), 94 | 'header' => explode("\r\n", substr($res, 0, curl_getinfo($this->_handle, CURLINFO_HEADER_SIZE))), 95 | 'body' => substr($res, curl_getinfo($this->_handle, CURLINFO_HEADER_SIZE)), 96 | ); 97 | 98 | return $data; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Poudriere/Jail.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Jail 15 | { 16 | protected $binpath = '/usr/local/bin/poudriere'; 17 | 18 | protected $_jailname; 19 | protected $_version; 20 | protected $_arch; 21 | protected $_method; 22 | protected $_path; 23 | protected $_fs; 24 | protected $_updated; 25 | protected $_queue; 26 | 27 | public function __construct($jailname) 28 | { 29 | $this->_load($jailname); 30 | } 31 | 32 | protected function _load($jailname) 33 | { 34 | exec(sprintf('%s jail -i -j %s', $this->binpath, $jailname), $output, $retval); 35 | 36 | if ($retval != 0 || count($output) < 1) { 37 | return false; 38 | } 39 | 40 | foreach ($output as $line) { 41 | $parts = explode(':', $line, 2); 42 | $tmp = explode(' ', $parts[0], 2); 43 | 44 | $key = trim($tmp[0].' '.$tmp[1]); 45 | $value = trim($parts[1]); 46 | 47 | switch ($key) { 48 | case 'Jail name': 49 | $this->_jailname = $value; 50 | break; 51 | case 'Jail version': 52 | $this->_version = $value; 53 | break; 54 | case 'Jail arch': 55 | $this->_arch = $value; 56 | break; 57 | case 'Jail method': 58 | $this->_method = $value; 59 | break; 60 | case 'Jail mount': 61 | $this->_path = $value; 62 | break; 63 | case 'Jail fs': 64 | $this->_fs = $value; 65 | break; 66 | case 'Jail updated': 67 | $this->_updated = $value; 68 | break; 69 | } 70 | } 71 | 72 | unset($output); 73 | exec(sprintf('zfs get -o value redports:queue %s', $this->_fs), $output, $retval); 74 | 75 | if ($retval == 0 && count($output) == 2) { 76 | $value = trim($output[1]); 77 | 78 | if ($value != '-' && $value != 'none') { 79 | $this->_queue = $value; 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | public function setQueue($queue) 87 | { 88 | if (!$this->_fs) { 89 | return false; 90 | } 91 | 92 | exec(sprintf('zfs set redports:queue=%s %s', $queue, $this->_fs)); 93 | 94 | return true; 95 | } 96 | 97 | public function unsetQueue() 98 | { 99 | if (!$this->_fs) { 100 | return false; 101 | } 102 | 103 | exec(sprintf('zfs inherit -Sr redports:queue %s', $this->_fs)); 104 | 105 | return true; 106 | } 107 | 108 | public function getJailname() 109 | { 110 | return $this->_jailname; 111 | } 112 | 113 | public function getVersion() 114 | { 115 | return $this->_version; 116 | } 117 | 118 | public function getArch() 119 | { 120 | return $this->_arch; 121 | } 122 | 123 | public function getMethod() 124 | { 125 | return $this->_method; 126 | } 127 | 128 | public function getPath() 129 | { 130 | return $this->_path; 131 | } 132 | 133 | public function getFilesystem() 134 | { 135 | return $this->_fs; 136 | } 137 | 138 | public function getUpdated() 139 | { 140 | return $this->_updated; 141 | } 142 | 143 | public function getQueue() 144 | { 145 | return $this->_queue; 146 | } 147 | 148 | public function getPortstree() 149 | { 150 | return new Portstree($this->_jailname); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /web/assets/js/ie/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill 2 | * Copyright 2014 Scott Jehl 3 | * Licensed under MIT 4 | * http://j.mp/respondjs */ 5 | 6 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Job 15 | { 16 | protected $_db; 17 | protected $_jobid = null; 18 | protected $_data = null; 19 | protected $_queues = array('preparequeue', 'waitqueue', 'runqueue', 'archivequeue'); 20 | 21 | public function __construct($jobid = null) 22 | { 23 | $this->_db = Config::getDatabaseHandle(); 24 | 25 | $this->_jobid = $jobid; 26 | $this->_load(); 27 | } 28 | 29 | public function exists() 30 | { 31 | if ($this->_jobid == null) { 32 | return false; 33 | } 34 | 35 | return $this->_db->exists('jobs:'.$this->_jobid); 36 | } 37 | 38 | public function _load() 39 | { 40 | if ($this->exists()) { 41 | $this->_data = json_decode($this->_db->get('jobs:'.$this->_jobid), true); 42 | 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | 49 | public function save() 50 | { 51 | if ($this->_jobid == null) { 52 | $this->_jobid = $this->_db->incr('sequ:jobs'); 53 | $this->_data['jobid'] = $this->_jobid; 54 | 55 | $this->_db->sAdd('alljobs', $this->_jobid); 56 | $this->_db->sAdd('repojobs:'.$this->getRepository(), $this->_jobid); 57 | $this->_db->zAdd($this->getFullQueue(), $this->getPriority(), $this->_jobid); 58 | } 59 | 60 | return $this->_db->set('jobs:'.$this->_jobid, json_encode($this->_data)); 61 | } 62 | 63 | public function getJobId() 64 | { 65 | return $this->_jobid; 66 | } 67 | 68 | public function getFullQueue() 69 | { 70 | return $this->_data['queue'].':'.$this->_data['jail']; 71 | } 72 | 73 | public function getQueue() 74 | { 75 | return $this->_data['queue']; 76 | } 77 | 78 | public function getRepository() 79 | { 80 | return $this->_data['repository']['url']; 81 | } 82 | 83 | public function getPriority() 84 | { 85 | return $this->_db->zScore($this->getFullQueue(), $this->_jobid); 86 | } 87 | 88 | public function incPriority($inc) 89 | { 90 | return $this->_db->zIncrBy($this->getFullQueue(), $inc, $this->_jobid); 91 | } 92 | 93 | public function setPriority($newprio) 94 | { 95 | return $this->incPriority($newprio - $this->getPriority()); 96 | } 97 | 98 | public function getJobData() 99 | { 100 | return $this->_data; 101 | } 102 | 103 | public function setJobData($data) 104 | { 105 | if (!isset($data['queue'])) { 106 | $data['queue'] = $this->_queues[0]; 107 | } 108 | 109 | if (!isset($data['priority'])) { 110 | $data['priority'] = 50; 111 | } 112 | 113 | if (!isset($data['jail'])) { 114 | return false; 115 | } 116 | 117 | if (!isset($data['repository'])) { 118 | return false; 119 | } 120 | 121 | if (!isset($data['port'])) { 122 | return false; 123 | } 124 | 125 | $data['jobid'] = $this->getJobId(); 126 | 127 | $this->_data = $data; 128 | 129 | return true; 130 | } 131 | 132 | public function get($field) 133 | { 134 | if (isset($this->_data[$field])) { 135 | return $this->_data[$field]; 136 | } 137 | 138 | return; 139 | } 140 | 141 | public function set($field, $value) 142 | { 143 | if ($field == 'jobid') { 144 | return false; 145 | } 146 | 147 | $this->_data[$field] = $value; 148 | 149 | return true; 150 | } 151 | 152 | public function del($field) 153 | { 154 | if ($field == 'jobid') { 155 | return false; 156 | } 157 | 158 | if (!isset($this->_data[$field])) { 159 | return false; 160 | } 161 | 162 | unset($this->_data[$field]); 163 | 164 | return true; 165 | } 166 | 167 | public function moveToQueue($queue) 168 | { 169 | if (!in_array($queue, $this->_queues)) { 170 | trigger_error('Queuename is invalid', E_USER_ERROR); 171 | 172 | return false; 173 | } 174 | 175 | $score = $this->getPriority(); 176 | 177 | if ($this->_db->zRem($this->getFullQueue(), $this->_jobid) !== true) { 178 | return false; 179 | } 180 | 181 | $this->_data['queue'] = $queue; 182 | 183 | if ($this->_db->zAdd($this->getFullQueue(), $score, $this->_jobid) !== true) { 184 | return false; 185 | } 186 | 187 | return $this->save(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/Session.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2015 Bernhard Froehlich 10 | * @license BSD License (2 Clause) 11 | * 12 | * @link https://freebsd.github.io/redports/ 13 | */ 14 | class Session 15 | { 16 | public function __construct() 17 | { 18 | self::initialize(); 19 | 20 | if (isset($_COOKIE['SESSIONID'])) { 21 | session_start(); 22 | 23 | if (isset($_SESSION['loginip']) && $_SESSION['loginip'] != $_SERVER['REMOTE_ADDR']) { 24 | $this->logout(); 25 | } 26 | } 27 | } 28 | 29 | public static function initialize() 30 | { 31 | // Set Redis as session handler 32 | ini_set('session.save_handler', 'redis'); 33 | ini_set('session.save_path', 'unix:///var/run/redis/redis.sock?persistent=1'); 34 | 35 | // Specify hash function used for session ids. Usually does not 36 | // work on FreeBSD unless hash functions are compiled into the binary 37 | // ini_set('session.hash_function', 'sha256'); 38 | ini_set('session.hash_bits_per_character', 5); 39 | ini_set('session.entropy_length', 512); 40 | 41 | // Set session lifetime in redis (8h) 42 | ini_set('session.gc_maxlifetime', 28800); 43 | 44 | // Set cookie lifetime on client 45 | ini_set('session.cookie_lifetime', 0); 46 | 47 | // do not expose Cookie value to JavaScript (enforced by browser) 48 | ini_set('session.cookie_httponly', 1); 49 | 50 | if (Config::get('https_only') === true) { 51 | // only send cookie over https 52 | ini_set('session.cookie_secure', 1); 53 | } 54 | 55 | // prevent caching by sending no-cache header 56 | session_cache_limiter('nocache'); 57 | 58 | // rename session 59 | session_name('SESSIONID'); 60 | } 61 | 62 | public static function getSessionId() 63 | { 64 | return session_id(); 65 | } 66 | 67 | public static function login($machineid, $secret) 68 | { 69 | $machines = new Machines(); 70 | if (($machine = $machines->getMachine($machineid)) === false) { 71 | return false; 72 | } 73 | 74 | if ($machine->getToken() !== $secret) { 75 | return false; 76 | } 77 | 78 | if (session_id() === '') { 79 | session_start(); 80 | } 81 | 82 | /* login successfull */ 83 | $_SESSION['authenticated'] = true; 84 | $_SESSION['machineid'] = $machine->getName(); 85 | $_SESSION['loginip'] = $_SERVER['REMOTE_ADDR']; 86 | $_SESSION['useragent'] = $_SERVER['HTTP_USER_AGENT']; 87 | 88 | if (isset($_SESSION['username'])) { 89 | unset($_SESSION['username']); 90 | } 91 | 92 | return true; 93 | } 94 | 95 | public static function loginUser($username) 96 | { 97 | if (session_id() === '') { 98 | session_start(); 99 | } 100 | 101 | /* login assumed to be successfull */ 102 | $_SESSION['authenticated'] = true; 103 | $_SESSION['username'] = $username; 104 | $_SESSION['loginip'] = $_SERVER['REMOTE_ADDR']; 105 | $_SESSION['useragent'] = $_SERVER['HTTP_USER_AGENT']; 106 | 107 | if (isset($_SESSION['machineid'])) { 108 | unset($_SESSION['machineid']); 109 | } 110 | 111 | return true; 112 | } 113 | 114 | public static function getMachineId() 115 | { 116 | if (isset($_SESSION['machineid'])) { 117 | return $_SESSION['machineid']; 118 | } 119 | 120 | return false; 121 | } 122 | 123 | public static function getUsername() 124 | { 125 | if (isset($_SESSION['username'])) { 126 | return $_SESSION['username']; 127 | } 128 | 129 | return false; 130 | } 131 | 132 | public static function isAuthenticated() 133 | { 134 | return isset($_SESSION['authenticated']); 135 | } 136 | 137 | public static function logout() 138 | { 139 | $_SESSION = array(); 140 | 141 | /* also destroy session cookie on client */ 142 | $params = session_get_cookie_params(); 143 | setcookie(session_name(), '', time() - 42000, 144 | $params['path'], $params['domain'], 145 | $params['secure'], $params['httponly'] 146 | ); 147 | 148 | session_destroy(); 149 | 150 | return true; 151 | } 152 | 153 | public static function generateRandomToken() 154 | { 155 | $cstrong = true; 156 | $bytes = ''; 157 | 158 | for ($i = 0; $i <= 32; $i++) { 159 | $bytes .= bin2hex(openssl_random_pseudo_bytes(8, $cstrong)); 160 | } 161 | 162 | return hash('sha256', $bytes); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /node/lib/Redports/Node/Process/ProcessManager.php: -------------------------------------------------------------------------------- 1 | 13 | * @copyright 2015 Bernhard Froehlich 14 | * @license BSD License (2 Clause) 15 | * 16 | * @link https://freebsd.github.io/redports/ 17 | */ 18 | class ProcessManager 19 | { 20 | protected $_stop = false; 21 | protected $_childs = array(); 22 | protected $_jails = array(); 23 | protected $_client; 24 | protected $_log; 25 | 26 | public function __construct() 27 | { 28 | if (!function_exists('pcntl_fork')) { 29 | trigger_error('pcntl extension not loaded!', E_USER_ERROR); 30 | } 31 | 32 | $this->_log = Config::getLogger(); 33 | $this->_client = new Client(); 34 | } 35 | 36 | public function addJail($jail) 37 | { 38 | $jailname = $jail->getJailname(); 39 | 40 | if (isset($this->_jails[$jailname])) { 41 | return false; 42 | } 43 | 44 | if (isset($this->_childs[$jailname])) { 45 | return false; 46 | } 47 | 48 | $this->_childs[$jailname] = 0; 49 | $this->_jails[$jailname] = $jail; 50 | 51 | return true; 52 | } 53 | 54 | public function getPid($jailname) 55 | { 56 | if (isset($this->_childs[$jailname])) { 57 | return $this->_childs[$jailname]; 58 | } 59 | } 60 | 61 | public function getJailname($pid) 62 | { 63 | return array_search($pid, $this->_childs); 64 | } 65 | 66 | public function countChilds() 67 | { 68 | $childs = 0; 69 | 70 | foreach ($this->_childs as $jail => $pid) { 71 | if ($pid > 0) { 72 | $childs++; 73 | } 74 | } 75 | 76 | return $childs; 77 | } 78 | 79 | public function stop() 80 | { 81 | $this->_stop = true; 82 | } 83 | 84 | public function sighandler($signo) 85 | { 86 | switch ($signo) { 87 | case SIGTERM: 88 | $this->_log->notice('Got SIGTERM ...'); 89 | $this->stop(); 90 | break; 91 | case SIGHUP: 92 | $this->_log->notice('Got SIGHUP ...'); 93 | $this->stop(); 94 | break; 95 | case SIGINT: 96 | $this->_log->notice('Got SIGINT ...'); 97 | $this->stop(); 98 | break; 99 | default: 100 | $this->_log->warning('Got unknown signal '.$signo); 101 | } 102 | } 103 | 104 | public function run() 105 | { 106 | if (!$this->_client->login()) { 107 | return false; 108 | } 109 | 110 | declare(ticks=100); 111 | 112 | pcntl_signal(SIGTERM, array($this, 'sighandler')); 113 | pcntl_signal(SIGHUP, array($this, 'sighandler')); 114 | pcntl_signal(SIGINT, array($this, 'sighandler')); 115 | $this->_stop = false; 116 | 117 | while (!$this->_stop) { 118 | foreach ($this->_childs as $jailname => $pid) { 119 | if ($pid != 0) { 120 | continue; 121 | } 122 | 123 | $pid = pcntl_fork(); 124 | if ($pid == -1) { 125 | trigger_error('Forking failed!!', E_USER_ERROR); 126 | } 127 | 128 | if ($pid) { 129 | /* Parent */ 130 | $this->_childs[$jailname] = $pid; 131 | } else { 132 | /* Child */ 133 | $child = new Child($this->_client, $this->_jails[$jailname]); 134 | $child->run(); 135 | 136 | /* delay to avoid fast respawning */ 137 | sleep(2); 138 | 139 | exit(); 140 | } 141 | } 142 | 143 | while (true) { 144 | $pid = pcntl_waitpid(-1, $status, WNOHANG); 145 | 146 | if ($pid === null || $pid < 1) { 147 | break; 148 | } 149 | 150 | $jailname = $this->getJailname($pid); 151 | if ($jailname === false) { 152 | $this->_log->error('No jail found for pid '.$pid); 153 | continue; 154 | } 155 | 156 | $this->_log->info('child '.$pid.' for '.$jailname.' removed'); 157 | $this->_childs[$jailname] = 0; 158 | } 159 | 160 | usleep(500000); 161 | } 162 | 163 | /* wait for childs to exit */ 164 | while ($this->countChilds() > 0) { 165 | $this->_log->info('waiting for '.$this->countChilds().' children'); 166 | $pid = pcntl_wait($status); 167 | 168 | $jailname = $this->getJailname($pid); 169 | $this->_childs[$jailname] = 0; 170 | } 171 | 172 | return true; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /master/lib/Redports/Master/GitHubWebhook.php: -------------------------------------------------------------------------------- 1 | 13 | * @copyright 2015 Bernhard Froehlich 14 | * @license BSD License (2 Clause) 15 | * 16 | * @link https://freebsd.github.io/redports/ 17 | */ 18 | class GitHubWebhook 19 | { 20 | public function __construct() 21 | { 22 | } 23 | 24 | public function handleEvent($event) 25 | { 26 | switch ($event) { 27 | case 'ping': 28 | return array('code' => 200, 'message' => 'pong'); 29 | break; 30 | case 'push': 31 | $payload = json_decode(file_get_contents('php://input'), true); 32 | if ($this->push($payload)) { 33 | return array('code' => 200, 'message' => 'ok'); 34 | } else { 35 | return array('code' => 500, 'message' => 'Webhook request failed'); 36 | } 37 | break; 38 | default: 39 | return array('code' => 500, 'message' => 'Event type not implemented'); 40 | break; 41 | } 42 | } 43 | 44 | public function push($payload) 45 | { 46 | $config = $this->_getUserConfig($payload['repository']['full_name'].'/'.$payload['commits'][0]['id']); 47 | 48 | $ports = array(); 49 | foreach ($payload['commits'] as $commit) { 50 | foreach ($commit['added'] as $file) { 51 | $port = substr($file, 0, strpos($file, '/', strpos($file, '/') + 1)); 52 | if (preg_match('/^([a-zA-Z0-9_+.-]+)\/([a-zA-Z0-9_+.-]+)$/', $port) == 1 && strlen($port) < 100) { 53 | $ports[] = $port; 54 | } 55 | } 56 | 57 | foreach ($commit['modified'] as $file) { 58 | $port = substr($file, 0, strpos($file, '/', strpos($file, '/') + 1)); 59 | if (preg_match('/^([a-zA-Z0-9_+.-]+)\/([a-zA-Z0-9_+.-]+)$/', $port) == 1 && strlen($port) < 100) { 60 | $ports[] = $port; 61 | } 62 | } 63 | } 64 | 65 | $data = array( 66 | 'commit' => array( 67 | 'id' => $payload['head_commit']['id'], 68 | 'url' => $payload['head_commit']['url'], 69 | 'message' => $payload['head_commit']['message'], 70 | 'time' => $payload['head_commit']['timestamp'], 71 | ), 72 | 'committer' => array( 73 | 'name' => $payload['head_commit']['committer']['name'], 74 | 'email' => $payload['head_commit']['committer']['email'], 75 | ), 76 | 'repository' => array( 77 | 'url' => $payload['repository']['url'], 78 | ), 79 | ); 80 | 81 | $ports = array_unique($ports); 82 | 83 | $jobgroupname = sprintf('github:%s:%s', $payload['repository']['owner']['name'], $payload['repository']['name']); 84 | $jobgroup = new Jobgroup($jobgroupname); 85 | $countjobs = $jobgroup->countJobs(); 86 | 87 | $jails = new Jails(); 88 | 89 | foreach ($config['jails'] as $jail) { 90 | if (!$jails->exists($jail)) { 91 | continue; 92 | } 93 | 94 | $queue = new Queue('preparequeue', $jail); 95 | 96 | foreach ($ports as $port) { 97 | $data['port'] = $port; 98 | if ($queue->createJob($data, $jobgroup) !== true) { 99 | return false; 100 | } 101 | } 102 | } 103 | 104 | if ($jobgroup->countJobs() > $countjobs) { 105 | $args = array( 106 | 'jobgroup' => $jobgroup->getJobgroupId(), 107 | 'repository' => $data['repository']['url'], 108 | 'commit' => $data['commit']['id'], 109 | 'created' => time(), 110 | ); 111 | 112 | \Resque::enqueue('default', '\Redports\Master\Task\TaskPreparePortstree', $args); 113 | } 114 | 115 | return true; 116 | } 117 | 118 | public function _getUserConfig($commitpath) 119 | { 120 | $defaultconfig = Config::get('userconfig'); 121 | 122 | try { 123 | $file = @file_get_contents('https://raw.githubusercontent.com/'.$commitpath.'/.redports.json'); 124 | if ($file === false) { 125 | return $defaultconfig; 126 | } 127 | } catch (Exception $e) { 128 | return $defaultconfig; 129 | } 130 | 131 | $config = json_decode($file, true); 132 | 133 | foreach ($config as $key => $value) { 134 | if (isset($defaultconfig[$key])) { 135 | if (gettype($defaultconfig[$key]) == gettype($value)) { 136 | $defaultconfig[$key] = $value; 137 | } else { 138 | if (settype($value, gettype($defaultconfig[$key]))) { 139 | $defaultconfig[$key] = $value; 140 | } 141 | } 142 | } 143 | } 144 | 145 | return $defaultconfig; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2015 Bernhard Froehlich 8 | * @license BSD License (2 Clause) 9 | * 10 | * @link https://freebsd.github.io/redports/ 11 | */ 12 | namespace Redports\Web; 13 | 14 | require_once __DIR__.'/vendor/autoload.php'; 15 | 16 | $session = new Session(); 17 | 18 | $app = new \Slim\App(new \Slim\Container(Config::get('slimconfig'))); 19 | 20 | /* init php-view */ 21 | $container = $app->getContainer(); 22 | $container['view'] = function($container) { 23 | return new \Slim\Views\PhpRenderer(__DIR__.'/templates/'); 24 | }; 25 | 26 | /* landing page */ 27 | $app->get('/', function($request, $response, $args) use ($session) { 28 | return $this->view->render($response, 'index.html', $args); 29 | }); 30 | 31 | /* GitHub OAuth login */ 32 | $app->get('/login', function($request, $response) use ($session) { 33 | $credentials = new \OAuth\Common\Consumer\Credentials( 34 | Config::get('github.oauth.key'), 35 | Config::get('github.oauth.secret'), 36 | Config::get('github.oauth.redirecturl') 37 | ); 38 | 39 | $serviceFactory = new \OAuth\ServiceFactory(); 40 | $gitHub = $serviceFactory->createService('GitHub', $credentials, 41 | new \OAuth\Common\Storage\Session(), array( 42 | 'user:email', 43 | 'public_repo', 44 | 'repo:status', 45 | 'admin:repo_hook', 46 | ) 47 | ); 48 | 49 | $queryParams = $request->getQueryParams(); 50 | 51 | if (isset($queryParams['code'])) { 52 | try { 53 | $token = $gitHub->requestAccessToken($queryParams['code']); 54 | 55 | $result = json_decode($gitHub->request('user'), true); 56 | 57 | /* TODO: register new user at master if it does not exist yet */ 58 | 59 | $session->login($result['login']); 60 | $_SESSION['name'] = $result['name']; 61 | $_SESSION['profile_url'] = $result['html_url']; 62 | $_SESSION['token'] = $token->getAccessToken(); 63 | 64 | return $response->withRedirect('/repositories'); 65 | } catch (\OAuth\Common\Http\Exception\TokenResponseException $e) { 66 | return $response->withStatus(500) 67 | ->write($e->getMessage()); 68 | } 69 | } else { 70 | return $response->withRedirect($gitHub->getAuthorizationUri()); 71 | } 72 | }); 73 | 74 | /* GitHub list repositories */ 75 | $app->get('/repositories', function($request, $response) use ($session) { 76 | if (!$session->getUsername()) { 77 | return $response->withStatus(403)->write('Not authenticated'); 78 | } 79 | 80 | $client = new \Github\Client(); 81 | $client->authenticate($_SESSION['token'], null, \Github\Client::AUTH_HTTP_TOKEN); 82 | 83 | try { 84 | $repos = $client->api('user')->repositories($session->getUsername()); 85 | 86 | foreach ($repos as $key => $repository) { 87 | $repos[$key]['redports_enabled'] = false; 88 | 89 | foreach ($client->api('repo')->hooks()->all($session->getUsername(), $repository['name']) as $hook) { 90 | if ($hook['name'] == 'web' && strpos($hook['config']['url'], 'redports.org') !== false) { 91 | $repos[$key]['redports_enabled'] = true; 92 | } 93 | } 94 | } 95 | } catch (\Github\Exception\RuntimeException $e) { 96 | return $response->withStatus(500) 97 | ->write($e->getMessage()); 98 | } 99 | 100 | return $this->view->render($response, 'repositories.html', array('repositories' => $repos)); 101 | }); 102 | 103 | /* GitHub repository setup */ 104 | $app->get('/repositories/{repository}/install', function($request, $response, $args) use ($session) { 105 | if (!$session->getUsername()) { 106 | return $response->withStatus(403)->write('Not authenticated'); 107 | } 108 | 109 | $client = new \Github\Client(); 110 | $client->authenticate($_SESSION['token'], null, \Github\Client::AUTH_HTTP_TOKEN); 111 | 112 | try { 113 | foreach ($client->api('repo')->hooks()->all($session->getUsername(), $args['repository']) as $hook) { 114 | if ($hook['name'] == 'web' && strpos($hook['config']['url'], 'redports.org') !== false) { 115 | $client->api('repo')->hooks()->remove($session->getUsername(), $args['repository'], $hook['id']); 116 | } 117 | } 118 | 119 | $webhook = $client->api('repo')->hooks()->create($session->getUsername(), $args['repository'], 120 | array( 121 | 'name' => 'web', 122 | 'active' => true, 123 | 'events' => array('push'), 124 | 'config' => array( 125 | 'url' => 'https://api.redports.org/github/', 126 | 'content_type' => 'json', 127 | ), 128 | ) 129 | ); 130 | } catch (\Github\Exception\RuntimeException $e) { 131 | return $response->withStatus(500) 132 | ->write($e->getMessage()); 133 | } 134 | 135 | /* TODO: register repository at master */ 136 | 137 | return $response->withRedirect('/repositories'); 138 | }); 139 | 140 | /* GitHub repository uninstall */ 141 | $app->get('/repositories/{repository}/uninstall', function($request, $response, $args) use ($session) { 142 | if (!$session->getUsername()) { 143 | return $response->withStatus(403)->write('Not authenticated'); 144 | } 145 | 146 | $client = new \Github\Client(); 147 | $client->authenticate($_SESSION['token'], null, \Github\Client::AUTH_HTTP_TOKEN); 148 | 149 | try { 150 | foreach ($client->api('repo')->hooks()->all($session->getUsername(), $args['repository']) as $hook) { 151 | if ($hook['name'] == 'web' && strpos($hook['config']['url'], 'redports.org') !== false) { 152 | $client->api('repo')->hooks()->remove($session->getUsername(), $args['repository'], $hook['id']); 153 | } 154 | } 155 | } catch (\Github\Exception\RuntimeException $e) { 156 | return $response->withStatus(500) 157 | ->write($e->getMessage()); 158 | } 159 | 160 | return $response->withRedirect('/repositories'); 161 | }); 162 | 163 | $app->run(); 164 | -------------------------------------------------------------------------------- /web/nginx.conf.sample: -------------------------------------------------------------------------------- 1 | 2 | worker_processes 2; 3 | 4 | events { 5 | worker_connections 2048; 6 | # multi_accept on; 7 | } 8 | 9 | http { 10 | include mime.types; 11 | default_type application/octet-stream; 12 | 13 | sendfile on; 14 | tcp_nodelay on; 15 | tcp_nopush on; 16 | keepalive_timeout 60; 17 | 18 | gzip on; 19 | gzip_types text/plain; 20 | gzip_comp_level 4; 21 | gzip_min_length 500; 22 | 23 | access_log off; 24 | log_not_found off; 25 | server_tokens off; 26 | 27 | # disable proxy buffering 28 | proxy_max_temp_file_size 0; 29 | client_max_body_size 50m; 30 | 31 | ssl_prefer_server_ciphers on; 32 | ssl_dhparam dhparam.pem; 33 | ssl_session_timeout 1d; 34 | ssl_session_cache shared:SSL:50m; 35 | 36 | # mozilla server team - Modern compatibility 37 | # https://wiki.mozilla.org/Security/Server_Side_TLS 38 | ssl_protocols TLSv1.1 TLSv1.2; 39 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK'; 40 | 41 | ssl_stapling on; 42 | ssl_stapling_verify on; 43 | 44 | # verify chain of trust of OCSP response using Root CA and Intermediate certs 45 | ssl_trusted_certificate /usr/local/etc/nginx/cert_www/trustchain.crt; 46 | 47 | resolver 4.2.2.4 8.8.8.8; 48 | 49 | server { 50 | listen 80; 51 | listen [::]:80; 52 | server_name redports.org www.redports.org; 53 | return 301 https://redports.org/; 54 | } 55 | 56 | server { 57 | listen 443 ssl http2; 58 | listen [::]:443 ssl http2; 59 | server_name www.redports.org; 60 | return 301 https://redports.org/; 61 | 62 | ssl on; 63 | ssl_certificate /usr/local/etc/nginx/cert_www/www.redports.org.crt; 64 | ssl_certificate_key /usr/local/etc/nginx/cert_www/www.redports.org.key; 65 | } 66 | 67 | server { 68 | listen 443 ssl http2; 69 | listen [::]:443 ssl http2; 70 | server_name redports.org; 71 | 72 | root /usr/local/www/redports/web; 73 | 74 | try_files $uri /index.php?$query_string; 75 | 76 | ssl on; 77 | ssl_certificate /usr/local/etc/nginx/cert_none/redports.org.crt; 78 | ssl_certificate_key /usr/local/etc/nginx/cert_none/redports.org.key; 79 | 80 | add_header Strict-Transport-Security max-age=15768000; # six months 81 | 82 | # block access to our code 83 | location ^~ /lib/ { 84 | return 403; 85 | } 86 | 87 | # block access to our code 88 | location ^~ /templates/ { 89 | return 403; 90 | } 91 | 92 | # block access to composer dependencies 93 | location ^~ /vendor/ { 94 | return 403; 95 | } 96 | 97 | # this will only pass index.php to the fastcgi process which is 98 | # generally safer but assumes the whole site is run via Slim. 99 | location /index.php { 100 | fastcgi_pass unix:/var/run/php5-fpm.sock; 101 | fastcgi_index index.php; 102 | fastcgi_connect_timeout 10s; 103 | fastcgi_read_timeout 120s; 104 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 105 | include fastcgi_params; 106 | } 107 | } 108 | 109 | server { 110 | listen 443 ssl http2; 111 | listen [::]:443 ssl http2; 112 | server_name logs.redports.org; 113 | root /usr/local/www/redports/logs; 114 | 115 | ssl on; 116 | ssl_certificate /usr/local/etc/nginx/cert_logs/logs.redports.org.crt; 117 | ssl_certificate_key /usr/local/etc/nginx/cert_logs/logs.redports.org.key; 118 | 119 | expires 30d; 120 | add_header Pragma public; 121 | add_header Cache-Control public; 122 | 123 | autoindex on; 124 | } 125 | 126 | server { 127 | listen 443 ssl http2; 128 | listen [::]:443 ssl http2; 129 | server_name api.redports.org; 130 | root /usr/local/www/redports/master; 131 | 132 | try_files $uri /index.php?$query_string; 133 | 134 | ssl on; 135 | ssl_certificate /usr/local/etc/nginx/cert_api/api.redports.org.crt; 136 | ssl_certificate_key /usr/local/etc/nginx/cert_api/api.redports.org.key; 137 | 138 | add_header Strict-Transport-Security max-age=15768000; # six months 139 | 140 | # block access to our code 141 | location ^~ /lib/ { 142 | return 403; 143 | } 144 | 145 | # block access to our code 146 | location ^~ /scripts/ { 147 | return 403; 148 | } 149 | 150 | # block access to composer dependencies 151 | location ^~ /vendor/ { 152 | return 403; 153 | } 154 | 155 | # Slim also always responds to /github even if only /github/ is set 156 | location ^~ /github { 157 | # Protect GitHub API from the outside world 158 | # https://help.github.com/articles/what-ip-addresses-does-github-use-that-i-should-whitelist/ 159 | 160 | allow 192.30.252.0/22; 161 | allow 2620:112:3000::/44; 162 | 163 | deny all; 164 | 165 | try_files $uri /index.php?$query_string; 166 | } 167 | 168 | # this will only pass index.php to the fastcgi process which is 169 | # generally safer but assumes the whole site is run via Slim. 170 | location /index.php { 171 | fastcgi_pass unix:/var/run/php5-fpm.sock; 172 | fastcgi_index index.php; 173 | fastcgi_connect_timeout 10s; 174 | fastcgi_read_timeout 120s; 175 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 176 | include fastcgi_params; 177 | } 178 | } 179 | } 180 | 181 | -------------------------------------------------------------------------------- /master/index.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2015 Bernhard Froehlich 8 | * @license BSD License (2 Clause) 9 | * 10 | * @link https://freebsd.github.io/redports/ 11 | */ 12 | namespace Redports\Master; 13 | 14 | require_once __DIR__.'/vendor/autoload.php'; 15 | 16 | $session = new Session(); 17 | 18 | $app = new \Slim\Slim(); 19 | $app->config('debug', Config::get('debug')); 20 | $app->response->headers->set('Content-Type', 'text/plain'); 21 | 22 | $redis = new \Redis(); 23 | $redis->pconnect(Config::get('datasource')); 24 | 25 | \Resque::setBackend(Config::get('datasource')); 26 | 27 | /* GitHub Webhooks */ 28 | $app->post('/github/', function() use ($app) { 29 | $github = new GitHubWebhook(); 30 | $result = $github->handleEvent($app->request->headers->get('X-GitHub-Event')); 31 | 32 | textResponse($result['code'], $result['message']); 33 | }); 34 | 35 | /* Authentication - Login */ 36 | $app->post('/auth/', function() use ($app, $session) { 37 | if (!isset($_POST['machineid']) || !isset($_POST['secret'])) { 38 | textResponse(403, 'Authentication failed'); 39 | } elseif (!$session->login($_POST['machineid'], $_POST['secret'])) { 40 | textResponse(403, 'Authentication failed'); 41 | } else { 42 | jsonResponse(200, array('sessionid' => $session->getSessionId())); 43 | } 44 | }); 45 | 46 | $app->get('/auth/', function() use ($app) { 47 | textResponse(400, 'Only POST method allowed for authentication'); 48 | }); 49 | 50 | /* Jails - List all jails */ 51 | $app->get('/jails/', 'isAllowed', function() use ($app) { 52 | $jails = new Jails(); 53 | jsonResponse(200, $jails->getJails()); 54 | }); 55 | 56 | /* Jails - List individual jail info */ 57 | $app->get('/jails/:jailname/', 'isAllowed', function($jailname) use ($app) { 58 | $jails = new Jails(); 59 | 60 | if (!$jails->exists($jailname)) { 61 | textResponse(404, 'Jail unknown'); 62 | } else { 63 | jsonResponse(200, $jails->getJail($jailname)); 64 | } 65 | }); 66 | 67 | /* Queues - info about a queue */ 68 | $app->get('/queues/:queuename/:jailname/', 'isAllowed', function($queuename, $jailname) use ($app) { 69 | $queue = new Queue($queuename, $jailname); 70 | 71 | if ($queue->exists()) { 72 | jsonResponse(200, $queue->getQueueInfo()); 73 | } else { 74 | textResponse(404, 'Queue unknown'); 75 | } 76 | }); 77 | 78 | /* Queues - Take next job */ 79 | $app->get('/queues/waitqueue/:jailname/take', 'isAllowed', function($jailname) use ($app) { 80 | $queue = new Queue('waitqueue', $jailname); 81 | $job = $queue->getNextJob(); 82 | if ($job === false) { 83 | textResponse(204); 84 | } 85 | 86 | $machine = new Machine(Session::getMachineId()); 87 | $machine->addJob($job->getJobId()); 88 | $job->set('machine', $machine->getName()); 89 | $job->moveToQueue('runqueue'); 90 | jsonResponse(200, $job->getJobData()); 91 | }); 92 | 93 | /* Jobs - Create new job */ 94 | $app->post('/jobs/create', 'isAllowed', function() use ($app) { 95 | $queue = new Queue(); 96 | 97 | $jail = new Jails(); 98 | if (!$jail->exists($_POST['jail'])) { 99 | textResponse(404, 'Jail unknown'); 100 | } 101 | 102 | $repos = new Repositories(); 103 | if (!$repos->exists($_POST['repository'])) { 104 | textResponse(404, 'Repository unknown'); 105 | } 106 | 107 | $jobgroup = new Jobgroup($_POST['jobgroup']); 108 | if ($jobgroup->exists()) { 109 | textResponse(403, 'Jobgroup already exists'); 110 | } 111 | 112 | $data = array( 113 | 'port' => $_POST['port'], 114 | 'jail' => $_POST['jail'], 115 | 'repository' => $_POST['repository'], 116 | 'jobgroup' => $_POST['jobgroup'], 117 | ); 118 | 119 | $job = $queue->createJob($data); 120 | $jobgroup->addJob($job->getJobId()); 121 | 122 | jsonResponse(200, $job->getJobData()); 123 | }); 124 | 125 | /* Jobs - Job details */ 126 | $app->get('/jobs/:jobid/', 'isAllowed', function($jobid) use ($app) { 127 | $job = new Job($jobid); 128 | 129 | if (!$job->exists()) { 130 | textResponse(404, 'Job not found'); 131 | } else { 132 | jsonResponse(200, $job->getJobData()); 133 | } 134 | })->conditions(array('jobid' => '[0-9]')); 135 | 136 | /* Jobs - Upload logfile ... */ 137 | $app->put('/jobs/:jobid/logfile/:filename', 'isAllowed', function($jobid, $filename) use ($app) { 138 | $job = new Job($jobid); 139 | 140 | if (!$job->exists()) { 141 | textResponse(404, 'Job not found'); 142 | } 143 | 144 | $machine = new Machine(Session::getMachineId()); 145 | if (!$machine->hasJob($jobid)) { 146 | textResponse(403, 'Job not assigned to you'); 147 | } 148 | 149 | $filepath = Config::get('logdir').'/'.$jobid.'/'.basename($filename); 150 | 151 | if (file_exists($filepath)) { 152 | textResponse(403, 'File already exists'); 153 | } 154 | 155 | if (!is_dir(dirname($filepath))) { 156 | mkdir(dirname($filepath), 0777, true); 157 | } 158 | 159 | $fi = fopen('php://input', 'rb'); 160 | $fo = fopen($filepath, 'w'); 161 | 162 | stream_copy_to_stream($fi, $fo); 163 | 164 | fclose($fo); 165 | fclose($fi); 166 | 167 | $job->set('logfile', $filename); 168 | $job->save(); 169 | 170 | textResponse(200); 171 | })->conditions(array('jobid' => '[0-9]')); 172 | 173 | /* Jobs - Finish a job (with buildstatus and buildreason) */ 174 | $app->post('/jobs/:jobid/finish', 'isAllowed', function($jobid) use ($app) { 175 | 176 | if (!isset($_POST['buildstatus']) || !isset($_POST['buildreason'])) { 177 | textResponse(400, 'Post data missing'); 178 | } 179 | 180 | $job = new Job($jobid); 181 | if ($job === false) { 182 | textResponse(204); 183 | } 184 | 185 | $machine = new Machine(Session::getMachineId()); 186 | if (!$machine->hasJob($job->getJobId())) { 187 | textResponse(403, 'Job not assigned to you'); 188 | } 189 | 190 | $job->unset('machine'); 191 | $job->set('buildstatus', $_POST['buildstatus']); 192 | $job->set('buildreason', $_POST['buildreason']); 193 | $job->moveToQueue('archivequeue'); 194 | jsonResponse(200, $job->getJobData()); 195 | })->conditions(array('jobid' => '[0-9]')); 196 | 197 | /* Jobgroup - List details of jobgroup */ 198 | $app->get('/group/:groupid/', 'isAllowed', function($groupid) use ($app) { 199 | 200 | $jobgroup = new Jobgroup($groupid); 201 | if (!$jobgroup->exists()) { 202 | textResponse(404, 'Jobgroup not found'); 203 | } 204 | 205 | jsonResponse(200, $jobgroup->getGroupInfo()); 206 | }); 207 | 208 | /* 404 - not found */ 209 | $app->notFound(function() use ($app) { 210 | textResponse(404, 'Not found'); 211 | }); 212 | 213 | /* 500 - internal server error */ 214 | $app->error(function(\Exception $e) use ($app) { 215 | textResponse(500, 'Internal Server Error'); 216 | }); 217 | 218 | $app->run(); 219 | -------------------------------------------------------------------------------- /web/assets/js/skel.min.js: -------------------------------------------------------------------------------- 1 | /* skel.js v3.0.0 | (c) n33 | skel.io | MIT licensed */ 2 | var skel=function(){"use strict";var t={breakpointIds:null,events:{},isInit:!1,obj:{attachments:{},breakpoints:{},head:null,states:{}},sd:"/",state:null,stateHandlers:{},stateId:"",vars:{},DOMReady:null,indexOf:null,isArray:null,iterate:null,matchesMedia:null,extend:function(e,n){t.iterate(n,function(i){t.isArray(n[i])?(t.isArray(e[i])||(e[i]=[]),t.extend(e[i],n[i])):"object"==typeof n[i]?("object"!=typeof e[i]&&(e[i]={}),t.extend(e[i],n[i])):e[i]=n[i]})},newStyle:function(t){var e=document.createElement("style");return e.type="text/css",e.innerHTML=t,e},_canUse:null,canUse:function(e){t._canUse||(t._canUse=document.createElement("div"));var n=t._canUse.style,i=e.charAt(0).toUpperCase()+e.slice(1);return e in n||"Moz"+i in n||"Webkit"+i in n||"O"+i in n||"ms"+i in n},on:function(e,n){var i=e.split(/[\s]+/);return t.iterate(i,function(e){var a=i[e];if(t.isInit){if("init"==a)return void n();if("change"==a)n();else{var r=a.charAt(0);if("+"==r||"!"==r){var o=a.substring(1);if(o in t.obj.breakpoints)if("+"==r&&t.obj.breakpoints[o].active)n();else if("!"==r&&!t.obj.breakpoints[o].active)return void n()}}}t.events[a]||(t.events[a]=[]),t.events[a].push(n)}),t},trigger:function(e){return t.events[e]&&0!=t.events[e].length?(t.iterate(t.events[e],function(n){t.events[e][n]()}),t):void 0},breakpoint:function(e){return t.obj.breakpoints[e]},breakpoints:function(e){function n(t,e){this.name=this.id=t,this.media=e,this.active=!1,this.wasActive=!1}return n.prototype.matches=function(){return t.matchesMedia(this.media)},n.prototype.sync=function(){this.wasActive=this.active,this.active=this.matches()},t.iterate(e,function(i){t.obj.breakpoints[i]=new n(i,e[i])}),window.setTimeout(function(){t.poll()},0),t},addStateHandler:function(e,n){t.stateHandlers[e]=n},callStateHandler:function(e){var n=t.stateHandlers[e]();t.iterate(n,function(e){t.state.attachments.push(n[e])})},changeState:function(e){t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].sync()}),t.vars.lastStateId=t.stateId,t.stateId=e,t.breakpointIds=t.stateId===t.sd?[]:t.stateId.substring(1).split(t.sd),t.obj.states[t.stateId]?t.state=t.obj.states[t.stateId]:(t.obj.states[t.stateId]={attachments:[]},t.state=t.obj.states[t.stateId],t.iterate(t.stateHandlers,t.callStateHandler)),t.detachAll(t.state.attachments),t.attachAll(t.state.attachments),t.vars.stateId=t.stateId,t.vars.state=t.state,t.trigger("change"),t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].active?t.obj.breakpoints[e].wasActive||t.trigger("+"+e):t.obj.breakpoints[e].wasActive&&t.trigger("-"+e)})},generateStateConfig:function(e,n){var i={};return t.extend(i,e),t.iterate(t.breakpointIds,function(e){t.extend(i,n[t.breakpointIds[e]])}),i},getStateId:function(){var e="";return t.iterate(t.obj.breakpoints,function(n){var i=t.obj.breakpoints[n];i.matches()&&(e+=t.sd+i.id)}),e},poll:function(){var e="";e=t.getStateId(),""===e&&(e=t.sd),e!==t.stateId&&t.changeState(e)},_attach:null,attach:function(e){var n=t.obj.head,i=e.element;return i.parentNode&&i.parentNode.tagName?!1:(t._attach||(t._attach=n.firstChild),n.insertBefore(i,t._attach.nextSibling),e.permanent&&(t._attach=i),!0)},attachAll:function(e){var n=[];t.iterate(e,function(t){n[e[t].priority]||(n[e[t].priority]=[]),n[e[t].priority].push(e[t])}),n.reverse(),t.iterate(n,function(e){t.iterate(n[e],function(i){t.attach(n[e][i])})})},detach:function(t){var e=t.element;return t.permanent||!e.parentNode||e.parentNode&&!e.parentNode.tagName?!1:(e.parentNode.removeChild(e),!0)},detachAll:function(e){var n={};t.iterate(e,function(t){n[e[t].id]=!0}),t.iterate(t.obj.attachments,function(e){e in n||t.detach(t.obj.attachments[e])})},attachment:function(e){return e in t.obj.attachments?t.obj.attachments[e]:null},newAttachment:function(e,n,i,a){return t.obj.attachments[e]={id:e,element:n,priority:i,permanent:a}},init:function(){t.initMethods(),t.initVars(),t.initEvents(),t.obj.head=document.getElementsByTagName("head")[0],t.isInit=!0,t.trigger("init")},initEvents:function(){t.on("resize",function(){t.poll()}),t.on("orientationChange",function(){t.poll()}),t.DOMReady(function(){t.trigger("ready")}),window.onload&&t.on("load",window.onload),window.onload=function(){t.trigger("load")},window.onresize&&t.on("resize",window.onresize),window.onresize=function(){t.trigger("resize")},window.onorientationchange&&t.on("orientationChange",window.onorientationchange),window.onorientationchange=function(){t.trigger("orientationChange")}},initMethods:function(){document.addEventListener?!function(e,n){t.DOMReady=n()}("domready",function(){function t(t){for(r=1;t=n.shift();)t()}var e,n=[],i=document,a="DOMContentLoaded",r=/^loaded|^c/.test(i.readyState);return i.addEventListener(a,e=function(){i.removeEventListener(a,e),t()}),function(t){r?t():n.push(t)}}):!function(e,n){t.DOMReady=n()}("domready",function(t){function e(t){for(h=1;t=i.shift();)t()}var n,i=[],a=!1,r=document,o=r.documentElement,s=o.doScroll,c="DOMContentLoaded",d="addEventListener",u="onreadystatechange",l="readyState",f=s?/^loaded|^c/:/^loaded|c/,h=f.test(r[l]);return r[d]&&r[d](c,n=function(){r.removeEventListener(c,n,a),e()},a),s&&r.attachEvent(u,n=function(){/^c/.test(r[l])&&(r.detachEvent(u,n),e())}),t=s?function(e){self!=top?h?e():i.push(e):function(){try{o.doScroll("left")}catch(n){return setTimeout(function(){t(e)},50)}e()}()}:function(t){h?t():i.push(t)}}),Array.prototype.indexOf?t.indexOf=function(t,e){return t.indexOf(e)}:t.indexOf=function(t,e){if("string"==typeof t)return t.indexOf(e);var n,i,a=e?e:0;if(!this)throw new TypeError;if(i=this.length,0===i||a>=i)return-1;for(0>a&&(a=i-Math.abs(a)),n=a;i>n;n++)if(this[n]===t)return n;return-1},Array.isArray?t.isArray=function(t){return Array.isArray(t)}:t.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)},Object.keys?t.iterate=function(t,e){if(!t)return[];var n,i=Object.keys(t);for(n=0;i[n]&&e(i[n],t[i[n]])!==!1;n++);}:t.iterate=function(t,e){if(!t)return[];var n;for(n in t)if(Object.prototype.hasOwnProperty.call(t,n)&&e(n,t[n])===!1)break},window.matchMedia?t.matchesMedia=function(t){return""==t?!0:window.matchMedia(t).matches}:window.styleMedia||window.media?t.matchesMedia=function(t){if(""==t)return!0;var e=window.styleMedia||window.media;return e.matchMedium(t||"all")}:window.getComputedStyle?t.matchesMedia=function(t){if(""==t)return!0;var e=document.createElement("style"),n=document.getElementsByTagName("script")[0],i=null;e.type="text/css",e.id="matchmediajs-test",n.parentNode.insertBefore(e,n),i="getComputedStyle"in window&&window.getComputedStyle(e,null)||e.currentStyle;var a="@media "+t+"{ #matchmediajs-test { width: 1px; } }";return e.styleSheet?e.styleSheet.cssText=a:e.textContent=a,"1px"===i.width}:t.matchesMedia=function(t){if(""==t)return!0;var e,n,i,a,r={"min-width":null,"max-width":null},o=!1;for(i=t.split(/\s+and\s+/),e=0;er["max-width"]||null!==r["min-height"]&&cr["max-height"]?!1:!0},navigator.userAgent.match(/MSIE ([0-9]+)/)&&RegExp.$1<9&&(t.newStyle=function(t){var e=document.createElement("span");return e.innerHTML=' ",e})},initVars:function(){var e,n,i,a=navigator.userAgent;e="other",n=0,i=[["firefox",/Firefox\/([0-9\.]+)/],["bb",/BlackBerry.+Version\/([0-9\.]+)/],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/],["opera",/OPR\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)/],["edge",/Edge\/([0-9\.]+)/],["safari",/Version\/([0-9\.]+).+Safari/],["chrome",/Chrome\/([0-9\.]+)/],["ie",/MSIE ([0-9]+)/],["ie",/Trident\/.+rv:([0-9]+)/]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(RegExp.$1),!1):void 0}),t.vars.browser=e,t.vars.browserVersion=n,e="other",n=0,i=[["ios",/([0-9_]+) like Mac OS X/,function(t){return t.replace("_",".").replace("_","")}],["ios",/CPU like Mac OS X/,function(t){return 0}],["android",/Android ([0-9\.]+)/,null],["mac",/Macintosh.+Mac OS X ([0-9_]+)/,function(t){return t.replace("_",".").replace("_","")}],["wp",/Windows Phone ([0-9\.]+)/,null],["windows",/Windows NT ([0-9\.]+)/,null],["bb",/BlackBerry.+Version\/([0-9\.]+)/,null],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/,null]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(i[2]?i[2](RegExp.$1):RegExp.$1),!1):void 0}),t.vars.os=e,t.vars.osVersion=n,t.vars.IEVersion="ie"==t.vars.browser?t.vars.browserVersion:99,t.vars.touch="wp"==t.vars.os?navigator.msMaxTouchPoints>0:!!("ontouchstart"in window),t.vars.mobile="wp"==t.vars.os||"android"==t.vars.os||"ios"==t.vars.os||"bb"==t.vars.os}};return t.init(),t}();!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.skel=e()}(this,function(){return skel}); 3 | -------------------------------------------------------------------------------- /web/assets/js/util.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | /** 4 | * Generate an indented list of links from a nav. Meant for use with panel(). 5 | * @return {jQuery} jQuery object. 6 | */ 7 | $.fn.navList = function() { 8 | 9 | var $this = $(this); 10 | $a = $this.find('a'), 11 | b = []; 12 | 13 | $a.each(function() { 14 | 15 | var $this = $(this), 16 | indent = Math.max(0, $this.parents('li').length - 1), 17 | href = $this.attr('href'), 18 | target = $this.attr('target'); 19 | 20 | b.push( 21 | '' + 26 | '' + 27 | $this.text() + 28 | '' 29 | ); 30 | 31 | }); 32 | 33 | return b.join(''); 34 | 35 | }; 36 | 37 | /** 38 | * Panel-ify an element. 39 | * @param {object} userConfig User config. 40 | * @return {jQuery} jQuery object. 41 | */ 42 | $.fn.panel = function(userConfig) { 43 | 44 | // No elements? 45 | if (this.length == 0) 46 | return $this; 47 | 48 | // Multiple elements? 49 | if (this.length > 1) { 50 | 51 | for (var i=0; i < this.length; i++) 52 | $(this[i]).panel(userConfig); 53 | 54 | return $this; 55 | 56 | } 57 | 58 | // Vars. 59 | var $this = $(this), 60 | $body = $('body'), 61 | $window = $(window), 62 | id = $this.attr('id'), 63 | config; 64 | 65 | // Config. 66 | config = $.extend({ 67 | 68 | // Delay. 69 | delay: 0, 70 | 71 | // Hide panel on link click. 72 | hideOnClick: false, 73 | 74 | // Hide panel on escape keypress. 75 | hideOnEscape: false, 76 | 77 | // Hide panel on swipe. 78 | hideOnSwipe: false, 79 | 80 | // Reset scroll position on hide. 81 | resetScroll: false, 82 | 83 | // Reset forms on hide. 84 | resetForms: false, 85 | 86 | // Side of viewport the panel will appear. 87 | side: null, 88 | 89 | // Target element for "class". 90 | target: $this, 91 | 92 | // Class to toggle. 93 | visibleClass: 'visible' 94 | 95 | }, userConfig); 96 | 97 | // Expand "target" if it's not a jQuery object already. 98 | if (typeof config.target != 'jQuery') 99 | config.target = $(config.target); 100 | 101 | // Panel. 102 | 103 | // Methods. 104 | $this._hide = function(event) { 105 | 106 | // Already hidden? Bail. 107 | if (!config.target.hasClass(config.visibleClass)) 108 | return; 109 | 110 | // If an event was provided, cancel it. 111 | if (event) { 112 | 113 | event.preventDefault(); 114 | event.stopPropagation(); 115 | 116 | } 117 | 118 | // Hide. 119 | config.target.removeClass(config.visibleClass); 120 | 121 | // Post-hide stuff. 122 | window.setTimeout(function() { 123 | 124 | // Reset scroll position. 125 | if (config.resetScroll) 126 | $this.scrollTop(0); 127 | 128 | // Reset forms. 129 | if (config.resetForms) 130 | $this.find('form').each(function() { 131 | this.reset(); 132 | }); 133 | 134 | }, config.delay); 135 | 136 | }; 137 | 138 | // Vendor fixes. 139 | $this 140 | .css('-ms-overflow-style', '-ms-autohiding-scrollbar') 141 | .css('-webkit-overflow-scrolling', 'touch'); 142 | 143 | // Hide on click. 144 | if (config.hideOnClick) { 145 | 146 | $this.find('a') 147 | .css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)'); 148 | 149 | $this 150 | .on('click', 'a', function(event) { 151 | 152 | var $a = $(this), 153 | href = $a.attr('href'), 154 | target = $a.attr('target'); 155 | 156 | if (!href || href == '#' || href == '' || href == '#' + id) 157 | return; 158 | 159 | // Cancel original event. 160 | event.preventDefault(); 161 | event.stopPropagation(); 162 | 163 | // Hide panel. 164 | $this._hide(); 165 | 166 | // Redirect to href. 167 | window.setTimeout(function() { 168 | 169 | if (target == '_blank') 170 | window.open(href); 171 | else 172 | window.location.href = href; 173 | 174 | }, config.delay + 10); 175 | 176 | }); 177 | 178 | } 179 | 180 | // Event: Touch stuff. 181 | $this.on('touchstart', function(event) { 182 | 183 | $this.touchPosX = event.originalEvent.touches[0].pageX; 184 | $this.touchPosY = event.originalEvent.touches[0].pageY; 185 | 186 | }) 187 | 188 | $this.on('touchmove', function(event) { 189 | 190 | if ($this.touchPosX === null 191 | || $this.touchPosY === null) 192 | return; 193 | 194 | var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX, 195 | diffY = $this.touchPosY - event.originalEvent.touches[0].pageY, 196 | th = $this.outerHeight(), 197 | ts = ($this.get(0).scrollHeight - $this.scrollTop()); 198 | 199 | // Hide on swipe? 200 | if (config.hideOnSwipe) { 201 | 202 | var result = false, 203 | boundary = 20, 204 | delta = 50; 205 | 206 | switch (config.side) { 207 | 208 | case 'left': 209 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta); 210 | break; 211 | 212 | case 'right': 213 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta)); 214 | break; 215 | 216 | case 'top': 217 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta); 218 | break; 219 | 220 | case 'bottom': 221 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta)); 222 | break; 223 | 224 | default: 225 | break; 226 | 227 | } 228 | 229 | if (result) { 230 | 231 | $this.touchPosX = null; 232 | $this.touchPosY = null; 233 | $this._hide(); 234 | 235 | return false; 236 | 237 | } 238 | 239 | } 240 | 241 | // Prevent vertical scrolling past the top or bottom. 242 | if (($this.scrollTop() < 0 && diffY < 0) 243 | || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) { 244 | 245 | event.preventDefault(); 246 | event.stopPropagation(); 247 | 248 | } 249 | 250 | }); 251 | 252 | // Event: Prevent certain events inside the panel from bubbling. 253 | $this.on('click touchend touchstart touchmove', function(event) { 254 | event.stopPropagation(); 255 | }); 256 | 257 | // Event: Hide panel if a child anchor tag pointing to its ID is clicked. 258 | $this.on('click', 'a[href="#' + id + '"]', function(event) { 259 | 260 | event.preventDefault(); 261 | event.stopPropagation(); 262 | 263 | config.target.removeClass(config.visibleClass); 264 | 265 | }); 266 | 267 | // Body. 268 | 269 | // Event: Hide panel on body click/tap. 270 | $body.on('click touchend', function(event) { 271 | $this._hide(event); 272 | }); 273 | 274 | // Event: Toggle. 275 | $body.on('click', 'a[href="#' + id + '"]', function(event) { 276 | 277 | event.preventDefault(); 278 | event.stopPropagation(); 279 | 280 | config.target.toggleClass(config.visibleClass); 281 | 282 | }); 283 | 284 | // Window. 285 | 286 | // Event: Hide on ESC. 287 | if (config.hideOnEscape) 288 | $window.on('keydown', function(event) { 289 | 290 | if (event.keyCode == 27) 291 | $this._hide(event); 292 | 293 | }); 294 | 295 | return $this; 296 | 297 | }; 298 | 299 | /** 300 | * Apply "placeholder" attribute polyfill to one or more forms. 301 | * @return {jQuery} jQuery object. 302 | */ 303 | $.fn.placeholder = function() { 304 | 305 | // Browser natively supports placeholders? Bail. 306 | if (typeof (document.createElement('input')).placeholder != 'undefined') 307 | return $(this); 308 | 309 | // No elements? 310 | if (this.length == 0) 311 | return $this; 312 | 313 | // Multiple elements? 314 | if (this.length > 1) { 315 | 316 | for (var i=0; i < this.length; i++) 317 | $(this[i]).placeholder(); 318 | 319 | return $this; 320 | 321 | } 322 | 323 | // Vars. 324 | var $this = $(this); 325 | 326 | // Text, TextArea. 327 | $this.find('input[type=text],textarea') 328 | .each(function() { 329 | 330 | var i = $(this); 331 | 332 | if (i.val() == '' 333 | || i.val() == i.attr('placeholder')) 334 | i 335 | .addClass('polyfill-placeholder') 336 | .val(i.attr('placeholder')); 337 | 338 | }) 339 | .on('blur', function() { 340 | 341 | var i = $(this); 342 | 343 | if (i.attr('name').match(/-polyfill-field$/)) 344 | return; 345 | 346 | if (i.val() == '') 347 | i 348 | .addClass('polyfill-placeholder') 349 | .val(i.attr('placeholder')); 350 | 351 | }) 352 | .on('focus', function() { 353 | 354 | var i = $(this); 355 | 356 | if (i.attr('name').match(/-polyfill-field$/)) 357 | return; 358 | 359 | if (i.val() == i.attr('placeholder')) 360 | i 361 | .removeClass('polyfill-placeholder') 362 | .val(''); 363 | 364 | }); 365 | 366 | // Password. 367 | $this.find('input[type=password]') 368 | .each(function() { 369 | 370 | var i = $(this); 371 | var x = $( 372 | $('
') 373 | .append(i.clone()) 374 | .remove() 375 | .html() 376 | .replace(/type="password"/i, 'type="text"') 377 | .replace(/type=password/i, 'type=text') 378 | ); 379 | 380 | if (i.attr('id') != '') 381 | x.attr('id', i.attr('id') + '-polyfill-field'); 382 | 383 | if (i.attr('name') != '') 384 | x.attr('name', i.attr('name') + '-polyfill-field'); 385 | 386 | x.addClass('polyfill-placeholder') 387 | .val(x.attr('placeholder')).insertAfter(i); 388 | 389 | if (i.val() == '') 390 | i.hide(); 391 | else 392 | x.hide(); 393 | 394 | i 395 | .on('blur', function(event) { 396 | 397 | event.preventDefault(); 398 | 399 | var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 400 | 401 | if (i.val() == '') { 402 | 403 | i.hide(); 404 | x.show(); 405 | 406 | } 407 | 408 | }); 409 | 410 | x 411 | .on('focus', function(event) { 412 | 413 | event.preventDefault(); 414 | 415 | var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']'); 416 | 417 | x.hide(); 418 | 419 | i 420 | .show() 421 | .focus(); 422 | 423 | }) 424 | .on('keypress', function(event) { 425 | 426 | event.preventDefault(); 427 | x.val(''); 428 | 429 | }); 430 | 431 | }); 432 | 433 | // Events. 434 | $this 435 | .on('submit', function() { 436 | 437 | $this.find('input[type=text],input[type=password],textarea') 438 | .each(function(event) { 439 | 440 | var i = $(this); 441 | 442 | if (i.attr('name').match(/-polyfill-field$/)) 443 | i.attr('name', ''); 444 | 445 | if (i.val() == i.attr('placeholder')) { 446 | 447 | i.removeClass('polyfill-placeholder'); 448 | i.val(''); 449 | 450 | } 451 | 452 | }); 453 | 454 | }) 455 | .on('reset', function(event) { 456 | 457 | event.preventDefault(); 458 | 459 | $this.find('select') 460 | .val($('option:first').val()); 461 | 462 | $this.find('input,textarea') 463 | .each(function() { 464 | 465 | var i = $(this), 466 | x; 467 | 468 | i.removeClass('polyfill-placeholder'); 469 | 470 | switch (this.type) { 471 | 472 | case 'submit': 473 | case 'reset': 474 | break; 475 | 476 | case 'password': 477 | i.val(i.attr('defaultValue')); 478 | 479 | x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 480 | 481 | if (i.val() == '') { 482 | i.hide(); 483 | x.show(); 484 | } 485 | else { 486 | i.show(); 487 | x.hide(); 488 | } 489 | 490 | break; 491 | 492 | case 'checkbox': 493 | case 'radio': 494 | i.attr('checked', i.attr('defaultValue')); 495 | break; 496 | 497 | case 'text': 498 | case 'textarea': 499 | i.val(i.attr('defaultValue')); 500 | 501 | if (i.val() == '') { 502 | i.addClass('polyfill-placeholder'); 503 | i.val(i.attr('placeholder')); 504 | } 505 | 506 | break; 507 | 508 | default: 509 | i.val(i.attr('defaultValue')); 510 | break; 511 | 512 | } 513 | }); 514 | 515 | }); 516 | 517 | return $this; 518 | 519 | }; 520 | 521 | /** 522 | * Moves elements to/from the first positions of their respective parents. 523 | * @param {jQuery} $elements Elements (or selector) to move. 524 | * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations. 525 | */ 526 | $.prioritize = function($elements, condition) { 527 | 528 | var key = '__prioritize'; 529 | 530 | // Expand $elements if it's not already a jQuery object. 531 | if (typeof $elements != 'jQuery') 532 | $elements = $($elements); 533 | 534 | // Step through elements. 535 | $elements.each(function() { 536 | 537 | var $e = $(this), $p, 538 | $parent = $e.parent(); 539 | 540 | // No parent? Bail. 541 | if ($parent.length == 0) 542 | return; 543 | 544 | // Not moved? Move it. 545 | if (!$e.data(key)) { 546 | 547 | // Condition is false? Bail. 548 | if (!condition) 549 | return; 550 | 551 | // Get placeholder (which will serve as our point of reference for when this element needs to move back). 552 | $p = $e.prev(); 553 | 554 | // Couldn't find anything? Means this element's already at the top, so bail. 555 | if ($p.length == 0) 556 | return; 557 | 558 | // Move element to top of parent. 559 | $e.prependTo($parent); 560 | 561 | // Mark element as moved. 562 | $e.data(key, $p); 563 | 564 | } 565 | 566 | // Moved already? 567 | else { 568 | 569 | // Condition is true? Bail. 570 | if (condition) 571 | return; 572 | 573 | $p = $e.data(key); 574 | 575 | // Move element back to its original location (using our placeholder). 576 | $e.insertAfter($p); 577 | 578 | // Unmark element as moved. 579 | $e.removeData(key); 580 | 581 | } 582 | 583 | }); 584 | 585 | }; 586 | 587 | })(jQuery); -------------------------------------------------------------------------------- /node/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "22eeabe9722e1739153b06363ed70a6e", 8 | "content-hash": "ddd7984319ba447a04a42e4dda759ca9", 9 | "packages": [ 10 | { 11 | "name": "amercier/cli-helpers", 12 | "version": "v1.4.4", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/amercier/php-cli-helpers.git", 16 | "reference": "78c77c30d41a0cd45a5be3289e90d688430cb28f" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/amercier/php-cli-helpers/zipball/78c77c30d41a0cd45a5be3289e90d688430cb28f", 21 | "reference": "78c77c30d41a0cd45a5be3289e90d688430cb28f", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3.2" 26 | }, 27 | "require-dev": { 28 | "pear-pear.php.net/php_codesniffer": ">=1.4.5", 29 | "phpunit/phpunit": "4.*", 30 | "satooshi/php-coveralls": ">=0.6.1" 31 | }, 32 | "type": "library", 33 | "autoload": { 34 | "psr-0": { 35 | "Cli": "src/" 36 | } 37 | }, 38 | "notification-url": "https://packagist.org/downloads/", 39 | "include-path": [ 40 | "src/" 41 | ], 42 | "license": [ 43 | "MIT" 44 | ], 45 | "description": "Utility classes to write PHP command-line scripts", 46 | "homepage": "https://github.com/amercier/php-cli-helpers", 47 | "keywords": [ 48 | "cli", 49 | "command", 50 | "command-line", 51 | "helpers", 52 | "line", 53 | "utilities", 54 | "utility" 55 | ], 56 | "time": "2014-09-18 17:37:41" 57 | }, 58 | { 59 | "name": "apix/log", 60 | "version": "1.0.2", 61 | "source": { 62 | "type": "git", 63 | "url": "https://github.com/frqnck/apix-log.git", 64 | "reference": "e48bae2fb139fe3cfe3987600a61e582ec7c9d11" 65 | }, 66 | "dist": { 67 | "type": "zip", 68 | "url": "https://api.github.com/repos/frqnck/apix-log/zipball/e48bae2fb139fe3cfe3987600a61e582ec7c9d11", 69 | "reference": "e48bae2fb139fe3cfe3987600a61e582ec7c9d11", 70 | "shasum": "" 71 | }, 72 | "require": { 73 | "php": ">=5.3", 74 | "psr/log": "~1.0" 75 | }, 76 | "provide": { 77 | "psr/log-implementation": "1.0.0" 78 | }, 79 | "require-dev": { 80 | "phpunit/phpunit": "3.7.*" 81 | }, 82 | "type": "library", 83 | "autoload": { 84 | "psr-4": { 85 | "Apix\\Log\\": "src/", 86 | "Apix\\Log\\tests\\": "tests/" 87 | } 88 | }, 89 | "notification-url": "https://packagist.org/downloads/", 90 | "license": [ 91 | "BSD-3-Clause" 92 | ], 93 | "authors": [ 94 | { 95 | "name": "Franck Cassedanne", 96 | "email": "franck@ouarz.net" 97 | } 98 | ], 99 | "description": "Minimalist, thin and fast PSR-3 compliant (multi-bucket) logger.", 100 | "homepage": "https://github.com/frqnck/apix-log", 101 | "keywords": [ 102 | "apix", 103 | "log", 104 | "logger", 105 | "psr", 106 | "psr-3" 107 | ], 108 | "time": "2015-06-10 18:31:13" 109 | }, 110 | { 111 | "name": "herrera-io/json", 112 | "version": "1.0.3", 113 | "source": { 114 | "type": "git", 115 | "url": "https://github.com/kherge-abandoned/php-json.git", 116 | "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1" 117 | }, 118 | "dist": { 119 | "type": "zip", 120 | "url": "https://api.github.com/repos/kherge-abandoned/php-json/zipball/60c696c9370a1e5136816ca557c17f82a6fa83f1", 121 | "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1", 122 | "shasum": "" 123 | }, 124 | "require": { 125 | "ext-json": "*", 126 | "justinrainbow/json-schema": ">=1.0,<2.0-dev", 127 | "php": ">=5.3.3", 128 | "seld/jsonlint": ">=1.0,<2.0-dev" 129 | }, 130 | "require-dev": { 131 | "herrera-io/phpunit-test-case": "1.*", 132 | "mikey179/vfsstream": "1.1.0", 133 | "phpunit/phpunit": "3.7.*" 134 | }, 135 | "type": "library", 136 | "extra": { 137 | "branch-alias": { 138 | "dev-master": "1.0-dev" 139 | } 140 | }, 141 | "autoload": { 142 | "files": [ 143 | "src/lib/json_version.php" 144 | ], 145 | "psr-0": { 146 | "Herrera\\Json": "src/lib" 147 | } 148 | }, 149 | "notification-url": "https://packagist.org/downloads/", 150 | "license": [ 151 | "MIT" 152 | ], 153 | "authors": [ 154 | { 155 | "name": "Kevin Herrera", 156 | "email": "kevin@herrera.io", 157 | "homepage": "http://kevin.herrera.io/", 158 | "role": "Developer" 159 | } 160 | ], 161 | "description": "A library for simplifying JSON linting and validation.", 162 | "homepage": "http://herrera-io.github.com/php-json", 163 | "keywords": [ 164 | "json", 165 | "lint", 166 | "schema", 167 | "validate" 168 | ], 169 | "time": "2013-10-30 16:51:34" 170 | }, 171 | { 172 | "name": "herrera-io/phar-update", 173 | "version": "1.0.3", 174 | "source": { 175 | "type": "git", 176 | "url": "https://github.com/kherge-abandoned/php-phar-update.git", 177 | "reference": "00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b" 178 | }, 179 | "dist": { 180 | "type": "zip", 181 | "url": "https://api.github.com/repos/kherge-abandoned/php-phar-update/zipball/00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b", 182 | "reference": "00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b", 183 | "shasum": "" 184 | }, 185 | "require": { 186 | "herrera-io/json": "1.*", 187 | "kherge/version": "1.*", 188 | "php": ">=5.3.3" 189 | }, 190 | "require-dev": { 191 | "herrera-io/phpunit-test-case": "1.*", 192 | "mikey179/vfsstream": "1.1.0", 193 | "phpunit/phpunit": "3.7.*" 194 | }, 195 | "type": "library", 196 | "extra": { 197 | "branch-alias": { 198 | "dev-master": "1.0-dev" 199 | } 200 | }, 201 | "autoload": { 202 | "files": [ 203 | "src/lib/constants.php" 204 | ], 205 | "psr-0": { 206 | "Herrera\\Phar\\Update": "src/lib" 207 | } 208 | }, 209 | "notification-url": "https://packagist.org/downloads/", 210 | "license": [ 211 | "MIT" 212 | ], 213 | "authors": [ 214 | { 215 | "name": "Kevin Herrera", 216 | "email": "kevin@herrera.io", 217 | "homepage": "http://kevin.herrera.io/", 218 | "role": "Developer" 219 | } 220 | ], 221 | "description": "A library for self-updating Phars.", 222 | "homepage": "http://herrera-io.github.com/php-phar-update", 223 | "keywords": [ 224 | "phar", 225 | "update" 226 | ], 227 | "time": "2013-10-30 17:23:01" 228 | }, 229 | { 230 | "name": "justinrainbow/json-schema", 231 | "version": "v1.6.0", 232 | "source": { 233 | "type": "git", 234 | "url": "https://github.com/justinrainbow/json-schema.git", 235 | "reference": "f9e27c3e202faf14fd581ef41355d83bb4b7eb7d" 236 | }, 237 | "dist": { 238 | "type": "zip", 239 | "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/f9e27c3e202faf14fd581ef41355d83bb4b7eb7d", 240 | "reference": "f9e27c3e202faf14fd581ef41355d83bb4b7eb7d", 241 | "shasum": "" 242 | }, 243 | "require": { 244 | "php": ">=5.3.2" 245 | }, 246 | "require-dev": { 247 | "json-schema/json-schema-test-suite": "1.1.0", 248 | "phpdocumentor/phpdocumentor": "~2", 249 | "phpunit/phpunit": "~3.7" 250 | }, 251 | "bin": [ 252 | "bin/validate-json" 253 | ], 254 | "type": "library", 255 | "extra": { 256 | "branch-alias": { 257 | "dev-master": "1.4.x-dev" 258 | } 259 | }, 260 | "autoload": { 261 | "psr-4": { 262 | "JsonSchema\\": "src/JsonSchema/" 263 | } 264 | }, 265 | "notification-url": "https://packagist.org/downloads/", 266 | "license": [ 267 | "BSD-3-Clause" 268 | ], 269 | "authors": [ 270 | { 271 | "name": "Bruno Prieto Reis", 272 | "email": "bruno.p.reis@gmail.com" 273 | }, 274 | { 275 | "name": "Justin Rainbow", 276 | "email": "justin.rainbow@gmail.com" 277 | }, 278 | { 279 | "name": "Igor Wiedler", 280 | "email": "igor@wiedler.ch" 281 | }, 282 | { 283 | "name": "Robert Schönthal", 284 | "email": "seroscho@googlemail.com" 285 | } 286 | ], 287 | "description": "A library to validate a json schema.", 288 | "homepage": "https://github.com/justinrainbow/json-schema", 289 | "keywords": [ 290 | "json", 291 | "schema" 292 | ], 293 | "time": "2016-01-06 14:37:04" 294 | }, 295 | { 296 | "name": "kherge/version", 297 | "version": "1.0.1", 298 | "source": { 299 | "type": "git", 300 | "url": "https://github.com/kherge-abandoned/Version.git", 301 | "reference": "f07cf83f8ce533be8f93d2893d96d674bbeb7e30" 302 | }, 303 | "dist": { 304 | "type": "zip", 305 | "url": "https://api.github.com/repos/kherge-abandoned/Version/zipball/f07cf83f8ce533be8f93d2893d96d674bbeb7e30", 306 | "reference": "f07cf83f8ce533be8f93d2893d96d674bbeb7e30", 307 | "shasum": "" 308 | }, 309 | "require": { 310 | "php": ">=5.3.3" 311 | }, 312 | "type": "library", 313 | "extra": { 314 | "branch-alias": { 315 | "dev-master": "1.0-dev" 316 | } 317 | }, 318 | "autoload": { 319 | "psr-0": { 320 | "KevinGH\\Version": "src/lib/" 321 | } 322 | }, 323 | "notification-url": "https://packagist.org/downloads/", 324 | "license": [ 325 | "MIT" 326 | ], 327 | "authors": [ 328 | { 329 | "name": "Kevin Herrera", 330 | "email": "me@kevingh.com", 331 | "homepage": "http://www.kevingh.com/" 332 | } 333 | ], 334 | "description": "A parsing and comparison library for semantic versioning.", 335 | "homepage": "http://github.com/kherge/Version", 336 | "time": "2012-08-16 17:13:03" 337 | }, 338 | { 339 | "name": "psr/log", 340 | "version": "1.0.0", 341 | "source": { 342 | "type": "git", 343 | "url": "https://github.com/php-fig/log.git", 344 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" 345 | }, 346 | "dist": { 347 | "type": "zip", 348 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", 349 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", 350 | "shasum": "" 351 | }, 352 | "type": "library", 353 | "autoload": { 354 | "psr-0": { 355 | "Psr\\Log\\": "" 356 | } 357 | }, 358 | "notification-url": "https://packagist.org/downloads/", 359 | "license": [ 360 | "MIT" 361 | ], 362 | "authors": [ 363 | { 364 | "name": "PHP-FIG", 365 | "homepage": "http://www.php-fig.org/" 366 | } 367 | ], 368 | "description": "Common interface for logging libraries", 369 | "keywords": [ 370 | "log", 371 | "psr", 372 | "psr-3" 373 | ], 374 | "time": "2012-12-21 11:40:51" 375 | }, 376 | { 377 | "name": "seld/jsonlint", 378 | "version": "1.4.0", 379 | "source": { 380 | "type": "git", 381 | "url": "https://github.com/Seldaek/jsonlint.git", 382 | "reference": "66834d3e3566bb5798db7294619388786ae99394" 383 | }, 384 | "dist": { 385 | "type": "zip", 386 | "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/66834d3e3566bb5798db7294619388786ae99394", 387 | "reference": "66834d3e3566bb5798db7294619388786ae99394", 388 | "shasum": "" 389 | }, 390 | "require": { 391 | "php": "^5.3 || ^7.0" 392 | }, 393 | "bin": [ 394 | "bin/jsonlint" 395 | ], 396 | "type": "library", 397 | "autoload": { 398 | "psr-4": { 399 | "Seld\\JsonLint\\": "src/Seld/JsonLint/" 400 | } 401 | }, 402 | "notification-url": "https://packagist.org/downloads/", 403 | "license": [ 404 | "MIT" 405 | ], 406 | "authors": [ 407 | { 408 | "name": "Jordi Boggiano", 409 | "email": "j.boggiano@seld.be", 410 | "homepage": "http://seld.be" 411 | } 412 | ], 413 | "description": "JSON Linter", 414 | "keywords": [ 415 | "json", 416 | "linter", 417 | "parser", 418 | "validator" 419 | ], 420 | "time": "2015-11-21 02:21:41" 421 | } 422 | ], 423 | "packages-dev": [], 424 | "aliases": [], 425 | "minimum-stability": "stable", 426 | "stability-flags": [], 427 | "prefer-stable": false, 428 | "prefer-lowest": false, 429 | "platform": { 430 | "php": ">=5.4.0" 431 | }, 432 | "platform-dev": [] 433 | } 434 | -------------------------------------------------------------------------------- /web/assets/sass/libs/_skel.scss: -------------------------------------------------------------------------------- 1 | // skel.scss v3.0.0 | (c) n33 | skel.io | MIT licensed */ 2 | 3 | // Vars. 4 | 5 | /// Breakpoints. 6 | /// @var {list} 7 | $breakpoints: () !global; 8 | 9 | /// Vendor prefixes. 10 | /// @var {list} 11 | $vendor-prefixes: ( 12 | '-moz-', 13 | '-webkit-', 14 | '-ms-', 15 | '' 16 | ); 17 | 18 | /// Properties that should be vendorized. 19 | /// @var {list} 20 | $vendor-properties: ( 21 | 'align-content', 22 | 'align-items', 23 | 'align-self', 24 | 'animation', 25 | 'animation-delay', 26 | 'animation-direction', 27 | 'animation-duration', 28 | 'animation-fill-mode', 29 | 'animation-iteration-count', 30 | 'animation-name', 31 | 'animation-play-state', 32 | 'animation-timing-function', 33 | 'appearance', 34 | 'backface-visibility', 35 | 'box-sizing', 36 | 'filter', 37 | 'flex', 38 | 'flex-basis', 39 | 'flex-direction', 40 | 'flex-flow', 41 | 'flex-grow', 42 | 'flex-shrink', 43 | 'flex-wrap', 44 | 'justify-content', 45 | 'order', 46 | 'perspective', 47 | 'pointer-events', 48 | 'transform', 49 | 'transform-origin', 50 | 'transform-style', 51 | 'transition', 52 | 'transition-delay', 53 | 'transition-duration', 54 | 'transition-property', 55 | 'transition-timing-function' 56 | ); 57 | 58 | /// Values that should be vendorized. 59 | /// @var {list} 60 | $vendor-values: ( 61 | 'filter', 62 | 'flex', 63 | 'linear-gradient', 64 | 'radial-gradient', 65 | 'transform' 66 | ); 67 | 68 | // Functions. 69 | 70 | /// Removes a specific item from a list. 71 | /// @author Hugo Giraudel 72 | /// @param {list} $list List. 73 | /// @param {integer} $index Index. 74 | /// @return {list} Updated list. 75 | @function remove-nth($list, $index) { 76 | 77 | $result: null; 78 | 79 | @if type-of($index) != number { 80 | @warn "$index: #{quote($index)} is not a number for `remove-nth`."; 81 | } 82 | @else if $index == 0 { 83 | @warn "List index 0 must be a non-zero integer for `remove-nth`."; 84 | } 85 | @else if abs($index) > length($list) { 86 | @warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`."; 87 | } 88 | @else { 89 | 90 | $result: (); 91 | $index: if($index < 0, length($list) + $index + 1, $index); 92 | 93 | @for $i from 1 through length($list) { 94 | 95 | @if $i != $index { 96 | $result: append($result, nth($list, $i)); 97 | } 98 | 99 | } 100 | 101 | } 102 | 103 | @return $result; 104 | 105 | } 106 | 107 | /// Replaces a substring within another string. 108 | /// @author Hugo Giraudel 109 | /// @param {string} $string String. 110 | /// @param {string} $search Substring. 111 | /// @param {string} $replace Replacement. 112 | /// @return {string} Updated string. 113 | @function str-replace($string, $search, $replace: '') { 114 | 115 | $index: str-index($string, $search); 116 | 117 | @if $index { 118 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); 119 | } 120 | 121 | @return $string; 122 | 123 | } 124 | 125 | /// Replaces a substring within each string in a list. 126 | /// @param {list} $strings List of strings. 127 | /// @param {string} $search Substring. 128 | /// @param {string} $replace Replacement. 129 | /// @return {list} Updated list of strings. 130 | @function str-replace-all($strings, $search, $replace: '') { 131 | 132 | @each $string in $strings { 133 | $strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace)); 134 | } 135 | 136 | @return $strings; 137 | 138 | } 139 | 140 | /// Gets a value from a map. 141 | /// @author Hugo Giraudel 142 | /// @param {map} $map Map. 143 | /// @param {string} $keys Key(s). 144 | /// @return {string} Value. 145 | @function val($map, $keys...) { 146 | 147 | @if nth($keys, 1) == null { 148 | $keys: remove-nth($keys, 1); 149 | } 150 | 151 | @each $key in $keys { 152 | $map: map-get($map, $key); 153 | } 154 | 155 | @return $map; 156 | 157 | } 158 | 159 | // Mixins. 160 | 161 | /// Sets the global box model. 162 | /// @param {string} $model Model (default is content). 163 | @mixin boxModel($model: 'content') { 164 | 165 | $x: $model + '-box'; 166 | 167 | *, *:before, *:after { 168 | -moz-box-sizing: #{$x}; 169 | -webkit-box-sizing: #{$x}; 170 | box-sizing: #{$x}; 171 | } 172 | 173 | } 174 | 175 | /// Wraps @content in a @media block using a given breakpoint. 176 | /// @param {string} $breakpoint Breakpoint. 177 | /// @param {map} $queries Additional queries. 178 | @mixin breakpoint($breakpoint: null, $queries: null) { 179 | 180 | $query: 'screen'; 181 | 182 | // Breakpoint. 183 | @if $breakpoint and map-has-key($breakpoints, $breakpoint) { 184 | $query: $query + ' and ' + map-get($breakpoints, $breakpoint); 185 | } 186 | 187 | // Queries. 188 | @if $queries { 189 | @each $k, $v in $queries { 190 | $query: $query + ' and (' + $k + ':' + $v + ')'; 191 | } 192 | } 193 | 194 | @media #{$query} { 195 | @content; 196 | } 197 | 198 | } 199 | 200 | /// Wraps @content in a @media block targeting a specific orientation. 201 | /// @param {string} $orientation Orientation. 202 | @mixin orientation($orientation) { 203 | @media screen and (orientation: #{$orientation}) { 204 | @content; 205 | } 206 | } 207 | 208 | /// Utility mixin for containers. 209 | /// @param {mixed} $width Width. 210 | @mixin containers($width) { 211 | 212 | // Locked? 213 | $lock: false; 214 | 215 | @if length($width) == 2 { 216 | $width: nth($width, 1); 217 | $lock: true; 218 | } 219 | 220 | // Modifiers. 221 | .container.\31 25\25 { width: 100%; max-width: $width * 1.25; min-width: $width; } 222 | .container.\37 5\25 { width: $width * 0.75; } 223 | .container.\35 0\25 { width: $width * 0.5; } 224 | .container.\32 5\25 { width: $width * 0.25; } 225 | 226 | // Main class. 227 | .container { 228 | @if $lock { 229 | width: $width !important; 230 | } 231 | @else { 232 | width: $width; 233 | } 234 | } 235 | 236 | } 237 | 238 | /// Utility mixin for grid. 239 | /// @param {list} $gutters Column and row gutters (default is 40px). 240 | /// @param {string} $breakpointName Optional breakpoint name. 241 | @mixin grid($gutters: 40px, $breakpointName: null) { 242 | 243 | // Gutters. 244 | @include grid-gutters($gutters); 245 | @include grid-gutters($gutters, \32 00\25, 2); 246 | @include grid-gutters($gutters, \31 50\25, 1.5); 247 | @include grid-gutters($gutters, \35 0\25, 0.5); 248 | @include grid-gutters($gutters, \32 5\25, 0.25); 249 | 250 | // Cells. 251 | $x: ''; 252 | 253 | @if $breakpointName { 254 | $x: '\\28' + $breakpointName + '\\29'; 255 | } 256 | 257 | .\31 2u#{$x}, .\31 2u\24#{$x} { width: 100%; clear: none; margin-left: 0; } 258 | .\31 1u#{$x}, .\31 1u\24#{$x} { width: 91.6666666667%; clear: none; margin-left: 0; } 259 | .\31 0u#{$x}, .\31 0u\24#{$x} { width: 83.3333333333%; clear: none; margin-left: 0; } 260 | .\39 u#{$x}, .\39 u\24#{$x} { width: 75%; clear: none; margin-left: 0; } 261 | .\38 u#{$x}, .\38 u\24#{$x} { width: 66.6666666667%; clear: none; margin-left: 0; } 262 | .\37 u#{$x}, .\37 u\24#{$x} { width: 58.3333333333%; clear: none; margin-left: 0; } 263 | .\36 u#{$x}, .\36 u\24#{$x} { width: 50%; clear: none; margin-left: 0; } 264 | .\35 u#{$x}, .\35 u\24#{$x} { width: 41.6666666667%; clear: none; margin-left: 0; } 265 | .\34 u#{$x}, .\34 u\24#{$x} { width: 33.3333333333%; clear: none; margin-left: 0; } 266 | .\33 u#{$x}, .\33 u\24#{$x} { width: 25%; clear: none; margin-left: 0; } 267 | .\32 u#{$x}, .\32 u\24#{$x} { width: 16.6666666667%; clear: none; margin-left: 0; } 268 | .\31 u#{$x}, .\31 u\24#{$x} { width: 8.3333333333%; clear: none; margin-left: 0; } 269 | 270 | .\31 2u\24#{$x} + *, 271 | .\31 1u\24#{$x} + *, 272 | .\31 0u\24#{$x} + *, 273 | .\39 u\24#{$x} + *, 274 | .\38 u\24#{$x} + *, 275 | .\37 u\24#{$x} + *, 276 | .\36 u\24#{$x} + *, 277 | .\35 u\24#{$x} + *, 278 | .\34 u\24#{$x} + *, 279 | .\33 u\24#{$x} + *, 280 | .\32 u\24#{$x} + *, 281 | .\31 u\24#{$x} + * { 282 | clear: left; 283 | } 284 | 285 | .\-11u#{$x} { margin-left: 91.6666666667% } 286 | .\-10u#{$x} { margin-left: 83.3333333333% } 287 | .\-9u#{$x} { margin-left: 75% } 288 | .\-8u#{$x} { margin-left: 66.6666666667% } 289 | .\-7u#{$x} { margin-left: 58.3333333333% } 290 | .\-6u#{$x} { margin-left: 50% } 291 | .\-5u#{$x} { margin-left: 41.6666666667% } 292 | .\-4u#{$x} { margin-left: 33.3333333333% } 293 | .\-3u#{$x} { margin-left: 25% } 294 | .\-2u#{$x} { margin-left: 16.6666666667% } 295 | .\-1u#{$x} { margin-left: 8.3333333333% } 296 | 297 | } 298 | 299 | /// Utility mixin for grid. 300 | /// @param {list} $gutters Gutters. 301 | /// @param {string} $class Optional class name. 302 | /// @param {integer} $multiplier Multiplier (default is 1). 303 | @mixin grid-gutters($gutters, $class: null, $multiplier: 1) { 304 | 305 | // Expand gutters if it's not a list. 306 | @if length($gutters) == 1 { 307 | $gutters: ($gutters, 0); 308 | } 309 | 310 | // Get column and row gutter values. 311 | $c: nth($gutters, 1); 312 | $r: nth($gutters, 2); 313 | 314 | // Get class (if provided). 315 | $x: ''; 316 | 317 | @if $class { 318 | $x: '.' + $class; 319 | } 320 | 321 | // Default. 322 | .row#{$x} > * { padding: ($r * $multiplier) 0 0 ($c * $multiplier); } 323 | .row#{$x} { margin: ($r * $multiplier * -1) 0 -1px ($c * $multiplier * -1); } 324 | 325 | // Uniform. 326 | .row.uniform#{$x} > * { padding: ($c * $multiplier) 0 0 ($c * $multiplier); } 327 | .row.uniform#{$x} { margin: ($c * $multiplier * -1) 0 -1px ($c * $multiplier * -1); } 328 | 329 | } 330 | 331 | /// Wraps @content in vendorized keyframe blocks. 332 | /// @param {string} $name Name. 333 | @mixin keyframes($name) { 334 | 335 | @-moz-keyframes #{$name} { @content; } 336 | @-webkit-keyframes #{$name} { @content; } 337 | @-ms-keyframes #{$name} { @content; } 338 | @keyframes #{$name} { @content; } 339 | 340 | } 341 | 342 | /// 343 | /// Sets breakpoints. 344 | /// @param {map} $x Breakpoints. 345 | /// 346 | @mixin skel-breakpoints($x: ()) { 347 | $breakpoints: $x !global; 348 | } 349 | 350 | /// 351 | /// Initializes layout module. 352 | /// @param {map} config Config. 353 | /// 354 | @mixin skel-layout($config: ()) { 355 | 356 | // Config. 357 | $configPerBreakpoint: (); 358 | 359 | $z: map-get($config, 'breakpoints'); 360 | 361 | @if $z { 362 | $configPerBreakpoint: $z; 363 | } 364 | 365 | // Reset. 366 | $x: map-get($config, 'reset'); 367 | 368 | @if $x { 369 | 370 | /* Reset */ 371 | 372 | @include reset($x); 373 | 374 | } 375 | 376 | // Box model. 377 | $x: map-get($config, 'boxModel'); 378 | 379 | @if $x { 380 | 381 | /* Box Model */ 382 | 383 | @include boxModel($x); 384 | 385 | } 386 | 387 | // Containers. 388 | $containers: map-get($config, 'containers'); 389 | 390 | @if $containers { 391 | 392 | /* Containers */ 393 | 394 | .container { 395 | margin-left: auto; 396 | margin-right: auto; 397 | } 398 | 399 | // Use default is $containers is just "true". 400 | @if $containers == true { 401 | $containers: 960px; 402 | } 403 | 404 | // Apply base. 405 | @include containers($containers); 406 | 407 | // Apply per-breakpoint. 408 | @each $name in map-keys($breakpoints) { 409 | 410 | // Get/use breakpoint setting if it exists. 411 | $x: map-get($configPerBreakpoint, $name); 412 | 413 | // Per-breakpoint config exists? 414 | @if $x { 415 | $y: map-get($x, 'containers'); 416 | 417 | // Setting exists? Use it. 418 | @if $y { 419 | $containers: $y; 420 | } 421 | 422 | } 423 | 424 | // Create @media block. 425 | @media screen and #{map-get($breakpoints, $name)} { 426 | @include containers($containers); 427 | } 428 | 429 | } 430 | 431 | } 432 | 433 | // Grid. 434 | $grid: map-get($config, 'grid'); 435 | 436 | @if $grid { 437 | 438 | /* Grid */ 439 | 440 | // Use defaults if $grid is just "true". 441 | @if $grid == true { 442 | $grid: (); 443 | } 444 | 445 | // Sub-setting: Gutters. 446 | $grid-gutters: 40px; 447 | $x: map-get($grid, 'gutters'); 448 | 449 | @if $x { 450 | $grid-gutters: $x; 451 | } 452 | 453 | // Rows. 454 | .row { 455 | border-bottom: solid 1px transparent; 456 | -moz-box-sizing: border-box; 457 | -webkit-box-sizing: border-box; 458 | box-sizing: border-box; 459 | } 460 | 461 | .row > * { 462 | float: left; 463 | -moz-box-sizing: border-box; 464 | -webkit-box-sizing: border-box; 465 | box-sizing: border-box; 466 | } 467 | 468 | .row:after, .row:before { 469 | content: ''; 470 | display: block; 471 | clear: both; 472 | height: 0; 473 | } 474 | 475 | .row.uniform > * > :first-child { 476 | margin-top: 0; 477 | } 478 | 479 | .row.uniform > * > :last-child { 480 | margin-bottom: 0; 481 | } 482 | 483 | // Gutters (0%). 484 | @include grid-gutters($grid-gutters, \30 \25, 0); 485 | 486 | // Apply base. 487 | @include grid($grid-gutters); 488 | 489 | // Apply per-breakpoint. 490 | @each $name in map-keys($breakpoints) { 491 | 492 | // Get/use breakpoint setting if it exists. 493 | $x: map-get($configPerBreakpoint, $name); 494 | 495 | // Per-breakpoint config exists? 496 | @if $x { 497 | $y: map-get($x, 'grid'); 498 | 499 | // Setting exists? 500 | @if $y { 501 | 502 | // Sub-setting: Gutters. 503 | $x: map-get($y, 'gutters'); 504 | 505 | @if $x { 506 | $grid-gutters: $x; 507 | } 508 | 509 | } 510 | 511 | } 512 | 513 | // Create @media block. 514 | @media screen and #{map-get($breakpoints, $name)} { 515 | @include grid($grid-gutters, $name); 516 | } 517 | 518 | } 519 | 520 | } 521 | 522 | } 523 | 524 | /// Resets browser styles. 525 | /// @param {string} $mode Mode (default is 'normalize'). 526 | @mixin reset($mode: 'normalize') { 527 | 528 | @if $mode == 'normalize' { 529 | 530 | // normalize.css v3.0.2 | MIT License | git.io/normalize 531 | html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} 532 | 533 | } 534 | @else if $mode == 'full' { 535 | 536 | // meyerweb.com/eric/tools/css/reset v2.0 | 20110126 | License: none (public domain) 537 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block;}body{line-height:1;}ol,ul{list-style:none;}blockquote,q{quotes:none;}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}table{border-collapse:collapse;border-spacing:0;}body{-webkit-text-size-adjust:none} 538 | 539 | } 540 | 541 | } 542 | 543 | /// Vendorizes a declaration's property and/or value(s). 544 | /// @param {string} $property Property. 545 | /// @param {mixed} $value String/list of value(s). 546 | @mixin vendor($property, $value) { 547 | 548 | // Determine if property should expand. 549 | $expandProperty: index($vendor-properties, $property); 550 | 551 | // Determine if value should expand (and if so, add '-prefix-' placeholder). 552 | $expandValue: false; 553 | 554 | @each $x in $value { 555 | @each $y in $vendor-values { 556 | @if $y == str-slice($x, 1, str-length($y)) { 557 | 558 | $value: set-nth($value, index($value, $x), '-prefix-' + $x); 559 | $expandValue: true; 560 | 561 | } 562 | } 563 | } 564 | 565 | // Expand property? 566 | @if $expandProperty { 567 | @each $vendor in $vendor-prefixes { 568 | #{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)}; 569 | } 570 | } 571 | 572 | // Expand just the value? 573 | @elseif $expandValue { 574 | @each $vendor in $vendor-prefixes { 575 | #{$property}: #{str-replace-all($value, '-prefix-', $vendor)}; 576 | } 577 | } 578 | 579 | // Neither? Treat them as a normal declaration. 580 | @else { 581 | #{$property}: #{$value}; 582 | } 583 | 584 | } -------------------------------------------------------------------------------- /master/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "87167de2eae1b754d7b205d2313e5713", 8 | "content-hash": "4106916a10fef47bda8fe5d9f6ba4c5a", 9 | "packages": [ 10 | { 11 | "name": "chrisboulton/php-resque", 12 | "version": "dev-master", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/chrisboulton/php-resque.git", 16 | "reference": "df69e8980cc21652f10cd775cb6a0e8c572ffd2d" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/chrisboulton/php-resque/zipball/df69e8980cc21652f10cd775cb6a0e8c572ffd2d", 21 | "reference": "df69e8980cc21652f10cd775cb6a0e8c572ffd2d", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "colinmollenhour/credis": "~1.2", 26 | "ext-pcntl": "*", 27 | "php": ">=5.3.0", 28 | "psr/log": "1.0.0" 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "3.7.*" 32 | }, 33 | "suggest": { 34 | "ext-proctitle": "Allows php-resque to rename the title of UNIX processes to show the status of a worker.", 35 | "ext-redis": "Native PHP extension for Redis connectivity. Credis will automatically utilize when available." 36 | }, 37 | "bin": [ 38 | "bin/resque" 39 | ], 40 | "type": "library", 41 | "autoload": { 42 | "psr-0": { 43 | "Resque": "lib" 44 | } 45 | }, 46 | "notification-url": "https://packagist.org/downloads/", 47 | "license": [ 48 | "MIT" 49 | ], 50 | "authors": [ 51 | { 52 | "name": "Chris Boulton", 53 | "email": "chris@bigcommerce.com" 54 | } 55 | ], 56 | "description": "Redis backed library for creating background jobs and processing them later. Based on resque for Ruby.", 57 | "homepage": "http://www.github.com/chrisboulton/php-resque/", 58 | "keywords": [ 59 | "background", 60 | "job", 61 | "redis", 62 | "resque" 63 | ], 64 | "time": "2015-05-13 11:58:23" 65 | }, 66 | { 67 | "name": "colinmollenhour/credis", 68 | "version": "1.6", 69 | "source": { 70 | "type": "git", 71 | "url": "https://github.com/colinmollenhour/credis.git", 72 | "reference": "409edfd0ea81f5cb74afbdb86df54890c207b5e4" 73 | }, 74 | "dist": { 75 | "type": "zip", 76 | "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/409edfd0ea81f5cb74afbdb86df54890c207b5e4", 77 | "reference": "409edfd0ea81f5cb74afbdb86df54890c207b5e4", 78 | "shasum": "" 79 | }, 80 | "require": { 81 | "php": ">=5.3.0" 82 | }, 83 | "type": "library", 84 | "autoload": { 85 | "classmap": [ 86 | "Client.php", 87 | "Cluster.php", 88 | "Sentinel.php" 89 | ] 90 | }, 91 | "notification-url": "https://packagist.org/downloads/", 92 | "license": [ 93 | "MIT" 94 | ], 95 | "authors": [ 96 | { 97 | "name": "Colin Mollenhour", 98 | "email": "colin@mollenhour.com" 99 | } 100 | ], 101 | "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", 102 | "homepage": "https://github.com/colinmollenhour/credis", 103 | "time": "2015-11-28 01:20:04" 104 | }, 105 | { 106 | "name": "guzzle/guzzle", 107 | "version": "v3.9.3", 108 | "source": { 109 | "type": "git", 110 | "url": "https://github.com/guzzle/guzzle3.git", 111 | "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" 112 | }, 113 | "dist": { 114 | "type": "zip", 115 | "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", 116 | "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", 117 | "shasum": "" 118 | }, 119 | "require": { 120 | "ext-curl": "*", 121 | "php": ">=5.3.3", 122 | "symfony/event-dispatcher": "~2.1" 123 | }, 124 | "replace": { 125 | "guzzle/batch": "self.version", 126 | "guzzle/cache": "self.version", 127 | "guzzle/common": "self.version", 128 | "guzzle/http": "self.version", 129 | "guzzle/inflection": "self.version", 130 | "guzzle/iterator": "self.version", 131 | "guzzle/log": "self.version", 132 | "guzzle/parser": "self.version", 133 | "guzzle/plugin": "self.version", 134 | "guzzle/plugin-async": "self.version", 135 | "guzzle/plugin-backoff": "self.version", 136 | "guzzle/plugin-cache": "self.version", 137 | "guzzle/plugin-cookie": "self.version", 138 | "guzzle/plugin-curlauth": "self.version", 139 | "guzzle/plugin-error-response": "self.version", 140 | "guzzle/plugin-history": "self.version", 141 | "guzzle/plugin-log": "self.version", 142 | "guzzle/plugin-md5": "self.version", 143 | "guzzle/plugin-mock": "self.version", 144 | "guzzle/plugin-oauth": "self.version", 145 | "guzzle/service": "self.version", 146 | "guzzle/stream": "self.version" 147 | }, 148 | "require-dev": { 149 | "doctrine/cache": "~1.3", 150 | "monolog/monolog": "~1.0", 151 | "phpunit/phpunit": "3.7.*", 152 | "psr/log": "~1.0", 153 | "symfony/class-loader": "~2.1", 154 | "zendframework/zend-cache": "2.*,<2.3", 155 | "zendframework/zend-log": "2.*,<2.3" 156 | }, 157 | "suggest": { 158 | "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." 159 | }, 160 | "type": "library", 161 | "extra": { 162 | "branch-alias": { 163 | "dev-master": "3.9-dev" 164 | } 165 | }, 166 | "autoload": { 167 | "psr-0": { 168 | "Guzzle": "src/", 169 | "Guzzle\\Tests": "tests/" 170 | } 171 | }, 172 | "notification-url": "https://packagist.org/downloads/", 173 | "license": [ 174 | "MIT" 175 | ], 176 | "authors": [ 177 | { 178 | "name": "Michael Dowling", 179 | "email": "mtdowling@gmail.com", 180 | "homepage": "https://github.com/mtdowling" 181 | }, 182 | { 183 | "name": "Guzzle Community", 184 | "homepage": "https://github.com/guzzle/guzzle/contributors" 185 | } 186 | ], 187 | "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", 188 | "homepage": "http://guzzlephp.org/", 189 | "keywords": [ 190 | "client", 191 | "curl", 192 | "framework", 193 | "http", 194 | "http client", 195 | "rest", 196 | "web service" 197 | ], 198 | "time": "2015-03-18 18:23:50" 199 | }, 200 | { 201 | "name": "knplabs/github-api", 202 | "version": "1.5.1", 203 | "source": { 204 | "type": "git", 205 | "url": "https://github.com/KnpLabs/php-github-api.git", 206 | "reference": "832b7be695ed2733741cd5c79166b4a88fb50786" 207 | }, 208 | "dist": { 209 | "type": "zip", 210 | "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/832b7be695ed2733741cd5c79166b4a88fb50786", 211 | "reference": "832b7be695ed2733741cd5c79166b4a88fb50786", 212 | "shasum": "" 213 | }, 214 | "require": { 215 | "ext-curl": "*", 216 | "guzzle/guzzle": "~3.7", 217 | "php": ">=5.3.2" 218 | }, 219 | "require-dev": { 220 | "phpunit/phpunit": "~4.0" 221 | }, 222 | "suggest": { 223 | "knplabs/gaufrette": "Needed for optional Gaufrette cache" 224 | }, 225 | "type": "library", 226 | "extra": { 227 | "branch-alias": { 228 | "dev-master": "1.4.x-dev" 229 | } 230 | }, 231 | "autoload": { 232 | "psr-4": { 233 | "Github\\": "lib/Github/" 234 | } 235 | }, 236 | "notification-url": "https://packagist.org/downloads/", 237 | "license": [ 238 | "MIT" 239 | ], 240 | "authors": [ 241 | { 242 | "name": "Thibault Duplessis", 243 | "email": "thibault.duplessis@gmail.com", 244 | "homepage": "http://ornicar.github.com" 245 | }, 246 | { 247 | "name": "KnpLabs Team", 248 | "homepage": "http://knplabs.com" 249 | } 250 | ], 251 | "description": "GitHub API v3 client", 252 | "homepage": "https://github.com/KnpLabs/php-github-api", 253 | "keywords": [ 254 | "api", 255 | "gh", 256 | "gist", 257 | "github" 258 | ], 259 | "time": "2015-10-11 02:38:28" 260 | }, 261 | { 262 | "name": "lusitanian/oauth", 263 | "version": "v0.8.6", 264 | "source": { 265 | "type": "git", 266 | "url": "https://github.com/Lusitanian/PHPoAuthLib.git", 267 | "reference": "769fea1bb53845c7b03cca97cbdd0708e9ec26da" 268 | }, 269 | "dist": { 270 | "type": "zip", 271 | "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/769fea1bb53845c7b03cca97cbdd0708e9ec26da", 272 | "reference": "769fea1bb53845c7b03cca97cbdd0708e9ec26da", 273 | "shasum": "" 274 | }, 275 | "require": { 276 | "php": ">=5.3.0" 277 | }, 278 | "require-dev": { 279 | "phpunit/phpunit": "3.7.*", 280 | "predis/predis": "0.8.*@dev", 281 | "squizlabs/php_codesniffer": "2.*", 282 | "symfony/http-foundation": "~2.1" 283 | }, 284 | "suggest": { 285 | "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.", 286 | "predis/predis": "Allows using the Redis storage backend.", 287 | "symfony/http-foundation": "Allows using the Symfony Session storage backend." 288 | }, 289 | "type": "library", 290 | "extra": { 291 | "branch-alias": { 292 | "dev-master": "0.1-dev" 293 | } 294 | }, 295 | "autoload": { 296 | "psr-0": { 297 | "OAuth": "src", 298 | "OAuth\\Unit": "tests" 299 | } 300 | }, 301 | "notification-url": "https://packagist.org/downloads/", 302 | "license": [ 303 | "MIT" 304 | ], 305 | "authors": [ 306 | { 307 | "name": "David Desberg", 308 | "email": "david@daviddesberg.com" 309 | }, 310 | { 311 | "name": "Elliot Chance", 312 | "email": "elliotchance@gmail.com" 313 | }, 314 | { 315 | "name": "Pieter Hordijk", 316 | "email": "info@pieterhordijk.com" 317 | } 318 | ], 319 | "description": "PHP 5.3+ oAuth 1/2 Library", 320 | "keywords": [ 321 | "Authentication", 322 | "authorization", 323 | "oauth", 324 | "security" 325 | ], 326 | "time": "2015-12-21 00:06:34" 327 | }, 328 | { 329 | "name": "psr/log", 330 | "version": "1.0.0", 331 | "source": { 332 | "type": "git", 333 | "url": "https://github.com/php-fig/log.git", 334 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" 335 | }, 336 | "dist": { 337 | "type": "zip", 338 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", 339 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", 340 | "shasum": "" 341 | }, 342 | "type": "library", 343 | "autoload": { 344 | "psr-0": { 345 | "Psr\\Log\\": "" 346 | } 347 | }, 348 | "notification-url": "https://packagist.org/downloads/", 349 | "license": [ 350 | "MIT" 351 | ], 352 | "authors": [ 353 | { 354 | "name": "PHP-FIG", 355 | "homepage": "http://www.php-fig.org/" 356 | } 357 | ], 358 | "description": "Common interface for logging libraries", 359 | "keywords": [ 360 | "log", 361 | "psr", 362 | "psr-3" 363 | ], 364 | "time": "2012-12-21 11:40:51" 365 | }, 366 | { 367 | "name": "slim/slim", 368 | "version": "2.6.2", 369 | "source": { 370 | "type": "git", 371 | "url": "https://github.com/slimphp/Slim.git", 372 | "reference": "20a02782f76830b67ae56a5c08eb1f563c351a37" 373 | }, 374 | "dist": { 375 | "type": "zip", 376 | "url": "https://api.github.com/repos/slimphp/Slim/zipball/20a02782f76830b67ae56a5c08eb1f563c351a37", 377 | "reference": "20a02782f76830b67ae56a5c08eb1f563c351a37", 378 | "shasum": "" 379 | }, 380 | "require": { 381 | "php": ">=5.3.0" 382 | }, 383 | "suggest": { 384 | "ext-mcrypt": "Required for HTTP cookie encryption" 385 | }, 386 | "type": "library", 387 | "autoload": { 388 | "psr-0": { 389 | "Slim": "." 390 | } 391 | }, 392 | "notification-url": "https://packagist.org/downloads/", 393 | "license": [ 394 | "MIT" 395 | ], 396 | "authors": [ 397 | { 398 | "name": "Josh Lockhart", 399 | "email": "info@joshlockhart.com", 400 | "homepage": "http://www.joshlockhart.com/" 401 | } 402 | ], 403 | "description": "Slim Framework, a PHP micro framework", 404 | "homepage": "http://github.com/codeguy/Slim", 405 | "keywords": [ 406 | "microframework", 407 | "rest", 408 | "router" 409 | ], 410 | "time": "2015-03-08 18:41:17" 411 | }, 412 | { 413 | "name": "symfony/event-dispatcher", 414 | "version": "v2.8.2", 415 | "source": { 416 | "type": "git", 417 | "url": "https://github.com/symfony/event-dispatcher.git", 418 | "reference": "ee278f7c851533e58ca307f66305ccb9188aceda" 419 | }, 420 | "dist": { 421 | "type": "zip", 422 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda", 423 | "reference": "ee278f7c851533e58ca307f66305ccb9188aceda", 424 | "shasum": "" 425 | }, 426 | "require": { 427 | "php": ">=5.3.9" 428 | }, 429 | "require-dev": { 430 | "psr/log": "~1.0", 431 | "symfony/config": "~2.0,>=2.0.5|~3.0.0", 432 | "symfony/dependency-injection": "~2.6|~3.0.0", 433 | "symfony/expression-language": "~2.6|~3.0.0", 434 | "symfony/stopwatch": "~2.3|~3.0.0" 435 | }, 436 | "suggest": { 437 | "symfony/dependency-injection": "", 438 | "symfony/http-kernel": "" 439 | }, 440 | "type": "library", 441 | "extra": { 442 | "branch-alias": { 443 | "dev-master": "2.8-dev" 444 | } 445 | }, 446 | "autoload": { 447 | "psr-4": { 448 | "Symfony\\Component\\EventDispatcher\\": "" 449 | }, 450 | "exclude-from-classmap": [ 451 | "/Tests/" 452 | ] 453 | }, 454 | "notification-url": "https://packagist.org/downloads/", 455 | "license": [ 456 | "MIT" 457 | ], 458 | "authors": [ 459 | { 460 | "name": "Fabien Potencier", 461 | "email": "fabien@symfony.com" 462 | }, 463 | { 464 | "name": "Symfony Community", 465 | "homepage": "https://symfony.com/contributors" 466 | } 467 | ], 468 | "description": "Symfony EventDispatcher Component", 469 | "homepage": "https://symfony.com", 470 | "time": "2016-01-13 10:28:07" 471 | } 472 | ], 473 | "packages-dev": [], 474 | "aliases": [ 475 | { 476 | "alias": "1.2.x-dev", 477 | "alias_normalized": "1.2.9999999.9999999-dev", 478 | "version": "9999999-dev", 479 | "package": "chrisboulton/php-resque" 480 | } 481 | ], 482 | "minimum-stability": "stable", 483 | "stability-flags": { 484 | "chrisboulton/php-resque": 20 485 | }, 486 | "prefer-stable": false, 487 | "prefer-lowest": false, 488 | "platform": { 489 | "php": ">=5.4.0" 490 | }, 491 | "platform-dev": [] 492 | } 493 | --------------------------------------------------------------------------------