diff --git a/plinth/modules/snapshot/__init__.py b/plinth/modules/snapshot/__init__.py
index fc7187073..f9de65643 100644
--- a/plinth/modules/snapshot/__init__.py
+++ b/plinth/modules/snapshot/__init__.py
@@ -18,13 +18,14 @@
FreedomBox app to manage filesystem snapshots.
"""
-import augeas
import json
+import augeas
from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth.menu import main_menu
+from plinth.modules import storage
from .manifest import backup
@@ -54,6 +55,8 @@ manual_page = 'Snapshots'
DEFAULT_FILE = '/etc/default/snapper'
+fs_types_supported = ['btrfs']
+
def init():
"""Initialize the module."""
@@ -61,12 +64,19 @@ def init():
menu.add_urlname(name, 'glyphicon-film', 'snapshot:index')
+def is_supported():
+ """Return whether snapshots are support on current setup."""
+ fs_type = storage.get_filesystem_type()
+ return fs_type in fs_types_supported
+
+
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(managed_packages)
- helper.call(
- 'post', actions.superuser_run, 'snapshot',
- ['setup', '--old-version', str(old_version)])
+ if is_supported():
+ helper.call('post', actions.superuser_run, 'snapshot',
+ ['setup', '--old-version',
+ str(old_version)])
def load_augeas():
diff --git a/plinth/modules/snapshot/templates/snapshot_not_supported.html b/plinth/modules/snapshot/templates/snapshot_not_supported.html
new file mode 100644
index 000000000..f555a0d08
--- /dev/null
+++ b/plinth/modules/snapshot/templates/snapshot_not_supported.html
@@ -0,0 +1,32 @@
+{% extends "service-subsubmenu.html" %}
+{% comment %}
+#
+# This file is part of FreedomBox.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+{% endcomment %}
+
+{% load bootstrap %}
+{% load i18n %}
+
+{% block configuration %}
+
+ {% blocktrans trimmed with fs_types_supported|join:', ' as types_supported %}
+ Your have a filesystem of type {{ fs_type }}. Snapshots
+ are currently only available on {{ types_supported }}
+ filesystems.
+ {% endblocktrans %}
+
+{% endblock %}
diff --git a/plinth/modules/snapshot/views.py b/plinth/modules/snapshot/views.py
index 33954ff06..9c56170dd 100644
--- a/plinth/modules/snapshot/views.py
+++ b/plinth/modules/snapshot/views.py
@@ -30,6 +30,7 @@ from django.utils.translation import ugettext_lazy
from plinth import actions
from plinth.errors import ActionError
from plinth.modules import snapshot as snapshot_module
+from plinth.modules import storage
from . import get_configuration
from .forms import SnapshotForm
@@ -43,8 +44,24 @@ subsubmenu = [{
}]
+def not_supported_view(request):
+ """Show that snapshots are not supported on the system."""
+ template_data = {
+ 'title': snapshot_module.name,
+ 'description': snapshot_module.description,
+ 'fs_type': storage.get_filesystem_type(),
+ 'fs_types_supported': snapshot_module.fs_types_supported,
+ 'manual_page': snapshot_module.manual_page,
+ }
+ return TemplateResponse(request, 'snapshot_not_supported.html',
+ template_data)
+
+
def index(request):
"""Show snapshot list."""
+ if not snapshot_module.is_supported():
+ return not_supported_view(request)
+
status = get_configuration()
if request.method == 'POST':
form = SnapshotForm(request.POST)
@@ -66,6 +83,9 @@ def index(request):
def manage(request):
"""Show snapshot list."""
+ if not snapshot_module.is_supported():
+ return not_supported_view(request)
+
if request.method == 'POST':
if 'create' in request.POST:
actions.superuser_run('snapshot', ['create'])
@@ -128,10 +148,10 @@ def update_configuration(request, old_status, new_status):
messages.success(request, _('Storage snapshots configuration updated'))
except ActionError as exception:
- messages.error(request,
- _('Action error: {0} [{1}] [{2}]').format(
- exception.args[0], exception.args[1],
- exception.args[2]))
+ messages.error(
+ request,
+ _('Action error: {0} [{1}] [{2}]').format(
+ exception.args[0], exception.args[1], exception.args[2]))
def delete_selected(request):
diff --git a/plinth/modules/storage/__init__.py b/plinth/modules/storage/__init__.py
index 08dcfe973..99c88de71 100644
--- a/plinth/modules/storage/__init__.py
+++ b/plinth/modules/storage/__init__.py
@@ -21,10 +21,11 @@ import json
import logging
import subprocess
+import psutil
from django.utils.translation import ugettext_lazy as _
-from plinth import service as service_module
from plinth import action_utils, actions, cfg
+from plinth import service as service_module
from plinth.errors import PlinthError
from plinth.menu import main_menu
from plinth.utils import format_lazy, import_from_gi, is_user_admin
@@ -132,6 +133,15 @@ def _get_disks_from_lsblk():
return disks
+def get_filesystem_type(mount_point='/'):
+ """Returns the type of the filesystem mounted at mountpoint."""
+ for partition in psutil.disk_partitions():
+ if partition.mountpoint == mount_point:
+ return partition.fstype
+
+ raise ValueError('No such mount point')
+
+
def get_disk_info(mountpoint, request):
"""Get information about the free space of a drive"""
if not is_user_admin(request, cached=True):