mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-09-19 04:59:01 +00:00
syncthing: Add backup/restore support
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
983f4d13e8
commit
529c281734
@ -15,7 +15,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
@apps @syncthing @sso
|
@apps @syncthing @sso @backups
|
||||||
Feature: Syncthing File Synchronization
|
Feature: Syncthing File Synchronization
|
||||||
Run Syncthing File Synchronization server.
|
Run Syncthing File Synchronization server.
|
||||||
|
|
||||||
@ -28,6 +28,27 @@ Scenario: Enable syncthing application
|
|||||||
When I enable the syncthing application
|
When I enable the syncthing application
|
||||||
Then the syncthing service should be running
|
Then the syncthing service should be running
|
||||||
|
|
||||||
|
Scenario: Add a syncthing folder
|
||||||
|
Given the syncthing application is enabled
|
||||||
|
And syncthing folder Test is not present
|
||||||
|
When I add a folder /tmp as syncthing folder Test
|
||||||
|
Then syncthing folder Test should be present
|
||||||
|
|
||||||
|
Scenario: Remove a syncthing folder
|
||||||
|
Given the syncthing application is enabled
|
||||||
|
And folder /tmp is present as syncthing folder Test
|
||||||
|
When I remove syncthing folder Test
|
||||||
|
Then syncthing folder Test should not be present
|
||||||
|
|
||||||
|
Scenario: Backup and restore syncthing
|
||||||
|
Given the syncthing application is enabled
|
||||||
|
And syncthing folder Test is not present
|
||||||
|
When I add a folder /tmp as syncthing folder Test
|
||||||
|
And I create a backup of the syncthing app data
|
||||||
|
And I remove syncthing folder Test
|
||||||
|
And I restore the syncthing app data backup
|
||||||
|
Then syncthing folder Test should be present
|
||||||
|
|
||||||
Scenario: Disable syncthing application
|
Scenario: Disable syncthing application
|
||||||
Given the syncthing application is enabled
|
Given the syncthing application is enabled
|
||||||
When I disable the syncthing application
|
When I disable the syncthing application
|
||||||
|
|||||||
@ -147,8 +147,7 @@ def transmission_upload_sample_torrent(browser):
|
|||||||
|
|
||||||
@then(
|
@then(
|
||||||
parsers.parse(
|
parsers.parse(
|
||||||
'there should be {torrents_number:d} torrents listed in transmission'
|
'there should be {torrents_number:d} torrents listed in transmission'))
|
||||||
))
|
|
||||||
def transmission_assert_number_of_torrents(browser, torrents_number):
|
def transmission_assert_number_of_torrents(browser, torrents_number):
|
||||||
assert torrents_number == site.transmission_get_number_of_torrents(browser)
|
assert torrents_number == site.transmission_get_number_of_torrents(browser)
|
||||||
|
|
||||||
@ -165,8 +164,7 @@ def deluge_upload_sample_torrent(browser):
|
|||||||
|
|
||||||
@then(
|
@then(
|
||||||
parsers.parse(
|
parsers.parse(
|
||||||
'there should be {torrents_number:d} torrents listed in deluge'
|
'there should be {torrents_number:d} torrents listed in deluge'))
|
||||||
))
|
|
||||||
def deluge_assert_number_of_torrents(browser, torrents_number):
|
def deluge_assert_number_of_torrents(browser, torrents_number):
|
||||||
assert torrents_number == site.deluge_get_number_of_torrents(browser)
|
assert torrents_number == site.deluge_get_number_of_torrents(browser)
|
||||||
|
|
||||||
@ -189,3 +187,39 @@ def assert_addressbook_is_available(browser):
|
|||||||
@then('the addressbook should not be available')
|
@then('the addressbook should not be available')
|
||||||
def assert_addressbook_is_not_available(browser):
|
def assert_addressbook_is_not_available(browser):
|
||||||
assert not site.addressbook_is_available(browser)
|
assert not site.addressbook_is_available(browser)
|
||||||
|
|
||||||
|
|
||||||
|
@given(parsers.parse('syncthing folder {folder_name:w} is not present'))
|
||||||
|
def syncthing_folder_not_present(browser, folder_name):
|
||||||
|
if site.syncthing_folder_is_present(browser, folder_name):
|
||||||
|
site.syncthing_remove_folder(browser, folder_name)
|
||||||
|
|
||||||
|
|
||||||
|
@given(
|
||||||
|
parsers.parse(
|
||||||
|
'folder {folder_path:S} is present as syncthing folder {folder_name:w}'))
|
||||||
|
def syncthing_folder_present(browser, folder_name, folder_path):
|
||||||
|
if not site.syncthing_folder_is_present(browser, folder_name):
|
||||||
|
site.syncthing_add_folder(browser, folder_name, folder_path)
|
||||||
|
|
||||||
|
|
||||||
|
@when(
|
||||||
|
parsers.parse(
|
||||||
|
'I add a folder {folder_path:S} as syncthing folder {folder_name:w}'))
|
||||||
|
def syncthing_add_folder(browser, folder_name, folder_path):
|
||||||
|
site.syncthing_add_folder(browser, folder_name, folder_path)
|
||||||
|
|
||||||
|
|
||||||
|
@when(parsers.parse('I remove syncthing folder {folder_name:w}'))
|
||||||
|
def syncthing_remove_folder(browser, folder_name):
|
||||||
|
site.syncthing_remove_folder(browser, folder_name)
|
||||||
|
|
||||||
|
|
||||||
|
@then(parsers.parse('syncthing folder {folder_name:w} should be present'))
|
||||||
|
def syncthing_assert_folder_present(browser, folder_name):
|
||||||
|
assert site.syncthing_folder_is_present(browser, folder_name)
|
||||||
|
|
||||||
|
|
||||||
|
@then(parsers.parse('syncthing folder {folder_name:w} should not be present'))
|
||||||
|
def syncthing_assert_folder_not_present(browser, folder_name):
|
||||||
|
assert not site.syncthing_folder_is_present(browser, folder_name)
|
||||||
|
|||||||
@ -31,7 +31,8 @@ from support.service import eventually, wait_for_page_update
|
|||||||
site_url = {
|
site_url = {
|
||||||
'wiki': '/ikiwiki',
|
'wiki': '/ikiwiki',
|
||||||
'jsxc': '/plinth/apps/jsxc/jsxc/',
|
'jsxc': '/plinth/apps/jsxc/jsxc/',
|
||||||
'cockpit': '/_cockpit/'
|
'cockpit': '/_cockpit/',
|
||||||
|
'syncthing': '/syncthing/',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -432,8 +433,8 @@ def calendar_is_available(browser):
|
|||||||
conf = config['DEFAULT']
|
conf = config['DEFAULT']
|
||||||
url = conf['url'] + '/.well-known/caldav'
|
url = conf['url'] + '/.well-known/caldav'
|
||||||
logging.captureWarnings(True)
|
logging.captureWarnings(True)
|
||||||
request = requests.get(
|
request = requests.get(url, auth=(conf['username'], conf['password']),
|
||||||
url, auth=(conf['username'], conf['password']), verify=False)
|
verify=False)
|
||||||
logging.captureWarnings(False)
|
logging.captureWarnings(False)
|
||||||
return request.status_code != 404
|
return request.status_code != 404
|
||||||
|
|
||||||
@ -443,7 +444,103 @@ def addressbook_is_available(browser):
|
|||||||
conf = config['DEFAULT']
|
conf = config['DEFAULT']
|
||||||
url = conf['url'] + '/.well-known/carddav'
|
url = conf['url'] + '/.well-known/carddav'
|
||||||
logging.captureWarnings(True)
|
logging.captureWarnings(True)
|
||||||
request = requests.get(
|
request = requests.get(url, auth=(conf['username'], conf['password']),
|
||||||
url, auth=(conf['username'], conf['password']), verify=False)
|
verify=False)
|
||||||
logging.captureWarnings(False)
|
logging.captureWarnings(False)
|
||||||
return request.status_code != 404
|
return request.status_code != 404
|
||||||
|
|
||||||
|
|
||||||
|
def _syncthing_load_main_interface(browser):
|
||||||
|
"""Close the dialog boxes that many popup after visiting the URL."""
|
||||||
|
access_url(browser, 'syncthing')
|
||||||
|
|
||||||
|
def service_is_available():
|
||||||
|
if browser.is_element_present_by_xpath(
|
||||||
|
'//h1[text()="Service Unavailable"]'):
|
||||||
|
access_url(browser, 'syncthing')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# After a backup restore, service may not be available immediately
|
||||||
|
eventually(service_is_available)
|
||||||
|
|
||||||
|
# Wait for javascript loading process to complete
|
||||||
|
browser.execute_script('''
|
||||||
|
document.is_ui_online = false;
|
||||||
|
var old_console_log = console.log;
|
||||||
|
console.log = function(message) {
|
||||||
|
old_console_log.apply(null, arguments);
|
||||||
|
if (message == 'UIOnline') {
|
||||||
|
document.is_ui_online = true;
|
||||||
|
console.log = old_console_log;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
''')
|
||||||
|
eventually(lambda: browser.evaluate_script('document.is_ui_online'),
|
||||||
|
timeout=5)
|
||||||
|
|
||||||
|
# Dismiss the Usage Reporting consent dialog
|
||||||
|
usage_reporting = browser.find_by_id('ur').first
|
||||||
|
eventually(lambda: usage_reporting.visible, timeout=2)
|
||||||
|
if usage_reporting.visible:
|
||||||
|
yes_xpath = './/button[contains(@ng-click, "declineUR")]'
|
||||||
|
usage_reporting.find_by_xpath(yes_xpath).first.click()
|
||||||
|
eventually(lambda: not usage_reporting.visible)
|
||||||
|
|
||||||
|
|
||||||
|
def syncthing_folder_is_present(browser, folder_name):
|
||||||
|
"""Return whether a folder is present in Syncthing."""
|
||||||
|
_syncthing_load_main_interface(browser)
|
||||||
|
folder_names = browser.find_by_css('#folders .panel-title-text span')
|
||||||
|
folder_names = [folder_name.text for folder_name in folder_names]
|
||||||
|
return folder_name in folder_names
|
||||||
|
|
||||||
|
|
||||||
|
def syncthing_add_folder(browser, folder_name, folder_path):
|
||||||
|
"""Add a new folder to Synthing."""
|
||||||
|
_syncthing_load_main_interface(browser)
|
||||||
|
add_folder_xpath = '//button[contains(@ng-click, "addFolder")]'
|
||||||
|
browser.find_by_xpath(add_folder_xpath).click()
|
||||||
|
|
||||||
|
folder_dialog = browser.find_by_id('editFolder').first
|
||||||
|
eventually(lambda: folder_dialog.visible)
|
||||||
|
browser.find_by_id('folderLabel').fill(folder_name)
|
||||||
|
browser.find_by_id('folderPath').fill(folder_path)
|
||||||
|
save_folder_xpath = './/button[contains(@ng-click, "saveFolder")]'
|
||||||
|
folder_dialog.find_by_xpath(save_folder_xpath).first.click()
|
||||||
|
eventually(lambda: not folder_dialog.visible)
|
||||||
|
|
||||||
|
|
||||||
|
def syncthing_remove_folder(browser, folder_name):
|
||||||
|
"""Remove a folder from Synthing."""
|
||||||
|
_syncthing_load_main_interface(browser)
|
||||||
|
|
||||||
|
# Find folder
|
||||||
|
folder = None
|
||||||
|
for current_folder in browser.find_by_css('#folders > .panel'):
|
||||||
|
name = current_folder.find_by_css('.panel-title-text span').first.text
|
||||||
|
if name == folder_name:
|
||||||
|
folder = current_folder
|
||||||
|
break
|
||||||
|
|
||||||
|
# Edit folder button
|
||||||
|
folder.find_by_css('button.panel-heading').first.click()
|
||||||
|
eventually(lambda: folder.find_by_css('div.collapse.in'))
|
||||||
|
edit_folder_xpath = './/button[contains(@ng-click, "editFolder")]'
|
||||||
|
edit_folder_button = folder.find_by_xpath(edit_folder_xpath).first
|
||||||
|
edit_folder_button.click()
|
||||||
|
|
||||||
|
# Edit folder dialog
|
||||||
|
folder_dialog = browser.find_by_id('editFolder').first
|
||||||
|
eventually(lambda: folder_dialog.visible)
|
||||||
|
remove_button_xpath = './/button[contains(@data-target, "remove-folder")]'
|
||||||
|
folder_dialog.find_by_xpath(remove_button_xpath).first.click()
|
||||||
|
|
||||||
|
# Remove confirmation dialog
|
||||||
|
remove_folder_dialog = browser.find_by_id('remove-folder-confirmation')
|
||||||
|
eventually(lambda: remove_folder_dialog.visible)
|
||||||
|
remove_button_xpath = './/button[contains(@ng-click, "deleteFolder")]'
|
||||||
|
remove_folder_dialog.find_by_xpath(remove_button_xpath).first.click()
|
||||||
|
|
||||||
|
eventually(lambda: not folder_dialog.visible)
|
||||||
|
|||||||
@ -26,11 +26,11 @@ from plinth.menu import main_menu
|
|||||||
from plinth.modules.users import register_group
|
from plinth.modules.users import register_group
|
||||||
from plinth.utils import format_lazy
|
from plinth.utils import format_lazy
|
||||||
|
|
||||||
from .manifest import clients
|
from .manifest import backup, clients
|
||||||
|
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
managed_services = ['syncthing']
|
managed_services = ['syncthing@syncthing']
|
||||||
|
|
||||||
managed_packages = ['syncthing']
|
managed_packages = ['syncthing']
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.clients import store_url, validate
|
from plinth.clients import store_url, validate
|
||||||
|
from plinth.modules.backups.api import validate as validate_backup
|
||||||
|
|
||||||
_package_id = 'com.nutomic.syncthingandroid'
|
_package_id = 'com.nutomic.syncthingandroid'
|
||||||
_download_url = 'https://syncthing.net/'
|
_download_url = 'https://syncthing.net/'
|
||||||
@ -60,3 +61,10 @@ clients = validate([{
|
|||||||
'url': '/syncthing'
|
'url': '/syncthing'
|
||||||
}]
|
}]
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
backup = validate_backup({
|
||||||
|
'secrets': {
|
||||||
|
'directories': ['/var/lib/syncthing/.config']
|
||||||
|
},
|
||||||
|
'services': ['syncthing@syncthing']
|
||||||
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user