diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 88d131573..c7384f71d 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -1,1131 +1,1133 @@
 # Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library 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
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 """
 A library for retrieving value dicts from VyOS configs in a declarative fashion.
 """
 import os
 import json
 
+from vyos.defaults import frr_debug_enable
 from vyos.utils.dict import dict_search
 from vyos.utils.process import cmd
 
 def retrieve_config(path_hash, base_path, config):
     """
     Retrieves a VyOS config as a dict according to a declarative description
 
     The description dict, passed in the first argument, must follow this format:
     ``field_name : <path, type, [inner_options_dict]>``.
 
     Supported types are: ``str`` (for normal nodes),
     ``list`` (returns a list of strings, for multi nodes),
     ``bool`` (returns True if valueless node exists),
     ``dict`` (for tag nodes, returns a dict indexed by node names,
     according to description in the third item of the tuple).
 
     Args:
         path_hash (dict): Declarative description of the config to retrieve
         base_path (list): A base path to prepend to all option paths
         config (vyos.config.Config): A VyOS config object
 
     Returns:
         dict: config dict
     """
     config_hash = {}
 
     for k in path_hash:
 
         if type(path_hash[k]) != tuple:
             raise ValueError("In field {0}: expected a tuple, got a value {1}".format(k, str(path_hash[k])))
         if len(path_hash[k]) < 2:
             raise ValueError("In field {0}: field description must be a tuple of at least two items, path (list) and type".format(k))
 
         path = path_hash[k][0]
         if type(path) != list:
             raise ValueError("In field {0}: path must be a list, not a {1}".format(k, type(path)))
 
         typ = path_hash[k][1]
         if type(typ) != type:
             raise ValueError("In field {0}: type must be a type, not a {1}".format(k, type(typ)))
 
         path = base_path + path
 
         path_str = " ".join(path)
 
         if typ == str:
             config_hash[k] = config.return_value(path_str)
         elif typ == list:
             config_hash[k] = config.return_values(path_str)
         elif typ == bool:
             config_hash[k] = config.exists(path_str)
         elif typ == dict:
             try:
                 inner_hash = path_hash[k][2]
             except IndexError:
                 raise ValueError("The type of the \'{0}\' field is dict, but inner options hash is missing from the tuple".format(k))
             config_hash[k] = {}
             nodes = config.list_nodes(path_str)
             for node in nodes:
                 config_hash[k][node] = retrieve_config(inner_hash, path + [node], config)
 
     return config_hash
 
 
 def dict_merge(source, destination):
     """ Merge two dictionaries. Only keys which are not present in destination
     will be copied from source, anything else will be kept untouched. Function
     will return a new dict which has the merged key/value pairs. """
     from copy import deepcopy
     tmp = deepcopy(destination)
 
     for key, value in source.items():
         if key not in tmp:
             tmp[key] = value
         elif isinstance(source[key], dict):
             tmp[key] = dict_merge(source[key], tmp[key])
 
     return tmp
 
 def list_diff(first, second):
     """ Diff two dictionaries and return only unique items """
     second = set(second)
     return [item for item in first if item not in second]
 
 def is_node_changed(conf, path):
    """
    Check if any key under path has been changed and return True.
    If nothing changed, return false
    """
    from vyos.configdiff import get_config_diff
    D = get_config_diff(conf, key_mangling=('-', '_'))
    return D.is_node_changed(path)
 
 def leaf_node_changed(conf, path):
     """
     Check if a leaf node was altered. If it has been altered - values has been
     changed, or it was added/removed, we will return a list containing the old
     value(s). If nothing has been changed, None is returned.
 
     NOTE: path must use the real CLI node name (e.g. with a hyphen!)
     """
     from vyos.configdiff import get_config_diff
     D = get_config_diff(conf, key_mangling=('-', '_'))
     (new, old) = D.get_value_diff(path)
     if new != old:
         if isinstance(old, dict):
             # valueLess nodes return {} if node is deleted
             return True
         if old is None and isinstance(new, dict):
             # valueLess nodes return {} if node was added
             return True
         if old is None:
             return []
         if isinstance(old, str):
             return [old]
         if isinstance(old, list):
             if isinstance(new, str):
                 new = [new]
             elif isinstance(new, type(None)):
                 new = []
             return list_diff(old, new)
 
     return None
 
 def node_changed(conf, path, key_mangling=None, recursive=False, expand_nodes=None) -> list:
     """
     Check if node under path (or anything under path if recursive=True) was changed. By default
     we only check if a node or subnode (recursive) was deleted from path. If expand_nodes
     is set to Diff.ADD we can also check if something was added to the path.
 
     If nothing changed, an empty list is returned.
     """
     from vyos.configdiff import get_config_diff
     from vyos.configdiff import Diff
     # to prevent circular dependencies we assign the default here
     if not expand_nodes: expand_nodes = Diff.DELETE
     D = get_config_diff(conf, key_mangling)
     # get_child_nodes_diff() will return dict_keys()
     tmp = D.get_child_nodes_diff(path, expand_nodes=expand_nodes, recursive=recursive)
     output = []
     if expand_nodes & Diff.DELETE:
         output.extend(list(tmp['delete'].keys()))
     if expand_nodes & Diff.ADD:
         output.extend(list(tmp['add'].keys()))
 
     # remove duplicate keys from list, this happens when a node (e.g. description) is altered
     output = list(dict.fromkeys(output))
     return output
 
 def get_removed_vlans(conf, path, dict):
     """
     Common function to parse a dictionary retrieved via get_config_dict() and
     determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q.
     """
     from vyos.configdiff import get_config_diff, Diff
 
     # Check vif, vif-s/vif-c VLAN interfaces for removal
     D = get_config_diff(conf, key_mangling=('-', '_'))
     D.set_level(conf.get_level())
 
     # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448
     keys = D.get_child_nodes_diff(path + ['vif'], expand_nodes=Diff.DELETE)['delete'].keys()
     if keys: dict['vif_remove'] = [*keys]
 
     # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448
     keys = D.get_child_nodes_diff(path + ['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys()
     if keys: dict['vif_s_remove'] = [*keys]
 
     for vif in dict.get('vif_s', {}).keys():
         keys = D.get_child_nodes_diff(path + ['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys()
         if keys: dict['vif_s'][vif]['vif_c_remove'] = [*keys]
 
     return dict
 
 def is_member(conf, interface, intftype=None):
     """
     Checks if passed interface is member of other interface of specified type.
     intftype is optional, if not passed it will search all known types
     (currently bridge and bonding)
 
     Returns: dict
     empty -> Interface is not a member
     key -> Interface is a member of this interface
     """
     ret_val = {}
     intftypes = ['bonding', 'bridge']
 
     if intftype not in intftypes + [None]:
         raise ValueError((
             f'unknown interface type "{intftype}" or it cannot '
             f'have member interfaces'))
 
     intftype = intftypes if intftype == None else [intftype]
 
     for iftype in intftype:
         base = ['interfaces', iftype]
         for intf in conf.list_nodes(base):
             member = base + [intf, 'member', 'interface', interface]
             if conf.exists(member):
                 tmp = conf.get_config_dict(member, key_mangling=('-', '_'),
                                            get_first_key=True,
                                            no_tag_node_value_mangle=True)
                 ret_val.update({intf : tmp})
 
     return ret_val
 
 def is_mirror_intf(conf, interface, direction=None):
     """
     Check whether the passed interface is used for port mirroring. Direction
     is optional, if not passed it will search all known direction
     (currently ingress and egress)
 
     Returns:
     None -> Interface is not a monitor interface
     Array() -> This interface is a monitor interface of interfaces
     """
     from vyos.ifconfig import Section
 
     directions = ['ingress', 'egress']
     if direction not in directions + [None]:
         raise ValueError(f'Unknown interface mirror direction "{direction}"')
 
     direction = directions if direction == None else [direction]
 
     ret_val = None
     base = ['interfaces']
 
     for dir in direction:
         for iftype in conf.list_nodes(base):
             iftype_base = base + [iftype]
             for intf in conf.list_nodes(iftype_base):
                 mirror = iftype_base + [intf, 'mirror', dir, interface]
                 if conf.exists(mirror):
                     path = ['interfaces', Section.section(intf), intf]
                     tmp = conf.get_config_dict(path, key_mangling=('-', '_'),
                                                get_first_key=True)
                     ret_val = {intf : tmp}
 
     return ret_val
 
 def has_address_configured(conf, intf):
     """
     Checks if interface has an address configured.
     Checks the following config nodes:
     'address', 'ipv6 address eui64', 'ipv6 address autoconf'
 
     Returns True if interface has address configured, False if it doesn't.
     """
     from vyos.ifconfig import Section
     ret = False
 
     old_level = conf.get_level()
     conf.set_level([])
 
     intfpath = ['interfaces', Section.get_config_path(intf)]
     if (conf.exists([intfpath, 'address']) or
         conf.exists([intfpath, 'ipv6', 'address', 'autoconf']) or
         conf.exists([intfpath, 'ipv6', 'address', 'eui64'])):
         ret = True
 
     conf.set_level(old_level)
     return ret
 
 def has_vrf_configured(conf, intf):
     """
     Checks if interface has a VRF configured.
 
     Returns True if interface has VRF configured, False if it doesn't.
     """
     from vyos.ifconfig import Section
     ret = False
 
     old_level = conf.get_level()
     conf.set_level([])
 
     if conf.exists(['interfaces', Section.get_config_path(intf), 'vrf']):
         ret = True
 
     conf.set_level(old_level)
     return ret
 
 def has_vlan_subinterface_configured(conf, intf):
     """
     Checks if interface has an VLAN subinterface configured.
     Checks the following config nodes:
     'vif', 'vif-s'
 
     Return True if interface has VLAN subinterface configured.
     """
     from vyos.ifconfig import Section
     ret = False
 
     intfpath = ['interfaces', Section.section(intf), intf]
     if (conf.exists(intfpath + ['vif']) or conf.exists(intfpath + ['vif-s'])):
         ret = True
 
     return ret
 
 def is_source_interface(conf, interface, intftype=None):
     """
     Checks if passed interface is configured as source-interface of other
     interfaces of specified type. intftype is optional, if not passed it will
     search all known types (currently pppoe, macsec, pseudo-ethernet, tunnel
     and vxlan)
 
     Returns:
     None -> Interface is not a member
     interface name -> Interface is a member of this interface
     False -> interface type cannot have members
     """
     ret_val = None
     intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan']
     if not intftype:
         intftype = intftypes
 
     if isinstance(intftype, str):
         intftype = [intftype]
     elif not isinstance(intftype, list):
         raise ValueError(f'Interface type "{type(intftype)}" must be either str or list!')
 
     if not all(x in intftypes for x in intftype):
         raise ValueError(f'unknown interface type "{intftype}" or it can not '
             'have a source-interface')
 
     for it in intftype:
         base = ['interfaces', it]
         for intf in conf.list_nodes(base):
             src_intf = base + [intf, 'source-interface']
             if conf.exists(src_intf) and interface in conf.return_values(src_intf):
                 ret_val = intf
                 break
 
     return ret_val
 
 def get_dhcp_interfaces(conf, vrf=None):
     """ Common helper functions to retrieve all interfaces from current CLI
     sessions that have DHCP configured. """
     dhcp_interfaces = {}
     dict = conf.get_config_dict(['interfaces'], get_first_key=True)
     if not dict:
         return dhcp_interfaces
 
     def check_dhcp(config):
         ifname = config['ifname']
         tmp = {}
         if 'address' in config and 'dhcp' in config['address']:
             options = {}
             if dict_search('dhcp_options.default_route_distance', config) != None:
                 options.update({'dhcp_options' : config['dhcp_options']})
             if 'vrf' in config:
                 if vrf == config['vrf']: tmp.update({ifname : options})
             else:
                 if vrf is None: tmp.update({ifname : options})
 
         return tmp
 
     for section, interface in dict.items():
         for ifname in interface:
             # always reset config level, as get_interface_dict() will alter it
             conf.set_level([])
             # we already have a dict representation of the config from get_config_dict(),
             # but with the extended information from get_interface_dict() we also
             # get the DHCP client default-route-distance default option if not specified.
             _, ifconfig = get_interface_dict(conf, ['interfaces', section], ifname)
 
             tmp = check_dhcp(ifconfig)
             dhcp_interfaces.update(tmp)
             # check per VLAN interfaces
             for vif, vif_config in ifconfig.get('vif', {}).items():
                 tmp = check_dhcp(vif_config)
                 dhcp_interfaces.update(tmp)
             # check QinQ VLAN interfaces
             for vif_s, vif_s_config in ifconfig.get('vif_s', {}).items():
                 tmp = check_dhcp(vif_s_config)
                 dhcp_interfaces.update(tmp)
                 for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
                     tmp = check_dhcp(vif_c_config)
                     dhcp_interfaces.update(tmp)
 
     return dhcp_interfaces
 
 def get_pppoe_interfaces(conf, vrf=None):
     """ Common helper functions to retrieve all interfaces from current CLI
     sessions that have DHCP configured. """
     pppoe_interfaces = {}
     conf.set_level([])
     for ifname in conf.list_nodes(['interfaces', 'pppoe']):
         # always reset config level, as get_interface_dict() will alter it
         conf.set_level([])
         # we already have a dict representation of the config from get_config_dict(),
         # but with the extended information from get_interface_dict() we also
         # get the DHCP client default-route-distance default option if not specified.
         _, ifconfig = get_interface_dict(conf, ['interfaces', 'pppoe'], ifname)
 
         options = {}
         if 'default_route_distance' in ifconfig:
             options.update({'default_route_distance' : ifconfig['default_route_distance']})
         if 'no_default_route' in ifconfig:
             options.update({'no_default_route' : {}})
         if 'vrf' in ifconfig:
             if vrf == ifconfig['vrf']: pppoe_interfaces.update({ifname : options})
         else:
             if vrf is None: pppoe_interfaces.update({ifname : options})
 
     return pppoe_interfaces
 
 def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pki=False):
     """
     Common utility function to retrieve and mangle the interfaces configuration
     from the CLI input nodes. All interfaces have a common base where value
     retrival is identical. This function must be used whenever possible when
     working on the interfaces node!
 
     Return a dictionary with the necessary interface config keys.
     """
     if not ifname:
         from vyos import ConfigError
         # determine tagNode instance
         if 'VYOS_TAGNODE_VALUE' not in os.environ:
             raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
         ifname = os.environ['VYOS_TAGNODE_VALUE']
 
     # Check if interface has been removed. We must use exists() as
     # get_config_dict() will always return {} - even when an empty interface
     # node like the following exists.
     # +macsec macsec1 {
     # +}
     if not config.exists(base + [ifname]):
         dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'),
                                       get_first_key=True,
                                       no_tag_node_value_mangle=True)
         dict.update({'deleted' : {}})
     else:
         # Get config_dict with default values
         dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'),
                                       get_first_key=True,
                                       no_tag_node_value_mangle=True,
                                       with_defaults=True,
                                       with_recursive_defaults=recursive_defaults,
                                       with_pki=with_pki)
 
         # If interface does not request an IPv4 DHCP address there is no need
         # to keep the dhcp-options key
         if 'address' not in dict or 'dhcp' not in dict['address']:
             if 'dhcp_options' in dict:
                 del dict['dhcp_options']
 
     # Add interface instance name into dictionary
     dict.update({'ifname': ifname})
 
     # Check if QoS policy applied on this interface - See ifconfig.interface.set_mirror_redirect()
     if config.exists(['qos', 'interface', ifname]):
         dict.update({'traffic_policy': {}})
 
     address = leaf_node_changed(config, base + [ifname, 'address'])
     if address: dict.update({'address_old' : address})
 
     # Check if we are a member of a bridge device
     bridge = is_member(config, ifname, 'bridge')
     if bridge: dict.update({'is_bridge_member' : bridge})
 
     # Check if it is a monitor interface
     mirror = is_mirror_intf(config, ifname)
     if mirror: dict.update({'is_mirror_intf' : mirror})
 
     # Check if we are a member of a bond device
     bond = is_member(config, ifname, 'bonding')
     if bond: dict.update({'is_bond_member' : bond})
 
     # Check if any DHCP options changed which require a client restat
     dhcp = is_node_changed(config, base + [ifname, 'dhcp-options'])
     if dhcp: dict.update({'dhcp_options_changed' : {}})
 
     # Changine interface VRF assignemnts require a DHCP restart, too
     dhcp = is_node_changed(config, base + [ifname, 'vrf'])
     if dhcp: dict.update({'dhcp_options_changed' : {}})
 
     # Some interfaces come with a source_interface which must also not be part
     # of any other bond or bridge interface as it is exclusivly assigned as the
     # Kernels "lower" interface to this new "virtual/upper" interface.
     if 'source_interface' in dict:
         # Check if source interface is member of another bridge
         tmp = is_member(config, dict['source_interface'], 'bridge')
         if tmp: dict.update({'source_interface_is_bridge_member' : tmp})
 
         # Check if source interface is member of another bridge
         tmp = is_member(config, dict['source_interface'], 'bonding')
         if tmp: dict.update({'source_interface_is_bond_member' : tmp})
 
     mac = leaf_node_changed(config, base + [ifname, 'mac'])
     if mac: dict.update({'mac_old' : mac})
 
     eui64 = leaf_node_changed(config, base + [ifname, 'ipv6', 'address', 'eui64'])
     if eui64:
         tmp = dict_search('ipv6.address', dict)
         if not tmp:
             dict.update({'ipv6': {'address': {'eui64_old': eui64}}})
         else:
             dict['ipv6']['address'].update({'eui64_old': eui64})
 
     for vif, vif_config in dict.get('vif', {}).items():
         # Add subinterface name to dictionary
         dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'})
 
         if config.exists(['qos', 'interface', f'{ifname}.{vif}']):
             dict['vif'][vif].update({'traffic_policy': {}})
 
         if 'deleted' not in dict:
             address = leaf_node_changed(config, base + [ifname, 'vif', vif, 'address'])
             if address: dict['vif'][vif].update({'address_old' : address})
 
             # If interface does not request an IPv4 DHCP address there is no need
             # to keep the dhcp-options key
             if 'address' not in dict['vif'][vif] or 'dhcp' not in dict['vif'][vif]['address']:
                 if 'dhcp_options' in dict['vif'][vif]:
                     del dict['vif'][vif]['dhcp_options']
 
         # Check if we are a member of a bridge device
         bridge = is_member(config, f'{ifname}.{vif}', 'bridge')
         if bridge: dict['vif'][vif].update({'is_bridge_member' : bridge})
 
         # Check if any DHCP options changed which require a client restat
         dhcp = is_node_changed(config, base + [ifname, 'vif', vif, 'dhcp-options'])
         if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : {}})
 
     for vif_s, vif_s_config in dict.get('vif_s', {}).items():
         # Add subinterface name to dictionary
         dict['vif_s'][vif_s].update({'ifname' : f'{ifname}.{vif_s}'})
 
         if config.exists(['qos', 'interface', f'{ifname}.{vif_s}']):
             dict['vif_s'][vif_s].update({'traffic_policy': {}})
 
         if 'deleted' not in dict:
             address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'address'])
             if address: dict['vif_s'][vif_s].update({'address_old' : address})
 
             # If interface does not request an IPv4 DHCP address there is no need
             # to keep the dhcp-options key
             if 'address' not in dict['vif_s'][vif_s] or 'dhcp' not in \
                 dict['vif_s'][vif_s]['address']:
                 if 'dhcp_options' in dict['vif_s'][vif_s]:
                     del dict['vif_s'][vif_s]['dhcp_options']
 
         # Check if we are a member of a bridge device
         bridge = is_member(config, f'{ifname}.{vif_s}', 'bridge')
         if bridge: dict['vif_s'][vif_s].update({'is_bridge_member' : bridge})
 
         # Check if any DHCP options changed which require a client restat
         dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'dhcp-options'])
         if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : {}})
 
         for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
             # Add subinterface name to dictionary
             dict['vif_s'][vif_s]['vif_c'][vif_c].update({'ifname' : f'{ifname}.{vif_s}.{vif_c}'})
 
             if config.exists(['qos', 'interface', f'{ifname}.{vif_s}.{vif_c}']):
                 dict['vif_s'][vif_s]['vif_c'][vif_c].update({'traffic_policy': {}})
 
             if 'deleted' not in dict:
                 address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'address'])
                 if address: dict['vif_s'][vif_s]['vif_c'][vif_c].update(
                         {'address_old' : address})
 
                 # If interface does not request an IPv4 DHCP address there is no need
                 # to keep the dhcp-options key
                 if 'address' not in dict['vif_s'][vif_s]['vif_c'][vif_c] or 'dhcp' \
                     not in dict['vif_s'][vif_s]['vif_c'][vif_c]['address']:
                     if 'dhcp_options' in dict['vif_s'][vif_s]['vif_c'][vif_c]:
                         del dict['vif_s'][vif_s]['vif_c'][vif_c]['dhcp_options']
 
             # Check if we are a member of a bridge device
             bridge = is_member(config, f'{ifname}.{vif_s}.{vif_c}', 'bridge')
             if bridge: dict['vif_s'][vif_s]['vif_c'][vif_c].update(
                 {'is_bridge_member' : bridge})
 
             # Check if any DHCP options changed which require a client restat
             dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'dhcp-options'])
             if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : {}})
 
     # Check vif, vif-s/vif-c VLAN interfaces for removal
     dict = get_removed_vlans(config, base + [ifname], dict)
     return ifname, dict
 
 def get_vlan_ids(interface):
     """
     Get the VLAN ID of the interface bound to the bridge
     """
     vlan_ids = set()
 
     bridge_status = cmd('bridge -j vlan show', shell=True)
     vlan_filter_status = json.loads(bridge_status)
 
     if vlan_filter_status is not None:
         for interface_status in vlan_filter_status:
             ifname = interface_status['ifname']
             if interface == ifname:
                 vlans_status = interface_status['vlans']
                 for vlan_status in vlans_status:
                     vlan_id = vlan_status['vlan']
                     vlan_ids.add(vlan_id)
 
     return vlan_ids
 
 def get_accel_dict(config, base, chap_secrets, with_pki=False):
     """
     Common utility function to retrieve and mangle the Accel-PPP configuration
     from different CLI input nodes. All Accel-PPP services have a common base
     where value retrival is identical. This function must be used whenever
     possible when working with Accel-PPP services!
 
     Return a dictionary with the necessary interface config keys.
     """
     from vyos.utils.cpu import get_core_count
     from vyos.template import is_ipv4
 
     dict = config.get_config_dict(base, key_mangling=('-', '_'),
                                   get_first_key=True,
                                   no_tag_node_value_mangle=True,
                                   with_recursive_defaults=True,
                                   with_pki=with_pki)
 
     # set CPUs cores to process requests
     dict.update({'thread_count' : get_core_count()})
     # we need to store the path to the secrets file
     dict.update({'chap_secrets_file' : chap_secrets})
 
     # We can only have two IPv4 and three IPv6 nameservers - also they are
     # configured in a different way in the configuration, this is why we split
     # the configuration
     if 'name_server' in dict:
         ns_v4 = []
         ns_v6 = []
         for ns in dict['name_server']:
             if is_ipv4(ns): ns_v4.append(ns)
             else: ns_v6.append(ns)
 
         dict.update({'name_server_ipv4' : ns_v4, 'name_server_ipv6' : ns_v6})
         del dict['name_server']
 
     # Check option "disable-accounting" per server and replace default value from '1813' to '0'
     for server in (dict_search('authentication.radius.server', dict) or []):
         if 'disable_accounting' in dict['authentication']['radius']['server'][server]:
             dict['authentication']['radius']['server'][server]['acct_port'] = '0'
 
     return dict
 
 def get_frrender_dict(conf) -> dict:
     from copy import deepcopy
     from vyos.config import config_dict_merge
     from vyos.frrender import frr_protocols
 
     # Create an empty dictionary which will be filled down the code path and
     # returned to the caller
     dict = {}
 
     def dict_helper_ospf_defaults(ospf, path):
         # We have gathered the dict representation of the CLI, but there are default
         # options which we need to update into the dictionary retrived.
         default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
                                                   get_first_key=True, recursive=True)
 
         # We have to cleanup the default dict, as default values could enable features
         # which are not explicitly enabled on the CLI. Example: default-information
         # originate comes with a default metric-type of 2, which will enable the
         # entire default-information originate tree, even when not set via CLI so we
         # need to check this first and probably drop that key.
         if dict_search('default_information.originate', ospf) is None:
             del default_values['default_information']
         if 'mpls_te' not in ospf:
             del default_values['mpls_te']
         if 'graceful_restart' not in ospf:
             del default_values['graceful_restart']
         for area_num in default_values.get('area', []):
             if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None:
                 del default_values['area'][area_num]['area_type']['nssa']
 
         for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
             if dict_search(f'redistribute.{protocol}', ospf) is None:
                 del default_values['redistribute'][protocol]
         if not bool(default_values['redistribute']):
             del default_values['redistribute']
 
         for interface in ospf.get('interface', []):
             # We need to reload the defaults on every pass b/c of
             # hello-multiplier dependency on dead-interval
             # If hello-multiplier is set, we need to remove the default from
             # dead-interval.
             if 'hello_multiplier' in ospf['interface'][interface]:
                 del default_values['interface'][interface]['dead_interval']
 
         ospf = config_dict_merge(default_values, ospf)
         return ospf
 
     def dict_helper_ospfv3_defaults(ospfv3, path):
         # We have gathered the dict representation of the CLI, but there are default
         # options which we need to update into the dictionary retrived.
         default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
                                                   get_first_key=True, recursive=True)
 
         # We have to cleanup the default dict, as default values could enable features
         # which are not explicitly enabled on the CLI. Example: default-information
         # originate comes with a default metric-type of 2, which will enable the
         # entire default-information originate tree, even when not set via CLI so we
         # need to check this first and probably drop that key.
         if dict_search('default_information.originate', ospfv3) is None:
             del default_values['default_information']
         if 'graceful_restart' not in ospfv3:
             del default_values['graceful_restart']
 
         for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']:
             if dict_search(f'redistribute.{protocol}', ospfv3) is None:
                 del default_values['redistribute'][protocol]
         if not bool(default_values['redistribute']):
             del default_values['redistribute']
 
         default_values.pop('interface', {})
 
         # merge in remaining default values
         ospfv3 = config_dict_merge(default_values, ospfv3)
         return ospfv3
 
     def dict_helper_pim_defaults(pim, path):
         # We have gathered the dict representation of the CLI, but there are default
         # options which we need to update into the dictionary retrived.
         default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
                                                   get_first_key=True, recursive=True)
 
         # We have to cleanup the default dict, as default values could enable features
         # which are not explicitly enabled on the CLI.
         for interface in pim.get('interface', []):
             if 'igmp' not in pim['interface'][interface]:
                 del default_values['interface'][interface]['igmp']
 
         pim = config_dict_merge(default_values, pim)
         return pim
 
     # Ethernet and bonding interfaces can participate in EVPN which is configured via FRR
     tmp = {}
     for if_type in ['ethernet', 'bonding']:
         interface_path = ['interfaces', if_type]
         if not conf.exists(interface_path):
             continue
         for interface in conf.list_nodes(interface_path):
             evpn_path = interface_path + [interface, 'evpn']
             if not conf.exists(evpn_path):
                 continue
 
             evpn = conf.get_config_dict(evpn_path, key_mangling=('-', '_'))
             tmp.update({interface : evpn})
     # At least one participating EVPN interface found, add to result dict
     if tmp: dict['interfaces'] = tmp
 
     # Enable SNMP agentx support
     # SNMP AgentX support cannot be disabled once enabled
     if conf.exists(['service', 'snmp']):
         dict['snmp'] = {}
 
     # We will always need the policy key
     dict['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'),
                                           get_first_key=True,
                                           no_tag_node_value_mangle=True)
 
     # We need to check the CLI if the BABEL node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     babel_cli_path = ['protocols', 'babel']
     if conf.exists(babel_cli_path):
         babel = conf.get_config_dict(babel_cli_path, key_mangling=('-', '_'),
                                      get_first_key=True,
                                      with_recursive_defaults=True)
         dict.update({'babel' : babel})
 
     # We need to check the CLI if the BFD node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     bfd_cli_path = ['protocols', 'bfd']
     if conf.exists(bfd_cli_path):
         bfd = conf.get_config_dict(bfd_cli_path, key_mangling=('-', '_'),
                                    get_first_key=True,
                                    no_tag_node_value_mangle=True,
                                    with_recursive_defaults=True)
         dict.update({'bfd' : bfd})
 
     # We need to check the CLI if the BGP node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     bgp_cli_path = ['protocols', 'bgp']
     if conf.exists(bgp_cli_path):
         bgp = conf.get_config_dict(bgp_cli_path, key_mangling=('-', '_'),
                                    get_first_key=True,
                                    no_tag_node_value_mangle=True,
                                    with_recursive_defaults=True)
         bgp['dependent_vrfs'] = {}
         dict.update({'bgp' : bgp})
     elif conf.exists_effective(bgp_cli_path):
         dict.update({'bgp' : {'deleted' : '', 'dependent_vrfs' : {}}})
 
     # We need to check the CLI if the EIGRP node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     eigrp_cli_path = ['protocols', 'eigrp']
     if conf.exists(eigrp_cli_path):
         isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'),
                                     get_first_key=True,
                                     no_tag_node_value_mangle=True,
                                     with_recursive_defaults=True)
         dict.update({'eigrp' : isis})
     elif conf.exists_effective(eigrp_cli_path):
         dict.update({'eigrp' : {'deleted' : ''}})
 
     # We need to check the CLI if the ISIS node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     isis_cli_path = ['protocols', 'isis']
     if conf.exists(isis_cli_path):
         isis = conf.get_config_dict(isis_cli_path, key_mangling=('-', '_'),
                                     get_first_key=True,
                                     no_tag_node_value_mangle=True,
                                     with_recursive_defaults=True)
         dict.update({'isis' : isis})
     elif conf.exists_effective(isis_cli_path):
         dict.update({'isis' : {'deleted' : ''}})
 
     # We need to check the CLI if the MPLS node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     mpls_cli_path = ['protocols', 'mpls']
     if conf.exists(mpls_cli_path):
         mpls = conf.get_config_dict(mpls_cli_path, key_mangling=('-', '_'),
                                     get_first_key=True)
         dict.update({'mpls' : mpls})
     elif conf.exists_effective(mpls_cli_path):
         dict.update({'mpls' : {'deleted' : ''}})
 
     # We need to check the CLI if the OPENFABRIC node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     openfabric_cli_path = ['protocols', 'openfabric']
     if conf.exists(openfabric_cli_path):
         openfabric = conf.get_config_dict(openfabric_cli_path, key_mangling=('-', '_'),
                                           get_first_key=True,
                                           no_tag_node_value_mangle=True)
         dict.update({'openfabric' : openfabric})
     elif conf.exists_effective(openfabric_cli_path):
         dict.update({'openfabric' : {'deleted' : ''}})
 
     # We need to check the CLI if the OSPF node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     ospf_cli_path = ['protocols', 'ospf']
     if conf.exists(ospf_cli_path):
         ospf = conf.get_config_dict(ospf_cli_path, key_mangling=('-', '_'),
                                     get_first_key=True)
         ospf = dict_helper_ospf_defaults(ospf, ospf_cli_path)
         dict.update({'ospf' : ospf})
     elif conf.exists_effective(ospf_cli_path):
         dict.update({'ospf' : {'deleted' : ''}})
 
     # We need to check the CLI if the OSPFv3 node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     ospfv3_cli_path = ['protocols', 'ospfv3']
     if conf.exists(ospfv3_cli_path):
         ospfv3 = conf.get_config_dict(ospfv3_cli_path, key_mangling=('-', '_'),
                                       get_first_key=True)
         ospfv3 = dict_helper_ospfv3_defaults(ospfv3, ospfv3_cli_path)
         dict.update({'ospfv3' : ospfv3})
     elif conf.exists_effective(ospfv3_cli_path):
         dict.update({'ospfv3' : {'deleted' : ''}})
 
     # We need to check the CLI if the PIM node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     pim_cli_path = ['protocols', 'pim']
     if conf.exists(pim_cli_path):
         pim = conf.get_config_dict(pim_cli_path, key_mangling=('-', '_'),
                                    get_first_key=True)
         pim = dict_helper_pim_defaults(pim, pim_cli_path)
         dict.update({'pim' : pim})
     elif conf.exists_effective(pim_cli_path):
         dict.update({'pim' : {'deleted' : ''}})
 
     # We need to check the CLI if the PIM6 node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     pim6_cli_path = ['protocols', 'pim6']
     if conf.exists(pim6_cli_path):
         pim6 = conf.get_config_dict(pim6_cli_path, key_mangling=('-', '_'),
                                     get_first_key=True,
                                     with_recursive_defaults=True)
         dict.update({'pim6' : pim6})
     elif conf.exists_effective(pim6_cli_path):
         dict.update({'pim6' : {'deleted' : ''}})
 
     # We need to check the CLI if the RIP node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     rip_cli_path = ['protocols', 'rip']
     if conf.exists(rip_cli_path):
         rip = conf.get_config_dict(rip_cli_path, key_mangling=('-', '_'),
                                    get_first_key=True,
                                    with_recursive_defaults=True)
         dict.update({'rip' : rip})
     elif conf.exists_effective(rip_cli_path):
         dict.update({'rip' : {'deleted' : ''}})
 
     # We need to check the CLI if the RIPng node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     ripng_cli_path = ['protocols', 'ripng']
     if conf.exists(ripng_cli_path):
         ripng = conf.get_config_dict(ripng_cli_path, key_mangling=('-', '_'),
                                      get_first_key=True,
                                      with_recursive_defaults=True)
         dict.update({'ripng' : ripng})
     elif conf.exists_effective(ripng_cli_path):
         dict.update({'ripng' : {'deleted' : ''}})
 
     # We need to check the CLI if the RPKI node is present and thus load in all the default
     # values present on the CLI - that's why we have if conf.exists()
     rpki_cli_path = ['protocols', 'rpki']
     if conf.exists(rpki_cli_path):
         rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'),
                                      get_first_key=True, with_pki=True,
                                      with_recursive_defaults=True)
         dict.update({'rpki' : rpki})
     elif conf.exists_effective(rpki_cli_path):
         dict.update({'rpki' : {'deleted' : ''}})
 
     # We need to check the CLI if the Segment Routing node is present and thus load in
     # all the default values present on the CLI - that's why we have if conf.exists()
     sr_cli_path = ['protocols', 'segment-routing']
     if conf.exists(sr_cli_path):
         sr = conf.get_config_dict(sr_cli_path, key_mangling=('-', '_'),
                                   get_first_key=True,
                                   no_tag_node_value_mangle=True,
                                   with_recursive_defaults=True)
         dict.update({'segment_routing' : sr})
     elif conf.exists_effective(sr_cli_path):
         dict.update({'segment_routing' : {'deleted' : ''}})
 
     # We need to check the CLI if the static node is present and thus load in
     # all the default values present on the CLI - that's why we have if conf.exists()
     static_cli_path = ['protocols', 'static']
     if conf.exists(static_cli_path):
         static = conf.get_config_dict(static_cli_path, key_mangling=('-', '_'),
                                   get_first_key=True,
                                   no_tag_node_value_mangle=True)
 
         # T3680 - get a list of all interfaces currently configured to use DHCP
         tmp = get_dhcp_interfaces(conf)
         if tmp: static.update({'dhcp' : tmp})
         tmp = get_pppoe_interfaces(conf)
         if tmp: static.update({'pppoe' : tmp})
 
         dict.update({'static' : static})
     elif conf.exists_effective(static_cli_path):
         dict.update({'static' : {'deleted' : ''}})
 
     # keep a re-usable list of dependent VRFs
     dependent_vrfs_default = {}
     if 'bgp' in dict:
         dependent_vrfs_default = deepcopy(dict['bgp'])
         # we do not need to nest the 'dependent_vrfs' key - simply remove it
         if 'dependent_vrfs' in dependent_vrfs_default:
             del dependent_vrfs_default['dependent_vrfs']
 
     vrf_cli_path = ['vrf', 'name']
     if conf.exists(vrf_cli_path):
         vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'),
                                    get_first_key=False,
                                    no_tag_node_value_mangle=True)
         # We do not have any VRF related default values on the CLI. The defaults will only
         # come into place under the protocols tree, thus we can safely merge them with the
         # appropriate routing protocols
         for vrf_name, vrf_config in vrf['name'].items():
             bgp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'bgp']
             if 'bgp' in vrf_config.get('protocols', []):
                 # We have gathered the dict representation of the CLI, but there are default
                 # options which we need to update into the dictionary retrived.
                 default_values = conf.get_config_defaults(bgp_vrf_path, key_mangling=('-', '_'),
                                                         get_first_key=True, recursive=True)
 
                 # merge in remaining default values
                 vrf_config['protocols']['bgp'] = config_dict_merge(default_values,
                                                                    vrf_config['protocols']['bgp'])
 
                 # Add this BGP VRF instance as dependency into the default VRF
                 if 'bgp' in dict:
                     dict['bgp']['dependent_vrfs'].update({vrf_name : deepcopy(vrf_config)})
 
                 vrf_config['protocols']['bgp']['dependent_vrfs'] = conf.get_config_dict(
                     vrf_cli_path, key_mangling=('-', '_'), get_first_key=True,
                     no_tag_node_value_mangle=True)
 
                 # We can safely delete ourself from the dependent VRF list
                 if vrf_name in vrf_config['protocols']['bgp']['dependent_vrfs']:
                     del vrf_config['protocols']['bgp']['dependent_vrfs'][vrf_name]
 
                 # Add dependency on possible existing default VRF to this VRF
                 if 'bgp' in dict:
                     vrf_config['protocols']['bgp']['dependent_vrfs'].update({'default': {'protocols': {
                         'bgp': dependent_vrfs_default}}})
             elif conf.exists_effective(bgp_vrf_path):
                 # Add this BGP VRF instance as dependency into the default VRF
                 tmp = {'deleted' : '', 'dependent_vrfs': deepcopy(vrf['name'])}
                 # We can safely delete ourself from the dependent VRF list
                 if vrf_name in tmp['dependent_vrfs']:
                     del tmp['dependent_vrfs'][vrf_name]
 
                 # Add dependency on possible existing default VRF to this VRF
                 if 'bgp' in dict:
                     tmp['dependent_vrfs'].update({'default': {'protocols': {
                         'bgp': dependent_vrfs_default}}})
 
                 if 'bgp' in dict:
                     dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} })
                 vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp})
 
             # We need to check the CLI if the EIGRP node is present and thus load in all the default
             # values present on the CLI - that's why we have if conf.exists()
             eigrp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'eigrp']
             if 'eigrp' in vrf_config.get('protocols', []):
                 eigrp = conf.get_config_dict(eigrp_vrf_path, key_mangling=('-', '_'), get_first_key=True,
                                             no_tag_node_value_mangle=True)
                 vrf['name'][vrf_name]['protocols'].update({'eigrp' : isis})
             elif conf.exists_effective(eigrp_vrf_path):
                 vrf['name'][vrf_name]['protocols'].update({'eigrp' : {'deleted' : ''}})
 
             # We need to check the CLI if the ISIS node is present and thus load in all the default
             # values present on the CLI - that's why we have if conf.exists()
             isis_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'isis']
             if 'isis' in vrf_config.get('protocols', []):
                 isis = conf.get_config_dict(isis_vrf_path, key_mangling=('-', '_'), get_first_key=True,
                                             no_tag_node_value_mangle=True, with_recursive_defaults=True)
                 vrf['name'][vrf_name]['protocols'].update({'isis' : isis})
             elif conf.exists_effective(isis_vrf_path):
                 vrf['name'][vrf_name]['protocols'].update({'isis' : {'deleted' : ''}})
 
             # We need to check the CLI if the OSPF node is present and thus load in all the default
             # values present on the CLI - that's why we have if conf.exists()
             ospf_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospf']
             if 'ospf' in vrf_config.get('protocols', []):
                 ospf = conf.get_config_dict(ospf_vrf_path, key_mangling=('-', '_'), get_first_key=True)
                 ospf = dict_helper_ospf_defaults(vrf_config['protocols']['ospf'], ospf_vrf_path)
                 vrf['name'][vrf_name]['protocols'].update({'ospf' : ospf})
             elif conf.exists_effective(ospf_vrf_path):
                 vrf['name'][vrf_name]['protocols'].update({'ospf' : {'deleted' : ''}})
 
             # We need to check the CLI if the OSPFv3 node is present and thus load in all the default
             # values present on the CLI - that's why we have if conf.exists()
             ospfv3_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospfv3']
             if 'ospfv3' in vrf_config.get('protocols', []):
                 ospfv3 = conf.get_config_dict(ospfv3_vrf_path, key_mangling=('-', '_'), get_first_key=True)
                 ospfv3 = dict_helper_ospfv3_defaults(vrf_config['protocols']['ospfv3'], ospfv3_vrf_path)
                 vrf['name'][vrf_name]['protocols'].update({'ospfv3' : ospfv3})
             elif conf.exists_effective(ospfv3_vrf_path):
                 vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}})
 
             # We need to check the CLI if the static node is present and thus load in all the default
             # values present on the CLI - that's why we have if conf.exists()
             static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static']
             if 'static' in vrf_config.get('protocols', []):
                 static = conf.get_config_dict(static_vrf_path, key_mangling=('-', '_'),
                                               get_first_key=True,
                                               no_tag_node_value_mangle=True)
                 # T3680 - get a list of all interfaces currently configured to use DHCP
                 tmp = get_dhcp_interfaces(conf, vrf_name)
                 if tmp: static.update({'dhcp' : tmp})
                 tmp = get_pppoe_interfaces(conf, vrf_name)
                 if tmp: static.update({'pppoe' : tmp})
 
                 vrf['name'][vrf_name]['protocols'].update({'static': static})
             elif conf.exists_effective(static_vrf_path):
                 vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}})
 
             vrf_vni_path = ['vrf', 'name', vrf_name, 'vni']
             if conf.exists_effective(vrf_vni_path):
                 vrf_config.update({'vni': conf.return_effective_value(vrf_vni_path)})
 
             dict.update({'vrf' : vrf})
     elif conf.exists_effective(vrf_cli_path):
         effective_vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'),
                                              get_first_key=False,
                                              no_tag_node_value_mangle=True,
                                              effective=True)
         vrf = {'name' : {}}
         for vrf_name, vrf_config in effective_vrf.get('name', {}).items():
             vrf['name'].update({vrf_name : {}})
             for protocol in frr_protocols:
                 if protocol in vrf_config.get('protocols', []):
                     # Create initial protocols key if not present
                     if 'protocols' not in vrf['name'][vrf_name]:
                         vrf['name'][vrf_name].update({'protocols' : {}})
                     # All routing protocols are deleted when we pass this point
                     tmp = {'deleted' : ''}
 
                     # Special treatment for BGP routing protocol
                     if protocol == 'bgp':
                         tmp['dependent_vrfs'] = {}
                         if 'name' in vrf:
                             tmp['dependent_vrfs'] = conf.get_config_dict(
                                 vrf_cli_path, key_mangling=('-', '_'),
                                 get_first_key=True, no_tag_node_value_mangle=True,
                                 effective=True)
                         # Add dependency on possible existing default VRF to this VRF
                         if 'bgp' in dict:
                             tmp['dependent_vrfs'].update({'default': {'protocols': {
                                 'bgp': dependent_vrfs_default}}})
                         # We can safely delete ourself from the dependent VRF list
                         if vrf_name in tmp['dependent_vrfs']:
                             del tmp['dependent_vrfs'][vrf_name]
 
                     # Update VRF related dict
                     vrf['name'][vrf_name]['protocols'].update({protocol : tmp})
 
         dict.update({'vrf' : vrf})
 
-    print('======== < > ==========')
-    import pprint
-    pprint.pprint(dict)
-    print('======== < > ==========')
+    if os.path.exists(frr_debug_enable):
+        print('======== < BEGIN > ==========')
+        import pprint
+        pprint.pprint(dict)
+        print('========= < END > ===========')
     return dict
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 425990967..9757a34df 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -1,64 +1,65 @@
 # Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library 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
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 base_dir = '/usr/libexec/vyos/'
 
 directories = {
   'base' : base_dir,
   'data' : '/usr/share/vyos/',
   'conf_mode' : f'{base_dir}/conf_mode',
   'op_mode' : f'{base_dir}/op_mode',
   'services' : f'{base_dir}/services',
   'config' : '/opt/vyatta/etc/config',
   'migrate' : '/opt/vyatta/etc/config-migrate/migrate',
   'activate' : f'{base_dir}/activate',
   'log' : '/var/log/vyatta',
   'templates' : '/usr/share/vyos/templates/',
   'certbot' : '/config/auth/letsencrypt',
   'api_schema': f'{base_dir}/services/api/graphql/graphql/schema/',
   'api_client_op': f'{base_dir}/services/api/graphql/graphql/client_op/',
   'api_templates': f'{base_dir}/services/api/graphql/session/templates/',
   'vyos_udev_dir' : '/run/udev/vyos',
   'isc_dhclient_dir' : '/run/dhclient',
   'dhcp6_client_dir' : '/run/dhcp6c',
   'vyos_configdir' : '/opt/vyatta/config',
   'completion_dir' : f'{base_dir}/completion',
   'ca_certificates' : '/usr/local/share/ca-certificates/vyos'
 }
 
 config_status = '/tmp/vyos-config-status'
 api_config_state = '/run/http-api-state'
+frr_debug_enable = '/tmp/vyos.frr.debug'
 
 cfg_group = 'vyattacfg'
 
 cfg_vintage = 'vyos'
 
 commit_lock = os.path.join(directories['vyos_configdir'], '.lock')
 
 component_version_json = os.path.join(directories['data'], 'component-versions.json')
 
 config_default = os.path.join(directories['data'], 'config.boot.default')
 
 rt_symbolic_names = {
   # Standard routing tables for Linux & reserved IDs for VyOS
   'default': 253, # Confusingly, a final fallthru, not the default. 
   'main': 254,    # The actual global table used by iproute2 unless told otherwise. 
   'local': 255,   # Special kernel loopback table.
 }
 
 rt_global_vrf = rt_symbolic_names['main']
 rt_global_table = rt_symbolic_names['main']
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index 183805e13..67279a6f7 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -1,568 +1,569 @@
 # Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library 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
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 r"""
 A Library for interracting with the FRR daemon suite.
 It supports simple configuration manipulation and loading using the official tools
 supplied with FRR (vtysh and frr-reload)
 
 All configuration management and manipulation is done using strings and regex.
 
 
 Example Usage
 #####
 
 # Reading configuration from frr:
 ```
 >>> original_config = get_configuration()
 >>> repr(original_config)
 '!\nfrr version 7.3.1\nfrr defaults traditional\nhostname debian\n......
 ```
 
 
 # Modify a configuration section:
 ```
 >>> new_bgp_section = 'router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n'
 >>> modified_config = replace_section(original_config, new_bgp_section, replace_re=r'router bgp \d+')
 >>> repr(modified_config)
 '............router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n...........'
 ```
 
 Remove a configuration section:
 ```
 >>> modified_config = remove_section(original_config, r'router ospf')
 ```
 
 Test the new configuration:
 ```
 >>> try:
 >>>     mark_configuration(modified configuration)
 >>> except ConfigurationNotValid as e:
 >>>     print('resulting configuration is not valid')
 >>>     sys.exit(1)
 ```
 
 Apply the new configuration:
 ```
 >>> try:
 >>>     replace_configuration(modified_config)
 >>> except CommitError as e:
 >>>     print('Exception while commiting the supplied configuration')
 >>>     print(e)
 >>>     exit(1)
 ```
 """
 
 import tempfile
 import re
 
 from vyos import ConfigError
+from vyos.defaults import frr_debug_enable
 from vyos.utils.process import cmd
 from vyos.utils.process import popen
 from vyos.utils.process import STDOUT
 
 import logging
 from logging.handlers import SysLogHandler
 import os
 import sys
 
 LOG = logging.getLogger(__name__)
 DEBUG = False
 
 ch = SysLogHandler(address='/dev/log')
 ch2 = logging.StreamHandler(stream=sys.stdout)
 LOG.addHandler(ch)
 LOG.addHandler(ch2)
 
 babel_daemon = 'babeld'
 bfd_daemon = 'bfdd'
 bgp_daemon = 'bgpd'
 eigrp_daemon = 'eigrpd'
 isis_daemon = 'isisd'
 ldpd_daemon = 'ldpd'
 mgmt_daemon = 'mgmtd'
 openfabric_daemon = 'fabricd'
 ospf_daemon = 'ospfd'
 ospf6_daemon = 'ospf6d'
 pim_daemon = 'pimd'
 pim6_daemon = 'pim6d'
 rip_daemon = 'ripd'
 ripng_daemon = 'ripngd'
 static_daemon = 'staticd'
 zebra_daemon = 'zebra'
 
 _frr_daemons = [zebra_daemon, static_daemon, bgp_daemon, ospf_daemon, ospf6_daemon, rip_daemon, ripng_daemon, mgmt_daemon,
                 isis_daemon, pim_daemon, pim6_daemon, ldpd_daemon, eigrp_daemon, babel_daemon, bfd_daemon, openfabric_daemon]
 
 path_vtysh = '/usr/bin/vtysh'
 path_frr_reload = '/usr/lib/frr/frr-reload.py'
 path_config = '/run/frr'
 
 default_add_before = r'(ip prefix-list .*|route-map .*|line vty|end)'
 
 
 class FrrError(Exception):
     pass
 
 
 class ConfigurationNotValid(FrrError):
     """
     The configuratioin supplied to vtysh is not valid
     """
     pass
 
 
 class CommitError(FrrError):
     """
     Commiting the supplied configuration failed to commit by a unknown reason
     see commit error and/or run mark_configuration on the specified configuration
     to se error generated
 
     used by: reload_configuration()
     """
     pass
 
 
 class ConfigSectionNotFound(FrrError):
     """
     Removal of configuration failed because it is not existing in the supplied configuration
     """
     pass
 
 def init_debugging():
     global DEBUG
 
-    DEBUG = os.path.exists('/tmp/vyos.frr.debug')
+    DEBUG = os.path.exists(frr_debug_enable)
     if DEBUG:
         LOG.setLevel(logging.DEBUG)
 
 def get_configuration(daemon=None, marked=False):
     """ Get current running FRR configuration
     daemon:  Collect only configuration for the specified FRR daemon,
              supplying daemon=None retrieves the complete configuration
     marked:  Mark the configuration with "end" tags
 
     return:  string containing the running configuration from frr
 
     """
     if daemon and daemon not in _frr_daemons:
         raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
 
     cmd = f"{path_vtysh} -c 'show run'"
     if daemon:
         cmd += f' -d {daemon}'
 
     output, code = popen(cmd, stderr=STDOUT)
     if code:
         raise OSError(code, output)
 
     config = output.replace('\r', '')
     # Remove first header lines from FRR config
     config = config.split("\n", 3)[-1]
     # Mark the configuration with end tags
     if marked:
         config = mark_configuration(config)
 
     return config
 
 
 def mark_configuration(config):
     """ Add end marks and Test the configuration for syntax faults
     If the configuration is valid a marked version of the configuration is returned,
     or else it failes with a ConfigurationNotValid Exception
 
     config:  The configuration string to mark/test
     return:  The marked configuration from FRR
     """
     output, code = popen(f"{path_vtysh} -m -f -", stderr=STDOUT, input=config)
 
     if code == 2:
         raise ConfigurationNotValid(str(output))
     elif code:
         raise OSError(code, output)
 
     config = output.replace('\r', '')
     return config
 
 
 def reload_configuration(config, daemon=None):
     """ Execute frr-reload with the new configuration
     This will try to reapply the supplied configuration inside FRR.
     The configuration needs to be a complete configuration from the integrated config or
     from a daemon.
 
     config:  The configuration to apply
     daemon:  Apply the conigutaion to the specified FRR daemon,
              supplying daemon=None applies to the integrated configuration
     return:  None
     """
     if daemon and daemon not in _frr_daemons:
         raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
 
     f = tempfile.NamedTemporaryFile('w')
     f.write(config)
     f.flush()
 
     LOG.debug(f'reload_configuration: Reloading config using temporary file: {f.name}')
     cmd = f'{path_frr_reload} --reload'
     if daemon:
         cmd += f' --daemon {daemon}'
 
     if DEBUG:
         cmd += f' --debug --stdout'
 
     cmd += f' {f.name}'
 
     LOG.debug(f'reload_configuration: Executing command against frr-reload: "{cmd}"')
     output, code = popen(cmd, stderr=STDOUT)
     f.close()
 
     for i, e in enumerate(output.split('\n')):
         LOG.debug(f'frr-reload output: {i:3} {e}')
 
     if code == 1:
         raise ConfigError(output)
     elif code:
         raise OSError(code, output)
 
     return output
 
 
 def save_configuration():
     """ T3217: Save FRR configuration to /run/frr/config/frr.conf """
     return cmd(f'{path_vtysh} -n -w')
 
 
 def execute(command):
     """ Run commands inside vtysh
     command:  str containing commands to execute inside a vtysh session
     """
     if not isinstance(command, str):
         raise ValueError(f'command needs to be a string: {repr(command)}')
 
     cmd = f"{path_vtysh} -c '{command}'"
 
     output, code = popen(cmd, stderr=STDOUT)
     if code:
         raise OSError(code, output)
 
     config = output.replace('\r', '')
     return config
 
 
 def configure(lines, daemon=False):
     """ run commands inside config mode vtysh
     lines:  list or str conaining commands to execute inside a configure session
             only one command executed on each configure()
             Executing commands inside a subcontext uses the list to describe the context
             ex: ['router bgp 6500', 'neighbor 192.0.2.1 remote-as 65000']
     return: None
     """
     if isinstance(lines, str):
         lines = [lines]
     elif not isinstance(lines, list):
         raise ValueError('lines needs to be string or list of commands')
 
     if daemon and daemon not in _frr_daemons:
         raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
 
     cmd = f'{path_vtysh}'
     if daemon:
         cmd += f' -d {daemon}'
 
     cmd += " -c 'configure terminal'"
     for x in lines:
         cmd += f" -c '{x}'"
 
     output, code = popen(cmd, stderr=STDOUT)
     if code == 1:
         raise ConfigurationNotValid(f'Configuration FRR failed: {repr(output)}')
     elif code:
         raise OSError(code, output)
 
     config = output.replace('\r', '')
     return config
 
 
 def _replace_section(config, replacement, replace_re, before_re):
     r"""Replace a section of FRR config
     config:      full original configuration
     replacement: replacement configuration section
     replace_re:  The regex to replace
                  example: ^router bgp \d+$.?*^!$
                  this will replace everything between ^router bgp X$ and ^!$
     before_re:   When replace_re is not existant, the config will be added before this tag
                  example: ^line vty$
 
     return:      modified configuration as a text file
     """
     # DEPRECATED, this is replaced by a new implementation
     # Check if block is configured, remove the existing instance else add a new one
     if re.findall(replace_re, config, flags=re.MULTILINE | re.DOTALL):
         # Section is in the configration, replace it
         return re.sub(replace_re, replacement, config, count=1,
                       flags=re.MULTILINE | re.DOTALL)
     if before_re:
         if not re.findall(before_re, config, flags=re.MULTILINE | re.DOTALL):
             raise ConfigSectionNotFound(f"Config section {before_re} not found in config")
 
         # If no section is in the configuration, add it before the line vty line
         return re.sub(before_re, rf'{replacement}\n\g<1>', config, count=1,
                       flags=re.MULTILINE | re.DOTALL)
 
     raise ConfigSectionNotFound(f"Config section {replacement} not found in config")
 
 
 def replace_section(config, replacement, from_re, to_re=r'!', before_re=r'line vty'):
     r"""Replace a section of FRR config
     config:      full original configuration
     replacement: replacement configuration section
     from_re:     Regex for the start of section matching
                  example: 'router bgp \d+'
     to_re:       Regex for stop of section matching
                  default: '!'
                  example: '!'  or  'end'
     before_re:   When from_re/to_re  does not return a match, the config will
                  be added before this tag
                  default: ^line vty$
 
     startline and endline tags will be automatically added to the resulting from_re/to_re and before_re regex'es
     """
     # DEPRECATED, this is replaced by a new implementation
     return _replace_section(config, replacement, replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=rf'^({before_re})$')
 
 
 def remove_section(config, from_re, to_re='!'):
     # DEPRECATED, this is replaced by a new implementation
     return _replace_section(config, '', replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=None)
 
 
 def _find_first_block(config, start_pattern, stop_pattern, start_at=0):
     '''Find start and stop line numbers for a config block
     config:        (list) A list conaining the configuration that is searched
     start_pattern: (raw-str) The pattern searched for a a start of block tag
     stop_pattern:  (raw-str) The pattern searched for to signify the end of the block
     start_at:      (int) The index to start searching at in the <config>
 
     Returns:
         None: No complete block could be found
         set(int, int): A complete block found between the line numbers returned in the set
 
     The object <config> is searched from the start for the regex <start_pattern> until the first match is found.
     On a successful match it continues the search for the regex <stop_pattern> until it is found.
     After a successful run a set is returned containing the start and stop line numbers.
     '''
     LOG.debug(f'_find_first_block: find start={repr(start_pattern)} stop={repr(stop_pattern)} start_at={start_at}')
     _start = None
     for i, element in enumerate(config[start_at:], start=start_at):
         # LOG.debug(f'_find_first_block: running line {i:3} "{element}"')
         if not _start:
             if not re.match(start_pattern, element):
                 LOG.debug(f'_find_first_block: no match     {i:3} "{element}"')
                 continue
             _start = i
             LOG.debug(f'_find_first_block: Found start  {i:3} "{element}"')
             continue
 
         if not re.match(stop_pattern, element):
             LOG.debug(f'_find_first_block: no match     {i:3} "{element}"')
             continue
 
         LOG.debug(f'_find_first_block: Found stop   {i:3} "{element}"')
         return (_start, i)
 
     LOG.debug('_find_first_block: exit start={repr(start_pattern)} stop={repr(stop_pattern)} start_at={start_at}')
     return None
 
 
 def _find_first_element(config, pattern, start_at=0):
     '''Find the first element that matches the current pattern in config
     config:        (list) A list containing the configuration that is searched
     start_pattern: (raw-str) The pattern searched for
     start_at:      (int) The index to start searching at in the <config>
 
     return:   Line index of the line containing the searched pattern
 
     TODO: for now it returns -1 on a no-match because 0 also returns as False
     TODO: that means that we can not use False matching to tell if its
     '''
     LOG.debug(f'_find_first_element: find start="{pattern}" start_at={start_at}')
     for i, element in enumerate(config[start_at:], start=0):
         if re.match(pattern + '$', element):
             LOG.debug(f'_find_first_element: Found stop {i:3} "{element}"')
             return i
         LOG.debug(f'_find_first_element: no match   {i:3} "{element}"')
     LOG.debug(f'_find_first_element: Did not find any match, exiting')
     return -1
 
 
 def _find_elements(config, pattern, start_at=0):
     '''Find all instances of pattern and return a list containing all element indexes
     config:        (list) A list containing the configuration that is searched
     start_pattern: (raw-str) The pattern searched for
     start_at:      (int) The index to start searching at in the <config>
 
     return:    A list of line indexes containing the searched pattern
     TODO: refactor this to return a generator instead
     '''
     return [i for i, element in enumerate(config[start_at:], start=0) if re.match(pattern + '$', element)]
 
 
 class FRRConfig:
     '''Main FRR Configuration manipulation object
     Using this object the user could load, manipulate and commit the configuration to FRR
     '''
     def __init__(self, config=[]):
         self.imported_config = ''
 
         if isinstance(config, list):
             self.config = config.copy()
             self.original_config = config.copy()
         elif isinstance(config, str):
             self.config = config.split('\n')
             self.original_config = self.config.copy()
         else:
             raise ValueError(
                 'The config element needs to be a string or list type object')
 
         if config:
             LOG.debug(f'__init__: frr library initiated with initial config')
             for i, e in enumerate(self.config):
                 LOG.debug(f'__init__: initial              {i:3} {e}')
 
     def load_configuration(self, daemon=None):
         '''Load the running configuration from FRR into the config object
         daemon: str with name of the FRR Daemon to load configuration from or
                 None to load the consolidated config
 
         Using this overwrites the current loaded config objects and replaces the original loaded config
         '''
         init_debugging()
 
         self.imported_config = get_configuration(daemon=daemon)
         if daemon:
             LOG.debug(f'load_configuration: Configuration loaded from FRR daemon {daemon}')
         else:
             LOG.debug(f'load_configuration: Configuration loaded from FRR integrated config')
 
         self.original_config = self.imported_config.split('\n')
         self.config = self.original_config.copy()
 
         for i, e in enumerate(self.imported_config.split('\n')):
             LOG.debug(f'load_configuration:  loaded    {i:3} {e}')
         return
 
     def test_configuration(self):
         '''Test the current configuration against FRR
         This will exception if FRR failes to load the current configuration object
         '''
         LOG.debug('test_configation: Testing configuration')
         mark_configuration('\n'.join(self.config))
 
     def commit_configuration(self, daemon=None):
         '''
         Commit the current configuration to FRR daemon: str with name of the
         FRR daemon to commit to or None to use the consolidated config.
 
         Configuration is automatically saved after apply
         '''
         LOG.debug('commit_configuration:  Commiting configuration')
         for i, e in enumerate(self.config):
             LOG.debug(f'commit_configuration: new_config {i:3} {e}')
 
         # https://github.com/FRRouting/frr/issues/10132
         # https://github.com/FRRouting/frr/issues/10133
         count = 0
         count_max = 5
         emsg = ''
         while count < count_max:
             count += 1
             try:
                 reload_configuration('\n'.join(self.config), daemon=daemon)
                 break
             except ConfigError as e:
                 emsg = str(e)
             except:
                 # we just need to re-try the commit of the configuration
                 # for the listed FRR issues above
                 pass
         if count >= count_max:
             if emsg:
                 raise ConfigError(emsg)
             raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} daemon!')
 
         # Save configuration to /run/frr/config/frr.conf
         save_configuration()
 
 
     def modify_section(self, start_pattern, replacement='!', stop_pattern=r'\S+', remove_stop_mark=False, count=0):
         if isinstance(replacement, str):
             replacement = replacement.split('\n')
         elif not isinstance(replacement, list):
             return ValueError("The replacement element needs to be a string or list type object")
         LOG.debug(f'modify_section: starting search for {repr(start_pattern)} until {repr(stop_pattern)}')
 
         _count = 0
         _next_start = 0
         while True:
             if count and count <= _count:
                 # Break out of the loop after specified amount of matches
                 LOG.debug(f'modify_section: reached limit ({_count}), exiting loop at line {_next_start}')
                 break
             # While searching, always assume that the user wants to search for the exact pattern he entered
             # To be more specific the user needs a override, eg. a "pattern.*"
             _w = _find_first_block(
                 self.config, start_pattern+'$', stop_pattern, start_at=_next_start)
             if not _w:
                 # Reached the end, no more elements to remove
                 LOG.debug(f'modify_section: No more config sections found, exiting')
                 break
             start_element, end_element = _w
             LOG.debug(f'modify_section:   found match between {start_element} and {end_element}')
             for i, e in enumerate(self.config[start_element:end_element+1 if remove_stop_mark else end_element],
                                   start=start_element):
                 LOG.debug(f'modify_section:   remove       {i:3} {e}')
             del self.config[start_element:end_element +
                             1 if remove_stop_mark else end_element]
             if replacement:
                 # Append the replacement config at the current position
                 for i, e in enumerate(replacement, start=start_element):
                     LOG.debug(f'modify_section:   add          {i:3} {e}')
                 self.config[start_element:start_element] = replacement
             _count += 1
             _next_start = start_element + len(replacement)
 
         return _count
 
     def add_before(self, before_pattern, addition):
         '''Add config block before this element in the configuration'''
         if isinstance(addition, str):
             addition = addition.split('\n')
         elif not isinstance(addition, list):
             return ValueError("The replacement element needs to be a string or list type object")
 
         start = _find_first_element(self.config, before_pattern)
         if start < 0:
             return False
         for i, e in enumerate(addition, start=start):
             LOG.debug(f'add_before:   add          {i:3} {e}')
         self.config[start:start] = addition
         return True
 
     def __str__(self):
         return '\n'.join(self.config)
 
     def __repr__(self):
         return f'frr({repr(str(self))})'
diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py
index 015596a8f..2069930a9 100644
--- a/python/vyos/frrender.py
+++ b/python/vyos/frrender.py
@@ -1,156 +1,156 @@
 # Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library 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
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 """
 Library used to interface with FRRs mgmtd introduced in version 10.0
 """
 
 import os
 
+from vyos.defaults import frr_debug_enable
 from vyos.utils.file import write_file
 from vyos.utils.process import rc_cmd
 from vyos.template import render_to_string
 from vyos import ConfigError
 
-DEBUG_ON = os.path.exists('/tmp/vyos.frr.debug')
-DEBUG_ON = True
+DEBUG_ON = os.path.exists(frr_debug_enable)
 
 def debug(message):
     if not DEBUG_ON:
         return
     print(message)
 
 pim_daemon = 'pimd'
 
 frr_protocols = ['babel', 'bfd', 'bgp', 'eigrp', 'isis', 'mpls', 'nhrp',
                  'openfabric', 'ospf', 'ospfv3', 'pim', 'pim6', 'rip',
                  'ripng', 'rpki', 'segment_routing', 'static']
 
 class FRRender:
     def __init__(self):
         self._frr_conf = '/run/frr/config/frr.conf'
 
     def generate(self, config):
         if not isinstance(config, dict):
             raise ValueError('config must be of type dict')
 
         def inline_helper(config_dict) -> str:
             output = '!\n'
             if 'babel' in config_dict and 'deleted' not in config_dict['babel']:
                 output += render_to_string('frr/babeld.frr.j2', config_dict['babel'])
                 output += '\n'
             if 'bfd' in config_dict and 'deleted' not in config_dict['bfd']:
                 output += render_to_string('frr/bfdd.frr.j2', config_dict['bfd'])
                 output += '\n'
             if 'bgp' in config_dict and 'deleted' not in config_dict['bgp']:
                 output += render_to_string('frr/bgpd.frr.j2', config_dict['bgp'])
                 output += '\n'
             if 'eigrp' in config_dict and 'deleted' not in config_dict['eigrp']:
                 output += render_to_string('frr/eigrpd.frr.j2', config_dict['eigrp'])
                 output += '\n'
             if 'isis' in config_dict and 'deleted' not in config_dict['isis']:
                 output += render_to_string('frr/isisd.frr.j2', config_dict['isis'])
                 output += '\n'
             if 'mpls' in config_dict and 'deleted' not in config_dict['mpls']:
                 output += render_to_string('frr/ldpd.frr.j2', config_dict['mpls'])
                 output += '\n'
             if 'openfabric' in config_dict and 'deleted' not in config_dict['openfabric']:
                 output += render_to_string('frr/fabricd.frr.j2', config_dict['openfabric'])
                 output += '\n'
             if 'ospf' in config_dict and 'deleted' not in config_dict['ospf']:
                 output += render_to_string('frr/ospfd.frr.j2', config_dict['ospf'])
                 output += '\n'
             if 'ospfv3' in config_dict and 'deleted' not in config_dict['ospfv3']:
                 output += render_to_string('frr/ospf6d.frr.j2', config_dict['ospfv3'])
                 output += '\n'
             if 'pim' in config_dict and 'deleted' not in config_dict['pim']:
                 output += render_to_string('frr/pimd.frr.j2', config_dict['pim'])
                 output += '\n'
             if 'pim6' in config_dict and 'deleted' not in config_dict['pim6']:
                 output += render_to_string('frr/pim6d.frr.j2', config_dict['pim6'])
                 output += '\n'
             if 'policy' in config_dict and len(config_dict['policy']) > 0:
                 output += render_to_string('frr/policy.frr.j2', config_dict['policy'])
                 output += '\n'
             if 'rip' in config_dict and 'deleted' not in config_dict['rip']:
                 output += render_to_string('frr/ripd.frr.j2', config_dict['rip'])
                 output += '\n'
             if 'ripng' in config_dict and 'deleted' not in config_dict['ripng']:
                 output += render_to_string('frr/ripngd.frr.j2', config_dict['ripng'])
                 output += '\n'
             if 'rpki' in config_dict and 'deleted' not in config_dict['rpki']:
                 output += render_to_string('frr/rpki.frr.j2', config_dict['rpki'])
                 output += '\n'
             if 'segment_routing' in config_dict and 'deleted' not in config_dict['segment_routing']:
                 output += render_to_string('frr/zebra.segment_routing.frr.j2', config_dict['segment_routing'])
                 output += '\n'
             if 'static' in config_dict and 'deleted' not in config_dict['static']:
                 output += render_to_string('frr/staticd.frr.j2', config_dict['static'])
                 output += '\n'
             return output
 
         debug('======< RENDERING CONFIG >======')
         # we can not reload an empty file, thus we always embed the marker
         output = '!\n'
         # Enable SNMP agentx support
         # SNMP AgentX support cannot be disabled once enabled
         if 'snmp' in config:
             output += 'agentx\n'
         # Add routing protocols in global VRF
         output += inline_helper(config)
         # Interface configuration for EVPN is not VRF related
         if 'interfaces' in config:
             output += render_to_string('frr/evpn.mh.frr.j2', {'interfaces' : config['interfaces']})
             output += '\n'
 
         if 'vrf' in config and 'name' in config['vrf']:
             output += render_to_string('frr/zebra.vrf.route-map.frr.j2', config['vrf']) + '\n'
             for vrf, vrf_config in config['vrf']['name'].items():
                 if 'protocols' not in vrf_config:
                     continue
                 for protocol in vrf_config['protocols']:
                     vrf_config['protocols'][protocol]['vrf'] = vrf
 
                 output += inline_helper(vrf_config['protocols'])
 
         debug(output)
         debug('======< RENDERING CONFIG COMPLETE >======')
         write_file(self._frr_conf, output)
         if DEBUG_ON: write_file('/tmp/frr.conf.debug', output)
 
     def apply(self):
         count = 0
         count_max = 5
         emsg = ''
         while count < count_max:
             count += 1
             print('FRR: Reloading configuration', count)
 
             cmdline = '/usr/lib/frr/frr-reload.py --reload'
             if DEBUG_ON:
                 cmdline += ' --debug'
             rc, emsg = rc_cmd(f'{cmdline} {self._frr_conf}')
             if rc != 0:
                 debug('FRR configuration reload failed, retrying')
                 continue
             debug(emsg)
             debug('======< DONE APPLYING CONFIG  >======')
             break
         if count >= count_max:
             raise ConfigError(emsg)
 
     def save_configuration():
         """ T3217: Save FRR configuration to /run/frr/config/frr.conf """
         return cmd('/usr/bin/vtysh -n --writeconfig')
diff --git a/src/init/vyos-router b/src/init/vyos-router
index e2e964656..00136309b 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -1,595 +1,596 @@
 #!/bin/bash
 # Copyright (C) 2021-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 . /lib/lsb/init-functions
 
 : ${vyatta_env:=/etc/default/vyatta}
 source $vyatta_env
 
 declare progname=${0##*/}
 declare action=$1; shift
 
 declare -x BOOTFILE=$vyatta_sysconfdir/config/config.boot
 declare -x DEFAULT_BOOTFILE=$vyatta_sysconfdir/config.boot.default
 
 declare -x VYCONF_CONFIG_DIR=/usr/libexec/vyos/vyconf/config
 
 # If vyos-config= boot option is present, use that file instead
 for x in $(cat /proc/cmdline); do
     [[ $x = vyos-config=* ]] || continue
     VYOS_CONFIG="${x#vyos-config=}"
 done
 
 if [ ! -z "$VYOS_CONFIG" ]; then
     if [ -r "$VYOS_CONFIG" ]; then
         echo "Config selected manually: $VYOS_CONFIG"
         declare -x BOOTFILE="$VYOS_CONFIG"
     else
         echo "WARNING: Could not read selected config file, using default!"
     fi
 fi
 
 declare -a subinit
 declare -a all_subinits=( firewall )
 
 if [ $# -gt 0 ] ; then
     for s in $@ ; do
         [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s
     done
 else
     for s in ${all_subinits[@]} ; do
         [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s
     done
 fi
 
 GROUP=vyattacfg
 
 # easy way to make empty file without any command
 empty()
 {
     >$1
 }
 
 # check if bootup of this portion is disabled
 disabled () {
     grep -q -w no-vyos-$1 /proc/cmdline
 }
 
 # Load encrypted config volume
 mount_encrypted_config() {
     persist_path=$(/opt/vyatta/sbin/vyos-persistpath)
     if [ $? == 0 ]; then
         if [ -e $persist_path/boot ]; then
             image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//')
 
             if [ -z "$image_name" ]; then
                 return
             fi
 
             if [ ! -f $persist_path/luks/$image_name ]; then
                 return
             fi
 
             vyos_tpm_key=$(python3 -c 'from vyos.tpm import read_tpm_key; print(read_tpm_key().decode())' 2>/dev/null)
 
             if [ $? -ne 0 ]; then
                 echo "ERROR: Failed to fetch encryption key from TPM. Encrypted config volume has not been mounted"
                 echo "Use 'encryption load' to load volume with recovery key"
                 echo "or 'encryption disable' to decrypt volume with recovery key"
                 return
             fi
 
             echo $vyos_tpm_key | tr -d '\r\n' | cryptsetup open $persist_path/luks/$image_name vyos_config --key-file=-
 
             if [ $? -ne 0 ]; then
                 echo "ERROR: Failed to decrypt config volume. Encrypted config volume has not been mounted"
                 echo "Use 'encryption load' to load volume with recovery key"
                 echo "or 'encryption disable' to decrypt volume with recovery key"
                 return
             fi
 
             mount /dev/mapper/vyos_config /config
             mount /dev/mapper/vyos_config $vyatta_sysconfdir/config
 
             echo "Mounted encrypted config volume"
         fi
     fi
 }
 
 unmount_encrypted_config() {
     persist_path=$(/opt/vyatta/sbin/vyos-persistpath)
     if [ $? == 0 ]; then
         if [ -e $persist_path/boot ]; then
             image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//')
 
             if [ -z "$image_name" ]; then
                 return
             fi
 
             if [ ! -f $persist_path/luks/$image_name ]; then
                 return
             fi
 
             umount /config
             umount $vyatta_sysconfdir/config
 
             cryptsetup close vyos_config
         fi
     fi
 }
 
 # if necessary, provide initial config
 init_bootfile () {
     # define and version default boot config if not present
     if [ ! -r $DEFAULT_BOOTFILE ]; then
         if [ -f $vyos_data_dir/config.boot.default ]; then
             cp $vyos_data_dir/config.boot.default $DEFAULT_BOOTFILE
             $vyos_libexec_dir/add-system-version.py >> $DEFAULT_BOOTFILE
         fi
     fi
     if [ ! -r $BOOTFILE ] ; then
         if [ -f $DEFAULT_BOOTFILE ]; then
             cp $DEFAULT_BOOTFILE $BOOTFILE
         else
             $vyos_libexec_dir/add-system-version.py > $BOOTFILE
         fi
         chgrp ${GROUP} $BOOTFILE
         chmod 660 $BOOTFILE
     fi
     if [ -d $VYCONF_CONFIG_DIR ] ; then
         cp -f $BOOTFILE $VYCONF_CONFIG_DIR/config.boot
         cp -f $DEFAULT_BOOTFILE $VYCONF_CONFIG_DIR/config.failsafe
     fi
 }
 
 # if necessary, migrate initial config
 migrate_bootfile ()
 {
     if [ -x $vyos_libexec_dir/run-config-migration.py ]; then
         log_progress_msg migrate
         sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE"
         # update vyconf copy after migration
         if [ -d $VYCONF_CONFIG_DIR ] ; then
             cp -f $BOOTFILE $VYCONF_CONFIG_DIR/config.boot
         fi
     fi
 }
 
 # configure system-specific settings
 system_config ()
 {
     if [ -x $vyos_libexec_dir/run-config-activation.py ]; then
         log_progress_msg system
         sg ${GROUP} -c "$vyos_libexec_dir/run-config-activation.py $BOOTFILE"
     fi
 }
 
 # load the initial config
 load_bootfile ()
 {
     log_progress_msg configure
     (
         if [ -f /etc/default/vyatta-load-boot ]; then
             # build-specific environment for boot-time config loading
             source /etc/default/vyatta-load-boot
         fi
         if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then
             sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE"
         fi
     )
 }
 
 # restore if missing pre-config script
 restore_if_missing_preconfig_script ()
 {
     if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script ]; then
         mkdir -p ${vyatta_sysconfdir}/config/scripts
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts
         chmod 775 ${vyatta_sysconfdir}/config/scripts
         cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
         chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
     fi
 }
 
 # execute the pre-config script
 run_preconfig_script ()
 {
     if [ -x $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script
     fi
 }
 
 # restore if missing post-config script
 restore_if_missing_postconfig_script ()
 {
     if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script ]; then
         mkdir -p ${vyatta_sysconfdir}/config/scripts
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts
         chmod 775 ${vyatta_sysconfdir}/config/scripts
         cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
         chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
     fi
 }
 
 # execute the post-config scripts
 run_postconfig_scripts ()
 {
     if [ -x $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script
     fi
     if [ -x $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script
     fi
 }
 
 run_postupgrade_script ()
 {
     if [ -f $vyatta_sysconfdir/config/.upgraded ]; then
         # Run the system script
         /usr/libexec/vyos/system/post-upgrade
 
         # Run user scripts
         if [ -d $vyatta_sysconfdir/config/scripts/post-upgrade.d ]; then
             run-parts $vyatta_sysconfdir/config/scripts/post-upgrade.d
         fi
         rm -f $vyatta_sysconfdir/config/.upgraded
     fi
 }
 
 #
 # On image booted machines, we need to mount /boot from the image-specific
 # boot directory so that kernel package installation will put the
 # files in the right place.  We also have to mount /boot/grub from the
 # system-wide grub directory so that tools that edit the grub.cfg
 # file will find it in the expected location.
 #
 bind_mount_boot ()
 {
     persist_path=$(/opt/vyatta/sbin/vyos-persistpath)
     if [ $? == 0 ]; then
         if [ -e $persist_path/boot ]; then
             image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//')
 
             if [ -n "$image_name" ]; then
                 mount --bind $persist_path/boot/$image_name /boot
                 if [ $? -ne 0 ]; then
                     echo "Couldn't bind mount /boot"
                 fi
 
                 if [ ! -d /boot/grub ]; then
                     mkdir /boot/grub
                 fi
 
                 mount --bind $persist_path/boot/grub /boot/grub
                 if [ $? -ne 0 ]; then
                     echo "Couldn't bind mount /boot/grub"
                 fi
             fi
         fi
     fi
 }
 
 clear_or_override_config_files ()
 {
     for conf in snmp/snmpd.conf snmp/snmptrapd.conf snmp/snmp.conf \
         keepalived/keepalived.conf cron.d/vyos-crontab \
         ipvsadm.rules default/ipvsadm resolv.conf
     do
     if [ -s /etc/$conf ] ; then
         empty /etc/$conf
         chmod 0644 /etc/$conf
     fi
     done
 }
 
 update_interface_config ()
 {
     if [ -d /run/udev/vyos ]; then
         $vyos_libexec_dir/vyos-interface-rescan.py $BOOTFILE
     fi
 }
 
 cleanup_post_commit_hooks () {
     # Remove links from the post-commit hooks directory.
     # note that this approach only supports hooks that are "configured",
     # i.e., it does not support hooks that need to always be present.
     cpostdir=$(cli-shell-api getPostCommitHookDir)
     # exclude commit hooks that need to always be present
     excluded="00vyos-sync 10vyatta-log-commit.pl 99vyos-user-postcommit-hooks"
     if [ -d "$cpostdir" ]; then
 	    for f in $cpostdir/*; do
 	        if [[ ! $excluded =~ $(basename $f) ]]; then
 		        rm -f $cpostdir/$(basename $f)
 	        fi
 	    done
     fi
 }
 
 # These are all the default security setting which are later
 # overridden when configuration is read. These are the values the
 # system defaults.
 security_reset ()
 {
 
     # restore NSS cofniguration back to sane system defaults
     # will be overwritten later when configuration is loaded
     cat <<EOF >/etc/nsswitch.conf
 passwd:         files
 group:          files
 shadow:         files
 gshadow:        files
 
 # Per T2678, commenting out myhostname
 hosts:          files dns #myhostname
 networks:       files
 
 protocols:      db files
 services:       db files
 ethers:         db files
 rpc:            db files
 
 netgroup:       nis
 EOF
 
     # restore PAM back to virgin state (no radius/tacacs services)
     pam-auth-update --disable radius-mandatory radius-optional
     rm -f /etc/pam_radius_auth.conf
     pam-auth-update --disable tacplus-mandatory tacplus-optional
     rm -f /etc/tacplus_nss.conf /etc/tacplus_servers
     # and no Google authenticator for 2FA/MFA
     pam-auth-update --disable mfa-google-authenticator
 
     # Certain configuration files are re-generated by the configuration
     # subsystem and must reside under /etc and can not easily be moved to /run.
     # So on every boot we simply delete any remaining files and let the CLI
     # regenearte them.
 
     # PPPoE
     rm -f /etc/ppp/peers/pppoe* /etc/ppp/peers/wlm*
 
     # IPSec
     rm -rf /etc/ipsec.conf /etc/ipsec.secrets
     find /etc/swanctl -type f | xargs rm -f
 
     # limit cleanup
     rm -f /etc/security/limits.d/10-vyos.conf
 
     # iproute2 cleanup
     rm -f /etc/iproute2/rt_tables.d/vyos-*.conf
 
     # Container
     rm -f /etc/containers/storage.conf /etc/containers/registries.conf /etc/containers/containers.conf
     # Clean all networks and re-create them from our CLI
     rm -f /etc/containers/networks/*
 
     # System Options (SSH/cURL)
     rm -f /etc/ssh/ssh_config.d/*vyos*.conf
     rm -f /etc/curlrc
 }
 
 # XXX: T3885 - generate persistend DHCPv6 DUID (Type4 - UUID based)
 gen_duid ()
 {
     DUID_FILE="/var/lib/dhcpv6/dhcp6c_duid"
     UUID_FILE="/sys/class/dmi/id/product_uuid"
     UUID_FILE_ALT="/sys/class/dmi/id/product_serial"
     if [ ! -f ${UUID_FILE} ] && [ ! -f ${UUID_FILE_ALT} ]; then
         return 1
     fi
 
     # DUID is based on the BIOS/EFI UUID. We omit additional - characters
     if [ -f ${UUID_FILE} ]; then
         UUID=$(cat ${UUID_FILE} | tr -d -)
     fi
     if [ -z ${UUID} ]; then
         UUID=$(uuidgen --sha1 --namespace @dns --name $(cat ${UUID_FILE_ALT}) | tr -d -)
     fi
     # Add DUID type4 (UUID) information
     DUID_TYPE="0004"
 
     # The length-information (as per RFC6355 UUID is 128 bits long) is in big-endian
     # format - beware when porting to ARM64. The length field consists out of the
     # UUID (128 bit + 16 bits DUID type) resulting in hex 12.
     DUID_LEN="0012"
     if [ "$(echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 )" -eq 1 ]; then
         # true on little-endian (x86) systems
         DUID_LEN="1200"
     fi
 
     for i in $(echo -n ${DUID_LEN}${DUID_TYPE}${UUID} | sed 's/../& /g'); do
         echo -ne "\x$i"
     done > ${DUID_FILE}
 }
 
 start ()
 {
     # reset and clean config files
     security_reset || log_failure_msg "security reset failed"
 
     # some legacy directories migrated over from old rl-system.init
     mkdir -p /var/run/vyatta /var/log/vyatta
     chgrp vyattacfg /var/run/vyatta /var/log/vyatta
     chmod 775 /var/run/vyatta /var/log/vyatta
 
     log_daemon_msg "Waiting for NICs to settle down"
     # On boot time udev migth take a long time to reorder nic's, this will ensure that
     # all udev activity is completed and all nics presented at boot-time will have their
     # final name before continuing with vyos-router initialization.
     SECONDS=0
     udevadm settle
     STATUS=$?
     log_progress_msg "settled in ${SECONDS}sec."
     log_end_msg ${STATUS}
 
     # mountpoint for bpf maps required by xdp
     mount -t bpf none /sys/fs/bpf
 
     # Clear out Debian APT source config file
     empty /etc/apt/sources.list
 
     # Generate DHCPv6 DUID
     gen_duid || log_failure_msg "could not generate DUID"
 
     # Mount a temporary filesystem for container networks.
     # Configuration should be loaded from VyOS cli.
     cni_dir="/etc/cni/net.d"
     [ ! -d ${cni_dir} ] && mkdir -p ${cni_dir}
     mount -t tmpfs none ${cni_dir}
 
     # Init firewall
     nfct helper add rpc inet tcp
     nfct helper add rpc inet udp
     nfct helper add tns inet tcp
     nfct helper add rpc inet6 tcp
     nfct helper add rpc inet6 udp
     nfct helper add tns inet6 tcp
     nft --file /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules"
 
     # As VyOS does not execute commands that are not present in the CLI we call
     # the script by hand to have a single source for the login banner and MOTD
     ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console"
     ${vyos_conf_scripts_dir}/system_login_banner.py || log_failure_msg "could not reset motd and issue files"
     ${vyos_conf_scripts_dir}/system_option.py || log_failure_msg "could not reset system option files"
     ${vyos_conf_scripts_dir}/system_ip.py || log_failure_msg "could not reset system IPv4 options"
     ${vyos_conf_scripts_dir}/system_ipv6.py || log_failure_msg "could not reset system IPv6 options"
     ${vyos_conf_scripts_dir}/system_conntrack.py || log_failure_msg "could not reset conntrack subsystem"
     ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem"
 
     clear_or_override_config_files || log_failure_msg "could not reset config files"
 
     # enable some debugging before loading the configuration
     if grep -q vyos-debug /proc/cmdline; then
         log_action_begin_msg "Enable runtime debugging options"
+        FRR_DEBUG=$(python3 -c "from vyos.defaults import frr_debug_enable; print(frr_debug_enable)")
+        touch $FRR_DEBUG
         touch /tmp/vyos.container.debug
         touch /tmp/vyos.ifconfig.debug
-        touch /tmp/vyos.frr.debug
         touch /tmp/vyos.container.debug
         touch /tmp/vyos.smoketest.debug
     fi
 
     # Cleanup PKI CAs
     if [ -d /usr/local/share/ca-certificates/vyos ]; then
         rm -f /usr/local/share/ca-certificates/vyos/*.crt
         update-ca-certificates >/dev/null 2>&1
     fi
 
     log_action_begin_msg "Mounting VyOS Config"
     # ensure the vyatta_configdir supports a large number of inodes since
     # the config hierarchy is often inode-bound (instead of size).
     # impose a minimum and then scale up dynamically with the actual size
     # of the system memory.
     local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo)
     local tpages
     local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes
     mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \
       && chgrp ${GROUP} ${vyatta_configdir}
     log_action_end_msg $?
 
     mount_encrypted_config
 
     # T5239: early read of system hostname as this value is read-only once during
     # FRR initialisation
     tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "system host-name")
     hostnamectl set-hostname --static "$tmp"
 
     ${vyos_conf_scripts_dir}/system_frr.py || log_failure_msg "could not reset FRR config"
     # If for any reason FRR was not started by system_frr.py - start it anyways.
     # This is a safety net!
     systemctl start frr.service
 
     disabled bootfile || init_bootfile
 
     cleanup_post_commit_hooks
 
     log_daemon_msg "Starting VyOS router"
     disabled migrate || migrate_bootfile
 
     restore_if_missing_preconfig_script
 
     run_preconfig_script
 
     run_postupgrade_script
 
     update_interface_config
 
     disabled system_config || system_config
 
     systemctl start vyconfd.service
 
     for s in ${subinit[@]} ; do
     if ! disabled $s; then
         log_progress_msg $s
         if ! ${vyatta_sbindir}/${s}.init start
         then log_failure_msg
          exit 1
         fi
     fi
     done
 
     bind_mount_boot
 
     disabled configure || load_bootfile
     log_end_msg $?
 
     telinit q
     chmod g-w,o-w /
 
     restore_if_missing_postconfig_script
 
     run_postconfig_scripts
     tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "protocols rpki cache")
     if [[ ! -z "$tmp" ]]; then
         vtysh -c "rpki start"
     fi
 }
 
 stop()
 {
     local -i status=0
     log_daemon_msg "Stopping VyOS router"
     for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do
     s=${subinit[$i]}
     log_progress_msg $s
     ${vyatta_sbindir}/${s}.init stop
     let status\|=$?
     done
     log_end_msg $status
     log_action_begin_msg "Un-mounting VyOS Config"
     umount ${vyatta_configdir}
     log_action_end_msg $?
 
     systemctl stop vyconfd.service
 
     systemctl stop frr.service
 
     unmount_encrypted_config
 }
 
 case "$action" in
     start) start ;;
     stop)  stop ;;
     restart|force-reload) stop && start ;;
     *)  log_failure_msg "usage: $progname [ start|stop|restart ] [ subinit ... ]" ;
     false ;;
 esac
 
 exit $?
 
 # Local Variables:
 # mode: shell-script
 # sh-indentation: 4
 # End: