More work on web installer

This commit is contained in:
Duncan Sommerville 2014-12-09 17:48:16 -05:00
parent 9fed113f74
commit f5b4928538
23 changed files with 728 additions and 186 deletions

View File

@ -34,7 +34,7 @@ class Config {
$CC_CONFIG['baseDir'] = $values['general']['base_dir'];
$CC_CONFIG['baseUrl'] = $values['general']['base_url'];
$CC_CONFIG['basePort'] = $values['general']['base_port'];
$CC_CONFIG['phpDir'] = $values['general']['airtime_dir'];
// $CC_CONFIG['phpDir'] = $values['general']['airtime_dir'];
$CC_CONFIG['cache_ahead_hours'] = $values['general']['cache_ahead_hours'];

View File

@ -20,19 +20,18 @@ $r = array_reduce($phpDependencies, "booleanReduce", true);
$result = $r && $database;
?>
<html style="background-color: #f5f5f5">
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/bootstrap-3.3.1.min.css">
<link rel="stylesheet" type="text/css" href="css/setup/config-check.css">
</head>
<body style="padding: 2em 0; min-width: 600px; width: 50%; text-align: center; margin: 3em auto;
border: 1px solid lightgray; border-radius: 5em;">
<body>
<h2>
<img src="css/images/airtime_logo_jp.png" style="margin-bottom: .5em;" /><br/>
<img class="logo" src="css/images/airtime_logo_jp.png" /><br/>
<strong>Configuration Checklist</strong>
</h2>
<table class="table" style="padding: 0; margin: 3em 0 0 0; font-weight: bold">
<table class="table">
<thead>
<tr>
<td class="component">
@ -61,10 +60,10 @@ $result = $r && $database;
<td class="description">
Zend MVC Framework
</td>
<td class="solution" <?php if ($zend) {echo 'style="background: #dff0d8 url(css/images/accept.png) no-repeat center"';?>>
<td class="solution <?php if ($zend) {echo 'check';?>">
<?php
} else {
?>>
?>">
<b>Ubuntu</b>: try running <code>sudo apt-get install libzend-framework-php</code>
<br/><b>Debian</b>: try running <code>sudo apt-get install zendframework</code>
<?php
@ -79,10 +78,10 @@ $result = $r && $database;
<td class="description">
PDO and PostgreSQL libraries
</td>
<td class="solution" <?php if ($postgres) {echo 'style="background: #dff0d8 url(css/images/accept.png) no-repeat center"';?>>
<td class="solution <?php if ($postgres) {echo 'check';?>">
<?php
} else {
?>>
?>">
Try running <code>sudo apt-get install php5-pgsql</code>
<?php
}
@ -104,10 +103,10 @@ $result = $r && $database;
<td class="description">
Database configuration for Airtime
</td>
<td class="solution" <?php if ($database) {echo 'style="background: #dff0d8 url(css/images/accept.png) no-repeat center"';?>>
<td class="solution <?php if ($database) {echo 'check';?>">
<?php
} else {
?>>
?>">
Make sure you aren't missing any of the Postgres dependencies in the table above.
If your dependencies check out, make sure your database configuration settings in
<code>airtime.conf</code> is correct and the Airtime database was installed correctly.
@ -118,6 +117,7 @@ $result = $r && $database;
</tr>
</tbody>
</table>
</div>
<?php
if (!$result) {
?>
@ -135,8 +135,6 @@ $result = $r && $database;
<?php
}
?>
</div>
<div class="footer">
<h3>
PHP Extension List

View File

@ -6,12 +6,12 @@
<span id="helpBlock" class="help-block help-message"></span>
<div class="form-group">
<label class="control-label" for="dbUser">Username</label>
<input required class="form-control" type="text" name="dbUser" id="dbUser" placeholder="Username"/>
<input required class="form-control" type="text" name="dbUser" id="dbUser" placeholder="Username" value="airtime"/>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
<div class="form-group">
<label class="control-label" for="dbPass">Password</label>
<input required class="form-control" type="password" name="dbPass" id="dbPass" placeholder="Password"/>
<input required class="form-control" type="password" name="dbPass" id="dbPass" placeholder="Password" value="airtime"/>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
<div class="form-group">
@ -26,25 +26,12 @@
<input class="form-control" type="hidden" name="dbErr" id="dbErr" aria-describedby="helpBlock"/>
<div>
<input type="submit" formtarget="dbSettingsForm" class="btn btn-primary btn-next" value="Next &#10097;"/>
<input type="button" class="btn btn-primary btn-back btn-disabled" value="&#10096; Back"/>
<input type="button" class="btn btn-default btn-skip" value="Skip this step &#10097;"/>
</div>
</form>
<script>
$("#dbSettingsForm").submit(function(e) {
resetFeedback();
e.preventDefault();
var d = $('#dbSettingsForm').serializeArray();
addOverlay();
// Append .promise().done() rather than using a
// callback to avoid weird alert duplication
$("#overlay, #loadingImage").fadeIn(500).promise().done(function() {
// Proxy function for passing the event to the cleanup function
var cleanupProxy = function(data) {
cleanupStep.call(this, data, e);
};
$.post('setup/setup-functions.php?obj=DatabaseSetup', d, cleanupProxy, "json");
});
submitForm(e, "DatabaseSetup");
});
</script>

View File

@ -0,0 +1,22 @@
<?php
?>
<form action="#" role="form" id="finishSettingsForm">
<h3 class="form-title">Media Settings</h3>
<span id="helpBlock" class="help-block help-message"></span>
<p>
Looks like you're almost done! Click "Done!" to bring up the Airtime configuration checklist; if
your configuration is all green, you're ready to get started with your personal Airtime station!
</p>
<div>
<input type="submit" formtarget="finishSettingsForm" class="btn btn-primary btn-next" value="Done!"/>
<input type="button" class="btn btn-primary btn-back" value="&#10096; Back"/>
</div>
</form>
<script>
$("#finishSettingsForm").submit(function(e) {
submitForm(e, "FinishSetup");
// window.location.replace("/?config");
});
</script>

View File

@ -0,0 +1,31 @@
<?php
?>
<form action="#" role="form" id="generalSettingsForm">
<h3 class="form-title">General Settings</h3>
<span id="helpBlock" class="help-block help-message"></span>
<div id="generalFormBody">
<div class="form-group">
<label class="control-label" for="generalHost">Webserver Host</label>
<input required class="form-control" type="text" name="generalHost" id="generalHost" placeholder="Host" value="localhost"/>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
<div class="form-group">
<label class="control-label" for="generalPort">Webserver Port</label>
<input required class="form-control" type="text" name="generalPort" id="generalPort" placeholder="Port" value="80"/>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
<input class="form-control" type="hidden" name="generalErr" id="generalErr" aria-describedby="helpBlock"/>
</div>
<div>
<input type="submit" formtarget="generalSettingsForm" class="btn btn-primary btn-next" value="Next &#10097;"/>
<input type="button" class="btn btn-primary btn-back" value="&#10096; Back"/>
<input type="button" class="btn btn-default btn-skip" value="Skip this step &#10097;"/>
</div>
</form>
<script>
$("#generalSettingsForm").submit(function(e) {
submitForm(e, "GeneralSetup");
});
</script>

View File

@ -0,0 +1,31 @@
<?php
?>
<form action="#" role="form" id="mediaSettingsForm">
<h3 class="form-title">Media Settings</h3>
<span id="helpBlock" class="help-block help-message"></span>
<p>
Here you can set the default media storage directory for Airtime. If left blank, we'll create a new
directory located at <code>/srv/airtime/stor/</code> for you.
</p>
<div class="form-group">
<label class="control-label" for="mediaFolder">Media folder</label>
<input class="form-control" type="text" name="mediaFolder" id="mediaFolder" placeholder="/path/to/my/airtime/music/directory/"/>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
<span id="mediaHelpBlock" class="help-block">
Note that you need to enter the <strong>fully qualified</strong> path name!
</span>
</div>
<input class="form-control" type="hidden" name="mediaErr" id="mediaErr" aria-describedby="helpBlock"/>
<div>
<input type="submit" formtarget="mediaSettingsForm" class="btn btn-primary btn-next" value="Next &#10097;"/>
<input type="button" class="btn btn-primary btn-back" value="&#10096; Back"/>
<input type="button" class="btn btn-default btn-skip" value="Skip this step &#10097;"/>
</div>
</form>
<script>
$("#mediaSettingsForm").submit(function(e) {
submitForm(e, "MediaSetup");
});
</script>

View File

@ -33,10 +33,12 @@
<div class="form-group">
<label class="control-label" for="rmqHost">Host</label>
<input required class="form-control" type="text" name="rmqHost" id="rmqHost" placeholder="Host" value="127.0.0.1"/>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
<div class="form-group">
<label class="control-label" for="rmqPort">Port</label>
<input required class="form-control" type="text" name="rmqPort" id="rmqPort" placeholder="Port" value="5672"/>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
<div class="form-group">
<label class="control-label" for="rmqVHost">Virtual Host</label>
@ -59,18 +61,6 @@
});
$("#rmqSettingsForm").submit(function(e) {
resetFeedback();
e.preventDefault();
var d = $('#rmqSettingsForm').serializeArray();
addOverlay();
// Append .promise().done() rather than using a
// callback to avoid weird alert duplication
$("#overlay, #loadingImage").fadeIn(500).promise().done(function() {
// Proxy function for passing the event to the cleanup function
var cleanupProxy = function(data) {
cleanupStep.call(this, data, e);
};
$.post('setup/setup-functions.php?obj=RabbitMQSetup', d, cleanupProxy, "json");
});
submitForm(e, "RabbitMQSetup");
});
</script>

View File

@ -1,5 +1,8 @@
<?php
define("RMQ_INI_SECTION", "rabbitmq");
require_once dirname(dirname( __DIR__)) . '/library/php-amqplib/amqp.inc';
/**
* Check to see if Airtime is properly configured.
*
@ -8,7 +11,8 @@
*/
function checkConfiguration() {
return checkPhpDependencies()
&& checkDatabaseConfiguration();
&& checkDatabaseConfiguration()
&& checkRMQConnection();
}
/**
@ -73,6 +77,23 @@ function configureDatabase() {
Propel::init(CONFIG_PATH . 'airtime-conf-production.php');
}
function validateDatabaseSchema() {
/**
* Check that we can connect to RabbitMQ
*/
function checkRMQConnection() {
// Check for airtime.conf in /etc/airtime/ first, then check in the build directory,
if (file_exists(AIRTIME_CONFIG_STOR . AIRTIME_CONFIG)) {
$ini = parse_ini_file(AIRTIME_CONFIG_STOR . AIRTIME_CONFIG, true);
} else if (file_exists(BUILD_PATH . AIRTIME_CONFIG)) {
$ini = parse_ini_file(BUILD_PATH . AIRTIME_CONFIG, true);
} else {
$ini = parse_ini_file(BUILD_PATH . "airtime.example.conf", true);
}
$conn = new AMQPConnection($ini[RMQ_INI_SECTION]["host"],
$ini[RMQ_INI_SECTION]["port"],
$ini[RMQ_INI_SECTION]["user"],
$ini[RMQ_INI_SECTION]["password"],
$ini[RMQ_INI_SECTION]["vhost"]);
return isset($conn);
}

View File

@ -1,5 +1,4 @@
<?php
?>
<html>
@ -17,7 +16,7 @@
<img src="css/images/airtime_logo_jp.png" id="airtimeLogo" /><br/>
<strong>Setup</strong>
</h3>
<strong>Step <span id="stepCount">1</span> of 4</strong>
<strong>Step <span id="stepCount">1</span> of 5</strong>
</div>
<div class="viewport">
@ -32,6 +31,21 @@
require_once SETUP_PATH . "forms/rabbitmq-settings.php";
?>
</div>
<div id="generalSettings" class="form-wrapper">
<?php
require_once SETUP_PATH . "forms/general-settings.php";
?>
</div>
<div id="mediaSettings" class="form-wrapper">
<?php
require_once SETUP_PATH . "forms/media-settings.php";
?>
</div>
<div id="finishSettings" class="form-wrapper">
<?php
require_once SETUP_PATH . "forms/finish-settings.php";
?>
</div>
</div>
</div>

View File

@ -436,10 +436,13 @@ class AMQPConnection extends AbstractChannel
$len = strlen($data);
while(true)
{
if(false === ($written = fwrite($this->sock, $data)))
$written = fwrite($this->sock, $data);
if($written == false || $written <= 0)
{
throw new Exception ("Error sending data");
}
$len = $len - $written;
if($len>0)
$data=substr($data,0-$len);
@ -599,12 +602,17 @@ class AMQPConnection extends AbstractChannel
*/
public function close($reply_code=0, $reply_text="", $method_sig=array(0, 0))
{
try {
$args = new AMQPWriter();
$args->write_short($reply_code);
$args->write_shortstr($reply_text);
$args->write_short($method_sig[0]); // class_id
$args->write_short($method_sig[1]); // method_id
$this->send_method_frame(array(10, 60), $args);
} catch(Exception $e) {
return;
}
return $this->wait(array(
"10,61", // Connection.close_ok
));

View File

@ -1,3 +1,26 @@
html {
background-color: #f5f5f5;
}
body {
padding: 2em 0;
min-width: 600px;
width: 50%;
text-align: center;
margin: 3em auto;
border: 1px solid lightgray;
border-radius: 5em;
}
.logo {
margin-bottom: .5em;
}
.table {
padding: 0;
margin: 3em 0 0 0;
}
.checklist {
overflow: auto;
height: 50%;
@ -16,6 +39,10 @@
width: 40%;
}
.check {
background: #dff0d8 url("css/images/accept.png") no-repeat center;
}
.footer {
margin: inherit;
width: inherit;

View File

@ -47,8 +47,12 @@ body {
* ############################################################################ */
form {
width: 80%;
margin: auto;
top: 0;
width: 99%;
position: absolute;
float: right;
}
form p {
@ -69,6 +73,10 @@ form .form-group {
overflow-y: auto;
}
.form-wrapper {
overflow: auto;
}
.form-title {
margin: 1em 0;
}
@ -166,23 +174,6 @@ form .form-group {
border-bottom: 4px solid #fff;
}
/* ############################################################################
*
* Database Settings Form Styles
*
* ############################################################################ */
#dbSettingsForm {
top: 0;
width: 99%;
position: absolute;
float: right;
}
#dbSettings {
overflow: auto;
}
/* ############################################################################
*
* RabbitMQ Settings Form Styles
@ -190,17 +181,39 @@ form .form-group {
* ############################################################################ */
#rmqSettingsForm {
top: 0;
left: 100%;
width: 99%;
position: absolute;
float: right;
}
#rmqSettings {
overflow: auto;
}
#rmqFormBody {
display: none;
}
/* ############################################################################
*
* General Settings Form Styles
*
* ############################################################################ */
#generalSettingsForm {
left: 200%;
}
/* ############################################################################
*
* Media Settings Form Styles
*
* ############################################################################ */
#mediaSettingsForm {
left: 300%;
}
/* ############################################################################
*
* Finish Settings Form Styles
*
* ############################################################################ */
#finishSettingsForm {
left: 400%;
}

View File

@ -24,6 +24,8 @@ define('SETUP_PATH', BUILD_PATH . 'airtime-setup/');
define('APPLICATION_PATH', ROOT_PATH . 'application/');
define('CONFIG_PATH', APPLICATION_PATH . 'configs/');
define("AIRTIME_CONFIG_STOR", "/etc/airtime/");
define('AIRTIME_CONFIG', 'airtime.conf');
require_once(LIB_PATH . "propel/runtime/lib/Propel.php");
@ -37,7 +39,7 @@ if (array_key_exists('config', $_GET)) {
}
// If a configuration file exists, forward to our boot script
if (file_exists(BUILD_PATH . AIRTIME_CONFIG)) {
if (file_exists(AIRTIME_CONFIG_STOR . AIRTIME_CONFIG)) {
/*
* Even if the user has been through the setup process and
* created an airtime.conf file (or they may have simply

View File

@ -2,6 +2,7 @@
* Do some cleanup when we get a success response from a POST request
* during setup
* @param data the POST request return data
* @param e the jquery event
*/
function cleanupStep(data, e) {
showFeedback(data);
@ -19,16 +20,16 @@ function cleanupStep(data, e) {
* @param data the POST request return data
*/
function showFeedback(data) {
if (data.errors.length > 0) {
$(".help-message").addClass("has-error");
$(".form-control-feedback").show();
} else {
$(".help-message").addClass("has-success");
}
toggleMessage(data.message);
for (var i = 0; i < data.errors.length; i++) {
$("#" + data.errors[i]).parent().addClass("has-error has-feedback");
}
if (data.errors.length > 0) {
$(".help-message").addClass("has-error");
$(".has-error .form-control-feedback").show();
} else {
$(".help-message").addClass("has-success");
}
}
/**
@ -36,8 +37,7 @@ function showFeedback(data) {
*/
function resetFeedback() {
$(".form-control-feedback").hide();
$("#helpBlock").html("");
$(".has-error, .has-feedback").removeClass("has-error has-feedback");
$(".has-success, .has-error, .has-feedback").removeClass("has-success has-error has-feedback");
}
/**
@ -47,16 +47,12 @@ function resetFeedback() {
function toggleMessage(msg) {
/*
* Since setting display:none; on this element causes odd behaviour
* with bootstrap, hide() the element so we can slide it in.
* with bootstrap, hide() the element so we can formSlide it in.
* This is only really only necessary the first time this
* function is called after page load.
*/
$(".help-message").hide();
$(".help-message").html(msg);
$(".help-message").slideDown(200);
window.setTimeout(function() {
$(".help-message").slideUp(200);
}, 3000);
var help = $(".help-message");
help.html(msg).show();
}
/**
@ -77,37 +73,60 @@ function removeOverlay() {
});
}
function formSlide(dir) {
var delta = (dir == "next") ? "-=100%" : "+=100%";
$(".btn").attr("disabled", "disabled");
$(".form-slider").animate({left: delta}, 500, function() {
$(".btn").removeAttr("disabled");
});
var stepCount = $("#stepCount"),
steps = parseInt(stepCount.html());
stepCount.html((dir == "next") ? (steps + 1) : (steps - 1));
hideRMQForm();
}
/**
* Fade out the previous setup step and fade in the next one
*/
function nextSlide() {
$(".btn").attr("disabled", "disabled");
$(".form-slider").animate({left: "-=100%"}, 500, function() {
$(".btn").removeAttr("disabled");
});
var stepCount = parseInt($("#stepCount").html());
$("#stepCount").html(stepCount + 1);
formSlide("next");
}
/**
* Fade out the current setup step and fade in the previous one
*/
function prevSlide() {
$(".btn").attr("disabled", "disabled");
$(".form-slider").animate({left: "+=100%"}, 500, function() {
$(".btn").removeAttr("disabled");
formSlide("prev");
}
/**
* Hide the RMQ form when the slider is called to avoid showing
* scrollbars on slider panels that fit vertically
*/
function hideRMQForm() {
$("#rmqFormBody").slideUp(500);
$("#advCaret").removeClass("caret-up");
}
function submitForm(e, obj) {
resetFeedback();
e.preventDefault();
var d = $(e.target).serializeArray();
addOverlay();
// Append .promise().done() rather than using a
// callback to avoid weird alert duplication
$("#overlay, #loadingImage").fadeIn(500).promise().done(function() {
// Proxy function for passing the event to the cleanup function
var cleanupProxy = function(data) {
cleanupStep.call(this, data, e);
};
$.post('setup/setup-functions.php?obj=' + obj, d, cleanupProxy, "json");
});
var stepCount = parseInt($("#stepCount").html());
$("#stepCount").html(stepCount - 1);
}
$(function() {
$(".form-slider").draggable({
revert: true,
axis: 'x',
snap: ".viewport",
snapMode: "both",
});
// Stop the user from dragging the slider
$(".form-slider").draggable('disable');
window.onresize = function() {
var headerHeight = $(".header").outerHeight(),

View File

@ -1,20 +1,29 @@
<?php
/**
* User: sourcefabric
* Date: 02/12/14
*
* Class DatabaseSetup
*
* Wrapper class for validating and installing the Airtime database during the installation process
*/
class DatabaseSetup extends Setup {
// airtime.conf section header
const SECTION = "[database]";
// Constant form field names for passing errors back to the front-end
const DB_USER = "dbUser",
DB_PASS = "dbPass",
DB_NAME = "dbName",
DB_HOST = "dbHost";
// Form field values
static $user, $pass, $name, $host;
// Array of key->value pairs for airtime.conf
static $properties;
// Message and error fields to return to the front-end
static $message = null;
static $errors = array();
@ -23,6 +32,13 @@ class DatabaseSetup extends Setup {
self::$pass = $settings[self::DB_PASS];
self::$name = $settings[self::DB_NAME];
self::$host = $settings[self::DB_HOST];
self::$properties = array(
"host" => self::$host,
"dbname" => self::$name,
"dbuser" => self::$user,
"dbpass" => self::$pass,
);
}
/**
@ -33,47 +49,70 @@ class DatabaseSetup extends Setup {
*/
function runSetup() {
// Check the connection and user credentials
if ($this->validateDatabaseConnection()) {
if ($this->checkDatabaseConnection()) {
// We know that the user credentials check out, so check if the database exists
if ($this->validateDatabaseSettings()) {
// The database already exists, so we can just set up the schema
if ($this->checkDatabaseExists()) {
// The database already exists, check if the schema exists as well
if ($this->checkSchemaExists()) {
self::$message = "Airtime is already installed in this database!";
} else {
if ($this->createDatabaseTables()) {
self::$message = "Successfully installed Airtime database to '" . self::$name . "'";
} else {
self::$message = "Something went wrong setting up the Airtime schema!";
self::$errors[] = self::DB_NAME;
}
}
} else {
// The database doesn't exist, so check if the user can create databases
if ($this->checkUserCanCreateDb()) {
// The user can create a database, do it
if ($this->createDatabase()) {
// Ensure that the database was installed in UTF8 (we only care about the Airtime database)
if ($this->checkDatabaseSchema()) {
if ($this->createDatabaseTables()) {
self::$message = "Successfully installed Airtime database to '" . self::$name . "'";
} else {
self::$message = "Something went wrong setting up the Airtime schema!";
self::$errors[] = self::DB_NAME;
}
} else {
self::$message = "The database was installed with an incorrect encoding type!";
self::$errors[] = self::DB_NAME;
}
} else {
self::$message = "There was an error installing the database!";
self::$errors[] = self::DB_NAME;
}
} // The user can't create databases, so we're done
else {
self::$message = "No database " . self::$name . " exists; user " . self::$user
. " does not have permission to create databases on " . self::$host;
self::$errors[] = self::DB_NAME;
}
}
}
if (count(self::$errors) <= 0) {
$this->writeToTemp();
}
return array(
"message" => self::$message,
"errors" => self::$errors,
);
}
function writeToTemp() {
parent::writeToTemp(self::SECTION, self::$properties);
}
/**
* Check if the user's database connection is valid
* @return boolean true if the connection are valid
*/
function validateDatabaseConnection() {
function checkDatabaseConnection() {
// This is pretty redundant, but we need to test both
// the existence and the validity of the given credentials
exec("export PGPASSWORD=" . self::$pass . " && psql -h "
@ -97,12 +136,23 @@ class DatabaseSetup extends Setup {
* Check if the database settings and credentials given are valid
* @return boolean true if the database given exists and the user is valid and can access it
*/
function validateDatabaseSettings() {
function checkDatabaseExists() {
exec("export PGPASSWORD=" . self::$pass . " && psql -lqt -h " . self::$host . " -U " . self::$user
. "| cut -d \\| -f 1 | grep -w " . self::$name, $out, $status);
return $status == 0;
}
/**
* Check if the database schema has already been set up
* @return boolean true if the database schema exists
*/
function checkSchemaExists() {
// Check for cc_pref to see if the schema is already installed in this database
exec("export PGPASSWORD=" . self::$pass . " && psql -U " . self::$user . " -h "
. self::$host . " -d " . self::$name . " -tAc \"SELECT * FROM cc_pref\"", $out, $status);
return $status == 0;
}
/**
* Check if the given user has access on the given host to create a new database
* @return boolean true if the given user has permission to create a database on the given host
@ -132,15 +182,33 @@ class DatabaseSetup extends Setup {
$sqlDir = dirname(dirname(__DIR__)) . "/build/sql/";
$files = array("schema.sql", "sequences.sql", "views.sql", "triggers.sql", "defaultdata.sql");
foreach($files as $f) {
foreach ($files as $f) {
try {
exec("export PGPASSWORD=" . self::$pass . " && psql -U " . self::$user . " --dbname "
. self::$name . " -h " . self::$host . " -f $sqlDir$f 2>/dev/null", $out, $status);
} catch(Exception $e) {
} catch (Exception $e) {
return false;
}
}
return true;
}
/**
* Checks whether the newly-created database's encoding was properly set to UTF8
* @return boolean true if the database encoding is UTF8
*/
function checkDatabaseEncoding() {
exec("export PGPASSWORD=" . self::$pass . " && psql -U " . self::$user . " -h "
. self::$host . " -d " . self::$name . " -tAc \"SHOW SERVER_ENCODING\"", $out, $status);
return $out && $out[0] == "UTF8";
}
// TODO Since we already check the encoding, is there a purpose to verifying the schema?
function checkDatabaseSchema() {
$outFile = "/tmp/tempSchema.xml";
exec("export PGPASSWORD=" . self::$pass . " && psql -U " . self::$user . " -h "
. self::$host . " -o ${outFile} -tAc \"SELECT database_to_xml(FALSE, FALSE, '"
. self::$name . "')\"", $out, $status);
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* User: sourcefabric
* Date: 09/12/14
*
* Class FinishSetup
*
* Wrapper class for finalizing and moving airtime.conf
*/
class FinishSetup extends Setup {
function __construct($settings) {
}
function runSetup() {
if ($this->createAirtimeConfigDirectory()) {
$this->moveAirtimeConfig();
}
}
function createAirtimeConfigDirectory() {
return file_exists("/etc/airtime/") ? true
: mkdir("/etc/airtime/", 0755, true);
}
function moveAirtimeConfig() {
return copy(AIRTIME_CONF_TEMP_PATH, "/etc/airtime/airtime.conf");
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* User: sourcefabric
* Date: 08/12/14
*
* Class GeneralSetup
*
* Wrapper class for validating and setting up general settings during the installation process
*/
class GeneralSetup extends Setup {
// airtime.conf section header
const SECTION = "[general]";
// Constant form field names for passing errors back to the front-end
const GENERAL_PORT = "generalPort",
GENERAL_HOST = "generalHost";
// Form field values
static $user, $host, $port, $root;
// Array of key->value pairs for airtime.conf
static $properties;
// Message and error fields to return to the front-end
static $message = null;
static $errors = array();
function __construct($settings) {
self::$host = $settings[self::GENERAL_HOST];
self::$port = $settings[self::GENERAL_PORT];
self::$properties = array(
"api_key" => $this->generateRandomString(),
"base_url" => self::$host,
"base_port" => self::$port,
);
}
function writeToTemp() {
parent::writeToTemp(self::SECTION, self::$properties);
}
/**
* @return array associative array containing a display message and fields with errors
*/
function runSetup() {
if (count(self::$errors) <= 0) {
$this->writeToTemp();
}
return array(
"message" => self::$message,
"errors" => self::$errors
);
}
}

View File

@ -0,0 +1,107 @@
<?php
define("CONFIG_PATH", dirname(dirname( __DIR__)) . "/application/configs/");
require_once(dirname(dirname( __DIR__)) . "/library/propel/runtime/lib/Propel.php");
require_once(CONFIG_PATH . 'conf.php');
require_once(dirname(dirname( __DIR__)) . "/application/models/airtime/map/CcMusicDirsTableMap.php");
require_once(dirname(dirname( __DIR__)) . "/application/models/airtime/om/BaseCcMusicDirsQuery.php");
require_once(dirname(dirname( __DIR__)) . "/application/models/airtime/CcMusicDirsQuery.php");
require_once(dirname(dirname( __DIR__)) . "/application/models/airtime/om/BaseCcMusicDirs.php");
require_once(dirname(dirname( __DIR__)) . "/application/models/airtime/CcMusicDirs.php");
require_once(dirname(dirname( __DIR__)) . "/application/models/airtime/om/BaseCcMusicDirsPeer.php");
require_once(dirname(dirname( __DIR__)) . "/application/models/airtime/CcMusicDirsPeer.php");
/**
* User: sourcefabric
* Date: 08/12/14
*
* Class MediaSetup
*
* Wrapper class for validating and setting up media folder during the installation process
*/
class MediaSetup extends Setup {
const MEDIA_FOLDER = "mediaFolder";
static $path;
static $message = null;
static $errors = array();
function __construct($settings) {
self::$path = $settings[self::MEDIA_FOLDER];
}
/**
* @return array associative array containing a display message and fields with errors
*/
function runSetup() {
// If the path passed in is empty, set it to the default
if (strlen(self::$path) == 0) {
self::$path = "/srv/airtime/stor/";
}
// Append a trailing / if they didn't
if (!(substr(self::$path, -1) == "/")) {
self::$path .= "/";
}
if (file_exists(self::$path)) {
$this->setupMusicDirectory();
} else {
self::$message = "Invalid path!";
self::$errors[] = self::MEDIA_FOLDER;
}
return array(
"message" => self::$message,
"errors" => self::$errors
);
}
/**
* Add the given directory to cc_music_dirs
* TODO Should we check for an existing entry in cc_music_dirs?
*/
function setupMusicDirectory() {
try {
$_SERVER['AIRTIME_CONF'] = AIRTIME_CONF_TEMP_PATH;
Propel::init(CONFIG_PATH . "airtime-conf-production.php");
$con = Propel::getConnection();
} catch(Exception $e) {
self::$message = "Failed to insert media folder; database isn't configured properly!";
self::$errors[] = self::MEDIA_FOLDER;
return;
}
$this->runMusicDirsQuery($con);
}
function runMusicDirsQuery($con) {
try {
if ($this->checkMusicDirectoryExists($con)) {
$dir = CcMusicDirsQuery::create()->findPk(1, $con);
} else {
$dir = new CcMusicDirs();
}
$dir->setDirectory(self::$path)
->setType("stor")
->save();
self::$message = "Successfully set up media folder!";
Propel::close();
unset($_SERVER['AIRTIME_CONF']);
} catch (Exception $e) {
self::$message = "Failed to insert " . self::$path . " into cc_music_dirs";
self::$errors[] = self::MEDIA_FOLDER;
}
}
function checkMusicDirectoryExists($con) {
$entry = CcMusicDirsQuery::create()->findPk(1, $con);
return isset($entry) && $entry;
}
}

View File

@ -1,4 +1,7 @@
<?php
require_once dirname(dirname( __DIR__)) . '/library/php-amqplib/amqp.inc';
/**
* User: sourcefabric
* Date: 02/12/14
@ -9,13 +12,25 @@
*/
class RabbitMQSetup extends Setup {
// airtime.conf section header
const SECTION = "[rabbitmq]";
// Constant form field names for passing errors back to the front-end
const RMQ_USER = "rmqUser",
RMQ_PASS = "rmqPass",
RMQ_PORT = "rmqPort",
RMQ_HOST = "rmqHost",
RMQ_VHOST = "rmqVHost";
static $user, $pass, $name, $host, $port, $vhost;
// Form field values
static $user, $pass, $host, $port, $vhost;
// Array of key->value pairs for airtime.conf
static $properties;
// Message and error fields to return to the front-end
static $message = null;
static $errors = array();
function __construct($settings) {
self::$user = $settings[self::RMQ_USER];
@ -23,19 +38,63 @@ class RabbitMQSetup extends Setup {
self::$port = $settings[self::RMQ_PORT];
self::$host = $settings[self::RMQ_HOST];
self::$vhost = $settings[self::RMQ_VHOST];
self::$properties = array(
"host" => self::$host,
"port" => self::$port,
"user" => self::$user,
"password" => self::$pass,
"vhost" => self::$vhost,
);
}
/**
* @return array associative array containing a display message and fields with errors
*/
function runSetup() {
$message = "";
$errors = array();
try {
if ($this->checkRMQConnection()) {
self::$message = "Connection successful!";
} else {
$this->identifyRMQConnectionError();
}
} catch(Exception $e) {
$this->identifyRMQConnectionError();
}
if (count(self::$errors) <= 0) {
$this->writeToTemp();
}
return array(
"message" => $message,
"errors" => $errors
"message" => self::$message,
"errors" => self::$errors
);
}
function writeToTemp() {
parent::writeToTemp(self::SECTION, self::$properties);
}
function checkRMQConnection() {
$conn = new AMQPConnection(self::$host,
self::$port,
self::$user,
self::$pass,
self::$vhost);
return isset($conn);
}
function identifyRMQConnectionError() {
// It's impossible to identify errors coming out of amqp.inc without a major
// rewrite, so for now just tell the user ALL THE THINGS went wrong
self::$message = "Couldn't connect to RabbitMQ server! Please check if the server "
. "is running and your credentials are correct.";
self::$errors[] = self::RMQ_USER;
self::$errors[] = self::RMQ_PASS;
self::$errors[] = self::RMQ_HOST;
self::$errors[] = self::RMQ_PORT;
self::$errors[] = self::RMQ_VHOST;
}
}

View File

@ -1,5 +1,8 @@
<?php
define("BUILD_PATH", dirname(dirname( __DIR__)) . "/build/");
define("AIRTIME_CONF_TEMP_PATH", "/tmp/airtime.conf.temp");
/**
* Class Setup
*
@ -11,6 +14,34 @@ abstract class Setup {
abstract function runSetup();
protected function writeToTemp($section, $properties) {
if (!file_exists(AIRTIME_CONF_TEMP_PATH)) {
copy(BUILD_PATH . "airtime.example.conf", AIRTIME_CONF_TEMP_PATH);
}
$file = file(AIRTIME_CONF_TEMP_PATH);
$fileOutput = "";
$inSection = false;
foreach($file as $line) {
if(strpos($line, $section) !== false) {
$inSection = true;
} else if (strpos($line, "[") !== false) {
$inSection = false;
}
if ($inSection) {
$key = trim(@substr($line, 0, strpos($line, "=")));
$fileOutput .= $key && isset($properties[$key]) ? $key . " = " . $properties[$key] . "\n" : $line;
} else {
$fileOutput .= $line;
}
}
file_put_contents(AIRTIME_CONF_TEMP_PATH, $fileOutput);
}
/**
* Generates a random string.
*
@ -18,8 +49,7 @@ abstract class Setup {
* @param string $p_chars characters to use in the output string
* @return string the generated random string
*/
protected function generateRandomString($p_len=20, $p_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
{
protected function generateRandomString($p_len=20, $p_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') {
$string = '';
for ($i = 0; $i < $p_len; $i++)
{
@ -31,11 +61,16 @@ abstract class Setup {
}
// Import Setup subclasses
require_once('database-setup.php');
require_once('rabbitmq-setup.php');
require_once('general-setup.php');
require_once('media-setup.php');
require_once('finish-setup.php');
// If airtime.conf exists, we shouldn't be here
if (!file_exists(dirname(dirname(__DIR__)) . '/build/airtime.conf')) {
if (!file_exists("/etc/airtime/airtime.conf")) {
if (isset($_GET["obj"]) && $objType = $_GET["obj"]) {
$obj = new $objType($_POST);
if ($obj instanceof Setup) {

View File

@ -17,7 +17,7 @@ class PreferenceUnitTest extends PHPUnit_Framework_TestCase
public function testSetHeadTitle()
{
$title = "unit test";
//This function is confusing and doesn't really work so we're just gonna let it slide...
//This function is confusing and doesn't really work so we're just gonna let it formSlide...
Application_Model_Preference::SetHeadTitle($title);
$this->assertEquals(Application_Model_Preference::GetHeadTitle(), $title);
}

View File

@ -9,22 +9,50 @@ fi
showhelp () {
echo "Usage: airtime-install [options]
--help|-h Displays usage information.
--apache|-a Installs apache and deploys a basic configuration for Airtime."
-h, --help Displays usage information.
-w, --web-user=WEB_USER Set the default apache web user.
-a, --apache Installs apache and deploys a basic configuration for Airtime."
exit 0
}
web_user="www-data"
apache="f"
while [ $# -gt 0 ]
do
while :; do
case "$1" in
(-h|--help) showhelp; exit 0;;
(-a|--apache) apache="t";;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
-h|-\?|--help)
showhelp
exit
;;
-a|--apache)
apache="t"
;;
-w|--web-user)
if [ "$2" ]; then
web_user=$2
shift 2
continue
else
echo 'ERROR: Must specify a non-empty "--web-user WEB_USER" argument.' >&2
exit 1
fi
;;
--web-user=?*)
web_user=${1#*=} # Delete everything up to "=" and assign the remainder.
;;
--web-user=)
echo 'ERROR: Must specify a non-empty "--web-user WEB_USER" argument.' >&2
exit 1
;;
--)
shift
break
;;
-?*)
echo "$0: error - unrecognized option $1" 1>&2;
;;
*)
break
esac
shift
done
@ -136,14 +164,26 @@ EXCHANGES="airtime-pypo|pypo-fetch|airtime-media-monitor|media-monitor"
rabbitmqctl list_vhosts | grep -w ${RABBITMQ_VHOST}
RESULT="$?"
# Only run these if the user doesn't exist
if [ ${RESULT} != "0" ]; then
echo " ## Creating RabbitMQ user $RABBITMQ_USER"
echo " ## Creating RabbitMQ user ${RABBITMQ_USER}..."
rabbitmqctl add_vhost ${RABBITMQ_VHOST}
rabbitmqctl add_user ${RABBITMQ_USER} ${RABBITMQ_PASSWORD}
rabbitmqctl set_permissions -p ${RABBITMQ_VHOST} ${RABBITMQ_USER} "$EXCHANGES" "$EXCHANGES" "$EXCHANGES"
else
echo "RabbitMQ user already exists, skipping creation"
fi
echo " ## Setting RabbitMQ user permissions..."
rabbitmqctl set_permissions -p ${RABBITMQ_VHOST} ${RABBITMQ_USER} "$EXCHANGES" "$EXCHANGES" "$EXCHANGES"
echo -e "\n-----------------------------------------------------"
echo " * Installing Airtime * "
echo "-----------------------------------------------------"
mkdir /etc/airtime
chown -R ${web_user}:${web_user} /etc/airtime
echo -e "\n-----------------------------------------------------"
echo " * Basic Setup DONE! * "
echo " "

View File

@ -1,52 +1,31 @@
<?php
date_default_timezone_set("UTC");
$values = parse_ini_file('/etc/airtime/airtime.conf', true);
$webRoot = apache_getenv("DOCUMENT_ROOT");
//require_once $webRoot . "/../application/configs/conf.php";
//$CC_CONFIG = Config::getConfig();
// Name of the web server user
$CC_CONFIG['webServerUser'] = $values['general']['web_server_user'];
$CC_CONFIG['phpDir'] = $values['general']['airtime_dir'];
$CC_CONFIG['rabbitmq'] = $values['rabbitmq'];
$CC_CONFIG['baseUrl'] = $values['general']['base_url'];
$CC_CONFIG['basePort'] = $values['general']['base_port'];
// Database config
$CC_CONFIG['dsn']['username'] = $values['database']['dbuser'];
$CC_CONFIG['dsn']['password'] = $values['database']['dbpass'];
$CC_CONFIG['dsn']['hostspec'] = $values['database']['host'];
$CC_CONFIG['dsn']['phptype'] = 'pgsql';
$CC_CONFIG['dsn']['database'] = $values['database']['dbname'];
$CC_CONFIG['soundcloud-connection-retries'] = $values['soundcloud']['connection_retries'];
$CC_CONFIG['soundcloud-connection-wait'] = $values['soundcloud']['time_between_retries'];
require_once($CC_CONFIG['phpDir'].'/application/configs/constants.php');
require_once($CC_CONFIG['phpDir'].'/application/configs/conf.php');
$CC_CONFIG['phpDir'] = $values['general']['airtime_dir'];
require_once($webRoot.'/application/configs/constants.php');
require_once($webRoot.'/application/configs/conf.php');
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
get_include_path(),
realpath($CC_CONFIG['phpDir'] . '/library')
realpath($webRoot . '/library')
)));
require_once($CC_CONFIG['phpDir'].'/application/common/Database.php');
require_once($CC_CONFIG['phpDir'].'/application/models/StoredFile.php');
require_once($CC_CONFIG['phpDir'].'/application/models/Preference.php');
require_once($CC_CONFIG['phpDir'].'/application/models/MusicDir.php');
require_once($CC_CONFIG['phpDir'].'/application/common/OsPath.php');
require_once($webRoot.'/application/common/Database.php');
require_once($webRoot.'/application/models/StoredFile.php');
require_once($webRoot.'/application/models/Preference.php');
require_once($webRoot.'/application/models/MusicDir.php');
require_once($webRoot.'/application/common/OsPath.php');
set_include_path($CC_CONFIG['phpDir'].'/library' . PATH_SEPARATOR . get_include_path());
require_once($CC_CONFIG['phpDir'].'/application/models/Soundcloud.php');
set_include_path($CC_CONFIG['phpDir']."/application/models" . PATH_SEPARATOR . get_include_path());
require_once($CC_CONFIG['phpDir']."/library/propel/runtime/lib/Propel.php");
Propel::init($CC_CONFIG['phpDir']."/application/configs/airtime-conf.php");
set_include_path($webRoot.'/library' . PATH_SEPARATOR . get_include_path());
require_once($webRoot.'/application/models/Soundcloud.php');
set_include_path($webRoot."/application/models" . PATH_SEPARATOR . get_include_path());
require_once 'propel/runtime/lib/Propel.php';
Propel::init($CC_CONFIG['phpDir']."/application/configs/airtime-conf-production.php");
Propel::init($webRoot."/application/configs/airtime-conf-production.php");
if(count($argv) != 2){
exit;
@ -58,4 +37,3 @@ $file = Application_Model_StoredFile::RecallById($id);
// set id with -2 which is indicator for processing
$file->setSoundCloudFileId(SOUNDCLOUD_PROGRESS);
$file->uploadToSoundCloud();