diff --git a/admin/.bowerrc b/admin/.bowerrc new file mode 100644 index 0000000000..6b8d547725 --- /dev/null +++ b/admin/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "public/js/lib" +} diff --git a/admin/admin.js b/admin/admin.js index d7e70c28c7..6d05987213 100644 --- a/admin/admin.js +++ b/admin/admin.js @@ -4,36 +4,62 @@ var config = require('./../licode_config'); GLOBAL.config = config || {}; var amqper = require('./../erizo_controller/common/amqper.js') -var app = require('express')(); +var express = require('express'); +var app = express(); // var http = require('http'); var server = require('http').Server(app); var io = require('socket.io').listen(server); +var latestStats = {}; -amqper.connect(function() { - server.listen(PORT); -}); +// app.set('view options', { layout: "layout" }); + +app.use('/public', express.static('public')); app.get('/', function (req, res) { - res.sendFile(__dirname + '/index.html'); + res.render('index.ejs'); }); app.get('/test', function (req, res) { - res.sendFile(__dirname + '/test.html'); + res.render('test.ejs'); }); -app.get('/admin.js', function(req, res) { - res.sendFile(__dirname + '/client/src/admin.js'); -}); +// app.get('/admin.js', function(req, res) { +// res.sendFile(__dirname + '/client/src/admin.js'); +// }); io.on('connection', function (socket) { - socket.on('call', function (req) { + socket.on('broadcast', function (req) { + console.log("broadcast - req:", req); amqper.broadcast(req.type, {method: req.method, args: req.args || []}, function (resp) { - console.log(arguments); + console.log("broadcast - resp:", arguments); resp.req = req; socket.emit('result', resp); }); }); + socket.on('call', function (req) { + console.log("call - req:", req.type); + amqper.callRpc(req.type, req.method, (req.args || []), {"callback": function (resp) { + console.log("call - resp:", arguments); + if (resp == "timeout") { + resp = {type: "timeout"}; + } + resp.req = req; + socket.emit('result', resp); + }}); + }); + + socket.on('subscribe', function (req) { + console.log("subscribe - req:", req.type); + amqper.bind_broadcast(req.type, function(resp, err) { + console.log("subscribe - resp:", resp); + resp.req = req; + socket.emit('subscription', resp); + }); + }); }); +amqper.connect(function() { + server.listen(PORT); +}); diff --git a/admin/bower.json b/admin/bower.json new file mode 100644 index 0000000000..08dbdb3e3c --- /dev/null +++ b/admin/bower.json @@ -0,0 +1,14 @@ +{ + "name": "admin", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "requirejs-react-jsx": "~1.0.2" + } +} diff --git a/admin/index.html b/admin/index.html deleted file mode 100644 index 353a7fcb8f..0000000000 --- a/admin/index.html +++ /dev/null @@ -1,165 +0,0 @@ - - - Licode Admin - - - - - - - - - - -
-
- - - - - diff --git a/admin/public/css/style.css b/admin/public/css/style.css new file mode 100644 index 0000000000..da393d5706 --- /dev/null +++ b/admin/public/css/style.css @@ -0,0 +1,118 @@ +.page-header { + padding-left:20px; + padding-right:20px; + margin-top:0px; + margin-bottom:0px; + padding-top:30px; + border-bottom-color:#ddd; + background-color:#eee; + +} +.x.axis line { + shape-rendering: auto; +} + +.host_stats { + padding:20px; + float:right; + background-color:; +} + +.host_stats h4 { + text-align: center +} + +.erizo_list { + margin-right:540px; + padding:20px; + +} + +.graph { + margin-top:10px; + margin-bottom:10px; +} + +.graph td { + padding:0px; + padding-top:10px; + margin-top:10px; + text-align:center; +} + +.graph td.header { + width: 90px; + text-align:left; + vertical-align: middle; + text-align: center; + font-size:15px; + text-transform: uppercase; +} +.graph td.values div { + margin-bottom:15px; + font-size:12px; + text-transform: uppercase; +} + +.graph svg { + overflow: visible; +} + +.graph svg .y_axis text { + font-size:10px; +} + +.graph svg .y_axis .domain { + fill: none; + stroke: #337ab7; + stroke-width: 1px; +} + +.graph svg .line { + fill: none; + stroke: #337ab7; + stroke-width: 2px; +} + +.erizo_item .panel-heading > div { + display:inline-block; + font-size:16px; + margin-right:30px; +} + +.erizo_list .panel-body::before, +.erizo_list .panel-body::after { + display:inline; +} + +.erizo_publisher .panel-heading > span { + display:inline-block; + font-size:13px; + margin:9px; + margin-right:15px; +} +.erizo_publisher .panel-heading > span.pub_id { + font-weight: bold; + font-size:14px; +} +.erizo_publisher .panel-heading > span.publisher_attrs { + font-family: monospace; + margin:0; + float:right; +} + +.erizo_publisher .stats_header { + font-family: monospace; + font-size:12px; + font-weight: bold; + margin:0; +} + +.erizo_publisher .stats_holder { + float:left; + text-align: center; + margin:20px 40px; +} + + + diff --git a/admin/public/js/components/charts.jsx b/admin/public/js/components/charts.jsx new file mode 100644 index 0000000000..00c8f75fa4 --- /dev/null +++ b/admin/public/js/components/charts.jsx @@ -0,0 +1,102 @@ +define(['d3', 'react'], function(d3, React) { + + return React.createClass({ + displayName: 'MovingLineChart', + + getInitialState: function() { + + var that = this; + this.x = d3.scale.linear().domain(this.props.xdomain).range([0, this.props.width]); + this.y = d3.scale.linear().domain(this.props.ydomain).range([this.props.height, 0]); + this.line = d3.svg.line() + .interpolate(this.props.interpolation) + .x(function(d, i) { return that.x(i); }) + .y(function(d, i) { return that.y(d); }); + + var data = d3.range(this.props.xdomain[1] - this.props.xdomain[0]).map(function() { return that.props.value; }); + + return { + maxVal: parseFloat(this.props.ydomain[0]), + minVal: parseFloat(this.props.ydomain[1]), + data: data, + lineData: "" + }; + + }, + + getDefaultProps: function() { + return { + title: "Chart", + ydomain: [0, 100], + xdomain: [1, 300], + interpolation: "basis", + width: 400, + y_width: 50, + height: 90, + value: 0, + animationRate: 40 + } + }, + + render: function() { + + return ( + + + + + + + + + + + +
+ {this.props.title} +
+ + + + + + + + +
Current{this.props.value}
+
Min{this.state.minVal}
+
Max{this.state.maxVal}
+
+ ); + }, + + updateGraphData: function() { + var newPoint = this.props.value; + var newData = this.state.data.slice(1).concat([newPoint]); + var newState = { data: newData, lineData: this.line(newData), source:"internal"}; + + if (newPoint > this.state.maxVal) { + newState.maxVal = newPoint; + } else if (newPoint < this.state.minVal) { + newState.minVal = newPoint; + } + this.setState(newState); + }, + + componentDidMount: function() { + d3.select(this.refs.y_axis).call(d3.svg.axis().scale(this.y).ticks(5).orient("left")); + }, + + componentDidUpdate: function () { + var that = this; + // redraw the line, and then slide it to the left + d3.select(that.refs.path) + .transition() + .duration(that.props.animationRate) + .ease("linear") + .attr("transform", "translate(" + that.x(0) + ")") + .each("end", function() { that.updateGraphData(); }); + } + + }); +}); diff --git a/admin/public/js/components/dashboard.jsx b/admin/public/js/components/dashboard.jsx new file mode 100644 index 0000000000..ac2e465287 --- /dev/null +++ b/admin/public/js/components/dashboard.jsx @@ -0,0 +1,18 @@ +define(['react', 'jsx!components/host_stats', 'jsx!components/erizo_list'], function (React, HostStats, ErizoList) { + + return React.createClass({ + displayName: 'Dashboard', + + render: function() { + return ( +
+

Licode Admin

+ + + + +
+ ); + } + }); +}); \ No newline at end of file diff --git a/admin/public/js/components/erizo_item.jsx b/admin/public/js/components/erizo_item.jsx new file mode 100644 index 0000000000..3120190f0a --- /dev/null +++ b/admin/public/js/components/erizo_item.jsx @@ -0,0 +1,63 @@ +define(['pubsub', 'react', 'jsx!components/erizo_publisher'], function (PubSub, React, ErizoPublisher) { + + return React.createClass({ + displayName: 'ErizoItem', + + getInitialState: function() { + return { + publisherMetadata: {} + }; + }, + + render: function() { + var that = this; + + var createItem = function(key) { + return ( + + ); + }; + + return ( +
+
+
ErizoJS_{this.props.item.id}
+
PID{this.props.item.pid}
+
State{this.props.item.idle ? "idle" : "busy"}
+
+ { (Object.keys(that.state.publisherMetadata).length == 0 ? "" : ( +
+ { Object.keys(that.state.publisherMetadata).map(createItem) } +
+ )) } +
+ ); + }, + + updatePublisherMetadata: function() { + var that = this; + + PubSub.call("ErizoJS_"+ this.props.item.id + ".getPublisherMetadata", null, function(resp) { + if (that.isMounted()) { + that.setState({publisherMetadata: resp}); + } + }); + }, + + componentDidMount: function() { + var that = this; + this.interval = setInterval(function() { + that.updatePublisherMetadata(); + }, 2000); + }, + + componentWillUnmount: function() { + if (this.interval) { + clearInterval(this.interval); + } + } + + }); + +}); + diff --git a/admin/public/js/components/erizo_list.jsx b/admin/public/js/components/erizo_list.jsx new file mode 100644 index 0000000000..f47f5873ed --- /dev/null +++ b/admin/public/js/components/erizo_list.jsx @@ -0,0 +1,61 @@ +define(['pubsub', 'react', 'jsx!components/erizo_item'], function (PubSub, React, ErizoItem) { + + var DATA_INTERVAL = 5000; + + return React.createClass({ + displayName: 'ErizoList', + + getInitialState: function() { + return { erizos:[], stats: {}, showStats: false } + }, + + getDefaultProps: function() { + return { stats: {} }; + }, + + updateErizoJSData: function() { + var that = this; + + PubSub.broadcast("ErizoAgent.getErizoJSInfo", null, function(resp) { + that.setState({erizos: resp.erizos}); + }); + + setTimeout(this.updateErizoJSData, DATA_INTERVAL); + }, + + componentDidMount: function () { + var that = this; + this.updateErizoJSData(); + + PubSub.subscribe('stats', function(stat) { + var stats = that.state.stats; + stats[stat.pub] = stat; + that.setState({stats: stats}); + }); + }, + + render: function() { + var that = this; + var createItem = function(item) { + return ( + + ); + }; + return ( +
+
+

Erizo JS Processes

+
+ +
+
{ this.state.erizos.map(createItem) }
+
+
+ ); + } + }); +}); + diff --git a/admin/public/js/components/erizo_publisher.jsx b/admin/public/js/components/erizo_publisher.jsx new file mode 100644 index 0000000000..3108738f4c --- /dev/null +++ b/admin/public/js/components/erizo_publisher.jsx @@ -0,0 +1,61 @@ +define(['react', 'jsx!components/charts'], function (React, MovingLineChart) { + var stat_keys = ["fractionLost", "jitter", "packetsLost", "rtcpBytesSent", "rtcpPacketSent"]; + var ydomains = { + "fractionLost": [0, 1000], + "jitter": [0, 10000000000], + "packetsLost": [0, 100000000], + "rtcpBytesSent": [0, 10000000], + "rtcpPacketSent": [0, 100000] + } + return React.createClass({ + displayName: 'ErizoPublisher', + + render: function() { + var that = this; + + var showStats = function(k) { + if (!that.props.stats) { + return null; + } + + return ( +
+ { that.props.stats.stats.map(function(stats) { + return ( +
+ {JSON.stringify({type:stats.type, ssrc:stats.ssrc, sourcSsrc:(stats.sourcSsrc || "")})} + { stat_keys.map(function(k) { + return (!(k in stats) ? null : ()); + }) } + +
+ ); + + }) } +
+ +
+ ); + + }; + + return ( +
+
+ Publisher: {this.props.pub} + Data + Audio + Video + Screen + {JSON.stringify(this.props.item.attributes)} +
+ { showStats() } +
+ ); + + }, + + }); + +}); + diff --git a/admin/public/js/components/host_stats.jsx b/admin/public/js/components/host_stats.jsx new file mode 100644 index 0000000000..5fc42402da --- /dev/null +++ b/admin/public/js/components/host_stats.jsx @@ -0,0 +1,45 @@ +define(['pubsub', 'react', 'jsx!components/charts'], function (PubSub, React, MovingLineChart) { + + var xdomain_length = 300; + var DATA_INTERVAL = 5000; + + return React.createClass({ + displayName: 'HostStats', + + getInitialState: function() { + return { cpu: 0, mem: 0 }; + }, + + updateHostData: function() { + var that = this; + + PubSub.broadcast("ErizoAgent.getErizoAgents", null, function(resp) { + that.setState({ + cpu: Math.ceil(resp.stats.perc_cpu*100), + mem: Math.ceil(resp.stats.perc_mem*100) + }); + }); + + setTimeout(this.updateHostData, DATA_INTERVAL); + }, + + componentDidMount: function () { + this.updateHostData(); + }, + + render: function() { + return ( +
+
+

Host Stats

+
+ + +
+
+
+ ); + } + }); +}); + diff --git a/admin/public/js/lib/react/.bower.json b/admin/public/js/lib/react/.bower.json new file mode 100644 index 0000000000..7048f28a82 --- /dev/null +++ b/admin/public/js/lib/react/.bower.json @@ -0,0 +1,19 @@ +{ + "name": "react", + "main": [ + "react.js", + "react-dom.js" + ], + "ignore": [], + "homepage": "https://github.com/facebook/react-bower", + "version": "0.14.6", + "_release": "0.14.6", + "_resolution": { + "type": "version", + "tag": "v0.14.6", + "commit": "b18cf6d3209c18da872e793d498e604e988d662d" + }, + "_source": "git://github.com/facebook/react-bower.git", + "_target": ">=0.11.2", + "_originalSource": "react" +} \ No newline at end of file diff --git a/admin/public/js/lib/react/LICENSE b/admin/public/js/lib/react/LICENSE new file mode 100644 index 0000000000..ce07c54d38 --- /dev/null +++ b/admin/public/js/lib/react/LICENSE @@ -0,0 +1,31 @@ +BSD License + +For react-bower software + +Copyright (c) 2013-2014, Facebook, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/admin/public/js/lib/react/PATENTS b/admin/public/js/lib/react/PATENTS new file mode 100644 index 0000000000..20279b243a --- /dev/null +++ b/admin/public/js/lib/react/PATENTS @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the react-bower software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook's rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/admin/public/js/lib/react/bower.json b/admin/public/js/lib/react/bower.json new file mode 100644 index 0000000000..4d0f00993d --- /dev/null +++ b/admin/public/js/lib/react/bower.json @@ -0,0 +1,5 @@ +{ + "name": "react", + "main": ["react.js", "react-dom.js"], + "ignore": [] +} diff --git a/admin/public/js/lib/react/react-dom-server.js b/admin/public/js/lib/react/react-dom-server.js new file mode 100644 index 0000000000..720f3fdd17 --- /dev/null +++ b/admin/public/js/lib/react/react-dom-server.js @@ -0,0 +1,42 @@ +/** + * ReactDOMServer v0.14.6 + * + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ +// Based off https://github.com/ForbesLindesay/umd/blob/master/template.js +;(function(f) { + // CommonJS + if (typeof exports === "object" && typeof module !== "undefined") { + module.exports = f(require('react')); + + // RequireJS + } else if (typeof define === "function" && define.amd) { + define(['react'], f); + + // + + +
+ + + diff --git a/admin/client/src/admin.js b/admin/views/layout.ejs similarity index 100% rename from admin/client/src/admin.js rename to admin/views/layout.ejs diff --git a/admin/test.html b/admin/views/test.ejs similarity index 100% rename from admin/test.html rename to admin/views/test.ejs diff --git a/erizo_controller/erizoAgent/erizoAgent.js b/erizo_controller/erizoAgent/erizoAgent.js index d75c43e481..f2b39ec570 100644 --- a/erizo_controller/erizoAgent/erizoAgent.js +++ b/erizo_controller/erizoAgent/erizoAgent.js @@ -184,15 +184,11 @@ var reporter = require('./erizoAgentReporter').Reporter({id: my_erizo_agent_id, var api = { getErizoJSInfo: function(callback) { - p_trans = {} + p_trans = [] for (i in processes) { - p_trans[i] = {pid: processes[i].pid, killed: processes[i].killed, connected: processes[i].connected}; + p_trans.push({ id: i, pid: processes[i].pid, killed: processes[i].killed, connected: processes[i].connected, idle: (idle_erizos.indexOf(i) >= 0) }); } - callback({ - processes: p_trans, - erizos: erizos, - idle_erizos: idle_erizos - }); + callback({erizos: p_trans}); }, createErizoJS: function(callback) { try { diff --git a/erizo_controller/erizoClient/src/Stream.js b/erizo_controller/erizoClient/src/Stream.js index c877f6eabc..ba338b03ff 100644 --- a/erizo_controller/erizoClient/src/Stream.js +++ b/erizo_controller/erizoClient/src/Stream.js @@ -85,7 +85,7 @@ Erizo.Stream = function (spec) { } else if (spec.screen == true && videoOpt === undefined){ videoOpt = true; } - var opt = {video: videoOpt, audio: spec.audio, fake: spec.fake, screen: spec.screen, extensionId:that.extensionId}; + var opt = {video: videoOpt, audio: spec.audio, fake: spec.fake, screen: spec.screen, extensionId:that.extensionId, attributes: spec.attributes}; L.Logger.debug(opt); Erizo.GetUserMedia(opt, function (stream) { //navigator.webkitGetUserMedia("audio, video", function (stream) { diff --git a/erizo_controller/erizoController/roomController.js b/erizo_controller/erizoController/roomController.js index 8fa7b74751..1d775511dc 100644 --- a/erizo_controller/erizoController/roomController.js +++ b/erizo_controller/erizoController/roomController.js @@ -190,9 +190,9 @@ exports.RoomController = function (spec) { //TODO: Possible race condition if we got an old id amqper.callRpc(getErizoQueue(publisher_id), "addPublisher", args, {callback: callback}); - // amqper.callRpc(getErizoQueue(publisher_id), "addPublisherMetadata", [options], {callback: function() { - // log.info("Passed publisher metadata over to erizoJS ", publisher_id); - // }}); + amqper.callRpc(getErizoQueue(publisher_id), "addPublisherMetadata", [publisher_id, options], {callback: function() { + log.info("Passed publisher metadata over to erizoJS ", publisher_id, options); + }}); erizos[erizo_id].publishers.push(publisher_id); }); diff --git a/erizo_controller/erizoJS/erizoJSController.js b/erizo_controller/erizoJS/erizoJSController.js index 048af5548c..d9a381a94d 100644 --- a/erizo_controller/erizoJS/erizoJSController.js +++ b/erizo_controller/erizoJS/erizoJSController.js @@ -176,6 +176,7 @@ exports.ErizoJSController = function (spec) { if (GLOBAL.config.erizoController.report.rtcp_stats) { wrtc.getStats(function (newStats){ var timeStamp = new Date(); + console.log("broadcasting stats:", id_pub, theStats); amqper.broadcast('stats', {pub: id_pub, subs: id_sub, stats: theStats, timestamp:timeStamp.getTime()}); }); } @@ -389,13 +390,13 @@ exports.ErizoJSController = function (spec) { }; - that.addPublisherMetadata = function(data, callback) { - metadata = data; - callback(true); + that.addPublisherMetadata = function(from, data, callback) { + metadata[from] = data; + callback('callback', true); }; that.getPublisherMetadata = function(callback) { - callback(metadata); + callback('callback', metadata); }; /* diff --git a/extras/basic_example/public/script.js b/extras/basic_example/public/script.js index 838d4dd3e3..3cca5f56f0 100644 --- a/extras/basic_example/public/script.js +++ b/extras/basic_example/public/script.js @@ -30,7 +30,7 @@ function startRecording () { window.onload = function () { recording = false; var screen = getParameterByName("screen"); - var config = {audio: true, video: true, data: true, screen: screen, videoSize: [640, 480, 640, 480]}; + var config = {audio: true, video: true, data: true, screen: screen, videoSize: [640, 480, 640, 480], attributes: {user: "gene", class: 1234}}; // If we want screen sharing we have to put our Chrome extension id. The default one only works in our Lynckia test servers. // If we are not using chrome, the creation of the stream will fail regardless. if (screen){ diff --git a/scripts/installUbuntuDepsUnattended.sh b/scripts/installUbuntuDepsUnattended.sh index 9883df6117..ca70fa01f9 100755 --- a/scripts/installUbuntuDepsUnattended.sh +++ b/scripts/installUbuntuDepsUnattended.sh @@ -58,7 +58,7 @@ install_openssl(){ if [ -d $LIB_DIR ]; then cd $LIB_DIR if [ ! -f ./openssl-1.0.1g.tar.gz ]; then - curl -O http://www.openssl.org/source/openssl-1.0.1g.tar.gz + curl -O ftp://ftp.openssl.org/source/old/1.0.1/openssl-1.0.1g.tar.gz tar -zxvf openssl-1.0.1g.tar.gz cd openssl-1.0.1g ./config --prefix=$PREFIX_DIR -fPIC