# 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