98_backup.pm 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. ################################################################
  2. # $Id: 98_backup.pm 17053 2018-07-30 17:16:17Z rudolfkoenig $
  3. # vim: ts=2:et
  4. #
  5. # (c) 2012 Copyright: Martin Fischer (m_fischer at gmx dot de)
  6. # All rights reserved
  7. #
  8. # This script free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # The GNU General Public License can be found at
  14. # http://www.gnu.org/copyleft/gpl.html.
  15. # A copy is found in the textfile GPL.txt and important notices to the license
  16. # from the author is found in LICENSE.txt distributed with these scripts.
  17. #
  18. # This script is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. # GNU General Public License for more details.
  22. #
  23. ################################################################
  24. package main;
  25. use strict;
  26. use warnings;
  27. sub CommandBackup($$);
  28. sub parseConfig($);
  29. sub readModpath($$);
  30. sub createArchiv($$$);
  31. my @pathname;
  32. #####################################
  33. sub
  34. backup_Initialize($$)
  35. {
  36. my %hash = ( Fn => "CommandBackup",
  37. Hlp => ",create a backup of fhem configuration, state and modpath" );
  38. $cmds{backup} = \%hash;
  39. }
  40. #####################################
  41. sub
  42. CommandBackup($$)
  43. {
  44. my ($cl, $param) = @_;
  45. my $byUpdate = ($param && $param eq "startedByUpdate");
  46. my $modpath = AttrVal("global", "modpath","");
  47. my $configfile = AttrVal("global", "configfile", "");
  48. my $statefile = AttrVal("global", "statefile", "");
  49. my $now = gettimeofday();
  50. my @t = localtime($now);
  51. $statefile = ResolveDateWildcards($statefile, @t);
  52. # prevent duplicate entries in backup list for default config, forum #54826
  53. $configfile = '' if ($configfile eq 'fhem.cfg' || configDBUsed());
  54. $statefile = '' if ($statefile eq "./log/fhem.save");
  55. my $msg;
  56. my $ret;
  57. Log 1, "NOTE: make sure you have a database backup!" if(configDBUsed());
  58. # set backupdir
  59. my $backupdir;
  60. if (!defined($attr{global}{backupdir})) {
  61. $backupdir = "$modpath/backup";
  62. } else {
  63. if ($attr{global}{backupdir} =~ m/^\/.*/) {
  64. $backupdir = $attr{global}{backupdir};
  65. } elsif ($attr{global}{backupdir} =~ m/^\.+\/.*/) {
  66. $backupdir = "$modpath/$attr{global}{backupdir}";
  67. } else {
  68. $backupdir = "$modpath/$attr{global}{backupdir}";
  69. }
  70. }
  71. # create backupdir if not exists
  72. if (!-d $backupdir) {
  73. Log 4, "backup create backupdir: '$backupdir'";
  74. $ret = `(mkdir -p $backupdir) 2>&1`;
  75. if ($ret) {
  76. chomp $ret;
  77. $msg = "backup: $ret";
  78. return $msg;
  79. }
  80. }
  81. if(configDBUsed()) {
  82. # add configDB configuration file
  83. push @pathname, 'configDB.conf';
  84. Log 4, "backup include: 'configDB.conf'";
  85. } else {
  86. # get pathnames to archiv
  87. push @pathname, $configfile if($configfile);
  88. Log 4, "backup include: '$configfile'";
  89. $ret = parseConfig($configfile);
  90. push @pathname, $statefile if($statefile);
  91. Log 4, "backup include: '$statefile'";
  92. }
  93. $ret = readModpath($modpath,$backupdir);
  94. # create archiv
  95. $ret = createArchiv($backupdir, $cl, $byUpdate);
  96. @pathname = [];
  97. undef @pathname;
  98. return $ret;
  99. }
  100. sub
  101. parseConfig($)
  102. {
  103. my $configfile = shift;
  104. # we need default value to read included files
  105. $configfile = $configfile ? $configfile : 'fhem.cfg';
  106. my $fh;
  107. my $msg;
  108. my $ret;
  109. if (!open($fh,$configfile)) {
  110. $msg = "Can't open $configfile: $!";
  111. Log 1, "backup $msg";
  112. return $msg;
  113. }
  114. while (my $l = <$fh>) {
  115. $l =~ s/[\r\n]//g;
  116. if ($l =~ m/^\s*include\s+(\S+)\s*.*$/) {
  117. if (-e $1) {
  118. push @pathname, $1;
  119. Log 4, "backup include: '$1'";
  120. $ret = parseConfig($1);
  121. } else {
  122. Log 1, "backup configfile: '$1' does not exists! File not included."
  123. }
  124. }
  125. }
  126. close $fh;
  127. return $ret;
  128. }
  129. sub
  130. readModpath($$)
  131. {
  132. my ($modpath,$backupdir) = @_;
  133. my $msg;
  134. my $ret;
  135. if (!opendir(DH, $modpath)) {
  136. $msg = "Can't open $modpath: $!";
  137. Log 1, "backup $msg";
  138. return $msg;
  139. }
  140. my @files = <$modpath/*>;
  141. foreach my $file (@files) {
  142. if ($file eq $backupdir && (-d $file || -l $file)) {
  143. Log 4, "backup exclude: '$file'";
  144. } else {
  145. Log 4, "backup include: '$file'";
  146. push @pathname, $file;
  147. }
  148. }
  149. return $ret;
  150. }
  151. sub
  152. createArchiv($$$)
  153. {
  154. my ($backupdir,$cl,$byUpdate) = @_;
  155. my $backupcmd = (!defined($attr{global}{backupcmd}) ? undef : $attr{global}{backupcmd});
  156. my $symlink = (!defined($attr{global}{backupsymlink}) ? "no" : $attr{global}{backupsymlink});
  157. my $tarOpts;
  158. my $msg;
  159. my $ret;
  160. my $dateTime = TimeNow();
  161. $dateTime =~ s/ /_/g;
  162. $dateTime =~ s/(:|-)//g;
  163. my $pathlist = join( "\" \"", @pathname );
  164. my $cmd="";
  165. if (!defined($backupcmd)) {
  166. if (lc($symlink) eq "no") {
  167. $tarOpts = "cf";
  168. } else {
  169. $tarOpts = "chf";
  170. }
  171. # prevents tar's output of "Removing leading /" and return total bytes of
  172. # archive
  173. $cmd = "tar -$tarOpts - \"$pathlist\" |gzip > $backupdir/FHEM-$dateTime.tar.gz";
  174. } else {
  175. $cmd = "$backupcmd \"$pathlist\"";
  176. }
  177. Log 2, "Backup with command: $cmd";
  178. if(!$fhemForked && !$byUpdate) {
  179. use Blocking;
  180. our $BC_telnetDevice;
  181. BC_searchTelnet("backup");
  182. my $tp = $defs{$BC_telnetDevice}{PORT};
  183. system("($cmd; echo Backup done;".
  184. "$^X $0 localhost:$tp 'trigger global backup done')2>&1 &");
  185. return "Started the backup in the background, watch the log for details";
  186. }
  187. $ret = `($cmd) 2>&1`;
  188. if($ret) {
  189. chomp $ret;
  190. Log 1, "backup $ret";
  191. }
  192. if (!defined($backupcmd) && -e "$backupdir/FHEM-$dateTime.tar.gz") {
  193. my $size = -s "$backupdir/FHEM-$dateTime.tar.gz";
  194. $msg = "backup done: FHEM-$dateTime.tar.gz ($size Bytes)";
  195. DoTrigger("global", $msg);
  196. Log 1, $msg;
  197. $ret .= "\n".$msg;
  198. }
  199. return $ret;
  200. }
  201. 1;
  202. =pod
  203. =item command
  204. =item summary create a backup of the FHEM installation
  205. =item summary_DE erzeugt eine Sicherungsdatei der FHEM Installation
  206. =begin html
  207. <a name="backup"></a>
  208. <h3>backup</h3>
  209. <ul>
  210. <code>backup</code><br>
  211. <br>
  212. The complete FHEM directory (containing the modules), the WebInterface
  213. pgm2 (if installed) and the config-file will be saved into a .tar.gz
  214. file by default. The file is stored with a timestamp in the
  215. <a href="#modpath">modpath</a>/backup directory or to a directory
  216. specified by the global attribute <a href="#backupdir">backupdir</a>.<br>
  217. Note: tar and gzip must be installed to use this feature.
  218. <br>
  219. <br>
  220. If you need to call tar with support for symlinks, you could set the
  221. global attribute <a href="#backupsymlink">backupsymlink</a> to everything
  222. else as "no".
  223. <br>
  224. <br>
  225. You could pass the backup to your own command / script by using the
  226. global attribute <a href="#backupcmd">backupcmd</a>.
  227. <br>
  228. <br>
  229. </ul>
  230. =end html
  231. =cut