diff --git a/plinth/dbus.py b/plinth/dbus.py
index c3e7a815b..ca4cad47e 100755
--- a/plinth/dbus.py
+++ b/plinth/dbus.py
@@ -100,6 +100,10 @@ class DBusServer():
self.package_handler = PackageHandler()
self.package_handler.register(connection)
+ from plinth.modules.letsencrypt.dbus import LetsEncrypt
+ lets_encrypt = LetsEncrypt()
+ lets_encrypt.register(connection)
+
@staticmethod
def on_name_acquired(_connection, name):
"""Callback when service name on D-Bus has been acquired."""
diff --git a/plinth/modules/letsencrypt/dbus.py b/plinth/modules/letsencrypt/dbus.py
new file mode 100644
index 000000000..27e36689a
--- /dev/null
+++ b/plinth/modules/letsencrypt/dbus.py
@@ -0,0 +1,78 @@
+#
+# 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 .
+#
+"""
+Expose API over D-Bus to receive renewal notifications.
+"""
+
+import logging
+
+from plinth.utils import import_from_gi
+
+gio = import_from_gi('Gio', '2.0')
+
+logger = logging.getLogger(__name__)
+
+
+class LetsEncrypt:
+ """D-Bus service to listen for Let's Encrypt renewal notifications."""
+
+ introspection_xml = '''
+
+
+
+
+
+
+
+
+
+
+'''
+
+ 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.LetsEncrypt')
+ connection.register_object('/org/freedombox/Service/LetsEncrypt',
+ 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 == 'DeployHook':
+ self._on_deploy_hook(parameters[0], parameters[1])
+ invocation.return_value()
+
+ @staticmethod
+ def _on_deploy_hook(renewed_domains, renewed_lineage):
+ """Run renewal hooks for each app.
+
+ Don't run this a new thread here because
+ component.on_certificate_event() creates one anyway.
+
+ """
+ logger.info('LE certificate renewed (deployed): %s, %s.',
+ renewed_domains, renewed_lineage)