My company has developed a Yii application that we now want to deploy as a debian package.
I’d prefer to have the install script (a CConsoleCommand), if it’s been given root access to the MySql instance, be able to create a database, a database-user, and then create the config file with those credentials. However it seems that CDbConnection insists on having an available database. Is there a way to do this from within Yii?
Has anyone ever created a self-installing Yii application? My current strategy is to have a CConsoleCommand doing the actual installation (set up a database, fill it with some starting data, then generate the config file), and have an InstallController in the application that calls it, so people will be able to do it both from the command line and with a web-interface. Is this sane?
We are not this far along yet but if you get an answer I’d love to hear it. I was thinking we would do the same thing. Unpack the folder then run a console command. I’ve not seen any other packaging system for Yii yet.
So, this is the code I currently use for the default installation (replaced the app name with "default"). It is called by an install script, and it provides a location to a file containing the root password of the db (in /etc/secretrootpw or something), as having actual passwords in your commandline is not secure enough for our security officer:
public function actionDb($username = 'root', $password = null, $pwdfile = null, $dbName = 'default', $dbHost = 'localhost') {
if (!$username || !$dbName || !$dbHost) {
return 1;
}
if ($pwdfile) {
$pwd = trim(file_get_contents($pwdfile), " \t\n\r\0\x0B");
if ($pwd) {
$password = $pwd;
}
}
// Check our parameters if they check out
$tmpDbName = ($username == 'root') ? 'mysql' : $dbName;
$dsn = 'mysql:host=' . $dbHost . ';dbname=' . $tmpDbName;
// Check if we can connect to MySql
try {
$db = new CDbConnection($dsn, $username, $password);
$db->active = true;
} catch (Exception $e) {
echo 'Connecting to the database using the given credentials failed. Message: ' . $e->getMessage() . "\n";
return 1;
}
// Create database if necessary
try {
if ($username == 'root') {
$sql = "CREATE DATABASE IF NOT EXISTS $dbName
DEFAULT CHARACTER SET utf8
DEFAULT COLLATE utf8_general_ci;";
$db->getCommandBuilder()->createSqlCommand($sql)->execute();
}
} catch (Exception $e) {
echo 'Creating new database using the given credentials failed. Message: ' . $e->getMessage() . "\n";
return 1;
}
$dsn = 'mysql:host=' . $dbHost . ';dbname=' . $dbName;
// Check if we can connect to newly created database
try {
$db = new CDbConnection($dsn, $username, $password);
$db->active = true;
} catch (Exception $e) {
echo 'Connecting to the database using the given credentials failed. Message: ' . $e->getMessage() . "\n";
return 1;
}
$transaction = $db->beginTransaction();
try {
// If we're root, also create a user for it
if ($username == 'root') {
$location = $dbHost == 'localhost' ? 'localhost' : '%';
$sql = "SELECT `User` FROM `mysql`.`user` WHERE `User` = 'default';";
$result = $db->getCommandBuilder()->createSqlCommand($sql)->queryScalar();
if ($result) {
// Delete existing default user. Yes, There Can Be Only One.
$sql = "DROP USER 'default'@'$location';";
$db->getCommandBuilder()->createSqlCommand($sql)->execute();
}
$username = 'default';
$password = $this->generatePassword();
// this is safe sql building because no external data is used. Keep it that way.
$sql = "CREATE USER '$username'@'$location' IDENTIFIED BY '$password';";
$db->getCommandBuilder()->createSqlCommand($sql)->execute();
$sql = "GRANT ALL ON $dbName.* TO '$username'@'$location';";
$db->getCommandBuilder()->createSqlCommand($sql)->execute();
}
// Create all the tables if needed
$sql = $this->dbStructure;
$db->getCommandBuilder()->createSqlCommand($sql)->execute();
foreach ($this->dbData as $table => $records) {
foreach ($records as $record) {
// find if the record already exists, identifying field should be first in the array
$result = $db->createCommand()->select('id')->from($table)->where(key($record) . '=:id', array(':id' => $record[key($record)]))->queryScalar();
if (!$result) {
$db->getCommandBuilder()->createInsertCommand($table, $record)->execute();
}
}
}
} catch (Exception $e) {
$transaction->rollback();
echo "Setting up database failed: {$e->getMessage()}\n\n";
return 1;
}
// Save the username and password to the sensitive-config
$this->saveSettings(array(
'components' => array(
'db' => array(
'connectionString' => $dsn,
'username' => $username,
'password' => $password,
))));
echo <<<EAH
Installation successful. You can now login using the following credentials:
username: admin
password:
Change the password for admin immediately after you log in for the first time!
";
EAH;
return 0;
}
The "saveSettings" method takes the current local config of the app or the default template, then merges in the settings, and saves it. This method also assumes MySql.