![]() |
SELFHTML Server Konfiguration:
|
|
| |
Das Backup-Konzept ist das wichtigste am ganzen Server, meiner Meinung nach. Denn im GAU-Fall dürfen nur so wenig Daten wie möglich verloren gehen. Deshalb ist es auch nicht sehr sinnvoll, auf dem eigenen Server (oder zumindest nur auf dem eigenen Server) zu sichern. Richtige Backups werden auf wenigstens zwei Rechner verteilt, oder sogar auf Band gesichert.
Ich habe ein Script geschrieben, das <n> Generationen an Backups verwalten kann. Derzeit sind sieben eingestellt. Diese sieben Generationen werden lokal auf dem Server gespeichert, allerdings wird jede Nacht ein Backup auf einen anderen Server geschoben, wo allerdings nur fünf Generationen gesichert werden. Außerdem werden die Logfiles noch einmal extra gesichert — wir sind verpflichtet, die Logfiles wenigstens ein halbes Jahr aufzubewahren, um im Zeifelsfall der Polizei Daten herausgeben zu können. Deshalb liegt hier nocheinmal ein besonderes Augenmerk. Das mag sich vielleicht paranoid anhören, ist jedoch dringend notwendig.
Das Backup-Script hat genau drei Aufgaben: Es muss den Verzeichnisbaum einlesen und die entsprechenden Dateien in ein Tar- Archiv packen, es muss die lokalen Backups verwalten und es muss die externen Backups verwalten. Dementsprechend komplex ist das Script:
#!/usr/bin/perl -w
use strict;
use vars qw($LIBPATH $CONFIGPATH $log $VERSION);
BEGIN {
$VERSION = 1.50;
$LIBPATH = "/home/cron/backup/";
$CONFIGPATH = "/home/cron/backup/config/";
}
use lib $LIBPATH;
use XML::Simple;
use Logging;
use Compress::Zlib;
use Archive::Tar;
sub read_conf($);
sub read_tree($);
sub generation_management($);
sub create_date();
my $conf = read_conf($CONFIGPATH."config.xml");
my $tar = new Archive::Tar;
my @afiles = ();
# natuerlich werden alle Aktivitaeten geloggt!
$log = new Logging($conf->{logfile}->{path}.$conf->{logfile}->{file});
$log->log("Startup");
$log->log("Starting generation management...");
generation_management($conf); # verwalte die lokalen Generationen
$log->log("Generation-Management endet...");
# Alle konfigurierten Dateien einlesen und in Archive speichern
foreach my $entry (@{$conf->{save}->{entry}})
{
my $tar = new Archive::Tar;
if(-d $entry->{content})
{
$tar->add_files(read_tree($entry->{content})) or die "Could not read: $!";
$log->log("Archived directory ".$entry->{content});
}
else
{
$tar->add_files($entry->{content}) or die "Could not read: $!";
$log->log("Archived file ".$entry->{content});
}
my $fname = $conf->{savefile}->{path}.$entry->{file};
$fname =~ s/\{date\}/create_date()/e;
$tar->write($fname,$conf->{savefile}->{compression});
push @afiles,$fname);
}
$log->log("Archive written to harddisk, compression level: ".$conf->{savefile}->{compression});
# jetzt das remote-Backup
my $tar = new Archive::Tar;
$tar->add_files(@afiles) or do {
$log->log("Archive of archive could not be written!");
die "Archive of archive could not be written!";
};
my $fname = $conf->{savefile}->{archive};
$fname =~ s/\Q{nr}\E/(localtime)[3] % $conf->{Generations}->{save_extern}/e;
$tar->write($fname,$conf->{savefile}->{compression}) or do {
$log->log("Archive of archive could not be written!");
die "Archive of archive could not be written!";
};
my $fname_remote = $fname;
$fname_remote =~ s!.*/!!;
$log->log(`$conf->{extern}->{scp} $fname $conf->{extern}->{path}/$fname_remote`);
$log->log("Transfered!");
unlink $fname;
$log->log("End");
#####################################################
#
# rekursive Funktion, um den Datei-Baum einzulesen
#
sub read_tree($) {
my $aktdir = shift;
local *DIR;
my @flist = ();
opendir(DIR,$aktdir) or do {
$log->log("Error reading directory '$aktdir': ".$!);
die("Error reading directory '$aktdir': ".$!);
};
while(my $entry = readdir(DIR)) {
next if $entry eq "." || $entry eq "..";
if(-d $aktdir."/".$entry) {
push @flist, read_tree($aktdir."/".$entry);
next;
}
push @flist,$aktdir."/".$entry;
}
closedir(DIR) and return @flist;
$log->log("Error reading the tree: ".$!);
die("Error reading the tree: ".$!);
}
#####################################################
#
# Konfiguration einlesen
#
sub read_conf($) {
my $file = shift;
my $conf = XMLin($file);
$conf->{Generations}->{pattern} =~ s/\{date\}/[0-9]{4}\-[0-9]{2}-[0-9]{2}/;
$conf->{logfile}->{path} .= '/' unless $conf->{logfile}->{path} =~ m!/$!;
$conf->{savefile}->{path} .= '/' unless $conf->{savefile}->{path} =~ m!/$!;
$conf->{save}->{entry} = [$conf->{save}->{entry}] unless ref $conf->{save}->{entry} eq "ARRAY";
return $conf;
}
#####################################################
#
# Einheitliches Datums-Format erzeugen (muesste ISO-Norm sein)
#
sub create_date() {
my ($day, $month, $year) = (localtime(time))[3,4,5];
$month++;
$year += 1900;
return sprintf("%4d-%02d-%02d",$year,$month,$day);
}
#####################################################
#
# Generations-Management fuer die lokal gesicherten Generationen
#
sub generation_management($) {
my $conf = shift;
opendir DIR,$conf->{savefile}->{path} or die $!;
foreach my $file (readdir DIR) {
next if -d $conf->{savefile}->{path}.$file;
unless($file =~ /$conf->{Generations}->{pattern}/) {
$log->log("file ".$conf->{savefile}->{path}.$file." skipped");
next;
}
foreach my $entry (@{$conf->{save}->{entry}}) {
my $fn1 = $entry->{file};
my $fn2 = $file;
$fn1 =~ s/-\{date\}//;
$fn2 =~ s/-[0-9]{4}-[0-9]{2}-[0-9]{2}//;
next if $fn1 ne $fn2;
my $delete = 1;
for(my $i=0;$i<$conf->{Generations}->{save_local};$i++) {
my $fname = $entry->{file};
my $time = time; $time -= $i * 24 * 60 * 60;
my ($today, $now_month, $now_year) = (localtime($time))[3,4,5];
$now_month++; $now_year += 1900;
$fname =~ s/\{date\}/sprintf("%4d-%02d-%02d",$now_year,$now_month,$today)/e;
$delete = 0 if $fname eq $file;
}
if($delete) {
unlink $conf->{savefile}->{path}.$file;
$log->log("Generation management: Deleting file $file");
}
}
}
}
# eof
Die Konfigurations-Datei ist (mal wieder) eine XML-Datei:
config.xml:
<?xml version="1.0"?>
<!DOCTYPE config SYSTEM "backup.dtd">
<config>
<savefile path="/usr/backup/" compression="9" archive="/usr/backup/archive-{nr}.tgz"/>
<logfile path="/var/log" file="backup.log"/>
<extern scp="/usr/local/bin/scp" server="backup.primekom.net" path="~"/>
<Generations save_local="7" save_extern="5" pattern="[\w\.-]+-{date}"/>
<save>
<entry file="alpentouren-{date}.tgz">/home/www/teamone.de/alpentouren</entry>
<entry file="rhps-{date}.tgz">/home/www/teamone.de/rockyhorrorpictureshow</entry>
<entry file="selfsuche-{date}.tgz">/home/www/teamone.de/selfsuche</entry>
<entry file="selfaktuell-{date}.tgz">/home/www/teamone.de/selfaktuell</entry>
<entry file="selfdeveloper-{date}.tgz">/home/www/teamone.de/selfdeveloper</entry>
<entry file="selffan-{date}.tgz">/home/www/teamone.de/selffan</entry>
<entry file="selfforum-{date}.tgz">/home/www/teamone.de/selfforum</entry>
<entry file="selfhtml-{date}.tgz">/home/www/teamone.de/selfhtml</entry>
<entry file="teamone-{date}.tgz">/home/www/teamone.de/www</entry>
<entry file="selfhtml.fr-{date}.tgz">/home/www/selfhtml.com.fr/selfhtml</entry>
<entry file="selfaktuell.fr-{date}.tgz">/home/www/selfhtml.com.fr/www</entry>
<entry file="selfforum.fr-{date}.tgz">/home/www/selfhtml.com.fr/selfforum</entry>
</save>
</config>
backup.dtd:
<!ELEMENT config (savefile,logfile,extern,Generations,save)>
<!ELEMENT savefile EMPTY>
<!ATTLIST savefile
path CDATA #REQUIRED
compression CDATA #REQUIRED
archive CDATA #REQUIRED
>
<!ELEMENT logfile EMPTY>
<!ATTLIST logfile
path CDATA #REQUIRED
file CDATA #REQUIRED
>
<!ELEMENT extern EMPTY>
<!ATTLIST extern
scp CDATA #REQUIRED
server CDATA #REQUIRED
path CDATA #REQUIRED
>
<!ELEMENT Generations EMPTY>
<!ATTLIST Generations
save_local CDATA #REQUIRED
save_extern CDATA #REQUIRED
pattern CDATA #REQUIRED
>
<!ELEMENT save (entry+)>
<!ELEMENT entry (#PCDATA)>
<!ATTLIST entry
file CDATA #REQUIRED
>
Tja, hier kann ich definitiv nicht sagen, ob es bugfrei ist. Ich weiss nur, es funktioniert so, wie ich es einsetze, wenn jemand Bugs findet — bitte sagt mir bescheid.
Ich hatte ja bereits angemerkt, dass auf den Logs ein besonderes Augenmerk liegt, aus rein gesetzestechnischen Gründen. Deshalb werden sie auch nochmal extra gesichert, mit dem folgenden Script:
#!/usr/bin/perl -w
use vars qw($VERSION $CONFIG $TIME);
BEGIN
{
my ($mday,$month,$year,$hour,$min,$sec) = (localtime)[3,4,5,2,1,0];
$year += 1900;
$month++;
$VERSION = '0.5';
$CONFIG = '/home/cron/logrolling/config/rolling.xml';
$TIME = $mday.'.'.$month.'.'.$year.'-'.$hour.':'.$min.':'.$sec;
}
use strict;
use XML::Simple;
sub roll_it($);
my $config = XMLin($CONFIG);
roll_it $_ foreach (@{$config->{log}});
print `$config->{apache} restart`;
sub roll_it($)
{
my $entry = shift;
my $flag = '';
local *DIR;
chdir $entry->{dir} or die $entry->{dir},': ',$!;
mkdir 'temp',oct(777);
if(-f $entry->{archive})
{
print `tar -C $entry->{dir}/temp -xzf $entry->{archive}`;
}
opendir DIR,$entry->{dir} or die $entry->{dir},': ',$!;
foreach (readdir DIR)
{
next if -d $_;
rename $_ => 'temp/'.$_.'.'.$TIME
or die $entry->{dir},$_,': ',$!;
}
chdir 'temp';
print `tar -czf $entry->{archive} *`;
print `rm *`;
chdir '..';
rmdir 'temp';
closedir DIR;
}
# eof
Ich benutze hier ganz aktiv Backpipes, weil Archive::Tar leider einen kleinen Bug beim einlesen von Archiven zu haben scheint.