From 81cb64dd1659f121ea4c79705fd5d09dfdb8bccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20JUMELLE?= Date: Mon, 24 May 2021 10:37:19 +0200 Subject: [PATCH] first commit --- plugin.py | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100755 plugin.py diff --git a/plugin.py b/plugin.py new file mode 100755 index 0000000..c8bc86d --- /dev/null +++ b/plugin.py @@ -0,0 +1,199 @@ +# Backup plugin for Domoticz +# +# Author: fjumelle +# +""" + + +

Domoticz Backup


+ Backup the Domoticz configuration (Database, plugins, scripts).
+
+ + + + + + + + + + + + +
+""" +import Domoticz +import os +import pwd +import grp +import shutil +import json +import zipfile + +import urllib.parse as parse +import urllib.request as request + +from datetime import datetime, timedelta + +DATE_FORMAT = "%Y%m%d%H%M%S" + +class BasePlugin: + def __init__(self): + return + + def onStart(self): + # setup the appropriate logging level + debuglevel = int(Parameters["Mode6"]) + if debuglevel != 0: + Domoticz.Debugging(debuglevel) + else: + Domoticz.Debugging(0) + + #Heartbeat + Domoticz.Heartbeat(30) + + # create the child devices if these do not exist yet + if 1 not in Devices: + Domoticz.Device(Name="Domoticz Backup", Unit=1, Image=9, TypeName="Switch", Used=1).Create() + Devices[1].Update(nValue=0, sValue="Off") + + def onCommand(self, Unit, Command, Level, Hue): + if Unit == 1: + if Command == "On": + destination = Parameters["Mode1"] + cleanup = Parameters["Mode2"] + domoticz_host = Parameters["Address"] + domoticz_port = Parameters["Port"] + domoticz_user = Parameters["Username"] + domoticz_pass = Parameters["Password"] + backupDomoticz(domoticz_host, domoticz_port, domoticz_user, domoticz_pass, destination, cleanup) + + #switch back to "Off" + Devices[1].Update(nValue=0, sValue="Off") + +global _plugin +_plugin = BasePlugin() + +def onStart(): + global _plugin + _plugin.onStart() + +def onCommand(Unit, Command, Level, Hue): + global _plugin + _plugin.onCommand(Unit, Command, Level, Hue) + +def onHeartbeat(): + pass + +def backupDomoticz(host, port, user, password, destination, cleanup): + Domoticz.Status("Backup Domoticz files (plugins, scripts, db, ...)") + try: + current_time = datetime.now().strftime(DATE_FORMAT) + + #Create current_time folder + work_folder = os.path.join(destination, current_time) + if not os.path.exists(work_folder): + os.mkdir(work_folder) + + try: + #Domoticz root folder + domoticz_root = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..")) + + #Archive plugins folder + archiveFolder(work_folder, os.path.join(domoticz_root, "plugins")) + #Archive scripts folder + archiveFolder(work_folder, os.path.join(domoticz_root, "scripts")) + #Archive WWW folder + if int(current_time[6:8]) in (1, 11, 21): + archiveFolder(work_folder, os.path.join(domoticz_root, "www")) + #Copy DB + archiveDatabase(host, port, user, password, work_folder) + + #Delete obsolete archives + deleteOldBackups(destination, cleanup) + + #Change owner + chown(work_folder) + + except: + #Change owner + chown(work_folder) + raise + except Exception as exc: + Domoticz.Error("Error during Domoticz backup!") + Domoticz.Error(str(exc)) + #Send notification + subject = "Error during Domoticz backup" + body = str(exc) + DomoticzAPI("type=command¶m=sendnotification&subject={}&body={}‬".format(subject, body)) + +def archiveFolder(archive_folder, folder): + folder = os.path.abspath(folder) + root_folder = os.path.abspath(os.path.join(folder, "..")) + archive = os.path.abspath(os.path.join(archive_folder, os.path.basename(folder) + ".zip")) + Domoticz.Status("Archive folder {} in {}".format(folder, archive)) + ziph = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED) + for root, dirs, files in os.walk(folder): + for current_file in files: + file_to_arch = os.path.abspath(os.path.join(root, current_file)) + file_in_arc = file_to_arch.replace(root_folder, ".") + ziph.write(file_to_arch, file_in_arc) + ziph.close() + +def archiveDatabase(host, port, user, password, archive_folder): + db_dst = os.path.join(archive_folder, "domoticz.db") + Domoticz.Status("Archive database in {}".format(db_dst)) + url = "http://{}:{}/backupdatabase.php".format(host, port) + request.urlretrieve(url, db_dst) + ziph = zipfile.ZipFile(db_dst + ".zip", 'w', zipfile.ZIP_DEFLATED) + ziph.write(db_dst, os.path.basename(db_dst)) + os.remove(db_dst) + +def deleteOldBackups(root_archive_folder, cleanup): + for dir in os.listdir(root_archive_folder): + try: + dir_date = datetime.strptime(dir, DATE_FORMAT) + if dir_date < datetime.now() - timedelta(days=int(cleanup)): + folder = os.path.join(root_archive_folder, dir) + Domoticz.Status("Remove backup '{}'".format(folder)) + chown(folder) + shutil.rmtree(folder, ignore_errors=True) + except: + pass + +def chown(folder): + uid = pwd.getpwnam("nobody").pw_uid + gid = grp.getgrnam("nogroup").gr_gid + os.chown(folder, uid, gid) + for root, dirs, files in os.walk(folder): + for momo in dirs: + os.chown(os.path.join(root, momo), uid, gid) + for momo in files: + os.chown(os.path.join(root, momo), uid, gid) + +def DomoticzAPI(APICall): + resultJson = None + url = "http://{}:{}/json.htm?{}".format(Parameters["Address"], Parameters["Port"], parse.quote(APICall, safe="&=")) + Domoticz.Debug("Calling domoticz API: {}".format(url)) + try: + req = request.Request(url) + if Parameters["Username"] != "": + Domoticz.Debug("Add authentification for user {}".format(Parameters["Username"])) + credentials = ('%s:%s' % (Parameters["Username"], Parameters["Password"])) + encoded_credentials = base64.b64encode(credentials.encode('ascii')) + req.add_header('Authorization', 'Basic %s' % encoded_credentials.decode("ascii")) + + response = request.urlopen(req) + if response.status == 200: + resultJson = json.loads(response.read().decode('utf-8')) + if resultJson["status"] != "OK": + Domoticz.Error("Domoticz API returned an error: status = {}".format(resultJson["status"])) + resultJson = None + else: + Domoticz.Error("Domoticz API: http error = {}".format(response.status)) + except Exception as err: + Domoticz.Error("Error calling '{}'".format(url)) + Domoticz.Error(str(err)) + return resultJson