Files
omv_backup/omv_backup_v2.py
2025-01-11 03:13:20 +01:00

481 lines
20 KiB
Python

#!/usr/bin/env python3
import datetime
import logging
from paho.mqtt import client as mqtt_client
import getopt
import json
import time
import socket
import subprocess
import sys
import os
import re
import platform
import requests
import fnmatch
from wakeonlan import send_magic_packet
pid = os.getpid()
host = platform.node().lower()
cmnd = "ps -ef|grep omv_backups.py|grep -v grep |grep -v {}|wc -l".format(pid)
status, output = subprocess.getstatusoutput(cmnd)
def is_port_open(host, port):
try:
sock = socket.create_connection((host, port))
sock.close()
return True
except socket.error:
return False
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# doesn't even have to be reachable
s.connect(('192.168.77.1', 1))
IP = s.getsockname()[0]
print(IP)
print(output)
if int(output) > 0:
print("Running already!")
sys.exit()
broker = 'mqtt.home.lan'
port = 1883
topic_sum = "sectorq/omv/backups"
mqtt_username = 'jaydee'
mqtt_password = 'jaydee1'
print("1")
try:
opts, args = getopt.getopt(sys.argv[1:], "amftdr:b", ["command=", "help", "output="])
except getopt.GetoptError as err:
#usage()
sys.exit(2)
output = None
# QJ : getopts
_MODE = "manual"
_FIRST = _TEST = _RESTORE = _BACKUP = False
_EXECUTE = True
for o, a in opts:
if o == "-a":
_MODE = "auto"
elif o in ("-m", "--manual"):
_MODE = "manual"
elif o in ("-f", "--first"):
_FIRST = True
elif o in ("-t", "--test"):
_TEST = True
elif o in ("-r", "--restore"):
_RESTORE = True
_APP = a
print("RESTORE")
elif o in ("-b", "--backup"):
_BACKUP = True
elif o in ("-d", "--dry"):
_EXECUTE = False
print("2")
client = mqtt_client.Client()
client.username_pw_set(mqtt_username, mqtt_password)
client.connect(broker,1883,60)
backups = {
"nas": {
"login": "admin@nas.home.lan",
"jobs": {
"github":
{"source":"/share/Data/__GITHUB",
"exclude":"",
"active": True
},
"photo": {
"source":"/share/Photo/Years",
"exclude":"",
"active":True
}
}
},
"m-server":{
"login": "jd@m-server.home.lan",
"jobs": {
"docker_data":{
"source":"/share/docker_data/",
"exclude":"",
"active":True
},
"fail2ban":{
"source":"/etc/fail2ban/",
"exclude":"",
"active":True
}
}
},
"rpi5.home.lan":{
"docker_data":{
"source":"/share/docker_data/",
"exclude":"",
"active":True
},
"fail2ban":{
"source":"/etc/fail2ban/",
"exclude":"",
"active":True
}
}
}
BACKUP_FS = "/srv/dev-disk-by-uuid-2f843500-95b6-43b0-bea1-9b67032989b8"
BACKUP_HOST = "omv.home.lan"
#BACKUP_HOST = "morefine.home.lan"
print("Test connection")
print("3")
hm = socket.gethostbyaddr(BACKUP_HOST)
print("Starting")
print(_RESTORE)
if _RESTORE:
print("Starting Restore")
now = datetime.datetime.now()
STARTTIME = now.strftime("%Y-%m-%d_%H:%M:%S")
if _APP == "all":
_APP = ["nginx","ha","gitlab","mailu","bitwarden","esphome","grafana","ingluxdb","kestra","matter-server","mosquitto","octoprint","octoprint2","pihole","unify_block","webhub"]
else:
_APP = _APP.split(",")
for app in _APP:
topic = "sectorq/omv/restore/{}".format(app)
client.connect(broker,1883,60)
msg = {"status":"inactive","bak_name":app,"start_time":"inactive","end_time":"inactive","progress":0}
client.publish(topic, json.dumps(msg))
client.disconnect()
now = datetime.datetime.now()
DATETIME = now.strftime("%Y-%m-%d_%H-%M-%S")
BACKUP_HOST = f"jd@{host}"
BACKUP_DEVICE = "/srv/dev-disk-by-uuid-2f843500-95b6-43b0-bea1-9b67032989b8"
BACKUP_DIR = f"/backup/{host}"
if app == "fail2ban":
print("?>?????")
NEW_BACKUP_DIR = f"/backup/m-server/fail2ban/latest/"
SOURCE_DIR = f"/etc/fail2ban"
else:
NEW_BACKUP_DIR = f"/backup/m-server/docker_data/latest/{app}"
SOURCE_DIR = f"/share/docker_data/"
if _FIRST:
BACKUP_PATH="{}/initial".format(BACKUP_DIR)
else:
BACKUP_PATH="{}/{}".format(BACKUP_DIR, DATETIME)
LATEST_LINK="{}/latest".format(BACKUP_DIR)
FULL_BACKUP_LATEST = f"{NEW_BACKUP_DIR}/latest"
LATEST_LINK = f"/{host}/{app}/latest"
msg = {"status":"started","bak_name":app,"start_time":DATETIME,"end_time":"in progress", "progress":0}
client.connect(broker,1883,60)
client.publish(topic, json.dumps(msg),2,True)
client.disconnect()
print("Create backup dir")
print(cmnd)
#cmnd = "rsync -av --delete {}/ --link-dest {} --exclude=\".cache\" {}".format(SOURCE_DIR, LATEST_LINK, BACKUP_PATH)
if app == "heimdall":
print("Stopping docker")
cmnd = "docker stop heimdall"
status, output = subprocess.getstatusoutput(cmnd)
cmnd = f"rsync -avz --delete rsync://{BACKUP_HOST}{NEW_BACKUP_DIR} {SOURCE_DIR}"
ans = "y"
print(cmnd)
print("Sync files")
if _TEST:
ans = input("continue?") or "n"
if ans == "y" and _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
entries = ["Home Assistant","Nginx Proxy Manager","Portainer","Roundcube","Authentik","Kestra"]
for e in entries:
cmnd = f"sqlite3 /share/docker_data/heimdall/config/www/app.sqlite \"SELECT url FROM items WHERE title = '{e}'\""
print(cmnd)
status, output = subprocess.getstatusoutput(cmnd)
regex = re.compile(r'[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}')
contents = re.sub(regex, IP , output)
cmnd = f"sqlite3 /share/docker_data/heimdall/config/www/app.sqlite \"UPDATE items SET url = '{contents}' WHERE title = '{e}'\""
print(cmnd)
status, output = subprocess.getstatusoutput(cmnd)
cmnd = "docker start heimdall"
status, output = subprocess.getstatusoutput(cmnd)
if app == "ha":
print("Stopping docker")
cmnd = "docker stop heimdall"
status, output = subprocess.getstatusoutput(cmnd)
cmnd = f"rsync -avz --delete rsync://{BACKUP_HOST}{NEW_BACKUP_DIR} {SOURCE_DIR}"
ans = "y"
print(cmnd)
print("Sync files")
if _TEST:
ans = input("continue?") or "n"
if ans == "y" and _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
print("Start docker")
cmnd = "docker start heimdall"
status, output = subprocess.getstatusoutput(cmnd)
if app == "fail2ban":
print("Stopping docker")
cmnd = f"rsync -avz --delete rsync://{BACKUP_HOST}{NEW_BACKUP_DIR} {SOURCE_DIR}"
ans = "y"
print(cmnd)
print("Sync files")
if _TEST:
ans = input("continue?") or "n"
if ans == "y" and _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
print("Start docker")
cmnd = "docker start heimdall"
status, output = subprocess.getstatusoutput(cmnd)
elif app == "nginx":
print("Stopping docker")
cmnd = "docker stop nginx-app-1"
status, output = subprocess.getstatusoutput(cmnd)
cmnd = f"rsync -avz --delete rsync://{BACKUP_HOST}{NEW_BACKUP_DIR} {SOURCE_DIR}"
ans = "y"
print(cmnd)
print("Sync files")
if _TEST:
ans = input("continue?") or "n"
if ans == "y" and _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
domains = ["sectorq.eu","gitlab.sectorq.eu","ha.sectorq.eu","mail.sectorq.eu","pw.sectorq.eu","semaphore.sectorq.eu","kestra.sectorq.eu","auth.sectorq.eu"]
for d in domains:
cmnd = f'sqlite3 /share/docker_data/nginx/data/database.sqlite "UPDATE proxy_host SET forward_host = \'{IP}\' WHERE domain_names = \'[\\"{d}\\"]\'"'
print(cmnd)
status, output = subprocess.getstatusoutput(cmnd)
cmnd = 'egrep -l "# kestra.sectorq.eu|# auth.sectorq.eu|# ha.sectorq.eu|# pw.sectorq.eu|# semaphore.sectorq.eu|# sectorq.eu|# gitlab.sectorq.eu|# ha.sectorq.eu" /share/docker_data/nginx/data/nginx/proxy_host/*'
status, output = subprocess.getstatusoutput(cmnd)
print(output.splitlines())
for file in output.splitlines():
print(file)
f = open(file)
contents = f.read()
f.close()
regex = re.compile(r'\n\s+set \$server\s+\"\w+.\w+.\w+.\w+\";')
contents = re.sub(regex, f'\n set $server \"{IP}\";', contents)
#print(contents)
print(regex)
f = open(file, "w")
contents = f.write(contents)
f.close()
status, output = subprocess.getstatusoutput(cmnd)
print("Starting docker")
cmnd = "docker start nginx-app-1"
status, output = subprocess.getstatusoutput(cmnd)
else:
cmnd = f"rsync -avz --delete rsync://{BACKUP_HOST}{NEW_BACKUP_DIR} {SOURCE_DIR}"
ans = "y"
print(cmnd)
print("Sync files")
if _TEST:
ans = input("continue?") or "n"
if ans == "y" and _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
now = datetime.datetime.now()
ENDTIME = now.strftime("%Y-%m-%d_%H:%M:%S")
msg = {"status":"finished","bak_name":app,"start_time":DATETIME,"end_time":ENDTIME,"progress":0}
client.connect(broker,1883,10)
client.publish(topic, json.dumps(msg),2,True)
client.disconnect()
now = datetime.datetime.now()
ENDJOB = now.strftime("%Y-%m-%d_%H:%M:%S")
print("Sending finished status")
msg = {"mode":_MODE,"status":"finished","bak_name":"complete","start_time":STARTTIME,"end_time":ENDJOB,"progress":0,"used_space":"?"}
print(msg)
client.connect(broker,1883,10)
client.publish(topic_sum, json.dumps(msg),2,True)
client.disconnect()
if _MODE == "auto":
cmnd = "ssh root@omv.home.lan 'systemctl suspend &'"
status, output = subprocess.getstatusoutput(cmnd)
if _BACKUP:
while True:
directory = '/backups/'
count = len(fnmatch.filter(os.listdir(directory), '*'))
print('File Count:', count)
if count == 0:
time.sleep(10)
continue
else:
finished = []
now = datetime.datetime.now()
STARTTIME = now.strftime("%Y-%m-%d_%H:%M:%S")
msg = {"mode":_MODE, "status":"started","bak_name":"complete","host":"","cur_job":"","start_time":STARTTIME,"end_time":"in progress","progress":0,"finished":",".join(finished)}
client.publish(topic_sum, json.dumps(msg),2,True);
client.disconnect()
# iterate over files in
# that directory
for filename in os.scandir(directory):
if filename.is_file():
print(filename.path)
print(filename.name)
host = filename.name
print("Backup")
for b in backups[host]["jobs"]:
topic = "sectorq/omv/backups"
if not backups[host]["jobs"][b]["active"]:
print("Backup {} is not active!".format(b))
client.connect(broker,1883,60)
msg = {"status":"inactive","bak_name":b,"start_time":"inactive","end_time":"inactive","progress":0}
client.publish(topic, json.dumps(msg))
client.disconnect()
continue
SOURCE_DIR = backups[host]["jobs"][b]["source"]
now = datetime.datetime.now()
BACKUP_HOST = backups[host]["login"]
BACKUP_DEVICE = "/srv/dev-disk-by-uuid-2f843500-95b6-43b0-bea1-9b67032989b8"
BACKUP_DIR = f"{BACKUP_HOST}:{SOURCE_DIR}"
BACKUP_ROOT = f"{BACKUP_DEVICE}/backup/{host}/{b}"
DATETIME = now.strftime("%Y-%m-%d_%H-%M-%S")
if _FIRST:
NEW_BACKUP_DIR = f"{BACKUP_ROOT}/initial"
else:
NEW_BACKUP_DIR = f"{BACKUP_ROOT}/{DATETIME}"
FULL_BACKUP_LATEST = f"{BACKUP_ROOT}/latest"
# msg = {"status":"started","bak_name":b,"start_time":DATETIME,"end_time":"in progress", "progress":0}
msg = {"mode":_MODE, "status":"started","bak_name":"complete","host":host,"cur_job":b,"start_time":STARTTIME,"end_time":"in progress","progress":0,"finished":",".join(finished)}
client.connect(broker,1883,60)
client.publish(topic, json.dumps(msg),2,True)
client.disconnect()
cmnd = "mkdir -p " + NEW_BACKUP_DIR
print(cmnd)
if _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
print(output)
print(status)
print("Create backup dir")
#cmnd = "rsync -av --delete {}/ --link-dest {} --exclude=\".cache\" {}".format(SOURCE_DIR, LATEST_LINK, BACKUP_PATH)
if _FIRST:
cmnd = f"rsync -avz --delete {SOURCE_DIR} --exclude=\"jellyfin/cache/transcodes\" --exclude=\"gitlab/logs/prometheus\" --exclude=\"home-assistant.log\" --exclude=\"gitlab/logs/*\" --exclude=\"esphome/config/.esphome\" --exclude=\".cache\" --exclude=\".git\" --exclude=\"var_lib_motioneye\" rsync://{BACKUP_HOST}{BACKUP_PATH}"
else:
cmnd = f"rsync -avz --delete {BACKUP_DIR} --link-dest {FULL_BACKUP_LATEST} --exclude=\"jellyfin/cache/transcodes\" --exclude=\"gitlab/logs/prometheus\" --exclude=\"home-assistant.log\" --exclude=\"gitlab/logs/*\" --exclude=\"esphome/config/.esphome\" --exclude=\".cache\" --exclude=\".git\" --exclude=\"var_lib_motioneye\" {NEW_BACKUP_DIR}"
ans = "y"
print(cmnd)
print("Sync files")
#input("??????")
if _TEST:
ans = input("continue?") or "n"
if ans == "y" and _EXECUTE:
# rsync --info=progress2 -avz --delete /share/docker_data/ --link-dest /m-server/docker_data/latest --exclude="gitlab/data/" --exclude="esphome/config/.esphome" --exclude="gitlab/logs/prometheus" --exclude=".cache" --exclude=".git" --exclude="var_lib_motioneye" /m-server/m-server/docker_data/newone1
# input("????")
status, output = subprocess.getstatusoutput(cmnd)
#proc = subprocess.Popen(cmnd,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,cwd = "/myapps/",shell=True)
cmnd = f"rm -rf {FULL_BACKUP_LATEST}"
print(cmnd)
print("Removing latest link")
# input("????")
if _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
if _FIRST:
cmnd = f"cd {BACKUP_ROOT}; ln -s initial latest"
else:
cmnd = f"cd {BACKUP_ROOT}; ln -s {DATETIME} latest"
print("Creating new latest link")
#print(cmnd)
# input("????")
if _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
#Remove old
print("Removing old dirs")
# input("????")
#cmnd = "find {} -maxdepth 1 -type d -mtime +30 -exec rm -rf {{}} \;".format(BACKUP_DIR)
cmnd = f"cd {BACKUP_ROOT} find ./ -maxdepth 1 -type d -mmin +30 -exec rm -rf {{}} \\;"
#print(cmnd)
# input("????")
if _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
now = datetime.datetime.now()
ENDTIME = now.strftime("%Y-%m-%d_%H:%M:%S")
#msg = {"status":"finished","bak_name":b,"start_time":DATETIME,"end_time":ENDTIME,"progress":0}
finished.append(b)
msg = {"mode":_MODE, "status":"finished","bak_name":"complete","host":host,"cur_job":b,"start_time":ENDTIME,"end_time":"in progress","progress":0,"finished":",".join(finished)}
client.connect(broker,1883,10)
client.publish(topic, json.dumps(msg),2,True)
client.disconnect()
print("Getting size of FS")
cmnd = "df -h /srv/dev-disk-by-uuid-2f843500-95b6-43b0-bea1-9b67032989b8|awk '{ print $3 }'|tail -1"
print(cmnd)
status, output = subprocess.getstatusoutput(cmnd)
used_space = (output.split())[0]
now = datetime.datetime.now()
ENDJOB = now.strftime("%Y-%m-%d_%H:%M:%S")
print("Size : {}".format(used_space))
print("Sending finished status")
#msg = {"mode":_MODE,"status":"finished","bak_name":"complete","start_time":STARTTIME,"end_time":ENDJOB,"progress":0,"used_space":used_space}
msg = {"mode":_MODE, "status":"finished","bak_name":"complete","host":host,"cur_job":b,"start_time":STARTTIME,"end_time":ENDTIME,"progress":0,"finished":",".join(finished)}
print(msg)
client.connect(broker,1883,10)
client.publish(topic_sum, json.dumps(msg),2,True)
client.disconnect()
os.remove(filename.path)
# if _MODE == "auto":
# hostup = True
# cmnd = "ssh root@omv.home.lan 'systemctl suspend &'"
# status, output = subprocess.getstatusoutput(cmnd)
# while hostup:
# #HOST_UP = os.system(f"ping -c 1 -w 2 omv.home.lan") == 0
# cmnd = f"ping -c 1 -w 2 {BACKUP_HOST}"
# status, output = subprocess.getstatusoutput(cmnd)
# # print(status)
# # print(output)
# if status == 0:
# print(f"Backup host up, waiting - {n}\r", end="")
# time.sleep(5)
# n += 1
# else:
# print("Backup host down " )
# hostup = False
# try:
# url = "http://m-server.home.lan:8123/api/webhook/-0eWYFhSTzdusAO8jwQS9t1AT?mode=off"
# x = requests.post(url)
# print(x.text)
# except:
# pass