Compare commits

...

95 Commits

Author SHA1 Message Date
122ef64ad7 build 2025-09-09 11:20:50 +02:00
99f673996e build 2025-09-03 00:44:11 +02:00
68e23da03b build 2025-09-03 00:42:59 +02:00
1c30fb9995 build 2025-09-03 00:42:34 +02:00
b94b61ba5d build 2025-09-03 00:42:04 +02:00
26c3245cf3 build 2025-09-03 00:41:26 +02:00
1510dc2e8d build 2025-09-03 00:40:13 +02:00
d949188a34 build 2025-09-03 00:39:24 +02:00
298c9aee23 build 2025-09-02 22:52:54 +02:00
808f92c4d1 build 2025-09-02 22:50:36 +02:00
43f6d2abcb build 2025-09-02 22:47:01 +02:00
81c7ead7b2 build 2025-09-02 22:44:12 +02:00
cd818da774 build 2025-09-02 22:26:51 +02:00
a1dfbd664c build 2025-09-02 22:23:40 +02:00
571219881d build 2025-09-01 14:01:43 +02:00
0c07fde85a build 2025-09-01 13:58:44 +02:00
f46eacf627 build 2025-09-01 13:54:13 +02:00
92226734ef build 2025-09-01 13:52:25 +02:00
d3359e9a68 build 2025-09-01 13:49:24 +02:00
fdbe4eebe1 build 2025-09-01 13:32:58 +02:00
3a3faad97e build 2025-09-01 13:22:21 +02:00
8be3e20523 build 2025-09-01 13:20:29 +02:00
126ab1813b build 2025-09-01 13:19:35 +02:00
28efc95b4d build 2025-09-01 13:18:50 +02:00
f6a106fd91 build 2025-09-01 13:17:03 +02:00
bbe4d72666 build 2025-09-01 12:47:25 +02:00
b536c8ecb1 build 2025-09-01 12:37:22 +02:00
7a8130c3f0 build 2025-09-01 12:35:46 +02:00
5756798269 build 2025-09-01 12:34:02 +02:00
d6af0c24b5 build 2025-09-01 12:30:32 +02:00
6f9a2bba67 build 2025-09-01 12:24:42 +02:00
11bc56ecb3 build 2025-09-01 12:23:17 +02:00
b4032eca7e Merge branch 'main' of gitlab.sectorq.eu:jaydee/omv_backup 2025-09-01 12:22:38 +02:00
f75ac2eb79 build 2025-09-01 12:22:00 +02:00
a555567c4b Update .gitlab-ci.yml file 2025-09-01 12:21:49 +02:00
de63a1e9aa build 2025-09-01 12:19:30 +02:00
d3eab9f50e build 2025-09-01 12:15:01 +02:00
72a2fa5710 build 2025-09-01 10:36:56 +02:00
b3be50bfdd build 2025-09-01 10:27:40 +02:00
f32ada9ad5 build 2025-09-01 10:16:52 +02:00
d6492ebf80 build 2025-08-27 04:38:57 +02:00
7cee7570b4 added v3 2025-08-27 04:37:26 +02:00
ce7c855808 added v3 2025-08-27 04:35:27 +02:00
d3e15e973a build 2025-08-26 16:00:01 +02:00
cee1cf353c build 2025-08-26 15:58:31 +02:00
f9e7654c47 build 2025-08-26 15:51:51 +02:00
1816bd516c build 2025-08-26 15:43:06 +02:00
dad1c49409 build 2025-08-26 15:38:20 +02:00
66644b6bb9 build 2025-08-26 15:30:19 +02:00
781a74ba20 build 2025-08-26 15:27:23 +02:00
29cb028c5c build 2025-08-26 15:24:05 +02:00
1667ce0079 build 2025-08-26 15:18:01 +02:00
984b3e09a0 build 2025-08-26 15:15:26 +02:00
64bea0a833 build 2025-08-26 15:07:51 +02:00
dd51f14699 build 2025-08-26 15:04:35 +02:00
35aa35a3b5 build 2025-08-26 14:57:00 +02:00
4f868047be build 2025-08-26 14:48:39 +02:00
ba09aeb502 build 2025-08-26 13:38:19 +02:00
8e9e915cdc build 2025-08-26 13:35:58 +02:00
1714817673 build 2025-08-26 12:40:37 +02:00
9cf123382e build 2025-08-26 12:38:36 +02:00
ad5e9e698f build 2025-08-26 12:36:32 +02:00
017653d34c build 2025-08-26 12:27:39 +02:00
cb2e06684b build 2025-08-26 12:21:53 +02:00
57ae295d22 build 2025-08-26 12:19:11 +02:00
27d0e121cd build 2025-08-26 12:14:18 +02:00
de97d2574a build 2025-08-26 12:12:17 +02:00
ca1c3a563f build 2025-08-26 11:59:19 +02:00
0d6ec1fbc1 build 2025-08-26 11:52:34 +02:00
a11076ef04 build 2025-08-26 11:52:00 +02:00
d6a3d3765b build 2025-08-26 11:49:23 +02:00
eababc2f7c build 2025-08-26 11:47:22 +02:00
528995cf75 build 2025-08-26 11:45:38 +02:00
695219f6e0 build 2025-08-26 11:43:14 +02:00
d6e41e2ceb build 2025-08-26 11:42:09 +02:00
0e6ebc0516 build 2025-08-26 11:38:42 +02:00
7c2c99fbef build 2025-08-26 11:15:05 +02:00
425f9aebaa build 2025-08-26 10:52:52 +02:00
f8ae15d195 build 2025-08-26 10:36:01 +02:00
c5d915ad87 build 2025-08-26 10:35:02 +02:00
9e7b774601 build 2025-08-26 10:34:17 +02:00
0eb8cc4afe build 2025-08-26 09:33:50 +02:00
ea1d933386 build 2025-08-26 09:29:46 +02:00
a407ee9339 build 2025-08-26 09:04:03 +02:00
1768fe1369 build 2025-08-26 09:03:02 +02:00
2c7dba48da build 2025-08-26 09:01:13 +02:00
bcd2b5de9e build 2025-08-26 09:01:00 +02:00
986f75e1dc build 2025-08-26 08:57:47 +02:00
2af8758261 build 2025-08-26 08:43:36 +02:00
fd94729a05 build 2025-08-23 11:43:33 +02:00
fb4acad563 build 2025-08-21 13:00:22 +02:00
a57c73cc74 build 2025-08-21 12:13:54 +02:00
4327d7490b build 2025-08-21 11:50:34 +02:00
c72aa231c7 build 2025-08-21 11:45:53 +02:00
169d43a75c build 2025-08-21 10:11:45 +02:00
4 changed files with 410 additions and 304 deletions

View File

@@ -25,6 +25,6 @@ build-job: # This job runs in the build stage, which runs first.
script: script:
- column=":" - column=":"
- echo "${flow_id}" - echo "${flow_id}"
- curl -X POST https://kestra.sectorq.eu/api/v1/executions/webhook/jaydee/ansible-all/${flow_id} -d '{"tag":["setup","omv_backup"],"target":["servers"]}' -H "Content-Type${column} application/json" - curl -X POST https://kestra.sectorq.eu/api/v1/executions/webhook/jaydee/ansible-all/${flow_id} -d '{"tag":["omv_backup"],"target":["servers"]}' -H "Content-Type${column} application/json"
rules: rules:
- if: '$CI_COMMIT_MESSAGE =~ /build/' - if: '$CI_COMMIT_MESSAGE =~ /build/'

View File

@@ -47,6 +47,13 @@ cmnd = f"zip -P {PASSWORD} authentik_{DATETIME}.sql.zip authentik_{DATETIME}.sql
status, output = subprocess.getstatusoutput(cmnd) status, output = subprocess.getstatusoutput(cmnd)
os.remove(f"authentik_{DATETIME}.sql") os.remove(f"authentik_{DATETIME}.sql")
print("Backup Zabbix")
cmnd = f"docker exec zabbix-server-db-server-1 sh -c 'pg_dump -h localhost -p 5432 -U zabbix -d zabbix' > /share/docker_data/__backups/zabbix_{DATETIME}.sql"
status, output = subprocess.getstatusoutput(cmnd)
cmnd = f"zip -P {PASSWORD} zabbix_{DATETIME}.sql.zip zabbix_{DATETIME}.sql"
status, output = subprocess.getstatusoutput(cmnd)
os.remove(f"zabbix_{DATETIME}.sql")
print("Backup portainer") print("Backup portainer")
headers = { headers = {

15
exclude.txt Normal file
View File

@@ -0,0 +1,15 @@
ha/home-assistant_v2.db
jellyfin/cache/transcodes
.@__thumb/*
ha/home-assistant.log
gitlab/logs/*
esphome/config/.esphome
.cache
.git
var_lib_motioneye/*
*/.esphome/build/*
nextcloud/mariadb/*
zabbix-server/postgres-data/*
gitea-runner/*
immich/library/*

View File

@@ -17,11 +17,21 @@ import fnmatch
import yaml import yaml
import paramiko import paramiko
import shutil import shutil
import signal
import paho.mqtt.client as mqtt
#import numpy as np #import numpy as np
def signal_handler(sig, frame):
print('You pressed Ctrl+C!')
conn.close()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
file_path = os.path.realpath(__file__) file_path = os.path.realpath(__file__)
dir_path = os.path.dirname(file_path) dir_path = os.path.dirname(file_path)
VERSION="1.0.9" VERSION="1.0.10"
# print(file_path) # print(file_path)
# print(dir_path) # print(dir_path)
os.chdir(dir_path) os.chdir(dir_path)
@@ -42,6 +52,7 @@ status, output = subprocess.getstatusoutput(cmnd)
if int(output) > 0: if int(output) > 0:
print("Running already!") print("Running already!")
sys.exit() sys.exit()
def is_port_open(host, port): def is_port_open(host, port):
try: try:
sock = socket.create_connection((host, port)) sock = socket.create_connection((host, port))
@@ -99,9 +110,6 @@ for o, a in opts:
elif o in ("-r", "--restore"): elif o in ("-r", "--restore"):
_RESTORE = True _RESTORE = True
_APP = a _APP = a
print("RESTORE")
elif o in ("-b", "--backup"):
_BACKUP = True
elif o in ("-D", "--dry"): elif o in ("-D", "--dry"):
_EXECUTE = False _EXECUTE = False
elif o in ("-T", "--dry"): elif o in ("-T", "--dry"):
@@ -125,13 +133,23 @@ logging.info("script started")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
client_id = "dasdasdasd333" client_id = "dasdasdasd333"
try: # try:
client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION1, client_id) # client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION1, client_id)
except: # except:
client = mqtt_client.Client() # client = mqtt_client.Client()
client.username_pw_set(mqtt_username, mqtt_password) # client.username_pw_set(mqtt_username, mqtt_password)
logging.info(f"Start OMV Backup")
BROKER = 'mqtt.home.lan' # e.g., 'mqtt.example.com'
PORT = 1883 # Typically 8883 for secure MQTT
TOPIC = 'sectorq/backups/start'
USERNAME = 'jaydee'
PASSWORD = 'jaydee1'
USE_TLS = False # Set to False if not using TLS
backups = { backups = {
"nas": { "nas": {
"login": "admin@nas.home.lan", "login": "admin@nas.home.lan",
@@ -185,11 +203,14 @@ logging.info("Test connection")
hm = socket.gethostbyaddr(BACKUP_HOST) hm = socket.gethostbyaddr(BACKUP_HOST)
logging.info(_RESTORE) logging.info(_RESTORE)
def send_mqtt_message(msg): def send_mqtt_message(topic,msg,qos=0,retain=False):
client2 = mqtt.Client()
client2.username_pw_set("jaydee", "jaydee1")
try: try:
client.connect(broker,1883,60) client2.connect("mqtt.home.lan",1883,60)
client.publish(topic, json.dumps(msg)) client2.publish(topic, json.dumps(msg), qos=qos, retain=retain)
client.disconnect() client2.disconnect()
logging.info(f"Message1 sent {topic}, {msg}")
except ValueError as e: except ValueError as e:
logging.error("Failed to send") logging.error("Failed to send")
print("Failed to send") print("Failed to send")
@@ -218,11 +239,13 @@ if _STOP:
continue continue
cmnd = f"docker stop {c.split()[-1]}" cmnd = f"docker stop {c.split()[-1]}"
status, running_containers = subprocess.getstatusoutput(cmnd) status, running_containers = subprocess.getstatusoutput(cmnd)
if _RESTORE: def restore_job(_APP):
#global VERSION
logging.info("Starting Restore") logging.info("Starting Restore")
print("Starting Restore") print(f"Starting restore : {VERSION}")
now = datetime.datetime.now() now = datetime.datetime.now()
STARTTIME = now.strftime("%Y-%m-%d_%H:%M:%S") STARTTIME = now.strftime("%Y-%m-%d_%H:%M:%S")
_DATE = "pick"
if _APP == "all": if _APP == "all":
_DATE = "latest" _DATE = "latest"
if host == "rpi5.home.lan" or host == "rpi5": if host == "rpi5.home.lan" or host == "rpi5":
@@ -235,6 +258,7 @@ if _RESTORE:
#input("????") #input("????")
else: else:
_APP = _APP.split(",") _APP = _APP.split(",")
PROGRESS = 0 PROGRESS = 0
topic = "sectorq/amd/restore" topic = "sectorq/amd/restore"
step = 100 / len(_APP) step = 100 / len(_APP)
@@ -244,7 +268,7 @@ if _RESTORE:
logging.info(msg) logging.info(msg)
send_mqtt_message(msg) send_mqtt_message(topic,msg)
PROGRESS = PROGRESS + step PROGRESS = PROGRESS + step
now = datetime.datetime.now() now = datetime.datetime.now()
DATETIME = now.strftime("%Y-%m-%d_%H-%M-%S") DATETIME = now.strftime("%Y-%m-%d_%H-%M-%S")
@@ -256,7 +280,7 @@ if _RESTORE:
if _DATE == "pick": if _DATE == "pick":
cmnd = f"ssh root@amd.home.lan 'ls {BACKUP_DEVICE}/backup/m-server/docker_data'" cmnd = f"ssh root@amd.home.lan 'ls {BACKUP_DEVICE}/backup/m-server/docker_data'"
status, output = subprocess.getstatusoutput(cmnd) status, output = subprocess.getstatusoutput(cmnd)
print(output) # print(output)
dates = output.splitlines() dates = output.splitlines()
n = 1 n = 1
for i in dates: for i in dates:
@@ -283,7 +307,7 @@ if _RESTORE:
LATEST_LINK = f"/{host}/{app}/{_DATE}" LATEST_LINK = f"/{host}/{app}/{_DATE}"
logging.info("Create backup dir") logging.info("Create backup dir")
logging.info(cmnd) #logging.info(cmnd)
#cmnd = "rsync -av --delete {}/ --link-dest {} --exclude=\".cache\" {}".format(SOURCE_DIR, LATEST_LINK, BACKUP_PATH) #cmnd = "rsync -av --delete {}/ --link-dest {} --exclude=\".cache\" {}".format(SOURCE_DIR, LATEST_LINK, BACKUP_PATH)
@@ -452,298 +476,305 @@ if _RESTORE:
msg = {"mode":_MODE, "status":"restore","bak_name":"Restore","host":host,"cur_job":app,"start_time":STARTTIME,"end_time":"","progress":100,"finished":ENDJOB,"used_space":1} msg = {"mode":_MODE, "status":"restore","bak_name":"Restore","host":host,"cur_job":app,"start_time":STARTTIME,"end_time":"","progress":100,"finished":ENDJOB,"used_space":1}
logging.info(msg) logging.info(msg)
send_mqtt_message(topic,msg)
send_mqtt_message(msg)
if _MODE == "auto": if _MODE == "auto":
cmnd = "ssh root@amd.home.lan 'systemctl suspend &'" cmnd = "ssh root@amd.home.lan 'systemctl suspend &'"
status, output = subprocess.getstatusoutput(cmnd) status, output = subprocess.getstatusoutput(cmnd)
if _BACKUP: def backup_job(pl):
last = 1 client2 = mqtt.Client()
while True: client2.username_pw_set("jaydee", "jaydee1")
directory = '/backups/' client2.connect("mqtt.home.lan",1883,60)
count = len(fnmatch.filter(os.listdir(directory), '*')) if "log" in pl:
if last != count: if pl["log"] == "debug":
logging.info(f'File Count: {count}') logging.info(f'Debug enabled')
last = count LOG_FILE = "omv_backup.log"
if count == 0: logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG, format='%(asctime)s : %(levelname)s : %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
time.sleep(10) logging.info(f'starting backup job')
continue
else:
finished = []
now = datetime.datetime.now()
STARTTIME = now.strftime("%Y-%m-%d_%H:%M:%S")
topic = "sectorq/amd/restore"
msg = {"mode":"restore", "status":"restore","bak_name":"s","host":0,"cur_job":"aaa","start_time":1,"end_time":1,"progress":0,"finished":0,"used_space":0}
send_mqtt_message(msg)
topic = "sectorq/amd/backups"
msg = {"mode":_MODE, "status":"started","bak_name":"complete","host":"","cur_job":"","start_time":STARTTIME,"end_time":"in progress","progress":0,"finished":",".join(finished)}
send_mqtt_message(msg)
# iterate over files in
# that directory
for filename in os.scandir(directory):
if filename.is_file():
logging.info(filename.path)
logging.info(filename.name)
if filename.name == "restore":
break
host = filename.name
logging.info("Backup")
for b in backups[host]["jobs"]:
if not backups[host]["jobs"][b]["active"]:
logging.info("Backup {} is not active!".format(b))
msg = {"status":"inactive","bak_name":b,"start_time":"inactive","end_time":"inactive","progress":0}
send_mqtt_message(msg)
continue
SOURCE_DIR = backups[host]["jobs"][b]["source"]
now = datetime.datetime.now()
BACKUP_HOST = backups[host]["login"]
BACKUP_DEVICE = "/mnt/raid"
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}_running"
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),qos=0, retain=True)
client.disconnect()
cmnd = "mkdir -p " + NEW_BACKUP_DIR
logging.info(cmnd)
if _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
logging.info(output)
logging.info(status)
logging.info("Create backup dir")
cmnd = f"ssh {BACKUP_HOST} 'ls {SOURCE_DIR}'"
logger.debug(cmnd)
status, output = subprocess.getstatusoutput(cmnd)
logger.debug(output)
apps = output.splitlines()
c = len(apps)
print(apps)
print(len(apps))
step = round(100 / c,1)
progress = 0
#cmd = 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}"
#cmd = [ 'rsync', '-avz','--info=progress2', BACKUP_DIR , NEW_BACKUP_DIR]
cmd = ['rsync', '-avz', '--delete', BACKUP_DIR, '--link-dest', FULL_BACKUP_LATEST, '--exclude="jellyfin/cache/transcodes"', '--exclude=".@__thumb/"', '--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]
logging.info(" ".join(cmd))
process = subprocess.Popen(cmd,
stdout=subprocess.PIPE)
while process.poll() is None:
line = process.stdout.readline().decode("utf-8").split("/")
print(line[0])
if line[0] in apps:
logging.info(f"Working on app {line[0]}")
while True:
if line[0] != apps[0]:
del apps[0]
progress = progress + step
else:
break
apps.remove(line[0])
#print(len(apps))
topic = "sectorq/amd/backups"
msg = {"mode":_MODE, "status":"started","bak_name":"complete","host":host,"cur_job":b,"sub":line[0],"start_time":STARTTIME,"end_time":"in progress","progress":str(round(progress)) + "%","finished":",".join(finished)}
send_mqtt_message(msg)
progress = progress + step
# input(apps)
# for a in apps:
# logging.info(f"App {a}")
# topic = "sectorq/amd/backups"
# msg = {"mode":_MODE, "status":"started","bak_name":"complete","host":host,"cur_job":b,"sub":a,"start_time":STARTTIME,"end_time":"in progress","progress":round(progress),"finished":",".join(finished)}
# send_mqtt_message(msg)
# logger.debug(cmnd)
# 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}{a} --link-dest {FULL_BACKUP_LATEST}/{a} --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"
# logging.info(cmnd)
# logging.info("Sync files1")
# #input("??????")
# if _TEST:
# ans = input("continue?") or "n"
# if ans == "y" and _EXECUTE:
# status, output = subprocess.getstatusoutput(cmnd)
# #proc = subprocess.Popen(cmnd,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,cwd = "/myapps/",shell=True)
# progress = progress + step
# topic = "sectorq/amd/backups"
# msg = {"mode":_MODE, "status":"started","bak_name":"complete","host":host,"cur_job":b,"sub":a,"start_time":STARTTIME,"end_time":"in progress","progress":round(progress),"finished":",".join(finished)}
# send_mqtt_message(msg)
cmnd = f"rm -rf {FULL_BACKUP_LATEST}"
logging.info(cmnd)
logging.info("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}; mv {DATETIME}_running {DATETIME};ln -s {DATETIME} latest"
logging.info("Creating new latest link")
#print(cmnd)
# input("????")
if _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
#Remove old
logging.info("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 {{}} \\;"
cmnd = "ls {BACKUP_ROOT}"
#print(cmnd)
# input("????")
if _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
for f in output.splitlines():
pattern = r"^[0-9]{4}.*" # regex pattern: string starts with 'abc'
if re.match(pattern, f):
logging.info("Match!")
dt = datetime.datetime.strptime(f, "%Y-%m-%d_%H-%M-%S")
epoch_time = int(dt.timestamp())
now_epoch = int(datetime.datetime.now().timestamp())
x = now_epoch - epoch_time
logging.info(epoch_time) # Output: 45
if x > "2592000":
dir_path = "{BACKUP_ROOT}/{f}"
logging.info(f"removing {dir_path}")
#shutil.rmtree(dir_path)
else:
print("No match.")
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)}
send_mqtt_message(msg)
logging.info("Getting size of FS")
cmnd = "df -h /mnt/raid|awk '{ print $3 }'|tail -1"
logging.info(cmnd)
status, output = subprocess.getstatusoutput(cmnd)
used_space = (output.split())[0]
now = datetime.datetime.now()
ENDJOB = now.strftime("%Y-%m-%d_%H:%M:%S")
logging.info("Size : {}".format(used_space))
logging.info("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),"used_space":used_space}
logging.info(msg)
send_mqtt_message(msg)
os.remove(filename.path)
server = pl["host"]
if pl["mode"] == "dry":
_DRYRUN = True
logging.info("Dry run active")
else:
_DRYRUN = False
logging.info("Full mode active")
finished = []
sub_finished = []
now = datetime.datetime.now()
STARTTIME = now.strftime("%Y-%m-%d_%H:%M:%S")
topic = "sectorq/amd/restore"
msg = {"mode":"restore", "status":"restore","bak_name":"s","host":0,"cur_job":"aaa","start_time":1,"end_time":1,"progress":0,"finished":0,"used_space":0}
client2.publish(topic, json.dumps(msg),qos=0, retain=True)
#client2.publish(topic, msg)
topic = "sectorq/amd/backups"
msg = {"mode":_MODE, "status":"started","bak_name":"complete","host":"","cur_job":"","start_time":STARTTIME,"end_time":"in progress","progress":0,"finished":",".join(finished)}
client2.publish(topic, json.dumps(msg),qos=0, retain=True)
# iterate over files in
# that directory
host = server
logging.info("Backup")
for b in backups[host]["jobs"]:
if not backups[host]["jobs"][b]["active"]:
logging.info("Backup {} is not active!".format(b))
msg = {"status":"inactive","bak_name":b,"start_time":"inactive","end_time":"inactive","progress":0}
client2.publish(topic, json.dumps(msg),qos=0, retain=True)
continue
SOURCE_DIR = backups[host]["jobs"][b]["source"]
now = datetime.datetime.now()
BACKUP_HOST = backups[host]["login"]
BACKUP_DEVICE = "/mnt/raid"
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}_running"
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)}
client2.publish(topic, json.dumps(msg),qos=0, retain=True)
cmnd = "mkdir -p " + NEW_BACKUP_DIR
logging.info(cmnd)
if _EXECUTE:
status, output = subprocess.getstatusoutput(cmnd)
logging.info(output)
logging.info(status)
logging.info("Create backup dir")
cmnd = f"ssh {BACKUP_HOST} 'ls {SOURCE_DIR}'"
logger.debug(cmnd)
status, output = subprocess.getstatusoutput(cmnd)
logger.debug(output)
apps = output.splitlines()
c = len(apps)
print(apps)
print(len(apps))
step = round(100 / c,1)
progress = 0
cmd = ['rsync', '-avz', '--delete', BACKUP_DIR, '--link-dest', FULL_BACKUP_LATEST, '--exclude-from=/myapps/exclude.txt', NEW_BACKUP_DIR]
logging.info(" ".join(cmd))
topic = "sectorq/amd/backups"
if not _DRYRUN:
process = subprocess.Popen(cmd,
stdout=subprocess.PIPE)
while process.poll() is None:
line = process.stdout.readline().decode("utf-8").split("/")
#print(line[0])
if line[0] in apps:
logging.info(f"Working on app {line[0]}")
while True:
if line[0] != apps[0]:
del apps[0]
progress = progress + step
else:
break
apps.remove(line[0])
sub_finished.append(line[0])
msg = {"mode":_MODE, "status":"started","bak_name":"complete","host":host,"cur_job":b,"sub":line[0],"start_time":STARTTIME,"end_time":"in progress","progress":str(round(progress)) + "%","finished":",".join(finished),"sub_finished":",".join(sub_finished)}
logging.info(f"Sending message with topic {topic} {json.dumps(msg)}")
if not "gitea-runner" == line[0]:
client2.publish(topic, json.dumps(msg),qos=0, retain=False)
progress = progress + step
topic = "sectorq/amd/restore" cmnd = f"rm -rf {FULL_BACKUP_LATEST}"
for s in servers:
#if s != "rack.home.lan": #logging.info(cmnd)
if s == "m-server.home.lan": logging.info("Removing latest link")
continue # input("????")
elif s == "nas.home.lan": if not _DRYRUN:
user = "admin" status, output = subprocess.getstatusoutput(cmnd)
cmnd = "/share/Data/python/bin/python3 /share/Data/__GITLAB/omv_backup/omv_backup.py -r all" if _FIRST:
cmnd = f"cd {BACKUP_ROOT}; ln -s initial latest"
else:
cmnd = f"cd {BACKUP_ROOT}; mv {DATETIME}_running {DATETIME};ln -s {DATETIME} latest"
logging.info("Creating new latest link")
#print(cmnd)
# input("????")
if not _DRYRUN:
status, output = subprocess.getstatusoutput(cmnd)
#Remove old
logging.info("Removing old dirs")
cmnd = f"ls {BACKUP_ROOT}"
if not _DRYRUN:
status, output = subprocess.getstatusoutput(cmnd)
for f in output.splitlines():
pattern = r"^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$" # regex pattern: string starts with 'abc'
# logging.info(f"Checking {f}")
if re.match(pattern, f):
# logging.info("Match!")
dt = datetime.datetime.strptime(f, "%Y-%m-%d_%H-%M-%S")
epoch_time = int(dt.timestamp())
now_epoch = int(datetime.datetime.now().timestamp())
x = now_epoch - epoch_time
# logging.info(epoch_time) # Output: 45
if x > 2592000:
dir_path = f"{BACKUP_ROOT}/{f}"
logging.info(f"removing {dir_path}")
shutil.rmtree(dir_path)
else: else:
user = "jd" print("No match.")
cmnd = "sudo /myapps/omv_backup.py -r all" if not _DRYRUN:
msg = {"mode":_MODE, "status":"restore","bak_name":"s","host":s,"cur_job":"aaa","start_time":1,"end_time":1,"progress":0,"finished":1,"used_space":1} logging.info(f"Clearing multiple days")
logging.info(msg) multiple_per_day = {}
to_remove = []
for f in output.splitlines():
pattern = r"^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$" # regex pattern: string starts with 'abc'
send_mqtt_message(msg) if re.match(pattern, f):
if is_port_open(s,22): cday = f.split("_")[0]
ssh = paramiko.SSHClient() if cday in multiple_per_day:
ssh.load_system_host_keys() multiple_per_day[cday].append(f)
# Add SSH host key automatically if needed. else:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) multiple_per_day[cday] = [f]
# Connect to router using username/password authentication.
logger.info(f"Sync {s}")
print(f"Sync {s}")
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
pkey = paramiko.RSAKey.from_private_key_file("/home/jd/.ssh/id_rsa")
ssh.connect(s,
username=user,
look_for_keys=False,
allow_agent=False,
pkey=pkey)
print(cmnd)
ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(cmnd)
for line in iter(ssh_stdout.readline, ""):
logger.info(line)
print(line, end="")
for line in iter(ssh_stderr.readline, ""):
logger.info(line)
ssh.close()
try:
os.remove("/backups/restore")
except:
pass
# 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: # # logging.info("Match!")
# pass # dt = datetime.datetime.strptime(f, "%Y-%m-%d_%H-%M-%S")
# epoch_time = int(dt.timestamp())
# now_epoch = int(datetime.datetime.now().timestamp())
# x = now_epoch - epoch_time
# # logging.info(epoch_time) # Output: 45
# if x > 2592000:
# dir_path = f"{BACKUP_ROOT}/{f}"
# logging.info(f"removing {dir_path}")
# shutil.rmtree(dir_path)
else:
print("No match.")
logging.info(f"Clearing multiple days: {multiple_per_day}")
for f in multiple_per_day:
logging.info(f"Looping multiple_per_day : {f}")
if len(multiple_per_day[f]) > 1:
last = multiple_per_day[f][-1]
multiple_per_day[f].pop()
logging.info(f"Last from day: {last}")
for d in multiple_per_day[f]:
logging.info(f"Looping multiple_per_day : {f} : {d}")
dir_path = f"{BACKUP_ROOT}/{d}"
logging.info(f"removing {dir_path}")
shutil.rmtree(dir_path)
cmnd = f"ls {BACKUP_ROOT}|grep _running"
logging.info(f"removing obsolete dirs")
if not _DRYRUN:
status, output = subprocess.getstatusoutput(cmnd)
for f in output.splitlines():
dir_path = f"{BACKUP_ROOT}/{f}"
logging.info(f"removing {dir_path}")
shutil.rmtree(dir_path)
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)}
client2.publish(topic, json.dumps(msg),qos=0, retain=True)
logging.info("Getting size of FS")
cmnd = "df -h /mnt/raid|awk '{ print $3 }'|tail -1"
logging.info(cmnd)
status, output = subprocess.getstatusoutput(cmnd)
used_space = (output.split())[0]
now = datetime.datetime.now()
ENDJOB = now.strftime("%Y-%m-%d_%H:%M:%S")
logging.info("Size : {}".format(used_space))
logging.info("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),"used_space":used_space}
logging.info(msg)
client2.publish(topic, json.dumps(msg),qos=0, retain=True)
topic = "sectorq/backups/start"
logging.info(f"LALA : {topic}")
client2.publish(topic, "finished",qos=0, retain=True)
time.sleep(1)
client2.publish(topic, "finished2",qos=0, retain=True)
client2.disconnect()
#return "finished"
if _DRYRUN:
return
topic = "sectorq/amd/restore"
for s in servers:
logging.info(f"Restoring {s}")
#if s != "rack.home.lan":
if s == "m-server.home.lan":
continue
elif s == "nas.home.lan":
user = "admin"
cmnd = "/share/Data/python/bin/python3 /share/Data/__GITLAB/omv_backup/omv_backup.py -r all"
else:
user = "jd"
cmnd = "sudo /myapps/omv_backup.py -r all"
msg = {"mode":_MODE, "status":"restore","bak_name":"s","host":s,"cur_job":"aaa","start_time":1,"end_time":1,"progress":0,"finished":1,"used_space":1}
#logging.info(msg)
send_mqtt_message(topic,msg)
#continue
if is_port_open(s,22):
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
# Add SSH host key automatically if needed.
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Connect to router using username/password authentication.
logger.info(f"Sync {s}")
print(f"Sync {s}")
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
pkey = paramiko.RSAKey.from_private_key_file("/home/jd/.ssh/id_rsa")
ssh.connect(s,
username=user,
look_for_keys=False,
allow_agent=False,
pkey=pkey)
print(cmnd)
ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(cmnd)
for line in iter(ssh_stdout.readline, ""):
logger.info(line)
print(line, end="")
for line in iter(ssh_stderr.readline, ""):
logger.info(line)
ssh.close()
try:
os.remove("/backups/restore")
except:
pass
return "finished1"
if _RESTORE:
restore_job(_APP)
sys.exit()
if _SSH_TEST: if _SSH_TEST:
user = "root" user = "root"
cmnd = "ls -la" cmnd = "ls -la"
@@ -755,9 +786,9 @@ if _SSH_TEST:
# user = "admin" # user = "admin"
# cmnd = "/share/Data/__GITLAB/omv_backup/venv/bin/python3 /share/Data/__GITLAB/omv_backup/omv_backup.py -r all" # cmnd = "/share/Data/__GITLAB/omv_backup/venv/bin/python3 /share/Data/__GITLAB/omv_backup/omv_backup.py -r all"
msg = {"mode":_MODE, "status":"restore","bak_name":"s","host":s,"cur_job":"aaa","start_time":1,"end_time":1,"progress":0,"finished":1,"used_space":1} msg = {"mode":_MODE, "status":"restore","bak_name":"s","host":s,"cur_job":"aaa","start_time":1,"end_time":1,"progress":0,"finished":1,"used_space":1}
logging.info(msg) #logging.info(msg)
send_mqtt_message(msg) send_mqtt_message(topic,msg)
if s != "rack.home.lan": if s != "rack.home.lan":
continue continue
if is_port_open(s,22): if is_port_open(s,22):
@@ -782,4 +813,57 @@ if _SSH_TEST:
print(line, end="") print(line, end="")
for line in iter(ssh_stderr.readline, ""): for line in iter(ssh_stderr.readline, ""):
logger.info(line) logger.info(line)
ssh.close() ssh.close()
# Define actions based on payload
def handle_payload(payload):
try:
pl = json.loads(payload)
except:
pl = payload
logging.debug(pl)
if "host" in pl:
if pl["host"] == 'm-server':
logging.info("💡 Starting backup job")
backup_job(pl)
logging.info(f"💡 Finished backup job")
elif pl["host"] == 'nas':
logging.info("💡 Starting backup job")
backup_job(pl)
logging.info(f"💡 Finished backup job")
else:
logging.error(f"⚠️ Unknown command: {pl}")
else:
logging.error(f"⚠️ Wrong payload: {pl}")
# Callback when connected
def on_connect(client, userdata, flags, rc):
if rc == 0:
logging.info("✅ Connected securely to broker")
client.subscribe(TOPIC)
logging.info(f"📡 Subscribed to topic: {TOPIC}")
else:
logging.error(f"❌ Connection failed with code {rc}")
# Callback when a message is received
def on_message(client, userdata, msg):
payload = msg.payload.decode()
logging.info(f"📨 Received message: {payload} on topic: {msg.topic}")
handle_payload(payload)
# MQTT client setup
client = mqtt.Client()
client.username_pw_set(USERNAME, PASSWORD)
client.on_connect = on_connect
client.on_message = on_message
# Use TLS for encrypted connection
if USE_TLS:
client.tls_set() # You can customize this with certs if needed
# Connect and loop forever
client.connect(BROKER, PORT, keepalive=60)
client.publish("sectorq/backups/start", "finished", qos=0, retain=True)
client.loop_forever()