diff --git a/debian/rules b/debian/rules
index d007089a4..9da40465f 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,134 +1,139 @@
 #!/usr/bin/make -f
 
 DIR := debian/tmp
 VYOS_SBIN_DIR := usr/sbin
 VYOS_BIN_DIR := usr/bin
 VYOS_LIBEXEC_DIR := usr/libexec/vyos
 VYOS_DATA_DIR := usr/share/vyos
 VYOS_CFG_TMPL_DIR := opt/vyatta/share/vyatta-cfg/templates
 VYOS_OP_TMPL_DIR := opt/vyatta/share/vyatta-op/templates
 VYOS_MIBS_DIR := usr/share/snmp/mibs
 VYOS_LOCALUI_DIR := srv/localui
 
 MIGRATION_SCRIPTS_DIR := opt/vyatta/etc/config-migrate/migrate
+ACTIVATION_SCRIPTS_DIR := usr/libexec/vyos/activate
 SYSTEM_SCRIPTS_DIR := usr/libexec/vyos/system
 SERVICES_DIR := usr/libexec/vyos/services
 
 DEB_TARGET_ARCH := $(shell dpkg-architecture -qDEB_TARGET_ARCH)
 
 %:
 	dh $@ --with python3, --with quilt
 
 # Skip dh_strip_nondeterminism - this is very time consuming
 # and we have no non deterministic output (yet)
 override_dh_strip_nondeterminism:
 
 override_dh_gencontrol:
 	dh_gencontrol -- -v$(shell (git describe --tags --long --match 'vyos/*' --match '1.4.*' --dirty 2>/dev/null || echo 0.0-no.git.tag) | sed -E 's%vyos/%%' | sed -E 's%-dirty%+dirty%')
 
 override_dh_auto_build:
 	make all
 
 override_dh_auto_install:
 	dh_auto_install
 
 	cd python; python3 setup.py install --install-layout=deb --root ../$(DIR); cd ..
 
 	# Install scripts
 	mkdir -p $(DIR)/$(VYOS_SBIN_DIR)
 	mkdir -p $(DIR)/$(VYOS_BIN_DIR)
 	cp -r src/utils/* $(DIR)/$(VYOS_BIN_DIR)
 	cp src/shim/vyshim $(DIR)/$(VYOS_SBIN_DIR)
 
 	# Install conf mode scripts
 	mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/conf_mode
 	cp -r src/conf_mode/* $(DIR)/$(VYOS_LIBEXEC_DIR)/conf_mode
 
 	# Install op mode scripts
 	mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/op_mode
 	cp -r src/op_mode/* $(DIR)/$(VYOS_LIBEXEC_DIR)/op_mode
 
 	# Install op mode scripts
 	mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/init
 	cp -r src/init/* $(DIR)/$(VYOS_LIBEXEC_DIR)/init
 
 	# Install validators
 	mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/validators
 	cp -r src/validators/* $(DIR)/$(VYOS_LIBEXEC_DIR)/validators
 
 	# Install completion helpers
 	mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/completion
 	cp -r src/completion/* $(DIR)/$(VYOS_LIBEXEC_DIR)/completion
 
 	# Install helper scripts
 	cp -r src/helpers/* $(DIR)/$(VYOS_LIBEXEC_DIR)/
 
 	# Install migration scripts
 	mkdir -p $(DIR)/$(MIGRATION_SCRIPTS_DIR)
 	cp -r src/migration-scripts/* $(DIR)/$(MIGRATION_SCRIPTS_DIR)
 
+	# Install activation scripts
+	mkdir -p $(DIR)/$(ACTIVATION_SCRIPTS_DIR)
+	cp -r src/activation-scripts/* $(DIR)/$(ACTIVATION_SCRIPTS_DIR)
+
 	# Install system scripts
 	mkdir -p $(DIR)/$(SYSTEM_SCRIPTS_DIR)
 	cp -r src/system/* $(DIR)/$(SYSTEM_SCRIPTS_DIR)
 
 	# Install system services
 	mkdir -p $(DIR)/$(SERVICES_DIR)
 	cp -r src/services/* $(DIR)/$(SERVICES_DIR)
 
 	# Install configuration command definitions
 	mkdir -p $(DIR)/$(VYOS_CFG_TMPL_DIR)
 	cp -r templates-cfg/* $(DIR)/$(VYOS_CFG_TMPL_DIR)
 
 	# Install operational command definitions
 	mkdir -p $(DIR)/$(VYOS_OP_TMPL_DIR)
 	cp -r templates-op/* $(DIR)/$(VYOS_OP_TMPL_DIR)
 
 	# Install data files
 	mkdir -p $(DIR)/$(VYOS_DATA_DIR)
 	cp -r data/* $(DIR)/$(VYOS_DATA_DIR)
 
 	# Create localui dir
 	mkdir -p $(DIR)/$(VYOS_LOCALUI_DIR)
 
 	# Install SNMP MIBs
 	mkdir -p $(DIR)/$(VYOS_MIBS_DIR)
 	cp -d mibs/* $(DIR)/$(VYOS_MIBS_DIR)
 
 	# Install etc configuration files
 	mkdir -p $(DIR)/etc
 	cp -r src/etc/* $(DIR)/etc
 
 	# Install PAM configuration snippets
 	mkdir -p $(DIR)/usr/share/pam-configs
 	cp -r src/pam-configs/* $(DIR)/usr/share/pam-configs
 
 	# Install systemd service units
 	mkdir -p $(DIR)/lib/systemd/system
 	cp -r src/systemd/* $(DIR)/lib/systemd/system
 
 	# Make directory for generated configuration file
 	mkdir -p $(DIR)/etc/vyos
 
 	# Install smoke test scripts
 	mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke/
 	cp -r smoketest/scripts/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke
 
 	# Install smoke test configs
 	mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config/
 	cp -r smoketest/configs/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config
 
 	# Install smoke test config tests
 	mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config-tests/
 	cp -r smoketest/config-tests/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config-tests
 
 	# Install system programs
 	mkdir -p $(DIR)/$(VYOS_BIN_DIR)
 	cp -r smoketest/bin/* $(DIR)/$(VYOS_BIN_DIR)
 
 	# Install udev script
 	mkdir -p $(DIR)/usr/lib/udev
 	cp src/helpers/vyos_net_name $(DIR)/usr/lib/udev
 
 override_dh_installsystemd:
 	dh_installsystemd -pvyos-1x --name vyos-router vyos-router.service
 	dh_installsystemd -pvyos-1x --name vyos vyos.target
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 9e43669be..b3978d38a 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -1,40 +1,41 @@
 etc/commit
 etc/dhcp
 etc/ipsec.d
 etc/logrotate.d
 etc/netplug
 etc/opennhrp
 etc/modprobe.d
 etc/ppp
 etc/rsyslog.conf
 etc/securetty
 etc/security
 etc/skel
 etc/sudoers.d
 etc/systemd
 etc/sysctl.d
 etc/telegraf
 etc/udev
 etc/update-motd.d
 etc/vyos
 lib/
 opt/
 srv/localui
 usr/sbin
 usr/bin/config-mgmt
 usr/bin/initial-setup
 usr/bin/vyos-config-file-query
 usr/bin/vyos-config-to-commands
 usr/bin/vyos-config-to-json
 usr/bin/vyos-hostsd-client
 usr/lib
+usr/libexec/vyos/activate
 usr/libexec/vyos/completion
 usr/libexec/vyos/conf_mode
 usr/libexec/vyos/init
 usr/libexec/vyos/op_mode
 usr/libexec/vyos/services
 usr/libexec/vyos/system
 usr/libexec/vyos/validators
 usr/libexec/vyos/*.py
 usr/libexec/vyos/*.sh
 usr/share
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 7b0f7d9d9..e7cd69a8b 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -1,50 +1,51 @@
 # Copyright 2018-2023 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 base_dir = '/usr/libexec/vyos/'
 
 directories = {
   'base' : base_dir,
   'data' : '/usr/share/vyos/',
   'conf_mode' : f'{base_dir}/conf_mode',
   'op_mode' : f'{base_dir}/op_mode',
   'services' : f'{base_dir}/services',
   'config' : '/opt/vyatta/etc/config',
   'migrate' : '/opt/vyatta/etc/config-migrate/migrate',
+  'activate' : f'{base_dir}/activate',
   'log' : '/var/log/vyatta',
   'templates' : '/usr/share/vyos/templates/',
   'certbot' : '/config/auth/letsencrypt',
   'api_schema': f'{base_dir}/services/api/graphql/graphql/schema/',
   'api_client_op': f'{base_dir}/services/api/graphql/graphql/client_op/',
   'api_templates': f'{base_dir}/services/api/graphql/session/templates/',
   'vyos_udev_dir' : '/run/udev/vyos',
   'isc_dhclient_dir' : '/run/dhclient',
   'dhcp6_client_dir' : '/run/dhcp6c',
 }
 
 config_status = '/tmp/vyos-config-status'
 api_config_state = '/run/http-api-state'
 
 cfg_group = 'vyattacfg'
 
 cfg_vintage = 'vyos'
 
 commit_lock = '/opt/vyatta/config/.lock'
 
 component_version_json = os.path.join(directories['data'], 'component-versions.json')
 
 config_default = os.path.join(directories['data'], 'config.boot.default')
diff --git a/src/activation-scripts/20-ethernet_offload.py b/src/activation-scripts/20-ethernet_offload.py
new file mode 100755
index 000000000..33b0ea469
--- /dev/null
+++ b/src/activation-scripts/20-ethernet_offload.py
@@ -0,0 +1,103 @@
+# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library.  If not, see <http://www.gnu.org/licenses/>.
+
+# T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS
+#        CLI. See https://vyos.dev/T3619#102254 for all the details.
+# T3787: Remove deprecated UDP fragmentation offloading option
+# T6006: add to activation-scripts: migration-scripts/interfaces/20-to-21
+
+from vyos.ethtool import Ethtool
+from vyos.configtree import ConfigTree
+
+def activate(config: ConfigTree):
+    base = ['interfaces', 'ethernet']
+
+    if not config.exists(base):
+        return
+
+    for ifname in config.list_nodes(base):
+        eth = Ethtool(ifname)
+
+        # If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is
+        # enabled via CLI but not supported by the NIC - we remove it from the CLI
+        configured = config.exists(base + [ifname, 'offload', 'gro'])
+        enabled, fixed = eth.get_generic_receive_offload()
+        if configured and fixed:
+            config.delete(base + [ifname, 'offload', 'gro'])
+        elif enabled and not fixed:
+            config.set(base + [ifname, 'offload', 'gro'])
+
+        # If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is
+        # enabled via CLI but not supported by the NIC - we remove it from the CLI
+        configured = config.exists(base + [ifname, 'offload', 'gso'])
+        enabled, fixed = eth.get_generic_segmentation_offload()
+        if configured and fixed:
+            config.delete(base + [ifname, 'offload', 'gso'])
+        elif enabled and not fixed:
+            config.set(base + [ifname, 'offload', 'gso'])
+
+        # If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is
+        # enabled via CLI but not supported by the NIC - we remove it from the CLI
+        configured = config.exists(base + [ifname, 'offload', 'lro'])
+        enabled, fixed = eth.get_large_receive_offload()
+        if configured and fixed:
+            config.delete(base + [ifname, 'offload', 'lro'])
+        elif enabled and not fixed:
+            config.set(base + [ifname, 'offload', 'lro'])
+
+        # If SG is enabled by the Kernel - we reflect this on the CLI. If SG is
+        # enabled via CLI but not supported by the NIC - we remove it from the CLI
+        configured = config.exists(base + [ifname, 'offload', 'sg'])
+        enabled, fixed = eth.get_scatter_gather()
+        if configured and fixed:
+            config.delete(base + [ifname, 'offload', 'sg'])
+        elif enabled and not fixed:
+            config.set(base + [ifname, 'offload', 'sg'])
+
+        # If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is
+        # enabled via CLI but not supported by the NIC - we remove it from the CLI
+        configured = config.exists(base + [ifname, 'offload', 'tso'])
+        enabled, fixed = eth.get_tcp_segmentation_offload()
+        if configured and fixed:
+            config.delete(base + [ifname, 'offload', 'tso'])
+        elif enabled and not fixed:
+            config.set(base + [ifname, 'offload', 'tso'])
+
+        # Remove deprecated UDP fragmentation offloading option
+        if config.exists(base + [ifname, 'offload', 'ufo']):
+            config.delete(base + [ifname, 'offload', 'ufo'])
+
+        # Also while processing the interface configuration, not all adapters support
+        # changing the speed and duplex settings. If the desired speed and duplex
+        # values do not work for the NIC driver, we change them back to the default
+        # value of "auto" - which will be applied if the CLI node is deleted.
+        speed_path = base + [ifname, 'speed']
+        duplex_path = base + [ifname, 'duplex']
+        # speed and duplex must always be set at the same time if not set to "auto"
+        if config.exists(speed_path) and config.exists(duplex_path):
+            speed = config.return_value(speed_path)
+            duplex = config.return_value(duplex_path)
+            if speed != 'auto' and duplex != 'auto':
+                if not eth.check_speed_duplex(speed, duplex):
+                    config.delete(speed_path)
+                    config.delete(duplex_path)
+
+        # Also while processing the interface configuration, not all adapters support
+        # changing disabling flow-control - or change this setting. If disabling
+        # flow-control is not supported by the NIC, we remove the setting from CLI
+        flow_control_path = base + [ifname, 'disable-flow-control']
+        if config.exists(flow_control_path):
+            if not eth.check_flow_control():
+                config.delete(flow_control_path)
diff --git a/src/helpers/run-config-activation.py b/src/helpers/run-config-activation.py
new file mode 100755
index 000000000..58293702a
--- /dev/null
+++ b/src/helpers/run-config-activation.py
@@ -0,0 +1,83 @@
+#!/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/>.
+
+import re
+import logging
+from pathlib import Path
+from argparse import ArgumentParser
+
+from vyos.compose_config import ComposeConfig
+from vyos.compose_config import ComposeConfigError
+from vyos.defaults import directories
+
+parser = ArgumentParser()
+parser.add_argument('config_file', type=str,
+                    help="configuration file to modify with system-specific settings")
+parser.add_argument('--test-script', type=str,
+                    help="test effect of named script")
+
+args = parser.parse_args()
+
+checkpoint_file = '/run/vyos-activate-checkpoint'
+log_file = Path(directories['config']).joinpath('vyos-activate.log')
+
+logger = logging.getLogger(__name__)
+fh = logging.FileHandler(log_file)
+formatter = logging.Formatter('%(message)s')
+fh.setFormatter(formatter)
+logger.addHandler(fh)
+
+if 'vyos-activate-debug' in Path('/proc/cmdline').read_text():
+    print(f'\nactivate-debug enabled: file {checkpoint_file}_* on error')
+    debug = checkpoint_file
+    logger.setLevel(logging.DEBUG)
+else:
+    debug = None
+    logger.setLevel(logging.INFO)
+
+def sort_key(s: Path):
+    s = s.stem
+    pre, rem = re.match(r'(\d*)(?:-)?(.+)', s).groups()
+    return int(pre or 0), rem
+
+def file_ext(file_name: str) -> str:
+    """Return an identifier from file name for checkpoint file extension.
+    """
+    return Path(file_name).stem
+
+script_dir = Path(directories['activate'])
+
+if args.test_script:
+    script_list = [script_dir.joinpath(args.test_script)]
+else:
+    script_list = sorted(script_dir.glob('*.py'), key=sort_key)
+
+config_file = args.config_file
+config_str = Path(config_file).read_text()
+
+compose = ComposeConfig(config_str, checkpoint_file=debug)
+
+for file in script_list:
+    file = file.as_posix()
+    logger.info(f'calling {file}')
+    try:
+        compose.apply_file(file, func_name='activate')
+    except ComposeConfigError as e:
+        if debug:
+            compose.write(f'{compose.checkpoint_file}_{file_ext(file)}')
+        logger.error(f'config-activation error in {file}: {e}')
+
+compose.write(config_file, with_version=True)
diff --git a/src/init/vyos-router b/src/init/vyos-router
index a88810f08..59004fdc1 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -1,564 +1,575 @@
 #!/bin/bash
 # Copyright (C) 2021-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 . /lib/lsb/init-functions
 
 : ${vyatta_env:=/etc/default/vyatta}
 source $vyatta_env
 
 declare progname=${0##*/}
 declare action=$1; shift
 
 declare -x BOOTFILE=$vyatta_sysconfdir/config/config.boot
 declare -x DEFAULT_BOOTFILE=$vyatta_sysconfdir/config.boot.default
 
 # If vyos-config= boot option is present, use that file instead
 for x in $(cat /proc/cmdline); do
     [[ $x = vyos-config=* ]] || continue
     VYOS_CONFIG="${x#vyos-config=}"
 done
 
 if [ ! -z "$VYOS_CONFIG" ]; then
     if [ -r "$VYOS_CONFIG" ]; then
         echo "Config selected manually: $VYOS_CONFIG"
         declare -x BOOTFILE="$VYOS_CONFIG"
     else
         echo "WARNING: Could not read selected config file, using default!"
     fi
 fi
 
 declare -a subinit
 declare -a all_subinits=( firewall )
 
 if [ $# -gt 0 ] ; then
     for s in $@ ; do
         [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s
     done
 else
     for s in ${all_subinits[@]} ; do
         [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s
     done
 fi
 
 GROUP=vyattacfg
 
 # easy way to make empty file without any command
 empty()
 {
     >$1
 }
 
 # check if bootup of this portion is disabled
 disabled () {
     grep -q -w no-vyos-$1 /proc/cmdline
 }
 
 # Load encrypted config volume
 mount_encrypted_config() {
     persist_path=$(/opt/vyatta/sbin/vyos-persistpath)
     if [ $? == 0 ]; then
         if [ -e $persist_path/boot ]; then
             image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//')
 
             if [ -z "$image_name" ]; then
                 return
             fi
 
             if [ ! -f $persist_path/luks/$image_name ]; then
                 return
             fi
 
             vyos_tpm_key=$(python3 -c 'from vyos.tpm import read_tpm_key; print(read_tpm_key().decode())' 2>/dev/null)
 
             if [ $? -ne 0 ]; then
                 echo "ERROR: Failed to fetch encryption key from TPM. Encrypted config volume has not been mounted"
                 echo "Use 'encryption load' to load volume with recovery key"
                 echo "or 'encryption disable' to decrypt volume with recovery key"
                 return
             fi
 
             echo $vyos_tpm_key | tr -d '\r\n' | cryptsetup open $persist_path/luks/$image_name vyos_config --key-file=-
 
             if [ $? -ne 0 ]; then
                 echo "ERROR: Failed to decrypt config volume. Encrypted config volume has not been mounted"
                 echo "Use 'encryption load' to load volume with recovery key"
                 echo "or 'encryption disable' to decrypt volume with recovery key"
                 return
             fi
 
             mount /dev/mapper/vyos_config /config
             mount /dev/mapper/vyos_config $vyatta_sysconfdir/config
 
             echo "Mounted encrypted config volume"
         fi
     fi
 }
 
 unmount_encrypted_config() {
     persist_path=$(/opt/vyatta/sbin/vyos-persistpath)
     if [ $? == 0 ]; then
         if [ -e $persist_path/boot ]; then
             image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//')
 
             if [ -z "$image_name" ]; then
                 return
             fi
 
             if [ ! -f $persist_path/luks/$image_name ]; then
                 return
             fi
 
             umount /config
             umount $vyatta_sysconfdir/config
 
             cryptsetup close vyos_config
         fi
     fi
 }
 
 # if necessary, provide initial config
 init_bootfile () {
     # define and version default boot config if not present
     if [ ! -r $DEFAULT_BOOTFILE ]; then
         if [ -f $vyos_data_dir/config.boot.default ]; then
             cp $vyos_data_dir/config.boot.default $DEFAULT_BOOTFILE
             $vyos_libexec_dir/system-versions-foot.py >> $DEFAULT_BOOTFILE
         fi
     fi
     if [ ! -r $BOOTFILE ] ; then
         if [ -f $DEFAULT_BOOTFILE ]; then
             cp $DEFAULT_BOOTFILE $BOOTFILE
         else
             $vyos_libexec_dir/system-versions-foot.py > $BOOTFILE
         fi
         chgrp ${GROUP} $BOOTFILE
         chmod 660 $BOOTFILE
     fi
 }
 
 # if necessary, migrate initial config
 migrate_bootfile ()
 {
     if [ -x $vyos_libexec_dir/run-config-migration.py ]; then
         log_progress_msg migrate
         sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE"
     fi
 }
 
+# configure system-specific settings
+system_config ()
+{
+    if [ -x $vyos_libexec_dir/run-config-activation.py ]; then
+        log_progress_msg system
+        sg ${GROUP} -c "$vyos_libexec_dir/run-config-activation.py $BOOTFILE"
+    fi
+}
+
 # load the initial config
 load_bootfile ()
 {
     log_progress_msg configure
     (
         if [ -f /etc/default/vyatta-load-boot ]; then
             # build-specific environment for boot-time config loading
             source /etc/default/vyatta-load-boot
         fi
         if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then
             sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE"
         fi
     )
 }
 
 # restore if missing pre-config script
 restore_if_missing_preconfig_script ()
 {
     if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script ]; then
         mkdir -p ${vyatta_sysconfdir}/config/scripts
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts
         chmod 775 ${vyatta_sysconfdir}/config/scripts
         cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
         chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
     fi
 }
 
 # execute the pre-config script
 run_preconfig_script ()
 {
     if [ -x $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script
     fi
 }
 
 # restore if missing post-config script
 restore_if_missing_postconfig_script ()
 {
     if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script ]; then
         mkdir -p ${vyatta_sysconfdir}/config/scripts
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts
         chmod 775 ${vyatta_sysconfdir}/config/scripts
         cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
         chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
     fi
 }
 
 # execute the post-config scripts
 run_postconfig_scripts ()
 {
     if [ -x $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script
     fi
     if [ -x $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script
     fi
 }
 
 run_postupgrade_script ()
 {
     if [ -f $vyatta_sysconfdir/config/.upgraded ]; then
         # Run the system script
         /usr/libexec/vyos/system/post-upgrade
 
         # Run user scripts
         if [ -d $vyatta_sysconfdir/config/scripts/post-upgrade.d ]; then
             run-parts $vyatta_sysconfdir/config/scripts/post-upgrade.d
         fi
         rm -f $vyatta_sysconfdir/config/.upgraded
     fi
 }
 
 #
 # On image booted machines, we need to mount /boot from the image-specific
 # boot directory so that kernel package installation will put the
 # files in the right place.  We also have to mount /boot/grub from the
 # system-wide grub directory so that tools that edit the grub.cfg
 # file will find it in the expected location.
 #
 bind_mount_boot ()
 {
     persist_path=$(/opt/vyatta/sbin/vyos-persistpath)
     if [ $? == 0 ]; then
         if [ -e $persist_path/boot ]; then
             image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//')
 
             if [ -n "$image_name" ]; then
                 mount --bind $persist_path/boot/$image_name /boot
                 if [ $? -ne 0 ]; then
                     echo "Couldn't bind mount /boot"
                 fi
 
                 if [ ! -d /boot/grub ]; then
                     mkdir /boot/grub
                 fi
 
                 mount --bind $persist_path/boot/grub /boot/grub
                 if [ $? -ne 0 ]; then
                     echo "Couldn't bind mount /boot/grub"
                 fi
             fi
         fi
     fi
 }
 
 clear_or_override_config_files ()
 {
     for conf in snmp/snmpd.conf snmp/snmptrapd.conf snmp/snmp.conf \
         keepalived/keepalived.conf cron.d/vyos-crontab \
         ipvsadm.rules default/ipvsadm resolv.conf
     do
     if [ -s /etc/$conf ] ; then
         empty /etc/$conf
         chmod 0644 /etc/$conf
     fi
     done
 }
 
 update_interface_config ()
 {
     if [ -d /run/udev/vyos ]; then
         $vyos_libexec_dir/vyos-interface-rescan.py $BOOTFILE
     fi
 }
 
 cleanup_post_commit_hooks () {
     # Remove links from the post-commit hooks directory.
     # note that this approach only supports hooks that are "configured",
     # i.e., it does not support hooks that need to always be present.
     cpostdir=$(cli-shell-api getPostCommitHookDir)
     # exclude commit hooks that need to always be present
     excluded="00vyos-sync 10vyatta-log-commit.pl 99vyos-user-postcommit-hooks"
     if [ -d "$cpostdir" ]; then
 	    for f in $cpostdir/*; do
 	        if [[ ! $excluded =~ $(basename $f) ]]; then
 		        rm -f $cpostdir/$(basename $f)
 	        fi
 	    done
     fi
 }
 
 # These are all the default security setting which are later
 # overridden when configuration is read. These are the values the
 # system defaults.
 security_reset ()
 {
 
     # restore NSS cofniguration back to sane system defaults
     # will be overwritten later when configuration is loaded
     cat <<EOF >/etc/nsswitch.conf
 passwd:         files
 group:          files
 shadow:         files
 gshadow:        files
 
 # Per T2678, commenting out myhostname
 hosts:          files dns #myhostname
 networks:       files
 
 protocols:      db files
 services:       db files
 ethers:         db files
 rpc:            db files
 
 netgroup:       nis
 EOF
 
     # restore PAM back to virgin state (no radius/tacacs services)
     pam-auth-update --disable radius-mandatory radius-optional
     rm -f /etc/pam_radius_auth.conf
     pam-auth-update --disable tacplus-mandatory tacplus-optional
     rm -f /etc/tacplus_nss.conf /etc/tacplus_servers
     # and no Google authenticator for 2FA/MFA
     pam-auth-update --disable mfa-google-authenticator
 
     # Certain configuration files are re-generated by the configuration
     # subsystem and must reside under /etc and can not easily be moved to /run.
     # So on every boot we simply delete any remaining files and let the CLI
     # regenearte them.
 
     # PPPoE
     rm -f /etc/ppp/peers/pppoe* /etc/ppp/peers/wlm*
 
     # IPSec
     rm -rf /etc/ipsec.conf /etc/ipsec.secrets
     find /etc/swanctl -type f | xargs rm -f
 
     # limit cleanup
     rm -f /etc/security/limits.d/10-vyos.conf
 
     # iproute2 cleanup
     rm -f /etc/iproute2/rt_tables.d/vyos-*.conf
 
     # Container
     rm -f /etc/containers/storage.conf /etc/containers/registries.conf /etc/containers/containers.conf
     # Clean all networks and re-create them from our CLI
     rm -f /etc/containers/networks/*
 
     # System Options (SSH/cURL)
     rm -f /etc/ssh/ssh_config.d/*vyos*.conf
     rm -f /etc/curlrc
 }
 
 # XXX: T3885 - generate persistend DHCPv6 DUID (Type4 - UUID based)
 gen_duid ()
 {
     DUID_FILE="/var/lib/dhcpv6/dhcp6c_duid"
     UUID_FILE="/sys/class/dmi/id/product_uuid"
     UUID_FILE_ALT="/sys/class/dmi/id/product_serial"
     if [ ! -f ${UUID_FILE} ] && [ ! -f ${UUID_FILE_ALT} ]; then
         return 1
     fi
 
     # DUID is based on the BIOS/EFI UUID. We omit additional - characters
     if [ -f ${UUID_FILE} ]; then
         UUID=$(cat ${UUID_FILE} | tr -d -)
     fi
     if [ -z ${UUID} ]; then
         UUID=$(uuidgen --sha1 --namespace @dns --name $(cat ${UUID_FILE_ALT}) | tr -d -)
     fi
     # Add DUID type4 (UUID) information
     DUID_TYPE="0004"
 
     # The length-information (as per RFC6355 UUID is 128 bits long) is in big-endian
     # format - beware when porting to ARM64. The length field consists out of the
     # UUID (128 bit + 16 bits DUID type) resulting in hex 12.
     DUID_LEN="0012"
     if [ "$(echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 )" -eq 1 ]; then
         # true on little-endian (x86) systems
         DUID_LEN="1200"
     fi
 
     for i in $(echo -n ${DUID_LEN}${DUID_TYPE}${UUID} | sed 's/../& /g'); do
         echo -ne "\x$i"
     done > ${DUID_FILE}
 }
 
 start ()
 {
     # reset and clean config files
     security_reset || log_failure_msg "security reset failed"
 
     # some legacy directories migrated over from old rl-system.init
     mkdir -p /var/run/vyatta /var/log/vyatta
     chgrp vyattacfg /var/run/vyatta /var/log/vyatta
     chmod 775 /var/run/vyatta /var/log/vyatta
 
     log_daemon_msg "Waiting for NICs to settle down"
     # On boot time udev migth take a long time to reorder nic's, this will ensure that
     # all udev activity is completed and all nics presented at boot-time will have their
     # final name before continuing with vyos-router initialization.
     SECONDS=0
     udevadm settle
     STATUS=$?
     log_progress_msg "settled in ${SECONDS}sec."
     log_end_msg ${STATUS}
 
     # mountpoint for bpf maps required by xdp
     mount -t bpf none /sys/fs/bpf
 
     # Clear out Debian APT source config file
     empty /etc/apt/sources.list
 
     # Generate DHCPv6 DUID
     gen_duid || log_failure_msg "could not generate DUID"
 
     # Mount a temporary filesystem for container networks.
     # Configuration should be loaded from VyOS cli.
     cni_dir="/etc/cni/net.d"
     [ ! -d ${cni_dir} ] && mkdir -p ${cni_dir}
     mount -t tmpfs none ${cni_dir}
 
     # Init firewall
     nfct helper add rpc inet tcp
     nfct helper add rpc inet udp
     nfct helper add tns inet tcp
     nfct helper add rpc inet6 tcp
     nfct helper add rpc inet6 udp
     nfct helper add tns inet6 tcp
     nft --file /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules"
 
     # As VyOS does not execute commands that are not present in the CLI we call
     # the script by hand to have a single source for the login banner and MOTD
     ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console"
     ${vyos_conf_scripts_dir}/system_login_banner.py || log_failure_msg "could not reset motd and issue files"
     ${vyos_conf_scripts_dir}/system_option.py || log_failure_msg "could not reset system option files"
     ${vyos_conf_scripts_dir}/system_ip.py || log_failure_msg "could not reset system IPv4 options"
     ${vyos_conf_scripts_dir}/system_ipv6.py || log_failure_msg "could not reset system IPv6 options"
     ${vyos_conf_scripts_dir}/system_conntrack.py || log_failure_msg "could not reset conntrack subsystem"
     ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem"
 
     clear_or_override_config_files || log_failure_msg "could not reset config files"
 
     # enable some debugging before loading the configuration
     if grep -q vyos-debug /proc/cmdline; then
         log_action_begin_msg "Enable runtime debugging options"
         touch /tmp/vyos.container.debug
         touch /tmp/vyos.ifconfig.debug
         touch /tmp/vyos.frr.debug
         touch /tmp/vyos.container.debug
         touch /tmp/vyos.smoketest.debug
     fi
 
     log_action_begin_msg "Mounting VyOS Config"
     # ensure the vyatta_configdir supports a large number of inodes since
     # the config hierarchy is often inode-bound (instead of size).
     # impose a minimum and then scale up dynamically with the actual size
     # of the system memory.
     local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo)
     local tpages
     local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes
     mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \
       && chgrp ${GROUP} ${vyatta_configdir}
     log_action_end_msg $?
 
     mount_encrypted_config
 
     # T5239: early read of system hostname as this value is read-only once during
     # FRR initialisation
     tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "system host-name")
     hostnamectl set-hostname --static "$tmp"
 
     ${vyos_conf_scripts_dir}/system_frr.py || log_failure_msg "could not reset FRR config"
     # If for any reason FRR was not started by system_frr.py - start it anyways.
     # This is a safety net!
     systemctl start frr.service
 
     disabled bootfile || init_bootfile
 
     cleanup_post_commit_hooks
 
     log_daemon_msg "Starting VyOS router"
     disabled migrate || migrate_bootfile
 
     restore_if_missing_preconfig_script
 
     run_preconfig_script
 
     run_postupgrade_script
 
     update_interface_config
 
+    disabled system_config || system_config
+
     for s in ${subinit[@]} ; do
     if ! disabled $s; then
         log_progress_msg $s
         if ! ${vyatta_sbindir}/${s}.init start
         then log_failure_msg
          exit 1
         fi
     fi
     done
 
     bind_mount_boot
 
     disabled configure || load_bootfile
     log_end_msg $?
 
     telinit q
     chmod g-w,o-w /
 
     restore_if_missing_postconfig_script
 
     run_postconfig_scripts
     tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "protocols rpki cache")
     if [[ ! -z "$tmp" ]]; then
         vtysh -c "rpki start"
     fi
 }
 
 stop()
 {
     local -i status=0
     log_daemon_msg "Stopping VyOS router"
     for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do
     s=${subinit[$i]}
     log_progress_msg $s
     ${vyatta_sbindir}/${s}.init stop
     let status\|=$?
     done
     log_end_msg $status
     log_action_begin_msg "Un-mounting VyOS Config"
     umount ${vyatta_configdir}
     log_action_end_msg $?
 
     systemctl stop frr.service
 
     unmount_encrypted_config
 }
 
 case "$action" in
     start) start ;;
     stop)  stop ;;
     restart|force-reload) stop && start ;;
     *)  log_failure_msg "usage: $progname [ start|stop|restart ] [ subinit ... ]" ;
     false ;;
 esac
 
 exit $?
 
 # Local Variables:
 # mode: shell-script
 # sh-indentation: 4
 # End: