How we built our Drupal distribution

Mar 17 2010

Over the last couple of years the UNT community has been steadily migrating to Drupal; we recently selected Drupal as the official campus CMS, and are currently running several hundred Drupal sites in production.

The university Central Web Support office (where I work) is tasked with managing the Drupal instances we give to folks around campus, along with applying security updates, end-user documentation, and other general support services. We face a number of unique challenges in this capacity:

  1. How do we maintain a consistent look and feel throughout the enterprise?
  2. How do we ensure a reasonably simple user experience, especially when many of our users are non-technical folks saddled with web responsibilities on top of their usual jobs?
  3. How do we manage our support workload in the midst of all this (e.g., time taken to install new sites, update modules, etc.)?

Our answer to all of these problems has been to build a custom Drupal distribution consisting of two basic pieces: a Drush Make make file, and our installation profile. The advantages?

  • The new site deployment process is almost entirely automated; total setup time for a new Drupal site is usually less than 5 minutes.
  • We can easily deploy Drupal sites pre-configured for simplicity without having to do that configuration manually every time; as a result, our users get a clean, easy-to-use end product even if they've never done web work before.
  • Every site we install this way includes a known collection of tried-and-true modules; when a user asks for a certain piece of functionality, nine times out of ten it's already installed and ready to go, in a module we already know how to help them with.

But how?

As I mentioned earlier, there are two basic pieces involved in our installation process: a make file and and installation profile. In the sections that follow, we'll discuss each component individually, and end by showing you how to tie them together.

Drush Make

Drush Make is a plugin for the Drush command-line tool, allowing you to specify a list of projects (modules and themes), libraries (e.g., TinyMCE) and patches to download to make up the code base for your distribution. It's a really simple way of getting together a known code base without having to download everything by hand (or maintain copies of everything in an SVN repository).

For example, here's a simplified version of the make file we use:

  1. core = 6.x
  2.  
  3. ; standard projects from drupal.org
  4. projects[] = admin_menu
  5. projects[] = adminrole
  6. projects[] = cck
  7. projects[] = features
  8. projects[] = imce
  9. projects[] = imce_wysiwyg
  10. projects[] = install_profile_api
  11. projects[] = ldap_integration
  12. projects[] = pathauto
  13. projects[] = securelogin
  14. projects[] = simpleviews
  15. projects[] = token
  16. projects[] = views
  17. projects[] = webform
  18. projects[] = wysiwyg
  19.  
  20. ; themes: one from d.o and one from local svn repo
  21. projects[] = zen
  22. projects[northtexas][type] = "theme"
  23. projects[northtexas][download][type] = "svn"
  24. projects[northtexas][download][url] = "file:///path/to/local/northtexas_theme"
  25.  
  26. ; third-party libraries (e.g., tinymce for the wysiwyg module)
  27. libraries[tinymce][download][type] = "get"
  28. libraries[tinymce][download][url] = "http://downloads.sourceforge.net/project/tinymce/TinyMCE/3.2.7/tinymce_3_2_7.zip"
  29. libraries[tinymce][directory_name] = "tinymce"

Once the make file is set up, deploying a site based on it is relatively simple (assuming you've got drush and drush make installed):

  1. drush make /path/to/makefile.make /path/to/intended/webroot

Installation profiles

Drush Make on its own isn't quite enough to handle everything you might need to do during the installation process; after all, all it really does is pull down code (which admittedly saves a lot of time). However, you can perform lots of additional site configuration tasks during the installation process through the use of the installation profile system.

An installation profile is simply a PHP script that is used by the Drupal installer to configure the site at install time; you can do pretty much anything in your install profile, from enabling modules and setting variables to installing content types and block layouts. The flexibility and power of install profiles makes them a bit difficult to describe in one article; so, to start with, here are some links to resources we found useful as we were developing ours:

That said, installation profiles don't have to be complicated. Here's a basic procedure to get you started:

  1. Every Drupal installation comes with a directory called "profiles/default"; copy this directory to "profiles/myprofile" (or something along those lines) and rename the "default.profile" file it contains to "myprofile.profile".
  2. Inside that "myprofile.profile" file, rename all the "default_" functions to "myprofile_".
  3. Also in that file, create a function for each task you'd like the installer to perform.
  4. For each of those functions, add a new entry to myprofile_profile_task_list() describing what it's called and what it does.
  5. Then, in myprofile_profile_tasks(), call each of the task functions you created in the order you'd like them to be executed. You should put these function calls just before the menu_rebuild() call at the end of the default function.

Once you've got all this set up, if you visit http://www.example.com/install.php, you'll be given the option to install Drupal using your specified profile. If you do so, Drupal will perform all the tasks you requested. The end result? A fully pre-configured Drupal site, just the way you like it.

Curious about the kinds of tasks you can perform inside an installation profile? Here are some quick example functions we're currently using:

Enable modules

  1. function cws_d6_profile_modules() {
  2. return array(
  3. // core modules
  4. 'block', 'color', 'comment', 'contact', 'dblog', 'filter',
  5. 'help', 'menu', 'node', 'path', 'profile', 'search', 'statistics',
  6. 'system', 'taxonomy', 'trigger', 'update', 'upload', 'user',
  7.  
  8. // contributed modules
  9. 'adminrole', 'admin_menu', 'content', 'content_copy', 'features',
  10. 'fieldgroup', 'imce', 'imce_wysiwyg', 'install_profile_api',
  11. 'ldapauth', 'ldapdata', 'ldapgroups', 'nodereference', 'number',
  12. 'optionwidgets', 'pathauto', 'securelogin', 'simpleviews',
  13. 'text', 'token', 'token_actions', 'userreference', 'wysiwyg',
  14. 'views', 'views_export', 'views_ui',
  15. );
  16. }

(Note: hook_profile_modules() is actually part of the core profile system; you don't need to define it in your task_list function or call it in your tasks function.)

Set variables

  1. function cws_d6_profile_set_variables() {
  2. variable_set('site_name', 'New UNT website');
  3. variable_set('date_default_timezone', '-18000');
  4. variable_set('clean_url', '1');
  5. // and many others...
  6. }

Configure admin role

  1. function cws_d6_profile_create_admin_role() {
  2. $admin_rid = install_add_role('owner');
  3. variable_set('user_admin_role', $admin_rid);
  4. variable_set('adminrole_exceptions', 'change own username');
  5.  
  6. // remove default role created by adminrole module
  7. $rid = install_get_rid('administrator');
  8. if ($rid) {
  9. db_query("DELETE FROM {role} WHERE name = '%s'", 'administrator');
  10. }
  11.  
  12. adminrole_update_permissions();
  13. }

Set the default theme

  1. function cws_d6_profile_enable_northtexas_theme() {
  2. install_disable_theme('garland');
  3. install_default_theme('northtexas');
  4. }

Add custom blocks

  1. function cws_d6_profile_configure_blocks() {
  2. install_init_blocks();
  3.  
  4. $body = 'University of North Texas<br />1155 Union Circle #311277<br />Denton, Texas';
  5. $description = 'Physical Address';
  6. $physadd_delta = install_create_custom_block($body, $description);
  7. install_set_block('block', $physadd_delta, 'northtexas', 'physicaladd', 0);
  8. }

Configure WYSIWYG

This one's kind of lengthy, but worth posting because of how much time it can save to have it in the profile instead of configuring it manually:

  1. function cws_d6_profile_configure_editor() {
  2. // create input format
  3. $richtext_fid = install_add_format('Rich Text');
  4.  
  5. // get role ids
  6. $roles_array = array();
  7. $roles_array[] = install_get_rid('anonymous user');
  8. $roles_array[] = install_get_rid('authenticated user');
  9. $roles_array[] = install_get_rid('owner');
  10.  
  11. // add role permissions for input format
  12. install_format_set_roles($roles_array, $richtext_fid);
  13.  
  14. // setup configuration variables for input format
  15. variable_set('filter_default_format', "{$richtext_fid}");
  16. variable_set("allowed_html_{$richtext_fid}", '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <img>');
  17.  
  18. $tinymce_settings = array(
  19. 'default' => 1,
  20. 'user_choose' => 0,
  21. 'show_toggle' => 1,
  22. 'theme' => 'advanced',
  23. 'language' => 'en',
  24. 'buttons' => array(
  25. 'default' => array(
  26. 'bold' => 1,
  27. 'italic' => 1,
  28. 'bullist' => 1,
  29. 'numlist' => 1,
  30. 'link' => 1,
  31. 'unlink' => 1,
  32. 'image' => 1,
  33. ),
  34. 'fullscreen' => array(
  35. 'fullscreen' => 1,
  36. ),
  37. 'paste' => array(
  38. 'pasteword' => 1,
  39. ),
  40. 'imce' => array(
  41. 'imce' => 1,
  42. ),
  43. ),
  44. 'toolbar_loc' => 'top',
  45. 'toolbar_align' => 'left',
  46. 'path_loc' => 'bottom',
  47. 'resizing' => 1,
  48. 'verify_html' => 1,
  49. 'preformatted' => 0,
  50. 'convert_fonts_to_spans' => 1,
  51. 'remove_linebreaks' => 1,
  52. 'apply_source_formatting' => 1,
  53. 'paste_auto_cleanup_on_paste' => 1,
  54. 'block_formats' => '',
  55. 'css_setting' => 'none',
  56. 'css_path' => '',
  57. 'css_classes' => '',
  58. );
  59. db_query("INSERT into {wysiwyg} (format, editor, settings) VALUES (%d, '%s', '%s')", $richtext_fid, 'tinymce', serialize($tinymce_settings));
  60. }

As you can see, you can do pretty much anything you need to do in your install profile, and the net result is a much faster setup time with a much more consistent end result.

Tying it together

Drush make files and install profiles are great by themselves, but they're even better when you can use them side by side. Based on this excellent post from mig5ter, here's how we're doing that:

  1. First, commit your make file and your install profile to a new repository in your favorite version control system. We're using SVN, so we set up the following structure:
    1. trunk
    2. - cws_d6.make
    3. - cws_d6.profile
    4.  
  2. Now, create a much simpler, separate make file in a location you'll easily remember; its contents should look like this:
    1. core = 6.x
    2. projects[] = drupal
    3. projects[cws_d6][type] = "profile"
    4. projects[cws_d6][download][type] = "svn"
    5. projects[cws_d6][download][url] = "file:///path/to/svn/repo/trunk"
    6. projects[cws_d6][download][branch] = "trunk"
    7.  
  3. To deploy a new site, simply run "drush make /path/to/makefile.make /path/for/deployment"; drush will automatically extract your real make file and install profile from the repository and pull down all the necessary code. You'll still need to visit install.php afterwards.

There are two main benefits to this kind of packaging: (1) your actual installation profile is in version control, and (2) the installation profile script is automatically put in the appropriate location inside your new Drupal site.

Hopefully this helps someone who's interested in putting together a custom distribution; it's really helped us keep things clean and simple.