You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

backr.py 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2019 Aiden Holmes (aidenholmes@teknik.io)
  3. # This program is licensed under the Apache License 2.0
  4. # You are free to copy, modify, and redistribute the code.
  5. # See LICENSE file.
  6. # backr backup tool in python
  7. import os
  8. import datetime
  9. import hashlib
  10. import sys
  11. import tarfile
  12. import shutil
  13. import pickle
  14. from distutils.dir_util import copy_tree
  15. # Take Arguments
  16. for i in range(len(sys.argv)):
  17. if "-h" in sys.argv or "--help" in sys.argv:
  18. print("backr simple backup tool")
  19. print("usage: backr.py [-h|--help] [-c|--compress] [-d|--default]")
  20. print("[-h|--help] - print this help")
  21. print("[-c|--compress] - use compression")
  22. print("[-n|--no-compress] - do not use compression")
  23. print("[-d|--default] - backup to default location, ~/backrs")
  24. print("[-w|--no-comment] - do not prompt for comment")
  25. sys.exit(0)
  26. if "-d" in sys.argv or "--default" in sys.argv:
  27. prompt_for_location = False
  28. else:
  29. prompt_for_location = True
  30. if "-c" in sys.argv or "--compress" in sys.argv:
  31. use_compression = True
  32. prompt_for_compression = False
  33. elif "-n" in sys.argv or "--no-compress" in sys.argv:
  34. use_compression = False
  35. prompt_for_compression = False
  36. else:
  37. prompt_for_compression = True
  38. if "-w" in sys.argv or "--no-comment" in sys.argv:
  39. prompt_for_comment = False
  40. else:
  41. prompt_for_comment = True
  42. # Define a function for asking the user yes or no questions
  43. # Taken from: http://stackoverflow.com/questions/3041986/ddg#3041990
  44. def query_yes_no(question):
  45. default = None
  46. valid = {"yes": True, "y": True, "ye": True,
  47. "no": False, "n": False}
  48. prompt = " [y/n] "
  49. while True:
  50. sys.stdout.write(question + prompt)
  51. choice = input().lower()
  52. if default is not None and choice == '':
  53. return valid[default]
  54. elif choice in valid:
  55. return valid[choice]
  56. else:
  57. sys.stdout.write("Please respond with 'yes' or 'no' "
  58. "(or 'y' or 'n').\n")
  59. # Define a function for asking the user for a comment
  60. # This comment will be part of a directory name
  61. # In UNIX a filename can contain everything except for "/" or NULL
  62. # And we don't have to worry about NULL because this will be appended to the date
  63. def get_comment():
  64. while True:
  65. comment = input("Enter a comment for this backup (or leave blank): ")
  66. if "/" in comment:
  67. print("comment cannot contain '/'")
  68. else:
  69. return comment
  70. # Define a function to make a tarball, used for the compression feature
  71. def make_tarfile(output_filename, source_dir):
  72. with tarfile.open(output_filename, "w:gz") as tar:
  73. tar.add(source_dir, arcname=os.path.basename(source_dir))
  74. # Main Function
  75. def main():
  76. # use_compression was defined by an argument in the initial for loop
  77. global use_compression
  78. if prompt_for_compression:
  79. use_compression = query_yes_no("Use compression?")
  80. # Determining save location:
  81. # Check for .backr_location file with stores save location after first run
  82. home = os.path.expanduser("~")
  83. default_location = home+"/backrs"
  84. if os.path.isfile(".backr-location"):
  85. with open('.backr-location', 'r') as myfile:
  86. backup_location = myfile.read()
  87. # If .backr_location does not exit, prompt the user for a location and save
  88. # it to the file, later this should be done entirely as an argument to make
  89. # backr more portable
  90. else:
  91. if not prompt_for_location:
  92. backup_location = default_location
  93. else:
  94. backup_location = input("Enter a location to save (or leave blank for default "+default_location+"): ")
  95. # If the user leaves the location blank, use the default (~/backrs)
  96. if backup_location == "":
  97. backup_location = default_location
  98. # If the user-provided location exists, write it to the file
  99. if os.path.isdir(backup_location):
  100. f = open('.backr-location', 'w')
  101. f.write(backup_location)
  102. f.close()
  103. else:
  104. # Create the folder only if it is ~/backrs
  105. if backup_location == default_location:
  106. os.makedirs(backup_location)
  107. else:
  108. print(backup_location + " does not exist")
  109. sys.exit(1)
  110. # Output
  111. print("will save to "+backup_location)
  112. # Get Comment
  113. if prompt_for_comment:
  114. comment = get_comment()
  115. else:
  116. comment = ""
  117. # Set some varibles:
  118. # Set current working directory
  119. cwd = os.getcwd()
  120. # Set basename directory variable
  121. basename = os.path.basename(cwd)
  122. # Set current time in a possible dir format
  123. time = basename + "-" + datetime.datetime.now().strftime('%G-%b-%d-%I_%M%p_%S') + "-" + comment
  124. # Generate a hash of the current dir
  125. qhash = hashlib.sha1(cwd.encode("UTF-8")).hexdigest()[:7]
  126. # Set basehash var to basename and hash
  127. basehash = basename + "-" + qhash
  128. # Backup folder name will be /location/basename-hash/time
  129. # Add basehash to backup_location
  130. backup_location += "/" + basehash
  131. # We'll need a variable set to backup_location at the point later
  132. backbase = backup_location
  133. # At this point we know the save location like ~/backrs exists
  134. # We need to create the folder specifically for this folder
  135. # This creates ~/backrs/basename-hash
  136. if not os.path.exists(backup_location):
  137. os.makedirs(backup_location)
  138. # At time to backup_location to create a folder for this specific backup
  139. # backup_location is now, for example ~/backrs/basename-hash/basename-time-comment
  140. # Remember that "time" variable contains basename-time-comment
  141. backup_location += "/" + time
  142. # Create the basename-time-comment directory
  143. if not os.path.exists(backup_location):
  144. os.makedirs(backup_location)
  145. # This line does the actual backup, it copies the current directory to the backup location
  146. copy_tree(cwd, backup_location)
  147. # If use_compression was set, make a tarball of the backup
  148. # Then delete the original backup, then output that folder was backed up
  149. # Output varies depending on whether or not it was compressed
  150. if use_compression:
  151. make_tarfile(backbase+"/"+time+".tar.gz", backup_location)
  152. shutil.rmtree(backup_location)
  153. print("folder backed up to "+backbase+"/"+time+".tar.gz")
  154. else:
  155. print("folder backed up to "+backup_location)
  156. # We use a file called backtrack.txt to keep track of how many backups
  157. # there are and their locations, this is necessary for restor.py
  158. # It is stored in the backbase, eg ~/backrs/basename-hash
  159. vc_file = backbase+"/backtrack.txt"
  160. # If the file does not exist we create it using pickle
  161. # We only write the backup location or, if compressed, the tarball location
  162. if not os.path.exists(vc_file):
  163. fw = open(vc_file, 'wb')
  164. if not use_compression:
  165. data = [backup_location]
  166. else:
  167. data = [backbase+"/"+time+".tar.gz"]
  168. pickle.dump(data, fw)
  169. fw.close()
  170. # If backtrack.txt already exists we use pickle to set its contents to the data var which we will add to
  171. else:
  172. data = pickle.load(open(vc_file, "rb"))
  173. if not use_compression:
  174. data += [backup_location]
  175. else:
  176. data += [backbase+"/"+time+".tar.gz"]
  177. fw = open(vc_file, 'wb')
  178. pickle.dump(data, fw)
  179. fw.close()
  180. # Use the except KeyboardInterrupt on main trick to allow the user to C-c anytime
  181. if __name__ == "__main__":
  182. try:
  183. main()
  184. except KeyboardInterrupt:
  185. print()
  186. print("exit")
  187. sys.exit(0)