Ticket #4760: mythtv-4760-database_backup_scripts-20080503.patch

File mythtv-4760-database_backup_scripts-20080503.patch, 78.0 KB (added by sphery <mtdean@…>, 16 years ago)

Updated scripts. Replaces mythtv-4760-database_backup_scripts.patch. Restore script was missing one config-file-parsing change required on some configurations.

  • programs/programs.pro

     
    1111}
    1212
    1313using_backend {
    14     SUBDIRS += mythbackend mythfilldatabase mythtv-setup
     14    SUBDIRS += mythbackend mythfilldatabase mythtv-setup scripts
    1515}
    1616
    1717using_frontend:using_backend {
  • programs/scripts/database/database_mythconverg_restore.pl

     
     1#!/usr/bin/perl -w
     2#
     3# database_mythconverg_restore.pl
     4#
     5# Restores a backup of the MythTV database.
     6#
     7# For details, see:
     8#   database_mythconverg_restore.pl --help
     9
     10# Includes
     11    use Getopt::Long;
     12    use File::Temp qw/ tempfile /;
     13
     14# Some variables we'll use here
     15    our ($username, $homedir, $mythconfdir, $database_information_file);
     16    our ($partial_restore, $mysql_client, $uncompress, $mc_sql);
     17    our ($usage, $debug, $dbh);
     18    our ($d_mysql_client, $d_db_name, $d_uncompress);
     19    our ($db_hostname, $db_port, $db_username, $db_name, $db_schema_version);
     20# This script does not accept a database password on the command-line.
     21# Any packager who enables the functionality should modify the --help output.
     22#    our ($db_password);
     23    our ($backup_directory, $backup_filename);
     24
     25    our %mysql_conf  = ('db_host'       => '',
     26                        'db_port'       => -1,
     27                        'db_user'       => '',
     28                        'db_pass'       => '',
     29                        'db_name'       => '',
     30                        'db_schemaver'  => ''
     31                        );
     32    our %backup_conf = ('directory'     => '',
     33                        'filename'      => ''
     34                       );
     35
     36# Defaults
     37    $d_db_name       = 'mythconverg';
     38    $d_mysql_client  = 'mysql';
     39    $d_uncompress    = 'gzip -d';
     40
     41# Provide default values for GetOptions
     42    $mysql_client    = $d_mysql_client;
     43    $uncompress      = $d_uncompress;
     44
     45# Load the cli options
     46    GetOptions('hostname|DBHostName=s'              => \$db_hostname,
     47               'port|DBPort=i'                      => \$db_port,
     48               'username|DBUserName=s'              => \$db_username,
     49# This script does not accept a database password on the command-line.
     50#               'password|DBPassword=s'              => \$db_password,
     51               'name|DBName=s'                      => \$db_name,
     52               'schemaver|DBSchemaVer=s'            => \$db_schema_version,
     53               'directory|DBBackupDirectory=s'      => \$backup_directory,
     54               'filename|DBBackupFilename=s'        => \$backup_filename,
     55               'partial_restore|new_hardware'       => \$partial_restore,
     56               'mysql_client=s'                     => \$mysql_client,
     57               'uncompress=s'                       => \$uncompress,
     58               'create_database|create_db|mc_sql=s' => \$mc_sql,
     59               'usage|help|h'                       => \$usage,
     60               'verbose|v|debug'                    => \$debug
     61              );
     62
     63# Print usage
     64    if ($usage)
     65    {
     66        print <<EOF;
     67Usage:
     68  $0 [options|database_information_file]
     69
     70Restores a backup of the MythTV database.
     71
     72DETAILED DESCRIPTION:
     73
     74This script is used to restore a backup of the MythTV database (as created by
     75the database_mythconverg_backup.pl script). It can be called with a single
     76command-line argument specifying the name of a "database information file" (see
     77DATABASE INFORMATION FILE, below), which contains sufficient information about
     78the database and the backup to allow the script to restore a backup without
     79needing any additional configuration files. In this mode, all other MythTV
     80configuration files (including config.xml, mysql.txt) are ignored, but the
     81backup resource file (see RESOURCE FILE, below) and the MySQL option files
     82(i.e. /etc/my.cnf or ~/.my.cnf) will be honored.
     83
     84The script can also be called  using command-line arguments to specify the
     85required information. If no database information file is specified, the script
     86will attempt to determine the appropriate configuration by using the MythTV
     87configuration file(s) (prefering config.xml, but falling back to mysql.txt if
     88no config.xml exists). Once the MythTV configuration file has been parsed, the
     89backup resource file (see RESOURCE FILE, below) will be parsed, then
     90command-line arguments will be applied (thus overriding any values determined
     91from the configuration files).
     92
     93The only information required by the script is the directory in which the
     94backup exists (the script will attempt to find the most current backup file,
     95based on the filename). Therefore, when using a database information file, the
     96DBBackupDirectory should be specified, or if running manually, the --directory
     97command-line argument should be specified. The DBBackupDirectory may be
     98specified in a backup resource file (see RESOURCE FILE, below). If the
     99specified directory is not readable, the script will terminate. Likewise, if
     100the backup file cannot be read, the script will terminate.
     101
     102If the database name is not specified, the script will attempt to use the
     103MythTV default database name, $d_db_name. Note that the same is not true for
     104the database username and database password. These must be explicitly
     105specified. The password must be specified in a database information file, a
     106backup resource file, or a MySQL options file. The username may be specified
     107the same way or may be specified using a command-line argument if not using a
     108database information file.
     109
     110If no database exists, the script can attempt to create the initial database. To allow this, specify the --create_database argument and specify the location of the mc.sql script (full directory/filename). On most MySQL configurations this will fail unless connecting to the database using the MySQL root user. Though you may specify '--username=root' on the command line, the script does not allow specifying a database password on the command line. Therefore, you'll need to specify 'DBPassword=<MySQL root user password>' in the backup resource file.
     111
     112If attempting to perform a full restore, the database must be empty (no tables), or--if the script is allowed to create the initial database, as explained above--must not exist.  If attempting to do a partial or "new hardware" restore, the database must exist and must have tables.  See QUICK START, below, for more information.
     113
     114DATABASE INFORMATION FILE
     115
     116The database information file contains information about the database and the
     117backup. The information within the file is specified as name=value pairs using
     118the same names as used by Myth's config.xml and mysql.txt configuration files.
     119The following variables are recognized:
     120
     121  DBHostName - The hostname (or IP address) which should be used to find the
     122               MySQL server.
     123  DBPort - The TCP/IP port number to use for the connection. This may have a
     124           value of 0, i.e. if the hostname is localhost or if the server is
     125           using the default MySQL port or the port specified in a MySQL
     126           options file.
     127  DBUserName - The database username to use when connecting to the server.
     128  DBPassword - The password to use when connecting to the server.
     129  DBName - The name of the database that contains the MythTV data.
     130  DBSchemaVer - The MythTV schema version of the database. This value will be
     131                used to create the backup filename, but only if the filename
     132                has not been specified using DBBackupFilename or the --filename
     133                argument.
     134  DBBackupDirectory - The directory in which the backup file should be
     135                      created. This directory may have been specially
     136                      configured by the user as the "DB Backups" storage
     137                      group. It is recommended that this directory be
     138                      used--especially in "common-use" scripts such as those
     139                      provided by distributions.
     140  DBBackupFilename - The name of the file in which the backup should be
     141                     created. Additional extensions may be added by this
     142                     script as required (i.e. adding an appropriate suffix,
     143                     such as ".gz", to the file if it's compressed). If the
     144                     filename recommended by mythbackend is used, it will be
     145                     displayed in the GUI messages provided for the user. If
     146                     the recommended filename is not used, the user will not be
     147                     told where to find the backup file. If no value is
     148                     provided, a filename using the default filename format
     149                     will be chosen.
     150  partial_restore  - Do a partial restore (as would be required when setting
     151                     up MythTV on new hardware) of only the MythTV recordings
     152                     and recording rules.
     153  mysql_client     - The path (including filename) of the mysql client
     154                     executable.
     155  uncompress       - The command (including path, if necessary)  to use to
     156                     uncompress the backup. If you specify an uncompress
     157                     program, the backup file will be assumed to be compressed,
     158                     so the command will be run on the file regardless.
     159                     If no value is specified for uncompress or if the value
     160                     '$d_uncompress' or 'gunzip' is specified, the script will
     161                     check to see if the file is actually a gzip-compressed
     162                     file, and if so, will first attempt to use the
     163                     IO::Uncompress::Gunzip module to uncompress the backup
     164                     file, but, if not available, will run the command
     165                     specified. Therefore, if IO::Uncompress::Gunzip is
     166                     installed and functional, specifying a value for
     167                     uncompress is unnecessary.
     168  create_database  - The location of the mc.sql script (full directory and
     169                     filename).  If specified, and if the database does not
     170                     exist, the script will attempt to create the initial
     171                     database by running the specified mc.sql script commands.
     172                     This probably requires running the restore script with
     173                     the DBUserName set to root.
     174
     175RESOURCE FILE
     176
     177The backup resource file specifies values using the same format as described
     178for the database information file, above, but is intended as a "permanent,"
     179user-created configuration file. The database information file is intended as a
     180"single-use" configuration file, often created automatically (i.e. by a
     181program, such as a script). The backup resource file should be placed at
     182"~/.mythtv/backuprc" and given appropriate permissions. To be usable by the
     183script, it must be readable. However, it should be protected as required--i.e.
     184if the DBPassword is specified, it should be made readable only by the owner.
     185
     186When specifying a database information file, the resource file is parsed before
     187the database information file to prevent the resource file from overriding the
     188information in the database information file. When no database information
     189file is specified, the resource file is parsed after the MythTV configuration
     190files, but before the command-line arguments to allow the resource file to
     191override values in the configuration files and to allow command-line arguments
     192to override resource file defaults.
     193
     194options:
     195
     196--hostname [database hostname]
     197
     198    The hostname (or IP address) which should be used to find the MySQL server.
     199    See DBHostName, above.
     200
     201--port [database port]
     202
     203    The TCP/IP port number to use for connection to the MySQL server. See
     204    DBPort, above.
     205
     206--username [database username]
     207
     208    The MySQL username to use for connection to the MySQL server. See
     209    DBUserName, above.
     210
     211--name [database name]
     212
     213    The name of the database containing the MythTV data. See DBName, above.
     214
     215    Default:  $d_db_name
     216
     217--schemaver [MythTV database schema version]
     218
     219    The MythTV schema version. See DBSchemaVer, above.
     220
     221--directory [directory]
     222
     223    The directory in which the backup file should be stored. See
     224    DBBackupDirectory, above.
     225
     226--filename [database backup filename]
     227
     228    The name to use for the database backup file. If not provided, a filename
     229    using a default format will be chosen. See DBBackupFilename, above.
     230
     231--partial_restore
     232
     233    Do a partial restore (as would be required when setting up MythTV on new
     234    hardware) of only the MythTV recordings and recording rules.
     235
     236--mysql_client [path]
     237
     238    The path (including filename) of the mysql client executable. See
     239    mysql_client in the DATABASE INFORMATION FILE description, above.
     240
     241    Default:  $d_mysql_client
     242
     243--uncompress [path]
     244
     245    The command (including path, if necessary) to use to uncompress the
     246    backup. See uncompress in the DATABASE INFORMATION FILE description, above.
     247
     248    Default:  $d_uncompress
     249
     250--create_database [path]
     251
     252    The location of the mc.sql script (full directory and filename).  See
     253    create_database in the DATABASE INFORMATION FILE description, above.
     254
     255--help
     256
     257    Show this help text.
     258
     259--verbose
     260
     261    Show what's happening.
     262
     263QUICK START:
     264
     265Create a file ~/.mythtv/backuprc with a single line,
     266"DBBackupDirectory=/home/mythtv" (no quotes).  For example:
     267
     268# echo "DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
     269
     270To do a full restore:
     271Ensure you have an empty database.  If you a replacing a corrupt database, you
     272must first drop the existing database.  You may do this using the mysql client
     273executable by issuing the statement, "DROP DATABASE mythconverg;" (no quotes;
     274fix the database name, as required).
     275
     276Run this script to restore the backup. Use the --verbose argument to see what's
     277happening.
     278
     279# $0 --verbose
     280
     281At this point, you may start mythtv-setup or mythbackend.  If you restored a
     282backup from an older version of MythTV, mythtv-setup will upgrade the database
     283for you.
     284
     285To do a partial restore:
     286You must have a fully-populated database schema (but without the data you wish
     287to import) from the version of MythTV used to create the backup.  You may
     288create and populate the database by running the mc.sql script (or run this
     289script, as described below--specifying the MySQL root username and
     290password--and provide the '--create_database=/path/to/mc.sql' argument) to
     291create the database.  Then, start and exit mythtv-setup to populate the
     292database.  And, finally, do the partial restore with:
     293
     294# $0 --verbose --partial_restore
     295
     296If you would like to do a partial/new-hardware restore and have upgraded
     297MythTV, you must first do a full restore, then start and exit mythtv-setup (to
     298upgrade the database), then create a backup, then drop the database, then
     299follow the instructions for doing a partial restore with the new (upgraded)
     300backup file.
     301
     302EOF
     303        exit;
     304    }
     305
     306    sub verbose
     307    {
     308        return unless (defined($debug));
     309        print join("\n", @_), "\n";
     310    }
     311
     312    sub print_configuration
     313    {
     314        verbose("\nDatabase Information:",
     315                "         DBHostName:  $mysql_conf{'db_host'}",
     316                "             DBPort:  $mysql_conf{'db_port'}",
     317                "         DBUserName:  $mysql_conf{'db_user'}",
     318                "         DBPassword:  " .
     319                    ( $mysql_conf{'db_pass'} ? 'XXX' : '' ),
     320                  #  "$mysql_conf{'db_pass'}",
     321                "             DBName:  $mysql_conf{'db_name'}",
     322                "        DBSchemaVer:  $mysql_conf{'db_schemaver'}",
     323                "  DBBackupDirectory:  $backup_conf{'directory'}",
     324                "   DBBackupFilename:  $backup_conf{'filename'}");
     325        verbose("\nExecutables:",
     326                "       mysql_client:  $mysql_client",
     327                "         uncompress:  $uncompress");
     328        verbose("\nMiscellaneous:",
     329                "    partial_restore:  " . ($partial_restore ? 'yes' : 'no'));
     330    }
     331
     332    sub configure_environment
     333    {
     334        verbose("\nConfiguring environment:");
     335
     336    # Get the user's login and home directory, so we can look for config files
     337        ($username, $homedir) = (getpwuid $>)[0,7];
     338        $username = $ENV{'USER'} if ($ENV{'USER'});
     339        $homedir  = $ENV{'HOME'} if ($ENV{'HOME'});
     340        if ($username && !$homedir)
     341        {
     342            $homedir = "/home/$username";
     343            if (!-e $homedir && -e "/Users/$username")
     344            {
     345                $homedir = "/Users/$username";
     346            }
     347        }
     348        verbose("  -    username:  $username",
     349                "  -        HOME:  $homedir");
     350
     351    # Find the config directory
     352        $mythconfdir = $ENV{'MYTHCONFDIR'}
     353            ? $ENV{'MYTHCONFDIR'}
     354            : "$homedir/.mythtv"
     355            ;
     356
     357        verbose("  - MYTHCONFDIR:  $mythconfdir");
     358    }
     359
     360# Though much of the configuration file parsing could be done by the MythTV
     361# Perl bindings, using them to retrieve database information is not appropriate
     362# for a backup script. The Perl bindings require the backend to be running and
     363# use UPnP for autodiscovery. Also, parsing the files "locally" allows
     364# supporting even the old MythTV database configuration file, mysql.txt.
     365    sub parse_database_information
     366    {
     367        my $file = shift;
     368        verbose("  - checking:  $file");
     369        return 0 unless ($file && -e $file);
     370        verbose("     parsing:  $file");
     371        open(CONF, $file) or die "\nERROR:  Unable to read $file:  $!\n";
     372        while (my $line = <CONF>)
     373        {
     374        # Cleanup
     375            next if ($line =~ m/^\s*#/);
     376            $line =~ s/^str //;
     377            chomp($line);
     378            $line =~ s/^\s+//;
     379            $line =~ s/\s+$//;
     380        # Split off the var=val pairs
     381            my ($var, $val) = split(/ *[\=\: ] */, $line, 2);
     382        # Also look for <var>val</var> from config.xml
     383            if ($line =~ m/<(\w+)>(.+)<\/(\w+)>$/ && $1 eq $3)
     384            {
     385                $var = $1; $val = $2;
     386            }
     387            next unless ($var && $var =~ m/\w/);
     388            if ($var eq 'DBHostName')
     389            {
     390                $mysql_conf{'db_host'} = $val;
     391            }
     392            elsif ($var eq 'DBPort')
     393            {
     394                $mysql_conf{'db_port'} = $val;
     395            }
     396            elsif ($var eq 'DBUserName')
     397            {
     398                $mysql_conf{'db_user'} = $val;
     399            }
     400            elsif ($var eq 'DBPassword')
     401            {
     402                $mysql_conf{'db_pass'} = $val;
     403            }
     404            elsif ($var eq 'DBName')
     405            {
     406                $mysql_conf{'db_name'} = $val;
     407            }
     408            elsif ($var eq 'DBSchemaVer')
     409            {
     410                $mysql_conf{'db_schemaver'} = $val;
     411            }
     412            elsif ($var eq 'DBBackupDirectory')
     413            {
     414                $backup_conf{'directory'} = $val;
     415            }
     416            elsif ($var eq 'DBBackupFilename')
     417            {
     418                $backup_conf{'filename'} = $val;
     419            }
     420            elsif ($var eq 'partial_restore')
     421            {
     422                $partial_restore = $val;
     423            }
     424            elsif ($var eq 'mysql_client')
     425            {
     426                $mysql_client = $val;
     427            }
     428            elsif ($var eq 'uncompress')
     429            {
     430                $uncompress = $val;
     431            }
     432            elsif ($var eq 'create_database')
     433            {
     434                $mc_sql = $val;
     435            }
     436        }
     437        close CONF;
     438        return 1;
     439    }
     440
     441    sub read_mysql_txt
     442    {
     443    # Read the "legacy" mysql.txt file in use by MythTV. It could be in a
     444    # couple places, so try the usual suspects in the same order that mythtv
     445    # does in libs/libmyth/mythcontext.cpp
     446        my $found = 0;
     447        my $result = 0;
     448        my @mysql = ('/usr/local/share/mythtv/mysql.txt',
     449                     '/usr/share/mythtv/mysql.txt',
     450                     '/usr/local/etc/mythtv/mysql.txt',
     451                     '/etc/mythtv/mysql.txt',
     452                     $homedir ? "$homedir/.mythtv/mysql.txt"    : '',
     453                     'mysql.txt',
     454                     $mythconfdir ? "$mythconfdir/mysql.txt"    : '',
     455                    );
     456        foreach my $file (@mysql)
     457        {
     458            $found = parse_database_information($file);
     459            $result = $result + $found;
     460        }
     461        return $result;
     462    }
     463
     464    sub read_resource_file
     465    {
     466        parse_database_information("$mythconfdir/backuprc");
     467    }
     468
     469    sub apply_arguments
     470    {
     471        verbose("\nApplying command-line arguments.");
     472        if ($db_hostname)
     473        {
     474            $mysql_conf{'db_host'} = $db_hostname;
     475        }
     476        if ($db_port)
     477        {
     478            $mysql_conf{'db_port'} = $db_port;
     479        }
     480        if ($db_username)
     481        {
     482            $mysql_conf{'db_user'} = $db_username;
     483        }
     484    # This script does not accept a database password on the command-line.
     485#        if ($db_password)
     486#        {
     487#            $mysql_conf{'db_pass'} = $db_password;
     488#        }
     489        if ($db_name)
     490        {
     491            $mysql_conf{'db_name'} = $db_name;
     492        }
     493        if ($db_schema_version)
     494        {
     495            $mysql_conf{'db_schemaver'} = $db_schema_version;
     496        }
     497        if ($backup_directory)
     498        {
     499            $backup_conf{'directory'} = $backup_directory;
     500        }
     501        if ($backup_filename)
     502        {
     503            $backup_conf{'filename'} = $backup_filename;
     504        }
     505    }
     506
     507    sub read_config
     508    {
     509        my $result = 0;
     510    # If specified, use only the database information file
     511        if ($database_information_file)
     512        {
     513            verbose("\nDatabase Information File specified. Ignoring all".
     514                    " command-line arguments");
     515            verbose("\nDatabase Information File:".
     516                    " $database_information_file");
     517            unless (-T "$database_information_file")
     518            {
     519                die "\nERROR:  Invalid database information file";
     520            }
     521        # When using a database information file, parse the resource file first
     522        # so it cannot override database information file settings
     523            read_resource_file;
     524            $result = parse_database_information($database_information_file);
     525            return $result;
     526        }
     527
     528    # No database information file, so try the MythTV configuration files.
     529        verbose("\nParsing configuration files:");
     530    # Prefer the config.xml file
     531        my $file = $mythconfdir ? "$mythconfdir/config.xml" : '';
     532        $result = parse_database_information($file);
     533        if (!$result)
     534        {
     535        # Use the "legacy" mysql.txt file as a fallback
     536            $result = read_mysql_txt;
     537        }
     538    # Read the resource file next to override the config file information, but
     539    # to allow command-line arguments to override resource file "defaults"
     540        read_resource_file;
     541    # Apply the command-line arguments to override the information provided
     542    # by the config file(s).
     543        apply_arguments;
     544        return $result;
     545    }
     546
     547    sub create_defaults_extra_file
     548    {
     549        return '' if (!$mysql_conf{'db_pass'});
     550        verbose("\nAttempting to use supplied password for $mysql_client".
     551                " command-line client.",
     552                "Any [client] or [mysql] password specified in the MySQL".
     553                " options file will",
     554                "take precedence.");
     555    # Let tempfile handle unlinking on exit so we don't have to verify that the
     556    # file with $filename is the file we created
     557        my ($fh, $filename) = tempfile(UNLINK => 1);
     558        print $fh  "[client]\npassword=$mysql_conf{'db_pass'}\n".
     559                   "[mysql]\npassword=$mysql_conf{'db_pass'}\n";
     560        return $filename;
     561    }
     562
     563    sub check_config
     564    {
     565        verbose("\nChecking configuration.");
     566    # Check directory/filename
     567        if (!$backup_conf{'directory'})
     568        {
     569            if (!-r "/$backup_conf{'filename'}")
     570            {
     571                print_configuration;
     572                die("\nERROR:  DBBackupDirectory not specified");
     573            }
     574        # The user must have specified an absolute path for the
     575        # DBBackupFilename. Though this is not how the script is meant to be
     576        # used, allow it.
     577            $backup_conf{'directory'} = '';
     578        }
     579        elsif (!-d $backup_conf{'directory'})
     580        {
     581            print_configuration;
     582            verbose("\nERROR:  DBBackupDirectory is not a directory. Please".
     583                    " specify a directory in",
     584                    "       your database information file using".
     585                    " DBBackupDirectory.",
     586                    "       If not using a database information file," .
     587                    " please specify the ",
     588                    "       --directory command-line option.");
     589            die("Invalid backup directory");
     590        }
     591        if (!$backup_conf{'filename'})
     592        {
     593        # Look for most current backup file
     594            verbose("\nNo filename specified. Attempting to find the newest".
     595                    " database backup.");
     596            $backup_conf{'filename'} = $mysql_conf{'db_name'};
     597            if (!$backup_conf{'filename'})
     598            {
     599                $backup_conf{'filename'} = $d_db_name;
     600            }
     601            my @files = <$backup_conf{'directory'}/$backup_conf{'filename'}*>;
     602            my $num_files = @files;
     603            if ($num_files < 1)
     604            {
     605                verbose("ERROR:  Unable to find any backup files in".
     606                        " DBBackupDir and none specified.");
     607            }
     608            else
     609            {
     610                my @sorted_files = sort { lc($b) cmp lc($0) } @files;
     611                $backup_conf{'filename'} = $sorted_files[0];
     612                $backup_conf{'filename'} =~ s#^$backup_conf{'directory'}/?##;
     613                verbose("Using database backup file:",
     614                        "$backup_conf{'directory'}/$backup_conf{'filename'}");
     615            }
     616        }
     617        if (!-e "$backup_conf{'directory'}/$backup_conf{'filename'}")
     618        {
     619            verbose("\nERROR:  The specified backup file does not exist.");
     620            die("Invalid backup filename");
     621        }
     622        if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
     623        {
     624            verbose("\nERROR:  The specified backup file cannot be read.");
     625            die("Invalid backup filename");
     626        }
     627        if (!$mysql_conf{'db_name'})
     628        {
     629            verbose("\nWARNING:  DBName not specified. Using $d_db_name");
     630            $mysql_conf{'db_name'} = $d_db_name;
     631        }
     632    # Though the script will attempt a restore even if no other database
     633    # information is provided (i.e. using "defaults" from the MySQL options
     634    # file, warning the user that some "normally-necessary" information is not
     635    # provided may be nice.
     636        return if (!$debug);
     637        if (!$mysql_conf{'db_host'})
     638        {
     639            verbose("\nWARNING:  DBHostName not specified.",
     640                    "         Assuming it's specified in the MySQL".
     641                    " options file.");
     642        }
     643        if (!$mysql_conf{'db_user'})
     644        {
     645            verbose("\nWARNING:  DBUserName not specified.",
     646                    "         Assuming it's specified in the MySQL".
     647                    " options file.");
     648        }
     649        if (!$mysql_conf{'db_pass'})
     650        {
     651            verbose("\nWARNING:  DBPassword not specified.",
     652                    "         Assuming it's specified in the MySQL".
     653                    " options file.");
     654        }
     655    }
     656
     657    sub database_exists
     658    {
     659        $result = 1;
     660        $dbh = DBI->connect("dbi:mysql:".
     661                            "database=$mysql_conf{'db_name'}:".
     662                            "host=$mysql_conf{'db_host'}",
     663                            "$mysql_conf{'db_user'}",
     664                            "$mysql_conf{'db_pass'}",
     665                            { PrintError => 0 });
     666        if (!defined($dbh))
     667        {
     668            verbose("Unable to connect to database.");
     669            $result = 0;
     670        }
     671        return $result;
     672    }
     673
     674    sub create_initial_database
     675    {
     676        return 1 if (!$mc_sql);
     677        return 1 if (!-r "$mc_sql");
     678
     679        my $defaults_extra_file = create_defaults_extra_file;
     680        my $host_arg = '';
     681        my $port_arg = '';
     682        my $user_arg = '';
     683        if ($defaults_extra_file)
     684        {
     685            $defaults_arg=" --defaults-extra-file='$defaults_extra_file'";
     686        }
     687        else
     688        {
     689            $defaults_arg='';
     690        }
     691    # Create the args for host, port, and user, shell-escaping values, as
     692    # necessary.
     693        if ($mysql_conf{'db_host'})
     694        {
     695            $mysql_conf{'db_host'} =~ s/'/'\\''/g;
     696            $host_arg=" --host='$mysql_conf{'db_host'}'";
     697        }
     698        if ($mysql_conf{'db_port'} > 0)
     699        {
     700            $mysql_conf{'db_port'} =~ s/'/'\\''/g;
     701            $port_arg=" --port='$mysql_conf{'db_port'}'";
     702        }
     703        if ($mysql_conf{'db_user'})
     704        {
     705            $mysql_conf{'db_user'} =~ s/'/'\\''/g;
     706            $user_arg=" --user='$mysql_conf{'db_user'}'";
     707        }
     708        verbose("\nAttempting to create initial database.");
     709    # Use redirects to capture stdout and stderr (for debug)
     710        my $command = "${mysql_client}${defaults_arg}${host_arg}${port_arg}".
     711                      "${user_arg}  2>&1 < $mc_sql";
     712        verbose("\nExecuting command:", $command);
     713        my $result = `$command`;
     714        my $exit = $? >> 8;
     715        verbose("\n$mysql_client exited with status:  $exit");
     716        verbose("$mysql_client output:", $result) if ($exit);
     717        return $exit;
     718    }
     719
     720    sub is_database_empty
     721    {
     722        $result = 1;
     723        if (defined($dbh))
     724        {
     725            my $num_tables = $dbh->tables;
     726            verbose("\nFound $num_tables tables in the database.");
     727            if ($num_tables > 0)
     728            {
     729                verbose("WARNING:  Database not empty.") if (!$partial_restore);
     730                $result = 0;
     731            }
     732        }
     733        return $result;
     734    }
     735
     736    sub check_database
     737    {
     738    # Try to load the DBI library if available (but don't require it)
     739        BEGIN
     740        {
     741            our $has_dbi = 1;
     742            eval 'use DBI;';
     743            if ($@)
     744            {
     745                $has_dbi = 0;
     746            }
     747        }
     748        verbose("\nDBI is not installed.") if (!$has_dbi);
     749    # Try to load the DBD::mysql library if available (but don't # require it)
     750        BEGIN
     751        {
     752            our $has_dbd = 1;
     753            eval 'use DBD::mysql;';
     754            if ($@)
     755            {
     756                $has_dbd = 0;
     757            }
     758        }
     759        verbose("\nDBD::mysql is not installed.") if (!$has_dbd);
     760        if (!$has_dbi || !$has_dbd)
     761        {
     762            verbose("Blindly assuming your database is prepared for a".
     763                    " restore.",
     764                    "For better checking, please install the Perl".
     765                    " DBI module.");
     766            return 1;
     767        }
     768    # DBI/DBD::mysql are available; check the DB status
     769        verbose("\nChecking database.");
     770        if (!database_exists)
     771        {
     772            if (create_initial_database)
     773            {
     774                verbose("\nERROR:  The database does not exist.");
     775                return 0;
     776            }
     777        }
     778        my $database_empty = is_database_empty;
     779        if ($partial_restore)
     780        {
     781            if ($database_empty)
     782            {
     783                verbose("\nERROR:  Unable to do a partial restore. The".
     784                        " database is empty.",
     785                        "Please run mythtv-setup, first, then re-run this".
     786                        " script.");
     787                return 0;
     788            }
     789        }
     790        else
     791        {
     792            if (!$database_empty)
     793            {
     794                verbose("\nERROR:  Unable to do a full restore. The".
     795                        " database contains data.");
     796                return 0;
     797            }
     798        }
     799        return 1;
     800    }
     801
     802    sub is_gzipped
     803    {
     804    # Simple magic number verification.
     805    # This naive approach works without requiring File::MMagic or any other
     806    # modules.
     807        my $result = 0;
     808        my $magic_number;
     809        my $gzip_magic_number = pack("C*", 0x1f, 0x8b);
     810        open(BACKUPFILE, "$backup_conf{'directory'}/$backup_conf{'filename'}")
     811          or return $result;
     812        binmode(BACKUPFILE);
     813        read(BACKUPFILE, $magic_number, 2);
     814        close(BACKUPFILE);
     815        return ($gzip_magic_number eq $magic_number);
     816    }
     817
     818# Though it's possible to uncompress the file without writing the uncompressed
     819# data to a file, doing so is complicated by supporting the use of
     820# IO::Uncompress::Gunzip /and/ external uncompress programs. Also,
     821# uncompressing the file separately allows for easier and more precise error
     822# reporting.
     823    sub uncompress_backup_file
     824    {
     825        if (($d_uncompress eq $uncompress) || ('gunzip' eq $uncompress))
     826        {
     827            if (!is_gzipped)
     828            {
     829                verbose("\nBackup file is uncompressed.");
     830                return 0;
     831            }
     832            verbose("\nBackup file is compressed.");
     833        # Try to load the IO::Uncompress::Gunzip library if available (but
     834        # don't require it)
     835            BEGIN
     836            {
     837                our $has_uncompress_gunzip = 1;
     838                # Though this does nothing, it prevents an invalid "only used
     839                # once" warning that occurs for users without IO::Uncompress
     840                # installed.
     841                undef $GunzipError;
     842                eval 'use IO::Uncompress::Gunzip qw(gunzip $GunzipError);';
     843                if ($@)
     844                {
     845                    $has_uncompress_gunzip = 0;
     846                }
     847            }
     848            if (!$has_uncompress_gunzip)
     849            {
     850                verbose(" - IO::Uncompress::Gunzip is not installed.");
     851            }
     852            else
     853            {
     854                verbose(" - Uncompressing backup file with".
     855                        " IO::Uncompress::Gunzip.");
     856                my ($bfh, $temp_backup_filename) = tempfile(UNLINK => 1);
     857                my $result = gunzip(
     858                    "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
     859                    $bfh);
     860                if ((defined($result)) &&
     861                    (-f "$temp_backup_filename") &&
     862                    (-r "$temp_backup_filename") &&
     863                    (-s "$temp_backup_filename"))
     864                {
     865                    $backup_conf{'directory'} = '';
     866                    $backup_conf{'filename'} = "$temp_backup_filename";
     867                    return 0;
     868                }
     869                verbose("   Error:  $GunzipError");
     870            }
     871        }
     872        else
     873        {
     874            verbose("\nUnrecognized uncompress program.".
     875                    " Assuming backup file is compressed.",
     876                    " - If the file is not compressed, please do not specify".
     877                    " a custom uncompress",
     878                    "   program name.");
     879        }
     880    # Try to uncompress the file with the uncompress binary.
     881    # With the approach, the original backup file will be uncompressed and
     882    # left uncompressed.
     883        verbose(" - Uncompressing backup file with $uncompress.",
     884                "   The original backup file will be left uncompressed.".
     885                " Please recompress,",
     886                "   if desired.");
     887        my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
     888        my $output = `$uncompress '$backup_path' 2>&1`;
     889        my $exit = $? >> 8;
     890        verbose("\n$uncompress exited with status:  $exit");
     891        if ($exit)
     892        {
     893            verbose("$uncompress output:", $output);
     894        }
     895        else
     896        {
     897            if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
     898            {
     899        # Assume the final extension was removed by uncompressing.
     900                $backup_conf{'filename'} =~ s/\.\w+$//;
     901                if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
     902                {
     903                    verbose("\nERROR:  Unable to find uncompressed backup".
     904                            " file.");
     905                    die("Invalid backup filename");
     906                }
     907            }
     908        }
     909        return $exit;
     910    }
     911
     912    sub restore_backup
     913    {
     914        my $exit = 0;
     915        my $defaults_extra_file = create_defaults_extra_file;
     916        my $host_arg = '';
     917        my $port_arg = '';
     918        my $user_arg = '';
     919        my $filter = '';
     920        if ($defaults_extra_file)
     921        {
     922            $defaults_arg=" --defaults-extra-file='$defaults_extra_file'";
     923        }
     924        else
     925        {
     926            $defaults_arg='';
     927        }
     928    # Create the args for host, port, and user, shell-escaping values, as
     929    # necessary.
     930        if ($mysql_conf{'db_host'})
     931        {
     932            $mysql_conf{'db_host'} =~ s/'/'\\''/g;
     933            $host_arg=" --host='$mysql_conf{'db_host'}'";
     934        }
     935        if ($mysql_conf{'db_port'} > 0)
     936        {
     937            $mysql_conf{'db_port'} =~ s/'/'\\''/g;
     938            $port_arg=" --port='$mysql_conf{'db_port'}'";
     939        }
     940        if ($mysql_conf{'db_user'})
     941        {
     942            $mysql_conf{'db_user'} =~ s/'/'\\''/g;
     943            $user_arg=" --user='$mysql_conf{'db_user'}'";
     944        }
     945    # Configure a filter for a partial/new-host restore
     946        if ($partial_restore)
     947        {
     948            my @partial_restore_tables = ('record',
     949                                          'recorded',
     950                                          'oldrecorded',
     951                                          'recordedprogram',
     952                                          'recordedrating',
     953                                          'recordedmarkup',
     954                                          'recordedseek');
     955            $filter = '^INSERT INTO \`(' .
     956                      join('|', @partial_restore_tables) . ')\` ';
     957            verbose("\nRestoring partial backup with filter:", $filter);
     958        }
     959        my $command = "${mysql_client}${defaults_arg}${host_arg}${port_arg}".
     960                      "${user_arg} $mysql_conf{'db_name'}";
     961        verbose("\nExecuting command:", $command);
     962        my $read_status = open(BACKUP,
     963            "<$backup_conf{'directory'}/$backup_conf{'filename'}");
     964        if (!defined($read_status))
     965        {
     966            verbose("\nERROR: Unable to read backup file.");
     967            return 255;
     968        }
     969        my $write_status = open(COMMAND, "| $command");
     970        if (!defined($write_status))
     971        {
     972            verbose("\nERROR: Unable to execute $mysql_client.");
     973            return 254;
     974        }
     975        my $lines_total = 0;
     976        my $lines_restored = 0;
     977        while (<BACKUP>)
     978        {
     979            $lines_total++;
     980            if ($partial_restore)
     981            {
     982                next if !/$filter/;
     983            }
     984            $lines_restored++;
     985            print COMMAND or die("\nERROR:  Cannot write to $mysql_client");
     986        }
     987        close(COMMAND);
     988        close(BACKUP);
     989        $exit = $?;
     990        verbose("\n$mysql_client exited with status:  $exit",
     991                "\nRestored $lines_restored of $lines_total lines.");
     992        return $exit;
     993    }
     994
     995##############################################################################
     996# Main functionality
     997##############################################################################
     998
     999# The first argument after option parsing, if it exists, should be a database
     1000# information file.
     1001    $database_information_file = shift;
     1002
     1003    configure_environment;
     1004    read_config;
     1005    check_config;
     1006
     1007    print_configuration;
     1008
     1009    my $status = 1;
     1010    if (check_database && !uncompress_backup_file)
     1011    {
     1012        $status = restore_backup;
     1013        print("\nSuccessfully restored backup.\n") if (!$status);
     1014    }
     1015
     1016    exit $status;
     1017
  • programs/scripts/database/database_mythconverg_backup.pl

    Property changes on: programs/scripts/database/database_mythconverg_restore.pl
    ___________________________________________________________________
    Name: svn:executable
       + *
    
     
     1#!/usr/bin/perl -w
     2#
     3# database_mythconverg_backup.pl
     4#
     5# Creates a backup of the MythTV database.
     6#
     7# For details, see:
     8#   database_mythconverg_backup.pl --help
     9
     10# Includes
     11    use Getopt::Long;
     12    use File::Temp qw/ tempfile /;
     13
     14# Some variables we'll use here
     15    our ($username, $homedir, $mythconfdir, $database_information_file);
     16    our ($mysqldump, $compress, $rotate, $rotateglob, $usage, $debug);
     17    our ($d_db_name, $d_mysqldump, $d_compress, $d_rotate, $d_rotateglob);
     18# This script does not accept a database password on the command-line.
     19# Any packager who enables the functionality should modify the --help output.
     20#    our ($db_password);
     21    our ($db_hostname, $db_port, $db_username, $db_name, $db_schema_version);
     22    our ($backup_directory, $backup_filename);
     23
     24    our %mysql_conf  = ('db_host'       => '',
     25                        'db_port'       => -1,
     26                        'db_user'       => '',
     27                        'db_pass'       => '',
     28                        'db_name'       => '',
     29                        'db_schemaver'  => ''
     30                        );
     31    our %backup_conf = ('directory'     => '',
     32                        'filename'      => ''
     33                       );
     34
     35# Defaults
     36    $d_db_name       = 'mythconverg';
     37    $d_mysqldump     = 'mysqldump';
     38    $d_compress      = 'gzip';
     39    $d_rotate        = 5;
     40    $d_rotateglob    = $d_db_name . '-????-??????????????.sql*';
     41
     42# Provide default values for GetOptions
     43    $mysqldump       = $d_mysqldump;
     44    $compress        = $d_compress;
     45    $rotate          = $d_rotate;
     46    $rotateglob      = $d_rotateglob;
     47
     48# Load the cli options
     49    GetOptions('hostname|DBHostName=s'              => \$db_hostname,
     50               'port|DBPort=i'                      => \$db_port,
     51               'username|DBUserName=s'              => \$db_username,
     52# This script does not accept a database password on the command-line.
     53#               'password|DBPassword=s'              => \$db_password,
     54               'name|DBName=s'                      => \$db_name,
     55               'schemaver|DBSchemaVer=s'            => \$db_schema_version,
     56               'directory|DBBackupDirectory=s'      => \$backup_directory,
     57               'filename|DBBackupFilename=s'        => \$backup_filename,
     58               'mysqldump=s'                        => \$mysqldump,
     59               'compress=s'                         => \$compress,
     60               'rotate=i'                           => \$rotate,
     61               'rotateglob=s'                       => \$rotateglob,
     62               'usage|help|h'                       => \$usage,
     63               'verbose|v|debug'                    => \$debug
     64              );
     65
     66# Print usage
     67    if ($usage)
     68    {
     69        print <<EOF;
     70Usage:
     71  $0 [options|database_information_file]
     72
     73Creates a backup of the MythTV database.
     74
     75DETAILED DESCRIPTION:
     76
     77This script can be called by MythTV for creating automatic database backups.
     78In this mode, it is always exected with a single command-line argument
     79specifying the name of a "database information file" (see DATABASE INFORMATION
     80FILE, below), which contains sufficient information about the database and the
     81backup to allow the script to create a backup without needing any additional
     82configuration files. In this mode, all other MythTV configuration files
     83(including config.xml, mysql.txt) are ignored, but the backup resource file
     84(see RESOURCE FILE, below) and the MySQL option files (i.e. /etc/my.cnf or
     85~/.my.cnf) will be honored.
     86
     87The script can also be called interactively (i.e. "manually") by the user to
     88create a database backup on demand. Required information may be passed into
     89the script using command-line arguments or with a database information file.
     90If a database information file is specified, all command-line arguments will be
     91ignored. If no database information file is specified, the script will attempt
     92to determine the appropriate configuration by using the MythTV configuration
     93file(s) (prefering config.xml, but falling back to mysql.txt if no config.xml
     94exists). Once the MythTV configuration file has been parsed, the backup
     95resource file (see RESOURCE FILE, below) will be parsed, then command-line
     96arguments will be applied (thus overriding any values determined from the
     97configuration files).
     98
     99The only information required by the script is the directory in which the
     100backup should be created. Therefore, when using a database information file,
     101the DBBackupDirectory should be specified, or if running manually, the
     102--directory command-line argument should be specified. The DBBackupDirectory
     103may be specified in a backup resource file (see RESOURCE FILE, below). Doing
     104so is especially useful for manual backups. If the specified directory is not
     105writable, the script will terminate. Likewise, if a file whose name matches
     106the name to be used for the backup file already exists, the script will
     107terminate.
     108
     109If the database name is not specified, the script will attempt to use the
     110MythTV default database name, $d_db_name. Note that the same is not true for
     111the database username and database password. These must be explicitly
     112specified. The password must be specified in a database information file, a
     113backup resource file, or a MySQL options file. The username may be specified
     114the same way or may be specified using a command-line argument if not using a
     115database information file.
     116
     117While this script may be called while MythTV is running, there is a possibility
     118of creating a backup with data integrity errors (i.e. if MythTV updates data in
     119multiple tables between the time the script backs up the first and subsequent
     120tables). Also, depending on your system configuration, performing a backup
     121(which may result in locking a table while it's being backed up) while
     122recording may cause corruption of the recording or inability to properly write
     123recording data (such as the recording's seek table) to the database.
     124Therefore, if configuring this script to run in a cron job, try to ensure it
     125runs at a time when recordings are least likely to occur. Alternatively, by
     126choosing to run the script in a system start/shutdown script (i.e. an init
     127script), you may call the script before starting mythbackend or after stopping
     128mythbackend. Note, however, that checking whether to perform the backup is the
     129responsibility of the init script (not this script)--i.e. in a system with
     130multiple frontends/backends, the init script should ensure the backup is
     131created only on the master backend.
     132
     133DATABASE INFORMATION FILE
     134
     135The database information file contains information about the database and the
     136backup. The information within the file is specified as name=value pairs using
     137the same names as used by Myth's config.xml and mysql.txt configuration files.
     138The following variables are recognized:
     139
     140  DBHostName - The hostname (or IP address) which should be used to find the
     141               MySQL server.
     142  DBPort - The TCP/IP port number to use for the connection. This may have a
     143           value of 0, i.e. if the hostname is localhost or if the server is
     144           using the default MySQL port or the port specified in a MySQL
     145           options file.
     146  DBUserName - The database username to use when connecting to the server.
     147  DBPassword - The password to use when connecting to the server.
     148  DBName - The name of the database that contains the MythTV data.
     149  DBSchemaVer - The MythTV schema version of the database. This value will be
     150                used to create the backup filename, but only if the filename
     151                has not been specified using DBBackupFilename or the --filename
     152                argument.
     153  DBBackupDirectory - The directory in which the backup file should be
     154                      created. This directory may have been specially
     155                      configured by the user as the "DB Backups" storage
     156                      group. It is recommended that this directory be
     157                      used--especially in "common-use" scripts such as those
     158                      provided by distributions.
     159  DBBackupFilename - The name of the file in which the backup should be
     160                     created. Additional extensions may be added by this
     161                     script as required (i.e. adding an appropriate suffix,
     162                     such as ".gz", to the file if it's compressed). If the
     163                     filename recommended by mythbackend is used, it will be
     164                     displayed in the GUI messages provided for the user. If
     165                     the recommended filename is not used, the user will not be
     166                     told where to find the backup file. If no value is
     167                     provided, a filename using the default filename format
     168                     will be chosen.
     169  mysqldump        - The path (including filename) of the mysqldump executable.
     170  compress         - The command (including path, if necessary) to use to
     171                     compress the backup. Using gzip is significantly less
     172                     resource intensive on an SQL backup file than using bzip2,
     173                     at the cost of a slightly (about 33%) larger compressed
     174                     filesize, a difference which should be irrelevant at the
     175                     filesizes involved (especially when compared to the size
     176                     of recording files). If you decide to use another
     177                     compression algorithm, please ensure you test it
     178                     appropriately to verify it does not negatively affect
     179                     operation of your system. If no value is specified for
     180                     compress or if the value '$d_compress' is specified, the
     181                     script will first attempt to use the IO::Compress::Gzip
     182                     module to compress the backup file, but, if not available,
     183                     will run the command specified.  Therefore, if
     184                     IO::Compress::Gzip is installed and functional, specifying
     185                     a value for compress is unnecessary. If neither approach
     186                     works, the backup file will be left uncompressed.
     187  rotate           - The number of backups to keep when rotating. To disable
     188                     rotation, specify -1. Backup rotation is performed by
     189                     identifying all files in DBBackupDirectory whose names
     190                     match the glob specified by rotateglob. It is critical
     191                     that the chosen backup filenames can be sorted properly
     192                     using an alphabetical sort. If using the default filename
     193                     format--which contains the DBSchemaVer--and you downgrade
     194                     MythTV and restore a backup from an older DBSchemaVer,
     195                     make sure you move the backups from the newer DBSchemaVer
     196                     out of the DBBackupDirectory or they may cause your new
     197                     backups to be deleted.
     198  rotateglob       - The sh-like glob used to identify files within
     199                     DBBackupDirectory to be considered for rotation. Be
     200                     very careful with the value--especially if using a
     201                     DBBackupDirectory that contains any files other than
     202                     backups.
     203
     204RESOURCE FILE
     205
     206The backup resource file specifies values using the same format as described
     207for the database information file, above, but is intended as a "permanent,"
     208user-created configuration file. The database information file is intended as
     209a "single-use" configuration file, often created automatically (i.e. by a
     210program, such as mythbackend, or a script). The backup resource file should be
     211placed at "~/.mythtv/backuprc" and given appropriate permissions. To be usable
     212by the script, it must be readable. However, it should be protected as
     213required--i.e. if the DBPassword is specified, it should be made readable only
     214by the owner.
     215
     216When specifying a database information file, the resource file is parsed before
     217the database information file to prevent the resource file from overriding the
     218information in the database information file. When no database information
     219file is specified, the resource file is parsed after the MythTV configuration
     220files, but before the command-line arguments to allow the resource file to
     221override values in the configuration files and to allow command-line arguments
     222to override resource file defaults.
     223
     224options:
     225
     226--hostname [database hostname]
     227
     228    The hostname (or IP address) which should be used to find the MySQL server.
     229    See DBHostName, above.
     230
     231--port [database port]
     232
     233    The TCP/IP port number to use for connection to the MySQL server. See
     234    DBPort, above.
     235
     236--username [database username]
     237
     238    The MySQL username to use for connection to the MySQL server. See
     239    DBUserName, above.
     240
     241--name [database name]
     242
     243    The name of the database containing the MythTV data. See DBName, above.
     244
     245    Default:  $d_db_name
     246
     247--schemaver [MythTV database schema version]
     248
     249    The MythTV schema version. See DBSchemaVer, above.
     250
     251--directory [directory]
     252
     253    The directory in which the backup file should be stored. See
     254    DBBackupDirectory, above.
     255
     256--filename [database backup filename]
     257
     258    The name to use for the database backup file. If not provided, a filename
     259    using a default format will be chosen. See DBBackupFilename, above.
     260
     261--mysqldump [path]
     262
     263    The path (including filename) of the mysqldump executable. See mysqldump
     264    in the DATABASE INFORMATION FILE description, above.
     265
     266    Default:  $d_mysqldump
     267
     268--compress [path]
     269
     270    The command (including path, if necessary) to use to compress the backup.
     271    See compress in the DATABASE INFORMATION FILE description, above.
     272
     273    Default:  $d_compress
     274
     275--rotate [number]
     276    The number of backups to keep when rotating. To disable rotation, specify
     277    -1. See rotate in the DATABASE INFORMATION FILE description, above.
     278
     279    Default:  $d_rotate
     280
     281--rotateglob [glob]
     282    The sh-like glob used to identify files within DBBackupDirectory to be
     283    considered for rotation. See rotateglob in the DATABASE INFORMATION FILE
     284    description, above.
     285
     286    Default:  $d_rotateglob
     287
     288--help
     289
     290    Show this help text.
     291
     292--verbose
     293
     294    Show what's happening.
     295
     296QUICK START:
     297
     298Create a file ~/.mythtv/backuprc with a single line,
     299"DBBackupDirectory=/home/mythtv" (no quotes), and run this script to create a
     300database backup. Use the --verbose argument to see what's happening.
     301
     302# echo "DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
     303# $0 --verbose
     304
     305Make sure you keep the backuprc file for next time.  Once you've successfully
     306created a backup, the script may be run without the --verbose argument.
     307
     308EOF
     309        exit;
     310    }
     311
     312    sub verbose
     313    {
     314        return unless (defined($debug));
     315        print join("\n", @_), "\n";
     316    }
     317
     318    sub print_configuration
     319    {
     320        verbose("\nDatabase Information:",
     321                "         DBHostName:  $mysql_conf{'db_host'}",
     322                "             DBPort:  $mysql_conf{'db_port'}",
     323                "         DBUserName:  $mysql_conf{'db_user'}",
     324                "         DBPassword:  " .
     325                    ( $mysql_conf{'db_pass'} ? 'XXX' : '' ),
     326                  #  "$mysql_conf{'db_pass'}",
     327                "             DBName:  $mysql_conf{'db_name'}",
     328                "        DBSchemaVer:  $mysql_conf{'db_schemaver'}",
     329                "  DBBackupDirectory:  $backup_conf{'directory'}",
     330                "   DBBackupFilename:  $backup_conf{'filename'}");
     331        verbose("\nExecutables:",
     332                "          mysqldump:  $mysqldump",
     333                "           compress:  $compress");
     334    }
     335
     336    sub configure_environment
     337    {
     338        verbose("\nConfiguring environment:");
     339
     340    # Get the user's login and home directory, so we can look for config files
     341        ($username, $homedir) = (getpwuid $>)[0,7];
     342        $username = $ENV{'USER'} if ($ENV{'USER'});
     343        $homedir  = $ENV{'HOME'} if ($ENV{'HOME'});
     344        if ($username && !$homedir)
     345        {
     346            $homedir = "/home/$username";
     347            if (!-e $homedir && -e "/Users/$username")
     348            {
     349                $homedir = "/Users/$username";
     350            }
     351        }
     352        verbose("  -    username:  $username",
     353                "  -        HOME:  $homedir");
     354
     355    # Find the config directory
     356        $mythconfdir = $ENV{'MYTHCONFDIR'}
     357            ? $ENV{'MYTHCONFDIR'}
     358            : "$homedir/.mythtv"
     359            ;
     360
     361        verbose("  - MYTHCONFDIR:  $mythconfdir");
     362    }
     363
     364# Though much of the configuration file parsing could be done by the MythTV
     365# Perl bindings, using them to retrieve database information is not appropriate
     366# for a backup script. The Perl bindings require the backend to be running and
     367# use UPnP for autodiscovery. Also, parsing the files "locally" allows
     368# supporting even the old MythTV database configuration file, mysql.txt.
     369    sub parse_database_information
     370    {
     371        my $file = shift;
     372        verbose("  - checking:  $file");
     373        return 0 unless ($file && -e $file);
     374        verbose("     parsing:  $file");
     375        open(CONF, $file) or die "ERROR:  Unable to read $file:  $!\n";
     376        while (my $line = <CONF>)
     377        {
     378        # Cleanup
     379            next if ($line =~ m/^\s*#/);
     380            $line =~ s/^str //;
     381            chomp($line);
     382            $line =~ s/^\s+//;
     383            $line =~ s/\s+$//;
     384        # Split off the var=val pairs
     385            my ($var, $val) = split(/ *[\=\: ] */, $line, 2);
     386        # Also look for <var>val</var> from config.xml
     387            if ($line =~ m/<(\w+)>(.+)<\/(\w+)>$/ && $1 eq $3)
     388            {
     389                $var = $1; $val = $2;
     390            }
     391            next unless ($var && $var =~ m/\w/);
     392            if ($var eq 'DBHostName')
     393            {
     394                $mysql_conf{'db_host'} = $val;
     395            }
     396            elsif ($var eq 'DBPort')
     397            {
     398                $mysql_conf{'db_port'} = $val;
     399            }
     400            elsif ($var eq 'DBUserName')
     401            {
     402                $mysql_conf{'db_user'} = $val;
     403            }
     404            elsif ($var eq 'DBPassword')
     405            {
     406                $mysql_conf{'db_pass'} = $val;
     407            }
     408            elsif ($var eq 'DBName')
     409            {
     410                $mysql_conf{'db_name'} = $val;
     411            }
     412            elsif ($var eq 'DBSchemaVer')
     413            {
     414                $mysql_conf{'db_schemaver'} = $val;
     415            }
     416            elsif ($var eq 'DBBackupDirectory')
     417            {
     418                $backup_conf{'directory'} = $val;
     419            }
     420            elsif ($var eq 'DBBackupFilename')
     421            {
     422                $backup_conf{'filename'} = $val;
     423            }
     424            elsif ($var eq 'mysqldump')
     425            {
     426                $mysqldump = $val;
     427            }
     428            elsif ($var eq 'compress')
     429            {
     430                $compress = $val;
     431            }
     432            elsif ($var eq 'rotate')
     433            {
     434                $rotate = $val;
     435            }
     436            elsif ($var eq 'rotateglob')
     437            {
     438                $rotateglob = $val;
     439            }
     440        }
     441        close CONF;
     442        return 1;
     443    }
     444
     445    sub read_mysql_txt
     446    {
     447    # Read the "legacy" mysql.txt file in use by MythTV. It could be in a
     448    # couple places, so try the usual suspects in the same order that mythtv
     449    # does in libs/libmyth/mythcontext.cpp
     450        my $found = 0;
     451        my $result = 0;
     452        my @mysql = ('/usr/local/share/mythtv/mysql.txt',
     453                     '/usr/share/mythtv/mysql.txt',
     454                     '/usr/local/etc/mythtv/mysql.txt',
     455                     '/etc/mythtv/mysql.txt',
     456                     $homedir ? "$homedir/.mythtv/mysql.txt"    : '',
     457                     'mysql.txt',
     458                     $mythconfdir ? "$mythconfdir/mysql.txt"    : '',
     459                    );
     460        foreach my $file (@mysql)
     461        {
     462            $found = parse_database_information($file);
     463            $result = $result + $found;
     464        }
     465        return $result;
     466    }
     467
     468    sub read_resource_file
     469    {
     470        parse_database_information("$mythconfdir/backuprc");
     471    }
     472
     473    sub apply_arguments
     474    {
     475        verbose("\nApplying command-line arguments.");
     476        if ($db_hostname)
     477        {
     478            $mysql_conf{'db_host'} = $db_hostname;
     479        }
     480        if ($db_port)
     481        {
     482            $mysql_conf{'db_port'} = $db_port;
     483        }
     484        if ($db_username)
     485        {
     486            $mysql_conf{'db_user'} = $db_username;
     487        }
     488    # This script does not accept a database password on the command-line.
     489#        if ($db_password)
     490#        {
     491#            $mysql_conf{'db_pass'} = $db_password;
     492#        }
     493        if ($db_name)
     494        {
     495            $mysql_conf{'db_name'} = $db_name;
     496        }
     497        if ($db_schema_version)
     498        {
     499            $mysql_conf{'db_schemaver'} = $db_schema_version;
     500        }
     501        if ($backup_directory)
     502        {
     503            $backup_conf{'directory'} = $backup_directory;
     504        }
     505        if ($backup_filename)
     506        {
     507            $backup_conf{'filename'} = $backup_filename;
     508        }
     509    }
     510
     511    sub read_config
     512    {
     513        my $result = 0;
     514    # If specified, use only the database information file
     515        if ($database_information_file)
     516        {
     517            verbose("\nDatabase Information File specified. Ignoring all".
     518                    " command-line arguments");
     519            verbose("\nDatabase Information File:".
     520                    " $database_information_file");
     521            unless (-T "$database_information_file")
     522            {
     523                die "\nERROR:  Invalid database information file";
     524            }
     525        # When using a database information file, parse the resource file first
     526        # so it cannot override database information file settings
     527            read_resource_file;
     528            $result = parse_database_information($database_information_file);
     529            return $result;
     530        }
     531
     532    # No database information file, so try the MythTV configuration files.
     533        verbose("\nParsing configuration files:");
     534    # Prefer the config.xml file
     535        my $file = $mythconfdir ? "$mythconfdir/config.xml" : '';
     536        $result = parse_database_information($file);
     537        if (!$result)
     538        {
     539        # Use the "legacy" mysql.txt file as a fallback
     540            $result = read_mysql_txt;
     541        }
     542    # Read the resource file next to override the config file information, but
     543    # to allow command-line arguments to override resource file "defaults"
     544        read_resource_file;
     545    # Apply the command-line arguments to override the information provided
     546    # by the config file(s).
     547        apply_arguments;
     548        return $result;
     549    }
     550
     551    sub check_config
     552    {
     553        verbose("\nChecking configuration.");
     554    # Check directory/filename
     555        if (!$backup_conf{'directory'})
     556        {
     557            print_configuration;
     558            die("\nERROR:  DBBackupDirectory not specified");
     559        }
     560        if ((!-d $backup_conf{'directory'}) ||
     561            (!-w $backup_conf{'directory'}))
     562        {
     563            print_configuration;
     564            verbose("\nERROR:  DBBackupDirectory is not a directory or is not" .
     565                    " writable. Please specify",
     566                    "       a directory in your database information file" .
     567                    " using DBBackupDirectory.",
     568                    "       If not using a database information file," .
     569                    " please specify the ",
     570                    "       --directory command-line option.");
     571            die("Invalid backup directory");
     572        }
     573        if (!$backup_conf{'filename'})
     574        {
     575        # Create a default backup filename
     576            $backup_conf{'filename'} = $mysql_conf{'db_name'};
     577            if (!$backup_conf{'filename'})
     578            {
     579                $backup_conf{'filename'} = $d_db_name;
     580            }
     581            if ((!$mysql_conf{'db_schemaver'}) &&
     582                ($mysql_conf{'db_host'}) && ($mysql_conf{'db_name'}) &&
     583                ($mysql_conf{'db_user'}) && ($mysql_conf{'db_pass'}))
     584            {
     585            # Try to load the DBI library if available (but don't require it)
     586                BEGIN
     587                {
     588                    our $has_dbi = 1;
     589                    eval 'use DBI;';
     590                    if ($@)
     591                    {
     592                        $has_dbi = 0;
     593                    }
     594                }
     595                verbose("\nDBI is not installed.") if (!$has_dbi);
     596            # Try to load the DBD::mysql library if available (but don't
     597            # require it)
     598                BEGIN
     599                {
     600                    our $has_dbd = 1;
     601                    eval 'use DBD::mysql;';
     602                    if ($@)
     603                    {
     604                        $has_dbd = 0;
     605                    }
     606                }
     607                verbose("\nDBD::mysql is not installed.") if (!$has_dbd);
     608            # If DBI is available, query the DB for the schema version
     609                if ($has_dbi && $has_dbd)
     610                {
     611                    verbose("\nNo DBSchemaVer specified, querying database.");
     612                    my $q = 'SELECT data FROM settings WHERE value = ?';
     613                    my $dbh = DBI->connect("dbi:mysql:".
     614                                           "database=$mysql_conf{'db_name'}:".
     615                                           "host=$mysql_conf{'db_host'}",
     616                                           "$mysql_conf{'db_user'}",
     617                                           "$mysql_conf{'db_pass'}",
     618                                           { PrintError => 0 });
     619                    if (defined($dbh))
     620                    {
     621                        my $sh = $dbh->prepare($q);
     622                        if ($sh->execute('DBSchemaVer'))
     623                        {
     624                            while (my @data = $sh->fetchrow_array)
     625                            {
     626                                $mysql_conf{'db_schemaver'} = $data[0];
     627                                verbose("Found DBSchemaVer:".
     628                                        " $mysql_conf{'db_schemaver'}.");
     629                            }
     630                        }
     631                        else
     632                        {
     633                            verbose("Unable to retrieve DBSchemaVer from".
     634                                    " database.  Filename will not contain ",
     635                                    "DBSchemaVer.");
     636                        }
     637                        $dbh->disconnect;
     638                    }
     639                }
     640                else
     641                {
     642                    verbose("\nNo DBSchemaVer specified.",
     643                            "DBI and/or DBD:mysql is not available. Unable".
     644                            " to query database to determine ",
     645                            "DBSchemaVer.  DBSchemaVer will not be included".
     646                            " in backup filename.",
     647                            "Please ensure DBI and DBD::mysql are".
     648                            " installed.");
     649                }
     650            }
     651            if ($mysql_conf{'db_schemaver'})
     652            {
     653                $backup_conf{'filename'} .= '-'.$mysql_conf{'db_schemaver'};
     654            }
     655        # Format the time using localtime data so we don't have to bring in
     656        # another dependency.
     657            my @timeData = localtime(time);
     658            $backup_conf{'filename'} .= sprintf('-%04d%02d%02d%02d%02d%02d.sql',
     659                                                ($timeData[5] + 1900),
     660                                                ($timeData[4] + 1),
     661                                                $timeData[3], $timeData[2],
     662                                                $timeData[1], $timeData[0]);
     663        }
     664        if ( -e "$backup_conf{'directory'}/$backup_conf{'filename'}")
     665        {
     666            verbose("\nERROR:  The specified file already exists.");
     667            die("Invalid backup filename");
     668        }
     669        if (!$mysql_conf{'db_name'})
     670        {
     671            verbose("\nWARNING:  DBName not specified. Using $d_db_name");
     672            $mysql_conf{'db_name'} = $d_db_name;
     673        }
     674    # Though the script will attempt a backup even if no other database
     675    # information is provided (i.e. using "defaults" from the MySQL options
     676    # file, warning the user that some "normally-necessary" information is not
     677    # provided may be nice.
     678        return if (!$debug);
     679        if (!$mysql_conf{'db_host'})
     680        {
     681            verbose("\nWARNING:  DBHostName not specified.",
     682                    "         Assuming it's specified in the MySQL".
     683                    " options file.");
     684        }
     685        if (!$mysql_conf{'db_user'})
     686        {
     687            verbose("\nWARNING:  DBUserName not specified.",
     688                    "         Assuming it's specified in the MySQL".
     689                    " options file.");
     690        }
     691        if (!$mysql_conf{'db_pass'})
     692        {
     693            verbose("\nWARNING:  DBPassword not specified.",
     694                    "         Assuming it's specified in the MySQL".
     695                    " options file.");
     696        }
     697    }
     698
     699    sub create_defaults_extra_file
     700    {
     701        return '' if (!$mysql_conf{'db_pass'});
     702        verbose("\nAttempting to use supplied password for $mysqldump.",
     703                "Any [client] or [mysqldump] password specified in the MySQL".
     704                " options file will",
     705                "take precedence.");
     706    # Let tempfile handle unlinking on exit so we don't have to verify that the
     707    # file with $filename is the file we created
     708        my ($fh, $filename) = tempfile(UNLINK => 1);
     709        print $fh  "[client]\npassword=$mysql_conf{'db_pass'}\n".
     710                   "[mysqldump]\npassword=$mysql_conf{'db_pass'}\n";
     711        return $filename;
     712    }
     713
     714    sub do_backup
     715    {
     716        my $defaults_extra_file = create_defaults_extra_file;
     717        my $host_arg = '';
     718        my $port_arg = '';
     719        my $user_arg = '';
     720        if ($defaults_extra_file)
     721        {
     722            $defaults_arg=" --defaults-extra-file='$defaults_extra_file'";
     723        }
     724        else
     725        {
     726            $defaults_arg='';
     727        }
     728    # Create the args for host, port, and user, shell-escaping values, as
     729    # necessary.
     730        if ($mysql_conf{'db_host'})
     731        {
     732            $mysql_conf{'db_host'} =~ s/'/'\\''/g;
     733            $host_arg=" --host='$mysql_conf{'db_host'}'";
     734        }
     735        if ($mysql_conf{'db_port'} > 0)
     736        {
     737            $mysql_conf{'db_port'} =~ s/'/'\\''/g;
     738            $port_arg=" --port='$mysql_conf{'db_port'}'";
     739        }
     740        if ($mysql_conf{'db_user'})
     741        {
     742            $mysql_conf{'db_user'} =~ s/'/'\\''/g;
     743            $user_arg=" --user='$mysql_conf{'db_user'}'";
     744        }
     745    # Use redirects to capture stderr (for debug) and send stdout (the backup)
     746    # to a file
     747        my $command = "${mysqldump}${defaults_arg}${host_arg}${port_arg}".
     748                      "${user_arg} --add-drop-table --add-locks ".
     749                      "--allow-keywords --complete-insert --extended-insert ".
     750                      "--lock-tables --no-create-db --quick ".
     751                      "$mysql_conf{'db_name'} 2>&1 ".
     752                      "1>$backup_conf{'directory'}/$backup_conf{'filename'}";
     753        verbose("\nExecuting command:", $command);
     754        my $result = `$command`;
     755        my $exit = $? >> 8;
     756        verbose("\n$mysqldump exited with status:  $exit");
     757        verbose("$mysqldump output:", $result) if ($exit);
     758        return $exit;
     759    }
     760
     761    sub compress_backup
     762    {
     763        if (!-e "$backup_conf{'directory'}/$backup_conf{'filename'}")
     764        {
     765            verbose("\nUnable to find backup file to compress");
     766            return 1;
     767        }
     768        my $result = 0;
     769        verbose("\nAttempting to compress backup file.");
     770        if ($d_compress eq $compress)
     771        {
     772        # Try to load the IO::Compress::Gzip library if available (but don't
     773        # require it)
     774            BEGIN
     775            {
     776                our $has_compress_gzip = 1;
     777                # Though this does nothing, it prevents an invalid "only used
     778                # once" warning that occurs for users without IO::Compress
     779                # installed.
     780                undef $GzipError;
     781                eval 'use IO::Compress::Gzip qw(gzip $GzipError);';
     782                if ($@)
     783                {
     784                    $has_compress_gzip = 0;
     785                }
     786            }
     787            if (!$has_compress_gzip)
     788            {
     789                verbose(" - IO::Compress::Gzip is not installed.");
     790            }
     791            else
     792            {
     793                if (-e "$backup_conf{'directory'}/$backup_conf{'filename'}.gz")
     794                {
     795                    verbose("\nA file whose name is the backup filename with".
     796                            " the '.gz' extension already",
     797                            "exists. Leaving backup uncompressed.");
     798                    return 1;
     799                }
     800                verbose(" - Compressing backup file with IO::Compress::Gzip.");
     801                $result = gzip(
     802                   "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
     803                   "$backup_conf{'directory'}/$backup_conf{'filename'}.gz");
     804                if ((defined($result)) &&
     805                    (-e "$backup_conf{'directory'}/".
     806                        "$backup_conf{'filename'}.gz"))
     807                {
     808                    unlink "$backup_conf{'directory'}/".
     809                           "$backup_conf{'filename'}";
     810                    $backup_conf{'filename'} = "$backup_conf{'filename'}.gz";
     811                    verbose("\nSuccessfully compressed backup to file:",
     812                            "$backup_conf{'directory'}/".
     813                            "$backup_conf{'filename'}");
     814                    return 0;
     815                }
     816                verbose("   Error:  $GzipError");
     817            }
     818        }
     819    # Try to compress the file with the compress binary.
     820        verbose(" - Compressing backup file with $compress.");
     821        my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
     822        my $output = `$compress '$backup_path' 2>&1`;
     823        my $exit = $? >> 8;
     824        verbose("\n$compress exited with status:  $exit");
     825        if ($exit)
     826        {
     827            verbose("$compress output:", $output);
     828        }
     829        else
     830        {
     831            $backup_conf{'filename'} = "$backup_conf{'filename'}.gz";
     832        }
     833        return $exit;
     834    }
     835
     836    sub rotate_backups
     837    {
     838        if (($rotate < 1) || (!defined($rotateglob)) || (!$rotateglob))
     839        {
     840            verbose("\nBackup file rotation disabled.");
     841            return 0;
     842        }
     843        verbose("\nRotating backups.");
     844        verbose("\nSearching for files matching pattern:",
     845                "$backup_conf{'directory'}/$rotateglob");
     846        my @files = <$backup_conf{'directory'}/$rotateglob>;
     847        my @sorted_files = sort { lc($a) cmp lc($b) } @files;
     848        my $num_files = @sorted_files;
     849        verbose(" - Found $num_files matching files.");
     850        $num_files = $num_files - $rotate;
     851        $num_files = 0 if ($num_files < 0);
     852        verbose("\nDeleting $num_files and keeping (up to) $rotate backup".
     853                " files.");
     854        my $index = 0;
     855        foreach my $file (@sorted_files)
     856        {
     857            if ($index++ < $num_files)
     858            {
     859                if ($file eq
     860                    "$backup_conf{'directory'}/$backup_conf{'filename'}")
     861                {
     862                # This is the just-created backup. Warn the user that older
     863                # backups with newer schema versions may cause rotation to
     864                # fail.
     865                    verbose("\nWARNING:  You seem to have reverted to an older".
     866                            " database schema version. You",
     867                            "should move all backups from newer schema".
     868                            " versions to another directory or",
     869                            "delete them to prevent your new backups from".
     870                            " being deleted on rotation.\n");
     871                    verbose(" - Keeping backup file:  $file");
     872
     873                }
     874                else
     875                {
     876                    verbose(" - Deleting old backup file:  $file");
     877                    unlink "$file";
     878                }
     879            }
     880            else
     881            {
     882                verbose(" - Keeping backup file:  $file");
     883            }
     884        }
     885        return 1;
     886    }
     887
     888##############################################################################
     889# Main functionality
     890##############################################################################
     891
     892# The first argument after option parsing, if it exists, should be a database
     893# information file.
     894    $database_information_file = shift;
     895
     896    configure_environment;
     897    read_config;
     898    check_config;
     899
     900    print_configuration;
     901
     902    my $status = do_backup;
     903    if (!$status)
     904    {
     905        compress_backup;
     906        rotate_backups;
     907    }
     908
     909    exit $status;
     910
  • programs/scripts/scripts.pro

    Property changes on: programs/scripts/database/database_mythconverg_backup.pl
    ___________________________________________________________________
    Name: svn:executable
       + *
    
     
     1include ( ../../config.mak )
     2include ( ../../settings.pro )
     3
     4QMAKE_STRIP = echo
     5
     6TEMPLATE = app
     7CONFIG -= moc qt
     8
     9installscripts.path = $${PREFIX}/share/mythtv
     10installscripts.files = database/*
     11
     12INSTALLS += installscripts
     13
     14SOURCES += dummy.c
  • programs/scripts/dummy.c

     
     1int main(void)
     2{
     3    return 0;
     4}