Sunil Mohan Adapa 186596cfbf
config: Install and configure zram for swap
Closes: Debian #805108.

Primary motivation is to provide swap for FreedomBox machines. On all FreedomBox
images, currently there is no swap configured. Swap on disk may not be good for
SBCs most of which use SD card for storage. We wish for processes to not get
killed when hard memory limit is reached.

Zram seems like a good solution to the problem suitable not only for SBCs but
also for desktops and bigger machines. Fedora is currently using Zram as its
default swap solution configured by the installer. Zram creates a block device
with a configured size. Writing blocks into the device compresses them and
stores them in RAM. This block device can be configured as swap among other
things. See:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/blockdev/zram.rst

Set the size of the swap to be 50% of RAM. Expected compression is about 1:2.
That means, in an average case, 25% of RAM is consumed to provide the swap
device. This results in the system being able to consume about 125% of RAM
capacity to run processes. This value is inspired by Fedora.
https://fedoraproject.org/wiki/Changes/SwapOnZRAM .

Zram based swap takes priority over disk based swap (due the priority being set
to 100). This reduces IO and improves latency on machines that already have a
swap device.

On containers, zramswap.service fails to start as it will not be possible to
insert the 'zram' kernel module from within the container. This should not cause
any further problems.

Since 'config' app is an essential app, zram-tools now becomes a hard dependency
of freedombox package.

For FreedomBox images, zram-tools will be pre-installed and pre-configured. So,
it will work on first boot. For users installing FreedomBox via apt or those
upgrading from an older version, zram-tools will be newly installed but
configuration will not be picked up until the next reboot. Restarting
zramswap.service is not done because it may not be a safe/successful operation.

systemd-zram-generator is a project that essentially does what zram-tools. It
appears to be a better implementation and we may migrate to it when it becomes
available in Debian. Migration expected to be straight forward.

Tests performed:

- Running `sudo -u plinth ./run --list-dependencies` shows zram-tools as a
dependency.

- On a container, `systemctl status zramswap.service` shows as failed.

- On a virtual machine, confirm that configuration is installed properly. Run
`./setup.py install; systemctl daemon-reload; systemctl show zramswap.service |
grep Environment`.

- On a virtual machine, ensure that you have more than 512MiB or RAM. Then
restart zramswap.service. This should create a swap space of 50% of RAM
capacity. Confirm with `free` and `zramswap status`.

- Restarting the VM retains the swap that has been setup.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2021-03-06 09:00:28 -05:00

214 lines
6.5 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for basic system configuration.
"""
import os
import socket
import augeas
from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth import app as app_module
from plinth import frontpage, menu
from plinth.modules.apache import (get_users_with_website, user_of_uws_url,
uws_url_of_user)
from plinth.modules.names.components import DomainType
from plinth.signals import domain_added
version = 3
is_essential = True
managed_services = ['systemd-journald', 'rsyslog']
_description = [
_('Here you can set some general configuration options '
'like hostname, domain name, webserver home page etc.')
]
depends = ['apache', 'firewall', 'names']
managed_packages = ['zram-tools']
APACHE_CONF_ENABLED_DIR = '/etc/apache2/conf-enabled'
APACHE_HOMEPAGE_CONF_FILE_NAME = 'freedombox-apache-homepage.conf'
APACHE_HOMEPAGE_CONFIG = os.path.join(APACHE_CONF_ENABLED_DIR,
APACHE_HOMEPAGE_CONF_FILE_NAME)
FREEDOMBOX_APACHE_CONFIG = os.path.join(APACHE_CONF_ENABLED_DIR,
'freedombox.conf')
ADVANCED_MODE_KEY = 'advanced_mode'
app = None
class ConfigApp(app_module.App):
"""FreedomBox app for basic system configuration."""
app_id = 'config'
can_be_disabled = False
def __init__(self):
"""Create components for the app."""
super().__init__()
info = app_module.Info(app_id=self.app_id, version=version,
is_essential=is_essential, depends=depends,
name=_('General Configuration'), icon='fa-cog',
description=_description,
manual_page='Configure')
self.add(info)
menu_item = menu.Menu('menu-config', _('Configure'), None, info.icon,
'config:index', parent_url_name='system')
self.add(menu_item)
domain_type = DomainType('domain-type-static', _('Domain Name'),
'config:index', can_have_certificate=True)
self.add(domain_type)
# Register domain with Name Services module.
domainname = get_domainname()
if domainname:
domain_added.send_robust(sender='config',
domain_type='domain-type-static',
name=domainname, services='__all__')
def get_domainname():
"""Return the domainname"""
fqdn = socket.getfqdn()
return '.'.join(fqdn.split('.')[1:])
def get_hostname():
"""Return the hostname"""
return socket.gethostname()
def home_page_url2scid(url):
"""Returns the shortcut ID of the given home page url."""
if url in ('/plinth/', '/plinth', 'plinth'):
return 'plinth'
if url == '/index.html':
return 'apache-default'
if url and url.startswith('/~'):
return 'uws-{}'.format(user_of_uws_url(url))
shortcuts = frontpage.Shortcut.list()
for shortcut in shortcuts:
if shortcut.url == url:
return shortcut.component_id
return None
def _home_page_scid2url(shortcut_id):
"""Returns the url for the given home page shortcut ID."""
if shortcut_id is None:
url = None
elif shortcut_id == 'plinth':
url = '/plinth/'
elif shortcut_id == 'apache-default':
url = '/index.html'
elif shortcut_id.startswith('uws-'):
user = shortcut_id[4:]
if user in get_users_with_website():
url = uws_url_of_user(user)
else:
url = None
else:
shortcuts = frontpage.Shortcut.list()
aux = [
shortcut.url for shortcut in shortcuts
if shortcut_id == shortcut.component_id
]
url = aux[0] if 1 == len(aux) else None
return url
def _get_home_page_url(conf_file):
"""Get the default application for the domain."""
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
aug.set('/augeas/load/Httpd/incl[last() + 1]', conf_file)
aug.load()
aug.defvar('conf', '/files' + conf_file)
for match in aug.match('/files' + conf_file +
'/directive["RedirectMatch"]'):
if aug.get(match + "/arg[1]") == '''"^/$"''':
return aug.get(match + "/arg[2]").strip('"')
return None
def get_home_page():
"""Return the shortcut ID that is set as current home page."""
CONF_FILE = APACHE_HOMEPAGE_CONFIG if os.path.exists(
APACHE_HOMEPAGE_CONFIG) else FREEDOMBOX_APACHE_CONFIG
url = _get_home_page_url(CONF_FILE)
return home_page_url2scid(url)
def change_home_page(shortcut_id):
"""Change the FreedomBox's default redirect to URL of the shortcut
specified.
"""
url = _home_page_scid2url(shortcut_id)
if url is None:
url = '/plinth/' # fall back to default url if scid is unknown.
# URL may be a reverse_lazy() proxy
actions.superuser_run('config', ['set-home-page', str(url)])
def get_advanced_mode():
"""Get whether option is enabled."""
from plinth import kvstore
return kvstore.get_default(ADVANCED_MODE_KEY, False)
def set_advanced_mode(advanced_mode):
"""Turn on/off advanced mode."""
from plinth import kvstore
kvstore.set(ADVANCED_MODE_KEY, advanced_mode)
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(managed_packages)
_migrate_home_page_config()
# systemd-journald is socket activated, it may not be running and it does
# not support reload.
actions.superuser_run('service', ['try-restart', 'systemd-journald'])
# rsyslog when enabled, is activated by syslog.socket (shipped by systemd).
# See: https://www.freedesktop.org/wiki/Software/systemd/syslog/ .
actions.superuser_run('service', ['disable', 'rsyslog'])
# Ensure that rsyslog is not started by something else as it is installed
# by default on Debian systems.
actions.superuser_run('service', ['mask', 'rsyslog'])
def _migrate_home_page_config():
"""Move the home page configuration to an external file."""
# Hold the current home page in a variable
home_page = get_home_page()
# Reset the home page to plinth in freedombox.conf
actions.superuser_run('config', ['reset-home-page'])
# Write the home page setting into the new conf file
# This step is run at the end because it reloads the Apache server
change_home_page(home_page)