backup.pl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. #!/usr/bin/perl
  2. use Cwd;
  3. use File::Basename;
  4. use File::Find;
  5. use File::stat qw(:FIELDS);
  6. use File::Spec::Functions;
  7. use Sys::Hostname;
  8. use DirHandle;
  9. use Time::localtime;
  10. use Fcntl;
  11. use Tie::File;
  12. use Net::FTP;
  13. use Data::Dumper;
  14. my $WAIT_TIME = 10800; # 3 hours
  15. my @FN=split("/",$0);
  16. my $SPID="/var/run/".$FN[-1];
  17. my $script_name= basename($0);
  18. my $cfg_file = "/usr/local/etc/backup.conf";
  19. my $ve_id = "";
  20. my $ve_root = "";
  21. if ($ARGV[0]) { $cfg_file = $ARGV[0]; }
  22. if ($ARGV[1]) { $ve_id = $ARGV[1]; }
  23. eval {
  24. #set timeout for script work
  25. $SIG{ALRM} = sub { die "Maximum worktime $WAIT_TIME sec reached!\n" };
  26. alarm $WAIT_TIME;
  27. if (! -e $cfg_file) { die ("File $cfg_file not found. Die...\n"); }
  28. require $cfg_file;
  29. my $filename = "";
  30. my $filefrom = "";
  31. my $tm = localtime;
  32. ($year,$month,$day,$wday) = ($tm->year+1900,$tm->mon+1,$tm->mday,$tm->wday);
  33. my $date = sprintf("%04d",$year).sprintf("%02d",$month).sprintf("%02d",$day);
  34. my $ttop = time;
  35. my $empty = 0;
  36. @dirlist = ();
  37. @exclist = ();
  38. *name = *File::Find::name;
  39. if (!($cfg_tmp_dir ne "" and $cfg_hostname ne "" and $cfg_ftp_hostname ne ""
  40. and $cfg_ftp_username ne "" and $cfg_ftp_password ne "" and $cfg_ftp_path
  41. ne "" and $cfg_full_day ne "" and $cfg_del_files ne "")) {
  42. die ("Not set configuration variables! Die...\n");
  43. }
  44. if ($cfg_admin_email eq "" or $cfg_from_email eq "") { $cfg_admin_email = "root"; $cfg_from_email = $cfg_admin_email; }
  45. if (!$cfg_use_ftp) { $cfg_use_ftp = "no"; }
  46. if (!$cfg_use_smb) { $cfg_use_smb = "no"; }
  47. if (!$tar_opts) { $tar_opts="--no-recursion"; }
  48. if (IsNotRun($SPID)) { Add_PID($SPID); }
  49. else { die ("Warning!!! backup.pl already runnning!\n"); }
  50. if (!$cfg_dirlist_file) { $cfg_dirlist_file ="/usr/local/etc/dirlist.conf"; }
  51. if (!$cfg_exclist_file) { $cfg_exclist_file ="/usr/local/etc/exclist.conf"; }
  52. if (!$cfg_mysql_dump) { $cfg_mysql_dump = "mysqldump"; }
  53. #for vz container
  54. if ($ve_id) {
  55. $cfg_exclist_file=~s/.conf//;
  56. $cfg_exclist_path=$cfg_exclist_file;
  57. #if found personal rule - use it
  58. if (-e $cfg_exclist_path."-$ve_id.conf") { $cfg_exclist_file=$cfg_exclist_path."-$ve_id.conf"; }
  59. else {
  60. #use common rule
  61. if (-e $cfg_exclist_path."-vz.conf") { $cfg_exclist_file=$cfg_exclist_path."-vz.conf"; }
  62. else {
  63. #if personal and common file not found - use default
  64. $cfg_exclist_file=$cfg_exclist_path.".conf";
  65. }
  66. }
  67. $ve_root = "/vz/root/$ve_id/";
  68. $cfg_hostname=$ve_id;
  69. }
  70. if (-e $cfg_dirlist_file) {
  71. open (FD,"<",$cfg_dirlist_file);
  72. while (<FD>) {
  73. chomp;
  74. push(@dirlist,$_);
  75. }
  76. close(FD);
  77. die ("Warning!!! no listing dir for backup. Exit.\n") if ($#dirlist eq "-1");
  78. }
  79. else { die ("File $cfg_dirlist_file not found! die...\n"); }
  80. if (-e $cfg_exclist_file) {
  81. open (FD1,"<",$cfg_exclist_file);
  82. while (<FD1>) {
  83. chomp;
  84. my $ex=$_;
  85. if ($ve_id) {
  86. if ($ex=~/\//) {
  87. if ($ex=~/\^(.*)/) {$ex="^".$ve_root.$1; } else { $ex=$ve_root.$ex; }
  88. $ex=~s/\/\//\//g;
  89. }
  90. }
  91. push(@exclist,$ex);
  92. }
  93. close(FD1);
  94. }
  95. #dirlist ignored if backup vz container
  96. #Left for compatibility
  97. if ($ve_id) { @dirlist = ( "./" ); }
  98. my $fullbackup = ($cfg_full_day eq $wday);
  99. if ((!$fullbackup) and (! -e "$cfg_status_dir/$cfg_hostname-back_file_size$ve_id.list")) { $fullbackup=1; }
  100. if (!$fullbackup) {
  101. open (FHD,">","$cfg_status_dir/$cfg_hostname-back_file_diff$ve_id.list");
  102. sub process_file_diff {
  103. if ($#exclist eq "-1") {
  104. if (-f) {
  105. $info = stat($name);
  106. print FHD $info->mtime." ".$info->size." ".$name."\n";
  107. }
  108. }else{
  109. if (-f) {
  110. $exists = 0;
  111. $info = stat($name);
  112. for ($i = 0; $i < @exclist; $i++) {
  113. $ii = $exclist[$i];
  114. $exists = ($exists or ($name =~ /$ii/));
  115. }
  116. if (!$exists) {
  117. print FHD $info->mtime." ".$info->size." ".$name."\n";
  118. }
  119. }
  120. }
  121. }
  122. if ($ve_root) { find(\&process_file_diff, $ve_root); }
  123. else {
  124. foreach $idir (@dirlist) {
  125. find(\&process_file_diff, $idir);
  126. }
  127. }
  128. close(FHD);
  129. system("diff -f $cfg_status_dir/$cfg_hostname-back_file_size$ve_id.list $cfg_status_dir/$cfg_hostname-back_file_diff$ve_id.list | grep / > $cfg_status_dir/$cfg_hostname-file$ve_id.diff");
  130. tie @ddiff, Tie::File, "$cfg_status_dir/$cfg_hostname-file$ve_id.diff";
  131. if ($#ddiff ne "-1") {
  132. open(FDD,">","$cfg_status_dir/$cfg_hostname-back_diff$ve_id.list");
  133. foreach $mdiff (@ddiff) {
  134. ($a,$b,$c) = split /\t/,$mdiff;
  135. $a = "";
  136. $b = "";
  137. print FDD $c."\n";
  138. }
  139. close (FDD);
  140. $filename = "$cfg_tmp_dir/$cfg_hostname-inc-$wday.tar.gz";
  141. $filefrom = "$cfg_status_dir/$cfg_hostname-back_diff$ve_id.list";
  142. }
  143. else { $empty=1; }
  144. }
  145. else{
  146. unlink <$cfg_tmp_dir/$cfg_hostname*>;
  147. open (FH1,">","$cfg_status_dir/$cfg_hostname-back_file_size$ve_id.list");
  148. open (FH2,">","$cfg_status_dir/$cfg_hostname-back_file$ve_id.list");
  149. sub process_file {
  150. if ($#exclist eq "-1") {
  151. if (-f) {
  152. $info = stat($name);
  153. print FH1 $info->mtime." ".$info->size." ".$name."\n";
  154. print FH2 $name."\n";
  155. }
  156. }else{
  157. if (-f) {
  158. $exists = 0;
  159. for ($i = 0; $i < @exclist; $i++) {
  160. $ii = $exclist[$i];
  161. $exists = ($exists or ($name =~ /$ii/));
  162. }
  163. if (!$exists) {
  164. $info = stat($name);
  165. print FH1 $info->mtime." ".$info->size." ".$name."\n";
  166. print FH2 $name."\n";
  167. }
  168. }
  169. }
  170. }
  171. if ($ve_root) { find(\&process_file, $ve_root); }
  172. else {
  173. foreach $idir (@dirlist) {
  174. find(\&process_file, $idir);
  175. }
  176. }
  177. close(FH1);
  178. close(FH2);
  179. $filename = "$cfg_tmp_dir/$cfg_hostname-full-$date.tar.gz";
  180. $filefrom = "$cfg_status_dir/$cfg_hostname-back_file$ve_id.list";
  181. }
  182. if ($empty eq 0) {
  183. my $ret=`tar $tar_opts -czPf $filename --files-from=$filefrom`;
  184. $tbot = time - $ttop;
  185. open(FHR,">","$cfg_status_dir/report$ve_id.txt");
  186. $minfo = stat($filename);
  187. print FHR sprintf("%-45s %10s %20s\n",basename($filename),$tbot,$minfo->size);
  188. close(FHR);
  189. }
  190. my @mysql_backup_files = ();
  191. my @mongo_backup_files = ();
  192. my @pgsql_backup_files = ();
  193. if ($cfg_mysql_backup eq "yes") {
  194. if ($cfg_mysql_db[0]=~/all/i) {
  195. my $run_cmd="echo 'show databases;' | mysql -u \"$cfg_mysql_user\" -h \"$cfg_mysql_host\" -s --password=\"$cfg_mysql_pass\"";
  196. if ($cfg_mysql_host!~/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/) {
  197. $run_cmd = "echo 'show databases;' | mysql -u \"$cfg_mysql_user\" -S \"$cfg_mysql_host\" -s --password=\"$cfg_mysql_pass\"";
  198. }
  199. @cfg_mysql_db = `$run_cmd`;
  200. chomp(@cfg_mysql_db);
  201. }
  202. $mysql_path = $0;
  203. $mysql_path=~ s/$script_name$/mysqld_backup/;
  204. $mysql_path=$mysql_path."2" if ($cfg_mysql_dump eq "mysqldump");
  205. foreach my $db (@cfg_mysql_db) {
  206. next if ($db=~/^information_schema$/);
  207. $ttop = time;
  208. my $lang='';
  209. if ($cfg_mysql_locale) { $lang="LANG=".$cfg_mysql_locale." "; }
  210. $fname="$cfg_tmp_dir/$cfg_hostname-mysql-$date-$db".".tgz";
  211. $ret=`$lang$mysql_path \"$cfg_mysql_host\" \"$cfg_mysql_user\" \"$cfg_mysql_pass\" \"$db\" \"$cfg_tmp_dir/$cfg_hostname-mysql-$date\"`;
  212. push (@mysql_backup_files,$fname) if ($? eq 0);
  213. $tbot = time - $ttop;
  214. open(FHR,">>","$cfg_status_dir/report$ve_id.txt");
  215. $minfo = stat($fname);
  216. print FHR sprintf("%-45s %10s %20s\n",$fname,$tbot,$minfo->size);
  217. close(FHR);
  218. }
  219. }
  220. if ($cfg_mongo_backup eq "yes") {
  221. $mongo_path = $0;
  222. $mongo_path=~ s/$script_name$/mongo_backup/;
  223. if ($cfg_mysql_db[0]=~/all/i) {
  224. $ttop = time;
  225. $fname="$cfg_tmp_dir/$cfg_hostname-mongo-$date-all".".tgz";
  226. my $ret=`$mongo_path \"$cfg_mongo_host\" \"$cfg_mongo_user\" \"$cfg_mongo_pass\" \"all\" \"$cfg_tmp_dir/$cfg_hostname-mongo-$date\"`;
  227. push (@mongo_backup_files,$fname) if ($? eq 0);
  228. $tbot = time - $ttop;
  229. open(FHR,">>","$cfg_status_dir/report$ve_id.txt");
  230. $minfo = stat($fname);
  231. print FHR sprintf("%-45s %10s %20s\n",$fname,$tbot,$minfo->size);
  232. close(FHR);
  233. } else {
  234. foreach my $db (@cfg_mongo_db) {
  235. $ttop = time;
  236. $fname="$cfg_tmp_dir/$cfg_hostname-mongo-$date-$db".".tgz";
  237. my $ret=`$mongo_path \"$cfg_mongo_host\" \"$cfg_mongo_user\" \"$cfg_mongo_pass\" \"$db\" \"$cfg_tmp_dir/$cfg_hostname-mongo-$date\"`;
  238. push (@mongo_backup_files,$fname) if ($? eq 0);
  239. $tbot = time - $ttop;
  240. open(FHR,">>","$cfg_status_dir/report$ve_id.txt");
  241. $minfo = stat($fname);
  242. print FHR sprintf("%-45s %10s %20s\n",$fname,$tbot,$minfo->size);
  243. close(FHR);
  244. }
  245. }
  246. }
  247. if ($cfg_pgsql_backup eq "yes") {
  248. $cfg_pgsql_su = "no" if (!$cfg_pgsql_su);
  249. open(PGP,">","/root/.pgpass");
  250. print PGP "$cfg_pgsql_host:5432:*:$cfg_pgsql_user:$cfg_pgsql_pass";
  251. close(PGP);
  252. chmod 0400,"/root/.pgpass";
  253. chown 0,0,"/root/.pgpass";
  254. if ($cfg_pgsql_dump eq "pg_dump") {
  255. $pgsql_path = $0;
  256. $pgsql_path=~ s/$script_name$/postgres_listdb/;
  257. if ($cfg_pgsql_db[0]=~/all/i) {
  258. @cfg_pgsql_db = `$lang$pgsql_path \"$cfg_pgsql_su\" \"$cfg_pgsql_host\" \"$cfg_pgsql_user\"`;
  259. chomp(@cfg_pgsql_db);
  260. }
  261. $pgsql_path = $0;
  262. $pgsql_path=~ s/$script_name$/postgres_backup1/;
  263. foreach my $db (@cfg_pgsql_db) {
  264. next if (!$db);
  265. my $lang='';
  266. $ttop = time;
  267. if ($cfg_pgsql_locale) { $lang="LANG=".$cfg_pgsql_locale." "; }
  268. $fname = "$cfg_tmp_dir/$cfg_hostname-pgsql-$date-$db".".tgz";
  269. $ret=`$lang$pgsql_path \"$db\" \"$cfg_tmp_dir/$cfg_hostname-pgsql-$date\" \"$cfg_pgsql_su\" \"$cfg_pgsql_host\" \"$cfg_pgsql_user\"`;
  270. push (@pgsql_backup_files,$fname) if ($? eq 0);
  271. $tbot = time - $ttop;
  272. open(FHR,">>","$cfg_status_dir/report$ve_id.txt");
  273. $minfo = stat($fname);
  274. print FHR sprintf("%-45s %10s %20s\n",$fname,$tbot,$minfo->size);
  275. close(FHR);
  276. }
  277. } else {
  278. $pgsql_path = $0;
  279. $pgsql_path=~ s/$script_name$/postgres_backup2/;
  280. my $lang='';
  281. $ttop = time;
  282. if ($cfg_pgsql_locale) { $lang="LANG=".$cfg_pgsql_locale." "; }
  283. $fname = "$cfg_tmp_dir/$cfg_hostname-pgsql-$date-$cfg_hostname".".tgz";
  284. $ret=`$lang$pgsql_path \"$cfg_tmp_dir/$cfg_hostname-pgsql-$date\" \"$cfg_pgsql_su\" \"$cfg_pgsql_user\" \"$cfg_pgsql_host\" \"$cfg_hostname\"`;
  285. push (@pgsql_backup_files,$fname) if ($? eq 0);
  286. $tbot = time - $ttop;
  287. open(FHR,">>","$cfg_status_dir/report$ve_id.txt");
  288. $minfo = stat($fname);
  289. print FHR sprintf("%-45s %10s %20s\n",$fname,$tbot,$minfo->size);
  290. close(FHR);
  291. }
  292. unlink "/root/.pgpass";
  293. }
  294. if ($cfg_use_ftp ne "no") {
  295. $ftp = Net::FTP->new($cfg_ftp_hostname,Timeout => 30,Passive => $cfg_ftp_mode);
  296. die("Can't connect to $cfg_ftp_hostname !\n") if (!$ftp);
  297. die ("Couldn't authenticate, even with explicit username and password.\n") if (!$ftp->login($cfg_ftp_username,$cfg_ftp_password));
  298. if (!$ftp->cwd($cfg_ftp_path)) {
  299. if ($ftp->mkdir ($cfg_ftp_path,1)) { $ftp->cwd($cfg_ftp_path); }
  300. else { die ("Can't create directory $cfg_ftp_path at $cfg_ftp_hostname! Die...\n"); }
  301. }
  302. $ftp->binary();
  303. if ($filename) {
  304. die ("Can't put backup $filename to $cfg_ftp_hostname!\n") if (!$ftp->put($filename));
  305. }
  306. foreach $db (@mysql_backup_files) {
  307. die ("Can't put $db to $cfg_ftp_hostname!\n") if (!$ftp->put("$db"));
  308. }
  309. foreach $db (@pgsql_backup_files) {
  310. die ("Can't put $db to $cfg_ftp_hostname!\n") if (!$ftp->put("$db"));
  311. }
  312. $ftp->put("$cfg_status_dir/report$ve_id.txt");
  313. $ftp->quit();
  314. };
  315. if ($cfg_use_smb ne "no") {
  316. my $smb_path=$0;
  317. $smb_path=~ s/$script_name$/smb_copy/;
  318. my $ret=`$smb_path \"$cfg_smb_hostname\" \"$cfg_smb_username\" \"$cfg_smb_password\" \"$cfg_smb_share\" \"$cfg_smb_path\" \"$filename\"`;
  319. my $res = $?;
  320. die "$ret" if ($res);
  321. foreach $db (@mysql_backup_files) {
  322. `$smb_path \"$cfg_smb_hostname\" \"$cfg_smb_username\" \"$cfg_smb_password\" \"$cfg_smb_share\" \"$cfg_smb_path\" \"$db\"`;
  323. $res = $?;
  324. die "$ret" if ($res);
  325. }
  326. foreach $db (@mongo_backup_files) {
  327. die ("Can't put $db to $cfg_ftp_hostname!\n") if (!$ftp->put("$db"));
  328. }
  329. foreach $db (@pgsql_backup_files) {
  330. `$smb_path \"$cfg_smb_hostname\" \"$cfg_smb_username\" \"$cfg_smb_password\" \"$cfg_smb_share\" \"$cfg_smb_path\" \"$db\"`;
  331. $res = $?;
  332. die "$ret" if ($res);
  333. }
  334. }
  335. unlink <$cfg_tmp_dir/$cfg_hostname*> if ($cfg_del_files eq "yes");
  336. $SIG{ALRM} = 'DEFAULT';
  337. };
  338. if ($@) { abort($cfg_admin_email,$cfg_from_email,"Script aborted. Error: $@\n",$SPID); };
  339. if (IsMyPID($SPID)) { Remove_PID($SPID); };
  340. exit 0;
  341. #---------------------------------------------------------------------------------------------------------
  342. sub sendEmail
  343. {
  344. my ($to, $from, $subject, $message) = @_;
  345. my $sendmail = '/usr/lib/sendmail';
  346. open(MAIL, "|$sendmail -oi -t");
  347. print MAIL "From: $from\n";
  348. print MAIL "To: $to\n";
  349. print MAIL "Subject: $subject\n\n";
  350. print MAIL "$message\n";
  351. close(MAIL);
  352. }
  353. #---------------------------------------------------------------------------------------------------------
  354. sub abort {
  355. my ($to,$from,$message, $pid) = @_;
  356. sendEmail ($to,$from,"Backup error at $cfg_hostname!",$message);
  357. if (IsMyPID($pid)) { Remove_PID($pid); };
  358. die ($message);
  359. }
  360. #---------------------------------------------------------------------------------------------------------
  361. sub IsNotRun {
  362. my $pname = shift;
  363. my $lockfile = $pname.".pid";
  364. if (! -e $lockfile) { return 1; }
  365. open (FF,"<$lockfile") or die "can't open file $lockfile: $!";
  366. my $lockid = <FF>;
  367. close(FF);
  368. if ($lockid eq $$) { return 1; }
  369. my $process_count = `ps axwu | awk '\{ print \$2 \}' | grep $lockid | wc -l`;
  370. if ($process_count lt 1) { unlink $lockfile; return 1; }
  371. return 0;
  372. }
  373. #---------------------------------------------------------------------------------------------------------
  374. sub IsMyPID {
  375. my $pname = shift;
  376. my $lockfile = $pname.".pid";
  377. if (! -e $lockfile) { return 0; }
  378. open (FF,"<$lockfile") or die "can't open file $lockfile: $!";
  379. my $lockid = <FF>;
  380. close(FF);
  381. if ($lockid eq $$) { return 1; }
  382. return 0;
  383. }
  384. #---------------------------------------------------------------------------------------------------------
  385. sub Add_PID {
  386. my $pname = shift;
  387. my $lockfile = $pname.".pid";
  388. open (FF,">$lockfile") or die "can't open file $lockfile: $!";
  389. flock(FF,2) or die "can't flock $lockfile: $!";
  390. print FF $$;
  391. close(FF);
  392. return 1;
  393. }
  394. #---------------------------------------------------------------------------------------------------------
  395. sub Remove_PID {
  396. my $pname = shift;
  397. my $lockfile = $pname.".pid";
  398. if (! -e $lockfile) { return 1; }
  399. unlink $lockfile or return 0;
  400. return 1;
  401. }