$value) { if (preg_match("/^load-([a-z\-]*)$/", $arg, $matches)) { $module = str_replace("-", "_", $matches[1]); // find file : can only search under provision path $files = file_scan_directory(dirname(__FILE__), "$module.module", array('.', '..', 'CVS'), 0, TRUE, 'name'); if (isset($files[$module])) { provision_log("notice", t("Loading @module on request", array("@module" => $module))); $loaded[$module]['name'] = $module; $loaded[$module]['filename'] = $files[$module]->filename; include_once($loaded[$module]['filename']); } $reset = TRUE; } } if ($reset && is_array($loaded)) { $existing = array(); $list = module_list(TRUE, FALSE); foreach ($list as $module) { $existing[$module]['name'] = $module; $existing[$module]['filename'] = drupal_get_filename('module', $module); drupal_load('module', $module); } $loaded = array_merge($existing, $loaded); module_list(TRUE, FALSE, TRUE, $loaded); // Force to regenerate the stored list of hook implementations. module_implements('', TRUE, TRUE); // As we have missed the bootstrap phase, we need to run // our own provision_init hook to initialize modules. module_invoke_all('provision_init'); } } /** * Implementation of hook_init * * Provide defines for all the major paths and settings. * These are settings that must not be modified during the running of the * program, but are configurable. */ function provision_init() { // Do not allow the program to be run as the root user. ever $name = posix_getpwuid(posix_geteuid()); if ($name['name'] == 'root') { provision_log('error', 'You are running the provision script as the root user. Exiting'); provision_set_error(PROVISION_FRAMEWORK_ERROR); provision_output($url); } // Set up defines for platform $docroot = provision_get_option(array("r", "root"), $_SERVER['PWD']); $backend = provision_get_option(array('b', 'backend'), 0); define("PROVISION_DRUSH_BACKEND", $backend); define('PROVISION_DOMAIN', $_SERVER['HTTP_HOST']); // Paths $path = ($docroot) ? $docroot : $_SERVER['DOCUMENT_ROOT']; define('PROVISION_DOCROOT_PATH', rtrim($path, '/')); define('PROVISION_SITES_PATH', rtrim($path, '/') .'/sites'); define('PROVISION_DRUSH_PATH', './drush.php'); $parts = explode("/", rtrim($path, '/')); array_pop($parts); define('PROVISION_PARENT_PATH', rtrim(implode("/" , $parts), '/')); define('PROVISION_BACKUP_PATH', provision_get_option('backup_path', PROVISION_PARENT_PATH . '/backups')); define('PROVISION_CONFIG_PATH', provision_get_option('config_path', PROVISION_PARENT_PATH .'/config')); define('PROVISION_VHOST_PATH', PROVISION_CONFIG_PATH .'/vhost.d'); // Commands define('PROVISION_RESTART_CMD', provision_get_option('restart_cmd', _provision_default_restart_cmd())); // System account $info = posix_getgrgid(posix_getgid()); define('PROVISION_WEB_GROUP', provision_get_option('web_group', $info['name'] )); define('PROVISION_SCRIPT_USER', provision_get_option('script_user', get_current_user() )); // Redirection urls define('PROVISION_MASTER_URL', provision_get_option('master_url', variable_get('install_url', $GLOBALS['base_url']))); define('PROVISION_WEB_DISABLE_URL', PROVISION_MASTER_URL .'/provision/disabled'); define('PROVISION_WEB_MAINTENENCE_URL', PROVISION_MASTER_URL .'/provision/maintenance'); // Database define('PROVISION_MASTER_DB', provision_get_option('master_db', $GLOBALS['db_url'])); $db = parse_url(PROVISION_MASTER_DB); define('PROVISION_DB_USER', $db['user']); define('PROVISION_DB_PASSWD', $db['pass']); define('PROVISION_DB_HOST', $db['host']); // Drupal does not support multiple types of connections in the same session preg_match("$^([a-z]*)://$", $GLOBALS['db_url'], $matches); define('PROVISION_DB_TYPE', $matches[1]); // Load requested additional modules provision_load_from_args(); } function _provision_default_restart_cmd() { # try to detect the apache restart command $command = '/usr/sbin/apachectl'; # a proper default for most of the world foreach (array('/usr/local/sbin/apachectl', # freebsd '/usr/sbin/apache2ctl', # debian + apache2 $command) as $test) { if (is_executable($test)) { $command = $test; } } return "sudo $command graceful"; } /** * Generate a provision.settings.php file to configure provision */ function _provision_generate_config() { provision_log('notice', t("Generating drushrc.php file")); provision_path("chmod", PROVISION_DOCROOT_PATH . '/drushrc.php', 0600, t('Changed permissions of drushrc.php to @confirm'), t('Could not change permissions of drushrc.php to @confirm')); provision_save_platform_data(); provision_path("chmod", PROVISION_DOCROOT_PATH . '/drushrc.php', 0400, t('Changed permissions of drushrc.php to @confirm'), t('Could not change permissions of drushrc.php to @confirm')); return TRUE; } /** * @defgroup provisionui Configure provisioning framework. * @{ */ /** * Implementation of hook_menu(). */ function provision_menu($may_cache) { $items[] = array( 'path' => 'provision', 'title' => t('Configure your platform'), 'description' => t('Configure your platform.'), 'callback' => 'provision_front', 'type' => MENU_CALLBACK, 'access' => TRUE, ); $items[] = array( 'path' => 'provision/disabled', 'title' => t('Site disabled'), 'description' => t('Page showed when a site is disabled.'), 'callback' => 'provision_disabled_site', 'type' => MENU_CALLBACK, 'access' => user_access('access content'), ); $items[] = array( 'path' => 'provision/maintenance', 'title' => t('Site is undergoing maintenance.'), 'description' => t('Page shown when a site is being restored or moved etc.'), 'callback' => 'provision_site_maintenance', 'type' => MENU_CALLBACK, 'access' => user_access('access content'), ); $items[] = array( 'path' => 'provision/notfound', 'title' => t('Site not found.'), 'description' => t('The site you have requested is not available.'), 'callback' => 'provision_site_notfound', 'type' => MENU_CALLBACK, 'access' => user_access('access content'), ); return $items; } function provision_front() { if (variable_get('provision_setup', FALSE)) { drupal_goto('provision/notfound'); } $docroot = PROVISION_DOCROOT_PATH; $uri = PROVISION_BASE_URL; $drush_path = rtrim(drupal_get_path('module', 'drush'), '/') . '/drush.php'; $username = PROVISION_SCRIPT_USER; $setup_cmd = <<Generate your configuration file using the Provision Setup command'); $return .= t('

The provision setup command inspects your environment and creates an initial configuration file in drushrc.php.
This configuration file stores important settings such as paths, binary locations and database credentials.

'); $return .= t('

If you are using this platform as the back end to an existing Hostmaster installation:
you need to run this command before adding the platform node to your hosting site, to allow the hosting site to communicate with this platform.

'); $return .= t('

Ensure that you are logged into the shell as %script_user, and then execute the following commands :

@setup_cmd

', array('%script_user' => $username, '@setup_cmd' => $setup_cmd)); return $return; } function provision_disabled_site() { drupal_set_breadcrumb(array()); return "

This site was disabled by the site administrators.

"; } function provision_site_maintenance() { drupal_set_breadcrumb(array()); return "

This site is being worked on presently. Check back later.

"; } function provision_site_notfound() { drupal_set_breadcrumb(array()); return "

The site you have requested does not exist.

"; } /** * @} End of "defgroup provisionui" */ /** * @defgroup provisiondrush Command line interface for Provision. * @{ */ /** * Implementation of hook_drush_command(). */ function provision_drush_command() { $items['provision install'] = array( 'callback' => 'provision_install_cmd', 'arguments' => array('domain.com' => t('The domain of the site to install.')), 'description' => t('Provision a new site using the provided data.') ); $items['provision import'] = array( 'callback' => '_provision_import', 'arguments' => array('domain.com' => t('The domain of the site to import.')), 'description' => t('Turn an already running site into a provisioned site.') ); $items['provision backup'] = array( 'callback' => '_provision_backup', 'arguments' => array('domain.com' => t('The domain of the site to back up.')), 'optional arguments' => array('backup-file' => t('The file to save the backup to. This will be a gzipped tarball.')), 'description' => t('Generate a back up for the site.') ); $items['provision enable'] = array( 'callback' => '_provision_enable', 'arguments' => array('domain.com' => t('The domain of the site to enable (only if enabled).')), 'description' => 'Enable a disabled site.' ); $items['provision disable'] = array( 'callback' => '_provision_disable', 'arguments' => array('domain.com' => t('The domain of the site to disable (only if disabled).')), 'description' => 'Disable a site.' ); $items['provision verify'] = array( 'callback' => '_provision_verify', 'arguments' => array('domain.com' => t('The domain of the site to verify).')), 'description' => 'Verify that the provisioning framework is correctly installed.' ); $items['provision restore'] = array( 'callback' => '_provision_restore', 'description' => 'Restore the site to a previous backup. This will also generate a backup of the site as it was.', 'arguments' => array('domain.com' => t('The domain of the site to be restored'), 'site_backup.tar.gz' => t('The backup to restore the site to.')) ); $items['provision delete'] = array( 'callback' => '_provision_delete', 'description' => 'Delete a site.' ); $items['provision cron'] = array( 'callback' => '_provision_cron', 'description' => 'Run cron process for site.', 'arguments' => array('domain.com' => t('The domain of the site to be processed')) ); if (!function_exists('hosting_setup')) { $items['provision setup'] = array( 'callback' => '_provision_setup_cmd', 'description' => 'Initialize this platform to be able to create hosted sites.',); } /* // Not implemented yet. $items['provision deploy'] = array( 'callback' => '_provision_deploy', 'description' => 'Deploy a backup made on another provisioning platform on this one.' ); $items['provision rename'] = array( 'callback' => '_provision_rename', 'description' => 'Change the url of an existing site.' ); */ return $items; } /** * Drush callback function * * Installs a new site at $url. * It does so by calling hook_provision_pre_install(), hook_provision_install() and hook_provision_post_install(). * * @param url * The url of the site being installed. * @return * Returns provision_output on success or error. * Will exit with a PROVISION_SITE_INSTALLED error if the site already exists. * Will exit with a PROVISION_FRAMEWORK_ERROR if the command is incorrectly used. */ function provision_install_cmd($url) { global $args; $data = provision_get_site_data($url); if (!$args['commands'][2]) { print "Usage: drush.php provision install DOMAIN [OPTIONS]\n"; print "Install a new site for the domain DOMAIN.\n"; print "Example: drush.php provision install mydomain.com --site-db-host localhost\n"; provision_log("error", "Incorrect usage of the provisioning framework"); provision_set_error(PROVISION_FRAMEWORK_ERROR); provision_output($url, $data); } if (provision_invoke("install", $url, $data)) { provision_save_site_data($url, $data); } provision_output($url, $data); } /** * Generate a backup of the site using a site package. * * @param url * The url of the site being backed up. * @return * Output of provision_output() function. * Will exit with a PROVISION_SITE_NOT_FOUND error if the site does not exist. */ function _provision_backup($url) { if (!_provision_drupal_site_installed($url)) { provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output($url, $data); } $data = provision_get_site_data($url); $args = func_get_args(); array_shift($args); $file = array_shift($args); _provision_backup_site($url, $data, $file); $data['installed'] = TRUE; provision_save_site_data($url, $data); provision_output($url, $data); } /** * Generate a backup tarbal for a site. */ function _provision_backup_site($url, &$data, $file = NULL) { // 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); provision_output($url, $data); } if (is_file($file)) { provision_log("File specified already exists."); provision_set_error(PROVISION_PERM_ERROR); provision_output($url, $data); } $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; provision_invoke("backup", $url, $data); provision_shell_exec("gzip %s", $data['backup_file']); $data['backup_file'] = $data['backup_file'] .'.gz'; } /** * Import a running Drupal site into a provisioned site. * * This is accomplished by inspecting the settings.php file and generating a site.php file. * * @param url * The url of the site being synched. * @return * Output of provision_output() function. * Will exit with a PROVISION_SITE_NOT_FOUND error if the site does not exist. */ function _provision_import($url) { if (!$url) { print "Usage: drush.php provision import DOMAIN\n"; provision_set_error(PROVISION_FRAMEWORK_ERROR); provision_output($url, $data); } $data = provision_get_site_data($url); provision_invoke("import", $url, $data); provision_save_site_data($url, $data); provision_output($url, $data); } /** * Import a running Drupal site into a provisioned site. * * This is accomplished by inspecting the settings.php file and generating a site.php file. * * @param url * The url of the site being verified. * Optional, if no site is specified the platform will be verified * @return * Output of provision_output() function. * Will exit with a PROVISION_SITE_NOT_FOUND error if the site specified does not exist. */ function _provision_verify($url = NULL) { if (!$url) { // we are verifying a platform $data = array(); _provision_create_dir(PROVISION_CONFIG_PATH, t('Provision configuration'), 0755); _provision_create_dir(PROVISION_BACKUP_PATH, t('Web server configuration'), 0700); } else { // we are verifying a site if (!_provision_drupal_site_installed($url)) { provision_log("error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output(); } $data = provision_get_site_data($url); } // We only save the settings if they validated correctly, not before. if (provision_invoke("verify", $url, $data)) { if (!$url) { _provision_generate_config(); } else { provision_save_site_data($url, $data); } } provision_output($url, $data); } /** * Restore command implementation * * This command when called will * 1. Make a backup of the current site, before modifications are made. * 2. Temporarily disable the site by causing apache to redirect to a help page. Restarting apache is required. * 3. Extract the backup that is being restored to to a temporary folder in the sites directory. * 4. Create a new database, belonging to the site's user, and switch it around with the current site's database. * 5. Import the old database and site.php details. * 6. Switch around the sites directory of the current site and the backup being restored. * 7. Regenerate configuration files. * 8. TODO: diagnostic to test that everything is ok? * 9. Remove the temporary redirect and restart apache so the previous site is available again. * 10. Remove the extranuous db and duplicate site directory. * * If at any time an error occurs, before step 9. It should reverse all the changes it has made, * and leave the current site directory and database in the right place, and remove all cruft that * was created by this process. */ function _provision_restore($url, $restore_file) { if (!($exists = _provision_drupal_site_installed($url))) { // this can probably be done more consistently with another // provision_path like function. provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); } $exists &= provision_path("exists", $restore_file, TRUE, t("Restoring site from @path"), t("Could not find backup file @path"), PROVISION_FRAMEWORK_ERROR); if ($exists) { $data = provision_get_site_data($url); $data['restore_file'] = $restore_file; if (provision_invoke("restore", $url, $data)) { provision_save_site_data($url, $data); } } provision_output($url, $data); } function provision_provision_pre_restore($url, &$data) { _provision_backup_site($url, $data); // Backup site for posterity, before rolling back. provision_path("extract", $data['restore_file'], PROVISION_SITES_PATH ."/$url.restore", t('Successfully extracted the contents of @path to @confirm'), t('Failed to extract the contents of @path to @confirm'), PROVISION_PERM_ERROR, PROVISION_FRAMEWORK_ERROR); } function provision_provision_pre_restore_rollback($url, $data) { _provision_recursive_delete(PROVISION_SITES_PATH ."/$url.restore"); } function provision_provision_post_restore($url, $data) { _provision_recursive_delete(PROVISION_SITES_PATH ."/$url.restore"); } function _provision_disable($url) { if (!_provision_drupal_site_installed($url)) { provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output($url, $data); } $data = provision_get_site_data($url); _provision_backup_site($url, $data); # Backup site for posterity, before disabling if (provision_invoke("disable", $url, $data)) { $data['enabled'] = FALSE; provision_save_site_data($url, $data); } provision_output($url, $data); } function _provision_enable($url) { if (!_provision_drupal_site_installed($url)) { provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output($url, $data); } $data = provision_get_site_data($url); if (provision_invoke("enable", $url, $data)) { $data['enabled'] = TRUE; provision_save_site_data($url, $data); } provision_output($url, $data); } function _provision_delete($url) { $data = provision_get_site_data($url); $args = func_get_args(); array_shift($args); $file = array_shift($args); _provision_backup_site($url, $data, $file); provision_invoke("delete", $url, $data); provision_output($url, $data); } /** * Initial setup of platform * * Creates symlink to drush.php * Creates config path * Creates drushrc path * * This function is re-used by the hosting_setup command, as it is a superset of this functionality. */ function _provision_setup() { $success = TRUE; $drush_path = sprintf("%s/%s/drush.php", PROVISION_DOCROOT_PATH, drupal_get_path('module', 'drush')); $success &= provision_path('symlink', $drush_path, PROVISION_DOCROOT_PATH . '/drush.php', t('Created symlink for drush.php file'), t('Could not create symlink for drush.php'), PROVISION_FRAMEWORK_ERROR); $success &= _provision_generate_config(); return $success; } /** * Drush command wrapper for the setup of the platform */ function _provision_setup_cmd() { if (_provision_setup()) { variable_set('provision_setup', TRUE); } // @TODO use provision_output for this, but we need pretty print first. $logs = provision_get_log(); foreach ($logs as $log) { print "$log[message]\n"; } if (provision_get_error()) { print "\nThe command did not complete successfully, please fix the issues and re-run this script."; } } /** * Drush command to run cron */ function _provision_cron($url) { if (!_provision_drupal_site_installed($url)) { provision_log("Error", "Site has not been installed yet."); provision_set_error(PROVISION_SITE_NOT_FOUND); provision_output($url, $data); } $data = provision_get_site_data($url); _provision_drupal_switch_active_site($url); drupal_cron_run(); _provision_drupal_switch_active_site(); }