diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in
index b922771c1..c69886a4b 100644
--- a/interface-definitions/pki.xml.in
+++ b/interface-definitions/pki.xml.in
@@ -1,287 +1,293 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="pki" owner="${vyos_conf_scripts_dir}/pki.py">
     <properties>
       <help>Public key infrastructure (PKI)</help>
       <priority>300</priority>
     </properties>
     <children>
       <tagNode name="ca">
         <properties>
           <help>Certificate Authority</help>
           <constraint>
             #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
           </constraint>
         </properties>
         <children>
           #include <include/pki/cli-certificate-base64.xml.i>
           #include <include/generic-description.xml.i>
           <node name="private">
             <properties>
               <help>CA private key in PEM format</help>
             </properties>
             <children>
               #include <include/pki/cli-private-key-base64.xml.i>
               #include <include/pki/password-protected.xml.i>
             </children>
           </node>
           <leafNode name="crl">
             <properties>
               <help>Certificate revocation list in PEM format</help>
               <constraint>
                 <validator name="base64"/>
               </constraint>
               <constraintErrorMessage>CRL is not base64-encoded</constraintErrorMessage>
               <multi/>
             </properties>
           </leafNode>
+          <leafNode name="system-install">
+            <properties>
+              <help>Install into CA certificate store on router</help>
+              <valueless/>
+            </properties>
+          </leafNode>
           #include <include/pki/cli-revoke.xml.i>
         </children>
       </tagNode>
       <tagNode name="certificate">
         <properties>
           <help>Certificate</help>
           <constraint>
             #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
           </constraint>
         </properties>
         <children>
           #include <include/pki/cli-certificate-base64.xml.i>
           <node name="acme">
             <properties>
               <help>Automatic Certificate Management Environment (ACME) request</help>
             </properties>
             <children>
               #include <include/url-http-https.xml.i>
               <leafNode name="url">
                 <defaultValue>https://acme-v02.api.letsencrypt.org/directory</defaultValue>
               </leafNode>
               <leafNode name="domain-name">
                 <properties>
                   <help>Domain Name</help>
                   <constraint>
                     <validator name="fqdn"/>
                   </constraint>
                   <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and .-_</constraintErrorMessage>
                   <multi/>
                 </properties>
               </leafNode>
               <leafNode name="email">
                 <properties>
                   <help>Email address to associate with certificate</help>
                   <constraint>
                     #include <include/constraint/email.xml.i>
                   </constraint>
                 </properties>
               </leafNode>
               #include <include/listen-address-ipv4-single.xml.i>
               <leafNode name="rsa-key-size">
                 <properties>
                   <help>Size of the RSA key</help>
                   <completionHelp>
                     <list>2048 3072 4096</list>
                   </completionHelp>
                   <valueHelp>
                     <format>2048</format>
                     <description>RSA key length 2048 bit</description>
                   </valueHelp>
                   <valueHelp>
                     <format>3072</format>
                     <description>RSA key length 3072 bit</description>
                   </valueHelp>
                   <valueHelp>
                     <format>4096</format>
                     <description>RSA key length 4096 bit</description>
                   </valueHelp>
                   <constraint>
                     <regex>(2048|3072|4096)</regex>
                   </constraint>
                 </properties>
                 <defaultValue>2048</defaultValue>
               </leafNode>
             </children>
           </node>
           #include <include/generic-description.xml.i>
           <node name="private">
             <properties>
               <help>Certificate private key</help>
             </properties>
             <children>
               #include <include/pki/cli-private-key-base64.xml.i>
               #include <include/pki/password-protected.xml.i>
             </children>
           </node>
           #include <include/pki/cli-revoke.xml.i>
         </children>
       </tagNode>
       <tagNode name="dh">
         <properties>
           <help>Diffie-Hellman parameters</help>
           <constraint>
             #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
           </constraint>
         </properties>
         <children>
           <leafNode name="parameters">
             <properties>
               <help>DH parameters in PEM format</help>
               <constraint>
                 <validator name="base64"/>
               </constraint>
               <constraintErrorMessage>DH parameters are not base64-encoded</constraintErrorMessage>
             </properties>
           </leafNode>
         </children>
       </tagNode>
       <tagNode name="key-pair">
         <properties>
           <help>Public and private keys</help>
         </properties>
         <children>
           <node name="public">
             <properties>
               <help>Public key</help>
             </properties>
             <children>
               #include <include/pki/cli-public-key-base64.xml.i>
             </children>
           </node>
           <node name="private">
             <properties>
               <help>Private key</help>
             </properties>
             <children>
               #include <include/pki/cli-private-key-base64.xml.i>
               #include <include/pki/password-protected.xml.i>
             </children>
           </node>
         </children>
       </tagNode>
       <tagNode name="openssh">
         <properties>
           <help>OpenSSH public and private keys</help>
         </properties>
         <children>
           <node name="public">
             <properties>
               <help>Public key</help>
             </properties>
             <children>
               #include <include/pki/cli-public-key-base64.xml.i>
               <leafNode name="type">
                 <properties>
                   <help>SSH public key type</help>
                   <completionHelp>
                     <list>ssh-rsa</list>
                   </completionHelp>
                   <valueHelp>
                     <format>ssh-rsa</format>
                     <description>Key pair based on RSA algorithm</description>
                   </valueHelp>
                   <constraint>
                     <regex>(ssh-rsa)</regex>
                   </constraint>
                 </properties>
               </leafNode>
             </children>
           </node>
           <node name="private">
             <properties>
               <help>Private key</help>
             </properties>
             <children>
               #include <include/pki/cli-private-key-base64.xml.i>
               #include <include/pki/password-protected.xml.i>
             </children>
           </node>
         </children>
       </tagNode>
       <tagNode name="openssh">
         <properties>
           <help>OpenSSH public and private keys</help>
         </properties>
         <children>
           <node name="public">
             <properties>
               <help>Public key</help>
             </properties>
             <children>
               #include <include/pki/cli-public-key-base64.xml.i>
             </children>
           </node>
           <node name="private">
             <properties>
               <help>Private key</help>
             </properties>
             <children>
               #include <include/pki/cli-private-key-base64.xml.i>
               #include <include/pki/password-protected.xml.i>
             </children>
           </node>
         </children>
       </tagNode>
       <node name="openvpn">
         <properties>
           <help>OpenVPN keys</help>
         </properties>
         <children>
           <tagNode name="shared-secret">
             <properties>
               <help>OpenVPN shared secret key</help>
             </properties>
             <children>
               <leafNode name="key">
                 <properties>
                   <help>OpenVPN shared secret key data</help>
                 </properties>
               </leafNode>
               <leafNode name="version">
                 <properties>
                   <help>OpenVPN shared secret key version</help>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
         </children>
       </node>
       <node name="x509">
         <properties>
           <help>X509 Settings</help>
         </properties>
         <children>
           <node name="default">
             <properties>
               <help>X509 Default Values</help>
             </properties>
             <children>
               <leafNode name="country">
                 <properties>
                   <help>Default country</help>
                 </properties>
                 <defaultValue>GB</defaultValue>
               </leafNode>
               <leafNode name="state">
                 <properties>
                   <help>Default state</help>
                 </properties>
                 <defaultValue>Some-State</defaultValue>
               </leafNode>
               <leafNode name="locality">
                 <properties>
                   <help>Default locality</help>
                 </properties>
                 <defaultValue>Some-City</defaultValue>
               </leafNode>
               <leafNode name="organization">
                 <properties>
                   <help>Default organization</help>
                 </properties>
                 <defaultValue>VyOS</defaultValue>
               </leafNode>
             </children>
           </node>
         </children>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index dec619d3e..425990967 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -1,63 +1,64 @@
 # Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 base_dir = '/usr/libexec/vyos/'
 
 directories = {
   'base' : base_dir,
   'data' : '/usr/share/vyos/',
   'conf_mode' : f'{base_dir}/conf_mode',
   'op_mode' : f'{base_dir}/op_mode',
   'services' : f'{base_dir}/services',
   'config' : '/opt/vyatta/etc/config',
   'migrate' : '/opt/vyatta/etc/config-migrate/migrate',
   'activate' : f'{base_dir}/activate',
   'log' : '/var/log/vyatta',
   'templates' : '/usr/share/vyos/templates/',
   'certbot' : '/config/auth/letsencrypt',
   'api_schema': f'{base_dir}/services/api/graphql/graphql/schema/',
   'api_client_op': f'{base_dir}/services/api/graphql/graphql/client_op/',
   'api_templates': f'{base_dir}/services/api/graphql/session/templates/',
   'vyos_udev_dir' : '/run/udev/vyos',
   'isc_dhclient_dir' : '/run/dhclient',
   'dhcp6_client_dir' : '/run/dhcp6c',
   'vyos_configdir' : '/opt/vyatta/config',
-  'completion_dir' : f'{base_dir}/completion'
+  'completion_dir' : f'{base_dir}/completion',
+  'ca_certificates' : '/usr/local/share/ca-certificates/vyos'
 }
 
 config_status = '/tmp/vyos-config-status'
 api_config_state = '/run/http-api-state'
 
 cfg_group = 'vyattacfg'
 
 cfg_vintage = 'vyos'
 
 commit_lock = os.path.join(directories['vyos_configdir'], '.lock')
 
 component_version_json = os.path.join(directories['data'], 'component-versions.json')
 
 config_default = os.path.join(directories['data'], 'config.boot.default')
 
 rt_symbolic_names = {
   # Standard routing tables for Linux & reserved IDs for VyOS
   'default': 253, # Confusingly, a final fallthru, not the default. 
   'main': 254,    # The actual global table used by iproute2 unless told otherwise. 
   'local': 255,   # Special kernel loopback table.
 }
 
 rt_global_vrf = rt_symbolic_names['main']
 rt_global_table = rt_symbolic_names['main']
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index 4ff15d56e..acea2c9be 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -1,497 +1,526 @@
 #!/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 argv
 from sys import exit
 
 from vyos.config import Config
 from vyos.config import config_dict_merge
 from vyos.configdep import set_dependents
 from vyos.configdep import call_dependents
 from vyos.configdict import node_changed
 from vyos.configdiff import Diff
 from vyos.configdiff import get_config_diff
 from vyos.defaults import directories
 from vyos.pki import encode_certificate
 from vyos.pki import is_ca_certificate
 from vyos.pki import load_certificate
 from vyos.pki import load_public_key
 from vyos.pki import load_openssh_public_key
 from vyos.pki import load_openssh_private_key
 from vyos.pki import load_private_key
 from vyos.pki import load_crl
 from vyos.pki import load_dh_parameters
 from vyos.utils.boot import boot_configuration_complete
 from vyos.utils.configfs import add_cli_node
 from vyos.utils.dict import dict_search
 from vyos.utils.dict import dict_search_args
 from vyos.utils.dict import dict_search_recursive
 from vyos.utils.file import read_file
 from vyos.utils.process import call
 from vyos.utils.process import cmd
 from vyos.utils.process import is_systemd_service_active
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 vyos_certbot_dir = directories['certbot']
+vyos_ca_certificates_dir = directories['ca_certificates']
 
 # keys to recursively search for under specified path
 sync_search = [
     {
         'keys': ['certificate'],
         'path': ['service', 'https'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['interfaces', 'ethernet'],
     },
     {
         'keys': ['certificate', 'ca_certificate', 'dh_params', 'shared_secret_key', 'auth_key', 'crypt_key'],
         'path': ['interfaces', 'openvpn'],
     },
     {
         'keys': ['ca_certificate'],
         'path': ['interfaces', 'sstpc'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['load_balancing', 'haproxy'],
     },
     {
         'keys': ['key'],
         'path': ['protocols', 'rpki', 'cache'],
     },
     {
         'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'],
         'path': ['vpn', 'ipsec'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['vpn', 'openconnect'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['vpn', 'sstp'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['service', 'stunnel'],
     }
 ]
 
 # key from other config nodes -> key in pki['changed'] and pki
 sync_translate = {
     'certificate': 'certificate',
     'ca_certificate': 'ca',
     'dh_params': 'dh',
     'local_key': 'key_pair',
     'remote_key': 'key_pair',
     'shared_secret_key': 'openvpn',
     'auth_key': 'openvpn',
     'crypt_key': 'openvpn',
     'key': 'openssh',
 }
 
 def certbot_delete(certificate):
     if not boot_configuration_complete():
         return
     if os.path.exists(f'{vyos_certbot_dir}/renewal/{certificate}.conf'):
         cmd(f'certbot delete --non-interactive --config-dir {vyos_certbot_dir} --cert-name {certificate}')
 
 def certbot_request(name: str, config: dict, dry_run: bool=True):
     # We do not call certbot when booting the system - there is no need to do so and
     # request new certificates during boot/image upgrade as the certbot configuration
     # is stored persistent under /config - thus we do not open the door to transient
     # errors
     if not boot_configuration_complete():
         return
 
     domains = '--domains ' + ' --domains '.join(config['domain_name'])
     tmp = f'certbot certonly --non-interactive --config-dir {vyos_certbot_dir} --cert-name {name} '\
           f'--standalone --agree-tos --no-eff-email --expand --server {config["url"]} '\
           f'--email {config["email"]} --key-type rsa --rsa-key-size {config["rsa_key_size"]} '\
           f'{domains}'
     if 'listen_address' in config:
         tmp += f' --http-01-address {config["listen_address"]}'
     # verify() does not need to actually request a cert but only test for plausability
     if dry_run:
         tmp += ' --dry-run'
 
     cmd(tmp, raising=ConfigError, message=f'ACME certbot request failed for "{name}"!')
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['pki']
 
     pki = conf.get_config_dict(base, key_mangling=('-', '_'),
                                      get_first_key=True,
                                      no_tag_node_value_mangle=True)
 
     if len(argv) > 1 and argv[1] == 'certbot_renew':
         pki['certbot_renew'] = {}
 
     changed_keys = ['ca', 'certificate', 'dh', 'key-pair', 'openssh', 'openvpn']
 
     for key in changed_keys:
         tmp = node_changed(conf, base + [key], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
 
         if 'changed' not in pki:
             pki.update({'changed':{}})
 
         pki['changed'].update({key.replace('-', '_') : tmp})
 
     # We only merge on the defaults of there is a configuration at all
     if conf.exists(base):
         # 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(**pki.kwargs, recursive=True)
         # remove ACME default configuration if unused by CLI
         if 'certificate' in pki:
             for name, cert_config in pki['certificate'].items():
                 if 'acme' not in cert_config:
                     # Remove ACME default values
                     del default_values['certificate'][name]['acme']
 
         # merge CLI and default dictionary
         pki = config_dict_merge(default_values, pki)
 
     # Certbot triggered an external renew of the certificates.
     # Mark all ACME based certificates as "changed" to trigger
     # update of dependent services
     if 'certificate' in pki and 'certbot_renew' in pki:
         renew = []
         for name, cert_config in pki['certificate'].items():
             if 'acme' in cert_config:
                 renew.append(name)
         # If triggered externally by certbot, certificate key is not present in changed
         if 'changed' not in pki: pki.update({'changed':{}})
         pki['changed'].update({'certificate' : renew})
 
     # We need to get the entire system configuration to verify that we are not
     # deleting a certificate that is still referenced somewhere!
     pki['system'] = conf.get_config_dict([], key_mangling=('-', '_'),
                                          get_first_key=True,
                                          no_tag_node_value_mangle=True)
     D = get_config_diff(conf)
 
     for search in sync_search:
         for key in search['keys']:
             changed_key = sync_translate[key]
             if 'changed' not in pki or changed_key not in pki['changed']:
                 continue
 
             for item_name in pki['changed'][changed_key]:
                 node_present = False
                 if changed_key == 'openvpn':
                     node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
                 else:
                     node_present = dict_search_args(pki, changed_key, item_name)
 
                 if node_present:
                     search_dict = dict_search_args(pki['system'], *search['path'])
                     if not search_dict:
                         continue
                     for found_name, found_path in dict_search_recursive(search_dict, key):
                         if isinstance(found_name, list) and item_name not in found_name:
                             continue
 
                         if isinstance(found_name, str) and found_name != item_name:
                             continue
 
                         path = search['path']
                         path_str = ' '.join(path + found_path)
                         #print(f'PKI: Updating config: {path_str} {item_name}')
 
                         if path[0] == 'interfaces':
                             ifname = found_path[0]
                             if not D.node_changed_presence(path + [ifname]):
                                 set_dependents(path[1], conf, ifname)
                         else:
                             if not D.node_changed_presence(path):
                                 set_dependents(path[1], conf)
 
     return pki
 
 def is_valid_certificate(raw_data):
     # If it loads correctly we're good, or return False
     return load_certificate(raw_data, wrap_tags=True)
 
 def is_valid_ca_certificate(raw_data):
     # Check if this is a valid certificate with CA attributes
     cert = load_certificate(raw_data, wrap_tags=True)
     if not cert:
         return False
     return is_ca_certificate(cert)
 
 def is_valid_public_key(raw_data):
     # If it loads correctly we're good, or return False
     return load_public_key(raw_data, wrap_tags=True)
 
 def is_valid_private_key(raw_data, protected=False):
     # If it loads correctly we're good, or return False
     # With encrypted private keys, we always return true as we cannot ask for password to verify
     if protected:
         return True
     return load_private_key(raw_data, passphrase=None, wrap_tags=True)
 
 def is_valid_openssh_public_key(raw_data, type):
     # If it loads correctly we're good, or return False
     return load_openssh_public_key(raw_data, type)
 
 def is_valid_openssh_private_key(raw_data, protected=False):
     # If it loads correctly we're good, or return False
     # With encrypted private keys, we always return true as we cannot ask for password to verify
     if protected:
         return True
     return load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True)
 
 def is_valid_crl(raw_data):
     # If it loads correctly we're good, or return False
     return load_crl(raw_data, wrap_tags=True)
 
 def is_valid_dh_parameters(raw_data):
     # If it loads correctly we're good, or return False
     return load_dh_parameters(raw_data, wrap_tags=True)
 
 def verify(pki):
     if not pki:
         return None
 
     if 'ca' in pki:
         for name, ca_conf in pki['ca'].items():
             if 'certificate' in ca_conf:
                 if not is_valid_ca_certificate(ca_conf['certificate']):
                     raise ConfigError(f'Invalid certificate on CA certificate "{name}"')
 
             if 'private' in ca_conf and 'key' in ca_conf['private']:
                 private = ca_conf['private']
                 protected = 'password_protected' in private
 
                 if not is_valid_private_key(private['key'], protected):
                     raise ConfigError(f'Invalid private key on CA certificate "{name}"')
 
             if 'crl' in ca_conf:
                 ca_crls = ca_conf['crl']
                 if isinstance(ca_crls, str):
                     ca_crls = [ca_crls]
 
                 for crl in ca_crls:
                     if not is_valid_crl(crl):
                         raise ConfigError(f'Invalid CRL on CA certificate "{name}"')
 
     if 'certificate' in pki:
         for name, cert_conf in pki['certificate'].items():
             if 'certificate' in cert_conf:
                 if not is_valid_certificate(cert_conf['certificate']):
                     raise ConfigError(f'Invalid certificate on certificate "{name}"')
 
             if 'private' in cert_conf and 'key' in cert_conf['private']:
                 private = cert_conf['private']
                 protected = 'password_protected' in private
 
                 if not is_valid_private_key(private['key'], protected):
                     raise ConfigError(f'Invalid private key on certificate "{name}"')
 
             if 'acme' in cert_conf:
                 if 'domain_name' not in cert_conf['acme']:
                     raise ConfigError(f'At least one domain-name is required to request '\
                                     f'certificate for "{name}" via ACME!')
 
                 if 'email' not in cert_conf['acme']:
                     raise ConfigError(f'An email address is required to request '\
                                     f'certificate for "{name}" via ACME!')
 
                 if 'certbot_renew' not in pki:
                     # Only run the ACME command if something on this entity changed,
                     # as this is time intensive
                     tmp = dict_search('changed.certificate', pki)
                     if tmp != None and name in tmp:
                         certbot_request(name, cert_conf['acme'])
 
     if 'dh' in pki:
         for name, dh_conf in pki['dh'].items():
             if 'parameters' in dh_conf:
                 if not is_valid_dh_parameters(dh_conf['parameters']):
                     raise ConfigError(f'Invalid DH parameters on "{name}"')
 
     if 'key_pair' in pki:
         for name, key_conf in pki['key_pair'].items():
             if 'public' in key_conf and 'key' in key_conf['public']:
                 if not is_valid_public_key(key_conf['public']['key']):
                     raise ConfigError(f'Invalid public key on key-pair "{name}"')
 
             if 'private' in key_conf and 'key' in key_conf['private']:
                 private = key_conf['private']
                 protected = 'password_protected' in private
                 if not is_valid_private_key(private['key'], protected):
                     raise ConfigError(f'Invalid private key on key-pair "{name}"')
 
     if 'openssh' in pki:
         for name, key_conf in pki['openssh'].items():
             if 'public' in key_conf and 'key' in key_conf['public']:
                 if 'type' not in key_conf['public']:
                     raise ConfigError(f'Must define OpenSSH public key type for "{name}"')
                 if not is_valid_openssh_public_key(key_conf['public']['key'], key_conf['public']['type']):
                     raise ConfigError(f'Invalid OpenSSH public key "{name}"')
 
             if 'private' in key_conf and 'key' in key_conf['private']:
                 private = key_conf['private']
                 protected = 'password_protected' in private
                 if not is_valid_openssh_private_key(private['key'], protected):
                     raise ConfigError(f'Invalid OpenSSH private key "{name}"')
 
     if 'x509' in pki:
         if 'default' in pki['x509']:
             default_values = pki['x509']['default']
             if 'country' in default_values:
                 country = default_values['country']
                 if len(country) != 2 or not country.isalpha():
                     raise ConfigError(f'Invalid default country value. Value must be 2 alpha characters.')
 
     if 'changed' in pki:
         # if the list is getting longer, we can move to a dict() and also embed the
         # search key as value from line 173 or 176
         for search in sync_search:
             for key in search['keys']:
                 changed_key = sync_translate[key]
 
                 if changed_key not in pki['changed']:
                     continue
 
                 for item_name in pki['changed'][changed_key]:
                     node_present = False
                     if changed_key == 'openvpn':
                         node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
                     else:
                         node_present = dict_search_args(pki, changed_key, item_name)
 
                     if not node_present:
                         search_dict = dict_search_args(pki['system'], *search['path'])
 
                         if not search_dict:
                             continue
 
                         for found_name, found_path in dict_search_recursive(search_dict, key):
                             if found_name == item_name:
                                 path_str = " ".join(search['path'] + found_path)
                                 raise ConfigError(f'PKI object "{item_name}" still in use by "{path_str}"')
 
     return None
 
+def cleanup_system_ca():
+    if not os.path.exists(vyos_ca_certificates_dir):
+        os.mkdir(vyos_ca_certificates_dir)
+    else:
+        for filename in os.listdir(vyos_ca_certificates_dir):
+            full_path = os.path.join(vyos_ca_certificates_dir, filename)
+            if os.path.isfile(full_path):
+                os.unlink(full_path)
+
 def generate(pki):
     if not pki:
+        cleanup_system_ca()
         return None
 
+    # Create or cleanup CA install directory
+    if 'changed' in pki and 'ca' in pki['changed']:
+        cleanup_system_ca()
+
+        if 'ca' in pki:
+            for ca, ca_conf in pki['ca'].items():
+                if 'system_install' in ca_conf:
+                    ca_obj = load_certificate(ca_conf['certificate'])
+                    ca_path = os.path.join(vyos_ca_certificates_dir, f'{ca}.crt')
+
+                    with open(ca_path, 'w') as f:
+                        f.write(encode_certificate(ca_obj))
+
     # Certbot renewal only needs to re-trigger the services to load up the
     # new PEM file
     if 'certbot_renew' in pki:
         return None
 
     certbot_list = []
     certbot_list_on_disk = []
     if os.path.exists(f'{vyos_certbot_dir}/live'):
         certbot_list_on_disk = [f.path.split('/')[-1] for f in os.scandir(f'{vyos_certbot_dir}/live') if f.is_dir()]
 
     if 'certificate' in pki:
         changed_certificates = dict_search('changed.certificate', pki)
         for name, cert_conf in pki['certificate'].items():
             if 'acme' in cert_conf:
                 certbot_list.append(name)
                 # generate certificate if not found on disk
                 if name not in certbot_list_on_disk:
                     certbot_request(name, cert_conf['acme'], dry_run=False)
                 elif changed_certificates != None and name in changed_certificates:
                     # when something for the certificate changed, we should delete it
                     if name in certbot_list_on_disk:
                         certbot_delete(name)
                     certbot_request(name, cert_conf['acme'], dry_run=False)
 
     # Cleanup certbot configuration and certificates if no longer in use by CLI
     # Get foldernames under vyos_certbot_dir which each represent a certbot cert
     if os.path.exists(f'{vyos_certbot_dir}/live'):
         for cert in certbot_list_on_disk:
             # ACME certificate is no longer in use by CLI remove it
             if cert not in certbot_list:
                 certbot_delete(cert)
                 continue
             # ACME not enabled for individual certificate - bail out early
             if 'acme' not in pki['certificate'][cert]:
                 continue
 
             # Read in ACME certificate chain information
             tmp = read_file(f'{vyos_certbot_dir}/live/{cert}/chain.pem')
             tmp = load_certificate(tmp, wrap_tags=False)
             cert_chain_base64 = "".join(encode_certificate(tmp).strip().split("\n")[1:-1])
 
             # Check if CA chain certificate is already present on CLI to avoid adding
             # a duplicate. This only checks for manual added CA certificates and not
             # auto added ones with the AUTOCHAIN_ prefix
             autochain_prefix = 'AUTOCHAIN_'
             ca_cert_present = False
             if 'ca' in pki:
                 for ca_base64, cli_path in dict_search_recursive(pki['ca'], 'certificate'):
                     # Ignore automatic added CA certificates
                     if any(item.startswith(autochain_prefix) for item in cli_path):
                         continue
                     if cert_chain_base64 == ca_base64:
                         ca_cert_present = True
 
             if not ca_cert_present:
                 tmp = dict_search_args(pki, 'ca', f'{autochain_prefix}{cert}', 'certificate')
                 if not bool(tmp) or tmp != cert_chain_base64:
                     print(f'Adding/replacing automatically imported CA certificate for "{cert}" ...')
                     add_cli_node(['pki', 'ca', f'{autochain_prefix}{cert}', 'certificate'], value=cert_chain_base64)
 
     return None
 
 def apply(pki):
     systemd_certbot_name = 'certbot.timer'
     if not pki:
         call(f'systemctl stop {systemd_certbot_name}')
+        call('update-ca-certificates')
         return None
 
     has_certbot = False
     if 'certificate' in pki:
         for name, cert_conf in pki['certificate'].items():
             if 'acme' in cert_conf:
                 has_certbot = True
                 break
 
     if not has_certbot:
         call(f'systemctl stop {systemd_certbot_name}')
     elif has_certbot and not is_systemd_service_active(systemd_certbot_name):
         call(f'systemctl restart {systemd_certbot_name}')
 
     if 'changed' in pki:
         call_dependents()
 
+        # Rebuild ca-certificates bundle
+        if 'ca' in pki['changed']:
+            call('update-ca-certificates')
+
     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/init/vyos-router b/src/init/vyos-router
index 8825cc16a..f8cc87507 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -1,575 +1,581 @@
 #!/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/add-system-version.py >> $DEFAULT_BOOTFILE
         fi
     fi
     if [ ! -r $BOOTFILE ] ; then
         if [ -f $DEFAULT_BOOTFILE ]; then
             cp $DEFAULT_BOOTFILE $BOOTFILE
         else
             $vyos_libexec_dir/add-system-version.py > $BOOTFILE
         fi
         chgrp ${GROUP} $BOOTFILE
         chmod 660 $BOOTFILE
     fi
 }
 
 # if 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
 
+    # Cleanup PKI CAs
+    if [ -d /usr/local/share/ca-certificates/vyos ]; then
+        rm -f /usr/local/share/ca-certificates/vyos/*.crt
+        update-ca-certificates >/dev/null 2>&1
+    fi
+
     log_action_begin_msg "Mounting VyOS Config"
     # ensure the vyatta_configdir supports a large number of inodes since
     # the config hierarchy is often inode-bound (instead of size).
     # impose a minimum and then scale up dynamically with the actual size
     # of the system memory.
     local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo)
     local tpages
     local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes
     mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \
       && chgrp ${GROUP} ${vyatta_configdir}
     log_action_end_msg $?
 
     mount_encrypted_config
 
     # T5239: early read of system hostname as this value is read-only once during
     # FRR initialisation
     tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "system host-name")
     hostnamectl set-hostname --static "$tmp"
 
     ${vyos_conf_scripts_dir}/system_frr.py || log_failure_msg "could not reset FRR config"
     # If for any reason FRR was not started by system_frr.py - start it anyways.
     # This is a safety net!
     systemctl start frr.service
 
     disabled bootfile || init_bootfile
 
     cleanup_post_commit_hooks
 
     log_daemon_msg "Starting VyOS router"
     disabled migrate || migrate_bootfile
 
     restore_if_missing_preconfig_script
 
     run_preconfig_script
 
     run_postupgrade_script
 
     update_interface_config
 
     disabled system_config || system_config
 
     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: