mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-14 09:12:58 -07:00
Backup config file
This commit is contained in:
parent
b0eb98c667
commit
6aa786698e
9 changed files with 127 additions and 64 deletions
4
API.md
4
API.md
|
@ -36,6 +36,10 @@ General optional parameters:
|
||||||
Get to the chopper!
|
Get to the chopper!
|
||||||
|
|
||||||
|
|
||||||
|
### backup_config
|
||||||
|
Create a manual backup of the `config.ini` file.
|
||||||
|
|
||||||
|
|
||||||
### backup_db
|
### backup_db
|
||||||
Create a manual backup of the `plexpy.db` file.
|
Create a manual backup of the `plexpy.db` file.
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ import signal
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import plexpy
|
import plexpy
|
||||||
from plexpy import logger, web_socket, webstart
|
from plexpy import config, database, logger, web_socket, webstart
|
||||||
|
|
||||||
|
|
||||||
# Register signals, such as CTRL + C
|
# Register signals, such as CTRL + C
|
||||||
|
@ -148,7 +148,7 @@ def main():
|
||||||
if args.config:
|
if args.config:
|
||||||
config_file = args.config
|
config_file = args.config
|
||||||
else:
|
else:
|
||||||
config_file = os.path.join(plexpy.DATA_DIR, 'config.ini')
|
config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME)
|
||||||
|
|
||||||
# Try to create the DATA_DIR if it doesn't exist
|
# Try to create the DATA_DIR if it doesn't exist
|
||||||
if not os.path.exists(plexpy.DATA_DIR):
|
if not os.path.exists(plexpy.DATA_DIR):
|
||||||
|
@ -164,7 +164,7 @@ def main():
|
||||||
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
|
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
|
||||||
|
|
||||||
# Put the database in the DATA_DIR
|
# Put the database in the DATA_DIR
|
||||||
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db')
|
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
|
||||||
|
|
||||||
if plexpy.DAEMON:
|
if plexpy.DAEMON:
|
||||||
plexpy.daemonize()
|
plexpy.daemonize()
|
||||||
|
|
|
@ -198,12 +198,11 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="backup_dir">Backup Directory</label>
|
<label for="backup_dir">Backup Directory</label>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-6">
|
||||||
<div class="input-group">
|
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}">
|
||||||
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}">
|
<div class="btn-group">
|
||||||
<span class="input-group-btn">
|
<button class="btn btn-form" type="button" id="backup_config">Backup Config</button>
|
||||||
<button class="btn btn-form" type="button" id="backup_database">Backup Database</button>
|
<button class="btn btn-form" type="button" id="backup_database">Backup Database</button>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -211,12 +210,11 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="cache_dir">Cache Directory</label>
|
<label for="cache_dir">Cache Directory</label>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-6">
|
||||||
<div class="input-group">
|
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}">
|
||||||
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}">
|
<div class="btn-group">
|
||||||
<span class="input-group-btn">
|
<button class="btn btn-form" type="button" id="clear_cache">Clear All Cache</button>
|
||||||
<button class="btn btn-form" type="button" id="clear_cache">Clear Cache</button>
|
<button class="btn btn-form" type="button" id="clear_image_cache">Clear Image Cache</button>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -224,12 +222,10 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="log_dir">Log Directory</label>
|
<label for="log_dir">Log Directory</label>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-6">
|
||||||
<div class="input-group">
|
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}">
|
||||||
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}">
|
<div class="btn-group">
|
||||||
<span class="input-group-btn">
|
<button class="btn btn-form" type="button" id="clear_logs">Clear Logs</button>
|
||||||
<button class="btn btn-form" type="button" id="clear_logs">Clear Logs</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -525,7 +521,7 @@
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="http_hash_password" id="http_hash_password" value="1" ${config['http_hash_password']} data-parsley-trigger="change"> Hash Password in the Config File
|
<input type="checkbox" name="http_hash_password" id="http_hash_password" value="1" ${config['http_hash_password']} data-parsley-trigger="change"> Hash Password in the Config File
|
||||||
</label>
|
</label>
|
||||||
<p class="help-block">Store a hashed password in the config.ini file.<br />Warning: Your password cannot be recovered if forgotten!</p>
|
<p class="help-block">Store a hashed password in the config file.<br />Warning: Your password cannot be recovered if forgotten!</p>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="http_hashed_password" name="http_hashed_password" value="${config['http_hashed_password']}" style="display: none;" data-parsley-trigger="change" data-parsley-type="integer" data-parsley-range="[0, 1]"
|
<input type="text" id="http_hashed_password" name="http_hashed_password" value="${config['http_hashed_password']}" style="display: none;" data-parsley-trigger="change" data-parsley-type="integer" data-parsley-range="[0, 1]"
|
||||||
data-parsley-errors-container="#http_hash_password_error" data-parsley-error-message="Cannot un-hash password, please set a new password." data-parsley-no-focus required>
|
data-parsley-errors-container="#http_hash_password_error" data-parsley-error-message="Cannot un-hash password, please set a new password." data-parsley-no-focus required>
|
||||||
|
@ -2138,12 +2134,12 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
getSchedulerTable();
|
getSchedulerTable();
|
||||||
|
|
||||||
$("#backup_database").click(function () {
|
function confirmAjaxCall (url, msg) {
|
||||||
$("#confirm-message").text("Are you sure you want to create a backup of the PlexPy database?");
|
$("#confirm-message").text(msg);
|
||||||
$('#confirm-modal').modal();
|
$('#confirm-modal').modal();
|
||||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
$('#confirm-modal').one('click', '#confirm-button', function () {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'backup_db',
|
url: url,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
complete: function (xhr, status) {
|
complete: function (xhr, status) {
|
||||||
result = $.parseJSON(xhr.responseText);
|
result = $.parseJSON(xhr.responseText);
|
||||||
|
@ -2156,46 +2152,36 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#backup_config").click(function () {
|
||||||
|
var msg = 'Are you sure you want to create a backup of the PlexPy config?';
|
||||||
|
var url = 'backup_config';
|
||||||
|
confirmAjaxCall(url, msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#backup_database").click(function () {
|
||||||
|
var msg = 'Are you sure you want to create a backup of the PlexPy database?';
|
||||||
|
var url = 'backup_db';
|
||||||
|
confirmAjaxCall(url, msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#clear_cache").click(function () {
|
$("#clear_cache").click(function () {
|
||||||
$("#confirm-message").text("Are you sure you want to clear the PlexPy cache?");
|
var msg = 'Are you sure you want to clear the PlexPy cache?';
|
||||||
$('#confirm-modal').modal();
|
var url = 'delete_cache';
|
||||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
confirmAjaxCall(url, msg);
|
||||||
$.ajax({
|
});
|
||||||
url: 'delete_cache',
|
|
||||||
type: 'POST',
|
$("#clear_image_cache").click(function () {
|
||||||
complete: function (xhr, status) {
|
var msg = 'Are you sure you want to clear the PlexPy image cache?';
|
||||||
result = $.parseJSON(xhr.responseText);
|
var url = 'delete_image_cache';
|
||||||
msg = result.message;
|
confirmAjaxCall(url, msg);
|
||||||
if (result.result == 'success') {
|
|
||||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
|
||||||
} else {
|
|
||||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#clear_logs").click(function () {
|
$("#clear_logs").click(function () {
|
||||||
$("#confirm-message").text("Are you sure you want to clear the PlexPy logs?");
|
var msg = 'Are you sure you want to clear the PlexPy logs?';
|
||||||
$('#confirm-modal').modal();
|
var url = 'delete_logs';
|
||||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
confirmAjaxCall(url, msg);
|
||||||
$.ajax({
|
|
||||||
url: 'delete_logs',
|
|
||||||
type: 'POST',
|
|
||||||
complete: function (xhr, status) {
|
|
||||||
result = $.parseJSON(xhr.responseText);
|
|
||||||
msg = result.message;
|
|
||||||
if (result.result == 'success') {
|
|
||||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
|
||||||
} else {
|
|
||||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -345,6 +345,7 @@ def initialize_scheduler():
|
||||||
hours=hours, minutes=0, seconds=0)
|
hours=hours, minutes=0, seconds=0)
|
||||||
|
|
||||||
schedule_job(database.make_backup, 'Backup PlexPy database', hours=6, minutes=0, seconds=0, args=(True, True))
|
schedule_job(database.make_backup, 'Backup PlexPy database', hours=6, minutes=0, seconds=0, args=(True, True))
|
||||||
|
schedule_job(config.make_backup, 'Backup PlexPy config', hours=6, minutes=0, seconds=0, args=(True, True))
|
||||||
|
|
||||||
# Start scheduler
|
# Start scheduler
|
||||||
if start_jobs and len(SCHED.get_jobs()):
|
if start_jobs and len(SCHED.get_jobs()):
|
||||||
|
|
|
@ -31,6 +31,7 @@ import cherrypy
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
|
||||||
import plexpy
|
import plexpy
|
||||||
|
import config
|
||||||
import database
|
import database
|
||||||
import logger
|
import logger
|
||||||
import plextv
|
import plextv
|
||||||
|
@ -295,6 +296,18 @@ class API2:
|
||||||
self.data = rows
|
self.data = rows
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
def backup_config(self):
|
||||||
|
""" Create a manual backup of the `config.ini` file. """
|
||||||
|
|
||||||
|
data = config.make_backup()
|
||||||
|
|
||||||
|
if data:
|
||||||
|
self.result_type = 'success'
|
||||||
|
else:
|
||||||
|
self.result_type = 'failed'
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
def backup_db(self):
|
def backup_db(self):
|
||||||
""" Create a manual backup of the `plexpy.db` file. """
|
""" Create a manual backup of the `plexpy.db` file. """
|
||||||
|
|
||||||
|
|
|
@ -69,5 +69,6 @@ SCHEDULER_LIST = ['Check GitHub for updates',
|
||||||
'Refresh libraries list',
|
'Refresh libraries list',
|
||||||
'Refresh Plex server URLs',
|
'Refresh Plex server URLs',
|
||||||
'Refresh Plex server name',
|
'Refresh Plex server name',
|
||||||
'Backup PlexPy database'
|
'Backup PlexPy database',
|
||||||
|
'Backup PlexPy config'
|
||||||
]
|
]
|
|
@ -13,10 +13,14 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import arrow
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
|
|
||||||
from configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
|
|
||||||
|
import plexpy
|
||||||
import logger
|
import logger
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +33,8 @@ def bool_int(value):
|
||||||
value = 0
|
value = 0
|
||||||
return int(bool(value))
|
return int(bool(value))
|
||||||
|
|
||||||
|
FILENAME = "config.ini"
|
||||||
|
|
||||||
_CONFIG_DEFINITIONS = {
|
_CONFIG_DEFINITIONS = {
|
||||||
'ALLOW_GUEST_ACCESS': (int, 'General', 0),
|
'ALLOW_GUEST_ACCESS': (int, 'General', 0),
|
||||||
'DATE_FORMAT': (str, 'General', 'YYYY-MM-DD'),
|
'DATE_FORMAT': (str, 'General', 'YYYY-MM-DD'),
|
||||||
|
@ -480,6 +486,43 @@ _BLACKLIST_KEYS = ['_APITOKEN', '_TOKEN', '_KEY', '_SECRET', '_PASSWORD', '_APIK
|
||||||
_WHITELIST_KEYS = ['HTTPS_KEY', 'UPDATE_SECTION_IDS']
|
_WHITELIST_KEYS = ['HTTPS_KEY', 'UPDATE_SECTION_IDS']
|
||||||
|
|
||||||
|
|
||||||
|
def make_backup(cleanup=False, scheduler=False):
|
||||||
|
""" Makes a backup of config file, removes all but the last 5 backups """
|
||||||
|
|
||||||
|
if scheduler:
|
||||||
|
backup_file = 'config.backup-%s.sched.ini' % arrow.now().format('YYYYMMDDHHmmss')
|
||||||
|
else:
|
||||||
|
backup_file = 'config.backup-%s.ini' % arrow.now().format('YYYYMMDDHHmmss')
|
||||||
|
backup_folder = plexpy.CONFIG.BACKUP_DIR
|
||||||
|
backup_file_fp = os.path.join(backup_folder, backup_file)
|
||||||
|
|
||||||
|
# In case the user has deleted it manually
|
||||||
|
if not os.path.exists(backup_folder):
|
||||||
|
os.makedirs(backup_folder)
|
||||||
|
|
||||||
|
plexpy.CONFIG.write()
|
||||||
|
shutil.copyfile(plexpy.CONFIG_FILE, backup_file_fp)
|
||||||
|
|
||||||
|
if cleanup:
|
||||||
|
# Delete all scheduled backup files except from the last 5.
|
||||||
|
for root, dirs, files in os.walk(backup_folder):
|
||||||
|
db_files = [os.path.join(root, f) for f in files if f.endswith('.sched.ini')]
|
||||||
|
if len(db_files) > 5:
|
||||||
|
backups_sorted_on_age = sorted(db_files, key=os.path.getctime, reverse=True)
|
||||||
|
for file_ in backups_sorted_on_age[5:]:
|
||||||
|
try:
|
||||||
|
os.remove(file_)
|
||||||
|
except OSError as e:
|
||||||
|
logger.error(u"PlexPy Config :: Failed to delete %s from the backup folder: %s" % (file_, e))
|
||||||
|
|
||||||
|
if backup_file in os.listdir(backup_folder):
|
||||||
|
logger.debug(u"PlexPy Config :: Successfully backed up %s to %s" % (plexpy.CONFIG_FILE, backup_file))
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warn(u"PlexPy Config :: Failed to backup %s to %s" % (plexpy.CONFIG_FILE, backup_file))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# pylint:disable=R0902
|
# pylint:disable=R0902
|
||||||
# it might be nice to refactor for fewer instance variables
|
# it might be nice to refactor for fewer instance variables
|
||||||
class Config(object):
|
class Config(object):
|
||||||
|
@ -557,12 +600,12 @@ class Config(object):
|
||||||
new_config[section][ini_key] = self._config[section][ini_key]
|
new_config[section][ini_key] = self._config[section][ini_key]
|
||||||
|
|
||||||
# Write it to file
|
# Write it to file
|
||||||
logger.info("Writing configuration to file")
|
logger.info(u"PlexPy Config :: Writing configuration to file")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_config.write()
|
new_config.write()
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error("Error writing configuration file: %s", e)
|
logger.error(u"PlexPy Config :: Error writing configuration file: %s", e)
|
||||||
|
|
||||||
self._blacklist()
|
self._blacklist()
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import time
|
||||||
import plexpy
|
import plexpy
|
||||||
import logger
|
import logger
|
||||||
|
|
||||||
|
FILENAME = "plexpy.db"
|
||||||
db_lock = threading.Lock()
|
db_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ def delete_sessions():
|
||||||
logger.warn(u"PlexPy Database :: Unable to clear temporary sessions from database: %s." % e)
|
logger.warn(u"PlexPy Database :: Unable to clear temporary sessions from database: %s." % e)
|
||||||
return 'Unable to clear temporary sessions.'
|
return 'Unable to clear temporary sessions.'
|
||||||
|
|
||||||
def db_filename(filename="plexpy.db"):
|
def db_filename(filename=FILENAME):
|
||||||
""" Returns the filepath to the db """
|
""" Returns the filepath to the db """
|
||||||
|
|
||||||
return os.path.join(plexpy.DATA_DIR, filename)
|
return os.path.join(plexpy.DATA_DIR, filename)
|
||||||
|
@ -115,7 +116,7 @@ def dict_factory(cursor, row):
|
||||||
|
|
||||||
class MonitorDatabase(object):
|
class MonitorDatabase(object):
|
||||||
|
|
||||||
def __init__(self, filename='plexpy.db'):
|
def __init__(self, filename=FILENAME):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.connection = sqlite3.connect(db_filename(filename), timeout=20)
|
self.connection = sqlite3.connect(db_filename(filename), timeout=20)
|
||||||
# Don't wait for the disk to finish writing
|
# Don't wait for the disk to finish writing
|
||||||
|
|
|
@ -30,6 +30,7 @@ from mako import exceptions
|
||||||
|
|
||||||
import plexpy
|
import plexpy
|
||||||
import common
|
import common
|
||||||
|
import config
|
||||||
import database
|
import database
|
||||||
import datafactory
|
import datafactory
|
||||||
import graphs
|
import graphs
|
||||||
|
@ -2428,6 +2429,19 @@ class WebInterface(object):
|
||||||
|
|
||||||
return {'result': 'success', 'message': 'Settings saved.'}
|
return {'result': 'success', 'message': 'Settings saved.'}
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@cherrypy.tools.json_out()
|
||||||
|
@requireAuth(member_of("admin"))
|
||||||
|
def backup_config(self):
|
||||||
|
""" Creates a manual backup of the plexpy.db file """
|
||||||
|
|
||||||
|
result = config.make_backup()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return {'result': 'success', 'message': 'Config backup successful.'}
|
||||||
|
else:
|
||||||
|
return {'result': 'error', 'message': 'Config backup failed.'}
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def get_scheduler_table(self, **kwargs):
|
def get_scheduler_table(self, **kwargs):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue