Over my Christmas vacation I released a new technical project in its own repo onto my GitHub profile: Easy Home Directory Backup. It’s a Command Line Interface (CLI) program that runs on any Linux distribution in the Terminal. It performs a local backup of the user’s Home Directory. I developed the program in the Python programming language. In this post I will discuss the following topics about this web application:
- Why I created it
- The code running it
- My future plans for the application
Why I Created Easy Home Directory Backup
I created Easy Home Directory Backup because I wanted to improve upon a Python script I made to back up specific directories in my Home Directory. That script is also on my GitHub account which you can view here. I used a separate disk installed in my computer to hold the backups.
While the script works great for me, I wanted to see if I could enhance it to work for other users of Linux distributions. Finally, I developed the application to continue on my current path to improve my Python programming skill-set.
The Code Running Easy Home Directory Backup To Validate User Input
During my design process for the program I decided to run the program in the Terminal using the Command Line Interface (CLI). I also wanted the user to provide the path of the local backup device. This imposed a problem I had to protect against: Improper user input.
A user could input gibberish or a different value than what the program required. To keep the program from crashing, and inform the user of their mistake, I used “Try/Except” statements for user input. Here’s an truncated example from the main.py file:
try:
menu_choice: int = int(input("Enter your choice here: "))
if menu_choice == 0:
clear_screen()
main_menu()
elif menu_choice == 1:
full_backup()
elif menu_choice == 2:
partial_backup()
else:
print()
print(center_text("*" * 80))
print(center_text("!! Enter either 0, 1, or 2 !!"))
print()
print(center_text(" -- The menu will reappear in a few seconds -- "))
print(center_text("*" * 80))
sleep_print()
clear_screen()
one_time_backup()
except ValueError:
print()
print(center_text("*" * 80))
print(center_text("!! This menu only accepts numbers !!"))
print()
print(center_text("-- The menu will reappear in a few seconds --"))
print(center_text("*" * 80))
sleep_print()
clear_screen()
one_time_backup()
If a user entered a “4” the Else clause would fire off and a message would appear in the Terminal alert the user of their error. Thus, the user would have another chance to input the correct information in the menu. However, if the user entered an “a” that would fire off the Except statement regarding a ValueError. Again, a message appears in the Terminal informing the user to only enter numbers. Finally, the menu appears again.
Not only did I have to validate user input, I had to validate if the path to the backup device was valid. Well, not only valid, but also a Linux-compatible file system, has read/write permissions, and mounted to name a few. I created a separate module (called validate_backup_device.py) to validate the path. I’ll explain a small portion of that code:
def check_backup_device_permissions(backup_path: Path) -> bool:
"""Checks if the user-supplied backup device for read and write permissions.
:param backup_path: The user-supplied path to the backup device.
:return: True if the device has both read and write permissions, but False if it doesn't.
"""
path_stats = os.stat(backup_path)
mode = path_stats.st_mode
return bool(mode & stat.S_IRUSR) and bool(mode & stat.S_IWUSR)
In the function above I check the backup device has read/write permissions using the “stat” command to pull the file information about the backup device. Then I check the device’s mode to determine it has both read and write permissions. That’s what the “S_IRUSR” and the “S_IWUSR” options mean. The function returns True if both are correct, but False if not.
I then run the function in one of the many conditional statements listed in the module:
elif not check_backup_device_permissions(path):
print()
print(center_text(f"Backup path {path} doesn't have read/write permissions."))
print(center_text("Please add the read/write permissions to the backup device and run this program again."))
sleep_print()
print()
print(center_text("*" * 80))
print(center_text("!! Program exited !!"))
print(center_text("*" * 80))
print()
sys.exit()
If the function returns False then the message displays in the Terminal about why the program cannot continue, and gives the user a suggestion on how to resolve the issue.
This is just a snippet of code running the program. Please go through the repo to review the entire codebase.
The Code Running The Backup
I developed the program around the “rsync” command to perform the backups. however, I had to change the module I used in my script to call/run the command in the program. Before I used the “os.system” module, but during my research using Gemini I found out using the “subprocess.run” module was better to use. Because it offers secure command execution. Here’s what that code looks like:
"""rsync options
a = Archive
z = Compression
"""
subprocess.run(["rsync", "-az",
f"--log-file={path}/easy_home_directory_backup_{formatted_today_date}_log_file",
home_dir_path, backup_device_path])
That snippet of code performs a full backup of the user’s Home Directory. (The program also offers an option for a partial backup.) The flags in the code both archives and compresses the data. Finally, it creates a log file using a specific path name, including the date of the backup, and contains a list of all the files in the directories.
My Future Plans For This Application
I do want to add the ability for users to schedule a backup using crontab. However, I will have to work on implementing this feature as a user can only have one crontab. Thus, my code would need to append the file.
Another feature I’m thinking about creating is allow users to delete the backups they create with the program. Again, that requires more testing.
Once I enable one or more of those features I’ll post an update on my website.