diff --git a/actions/letsencrypt b/actions/letsencrypt index c2ce1d70f..dcf09632e 100755 --- a/actions/letsencrypt +++ b/actions/letsencrypt @@ -22,15 +22,11 @@ Configuration helper for Let's Encrypt. import argparse import json import os -import shutil +import re import subprocess import sys -import re -import configobj from plinth import action_utils -from plinth.errors import ActionError -from plinth.modules import config from plinth.modules import letsencrypt as le TEST_MODE = False @@ -69,43 +65,18 @@ def parse_arguments(): delete_parser.add_argument('--domain', required=True, help='Domain name to delete certificate of') - help_hooks = 'FreedomBox management of certificates (for current domain).' - manage_hook_parser = subparsers.add_parser('manage_hooks', help=help_hooks) - manage_hook_parser.add_argument('command', help=help_hooks, - choices=('enable', 'disable', 'status')) - manage_hook_parser.add_argument('--domain', - help='Domain for hook management command.') - help_module_arg = 'For enable: Also use LE cert with other FreedomBox ' \ - 'apps.' - manage_hook_parser.add_argument('--modules', help=help_module_arg, - nargs='+', default=[], - choices=le.MODULES_WITH_HOOKS) + help_hooks = 'Does nothing, kept for compatibility.' + subparser = subparsers.add_parser('run_pre_hooks', help=help_hooks) + subparser.add_argument('--domain') + subparser.add_argument('--modules', nargs='+', default=[]) - help_domain_arg = 'Domain name to run the hook scripts with.' - help_module_arg = 'Include hooks from the provided module names.' - help_pre_hooks = 'Maintenance tasks before a cert is obtained or renewed.' - run_pre_hooks_parser = subparsers.add_parser('run_pre_hooks', - help=help_pre_hooks) - run_pre_hooks_parser.add_argument('--domain', help=help_domain_arg) - run_pre_hooks_parser.add_argument('--modules', help=help_module_arg, - nargs='+', default=[], - choices=le.MODULES_WITH_HOOKS) + subparser = subparsers.add_parser('run_renew_hooks', help=help_hooks) + subparser.add_argument('--domain') + subparser.add_argument('--modules', nargs='+', default=[]) - help_renew_hooks = 'Maintenance tasks after a cert is actually renewed.' - run_renew_hooks_parser = subparsers.add_parser('run_renew_hooks', - help=help_renew_hooks) - run_renew_hooks_parser.add_argument('--domain', help=help_domain_arg) - run_renew_hooks_parser.add_argument('--modules', help=help_module_arg, - nargs='+', default=[], - choices=le.MODULES_WITH_HOOKS) - - help_post_hooks = 'Maintenance tasks after a cert is obtained or renewed.' - run_post_hooks_parser = subparsers.add_parser('run_post_hooks', - help=help_post_hooks) - run_post_hooks_parser.add_argument('--domain', help=help_domain_arg) - run_post_hooks_parser.add_argument('--modules', help=help_module_arg, - nargs='+', default=[], - choices=le.MODULES_WITH_HOOKS) + subparser = subparsers.add_parser('run_post_hooks', help=help_hooks) + subparser.add_argument('--domain') + subparser.add_argument('--modules', nargs='+', default=[]) subparsers.required = True return parser.parse_args() @@ -231,259 +202,43 @@ def subcommand_obtain(arguments): setup_webserver_config(domain, webserver_change) -def subcommand_manage_hooks(arguments): +def subcommand_run_pre_hooks(_): + """Do nothing, kept for legacy LE configuration. + + If new version of Plinth is deployed and before it can update the Let's + Encrypt configuration and remove these old hooks, if a renew operation is + run, then we don't want it to exit with non-zero error code because this + hook could not be run. + + Remove at some point in the future. + """ - Enable/disable/status of certbot's pre-, renew-, & post-hooks for renewal. - Enable edits renewal config of current domain (RENEWAL_DIR/DOMAIN.config), - and creates a backup beforehand that is restored if disable is called. - Commands disable and status work without error on any domain string. + + +def subcommand_run_renew_hooks(_): + """Do nothing, kept for legacy LE configuration. + + If new version of Plinth is deployed and before it can update the Let's + Encrypt configuration and remove these old hooks, if a renew operation is + run, then we don't want it to exit with non-zero error code because this + hook could not be run. + + Remove at some point in the future. + """ - if arguments.command not in ('enable', 'disable', 'status'): - print('Aborted: Unknown command "%s".' % arguments.command) - sys.exit(1) - - cmd_is_enable = arguments.command == 'enable' - current_domain = config.get_domainname() - if not arguments.domain: - arguments.domain = current_domain - - if cmd_is_enable: - if not current_domain: - print('Aborted: No current domain set.') - sys.exit(2) - - if not arguments.domain == current_domain: - print('Aborted: Passed domain (%s) is not current domain (%s).' % - (arguments.domain, current_domain)) - sys.exit(3) - - domain_status = get_status() - if current_domain not in domain_status: - print('Aborted: Current domain (%s) has no LE certificate.' % - current_domain) - sys.exit(4) - - config_path = RENEWAL_DIRECTORY + arguments.domain + '.conf' - if not os.path.exists(config_path): - msg, code = ('Aborted', 5) if cmd_is_enable else ('Disabled', 0) - print('%s: No certbot config file at %s.' % (msg, config_path)) - sys.exit(code) - - config_certbot = configobj.ConfigObj(config_path) - if 'renewalparams' not in config_certbot: - msg, code = ('Aborted', 6) if cmd_is_enable else ('Disabled', 0) - print('%s: No section [renewalparams] in config file at %s.' % - (msg, config_path)) - sys.exit(code) - - script_path = os.path.realpath(__file__) - config_backup_path = config_path + '_plinth_backup' - call_pre = script_path + ' run_pre_hooks --domain ' + arguments.domain - call_renew = script_path + ' run_renew_hooks --domain ' + arguments.domain - call_post = script_path + ' run_post_hooks --domain ' + arguments.domain - config_plinth = { - 'renewalparams': { - 'authenticator': AUTHENTICATOR, - # 'webroot_path': [WEB_ROOT_PATH], # removed by renew... - 'webroot_map': { - arguments.domain: WEB_ROOT_PATH - }, - 'installer': 'None', - 'pre_hook': call_pre, - 'renew_hook': call_renew, - 'post_hook': call_post - } - } - comment_plinth = '# This file was edited by Plinth.' - config_edited_by_plinth = any([ - 'edited by plinth' in line.lower() - for line in config_certbot.initial_comment - ]) - - if arguments.command == 'status': - # check for presence of expected minimal configuration - config_checks = [ - (entry in config_certbot['renewalparams']) - and (str(config_plinth['renewalparams'][entry]) in str( - config_certbot['renewalparams'][entry])) - for entry in config_plinth['renewalparams'].keys() - ] - - if not all(config_checks): - print('disabled') - sys.exit(0) - - # is enabled; check for which selected modules (only for renew_hook) - cmd_str = config_certbot['renewalparams']['renew_hook'] - module_list = [] - for mod_str in le.MODULES_WITH_HOOKS: - mod_pattern = 'letsencrypt .*--modules .*%s.*' % mod_str - match = re.search(mod_pattern, cmd_str) - if match is not None: - module_list.append(mod_str) - - if module_list != []: - print('enabled, with modules: ' + ', '.join(module_list)) - else: - print('enabled, without modules') - - elif arguments.command == 'enable': - if not config_edited_by_plinth: - shutil.copy(config_path, config_backup_path) - config_certbot.initial_comment.append(comment_plinth) - - if arguments.modules != []: - call_renew += ' --modules ' + ' '.join(arguments.modules) - config_plinth['renewalparams']['renew_hook'] = call_renew - - config_certbot['renewalparams'].update(config_plinth['renewalparams']) - config_certbot.write() - - if arguments.modules != []: - print('enabled, with modules: ' + ', '.join(arguments.modules)) - else: - print('enabled, without modules') - - elif arguments.command == 'disable': - # if changed, restore from backup; refuse disabling if no backup exists - if not config_edited_by_plinth: - print('Disabled: Nothing to do, hook management was not enabled.') - elif os.path.exists(config_backup_path): - shutil.move(config_backup_path, config_path) - print('disabled successfully') - else: - print('Aborted: No backup config file at %s.' % config_backup_path) - sys.exit(7) - - sys.exit(0) -def subcommand_run_pre_hooks(arguments): +def subcommand_run_post_hooks(_): + """Do nothing, kept for legacy LE configuration. + + If new version of Plinth is deployed and before it can update the Let's + Encrypt configuration and remove these old hooks, if a renew operation is + run, then we don't want it to exit with non-zero error code because this + hook could not be run. + + Remove at some point in the future. + """ - Execute all needed maintenance tasks BEFORE a cert is obtained/renewed. - If registered as certbot's pre-hook, this script gets ALWAYS executed when - certbot attempts a renewal, irrespective of necessity/success (2x per day). - """ - # Require current domain, to avoid confusion (e.g. call from old cron job). - if not arguments.domain: - print('Aborted: You must specify the current domain.') - sys.exit(1) - - current_domain = config.get_domainname() - if not arguments.domain == current_domain: - print('Aborted: Current domain is %s, but called for %s.' % - (current_domain, arguments.domain)) - sys.exit(2) - - sys.exit(0) - - -def subcommand_run_renew_hooks(arguments): - """ - Execute ALL maintenance tasks when (just after) certbot renews a cert, i.e. - run all tasks of every app/plinth module that supports usage of LE certs. - If registered as certbot's renew-hook, this script gets ONLY executed when - certbot successfully renewed a certificate; with Debian default config, - this means it would run about once every 60 days (renewals get executed - if a cert is <30 days before expiry, and current default is 90 days). - Errors will be logged by certbot to /var/log/letsencrypt/letsencrypt.log. - """ - # Require current domain, to avoid confusion (e.g. call from old cron job). - if not arguments.domain: - print('Aborted: You must specify the current domain.') - sys.exit(1) - - current_domain = config.get_domainname() - if not arguments.domain == current_domain: - print('Aborted: Current domain is %s, but called for %s.' % - (current_domain, arguments.domain)) - sys.exit(2) - - if action_utils.service_is_running('apache2'): - action_utils.service_restart('apache2') - - for module in arguments.modules: - # If >1 modules, collect errors and raise just one in the end, - # for certbot to log ALL failed attempts, not just 1st fail. - error_messages = [] - try: - _run_action(module, ['letsencrypt', 'add']) - except Exception as err: - error_messages.append(err.message) - - if error_messages: - raise ActionError(message="\n".join(error_messages)) - sys.exit(3) - - sys.exit(0) - - -def _run_action(action, action_options=None): - """ - Run a specific action from another module, for the run_renew_hooks command. - This function is a simplified version of plinth/actions.py, to enable - somewhat safe calls of other actions from outside the FreedomBox Service - (Plinth) process. The comments about the action contracts refer to - plinth/actions.py. - """ - if action_options is None: - action_options = [] - - # Contract 3A and 3B: don't call anything outside of the actions directory. - # Assume the current path is the actions directly. - script_path = os.path.realpath(__file__) - actions_dir, _ = os.path.split(script_path) - if os.sep in action: - raise ValueError('Action cannot contain: ' + os.sep) - - cmd = os.path.join(actions_dir, action) - if not os.path.realpath(cmd).startswith(actions_dir): - raise ValueError('Action has to be in directory %s' % actions_dir) - - # Contract 3C: interpret shell escape sequences as literal file names. - # Contract 3E: fail if the action doesn't exist or exists elsewhere. - if not os.access(cmd, os.F_OK): - raise ValueError('Action must exist in action directory.') - - cmd = [cmd] - - # Contract: 3C, 3D: don't allow shell special characters in - # options be interpreted by the shell. - if action_options: - if not isinstance(action_options, (list, tuple)): - raise ValueError('Options must be list or tuple.') - - cmd += list(action_options) # No escaping necessary - - # Contract 3C: don't interpret shell escape sequences. - # Contract 5 (and 6-ish). - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=False) - - output, error = proc.communicate() - output, error = output.decode(), error.decode() - if proc.returncode != 0: - raise ActionError(action, output, error) - - -def subcommand_run_post_hooks(arguments): - """ - Execute all needed maintenance tasks AFTER a cert is obtained/renewed. - If registered as certbot's post-hook, this script gets ALWAYS executed when - certbot attempts a renewal, irrespective of necessity/success (2x per day). - """ - # Require current domain, to avoid confusion (e.g. call from old cron job). - if not arguments.domain: - print('Aborted: You must specify the current domain.') - sys.exit(1) - - current_domain = config.get_domainname() - if not arguments.domain == current_domain: - print('Aborted: Current domain is %s, but called for %s.' % - (current_domain, arguments.domain)) - sys.exit(2) - - sys.exit(0) def subcommand_delete(arguments): diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 207136dbe..84423cb61 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -25,7 +25,7 @@ from django.utils.translation import ugettext_lazy as _ from plinth import action_utils, actions from plinth import app as app_module -from plinth import cfg, menu, module_loader +from plinth import cfg, menu from plinth.errors import ActionError from plinth.modules import config, names from plinth.signals import domain_added, domain_removed, domainname_change @@ -62,7 +62,6 @@ description = [ manual_page = 'LetsEncrypt' -MODULES_WITH_HOOKS = ['ejabberd', 'matrixsynapse'] LIVE_DIRECTORY = '/etc/letsencrypt/live/' logger = logging.getLogger(__name__) @@ -120,56 +119,12 @@ def try_action(domain, action): actions.superuser_run('letsencrypt', [action, '--domain', domain]) -def enable_renewal_management(domain): - if domain == config.get_domainname(): - try: - actions.superuser_run('letsencrypt', ['manage_hooks', 'enable']) - logger.info( - _('Certificate renewal management enabled for {domain}.'). - format(domain=domain)) - except ActionError as exception: - logger.error( - _('Failed to enable certificate renewal management for ' - '{domain}: {error}').format(domain=domain, - error=exception.args[2])) - - def on_domainname_change(sender, old_domainname, new_domainname, **kwargs): - """Disable renewal hook management after a domain name change.""" + """Drop the certificate after a domain name change.""" del sender # Unused del new_domainname # Unused del kwargs # Unused - for module in MODULES_WITH_HOOKS: - actions.superuser_run( - module, ['letsencrypt', 'drop', '--domain', old_domainname], - run_in_background=True) - actions.superuser_run( - 'letsencrypt', ['manage_hooks', 'disable', '--domain', old_domainname], - run_in_background=True) - - -def get_manage_hooks_status(): - """Return status of hook management for current domain.""" - try: - output = actions.superuser_run('letsencrypt', - ['manage_hooks', 'status']) - except ActionError: - return False - - return output.strip() - - -def get_installed_modules(): - installed_modules = [ - module_name - for module_name, module in module_loader.loaded_modules.items() - if module_name in MODULES_WITH_HOOKS - and module.setup_helper.get_state() == 'up-to-date' - ] - - return installed_modules - def on_domain_added(sender, domain_type='', name='', description='', services=None, **kwargs): @@ -189,7 +144,6 @@ def on_domain_added(sender, domain_type='', name='', description='', if sender != 'test' and name: logger.info("Obtaining a Let\'s Encrypt certificate for " + name) try_action(name, 'obtain') - enable_renewal_management(name) return True except ActionError as ex: return False @@ -220,8 +174,6 @@ def get_status(): curr_dom, 'has_cert': (curr_dom in status['domains'] and status['domains'][curr_dom]['certificate_available']), - 'manage_hooks_status': - get_manage_hooks_status() } status['current_domain'] = current_domain diff --git a/plinth/modules/letsencrypt/templates/letsencrypt.html b/plinth/modules/letsencrypt/templates/letsencrypt.html index b9c160cba..6b9379ddd 100644 --- a/plinth/modules/letsencrypt/templates/letsencrypt.html +++ b/plinth/modules/letsencrypt/templates/letsencrypt.html @@ -35,7 +35,7 @@ {% block configuration %} -

{% trans "Configuration" %}

+

{% trans "Status" %}

{% if status.domains %} @@ -135,191 +135,6 @@ {% include "diagnostics_button.html" with module="letsencrypt" enabled=True %} -

{% trans "Certificate renewal management and use by other modules" %}

-

- {% blocktrans trimmed %} - If you have a Let's Encrypt certificate for your current domain, you may - let {{ box_name }} manage its renewal process. This also enables other apps - to use that certificate, so that users would not prompted with security - warnings when using them. - {% endblocktrans %} -

- - {% if status.current_domain.name %} - - {% csrf_token %} -
- -
-

- {% if status.current_domain.has_cert %} - {% blocktrans trimmed %} - If enabled, {{ box_name }} can make sure that all apps can use the - certificate as soon as it is renewed. - {% endblocktrans %} - {% else %} - {% blocktrans trimmed %} - No certificate available for the current domain. - First obtain a certificate to enable its management. - {% endblocktrans %} - {% endif %} -

- - - - {% csrf_token %} -
- -
-

- {% url 'ejabberd:index' as ejabberd_url %} - {% if 'ejabberd' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %} - {% blocktrans trimmed %} - If enabled, the app ejabberd - will also use the Let's Encrypt certificate. - This will reduce warnings about self-signed certificates in - client applications, and enable more wide-spread federation with - other XMPP servers in the Internet. - {% endblocktrans %} - {% elif 'ejabberd' not in installed_modules %} - {% blocktrans trimmed %} - This feature only makes sense if you are using the - ejabberd chat server app. - {% endblocktrans %} - {% else %} - {% blocktrans trimmed %} - To use a Let's Encrypt certificate for - ejabberd chat server app, you - must first enable certificate renewal of the current domain. - {% endblocktrans %} - {% endif %} -

- - - {% csrf_token %} -
- -
-

- {% url 'matrixsynapse:index' as matrixsynapse_url %} - {% if 'matrixsynapse' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %} - {% blocktrans trimmed %} - If enabled, the app Matrix Synapse - will also use the same Let's Encrypt certificate. - Other Matrix Synapse instances running version 1.0 or greater expect - your server to produce a valid TLS certificate. Federation with other - instances will not work without this. - {% endblocktrans %} - {% elif 'matrixsynapse' not in installed_modules %} - {% blocktrans trimmed %} - This feature only makes sense if you are using the - Matrix Synapse chat server app. - {% endblocktrans %} - {% else %} - {% blocktrans trimmed %} - To use a Let's Encrypt certificate for - Matrix Synapse chat server app, you - must first enable certificate renewal of the current domain. - {% endblocktrans %} - {% endif %} -

- - {% else %} - {% blocktrans trimmed %} - No current domain is configured. - First configure a domain to enable management of its certificates. - {% endblocktrans %} - {% endif %} - {% else %} {% url 'config:index' as config_url %} {% blocktrans trimmed %} diff --git a/plinth/modules/letsencrypt/urls.py b/plinth/modules/letsencrypt/urls.py index cdaff5acc..a2dc84a15 100644 --- a/plinth/modules/letsencrypt/urls.py +++ b/plinth/modules/letsencrypt/urls.py @@ -14,14 +14,13 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # - """ URLs for the Let's Encrypt module. """ from django.conf.urls import url -from . import views +from . import views urlpatterns = [ url(r'^sys/letsencrypt/$', views.index, name='index'), @@ -31,8 +30,4 @@ urlpatterns = [ name='obtain'), url(r'^sys/letsencrypt/delete/(?P[^/]+)/$', views.delete, name='delete'), - url(r'^sys/letsencrypt/toggle_hooks/(?P[^/]+)/$', - views.toggle_hooks, name='toggle_hooks'), - url(r'^sys/letsencrypt/toggle_module/(?P[^/]+)/' - '(?P[^/]+)/$', views.toggle_module, name='toggle_module'), ] diff --git a/plinth/modules/letsencrypt/views.py b/plinth/modules/letsencrypt/views.py index 54740fad7..91fcb3646 100644 --- a/plinth/modules/letsencrypt/views.py +++ b/plinth/modules/letsencrypt/views.py @@ -29,7 +29,7 @@ from django.views.decorators.http import require_POST from plinth import actions from plinth.errors import ActionError -from plinth.modules import config, letsencrypt +from plinth.modules import letsencrypt logger = logging.getLogger(__name__) @@ -43,7 +43,6 @@ def index(request): 'description': letsencrypt.description, 'status': status, 'manual_page': letsencrypt.manual_page, - 'installed_modules': letsencrypt.get_installed_modules() }) @@ -60,8 +59,8 @@ def revoke(request, domain): except ActionError as exception: messages.error( request, - _('Failed to revoke certificate for domain {domain}: {error}') - .format(domain=domain, error=exception.args[2])) + _('Failed to revoke certificate for domain {domain}: {error}'). + format(domain=domain, error=exception.args[2])) return redirect(reverse_lazy('letsencrypt:index')) @@ -75,125 +74,17 @@ def obtain(request, domain): request, _('Certificate successfully obtained for domain {domain}').format( domain=domain)) - enable_renewal_management(request, domain) except ActionError as exception: messages.error( request, - _('Failed to obtain certificate for domain {domain}: {error}') - .format(domain=domain, error=exception.args[2])) - return redirect(reverse_lazy('letsencrypt:index')) - - -def enable_renewal_management(request, domain): - if domain == config.get_domainname(): - try: - actions.superuser_run('letsencrypt', ['manage_hooks', 'enable']) - messages.success( - request, - _('Certificate renewal management enabled for {domain}.') - .format(domain=domain)) - except ActionError as exception: - messages.error( - request, - _('Failed to enable certificate renewal management for ' - '{domain}: {error}').format(domain=domain, - error=exception.args[2])) - - -@require_POST -def toggle_hooks(request, domain): - """Toggle pointing of certbot's hooks to FreedomBox. - - For the current domain. - - """ - manage_hooks_status = letsencrypt.get_manage_hooks_status() - subcommand = 'disable' if 'enabled' in manage_hooks_status else 'enable' - - try: - if subcommand == 'disable': - enabled_modules = [ - module for module in letsencrypt.MODULES_WITH_HOOKS - if module in manage_hooks_status - ] - for module in enabled_modules: - actions.superuser_run(module, ['letsencrypt', 'drop'], - run_in_background=True) - actions.superuser_run('letsencrypt', ['manage_hooks', subcommand]) - if subcommand == 'enable': - msg = _('Certificate renewal management enabled for {domain}.')\ - .format(domain=domain) - else: - msg = _('Certificate renewal management disabled for {domain}.')\ - .format(domain=domain) - messages.success(request, msg) - except ActionError as exception: - messages.error( - request, - _('Failed to switch certificate renewal management for {domain}: ' - '{error}').format(domain=domain, error=exception.args[2])) - - return redirect(reverse_lazy('letsencrypt:index')) - - -@require_POST -def toggle_module(request, domain, module): - """Toggle usage of LE cert for a module name, for the current domain.""" - manage_hooks_status = letsencrypt.get_manage_hooks_status() - enabled_modules = [ - module for module in letsencrypt.MODULES_WITH_HOOKS - if module in manage_hooks_status - ] - - if module in enabled_modules: - mod_le_arg = 'drop' - enabled_modules.remove(module) - else: - mod_le_arg = 'add' - enabled_modules.append(module) - - module_args = ['letsencrypt', mod_le_arg] - le_arguments = ['manage_hooks', 'enable'] - - if not enabled_modules == []: - le_arguments.extend(['--modules'] + enabled_modules) - - try: - actions.superuser_run(module, module_args) - actions.superuser_run('letsencrypt', le_arguments) - messages.success(request, - _('Switched use of certificate for app {module}') - .format(module=module)) - except ActionError as exception: - messages.error( - request, - _('Failed to switch certificate use for app {module}: {error}') - .format(module=module, error=exception.args[2])) - + _('Failed to obtain certificate for domain {domain}: {error}'). + format(domain=domain, error=exception.args[2])) return redirect(reverse_lazy('letsencrypt:index')) @require_POST def delete(request, domain): """Delete a certificate for a given domain, and cleanup renewal config.""" - manage_hooks_status = letsencrypt.get_manage_hooks_status() - enabled_modules = [ - module for module in letsencrypt.MODULES_WITH_HOOKS - if module in manage_hooks_status - ] - - try: - for module in enabled_modules: - actions.superuser_run(module, ['letsencrypt', 'drop'], - run_in_background=True) - actions.superuser_run('letsencrypt', - ['manage_hooks', 'disable', '--domain', domain]) - except ActionError as exception: - messages.error( - request, - _('Failed to disable certificate renewal management for {domain}: ' - '{error}').format(domain=domain, error=exception.args[2])) - try: actions.superuser_run('letsencrypt', ['delete', '--domain', domain]) messages.success( @@ -203,7 +94,7 @@ def delete(request, domain): except ActionError as exception: messages.error( request, - _('Failed to delete certificate for domain {domain}: {error}') - .format(domain=domain, error=exception.args[2])) + _('Failed to delete certificate for domain {domain}: {error}'). + format(domain=domain, error=exception.args[2])) return redirect(reverse_lazy('letsencrypt:index'))