1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
""" Python script to automate regular backups via external HDD and server upload"""
# TODO: bash instead? >.>
# TODO: Generate and add package list and basic system info to sync
import re # Regex for str parsing
import subprocess # Executes OS functions
from datetime import datetime # Handles dir_date creation
from pathlib import Path # Handles paths nicer
from typing import List # Handles lists nicer
# GLOBALS
BACKUP_DIRECTORIES: List[Path] = [Path("/etc/"), Path("/home/schark/"]
BACKUP_DESTINATION: Path = Path("/mnt/backup/")
BACKUP_UUID: Path = Path("e2408718-ac56-4d92-a4e3-4a775b60b347")
UPLOAD_SERVER: str = "root@schark.online"
UPLOAD_DIR: Path = Path("/samurai-backup")
UPLOAD_DESTINATION: Path = Path(f"{UPLOAD_SERVER}:{UPLOAD_DIR}")
LOGFILE: Path = Path(f"/home/schark/BACKUP_{datetime.today().strftime('%Y-%m-%d')}.log")
IGNORE_LIST: List[Path] = [Path("*cache*"), Path("*Cache*"), Path("*.config/discord*"), Path("*.dbus*"), Path("*.fontconfig*"), Path("*.java*"), Path("*.local*"), Path("*.mozilla*"), Path("*.steam*"), Path("*.thunderbird*"), Path("*.var*"), Path("*/videos/*")]
def _logger(msg: str):
print(f"\n[Backup] {msg}")
with open(LOGFILE, 'a+') as file:
file.write(f"\n[Backup] {msg}")
_logger("Starting logging service...")
# ensure /mnt/backup is mounted
_logger(f"Checking that {BACKUP_DESTINATION} is mounted...")
try:
subprocess.run(['findmnt', '-rn', '-S', BACKUP_UUID, BACKUP_DESTINATION], check=True)
except subprocess.CalledProcessError:
_logger(f"{BACKUP_DESTINATION} is not mounted. Attempting to mount...")
subprocess.run(['mount', f'UUID={BACKUP_UUID}', BACKUP_DESTINATION])
# check storage use of BACKUP_DESTINATION
_logger(f"Checking disk usage of {BACKUP_DESTINATION}...")
output = subprocess.run(['df', BACKUP_DESTINATION, '--output=pcent'], capture_output=True, text=True)
percent = int(re.findall(r'\d+', output.stdout.strip().split('\n')[1])[0])
if percent >= 90:
_logger(f"Disk usage >90%! Cleaning oldest backup...")
output = subprocess.run(['ls', BACKUP_DESTINATION], capture_output=True, text=True)
oldest = Path(BACKUP_DESTINATION / output.stdout.strip().split('\n')[0])
subprocess.run(['rm', '-rf', oldest])
_logger(f"{output} has been deleted from the system.")
# rsync /etc/* and /home/schark/* to /mnt/backup/{date}
_logger(f"Syncing the following directories: {BACKUP_DIRECTORIES}")
date_dir = Path(BACKUP_DESTINATION / datetime.today().strftime('%Y-%m-%d'))
with open("/home/schark/ignore-list", "w+") as file:
for path in IGNORE_LIST:
file.write(f"{path}\n")
try:
_logger(f"Syncing with drive {date_dir}...")
for path in BACKUP_DIRECTORIES:
subprocess.run(['rsync', '-aH', '--progress', '--exclude-from=/home/schark/ignore-list', path, date_dir], text=True)
except subprocess.CalledProcessError:
raise Exception(f"Error in syncing!")
# change ownership and umount
_logger(f'Changing ownership on drive...')
subprocess.run(['chown', 'schark', '-R', date_dir])
subprocess.run(['chgrp', 'schark', '-R', date_dir])
_logger(f'Unmounting drive...')
subprocess.run(['umount', BACKUP_DESTINATION])
# rsync /etc/* and /home/schark/* to root@schark.online:/samurai-backup/{$DATE}
date_dir = Path(UPLOAD_DESTINATION / datetime.today().strftime('%Y-%m-%d'))
try:
_logger(f"Uploading to the server {date_dir}...")
for path in BACKUP_DIRECTORIES:
# NOTE: It is important we run this as root, as we have a special ssh key configured to complete
# this command without need for an additional password entry
subprocess.run(['rsync', '-aH', '--progress', '--exclude-from=/home/schark/ignore-list', path, date_dir], text=True)
except subprocess.CalledProcessError:
raise Exception(f"Error in uploading!")
# delete old backup from root@schark.online:/samurai-backup
def _run_ssh_command(cmd):
try:
return subprocess.run(['ssh', UPLOAD_SERVER, cmd], capture_output=True, text=True, check=True)
except:
raise Exception(f"Failed ssh command [{cmd}]")
uploaded_backups = _run_ssh_command(f'ls {UPLOAD_DIR}')
parse_backups = uploaded_backups.stdout.strip().split('\n')
if len(parse_backups) > 1:
old_backup = Path(UPLOAD_DIR / parse_backups[0])
_logger(f'Deleting old backup {old_backup}')
_run_ssh_command(f'rm -rf {old_backup}')
# clean up uneeded files
_logger(f'Cleaning up ignore-list...')
subprocess.run(['rm', '/home/schark/ignore-list'])
_logger('Backup successful!')
_logger('Awaiting manual termination...\n')
while True:
continue
|