dbus: Add new module for D-Bus services

- Implement listening for CacheUpdated notification.

- Configuration to allow only root to trigger the notification.

- Trigger the notification from an apt update hook.

- Retrieve the list of packages available for upgrade and print them to log.

- Add dependency on libglib2.0-bin for the gdbus command line tool.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2019-02-21 12:28:19 -08:00 committed by James Valleroy
parent 94255806cf
commit 2df02b059c
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
7 changed files with 228 additions and 8 deletions

View File

@ -0,0 +1,8 @@
// This configuration is installed by FreedomBox.
//
// When Apt's cache is updated (i.e. apt-cache update) notify FreedomBox service
// via it's D-Bus API. FreedomBox may then handle upgrade of some packages.
//
APT::Update::Post-Invoke-Success {
"/usr/bin/test -S /var/run/dbus/system_bus_socket && /usr/bin/gdbus call --system --dest org.freedombox.Service --object-path /org/freedombox/Service/PackageHandler --timeout 10 --method org.freedombox.Service.PackageHandler.CacheUpdated 2> /dev/null > /dev/null; /bin/echo > /dev/null";
};

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<!--
This file is part of FreedomBox.
Allow only root user to send any notifications to FreedomBox.
-->
<busconfig>
<policy user="root">
<allow own="org.freedombox.Service"/>
<allow send_destination="org.freedombox.Service"
send_path="/org/freedombox/Service/PackageHandler"/>
</policy>
</busconfig>

2
debian/control vendored
View File

@ -70,6 +70,8 @@ Depends:
gir1.2-nm-1.0,
javascript-common,
ldapscripts,
# For gdbus used to call hooks into service
libglib2.0-bin,
libjs-bootstrap,
libjs-jquery,
libjs-modernizr,

View File

@ -23,7 +23,7 @@ import sys
import axes
from . import (cfg, frontpage, log, menu, module_loader, service, setup,
from . import (cfg, dbus, frontpage, log, menu, module_loader, service, setup,
web_framework, web_server)
axes.default_app_config = "plinth.axes_app_config.AppConfig"
@ -129,6 +129,7 @@ def adapt_config(arguments):
def on_web_server_stop():
"""Stop all other threads since web server is trying to exit."""
setup.stop()
dbus.stop()
def main():
@ -177,6 +178,8 @@ def main():
setup.run_setup_in_background()
dbus.run()
web_server.init()
web_server.run(on_web_server_stop)

154
plinth/dbus.py Executable file
View File

@ -0,0 +1,154 @@
#
# 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 <http://www.gnu.org/licenses/>.
#
"""
Expose some API over D-Bus.
"""
import logging
import threading
from plinth.utils import import_from_gi
from . import setup
glib = import_from_gi('GLib', '2.0')
gio = import_from_gi('Gio', '2.0')
logger = logging.getLogger(__name__)
_thread = None
_server = None
_main_loop = None
class PackageHandler():
"""D-Bus service to listen for messages when apt cache is updated."""
introspection_xml = '''
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/org/freedombox/Service/PackageHandler">
<interface name="org.freedombox.Service.PackageHandler">
<method name="CacheUpdated"/>
</interface>
</node>
'''
def register(self, connection):
"""Register the object in D-Bus connection."""
introspection_data = gio.DBusNodeInfo.new_for_xml(
self.introspection_xml)
interface_info = gio.DBusNodeInfo.lookup_interface(
introspection_data, 'org.freedombox.Service.PackageHandler')
connection.register_object('/org/freedombox/Service/PackageHandler',
interface_info, self.on_method_call, None,
None)
def on_method_call(self, _connection, _sender, _object_path,
_interface_name, method_name, _parameters, invocation):
"""Handle method being called.
No need to check all the incoming parameters as D-Bus will validate all
the incoming parameters using introspection data.
"""
if method_name == 'CacheUpdated':
self.on_cache_updated()
invocation.return_value()
@staticmethod
def on_cache_updated():
"""Called when system package cache is updated."""
logger.info('Apt package cache updated.')
# Run in a new thread because we don't want to block the thread running
# Glib main loop.
threading.Thread(target=setup.on_package_cache_updated).start()
class DBusServer():
"""Abstraction over a connection to D-Bus."""
def __init__(self):
"""Initialize the server object."""
self.package_handler = None
def connect(self):
"""Connect to bus with well-known name."""
gio.bus_own_name(gio.BusType.SYSTEM, 'org.freedombox.Service',
gio.BusNameOwnerFlags.NONE, self.on_bus_acquired,
self.on_name_acquired, self.on_name_lost)
def on_bus_acquired(self, connection, name):
"""Callback when connection to D-Bus has been acquired."""
logger.info('D-Bus connection acquired: %s', name)
self.package_handler = PackageHandler()
self.package_handler.register(connection)
@staticmethod
def on_name_acquired(_connection, name):
"""Callback when service name on D-Bus has been acquired."""
logger.info('D-Bus name acquired: %s', name)
@staticmethod
def on_name_lost(_connection, name):
"""Callback when service name or DBus connection is closed."""
logger.info('D-Bus connection lost: %s', name)
# XXX: Reconnect after a while
#
# Such as by doing:
# connection.set_exit_on_close(False)
# gio.timeout_add(10000, self.connect)
#
# However, perhaps due to some cleanup issues, reconnection is not
# happening if it is does after an incoming method call.
#
# If D-Bus connection is lost due to daemon restart, FreedomBox service
# will receive a SIGTERM and exit. systemd should then restart the
# service again.
def run():
"""Run a glib main loop forever in a thread."""
global _thread
_thread = threading.Thread(target=_run)
_thread.start()
def stop():
"""Exit glib main loop and end the thread."""
if _main_loop:
logger.info('Exiting main loop for D-Bus services')
_main_loop.quit()
def _run():
"""Connect to D-Bus and run main loop."""
logger.info('Started new thread for D-Bus services')
global _server
_server = DBusServer()
_server.connect()
global _main_loop
_main_loop = glib.MainLoop()
_main_loop.run()
_main_loop = None
logger.info('D-Bus services thread exited.')

View File

@ -37,6 +37,8 @@ _is_first_setup = False
is_first_setup_running = False
_is_shutting_down = False
_force_upgrader = None
class Helper(object):
"""Helper routines for modules to show progress."""
@ -335,3 +337,31 @@ def run_setup_on_modules(module_list, allow_install=True):
except Exception as exception:
logger.error('Error running setup - %s', exception)
raise
class ForceUpgrader():
"""Find and upgrade packages by force when conffile prompt is needed."""
def on_package_cache_updated(self):
"""Find an upgrade packages."""
packages = self.get_list_of_upgradeable_packages()
if packages:
logger.info('Packages available for upgrade: %s',
', '.join([package.name for package in packages]))
# XXX: Implement force upgrading of selected packages
@staticmethod
def get_list_of_upgradeable_packages():
"""Return list of packages that can be upgraded."""
cache = apt.cache.Cache()
return [package for package in cache if package.is_upgradable]
def on_package_cache_updated():
"""Called by D-Bus service when apt package cache is updated."""
global _force_upgrader
if not _force_upgrader:
_force_upgrader = ForceUpgrader()
_force_upgrader.on_package_cache_updated()

View File

@ -236,10 +236,11 @@ setuptools.setup(
glob.glob('data/etc/apache2/sites-available/*.conf')),
('/etc/apache2/includes',
glob.glob('data/etc/apache2/includes/*.conf')),
('/etc/apt/apt.conf.d',
glob.glob('data/etc/apt/apt.conf.d/60unattended-upgrades')),
('/etc/avahi/services/',
glob.glob('data/etc/avahi/services/*.service')),
('/etc/apt/apt.conf.d', [
'data/etc/apt/apt.conf.d/60unattended-upgrades',
'data/etc/apt/apt.conf.d/20freedombox'
]), ('/etc/avahi/services/',
glob.glob('data/etc/avahi/services/*.service')),
('/etc/ikiwiki', glob.glob('data/etc/ikiwiki/*.setup')),
('/etc/NetworkManager/dispatcher.d/', [
'data/etc/NetworkManager/dispatcher.d/10-freedombox-batman'
@ -247,9 +248,10 @@ setuptools.setup(
'data/etc/sudoers.d/plinth'
]), ('/lib/systemd/system',
glob.glob('data/lib/systemd/system/*.service')),
('/lib/systemd/system/mldonkey-server.service.d',
['data/lib/systemd/system/mldonkey-server.service.d/freedombox.conf']),
('/lib/systemd/system', glob.glob('data/lib/systemd/system/*.timer')),
('/lib/systemd/system/mldonkey-server.service.d', [
'data/lib/systemd/system/mldonkey-server.service.d/freedombox.conf'
]), ('/lib/systemd/system',
glob.glob('data/lib/systemd/system/*.timer')),
('/etc/mediawiki',
glob.glob('data/etc/mediawiki/*.php')), ('/etc/update-motd.d/', [
'data/etc/update-motd.d/50-freedombox'
@ -265,6 +267,8 @@ setuptools.setup(
glob.glob('data/usr/share/augeas/lenses/*.aug')),
('/usr/share/augeas/lenses/tests',
glob.glob('data/usr/share/augeas/lenses/tests/test_*.aug')),
('/usr/share/dbus-1/system.d',
glob.glob('data/usr/share/dbus-1/system.d/*.conf')),
('/usr/share/pam-configs/',
glob.glob('data/usr/share/pam-configs/*-freedombox')),
('/etc/fail2ban/jail.d', glob.glob('data/etc/fail2ban/jail.d/*.conf')),