Files
domoticz-Backup/plugin.py
2021-05-24 10:37:19 +02:00

200 lines
7.5 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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