← Back to team overview

mvhub-dev team mailing list archive

[Merge] lp:~leegoodrich/mvhub/refactor_mv_setup into lp:mvhub

 

Lee Goodrich has proposed merging lp:~leegoodrich/mvhub/refactor_mv_setup into lp:mvhub.

Requested reviews:
  MVHub devs with commit rights (mvhub-commit)


refactored mv_setup, moving all resusable subroutines to 
MVHub/Utils/Setup.pm and then splitting the script into three new ones: 
mv_update_development, mv_update_staging, and mv_update_production. 
Each script has been custom-tailored to the tasks needed for its 
environment, including recreating developer folders, updating the 
config files, recreating the database with either production or testing 
data, updating what is in /etc, and running the tests.
-- 
https://code.launchpad.net/~leegoodrich/mvhub/refactor_mv_setup/+merge/25461
Your team MVHub Developers is subscribed to branch lp:mvhub.
=== renamed file 'app-mvhub/project-tools/bin/mv_setup' => 'app-mvhub/project-tools/bin/mv_update_development'
--- app-mvhub/project-tools/bin/mv_setup	2010-04-30 21:05:11 +0000
+++ app-mvhub/project-tools/bin/mv_update_development	2010-05-17 19:10:48 +0000
@@ -1,26 +1,42 @@
 #!/usr/bin/perl 
 
-# Purpose: automate setup / updaing of developer setup on brave
+# Purpose: automate setup / updating of developer setup on brave
 
 # after modifiying test with:
-#      sudo mv_setup $OTHER_USER_THAT_EXISTS
-#      sudo mv_setup $NEW_USER
-#      mv_setup
+#      sudo mv_dev_update $OTHER_USER_THAT_EXISTS
+#      sudo mv_dev_update $NEW_USER
+#      mv_dev_update
 
 use strict;
 use warnings;
 use English qw/-no_match_vars/;
 
 use Carp;
-use DBI;
-use File::Copy qw/move/;
+use Getopt::Long;
 use IO::Prompt;
-use Config::Simple;
-use MVHub::Utils::ConfigSimple;
-use MVHub::Utils::DB qw/get_data_source /;
+
+use MVHub::Utils::ConfigSimple qw/ create_config_from /;
+use MVHub::Utils::Setup qw/ add_apache_config
+    						add_dns
+                            load_test_data_for
+    						make_config_files_for
+    						make_databases_for
+    						make_dirs_from /;
+
+my @ALLOWED_HOSTS = qw/ brave cricket /;
 my @WEBSITE_CODES = qw/ mvh nsp /;
 
 {    # main
+	my $interactive = 0;
+	my $usage  = 0;
+	my $hostname = `hostname`;
+    chomp $hostname;
+    
+    Getopt::Long::GetOptions( 'prompt' => \$interactive, 
+						      'help|?' => \$usage )
+		or die "GetOptions failed";
+    
+    die usage() if $usage || ( grep /^$hostname$/, @ALLOWED_HOSTS ) == 0;
     local $OUTPUT_AUTOFLUSH = 1;
 
     my $USERNAME = get_username_or_die();
@@ -32,305 +48,39 @@
     system('clear');
 
     my $base_dir = $cfg->param('BASE.dir');
-    my @dirs = get_dirs_with( "$base_dir", $USERNAME );
-    make_dirs_from( $USERNAME, \@dirs );
-
-    make_databases_for( $USERNAME, @WEBSITE_CODES );
-
-    make_config_files_for( $USERNAME, $cfg, @WEBSITE_CODES );
-
+    my @dirs = MVHub::Utils::Setup::get_dirs_with( "$base_dir", $USERNAME );
+    
+    my $prompt = "Create $USERNAME directory structure? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::make_dirs_from( $USERNAME, \@dirs );
+	}
+	
+	$prompt = "Update config files for $USERNAME? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::make_config_files_for( $USERNAME, $cfg, @WEBSITE_CODES );
+	}
+	
+	$prompt = "DESTROY and reload databases for $USERNAME (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::make_databases_for( $USERNAME, @WEBSITE_CODES );
+    	MVHub::Utils::Setup::load_test_data_for( $USERNAME, $cfg, @WEBSITE_CODES );
+	}
+	
+	$prompt = "Apply database updates for $USERNAME? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::apply_database_updates_for( $USERNAME, $cfg, @WEBSITE_CODES );
+	}
+    
     if ( defined $ENV{SUDO_USER} ) {
         foreach my $prefix (@WEBSITE_CODES) {
-            add_dns( $USERNAME, $prefix );
-            add_apache_config( $USERNAME, $prefix );
-        }
-    }
-}
-
-sub add_dns {
-    my $username = shift or croak 'missing paramater: $username';
-    my $prefix   = shift or croak 'missing parameter: $prefix';
-
-    my $host = "$prefix.$username";
-    my $FILE = '/etc/bind/db.testing123.net';
-
-    my $line = sprintf( "%-15s IN      A       129.63.96.180\n", $host );
-    if ( possible_dup_line( $host, $FILE ) ) {
-        warn "$host found in $FILE, skipping append\n";
-        return;
-    }
-
-    append_to_file( $FILE, $line );
-}
-
-sub add_apache_config {
-    my $username = shift or croak 'missing paramater: $username';
-    my $prefix   = shift or croak 'missing parameter: $prefix';
-
-    my $FILE = '/etc/apache2/sites-available/mvhub_developer.data';
-    my $host = "$prefix.$username.testing123.net";
-
-    my $line = sprintf( "Use MVhub %-15s $prefix $host\n", $username );
-
-    if ( possible_dup_line( $host, $FILE ) ) {
-        warn "$host found in $FILE, skipping append\n";
-        return;
-    }
-
-    append_to_file( $FILE, $line );
-}
-
-sub append_to_file {
-    my ( $file, $append_lines ) = @_;
-    my $tmp_file = "$file.tmp";
-
-    open( IN, $file )
-      or die "bad open: $file  error: $! \n";
-    my @lines = <IN>;
-    close(IN) or die "bad close $file error: $!\n";
-
-    push @lines, $append_lines;
-
-    open( OUT, ">", "$tmp_file" )
-      or die "couldn't open $tmp_file.tmp error: $! \n";
-    ( print OUT @lines )
-      or die "couldn't write to $tmp_file $!\n";
-
-    close(OUT) or die "couldn't close $tmp_file $!\n";
-    File::Copy::move( $tmp_file, $file )
-      or die "couldn't rename $tmp_file to $file\n";
-}
-
-sub add_calculable_values {
-    my $template_cfg = shift or croak 'missing param: $template_cfg';
-    my $username     = shift or croak 'missing param: $username';
-    my $site_code    = shift or croak 'missing param: $site_code';
-
-    $template_cfg->param( 'DATABASE.database_name',   "$username.$site_code" );
-    $template_cfg->param( 'DATABASE.database_user',   "$username" );
-    $template_cfg->param( 'NOTIFICATION.admin_email', "$username\@thecsl.org" );
-    $template_cfg->param( 'NOTIFICATION.dev_email',   "$username\@thecsl.org" );
-    $template_cfg->param( 'SITE.website_name',
-        "$site_code.$username.testing123.net" );
-    $template_cfg->param( 'SITE.website_code', "$site_code" );
-
-    return $template_cfg;
-}
-
-sub add_site_specific_values {
-    my $template_cfg = shift or croak 'missing param: $template_cfg';
-    my $username     = shift or croak 'missing param: $username';
-    my %site_specific_values = %_;
-
-    foreach my $key ( keys %site_specific_values ) {
-        $template_cfg->param( $key, $site_specific_values{$key} );
-    }
-    return $template_cfg;
-}
-
-sub add_user_generated_values {
-    my $template_cfg = shift or croak 'missing param: $template_cfg';
-    my $user_cfg     = shift or croak 'missing param: $user_cfg';
-    my @keys         = @_;
-
-    foreach my $key (@keys) {
-        if ( !( $user_cfg->param($key) ) ) {
-            prompt("Please enter a value for $key: ");
-            $template_cfg->param( $key, $_ );
-        }
-    }
-    return $template_cfg;
-}
-
-sub db_exists {
-    my $dbh     = shift or croak 'missing param: $dbh';
-    my $db_name = shift or croak 'missing param: $db_name';
-
-    my $sql = <<"SQL";
-SELECT 
-     datname 
-FROM 
-     pg_database
-WHERE 
-     datname = ?
-SQL
-
-    my $matching_dbs_aref = $dbh->selectall_arrayref( $sql, {}, ($db_name) );
-    return scalar @$matching_dbs_aref;
-
-}
-
-sub db_user_found {
-    my $dbh      = shift or croak 'missing param: $dbh';
-    my $username = shift or croak 'missing param: $username';
-
-    my $sql = <<"SQL";
-SELECT 
-     usename 
-FROM 
-     pg_user
-WHERE 
-     usename = ?
-SQL
-
-    my $matching_db_users_aref =
-      $dbh->selectall_arrayref( $sql, {}, ($username) );
-    return scalar @$matching_db_users_aref;
-}
-
-sub db_user_has_needed_roles {
-    my $dbh      = shift or croak 'missing param: $dbh';
-    my $username = shift or croak 'missing param: $username';
-
-    my $sql = <<"SQL";
-SELECT 
-     rolname,rolcreaterole,rolcreatedb 
-FROM 
-     pg_roles
-WHERE 
-     rolname = ?
-SQL
-
-    my $role_rows_aref =
-      $dbh->selectall_arrayref( $sql, { Slice => {} }, ($username) );
-    my $result = $$role_rows_aref[0]{rolcreaterole}
-      && $$role_rows_aref[0]{rolcreatedb};
-
-    return $result ? 1 : 0;
-}
-
-sub db_user_has_superuser_role {
-    my $dbh      = shift or croak 'missing param: $dbh';
-    my $username = shift or croak 'missing param: $username';
-
-    my $sql = <<"SQL";
-SELECT 
-     rolname,rolsuper 
-FROM 
-     pg_roles
-WHERE 
-     rolname = ?
-SQL
-
-    my $role_rows_aref =
-      $dbh->selectall_arrayref( $sql, { Slice => {} }, ($username) );
-    return $$role_rows_aref[0]{rolsuper} ? 1 : 0;
-}
-
-sub do_or_die {
-    my $cmd = shift or croak 'missing param: $cmd';
-    $cmd =~ s/^\s+//;
-    my @cmd = split /\s+/, $cmd;
-    ( system(@cmd) == 0 )
-      or die "$cmd failed ABORTING\n";
-}
-
-sub get_dbh {
-    my $config_file = shift or croak "missing param: config_file";
-     croak "couldn't read config_file: '$config_file'"
-      if not -r $config_file;
-
-    my $data_source = MVHub::Utils::DB::get_data_source($config_file);
-
-    my $cfg               = new Config::Simple($config_file);
-    my $database_username = $cfg->param('DATABASE.database_user');
-    my $auth              = $cfg->param('DATABASE.database_magic_word');
-
-    my %attr = (
-        'PrintError'         => 1,
-        'RaiseError'         => 1,
-        'AutoCommit'         => 1,
-        'ShowErrorStatement' => 1,
-    );
-
-    my $dbh = DBI->connect('dbi:Pg:dbname=template1',$database_username, $auth, \%attr )
-      or die "couldn't connect to database";
-    return $dbh;
-
-}
-
-sub get_dirs_with {
-    my $base_dir = shift or croak 'missing param: $base_dir';
-    my $username = shift or croak 'missing param: $username';
-    my @dirs     = (
-        {
-            dir         => 'conf',
-            owner       => '',
-            group       => '',
-            permissions => 'u=rwx,g=rwsx,o=rx',
-        },
-        {
-            dir         => 'cover_db',
-            owner       => '',
-            group       => '',
-            permissions => 'u=rwx,g=rwsx,o=rx',
-        },
-        {
-            dir         => 'reports',
-            owner       => '',
-            group       => '',
-            permissions => 'u=rwx,g=rwsx,o=r-x',
-        },
-        {
-            dir         => 'reports',
-            owner       => '',
-            group       => '',
-            permissions => 'u=rwx,g=rwsx,o=rx',
-        },
-        {
-            dir         => 'source-code',
-            owner       => '',
-            group       => '',
-            permissions => 'u=rwx,g=rwsx,o=rx',
-        },
-        {
-            dir         => 'log/mvh',
-            owner       => '',
-            group       => '',
-            permissions => 'u=rwx,g=rwsx,o=rx',
-        },
-        {
-            dir         => 'log/nsp',
-            owner       => '',
-            group       => '',
-            permissions => 'u=rwx,g=rwsx,o=rx',
-        },
-        {
-            dir         => 'tmp',
-            owner       => '',
-            group       => '',
-            permissions => 'u=rwx,g=rwsx,o=rx',
-        }
-    );
-    foreach my $dir_href (@dirs) {
-        $dir_href->{owner} = $username
-          if not $dir_href->{owner};
-        $dir_href->{group} = $username
-          if not $dir_href->{group};
-
-        $dir_href->{dir} = "$base_dir/$username/" . $dir_href->{dir};
-    }
-    return @dirs;
-}
-
-sub get_site_specific_values {
-    my $site_code = shift or croak 'missing param: $site_code';
-
-    my %hash_of_site_specific_values = (
-        'mvh' => {
-            'SITE.location'            => '"Massachusetts Merrimack Valley"',
-            'SITE.website_description' => 'Merrimack Valley Hub',
-            'NOTIFICATION.team_name'   => 'Merrimack Valley Hub Team'
-        },
-        'nsp' => {
-            'SITE.location'            => '"Massachusetts North Shore"',
-            'SITE.website_description' => 'North Shore Portal',
-            'NOTIFICATION.team_name'   => 'North Shore Portal Team'
-        }
-    );
-    return %{ $hash_of_site_specific_values{$site_code} };
-}
-
+            $prompt = "Add $prefix entries for $USERNAME into DNS and Apache? (Y/N):";
+    		if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+            	MVHub::Utils::Setup::add_dns( $USERNAME, $prefix );
+            	MVHub::Utils::Setup::add_apache_config( $USERNAME, $prefix );
+			}
+        }
+    }
+}
 sub die_if_bad_env_for {
     my $username = shift or croak 'missing parameter $username\n';
     my $msg = '';
@@ -358,7 +108,7 @@
       if ( $username ne $ENV{USER} && $EFFECTIVE_USER_ID != 0 );
 
     $msg .=
-"\ncan't run mv_setup for user root, did you forget a \$user param on command line?\n"
+"\ncan't run mv_dev_update for user root, did you forget a \$user param on command line?\n"
       if $username eq 'root';
 
     die $msg if $msg;
@@ -387,223 +137,32 @@
     return $username;
 }
 
-sub load_test_data_for {
-    my $dbh      = shift or croak 'missing param: $dbh';
-    my $username = shift or croak 'missing param: $username';
-    my $suffix   = shift or croak 'missing param:   $db_name';
-
-    my $cfg =
-      MVHub::Utils::ConfigSimple::create_config_from( $ENV{MV_CONFIG_FILE} );
-
-    my $path_to_project_tools = $cfg->param('ABSOLUTE_PATH.project_tools_dir');
-    my $db_password           = $cfg->param('DATABASE.database_magic_word');
-
-    my $cmd = "export PGPASSWORD=$db_password ";
-    $cmd .= "&& psql -h localhost -U $username -d  $username.$suffix ";
-    $cmd .= "-f ${path_to_project_tools}/test_${suffix}_db.sql >/dev/null";
-
-    ( system($cmd) == 0 )
-      or warn "failed to load data for $username.$suffix";
-
-}
-
-sub make_config_files_for {
-    my $username                       = shift;
-    my $cfg                            = shift;
-    my @website_codes                  = @_;
-    my @keys_for_user_generated_values = qw/ DATABASE.database_magic_word /;
-
-    my $base_dir = $cfg->param('BASE.dir');
-    my $template_config_file =
-      $cfg->param('ABSOLUTE_PATH.template_conf_dir') . "/template.conf";
-    my $template_cfg = new Config::Simple($template_config_file)
-      or die "failed to create config object from $template_config_file";
-
-    foreach my $site_code (@website_codes) {
-
-        my $cfg_file = "$base_dir/$username/conf/$site_code.conf";
-        my $user_cfg = new Config::Simple( syntax => 'ini' );
-        if ( !-e $cfg_file ) {
-            make_empty_config_file_named($cfg_file);
-        }
-        $user_cfg->read($cfg_file);
-
-        print "Editing Config: $cfg_file\n";
-        my %site_specific_values = get_site_specific_values($site_code);
-
-        $template_cfg = add_site_specific_values( $template_cfg, $username,
-            %site_specific_values );
-        $template_cfg =
-          add_calculable_values( $template_cfg, $username, $site_code );
-        $template_cfg = add_user_generated_values( $template_cfg, $user_cfg,
-            @keys_for_user_generated_values );
-
-        merge_conf_objects( $template_cfg, $user_cfg );
-
-        $user_cfg->write("$base_dir/$username/conf/$site_code.conf");
-    }
-}
-
-sub make_dbs_for {
-    my $dbh      = shift or croak 'missing param: $dbh';
-    my $username = shift or croak 'missing param: $username';
-    my @website_codes = @_;
-
-    my $sql;
-    my $user_running_cmd =
-      defined $ENV{SUDO_USER} ? $ENV{SUDO_USER} : $ENV{USER};
-    if ( !db_user_has_needed_roles( $dbh, $user_running_cmd ) ) {
-        warn
-"\nSkipping db stuff $user_running_cmd lacks CREATEDB or CREATEROLE\n";
-        return;
-    }
-
-    if ( ( $user_running_cmd ne $username )
-        && !db_user_has_superuser_role( $dbh, $user_running_cmd ) )
-    {
-        my $msg = qq/
-        skipping db stuff $user_running_cmd can't drop table owned by $username 
-        without role SUPERUSER
-        /;
-        warn $msg;
-        return;
-    }
-
-    if ( !db_user_found( $dbh, $username ) ) {
-        $sql = "CREATE USER $username WITH PASSWORD 'test' CREATEDB CREATEROLE";
-        $dbh->do($sql);
-    }
-
-    foreach my $suffix (@website_codes) {
-        if ( db_exists( $dbh, "$username.$suffix" ) ) {
-            $sql = qq{DROP DATABASE "$username.${suffix}"};
-            $dbh->do($sql);
-        }
-
-        # kludge to avoid 'db being currently being accessed' errs
-        sleep 1;
-
-        $sql = qq{CREATE DATABASE "$username.${suffix}" OWNER $username ENCODING='SQL_ASCII' };
-        $dbh->do($sql);
-
-        load_test_data_for( $dbh, $username, $suffix );
-    }
-    return 1;
-}
-
-sub make_empty_config_file_named {
-    my $filename = shift or croak 'missing Param $filename';
-
-    open my $out_fh, '>', $filename
-      or die "failed to open $filename for writing error:$!\n";
-    print $out_fh "#Config file\n [ignore]\n";
-    close $out_fh
-      or die "failed to close $filename error:$!\n";
-    return 1;
-}
-
-sub make_databases_for {
-    my $username      = shift or croak 'missing parameter: $username';
-    my @website_codes = @_    or croak 'mssing parameter: @website_codes';
-
-    my $prompt = "DESTROY and reload databases for $username (Y/N):";
-    if ( IO::Prompt::prompt( $prompt, -YN ) ) {
-
-        die "I refuse to destroy production database data"
-          if $username =~ /www-data|production/;
-
-        print
-          "(re)making databases for $username this may take 50 seconds....\n";
-        my $DBH = get_dbh( $ENV{MV_CONFIG_FILE} );
-
-        print "...done\n"
-          if make_dbs_for( $DBH, $username, @website_codes );
-
-    }
-}
-
-sub make_dirs_from {
-    my $username       = shift or croak 'missing param: $username';
-    my $dir_hrefs_href = shift or croak 'missing param: $dir_hrefs_href';
-    my @dir_hrefs = @$dir_hrefs_href;
-
-    foreach my $dir_href (@dir_hrefs) {
-        my $mkdir_cmd = "mkdir -p $dir_href->{dir}";
-        my $chown_cmd =
-          "chown -R $dir_href->{owner}:$dir_href->{group} $dir_href->{dir}";
-        if ( $dir_href->{dir} =~ /log/ ) {
-
-          # root owns log files it creates don't try to change ownership on them
-            $chown_cmd =
-              "chown $dir_href->{owner}:$dir_href->{group} $dir_href->{dir}";
-        }
-        else {
-            $chown_cmd =
-              "chown -R $dir_href->{owner}:$dir_href->{group} $dir_href->{dir}";
-        }
-        my $chmod_cmd = "chmod $dir_href->{permissions} $dir_href->{dir}";
-        do_or_die($mkdir_cmd);
-        do_or_die($chown_cmd);
-        do_or_die($chmod_cmd);
-    }
-}
-
-sub merge_conf_objects {
-    my $template_cfg = shift or croak 'missing param: $template_cfg';
-    my $user_cfg     = shift or croak 'missing param: $user_cfg';
-
-    foreach my $key ( $template_cfg->param() ) {
-        if ( !( $user_cfg->param($key) ) ) {
-            my $value = $template_cfg->param($key);
-            $value = defined $value ? $value : 'BAD_MISSING';
-            $user_cfg->param( $key, $value );
-        }
-    }
-
-    #remove keys no longer used
-    foreach my $key ( $user_cfg->param() ) {
-        if ( !defined $template_cfg->param($key) ) {
-            $user_cfg->delete($key);
-        }
-    }
-
-    return $user_cfg;
-}
-
 sub usage {
     return << 'USAGE';
+mv_dev_update - Utility for updating a CSL developer's MVHub environment
+
+Options:
+	--prompt	Prompts the user before any action.
+	--help		Gets this screen
 
 Usage to update your setup:
 
    # to update your own developer setup
-   cdw && mv_setup
+   cdw && mv_dev_update
 
    # to update parts of your developer setup 
    # (apache & dns) that require root
-   cdw && sudo app-mvhub/project-tools/bin/mv_setup $USER
+   cdw && sudo app-mvhub/project-tools/bin/mv_dev_update $USER
 
 Usage to update or partly create a developer setup for somebody else: 
  
  # includes apache, & DNS
- sudo app-mvhub/project-tools/bin/mv_setup <username>
+ sudo app-mvhub/project-tools/bin/mv_dev_update <username>
 
 or: 
- export NEW_DEV_USER=<username> && mv_setup
- sudo app-mvhub/project-tools/bin/mv_setup
+ export NEW_DEV_USER=<username> && mv_dev_update
+ sudo app-mvhub/project-tools/bin/mv_dev_update
 
 USAGE
 
 }
-
-sub possible_dup_line {
-    my $host     = shift or croak 'missing parameter: $host';
-    my $filename = shift or croak 'missing parameter: $filename';
-
-    open my $in_fh, $filename
-      or die "failed to open $filename error:$!";
-
-    my @contents = <$in_fh>;
-    my $regex    = qr/$host/;
-    return scalar grep { $_ =~ $regex } @contents;
-}
-

=== added file 'app-mvhub/project-tools/bin/mv_update_production'
--- app-mvhub/project-tools/bin/mv_update_production	1970-01-01 00:00:00 +0000
+++ app-mvhub/project-tools/bin/mv_update_production	2010-05-17 19:10:48 +0000
@@ -0,0 +1,147 @@
+#!/usr/bin/perl 
+
+# Purpose: automate updating of production environment on parrot
+
+# after modifiying test with:
+#      sudo mv_dev_update $OTHER_USER_THAT_EXISTS
+#      sudo mv_dev_update $NEW_USER
+#      mv_dev_update
+
+use strict;
+use warnings;
+use English qw/-no_match_vars/;
+
+use lib "/var/www/mvhub/www-data/link-to-live-code/lib-mvhub/lib/";
+
+use Carp;
+use Getopt::Long;
+use IO::Prompt;
+
+use MVHub::Utils::ConfigSimple qw/ create_config_from /;
+use MVHub::Utils::Setup qw/ add_apache_config
+    						add_dns
+    						make_config_files_for
+    						make_databases_for
+    						make_dirs_from /;
+
+my @ALLOWED_HOSTS = qw/ parrot /;
+my $USERNAME = 'www-data';
+my @WEBSITE_CODES = qw/ mvh nsp /;
+
+$ENV{MV_CONFIG_FILE} = '/var/www/mvhub/www-data/conf/nsp.conf';
+$ENV{SUDO_USER}		 = 'www-data';
+
+{    # main
+	my $interactive = 0;
+	my $usage  = 0;
+	my $hostname = `hostname`;
+    chomp $hostname;
+	
+    Getopt::Long::GetOptions( 'prompt' => \$interactive, 
+						      'help|?' => \$usage )
+		or die "GetOptions failed";
+    
+    die usage() if $usage || $ENV{USER} ne 'root' || ( grep /^$hostname$/, @ALLOWED_HOSTS ) == 0;
+    local $OUTPUT_AUTOFLUSH = 1;
+
+    my $cfg =
+      MVHub::Utils::ConfigSimple::create_config_from( $ENV{MV_CONFIG_FILE} );
+
+    system('clear');
+
+    my $base_dir = $cfg->param('BASE.dir');
+    my @dirs = MVHub::Utils::Setup::get_dirs_with( "$base_dir", $USERNAME );
+    
+    my $prompt = "Create $USERNAME directory structure? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::make_dirs_from( $USERNAME, \@dirs );
+	}
+	
+	$prompt = "Update config files for $USERNAME? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::make_config_files_for( $USERNAME, $cfg, @WEBSITE_CODES );
+	}
+	
+	$prompt = "Apply database updates for $USERNAME? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::apply_database_updates_for( $USERNAME, $cfg, @WEBSITE_CODES );
+	}
+
+    $prompt = "Update contents of /etc? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::update_etc_files( $cfg );
+        MVHub::Utils::Setup::restart_services();
+	}
+
+}
+
+sub die_if_bad_env_for {
+    my $username = shift or croak 'missing parameter $username\n';
+    my $msg = '';
+
+    foreach my $var (qw/PERL5LIB MV_CONFIG_FILE/) {
+        $msg .= "$var not set"
+          if !defined $ENV{$var};
+    }
+
+    my $xtra_msg = <<'HERE';
+     if running with sudo edit /etc/sudoers to include line:
+
+        Defaults     env_keep+="PERL5LIB MV_CONFIG_FILE"
+
+    also be sure you ran:
+    
+        cdw
+HERE
+
+    if ($msg) {
+        $msg .= $xtra_msg;
+    }
+
+    $msg .= "\nneed to run under sudo if you aren't $username\n"
+      if ( $username ne $ENV{USER} && $EFFECTIVE_USER_ID != 0 );
+
+    $msg .=
+"\ncan't run mv_dev_update for user root, did you forget a \$user param on command line?\n"
+      if $username eq 'root';
+
+    die $msg if $msg;
+    return 1;
+}
+
+sub get_username_or_die {
+    my $username =
+         shift @ARGV
+      || $ENV{NEW_DEV_USER}
+      || ( defined $ENV{MV_CONFIG_FILE} ? $ENV{USER} : undef );
+
+    if ( !$username ) {
+        die "No username defined" . usage();
+    }
+    my @ignore = getpwnam($username);
+
+    if ( !scalar @ignore ) {
+        die "'$username' is not a valid username" . usage();
+    }
+
+    # should have nothing left on command line
+    # at this point
+    die usage() if scalar @ARGV;
+
+    return $username;
+}
+
+sub usage {
+    return << 'USAGE';
+mv_prod_update - Utility for updating the production MVHub environment
+
+Usage: sudo mv_prod_update [options]
+
+Options:
+	--prompt	Prompts the user before any action.
+	--help		Gets this screen
+
+*** THIS COMMAND MUST BE RUN ON THE PRODUCTION SERVER WITH SUDO ***
+USAGE
+
+}

=== added file 'app-mvhub/project-tools/bin/mv_update_staging'
--- app-mvhub/project-tools/bin/mv_update_staging	1970-01-01 00:00:00 +0000
+++ app-mvhub/project-tools/bin/mv_update_staging	2010-05-17 19:10:48 +0000
@@ -0,0 +1,162 @@
+#!/usr/bin/perl 
+
+# Purpose: automate updating of staging environment on penguin
+
+# after modifiying test with:
+#      sudo mv_dev_update $OTHER_USER_THAT_EXISTS
+#      sudo mv_dev_update $NEW_USER
+#      mv_dev_update
+
+use strict;
+use warnings;
+use English qw/-no_match_vars/;
+
+use lib "/var/www/mvhub/www-data/link-to-live-code/lib-mvhub/lib/";
+
+use Carp;
+use Getopt::Long;
+use IO::Prompt;
+
+use MVHub::Utils::ConfigSimple qw/ create_config_from /;
+use MVHub::Utils::Setup qw/ add_apache_config
+    						add_dns
+                            get_production_data
+                            load_production_data
+    						make_config_files_for
+    						make_databases_for
+    						make_dirs_from
+                            restart_services
+                            update_etc_files /;
+
+my @ALLOWED_HOSTS = qw/ penguin /;
+my $USERNAME = 'www-data';
+my @WEBSITE_CODES = qw/ mvh nsp /;
+
+$ENV{MV_CONFIG_FILE} = '/var/www/mvhub/www-data/conf/nsp.conf';
+$ENV{SUDO_USER}		 = 'www-data';
+
+{    # main
+	my $interactive = 0;
+	my $usage  = 0;
+    my $hostname = `hostname`;
+    chomp $hostname;
+    
+    Getopt::Long::GetOptions( 'prompt' => \$interactive, 
+						      'help|?' => \$usage )
+		or die "GetOptions failed";
+    die usage() if $usage || $ENV{USER} ne 'root' || ( grep /^$hostname$/, @ALLOWED_HOSTS ) == 0;
+    local $OUTPUT_AUTOFLUSH = 1;
+
+    my $cfg =
+      MVHub::Utils::ConfigSimple::create_config_from( $ENV{MV_CONFIG_FILE} );
+
+    system('clear');
+
+    my $base_dir = $cfg->param('BASE.dir');
+    my @dirs = MVHub::Utils::Setup::get_dirs_with( "$base_dir", $USERNAME );
+    
+    my $prompt = "Recreate $USERNAME directory structure? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::make_dirs_from( $USERNAME, \@dirs );
+	}
+	
+	$prompt = "Update config files? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::make_config_files_for( $USERNAME, $cfg, @WEBSITE_CODES );
+        $cfg = MVHub::Utils::ConfigSimple::create_config_from( $ENV{MV_CONFIG_FILE} );
+	}
+	
+	$prompt = "Reload databases with recent production data? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::get_production_data( @WEBSITE_CODES ); 
+        MVHub::Utils::Setup::make_databases_for( $USERNAME, @WEBSITE_CODES );
+    	MVHub::Utils::Setup::load_production_data( $USERNAME, $cfg, @WEBSITE_CODES );
+	}
+	
+    $prompt = "Apply database updates for $USERNAME? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::apply_database_updates_for( $USERNAME, $cfg, @WEBSITE_CODES );
+	}
+
+	$prompt = "Update contents of /etc? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::update_etc_files( $cfg );
+        MVHub::Utils::Setup::restart_services();
+	}
+
+    $prompt = "Run tests? (Y/N):";
+    if ( !($interactive) || IO::Prompt::prompt( $prompt, -YN ) ) {
+    	MVHub::Utils::Setup::run_tests( $USERNAME, $cfg );
+	}
+}
+
+sub die_if_bad_env_for {
+    my $username = shift or croak 'missing parameter $username\n';
+    my $msg = '';
+
+    foreach my $var (qw/PERL5LIB MV_CONFIG_FILE/) {
+        $msg .= "$var not set"
+          if !defined $ENV{$var};
+    }
+
+    my $xtra_msg = <<'HERE';
+     if running with sudo edit /etc/sudoers to include line:
+
+        Defaults     env_keep+="PERL5LIB MV_CONFIG_FILE"
+
+    also be sure you ran:
+    
+        cdw
+HERE
+
+    if ($msg) {
+        $msg .= $xtra_msg;
+    }
+
+    $msg .= "\nneed to run under sudo if you aren't $username\n"
+      if ( $username ne $ENV{USER} && $EFFECTIVE_USER_ID != 0 );
+
+    $msg .=
+"\ncan't run mv_dev_update for user root, did you forget a \$user param on command line?\n"
+      if $username eq 'root';
+
+    die $msg if $msg;
+    return 1;
+}
+
+sub get_username_or_die {
+    my $username =
+         shift @ARGV
+      || $ENV{NEW_DEV_USER}
+      || ( defined $ENV{MV_CONFIG_FILE} ? $ENV{USER} : undef );
+
+    if ( !$username ) {
+        die "No username defined" . usage();
+    }
+    my @ignore = getpwnam($username);
+
+    if ( !scalar @ignore ) {
+        die "'$username' is not a valid username" . usage();
+    }
+
+    # should have nothing left on command line
+    # at this point
+    die usage() if scalar @ARGV;
+
+    return $username;
+}
+
+sub usage {
+    return << 'USAGE';
+mv_update_staging - Utility for updating the staging MVHub environment
+
+Usage: sudo mv_update_staging [options]
+
+Options:
+	--prompt	Prompts the user before any action.
+	--help		Gets this screen
+
+*** THIS COMMAND WILL ONLY RUN ON THE STAGING SERVER WITH SUDO ***
+USAGE
+
+}

=== modified file 'app-mvhub/project-tools/templates/template.conf'
--- app-mvhub/project-tools/templates/template.conf	2010-04-30 20:23:54 +0000
+++ app-mvhub/project-tools/templates/template.conf	2010-05-17 19:10:48 +0000
@@ -110,6 +110,8 @@
 project_tools_dir=link-to-live-code/app-mvhub/project-tools/
 tmp_dir=tmp/
 setup_db_dir=link-to-live-code/app-mvhub/setup/database/sql/
+setup_etc_dir=link-to-live-code/app-mvhub/setup/etc/
+user_conf_dir=conf/
 
 [COOKIES]
 # name of cookie used to store 

=== modified file 'app-mvhub/setup/database/update_db.pl'
--- app-mvhub/setup/database/update_db.pl	2010-05-03 21:01:56 +0000
+++ app-mvhub/setup/database/update_db.pl	2010-05-17 19:10:48 +0000
@@ -53,7 +53,7 @@
     $ENV{PGUSER}     = $cfg->param('DATABASE.database_user');
 }
 
-sub get_version {
+sub get_db_version {
     my $dbh = shift;
     my $version;
 

=== added file 'lib-mvhub/lib/MVHub/Utils/Setup.pm'
--- lib-mvhub/lib/MVHub/Utils/Setup.pm	1970-01-01 00:00:00 +0000
+++ lib-mvhub/lib/MVHub/Utils/Setup.pm	2010-05-17 19:10:48 +0000
@@ -0,0 +1,674 @@
+package MVHub::Utils::Setup;
+
+use strict;
+use warnings;
+
+use Carp;
+use Config::Simple;
+use DateTime;
+use DBI;
+use IO::Prompt;
+use File::Basename;
+use File::Copy qw/ move /;
+
+use MVHub::Utils::DB qw/ get_data_source /;
+
+our @EXPORT_OK = qw(
+    add_apache_config
+    add_dns
+    get_production_data
+    get_testing_data
+    make_config_files_for
+    make_databases_for
+    make_dirs_from
+    load_production_data
+    load_test_data_for
+    restart_services
+    update_etc_files
+
+);
+
+sub _append_to_file {
+    my ( $file, $append_lines ) = @_;
+    my $tmp_file = "$file.tmp";
+
+    open( IN, $file )
+      or die "bad open: $file  error: $! \n";
+    my @lines = <IN>;
+    close(IN) or die "bad close $file error: $!\n";
+
+    push @lines, $append_lines;
+
+    open( OUT, ">", "$tmp_file" )
+      or die "couldn't open $tmp_file.tmp error: $! \n";
+    ( print OUT @lines )
+      or die "couldn't write to $tmp_file $!\n";
+
+    close(OUT) or die "couldn't close $tmp_file $!\n";
+    File::Copy::move( $tmp_file, $file )
+      or die "couldn't rename $tmp_file to $file\n";
+}
+
+sub _add_calculable_values {
+    my $template_cfg = shift or croak 'missing param: $template_cfg';
+    my $username     = shift or croak 'missing param: $username';
+    my $site_code    = shift or croak 'missing param: $site_code';
+
+    $template_cfg->param( 'DATABASE.database_name',   "$username.$site_code" );
+    $template_cfg->param( 'DATABASE.database_user',   "$username" );
+    $template_cfg->param( 'NOTIFICATION.admin_email', "$username\@thecsl.org" );
+    $template_cfg->param( 'NOTIFICATION.dev_email',   "$username\@thecsl.org" );
+    $template_cfg->param( 'SITE.website_name',
+        "$site_code.$username.testing123.net" );
+    $template_cfg->param( 'SITE.website_code', "$site_code" );
+
+    return $template_cfg;
+}
+
+sub _add_site_specific_values {
+    my $template_cfg = shift or croak 'missing param: $template_cfg';
+    my $username     = shift or croak 'missing param: $username';
+    my %site_specific_values = @_;
+
+    foreach my $key ( keys %site_specific_values ) {
+        $template_cfg->param( $key, $site_specific_values{$key} );
+    }
+    return $template_cfg;
+}
+
+sub _add_user_generated_values {
+    my $template_cfg = shift or croak 'missing param: $template_cfg';
+    my $user_cfg     = shift or croak 'missing param: $user_cfg';
+    my @keys         = @_;
+
+    foreach my $key (@keys) {
+        if ( !( $user_cfg->param($key) ) ) {
+            prompt("Please enter a value for $key: ");
+            $template_cfg->param( $key, $_ );
+        }
+    }
+    return $template_cfg;
+}
+
+sub _db_exists {
+    my $dbh     = shift or croak 'missing param: $dbh';
+    my $db_name = shift or croak 'missing param: $db_name';
+
+    my $sql = <<"SQL";
+SELECT 
+     datname 
+FROM 
+     pg_database
+WHERE 
+     datname = ?
+SQL
+
+    my $matching_dbs_aref = $dbh->selectall_arrayref( $sql, {}, ($db_name) );
+    return scalar @$matching_dbs_aref;
+
+}
+
+sub _db_user_found {
+    my $dbh      = shift or croak 'missing param: $dbh';
+    my $username = shift or croak 'missing param: $username';
+
+    my $sql = <<"SQL";
+SELECT 
+     usename 
+FROM 
+     pg_user
+WHERE 
+     usename = ?
+SQL
+
+    my $matching_db_users_aref =
+      $dbh->selectall_arrayref( $sql, {}, ($username) );
+    return scalar @$matching_db_users_aref;
+}
+
+sub _db_user_has_needed_roles {
+    my $dbh      = shift or croak 'missing param: $dbh';
+    my $username = shift or croak 'missing param: $username';
+
+    my $sql = <<"SQL";
+SELECT 
+     rolname,rolcreaterole,rolcreatedb 
+FROM 
+     pg_roles
+WHERE 
+     rolname = ?
+SQL
+
+    my $role_rows_aref =
+      $dbh->selectall_arrayref( $sql, { Slice => {} }, ($username) );
+    my $result = $$role_rows_aref[0]{rolcreaterole}
+      && $$role_rows_aref[0]{rolcreatedb};
+
+    return $result ? 1 : 0;
+}
+
+sub _db_user_has_superuser_role {
+    my $dbh      = shift or croak 'missing param: $dbh';
+    my $username = shift or croak 'missing param: $username';
+
+    my $sql = <<"SQL";
+SELECT 
+     rolname,rolsuper 
+FROM 
+     pg_roles
+WHERE 
+     rolname = ?
+SQL
+
+    my $role_rows_aref =
+      $dbh->selectall_arrayref( $sql, { Slice => {} }, ($username) );
+    return $$role_rows_aref[0]{rolsuper} ? 1 : 0;
+}
+
+sub _determine_user_config_filename_from {
+	my $username  = shift;
+	my $site_code = shift;
+	my $cfg		  = shift;
+	
+	my $base_dir = $cfg->param('BASE.dir');
+    my $relative_conf_dir = $cfg->param('RELATIVE_PATH.user_conf_dir');
+    
+    return "$base_dir/$username/$relative_conf_dir/$site_code.conf";
+}
+
+sub _do_or_die {
+    my $cmd = shift or croak 'missing param: $cmd';
+    $cmd =~ s/^\s+//;
+    my @cmd = split /\s+/, $cmd;
+    ( system(@cmd) == 0 )
+      or die "$cmd failed ABORTING\n";
+}
+
+sub _get_dbh {
+    my $config_file = shift or croak "missing param: config_file";
+     croak "couldn't read config_file: '$config_file'"
+      if not -r $config_file;
+
+    my $cfg               = new Config::Simple($config_file);
+    my $database_username = $cfg->param('DATABASE.database_user');
+    my $auth              = $cfg->param('DATABASE.database_magic_word');
+	my $host			  = $cfg->param('DATABASE.database_host');
+	my $port			  = $cfg->param('DATABASE.database_port');
+	
+	my $data_source = "dbi:Pg:dbname=template1;host=$host;port=$port;";
+	
+    my %attr = (
+        'PrintError'         => 1,
+        'RaiseError'         => 1,
+        'AutoCommit'         => 1,
+        'ShowErrorStatement' => 1,
+    );
+
+    my $dbh = DBI->connect($data_source, $database_username, $auth, \%attr )
+      or die "couldn't connect to database";
+    return $dbh;
+
+}
+
+sub _get_site_specific_values {
+    my $site_code = shift or croak 'missing param: $site_code';
+
+    my %hash_of_site_specific_values = (
+        'mvh' => {
+            'SITE.location'            => '"Massachusetts Merrimack Valley"',
+            'SITE.website_description' => 'Merrimack Valley Hub',
+            'NOTIFICATION.team_name'   => 'Merrimack Valley Hub Team'
+        },
+        'nsp' => {
+            'SITE.location'            => '"Massachusetts North Shore"',
+            'SITE.website_description' => 'North Shore Portal',
+            'NOTIFICATION.team_name'   => 'North Shore Portal Team'
+        }
+    );
+    return %{ $hash_of_site_specific_values{$site_code} };
+}
+
+sub _make_empty_config_file_named {
+    my $filename = shift or croak 'missing Param $filename';
+
+    open my $out_fh, '>', $filename
+      or die "failed to open $filename for writing error:$!\n";
+    print $out_fh "#Config file\n [ignore]\n";
+    close $out_fh
+      or die "failed to close $filename error:$!\n";
+    return 1;
+}
+
+sub _merge_conf_objects {
+    my $template_cfg = shift or croak 'missing param: $template_cfg';
+    my $user_cfg     = shift or croak 'missing param: $user_cfg';
+
+    foreach my $key ( $template_cfg->param() ) {
+        if ( !( $user_cfg->param($key) ) ) {
+            my $value = $template_cfg->param($key);
+            $value = defined $value ? $value : 'BAD_MISSING';
+            $user_cfg->param( $key, $value );
+        }
+    }
+
+    #remove keys no longer used
+    foreach my $key ( $user_cfg->param() ) {
+        if ( !defined $template_cfg->param($key) ) {
+            $user_cfg->delete($key);
+        }
+    }
+
+    return $user_cfg;
+}
+
+sub _possible_dup_line {
+    my $host     = shift or croak 'missing parameter: $host';
+    my $filename = shift or croak 'missing parameter: $filename';
+
+    open my $in_fh, $filename
+      or die "failed to open $filename error:$!";
+
+    my @contents = <$in_fh>;
+    my $regex    = qr/$host/;
+    return scalar grep { $_ =~ $regex } @contents;
+}
+
+sub _run_scripts {
+    my @scripts = @_;
+
+    foreach my $script (@scripts) {
+        my $filename = File::Basename::basename($script);
+        print "Executing $filename...";
+        my $output = `psql -f $script 2>&1`;
+        if ( $output =~ /COMMIT/ ) {
+            print "SUCCESS\n";
+        }
+        elsif ( $output =~ /ROLLBACK/ ) {
+            print "FAIL\n";
+            print
+                "The following OUTPUT occured while processing $filename:\n";
+            die "$output\n\n****Processing $filename FAILED****\n";
+        }
+    }
+
+}
+
+
+sub _set_db_ENV_vars {
+    my $cfg = shift;
+
+    # so psql stops on error
+    $ENV{ON_ERROR_STOP} = 1;
+
+    $ENV{PGDATABASE} = $cfg->param('DATABASE.database_name');
+    $ENV{PGHOST}     = $cfg->param('DATABASE.database_host');
+    $ENV{PGPASSWORD} = $cfg->param('DATABASE.database_magic_word');
+    $ENV{PGUSER}     = $cfg->param('DATABASE.database_user');
+}
+
+sub add_dns {
+    my $username = shift or croak 'missing paramater: $username';
+    my $prefix   = shift or croak 'missing parameter: $prefix';
+
+    my $host = "$prefix.$username";
+    my $FILE = '/etc/bind/db.testing123.net';
+
+    my $line = sprintf( "%-15s IN      A       129.63.96.180\n", $host );
+    if ( _possible_dup_line( $host, $FILE ) ) {
+        warn "$host found in $FILE, skipping append\n";
+        return;
+    }
+
+    _append_to_file( $FILE, $line );
+}
+
+sub add_apache_config {
+    my $username = shift or croak 'missing paramater: $username';
+    my $prefix   = shift or croak 'missing parameter: $prefix';
+
+    my $FILE = '/etc/apache2/sites-available/mvhub_developer.data';
+    my $host = "$prefix.$username.testing123.net";
+
+    my $line = sprintf( "Use MVhub %-15s $prefix $host\n", $username );
+
+    if ( _possible_dup_line( $host, $FILE ) ) {
+        warn "$host found in $FILE, skipping append\n";
+        return;
+    }
+
+    _append_to_file( $FILE, $line );
+}
+
+sub apply_database_updates_for {
+	my $username 		= shift or croak 'missing paramater: $username';
+    my $cfg 			= shift or croak 'missing paramter: $cfg';
+    my @website_codes 	= @_;
+    
+    foreach my $prefix (@website_codes) {
+    	my $update_user_cfg_filename = _determine_user_config_filename_from($username, $prefix, $cfg);
+    	my $update_user_cfg = MVHub::Utils::ConfigSimple::create_config_from($update_user_cfg_filename);
+    	my $dbh = MVHub::Utils::DB::get_dbh($update_user_cfg_filename);
+    	
+    	my $db_version = get_db_version($dbh);
+    	my $scripts_path = $cfg->param("ABSOLUTE_PATH.setup_db_dir");
+    	my @db_scripts = get_scripts( version => $db_version, path => $scripts_path  );
+    	
+    	if ( scalar @db_scripts ) {
+    		print "Applying $prefix database updates...\n";
+    		_set_db_ENV_vars($update_user_cfg);
+    		_run_scripts(@db_scripts);
+   		}
+   		else {
+   			print "$prefix database up-to-date at Version $db_version.\n";
+		}
+	}
+}
+
+sub get_db_version {
+    my $dbh = shift;
+    my $version;
+
+    my $exist_sql
+        = "SELECT 1 FROM information_schema.tables WHERE table_name='version_log' LIMIT 1";
+    my $table_exists = ( $dbh->selectrow_array($exist_sql) )[0];
+
+    if ($table_exists) {
+        my $sql = "SELECT max(version) FROM version_log;";
+        $version = ( $dbh->selectrow_array($sql) )[0];
+    }
+    else {
+        $version = 0;
+    }
+
+    return $version;
+}
+
+sub get_scripts {
+    my %args = @_;
+
+    my @raw_scripts = glob "$args{path}/*.sql";
+    my @scripts;
+
+    foreach my $script ( sort @raw_scripts ) {
+
+        if ( $script =~ m#\/(\d{3})_[\w\-]+\.sql# ) {
+            my $script_version = int $1;
+
+            if ( $script_version > $args{'version'} ) {
+                push @scripts, $script;
+            }
+        }
+        else {
+            die
+                "ERROR: '$script' does not conform to naming standard: nnn_word_description.sql\n";
+        }
+    }
+    return sort @scripts;
+}
+
+sub get_dirs_with {
+    my $base_dir = shift or croak 'missing param: $base_dir';
+    my $username = shift or croak 'missing param: $username';
+    my @dirs     = (
+        {
+            dir         => 'conf',
+            owner       => '',
+            group       => '',
+            permissions => 'u=rwx,g=rwsx,o=rx',
+        },
+        {
+            dir         => 'cover_db',
+            owner       => '',
+            group       => '',
+            permissions => 'u=rwx,g=rwsx,o=rx',
+        },
+        {
+            dir         => 'reports',
+            owner       => '',
+            group       => '',
+            permissions => 'u=rwx,g=rwsx,o=r-x',
+        },
+        {
+            dir         => 'reports',
+            owner       => '',
+            group       => '',
+            permissions => 'u=rwx,g=rwsx,o=rx',
+        },
+        {
+            dir         => 'source-code',
+            owner       => '',
+            group       => '',
+            permissions => 'u=rwx,g=rwsx,o=rx',
+        },
+        {
+            dir         => 'log/mvh',
+            owner       => '',
+            group       => '',
+            permissions => 'u=rwx,g=rwsx,o=rx',
+        },
+        {
+            dir         => 'log/nsp',
+            owner       => '',
+            group       => '',
+            permissions => 'u=rwx,g=rwsx,o=rx',
+        },
+        {
+            dir         => 'tmp',
+            owner       => '',
+            group       => '',
+            permissions => 'u=rwx,g=rwsx,o=rx',
+        }
+    );
+    foreach my $dir_href (@dirs) {
+        $dir_href->{owner} = $username
+          if not $dir_href->{owner};
+        $dir_href->{group} = $username
+          if not $dir_href->{group};
+
+        $dir_href->{dir} = "$base_dir/$username/" . $dir_href->{dir};
+    }
+    return @dirs;
+}
+
+sub get_production_data {
+	my @website_codes = @_;
+	my $cmd;
+	
+	my $dt = DateTime->now()->subtract( days => 1 );
+	my $ymd = $dt->ymd;
+	my $dow = $dt->day_abbr;
+	my $timestamp = "${ymd}-06_25-${dow}";
+	
+	print "Getting recent backup of production data...";
+	foreach my $site_code (@website_codes) {
+		$cmd  = "rsync csl-db-02.inside::backup/postgres/";
+		$cmd .= "${timestamp}_${site_code}_production_db.dump ";
+		$cmd .= "/tmp/${site_code}_production_data.dump";
+		
+		( system($cmd) == 0 ) or die "Failed to rsync ${timestamp}_${site_code}_production_db.sql\n";
+	}
+	print "done\n";
+}
+
+sub load_production_data {
+    my $username 	  = shift or croak 'missing param: $username';
+    my $cfg       	  = shift or croak 'missing param: $cfg';
+    my @website_codes = @_;
+
+    my $cmd;
+    
+    print "Loading production data...";
+    foreach my $suffix ( @website_codes ) {
+    	my $cfg_filename = _determine_user_config_filename_from($username, $suffix, $cfg);
+    	my $user_cfg = MVHub::Utils::ConfigSimple::create_config_from($cfg_filename);
+    	
+    	_set_db_ENV_vars($user_cfg);
+    	
+    	$cmd = "pg_restore -O -x -d $username.$suffix /tmp/${suffix}_production_data.dump >/dev/null";
+
+    	( system($cmd) == 0 )
+      	or warn "failed to load ${suffix}_production_data.dump";
+	}
+	print "done\n";
+}
+
+sub load_test_data_for {
+    my $username 	  = shift or croak 'missing param: $username';
+    my $cfg       	  = shift or croak 'missing param: $cfg';
+    my @website_codes = @_;
+
+    my $path_to_project_tools = $cfg->param('ABSOLUTE_PATH.project_tools_dir');
+    my $cmd;
+    
+    print "Loading test data...";
+    foreach my $suffix ( @website_codes ) {
+    	my $cfg_filename = _determine_user_config_filename_from($username, $suffix, $cfg);
+    	my $user_cfg = MVHub::Utils::ConfigSimple::create_config_from($cfg_filename);
+    	
+    	_set_db_ENV_vars($user_cfg);
+    	
+    	$cmd = "psql -f ${path_to_project_tools}/test_${suffix}_db.sql >/dev/null";
+
+    	( system($cmd) == 0 )
+      	or warn "failed to load data for $username.$suffix";
+	}
+	print "done\n";
+}
+
+sub make_config_files_for {
+    my $username                       = shift;
+    my $cfg                            = shift;
+    my @website_codes                  = @_;
+    my @keys_for_user_generated_values = qw/ DATABASE.database_magic_word /;
+
+    my $template_config_file =
+      $cfg->param('ABSOLUTE_PATH.template_conf_dir') . "/template.conf";
+    my $template_cfg = new Config::Simple($template_config_file)
+      or die "failed to create config object from $template_config_file";
+
+    foreach my $site_code (@website_codes) {
+
+        my $cfg_filename = _determine_user_config_filename_from($username, $site_code, $cfg);
+        my $user_cfg = new Config::Simple( syntax => 'ini' );
+        if ( !-e $cfg_filename ) {
+            _make_empty_config_file_named($cfg_filename);
+        }
+        $user_cfg->read($cfg_filename);
+
+        print "Updating Config: $cfg_filename\n";
+        my %site_specific_values = _get_site_specific_values($site_code);
+
+        $template_cfg = _add_site_specific_values( $template_cfg, $username,
+            %site_specific_values );
+        $template_cfg =
+          _add_calculable_values( $template_cfg, $username, $site_code );
+        $template_cfg = _add_user_generated_values( $template_cfg, $user_cfg,
+            @keys_for_user_generated_values );
+
+        _merge_conf_objects( $template_cfg, $user_cfg );
+
+        $user_cfg->write($cfg_filename);
+    }
+}
+
+sub make_databases_for {
+    my $username      = shift or croak 'missing parameter: $username';
+    my @website_codes = @_    or croak 'mssing parameter: @website_codes'; 
+    
+    my $dbh = _get_dbh( $ENV{MV_CONFIG_FILE} );	
+	my $sql;
+    my $user_running_cmd =
+      defined $ENV{SUDO_USER} ? $ENV{SUDO_USER} : $ENV{USER};
+    
+    print "(re)making databases for $username...";
+    
+    if ( !_db_user_has_needed_roles( $dbh, $user_running_cmd ) ) {
+        warn
+"\nSkipping db stuff $user_running_cmd lacks CREATEDB or CREATEROLE\n";
+        return;
+    }
+
+    if ( ( $user_running_cmd ne $username )
+        && !_db_user_has_superuser_role( $dbh, $user_running_cmd ) )
+    {
+        my $msg = qq/
+        skipping db stuff $user_running_cmd can't drop table owned by $username 
+        without role SUPERUSER
+        /;
+        warn $msg;
+        return;
+    }
+
+    if ( !_db_user_found( $dbh, $username ) ) {
+        $sql = "CREATE USER $username WITH PASSWORD 'test' CREATEDB CREATEROLE";
+        $dbh->do($sql);
+    }
+
+    foreach my $suffix (@website_codes) {
+        if ( _db_exists( $dbh, "$username.$suffix" ) ) {
+            $sql = qq{DROP DATABASE "$username.${suffix}"};
+            $dbh->do($sql);
+        }
+
+        # kludge to avoid 'db being currently being accessed' errs
+        sleep 1;
+
+        $sql = qq{CREATE DATABASE "$username.${suffix}" OWNER "$username" ENCODING='SQL_ASCII' };
+        $dbh->do($sql);
+        
+    }
+    print "done\n";
+}
+
+sub make_dirs_from {
+    my $username       = shift or croak 'missing param: $username';
+    my $dir_hrefs_href = shift or croak 'missing param: $dir_hrefs_href';
+    my @dir_hrefs = @$dir_hrefs_href;
+
+    print "Updating $username directory structure...";
+    foreach my $dir_href (@dir_hrefs) {
+        my $mkdir_cmd = "mkdir -p $dir_href->{dir}";
+        my $chown_cmd =
+          "chown -R $dir_href->{owner}:$dir_href->{group} $dir_href->{dir}";
+        if ( $dir_href->{dir} =~ /log/ ) {
+
+          # root owns log files it creates don't try to change ownership on them
+            $chown_cmd =
+              "chown $dir_href->{owner}:$dir_href->{group} $dir_href->{dir}";
+        }
+        else {
+            $chown_cmd =
+              "chown -R $dir_href->{owner}:$dir_href->{group} $dir_href->{dir}";
+        }
+        my $chmod_cmd = "chmod $dir_href->{permissions} $dir_href->{dir}";
+        _do_or_die($mkdir_cmd);
+        _do_or_die($chown_cmd);
+        _do_or_die($chmod_cmd);
+    }
+    print "done\n";
+}
+
+sub restart_services {
+    system('apache2ctl restart');
+    my $is_apache_running = `ps aux | grep 'apache2' | grep -v grep`;
+    die "Apache failed to start" if !($is_apache_running);
+}
+
+sub run_tests {
+	my $username = shift;
+    my $cfg      = shift;
+    
+    print "Running tests...\n";
+    my $project_tools_path = $cfg->param('ABSOLUTE_PATH.project_tools_dir');
+    my $cmd = "$project_tools_path/bin/mv_su_test $username";
+    ( system($cmd) == 0 ) or die "Failed to run tests\n";
+}
+
+sub update_etc_files {
+    my $cfg = shift;
+    my $etc_path = $cfg->param('ABSOLUTE_PATH.setup_etc_dir');
+    print "Updating contents of /etc...\n";
+    my $cmd = "cp -vur $etc_path /";
+    ( system($cmd) == 0 ) or die "Failed to copy etc files\n";
+}
+

=== modified file 'lib-mvhub/t/conf/all.conf'
--- lib-mvhub/t/conf/all.conf	2010-04-30 20:23:54 +0000
+++ lib-mvhub/t/conf/all.conf	2010-05-17 19:10:48 +0000
@@ -94,6 +94,8 @@
 project_tools_dir=link-to-live-code/app-mvhub/project-tools/
 tmp_dir=tmp/
 setup_db_dir=link-to-live-code/app-mvhub/setup/database/sql/
+setup_etc_dir=link-to-live-code/app-mvhub/setup/etc/
+user_conf_dir=conf/
 
 [COOKIES]
 # name of cookie used to store 


Follow ups