summaryrefslogtreecommitdiff
path: root/src/migration-scripts
diff options
context:
space:
mode:
Diffstat (limited to 'src/migration-scripts')
-rwxr-xr-xsrc/migration-scripts/config-management/0-to-131
-rwxr-xr-xsrc/migration-scripts/dhcp-relay/1-to-235
-rwxr-xr-xsrc/migration-scripts/dhcp-server/4-to-5122
-rwxr-xr-xsrc/migration-scripts/dhcpv6-server/0-to-161
-rwxr-xr-xsrc/migration-scripts/dns-forwarding/0-to-150
-rwxr-xr-xsrc/migration-scripts/dns-forwarding/1-to-283
-rwxr-xr-xsrc/migration-scripts/dns-forwarding/2-to-351
-rwxr-xr-xsrc/migration-scripts/https/0-to-169
-rwxr-xr-xsrc/migration-scripts/https/1-to-254
-rwxr-xr-xsrc/migration-scripts/interfaces/0-to-1118
-rwxr-xr-xsrc/migration-scripts/interfaces/1-to-263
-rwxr-xr-xsrc/migration-scripts/interfaces/10-to-1155
-rwxr-xr-xsrc/migration-scripts/interfaces/11-to-1258
-rwxr-xr-xsrc/migration-scripts/interfaces/2-to-343
-rwxr-xr-xsrc/migration-scripts/interfaces/3-to-497
-rwxr-xr-xsrc/migration-scripts/interfaces/4-to-5112
-rwxr-xr-xsrc/migration-scripts/interfaces/5-to-6123
-rwxr-xr-xsrc/migration-scripts/interfaces/6-to-763
-rwxr-xr-xsrc/migration-scripts/interfaces/7-to-876
-rwxr-xr-xsrc/migration-scripts/interfaces/8-to-952
-rwxr-xr-xsrc/migration-scripts/interfaces/9-to-1064
-rwxr-xr-xsrc/migration-scripts/ipoe-server/0-to-1133
-rwxr-xr-xsrc/migration-scripts/ipsec/4-to-533
-rwxr-xr-xsrc/migration-scripts/l2tp/0-to-160
-rwxr-xr-xsrc/migration-scripts/l2tp/1-to-233
-rwxr-xr-xsrc/migration-scripts/l2tp/2-to-3111
-rwxr-xr-xsrc/migration-scripts/lldp/0-to-135
-rwxr-xr-xsrc/migration-scripts/nat/4-to-558
-rwxr-xr-xsrc/migration-scripts/ntp/0-to-136
-rwxr-xr-xsrc/migration-scripts/pppoe-server/0-to-137
-rwxr-xr-xsrc/migration-scripts/pppoe-server/1-to-238
-rwxr-xr-xsrc/migration-scripts/pppoe-server/2-to-3141
-rwxr-xr-xsrc/migration-scripts/pppoe-server/3-to-454
-rwxr-xr-xsrc/migration-scripts/pptp/0-to-159
-rwxr-xr-xsrc/migration-scripts/pptp/1-to-271
-rwxr-xr-xsrc/migration-scripts/quagga/2-to-3203
-rwxr-xr-xsrc/migration-scripts/quagga/3-to-476
-rwxr-xr-xsrc/migration-scripts/quagga/4-to-563
-rwxr-xr-xsrc/migration-scripts/quagga/5-to-663
-rwxr-xr-xsrc/migration-scripts/salt/0-to-158
-rwxr-xr-xsrc/migration-scripts/snmp/0-to-156
-rwxr-xr-xsrc/migration-scripts/snmp/1-to-289
-rwxr-xr-xsrc/migration-scripts/ssh/0-to-132
-rwxr-xr-xsrc/migration-scripts/ssh/1-to-255
-rwxr-xr-xsrc/migration-scripts/sstp/0-to-1130
-rwxr-xr-xsrc/migration-scripts/sstp/1-to-2110
-rwxr-xr-xsrc/migration-scripts/system/10-to-1171
-rwxr-xr-xsrc/migration-scripts/system/11-to-1247
-rwxr-xr-xsrc/migration-scripts/system/12-to-1370
-rwxr-xr-xsrc/migration-scripts/system/13-to-1440
-rwxr-xr-xsrc/migration-scripts/system/14-to-1537
-rwxr-xr-xsrc/migration-scripts/system/15-to-1655
-rwxr-xr-xsrc/migration-scripts/system/16-to-1776
-rwxr-xr-xsrc/migration-scripts/system/17-to-18105
-rwxr-xr-xsrc/migration-scripts/system/6-to-748
-rwxr-xr-xsrc/migration-scripts/system/7-to-845
-rwxr-xr-xsrc/migration-scripts/system/8-to-932
-rwxr-xr-xsrc/migration-scripts/system/9-to-1036
-rwxr-xr-xsrc/migration-scripts/vrrp/1-to-2270
-rwxr-xr-xsrc/migration-scripts/webproxy/1-to-239
60 files changed, 4285 insertions, 0 deletions
diff --git a/src/migration-scripts/config-management/0-to-1 b/src/migration-scripts/config-management/0-to-1
new file mode 100755
index 000000000..344359110
--- /dev/null
+++ b/src/migration-scripts/config-management/0-to-1
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+
+# Add commit-revisions option if it doesn't exist
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if config.exists(['system', 'config-management', 'commit-revisions']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ config.set(['system', 'config-management', 'commit-revisions'], value='200')
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/dhcp-relay/1-to-2 b/src/migration-scripts/dhcp-relay/1-to-2
new file mode 100755
index 000000000..b72da1028
--- /dev/null
+++ b/src/migration-scripts/dhcp-relay/1-to-2
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+
+# Delete "set service dhcp-relay relay-options port" option
+# Delete "set service dhcpv6-relay listen-port" option
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not (config.exists(['service', 'dhcp-relay', 'relay-options', 'port']) or config.exists(['service', 'dhcpv6-relay', 'listen-port'])):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Delete abandoned node
+ config.delete(['service', 'dhcp-relay', 'relay-options', 'port'])
+ # Delete abandoned node
+ config.delete(['service', 'dhcpv6-relay', 'listen-port'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/dhcp-server/4-to-5 b/src/migration-scripts/dhcp-server/4-to-5
new file mode 100755
index 000000000..313b5279a
--- /dev/null
+++ b/src/migration-scripts/dhcp-server/4-to-5
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+
+# Removes boolean operator from:
+# - "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable (true|false)"
+# - "set service dhcp-server shared-network-name <xyz> authoritative (true|false)"
+# - "set service dhcp-server disabled (true|false)"
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['service', 'dhcp-server']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ base = ['service', 'dhcp-server']
+ # Make node "set service dhcp-server dynamic-dns-update enable (true|false)" valueless
+ if config.exists(base + ['dynamic-dns-update']):
+ bool_val = config.return_value(base + ['dynamic-dns-update', 'enable'])
+
+ # Delete the node with the old syntax
+ config.delete(base + ['dynamic-dns-update'])
+ if str(bool_val) == 'true':
+ # Enable dynamic-dns-update with new syntax
+ config.set(base + ['dynamic-dns-update'], value=None)
+
+ # Make node "set service dhcp-server disabled (true|false)" valueless
+ if config.exists(base + ['disabled']):
+ bool_val = config.return_value(base + ['disabled'])
+
+ # Delete the node with the old syntax
+ config.delete(base + ['disabled'])
+ if str(bool_val) == 'true':
+ # Now disable DHCP server with the new syntax
+ config.set(base + ['disable'], value=None)
+
+ # Make node "set service dhcp-server hostfile-update (enable|disable) valueless
+ if config.exists(base + ['hostfile-update']):
+ bool_val = config.return_value(base + ['hostfile-update'])
+
+ # Delete the node with the old syntax incl. all subnodes
+ config.delete(base + ['hostfile-update'])
+ if str(bool_val) == 'enable':
+ # Enable hostfile update with new syntax
+ config.set(base + ['hostfile-update'], value=None)
+
+ # Run this for every instance if 'shared-network-name'
+ for network in config.list_nodes(base + ['shared-network-name']):
+ base_network = base + ['shared-network-name', network]
+ # format as tag node to avoid loading problems
+ config.set_tag(base + ['shared-network-name'])
+
+ # Run this for every specified 'subnet'
+ for subnet in config.list_nodes(base_network + ['subnet']):
+ base_subnet = base_network + ['subnet', subnet]
+ # format as tag node to avoid loading problems
+ config.set_tag(base_network + ['subnet'])
+
+ # Make node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable" valueless
+ if config.exists(base_subnet + ['ip-forwarding', 'enable']):
+ bool_val = config.return_value(base_subnet + ['ip-forwarding', 'enable'])
+ # Delete the node with the old syntax
+ config.delete(base_subnet + ['ip-forwarding'])
+ if str(bool_val) == 'true':
+ # Recreate node with new syntax
+ config.set(base_subnet + ['ip-forwarding'], value=None)
+
+ # Rename node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 start <172.16.0.4> stop <172.16.0.9>
+ if config.exists(base_subnet + ['start']):
+ # This is the new "range" id for DHCP lease ranges
+ r_id = 0
+ for range in config.list_nodes(base_subnet + ['start']):
+ range_start = range
+ range_stop = config.return_value(base_subnet + ['start', range_start, 'stop'])
+
+ # Delete the node with the old syntax
+ config.delete(base_subnet + ['start', range_start])
+
+ # Create the node for the new syntax
+ # Note: range is a tag node, counter is its child, not a value
+ config.set(base_subnet + ['range', r_id])
+ config.set(base_subnet + ['range', r_id, 'start'], value=range_start)
+ config.set(base_subnet + ['range', r_id, 'stop'], value=range_stop)
+
+ # format as tag node to avoid loading problems
+ config.set_tag(base_subnet + ['range'])
+
+ # increment range id for possible next range definition
+ r_id += 1
+
+ # Delete the node with the old syntax
+ config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'subnet', subnet, 'start'])
+
+
+ # Make node "set service dhcp-server shared-network-name <xyz> authoritative" valueless
+ if config.exists(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']):
+ authoritative = config.return_value(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative'])
+
+ # Delete the node with the old syntax
+ config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative'])
+
+ # Recreate node with new syntax - if required
+ if authoritative == "enable":
+ config.set(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/dhcpv6-server/0-to-1 b/src/migration-scripts/dhcpv6-server/0-to-1
new file mode 100755
index 000000000..6f1150da1
--- /dev/null
+++ b/src/migration-scripts/dhcpv6-server/0-to-1
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# combine both sip-server-address and sip-server-name nodes to common sip-server
+
+from sys import argv, exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'dhcpv6-server', 'shared-network-name']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+ # we need to run this for every configured network
+ for network in config.list_nodes(base):
+ for subnet in config.list_nodes(base + [network, 'subnet']):
+ sip_server = []
+
+ # Do we have 'sip-server-address' configured?
+ if config.exists(base + [network, 'subnet', subnet, 'sip-server-address']):
+ sip_server += config.return_values(base + [network, 'subnet', subnet, 'sip-server-address'])
+ config.delete(base + [network, 'subnet', subnet, 'sip-server-address'])
+
+ # Do we have 'sip-server-name' configured?
+ if config.exists(base + [network, 'subnet', subnet, 'sip-server-name']):
+ sip_server += config.return_values(base + [network, 'subnet', subnet, 'sip-server-name'])
+ config.delete(base + [network, 'subnet', subnet, 'sip-server-name'])
+
+ # Write new CLI value for sip-server
+ for server in sip_server:
+ config.set(base + [network, 'subnet', subnet, 'sip-server'], value=server, replace=False)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/dns-forwarding/0-to-1 b/src/migration-scripts/dns-forwarding/0-to-1
new file mode 100755
index 000000000..6e8720eef
--- /dev/null
+++ b/src/migration-scripts/dns-forwarding/0-to-1
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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/>.
+#
+
+# This migration script will check if there is a allow-from directive configured
+# for the dns forwarding service - if not, the node will be created with the old
+# default values of 0.0.0.0/0 and ::/0
+
+import sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['service', 'dns', 'forwarding']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ if not config.exists(base + ['allow-from']):
+ config.set(base + ['allow-from'], value='0.0.0.0/0', replace=False)
+ config.set(base + ['allow-from'], value='::/0', replace=False)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2
new file mode 100755
index 000000000..8c4f4b5c7
--- /dev/null
+++ b/src/migration-scripts/dns-forwarding/1-to-2
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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/>.
+#
+
+# This migration script will remove the deprecated 'listen-on' statement
+# from the dns forwarding service and will add the corresponding
+# listen-address nodes instead. This is required as PowerDNS can only listen
+# on interface addresses and not on interface names.
+
+from ipaddress import ip_interface
+from sys import argv, exit
+from vyos.ifconfig import Interface
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['service', 'dns', 'forwarding']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['listen-on']):
+ listen_intf = config.return_values(base + ['listen-on'])
+ # Delete node with abandoned command
+ config.delete(base + ['listen-on'])
+
+ # retrieve interface addresses for every configured listen-on interface
+ listen_addr = []
+ for intf in listen_intf:
+ # we need to evaluate the interface section before manipulating the 'intf' variable
+ section = Interface.section(intf)
+ if not section:
+ raise ValueError(f'Invalid interface name {intf}')
+
+ # we need to treat vif and vif-s interfaces differently,
+ # both "real interfaces" use dots for vlan identifiers - those
+ # need to be exchanged with vif and vif-s identifiers
+ if intf.count('.') == 1:
+ # this is a regular VLAN interface
+ intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1]
+ elif intf.count('.') == 2:
+ # this is a QinQ VLAN interface
+ intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2]
+
+ # retrieve corresponding interface addresses in CIDR format
+ # those need to be converted in pure IP addresses without network information
+ path = ['interfaces', section, intf, 'address']
+ for addr in config.return_values(path):
+ listen_addr.append( ip_interface(addr).ip )
+
+ for addr in listen_addr:
+ config.set(base + ['listen-address'], value=addr, replace=False)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
+
+exit(0)
diff --git a/src/migration-scripts/dns-forwarding/2-to-3 b/src/migration-scripts/dns-forwarding/2-to-3
new file mode 100755
index 000000000..01e445b22
--- /dev/null
+++ b/src/migration-scripts/dns-forwarding/2-to-3
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+#
+
+# Sets the new options "addnta" and "recursion-desired" for all
+# 'dns forwarding domain' as this is usually desired
+
+import sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['service', 'dns', 'forwarding']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+if config.exists(base + ['domain']):
+ for domain in config.list_nodes(base + ['domain']):
+ domain_base = base + ['domain', domain]
+ config.set(domain_base + ['addnta'])
+ config.set(domain_base + ['recursion-desired'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/https/0-to-1 b/src/migration-scripts/https/0-to-1
new file mode 100755
index 000000000..23809f5ad
--- /dev/null
+++ b/src/migration-scripts/https/0-to-1
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# * Move server block directives under 'virtual-host' tag node, instead of
+# relying on 'listen-address' tag node
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 2):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+old_base = ['service', 'https', 'listen-address']
+if not config.exists(old_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ new_base = ['service', 'https', 'virtual-host']
+ config.set(new_base)
+ config.set_tag(new_base)
+
+ index = 0
+ for addr in config.list_nodes(old_base):
+ tag_name = f'vhost{index}'
+ config.set(new_base + [tag_name])
+ config.set(new_base + [tag_name, 'listen-address'], value=addr)
+
+ if config.exists(old_base + [addr, 'listen-port']):
+ port = config.return_value(old_base + [addr, 'listen-port'])
+ config.set(new_base + [tag_name, 'listen-port'], value=port)
+
+ if config.exists(old_base + [addr, 'server-name']):
+ names = config.return_values(old_base + [addr, 'server-name'])
+ for name in names:
+ config.set(new_base + [tag_name, 'server-name'], value=name,
+ replace=False)
+
+ index += 1
+
+ config.delete(old_base)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/https/1-to-2 b/src/migration-scripts/https/1-to-2
new file mode 100755
index 000000000..b1cf37ea6
--- /dev/null
+++ b/src/migration-scripts/https/1-to-2
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# * Move 'api virtual-host' list to 'api-restrict virtual-host' so it
+# is owned by https.py instead of http-api.py
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 2):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+old_base = ['service', 'https', 'api', 'virtual-host']
+if not config.exists(old_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ new_base = ['service', 'https', 'api-restrict', 'virtual-host']
+ config.set(new_base)
+
+ names = config.return_values(old_base)
+ for name in names:
+ config.set(new_base, value=name, replace=False)
+
+ config.delete(old_base)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1
new file mode 100755
index 000000000..ee4d6b82c
--- /dev/null
+++ b/src/migration-scripts/interfaces/0-to-1
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+
+# Change syntax of bridge interface
+# - move interface based bridge-group to actual bridge (de-nest)
+# - make stp and igmp-snooping nodes valueless
+# https://phabricator.vyos.net/T1556
+
+import sys
+from vyos.configtree import ConfigTree
+
+def migrate_bridge(config, tree, intf):
+ # check if bridge-group exists
+ tree_bridge = tree + ['bridge-group']
+ if config.exists(tree_bridge):
+ bridge = config.return_value(tree_bridge + ['bridge'])
+ # create new bridge member interface
+ config.set(base + [bridge, 'member', 'interface', intf])
+ # format as tag node to avoid loading problems
+ config.set_tag(base + [bridge, 'member', 'interface'])
+
+ # cost: migrate if configured
+ tree_cost = tree + ['bridge-group', 'cost']
+ if config.exists(tree_cost):
+ cost = config.return_value(tree_cost)
+ # set new node
+ config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost)
+
+ # priority: migrate if configured
+ tree_priority = tree + ['bridge-group', 'priority']
+ if config.exists(tree_priority):
+ priority = config.return_value(tree_priority)
+ # set new node
+ config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority)
+
+ # Delete the old bridge-group assigned to an interface
+ config.delete(tree_bridge)
+
+
+if __name__ == '__main__':
+ if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+ file_name = sys.argv[1]
+
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+ base = ['interfaces', 'bridge']
+
+ if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+ else:
+ #
+ # make stp and igmp-snooping nodes valueless
+ #
+ for br in config.list_nodes(base):
+ # STP: check if enabled
+ if config.exists(base + [br, 'stp']):
+ stp_val = config.return_value(base + [br, 'stp'])
+ # STP: delete node with old syntax
+ config.delete(base + [br, 'stp'])
+ # STP: set new node - if enabled
+ if stp_val == "true":
+ config.set(base + [br, 'stp'], value=None)
+
+ # igmp-snooping: check if enabled
+ if config.exists(base + [br, 'igmp-snooping', 'querier']):
+ igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier'])
+ # igmp-snooping: delete node with old syntax
+ config.delete(base + [br, 'igmp-snooping', 'querier'])
+ # igmp-snooping: set new node - if enabled
+ if igmp_val == "enable":
+ config.set(base + [br, 'igmp', 'querier'], value=None)
+
+ #
+ # move interface based bridge-group to actual bridge (de-nest)
+ #
+ bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless']
+ for type in bridge_types:
+ if not config.exists(['interfaces', type]):
+ continue
+
+ for interface in config.list_nodes(['interfaces', type]):
+ # check if bridge-group exists
+ bridge_group = ['interfaces', type, interface]
+ if config.exists(bridge_group + ['bridge-group']):
+ migrate_bridge(config, bridge_group, interface)
+
+ # We also need to migrate VLAN interfaces
+ vlan_base = ['interfaces', type, interface, 'vif']
+ if config.exists(vlan_base):
+ for vlan in config.list_nodes(vlan_base):
+ intf = "{}.{}".format(interface, vlan)
+ migrate_bridge(config, vlan_base + [vlan], intf)
+
+ # And then we have service VLANs (vif-s) interfaces
+ vlan_base = ['interfaces', type, interface, 'vif-s']
+ if config.exists(vlan_base):
+ for vif_s in config.list_nodes(vlan_base):
+ intf = "{}.{}".format(interface, vif_s)
+ migrate_bridge(config, vlan_base + [vif_s], intf)
+
+ # Every service VLAN can have multiple customer VLANs (vif-c)
+ vlan_c = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c']
+ if config.exists(vlan_c):
+ for vif_c in config.list_nodes(vlan_c):
+ intf = "{}.{}.{}".format(interface, vif_s, vif_c)
+ migrate_bridge(config, vlan_c + [vif_c], intf)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/1-to-2 b/src/migration-scripts/interfaces/1-to-2
new file mode 100755
index 000000000..050137318
--- /dev/null
+++ b/src/migration-scripts/interfaces/1-to-2
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+
+# Change syntax of bond interface
+# - move interface based bond-group to actual bond (de-nest)
+# https://phabricator.vyos.net/T1614
+
+import sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['interfaces', 'bonding']
+
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ #
+ # move interface based bond-group to actual bond (de-nest)
+ #
+ for intf in config.list_nodes(['interfaces', 'ethernet']):
+ # check if bond-group exists
+ if config.exists(['interfaces', 'ethernet', intf, 'bond-group']):
+ # get configured bond interface
+ bond = config.return_value(['interfaces', 'ethernet', intf, 'bond-group'])
+ # delete old interface asigned (nested) bond group
+ config.delete(['interfaces', 'ethernet', intf, 'bond-group'])
+ # create new bond member interface
+ config.set(base + [bond, 'member', 'interface'], value=intf, replace=False)
+
+ #
+ # some combinations were allowed in the past from a CLI perspective
+ # but the kernel overwrote them - remove from CLI to not confuse the users.
+ # In addition new consitency checks are in place so users can't repeat the
+ # mistake. One of those nice issues is https://phabricator.vyos.net/T532
+ for bond in config.list_nodes(base):
+ if config.exists(base + [bond, 'arp-monitor', 'interval']) and config.exists(base + [bond, 'mode']):
+ mode = config.return_value(base + [bond, 'mode'])
+ if mode in ['802.3ad', 'transmit-load-balance', 'adaptive-load-balance']:
+ intvl = int(config.return_value(base + [bond, 'arp-monitor', 'interval']))
+ if intvl > 0:
+ # this is not allowed and the linux kernel replies with:
+ # option arp_interval: mode dependency failed, not supported in mode 802.3ad(4)
+ # option arp_interval: mode dependency failed, not supported in mode balance-alb(6)
+ # option arp_interval: mode dependency failed, not supported in mode balance-tlb(5)
+ #
+ # so we simply disable arp_interval by setting it to 0 and miimon will take care about the link
+ config.set(base + [bond, 'arp-monitor', 'interval'], value='0')
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/10-to-11 b/src/migration-scripts/interfaces/10-to-11
new file mode 100755
index 000000000..6b8e49ed9
--- /dev/null
+++ b/src/migration-scripts/interfaces/10-to-11
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# rename WWAN (wirelessmodem) serial interface from non persistent ttyUSB2 to
+# a bus like name, e.g. "usb0b1.3p1.3"
+
+import os
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+ base = ['interfaces', 'wirelessmodem']
+ if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+ for wwan in config.list_nodes(base):
+ if config.exists(base + [wwan, 'device']):
+ device = config.return_value(base + [wwan, 'device'])
+
+ for root, dirs, files in os.walk('/dev/serial/by-bus'):
+ for file in files:
+ device_file = os.path.realpath(os.path.join(root, file))
+ if os.path.basename(device_file) == device:
+ config.set(base + [wwan, 'device'], value=file, replace=True)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12
new file mode 100755
index 000000000..0dad24642
--- /dev/null
+++ b/src/migration-scripts/interfaces/11-to-12
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# - rename 'dhcpv6-options prefix-delegation' from single node to a new tag node
+# 'dhcpv6-options pd 0'
+# - delete 'sla-len' from CLI - value is calculated on demand
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+
+ for type in config.list_nodes(['interfaces']):
+ for interface in config.list_nodes(['interfaces', type]):
+ # cache current config tree
+ base_path = ['interfaces', type, interface, 'dhcpv6-options']
+ old_base = base_path + ['prefix-delegation']
+ new_base = base_path + ['pd']
+ if config.exists(old_base):
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(old_base, new_base + ['0'])
+ config.delete(old_base)
+
+ for pd in config.list_nodes(new_base):
+ for tmp in config.list_nodes(new_base + [pd, 'interface']):
+ sla_config = new_base + [pd, 'interface', tmp, 'sla-len']
+ if config.exists(sla_config):
+ config.delete(sla_config)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/interfaces/2-to-3 b/src/migration-scripts/interfaces/2-to-3
new file mode 100755
index 000000000..a63a54cdf
--- /dev/null
+++ b/src/migration-scripts/interfaces/2-to-3
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+
+# Change syntax of openvpn encryption settings
+# - move cipher from encryption to encryption cipher
+# https://phabricator.vyos.net/T1704
+
+import sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['interfaces', 'openvpn']
+
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ #
+ # move cipher from "encryption" to "encryption cipher"
+ #
+ for intf in config.list_nodes(['interfaces', 'openvpn']):
+ # Check if encryption is set
+ if config.exists(['interfaces', 'openvpn', intf, 'encryption']):
+ # Get cipher used
+ cipher = config.return_value(['interfaces', 'openvpn', intf, 'encryption'])
+ # Delete old syntax
+ config.delete(['interfaces', 'openvpn', intf, 'encryption'])
+ # Add new syntax to config
+ config.set(['interfaces', 'openvpn', intf, 'encryption', 'cipher'], value=cipher)
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/3-to-4 b/src/migration-scripts/interfaces/3-to-4
new file mode 100755
index 000000000..e3bd25a68
--- /dev/null
+++ b/src/migration-scripts/interfaces/3-to-4
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+
+# Change syntax of wireless interfaces
+# Migrate boolean nodes to valueless
+
+import sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['interfaces', 'wireless']
+
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ for wifi in config.list_nodes(base):
+ # as converting a node to bool is always the same, we can script it
+ to_bool_nodes = ['capabilities ht 40MHz-incapable',
+ 'capabilities ht auto-powersave',
+ 'capabilities ht delayed-block-ack',
+ 'capabilities ht dsss-cck-40',
+ 'capabilities ht greenfield',
+ 'capabilities ht ldpc',
+ 'capabilities ht lsig-protection',
+ 'capabilities ht stbc tx',
+ 'capabilities require-ht',
+ 'capabilities require-vht',
+ 'capabilities vht antenna-pattern-fixed',
+ 'capabilities vht ldpc',
+ 'capabilities vht stbc tx',
+ 'capabilities vht tx-powersave',
+ 'capabilities vht vht-cf',
+ 'expunge-failing-stations',
+ 'isolate-stations']
+
+ for node in to_bool_nodes:
+ if config.exists(base + [wifi, node]):
+ tmp = config.return_value(base + [wifi, node])
+ # delete old node
+ config.delete(base + [wifi, node])
+ # set new node if it was enabled
+ if tmp == 'true':
+ # OLD CLI used camel casing in 40MHz-incapable which is
+ # not supported in the new backend. Convert all to lower-case
+ config.set(base + [wifi, node.lower()])
+
+ # Remove debug node
+ if config.exists(base + [wifi, 'debug']):
+ config.delete(base + [wifi, 'debug'])
+
+ # RADIUS servers
+ if config.exists(base + [wifi, 'security', 'wpa', 'radius-server']):
+ for server in config.list_nodes(base + [wifi, 'security', 'wpa', 'radius-server']):
+ base_server = base + [wifi, 'security', 'wpa', 'radius-server', server]
+
+ # Migrate RADIUS shared secret
+ if config.exists(base_server + ['secret']):
+ key = config.return_value(base_server + ['secret'])
+ # write new configuration node
+ config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'key'], value=key)
+ # format as tag node
+ config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server'])
+
+ # Migrate RADIUS port
+ if config.exists(base_server + ['port']):
+ port = config.return_value(base_server + ['port'])
+ # write new configuration node
+ config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'port'], value=port)
+ # format as tag node
+ config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server'])
+
+ # Migrate RADIUS accounting
+ if config.exists(base_server + ['accounting']):
+ port = config.return_value(base_server + ['accounting'])
+ # write new configuration node
+ config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'accounting'])
+ # format as tag node
+ config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server'])
+
+ # delete old radius-server nodes
+ config.delete(base + [wifi, 'security', 'wpa', 'radius-server'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/4-to-5 b/src/migration-scripts/interfaces/4-to-5
new file mode 100755
index 000000000..2a42c60ff
--- /dev/null
+++ b/src/migration-scripts/interfaces/4-to-5
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+
+# De-nest PPPoE interfaces
+# Migrate boolean nodes to valueless
+
+import sys
+from vyos.configtree import ConfigTree
+
+def migrate_dialer(config, tree, intf):
+ for pppoe in config.list_nodes(tree):
+ # assemble string, 0 -> pppoe0
+ new_base = ['interfaces', 'pppoe']
+ pppoe_base = new_base + ['pppoe' + pppoe]
+ config.set(new_base)
+ # format as tag node to avoid loading problems
+ config.set_tag(new_base)
+
+ # Copy the entire old node to the new one before migrating individual
+ # parts
+ config.copy(tree + [pppoe], pppoe_base)
+
+ # Instead of letting the user choose between auto and none
+ # where auto is default, it makes more sesne to just offer
+ # an option to disable the default behavior (declutter CLI)
+ if config.exists(pppoe_base + ['name-server']):
+ tmp = config.return_value(pppoe_base + ['name-server'])
+ if tmp == "none":
+ config.set(pppoe_base + ['no-peer-dns'])
+ config.delete(pppoe_base + ['name-server'])
+
+ # Migrate user-id and password nodes under an 'authentication'
+ # node
+ if config.exists(pppoe_base + ['user-id']):
+ user = config.return_value(pppoe_base + ['user-id'])
+ config.set(pppoe_base + ['authentication', 'user'], value=user)
+ config.delete(pppoe_base + ['user-id'])
+
+ if config.exists(pppoe_base + ['password']):
+ pwd = config.return_value(pppoe_base + ['password'])
+ config.set(pppoe_base + ['authentication', 'password'], value=pwd)
+ config.delete(pppoe_base + ['password'])
+
+ # remove enable-ipv6 node and rather place it under ipv6 node
+ if config.exists(pppoe_base + ['enable-ipv6']):
+ config.set(pppoe_base + ['ipv6', 'enable'])
+ config.delete(pppoe_base + ['enable-ipv6'])
+
+ # Source interface migration
+ config.set(pppoe_base + ['source-interface'], value=intf)
+
+ # Remove IPv6 router-advert nodes as this makes no sense on a
+ # client diale rinterface to send RAs back into the network
+ # https://phabricator.vyos.net/T2055
+ ipv6_ra = pppoe_base + ['ipv6', 'router-advert']
+ if config.exists(ipv6_ra):
+ config.delete(ipv6_ra)
+
+
+if __name__ == '__main__':
+ if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = sys.argv[1]
+
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+ pppoe_links = ['bonding', 'ethernet']
+
+ for link_type in pppoe_links:
+ if not config.exists(['interfaces', link_type]):
+ continue
+
+ for interface in config.list_nodes(['interfaces', link_type]):
+ # check if PPPoE exists
+ base_if = ['interfaces', link_type, interface]
+ pppoe_if = base_if + ['pppoe']
+ if config.exists(pppoe_if):
+ for dialer in config.list_nodes(pppoe_if):
+ migrate_dialer(config, pppoe_if, interface)
+
+ # Delete old PPPoE interface
+ config.delete(pppoe_if)
+
+ # bail out early if there are no VLAN interfaces to migrate
+ if not config.exists(base_if + ['vif']):
+ continue
+
+ # Migrate PPPoE interfaces attached to a VLAN
+ for vlan in config.list_nodes(base_if + ['vif']):
+ vlan_if = base_if + ['vif', vlan]
+ pppoe_if = vlan_if + ['pppoe']
+ if config.exists(pppoe_if):
+ for dialer in config.list_nodes(pppoe_if):
+ intf = "{}.{}".format(interface, vlan)
+ migrate_dialer(config, pppoe_if, intf)
+
+ # Delete old PPPoE interface
+ config.delete(pppoe_if)
+
+ # Add interface description that this is required for PPPoE
+ if not config.exists(vlan_if + ['description']):
+ config.set(vlan_if + ['description'], value='PPPoE link interface')
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6
new file mode 100755
index 000000000..1291751d8
--- /dev/null
+++ b/src/migration-scripts/interfaces/5-to-6
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Migrate IPv6 router advertisments from a nested interface configuration to
+# a denested "service router-advert"
+
+import sys
+from vyos.configtree import ConfigTree
+
+def copy_rtradv(c, old_base, interface):
+ base = ['service', 'router-advert', 'interface']
+
+ if c.exists(old_base):
+ if not c.exists(base):
+ c.set(base)
+ c.set_tag(base)
+
+ # take the old node as a whole and copy it to new new path,
+ # additional migrations will be done afterwards
+ new_base = base + [interface]
+ c.copy(old_base, new_base)
+ c.delete(old_base)
+
+ # cur-hop-limit has been renamed to hop-limit
+ if c.exists(new_base + ['cur-hop-limit']):
+ c.rename(new_base + ['cur-hop-limit'], 'hop-limit')
+
+ bool_cleanup = ['managed-flag', 'other-config-flag']
+ for bool in bool_cleanup:
+ if c.exists(new_base + [bool]):
+ tmp = c.return_value(new_base + [bool])
+ c.delete(new_base + [bool])
+ if tmp == 'true':
+ c.set(new_base + [bool])
+
+ # max/min interval moved to subnode
+ intervals = ['max-interval', 'min-interval']
+ for interval in intervals:
+ if c.exists(new_base + [interval]):
+ tmp = c.return_value(new_base + [interval])
+ c.delete(new_base + [interval])
+ min_max = interval.split('-')[0]
+ c.set(new_base + ['interval', min_max], value=tmp)
+
+ # cleanup boolean nodes in individual prefix
+ prefix_base = new_base + ['prefix']
+ if c.exists(prefix_base):
+ for prefix in config.list_nodes(prefix_base):
+ if c.exists(prefix_base + [prefix, 'autonomous-flag']):
+ tmp = c.return_value(prefix_base + [prefix, 'autonomous-flag'])
+ c.delete(prefix_base + [prefix, 'autonomous-flag'])
+ if tmp == 'false':
+ c.set(prefix_base + [prefix, 'no-autonomous-flag'])
+
+ if c.exists(prefix_base + [prefix, 'on-link-flag']):
+ tmp = c.return_value(prefix_base + [prefix, 'on-link-flag'])
+ c.delete(prefix_base + [prefix, 'on-link-flag'])
+ if tmp == 'true':
+ c.set(prefix_base + [prefix, 'on-link-flag'])
+
+ # router advertisement can be individually disabled per interface
+ # the node has been renamed from send-advert {true | false} to no-send-advert
+ if c.exists(new_base + ['send-advert']):
+ tmp = c.return_value(new_base + ['send-advert'])
+ c.delete(new_base + ['send-advert'])
+ if tmp == 'false':
+ c.set(new_base + ['no-send-advert'])
+
+ # link-mtu advertisement was formerly disabled by setting its value to 0
+ # ... this makes less sense - if it should not be send, just do not
+ # configure it
+ if c.exists(new_base + ['link-mtu']):
+ tmp = c.return_value(new_base + ['link-mtu'])
+ if tmp == '0':
+ c.delete(new_base + ['link-mtu'])
+
+if __name__ == '__main__':
+ if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = sys.argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+
+ # list all individual interface types like dummy, ethernet and so on
+ for if_type in config.list_nodes(['interfaces']):
+ base_if_type = ['interfaces', if_type]
+
+ # for every individual interface we need to check if there is an
+ # ipv6 ra configured ... and also for every VIF (VLAN) interface
+ for intf in config.list_nodes(base_if_type):
+ old_base = base_if_type + [intf, 'ipv6', 'router-advert']
+ copy_rtradv(config, old_base, intf)
+
+ vif_base = base_if_type + [intf, 'vif']
+ if config.exists(vif_base):
+ for vif in config.list_nodes(vif_base):
+ old_base = vif_base + [vif, 'ipv6', 'router-advert']
+ vlan_name = f'{intf}.{vif}'
+ copy_rtradv(config, old_base, vlan_name)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/6-to-7 b/src/migration-scripts/interfaces/6-to-7
new file mode 100755
index 000000000..220c7e601
--- /dev/null
+++ b/src/migration-scripts/interfaces/6-to-7
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Remove network provider name from CLI and rather use provider APN from CLI
+
+import sys
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = sys.argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+ base = ['interfaces', 'wirelessmodem']
+
+ if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+ # list all individual wwan/wireless modem interfaces
+ for i in config.list_nodes(base):
+ iface = base + [i]
+
+ # only three carries have been supported in the past, thus
+ # this will be fairly simple \o/ - and only one (AT&T) did
+ # configure an APN
+ if config.exists(iface + ['network']):
+ network = config.return_value(iface + ['network'])
+ if network == "att":
+ apn = 'isp.cingular'
+ config.set(iface + ['apn'], value=apn)
+
+ config.delete(iface + ['network'])
+
+ # synchronize DNS configuration with PPPoE interfaces to have a
+ # uniform CLI experience
+ if config.exists(iface + ['no-dns']):
+ config.rename(iface + ['no-dns'], 'no-peer-dns')
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/7-to-8 b/src/migration-scripts/interfaces/7-to-8
new file mode 100755
index 000000000..a4051301f
--- /dev/null
+++ b/src/migration-scripts/interfaces/7-to-8
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# Split WireGuard endpoint into address / port nodes to make use of common
+# validators
+
+import os
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+from vyos.util import chown, chmod_750
+
+def migrate_default_keys():
+ kdir = r'/config/auth/wireguard'
+ if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'):
+ location = f'{kdir}/default'
+ if not os.path.exists(location):
+ os.makedirs(location)
+
+ chown(location, 'root', 'vyattacfg')
+ chmod_750(location)
+ os.rename(f'{kdir}/private.key', f'{location}/private.key')
+ os.rename(f'{kdir}/public.key', f'{location}/public.key')
+
+if __name__ == '__main__':
+ if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+ base = ['interfaces', 'wireguard']
+
+ migrate_default_keys()
+
+ if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+ # list all individual wireguard interface isntance
+ for i in config.list_nodes(base):
+ iface = base + [i]
+ for peer in config.list_nodes(iface + ['peer']):
+ base_peer = iface + ['peer', peer]
+ if config.exists(base_peer + ['endpoint']):
+ endpoint = config.return_value(base_peer + ['endpoint'])
+ address = endpoint.split(':')[0]
+ port = endpoint.split(':')[1]
+ # delete old node
+ config.delete(base_peer + ['endpoint'])
+ # setup new nodes
+ config.set(base_peer + ['address'], value=address)
+ config.set(base_peer + ['port'], value=port)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/interfaces/8-to-9 b/src/migration-scripts/interfaces/8-to-9
new file mode 100755
index 000000000..2d1efd418
--- /dev/null
+++ b/src/migration-scripts/interfaces/8-to-9
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# Rename link nodes to source-interface for the following interface types:
+# - vxlan
+# - pseudo-ethernet
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+
+ for if_type in ['vxlan', 'pseudo-ethernet']:
+ base = ['interfaces', if_type]
+ if not config.exists(base):
+ # Nothing to do
+ continue
+
+ # list all individual interface isntance
+ for i in config.list_nodes(base):
+ iface = base + [i]
+ if config.exists(iface + ['link']):
+ config.rename(iface + ['link'], 'source-interface')
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/interfaces/9-to-10 b/src/migration-scripts/interfaces/9-to-10
new file mode 100755
index 000000000..4aa2c42b5
--- /dev/null
+++ b/src/migration-scripts/interfaces/9-to-10
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# - rename CLI node 'dhcpv6-options delgate' to 'dhcpv6-options prefix-delegation
+# interface'
+# - rename CLI node 'interface-id' for prefix-delegation to 'address' as it
+# represents the local interface IPv6 address assigned by DHCPv6-PD
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+
+ for intf_type in config.list_nodes(['interfaces']):
+ for intf in config.list_nodes(['interfaces', intf_type]):
+ # cache current config tree
+ base_path = ['interfaces', intf_type, intf, 'dhcpv6-options',
+ 'delegate']
+
+ if config.exists(base_path):
+ # cache new config tree
+ new_path = ['interfaces', intf_type, intf, 'dhcpv6-options',
+ 'prefix-delegation']
+ if not config.exists(new_path):
+ config.set(new_path)
+
+ # copy to new node
+ config.copy(base_path, new_path + ['interface'])
+
+ # rename interface-id to address
+ for interface in config.list_nodes(new_path + ['interface']):
+ config.rename(new_path + ['interface', interface, 'interface-id'], 'address')
+
+ # delete old noe
+ config.delete(base_path)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/ipoe-server/0-to-1 b/src/migration-scripts/ipoe-server/0-to-1
new file mode 100755
index 000000000..f328ebced
--- /dev/null
+++ b/src/migration-scripts/ipoe-server/0-to-1
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# - remove primary/secondary identifier from nameserver
+# - Unifi RADIUS configuration by placing it all under "authentication radius" node
+
+import os
+import sys
+
+from sys import argv, exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'ipoe-server']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+
+ # Migrate IPv4 DNS servers
+ dns_base = base + ['dns-server']
+ if config.exists(dns_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(dns_base + [server]):
+ dns = config.return_value(dns_base + [server])
+ config.set(base + ['name-server'], value=dns, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate IPv6 DNS servers
+ dns_base = base + ['dnsv6-server']
+ if config.exists(dns_base):
+ for server in ['server-1', 'server-2', 'server-3']:
+ if config.exists(dns_base + [server]):
+ dns = config.return_value(dns_base + [server])
+ config.set(base + ['name-server'], value=dns, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate radius-settings node to RADIUS and use this as base for the
+ # later migration of the RADIUS servers - this will save a lot of code
+ radius_settings = base + ['authentication', 'radius-settings']
+ if config.exists(radius_settings):
+ config.rename(radius_settings, 'radius')
+
+ # Migrate RADIUS dynamic author / change of authorisation server
+ dae_old = base + ['authentication', 'radius', 'dae-server']
+ if config.exists(dae_old):
+ config.rename(dae_old, 'dynamic-author')
+ dae_new = base + ['authentication', 'radius', 'dynamic-author']
+
+ if config.exists(dae_new + ['ip-address']):
+ config.rename(dae_new + ['ip-address'], 'server')
+
+ if config.exists(dae_new + ['secret']):
+ config.rename(dae_new + ['secret'], 'key')
+
+ # Migrate RADIUS server
+ radius_server = base + ['authentication', 'radius-server']
+ if config.exists(radius_server):
+ new_base = base + ['authentication', 'radius', 'server']
+ config.set(new_base)
+ config.set_tag(new_base)
+ for server in config.list_nodes(radius_server):
+ old_base = radius_server + [server]
+ config.copy(old_base, new_base + [server])
+
+ # migrate key
+ if config.exists(new_base + [server, 'secret']):
+ config.rename(new_base + [server, 'secret'], 'key')
+
+ # remove old req-limit node
+ if config.exists(new_base + [server, 'req-limit']):
+ config.delete(new_base + [server, 'req-limit'])
+
+ config.delete(radius_server)
+
+ # Migrate IPv6 prefixes
+ ipv6_base = base + ['client-ipv6-pool']
+ if config.exists(ipv6_base + ['prefix']):
+ prefix_old = config.return_values(ipv6_base + ['prefix'])
+ # delete old prefix CLI nodes
+ config.delete(ipv6_base + ['prefix'])
+ # create ned prefix tag node
+ config.set(ipv6_base + ['prefix'])
+ config.set_tag(ipv6_base + ['prefix'])
+
+ for p in prefix_old:
+ prefix = p.split(',')[0]
+ mask = p.split(',')[1]
+ config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask)
+
+ if config.exists(ipv6_base + ['delegate-prefix']):
+ prefix_old = config.return_values(ipv6_base + ['delegate-prefix'])
+ # delete old delegate prefix CLI nodes
+ config.delete(ipv6_base + ['delegate-prefix'])
+ # create ned delegation tag node
+ config.set(ipv6_base + ['delegate'])
+ config.set_tag(ipv6_base + ['delegate'])
+
+ for p in prefix_old:
+ prefix = p.split(',')[0]
+ mask = p.split(',')[1]
+ config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/ipsec/4-to-5 b/src/migration-scripts/ipsec/4-to-5
new file mode 100755
index 000000000..b64aa8462
--- /dev/null
+++ b/src/migration-scripts/ipsec/4-to-5
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+
+# log-modes have changed, keyword all to any
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ctree = ConfigTree(config_file)
+
+if not ctree.exists(['vpn', 'ipsec', 'logging','log-modes']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ lmodes = ctree.return_values(['vpn', 'ipsec', 'logging','log-modes'])
+ for mode in lmodes:
+ if mode == 'all':
+ ctree.set(['vpn', 'ipsec', 'logging','log-modes'], value='any', replace=True)
+
+ try:
+ open(file_name,'w').write(ctree.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/l2tp/0-to-1 b/src/migration-scripts/l2tp/0-to-1
new file mode 100755
index 000000000..686ebc655
--- /dev/null
+++ b/src/migration-scripts/l2tp/0-to-1
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+
+# Unclutter L2TP VPN configuiration - move radius-server top level tag
+# nodes to a regular node which now also configures the radius source address
+# used when querying a radius server
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+cfg_base = ['vpn', 'l2tp', 'remote-access', 'authentication']
+if not config.exists(cfg_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Migrate "vpn l2tp authentication radius-source-address" to new
+ # "vpn l2tp authentication radius source-address"
+ if config.exists(cfg_base + ['radius-source-address']):
+ address = config.return_value(cfg_base + ['radius-source-address'])
+ # delete old configuration node
+ config.delete(cfg_base + ['radius-source-address'])
+ # write new configuration node
+ config.set(cfg_base + ['radius', 'source-address'], value=address)
+
+ # Migrate "vpn l2tp authentication radius-server" tag node to new
+ # "vpn l2tp authentication radius server" tag node
+ if config.exists(cfg_base + ['radius-server']):
+ for server in config.list_nodes(cfg_base + ['radius-server']):
+ base_server = cfg_base + ['radius-server', server]
+ key = config.return_value(base_server + ['key'])
+
+ # delete old configuration node
+ config.delete(base_server)
+ # write new configuration node
+ config.set(cfg_base + ['radius', 'server', server, 'key'], value=key)
+
+ # format as tag node
+ config.set_tag(cfg_base + ['radius', 'server'])
+
+ # delete top level tag node
+ if config.exists(cfg_base + ['radius-server']):
+ config.delete(cfg_base + ['radius-server'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/l2tp/1-to-2 b/src/migration-scripts/l2tp/1-to-2
new file mode 100755
index 000000000..c46eba8f8
--- /dev/null
+++ b/src/migration-scripts/l2tp/1-to-2
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+
+# Delete depricated outside-nexthop address
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+cfg_base = ['vpn', 'l2tp', 'remote-access']
+if not config.exists(cfg_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ if config.exists(cfg_base + ['outside-nexthop']):
+ config.delete(cfg_base + ['outside-nexthop'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/l2tp/2-to-3 b/src/migration-scripts/l2tp/2-to-3
new file mode 100755
index 000000000..3472ee3ed
--- /dev/null
+++ b/src/migration-scripts/l2tp/2-to-3
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# - remove primary/secondary identifier from nameserver
+# - TODO: remove radius server req-limit
+
+import os
+import sys
+
+from sys import argv, exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'l2tp', 'remote-access']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+
+ # Migrate IPv4 DNS servers
+ dns_base = base + ['dns-servers']
+ if config.exists(dns_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(dns_base + [server]):
+ dns = config.return_value(dns_base + [server])
+ config.set(base + ['name-server'], value=dns, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate IPv6 DNS servers
+ dns_base = base + ['dnsv6-servers']
+ if config.exists(dns_base):
+ for server in config.return_values(dns_base):
+ config.set(base + ['name-server'], value=server, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate IPv4 WINS servers
+ wins_base = base + ['wins-servers']
+ if config.exists(wins_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(wins_base + [server]):
+ wins = config.return_value(wins_base + [server])
+ config.set(base + ['wins-server'], value=wins, replace=False)
+
+ config.delete(wins_base)
+
+
+ # Remove RADIUS server req-limit node
+ radius_base = base + ['authentication', 'radius']
+ if config.exists(radius_base):
+ for server in config.list_nodes(radius_base + ['server']):
+ if config.exists(radius_base + ['server', server, 'req-limit']):
+ config.delete(radius_base + ['server', server, 'req-limit'])
+
+ # Migrate IPv6 prefixes
+ ipv6_base = base + ['client-ipv6-pool']
+ if config.exists(ipv6_base + ['prefix']):
+ prefix_old = config.return_values(ipv6_base + ['prefix'])
+ # delete old prefix CLI nodes
+ config.delete(ipv6_base + ['prefix'])
+ # create ned prefix tag node
+ config.set(ipv6_base + ['prefix'])
+ config.set_tag(ipv6_base + ['prefix'])
+
+ for p in prefix_old:
+ prefix = p.split(',')[0]
+ mask = p.split(',')[1]
+ config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask)
+
+ if config.exists(ipv6_base + ['delegate-prefix']):
+ prefix_old = config.return_values(ipv6_base + ['delegate-prefix'])
+ # delete old delegate prefix CLI nodes
+ config.delete(ipv6_base + ['delegate-prefix'])
+ # create ned delegation tag node
+ config.set(ipv6_base + ['delegate'])
+ config.set_tag(ipv6_base + ['delegate'])
+
+ for p in prefix_old:
+ prefix = p.split(',')[0]
+ mask = p.split(',')[1]
+ config.set(ipv6_base + ['delegate', prefix, 'delegate-prefix'], value=mask)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/lldp/0-to-1 b/src/migration-scripts/lldp/0-to-1
new file mode 100755
index 000000000..5f66570e7
--- /dev/null
+++ b/src/migration-scripts/lldp/0-to-1
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+
+# Delete "set service lldp interface <interface> location civic-based" option
+# as it was broken most of the time anyways
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'lldp', 'interface']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Delete nodes with abandoned CLI syntax
+ for interface in config.list_nodes(base):
+ if config.exists(base + [interface, 'location', 'civic-based']):
+ config.delete(base + [interface, 'location', 'civic-based'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/nat/4-to-5 b/src/migration-scripts/nat/4-to-5
new file mode 100755
index 000000000..dda191719
--- /dev/null
+++ b/src/migration-scripts/nat/4-to-5
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# Drop the enable/disable from the nat "log" node. If log node is specified
+# it is "enabled"
+
+from sys import argv,exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['nat']):
+ # Nothing to do
+ exit(0)
+else:
+ for direction in ['source', 'destination']:
+ if not config.exists(['nat', direction]):
+ continue
+
+ for rule in config.list_nodes(['nat', direction, 'rule']):
+ base = ['nat', direction, 'rule', rule]
+
+ # Check if the log node exists and if log is enabled,
+ # migrate it to the new valueless 'log' node
+ if config.exists(base + ['log']):
+ tmp = config.return_value(base + ['log'])
+ config.delete(base + ['log'])
+ if tmp == 'enable':
+ config.set(base + ['log'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/ntp/0-to-1 b/src/migration-scripts/ntp/0-to-1
new file mode 100755
index 000000000..9c66f3109
--- /dev/null
+++ b/src/migration-scripts/ntp/0-to-1
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+
+# Delete "set system ntp server <n> dynamic" option
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['system', 'ntp']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Delete abandoned leaf node if found inside tag node for
+ # "set system ntp server <n> dynamic"
+ base = ['system', 'ntp', 'server']
+ for server in config.list_nodes(base):
+ if config.exists(base + [server, 'dynamic']):
+ config.delete(base + [server, 'dynamic'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/pppoe-server/0-to-1 b/src/migration-scripts/pppoe-server/0-to-1
new file mode 100755
index 000000000..bb24211b6
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/0-to-1
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+# Convert "service pppoe-server authentication radius-server node key"
+# to:
+# "service pppoe-server authentication radius-server node secret"
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ctree = ConfigTree(config_file)
+
+
+if not ctree.exists(['service', 'pppoe-server', 'authentication','radius-server']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ nodes = ctree.list_nodes(['service', 'pppoe-server', 'authentication','radius-server'])
+ for node in nodes:
+ if ctree.exists(['service', 'pppoe-server', 'authentication', 'radius-server', node, 'key']):
+ val = ctree.return_value(['service', 'pppoe-server', 'authentication', 'radius-server', node, 'key'])
+ ctree.set(['service', 'pppoe-server', 'authentication', 'radius-server', node, 'secret'], value=val, replace=False)
+ ctree.delete(['service', 'pppoe-server', 'authentication', 'radius-server', node, 'key'])
+ try:
+ open(file_name,'w').write(ctree.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/pppoe-server/1-to-2 b/src/migration-scripts/pppoe-server/1-to-2
new file mode 100755
index 000000000..fa83896d3
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/1-to-2
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+# Convert "service pppoe-server interface ethX"
+# to:
+# "service pppoe-server interface ethX {}"
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ctree = ConfigTree(config_file)
+cbase = ['service', 'pppoe-server','interface']
+
+if not ctree.exists(cbase):
+ sys.exit(0)
+else:
+ nics = ctree.return_values(cbase)
+ # convert leafNode to a tagNode
+ ctree.set(cbase)
+ ctree.set_tag(cbase)
+ for nic in nics:
+ ctree.set(cbase + [nic])
+
+ try:
+ open(file_name,'w').write(ctree.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
+
diff --git a/src/migration-scripts/pppoe-server/2-to-3 b/src/migration-scripts/pppoe-server/2-to-3
new file mode 100755
index 000000000..5f9730a41
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/2-to-3
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# - remove primary/secondary identifier from nameserver
+
+import os
+
+from sys import argv, exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'pppoe-server']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+
+ # Migrate IPv4 DNS servers
+ dns_base = base + ['dns-servers']
+ if config.exists(dns_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(dns_base + [server]):
+ dns = config.return_value(dns_base + [server])
+ config.set(base + ['name-server'], value=dns, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate IPv6 DNS servers
+ dns_base = base + ['dnsv6-servers']
+ if config.exists(dns_base):
+ for server in ['server-1', 'server-2', 'server-3']:
+ if config.exists(dns_base + [server]):
+ dns = config.return_value(dns_base + [server])
+ config.set(base + ['name-server'], value=dns, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate IPv4 WINS servers
+ wins_base = base + ['wins-servers']
+ if config.exists(wins_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(wins_base + [server]):
+ wins = config.return_value(wins_base + [server])
+ config.set(base + ['wins-server'], value=wins, replace=False)
+
+ config.delete(wins_base)
+
+ # Migrate radius-settings node to RADIUS and use this as base for the
+ # later migration of the RADIUS servers - this will save a lot of code
+ radius_settings = base + ['authentication', 'radius-settings']
+ if config.exists(radius_settings):
+ config.rename(radius_settings, 'radius')
+
+ # Migrate RADIUS dynamic author / change of authorisation server
+ dae_old = base + ['authentication', 'radius', 'dae-server']
+ if config.exists(dae_old):
+ config.rename(dae_old, 'dynamic-author')
+ dae_new = base + ['authentication', 'radius', 'dynamic-author']
+
+ if config.exists(dae_new + ['ip-address']):
+ config.rename(dae_new + ['ip-address'], 'server')
+
+ if config.exists(dae_new + ['secret']):
+ config.rename(dae_new + ['secret'], 'key')
+
+ # Migrate RADIUS server
+ radius_server = base + ['authentication', 'radius-server']
+ if config.exists(radius_server):
+ new_base = base + ['authentication', 'radius', 'server']
+ config.set(new_base)
+ config.set_tag(new_base)
+ for server in config.list_nodes(radius_server):
+ old_base = radius_server + [server]
+ config.copy(old_base, new_base + [server])
+
+ # migrate key
+ if config.exists(new_base + [server, 'secret']):
+ config.rename(new_base + [server, 'secret'], 'key')
+
+ # remove old req-limit node
+ if config.exists(new_base + [server, 'req-limit']):
+ config.delete(new_base + [server, 'req-limit'])
+
+ config.delete(radius_server)
+
+ # Migrate IPv6 prefixes
+ ipv6_base = base + ['client-ipv6-pool']
+ if config.exists(ipv6_base + ['prefix']):
+ prefix_old = config.return_values(ipv6_base + ['prefix'])
+ # delete old prefix CLI nodes
+ config.delete(ipv6_base + ['prefix'])
+ # create ned prefix tag node
+ config.set(ipv6_base + ['prefix'])
+ config.set_tag(ipv6_base + ['prefix'])
+
+ for p in prefix_old:
+ prefix = p.split(',')[0]
+ mask = p.split(',')[1]
+ config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask)
+
+ if config.exists(ipv6_base + ['delegate-prefix']):
+ prefix_old = config.return_values(ipv6_base + ['delegate-prefix'])
+ # delete old delegate prefix CLI nodes
+ config.delete(ipv6_base + ['delegate-prefix'])
+ # create ned delegation tag node
+ config.set(ipv6_base + ['delegate'])
+ config.set_tag(ipv6_base + ['delegate'])
+
+ for p in prefix_old:
+ prefix = p.split(',')[0]
+ mask = p.split(',')[1]
+ config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/pppoe-server/3-to-4 b/src/migration-scripts/pppoe-server/3-to-4
new file mode 100755
index 000000000..ed5a01625
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/3-to-4
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# change mppe node to a leaf node with value prefer
+
+import os
+
+from sys import argv, exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'pppoe-server']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+ mppe_base = base + ['ppp-options', 'mppe']
+ if config.exists(mppe_base):
+ # drop node first ...
+ config.delete(mppe_base)
+ # ... and set new default
+ config.set(mppe_base, value='prefer')
+
+ print(config.to_string())
+ exit(1)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/pptp/0-to-1 b/src/migration-scripts/pptp/0-to-1
new file mode 100755
index 000000000..d0c7a83b5
--- /dev/null
+++ b/src/migration-scripts/pptp/0-to-1
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+
+# Unclutter PPTP VPN configuiration - move radius-server top level tag
+# nodes to a regular node which now also configures the radius source address
+# used when querying a radius server
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+cfg_base = ['vpn', 'pptp', 'remote-access', 'authentication']
+if not config.exists(cfg_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Migrate "vpn pptp authentication radius-source-address" to new
+ # "vpn pptp authentication radius source-address"
+ if config.exists(cfg_base + ['radius-source-address']):
+ address = config.return_value(cfg_base + ['radius-source-address'])
+ # delete old configuration node
+ config.delete(cfg_base + ['radius-source-address'])
+ # write new configuration node
+ config.set(cfg_base + ['radius', 'source-address'], value=address)
+
+ # Migrate "vpn pptp authentication radius-server" tag node to new
+ # "vpn pptp authentication radius server" tag node
+ for server in config.list_nodes(cfg_base + ['radius-server']):
+ base_server = cfg_base + ['radius-server', server]
+ key = config.return_value(base_server + ['key'])
+
+ # delete old configuration node
+ config.delete(base_server)
+ # write new configuration node
+ config.set(cfg_base + ['radius', 'server', server, 'key'], value=key)
+
+ # format as tag node
+ config.set_tag(cfg_base + ['radius', 'server'])
+
+ # delete top level tag node
+ if config.exists(cfg_base + ['radius-server']):
+ config.delete(cfg_base + ['radius-server'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/pptp/1-to-2 b/src/migration-scripts/pptp/1-to-2
new file mode 100755
index 000000000..a13cc3a4f
--- /dev/null
+++ b/src/migration-scripts/pptp/1-to-2
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# - migrate dns-servers node to common name-servers
+# - remove radios req-limit node
+
+from sys import argv, exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'pptp', 'remote-access']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+ # Migrate IPv4 DNS servers
+ dns_base = base + ['dns-servers']
+ if config.exists(dns_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(dns_base + [server]):
+ dns = config.return_value(dns_base + [server])
+ config.set(base + ['name-server'], value=dns, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate IPv4 WINS servers
+ wins_base = base + ['wins-servers']
+ if config.exists(wins_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(wins_base + [server]):
+ wins = config.return_value(wins_base + [server])
+ config.set(base + ['wins-server'], value=wins, replace=False)
+
+ config.delete(wins_base)
+
+ # Remove RADIUS server req-limit node
+ radius_base = base + ['authentication', 'radius']
+ if config.exists(radius_base):
+ for server in config.list_nodes(radius_base + ['server']):
+ if config.exists(radius_base + ['server', server, 'req-limit']):
+ config.delete(radius_base + ['server', server, 'req-limit'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/quagga/2-to-3 b/src/migration-scripts/quagga/2-to-3
new file mode 100755
index 000000000..4c1cd86a3
--- /dev/null
+++ b/src/migration-scripts/quagga/2-to-3
@@ -0,0 +1,203 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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 sys
+
+from vyos.configtree import ConfigTree
+
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+def migrate_neighbor(config, neighbor_path, neighbor):
+ if config.exists(neighbor_path):
+ neighbors = config.list_nodes(neighbor_path)
+ for neighbor in neighbors:
+ # Move the valueless options: as-override, next-hop-self, route-reflector-client, route-server-client,
+ # remove-private-as
+ for valueless_option in ['as-override', 'nexthop-self', 'route-reflector-client', 'route-server-client',
+ 'remove-private-as']:
+ if config.exists(neighbor_path + [neighbor, valueless_option]):
+ config.set(neighbor_path + [neighbor] + af_path + [valueless_option])
+ config.delete(neighbor_path + [neighbor, valueless_option])
+
+ # Move filter options: distribute-list, filter-list, prefix-list, and route-map
+ # They share the same syntax inside so we can group them
+ for filter_type in ['distribute-list', 'filter-list', 'prefix-list', 'route-map']:
+ if config.exists(neighbor_path + [neighbor, filter_type]):
+ for filter_dir in ['import', 'export']:
+ if config.exists(neighbor_path + [neighbor, filter_type, filter_dir]):
+ filter_name = config.return_value(neighbor_path + [neighbor, filter_type, filter_dir])
+ config.set(neighbor_path + [neighbor] + af_path + [filter_type, filter_dir], value=filter_name)
+ config.delete(neighbor_path + [neighbor, filter_type])
+
+ # Move simple leaf node options: maximum-prefix, unsuppress-map, weight
+ for leaf_option in ['maximum-prefix', 'unsuppress-map', 'weight']:
+ if config.exists(neighbor_path + [neighbor, leaf_option]):
+ if config.exists(neighbor_path + [neighbor, leaf_option]):
+ leaf_opt_value = config.return_value(neighbor_path + [neighbor, leaf_option])
+ config.set(neighbor_path + [neighbor] + af_path + [leaf_option], value=leaf_opt_value)
+ config.delete(neighbor_path + [neighbor, leaf_option])
+
+ # The rest is special cases, for better or worse
+
+ # Move allowas-in
+ if config.exists(neighbor_path + [neighbor, 'allowas-in']):
+ if config.exists(neighbor_path + [neighbor, 'allowas-in', 'number']):
+ allowas_in = config.return_value(neighbor_path + [neighbor, 'allowas-in', 'number'])
+ config.set(neighbor_path + [neighbor] + af_path + ['allowas-in', 'number'], value=allowas_in)
+ config.delete(neighbor_path + [neighbor, 'allowas-in'])
+
+ # Move attribute-unchanged options
+ if config.exists(neighbor_path + [neighbor, 'attribute-unchanged']):
+ for attr in ['as-path', 'med', 'next-hop']:
+ if config.exists(neighbor_path + [neighbor, 'attribute-unchanged', attr]):
+ config.set(neighbor_path + [neighbor] + af_path + ['attribute-unchanged', attr])
+ config.delete(neighbor_path + [neighbor, 'attribute-unchanged', attr])
+ config.delete(neighbor_path + [neighbor, 'attribute-unchanged'])
+
+ # Move capability options
+ if config.exists(neighbor_path + [neighbor, 'capability']):
+ # "capability dynamic" is a peer-global option, we only migrate ORF
+ if config.exists(neighbor_path + [neighbor, 'capability', 'orf']):
+ if config.exists(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list']):
+ for orf in ['send', 'receive']:
+ if config.exists(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list', orf]):
+ config.set(neighbor_path + [neighbor] + af_path + ['capability', 'orf', 'prefix-list', orf])
+ config.delete(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list', orf])
+ config.delete(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list'])
+ config.delete(neighbor_path + [neighbor, 'capability', 'orf'])
+
+ # Move default-originate
+ if config.exists(neighbor_path + [neighbor, 'default-originate']):
+ if config.exists(neighbor_path + [neighbor, 'default-originate', 'route-map']):
+ route_map = config.return_value(neighbor_path + [neighbor, 'default-originate', 'route-map'])
+ config.set(neighbor_path + [neighbor] + af_path + ['default-originate', 'route-map'], value=route_map)
+ else:
+ # Empty default-originate node is meaningful so we re-create it
+ config.set(neighbor_path + [neighbor] + af_path + ['default-originate'])
+ config.delete(neighbor_path + [neighbor, 'default-originate'])
+
+ # Move soft-reconfiguration
+ if config.exists(neighbor_path + [neighbor, 'soft-reconfiguration']):
+ if config.exists(neighbor_path + [neighbor, 'soft-reconfiguration', 'inbound']):
+ config.set(neighbor_path + [neighbor] + af_path + ['soft-reconfiguration', 'inbound'])
+ # Empty soft-reconfiguration is meaningless, so we just remove it
+ config.delete(neighbor_path + [neighbor, 'soft-reconfiguration'])
+
+ # Move disable-send-community
+ if config.exists(neighbor_path + [neighbor, 'disable-send-community']):
+ for comm_type in ['standard', 'extended']:
+ if config.exists(neighbor_path + [neighbor, 'disable-send-community', comm_type]):
+ config.set(neighbor_path + [neighbor] + af_path + ['disable-send-community', comm_type])
+ config.delete(neighbor_path + [neighbor, 'disable-send-community', comm_type])
+ config.delete(neighbor_path + [neighbor, 'disable-send-community'])
+
+
+if not config.exists(['protocols', 'bgp']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Just to avoid writing it so many times
+ af_path = ['address-family', 'ipv4-unicast']
+
+ # Check if BGP is actually configured and obtain the ASN
+ asn_list = config.list_nodes(['protocols', 'bgp'])
+ if asn_list:
+ # There's always just one BGP node, if any
+ asn = asn_list[0]
+ bgp_path = ['protocols', 'bgp', asn]
+ else:
+ # There's actually no BGP, just its empty shell
+ sys.exit(0)
+
+ ## Move global IPv4-specific BGP options to "address-family ipv4-unicast"
+
+ # Move networks
+ network_path = ['protocols', 'bgp', asn, 'network']
+ if config.exists(network_path):
+ config.set(bgp_path + af_path + ['network'])
+ config.set_tag(bgp_path + af_path + ['network'])
+
+ networks = config.list_nodes(network_path)
+ for network in networks:
+ config.set(bgp_path + af_path + ['network', network])
+ if config.exists(network_path + [network, 'route-map']):
+ route_map = config.return_value(network_path + [network, 'route-map'])
+ config.set(bgp_path + af_path + ['network', network, 'route-map'], value=route_map)
+ config.delete(network_path)
+
+ # Move aggregate-address statements
+ aggregate_path = ['protocols', 'bgp', asn, 'aggregate-address']
+ if config.exists(aggregate_path):
+ config.set(bgp_path + af_path + ['aggregate-address'])
+ config.set_tag(bgp_path + af_path + ['aggregate-address'])
+
+ aggregates = config.list_nodes(aggregate_path)
+ for aggregate in aggregates:
+ config.set(bgp_path + af_path + ['aggregate-address', aggregate])
+ if config.exists(aggregate_path + [aggregate, 'as-set']):
+ config.set(bgp_path + af_path + ['aggregate-address', aggregate, 'as-set'])
+ if config.exists(aggregate_path + [aggregate, 'summary-only']):
+ config.set(bgp_path + af_path + ['aggregate-address', aggregate, 'summary-only'])
+ config.delete(aggregate_path)
+
+ ## Migrate neighbor options
+ neighbor_path = ['protocols', 'bgp', asn, 'neighbor']
+ if config.exists(neighbor_path):
+ neighbors = config.list_nodes(neighbor_path)
+ for neighbor in neighbors:
+ migrate_neighbor(config, neighbor_path, neighbor)
+
+ peer_group_path = ['protocols', 'bgp', asn, 'peer-group']
+ if config.exists(peer_group_path):
+ peer_groups = config.list_nodes(peer_group_path)
+ for peer_group in peer_groups:
+ migrate_neighbor(config, peer_group_path, peer_group)
+
+ ## Migrate redistribute statements
+ redistribute_path = ['protocols', 'bgp', asn, 'redistribute']
+ if config.exists(redistribute_path):
+ config.set(bgp_path + af_path + ['redistribute'])
+
+ redistributes = config.list_nodes(redistribute_path)
+ for redistribute in redistributes:
+ config.set(bgp_path + af_path + ['redistribute', redistribute])
+ if config.exists(redistribute_path + [redistribute, 'metric']):
+ redist_metric = config.return_value(redistribute_path + [redistribute, 'metric'])
+ config.set(bgp_path + af_path + ['redistribute', redistribute, 'metric'], value=redist_metric)
+ if config.exists(redistribute_path + [redistribute, 'route-map']):
+ redist_route_map = config.return_value(redistribute_path + [redistribute, 'route-map'])
+ config.set(bgp_path + af_path + ['redistribute', redistribute, 'route-map'], value=redist_route_map)
+
+ config.delete(redistribute_path)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/quagga/3-to-4 b/src/migration-scripts/quagga/3-to-4
new file mode 100755
index 000000000..be3528391
--- /dev/null
+++ b/src/migration-scripts/quagga/3-to-4
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+#
+#
+
+# Between 1.2.3 and 1.2.4, FRR added per-neighbor enforce-first-as option.
+# Unfortunately they also removed the global enforce-first-as option,
+# which broke all old configs that used to have it.
+#
+# To emulate the effect of the original option, we insert it in every neighbor
+# if the config used to have the original global option
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['protocols', 'bgp']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Check if BGP is actually configured and obtain the ASN
+ asn_list = config.list_nodes(['protocols', 'bgp'])
+ if asn_list:
+ # There's always just one BGP node, if any
+ asn = asn_list[0]
+ else:
+ # There's actually no BGP, just its empty shell
+ sys.exit(0)
+
+ # Check if BGP enforce-first-as option is set
+ enforce_first_as_path = ['protocols', 'bgp', asn, 'parameters', 'enforce-first-as']
+ if config.exists(enforce_first_as_path):
+ # Delete the obsolete option
+ config.delete(enforce_first_as_path)
+
+ # Now insert it in every peer
+ peers = config.list_nodes(['protocols', 'bgp', asn, 'neighbor'])
+ for p in peers:
+ config.set(['protocols', 'bgp', asn, 'neighbor', p, 'enforce-first-as'])
+ else:
+ # Do nothing
+ sys.exit(0)
+
+ # Save a new configuration file
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
+
diff --git a/src/migration-scripts/quagga/4-to-5 b/src/migration-scripts/quagga/4-to-5
new file mode 100755
index 000000000..f8c87ce8c
--- /dev/null
+++ b/src/migration-scripts/quagga/4-to-5
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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 sys
+
+from vyos.configtree import ConfigTree
+
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['protocols', 'bgp']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Check if BGP is actually configured and obtain the ASN
+ asn_list = config.list_nodes(['protocols', 'bgp'])
+ if asn_list:
+ # There's always just one BGP node, if any
+ asn = asn_list[0]
+ else:
+ # There's actually no BGP, just its empty shell
+ sys.exit(0)
+
+ # Check if BGP scan-time parameter exist
+ scan_time_param = ['protocols', 'bgp', asn, 'parameters', 'scan-time']
+ if config.exists(scan_time_param):
+ # Delete BGP scan-time parameter
+ config.delete(scan_time_param)
+ else:
+ # Do nothing
+ sys.exit(0)
+
+ # Save a new configuration file
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/quagga/5-to-6 b/src/migration-scripts/quagga/5-to-6
new file mode 100755
index 000000000..a71851942
--- /dev/null
+++ b/src/migration-scripts/quagga/5-to-6
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# * Remove parameter 'disable-network-import-check' which, as implemented,
+# had no effect on boot.
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+
+if (len(sys.argv) < 2):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['protocols', 'bgp']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Check if BGP is actually configured and obtain the ASN
+ asn_list = config.list_nodes(['protocols', 'bgp'])
+ if asn_list:
+ # There's always just one BGP node, if any
+ asn = asn_list[0]
+ else:
+ # There's actually no BGP, just its empty shell
+ sys.exit(0)
+
+ # Check if BGP parameter disable-network-import-check exists
+ param = ['protocols', 'bgp', asn, 'parameters', 'disable-network-import-check']
+ if config.exists(param):
+ # Delete parameter
+ config.delete(param)
+ else:
+ # Do nothing
+ sys.exit(0)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/salt/0-to-1 b/src/migration-scripts/salt/0-to-1
new file mode 100755
index 000000000..79053c056
--- /dev/null
+++ b/src/migration-scripts/salt/0-to-1
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# Delete log_file, log_level and user nodes
+# rename hash_type to hash
+# rename mine_interval to interval
+
+from sys import argv,exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['service', 'salt-minion']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+
+ # delete nodes which are now populated with sane defaults
+ for node in ['log_file', 'log_level', 'user']:
+ if config.exists(base + [node]):
+ config.delete(base + [node])
+
+ if config.exists(base + ['hash_type']):
+ config.rename(base + ['hash_type'], 'hash')
+
+ if config.exists(base + ['mine_interval']):
+ config.rename(base + ['mine_interval'], 'interval')
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/snmp/0-to-1 b/src/migration-scripts/snmp/0-to-1
new file mode 100755
index 000000000..a836f7011
--- /dev/null
+++ b/src/migration-scripts/snmp/0-to-1
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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 sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+config_base = ['service', 'snmp', 'v3']
+
+if not config.exists(config_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # we no longer support a per trap target engine ID (https://phabricator.vyos.net/T818)
+ if config.exists(config_base + ['v3', 'trap-target']):
+ for target in config.list_nodes(config_base + ['v3', 'trap-target']):
+ config.delete(config_base + ['v3', 'trap-target', target, 'engineid'])
+
+ # we no longer support a per user engine ID (https://phabricator.vyos.net/T818)
+ if config.exists(config_base + ['v3', 'user']):
+ for user in config.list_nodes(config_base + ['v3', 'user']):
+ config.delete(config_base + ['v3', 'user', user, 'engineid'])
+
+ # we drop TSM support as there seem to be no users and this code is untested
+ # https://phabricator.vyos.net/T1769
+ if config.exists(config_base + ['v3', 'tsm']):
+ config.delete(config_base + ['v3', 'tsm'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/snmp/1-to-2 b/src/migration-scripts/snmp/1-to-2
new file mode 100755
index 000000000..466a624e6
--- /dev/null
+++ b/src/migration-scripts/snmp/1-to-2
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+from sys import argv, exit
+from vyos.configtree import ConfigTree
+
+def migrate_keys(config, path):
+ # authentication: rename node 'encrypted-key' -> 'encrypted-password'
+ config_path_auth = path + ['auth', 'encrypted-key']
+ if config.exists(config_path_auth):
+ config.rename(config_path_auth, 'encrypted-password')
+ config_path_auth = path + ['auth', 'encrypted-password']
+
+ # remove leading '0x' from string if present
+ tmp = config.return_value(config_path_auth)
+ if tmp.startswith(prefix):
+ tmp = tmp.replace(prefix, '')
+ config.set(config_path_auth, value=tmp)
+
+ # privacy: rename node 'encrypted-key' -> 'encrypted-password'
+ config_path_priv = path + ['privacy', 'encrypted-key']
+ if config.exists(config_path_priv):
+ config.rename(config_path_priv, 'encrypted-password')
+ config_path_priv = path + ['privacy', 'encrypted-password']
+
+ # remove leading '0x' from string if present
+ tmp = config.return_value(config_path_priv)
+ if tmp.startswith(prefix):
+ tmp = tmp.replace(prefix, '')
+ config.set(config_path_priv, value=tmp)
+
+if __name__ == '__main__':
+ if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = argv[1]
+
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+ config_base = ['service', 'snmp', 'v3']
+
+ if not config.exists(config_base):
+ # Nothing to do
+ exit(0)
+ else:
+ # We no longer support hashed values prefixed with '0x' to unclutter
+ # CLI and also calculate the hases in advance instead of retrieving
+ # them after service startup - which was always a bad idea
+ prefix = '0x'
+
+ config_engineid = config_base + ['engineid']
+ if config.exists(config_engineid):
+ tmp = config.return_value(config_engineid)
+ if tmp.startswith(prefix):
+ tmp = tmp.replace(prefix, '')
+ config.set(config_engineid, value=tmp)
+
+ config_user = config_base + ['user']
+ if config.exists(config_user):
+ for user in config.list_nodes(config_user):
+ migrate_keys(config, config_user + [user])
+
+ config_trap = config_base + ['trap-target']
+ if config.exists(config_trap):
+ for trap in config.list_nodes(config_trap):
+ migrate_keys(config, config_trap + [trap])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/ssh/0-to-1 b/src/migration-scripts/ssh/0-to-1
new file mode 100755
index 000000000..91b832276
--- /dev/null
+++ b/src/migration-scripts/ssh/0-to-1
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+
+# Delete "service ssh allow-root" option
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['service', 'ssh', 'allow-root']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Delete node with abandoned command
+ config.delete(['service', 'ssh', 'allow-root'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/ssh/1-to-2 b/src/migration-scripts/ssh/1-to-2
new file mode 100755
index 000000000..bc8815753
--- /dev/null
+++ b/src/migration-scripts/ssh/1-to-2
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# VyOS 1.2 crux allowed configuring a lower or upper case loglevel. This
+# is no longer supported as the input data is validated and will lead to
+# an error. If user specifies an upper case logleve, make it lowercase
+
+from sys import argv,exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'ssh', 'loglevel']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+ # red in configured loglevel and convert it to lower case
+ tmp = config.return_value(base).lower()
+
+ # VyOS 1.2 had no proper value validation on the CLI thus the
+ # user could use any arbitrary values - sanitize them
+ if tmp not in ['quiet', 'fatal', 'error', 'info', 'verbose']:
+ tmp = 'info'
+
+ config.set(base, value=tmp)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/sstp/0-to-1 b/src/migration-scripts/sstp/0-to-1
new file mode 100755
index 000000000..0e8dd1c4b
--- /dev/null
+++ b/src/migration-scripts/sstp/0-to-1
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# - migrate from "service sstp-server" to "vpn sstp"
+# - remove primary/secondary identifier from nameserver
+# - migrate RADIUS configuration to a more uniform syntax accross the system
+# - authentication radius-server x.x.x.x to authentication radius server x.x.x.x
+# - authentication radius-settings to authentication radius
+# - do not migrate radius server req-limit, use default of unlimited
+# - migrate SSL certificate path
+
+import os
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+old_base = ['service', 'sstp-server']
+if not config.exists(old_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # ensure new base path exists
+ if not config.exists(['vpn']):
+ config.set(['vpn'])
+
+ new_base = ['vpn', 'sstp']
+ # copy entire tree
+ config.copy(old_base, new_base)
+ config.delete(old_base)
+
+ # migrate DNS servers
+ dns_base = new_base + ['network-settings', 'dns-server']
+ if config.exists(dns_base):
+ if config.exists(dns_base + ['primary-dns']):
+ dns = config.return_value(dns_base + ['primary-dns'])
+ config.set(new_base + ['network-settings', 'name-server'], value=dns, replace=False)
+
+ if config.exists(dns_base + ['secondary-dns']):
+ dns = config.return_value(dns_base + ['secondary-dns'])
+ config.set(new_base + ['network-settings', 'name-server'], value=dns, replace=False)
+
+ config.delete(dns_base)
+
+
+ # migrate radius options - copy subtree
+ # thus must happen before migration of the individual RADIUS servers
+ old_options = new_base + ['authentication', 'radius-settings']
+ if config.exists(old_options):
+ new_options = new_base + ['authentication', 'radius']
+ config.copy(old_options, new_options)
+ config.delete(old_options)
+
+ # migrate radius dynamic author / change of authorisation server
+ dae_old = new_base + ['authentication', 'radius', 'dae-server']
+ if config.exists(dae_old):
+ config.rename(dae_old, 'dynamic-author')
+ dae_new = new_base + ['authentication', 'radius', 'dynamic-author']
+
+ if config.exists(dae_new + ['ip-address']):
+ config.rename(dae_new + ['ip-address'], 'server')
+
+ if config.exists(dae_new + ['secret']):
+ config.rename(dae_new + ['secret'], 'key')
+
+
+ # migrate radius server
+ radius_server = new_base + ['authentication', 'radius-server']
+ if config.exists(radius_server):
+ for server in config.list_nodes(radius_server):
+ base = radius_server + [server]
+ new = new_base + ['authentication', 'radius', 'server', server]
+
+ # convert secret to key
+ if config.exists(base + ['secret']):
+ tmp = config.return_value(base + ['secret'])
+ config.set(new + ['key'], value=tmp)
+
+ if config.exists(base + ['fail-time']):
+ tmp = config.return_value(base + ['fail-time'])
+ config.set(new + ['fail-time'], value=tmp)
+
+ config.set_tag(new_base + ['authentication', 'radius', 'server'])
+ config.delete(radius_server)
+
+ # migrate SSL certificates
+ old_ssl = new_base + ['sstp-settings', 'ssl-certs']
+ new_ssl = new_base + ['ssl']
+ config.copy(old_ssl, new_ssl)
+ config.delete(old_ssl)
+
+ if config.exists(new_ssl + ['ca']):
+ config.rename(new_ssl + ['ca'], 'ca-cert-file')
+
+ if config.exists(new_ssl + ['server-cert']):
+ config.rename(new_ssl + ['server-cert'], 'cert-file')
+
+ if config.exists(new_ssl + ['server-key']):
+ config.rename(new_ssl + ['server-key'], 'key-file')
+
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/sstp/1-to-2 b/src/migration-scripts/sstp/1-to-2
new file mode 100755
index 000000000..94cb04831
--- /dev/null
+++ b/src/migration-scripts/sstp/1-to-2
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# - migrate relative path SSL certificate to absolute path, as certs are only
+# allowed to stored in /config/user-data/sstp/ this is pretty straight
+# forward move. Delete certificates from source directory
+
+import os
+import sys
+
+from shutil import copy2
+from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IROTH
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base_path = ['vpn', 'sstp', 'ssl']
+if not config.exists(base_path):
+ # Nothing to do
+ sys.exit(0)
+else:
+ cert_path_old ='/config/user-data/sstp/'
+ cert_path_new ='/config/auth/sstp/'
+
+ if not os.path.isdir(cert_path_new):
+ os.mkdir(cert_path_new)
+
+ #
+ # migrate ca-cert-file to new path
+ if config.exists(base_path + ['ca-cert-file']):
+ tmp = config.return_value(base_path + ['ca-cert-file'])
+ cert_old = cert_path_old + tmp
+ cert_new = cert_path_new + tmp
+
+ if os.path.isfile(cert_old):
+ # adjust file permissions on source file,
+ # permissions will be copied by copy2()
+ os.chmod(cert_old, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
+ copy2(cert_old, cert_path_new)
+ # delete old certificate file
+ os.unlink(cert_old)
+
+ config.set(base_path + ['ca-cert-file'], value=cert_new, replace=True)
+
+ #
+ # migrate cert-file to new path
+ if config.exists(base_path + ['cert-file']):
+ tmp = config.return_value(base_path + ['cert-file'])
+ cert_old = cert_path_old + tmp
+ cert_new = cert_path_new + tmp
+
+ if os.path.isfile(cert_old):
+ # adjust file permissions on source file,
+ # permissions will be copied by copy2()
+ os.chmod(cert_old, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
+ copy2(cert_old, cert_path_new)
+ # delete old certificate file
+ os.unlink(cert_old)
+
+ config.set(base_path + ['cert-file'], value=cert_new, replace=True)
+
+ #
+ # migrate key-file to new path
+ if config.exists(base_path + ['key-file']):
+ tmp = config.return_value(base_path + ['key-file'])
+ cert_old = cert_path_old + tmp
+ cert_new = cert_path_new + tmp
+
+ if os.path.isfile(cert_old):
+ # adjust file permissions on source file,
+ # permissions will be copied by copy2()
+ os.chmod(cert_old, S_IRUSR | S_IWUSR)
+ copy2(cert_old, cert_path_new)
+ # delete old certificate file
+ os.unlink(cert_old)
+
+ config.set(base_path + ['key-file'], value=cert_new, replace=True)
+
+ #
+ # check if old certificate directory exists but is empty
+ if os.path.isdir(cert_path_old) and not os.listdir(cert_path_old):
+ os.rmdir(cert_path_old)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/10-to-11 b/src/migration-scripts/system/10-to-11
new file mode 100755
index 000000000..1a0233c7d
--- /dev/null
+++ b/src/migration-scripts/system/10-to-11
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+
+# Unclutter RADIUS configuration
+#
+# Move radius-server top level tag nodes to a regular node which allows us
+# to specify additional general features for the RADIUS client.
+
+import sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+cfg_base = ['system', 'login']
+if not (config.exists(cfg_base + ['radius-server']) or config.exists(cfg_base + ['radius-source-address'])):
+ # Nothing to do
+ sys.exit(0)
+else:
+ #
+ # Migrate "system login radius-source-address" to "system login radius"
+ #
+ if config.exists(cfg_base + ['radius-source-address']):
+ address = config.return_value(cfg_base + ['radius-source-address'])
+ # delete old configuration node
+ config.delete(cfg_base + ['radius-source-address'])
+ # write new configuration node
+ config.set(cfg_base + ['radius', 'source-address'], value=address)
+
+ #
+ # Migrate "system login radius-server" tag node to new
+ # "system login radius server" tag node and also rename the "secret" node to "key"
+ #
+ for server in config.list_nodes(cfg_base + ['radius-server']):
+ base_server = cfg_base + ['radius-server', server]
+ # "key" node is mandatory
+ key = config.return_value(base_server + ['secret'])
+ config.set(cfg_base + ['radius', 'server', server, 'key'], value=key)
+
+ # "port" is optional
+ if config.exists(base_server + ['port']):
+ port = config.return_value(base_server + ['port'])
+ config.set(cfg_base + ['radius', 'server', server, 'port'], value=port)
+
+ # "timeout is optional"
+ if config.exists(base_server + ['timeout']):
+ timeout = config.return_value(base_server + ['timeout'])
+ config.set(cfg_base + ['radius', 'server', server, 'timeout'], value=timeout)
+
+ # format as tag node
+ config.set_tag(cfg_base + ['radius', 'server'])
+
+ # delete old configuration node
+ config.delete(base_server)
+
+ # delete top level tag node
+ if config.exists(cfg_base + ['radius-server']):
+ config.delete(cfg_base + ['radius-server'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/11-to-12 b/src/migration-scripts/system/11-to-12
new file mode 100755
index 000000000..0c92a0746
--- /dev/null
+++ b/src/migration-scripts/system/11-to-12
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+
+# converts 'set system syslog host <address>:<port>'
+# to 'set system syslog host <address> port <port>'
+
+import sys
+import re
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+cbase = ['system', 'syslog', 'host']
+
+if not config.exists(cbase):
+ sys.exit(0)
+
+for host in config.list_nodes(cbase):
+ if re.search(':[0-9]{1,5}$',host):
+ h = re.search('^[a-zA-Z\-0-9\.]+', host).group(0)
+ p = re.sub(':', '', re.search(':[0-9]+$', host).group(0))
+ config.set(cbase + [h])
+ config.set(cbase + [h, 'port'], value=p)
+ for fac in config.list_nodes(cbase + [host, 'facility']):
+ config.set(cbase + [h, 'facility', fac])
+ config.set_tag(cbase + [h, 'facility'])
+ if config.exists(cbase + [host, 'facility', fac, 'protocol']):
+ proto = config.return_value(cbase + [host, 'facility', fac, 'protocol'])
+ config.set(cbase + [h, 'facility', fac, 'protocol'], value=proto)
+ if config.exists(cbase + [host, 'facility', fac, 'level']):
+ lvl = config.return_value(cbase + [host, 'facility', fac, 'level'])
+ config.set(cbase + [h, 'facility', fac, 'level'], value=lvl)
+ config.delete(cbase + [host])
+
+ try:
+ open(file_name,'w').write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/12-to-13 b/src/migration-scripts/system/12-to-13
new file mode 100755
index 000000000..5b068f4fc
--- /dev/null
+++ b/src/migration-scripts/system/12-to-13
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+
+# Fixup non existent time-zones. Some systems have time-zone set to: Los*
+# (Los_Angeles), Den* (Denver), New* (New_York) ... but those are no real IANA
+# assigned time zones. In the past they have been silently remapped.
+#
+# Time to clean it up!
+#
+# Migrate all configured timezones to real IANA assigned timezones!
+
+import re
+import sys
+
+from vyos.configtree import ConfigTree
+from vyos.util import cmd
+
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+tz_base = ['system', 'time-zone']
+if not config.exists(tz_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ tz = config.return_value(tz_base)
+
+ # retrieve all valid timezones
+ try:
+ tz_datas = cmd('find /usr/share/zoneinfo/posix -type f -or -type l | sed -e s:/usr/share/zoneinfo/posix/::')
+ except OSError:
+ tz_datas = ''
+ tz_data = tz_datas.split('\n')
+
+ if re.match(r'[Ll][Oo][Ss].+', tz):
+ tz = 'America/Los_Angeles'
+ elif re.match(r'[Dd][Ee][Nn].+', tz):
+ tz = 'America/Denver'
+ elif re.match(r'[Hh][Oo][Nn][Oo].+', tz):
+ tz = 'Pacific/Honolulu'
+ elif re.match(r'[Nn][Ee][Ww].+', tz):
+ tz = 'America/New_York'
+ elif re.match(r'[Cc][Hh][Ii][Cc]*.+', tz):
+ tz = 'America/Chicago'
+ elif re.match(r'[Aa][Nn][Cc].+', tz):
+ tz = 'America/Anchorage'
+ elif re.match(r'[Pp][Hh][Oo].+', tz):
+ tz = 'America/Phoenix'
+ elif re.match(r'GMT(.+)?', tz):
+ tz = 'Etc/' + tz
+ elif tz not in tz_data:
+ # assign default UTC timezone
+ tz = 'UTC'
+
+ # replace timezone data is required
+ config.set(tz_base, value=tz)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/13-to-14 b/src/migration-scripts/system/13-to-14
new file mode 100755
index 000000000..c055dad1f
--- /dev/null
+++ b/src/migration-scripts/system/13-to-14
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+#
+# Delete 'system ipv6 blacklist' option as the IPv6 module can no longer be
+# blacklisted as it is required by e.g. WireGuard and thus will always be
+# loaded.
+
+import os
+import sys
+
+ipv6_blacklist_file = '/etc/modprobe.d/vyatta_blacklist_ipv6.conf'
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+ip_base = ['system', 'ipv6']
+if not config.exists(ip_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # delete 'system ipv6 blacklist' node
+ if config.exists(ip_base + ['blacklist']):
+ config.delete(ip_base + ['blacklist'])
+ if os.path.isfile(ipv6_blacklist_file):
+ os.unlink(ipv6_blacklist_file)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/14-to-15 b/src/migration-scripts/system/14-to-15
new file mode 100755
index 000000000..2491e3d0d
--- /dev/null
+++ b/src/migration-scripts/system/14-to-15
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+#
+# Make 'system options reboot-on-panic' valueless
+
+import os
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['system', 'options']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ if config.exists(base + ['reboot-on-panic']):
+ reboot = config.return_value(base + ['reboot-on-panic'])
+ config.delete(base + ['reboot-on-panic'])
+ # create new valueless node if action was true
+ if reboot == "true":
+ config.set(base + ['reboot-on-panic'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/15-to-16 b/src/migration-scripts/system/15-to-16
new file mode 100755
index 000000000..e70893d55
--- /dev/null
+++ b/src/migration-scripts/system/15-to-16
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# * remove "system login user <user> group" node, Why should be add a user to a
+# 3rd party group when the system is fully managed by CLI?
+# * remove "system login user <user> level" node
+# This is the only privilege level left and also the default, what is the
+# sense in keeping this orphaned node?
+
+import os
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['system', 'login', 'user']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ for user in config.list_nodes(base):
+ if config.exists(base + [user, 'group']):
+ config.delete(base + [user, 'group'])
+
+ if config.exists(base + [user, 'level']):
+ config.delete(base + [user, 'level'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/16-to-17 b/src/migration-scripts/system/16-to-17
new file mode 100755
index 000000000..8f762c0e2
--- /dev/null
+++ b/src/migration-scripts/system/16-to-17
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# remove "system console netconsole"
+# remove "system console device <device> modem"
+
+import os
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['system', 'console']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # remove "system console netconsole" (T2561)
+ if config.exists(base + ['netconsole']):
+ config.delete(base + ['netconsole'])
+
+ if config.exists(base + ['device']):
+ for device in config.list_nodes(base + ['device']):
+ dev_path = base + ['device', device]
+ # remove "system console device <device> modem" (T2570)
+ if config.exists(dev_path + ['modem']):
+ config.delete(dev_path + ['modem'])
+
+ # Only continue on USB based serial consoles
+ if not 'ttyUSB' in device:
+ continue
+
+ # A serial console has been configured but it does no longer
+ # exist on the system - cleanup
+ if not os.path.exists(f'/dev/{device}'):
+ config.delete(dev_path)
+ continue
+
+ # migrate from ttyUSB device to new device in /dev/serial/by-bus
+ for root, dirs, files in os.walk('/dev/serial/by-bus'):
+ for usb_device in files:
+ device_file = os.path.realpath(os.path.join(root, usb_device))
+ # migrate to new USB device names (T2529)
+ if os.path.basename(device_file) == device:
+ config.copy(dev_path, base + ['device', usb_device])
+ # Delete old USB node from config
+ config.delete(dev_path)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/17-to-18 b/src/migration-scripts/system/17-to-18
new file mode 100755
index 000000000..dd2abce00
--- /dev/null
+++ b/src/migration-scripts/system/17-to-18
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# migrate disable-dhcp-nameservers (boolean) to name-servers-dhcp <interface>
+# if disable-dhcp-nameservers is set, just remove it
+# else retrieve all interface names that have configured dhcp(v6) address and
+# add them to the new name-servers-dhcp node
+
+from sys import argv, exit
+from vyos.ifconfig import Interface
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['system']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['disable-dhcp-nameservers']):
+ config.delete(base + ['disable-dhcp-nameservers'])
+else:
+ dhcp_interfaces = []
+
+ # go through all interfaces searching for 'address dhcp(v6)?'
+ for sect in Interface.sections():
+ sect_base = ['interfaces', sect]
+
+ if not config.exists(sect_base):
+ continue
+
+ for intf in config.list_nodes(sect_base):
+ intf_base = sect_base + [intf]
+
+ # try without vlans
+ if config.exists(intf_base + ['address']):
+ for addr in config.return_values(intf_base + ['address']):
+ if addr in ['dhcp', 'dhcpv6']:
+ dhcp_interfaces.append(intf)
+
+ # try vif
+ if config.exists(intf_base + ['vif']):
+ for vif in config.list_nodes(intf_base + ['vif']):
+ vif_base = intf_base + ['vif', vif]
+ if config.exists(vif_base + ['address']):
+ for addr in config.return_values(vif_base + ['address']):
+ if addr in ['dhcp', 'dhcpv6']:
+ dhcp_interfaces.append(f'{intf}.{vif}')
+
+ # try vif-s
+ if config.exists(intf_base + ['vif-s']):
+ for vif_s in config.list_nodes(intf_base + ['vif-s']):
+ vif_s_base = intf_base + ['vif-s', vif_s]
+ if config.exists(vif_s_base + ['address']):
+ for addr in config.return_values(vif_s_base + ['address']):
+ if addr in ['dhcp', 'dhcpv6']:
+ dhcp_interfaces.append(f'{intf}.{vif_s}')
+
+ # try vif-c
+ if config.exists(intf_base + ['vif-c', vif_c]):
+ for vif_c in config.list_nodes(vif_s_base + ['vif-c', vif_c]):
+ vif_c_base = vif_s_base + ['vif-c', vif_c]
+ if config.exists(vif_c_base + ['address']):
+ for addr in config.return_values(vif_c_base + ['address']):
+ if addr in ['dhcp', 'dhcpv6']:
+ dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}')
+
+ # set new config nodes
+ for intf in dhcp_interfaces:
+ config.set(base + ['name-servers-dhcp'], value=intf, replace=False)
+
+ # delete old node
+ config.delete(base + ['disable-dhcp-nameservers'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
+
+exit(0)
diff --git a/src/migration-scripts/system/6-to-7 b/src/migration-scripts/system/6-to-7
new file mode 100755
index 000000000..bf07abf3a
--- /dev/null
+++ b/src/migration-scripts/system/6-to-7
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+
+# Change smp_affinity to smp-affinity
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+update_required = False
+
+intf_types = config.list_nodes(["interfaces"])
+
+for intf_type in intf_types:
+ intf_type_path = ["interfaces", intf_type]
+ intfs = config.list_nodes(intf_type_path)
+
+ for intf in intfs:
+ intf_path = intf_type_path + [intf]
+ if not config.exists(intf_path + ["smp_affinity"]):
+ # Nothing to do.
+ continue
+ else:
+ # Rename the node.
+ old_smp_affinity_path = intf_path + ["smp_affinity"]
+ config.rename(old_smp_affinity_path, "smp-affinity")
+ update_required = True
+
+if update_required:
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("failed to save the modified config: {}".format(e))
+ sys.exit(1)
+
+
+
diff --git a/src/migration-scripts/system/7-to-8 b/src/migration-scripts/system/7-to-8
new file mode 100755
index 000000000..4cbb21f17
--- /dev/null
+++ b/src/migration-scripts/system/7-to-8
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+# Converts "system gateway-address" option to "protocols static route 0.0.0.0/0 next-hop $gw"
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['system', 'gateway-address']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Save the address
+ gw = config.return_value(['system', 'gateway-address'])
+
+ # Create the node for the new syntax
+ # Note: next-hop is a tag node, gateway address is its child, not a value
+ config.set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', gw])
+
+ # Delete the node with the old syntax
+ config.delete(['system', 'gateway-address'])
+
+ # Now, the interesting part. Both route and next-hop are supposed to be tag nodes,
+ # which you can verify with "cli-shell-api isTag $configPath".
+ # They must be formatted as such to load correctly.
+ config.set_tag(['protocols', 'static', 'route'])
+ config.set_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/8-to-9 b/src/migration-scripts/system/8-to-9
new file mode 100755
index 000000000..db3fefdea
--- /dev/null
+++ b/src/migration-scripts/system/8-to-9
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+
+# Deletes "system package" option as it is deprecated
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['system', 'package']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Delete the node with the old syntax
+ config.delete(['system', 'package'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/system/9-to-10 b/src/migration-scripts/system/9-to-10
new file mode 100755
index 000000000..3c49f0d95
--- /dev/null
+++ b/src/migration-scripts/system/9-to-10
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+
+# Operator accounts have been deprecated due to a security issue. Those accounts
+# will be converted to regular admin accounts.
+
+import sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base_level = ['system', 'login', 'user']
+
+if not config.exists(base_level):
+ # Nothing to do, which shouldn't happen anyway
+ # only if you wipe the config and reboot.
+ sys.exit(0)
+else:
+ for user in config.list_nodes(base_level):
+ if config.exists(base_level + [user, 'level']):
+ if config.return_value(base_level + [user, 'level']) == 'operator':
+ config.set(base_level + [user, 'level'], value="admin", replace=True)
+
+ try:
+ open(file_name,'w').write(config.to_string())
+
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/vrrp/1-to-2 b/src/migration-scripts/vrrp/1-to-2
new file mode 100755
index 000000000..b2e61dd38
--- /dev/null
+++ b/src/migration-scripts/vrrp/1-to-2
@@ -0,0 +1,270 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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 sys
+
+from vyos.configtree import ConfigTree
+
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+# Convert the old VRRP syntax to the new syntax
+
+# The old approach was to put VRRP groups inside interfaces,
+# as in "interfaces ethernet eth0 vrrp vrrp-group 10 ...".
+# It was supported only under ethernet and bonding and their
+# respective vif, vif-s, and vif-c subinterfaces
+
+def get_vrrp_group(path):
+ group = {"preempt": True, "rfc_compatibility": False, "disable": False}
+
+ if config.exists(path + ["advertise-interval"]):
+ group["advertise_interval"] = config.return_value(path + ["advertise-interval"])
+
+ if config.exists(path + ["description"]):
+ group["description"] = config.return_value(path + ["description"])
+
+ if config.exists(path + ["disable"]):
+ group["disable"] = True
+
+ if config.exists(path + ["hello-source-address"]):
+ group["hello_source"] = config.return_value(path + ["hello-source-address"])
+
+ # 1.1.8 didn't have it, but earlier 1.2.0 did, we don't want to break
+ # configs of early adopters!
+ if config.exists(path + ["peer-address"]):
+ group["peer_address"] = config.return_value(path + ["peer-address"])
+
+ if config.exists(path + ["preempt"]):
+ preempt = config.return_value(path + ["preempt"])
+ if preempt == "false":
+ group["preempt"] = False
+
+ if config.exists(path + ["rfc3768-compatibility"]):
+ group["rfc_compatibility"] = True
+
+ if config.exists(path + ["preempt-delay"]):
+ group["preempt_delay"] = config.return_value(path + ["preempt-delay"])
+
+ if config.exists(path + ["priority"]):
+ group["priority"] = config.return_value(path + ["priority"])
+
+ if config.exists(path + ["sync-group"]):
+ group["sync_group"] = config.return_value(path + ["sync-group"])
+
+ if config.exists(path + ["authentication", "type"]):
+ group["auth_type"] = config.return_value(path + ["authentication", "type"])
+
+ if config.exists(path + ["authentication", "password"]):
+ group["auth_password"] = config.return_value(path + ["authentication", "password"])
+
+ if config.exists(path + ["virtual-address"]):
+ group["virtual_addresses"] = config.return_values(path + ["virtual-address"])
+
+ if config.exists(path + ["run-transition-scripts"]):
+ if config.exists(path + ["run-transition-scripts", "master"]):
+ group["master_script"] = config.return_value(path + ["run-transition-scripts", "master"])
+ if config.exists(path + ["run-transition-scripts", "backup"]):
+ group["backup_script"] = config.return_value(path + ["run-transition-scripts", "backup"])
+ if config.exists(path + ["run-transition-scripts", "fault"]):
+ group["fault_script"] = config.return_value(path + ["run-transition-scripts", "fault"])
+
+ # Also not present in 1.1.8, but supported by earlier 1.2.0
+ if config.exists(path + ["health-check"]):
+ if config.exists(path + ["health-check", "interval"]):
+ group["health_check_interval"] = config.return_value(path + ["health-check", "interval"])
+ if config.exists(path + ["health-check", "failure-count"]):
+ group["health_check_count"] = config.return_value(path + ["health-check", "failure-count"])
+ if config.exists(path + ["health-check", "script"]):
+ group["health_check_script"] = config.return_value(path + ["health-check", "script"])
+
+ return group
+
+# Since VRRP is all over the place, there's no way to just check a path and exit early
+# if it doesn't exist, we have to walk all interfaces and collect VRRP settings from them.
+# Only if no data is collected from any interface we can conclude that VRRP is not configured
+# and exit.
+
+groups = []
+base_paths = []
+
+if config.exists(["interfaces", "ethernet"]):
+ base_paths.append("ethernet")
+if config.exists(["interfaces", "bonding"]):
+ base_paths.append("bonding")
+
+for bp in base_paths:
+ parent_path = ["interfaces", bp]
+
+ parent_intfs = config.list_nodes(parent_path)
+
+ for pi in parent_intfs:
+ # Extract VRRP groups from the parent interface
+ vg_path =[pi, "vrrp", "vrrp-group"]
+ if config.exists(parent_path + vg_path):
+ pgroups = config.list_nodes(parent_path + vg_path)
+ for pg in pgroups:
+ g = get_vrrp_group(parent_path + vg_path + [pg])
+ g["interface"] = pi
+ g["vrid"] = pg
+ groups.append(g)
+
+ # Delete the VRRP subtree
+ # If left in place, configs will not load correctly
+ config.delete(parent_path + [pi, "vrrp"])
+
+ # Extract VRRP groups from 802.1q VLAN interfaces
+ if config.exists(parent_path + [pi, "vif"]):
+ vifs = config.list_nodes(parent_path + [pi, "vif"])
+ for vif in vifs:
+ vif_vg_path = [pi, "vif", vif, "vrrp", "vrrp-group"]
+ if config.exists(parent_path + vif_vg_path):
+ vifgroups = config.list_nodes(parent_path + vif_vg_path)
+ for vif_group in vifgroups:
+ g = get_vrrp_group(parent_path + vif_vg_path + [vif_group])
+ g["interface"] = "{0}.{1}".format(pi, vif)
+ g["vrid"] = vif_group
+ groups.append(g)
+
+ config.delete(parent_path + [pi, "vif", vif, "vrrp"])
+
+ # Extract VRRP groups from 802.3ad QinQ service VLAN interfaces
+ if config.exists(parent_path + [pi, "vif-s"]):
+ vif_ss = config.list_nodes(parent_path + [pi, "vif-s"])
+ for vif_s in vif_ss:
+ vifs_vg_path = [pi, "vif-s", vif_s, "vrrp", "vrrp-group"]
+ if config.exists(parent_path + vifs_vg_path):
+ vifsgroups = config.list_nodes(parent_path + vifs_vg_path)
+ for vifs_group in vifsgroups:
+ g = get_vrrp_group(parent_path + vifs_vg_path + [vifs_group])
+ g["interface"] = "{0}.{1}".format(pi, vif_s)
+ g["vrid"] = vifs_group
+ groups.append(g)
+
+ config.delete(parent_path + [pi, "vif-s", vif_s, "vrrp"])
+
+ # Extract VRRP groups from QinQ client VLAN interfaces nested in the vif-s
+ if config.exists(parent_path + [pi, "vif-s", vif_s, "vif-c"]):
+ vif_cs = config.list_nodes(parent_path + [pi, "vif-s", vif_s, "vif-c"])
+ for vif_c in vif_cs:
+ vifc_vg_path = [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp", "vrrp-group"]
+ vifcgroups = config.list_nodes(parent_path + vifc_vg_path)
+ for vifc_group in vifcgroups:
+ g = get_vrrp_group(parent_path + vifc_vg_path + [vifc_group])
+ g["interface"] = "{0}.{1}.{2}".format(pi, vif_s, vif_c)
+ g["vrid"] = vifc_group
+ groups.append(g)
+
+ config.delete(parent_path + [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp"])
+
+# If nothing was collected before this point, it means the config has no VRRP setup
+if not groups:
+ sys.exit(0)
+
+# Otherwise, there is VRRP to convert
+
+# Now convert the collected groups to the new syntax
+base_group_path = ["high-availability", "vrrp", "group"]
+sync_path = ["high-availability", "vrrp", "sync-group"]
+
+for g in groups:
+ group_name = "{0}-{1}".format(g["interface"], g["vrid"])
+ group_path = base_group_path + [group_name]
+
+ config.set(group_path + ["interface"], value=g["interface"])
+ config.set(group_path + ["vrid"], value=g["vrid"])
+
+ if "advertise_interval" in g:
+ config.set(group_path + ["advertise-interval"], value=g["advertise_interval"])
+
+ if "priority" in g:
+ config.set(group_path + ["priority"], value=g["priority"])
+
+ if not g["preempt"]:
+ config.set(group_path + ["no-preempt"], value=None)
+
+ if "preempt_delay" in g:
+ config.set(group_path + ["preempt-delay"], value=g["preempt_delay"])
+
+ if g["rfc_compatibility"]:
+ config.set(group_path + ["rfc3768-compatibility"], value=None)
+
+ if g["disable"]:
+ config.set(group_path + ["disable"], value=None)
+
+ if "hello_source" in g:
+ config.set(group_path + ["hello-source-address"], value=g["hello_source"])
+
+ if "peer_address" in g:
+ config.set(group_path + ["peer-address"], value=g["peer_address"])
+
+ if "auth_password" in g:
+ config.set(group_path + ["authentication", "password"], value=g["auth_password"])
+ if "auth_type" in g:
+ config.set(group_path + ["authentication", "type"], value=g["auth_type"])
+
+ if "master_script" in g:
+ config.set(group_path + ["transition-script", "master"], value=g["master_script"])
+ if "backup_script" in g:
+ config.set(group_path + ["transition-script", "backup"], value=g["backup_script"])
+ if "fault_script" in g:
+ config.set(group_path + ["transition-script", "fault"], value=g["fault_script"])
+
+ if "health_check_interval" in g:
+ config.set(group_path + ["health-check", "interval"], value=g["health_check_interval"])
+ if "health_check_count" in g:
+ config.set(group_path + ["health-check", "failure-count"], value=g["health_check_count"])
+ if "health_check_script" in g:
+ config.set(group_path + ["health-check", "script"], value=g["health_check_script"])
+
+ # Not that it should ever be absent...
+ if "virtual_addresses" in g:
+ # The new CLI disallows addresses without prefix length
+ # Pre-rewrite configs didn't support IPv6 VRRP, but handle it anyway
+ for va in g["virtual_addresses"]:
+ if not re.search(r'/', va):
+ if re.search(r':', va):
+ va = "{0}/128".format(va)
+ else:
+ va = "{0}/32".format(va)
+ config.set(group_path + ["virtual-address"], value=va, replace=False)
+
+ # Sync group
+ if "sync_group" in g:
+ config.set(sync_path + [g["sync_group"], "member"], value=group_name, replace=False)
+
+# Set the tag flag
+config.set_tag(base_group_path)
+if config.exists(sync_path):
+ config.set_tag(sync_path)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/webproxy/1-to-2 b/src/migration-scripts/webproxy/1-to-2
new file mode 100755
index 000000000..070ff356d
--- /dev/null
+++ b/src/migration-scripts/webproxy/1-to-2
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+# migrate old style `webproxy proxy-bypass 1.2.3.4/24`
+# to new style `webproxy whitelist destination-address 1.2.3.4/24`
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if len(sys.argv) < 1:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+cfg_webproxy_base = ['service', 'webproxy']
+if not config.exists(cfg_webproxy_base + ['proxy-bypass']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ bypass_addresses = config.return_values(cfg_webproxy_base + ['proxy-bypass'])
+ # delete old configuration node
+ config.delete(cfg_webproxy_base + ['proxy-bypass'])
+ for bypass_address in bypass_addresses:
+ # add data to new configuration node
+ config.set(cfg_webproxy_base + ['whitelist', 'destination-address'], value=bypass_address, replace=False)
+
+ # save updated configuration
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)