#!/usr/bin/perl -w # amanda_panel.pl # Amanda administration/recovery console # # It's a CGI, must run as "amanda" user. # Simply setup single-purpose Apache and run it as this user # (in secure intranet!) # # Status: Works For Me (TM) # Written by Filip Rembialkowski (mailto:plk.zuber@gmail.com) # This code is placed by the author in public domain # use strict; use CGI qw/:standard -no_xhtml *table *Tr *td/; $|=1; my $title="Amanda Administrative Panel at " . `hostname -f`; my $host = 'amy'; # default Amanda/Samba server host my ($winuser,$winpass) = ('ntadmin','ntadminpass'); my $CSS=<'error'}, [ @_ ] ); } sub list_of_indexes { my $ajob = shift; $ajob = 'DailySet1' unless $ajob; my $ahost = shift; $ahost = $host unless $ahost; my @l; my $ipath = "/var/log/amanda/$ajob/index/$ahost"; for(qx " find $ipath -type f -name \"*.gz\" ") { chomp; s/$ipath\///g; push @l, $_; } return @l; } sub index { my $ajob = shift; $ajob = 'DailySet1' unless $ajob; my $ahost = shift; $ahost = $host unless $ahost; my $ipath = "/var/log/amanda/$ajob/index/$ahost"; my $file=shift; my($nd,$nf)=(0,0); my $result=''; for (`zcat '$ipath/$file'`) { if(/\/$/) { $nd++ } else { $nf++; $result .= $_; } }; return "index file: $file\n\n" . "Total $nf files and $nd directories\n\n" . $result; } sub list_of_active_tapes { my $ajob = shift; my %T; my $apid = open(APIPE, "amadmin $ajob export 2>&1 |"); # with an open pipe $apid or return; while () { # parse export if (/^stats: ([01]) (\d+).*\s([a-z0-9]+)$/i) { $T{$1}{$3}+=$2; } } close APIPE; my $OUT=''; for my $l(keys%T) { my $sum = 0; $OUT .= "tapes for lev $l:\n"; for my $t(sort keys %{$T{$l}}) { $OUT .= "$t with $T{$l}{$t} Kbytes\n"; $sum += $T{$l}{$t}; } $OUT .= " Total for lev $l: $sum Kbytes\n"; } return $OUT; } my $hint=" take them from Amanda mail report "; my $hint2=" Attention! This task could take some time, and it CANNOT be stopped from browser! "; my $hint3=" must exist, with proper permissions"; my $job_default = 'DailySet1'; my $label_default = $job_default . `date +%d`; my $dest_default = '//BIG/disk'; print header(-type => 'text/html; charset=UTF-8'), start_html( -title => $title, -style => { -code => $CSS } ), p("Welcome to ${title}", br(), "Please select your task below."), hr(), start_form, start_table, Tr( td("Job name (eg. $job_default): "), td(textfield('job', $job_default, 20, 60)), td({-rowspan=>"3"}, small($hint)) ), Tr( td("Tape label (eg. $label_default): "), td(textfield('label', $job_default.'xx', 20, 60)) ), Tr( td("\"disk\" (eg. //PCHOST/user\$): "), td(textfield('disk', '', 40, 200)) ), Tr( td("Destination (eg. //BIG/C\$): "), td(textfield('dest', $dest_default, 40, 200)), td(small($hint3)) ), end_table, p, start_table, Tr( th({ -scope=>"row"}, "Info commands"), td( {-colspan=>"2"}, submit(-name => 'op', -value => 'backup history'), submit(-name => 'op', -value => 'backup info'), submit(-name => 'op', -value => 'amoverview'), submit(-name => 'op', -value => 'balance'), submit(-name => 'op', -value => 'due'), submit(-name => 'op', -value => 'list tapes which are REALLY active') ) ), Tr( th({ -scope=>"row"}, "Tape commands: "), td( {-colspan=>"2"}, submit(-name => 'op', -value => 'reset changer', -disabled =>'yes'), submit(-name => 'op', -value => 'show all tapes', -disabled =>'yes'), submit(-name => 'op', -value => 'load tape', -disabled =>'yes'), submit(-name => 'op', -value => 'eject tape'), submit(-name => 'op', -value => 'tape status'), submit(-name => 'op', -value => 'label tape', -class => 'red'), submit(-name => 'op', -value => 'remove tape', -class => 'red') ) ), Tr( th({ -scope=>"row"}, "Restore commands: "), td( {-colspan=>"2"}, submit(-name => 'op', -value => 'test archive', -class => 'red', -title => $hint2), submit(-name => 'op', -value => 'restore files locally', -class => 'red', -title => $hint2), submit(-name => 'op', -value => 'restore tar locally', -class => 'red', -title => $hint2), submit(-name => 'op', -value => 'restore', -class => 'red', -title => $hint2) ) ), Tr( th({ -scope=>"row"}, "Other commands: "), td( {-colspan=>"2"}, submit(-name => 'op', -value => 'force disk', -class => 'red'), submit(-name => 'op', -value => 'amflush', -class => 'red', -title => $hint2) ) ), Tr( th({ -scope=>"row"}, "Browse index: "), td( {-colspan=>"2"}, popup_menu(-name => 'index', -values => [ sort &list_of_indexes ] ), submit(-name => 'op', -value => 'show index') ) ), end_table, end_form(), hr(); if(param('op')) { my ($job, $label, $disk, $dest, $index, $op); $job=param('job'); $label=param('label'); $disk=param('disk'); $dest=param('dest'); $op=param('op'); $index=param('index'); my $amlog='/var/log/amanda'; my $tpdev = '/dev/nst0'; my $disk_esc = $disk; $disk_esc =~ s/\$$/\\\$/; my $index_esc = $index; $index_esc =~ s/\$$/\\\$/; $dest =~ /(\/\/[\w\-_]+\/[^\s\/]+)(.*)$/i; my $destshare = $1 ? $1 : ''; my $destdir = $2 ? $2 : '/'; # if ($job and $label and $disk and $dest) { if ($job) { my @com; #push @com, "id"; # test #push @com, "false"; # test $op eq 'reset changer' and push @com, "amtape $job reset"; $op eq 'show all tapes' and push @com, "amtape $job show"; $op eq 'load tape' and push @com, "amtape $job label $label"; # prior to list/extract $op eq 'eject tape' and push @com, "mt offline"; $op eq 'tape status' and push @com, "mt status", "mt tell"; $op eq 'label tape' and push @com, "amlabel $job $label"; $op eq 'list tapes which are REALLY active' and print pre(&list_of_active_tapes($job)); $op eq 'show index' and print pre(&index($job, $host, $index_esc)); $op eq 'remove tape' and push @com, "amrmtape -v $job $label"; $op eq 'backup history' and push @com, "amadmin $job find $host '$disk_esc'"; $op eq 'backup info' and push @com, "amadmin $job info $host '$disk_esc'"; $op eq 'amoverview' and push @com, "amoverview $job -diskwidth 40"; $op eq 'balance' and push @com, "amadmin $job balance"; $op eq 'due' and push @com, "amadmin $job due"; $op eq 'amflush' and push @com, "amflush -b -f -s $job"; $op eq 'force disk' and push @com, "amadmin $job force $host '$disk_esc'" if $disk_esc; $op eq 'test archive' and push @com, "amrestore -p -l $label $tpdev $host '$disk_esc' | tar -tvf - "; $op eq 'restore' and $destshare and push @com, "amrestore -p -l $label $tpdev $host '$disk_esc' | smbclient $destshare -d 1 -D $destdir -U ${winuser}\%${winpass} -Tx - "; $op eq 'restore files locally' and push @com, "amrestore -p -l $label $tpdev $host '$disk_esc' | tar -C /var/amanda/restorage -xvf - "; $op eq 'restore tar locally' and push @com, "cd /var/amanda/restorage && amrestore -l $label $tpdev $host '$disk_esc'"; foreach my $cmd (@com) { print p("running: "), b($cmd); my $pid = open(PH, "( $cmd ) &1 |"); # with an open pipe $pid or do { cgiwarn("can't execute $cmd"); next; }; while () { print pre($_) } close PH; print p, b("...finished"); } } } print end_html(); =pod =pod UTIL=amtape Usage: amtape Valid commands are: reset Reset changer to known state eject Eject current tape from drive clean Clean the drive show Show contents of all slots current Show contents of current slot slot load tape from slot slot current load tape from current slot slot prev load tape from previous slot slot next load tape from next slot slot advance advance to next slot but do not load slot first load tape from first slot slot last load tape from last slot label