Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legacy backup and restore #204

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
106 changes: 106 additions & 0 deletions load_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
#
# This file is part of photoframe (https://github.com/mrworf/photoframe).
#
# photoframe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# photoframe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with photoframe. If not, see <http://www.gnu.org/licenses/>.
#
# Synopsis:
# load_config.py settings.tar.gz
#
# Description:
# This program loads a saved configuration into the system. It will:
# Verify config file,
# stop frame process,
# backup current config,
# set desired config.
# Note: restarting the frame service is up to the caller.
#
#
import sys
import os
import subprocess
import shutil

from modules.path import path

# Make sure we run from our own directory
os.chdir(os.path.dirname(sys.argv[0]))

# get input filename from command line
if len(sys.argv) < 2:
print('Argument missing')
print('Usage:', sys.argv[0], ' settings.tar.gz')
sys.exit(1)
if len(sys.argv) > 2:
print('Too many arguments')
print('Usage:', sys.argv[0], ' settings.tar.gz')
sys.exit(1)
updatefile = sys.argv[1]

# test that the file exists and is a tar.gz file
if not os.path.isfile(updatefile):
print('file not found: ', updatefile)
sys.exit(1)
try:
filetype = subprocess.check_output(['file', '-b', '--mime-type', updatefile]).decode("utf-8").strip()
except Exception:
print('Error determining Mime-type of ', updatefile)
sys.exit(1)
if filetype != 'application/gzip':
print(updatefile, 'is not a tar.gz archive')
sys.exit(1)

#Stop existing frame thread
# Do NOT kill the service itself, since it will actually tear down the python script
subprocess.call(['pkill', '-SIGHUP', '-f', 'frame.py'])

# move existing config if necessary
configdir=path.CONFIGFOLDER
if os.path.isdir(configdir):
had_config = True
shutil.rmtree(configdir + '.bak', ignore_errors = True)
os.rename(configdir, configdir + '.bak')
else:
had_config = False
try:
os.mkdir(configdir)
except Exception:
print('Cannot make new configuration directory:', configdir)
os.rename(configdir + '.bak', configdir)
sys.exit(1)

#Un-tar the config file
try:
subprocess.call(['tar', '-xzf', updatefile, '-C', configdir])
except Exception:
print('tar failed to unpack', updatefile, 'into', configdir)
if had_config:
print('Restoring Previous configuration')
shutil.rmtree(configdir, ignore_errors = True)
os.rename(configdir + '.bak', configdir)
sys.exit(1)

#Test config data for validity
if not all([os.path.isdir(configdir + '/display-drivers'), os.path.isdir(configdir + '/services'),
os.path.isfile(configdir + '/settings.json'), os.path.isfile(configdir + '/version.json')]):
print('New config is incomplete')
if had_config:
print('Restoring Previous configuration')
shutil.rmtree(configdir, ignore_errors = True)
os.rename(configdir + '.bak', configdir)
sys.exit(1)

#All done
print('Successfully loaded new config from ', updatefile)
sys.exit(0)
32 changes: 32 additions & 0 deletions modules/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
import subprocess
import logging
import os
import platform
import datetime
import sys
import traceback
import json
from modules.path import path

def _stringify(args):
result = ''
Expand Down Expand Up @@ -71,3 +74,32 @@ def version():
if lines:
lines = lines.splitlines()
return (title, lines, None)

def config_version():
origin = subprocess_check_output(['git', 'config', '--get', 'remote.origin.url'])
if origin:
origin = origin.strip()

branch = ""
branchlines = subprocess_check_output(['git', 'status']).splitlines()
for line in branchlines:
if line.startswith('On branch'):
branch = line.partition("branch")[2].strip()

commit = ""
commitlines = subprocess_check_output(['git', 'log', '-n 1']).splitlines()
for line in commitlines:
if line.startswith('commit'):
commit = line.partition("commit")[2].strip()

config = {
"release": platform.release(),
"python_version": platform.python_version(),
"origin": origin,
"branch": branch,
"commit": commit
}
versionfile = open(path.CONFIGFOLDER + "/version.json", 'w+')
json.dump(config, versionfile)

return (True)
2 changes: 2 additions & 0 deletions modules/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import logging

class path:
BASEDIR = '/root/'
CONFIGFOLDER = '/root/photoframe_config'
CONFIGFILE = '/root/photoframe_config/settings.json'
COLORMATCH = '/root/photoframe_config/colortemp.sh'
Expand All @@ -33,6 +34,7 @@ def reassignConfigTxt(self, newconfig):
path.CONFIG_TXT = newconfig

def reassignBase(self, newbase):
path.BASEDIR = path.BASEDIR = newbase
path.CONFIGFOLDER = path.CONFIGFOLDER.replace('/root/', newbase)
path.CONFIGFILE = path.CONFIGFILE.replace('/root/', newbase)
path.OPTIONSFILE = path.OPTIONSFILE.replace('/root/', newbase)
Expand Down
41 changes: 41 additions & 0 deletions routes/maintenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
# You should have received a copy of the GNU General Public License
# along with photoframe. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import os
import subprocess
import shutil
import flask
import modules.debug as debug

from baseroute import BaseRoute
from modules.path import path
Expand Down Expand Up @@ -83,3 +86,41 @@ def handle(self, app, cmd):
elif cmd == 'ssh':
subprocess.call(['systemctl', 'restart', 'ssh'], stderr=self.void)
return self.jsonify({'ssh': True})
elif cmd == 'backup':
if debug.config_version():
try:
subprocess.call(['tar', '-czf', '/boot/settings.tar.gz', '-C', path.CONFIGFOLDER, '.'])
except:
return 'Backup Failed', 404
else:
return 'Backup Successful', 200
else:
return 'Backup Failed', 404
elif cmd == 'restore':
if os.path.isfile("/boot/settings.tar.gz"):
try:
subprocess.call(path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz', shell=True)
except:
logging.info('FAILED to load new settings with: ' + path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz')
return 'Failed to load new settings', 404
else:
logging.info('Loaded new settings with: ' + path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz')
subprocess.Popen('systemctl restart frame', shell=True)
return 'Restoring settings and restarting photofame', 200
else:
return 'File not found: /boot/settings.tar.gz', 404
elif cmd == 'dnldcfg':
if debug.config_version():
try:
subprocess.call(['tar', '-czf', '/tmp/settings.tar.gz', '-C', path.CONFIGFOLDER, '.'], stderr=self.void)
except:
return 'Download Failed', 404
else:
return flask.send_from_directory("/tmp", "settings.tar.gz", as_attachment=True)
else:
return 'Download failed', 404
# The route to upload settings from the browser is in routes/upload.py
elif cmd == 'restart':
subprocess.Popen('systemctl restart frame', shell=True)
return 'Restarting photoframe', 200

19 changes: 19 additions & 0 deletions routes/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

import logging
import os
import subprocess

from werkzeug.utils import secure_filename
from baseroute import BaseRoute
from modules.path import path

class RouteUpload(BaseRoute):
def setupex(self, settingsmgr, drivermgr):
Expand All @@ -43,6 +45,13 @@ def handle(self, app, item):
logging.error('No filename or invalid filename')
self.setAbort(405)
return
elif item == 'config':
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '' or not file.filename.lower().endswith('.tar.gz'):
logging.error('No configuration filename or does not end in .tar.gz')
self.setAbort(405)
return

filename = os.path.join('/tmp/', secure_filename(file.filename))
file.save(filename)
Expand All @@ -64,6 +73,16 @@ def handle(self, app, item):
else:
retval['return'] = {'reboot' : False}

elif item == 'config':
try:
subprocess.call(path.BASEDIR + 'photoframe/load_config.py ' + filename, shell=True)
except:
logging.info('FAILED to load settings with: ' + path.BASEDIR + 'photoframe/load_config.py ' + filename)
retval['status'] = 500
else:
logging.info('Loading settings with: ' + path.BASEDIR + 'photoframe/load_config.py ' + filename)
retval['return'] = {'reboot': False, 'restart': True}

try:
os.remove(filename)
except:
Expand Down
49 changes: 49 additions & 0 deletions static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,55 @@ $("#reboot").click(function() {
}
});

$("#backup").click(function() {
if (confirm("Backup current settings to /boot/settings.tar.gz ?")) {
$.ajax({
url:"/maintenance/backup"
}).done(function (){
});
}
});

$("#restore").click(function() {
if (confirm("This will remove the current configuration and restore saved settings from /boot/settings.tar.gz ?")) {
$.ajax({
url:"/maintenance/restore"
}).done(function(){
rebootWatch();
});
}
});

$("#dnldcfg").click(function() {
window.location.assign("/maintenance/dnldcfg")
});

$('#config').fileupload({
add: function (e, data) {
data.submit();
},
done: function (e, data) {
console.log(data);
if (data.result['restart']) {
$.ajax({
url:"/maintenance/restart"
}).done(function(){
rebootWatch();
});
} else {
alert("Failed to install configuration - is the file OK?");
location.reload();
}
},
fail: function (e, data) {
alert('Failed to upload configuration');
},
});

$("#config-button").click(function() {
$('#config').trigger('click');
});

$("#shutdown").click(function() {
if (confirm("Are you sure you want to POWER OFF the frame?")) {
$.ajax({
Expand Down
11 changes: 9 additions & 2 deletions static/template/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,17 @@ <h1><a href="https://github.com/mrworf/photoframe">PhotoFrame Configuration</a>
<div style="display: inline-block; width: 100px"></div>
<input type="button" id="forgetMemory" title="Show images that have already been displayed" value="Forget Memory">
<input type="button" id="clearCache" title="In case you have recently cropped or edited your photos" value="Clear Cache">
<input type="button" id="reset" value="Factory Reset">
<div style="display: inline-block; width: 100px"></div>
<input type="button" id="debug" value="Log report">
</div>
<br>
<input type="button" id="reset" value="Factory Reset">
<div style="display: inline-block; width: 50px"></div>
<input type="button" id="backup" value="Backup Settings">
<input type="button" id="restore" value="Restore Settings">
<div style="display: inline-block; width: 50px"></div>
<input type="button" id="dnldcfg" value="Download Settings">
<input style="position:absolute; top: -100px" type="file" data-url="upload/config" id="config" name="filename"><button id="config-button">Upload Settings</button>
</div>
</div>
<br>
{{#if service-defined}}
Expand Down