Commit 415ea351 authored by Adrian Rossouw's avatar Adrian Rossouw Committed by adrian

First import of the basis of the 0.2 release of provision. This version has a...

First import of the basis of the 0.2 release of provision. This version has a much cleaner api, through the dynamic inclusion of <command>.provision.inc files , which any module can overload. Each of these files has the exact same structure, and thus it is much clearer what happens when, and much much simpler to document / extend. I also removed all the drupal dependencies i could find, in preperation for moving to a version agnostic drush, for all buy provision_drupal (which is going to be spun off into a different project). This also includes a working update command, and keeps track of the modules installed on a site after every verify / upgrade process. This information is not being tracked in hosting, yet
parent a27c9a63
<?php
function provision_mysql_provision_backup(&$data, $url = NULL) {
provision_log("backup", "Generating mysql dump for $url.");
provision_shell_exec("mysqldump -u%s -p%s %s > sites/%s/database.sql", $data['db_user'], $data['db_passwd'], $data['db_name'], $url);
provision_shell_exec("cd sites/%; tar -rf %s database.sql; rm database.sql", $url, $data['backup_file']);
}
<?php
function provision_mysql_provision_delete_validate() {
provision_db_connect();
}
/**
* Implementation of hook_provision_delete()
*
* This will drop the database, revoke the privileges and flush the privileges.
*/
function provision_mysql_provision_delete(&$data, $url = NULL) {
return _provision_mysql_destroy_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
}
<?php
function provision_mysql_provision_install_validate() {
provision_db_connect();
}
function provision_mysql_provision_pre_install(&$data, $url = NULL) {
$data['db_type'] = ($data['db_type']) ? $data['db_type'] : PROVISION_DB_TYPE;
$data['db_host'] = ($data['db_host']) ? $data['db_host'] : PROVISION_DB_HOST;
# generate a random password for use
$data['db_passwd'] = user_password();
$data['db_name'] = _provision_mysql_suggest_db_name($data, $url);
$data['db_user'] = $data['db_name'];
_provision_mysql_new_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
}
function provision_mysql_provision_pre_install_rollback(&$data, $url = NULL) {
_provision_mysql_destroy_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
unset($data['db_host']);
unset($data['db_type']);
unset($data['db_passwd']);
unset($data['db_user']);
unset($data['db_name']);
}
......@@ -6,159 +6,104 @@
* A collection of helper functions used by the main provision hooks to accomplish their tasks.
*/
/**
* Generate a new mysql database and user account for the specified credentials
*/
function _provision_mysql_new_site_db($db_name, $db_user, $db_passwd, $db_host) {
provision_set_active_db(_provision_master_db_url());
if (!_provision_mysql_create_database($db_name) ||
!_provision_mysql_database_exists($db_name) ) {
provision_set_error(PROVISION_DB_ERROR);
provision_log("error", "Database could not be created.");
provision_set_active_db();
return FALSE;
function _provision_db_connection($conn = NULL) {
static $connection = NULL;
if (!is_null($conn)) {
$connection = $conn;
}
if (!_provision_mysql_grant($db_name, $db_user, $db_passwd)) {
provision_log("warning", "Could not GRANT user access.");
}
$status = _provision_mysql_database_exists($db_name);
provision_set_active_db();
return $status;
//TODO : Test to confirm that the database is actually writeable. Taking this on faith for now.
return $connection;
}
/**
* Remove the database and user account for the supplied credentials
*/
function _provision_mysql_destroy_site_db($db_name, $db_user, $db_passwd, $db_host) {
provision_set_active_db(_provision_master_db_url());
if ( _provision_mysql_database_exists($data['db_name']) ) {
provision_log("notice", t("Dropping database @dbname", array('@dbname' => $db_name)));
if (!_provision_mysql_drop_database($db_name)) {
provision_log("warning", t("Failed to drop database @dbname", array('@dbname' => $db_name)));
}
function provision_db_connect() {
if (_provision_db_connection()) {
return TRUE;
}
if ( _provision_mysql_database_exists($db_name) ) {
provision_set_error(PROVISION_DB_ERROR);
provision_log("error", "Database could not be destroyed.");
provision_set_active_db();
return FALSE;
$connection = @mysql_connect(PROVISION_DB_HOST, PROVISION_DB_USER, PROVISION_DB_PASSWD);
if (!$connection) {
provision_set_error(PROVISION_DB_ERROR);
provision_log('error', pt('Could not connect to the master database.'));
}
provision_log("notice", "Revoking privileges");
if (!_provision_mysql_revoke($db_name, $db_user)) {
provision_log("warning", t("Failed to revoke user privileges"));
else {
$success = @mysql_select_db('mysql', $connection);
if ($success) {
_provision_db_connection($connection);
}
else {
provision_set_error(PROVISION_DB_ERROR);
provision_log('error', pt('Could not select the mysql database.'));
return FALSE;
}
}
provision_set_active_db();
}
function _provision_mysql_database_exists($name) {
return db_result(db_query("SHOW DATABASES LIKE '%s'", $name));
return TRUE;
}
function _provision_mysql_drop_database($name) {
return db_query("DROP DATABASE `%s`", $name);
}
function _provision_mysql_create_database($name) {
return db_query("CREATE DATABASE %s", $name);
function provision_db_query($query) {
$args = func_get_args();
array_shift($args);
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_provision_db_query_callback($args, TRUE);
$query = preg_replace_callback(PROVISION_QUERY_REGEXP, '_provision_db_query_callback', $query);
return _provision_db_query($query);
}
/**
* Close an active connection. There's no clean way to close a connection
* in the Drupal db api. Sadly.
* Helper function for db_query().
*/
function db_close($connection) {
$func = PROVISION_DB_TYPE . '_close';
return $func($connection);
}
function _provision_db_query($query, $debug = 0) {
$result = mysql_query($query, _provision_db_connection());
function _provision_mysql_can_create_database() {
$test = 'provision_test';
_provision_mysql_create_database($test);
if (_provision_mysql_database_exists($test)) {
if (!_provision_mysql_drop_database($test)) {
provision_log("warning", t("Failed to drop database @dbname", array('@dbname' => $test)));
}
return TRUE;
if (!mysql_errno(_provision_db_connection())) {
return $result;
}
else {
provision_log("error",
mysql_error(_provision_db_connection()) ."\nquery: ". $query);
return FALSE;
}
return FALSE;
}
function _provision_mysql_grant($name, $username, $password, $host = '') {
$host = ($host) ? $host : '%';
return db_query("GRANT ALL PRIVILEGES ON `%s`.* TO `%s`@`%s` IDENTIFIED BY '%s'", $name, $username, $host, $password);
function provision_db_result($result, $row = 0) {
if ($result && mysql_num_rows($result) > $row) {
return mysql_result($result, $row);
}
return FALSE;
}
function _provision_mysql_revoke($name, $username, $host = '') {
$host = ($host) ? $host : '%';
return db_query("REVOKE ALL PRIVILEGES ON `%s`.* FROM `%s`@`%s`", $name, $username, $host);
}
function _provision_master_db_url($data = array()) {
return sprintf("%s://%s:%s@%s/mysql", PROVISION_DB_TYPE, PROVISION_DB_USER, PROVISION_DB_PASSWD, PROVISION_DB_HOST);
}
/**
* Indicates the place holders that should be replaced in _db_query_callback().
*/
define('PROVISION_QUERY_REGEXP', '/(%d|%s|%%|%f|%b)/');
function _provision_mysql_import_dump($dump_file, $db_name, $db_user, $db_passwd, $db_host) {
$exists = provision_path("exists", $dump_file, TRUE,
t('Found database dump at @path.'),
t('No database dump was found at @path.'),
PROVISION_FRAMEWORK_ERROR);
if ($exists) {
$readable = provision_path("readable", $dump_file, TRUE, '',
t('The database dump at @path could not be read.'),
PROVISION_PERM_ERROR);
if ($readable) {
provision_log("notice", sprintf("Importing database using command: mysql -u%s -p%s -h%s %s < %s",
$db_user, $db_passwd, $db_host, $db_name, $dump_file));
if (!provision_shell_exec("mysql -u%s -p%s -h%s %s < %s", $db_user, $db_passwd, $db_host, $db_name, $dump_file )) {
provision_log("error", t("Database import failed"));
}
}
}
}
/**
* Find a viable database name, based on available information.
*
* This function exists solely to work past mysql's database name restrictions.
* As mysql also does not have the ability to rename databases, it is completely
* possible that sites will be running with derivative names on the same server,
* until the upgrade / restore process is completed.
*/
function _provision_mysql_suggest_db_name($url, $data) {
provision_set_active_db(_provision_master_db_url());
if ($data['site_id']) {
$id_suggest = 'site_'. $data['site_id'];
$suggest[] = $id_suggest;
for ($i = 0; $i < 100; $i++) {
$suggest[] = $id_suggest .'_'. $i;
}
}
else {
// This is a last option, and not ideal
// Provision only users will trigger this mostly.
$url_suggest = substr(0, 14, str_replace(array(".", "-"), '' , ereg_replace("^www\.", "", $url)));
$suggest[] = $url_suggest;
for ($i = 0; $i < 100; $i++) {
$suggest[] = $url_suggest .'_'. $i;
}
function _provision_db_query_callback($match, $init = FALSE) {
static $args = NULL;
if ($init) {
$args = $match;
return;
}
foreach ($suggest as $option) {
if (!_provision_mysql_database_exists($option)) {
provision_set_active_db();
return $option;
}
switch ($match[1]) {
case '%d': // We must use type casting to int to convert FALSE/NULL/(TRUE?)
return (int) array_shift($args); // We don't need db_escape_string as numbers are db-safe
case '%s':
return mysql_real_escape_string(array_shift($args), _provision_db_connection());
case '%%':
return '%';
case '%f':
return (float) array_shift($args);
case '%b': // binary data
return "'" . mysql_real_escape_string(array_shift($args), _provision_db_connection()) . "'";
}
}
function provision_db_close() {
if (_provision_db_connection()) {
$conn = _provision_db_connection();
mysql_close($conn);
}
}
......@@ -7,99 +7,154 @@
* The goal of this module is to create mysql databases and user accounts, for sites that are about to be created.
* It uses the provision API to tie into the right places in the site creation work flow.
*/
include_once('provision.mysql.inc');
function provision_mysql_provision_pre_install($url, &$data) {
$data['db_type'] = ($data['db_type']) ? $data['db_type'] : PROVISION_DB_TYPE;
$data['db_host'] = ($data['db_host']) ? $data['db_host'] : PROVISION_DB_HOST;
# generate a random password for use
$data['db_passwd'] = user_password();
$data['db_name'] = _provision_mysql_suggest_db_name($url, $data);
$data['db_user'] = $data['db_name'];
return _provision_mysql_new_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
function provision_mysql_provision_finalize() {
provision_db_close();
}
function provision_mysql_provision_pre_install_rollback($url, &$data) {
_provision_mysql_destroy_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
unset($data['db_host']);
unset($data['db_type']);
unset($data['db_passwd']);
unset($data['db_user']);
unset($data['db_name']);
}
/**
* Generate a new mysql database and user account for the specified credentials
*/
function _provision_mysql_new_site_db($db_name, $db_user, $db_passwd, $db_host) {
if (!_provision_mysql_create_database($db_name) ||
!_provision_mysql_database_exists($db_name) ) {
provision_set_error(PROVISION_DB_ERROR);
provision_log("error", "Database could not be created.");
return FALSE;
}
if (!_provision_mysql_grant($db_name, $db_user, $db_passwd)) {
provision_log("warning", "Could not GRANT user access.");
}
_provision_mysql_flush_privileges();
function provision_mysql_provision_pre_restore($url, &$data) {
// store a backup of the credentials of the site.
$data['old_db_name'] = $data['db_name'];
$data['old_db_passwd'] = $data['db_passwd'];
$data['old_db_user'] = $data['db_user'];
$data['old_db_host'] = $data['db_host'];
# generate a random password for use
$data['db_passwd'] = user_password();
$data['db_name'] = _provision_mysql_suggest_db_name($url, $data);
$data['db_user'] = $data['db_name'];
$success = _provision_mysql_new_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
if ($success) {
_provision_mysql_import_dump(PROVISION_SITES_PATH .'/'. $url .'.restore/database.sql', $data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
$status = _provision_mysql_database_exists($db_name);
if ($status) {
provision_log('success', pt('Created @name database', array("@name" => $db_name)));
}
else {
provision_set_error(PROVISION_DB_ERROR);
provision_log("error", "could not create new database to be imported into");
provision_log("error", pt("Could not create @name database", array("@name" => $db_name)));
}
return $status;
//TODO : Test to confirm that the database is actually writeable. Taking this on faith for now.
}
function provision_mysql_provision_pre_restore_rollback($url, &$data) {
_provision_mysql_destroy_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
//Restore the original database credentials of the site.
foreach ($keys as $key) {
$data[$key] = $data['old_'. $key];
/**
* Remove the database and user account for the supplied credentials
*/
function _provision_mysql_destroy_site_db($db_name, $db_user, $db_passwd, $db_host) {
if ( _provision_mysql_database_exists($data['db_name']) ) {
provision_log("notice", t("Dropping database @dbname", array('@dbname' => $db_name)));
if (!_provision_mysql_drop_database($db_name)) {
provision_log("warning", t("Failed to drop database @dbname", array('@dbname' => $db_name)));
}
}
if ( _provision_mysql_database_exists($db_name) ) {
provision_set_error(PROVISION_DB_ERROR);
provision_log("error", "Database could not be destroyed.");
return FALSE;
}
provision_log("notice", "Revoking privileges");
_provision_mysql_flush_privileges();
if (!_provision_mysql_revoke($db_name, $db_user)) {
provision_log("warning", t("Failed to revoke user privileges"));
}
}
/**
* Implementation of hook_provision_delete()
*
* This will drop the database, revoke the privileges and flush the privileges.
*/
function provision_mysql_provision_delete($url, &$data) {
return _provision_mysql_destroy_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
function _provision_mysql_database_exists($name) {
return provision_db_result(provision_db_query("SHOW DATABASES LIKE '%s'", $name));
}
function _provision_mysql_drop_database($name) {
return provision_db_query("DROP DATABASE `%s`", $name);
}
function _provision_mysql_create_database($name) {
return provision_db_query("CREATE DATABASE %s", $name);
}
function _provision_mysql_flush_privileges() {
return provision_db_query("FLUSH PRIVILEGES");
}
// Rollback doesn't apply here yet. Unless we trigger a restore of the first dump
// made. Which could go on infinitely if something is really long.
function provision_mysql_provision_post_restore($url, &$data) {
provision_path('unlink', 'sites/'. $url .'/database.sql', TRUE,
t("Removed dump file @path after restoring from it"),
t("Could not remove dump file @path"), PROVISION_PERM_ERROR);
return _provision_mysql_destroy_site_db($data['old_db_name'], $data['old_db_user'], $data['old_db_passwd'], $data['old_db_host']);
function _provision_mysql_can_create_database() {
$test = 'provision_test';
_provision_mysql_create_database($test);
if (_provision_mysql_database_exists($test)) {
if (!_provision_mysql_drop_database($test)) {
provision_log("warning", t("Failed to drop database @dbname", array('@dbname' => $test)));
}
return TRUE;
}
return FALSE;
}
function _provision_mysql_grant($name, $username, $password, $host = '') {
$host = ($host) ? $host : '%';
return provision_db_query("GRANT ALL PRIVILEGES ON `%s`.* TO `%s`@`%s` IDENTIFIED BY '%s'", $name, $username, $host, $password);
}
function _provision_mysql_revoke($name, $username, $host = '') {
$host = ($host) ? $host : '%';
return provision_db_query("REVOKE ALL PRIVILEGES ON `%s`.* FROM `%s`@`%s`", $name, $username, $host);
}
function provision_mysql_provision_backup($url, &$data) {
provision_log("backup", "Generating mysql dump for $url.");
provision_shell_exec("mysqldump -u%s -p%s %s > sites/%s/database.sql", $data['db_user'], $data['db_passwd'], $data['db_name'], $url);
provision_shell_exec("cd sites/%; tar -rf %s database.sql; rm database.sql", $url, $data['backup_file']);
function _provision_mysql_import_dump($dump_file, $db_name, $db_user, $db_passwd, $db_host) {
$exists = provision_path("exists", $dump_file, TRUE,
t('Found database dump at @path.'),
t('No database dump was found at @path.'),
PROVISION_FRAMEWORK_ERROR);
if ($exists) {
$readable = provision_path("readable", $dump_file, TRUE, t('Database dump at @path is readable'),
t('The database dump at @path could not be read.'),
PROVISION_PERM_ERROR);
if ($readable) {
provision_log("notice", sprintf("Importing database using command: mysql -u%s -p%s -h%s %s < %s",
$db_user, $db_passwd, $db_host, $db_name, $dump_file));
if (!provision_shell_exec("mysql -u%s -p%s -h%s %s < %s", $db_user, $db_passwd, $db_host, $db_name, $dump_file )) {
provision_log("error", t("Database import failed"));
}
}
}
}
/**
* Implementation of hook_provision_verify
* Find a viable database name, based on available information.
*
* Can't be rolled back.
*/
function provision_mysql_provision_verify() {
provision_set_active_db(_provision_master_db_url());
if (!_provision_mysql_can_create_database()) {
provision_set_error(PROVISION_DB_ERROR | PROVISION_FRAMEWORK_ERROR);
provision_log('error', t('Unable to create new databases.'));
* This function exists solely to work past mysql's database name restrictions.
* As mysql also does not have the ability to rename databases, it is completely
* possible that sites will be running with derivative names on the same server,
* until the upgrade / restore process is completed.
*/
function _provision_mysql_suggest_db_name($data, $url) {
if ($data['site_id']) {
$id_suggest = 'site_'. $data['site_id'];
$suggest[] = $id_suggest;
for ($i = 0; $i < 100; $i++) {
$suggest[] = $id_suggest .'_'. $i;
}
}
else {
provision_log("message", t('Mysql can create new databases.'));
// This is a last option, and not ideal
// Provision only users will trigger this mostly.
$url_suggest = substr(0, 14, str_replace(array(".", "-"), '' , ereg_replace("^www\.", "", $url)));
$suggest[] = $url_suggest;
for ($i = 0; $i < 100; $i++) {
$suggest[] = $url_suggest .'_'. $i;
}
}
foreach ($suggest as $option) {
if (!_provision_mysql_database_exists($option)) {
return $option;
}
}
provision_set_active_db();
}
<?php
function provision_mysql_provision_restore_validate() {
provision_db_connect();
}
function provision_mysql_provision_pre_restore(&$data, $url = NULL) {
// store a backup of the credentials of the site.
$data['old_db_name'] = $data['db_name'];
$data['old_db_passwd'] = $data['db_passwd'];
$data['old_db_user'] = $data['db_user'];
$data['old_db_host'] = $data['db_host'];
# generate a random password for use
$data['db_passwd'] = user_password();
$data['db_name'] = _provision_mysql_suggest_db_name($data, $url);
$data['db_user'] = $data['db_name'];
_provision_mysql_new_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
}
function provision_mysql_provision_restore(&$data, $url) {
_provision_mysql_import_dump(PROVISION_SITES_PATH .'/'. $url .'.restore/database.sql', $data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
}
function provision_mysql_provision_pre_restore_rollback(&$data, $url = NULL) {
_provision_mysql_destroy_site_db($data['db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
$keys = array('db_name', 'db_passwd', 'db_user', 'db_host');
//Restore the original database credentials of the site.
foreach ($keys as $key) {
$data[$key] = $data['old_'. $key];
}
}
// Rollback doesn't apply here yet. Unless we trigger a restore of the first dump
// made. Which could go on infinitely if something is really long.
function provision_mysql_provision_post_restore(&$data, $url = NULL) {
provision_path('unlink', 'sites/'. $url .'/database.sql', TRUE,
t("Removed dump file @path after restoring from it"),
t("Could not remove dump file @path"), PROVISION_PERM_ERROR);
_provision_mysql_destroy_site_db($data['old_db_name'], $data['old_db_user'], $data['old_db_passwd'], $data['old_db_host']);
}
<?php
function provision_mysql_provision_verify_validate() {
provision_db_connect();
}
/**
* Implementation of hook_provision_verify
*
* Can't be rolled back.
*/
function provision_mysql_provision_verify() {
if (PROVISION_CONTEXT_PLATFORM) {
if (!_provision_mysql_can_create_database()) {
provision_set_error(PROVISION_DB_ERROR | PROVISION_FRAMEWORK_ERROR);
provision_log('error', t('Unable to create new databases.'));
}
else {
provision_log("message", t('Mysql can create new databases.'));
}
}
}
<?php
/**
* Provision backup command
*
* Back up an existing site
*/
/**
* Make sure the site is installed and enabled, and that we have a valid target to back up to.
*/
function provision_drupal_provision_backup_validate(&$data, $url = NULL, $backup_file = NULL) {
_provision_drupal_url_required();
_provision_drupal_valid_installed_site();
// This is the actual drupal provisioning requirements.
if (!is_dir(PROVISION_BACKUP_PATH)) {
provision_log("Backup directory does not exist.");
provision_set_error(PROVISION_PERM_ERROR);
}
if ($backup_file) {
if ( provision_path("exists", $backup_file, FALSE,
t("Backing site up to @path."),
t("Back up file @path already exists."),
PROVISION_FRAMEWORK_ERROR)) {
$data['backup_file'] = $backup_file;
}
}
if (!$backup_file) {
$suggested = PROVISION_BACKUP_PATH ."/$url-". date("Y-m-d", mktime()) .".tar";
// Use format of mysite.com-2008-01-02, if already existing, add number.
while (is_file($suggested .'.gz')) {
$count++;
$suggested = PROVISION_BACKUP_PATH ."/$url-". date("Y-m-d", mktime()) ."_$count.tar";
}
$data['backup_file'] = (!empty($file)) ? ereg_replace('.gz$', '', $file) : $suggested;
}
}
/**
* Implentation of hook_provision_backup()
*/
function provision_drupal_provision_backup(&$data, $url, $backup_file) {
// Adds the site directory into the backup file
provision_log("backup", "Adding sites directory to $data[backup_file].gz");
$result = provision_shell_exec("cd %s; tar -rf %s * ", "sites/$url", $data['backup_file']);
if (!$result) {
provision_log("error", "Could not back up sites directory for drupal");
provision_set_error(PROVISION_FRAMEWORK_ERROR);
}
}
/**
* Generate a backup tarbal for a site.
*/
function provision_drupal_provision_post_backup(&$data, $url, $backup_file = NULL) {
provision_shell_exec("gzip %s", $data['backup_file']);
$data['backup_file'] = $data['backup_file'] .'.gz';
}
<?php
/**
* Provision cron command
*/
function provision_drupal_provision_cron_verify() {
_provision_drupal_url_required();
_provision_drupal_valid_installed_site();
}
/**
* Drush command to run cron
*/
function _provision_cron(&$data, $url) {
_provision_drupal_rebuild_caches($url);
}
<?php
function provision_drupal_provision_delete_validate(&$data, $url = NULL, $backup_file = NULL) {
_provision_drupal_valid_site();
}
/**
* Before starting to delete the site, make a backup
*/
function provision_drupal_provision_pre_delete(&$data, $url, $backup_file) {
// @TODO : fix the inline calling of provision_invoke
provision_invoke('backup', $data, $url, $backup_file);
}
/**
* Remove any directories for the site in sites
* This can't be rolled back. so won't even try.
*/
function provision_drupal_provision_delete(&$data, $url) {
if ($old_data = provision_load_site_data($url)) {
if (sizeof($old_aliases = $old_data['aliases'])) {
_provision_drupal_delete_aliases($old_aliases);
}
}
_provision_recursive_delete("sites/$url");
}
<?php
// $Id$
/**
* Provision disable command
*
* Disable an enabled and installed site
*/