first commit

This commit is contained in:
2021-05-24 10:37:19 +02:00
commit 81cb64dd16

199
plugin.py Executable file
View File

@@ -0,0 +1,199 @@
# Backup plugin for Domoticz
#
# Author: fjumelle
#
"""
<plugin key="DomoticzBackup" name="Domoticz Backup" author="fjumelle" version="1.0.0" wikilink="" externallink="">
<description>
<h2>Domoticz Backup</h2><br/>
Backup the Domoticz configuration (Database, plugins, scripts).<br/>
</description>
<params>
<param field="Address" label="Domoticz IP Address" width="200px" required="true" default="127.0.0.1"/>
<param field="Port" label="Port" width="40px" required="true" default="8080"/>
<param field="Username" label="Domoticz Username" width="200px" required="false" default=""/>
<param field="Password" label="Domoticz Password" width="200px" required="false" default=""/>
<param field="Mode1" label="Destination" width="400px" required="true" default=""/>
<param field="Mode2" label="Cleanup (days)" width="40px" required="true" default="30"/>
<param field="Mode6" label="Logging Level" width="200px">
<options>
<option label="Normal" value="0" default="true"/>
<option label="Verbose" value="1"/>
</options>
</param>
</params>
</plugin>
"""
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&param=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