diff --git a/Makefile b/Makefile
index 432de7547..44a6e35ed 100644
--- a/Makefile
+++ b/Makefile
@@ -1,125 +1,125 @@
 TMPL_DIR := templates-cfg
 OP_TMPL_DIR := templates-op
 BUILD_DIR := build
 DATA_DIR := data
 SHIM_DIR := src/shim
 LIBS := -lzmq
 CFLAGS :=
 BUILD_ARCH := $(shell dpkg-architecture -q DEB_BUILD_ARCH)
 J2LINT := $(shell command -v j2lint 2> /dev/null)
-PYLINT_FILES := $(shell git ls-files *.py)
+PYLINT_FILES := $(shell git ls-files *.py src/migration-scripts)
 
 config_xml_src = $(wildcard interface-definitions/*.xml.in)
 config_xml_obj = $(config_xml_src:.xml.in=.xml)
 op_xml_src = $(wildcard op-mode-definitions/*.xml.in)
 op_xml_obj = $(op_xml_src:.xml.in=.xml)
 
 %.xml: %.xml.in
 	@echo Generating $(BUILD_DIR)/$@ from $<
 	mkdir -p $(BUILD_DIR)/$(dir $@)
 	$(CURDIR)/scripts/transclude-template $< > $(BUILD_DIR)/$@
 
 .PHONY: interface_definitions
 .ONESHELL:
 interface_definitions: $(config_xml_obj)
 	mkdir -p $(TMPL_DIR)
 
 	$(CURDIR)/scripts/override-default $(BUILD_DIR)/interface-definitions
 
 	find $(BUILD_DIR)/interface-definitions -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1
 
 	$(CURDIR)/python/vyos/xml_ref/generate_cache.py --xml-dir $(BUILD_DIR)/interface-definitions || exit 1
 
 	# XXX: delete top level node.def's that now live in other packages
 	# IPSec VPN EAP-RADIUS does not support source-address
 	rm -rf $(TMPL_DIR)/vpn/ipsec/remote-access/radius/source-address
 
 	# T2472 - EIGRP support
 	rm -rf $(TMPL_DIR)/protocols/eigrp
 	# T2773 - EIGRP support for VRF
 	rm -rf $(TMPL_DIR)/vrf/name/node.tag/protocols/eigrp
 
 	# XXX: test if there are empty node.def files - this is not allowed as these
 	# could mask help strings or mandatory priority statements
 	find $(TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1'
 
 ifeq ($(BUILD_ARCH),arm64)
 	# There is currently no telegraf support in VyOS for ARM64, remove CLI definitions
 	rm -rf $(TMPL_DIR)/service/monitoring/telegraf
 endif
 
 .PHONY: op_mode_definitions
 .ONESHELL:
 op_mode_definitions: $(op_xml_obj)
 	mkdir -p $(OP_TMPL_DIR)
 
 	find $(BUILD_DIR)/op-mode-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1
 
 	# XXX: delete top level op mode node.def's that now live in other packages
 	rm -f $(OP_TMPL_DIR)/add/node.def
 	rm -f $(OP_TMPL_DIR)/clear/interfaces/node.def
 	rm -f $(OP_TMPL_DIR)/clear/node.def
 	rm -f $(OP_TMPL_DIR)/delete/node.def
 
 	# XXX: ping, traceroute and mtr must be able to recursivly call themselves as the
 	# options are provided from the scripts themselves
 	ln -s ../node.tag $(OP_TMPL_DIR)/ping/node.tag/node.tag/
 	ln -s ../node.tag $(OP_TMPL_DIR)/traceroute/node.tag/node.tag/
 	ln -s ../node.tag $(OP_TMPL_DIR)/mtr/node.tag/node.tag/
 
 	# XXX: test if there are empty node.def files - this is not allowed as these
 	# could mask help strings or mandatory priority statements
 	find $(OP_TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1'
 
 .PHONY: vyshim
 vyshim:
 	$(MAKE) -C $(SHIM_DIR)
 
 .PHONY: all
 all: clean interface_definitions op_mode_definitions check test j2lint vyshim
 
 .PHONY: check
 .ONESHELL:
 check:
 	@echo "Checking which CLI scripts are not enabled to work with vyos-configd..."
 	@for file in `ls src/conf_mode -I__pycache__`
 	do
 		if ! grep -q $$file data/configd-include.json; then
 			echo "* $$file"
 		fi
 	done
 
 .PHONY: clean
 clean:
 	rm -rf $(BUILD_DIR)
 	rm -rf $(TMPL_DIR)
 	rm -rf $(OP_TMPL_DIR)
 	$(MAKE) -C $(SHIM_DIR) clean
 
 .PHONY: test
 test:
 	set -e; python3 -m compileall -q -x '/vmware-tools/scripts/, /ppp/' .
 	PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose
 
 .PHONY: j2lint
 j2lint:
 ifndef J2LINT
 	$(error "j2lint binary not found, consider installing: pip install git+https://github.com/aristanetworks/j2lint.git@341b5d5db86")
 endif
 	$(J2LINT) data/
 
 .PHONY: sonar
 sonar:
 	sonar-scanner -X -Dsonar.login=${SONAR_TOKEN}
 
 .PHONY: unused-imports
 unused-imports:
 	@pylint --disable=all --enable=W0611 $(PYLINT_FILES)
 
 deb:
 	dpkg-buildpackage -uc -us -tc -b
 
 .PHONY: schema
 schema:
 	trang -I rnc -O rng schema/interface_definition.rnc schema/interface_definition.rng
 	trang -I rnc -O rng schema/op-mode-definition.rnc schema/op-mode-definition.rng
diff --git a/debian/control b/debian/control
index 2f32e7f35..65e580f03 100644
--- a/debian/control
+++ b/debian/control
@@ -1,211 +1,212 @@
 Source: vyos-1x
 Section: contrib/net
 Priority: extra
 Maintainer: VyOS Package Maintainers <maintainers@vyos.net>
 Build-Depends:
   debhelper (>= 9),
   dh-python,
   fakeroot,
   gcc,
   iproute2,
   libvyosconfig0 (>= 0.0.7),
   libzmq3-dev,
   python3 (>= 3.10),
+  pylint,
   python3-coverage,
   python3-lxml,
   python3-netifaces,
   python3-nose,
   python3-jinja2,
   python3-psutil,
   python3-setuptools,
   python3-xmltodict,
   quilt,
   whois
 Standards-Version: 3.9.6
 
 Package: vyos-1x
 Architecture: amd64 arm64
 Pre-Depends:
   libnss-tacplus [amd64],
   libpam-tacplus [amd64],
   libpam-radius-auth [amd64]
 Depends:
   ${python3:Depends} (>= 3.10),
   aardvark-dns,
   accel-ppp,
   auditd,
   avahi-daemon,
   beep,
   bmon,
   bsdmainutils,
   certbot,
   charon-systemd,
   conntrack,
   conntrackd,
   conserver-client,
   conserver-server,
   console-data,
   cron,
   curl,
   dbus,
   ddclient (>= 3.11.1),
   dmidecode,
   dropbear,
   easy-rsa,
   etherwake,
   ethtool,
   fdisk,
   fastnetmon [amd64],
   file,
   frr (>= 7.5),
   frr-pythontools,
   frr-rpki-rtrlib,
   frr-snmp,
   fuse-overlayfs,
   libpam-google-authenticator,
   git,
   grc,
   haproxy,
   hostapd,
   hsflowd,
   hvinfo,
   igmpproxy,
   ipaddrcheck,
   iperf,
   iperf3,
   iproute2 (>= 6.0.0),
   iptables,
   iputils-arping,
   isc-dhcp-client,
   isc-dhcp-relay,
   isc-dhcp-server,
   iw,
   jool,
   keepalived (>=2.0.5),
   lcdproc,
   lcdproc-extra-drivers,
   libatomic1,
   libauparse0,
   libcharon-extra-plugins (>=5.9),
   libcharon-extauth-plugins (>=5.9),
   libndp-tools,
   libnetfilter-conntrack3,
   libnfnetlink0,
   libqmi-utils,
   libstrongswan-extra-plugins (>=5.9),
   libstrongswan-standard-plugins (>=5.9),
   libvyosconfig0,
   linux-cpupower,
   lldpd,
   lm-sensors,
   lsscsi,
   minisign,
   modemmanager,
   mtr-tiny,
   ndisc6,
   ndppd,
   netavark,
   netplug,
   nfct,
   nftables (>= 0.9.3),
   nginx-light,
   chrony,
   nvme-cli,
   ocserv,
   opennhrp,
   openssh-server,
   openssl,
   openvpn,
   openvpn-auth-ldap,
   openvpn-auth-radius,
   openvpn-otp,
   owamp-client,
   owamp-server,
   pciutils,
   pdns-recursor,
   pmacct (>= 1.6.0),
   podman,
   pppoe,
   procps,
   python3,
   python3-cryptography,
   python3-hurry.filesize,
   python3-inotify,
   python3-isc-dhcp-leases,
   python3-jinja2,
   python3-jmespath,
   python3-netaddr,
   python3-netifaces,
   python3-paramiko,
   python3-passlib,
   python3-psutil,
   python3-pyhumps,
   python3-pystache,
   python3-pyudev,
   python3-six,
   python3-tabulate,
   python3-vici (>= 5.7.2),
   python3-voluptuous,
   python3-xmltodict,
   python3-zmq,
   qrencode,
   radvd,
   salt-minion,
   sed,
   smartmontools,
   snmp,
   snmpd,
   squashfs-tools,
   squid,
   squidclient,
   squidguard,
   sshguard,
   ssl-cert,
   sstp-client,
   strongswan (>= 5.9),
   strongswan-swanctl (>= 5.9),
   stunnel4,
   sudo,
   systemd,
   telegraf (>= 1.20),
   tcpdump,
   tcptraceroute,
   telnet,
   tftpd-hpa,
   traceroute,
   tuned,
   twamp-client,
   twamp-server,
   udp-broadcast-relay,
   uidmap,
   usb-modeswitch,
   usbutils,
   vyatta-bash,
   vyatta-cfg,
   vyos-http-api-tools,
   vyos-utils,
   wide-dhcpv6-client,
   wireguard-tools,
   wireless-regdb,
   wpasupplicant (>= 0.6.7),
   zabbix-agent2,
   ndppd,
   miniupnpd-nftables
 Description: VyOS configuration scripts and data
  VyOS configuration scripts, interface definitions, and everything
 
 Package: vyos-1x-vmware
 Architecture: amd64
 Depends:
  vyos-1x,
  open-vm-tools
 Description: VyOS configuration scripts and data for VMware
  Adds configuration files required for VyOS running on VMware hosts.
 
 Package: vyos-1x-smoketest
 Architecture: all
 Depends:
  skopeo,
  snmp,
  vyos-1x
 Description: VyOS build sanity checking toolkit
diff --git a/smoketest/scripts/cli/test_netns.py b/smoketest/scripts/cli/test_netns.py
index 55ad7c83b..d11a5d8f1 100755
--- a/smoketest/scripts/cli/test_netns.py
+++ b/smoketest/scripts/cli/test_netns.py
@@ -1,75 +1,74 @@
 #!/usr/bin/env python3
 #
 # 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/>.
 
 import unittest
 
-from netifaces import interfaces
 from base_vyostest_shim import VyOSUnitTestSHIM
 
 from vyos.utils.process import cmd
 
 base_path = ['netns']
 namespaces = ['mgmt', 'front', 'back', 'ams-ix']
 
 class NETNSTest(VyOSUnitTestSHIM.TestCase):
     def setUp(self):
         self._interfaces = ['dum10', 'dum12', 'dum50']
 
     def test_create_netns(self):
         for netns in namespaces:
             base = base_path + ['name', netns]
             self.cli_set(base)
 
         # commit changes
         self.cli_commit()
 
         netns_list = cmd('ip netns ls')
 
         # Verify NETNS configuration
         for netns in namespaces:
             self.assertTrue(netns in netns_list)
 
 
     def test_netns_assign_interface(self):
         netns = 'foo'
         self.cli_set(['netns', 'name', netns])
 
         # Set
         for iface in self._interfaces:
             self.cli_set(['interfaces', 'dummy', iface, 'netns', netns])
 
         # commit changes
         self.cli_commit()
 
         netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show')
 
         for iface in self._interfaces:
             self.assertTrue(iface in netns_iface_list)
 
         # Delete
         for iface in self._interfaces:
             self.cli_delete(['interfaces', 'dummy', iface, 'netns', netns])
 
         # commit changes
         self.cli_commit()
 
         netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show')
 
         for iface in self._interfaces:
             self.assertNotIn(iface, netns_iface_list)
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index 4d9dabc3f..cb6206632 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -1,182 +1,181 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-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/>.
 
 import unittest
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
-from vyos.configsession import ConfigSessionError
 from vyos.template import inc_ip
 from vyos.utils.process import process_named_running
 from vyos.utils.file import read_file
 
 PROCESS_NAME = 'dhcpd'
 DHCPD_CONF = '/run/dhcp-server/dhcpdv6.conf'
 base_path = ['service', 'dhcpv6-server']
 
 subnet = '2001:db8:f00::/64'
 dns_1 = '2001:db8::1'
 dns_2 = '2001:db8::2'
 domain = 'vyos.net'
 nis_servers = ['2001:db8:ffff::1', '2001:db8:ffff::2']
 interface = 'eth0'
 interface_addr = inc_ip(subnet, 1) + '/64'
 
 class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestServiceDHCPv6Server, cls).setUpClass()
         cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', interface_addr])
 
     @classmethod
     def tearDownClass(cls):
         cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', interface_addr])
         cls.cli_commit(cls)
 
         super(TestServiceDHCPv6Server, cls).tearDownClass()
 
     def tearDown(self):
         self.cli_delete(base_path)
         self.cli_commit()
 
     def test_single_pool(self):
         shared_net_name = 'SMOKE-1'
         search_domains  = ['foo.vyos.net', 'bar.vyos.net']
         lease_time = '1200'
         max_lease_time = '72000'
         min_lease_time = '600'
         preference = '10'
         sip_server = 'sip.vyos.net'
         sntp_server = inc_ip(subnet, 100)
         range_start = inc_ip(subnet, 256)  # ::100
         range_stop = inc_ip(subnet, 65535) # ::ffff
 
         pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
 
         self.cli_set(base_path + ['preference', preference])
 
         # we use the first subnet IP address as default gateway
         self.cli_set(pool + ['name-server', dns_1])
         self.cli_set(pool + ['name-server', dns_2])
         self.cli_set(pool + ['name-server', dns_2])
         self.cli_set(pool + ['lease-time', 'default', lease_time])
         self.cli_set(pool + ['lease-time', 'maximum', max_lease_time])
         self.cli_set(pool + ['lease-time', 'minimum', min_lease_time])
         self.cli_set(pool + ['nis-domain', domain])
         self.cli_set(pool + ['nisplus-domain', domain])
         self.cli_set(pool + ['sip-server', sip_server])
         self.cli_set(pool + ['sntp-server', sntp_server])
         self.cli_set(pool + ['address-range', 'start', range_start, 'stop', range_stop])
 
         for server in nis_servers:
             self.cli_set(pool + ['nis-server', server])
             self.cli_set(pool + ['nisplus-server', server])
 
         for search in search_domains:
             self.cli_set(pool + ['domain-search', search])
 
         client_base = 1
         for client in ['client1', 'client2', 'client3']:
             cid = '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{}'.format(client_base)
             self.cli_set(pool + ['static-mapping', client, 'identifier', cid])
             self.cli_set(pool + ['static-mapping', client, 'ipv6-address', inc_ip(subnet, client_base)])
             self.cli_set(pool + ['static-mapping', client, 'ipv6-prefix', inc_ip(subnet, client_base << 64) + '/64'])
             client_base += 1
 
         # commit changes
         self.cli_commit()
 
         config = read_file(DHCPD_CONF)
         self.assertIn(f'option dhcp6.preference {preference};', config)
 
         self.assertIn(f'subnet6 {subnet}' + r' {', config)
         search = '"' + '", "'.join(search_domains) + '"'
         nissrv = ', '.join(nis_servers)
         self.assertIn(f'range6 {range_start} {range_stop};', config)
         self.assertIn(f'default-lease-time {lease_time};', config)
         self.assertIn(f'default-lease-time {lease_time};', config)
         self.assertIn(f'max-lease-time {max_lease_time};', config)
         self.assertIn(f'min-lease-time {min_lease_time};', config)
         self.assertIn(f'option dhcp6.domain-search {search};', config)
         self.assertIn(f'option dhcp6.name-servers {dns_1}, {dns_2};', config)
         self.assertIn(f'option dhcp6.nis-domain-name "{domain}";', config)
         self.assertIn(f'option dhcp6.nis-servers {nissrv};', config)
         self.assertIn(f'option dhcp6.nisp-domain-name "{domain}";', config)
         self.assertIn(f'option dhcp6.nisp-servers {nissrv};', config)
         self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
 
         client_base = 1
         for client in ['client1', 'client2', 'client3']:
             cid = '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{}'.format(client_base)
             ip = inc_ip(subnet, client_base)
             prefix = inc_ip(subnet, client_base << 64) + '/64'
             self.assertIn(f'host {shared_net_name}_{client}' + ' {', config)
             self.assertIn(f'fixed-address6 {ip};', config)
             self.assertIn(f'fixed-prefix6 {prefix};', config)
             self.assertIn(f'host-identifier option dhcp6.client-id {cid};', config)
             client_base += 1
 
         # Check for running process
         self.assertTrue(process_named_running(PROCESS_NAME))
 
 
     def test_prefix_delegation(self):
         shared_net_name = 'SMOKE-2'
         range_start = inc_ip(subnet, 256)  # ::100
         range_stop = inc_ip(subnet, 65535) # ::ffff
         delegate_start = '2001:db8:ee::'
         delegate_stop = '2001:db8:ee:ff00::'
         delegate_len = '56'
 
         pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
 
         self.cli_set(pool + ['address-range', 'start', range_start, 'stop', range_stop])
         self.cli_set(pool + ['prefix-delegation', 'start', delegate_start, 'stop', delegate_stop])
         self.cli_set(pool + ['prefix-delegation', 'start', delegate_start, 'prefix-length', delegate_len])
 
         # commit changes
         self.cli_commit()
 
         config = read_file(DHCPD_CONF)
         self.assertIn(f'subnet6 {subnet}' + r' {', config)
         self.assertIn(f'range6 {range_start} {range_stop};', config)
         self.assertIn(f'prefix6 {delegate_start} {delegate_stop} /{delegate_len};', config)
 
         # Check for running process
         self.assertTrue(process_named_running(PROCESS_NAME))
 
     def test_global_nameserver(self):
         shared_net_name = 'SMOKE-3'
         ns_global_1 = '2001:db8::1111'
         ns_global_2 = '2001:db8::2222'
 
         self.cli_set(base_path + ['global-parameters', 'name-server', ns_global_1])
         self.cli_set(base_path + ['global-parameters', 'name-server', ns_global_2])
         self.cli_set(base_path + ['shared-network-name', shared_net_name, 'subnet', subnet])
 
         # commit changes
         self.cli_commit()
 
         config = read_file(DHCPD_CONF)
         self.assertIn(f'option dhcp6.name-servers {ns_global_1}, {ns_global_2};', config)
         self.assertIn(f'subnet6 {subnet}' + r' {', config)
         self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
 
         # Check for running process
         self.assertTrue(process_named_running(PROCESS_NAME))
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index ee29f8198..6fffe7e0d 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -1,292 +1,290 @@
 #!/usr/bin/env python3
 #
 # 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/>.
 
-import os
-
 from sys import exit
 from sys import argv
 
 from vyos.config import Config
 from vyos.config import config_dict_merge
 from vyos.configdict import dict_merge
 from vyos.configdict import node_changed
 from vyos.configverify import verify_common_route_maps
 from vyos.configverify import verify_route_map
 from vyos.configverify import verify_interface_exists
 from vyos.configverify import verify_access_list
 from vyos.template import render_to_string
 from vyos.utils.dict import dict_search
 from vyos.utils.network import get_interface_config
 from vyos import ConfigError
 from vyos import frr
 from vyos import airbag
 airbag.enable()
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
     vrf = None
     if len(argv) > 1:
         vrf = argv[1]
 
     base_path = ['protocols', 'ospf']
 
     # eqivalent of the C foo ? 'a' : 'b' statement
     base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospf'] or base_path
     ospf = conf.get_config_dict(base, key_mangling=('-', '_'),
                                 get_first_key=True)
 
     # Assign the name of our VRF context. This MUST be done before the return
     # statement below, else on deletion we will delete the default instance
     # instead of the VRF instance.
     if vrf: ospf['vrf'] = vrf
 
     # FRR has VRF support for different routing daemons. As interfaces belong
     # to VRFs - or the global VRF, we need to check for changed interfaces so
     # that they will be properly rendered for the FRR config. Also this eases
     # removal of interfaces from the running configuration.
     interfaces_removed = node_changed(conf, base + ['interface'])
     if interfaces_removed:
         ospf['interface_removed'] = list(interfaces_removed)
 
     # Bail out early if configuration tree does no longer exist. this must
     # be done after retrieving the list of interfaces to be removed.
     if not conf.exists(base):
         ospf.update({'deleted' : ''})
         return ospf
 
     # 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(**ospf.kwargs, 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)
 
     # We also need some additional information from the config, prefix-lists
     # and route-maps for instance. They will be used in verify().
     #
     # XXX: one MUST always call this without the key_mangling() option! See
     # vyos.configverify.verify_common_route_maps() for more information.
     tmp = conf.get_config_dict(['policy'])
     # Merge policy dict into "regular" config dict
     ospf = dict_merge(tmp, ospf)
 
     return ospf
 
 def verify(ospf):
     if not ospf:
         return None
 
     verify_common_route_maps(ospf)
 
     # As we can have a default-information route-map, we need to validate it!
     route_map_name = dict_search('default_information.originate.route_map', ospf)
     if route_map_name: verify_route_map(route_map_name, ospf)
 
     # Validate if configured Access-list exists
     if 'area' in ospf:
           networks = []
           for area, area_config in ospf['area'].items():
               if 'import_list' in area_config:
                   acl_import = area_config['import_list']
                   if acl_import: verify_access_list(acl_import, ospf)
               if 'export_list' in area_config:
                   acl_export = area_config['export_list']
                   if acl_export: verify_access_list(acl_export, ospf)
 
               if 'network' in area_config:
                   for network in area_config['network']:
                       if network in networks:
                           raise ConfigError(f'Network "{network}" already defined in different area!')
                       networks.append(network)
 
     if 'interface' in ospf:
         for interface, interface_config in ospf['interface'].items():
             verify_interface_exists(interface)
             # One can not use dead-interval and hello-multiplier at the same
             # time. FRR will only activate the last option set via CLI.
             if {'hello_multiplier', 'dead_interval'} <= set(interface_config):
                 raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \
                                   f'concurrently for {interface}!')
 
             # One can not use the "network <prefix> area <id>" command and an
             # per interface area assignment at the same time. FRR will error
             # out using: "Please remove all network commands first."
             if 'area' in ospf and 'area' in interface_config:
                 for area, area_config in ospf['area'].items():
                     if 'network' in area_config:
                         raise ConfigError('Can not use OSPF interface area and area ' \
                                           'network configuration at the same time!')
 
             # If interface specific options are set, we must ensure that the
             # interface is bound to our requesting VRF. Due to the VyOS
             # priorities the interface is bound to the VRF after creation of
             # the VRF itself, and before any routing protocol is configured.
             if 'vrf' in ospf:
                 vrf = ospf['vrf']
                 tmp = get_interface_config(interface)
                 if 'master' not in tmp or tmp['master'] != vrf:
                     raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!')
 
     # Segment routing checks
     if dict_search('segment_routing.global_block', ospf):
         g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf)
         g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf)
 
         # If segment routing global block high or low value is blank, throw error
         if not (g_low_label_value or g_high_label_value):
             raise ConfigError('Segment routing global-block requires both low and high value!')
 
         # If segment routing global block low value is higher than the high value, throw error
         if int(g_low_label_value) > int(g_high_label_value):
             raise ConfigError('Segment routing global-block low value must be lower than high value')
 
     if dict_search('segment_routing.local_block', ospf):
         if dict_search('segment_routing.global_block', ospf) == None:
             raise ConfigError('Segment routing local-block requires global-block to be configured!')
 
         l_high_label_value = dict_search('segment_routing.local_block.high_label_value', ospf)
         l_low_label_value = dict_search('segment_routing.local_block.low_label_value', ospf)
 
         # If segment routing local-block high or low value is blank, throw error
         if not (l_low_label_value or l_high_label_value):
             raise ConfigError('Segment routing local-block requires both high and low value!')
 
         # If segment routing local-block low value is higher than the high value, throw error
         if int(l_low_label_value) > int(l_high_label_value):
             raise ConfigError('Segment routing local-block low value must be lower than high value')
 
         # local-block most live outside global block
         global_range = range(int(g_low_label_value), int(g_high_label_value) +1)
         local_range  = range(int(l_low_label_value), int(l_high_label_value) +1)
 
         # Check for overlapping ranges
         if list(set(global_range) & set(local_range)):
             raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\
                               f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!')
 
     # Check for a blank or invalid value per prefix
     if dict_search('segment_routing.prefix', ospf):
         for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
             if 'index' in prefix_config:
                 if prefix_config['index'].get('value') is None:
                     raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.')
 
     # Check for explicit-null and no-php-flag configured at the same time per prefix
     if dict_search('segment_routing.prefix', ospf):
         for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
             if 'index' in prefix_config:
                 if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
                     raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
                                       f'and no-php-flag configured at the same time.')
 
     # Check for index ranges being larger than the segment routing global block
     if dict_search('segment_routing.global_block', ospf):
         g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf)
         g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf)
         g_label_difference = int(g_high_label_value) - int(g_low_label_value)
         if dict_search('segment_routing.prefix', ospf):
             for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
                 if 'index' in prefix_config:
                     index_size = ospf['segment_routing']['prefix'][prefix]['index']['value']
                     if int(index_size) > int(g_label_difference):
                         raise ConfigError(f'Segment routing prefix {prefix} cannot have an '\
                                           f'index base size larger than the SRGB label base.')
 
     # Check route summarisation
     if 'summary_address' in ospf:
         for prefix, prefix_options in ospf['summary_address'].items():
             if {'tag', 'no_advertise'} <= set(prefix_options):
                 raise ConfigError(f'Can not set both "tag" and "no-advertise" for Type-5 '\
                                   f'and Type-7 route summarisation of "{prefix}"!')
 
     return None
 
 def generate(ospf):
     if not ospf or 'deleted' in ospf:
         return None
 
     ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf)
     return None
 
 def apply(ospf):
     ospf_daemon = 'ospfd'
 
     # Save original configuration prior to starting any commit actions
     frr_cfg = frr.FRRConfig()
 
     # Generate empty helper string which can be ammended to FRR commands, it
     # will be either empty (default VRF) or contain the "vrf <name" statement
     vrf = ''
     if 'vrf' in ospf:
         vrf = ' vrf ' + ospf['vrf']
 
     frr_cfg.load_configuration(ospf_daemon)
     frr_cfg.modify_section(f'^router ospf{vrf}', stop_pattern='^exit', remove_stop_mark=True)
 
     for key in ['interface', 'interface_removed']:
         if key not in ospf:
             continue
         for interface in ospf[key]:
             frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
 
     if 'frr_ospfd_config' in ospf:
         frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config'])
 
     frr_cfg.commit_configuration(ospf_daemon)
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py
index 3cfd74a19..544b759ac 100755
--- a/src/conf_mode/service_dhcp-server.py
+++ b/src/conf_mode/service_dhcp-server.py
@@ -1,353 +1,352 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2018-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/>.
 
 import os
 
 from ipaddress import ip_address
 from ipaddress import ip_network
-from netaddr import IPAddress
 from netaddr import IPRange
 from sys import exit
 
 from vyos.base import DeprecationWarning
 from vyos.config import Config
 from vyos.template import render
 from vyos.utils.dict import dict_search
 from vyos.utils.process import call
 from vyos.utils.process import run
 from vyos.utils.network import is_subnet_connected
 from vyos.utils.network import is_addr_assigned
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 config_file = '/run/dhcp-server/dhcpd.conf'
 systemd_override = r'/run/systemd/system/isc-dhcp-server.service.d/10-override.conf'
 
 def dhcp_slice_range(exclude_list, range_dict):
     """
     This function is intended to slice a DHCP range. What does it mean?
 
     Lets assume we have a DHCP range from '192.0.2.1' to '192.0.2.100'
     but want to exclude address '192.0.2.74' and '192.0.2.75'. We will
     pass an input 'range_dict' in the format:
       {'start' : '192.0.2.1', 'stop' : '192.0.2.100' }
     and we will receive an output list of:
       [{'start' : '192.0.2.1' , 'stop' : '192.0.2.73'  },
        {'start' : '192.0.2.76', 'stop' : '192.0.2.100' }]
     The resulting list can then be used in turn to build the proper dhcpd
     configuration file.
     """
     output = []
     # exclude list must be sorted for this to work
     exclude_list = sorted(exclude_list)
     range_start = range_dict['start']
     range_stop = range_dict['stop']
     range_last_exclude = ''
 
     for e in exclude_list:
         if (ip_address(e) >= ip_address(range_start)) and \
            (ip_address(e) <= ip_address(range_stop)):
             range_last_exclude = e
 
     for e in exclude_list:
         if (ip_address(e) >= ip_address(range_start)) and \
            (ip_address(e) <= ip_address(range_stop)):
 
             # Build new address range ending one address before exclude address
             r = {
                 'start' : range_start,
                 'stop' : str(ip_address(e) -1)
             }
             # On the next run our address range will start one address after
             # the exclude address
             range_start = str(ip_address(e) + 1)
 
             # on subsequent exclude addresses we can not
             # append them to our output
             if not (ip_address(r['start']) > ip_address(r['stop'])):
                 # Everything is fine, add range to result
                 output.append(r)
 
             # Take care of last IP address range spanning from the last exclude
             # address (+1) to the end of the initial configured range
             if ip_address(e) == ip_address(range_last_exclude):
                 r = {
                   'start': str(ip_address(e) + 1),
                   'stop': str(range_stop)
                 }
                 if not (ip_address(r['start']) > ip_address(r['stop'])):
                     output.append(r)
         else:
           # if the excluded address was not part of the range, we simply return
           # the entire ranga again
           if not range_last_exclude:
               if range_dict not in output:
                   output.append(range_dict)
 
     return output
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['service', 'dhcp-server']
     if not conf.exists(base):
         return None
 
     dhcp = conf.get_config_dict(base, key_mangling=('-', '_'),
                                 no_tag_node_value_mangle=True,
                                 get_first_key=True,
                                 with_recursive_defaults=True)
 
     if 'shared_network_name' in dhcp:
         for network, network_config in dhcp['shared_network_name'].items():
             if 'subnet' in network_config:
                 for subnet, subnet_config in network_config['subnet'].items():
                     # If exclude IP addresses are defined we need to slice them out of
                     # the defined ranges
                     if {'exclude', 'range'} <= set(subnet_config):
                         new_range_id = 0
                         new_range_dict = {}
                         for r, r_config in subnet_config['range'].items():
                             for slice in dhcp_slice_range(subnet_config['exclude'], r_config):
                                 new_range_dict.update({new_range_id : slice})
                                 new_range_id +=1
 
                         dhcp['shared_network_name'][network]['subnet'][subnet].update(
                                 {'range' : new_range_dict})
 
     if len(dhcp['high_availability']) == 1:
         ## only default value for mode is set, need to remove ha node
         del dhcp['high_availability']
 
     return dhcp
 
 def verify(dhcp):
     # bail out early - looks like removal from running config
     if not dhcp or 'disable' in dhcp:
         return None
 
     # If DHCP is enabled we need one share-network
     if 'shared_network_name' not in dhcp:
         raise ConfigError('No DHCP shared networks configured.\n' \
                           'At least one DHCP shared network must be configured.')
 
     # Inspect shared-network/subnet
     listen_ok = False
     subnets = []
     failover_ok = False
     shared_networks =  len(dhcp['shared_network_name'])
     disabled_shared_networks = 0
 
     common_deprecation_msg = 'are subject of removal in VyOS 1.5! Please raise a feature request for proper CLI nodes!'
     if 'global_parameters' in dhcp:
         DeprecationWarning(f'Additional global parameters {common_deprecation_msg}')
 
     # A shared-network requires a subnet definition
     for network, network_config in dhcp['shared_network_name'].items():
         if 'shared_network_parameters' in network_config:
             DeprecationWarning(f'Additional shared network parameters in "{network}" {common_deprecation_msg}')
 
         if 'disable' in network_config:
             disabled_shared_networks += 1
 
         if 'subnet' not in network_config:
             raise ConfigError(f'No subnets defined for {network}. At least one\n' \
                               'lease subnet must be configured.')
 
         for subnet, subnet_config in network_config['subnet'].items():
             if 'subnet_parameters' in subnet_config:
                 DeprecationWarning(f'Additional subnet parameters in "{subnet}" {common_deprecation_msg}')
 
             # All delivered static routes require a next-hop to be set
             if 'static_route' in subnet_config:
                 for route, route_option in subnet_config['static_route'].items():
                     if 'next_hop' not in route_option:
                         raise ConfigError(f'DHCP static-route "{route}" requires router to be defined!')
 
             # DHCP failover needs at least one subnet that uses it
             if 'enable_failover' in subnet_config:
                 if 'high_availability' not in dhcp:
                     raise ConfigError(f'Can not enable high availability for "{subnet}" in "{network}".\n' \
                                       'High availability is not configured globally!')
                 failover_ok = True
 
             # Check if DHCP address range is inside configured subnet declaration
             if 'range' in subnet_config:
                 networks = []
                 for range, range_config in subnet_config['range'].items():
                     if not {'start', 'stop'} <= set(range_config):
                         raise ConfigError(f'DHCP range "{range}" start and stop address must be defined!')
 
                     # Start/Stop address must be inside network
                     for key in ['start', 'stop']:
                         if ip_address(range_config[key]) not in ip_network(subnet):
                             raise ConfigError(f'DHCP range "{range}" {key} address not within shared-network "{network}, {subnet}"!')
 
                     # Stop address must be greater or equal to start address
                     if ip_address(range_config['stop']) < ip_address(range_config['start']):
                         raise ConfigError(f'DHCP range "{range}" stop address must be greater or equal\n' \
                                           'to the ranges start address!')
 
                     for network in networks:
                         start = range_config['start']
                         stop = range_config['stop']
                         if start in network:
                             raise ConfigError(f'Range "{range}" start address "{start}" already part of another range!')
                         if stop in network:
                             raise ConfigError(f'Range "{range}" stop address "{stop}" already part of another range!')
 
                     tmp = IPRange(range_config['start'], range_config['stop'])
                     networks.append(tmp)
 
             # Exclude addresses must be in bound
             if 'exclude' in subnet_config:
                 for exclude in subnet_config['exclude']:
                     if ip_address(exclude) not in ip_network(subnet):
                         raise ConfigError(f'Excluded IP address "{exclude}" not within shared-network "{network}, {subnet}"!')
 
             # At least one DHCP address range or static-mapping required
             if 'range' not in subnet_config and 'static_mapping' not in subnet_config:
                 raise ConfigError(f'No DHCP address range or active static-mapping configured\n' \
                                   f'within shared-network "{network}, {subnet}"!')
 
             if 'static_mapping' in subnet_config:
                 # Static mappings require just a MAC address (will use an IP from the dynamic pool if IP is not set)
                 used_ips = []
                 used_mac = []
                 for mapping, mapping_config in subnet_config['static_mapping'].items():
                     if 'ip_address' in mapping_config:
                         if ip_address(mapping_config['ip_address']) not in ip_network(subnet):
                             raise ConfigError(f'Configured static lease address for mapping "{mapping}" is\n' \
                                               f'not within shared-network "{network}, {subnet}"!')
 
                         if 'mac_address' not in mapping_config:
                             raise ConfigError(f'MAC address required for static mapping "{mapping}"\n' \
                                               f'within shared-network "{network}, {subnet}"!')
 
                         if 'disable' not in mapping_config:
                             if mapping_config['ip_address'] in used_ips:
                                 raise ConfigError(f'Configured IP address for static mapping "{mapping}" already exists on another static mapping')
                             used_ips.append(mapping_config['ip_address'])
 
                     if 'mac_address' in mapping_config and 'disable' not in mapping_config:
                         if mapping_config['mac_address'] in used_mac:
                             raise ConfigError(f'Configured MAC address for static mapping "{mapping}" already exists on another static mapping')
                         used_mac.append(mapping_config['mac_address'])
 
             # There must be one subnet connected to a listen interface.
             # This only counts if the network itself is not disabled!
             if 'disable' not in network_config:
                 if is_subnet_connected(subnet, primary=False):
                     listen_ok = True
 
             # Subnets must be non overlapping
             if subnet in subnets:
                 raise ConfigError(f'Configured subnets must be unique! Subnet "{subnet}"\n'
                                    'defined multiple times!')
             subnets.append(subnet)
 
             # Check for overlapping subnets
             net = ip_network(subnet)
             for n in subnets:
                 net2 = ip_network(n)
                 if (net != net2):
                     if net.overlaps(net2):
                         raise ConfigError(f'Conflicting subnet ranges: "{net}" overlaps "{net2}"!')
 
     # Prevent 'disable' for shared-network if only one network is configured
     if (shared_networks - disabled_shared_networks) < 1:
         raise ConfigError(f'At least one shared network must be active!')
 
     if 'high_availability' in dhcp:
         if not failover_ok:
             raise ConfigError('DHCP failover must be enabled for at least one subnet!')
 
         for key in ['name', 'remote', 'source_address', 'status']:
             if key not in dhcp['high_availability']:
                 tmp = key.replace('_', '-')
                 raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!')
 
     for address in (dict_search('listen_address', dhcp) or []):
         if is_addr_assigned(address, include_vrf=True):
             listen_ok = True
             # no need to probe further networks, we have one that is valid
             continue
         else:
             raise ConfigError(f'listen-address "{address}" not configured on any interface')
 
     if not listen_ok:
         raise ConfigError('None of the configured subnets have an appropriate primary IP address on any\n'
                           'broadcast interface configured, nor was there an explicit listen-address\n'
                           'configured for serving DHCP relay packets!')
 
     return None
 
 def generate(dhcp):
     # bail out early - looks like removal from running config
     if not dhcp or 'disable' in dhcp:
         return None
 
     # Please see: https://vyos.dev/T1129 for quoting of the raw
     # parameters we can pass to ISC DHCPd
     tmp_file = '/tmp/dhcpd.conf'
     render(tmp_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
            formater=lambda _: _.replace("&quot;", '"'))
     # XXX: as we have the ability for a user to pass in "raw" options via VyOS
     # CLI (see T3544) we now ask ISC dhcpd to test the newly rendered
     # configuration
     tmp = run(f'/usr/sbin/dhcpd -4 -q -t -cf {tmp_file}')
     if tmp > 0:
         if os.path.exists(tmp_file):
             os.unlink(tmp_file)
         raise ConfigError('Configuration file errors encountered - check your options!')
 
     # Now that we know that the newly rendered configuration is "good" we can
     # render the "real" configuration
     render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
            formater=lambda _: _.replace("&quot;", '"'))
     render(systemd_override, 'dhcp-server/10-override.conf.j2', dhcp)
 
     # Clean up configuration test file
     if os.path.exists(tmp_file):
         os.unlink(tmp_file)
 
     return None
 
 def apply(dhcp):
     call('systemctl daemon-reload')
     # bail out early - looks like removal from running config
     if not dhcp or 'disable' in dhcp:
         call('systemctl stop isc-dhcp-server.service')
         if os.path.exists(config_file):
             os.unlink(config_file)
 
         return None
 
     call('systemctl restart isc-dhcp-server.service')
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py
index 427001609..5489a744e 100755
--- a/src/conf_mode/service_dhcpv6-server.py
+++ b/src/conf_mode/service_dhcpv6-server.py
@@ -1,195 +1,194 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-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/>.
 
 import os
 
 from ipaddress import ip_address
 from ipaddress import ip_network
 from sys import exit
 
 from vyos.config import Config
 from vyos.template import render
-from vyos.template import is_ipv6
 from vyos.utils.process import call
 from vyos.utils.dict import dict_search
 from vyos.utils.network import is_subnet_connected
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 config_file = '/run/dhcp-server/dhcpdv6.conf'
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['service', 'dhcpv6-server']
     if not conf.exists(base):
         return None
 
     dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'),
                                   get_first_key=True,
                                   no_tag_node_value_mangle=True)
     return dhcpv6
 
 def verify(dhcpv6):
     # bail out early - looks like removal from running config
     if not dhcpv6 or 'disable' in dhcpv6:
         return None
 
     # If DHCP is enabled we need one share-network
     if 'shared_network_name' not in dhcpv6:
         raise ConfigError('No DHCPv6 shared networks configured. At least '\
                           'one DHCPv6 shared network must be configured.')
 
     # Inspect shared-network/subnet
     subnets = []
     listen_ok = False
     for network, network_config in dhcpv6['shared_network_name'].items():
         # A shared-network requires a subnet definition
         if 'subnet' not in network_config:
             raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". '\
                               'At least one lease subnet must be configured for '\
                               'each shared network!')
 
         for subnet, subnet_config in network_config['subnet'].items():
             if 'address_range' in subnet_config:
                 if 'start' in subnet_config['address_range']:
                     range6_start = []
                     range6_stop = []
                     for start, start_config in subnet_config['address_range']['start'].items():
                         if 'stop' not in start_config:
                             raise ConfigError(f'address-range stop address for start "{start}" is not defined!')
                         stop = start_config['stop']
 
                         # Start address must be inside network
                         if not ip_address(start) in ip_network(subnet):
                             raise ConfigError(f'address-range start address "{start}" is not in subnet "{subnet}"!')
 
                         # Stop address must be inside network
                         if not ip_address(stop) in ip_network(subnet):
                              raise ConfigError(f'address-range stop address "{stop}" is not in subnet "{subnet}"!')
 
                         # Stop address must be greater or equal to start address
                         if not ip_address(stop) >= ip_address(start):
                             raise ConfigError(f'address-range stop address "{stop}" must be greater then or equal ' \
                                               f'to the range start address "{start}"!')
 
                         # DHCPv6 range start address must be unique - two ranges can't
                         # start with the same address - makes no sense
                         if start in range6_start:
                             raise ConfigError(f'Conflicting DHCPv6 lease range: '\
                                               f'Pool start address "{start}" defined multipe times!')
                         range6_start.append(start)
 
                         # DHCPv6 range stop address must be unique - two ranges can't
                         # end with the same address - makes no sense
                         if stop in range6_stop:
                             raise ConfigError(f'Conflicting DHCPv6 lease range: '\
                                               f'Pool stop address "{stop}" defined multipe times!')
                         range6_stop.append(stop)
 
                 if 'prefix' in subnet_config:
                     for prefix in subnet_config['prefix']:
                         if ip_network(prefix) not in ip_network(subnet):
                             raise ConfigError(f'address-range prefix "{prefix}" is not in subnet "{subnet}""')
 
             # Prefix delegation sanity checks
             if 'prefix_delegation' in subnet_config:
                 if 'start' not in subnet_config['prefix_delegation']:
                     raise ConfigError('prefix-delegation start address not defined!')
 
                 for prefix, prefix_config in subnet_config['prefix_delegation']['start'].items():
                     if 'stop' not in prefix_config:
                         raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}" '\
                                           f'must be configured')
 
                     if 'prefix_length' not in prefix_config:
                         raise ConfigError('Length of delegated IPv6 prefix must be configured')
 
             # Static mappings don't require anything (but check if IP is in subnet if it's set)
             if 'static_mapping' in subnet_config:
                 for mapping, mapping_config in subnet_config['static_mapping'].items():
                     if 'ipv6_address' in mapping_config:
                         # Static address must be in subnet
                         if ip_address(mapping_config['ipv6_address']) not in ip_network(subnet):
                             raise ConfigError(f'static-mapping address for mapping "{mapping}" is not in subnet "{subnet}"!')
 
             if 'vendor_option' in subnet_config:
                 if len(dict_search('vendor_option.cisco.tftp_server', subnet_config)) > 2:
                     raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!')
 
             # Subnets must be unique
             if subnet in subnets:
                 raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!')
             subnets.append(subnet)
 
         # DHCPv6 requires at least one configured address range or one static mapping
         # (FIXME: is not actually checked right now?)
 
         # There must be one subnet connected to a listen interface if network is not disabled.
         if 'disable' not in network_config:
             if is_subnet_connected(subnet):
                 listen_ok = True
 
             # DHCPv6 subnet must not overlap. ISC DHCP also complains about overlapping
             # subnets: "Warning: subnet 2001:db8::/32 overlaps subnet 2001:db8:1::/32"
             net = ip_network(subnet)
             for n in subnets:
                 net2 = ip_network(n)
                 if (net != net2):
                     if net.overlaps(net2):
                         raise ConfigError('DHCPv6 conflicting subnet ranges: {0} overlaps {1}'.format(net, net2))
 
     if not listen_ok:
         raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on '\
                           'this machine. At least one subnet6 must be connected such that '\
                           'DHCPv6 listens on an interface!')
 
 
     return None
 
 def generate(dhcpv6):
     # bail out early - looks like removal from running config
     if not dhcpv6 or 'disable' in dhcpv6:
         return None
 
     render(config_file, 'dhcp-server/dhcpdv6.conf.j2', dhcpv6)
     return None
 
 def apply(dhcpv6):
     # bail out early - looks like removal from running config
     service_name = 'isc-dhcp-server6.service'
     if not dhcpv6 or 'disable' in dhcpv6:
         # DHCP server is removed in the commit
         call(f'systemctl stop {service_name}')
         if os.path.exists(config_file):
             os.unlink(config_file)
         return None
 
     call(f'systemctl restart {service_name}')
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py
index 77f7cd810..7eec3f4f3 100755
--- a/src/helpers/vyos_config_sync.py
+++ b/src/helpers/vyos_config_sync.py
@@ -1,202 +1,202 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2023-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/>.
 #
 #
 
 import os
 import json
 import requests
 import urllib3
 import logging
-from typing import Optional, List, Union, Dict, Any
+from typing import Optional, List, Dict, Any
 
 from vyos.config import Config
 from vyos.template import bracketize_ipv6
 
 
 CONFIG_FILE = '/run/config_sync_conf.conf'
 
 # Logging
 logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger(__name__)
 logger.name = os.path.basename(__file__)
 
 # API
 API_HEADERS = {'Content-Type': 'application/json'}
 
 
 def post_request(url: str,
                  data: str,
                  headers: Dict[str, str]) -> requests.Response:
     """Sends a POST request to the specified URL
 
     Args:
         url (str): The URL to send the POST request to.
         data (Dict[str, Any]): The data to send with the POST request.
         headers (Dict[str, str]): The headers to include with the POST request.
 
     Returns:
         requests.Response: The response object representing the server's response to the request
     """
 
     response = requests.post(url,
                              data=data,
                              headers=headers,
                              verify=False,
                              timeout=timeout)
     return response
 
 
 
 def retrieve_config(section: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
     """Retrieves the configuration from the local server.
 
     Args:
         section: List[str]: The section of the configuration to retrieve.
         Default is None.
 
     Returns:
         Optional[Dict[str, Any]]: The retrieved configuration as a
         dictionary, or None if an error occurred.
     """
     if section is None:
         section = []
 
     conf = Config()
     config = conf.get_config_dict(section, get_first_key=True)
     if config:
         return config
     return None
 
 
 def set_remote_config(
         address: str,
         key: str,
         commands: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
     """Loads the VyOS configuration in JSON format to a remote host.
 
     Args:
         address (str): The address of the remote host.
         key (str): The key to use for loading the configuration.
         commands (list): List of set/load commands for request, given as:
                          [{'op': str, 'path': list[str], 'section': dict},
                          ...]
 
     Returns:
         Optional[Dict[str, Any]]: The response from the remote host as a
         dictionary, or None if a RequestException occurred.
     """
 
     headers = {'Content-Type': 'application/json'}
 
     # Disable the InsecureRequestWarning
     urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
     url = f'https://{address}/configure-section'
     data = json.dumps({
         'commands': commands,
         'key': key
     })
 
     try:
         config = post_request(url, data, headers)
         return config.json()
     except requests.exceptions.RequestException as e:
         print(f"An error occurred: {e}")
         logger.error(f"An error occurred: {e}")
         return None
 
 
 def is_section_revised(section: List[str]) -> bool:
     from vyos.config_mgmt import is_node_revised
     return is_node_revised(section)
 
 
 def config_sync(secondary_address: str,
                 secondary_key: str,
                 sections: List[list[str]],
                 mode: str):
     """Retrieve a config section from primary router in JSON format and send it to
        secondary router
     """
     if not any(map(is_section_revised, sections)):
         return
 
     logger.info(
         f"Config synchronization: Mode={mode}, Secondary={secondary_address}"
     )
 
     # Sync sections ("nat", "firewall", etc)
     commands = []
     for section in sections:
         config_json = retrieve_config(section=section)
         # Check if config path deesn't exist, for example "set nat"
         # we set empty value for config_json data
         # As we cannot send to the remote host section "nat None" config
         if not config_json:
             config_json = {}
         logger.debug(
             f"Retrieved config for section '{section}': {config_json}")
 
         d = {'op': mode, 'path': section, 'section': config_json}
         commands.append(d)
 
     set_config = set_remote_config(address=secondary_address,
                                    key=secondary_key,
                                    commands=commands)
 
     logger.debug(f"Set config for sections '{sections}': {set_config}")
 
 
 if __name__ == '__main__':
     # Read configuration from file
     if not os.path.exists(CONFIG_FILE):
         logger.error(f"Post-commit: No config file '{CONFIG_FILE}' exists")
         exit(0)
 
     with open(CONFIG_FILE, 'r') as f:
         config_data = f.read()
 
     config = json.loads(config_data)
 
     mode = config.get('mode')
     secondary_address = config.get('secondary', {}).get('address')
     secondary_address = bracketize_ipv6(secondary_address)
     secondary_key = config.get('secondary', {}).get('key')
     sections = config.get('section')
     timeout = int(config.get('secondary', {}).get('timeout'))
 
     if not all([
             mode, secondary_address, secondary_key, sections
     ]):
         logger.error(
             "Missing required configuration data for config synchronization.")
         exit(0)
 
     # Generate list_sections of sections/subsections
     # [
     #   ['interfaces', 'pseudo-ethernet'], ['interfaces', 'virtual-ethernet'], ['nat'], ['nat66']
     # ]
     list_sections = []
     for section, subsections in sections.items():
         if subsections:
             for subsection in subsections:
                 list_sections.append([section, subsection])
         else:
             list_sections.append([section])
 
     config_sync(secondary_address, secondary_key,
                 list_sections, mode)
diff --git a/src/migration-scripts/conntrack/2-to-3 b/src/migration-scripts/conntrack/2-to-3
index 6fb457b7f..6bb42be1e 100755
--- a/src/migration-scripts/conntrack/2-to-3
+++ b/src/migration-scripts/conntrack/2-to-3
@@ -1,37 +1,36 @@
 #!/usr/bin/env python3
 
 # Conntrack syntax version 3
 # Enables all conntrack modules (previous default behaviour) and omits manually disabled modules.
 
 import sys
 
 from vyos.configtree import ConfigTree
-from vyos.version import get_version
 
 if len(sys.argv) < 2:
     print('Must specify file name!')
     sys.exit(1)
 
 filename = sys.argv[1]
 
 with open(filename, 'r') as f:
     config = ConfigTree(f.read())
 
 module_path = ['system', 'conntrack', 'modules']
 
 # Go over all conntrack modules available as of v1.3.0.
 for module in ['ftp', 'h323', 'nfs', 'pptp', 'sip', 'sqlnet', 'tftp']:
     # 'disable' is being phased out.
     if config.exists(module_path + [module, 'disable']):
         config.delete(module_path + [module])
     # If it wasn't manually 'disable'd, it was enabled by default.
     else:
         config.set(module_path + [module])
 
 try:
     if config.exists(module_path):
         with open(filename, 'w') as f:
             f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     sys.exit(1)
diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11
index abb804a28..854d5a558 100755
--- a/src/migration-scripts/firewall/10-to-11
+++ b/src/migration-scripts/firewall/10-to-11
@@ -1,210 +1,207 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # T5160: Firewall re-writing
 
-#  cli changes from:       
+#  cli changes from:
 #  set firewall name <name> ...
 #  set firewall ipv6-name <name> ...
 #  To
-#  set firewall ipv4 name <name> 
-#  set firewall ipv6 name <name> 
+#  set firewall ipv4 name <name>
+#  set firewall ipv6 name <name>
 
 ## Also from 'firewall interface' removed.
 ## in and out:
     # set firewall interface <iface> [in|out] [name | ipv6-name] <name>
     # To
     # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> [inbound-interface | outboubd-interface] interface-name <iface>
     # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> action jump
     # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> jump-target <name>
 ## local:
     # set firewall interface <iface> local [name | ipv6-name] <name>
     # To
     # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> inbound-interface interface-name <iface>
     # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> action jump
     # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> jump-target <name>
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['firewall']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 ### Migration of state policies
 if config.exists(base + ['state-policy']):
     for state in config.list_nodes(base + ['state-policy']):
         action = config.return_value(base + ['state-policy', state, 'action'])
         config.set(base + ['global-options', 'state-policy', state, 'action'], value=action)
         if config.exists(base + ['state-policy', state, 'log']):
             config.set(base + ['global-options', 'state-policy', state, 'log'], value='enable')
     config.delete(base + ['state-policy'])
 
 ## migration of global options:
 for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv6-receive-redirects', 'ipv6-src-route', 'log-martians',
                 'receive-redirects', 'resolver-cache', 'resolver-internal', 'send-redirects', 'source-validation', 'syn-cookies', 'twa-hazards-protection']:
     if config.exists(base + [option]):
         if option != 'config-trap':
             val = config.return_value(base + [option])
             config.set(base + ['global-options', option], value=val)
         config.delete(base + [option])
 
 ### Migration of firewall name and ipv6-name
 ### Also migrate legacy 'accept' behaviour
 if config.exists(base + ['name']):
     config.set(['firewall', 'ipv4', 'name'])
     config.set_tag(['firewall', 'ipv4', 'name'])
 
     for ipv4name in config.list_nodes(base + ['name']):
         config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name])
 
         if config.exists(base + ['ipv4', 'name', ipv4name, 'default-action']):
             action = config.return_value(base + ['ipv4', 'name', ipv4name, 'default-action'])
 
             if action == 'accept':
                 config.set(base + ['ipv4', 'name', ipv4name, 'default-action'], value='return')
 
         if config.exists(base + ['ipv4', 'name', ipv4name, 'rule']):
             for rule_id in config.list_nodes(base + ['ipv4', 'name', ipv4name, 'rule']):
                 action = config.return_value(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action'])
 
                 if action == 'accept':
                     config.set(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action'], value='return')
 
     config.delete(base + ['name'])
 
 if config.exists(base + ['ipv6-name']):
     config.set(['firewall', 'ipv6', 'name'])
     config.set_tag(['firewall', 'ipv6', 'name'])
 
     for ipv6name in config.list_nodes(base + ['ipv6-name']):
         config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name])
 
         if config.exists(base + ['ipv6', 'name', ipv6name, 'default-action']):
             action = config.return_value(base + ['ipv6', 'name', ipv6name, 'default-action'])
 
             if action == 'accept':
                 config.set(base + ['ipv6', 'name', ipv6name, 'default-action'], value='return')
 
         if config.exists(base + ['ipv6', 'name', ipv6name, 'rule']):
             for rule_id in config.list_nodes(base + ['ipv6', 'name', ipv6name, 'rule']):
                 action = config.return_value(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action'])
 
                 if action == 'accept':
                     config.set(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action'], value='return')
 
     config.delete(base + ['ipv6-name'])
 
 ### Migration of firewall interface
 if config.exists(base + ['interface']):
     fwd_ipv4_rule = 5
     inp_ipv4_rule = 5
     fwd_ipv6_rule = 5
     inp_ipv6_rule = 5
     for direction in ['in', 'out', 'local']:
         for iface in config.list_nodes(base + ['interface']):
             if config.exists(base + ['interface', iface, direction]):
                 if config.exists(base + ['interface', iface, direction, 'name']):
                     target = config.return_value(base + ['interface', iface, direction, 'name'])
                     if direction == 'in':
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv4', 'forward', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [fwd_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump')
                         config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target)
                         fwd_ipv4_rule = fwd_ipv4_rule + 5
                     elif direction == 'out':
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv4', 'forward', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [fwd_ipv4_rule, 'outbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump')
                         config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target)
                         fwd_ipv4_rule = fwd_ipv4_rule + 5
                     else:
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv4', 'input', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [inp_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [inp_ipv4_rule, 'action'], value='jump')
                         config.set(new_base + [inp_ipv4_rule, 'jump-target'], value=target)
                         inp_ipv4_rule = inp_ipv4_rule + 5
 
                 if config.exists(base + ['interface', iface, direction, 'ipv6-name']):
                     target = config.return_value(base + ['interface', iface, direction, 'ipv6-name'])
                     if direction == 'in':
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv6', 'forward', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [fwd_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump')
                         config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target)
                         fwd_ipv6_rule = fwd_ipv6_rule + 5
                     elif direction == 'out':
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv6', 'forward', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [fwd_ipv6_rule, 'outbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump')
                         config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target)
                         fwd_ipv6_rule = fwd_ipv6_rule + 5
                     else:
                         new_base = base + ['ipv6', 'input', 'filter', 'rule']
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept')
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [inp_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [inp_ipv6_rule, 'action'], value='jump')
                         config.set(new_base + [inp_ipv6_rule, 'jump-target'], value=target)
                         inp_ipv6_rule = inp_ipv6_rule + 5
 
     config.delete(base + ['interface'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
-    exit(1)
\ No newline at end of file
+    exit(1)
diff --git a/src/migration-scripts/firewall/11-to-12 b/src/migration-scripts/firewall/11-to-12
index ba8374d66..f9122e74c 100755
--- a/src/migration-scripts/firewall/11-to-12
+++ b/src/migration-scripts/firewall/11-to-12
@@ -1,74 +1,71 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # T5681: Firewall re-writing. Simplify cli when mathcing interface
 # From
     # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-name <iface>
     # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-group <iface_group>
 # To
     # set firewall ... rule <rule> [inbound-interface | outboubd-interface] name <iface>
     # set firewall ... rule <rule> [inbound-interface | outboubd-interface] group <iface_group>
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['firewall']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 ## Migration from base chains
 #if config.exists(base + ['interface', iface, direction]):
 for family in ['ipv4', 'ipv6']:
     if config.exists(base + [family]):
         for hook in ['forward', 'input', 'output', 'name']:
             if config.exists(base + [family, hook]):
                 for priority in config.list_nodes(base + [family, hook]):
                     if config.exists(base + [family, hook, priority, 'rule']):
                         for rule in config.list_nodes(base + [family, hook, priority, 'rule']):
                             for direction in ['inbound-interface', 'outbound-interface']:
                                 if config.exists(base + [family, hook, priority, 'rule', rule, direction]):
                                     if config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']):
                                         iface = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-name'])
                                         config.set(base + [family, hook, priority, 'rule', rule, direction, 'name'], value=iface)
                                         config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-name'])
                                     elif config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']):
                                         group = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-group'])
                                         config.set(base + [family, hook, priority, 'rule', rule, direction, 'group'], value=group)
                                         config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-group'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
-    exit(1)
\ No newline at end of file
+    exit(1)
diff --git a/src/migration-scripts/firewall/12-to-13 b/src/migration-scripts/firewall/12-to-13
index 8396dd9d1..d72ba834d 100755
--- a/src/migration-scripts/firewall/12-to-13
+++ b/src/migration-scripts/firewall/12-to-13
@@ -1,92 +1,89 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # T5729: Switch to valueless whenever is possible.
 # From
     # set firewall ... rule <rule> log enable
     # set firewall ... rule <rule> state <state> enable
     # set firewall ... rule <rule> log disable
     # set firewall ... rule <rule> state <state> disable
 # To
     # set firewall ... rule <rule> log
     # set firewall ... rule <rule> state <state>
     # Remove command if log=disable or <state>=disable
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['firewall']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 # State Policy logs:
 if config.exists(base + ['global-options', 'state-policy']):
     for state in config.list_nodes(base + ['global-options', 'state-policy']):
         if config.exists(base + ['global-options', 'state-policy', state, 'log']):
             log_value = config.return_value(base + ['global-options', 'state-policy', state, 'log'])
             config.delete(base + ['global-options', 'state-policy', state, 'log'])
             if log_value == 'enable':
                 config.set(base + ['global-options', 'state-policy', state, 'log'])
 
 for family in ['ipv4', 'ipv6', 'bridge']:
     if config.exists(base + [family]):
         for hook in ['forward', 'input', 'output', 'name']:
             if config.exists(base + [family, hook]):
                 for priority in config.list_nodes(base + [family, hook]):
                     if config.exists(base + [family, hook, priority, 'rule']):
                         for rule in config.list_nodes(base + [family, hook, priority, 'rule']):
                             # Log
                             if config.exists(base + [family, hook, priority, 'rule', rule, 'log']):
                                 log_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'log'])
                                 config.delete(base + [family, hook, priority, 'rule', rule, 'log'])
                                 if log_value == 'enable':
                                     config.set(base + [family, hook, priority, 'rule', rule, 'log'])
                             # State
                             if config.exists(base + [family, hook, priority, 'rule', rule, 'state']):
                                 flag_enable = 'False'
                                 for state in ['established', 'invalid', 'new', 'related']:
                                     if config.exists(base + [family, hook, priority, 'rule', rule, 'state', state]):
                                         state_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'state', state])
                                         config.delete(base + [family, hook, priority, 'rule', rule, 'state', state])
                                         if state_value == 'enable':
                                             config.set(base + [family, hook, priority, 'rule', rule, 'state'], value=state, replace=False)
                                             flag_enable = 'True'
                                 if flag_enable == 'False':
                                     config.delete(base + [family, hook, priority, 'rule', rule, 'state'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
-    exit(1)
\ No newline at end of file
+    exit(1)
diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8
index d06c3150a..bbaba113a 100755
--- a/src/migration-scripts/firewall/7-to-8
+++ b/src/migration-scripts/firewall/7-to-8
@@ -1,98 +1,95 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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/>.
 
 # T2199: Migrate interface firewall nodes to firewall interfaces <ifname> <direction> name/ipv6-name <name>
 # T2199: Migrate zone-policy to firewall node
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['firewall']
 zone_base = ['zone-policy']
 config = ConfigTree(config_file)
 
 if not config.exists(base) and not config.exists(zone_base):
     # Nothing to do
     exit(0)
 
 def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None):
     if_path = ['interfaces', iftype, ifname]
     ifname_full = ifname
 
     if vif:
         if_path += ['vif', vif]
         ifname_full = f'{ifname}.{vif}'
     elif vifs:
         if_path += ['vif-s', vifs]
         ifname_full = f'{ifname}.{vifs}'
         if vifc:
             if_path += ['vif-c', vifc]
             ifname_full = f'{ifname}.{vifs}.{vifc}'
 
     if not config.exists(if_path + ['firewall']):
         return
 
     if not config.exists(['firewall', 'interface']):
         config.set(['firewall', 'interface'])
         config.set_tag(['firewall', 'interface'])
 
     config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full])
     config.delete(if_path + ['firewall'])
 
 for iftype in config.list_nodes(['interfaces']):
     for ifname in config.list_nodes(['interfaces', iftype]):
         migrate_interface(config, iftype, ifname)
 
         if config.exists(['interfaces', iftype, ifname, 'vif']):
             for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']):
                 migrate_interface(config, iftype, ifname, vif=vif)
 
         if config.exists(['interfaces', iftype, ifname, 'vif-s']):
             for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']):
                 migrate_interface(config, iftype, ifname, vifs=vifs)
 
                 if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
                     for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
                         migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc)
 
 if config.exists(zone_base + ['zone']):
     config.set(['firewall', 'zone'])
     config.set_tag(['firewall', 'zone'])
 
     for zone in config.list_nodes(zone_base + ['zone']):
         config.copy(zone_base + ['zone', zone], ['firewall', 'zone', zone])
     config.delete(zone_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/firewall/8-to-9 b/src/migration-scripts/firewall/8-to-9
index d7647354a..6e019beb2 100755
--- a/src/migration-scripts/firewall/8-to-9
+++ b/src/migration-scripts/firewall/8-to-9
@@ -1,91 +1,88 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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/>.
 
 # T4780: Add firewall interface group
-#  cli changes from:       
+#  cli changes from:
 #  set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface] <interface_name>
 #  To
 #  set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface]  [interface-name | interface-group] <interface_name | interface_group>
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['firewall']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 if config.exists(base + ['name']):
     for name in config.list_nodes(base + ['name']):
         if not config.exists(base + ['name', name, 'rule']):
             continue
 
         for rule in config.list_nodes(base + ['name', name, 'rule']):
             rule_iiface = base + ['name', name, 'rule', rule, 'inbound-interface']
             rule_oiface = base + ['name', name, 'rule', rule, 'outbound-interface']
 
             if config.exists(rule_iiface):
                 tmp = config.return_value(rule_iiface)
                 config.delete(rule_iiface)
                 config.set(rule_iiface + ['interface-name'], value=tmp)
 
             if config.exists(rule_oiface):
                 tmp = config.return_value(rule_oiface)
                 config.delete(rule_oiface)
                 config.set(rule_oiface + ['interface-name'], value=tmp)
 
 
 if config.exists(base + ['ipv6-name']):
     for name in config.list_nodes(base + ['ipv6-name']):
         if not config.exists(base + ['ipv6-name', name, 'rule']):
             continue
 
         for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']):
             rule_iiface = base + ['ipv6-name', name, 'rule', rule, 'inbound-interface']
             rule_oiface = base + ['ipv6-name', name, 'rule', rule, 'outbound-interface']
 
             if config.exists(rule_iiface):
                 tmp = config.return_value(rule_iiface)
                 config.delete(rule_iiface)
                 config.set(rule_iiface + ['interface-name'], value=tmp)
 
             if config.exists(rule_oiface):
                 tmp = config.return_value(rule_oiface)
                 config.delete(rule_oiface)
                 config.set(rule_oiface + ['interface-name'], value=tmp)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
-    exit(1)
\ No newline at end of file
+    exit(1)
diff --git a/src/migration-scripts/firewall/9-to-10 b/src/migration-scripts/firewall/9-to-10
index a70460718..ce509a731 100755
--- a/src/migration-scripts/firewall/9-to-10
+++ b/src/migration-scripts/firewall/9-to-10
@@ -1,80 +1,77 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # T5050: Log options
-#  cli changes from:       
+#  cli changes from:
 #  set firewall [name | ipv6-name] <name> rule <number> log-level <log_level>
 #  To
 #  set firewall [name | ipv6-name] <name> rule <number> log-options level <log_level>
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['firewall']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 if config.exists(base + ['name']):
     for name in config.list_nodes(base + ['name']):
         if not config.exists(base + ['name', name, 'rule']):
             continue
 
         for rule in config.list_nodes(base + ['name', name, 'rule']):
-            log_options_base = base + ['name', name, 'rule', rule, 'log-options'] 
+            log_options_base = base + ['name', name, 'rule', rule, 'log-options']
             rule_log_level = base + ['name', name, 'rule', rule, 'log-level']
 
             if config.exists(rule_log_level):
                 tmp = config.return_value(rule_log_level)
                 config.delete(rule_log_level)
                 config.set(log_options_base + ['level'], value=tmp)
 
 if config.exists(base + ['ipv6-name']):
     for name in config.list_nodes(base + ['ipv6-name']):
         if not config.exists(base + ['ipv6-name', name, 'rule']):
             continue
 
         for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']):
-            log_options_base = base + ['ipv6-name', name, 'rule', rule, 'log-options'] 
+            log_options_base = base + ['ipv6-name', name, 'rule', rule, 'log-options']
             rule_log_level = base + ['ipv6-name', name, 'rule', rule, 'log-level']
 
             if config.exists(rule_log_level):
                 tmp = config.return_value(rule_log_level)
                 config.delete(rule_log_level)
                 config.set(log_options_base + ['level'], value=tmp)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
-    exit(1)
\ No newline at end of file
+    exit(1)
diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27
index 4967a29fa..429ab650f 100755
--- a/src/migration-scripts/interfaces/26-to-27
+++ b/src/migration-scripts/interfaces/26-to-27
@@ -1,54 +1,52 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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/>.
 
 # T4384: pppoe: replace default-route CLI option with common CLI nodes already
 #        present for DHCP
 
 from sys import argv
-
-from vyos.ethtool import Ethtool
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['interfaces', 'pppoe']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     exit(0)
 
 for ifname in config.list_nodes(base):
     tmp_config = base + [ifname, 'default-route']
     if config.exists(tmp_config):
         # Retrieve current config value
         value = config.return_value(tmp_config)
         # Delete old Config node
         config.delete(tmp_config)
         if value == 'none':
             config.set(base + [ifname, 'no-default-route'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/migration-scripts/interfaces/27-to-28 b/src/migration-scripts/interfaces/27-to-28
index a0d043d11..9f5e93b5f 100755
--- a/src/migration-scripts/interfaces/27-to-28
+++ b/src/migration-scripts/interfaces/27-to-28
@@ -1,49 +1,48 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node
 #        to "authentication username"
 
 from sys import argv
 
-from vyos.ethtool import Ethtool
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 
 for type in ['pppoe', 'sstpc-client', 'wwam']:
     base = ['interfaces', type]
     if not config.exists(base):
         continue
     for interface in config.list_nodes(base):
         auth_base = base + [interface, 'authentication', 'user']
         if config.exists(auth_base):
             config.rename(auth_base, 'username')
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/migration-scripts/interfaces/28-to-29 b/src/migration-scripts/interfaces/28-to-29
index ad5bfa653..0437977dc 100755
--- a/src/migration-scripts/interfaces/28-to-29
+++ b/src/migration-scripts/interfaces/28-to-29
@@ -1,54 +1,52 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast"
 #        valueless node.
 
 from sys import argv
-
-from vyos.ethtool import Ethtool
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['interfaces', 'tunnel']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     exit(0)
 
 for ifname in config.list_nodes(base):
     multicast_base = base + [ifname, 'multicast']
     if config.exists(multicast_base):
         tmp = config.return_value(multicast_base)
         print(tmp)
         # Delete old Config node
         config.delete(multicast_base)
         if tmp == 'enable':
             config.set(base + [ifname, 'enable-multicast'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/migration-scripts/interfaces/29-to-30 b/src/migration-scripts/interfaces/29-to-30
index acb6ee1fb..80aad1d44 100755
--- a/src/migration-scripts/interfaces/29-to-30
+++ b/src/migration-scripts/interfaces/29-to-30
@@ -1,49 +1,47 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # T5286: remove XDP support in favour of VPP
 
 from sys import argv
-
-from vyos.ethtool import Ethtool
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 supports_xdp = ['bonding', 'ethernet']
 config = ConfigTree(config_file)
 
 for if_type in supports_xdp:
     base = ['interfaces', if_type]
     if not config.exists(base):
         continue
     for interface in config.list_nodes(base):
         if_base = base + [interface]
         if config.exists(if_base + ['xdp']):
             config.delete(if_base + ['xdp'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/migration-scripts/ipoe-server/0-to-1 b/src/migration-scripts/ipoe-server/0-to-1
index ac9d13abc..a6dd46ac1 100755
--- a/src/migration-scripts/ipoe-server/0-to-1
+++ b/src/migration-scripts/ipoe-server/0-to-1
@@ -1,74 +1,71 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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/>.
 
 # - T4703: merge vlan-id and vlan-range to vlan CLI node
 
 # L2|L3 -> l2|l3
 # mac-address -> mac
 # network-mode -> mode
 
-import os
-import sys
-
 from sys import argv, exit
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['service', 'ipoe-server']
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 if config.exists(base + ['authentication', 'interface']):
     for interface in config.list_nodes(base + ['authentication', 'interface']):
         config.rename(base + ['authentication', 'interface', interface, 'mac-address'], 'mac')
 
         mac_base = base + ['authentication', 'interface', interface, 'mac']
         for mac in config.list_nodes(mac_base):
             vlan_config = mac_base + [mac, 'vlan-id']
             if config.exists(vlan_config):
                 config.rename(vlan_config, 'vlan')
 
 for interface in config.list_nodes(base + ['interface']):
     base_path = base + ['interface', interface]
     for vlan in ['vlan-id', 'vlan-range']:
         if config.exists(base_path + [vlan]):
             print(interface, vlan)
             for tmp in config.return_values(base_path + [vlan]):
                 config.set(base_path + ['vlan'], value=tmp, replace=False)
             config.delete(base_path + [vlan])
 
     if config.exists(base_path + ['network-mode']):
         tmp = config.return_value(base_path + ['network-mode'])
         config.delete(base_path + ['network-mode'])
         # Change L2|L3 to lower case l2|l3
         config.set(base_path + ['mode'], value=tmp.lower())
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/ipoe-server/1-to-2 b/src/migration-scripts/ipoe-server/1-to-2
index 11d7911e9..378702693 100755
--- a/src/migration-scripts/ipoe-server/1-to-2
+++ b/src/migration-scripts/ipoe-server/1-to-2
@@ -1,87 +1,85 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2023 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/>.
 
 # - changed cli of all named pools
 # - moved gateway-address from pool to global configuration with / netmask
 #   gateway can exist without pool if radius is used
 #   and Framed-ip-address is transmited
 # - There are several gateway-addresses in ipoe
 # - default-pool by migration.
 #       1. The first pool that contains next-poll.
 #       2. Else, the first pool in the list
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['service', 'ipoe-server']
 pool_base = base + ['client-ip-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 default_pool = ''
 gateway = ''
 
 #named pool migration
 namedpools_base = pool_base + ['name']
 
 for pool_name in config.list_nodes(namedpools_base):
     pool_path = namedpools_base + [pool_name]
     if config.exists(pool_path + ['subnet']):
         subnet = config.return_value(pool_path + ['subnet'])
         config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False)
         # Get netmask from subnet
         mask = subnet.split("/")[1]
     if config.exists(pool_path + ['next-pool']):
         next_pool = config.return_value(pool_path + ['next-pool'])
         config.set(pool_base + [pool_name, 'next-pool'], value=next_pool)
         if not default_pool:
             default_pool = pool_name
     if config.exists(pool_path + ['gateway-address']) and mask:
         gateway = f'{config.return_value(pool_path + ["gateway-address"])}/{mask}'
         config.set(base + ['gateway-address'], value=gateway, replace=False)
 
 if not default_pool and config.list_nodes(namedpools_base):
     default_pool = config.list_nodes(namedpools_base)[0]
 
 config.delete(namedpools_base)
 
 if default_pool:
     config.set(base + ['default-pool'], value=default_pool)
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/ipoe-server/2-to-3 b/src/migration-scripts/ipoe-server/2-to-3
index d4ae0a7ba..0909315a8 100755
--- a/src/migration-scripts/ipoe-server/2-to-3
+++ b/src/migration-scripts/ipoe-server/2-to-3
@@ -1,61 +1,58 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # Migrating to named ipv6 pools
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['service', 'ipoe-server']
 pool_base = base + ['client-ipv6-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 
 ipv6_pool_name = 'ipv6-pool'
 config.copy(pool_base, pool_base + [ipv6_pool_name])
 
 if config.exists(pool_base + ['prefix']):
     config.delete(pool_base + ['prefix'])
     config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name)
 if config.exists(pool_base + ['delegate']):
     config.delete(pool_base + ['delegate'])
 
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/ipsec/11-to-12 b/src/migration-scripts/ipsec/11-to-12
index e34882c23..4833d0876 100755
--- a/src/migration-scripts/ipsec/11-to-12
+++ b/src/migration-scripts/ipsec/11-to-12
@@ -1,53 +1,51 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # Remove legacy ipsec.conf and ipsec.secrets - Not supported with swanctl
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['vpn', 'ipsec']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 if config.exists(base + ['include-ipsec-conf']):
     config.delete(base + ['include-ipsec-conf'])
 
 if config.exists(base + ['include-ipsec-secrets']):
     config.delete(base + ['include-ipsec-secrets'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/migration-scripts/ipsec/12-to-13 b/src/migration-scripts/ipsec/12-to-13
index c11f708bd..d90c70314 100755
--- a/src/migration-scripts/ipsec/12-to-13
+++ b/src/migration-scripts/ipsec/12-to-13
@@ -1,59 +1,57 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # Changed value of dead-peer-detection.action from hold to trap
 # Changed value of close-action from hold to trap and from restart to start
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['vpn', 'ipsec', 'ike-group']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 else:
     for ike_group in config.list_nodes(base):
         base_dpd_action = base + [ike_group, 'dead-peer-detection', 'action']
         base_close_action = base + [ike_group, 'close-action']
         if config.exists(base_dpd_action) and config.return_value(base_dpd_action) == 'hold':
             config.set(base_dpd_action, 'trap', replace=True)
         if config.exists(base_close_action):
             if config.return_value(base_close_action) == 'hold':
                 config.set(base_close_action, 'trap', replace=True)
             if config.return_value(base_close_action) == 'restart':
                 config.set(base_close_action, 'start', replace=True)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/migration-scripts/ipsec/7-to-8 b/src/migration-scripts/ipsec/7-to-8
index e002db0b1..9acc737d5 100755
--- a/src/migration-scripts/ipsec/7-to-8
+++ b/src/migration-scripts/ipsec/7-to-8
@@ -1,125 +1,124 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2021 VyOS maintainers and contributors
+# 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/>.
 
 # Migrate rsa keys into PKI configuration
 
 import base64
 import os
 import struct
 
 from cryptography.hazmat.primitives.asymmetric import rsa
 
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.pki import load_public_key
 from vyos.pki import load_private_key
 from vyos.pki import encode_public_key
 from vyos.pki import encode_private_key
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 pki_base = ['pki']
 ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer']
 rsa_keys_base = ['vpn', 'rsa-keys']
 
 config = ConfigTree(config_file)
 
 LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/']
 
 def migrate_from_vyatta_key(data):
     data = base64.b64decode(data[2:])
     length = struct.unpack('B', data[:1])[0]
     e = int.from_bytes(data[1:1+length], 'big')
     n = int.from_bytes(data[1+length:], 'big')
     public_numbers = rsa.RSAPublicNumbers(e, n)
     return public_numbers.public_key()
 
 def wrapped_pem_to_config_value(pem):
     return "".join(pem.strip().split("\n")[1:-1])
 
 local_key_name = 'localhost'
 
 if config.exists(rsa_keys_base):
     if not config.exists(pki_base + ['key-pair']):
         config.set(pki_base + ['key-pair'])
         config.set_tag(pki_base + ['key-pair'])
 
     if config.exists(rsa_keys_base + ['local-key', 'file']):
         local_file = config.return_value(rsa_keys_base + ['local-key', 'file'])
         local_path = None
         local_key = None
 
         for path in LOCAL_KEY_PATHS:
             full_path = os.path.join(path, local_file)
             if os.path.exists(full_path):
                 local_path = full_path
                 break
 
         if local_path:
             with open(local_path, 'r') as f:
                 local_key_data = f.read()
                 local_key = load_private_key(local_key_data, wrap_tags=False)
 
         if local_key:
             local_key_pem = encode_private_key(local_key)
             config.set(pki_base + ['key-pair', local_key_name, 'private', 'key'], value=wrapped_pem_to_config_value(local_key_pem))
         else:
             print('Failed to migrate local RSA key')
 
     if config.exists(rsa_keys_base + ['rsa-key-name']):
         for rsa_name in config.list_nodes(rsa_keys_base + ['rsa-key-name']):
             if not config.exists(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']):
                 continue
 
             vyatta_key = config.return_value(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key'])
             public_key = migrate_from_vyatta_key(vyatta_key)
 
             if public_key:
                 public_key_pem = encode_public_key(public_key)
                 config.set(pki_base + ['key-pair', rsa_name, 'public', 'key'], value=wrapped_pem_to_config_value(public_key_pem))
             else:
                 print(f'Failed to migrate rsa-key "{rsa_name}"')
 
     config.delete(rsa_keys_base)
 
 if config.exists(ipsec_site_base):
     for peer in config.list_nodes(ipsec_site_base):
         mode = config.return_value(ipsec_site_base + [peer, 'authentication', 'mode'])
 
         if mode != 'rsa':
             continue
 
         config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'local-key'], value=local_key_name)
 
         remote_key_name = config.return_value(ipsec_site_base + [peer, 'authentication', 'rsa-key-name'])
         config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'remote-key'], value=remote_key_name)
         config.delete(ipsec_site_base + [peer, 'authentication', 'rsa-key-name'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     sys.exit(1)
diff --git a/src/migration-scripts/ipsec/9-to-10 b/src/migration-scripts/ipsec/9-to-10
index a4a71d38e..bc10e1997 100755
--- a/src/migration-scripts/ipsec/9-to-10
+++ b/src/migration-scripts/ipsec/9-to-10
@@ -1,134 +1,131 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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/>.
 
 import re
 
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.template import is_ipv4
-from vyos.template import is_ipv6
-
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['vpn', 'ipsec']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 # IKE changes, T4118:
 if config.exists(base + ['ike-group']):
     for ike_group in config.list_nodes(base + ['ike-group']):
         # replace 'ipsec ike-group <tag> mobike disable'
         #      => 'ipsec ike-group <tag> disable-mobike'
         mobike = base + ['ike-group', ike_group, 'mobike']
         if config.exists(mobike):
             if config.return_value(mobike) == 'disable':
                 config.set(base + ['ike-group', ike_group, 'disable-mobike'])
             config.delete(mobike)
 
         # replace 'ipsec ike-group <tag> ikev2-reauth yes'
         #      => 'ipsec ike-group <tag> ikev2-reauth'
         reauth = base + ['ike-group', ike_group, 'ikev2-reauth']
         if config.exists(reauth):
             if config.return_value(reauth) == 'yes':
                 config.delete(reauth)
                 config.set(reauth)
             else:
                 config.delete(reauth)
 
 # ESP changes
 # replace 'ipsec esp-group <tag> compression enable'
 #      => 'ipsec esp-group <tag> compression'
 if config.exists(base + ['esp-group']):
     for esp_group in config.list_nodes(base + ['esp-group']):
         compression = base + ['esp-group', esp_group, 'compression']
         if config.exists(compression):
             if config.return_value(compression) == 'enable':
                 config.delete(compression)
                 config.set(compression)
             else:
                 config.delete(compression)
 
 # PEER changes
 if config.exists(base + ['site-to-site', 'peer']):
     for peer in config.list_nodes(base + ['site-to-site', 'peer']):
         peer_base = base + ['site-to-site', 'peer', peer]
 
         # replace: 'peer <tag> id x'
         #       => 'peer <tag> local-id x'
         if config.exists(peer_base + ['authentication', 'id']):
             config.rename(peer_base + ['authentication', 'id'], 'local-id')
 
         # For the peer '@foo' set remote-id 'foo' if remote-id is not defined
         # For the peer '192.0.2.1' set remote-id '192.0.2.1' if remote-id is not defined
         if not config.exists(peer_base + ['authentication', 'remote-id']):
             tmp = peer.replace('@', '') if peer.startswith('@') else peer
             config.set(peer_base + ['authentication', 'remote-id'], value=tmp)
 
         # replace: 'peer <tag> force-encapsulation enable'
         #       => 'peer <tag> force-udp-encapsulation'
         force_enc = peer_base + ['force-encapsulation']
         if config.exists(force_enc):
             if config.return_value(force_enc) == 'enable':
                 config.delete(force_enc)
                 config.set(peer_base + ['force-udp-encapsulation'])
             else:
                 config.delete(force_enc)
 
         # add option: 'peer <tag> remote-address x.x.x.x'
         remote_address = peer
         if peer.startswith('@'):
             remote_address = 'any'
         config.set(peer_base + ['remote-address'], value=remote_address)
         # Peer name it is swanctl connection name and shouldn't contain dots or colons
         # rename peer:
         #   peer 192.0.2.1   => peer peer_192-0-2-1
         #   peer 2001:db8::2 => peer peer_2001-db8--2
         #   peer @foo        => peer peer_foo
         re_peer_name = re.sub(':|\.', '-', peer)
         if re_peer_name.startswith('@'):
             re_peer_name = re.sub('@', '', re_peer_name)
         new_peer_name = f'peer_{re_peer_name}'
 
         config.rename(peer_base, new_peer_name)
 
 # remote-access/road-warrior changes
 if config.exists(base + ['remote-access', 'connection']):
     for connection in config.list_nodes(base + ['remote-access', 'connection']):
         ra_base = base + ['remote-access', 'connection', connection]
         # replace: 'remote-access connection <tag> authentication id x'
         #       => 'remote-access connection <tag> authentication local-id x'
         if config.exists(ra_base + ['authentication', 'id']):
             config.rename(ra_base + ['authentication', 'id'], 'local-id')
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/migration-scripts/l2tp/2-to-3 b/src/migration-scripts/l2tp/2-to-3
index b46b0f22e..8527c2d4a 100755
--- a/src/migration-scripts/l2tp/2-to-3
+++ b/src/migration-scripts/l2tp/2-to-3
@@ -1,111 +1,107 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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/>.
 
 # - remove primary/secondary identifier from nameserver
 # - TODO: remove radius server req-limit
 
-import os
-import sys
-
 from sys import argv, exit
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'l2tp', 'remote-access']
 if not config.exists(base):
     # Nothing to do
     exit(0)
 else:
-
     # Migrate IPv4 DNS servers
     dns_base = base + ['dns-servers']
     if config.exists(dns_base):
         for server in ['server-1', 'server-2']:
           if config.exists(dns_base + [server]):
             dns = config.return_value(dns_base + [server])
             config.set(base + ['name-server'], value=dns, replace=False)
 
         config.delete(dns_base)
 
     # Migrate IPv6 DNS servers
     dns_base = base + ['dnsv6-servers']
     if config.exists(dns_base):
         for server in config.return_values(dns_base):
             config.set(base + ['name-server'], value=server, replace=False)
 
         config.delete(dns_base)
 
     # Migrate IPv4 WINS servers
     wins_base = base + ['wins-servers']
     if config.exists(wins_base):
         for server in ['server-1', 'server-2']:
           if config.exists(wins_base + [server]):
             wins = config.return_value(wins_base + [server])
             config.set(base + ['wins-server'], value=wins, replace=False)
 
         config.delete(wins_base)
 
 
     # Remove RADIUS server req-limit node
     radius_base = base + ['authentication', 'radius']
     if config.exists(radius_base):
         for server in config.list_nodes(radius_base + ['server']):
             if config.exists(radius_base + ['server', server, 'req-limit']):
                 config.delete(radius_base + ['server', server, 'req-limit'])
 
     # Migrate IPv6 prefixes
     ipv6_base = base + ['client-ipv6-pool']
     if config.exists(ipv6_base + ['prefix']):
         prefix_old = config.return_values(ipv6_base + ['prefix'])
         # delete old prefix CLI nodes
         config.delete(ipv6_base + ['prefix'])
         # create ned prefix tag node
         config.set(ipv6_base + ['prefix'])
         config.set_tag(ipv6_base + ['prefix'])
 
         for p in prefix_old:
             prefix = p.split(',')[0]
             mask = p.split(',')[1]
             config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask)
 
     if config.exists(ipv6_base + ['delegate-prefix']):
         prefix_old = config.return_values(ipv6_base + ['delegate-prefix'])
         # delete old delegate prefix CLI nodes
         config.delete(ipv6_base + ['delegate-prefix'])
         # create ned delegation tag node
         config.set(ipv6_base + ['delegate'])
         config.set_tag(ipv6_base + ['delegate'])
 
         for p in prefix_old:
             prefix = p.split(',')[0]
             mask = p.split(',')[1]
             config.set(ipv6_base + ['delegate', prefix, 'delegate-prefix'], value=mask)
 
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
     except OSError as e:
         print("Failed to save the modified config: {}".format(e))
         exit(1)
diff --git a/src/migration-scripts/l2tp/3-to-4 b/src/migration-scripts/l2tp/3-to-4
index 8c2b909b7..14b86ff04 100755
--- a/src/migration-scripts/l2tp/3-to-4
+++ b/src/migration-scripts/l2tp/3-to-4
@@ -1,169 +1,168 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# 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/>.
 
 # - remove primary/secondary identifier from nameserver
 # - TODO: remove radius server req-limit
 
 import os
 
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 from vyos.pki import load_certificate
-from vyos.pki import load_crl
 from vyos.pki import load_private_key
 from vyos.pki import encode_certificate
 from vyos.pki import encode_private_key
 from vyos.utils.process import run
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'l2tp', 'remote-access', 'ipsec-settings']
 pki_base = ['pki']
 
 if not config.exists(base):
     exit(0)
 
 AUTH_DIR = '/config/auth'
 
 def wrapped_pem_to_config_value(pem):
     return "".join(pem.strip().split("\n")[1:-1])
 
 if not config.exists(base + ['authentication', 'x509']):
     exit(0)
 
 x509_base = base + ['authentication', 'x509']
 pki_name = 'l2tp_remote_access'
 
 if not config.exists(pki_base + ['ca']):
     config.set(pki_base + ['ca'])
     config.set_tag(pki_base + ['ca'])
 
 if not config.exists(pki_base + ['certificate']):
     config.set(pki_base + ['certificate'])
     config.set_tag(pki_base + ['certificate'])
 
 if config.exists(x509_base + ['ca-cert-file']):
     cert_file = config.return_value(x509_base + ['ca-cert-file'])
     cert_path = os.path.join(AUTH_DIR, cert_file)
     cert = None
 
     if os.path.isfile(cert_path):
         if not os.access(cert_path, os.R_OK):
             run(f'sudo chmod 644 {cert_path}')
 
         with open(cert_path, 'r') as f:
             cert_data = f.read()
             cert = load_certificate(cert_data, wrap_tags=False)
 
     if cert:
         cert_pem = encode_certificate(cert)
         config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
         config.set(x509_base + ['ca-certificate'], value=pki_name)
     else:
         print(f'Failed to migrate CA certificate on l2tp remote-access config')
 
     config.delete(x509_base + ['ca-cert-file'])
 
 if config.exists(x509_base + ['crl-file']):
     crl_file = config.return_value(x509_base + ['crl-file'])
     crl_path = os.path.join(AUTH_DIR, crl_file)
     crl = None
 
     if os.path.isfile(crl_path):
         if not os.access(crl_path, os.R_OK):
             run(f'sudo chmod 644 {crl_path}')
 
         with open(crl_path, 'r') as f:
             crl_data = f.read()
             crl = load_certificate(crl_data, wrap_tags=False)
 
     if crl:
         crl_pem = encode_certificate(crl)
         config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
     else:
         print(f'Failed to migrate CRL on l2tp remote-access config')
 
     config.delete(x509_base + ['crl-file'])
 
 if config.exists(x509_base + ['server-cert-file']):
     cert_file = config.return_value(x509_base + ['server-cert-file'])
     cert_path = os.path.join(AUTH_DIR, cert_file)
     cert = None
 
     if os.path.isfile(cert_path):
         if not os.access(cert_path, os.R_OK):
             run(f'sudo chmod 644 {cert_path}')
 
         with open(cert_path, 'r') as f:
             cert_data = f.read()
             cert = load_certificate(cert_data, wrap_tags=False)
 
     if cert:
         cert_pem = encode_certificate(cert)
         config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
         config.set(x509_base + ['certificate'], value=pki_name)
     else:
         print(f'Failed to migrate certificate on l2tp remote-access config')
 
     config.delete(x509_base + ['server-cert-file'])
 
 if config.exists(x509_base + ['server-key-file']):
     key_file = config.return_value(x509_base + ['server-key-file'])
     key_passphrase = None
 
     if config.exists(x509_base + ['server-key-password']):
         key_passphrase = config.return_value(x509_base + ['server-key-password'])
 
     key_path = os.path.join(AUTH_DIR, key_file)
     key = None
 
     if os.path.isfile(key_path):
         if not os.access(key_path, os.R_OK):
             run(f'sudo chmod 644 {key_path}')
 
         with open(key_path, 'r') as f:
             key_data = f.read()
             key = load_private_key(key_data, passphrase=key_passphrase, wrap_tags=False)
 
     if key:
         key_pem = encode_private_key(key, passphrase=key_passphrase)
         config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
 
         if key_passphrase:
             config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected'])
             config.set(x509_base + ['private-key-passphrase'], value=key_passphrase)
     else:
         print(f'Failed to migrate private key on l2tp remote-access config')
 
     config.delete(x509_base + ['server-key-file'])
     if config.exists(x509_base + ['server-key-password']):
         config.delete(x509_base + ['server-key-password'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/l2tp/4-to-5 b/src/migration-scripts/l2tp/4-to-5
index 3176f895a..b7f4d2677 100755
--- a/src/migration-scripts/l2tp/4-to-5
+++ b/src/migration-scripts/l2tp/4-to-5
@@ -1,87 +1,85 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # - move all pool to named pools
 #       'start-stop' migrate to namedpool 'default-range-pool'
 #       'subnet' migrate to namedpool 'default-subnet-pool'
 #       'default-subnet-pool' is the next pool for 'default-range-pool'
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 from vyos.base import Warning
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'l2tp', 'remote-access']
 pool_base = base + ['client-ip-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 default_pool = ''
 range_pool_name = 'default-range-pool'
 
 if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
     def is_legalrange(ip1: str, ip2: str, mask: str):
         from ipaddress import IPv4Interface
         interface1 = IPv4Interface(f'{ip1}/{mask}')
 
         interface2 = IPv4Interface(f'{ip2}/{mask}')
         return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip
 
     start_ip = config.return_value(pool_base + ['start'])
     stop_ip = config.return_value(pool_base + ['stop'])
     if is_legalrange(start_ip, stop_ip,'24'):
         ip_range = f'{start_ip}-{stop_ip}'
         config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False)
         default_pool = range_pool_name
     else:
         Warning(
             f'L2TP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.')
 
     config.delete(pool_base + ['start'])
     config.delete(pool_base + ['stop'])
 
 if config.exists(pool_base + ['subnet']):
     for subnet in config.return_values(pool_base + ['subnet']):
         config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False)
 
     config.delete(pool_base + ['subnet'])
     default_pool = range_pool_name
 
 if default_pool:
     config.set(base + ['default-pool'], value=default_pool)
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/l2tp/5-to-6 b/src/migration-scripts/l2tp/5-to-6
index ca0b13dcc..ac40b89c8 100755
--- a/src/migration-scripts/l2tp/5-to-6
+++ b/src/migration-scripts/l2tp/5-to-6
@@ -1,110 +1,106 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 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/>.
 
-
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'l2tp', 'remote-access']
 if not config.exists(base):
     exit(0)
 
 #migrate idle to ppp option lcp-echo-timeout
 idle_path = base + ['idle']
 if config.exists(idle_path):
     config.set(base + ['ppp-options', 'lcp-echo-timeout'],
                value=config.return_value(idle_path))
     config.delete(idle_path)
 
 #migrate mppe from authentication to ppp-otion
 mppe_path = base + ['authentication', 'mppe']
 if config.exists(mppe_path):
     config.set(base + ['ppp-options', 'mppe'],
                value=config.return_value(mppe_path))
     config.delete(mppe_path)
 
 #migrate require to protocol
 require_path = base + ['authentication', 'require']
 if config.exists(require_path):
     protocols = list(config.return_values(require_path))
     for protocol in protocols:
         config.set(base + ['authentication', 'protocols'], value=protocol,
                    replace=False)
     config.delete(require_path)
 else:
     config.set(base + ['authentication', 'protocols'], value='mschap-v2')
 
 #migrate default gateway if not exist
 if not config.exists(base + ['gateway-address']):
     config.set(base + ['gateway-address'], value='10.255.255.0')
 
 #migrate authentication radius timeout
 rad_timeout_path = base + ['authentication', 'radius', 'timeout']
 if config.exists(rad_timeout_path):
     if int(config.return_value(rad_timeout_path)) > 60:
         config.set(rad_timeout_path, value=60)
 
 #migrate authentication radius acct timeout
 rad_acct_timeout_path = base + ['authentication', 'radius', 'acct-timeout']
 if config.exists(rad_acct_timeout_path):
     if int(config.return_value(rad_acct_timeout_path)) > 60:
         config.set(rad_acct_timeout_path,value=60)
 
 #migrate authentication radius max-try
 rad_max_try_path = base + ['authentication', 'radius', 'max-try']
 if config.exists(rad_max_try_path):
     if int(config.return_value(rad_max_try_path)) > 20:
         config.set(rad_max_try_path, value=20)
 
 #migrate dae-server to dynamic-author
 dae_path_old = base + ['authentication', 'radius', 'dae-server']
 dae_path_new = base + ['authentication', 'radius', 'dynamic-author']
 
 if config.exists(dae_path_old + ['ip-address']):
     config.set(dae_path_new + ['server'],
                value=config.return_value(dae_path_old + ['ip-address']))
 
 if config.exists(dae_path_old + ['port']):
     config.set(dae_path_new + ['port'],
                value=config.return_value(dae_path_old + ['port']))
 
 if config.exists(dae_path_old + ['secret']):
     config.set(dae_path_new + ['key'],
                value=config.return_value(dae_path_old + ['secret']))
 
 if config.exists(dae_path_old):
     config.delete(dae_path_old)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/l2tp/6-to-7 b/src/migration-scripts/l2tp/6-to-7
index f49c4ab08..1c536585c 100755
--- a/src/migration-scripts/l2tp/6-to-7
+++ b/src/migration-scripts/l2tp/6-to-7
@@ -1,60 +1,57 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # Migrating to named ipv6 pools
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'l2tp', 'remote-access']
 pool_base = base + ['client-ipv6-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 
 ipv6_pool_name = 'ipv6-pool'
 config.copy(pool_base, pool_base + [ipv6_pool_name])
 
 if config.exists(pool_base + ['prefix']):
     config.delete(pool_base + ['prefix'])
     config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name)
 if config.exists(pool_base + ['delegate']):
     config.delete(pool_base + ['delegate'])
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/l2tp/7-to-8 b/src/migration-scripts/l2tp/7-to-8
index 4956e1155..e429ed057 100755
--- a/src/migration-scripts/l2tp/7-to-8
+++ b/src/migration-scripts/l2tp/7-to-8
@@ -1,68 +1,65 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # Migrate from 'ccp-disable' to 'ppp-options.disable-ccp'
 # Migration ipv6 options
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'l2tp', 'remote-access']
 if not config.exists(base):
     exit(0)
 
 #CCP migration
 if config.exists(base + ['ccp-disable']):
     config.delete(base + ['ccp-disable'])
     config.set(base + ['ppp-options', 'disable-ccp'])
 
 #IPV6 options migrations
 if config.exists(base + ['ppp-options','ipv6-peer-intf-id']):
     intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id'])
     if intf_peer_id == 'ipv4':
         intf_peer_id = 'ipv4-addr'
     config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True)
     config.delete(base + ['ppp-options','ipv6-peer-intf-id'])
 
 if config.exists(base + ['ppp-options','ipv6-intf-id']):
     intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id'])
     config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True)
     config.delete(base + ['ppp-options','ipv6-intf-id'])
 
 if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']):
     config.set(base + ['ppp-options','ipv6-accept-peer-interface-id'])
     config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/l2tp/8-to-9 b/src/migration-scripts/l2tp/8-to-9
index e85a3892b..672180e25 100755
--- a/src/migration-scripts/l2tp/8-to-9
+++ b/src/migration-scripts/l2tp/8-to-9
@@ -1,49 +1,46 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # Deleted 'dhcp-interface' from l2tp
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'l2tp', 'remote-access']
 if not config.exists(base):
     exit(0)
 
-#deleting unused dhcp-interface
+# deleting unused dhcp-interface
 if config.exists(base + ['dhcp-interface']):
     config.delete(base + ['dhcp-interface'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/openconnect/0-to-1 b/src/migration-scripts/openconnect/0-to-1
index 8be15fad1..c64b16cb2 100755
--- a/src/migration-scripts/openconnect/0-to-1
+++ b/src/migration-scripts/openconnect/0-to-1
@@ -1,136 +1,135 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# 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/>.
 
 # - Update SSL to use PKI configuration
 
 import os
 
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 from vyos.pki import load_certificate
-from vyos.pki import load_crl
 from vyos.pki import load_private_key
 from vyos.pki import encode_certificate
 from vyos.pki import encode_private_key
 from vyos.utils.process import run
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'openconnect']
 pki_base = ['pki']
 
 if not config.exists(base):
     exit(0)
 
 AUTH_DIR = '/config/auth'
 
 def wrapped_pem_to_config_value(pem):
     return "".join(pem.strip().split("\n")[1:-1])
 
 if not config.exists(base + ['ssl']):
     exit(0)
 
 x509_base = base + ['ssl']
 pki_name = 'openconnect'
 
 if not config.exists(pki_base + ['ca']):
     config.set(pki_base + ['ca'])
     config.set_tag(pki_base + ['ca'])
 
 if not config.exists(pki_base + ['certificate']):
     config.set(pki_base + ['certificate'])
     config.set_tag(pki_base + ['certificate'])
 
 if config.exists(x509_base + ['ca-cert-file']):
     cert_file = config.return_value(x509_base + ['ca-cert-file'])
     cert_path = os.path.join(AUTH_DIR, cert_file)
     cert = None
 
     if os.path.isfile(cert_path):
         if not os.access(cert_path, os.R_OK):
             run(f'sudo chmod 644 {cert_path}')
 
         with open(cert_path, 'r') as f:
             cert_data = f.read()
             cert = load_certificate(cert_data, wrap_tags=False)
 
     if cert:
         cert_pem = encode_certificate(cert)
         config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
         config.set(x509_base + ['ca-certificate'], value=pki_name)
     else:
         print(f'Failed to migrate CA certificate on openconnect config')
 
     config.delete(x509_base + ['ca-cert-file'])
 
 if config.exists(x509_base + ['cert-file']):
     cert_file = config.return_value(x509_base + ['cert-file'])
     cert_path = os.path.join(AUTH_DIR, cert_file)
     cert = None
 
     if os.path.isfile(cert_path):
         if not os.access(cert_path, os.R_OK):
             run(f'sudo chmod 644 {cert_path}')
 
         with open(cert_path, 'r') as f:
             cert_data = f.read()
             cert = load_certificate(cert_data, wrap_tags=False)
 
     if cert:
         cert_pem = encode_certificate(cert)
         config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
         config.set(x509_base + ['certificate'], value=pki_name)
     else:
         print(f'Failed to migrate certificate on openconnect config')
 
     config.delete(x509_base + ['cert-file'])
 
 if config.exists(x509_base + ['key-file']):
     key_file = config.return_value(x509_base + ['key-file'])
     key_path = os.path.join(AUTH_DIR, key_file)
     key = None
 
     if os.path.isfile(key_path):
         if not os.access(key_path, os.R_OK):
             run(f'sudo chmod 644 {key_path}')
 
         with open(key_path, 'r') as f:
             key_data = f.read()
             key = load_private_key(key_data, passphrase=None, wrap_tags=False)
 
     if key:
         key_pem = encode_private_key(key, passphrase=None)
         config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
     else:
         print(f'Failed to migrate private key on openconnect config')
 
     config.delete(x509_base + ['key-file'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/policy/4-to-5 b/src/migration-scripts/policy/4-to-5
index 5b8fee17e..738850f67 100755
--- a/src/migration-scripts/policy/4-to-5
+++ b/src/migration-scripts/policy/4-to-5
@@ -1,138 +1,135 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2022-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/>.
 
 # T2199: Migrate interface policy nodes to policy route <name> interface <ifname>
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base4 = ['policy', 'route']
 base6 = ['policy', 'route6']
 config = ConfigTree(config_file)
 
 
 def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None):
     """Delete unexpected policy on interfaces in cases when
        policy does not exist but inreface has a policy configuration
        Example T5941:
          set interfaces bonding bond0 vif 995 policy
     """
     if_path = ['interfaces', iftype, ifname]
 
     if vif:
         if_path += ['vif', vif]
     elif vifs:
         if_path += ['vif-s', vifs]
         if vifc:
             if_path += ['vif-c', vifc]
 
     if not config.exists(if_path + ['policy']):
         return
 
     config.delete(if_path + ['policy'])
 
 
 if not config.exists(base4) and not config.exists(base6):
     # Delete orphaned nodes on interfaces T5941
     for iftype in config.list_nodes(['interfaces']):
         for ifname in config.list_nodes(['interfaces', iftype]):
             delete_orphaned_interface_policy(config, iftype, ifname)
 
             if config.exists(['interfaces', iftype, ifname, 'vif']):
                 for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']):
                     delete_orphaned_interface_policy(config, iftype, ifname, vif=vif)
 
             if config.exists(['interfaces', iftype, ifname, 'vif-s']):
                 for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']):
                     delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs)
 
                     if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
                         for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
                             delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc)
 
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
     except OSError as e:
         print("Failed to save the modified config: {}".format(e))
         exit(1)
 
     # Nothing to do
     exit(0)
 
 def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None):
     if_path = ['interfaces', iftype, ifname]
     ifname_full = ifname
 
     if vif:
         if_path += ['vif', vif]
         ifname_full = f'{ifname}.{vif}'
     elif vifs:
         if_path += ['vif-s', vifs]
         ifname_full = f'{ifname}.{vifs}'
         if vifc:
             if_path += ['vif-c', vifc]
             ifname_full = f'{ifname}.{vifs}.{vifc}'
 
     if not config.exists(if_path + ['policy']):
         return
 
     if config.exists(if_path + ['policy', 'route']):
         route_name = config.return_value(if_path + ['policy', 'route'])
         config.set(base4 + [route_name, 'interface'], value=ifname_full, replace=False)
 
     if config.exists(if_path + ['policy', 'route6']):
         route_name = config.return_value(if_path + ['policy', 'route6'])
         config.set(base6 + [route_name, 'interface'], value=ifname_full, replace=False)
 
     config.delete(if_path + ['policy'])
 
 for iftype in config.list_nodes(['interfaces']):
     for ifname in config.list_nodes(['interfaces', iftype]):
         migrate_interface(config, iftype, ifname)
 
         if config.exists(['interfaces', iftype, ifname, 'vif']):
             for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']):
                 migrate_interface(config, iftype, ifname, vif=vif)
 
         if config.exists(['interfaces', iftype, ifname, 'vif-s']):
             for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']):
                 migrate_interface(config, iftype, ifname, vifs=vifs)
 
                 if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
                     for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
                         migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/policy/5-to-6 b/src/migration-scripts/policy/5-to-6
index f1545cddb..86287d578 100755
--- a/src/migration-scripts/policy/5-to-6
+++ b/src/migration-scripts/policy/5-to-6
@@ -1,65 +1,62 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2023 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/>.
 
 # T5165: Migrate policy local-route rule <tag> destination|source
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base4 = ['policy', 'local-route']
 base6 = ['policy', 'local-route6']
 config = ConfigTree(config_file)
 
 if not config.exists(base4) and not config.exists(base6):
     # Nothing to do
     exit(0)
 
 # replace 'policy local-route{v6} rule <tag> destination|source <x.x.x.x>'
 #      => 'policy local-route{v6} rule <tag> destination|source address <x.x.x.x>'
 for base in [base4, base6]:
     if config.exists(base + ['rule']):
         for rule in config.list_nodes(base + ['rule']):
             dst_path = base + ['rule', rule, 'destination']
             src_path = base + ['rule', rule, 'source']
             # Destination
             if config.exists(dst_path):
                 for dst_addr in config.return_values(dst_path):
                     config.set(dst_path + ['address'], value=dst_addr, replace=False)
             # Source
             if config.exists(src_path):
                 for src_addr in config.return_values(src_path):
                     config.set(src_path + ['address'], value=src_addr, replace=False)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/policy/6-to-7 b/src/migration-scripts/policy/6-to-7
index e07822ffd..cdefc6837 100755
--- a/src/migration-scripts/policy/6-to-7
+++ b/src/migration-scripts/policy/6-to-7
@@ -1,79 +1,76 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # T5729: Switch to valueless whenever is possible.
 # From
     # set policy [route | route6] ... rule <rule> log enable
     # set policy [route | route6] ... rule <rule> log disable
 # To
     # set policy [route | route6] ... rule <rule> log
     # Remove command if log=disable
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['policy']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 for family in ['route', 'route6']:
     if config.exists(base + [family]):
 
         for policy_name in config.list_nodes(base + [family]):
             if config.exists(base + [family, policy_name, 'rule']):
                 for rule in config.list_nodes(base + [family, policy_name, 'rule']):
                     # Log
                     if config.exists(base + [family, policy_name, 'rule', rule, 'log']):
                         log_value = config.return_value(base + [family, policy_name, 'rule', rule, 'log'])
                         config.delete(base + [family, policy_name, 'rule', rule, 'log'])
                         if log_value == 'enable':
                             config.set(base + [family, policy_name, 'rule', rule, 'log'])
                     # State
                     if config.exists(base + [family, policy_name, 'rule', rule, 'state']):
                         flag_enable = 'False'
                         for state in ['established', 'invalid', 'new', 'related']:
                             if config.exists(base + [family, policy_name, 'rule', rule, 'state', state]):
                                 state_value = config.return_value(base + [family, policy_name, 'rule', rule, 'state', state])
                                 config.delete(base + [family, policy_name, 'rule', rule, 'state', state])
                                 if state_value == 'enable':
                                     config.set(base + [family, policy_name, 'rule', rule, 'state'], value=state, replace=False)
                                     flag_enable = 'True'
                         if flag_enable == 'False':
                             config.delete(base + [family, policy_name, 'rule', rule, 'state'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
-    exit(1)
\ No newline at end of file
+    exit(1)
diff --git a/src/migration-scripts/pppoe-server/1-to-2 b/src/migration-scripts/pppoe-server/1-to-2
index c73899ca1..b266893c0 100755
--- a/src/migration-scripts/pppoe-server/1-to-2
+++ b/src/migration-scripts/pppoe-server/1-to-2
@@ -1,61 +1,58 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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/>.
 
 # change mppe node to a leaf node with value prefer
 
-import os
-
 from sys import argv, exit
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['service', 'pppoe-server']
 if not config.exists(base):
     # Nothing to do
     exit(0)
 else:
     mppe_base = base + ['ppp-options', 'mppe']
     if config.exists(mppe_base):
         # get current values
         tmp = config.list_nodes(mppe_base)
         # drop node(s) first ...
         config.delete(mppe_base)
 
         print(tmp)
         # set new value based on preference
         if 'require' in tmp:
             config.set(mppe_base, value='require')
         elif 'prefer' in tmp:
             config.set(mppe_base, value='prefer')
         elif 'deny' in tmp:
             config.set(mppe_base, value='deny')
 
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
     except OSError as e:
         print("Failed to save the modified config: {}".format(e))
         exit(1)
-
diff --git a/src/migration-scripts/pppoe-server/3-to-4 b/src/migration-scripts/pppoe-server/3-to-4
index c07bbb1df..477ed6f22 100755
--- a/src/migration-scripts/pppoe-server/3-to-4
+++ b/src/migration-scripts/pppoe-server/3-to-4
@@ -1,141 +1,139 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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/>.
 
 # - remove primary/secondary identifier from nameserver
 
-import os
-
 from sys import argv, exit
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['service', 'pppoe-server']
 if not config.exists(base):
     # Nothing to do
     exit(0)
 else:
 
     # Migrate IPv4 DNS servers
     dns_base = base + ['dns-servers']
     if config.exists(dns_base):
         for server in ['server-1', 'server-2']:
           if config.exists(dns_base + [server]):
             dns = config.return_value(dns_base + [server])
             config.set(base + ['name-server'], value=dns, replace=False)
 
         config.delete(dns_base)
 
     # Migrate IPv6 DNS servers
     dns_base = base + ['dnsv6-servers']
     if config.exists(dns_base):
         for server in ['server-1', 'server-2', 'server-3']:
           if config.exists(dns_base + [server]):
             dns = config.return_value(dns_base + [server])
             config.set(base + ['name-server'], value=dns, replace=False)
 
         config.delete(dns_base)
 
     # Migrate IPv4 WINS servers
     wins_base = base + ['wins-servers']
     if config.exists(wins_base):
         for server in ['server-1', 'server-2']:
           if config.exists(wins_base + [server]):
             wins = config.return_value(wins_base + [server])
             config.set(base + ['wins-server'], value=wins, replace=False)
 
         config.delete(wins_base)
 
     # Migrate radius-settings node to RADIUS and use this as base for the
     # later migration of the RADIUS servers - this will save a lot of code
     radius_settings = base + ['authentication', 'radius-settings']
     if config.exists(radius_settings):
         config.rename(radius_settings, 'radius')
 
     # Migrate RADIUS dynamic author / change of authorisation server
     dae_old = base + ['authentication', 'radius', 'dae-server']
     if config.exists(dae_old):
         config.rename(dae_old, 'dynamic-author')
         dae_new = base + ['authentication', 'radius', 'dynamic-author']
 
         if config.exists(dae_new + ['ip-address']):
             config.rename(dae_new + ['ip-address'], 'server')
 
         if config.exists(dae_new + ['secret']):
             config.rename(dae_new + ['secret'], 'key')
 
     # Migrate RADIUS server
     radius_server = base + ['authentication', 'radius-server']
     if config.exists(radius_server):
         new_base = base + ['authentication', 'radius', 'server']
         config.set(new_base)
         config.set_tag(new_base)
         for server in config.list_nodes(radius_server):
             old_base = radius_server + [server]
             config.copy(old_base, new_base + [server])
 
             # migrate key
             if config.exists(new_base + [server, 'secret']):
                 config.rename(new_base + [server, 'secret'], 'key')
 
             # remove old req-limit node
             if config.exists(new_base + [server, 'req-limit']):
                 config.delete(new_base + [server, 'req-limit'])
 
         config.delete(radius_server)
 
     # Migrate IPv6 prefixes
     ipv6_base = base + ['client-ipv6-pool']
     if config.exists(ipv6_base + ['prefix']):
         prefix_old = config.return_values(ipv6_base + ['prefix'])
         # delete old prefix CLI nodes
         config.delete(ipv6_base + ['prefix'])
         # create ned prefix tag node
         config.set(ipv6_base + ['prefix'])
         config.set_tag(ipv6_base + ['prefix'])
 
         for p in prefix_old:
             prefix = p.split(',')[0]
             mask = p.split(',')[1]
             config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask)
 
     if config.exists(ipv6_base + ['delegate-prefix']):
         prefix_old = config.return_values(ipv6_base + ['delegate-prefix'])
         # delete old delegate prefix CLI nodes
         config.delete(ipv6_base + ['delegate-prefix'])
         # create ned delegation tag node
         config.set(ipv6_base + ['delegate'])
         config.set_tag(ipv6_base + ['delegate'])
 
         for p in prefix_old:
             prefix = p.split(',')[0]
             mask = p.split(',')[1]
             config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask)
 
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
     except OSError as e:
         print("Failed to save the modified config: {}".format(e))
         exit(1)
diff --git a/src/migration-scripts/pppoe-server/6-to-7 b/src/migration-scripts/pppoe-server/6-to-7
index b94ce57f9..d51c1c9d8 100755
--- a/src/migration-scripts/pppoe-server/6-to-7
+++ b/src/migration-scripts/pppoe-server/6-to-7
@@ -1,119 +1,117 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # - move all pool to named pools
 #       'start-stop' migrate to namedpool 'default-range-pool'
 #       'subnet' migrate to namedpool 'default-subnet-pool'
 #       'default-subnet-pool' is the next pool for 'default-range-pool'
 # - There is only one gateway-address, take the first which is configured
 # - default-pool by migration.
 #       1. If authentication mode = 'local' then it is first named pool.
 #       If there are not named pools, namedless pool will be default.
 #       2. If authentication mode = 'radius' then namedless pool will be default
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 from vyos.base import Warning
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['service', 'pppoe-server']
 pool_base = base + ['client-ip-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 
 default_pool = ''
 range_pool_name = 'default-range-pool'
 
 #Default nameless pools migrations
 if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
     def is_legalrange(ip1: str, ip2: str, mask: str):
         from ipaddress import IPv4Interface
         interface1 = IPv4Interface(f'{ip1}/{mask}')
         interface2 = IPv4Interface(f'{ip2}/{mask}')
         return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip
 
     start_ip = config.return_value(pool_base + ['start'])
     stop_ip = config.return_value(pool_base + ['stop'])
     if is_legalrange(start_ip, stop_ip, '24'):
         ip_range = f'{start_ip}-{stop_ip}'
         config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False)
         default_pool = range_pool_name
     else:
         Warning(
             f'PPPoE client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.')
     config.delete(pool_base + ['start'])
     config.delete(pool_base + ['stop'])
 
 if config.exists(pool_base + ['subnet']):
     default_pool = range_pool_name
     for subnet in config.return_values(pool_base + ['subnet']):
         config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False)
     config.delete(pool_base + ['subnet'])
 
 gateway = ''
 if config.exists(base + ['gateway-address']):
     gateway = config.return_value(base + ['gateway-address'])
 
 #named pool migration
 namedpools_base = pool_base + ['name']
 if config.exists(namedpools_base):
     if config.exists(base + ['authentication', 'mode']):
         if config.return_value(base + ['authentication', 'mode']) == 'local':
             if config.list_nodes(namedpools_base):
                 default_pool = config.list_nodes(namedpools_base)[0]
 
     for pool_name in config.list_nodes(namedpools_base):
         pool_path = namedpools_base + [pool_name]
         if config.exists(pool_path + ['subnet']):
             subnet = config.return_value(pool_path + ['subnet'])
             config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False)
         if config.exists(pool_path + ['next-pool']):
             next_pool = config.return_value(pool_path + ['next-pool'])
             config.set(pool_base + [pool_name, 'next-pool'], value=next_pool)
         if not gateway:
             if config.exists(pool_path + ['gateway-address']):
                 gateway = config.return_value(pool_path + ['gateway-address'])
 
     config.delete(namedpools_base)
 
 if gateway:
     config.set(base + ['gateway-address'], value=gateway)
 if default_pool:
     config.set(base + ['default-pool'], value=default_pool)
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/pppoe-server/7-to-8 b/src/migration-scripts/pppoe-server/7-to-8
index b0d9bb464..0381f0bf9 100755
--- a/src/migration-scripts/pppoe-server/7-to-8
+++ b/src/migration-scripts/pppoe-server/7-to-8
@@ -1,61 +1,58 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # Migrating to named ipv6 pools
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['service', 'pppoe-server']
 pool_base = base + ['client-ipv6-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 
 ipv6_pool_name = 'ipv6-pool'
 config.copy(pool_base, pool_base + [ipv6_pool_name])
 
 if config.exists(pool_base + ['prefix']):
     config.delete(pool_base + ['prefix'])
     config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name)
 if config.exists(pool_base + ['delegate']):
     config.delete(pool_base + ['delegate'])
 
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/pppoe-server/8-to-9 b/src/migration-scripts/pppoe-server/8-to-9
index ad75c28a1..4932a766f 100755
--- a/src/migration-scripts/pppoe-server/8-to-9
+++ b/src/migration-scripts/pppoe-server/8-to-9
@@ -1,69 +1,66 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # Change from 'ccp' to 'disable-ccp' in ppp-option section
 # Migration ipv6 options
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['service', 'pppoe-server']
 if not config.exists(base):
     exit(0)
 
 #CCP migration
 if config.exists(base + ['ppp-options', 'ccp']):
     config.delete(base + ['ppp-options', 'ccp'])
 else:
     config.set(base + ['ppp-options', 'disable-ccp'])
 
 #IPV6 options migrations
 if config.exists(base + ['ppp-options','ipv6-peer-intf-id']):
     intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id'])
     if intf_peer_id == 'ipv4':
         intf_peer_id = 'ipv4-addr'
     config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True)
     config.delete(base + ['ppp-options','ipv6-peer-intf-id'])
 
 if config.exists(base + ['ppp-options','ipv6-intf-id']):
     intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id'])
     config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True)
     config.delete(base + ['ppp-options','ipv6-intf-id'])
 
 if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']):
     config.set(base + ['ppp-options','ipv6-accept-peer-interface-id'])
     config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/pptp/2-to-3 b/src/migration-scripts/pptp/2-to-3
index 091cb68ec..42c4dedf4 100755
--- a/src/migration-scripts/pptp/2-to-3
+++ b/src/migration-scripts/pptp/2-to-3
@@ -1,75 +1,73 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # - move all pool to named pools
 #       'start-stop' migrate to namedpool 'default-range-pool'
 #       'default-subnet-pool' is the next pool for 'default-range-pool'
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 from vyos.base import Warning
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'pptp', 'remote-access']
 pool_base = base + ['client-ip-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 
 range_pool_name = 'default-range-pool'
 
 if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
     def is_legalrange(ip1: str, ip2: str, mask: str):
         from ipaddress import IPv4Interface
         interface1 = IPv4Interface(f'{ip1}/{mask}')
         interface2 = IPv4Interface(f'{ip2}/{mask}')
         return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip
 
     start_ip = config.return_value(pool_base + ['start'])
     stop_ip = config.return_value(pool_base + ['stop'])
     if is_legalrange(start_ip, stop_ip, '24'):
         ip_range = f'{start_ip}-{stop_ip}'
         config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False)
         config.set(base + ['default-pool'], value=range_pool_name)
     else:
         Warning(
             f'PPTP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.')
 
     config.delete(pool_base + ['start'])
     config.delete(pool_base + ['stop'])
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/pptp/3-to-4 b/src/migration-scripts/pptp/3-to-4
index 0a8dad2f4..ebd343028 100755
--- a/src/migration-scripts/pptp/3-to-4
+++ b/src/migration-scripts/pptp/3-to-4
@@ -1,51 +1,48 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # - Move 'mppe' from 'authentication' node to 'ppp-options'
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'pptp', 'remote-access']
 
 if not config.exists(base):
     exit(0)
 
 if config.exists(base + ['authentication','mppe']):
     mppe = config.return_value(base + ['authentication','mppe'])
     config.set(base + ['ppp-options', 'mppe'], value=mppe, replace=True)
     config.delete(base + ['authentication','mppe'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/pptp/4-to-5 b/src/migration-scripts/pptp/4-to-5
index d4b3f9a14..83632b6d8 100755
--- a/src/migration-scripts/pptp/4-to-5
+++ b/src/migration-scripts/pptp/4-to-5
@@ -1,66 +1,63 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # - Move 'require' from 'protocols' in  'authentication' node
 # - Migrate to new default values in radius timeout and acct-timeout
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'pptp', 'remote-access']
 
 if not config.exists(base):
     exit(0)
 
 #migrate require to protocols
 require_path = base + ['authentication', 'require']
 if config.exists(require_path):
     protocols = list(config.return_values(require_path))
     for protocol in protocols:
         config.set(base + ['authentication', 'protocols'], value=protocol,
                    replace=False)
     config.delete(require_path)
 else:
     config.set(base + ['authentication', 'protocols'], value='mschap-v2')
 
 radius_path = base + ['authentication', 'radius']
 if config.exists(radius_path):
     if not config.exists(radius_path + ['timeout']):
         config.set(radius_path + ['timeout'], value=3)
     if not config.exists(radius_path + ['acct-timeout']):
         config.set(radius_path + ['acct-timeout'], value=3)
 
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/snmp/2-to-3 b/src/migration-scripts/snmp/2-to-3
index 30911aa27..ab9b5dcba 100755
--- a/src/migration-scripts/snmp/2-to-3
+++ b/src/migration-scripts/snmp/2-to-3
@@ -1,57 +1,54 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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/>.
 
 # T4857: Implement FRR SNMP recomendations
 #  cli changes from:
 #  set service snmp oid-enable route-table
 #  To
 #  set service snmp oid-enable ip-forward
 
-import re
-
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
-from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['service snmp']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 if config.exists(base + ['oid-enable']):
     config.delete(base + ['oid-enable'])
     config.set(base + ['oid-enable'], 'ip-forward')
 
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/sstp/0-to-1 b/src/migration-scripts/sstp/0-to-1
index e2fe1ea8f..150127aaf 100755
--- a/src/migration-scripts/sstp/0-to-1
+++ b/src/migration-scripts/sstp/0-to-1
@@ -1,129 +1,128 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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/>.
 
 
 # - migrate from "service sstp-server" to "vpn sstp"
 # - remove primary/secondary identifier from nameserver
 # - migrate RADIUS configuration to a more uniform syntax accross the system
 #   - authentication radius-server x.x.x.x to authentication radius server x.x.x.x
 #   - authentication radius-settings to authentication radius
 #   - do not migrate radius server req-limit, use default of unlimited
 # - migrate SSL certificate path
 
-import os
 import sys
 
 from vyos.configtree import ConfigTree
 
 if len(sys.argv) < 2:
     print("Must specify file name!")
     sys.exit(1)
 
 file_name = sys.argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 old_base = ['service', 'sstp-server']
 if not config.exists(old_base):
     # Nothing to do
     sys.exit(0)
 else:
     # ensure new base path exists
     if not config.exists(['vpn']):
         config.set(['vpn'])
 
     new_base = ['vpn', 'sstp']
     # copy entire tree
     config.copy(old_base, new_base)
     config.delete(old_base)
 
     # migrate DNS servers
     dns_base = new_base + ['network-settings', 'dns-server']
     if config.exists(dns_base):
         if config.exists(dns_base + ['primary-dns']):
             dns = config.return_value(dns_base + ['primary-dns'])
             config.set(new_base + ['network-settings', 'name-server'], value=dns, replace=False)
 
         if config.exists(dns_base + ['secondary-dns']):
             dns = config.return_value(dns_base + ['secondary-dns'])
             config.set(new_base + ['network-settings', 'name-server'], value=dns, replace=False)
 
         config.delete(dns_base)
 
 
     # migrate radius options - copy subtree
     # thus must happen before migration of the individual RADIUS servers
     old_options = new_base + ['authentication', 'radius-settings']
     if config.exists(old_options):
         new_options = new_base + ['authentication', 'radius']
         config.copy(old_options, new_options)
         config.delete(old_options)
 
     # migrate radius dynamic author / change of authorisation server
     dae_old = new_base + ['authentication', 'radius', 'dae-server']
     if config.exists(dae_old):
         config.rename(dae_old, 'dynamic-author')
         dae_new = new_base + ['authentication', 'radius', 'dynamic-author']
 
         if config.exists(dae_new + ['ip-address']):
             config.rename(dae_new + ['ip-address'], 'server')
 
         if config.exists(dae_new + ['secret']):
             config.rename(dae_new + ['secret'], 'key')
 
 
     # migrate radius server
     radius_server = new_base + ['authentication', 'radius-server']
     if config.exists(radius_server):
         for server in config.list_nodes(radius_server):
             base = radius_server + [server]
             new = new_base + ['authentication', 'radius', 'server', server]
 
             # convert secret to key
             if config.exists(base + ['secret']):
                 tmp = config.return_value(base + ['secret'])
                 config.set(new + ['key'], value=tmp)
 
             if config.exists(base + ['fail-time']):
                 tmp = config.return_value(base + ['fail-time'])
                 config.set(new + ['fail-time'], value=tmp)
 
         config.set_tag(new_base + ['authentication', 'radius', 'server'])
         config.delete(radius_server)
 
     # migrate SSL certificates
     old_ssl = new_base + ['sstp-settings']
     new_ssl = new_base + ['ssl']
     config.copy(old_ssl + ['ssl-certs'], new_ssl)
     config.delete(old_ssl)
 
     if config.exists(new_ssl + ['ca']):
         config.rename(new_ssl + ['ca'], 'ca-cert-file')
 
     if config.exists(new_ssl + ['server-cert']):
         config.rename(new_ssl + ['server-cert'], 'cert-file')
 
     if config.exists(new_ssl + ['server-key']):
         config.rename(new_ssl + ['server-key'], 'key-file')
 
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
     except OSError as e:
         print("Failed to save the modified config: {}".format(e))
         sys.exit(1)
diff --git a/src/migration-scripts/sstp/3-to-4 b/src/migration-scripts/sstp/3-to-4
index 00ca7a52d..5b7757e60 100755
--- a/src/migration-scripts/sstp/3-to-4
+++ b/src/migration-scripts/sstp/3-to-4
@@ -1,136 +1,135 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# 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/>.
 
 # - Update SSL to use PKI configuration
 
 import os
 
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 from vyos.pki import load_certificate
-from vyos.pki import load_crl
 from vyos.pki import load_private_key
 from vyos.pki import encode_certificate
 from vyos.pki import encode_private_key
 from vyos.utils.process import run
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'sstp']
 pki_base = ['pki']
 
 if not config.exists(base):
     exit(0)
 
 AUTH_DIR = '/config/auth'
 
 def wrapped_pem_to_config_value(pem):
     return "".join(pem.strip().split("\n")[1:-1])
 
 if not config.exists(base + ['ssl']):
     exit(0)
 
 x509_base = base + ['ssl']
 pki_name = 'sstp'
 
 if not config.exists(pki_base + ['ca']):
     config.set(pki_base + ['ca'])
     config.set_tag(pki_base + ['ca'])
 
 if not config.exists(pki_base + ['certificate']):
     config.set(pki_base + ['certificate'])
     config.set_tag(pki_base + ['certificate'])
 
 if config.exists(x509_base + ['ca-cert-file']):
     cert_file = config.return_value(x509_base + ['ca-cert-file'])
     cert_path = os.path.join(AUTH_DIR, cert_file)
     cert = None
 
     if os.path.isfile(cert_path):
         if not os.access(cert_path, os.R_OK):
             run(f'sudo chmod 644 {cert_path}')
 
         with open(cert_path, 'r') as f:
             cert_data = f.read()
             cert = load_certificate(cert_data, wrap_tags=False)
 
     if cert:
         cert_pem = encode_certificate(cert)
         config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
         config.set(x509_base + ['ca-certificate'], value=pki_name)
     else:
         print(f'Failed to migrate CA certificate on sstp config')
 
     config.delete(x509_base + ['ca-cert-file'])
 
 if config.exists(x509_base + ['cert-file']):
     cert_file = config.return_value(x509_base + ['cert-file'])
     cert_path = os.path.join(AUTH_DIR, cert_file)
     cert = None
 
     if os.path.isfile(cert_path):
         if not os.access(cert_path, os.R_OK):
             run(f'sudo chmod 644 {cert_path}')
 
         with open(cert_path, 'r') as f:
             cert_data = f.read()
             cert = load_certificate(cert_data, wrap_tags=False)
 
     if cert:
         cert_pem = encode_certificate(cert)
         config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
         config.set(x509_base + ['certificate'], value=pki_name)
     else:
         print(f'Failed to migrate certificate on sstp config')
 
     config.delete(x509_base + ['cert-file'])
 
 if config.exists(x509_base + ['key-file']):
     key_file = config.return_value(x509_base + ['key-file'])
     key_path = os.path.join(AUTH_DIR, key_file)
     key = None
 
     if os.path.isfile(key_path):
         if not os.access(key_path, os.R_OK):
             run(f'sudo chmod 644 {key_path}')
 
         with open(key_path, 'r') as f:
             key_data = f.read()
             key = load_private_key(key_data, passphrase=None, wrap_tags=False)
 
     if key:
         key_pem = encode_private_key(key, passphrase=None)
         config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
     else:
         print(f'Failed to migrate private key on sstp config')
 
     config.delete(x509_base + ['key-file'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/sstp/4-to-5 b/src/migration-scripts/sstp/4-to-5
index 95e482713..6907240a0 100755
--- a/src/migration-scripts/sstp/4-to-5
+++ b/src/migration-scripts/sstp/4-to-5
@@ -1,62 +1,59 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-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/>.
 
 # - move all pool to named pools
 #       'subnet' migrate to namedpool 'default-subnet-pool'
 #       'default-subnet-pool' is the next pool for 'default-range-pool'
 
-import os
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'sstp']
 pool_base = base + ['client-ip-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 
 range_pool_name = 'default-range-pool'
 
 if config.exists(pool_base + ['subnet']):
     default_pool = range_pool_name
     for subnet in config.return_values(pool_base + ['subnet']):
         config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False)
     config.delete(pool_base + ['subnet'])
     config.set(base + ['default-pool'], value=default_pool)
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/sstp/5-to-6 b/src/migration-scripts/sstp/5-to-6
index bac9975b2..43b99044d 100755
--- a/src/migration-scripts/sstp/5-to-6
+++ b/src/migration-scripts/sstp/5-to-6
@@ -1,62 +1,58 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 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/>.
 
 # Migrating to named ipv6 pools
 
-import os
-import pprint
-
 from sys import argv
 from sys import exit
 from vyos.configtree import ConfigTree
 
-
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['vpn', 'sstp']
 pool_base = base + ['client-ipv6-pool']
 if not config.exists(base):
     exit(0)
 
 if not config.exists(pool_base):
     exit(0)
 
 ipv6_pool_name = 'ipv6-pool'
 config.copy(pool_base, pool_base + [ipv6_pool_name])
 
 if config.exists(pool_base + ['prefix']):
     config.delete(pool_base + ['prefix'])
     config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name)
 if config.exists(pool_base + ['delegate']):
     config.delete(pool_base + ['delegate'])
 
 # format as tag node
 config.set_tag(pool_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/system/15-to-16 b/src/migration-scripts/system/15-to-16
index aa1c34032..2944cdb1e 100755
--- a/src/migration-scripts/system/15-to-16
+++ b/src/migration-scripts/system/15-to-16
@@ -1,37 +1,36 @@
 #!/usr/bin/env python3
 #
 # Make 'system options reboot-on-panic' valueless
 
-import os
 import sys
 
 from vyos.configtree import ConfigTree
 
 if len(sys.argv) < 2:
     print("Must specify file name!")
     sys.exit(1)
 
 file_name = sys.argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['system', 'options']
 if not config.exists(base):
     # Nothing to do
     sys.exit(0)
 else:
     if config.exists(base + ['reboot-on-panic']):
         reboot = config.return_value(base + ['reboot-on-panic'])
         config.delete(base + ['reboot-on-panic'])
         # create new valueless node if action was true
         if reboot == "true":
             config.set(base + ['reboot-on-panic'])
 
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
     except OSError as e:
         print("Failed to save the modified config: {}".format(e))
         sys.exit(1)
diff --git a/src/migration-scripts/system/16-to-17 b/src/migration-scripts/system/16-to-17
index 37e02611d..afa171a9b 100755
--- a/src/migration-scripts/system/16-to-17
+++ b/src/migration-scripts/system/16-to-17
@@ -1,55 +1,54 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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/>.
 
 # * remove "system login user <user> group" node, Why should be add a user to a
 #   3rd party group when the system is fully managed by CLI?
 # * remove "system login user <user> level" node
 #   This is the only privilege level left and also the default, what is the
 #   sense in keeping this orphaned node?
 
-import os
 import sys
 
 from vyos.configtree import ConfigTree
 
 if len(sys.argv) < 2:
     print("Must specify file name!")
     sys.exit(1)
 
 file_name = sys.argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 config = ConfigTree(config_file)
 base = ['system', 'login', 'user']
 if not config.exists(base):
     # Nothing to do
     sys.exit(0)
 else:
     for user in config.list_nodes(base):
         if config.exists(base + [user, 'group']):
             config.delete(base + [user, 'group'])
 
         if config.exists(base + [user, 'level']):
             config.delete(base + [user, 'level'])
 
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
     except OSError as e:
         print("Failed to save the modified config: {}".format(e))
         sys.exit(1)
diff --git a/src/migration-scripts/system/19-to-20 b/src/migration-scripts/system/19-to-20
index c04e6a5a6..177173c50 100755
--- a/src/migration-scripts/system/19-to-20
+++ b/src/migration-scripts/system/19-to-20
@@ -1,64 +1,62 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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/>.
 
 # T3048: remove smp-affinity node from ethernet and use tuned instead
 
-import os
-
 from sys import exit, argv
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['system', 'options']
 base_new = ['system', 'option']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 if config.exists(base_new):
     for node in config.list_nodes(base):
         config.copy(base + [node], base_new + [node])
 else:
     config.copy(base, base_new)
 
 config.delete(base)
 
 # Rename "system option beep-if-fully-booted" -> "system option startup-beep"
 base_beep = base_new + ['beep-if-fully-booted']
 if config.exists(base_beep):
     config.rename(base_beep, 'startup-beep')
 
 # Rename "system option ctrl-alt-del-action" -> "system option ctrl-alt-delete"
 base_ctrl_alt_del = base_new + ['ctrl-alt-del-action']
 if config.exists(base_ctrl_alt_del):
     config.rename(base_ctrl_alt_del, 'ctrl-alt-delete')
 
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/system/20-to-21 b/src/migration-scripts/system/20-to-21
index 4bcf4edab..24e042ce2 100755
--- a/src/migration-scripts/system/20-to-21
+++ b/src/migration-scripts/system/20-to-21
@@ -1,48 +1,46 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2021 VyOS maintainers and contributors
+# 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/>.
 
 # T3795: merge "system name-servers-dhcp" into "system name-server"
 
-import os
-
 from sys import argv
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['system', 'name-servers-dhcp']
 config = ConfigTree(config_file)
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 for interface in config.return_values(base):
     config.set(['system', 'name-server'], value=interface, replace=False)
 
 config.delete(base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/system/21-to-22 b/src/migration-scripts/system/21-to-22
index 810b634ab..2a1b603c6 100755
--- a/src/migration-scripts/system/21-to-22
+++ b/src/migration-scripts/system/21-to-22
@@ -1,57 +1,55 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2021 VyOS maintainers and contributors
+# 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/>.
 
-import os
-
 from sys import exit, argv
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['system', 'sysctl']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 for all_custom in ['all', 'custom']:
     if config.exists(base + [all_custom]):
         for key in config.list_nodes(base + [all_custom]):
             tmp = config.return_value(base + [all_custom, key, 'value'])
             config.set(base + ['parameter', key, 'value'], value=tmp)
             config.set_tag(base + ['parameter'])
         config.delete(base + [all_custom])
 
 for ipv4_param in ['net.ipv4.igmp_max_memberships', 'net.ipv4.ipfrag_time']:
     if config.exists(base + [ipv4_param]):
         tmp = config.return_value(base + [ipv4_param])
         config.set(base + ['parameter', ipv4_param, 'value'], value=tmp)
         config.set_tag(base + ['parameter'])
         config.delete(base + [ipv4_param])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
diff --git a/src/migration-scripts/system/22-to-23 b/src/migration-scripts/system/22-to-23
index 8ed198383..f83279b88 100755
--- a/src/migration-scripts/system/22-to-23
+++ b/src/migration-scripts/system/22-to-23
@@ -1,50 +1,48 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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/>.
 
-import os
-
 from sys import exit, argv
 from vyos.configtree import ConfigTree
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['system', 'ipv6']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 # T4346: drop support to disbale IPv6 address family within the OS Kernel
 if config.exists(base + ['disable']):
     config.delete(base + ['disable'])
     # IPv6 address family disable was the only CLI option set - we can cleanup
     # the entire tree
     if len(config.list_nodes(base)) == 0:
         config.delete(base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/migration-scripts/system/23-to-24 b/src/migration-scripts/system/23-to-24
index fd68dbf22..1fd61d83b 100755
--- a/src/migration-scripts/system/23-to-24
+++ b/src/migration-scripts/system/23-to-24
@@ -1,89 +1,87 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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/>.
 
-import os
-
 from ipaddress import ip_interface
 from ipaddress import ip_address
 from sys import exit, argv
 from vyos.configtree import ConfigTree
 from vyos.template import is_ipv4
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['protocols', 'static', 'arp']
 tmp_base = ['protocols', 'static', 'arp-tmp']
 config = ConfigTree(config_file)
 
 def fixup_cli(config, path, interface):
     if config.exists(path + ['address']):
         for address in config.return_values(path + ['address']):
             tmp = ip_interface(address)
             # ARP is only available for IPv4 ;-)
             if not is_ipv4(tmp):
                 continue
             if ip_address(host) in tmp.network.hosts():
                 mac = config.return_value(tmp_base + [host, 'hwaddr'])
                 iface_path = ['protocols', 'static', 'arp', 'interface']
                 config.set(iface_path + [interface, 'address', host, 'mac'], value=mac)
                 config.set_tag(iface_path)
                 config.set_tag(iface_path + [interface, 'address'])
                 continue
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 # We need a temporary copy of the config tree as the original one needs to be
 # deleted first due to a change iun thge tagNode structure.
 config.copy(base, tmp_base)
 config.delete(base)
 
 for host in config.list_nodes(tmp_base):
     for type in config.list_nodes(['interfaces']):
         for interface in config.list_nodes(['interfaces', type]):
             if_base = ['interfaces', type, interface]
             fixup_cli(config, if_base, interface)
 
             if config.exists(if_base + ['vif']):
                 for vif in config.list_nodes(if_base + ['vif']):
                     vif_base = ['interfaces', type, interface, 'vif', vif]
                     fixup_cli(config, vif_base, f'{interface}.{vif}')
 
             if config.exists(if_base + ['vif-s']):
                 for vif_s in config.list_nodes(if_base + ['vif-s']):
                     vif_s_base = ['interfaces', type, interface, 'vif-s', vif_s]
                     fixup_cli(config, vif_s_base, f'{interface}.{vif_s}')
 
                     if config.exists(if_base + ['vif-s', vif_s, 'vif-c']):
                         for vif_c in config.list_nodes(if_base + ['vif-s', vif_s, 'vif-c']):
                             vif_c_base = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c', vif_c]
                             fixup_cli(config, vif_c_base, f'{interface}.{vif_s}.{vif_c}')
 
 config.delete(tmp_base)
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print(f'Failed to save the modified config: {e}')
     exit(1)
diff --git a/src/op_mode/clear_dhcp_lease.py b/src/op_mode/clear_dhcp_lease.py
index f372d3af0..b37b62bca 100755
--- a/src/op_mode/clear_dhcp_lease.py
+++ b/src/op_mode/clear_dhcp_lease.py
@@ -1,78 +1,77 @@
 #!/usr/bin/env python3
 
 import argparse
 import re
 
-from isc_dhcp_leases import Lease
 from isc_dhcp_leases import IscDhcpLeases
 
 from vyos.configquery import ConfigTreeQuery
 from vyos.utils.io import ask_yes_no
 from vyos.utils.process import call
 from vyos.utils.commit import commit_in_progress
 
 
 config = ConfigTreeQuery()
 base = ['service', 'dhcp-server']
 lease_file = '/config/dhcpd.leases'
 
 
 def del_lease_ip(address):
     """
     Read lease_file and write data to this file
     without specific section "lease ip"
     Delete section "lease x.x.x.x { x;x;x; }"
     """
     with open(lease_file, encoding='utf-8') as f:
         data = f.read().rstrip()
         lease_config_ip = '{(?P<config>[\s\S]+?)\n}'
         pattern = rf"lease {address} {lease_config_ip}"
         # Delete lease for ip block
         data = re.sub(pattern, '', data)
 
     # Write new data to original lease_file
     with open(lease_file, 'w', encoding='utf-8') as f:
         f.write(data)
 
 def is_ip_in_leases(address):
     """
     Return True if address found in the lease file
     """
     leases = IscDhcpLeases(lease_file)
     lease_ips = []
     for lease in leases.get():
         lease_ips.append(lease.ip)
     if address not in lease_ips:
         print(f'Address "{address}" not found in "{lease_file}"')
         return False
     return True
 
 
 if not config.exists(base):
     print('DHCP-server not configured!')
     exit(0)
 
 if config.exists(base + ['failover']):
     print('Lease cannot be reset in failover mode!')
     exit(0)
 
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
     parser.add_argument('--ip', help='IPv4 address', action='store', required=True)
 
     args = parser.parse_args()
     address = args.ip
 
     if not is_ip_in_leases(address):
         exit(1)
 
     if commit_in_progress():
         print('Cannot clear DHCP lease while a commit is in progress')
         exit(1)
 
     if not ask_yes_no(f'This will restart DHCP server.\nContinue?'):
         exit(1)
     else:
         del_lease_ip(address)
         call('systemctl restart isc-dhcp-server.service')
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index 2f90865fd..a7143d664 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -1,435 +1,432 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2022-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/>.
 
 import os
 import sys
 import typing
 
 from datetime import datetime
 from glob import glob
 from ipaddress import ip_address
 from isc_dhcp_leases import IscDhcpLeases
 from tabulate import tabulate
 
 import vyos.opmode
 
 from vyos.base import Warning
 from vyos.configquery import ConfigTreeQuery
 
-from vyos.utils.dict import dict_search
-from vyos.utils.file import read_file
-from vyos.utils.process import cmd
 from vyos.utils.process import is_systemd_service_running
 from vyos.utils.process import call
 
 time_string = "%a %b %d %H:%M:%S %Z %Y"
 
 config = ConfigTreeQuery()
 lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
 sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state']
 sort_valid_inet6 = ['end', 'iaid_duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type']
 
 ArgFamily = typing.Literal['inet', 'inet6']
 ArgState = typing.Literal['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
 ArgOrigin = typing.Literal['local', 'remote']
 
 def _utc_to_local(utc_dt):
     return datetime.fromtimestamp((datetime.fromtimestamp(utc_dt) - datetime(1970, 1, 1)).total_seconds())
 
 
 def _format_hex_string(in_str):
     out_str = ""
     # if input is divisible by 2, add : every 2 chars
     if len(in_str) > 0 and len(in_str) % 2 == 0:
         out_str = ':'.join(a+b for a,b in zip(in_str[::2], in_str[1::2]))
     else:
         out_str = in_str
 
     return out_str
 
 
 def _find_list_of_dict_index(lst, key='ip', value='') -> int:
     """
     Find the index entry of list of dict matching the dict value
     Exampe:
         % lst = [{'ip': '192.0.2.1'}, {'ip': '192.0.2.2'}]
         % _find_list_of_dict_index(lst, key='ip', value='192.0.2.2')
         % 1
     """
     idx = next((index for (index, d) in enumerate(lst) if d[key] == value), None)
     return idx
 
 
 def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], origin=None) -> list:
     """
     Get DHCP server leases
     :return list
     """
     lease_file = '/config/dhcpdv6.leases' if family == 'inet6' else '/config/dhcpd.leases'
     data = []
     leases = IscDhcpLeases(lease_file).get(include_backups=True)
     if pool is None:
         pool = _get_dhcp_pools(family=family)
         aux = False
     else:
         pool = [pool]
         aux = True
 
     ## Search leases for every pool
     for pool_name in pool:
         for lease in leases:
             if lease.sets.get('shared-networkname', '') == pool_name or lease.sets.get('shared-networkname', '') == '':
             #if lease.sets.get('shared-networkname', '') == pool_name:
                 data_lease = {}
                 data_lease['ip'] = lease.ip
                 data_lease['state'] = lease.binding_state
                 #data_lease['pool'] = pool_name if lease.sets.get('shared-networkname', '') != '' else 'Fail-Over Server'
                 data_lease['pool'] = lease.sets.get('shared-networkname', '')
                 data_lease['end'] = lease.end.timestamp() if lease.end else None
                 data_lease['origin'] = 'local' if data_lease['pool'] != '' else 'remote'
 
                 if family == 'inet':
                     data_lease['mac'] = lease.ethernet
                     data_lease['start'] = lease.start.timestamp()
                     data_lease['hostname'] = lease.hostname
 
                 if family == 'inet6':
                     data_lease['last_communication'] = lease.last_communication.timestamp()
                     data_lease['iaid_duid'] = _format_hex_string(lease.host_identifier_string)
                     lease_types_long = {'na': 'non-temporary', 'ta': 'temporary', 'pd': 'prefix delegation'}
                     data_lease['type'] = lease_types_long[lease.type]
 
                 data_lease['remaining'] = '-'
 
                 if lease.end:
                     data_lease['remaining'] = lease.end - datetime.utcnow()
 
                     if data_lease['remaining'].days >= 0:
                         # substraction gives us a timedelta object which can't be formatted with strftime
                         # so we use str(), split gets rid of the microseconds
                         data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0]
 
                 # Do not add old leases
                 if data_lease['remaining'] != '' and data_lease['state'] != 'free':
                     if not state or data_lease['state'] in state or state == 'all':
                         if not origin or data_lease['origin'] in origin:
                             if not aux or (aux and data_lease['pool'] == pool_name):
                                 data.append(data_lease)
 
                 # deduplicate
                 checked = []
                 for entry in data:
                     addr = entry.get('ip')
                     if addr not in checked:
                         checked.append(addr)
                     else:
                         idx = _find_list_of_dict_index(data, key='ip', value=addr)
                         data.pop(idx)
 
     if sorted:
         if sorted == 'ip':
             data.sort(key = lambda x:ip_address(x['ip']))
         else:
             data.sort(key = lambda x:x[sorted])
     return data
 
 
 def _get_formatted_server_leases(raw_data, family='inet'):
     data_entries = []
     if family == 'inet':
         for lease in raw_data:
             ipaddr = lease.get('ip')
             hw_addr = lease.get('mac')
             state = lease.get('state')
             start = lease.get('start')
             start =  _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S')
             end = lease.get('end')
             end =  _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') if end else '-'
             remain = lease.get('remaining')
             pool = lease.get('pool')
             hostname = lease.get('hostname')
             origin = lease.get('origin')
             data_entries.append([ipaddr, hw_addr, state, start, end, remain, pool, hostname, origin])
 
         headers = ['IP Address', 'MAC address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool',
                    'Hostname', 'Origin']
 
     if family == 'inet6':
         for lease in raw_data:
             ipaddr = lease.get('ip')
             state = lease.get('state')
             start = lease.get('last_communication')
             start =  _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S')
             end = lease.get('end')
             end =  _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S')
             remain = lease.get('remaining')
             lease_type = lease.get('type')
             pool = lease.get('pool')
             host_identifier = lease.get('iaid_duid')
             data_entries.append([ipaddr, state, start, end, remain, lease_type, pool, host_identifier])
 
         headers = ['IPv6 address', 'State', 'Last communication', 'Lease expiration', 'Remaining', 'Type', 'Pool',
                    'IAID_DUID']
 
     output = tabulate(data_entries, headers, numalign='left')
     return output
 
 
 def _get_dhcp_pools(family='inet') -> list:
     v = 'v6' if family == 'inet6' else ''
     pools = config.list_nodes(f'service dhcp{v}-server shared-network-name')
     return pools
 
 
 def _get_pool_size(pool, family='inet'):
     v = 'v6' if family == 'inet6' else ''
     base = f'service dhcp{v}-server shared-network-name {pool}'
     size = 0
     subnets = config.list_nodes(f'{base} subnet')
     for subnet in subnets:
         if family == 'inet6':
             ranges = config.list_nodes(f'{base} subnet {subnet} address-range start')
         else:
             ranges = config.list_nodes(f'{base} subnet {subnet} range')
         for range in ranges:
             if family == 'inet6':
                 start = config.list_nodes(f'{base} subnet {subnet} address-range start')[0]
                 stop = config.value(f'{base} subnet {subnet} address-range start {start} stop')
             else:
                 start = config.value(f'{base} subnet {subnet} range {range} start')
                 stop = config.value(f'{base} subnet {subnet} range {range} stop')
             # Add +1 because both range boundaries are inclusive
             size += int(ip_address(stop)) - int(ip_address(start)) + 1
     return size
 
 
 def _get_raw_pool_statistics(family='inet', pool=None):
     if pool is None:
         pool = _get_dhcp_pools(family=family)
     else:
         pool = [pool]
 
     v = 'v6' if family == 'inet6' else ''
     stats = []
     for p in pool:
         subnet = config.list_nodes(f'service dhcp{v}-server shared-network-name {p} subnet')
         size = _get_pool_size(family=family, pool=p)
         leases = len(_get_raw_server_leases(family=family, pool=p))
         use_percentage = round(leases / size * 100) if size != 0 else 0
         pool_stats = {'pool': p, 'size': size, 'leases': leases,
                       'available': (size - leases), 'use_percentage': use_percentage, 'subnet': subnet}
         stats.append(pool_stats)
     return stats
 
 
 def _get_formatted_pool_statistics(pool_data, family='inet'):
     data_entries = []
     for entry in pool_data:
         pool = entry.get('pool')
         size = entry.get('size')
         leases = entry.get('leases')
         available = entry.get('available')
         use_percentage = entry.get('use_percentage')
         use_percentage = f'{use_percentage}%'
         data_entries.append([pool, size, leases, available, use_percentage])
 
     headers = ['Pool', 'Size','Leases', 'Available', 'Usage']
     output = tabulate(data_entries, headers, numalign='left')
     return output
 
 
 def _verify(func):
     """Decorator checks if DHCP(v6) config exists"""
     from functools import wraps
 
     @wraps(func)
     def _wrapper(*args, **kwargs):
         config = ConfigTreeQuery()
         family = kwargs.get('family')
         v = 'v6' if family == 'inet6' else ''
         unconf_message = f'DHCP{v} server is not configured'
         # Check if config does not exist
         if not config.exists(f'service dhcp{v}-server'):
             raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
         return func(*args, **kwargs)
     return _wrapper
 
 def _verify_client(func):
     """Decorator checks if interface is configured as DHCP client"""
     from functools import wraps
     from vyos.ifconfig import Section
 
     @wraps(func)
     def _wrapper(*args, **kwargs):
         config = ConfigTreeQuery()
         family = kwargs.get('family')
         v = 'v6' if family == 'inet6' else ''
         interface = kwargs.get('interface')
         interface_path = Section.get_config_path(interface)
         unconf_message = f'DHCP{v} client not configured on interface {interface}!'
 
         # Check if config does not exist
         if not config.exists(f'interfaces {interface_path} address dhcp{v}'):
             raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
         return func(*args, **kwargs)
     return _wrapper
 
 @_verify
 def show_pool_statistics(raw: bool, family: ArgFamily, pool: typing.Optional[str]):
     pool_data = _get_raw_pool_statistics(family=family, pool=pool)
     if raw:
         return pool_data
     else:
         return _get_formatted_pool_statistics(pool_data, family=family)
 
 
 @_verify
 def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str],
                        sorted: typing.Optional[str], state: typing.Optional[ArgState],
                        origin: typing.Optional[ArgOrigin] ):
     # if dhcp server is down, inactive leases may still be shown as active, so warn the user.
     v = '6' if family == 'inet6' else ''
     service_name = 'DHCPv6' if family == 'inet6' else 'DHCP'
     if not is_systemd_service_running(f'isc-dhcp-server{v}.service'):
         Warning(f'{service_name} server is configured but not started. Data may be stale.')
 
     v = 'v6' if family == 'inet6' else ''
     if pool and pool not in _get_dhcp_pools(family=family):
         raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!')
 
     if state and state not in lease_valid_states:
         raise vyos.opmode.IncorrectValue(f'DHCP{v} state "{state}" is invalid!')
 
     sort_valid = sort_valid_inet6 if family == 'inet6' else sort_valid_inet
     if sorted and sorted not in sort_valid:
         raise vyos.opmode.IncorrectValue(f'DHCP{v} sort "{sorted}" is invalid!')
 
     lease_data = _get_raw_server_leases(family=family, pool=pool, sorted=sorted, state=state, origin=origin)
     if raw:
         return lease_data
     else:
         return _get_formatted_server_leases(lease_data, family=family)
 
 
 def _get_raw_client_leases(family='inet', interface=None):
     from time import mktime
     from datetime import datetime
     from vyos.defaults import directories
     from vyos.utils.network import get_interface_vrf
 
     lease_dir = directories['isc_dhclient_dir']
     lease_files = []
     lease_data = []
 
     if interface:
         tmp = f'{lease_dir}/dhclient_{interface}.lease'
         if os.path.exists(tmp):
             lease_files.append(tmp)
     else:
         # All DHCP leases
         lease_files = glob(f'{lease_dir}/dhclient_*.lease')
 
     for lease in lease_files:
         tmp = {}
         with open(lease, 'r') as f:
             for line in f.readlines():
                 line = line.rstrip()
                 if 'last_update' not in tmp:
                     # ISC dhcp client contains least_update timestamp in human readable
                     # format this makes less sense for an API and also the expiry
                     # timestamp is provided in UNIX time. Convert string (e.g. Sun Jul
                     # 30 18:13:44 CEST 2023) to UNIX time (1690733624)
                     tmp.update({'last_update' : int(mktime(datetime.strptime(line, time_string).timetuple()))})
                     continue
 
                 k, v = line.split('=')
                 tmp.update({k : v.replace("'", "")})
 
         if 'interface' in tmp:
             vrf = get_interface_vrf(tmp['interface'])
             if vrf: tmp.update({'vrf' : vrf})
 
         lease_data.append(tmp)
 
     return lease_data
 
 def _get_formatted_client_leases(lease_data, family):
     from time import localtime
     from time import strftime
 
     from vyos.utils.network import is_intf_addr_assigned
 
     data_entries = []
     for lease in lease_data:
         if not lease.get('new_ip_address'):
             continue
         data_entries.append(["Interface", lease['interface']])
         if 'new_ip_address' in lease:
             tmp = '[Active]' if is_intf_addr_assigned(lease['interface'], lease['new_ip_address']) else '[Inactive]'
             data_entries.append(["IP address", lease['new_ip_address'], tmp])
         if 'new_subnet_mask' in lease:
             data_entries.append(["Subnet Mask", lease['new_subnet_mask']])
         if 'new_domain_name' in lease:
             data_entries.append(["Domain Name", lease['new_domain_name']])
         if 'new_routers' in lease:
             data_entries.append(["Router", lease['new_routers']])
         if 'new_domain_name_servers' in lease:
             data_entries.append(["Name Server", lease['new_domain_name_servers']])
         if 'new_dhcp_server_identifier' in lease:
             data_entries.append(["DHCP Server", lease['new_dhcp_server_identifier']])
         if 'new_dhcp_lease_time' in lease:
             data_entries.append(["DHCP Server", lease['new_dhcp_lease_time']])
         if 'vrf' in lease:
             data_entries.append(["VRF", lease['vrf']])
         if 'last_update' in lease:
             tmp = strftime(time_string, localtime(int(lease['last_update'])))
             data_entries.append(["Last Update", tmp])
         if 'new_expiry' in lease:
             tmp = strftime(time_string, localtime(int(lease['new_expiry'])))
             data_entries.append(["Expiry", tmp])
 
         # Add empty marker
         data_entries.append([''])
 
     output = tabulate(data_entries, tablefmt='plain')
 
     return output
 
 def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[str]):
     lease_data = _get_raw_client_leases(family=family, interface=interface)
     if raw:
         return lease_data
     else:
         return _get_formatted_client_leases(lease_data, family=family)
 
 @_verify_client
 def renew_client_lease(raw: bool, family: ArgFamily, interface: str):
     if not raw:
         v = 'v6' if family == 'inet6' else ''
         print(f'Restarting DHCP{v} client on interface {interface}...')
     if family == 'inet6':
         call(f'systemctl restart dhcp6c@{interface}.service')
     else:
         call(f'systemctl restart dhclient@{interface}.service')
 
 if __name__ == '__main__':
     try:
         res = vyos.opmode.run(sys.modules[__name__])
         if res:
             print(res)
     except (ValueError, vyos.opmode.Error) as e:
         print(e)
         sys.exit(1)