diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py
index 648d690b9..1513ac5ed 100644
--- a/python/vyos/component_version.py
+++ b/python/vyos/component_version.py
@@ -1,315 +1,330 @@
 # Copyright 2022-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/>.
 
 """
 Functions for reading/writing component versions.
 
 The config file version string has the following form:
 
 VyOS 1.3/1.4:
 
 // Warning: Do not remove the following line.
 // vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1"
 // Release version: 1.3.0
 
 VyOS 1.2:
 
 /* Warning: Do not remove the following line. */
 /* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */
 /* Release version: 1.2.8 */
 
 """
 
 import os
 import re
 import sys
 from dataclasses import dataclass
 from dataclasses import replace
 from typing import Optional
 
 from vyos.xml_ref import component_version
 from vyos.utils.file import write_file
 from vyos.version import get_version
 from vyos.defaults import directories
 
 DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot')
 
 REGEX_WARN_VYOS = r'(// Warning: Do not remove the following line.)'
 REGEX_WARN_VYATTA = r'(/\* Warning: Do not remove the following line. \*/)'
 REGEX_COMPONENT_VERSION_VYOS = r'// vyos-config-version:\s+"([\w@:-]+)"\s*'
 REGEX_COMPONENT_VERSION_VYATTA = r'/\* === vyatta-config-version:\s+"([\w@:-]+)"\s+=== \*/'
 REGEX_RELEASE_VERSION_VYOS = r'// Release version:\s+(\S*)\s*'
 REGEX_RELEASE_VERSION_VYATTA = r'/\* Release version:\s+(\S*)\s*\*/'
 
 CONFIG_FILE_VERSION = """\
 // Warning: Do not remove the following line.
 // vyos-config-version: "{}"
 // Release version: {}\n
 """
 
 warn_filter_vyos = re.compile(REGEX_WARN_VYOS)
 warn_filter_vyatta = re.compile(REGEX_WARN_VYATTA)
 
 regex_filter = { 'vyos': dict(zip(['component', 'release'],
                                   [re.compile(REGEX_COMPONENT_VERSION_VYOS),
                                    re.compile(REGEX_RELEASE_VERSION_VYOS)])),
                  'vyatta': dict(zip(['component', 'release'],
                                     [re.compile(REGEX_COMPONENT_VERSION_VYATTA),
                                      re.compile(REGEX_RELEASE_VERSION_VYATTA)])) }
 
 @dataclass
 class VersionInfo:
     component: Optional[dict[str,int]] = None
     release: str = get_version()
     vintage: str = 'vyos'
     config_body: Optional[str] = None
     footer_lines: Optional[list[str]] = None
 
     def component_is_none(self) -> bool:
         return bool(self.component is None)
 
     def config_body_is_none(self) -> bool:
         return bool(self.config_body is None)
 
     def update_footer(self):
         f = CONFIG_FILE_VERSION.format(component_to_string(self.component),
                                        self.release)
         self.footer_lines = f.splitlines()
 
     def update_syntax(self):
         self.vintage = 'vyos'
         self.update_footer()
 
     def update_release(self, release: str):
         self.release = release
         self.update_footer()
 
     def update_component(self, key: str, version: int):
         if not isinstance(version, int):
             raise ValueError('version must be int')
         if self.component is None:
             self.component = {}
         self.component[key] = version
         self.component = dict(sorted(self.component.items(), key=lambda x: x[0]))
         self.update_footer()
 
     def update_config_body(self, config_str: str):
         self.config_body = config_str
 
     def write_string(self) -> str:
         config_body = '' if self.config_body is None else self.config_body
         footer_lines = [] if self.footer_lines is None else self.footer_lines
 
         return config_body + '\n' + '\n'.join(footer_lines) + '\n'
 
     def write(self, config_file):
         string = self.write_string()
         try:
             write_file(config_file, string, mode=0o660)
         except Exception as e:
             raise ValueError(e) from e
 
 def component_to_string(component: dict) -> str:
     l = [f'{k}@{v}' for k, v in sorted(component.items(), key=lambda x: x[0])]
     return ':'.join(l)
 
 def component_from_string(string: str) -> dict:
     return {k: int(v) for k, v in re.findall(r'([\w,-]+)@(\d+)', string)}
 
 def version_info_from_file(config_file) -> VersionInfo:
     version_info = VersionInfo()
     try:
         with open(config_file) as f:
             config_str = f.read()
     except OSError:
         return None
 
     if len(parts := warn_filter_vyos.split(config_str)) > 1:
         vintage = 'vyos'
     elif len(parts := warn_filter_vyatta.split(config_str)) > 1:
         vintage = 'vyatta'
     else:
         version_info.config_body = parts[0] if parts else None
         return version_info
 
     version_info.vintage = vintage
     version_info.config_body = parts[0]
     version_lines = ''.join(parts[1:]).splitlines()
     version_lines = [k for k in version_lines if k]
     if len(version_lines) != 3:
         raise ValueError(f'Malformed version strings: {version_lines}')
 
     m = regex_filter[vintage]['component'].match(version_lines[1])
     if not m:
         raise ValueError(f'Malformed component string: {version_lines[1]}')
     version_info.component = component_from_string(m.group(1))
 
     m = regex_filter[vintage]['release'].match(version_lines[2])
     if not m:
         raise ValueError(f'Malformed component string: {version_lines[2]}')
     version_info.release = m.group(1)
 
     version_info.footer_lines = version_lines
 
     return version_info
 
 def version_info_from_system() -> VersionInfo:
     """
     Return system component versions.
     """
     d = component_version()
     sort_d = dict(sorted(d.items(), key=lambda x: x[0]))
     version_info = VersionInfo(
         component = sort_d,
         release =  get_version(),
         vintage = 'vyos'
     )
 
     return version_info
 
 def version_info_copy(v: VersionInfo) -> VersionInfo:
     """
     Make a copy of dataclass.
     """
     return replace(v)
 
 def version_info_prune_component(x: VersionInfo, y: VersionInfo) -> VersionInfo:
     """
     In place pruning of component keys of x not in y.
     """
     x.component = { k: v for k,v in x.component.items() if k in y.component }
 
+def add_system_version(config_str: str = None, out_file: str = None):
+    """
+    Wrap config string with system version and write to out_file.
+    For convenience, calling with no argument will write system version
+    string to stdout, for use in bash scripts.
+    """
+    version_info = version_info_from_system()
+    if config_str is not None:
+        version_info.update_config_body(config_str)
+    version_info.update_footer()
+    if out_file is not None:
+        version_info.write(out_file)
+    else:
+        sys.stdout.write(version_info.write_string())
+
 def from_string(string_line, vintage='vyos'):
     """
     Get component version dictionary from string.
     Return empty dictionary if string contains no config information
     or raise error if component version string malformed.
     """
     version_dict = {}
 
     if vintage == 'vyos':
         if re.match(r'// vyos-config-version:.+', string_line):
             if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', string_line):
                 raise ValueError(f"malformed configuration string: {string_line}")
 
             for pair in re.findall(r'([\w,-]+)@(\d+)', string_line):
                 version_dict[pair[0]] = int(pair[1])
 
     elif vintage == 'vyatta':
         if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line):
             if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line):
                 raise ValueError(f"malformed configuration string: {string_line}")
 
             for pair in re.findall(r'([\w,-]+)@(\d+)', string_line):
                 version_dict[pair[0]] = int(pair[1])
     else:
         raise ValueError("Unknown config string vintage")
 
     return version_dict
 
 def from_file(config_file_name=DEFAULT_CONFIG_PATH, vintage='vyos'):
     """
     Get component version dictionary parsing config file line by line
     """
     with open(config_file_name, 'r') as f:
         for line_in_config in f:
             version_dict = from_string(line_in_config, vintage=vintage)
             if version_dict:
                 return version_dict
 
     # no version information
     return {}
 
 def from_system():
     """
     Get system component version dict.
     """
     return component_version()
 
 def format_string(ver: dict) -> str:
     """
     Version dict to string.
     """
     keys = list(ver)
     keys.sort()
     l = []
     for k in keys:
         v = ver[k]
         l.append(f'{k}@{v}')
     sep = ':'
     return sep.join(l)
 
 def version_footer(ver: dict, vintage='vyos') -> str:
     """
     Version footer as string.
     """
     ver_str = format_string(ver)
     release = get_version()
     if vintage == 'vyos':
         ret_str = (f'// Warning: Do not remove the following line.\n'
                 +  f'// vyos-config-version: "{ver_str}"\n'
                 +  f'// Release version: {release}\n')
     elif vintage == 'vyatta':
         ret_str = (f'/* Warning: Do not remove the following line. */\n'
                 +  f'/* === vyatta-config-version: "{ver_str}" === */\n'
                 +  f'/* Release version: {release} */\n')
     else:
         raise ValueError("Unknown config string vintage")
 
     return ret_str
 
 def system_footer(vintage='vyos') -> str:
     """
     System version footer as string.
     """
     ver_d = from_system()
     return version_footer(ver_d, vintage=vintage)
 
 def write_version_footer(ver: dict, file_name, vintage='vyos'):
     """
     Write version footer to file.
     """
     footer = version_footer(ver=ver, vintage=vintage)
     if file_name:
         with open(file_name, 'a') as f:
             f.write(footer)
     else:
         sys.stdout.write(footer)
 
 def write_system_footer(file_name, vintage='vyos'):
     """
     Write system version footer to file.
     """
     ver_d = from_system()
     return write_version_footer(ver_d, file_name=file_name, vintage=vintage)
 
 def remove_footer(file_name):
     """
     Remove old version footer.
     """
     for line in fileinput.input(file_name, inplace=True):
         if re.match(r'/\* Warning:.+ \*/$', line):
             continue
         if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line):
             continue
         if re.match(r'/\* Release version:.+ \*/$', line):
             continue
         if re.match('// vyos-config-version:.+', line):
             continue
         if re.match('// Warning:.+', line):
             continue
         if re.match('// Release version:.+', line):
             continue
         sys.stdout.write(line)
diff --git a/src/helpers/system-versions-foot.py b/src/helpers/add-system-version.py
similarity index 61%
rename from src/helpers/system-versions-foot.py
rename to src/helpers/add-system-version.py
index 9614f0d28..5270ee7d3 100755
--- a/src/helpers/system-versions-foot.py
+++ b/src/helpers/add-system-version.py
@@ -1,28 +1,20 @@
 #!/usr/bin/python3
 
-# Copyright 2019, 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
-import sys
-import vyos.defaults
-from vyos.component_version import write_system_footer
+from vyos.component_version import add_system_version
 
-sys.stdout.write("\n\n")
-if vyos.defaults.cfg_vintage == 'vyos':
-    write_system_footer(None, vintage='vyos')
-elif vyos.defaults.cfg_vintage == 'vyatta':
-    write_system_footer(None, vintage='vyatta')
-else:
-    write_system_footer(None, vintage='vyos')
+add_system_version()
diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py
index 518bd9864..fa2ea0ce4 100755
--- a/src/helpers/vyos-save-config.py
+++ b/src/helpers/vyos-save-config.py
@@ -1,73 +1,72 @@
 #!/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/>.
 #
 #
 import os
 import re
 import sys
 from tempfile import NamedTemporaryFile
 from argparse import ArgumentParser
 
 from vyos.config import Config
 from vyos.remote import urlc
-from vyos.component_version import system_footer
+from vyos.component_version import add_system_version
 from vyos.defaults import directories
 
 DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot')
 remote_save = None
 
 parser = ArgumentParser(description='Save configuration')
 parser.add_argument('file', type=str, nargs='?', help='Save configuration to file')
 parser.add_argument('--write-json-file', type=str, help='Save JSON of configuration to file')
 args = parser.parse_args()
 file = args.file
 json_file = args.write_json_file
 
 if file is not None:
     save_file = file
 else:
     save_file = DEFAULT_CONFIG_PATH
 
 if re.match(r'\w+:/', save_file):
     try:
         remote_save = urlc(save_file)
     except ValueError as e:
         sys.exit(e)
 
 config = Config()
 ct = config.get_config_tree(effective=True)
 
+# pylint: disable=consider-using-with
 write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name
-with open(write_file, 'w') as f:
-    # config_tree is None before boot configuration is complete;
-    # automated saves should check boot_configuration_complete
-    if ct is not None:
-        f.write(ct.to_string())
-    f.write("\n")
-    f.write(system_footer())
+
+# config_tree is None before boot configuration is complete;
+# automated saves should check boot_configuration_complete
+config_str = None if ct is None else ct.to_string()
+add_system_version(config_str, write_file)
 
 if json_file is not None and ct is not None:
     try:
         with open(json_file, 'w') as f:
             f.write(ct.to_json())
     except OSError as e:
         print(f'failed to write JSON file: {e}')
 
 if remote_save is not None:
     try:
         remote_save.upload(write_file)
     finally:
         os.remove(write_file)
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 59004fdc1..8825cc16a 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -1,575 +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
+            $vyos_libexec_dir/add-system-version.py >> $DEFAULT_BOOTFILE
         fi
     fi
     if [ ! -r $BOOTFILE ] ; then
         if [ -f $DEFAULT_BOOTFILE ]; then
             cp $DEFAULT_BOOTFILE $BOOTFILE
         else
-            $vyos_libexec_dir/system-versions-foot.py > $BOOTFILE
+            $vyos_libexec_dir/add-system-version.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: