first commit
This commit is contained in:
199
plugin.py
Executable file
199
plugin.py
Executable 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¶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
|
||||
Reference in New Issue
Block a user