summaryrefslogtreecommitdiff
path: root/data
diff options
context:
space:
mode:
Diffstat (limited to 'data')
-rw-r--r--data/templates/bcast-relay/udp-broadcast-relay.tmpl7
-rw-r--r--data/templates/dhcp-relay/config.tmpl17
-rw-r--r--data/templates/dhcp-server/daemon.tmpl8
-rw-r--r--data/templates/dhcp-server/dhcpd.conf.tmpl195
-rw-r--r--data/templates/dhcpv6-relay/config.tmpl4
-rw-r--r--data/templates/dhcpv6-server/daemon.tmpl8
-rw-r--r--data/templates/dhcpv6-server/dhcpdv6.conf.tmpl78
-rw-r--r--data/templates/dns-forwarding/recursor.conf.tmpl44
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.tmpl49
-rw-r--r--data/templates/frr-bfd/bfd.frr.tmpl16
-rw-r--r--data/templates/https/nginx.default.tmpl61
-rw-r--r--data/templates/igmp-proxy/igmpproxy.conf.tmpl37
-rw-r--r--data/templates/igmp/igmp.frr.tmpl41
-rw-r--r--data/templates/ipoe-server/chap-secrets.tmpl18
-rw-r--r--data/templates/ipoe-server/ipoe.config.tmpl123
-rw-r--r--data/templates/ipsec/ipsec.conf.tmpl3
-rw-r--r--data/templates/ipsec/ipsec.secrets.tmpl7
-rw-r--r--data/templates/ipsec/remote-access.tmpl28
-rw-r--r--data/templates/l2tp/chap-secrets.tmpl11
-rw-r--r--data/templates/l2tp/l2tp.config.tmpl173
-rw-r--r--data/templates/lldp/lldpd.tmpl3
-rw-r--r--data/templates/lldp/vyos.conf.tmpl20
-rw-r--r--data/templates/mdns-repeater/mdns-repeater.tmpl2
-rw-r--r--data/templates/mpls/ldpd.frr.tmpl58
-rw-r--r--data/templates/netflow/uacctd.conf.tmpl69
-rw-r--r--data/templates/ntp/ntp.conf.tmpl38
-rw-r--r--data/templates/openvpn/client.conf.tmpl16
-rw-r--r--data/templates/openvpn/server.conf.tmpl223
-rw-r--r--data/templates/pim/pimd.frr.tmpl34
-rw-r--r--data/templates/pppoe-server/chap-secrets.tmpl11
-rw-r--r--data/templates/pppoe-server/pppoe.config.tmpl228
-rw-r--r--data/templates/pppoe/ip-down.script.tmpl26
-rw-r--r--data/templates/pppoe/ip-pre-up.script.tmpl19
-rw-r--r--data/templates/pppoe/ip-up.script.tmpl47
-rw-r--r--data/templates/pppoe/ipv6-up.script.tmpl41
-rw-r--r--data/templates/pppoe/peer.tmpl63
-rw-r--r--data/templates/pptp/chap-secrets.tmpl6
-rw-r--r--data/templates/pptp/pptp.config.tmpl87
-rw-r--r--data/templates/router-advert/radvd.conf.tmpl37
-rw-r--r--data/templates/salt-minion/minion.tmpl63
-rw-r--r--data/templates/snmp/etc.snmp.conf.tmpl4
-rw-r--r--data/templates/snmp/etc.snmpd.conf.tmpl118
-rw-r--r--data/templates/snmp/usr.snmpd.conf.tmpl6
-rw-r--r--data/templates/snmp/var.snmpd.conf.tmpl16
-rw-r--r--data/templates/ssh/sshd_config.tmpl125
-rw-r--r--data/templates/sstp/chap-secrets.tmpl10
-rw-r--r--data/templates/sstp/sstp.config.tmpl114
-rw-r--r--data/templates/syslog/logrotate.tmpl12
-rw-r--r--data/templates/syslog/rsyslog.conf.tmpl44
-rw-r--r--data/templates/system-login/pam_radius_auth.conf.tmpl13
-rw-r--r--data/templates/tftp-server/default.tmpl2
-rw-r--r--data/templates/vrf/vrf.conf.tmpl8
-rw-r--r--data/templates/vrrp/daemon.tmpl5
-rw-r--r--data/templates/vrrp/keepalived.conf.tmpl97
-rw-r--r--data/templates/wifi/cfg80211.conf.tmpl3
-rw-r--r--data/templates/wifi/crda.tmpl3
-rw-r--r--data/templates/wifi/hostapd.conf.tmpl728
-rw-r--r--data/templates/wifi/wpa_supplicant.conf.tmpl9
-rw-r--r--data/templates/wwan/chat.tmpl6
-rw-r--r--data/templates/wwan/ip-down.script.tmpl26
-rw-r--r--data/templates/wwan/ip-pre-up.script.tmpl23
-rw-r--r--data/templates/wwan/ip-up.script.tmpl25
-rw-r--r--data/templates/wwan/peer.tmpl30
63 files changed, 3446 insertions, 0 deletions
diff --git a/data/templates/bcast-relay/udp-broadcast-relay.tmpl b/data/templates/bcast-relay/udp-broadcast-relay.tmpl
new file mode 100644
index 000000000..3d8c3fe94
--- /dev/null
+++ b/data/templates/bcast-relay/udp-broadcast-relay.tmpl
@@ -0,0 +1,7 @@
+### Autogenerated by bcast_relay.py ###
+
+# UDP broadcast relay configuration for instance {{ id }}
+{%- if description %}
+# Comment: {{ description }}
+{% endif %}
+DAEMON_ARGS="{% if address %}-s {{ address }} {% endif %}{{ id }} {{ port }} {{ interfaces | join(' ') }}"
diff --git a/data/templates/dhcp-relay/config.tmpl b/data/templates/dhcp-relay/config.tmpl
new file mode 100644
index 000000000..7203ae9fb
--- /dev/null
+++ b/data/templates/dhcp-relay/config.tmpl
@@ -0,0 +1,17 @@
+### Autogenerated by dhcp_relay.py ###
+
+# Defaults for isc-dhcp-relay initscript
+# sourced by /etc/init.d/isc-dhcp-relay
+
+#
+# This is a POSIX shell fragment
+#
+
+# What servers should the DHCP relay forward requests to?
+SERVERS="{{ server | join(' ') }}"
+
+# On what interfaces should the DHCP relay (dhrelay) serve DHCP requests?
+INTERFACES="{{ interface | join(' ') }}"
+
+# Additional options that are passed to the DHCP relay daemon?
+OPTIONS="-4 {{ options | join(' ') }}"
diff --git a/data/templates/dhcp-server/daemon.tmpl b/data/templates/dhcp-server/daemon.tmpl
new file mode 100644
index 000000000..f88032d38
--- /dev/null
+++ b/data/templates/dhcp-server/daemon.tmpl
@@ -0,0 +1,8 @@
+### Autogenerated by dhcp_server.py ###
+
+# sourced by /etc/init.d/isc-dhcpv4-server
+
+DHCPD_CONF={{ config_file }}
+DHCPD_PID={{ pid_file }}
+OPTIONS="-4 -lf {{ lease_file }}"
+INTERFACES=""
diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl
new file mode 100644
index 000000000..5f5129451
--- /dev/null
+++ b/data/templates/dhcp-server/dhcpd.conf.tmpl
@@ -0,0 +1,195 @@
+
+### Autogenerated by dhcp_server.py ###
+
+# For options please consult the following website:
+# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html
+#
+# log-facility local7;
+
+{% if hostfile_update %}
+on release {
+ set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name);
+ set ClientIp = binary-to-ascii(10, 8, ".",leased-address);
+ set ClientMac = binary-to-ascii(16, 8, ":",substring(hardware, 1, 6));
+ set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!");
+ execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", ClientName, ClientIp, ClientMac, ClientDomain);
+}
+
+on expiry {
+ set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name);
+ set ClientIp = binary-to-ascii(10, 8, ".",leased-address);
+ set ClientMac = binary-to-ascii(16, 8, ":",substring(hardware, 1, 6));
+ set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!");
+ execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", ClientName, ClientIp, ClientMac, ClientDomain);
+}
+{% endif %}
+{%- if host_decl_name %}
+use-host-decl-names on;
+{%- endif %}
+ddns-update-style {% if ddns_enable -%} interim {%- else -%} none {%- endif %};
+{% if static_route -%}
+option rfc3442-static-route code 121 = array of integer 8;
+option windows-static-route code 249 = array of integer 8;
+{%- endif %}
+{% if wpad -%}
+option wpad-url code 252 = text;
+{% endif %}
+
+{%- if global_parameters %}
+# The following {{ global_parameters | length }} line(s) were added as global-parameters in the CLI and have not been validated
+{%- for param in global_parameters %}
+{{ param }}
+{%- endfor -%}
+{%- endif %}
+
+# Failover configuration
+{% for network in shared_network %}
+{%- if not network.disabled -%}
+{%- for subnet in network.subnet %}
+{%- if subnet.failover_name -%}
+failover peer "{{ subnet.failover_name }}" {
+{%- if subnet.failover_status == 'primary' %}
+ primary;
+ mclt 1800;
+ split 128;
+{%- elif subnet.failover_status == 'secondary' %}
+ secondary;
+{%- endif %}
+ address {{ subnet.failover_local_addr }};
+ port 520;
+ peer address {{ subnet.failover_peer_addr }};
+ peer port 520;
+ max-response-delay 30;
+ max-unacked-updates 10;
+ load balance max seconds 3;
+}
+{% endif -%}
+{% endfor -%}
+{% endif -%}
+{% endfor %}
+
+# Shared network configration(s)
+{% for network in shared_network %}
+{%- if not network.disabled -%}
+shared-network {{ network.name }} {
+ {%- if network.authoritative %}
+ authoritative;
+ {%- endif %}
+ {%- if network.network_parameters %}
+ # The following {{ network.network_parameters | length }} line(s) were added as shared-network-parameters in the CLI and have not been validated
+ {%- for param in network.network_parameters %}
+ {{ param }}
+ {%- endfor %}
+ {%- endif %}
+ {%- for subnet in network.subnet %}
+ subnet {{ subnet.address }} netmask {{ subnet.netmask }} {
+ {%- if subnet.dns_server %}
+ option domain-name-servers {{ subnet.dns_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.domain_search %}
+ option domain-search {{ subnet.domain_search | join(', ') }};
+ {%- endif %}
+ {%- if subnet.ntp_server %}
+ option ntp-servers {{ subnet.ntp_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.pop_server %}
+ option pop-server {{ subnet.pop_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.smtp_server %}
+ option smtp-server {{ subnet.smtp_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.time_server %}
+ option time-servers {{ subnet.time_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.wins_server %}
+ option netbios-name-servers {{ subnet.wins_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.static_route %}
+ option rfc3442-static-route {{ subnet.static_route }}{% if subnet.rfc3442_default_router %}, {{ subnet.rfc3442_default_router }}{% endif %};
+ option windows-static-route {{ subnet.static_route }};
+ {%- endif %}
+ {%- if subnet.ip_forwarding %}
+ option ip-forwarding true;
+ {%- endif -%}
+ {%- if subnet.default_router %}
+ option routers {{ subnet.default_router }};
+ {%- endif -%}
+ {%- if subnet.server_identifier %}
+ option dhcp-server-identifier {{ subnet.server_identifier }};
+ {%- endif -%}
+ {%- if subnet.domain_name %}
+ option domain-name "{{ subnet.domain_name }}";
+ {%- endif -%}
+ {%- if subnet.subnet_parameters %}
+ # The following {{ subnet.subnet_parameters | length }} line(s) were added as subnet-parameters in the CLI and have not been validated
+ {%- for param in subnet.subnet_parameters %}
+ {{ param }}
+ {%- endfor -%}
+ {%- endif %}
+ {%- if subnet.tftp_server %}
+ option tftp-server-name "{{ subnet.tftp_server }}";
+ {%- endif -%}
+ {%- if subnet.bootfile_name %}
+ option bootfile-name "{{ subnet.bootfile_name }}";
+ filename "{{ subnet.bootfile_name }}";
+ {%- endif -%}
+ {%- if subnet.bootfile_server %}
+ next-server {{ subnet.bootfile_server }};
+ {%- endif -%}
+ {%- if subnet.time_offset %}
+ option time-offset {{ subnet.time_offset }};
+ {%- endif -%}
+ {%- if subnet.wpad_url %}
+ option wpad-url "{{ subnet.wpad_url }}";
+ {%- endif -%}
+ {%- if subnet.client_prefix_length %}
+ option subnet-mask {{ subnet.client_prefix_length }};
+ {%- endif -%}
+ {% if subnet.lease %}
+ default-lease-time {{ subnet.lease }};
+ max-lease-time {{ subnet.lease }};
+ {%- endif -%}
+ {%- for host in subnet.static_mapping %}
+ {% if not host.disabled -%}
+ host {% if host_decl_name -%} {{ host.name }} {%- else -%} {{ network.name }}_{{ host.name }} {%- endif %} {
+ {%- if host.ip_address %}
+ fixed-address {{ host.ip_address }};
+ {%- endif %}
+ hardware ethernet {{ host.mac_address }};
+ {%- if host.static_parameters %}
+ # The following {{ host.static_parameters | length }} line(s) were added as static-mapping-parameters in the CLI and have not been validated
+ {%- for param in host.static_parameters %}
+ {{ param }}
+ {%- endfor -%}
+ {%- endif %}
+ }
+ {%- endif %}
+ {%- endfor %}
+ {%- if subnet.failover_name %}
+ pool {
+ failover peer "{{ subnet.failover_name }}";
+ deny dynamic bootp clients;
+ {%- for range in subnet.range %}
+ range {{ range.start }} {{ range.stop }};
+ {%- endfor %}
+ }
+ {%- else %}
+ {%- for range in subnet.range %}
+ range {{ range.start }} {{ range.stop }};
+ {%- endfor %}
+ {%- endif %}
+ }
+ {%- endfor %}
+ on commit {
+ set shared-networkname = "{{ network.name }}";
+ {% if hostfile_update -%}
+ set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name);
+ set ClientIp = binary-to-ascii(10, 8, ".", leased-address);
+ set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
+ set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!");
+ execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "commit", ClientName, ClientIp, ClientMac, ClientDomain);
+ {%- endif %}
+ }
+}
+{%- endif %}
+{% endfor %}
diff --git a/data/templates/dhcpv6-relay/config.tmpl b/data/templates/dhcpv6-relay/config.tmpl
new file mode 100644
index 000000000..28f7a1a58
--- /dev/null
+++ b/data/templates/dhcpv6-relay/config.tmpl
@@ -0,0 +1,4 @@
+### Autogenerated by dhcpv6_relay.py ###
+
+# Defaults for isc-dhcpv6-relay initscript sourced by /etc/init.d/isc-dhcpv6-relay
+OPTIONS="-6 -l {{ listen_addr | join(' -l ') }} -u {{ upstream_addr | join(' -u ') }} {{ options | join(' ') }}"
diff --git a/data/templates/dhcpv6-server/daemon.tmpl b/data/templates/dhcpv6-server/daemon.tmpl
new file mode 100644
index 000000000..a4967e7c3
--- /dev/null
+++ b/data/templates/dhcpv6-server/daemon.tmpl
@@ -0,0 +1,8 @@
+### Autogenerated by dhcpv6_server.py ###
+
+# sourced by /etc/init.d/isc-dhcpv6-server
+
+DHCPD_CONF={{ config_file }}
+DHCPD_PID={{ pid_file }}
+OPTIONS="-6 -lf {{ lease_file }}"
+INTERFACES=""
diff --git a/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl b/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl
new file mode 100644
index 000000000..80d620fcf
--- /dev/null
+++ b/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl
@@ -0,0 +1,78 @@
+### Autogenerated by dhcpv6_server.py ###
+
+# For options please consult the following website:
+# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html
+
+log-facility local7;
+{%- if preference %}
+option dhcp6.preference {{ preference }};
+{%- endif %}
+
+# Shared network configration(s)
+{% for network in shared_network %}
+{%- if not network.disabled -%}
+shared-network {{ network.name }} {
+ {%- for subnet in network.subnet %}
+ subnet6 {{ subnet.network }} {
+ {%- for range in subnet.range6_prefix %}
+ range6 {{ range.prefix }}{{ " temporary" if range.temporary }};
+ {%- endfor %}
+ {%- for range in subnet.range6 %}
+ range6 {{ range.start }} {{ range.stop }};
+ {%- endfor %}
+ {%- if subnet.domain_search %}
+ option dhcp6.domain-search {{ subnet.domain_search | join(', ') }};
+ {%- endif %}
+ {%- if subnet.lease_def %}
+ default-lease-time {{ subnet.lease_def }};
+ {%- endif %}
+ {%- if subnet.lease_max %}
+ max-lease-time {{ subnet.lease_max }};
+ {%- endif %}
+ {%- if subnet.lease_min %}
+ min-lease-time {{ subnet.lease_min }};
+ {%- endif %}
+ {%- if subnet.dns_server %}
+ option dhcp6.name-servers {{ subnet.dns_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.nis_domain %}
+ option dhcp6.nis-domain-name "{{ subnet.nis_domain }}";
+ {%- endif %}
+ {%- if subnet.nis_server %}
+ option dhcp6.nis-servers {{ subnet.nis_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.nisp_domain %}
+ option dhcp6.nisp-domain-name "{{ subnet.nisp_domain }}";
+ {%- endif %}
+ {%- if subnet.nisp_server %}
+ option dhcp6.nisp-servers {{ subnet.nisp_server | join(', ') }};
+ {%- endif %}
+ {%- if subnet.sip_address %}
+ option dhcp6.sip-servers-addresses {{ subnet.sip_address | join(', ') }};
+ {%- endif %}
+ {%- if subnet.sip_hostname %}
+ option dhcp6.sip-servers-names {{ subnet.sip_hostname | join(', ') }};
+ {%- endif %}
+ {%- if subnet.sntp_server %}
+ option dhcp6.sntp-servers {{ subnet.sntp_server | join(', ') }};
+ {%- endif %}
+ {%- for host in subnet.static_mapping %}
+ {% if not host.disabled -%}
+ host {{ network.name }}_{{ host.name }} {
+ {%- if host.client_identifier %}
+ host-identifier option dhcp6.client-id {{ host.client_identifier }};
+ {%- endif %}
+ {%- if host.ipv6_address %}
+ fixed-address6 {{ host.ipv6_address }};
+ {%- endif %}
+ }
+ {%- endif %}
+ {%- endfor %}
+ }
+ {%- endfor %}
+ on commit {
+ set shared-networkname = "{{ network.name }}";
+ }
+}
+{%- endif %}
+{% endfor %}
diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl
new file mode 100644
index 000000000..9d1e019fa
--- /dev/null
+++ b/data/templates/dns-forwarding/recursor.conf.tmpl
@@ -0,0 +1,44 @@
+### Autogenerated by dns_forwarding.py ###
+
+# XXX: pdns recursor doesn't like whitespace near entry separators,
+# especially in the semicolon-separated lists of name servers.
+# Please be careful if you edit the template.
+
+# Non-configurable defaults
+daemon=yes
+threads=1
+allow-from={{ allow_from | join(',') }}
+log-common-errors=yes
+non-local-bind=yes
+query-local-address=0.0.0.0
+query-local-address6=::
+
+# cache-size
+max-cache-entries={{ cache_size }}
+
+# negative TTL for NXDOMAIN
+max-negative-ttl={{ negative_ttl }}
+
+# ignore-hosts-file
+export-etc-hosts={{ export_hosts_file }}
+
+# listen-on
+local-address={{ listen_on | join(',') }}
+
+# dnssec
+dnssec={{ dnssec }}
+
+# forward-zones / recursion
+#
+# statement is only inserted if either one forwarding domain or nameserver is configured
+# if nothing is given at all, powerdns will act as a real recursor and resolve all requests by its own
+#
+{% if name_servers or domains %}forward-zones-recurse=
+{%- for d in domains %}
+{{ d.name }}={{ d.servers | join(";") }}
+{{- ", " if not loop.last -}}
+{%- endfor -%}
+{%- if name_servers -%}
+{%- if domains -%}, {% endif -%}.={{ name_servers | join(';') }}
+{% endif %}
+{% endif %}
diff --git a/data/templates/dynamic-dns/ddclient.conf.tmpl b/data/templates/dynamic-dns/ddclient.conf.tmpl
new file mode 100644
index 000000000..22cb38f4e
--- /dev/null
+++ b/data/templates/dynamic-dns/ddclient.conf.tmpl
@@ -0,0 +1,49 @@
+
+### Autogenerated by dynamic_dns.py ###
+daemon=1m
+syslog=yes
+ssl=yes
+pid={{ pid_file }}
+cache={{ cache_file }}
+
+{% for interface in interfaces -%}
+
+#
+# ddclient configuration for interface "{{ interface.interface }}":
+#
+{% if interface.web_url -%}
+use=web, web='{{ interface.web_url}}' {%- if interface.web_skip %}, web-skip='{{ interface.web_skip }}'{% endif %}
+{% else -%}
+use=if, if={{ interface.interface }}
+{% endif -%}
+
+{% for rfc in interface.rfc2136 -%}
+{% for record in rfc.record %}
+# RFC2136 dynamic DNS configuration for {{ record }}.{{ rfc.zone }}
+server={{ rfc.server }}
+protocol=nsupdate
+password={{ rfc.keyfile }}
+ttl={{ rfc.ttl }}
+zone={{ rfc.zone }}
+{{ record }}
+{% endfor -%}
+{% endfor -%}
+
+{% for srv in interface.service %}
+{% for host in srv.host %}
+# DynDNS provider configuration for {{ host }}
+protocol={{ srv.protocol }},
+max-interval=28d,
+login={{ srv.login }},
+password='{{ srv.password }}',
+{% if srv.server -%}
+server={{ srv.server }},
+{% endif -%}
+{% if srv.zone -%}
+zone={{ srv.zone }},
+{% endif -%}
+{{ host }}
+{% endfor %}
+{% endfor %}
+
+{% endfor %}
diff --git a/data/templates/frr-bfd/bfd.frr.tmpl b/data/templates/frr-bfd/bfd.frr.tmpl
new file mode 100644
index 000000000..7df4bfd01
--- /dev/null
+++ b/data/templates/frr-bfd/bfd.frr.tmpl
@@ -0,0 +1,16 @@
+!
+bfd
+{% for peer in old_peers -%}
+ no peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %}
+{% endfor -%}
+!
+{% for peer in new_peers -%}
+ peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %}
+ detect-multiplier {{ peer.multiplier }}
+ receive-interval {{ peer.rx_interval }}
+ transmit-interval {{ peer.tx_interval }}
+ {% if peer.echo_mode %}echo-mode{% endif %}
+ {% if peer.echo_interval != '' %}echo-interval {{ peer.echo_interval }}{% endif %}
+ {% if not peer.shutdown %}no {% endif %}shutdown
+{% endfor -%}
+!
diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl
new file mode 100644
index 000000000..b6d3aaf83
--- /dev/null
+++ b/data/templates/https/nginx.default.tmpl
@@ -0,0 +1,61 @@
+### Autogenerated by https.py ###
+# Default server configuration
+#
+server {
+ listen 80 default_server;
+ listen [::]:80 default_server;
+ server_name _;
+ return 301 https://$server_name$request_uri;
+}
+
+{% for server in server_block_list %}
+server {
+
+ # SSL configuration
+ #
+{% if server.address == '*' %}
+ listen {{ server.port }} ssl;
+ listen [::]:{{ server.port }} ssl;
+{% else %}
+ listen {{ server.address }}:{{ server.port }} ssl;
+{% endif %}
+
+{% for name in server.name %}
+ server_name {{ name }};
+{% endfor %}
+
+{% if server.certbot %}
+ ssl_certificate /etc/letsencrypt/live/{{ server.certbot_dir }}/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/{{ server.certbot_dir }}/privkey.pem;
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
+{% elif server.vyos_cert %}
+ include {{ server.vyos_cert.conf }};
+{% else %}
+ #
+ # Self signed certs generated by the ssl-cert package
+ # Don't use them in a production server!
+ #
+ include snippets/snakeoil.conf;
+{% endif %}
+
+ # proxy settings for HTTP API, if enabled; 503, if not
+ location ~ /(retrieve|configure|config-file|image|generate|show) {
+{% if server.api %}
+ proxy_pass http://localhost:{{ server.api.port }};
+ proxy_buffering off;
+{% else %}
+ return 503;
+{% endif %}
+ }
+
+ error_page 501 502 503 =200 @50*_json;
+
+ location @50*_json {
+ default_type application/json;
+ return 200 '{"error": "Start service in configuration mode: set service https api"}';
+ }
+
+}
+
+{% endfor %}
diff --git a/data/templates/igmp-proxy/igmpproxy.conf.tmpl b/data/templates/igmp-proxy/igmpproxy.conf.tmpl
new file mode 100644
index 000000000..c7fc5cef5
--- /dev/null
+++ b/data/templates/igmp-proxy/igmpproxy.conf.tmpl
@@ -0,0 +1,37 @@
+########################################################
+#
+# autogenerated by igmp_proxy.py
+#
+# The configuration file must define one upstream
+# interface, and one or more downstream interfaces.
+#
+# If multicast traffic originates outside the
+# upstream subnet, the "altnet" option can be
+# used in order to define legal multicast sources.
+# (Se example...)
+#
+# The "quickleave" should be used to avoid saturation
+# of the upstream link. The option should only
+# be used if it's absolutely nessecary to
+# accurately imitate just one Client.
+#
+########################################################
+
+{% if not disable_quickleave -%}
+quickleave
+{% endif -%}
+
+{% for interface in interfaces %}
+# Configuration for {{ interface.name }} ({{ interface.role }} interface)
+{% if interface.role == 'disabled' -%}
+phyint {{ interface.name }} disabled
+{%- else -%}
+phyint {{ interface.name }} {{ interface.role }} ratelimit 0 threshold {{ interface.threshold }}
+{%- endif -%}
+{%- for subnet in interface.alt_subnet %}
+ altnet {{ subnet }}
+{%- endfor %}
+{%- for subnet in interface.whitelist %}
+ whitelist {{ subnet }}
+{%- endfor %}
+{% endfor %}
diff --git a/data/templates/igmp/igmp.frr.tmpl b/data/templates/igmp/igmp.frr.tmpl
new file mode 100644
index 000000000..de4696c1f
--- /dev/null
+++ b/data/templates/igmp/igmp.frr.tmpl
@@ -0,0 +1,41 @@
+!
+{% for iface in old_ifaces -%}
+interface {{ iface }}
+{% for group in old_ifaces[iface].gr_join -%}
+{% if old_ifaces[iface].gr_join[group] -%}
+{% for source in old_ifaces[iface].gr_join[group] -%}
+no ip igmp join {{ group }} {{ source }}
+{% endfor -%}
+{% else -%}
+no ip igmp join {{ group }}
+{% endif -%}
+{% endfor -%}
+no ip igmp
+!
+{% endfor -%}
+{% for iface in ifaces -%}
+interface {{ iface }}
+{% if ifaces[iface].version -%}
+ip igmp version {{ ifaces[iface].version }}
+{% else -%}
+{# IGMP default version 3 #}
+ip igmp
+{% endif -%}
+{% if ifaces[iface].query_interval -%}
+ip igmp query-interval {{ ifaces[iface].query_interval }}
+{% endif -%}
+{% if ifaces[iface].query_max_resp_time -%}
+ip igmp query-max-response-time {{ ifaces[iface].query_max_resp_time }}
+{% endif -%}
+{% for group in ifaces[iface].gr_join -%}
+{% if ifaces[iface].gr_join[group] -%}
+{% for source in ifaces[iface].gr_join[group] -%}
+ip igmp join {{ group }} {{ source }}
+{% endfor -%}
+{% else -%}
+ip igmp join {{ group }}
+{% endif -%}
+{% endfor -%}
+!
+{% endfor -%}
+!
diff --git a/data/templates/ipoe-server/chap-secrets.tmpl b/data/templates/ipoe-server/chap-secrets.tmpl
new file mode 100644
index 000000000..707718e94
--- /dev/null
+++ b/data/templates/ipoe-server/chap-secrets.tmpl
@@ -0,0 +1,18 @@
+# username server password acceptable local IP addresses shaper
+{% for aifc in auth['auth_if'] %}
+{% for mac in auth['auth_if'][aifc] %}
+{% if (auth['auth_if'][aifc][mac]['up']) and (auth['auth_if'][aifc][mac]['down']) %}
+{% if auth['auth_if'][aifc][mac]['vlan'] %}
+{{aifc}}.{{auth['auth_if'][aifc][mac]['vlan']}}\t*\t{{mac.lower()}}\t*\t{{auth['auth_if'][aifc][mac]['down']}}/{{auth['auth_if'][aifc][mac]['up']}}
+{% else %}
+{{aifc}}\t*\t{{mac.lower()}}\t*\t{{auth['auth_if'][aifc][mac]['down']}}/{{auth['auth_if'][aifc][mac]['up']}}
+{% endif %}
+{% else %}
+{% if auth['auth_if'][aifc][mac]['vlan'] %}
+{{aifc}}.{{auth['auth_if'][aifc][mac]['vlan']}}\t*\t{{mac.lower()}}\t*
+{% else %}
+{{aifc}}\t*\t{{mac.lower()}}\t*
+{% endif %}
+{% endif %}
+{% endfor %}
+{% endfor %}
diff --git a/data/templates/ipoe-server/ipoe.config.tmpl b/data/templates/ipoe-server/ipoe.config.tmpl
new file mode 100644
index 000000000..109bc0d92
--- /dev/null
+++ b/data/templates/ipoe-server/ipoe.config.tmpl
@@ -0,0 +1,123 @@
+### generated by ipoe.py ###
+[modules]
+log_syslog
+ipoe
+shaper
+ipv6pool
+ipv6_nd
+ipv6_dhcp
+{% if auth['mech'] == 'radius' %}
+radius
+{% endif -%}
+ippool
+{% if auth['mech'] == 'local' %}
+chap-secrets
+{% endif %}
+
+[core]
+thread-count={{thread_cnt}}
+
+[log]
+syslog=accel-ipoe,daemon
+copy=1
+level=5
+
+[ipoe]
+verbose=1
+{% for intfc in interfaces %}
+{% if interfaces[intfc]['vlan_mon'] %}
+interface=re:{{intfc}}\.\d+,\
+{% else %}
+interface={{intfc}},\
+{% endif %}
+shared={{interfaces[intfc]['shared']}},\
+mode={{interfaces[intfc]['mode']}},\
+ifcfg={{interfaces[intfc]['ifcfg']}},\
+range={{interfaces[intfc]['range']}},\
+start={{interfaces[intfc]['sess_start']}},\
+ipv6=1
+{% endfor %}
+{% if auth['mech'] == 'noauth' %}
+noauth=1
+{% endif %}
+{% if auth['mech'] == 'local' %}
+username=ifname
+password=csid
+{% endif %}
+
+{%- for intfc in interfaces %}
+{% if (interfaces[intfc]['shared'] == '0') and (interfaces[intfc]['vlan_mon']) %}
+vlan-mon={{intfc}},{{interfaces[intfc]['vlan_mon']|join(',')}}
+{% endif %}
+{% endfor %}
+
+{% if (dns['server1']) or (dns['server2']) %}
+[dns]
+{% if dns['server1'] %}
+dns1={{dns['server1']}}
+{% endif -%}
+{% if dns['server2'] %}
+dns2={{dns['server2']}}
+{% endif -%}
+{% endif -%}
+
+{% if (dnsv6['server1']) or (dnsv6['server2']) or (dnsv6['server3']) %}
+[dnsv6]
+dns={{dnsv6['server1']}}
+dns={{dnsv6['server2']}}
+dns={{dnsv6['server3']}}
+{% endif %}
+
+[ipv6-nd]
+verbose=1
+
+[ipv6-dhcp]
+verbose=1
+
+{% if ipv6['prfx'] %}
+[ipv6-pool]
+{% for prfx in ipv6['prfx'] %}
+{{prfx}}
+{% endfor %}
+{% for pd in ipv6['pd'] %}
+delegate={{pd}}
+{% endfor %}
+{% endif %}
+
+{% if auth['mech'] == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/ipoe/chap-secrets
+{% endif %}
+
+{% if auth['mech'] == 'radius' %}
+[radius]
+verbose=1
+{% for srv in auth['radius'] %}
+server={{srv}},{{auth['radius'][srv]['secret']}},\
+req-limit={{auth['radius'][srv]['req-limit']}},\
+fail-time={{auth['radius'][srv]['fail-time']}}
+{% endfor %}
+{% if auth['radsettings']['dae-server']['ip-address'] %}
+dae-server={{auth['radsettings']['dae-server']['ip-address']}}:\
+{{auth['radsettings']['dae-server']['port']}},\
+{{auth['radsettings']['dae-server']['secret']}}
+{% endif -%}
+{% if auth['radsettings']['acct-timeout'] %}
+acct-timeout={{auth['radsettings']['acct-timeout']}}
+{% endif -%}
+{% if auth['radsettings']['max-try'] %}
+max-try={{auth['radsettings']['max-try']}}
+{% endif -%}
+{% if auth['radsettings']['timeout'] %}
+timeout={{auth['radsettings']['timeout']}}
+{% endif -%}
+{% if auth['radsettings']['nas-ip-address'] %}
+nas-ip-address={{auth['radsettings']['nas-ip-address']}}
+{% endif -%}
+{% if auth['radsettings']['nas-identifier'] %}
+nas-identifier={{auth['radsettings']['nas-identifier']}}
+{% endif -%}
+{% endif %}
+
+[cli]
+tcp=127.0.0.1:2002
diff --git a/data/templates/ipsec/ipsec.conf.tmpl b/data/templates/ipsec/ipsec.conf.tmpl
new file mode 100644
index 000000000..d0b60765b
--- /dev/null
+++ b/data/templates/ipsec/ipsec.conf.tmpl
@@ -0,0 +1,3 @@
+{{delim_ipsec_l2tp_begin}}
+include {{ipsec_ra_conn_file}}
+{{delim_ipsec_l2tp_end}}
diff --git a/data/templates/ipsec/ipsec.secrets.tmpl b/data/templates/ipsec/ipsec.secrets.tmpl
new file mode 100644
index 000000000..55c010a3b
--- /dev/null
+++ b/data/templates/ipsec/ipsec.secrets.tmpl
@@ -0,0 +1,7 @@
+{{delim_ipsec_l2tp_begin}}
+{% if ipsec_l2tp_auth_mode == 'pre-shared-secret' %}
+{{outside_addr}} %any : PSK "{{ipsec_l2tp_secret}}"
+{% elif ipsec_l2tp_auth_mode == 'x509' %}
+: RSA {{server_key_file_copied}}
+{% endif%}
+{{delim_ipsec_l2tp_end}}
diff --git a/data/templates/ipsec/remote-access.tmpl b/data/templates/ipsec/remote-access.tmpl
new file mode 100644
index 000000000..fae48232f
--- /dev/null
+++ b/data/templates/ipsec/remote-access.tmpl
@@ -0,0 +1,28 @@
+{{delim_ipsec_l2tp_begin}}
+conn {{ra_conn_name}}
+ type=transport
+ left={{outside_addr}}
+ leftsubnet=%dynamic[/1701]
+ rightsubnet=%dynamic
+ mark_in=%unique
+ auto=add
+ ike=aes256-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1024!
+ dpddelay=15
+ dpdtimeout=45
+ dpdaction=clear
+ esp=aes256-sha1,3des-sha1!
+ rekey=no
+{% if ipsec_l2tp_auth_mode == 'pre-shared-secret' %}
+ authby=secret
+ leftauth=psk
+ rightauth=psk
+{% elif ipsec_l2tp_auth_mode == 'x509' %}
+ authby=rsasig
+ leftrsasigkey=%cert
+ rightrsasigkey=%cert
+ rightca=%same
+ leftcert={{server_cert_file_copied}}
+{% endif %}
+ ikelifetime={{ipsec_l2tp_ike_lifetime}}
+ keylife={{ipsec_l2tp_lifetime}}
+{{delim_ipsec_l2tp_end}}
diff --git a/data/templates/l2tp/chap-secrets.tmpl b/data/templates/l2tp/chap-secrets.tmpl
new file mode 100644
index 000000000..323528b3c
--- /dev/null
+++ b/data/templates/l2tp/chap-secrets.tmpl
@@ -0,0 +1,11 @@
+# username server password acceptable local IP addresses shaper
+{% for user in authentication['local-users'] %}
+{% if authentication['local-users'][user]['state'] == 'enabled' %}
+{% if (authentication['local-users'][user]['upload']) and (authentication['local-users'][user]['download']) %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}\t\
+{{authentication['local-users'][user]['download']}}/{{authentication['local-users'][user]['upload']}}
+{% else %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}
+{% endif %}
+{% endif %}
+{% endfor %}
diff --git a/data/templates/l2tp/l2tp.config.tmpl b/data/templates/l2tp/l2tp.config.tmpl
new file mode 100644
index 000000000..b8637e256
--- /dev/null
+++ b/data/templates/l2tp/l2tp.config.tmpl
@@ -0,0 +1,173 @@
+### generated by accel_l2tp.py ###
+[modules]
+log_syslog
+l2tp
+chap-secrets
+{% for proto in authentication['auth_proto']: %}
+{{proto}}
+{% endfor%}
+{% if authentication['mode'] == 'radius' %}
+radius
+{% endif -%}
+ippool
+shaper
+ipv6pool
+ipv6_nd
+ipv6_dhcp
+
+[core]
+thread-count={{thread_cnt}}
+
+[log]
+syslog=accel-l2tp,daemon
+copy=1
+level=5
+
+{% if dns %}
+[dns]
+{% if dns[0] %}
+dns1={{dns[0]}}
+{% endif %}
+{% if dns[1] %}
+dns2={{dns[1]}}
+{% endif %}
+{% endif -%}
+
+{% if dnsv6 %}
+[ipv6-dns]
+{% for srv in dnsv6: %}
+{{srv}}
+{% endfor %}
+{% endif %}
+
+{% if wins %}
+[wins]
+{% if wins[0] %}
+wins1={{wins[0]}}
+{% endif %}
+{% if wins[1] %}
+wins2={{wins[1]}}
+{% endif %}
+{% endif -%}
+
+[l2tp]
+verbose=1
+ifname=l2tp%d
+ppp-max-mtu={{mtu}}
+mppe={{authentication['mppe']}}
+{% if outside_addr %}
+bind={{outside_addr}}
+{% endif %}
+{% if lns_shared_secret %}
+secret={{lns_shared_secret}}
+{% endif %}
+
+[client-ip-range]
+0.0.0.0/0
+
+{% if (client_ip_pool) or (client_ip_subnets) %}
+[ip-pool]
+{% if client_ip_pool %}
+{{client_ip_pool}}
+{% endif -%}
+{% if client_ip_subnets %}
+{% for sn in client_ip_subnets %}
+{{sn}}
+{% endfor -%}
+{% endif %}
+{% endif %}
+{% if gateway_address %}
+gw-ip-address={{gateway_address}}
+{% endif %}
+
+{% if authentication['mode'] == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/l2tp/chap-secrets
+{% if gateway_address %}
+gw-ip-address={{gateway_address}}
+{% endif %}
+{% endif %}
+
+[ppp]
+verbose=1
+check-ip=1
+single-session=replace
+{% if idle_timeout %}
+lcp-echo-timeout={{idle_timeout}}
+{% endif %}
+{% if ppp_options['lcp-echo-interval'] %}
+lcp-echo-interval={{ppp_options['lcp-echo-interval']}}
+{% else %}
+lcp-echo-interval=30
+{% endif %}
+{% if ppp_options['lcp-echo-failure'] %}
+lcp-echo-failure={{ppp_options['lcp-echo-failure']}}
+{% else %}
+lcp-echo-failure=3
+{% endif %}
+{% if ccp_disable %}
+ccp=0
+{% endif %}
+{% if client_ipv6_pool %}
+ipv6=allow
+{% endif %}
+
+{% if authentication['mode'] == 'radius' %}
+[radius]
+{% for rsrv in authentication['radiussrv']: %}
+server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\
+req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\
+fail-time={{authentication['radiussrv'][rsrv]['fail-time']}}
+{% endfor %}
+{% if authentication['radiusopt']['timeout'] %}
+timeout={{authentication['radiusopt']['timeout']}}
+{% endif %}
+{% if authentication['radiusopt']['acct-timeout'] %}
+acct-timeout={{authentication['radiusopt']['acct-timeout']}}
+{% endif %}
+{% if authentication['radiusopt']['max-try'] %}
+max-try={{authentication['radiusopt']['max-try']}}
+{% endif %}
+{% if authentication['radiusopt']['nas-id'] %}
+nas-identifier={{authentication['radiusopt']['nas-id']}}
+{% endif %}
+{% if authentication['radius_source_address'] %}
+nas-ip-address={{authentication['radius_source_address']}}
+{% endif -%}
+{% if authentication['radiusopt']['dae-srv'] %}
+dae-server={{authentication['radiusopt']['dae-srv']['ip-addr']}}:\
+{{authentication['radiusopt']['dae-srv']['port']}},\
+{{authentication['radiusopt']['dae-srv']['secret']}}
+{% endif -%}
+gw-ip-address={{gateway_address}}
+verbose=1
+{% endif -%}
+
+{% if client_ipv6_pool %}
+[ipv6-pool]
+{% for prfx in client_ipv6_pool.prefix: %}
+{{prfx}}
+{% endfor %}
+{% for prfx in client_ipv6_pool.delegate_prefix: %}
+delegate={{prfx}}
+{% endfor %}
+{% endif %}
+
+{% if client_ipv6_pool['delegate_prefix'] %}
+[ipv6-dhcp]
+verbose=1
+{% endif %}
+
+{% if authentication['radiusopt']['shaper'] %}
+[shaper]
+verbose=1
+attr={{authentication['radiusopt']['shaper']['attr']}}
+{% if authentication['radiusopt']['shaper']['vendor'] %}
+vendor={{authentication['radiusopt']['shaper']['vendor']}}
+{% endif -%}
+{% endif %}
+
+[cli]
+tcp=127.0.0.1:2004
+sessions-columns=ifname,username,calling-sid,ip,{{ip6_column}}{{ip6_dp_column}}rate-limit,type,comp,state,rx-bytes,tx-bytes,uptime
+
diff --git a/data/templates/lldp/lldpd.tmpl b/data/templates/lldp/lldpd.tmpl
new file mode 100644
index 000000000..3db955b48
--- /dev/null
+++ b/data/templates/lldp/lldpd.tmpl
@@ -0,0 +1,3 @@
+### Autogenerated by lldp.py ###
+DAEMON_ARGS="-M 4{% if options.snmp %} -x{% endif %}{% if options.cdp %} -c{% endif %}{% if options.edp %} -e{% endif %}{% if options.fdp %} -f{% endif %}{% if options.sonmp %} -s{% endif %}"
+
diff --git a/data/templates/lldp/vyos.conf.tmpl b/data/templates/lldp/vyos.conf.tmpl
new file mode 100644
index 000000000..e724f42c6
--- /dev/null
+++ b/data/templates/lldp/vyos.conf.tmpl
@@ -0,0 +1,20 @@
+### Autogenerated by lldp.py ###
+
+configure system platform VyOS
+configure system description "VyOS {{ options.description }}"
+{% if options.listen_on -%}
+configure system interface pattern "{{ ( options.listen_on | select('equalto','all') | map('replace','all','*') | list + options.listen_on | select('equalto','!all') | map('replace','!all','!*') | list + options.listen_on | reject('equalto','all') | reject('equalto','!all') | list ) | unique | join(",") }}"
+{%- endif %}
+{% if options.mgmt_addr -%}
+configure system ip management pattern {{ options.mgmt_addr | join(",") }}
+{%- endif %}
+{%- for loc in location -%}
+{%- if loc.elin %}
+configure ports {{ loc.name }} med location elin "{{ loc.elin }}"
+{%- endif %}
+{%- if loc.coordinate_based %}
+configure ports {{ loc.name }} med location coordinate {% if loc.coordinate_based.latitude %}latitude {{ loc.coordinate_based.latitude }}{% endif %} {% if loc.coordinate_based.longitude %}longitude {{ loc.coordinate_based.longitude }}{% endif %} {% if loc.coordinate_based.altitude %}altitude {{ loc.coordinate_based.altitude }} m{% endif %} {% if loc.coordinate_based.datum %}datum {{ loc.coordinate_based.datum }}{% endif %}
+{%- endif %}
+
+
+{% endfor %}
diff --git a/data/templates/mdns-repeater/mdns-repeater.tmpl b/data/templates/mdns-repeater/mdns-repeater.tmpl
new file mode 100644
index 000000000..3fc4db67e
--- /dev/null
+++ b/data/templates/mdns-repeater/mdns-repeater.tmpl
@@ -0,0 +1,2 @@
+### Autogenerated by mdns_repeater.py ###
+DAEMON_ARGS="{{ interfaces | join(' ') }}"
diff --git a/data/templates/mpls/ldpd.frr.tmpl b/data/templates/mpls/ldpd.frr.tmpl
new file mode 100644
index 000000000..bbff88ae5
--- /dev/null
+++ b/data/templates/mpls/ldpd.frr.tmpl
@@ -0,0 +1,58 @@
+!
+{% if mpls_ldp -%}
+mpls ldp
+{% if old_router_id -%}
+no router-id {{ old_router_id }}
+{% endif -%}
+{% if router_id -%}
+router-id {{ router_id }}
+{% endif -%}
+{% for neighbor_id in old_ldp.neighbors -%}
+no neighbor {{neighbor_id}} password {{old_ldp.neighbors[neighbor_id].password}}
+{% endfor -%}
+{% for neighbor_id in ldp.neighbors -%}
+neighbor {{neighbor_id}} password {{ldp.neighbors[neighbor_id].password}}
+{% endfor -%}
+address-family ipv4
+label local allocate host-routes
+{% if old_ldp.d_transp_ipv4 -%}
+no discovery transport-address {{ old_ldp.d_transp_ipv4 }}
+{% endif -%}
+{% if ldp.d_transp_ipv4 -%}
+discovery transport-address {{ ldp.d_transp_ipv4 }}
+{% endif -%}
+{% for interface in old_ldp.interfaces -%}
+no interface {{interface}}
+{% endfor -%}
+{% for interface in ldp.interfaces -%}
+interface {{interface}}
+{% endfor -%}
+!
+!
+exit-address-family
+!
+{% if ldp.d_transp_ipv6 -%}
+address-family ipv6
+label local allocate host-routes
+{% if old_ldp.d_transp_ipv6 -%}
+no discovery transport-address {{ old_ldp.d_transp_ipv6 }}
+{% endif -%}
+{% if ldp.d_transp_ipv6 -%}
+discovery transport-address {{ ldp.d_transp_ipv6 }}
+{% endif -%}
+{% for interface in old_ldp.interfaces -%}
+no interface {{interface}}
+{% endfor -%}
+{% for interface in ldp.interfaces -%}
+interface {{interface}}
+{% endfor -%}
+!
+exit-address-family
+{% else -%}
+no address-family ipv6
+{% endif -%}
+!
+{% else -%}
+no mpls ldp
+{% endif -%}
+!
diff --git a/data/templates/netflow/uacctd.conf.tmpl b/data/templates/netflow/uacctd.conf.tmpl
new file mode 100644
index 000000000..d8615566f
--- /dev/null
+++ b/data/templates/netflow/uacctd.conf.tmpl
@@ -0,0 +1,69 @@
+# Genereated from VyOS configuration
+daemonize: true
+promisc: false
+pidfile: /var/run/uacctd.pid
+uacctd_group: 2
+uacctd_nl_size: 2097152
+snaplen: {{ snaplen }}
+aggregate: in_iface,src_mac,dst_mac,vlan,src_host,dst_host,src_port,dst_port,proto,tos,flows
+plugin_pipe_size: {{ templatecfg['plugin_pipe_size'] }}
+plugin_buffer_size: {{ templatecfg['plugin_buffer_size'] }}
+{%- if templatecfg['syslog-facility'] != none %}
+syslog: {{ templatecfg['syslog-facility'] }}
+{%- endif %}
+{%- if templatecfg['disable-imt'] == none %}
+imt_path: /tmp/uacctd.pipe
+imt_mem_pools_number: 169
+{%- endif %}
+plugins:
+{%- if templatecfg['netflow']['servers'] != none -%}
+ {% for server in templatecfg['netflow']['servers'] %}
+ {%- if loop.last -%}nfprobe[nf_{{ server['address'] }}]{%- else %}nfprobe[nf_{{ server['address'] }}],{%- endif %}
+ {%- endfor -%}
+ {% set plugins_presented = true %}
+{%- endif %}
+{%- if templatecfg['sflow']['servers'] != none -%}
+ {% if plugins_presented -%}
+ {%- for server in templatecfg['sflow']['servers'] -%}
+ ,sfprobe[sf_{{ server['address'] }}]
+ {%- endfor %}
+ {%- else %}
+ {%- for server in templatecfg['sflow']['servers'] %}
+ {%- if loop.last -%}sfprobe[sf_{{ server['address'] }}]{%- else %}sfprobe[sf_{{ server['address'] }}],{%- endif %}
+ {%- endfor %}
+ {%- endif -%}
+ {% set plugins_presented = true %}
+{%- endif %}
+{%- if templatecfg['disable-imt'] == none %}
+ {%- if plugins_presented -%},memory{%- else %}memory{%- endif %}
+{%- endif %}
+{%- if templatecfg['netflow']['servers'] != none %}
+{%- for server in templatecfg['netflow']['servers'] %}
+nfprobe_receiver[nf_{{ server['address'] }}]: {{ server['address'] }}:{{ server['port'] }}
+nfprobe_version[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['version'] }}
+{%- if templatecfg['netflow']['engine-id'] != none %}
+nfprobe_engine[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['engine-id'] }}
+{%- endif %}
+{%- if templatecfg['netflow']['max-flows'] != none %}
+nfprobe_maxflows[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['max-flows'] }}
+{%- endif %}
+{%- if templatecfg['netflow']['sampling-rate'] != none %}
+sampling_rate[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['sampling-rate'] }}
+{%- endif %}
+{%- if templatecfg['netflow']['source-ip'] != none %}
+nfprobe_source_ip[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['source-ip'] }}
+{%- endif %}
+{%- if templatecfg['netflow']['timeout_string'] != '' %}
+nfprobe_timeouts[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['timeout_string'] }}
+{%- endif %}
+{%- endfor %}
+{%- endif %}
+{%- if templatecfg['sflow']['servers'] != none %}
+{%- for server in templatecfg['sflow']['servers'] %}
+sfprobe_receiver[sf_{{ server['address'] }}]: {{ server['address'] }}:{{ server['port'] }}
+sfprobe_agentip[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['agent-address'] }}
+{%- if templatecfg['sflow']['sampling-rate'] != none %}
+sampling_rate[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['sampling-rate'] }}
+{%- endif %}
+{%- endfor %}
+{% endif %}
diff --git a/data/templates/ntp/ntp.conf.tmpl b/data/templates/ntp/ntp.conf.tmpl
new file mode 100644
index 000000000..52042d218
--- /dev/null
+++ b/data/templates/ntp/ntp.conf.tmpl
@@ -0,0 +1,38 @@
+### Autogenerated by ntp.py ###
+
+#
+# Non-configurable defaults
+#
+driftfile /var/lib/ntp/ntp.drift
+# By default, only allow ntpd to query time sources, ignore any incoming requests
+restrict default noquery nopeer notrap nomodify
+# Local users have unrestricted access, allowing reconfiguration via ntpdc
+restrict 127.0.0.1
+restrict -6 ::1
+
+#
+# Configurable section
+#
+
+{% if servers -%}
+{% for s in servers -%}
+# Server configuration for: {{ s.name }}
+server {{ s.name }} iburst {{ s.options | join(" ") }}
+{% endfor -%}
+{% endif %}
+
+{% if allowed_networks -%}
+{% for n in allowed_networks -%}
+# Client configuration for network: {{ n.network }}
+restrict {{ n.address }} mask {{ n.netmask }} nomodify notrap nopeer
+
+{% endfor -%}
+{% endif %}
+
+{% if listen_address -%}
+# NTP should listen on configured addresses only
+interface ignore wildcard
+{% for a in listen_address -%}
+interface listen {{ a }}
+{% endfor -%}
+{% endif %}
diff --git a/data/templates/openvpn/client.conf.tmpl b/data/templates/openvpn/client.conf.tmpl
new file mode 100644
index 000000000..3099f2ca7
--- /dev/null
+++ b/data/templates/openvpn/client.conf.tmpl
@@ -0,0 +1,16 @@
+### Autogenerated by interfaces-openvpn.py ###
+
+{% if ip -%}
+ifconfig-push {{ ip }} {{ remote_netmask }}
+{% endif -%}
+{% for route in push_route -%}
+push "route {{ route }}"
+{% endfor -%}
+
+{% for net in subnet -%}
+iroute {{ net }}
+{% endfor -%}
+
+{% if disable -%}
+disable
+{% endif -%}
diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl
new file mode 100644
index 000000000..e7715dfb5
--- /dev/null
+++ b/data/templates/openvpn/server.conf.tmpl
@@ -0,0 +1,223 @@
+### Autogenerated by interfaces-openvpn.py ###
+#
+# See https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
+# for individual keyword definition
+
+{% if description %}
+# {{ description }}
+{% endif %}
+
+verb 3
+status /opt/vyatta/etc/openvpn/status/{{ intf }}.status 30
+writepid /var/run/openvpn/{{ intf }}.pid
+
+dev-type {{ type }}
+dev {{ intf }}
+user {{ uid }}
+group {{ gid }}
+persist-key
+iproute /usr/libexec/vyos/system/unpriv-ip
+
+proto {% if 'tcp-active' in protocol -%}tcp-client{% elif 'tcp-passive' in protocol -%}tcp-server{% else %}udp{% endif %}
+
+{%- if local_host %}
+local {{ local_host }}
+{% endif %}
+
+{%- if mode == 'server' and protocol == 'udp' and not local_host %}
+multihome
+{% endif %}
+
+{%- if local_port %}
+lport {{ local_port }}
+{% endif %}
+
+{%- if remote_port %}
+rport {{ remote_port }}
+{% endif %}
+
+{%- if remote_host %}
+{% for remote in remote_host -%}
+remote {{ remote }}
+{% endfor -%}
+{% endif %}
+
+{%- if shared_secret_file %}
+secret {{ shared_secret_file }}
+{% endif %}
+
+{%- if persistent_tunnel %}
+persist-tun
+{% endif %}
+
+{%- if mode %}
+{%- if 'client' in mode %}
+#
+# OpenVPN Client mode
+#
+client
+nobind
+{%- elif 'server' in mode %}
+#
+# OpenVPN Server mode
+#
+mode server
+tls-server
+keepalive {{ ping_interval }} {{ ping_restart }}
+management /tmp/openvpn-mgmt-intf unix
+
+{%- if server_topology %}
+topology {% if 'point-to-point' in server_topology %}p2p{% else %}subnet{% endif %}
+{% endif %}
+
+{% for ns in server_dns_nameserver -%}
+push "dhcp-option DNS {{ ns }}"
+{% endfor -%}
+
+{% for route in server_push_route -%}
+push "route {{ route }}"
+{% endfor -%}
+
+{%- if server_domain %}
+push "dhcp-option DOMAIN {{ server_domain }}"
+{% endif %}
+
+{%- if server_max_conn %}
+max-clients {{ server_max_conn }}
+{% endif %}
+
+{%- if bridge_member %}
+server-bridge nogw
+{%- else %}
+server {{ server_subnet }}
+{% endif %}
+
+{%- if server_reject_unconfigured %}
+ccd-exclusive
+{% endif %}
+
+{%- else %}
+#
+# OpenVPN site-2-site mode
+#
+ping {{ ping_interval }}
+ping-restart {{ ping_restart }}
+
+{%- if local_address_subnet %}
+ifconfig {{ local_address }} {{ local_address_subnet }}
+{% elif remote_address %}
+ifconfig {{ local_address }} {{ remote_address }}
+{% endif %}
+
+{% endif %}
+{% endif %}
+
+{%- if tls_ca_cert %}
+ca {{ tls_ca_cert }}
+{% endif %}
+
+{%- if tls_cert %}
+cert {{ tls_cert }}
+{% endif %}
+
+{%- if tls_key %}
+key {{ tls_key }}
+{% endif %}
+
+{%- if tls_crypt %}
+tls-crypt {{ tls_crypt }}
+{% endif %}
+
+{%- if tls_crl %}
+crl-verify {{ tls_crl }}
+{% endif %}
+
+{%- if tls_version_min %}
+tls-version-min {{tls_version_min}}
+{% endif %}
+
+{%- if tls_dh %}
+dh {{ tls_dh }}
+{% endif %}
+
+{%- if tls_auth %}
+tls-auth {{tls_auth}}
+{% endif %}
+
+{%- if 'active' in tls_role %}
+tls-client
+{%- elif 'passive' in tls_role %}
+tls-server
+{% endif %}
+
+{%- if redirect_gateway %}
+push "redirect-gateway {{ redirect_gateway }}"
+{% endif %}
+
+{%- if compress_lzo %}
+compress lzo
+{% endif %}
+
+{%- if hash %}
+auth {{ hash }}
+{% endif %}
+
+{%- if encryption %}
+{%- if 'des' in encryption %}
+cipher des-cbc
+{%- elif '3des' in encryption %}
+cipher des-ede3-cbc
+{%- elif 'bf128' in encryption %}
+cipher bf-cbc
+keysize 128
+{%- elif 'bf256' in encryption %}
+cipher bf-cbc
+keysize 25
+{%- elif 'aes128gcm' in encryption %}
+cipher aes-128-gcm
+{%- elif 'aes128' in encryption %}
+cipher aes-128-cbc
+{%- elif 'aes192gcm' in encryption %}
+cipher aes-192-gcm
+{%- elif 'aes192' in encryption %}
+cipher aes-192-cbc
+{%- elif 'aes256gcm' in encryption %}
+cipher aes-256-gcm
+{%- elif 'aes256' in encryption %}
+cipher aes-256-cbc
+{% endif %}
+{% endif %}
+
+{%- if ncp_ciphers %}
+ncp-ciphers {{ncp_ciphers}}
+{% endif %}
+{%- if disable_ncp %}
+ncp-disable
+{% endif %}
+
+{%- if auth %}
+auth-user-pass /tmp/openvpn-{{ intf }}-pw
+auth-retry nointeract
+{% endif %}
+
+{%- if client %}
+client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }}
+{% endif %}
+
+# DEPRECATED This option will be removed in OpenVPN 2.5
+# Until OpenVPN v2.3 the format of the X.509 Subject fields was formatted like this:
+# /C=US/L=Somewhere/CN=John Doe/emailAddress=john@example.com In addition the old
+# behaviour was to remap any character other than alphanumeric, underscore ('_'),
+# dash ('-'), dot ('.'), and slash ('/') to underscore ('_'). The X.509 Subject
+# string as returned by the tls_id environmental variable, could additionally
+# contain colon (':') or equal ('='). When using the --compat-names option, this
+# old formatting and remapping will be re-enabled again. This is purely implemented
+# for compatibility reasons when using older plug-ins or scripts which does not
+# handle the new formatting or UTF-8 characters.
+#
+# See https://phabricator.vyos.net/T1512
+compat-names
+
+{% for option in options -%}
+{{ option }}
+{% endfor -%}
diff --git a/data/templates/pim/pimd.frr.tmpl b/data/templates/pim/pimd.frr.tmpl
new file mode 100644
index 000000000..1d1532c60
--- /dev/null
+++ b/data/templates/pim/pimd.frr.tmpl
@@ -0,0 +1,34 @@
+!
+{% for rp_addr in old_pim.rp -%}
+{% for group in old_pim.rp[rp_addr] -%}
+no ip pim rp {{ rp_addr }} {{ group }}
+{% endfor -%}
+{% endfor -%}
+{% if old_pim.rp_keep_alive -%}
+no ip pim rp keep-alive-timer {{ old_pim.rp_keep_alive }}
+{% endif -%}
+{% for iface in old_pim.ifaces -%}
+interface {{ iface }}
+no ip pim
+!
+{% endfor -%}
+{% for iface in pim.ifaces -%}
+interface {{ iface }}
+ip pim
+{% if pim.ifaces[iface].dr_prio -%}
+ip pim drpriority {{ pim.ifaces[iface].dr_prio }}
+{% endif -%}
+{% if pim.ifaces[iface].hello -%}
+ip pim hello {{ pim.ifaces[iface].hello }}
+{% endif -%}
+!
+{% endfor -%}
+{% for rp_addr in pim.rp -%}
+{% for group in pim.rp[rp_addr] -%}
+ip pim rp {{ rp_addr }} {{ group }}
+{% endfor -%}
+{% endfor -%}
+{% if pim.rp_keep_alive -%}
+ip pim rp keep-alive-timer {{ pim.rp_keep_alive }}
+{% endif -%}
+!
diff --git a/data/templates/pppoe-server/chap-secrets.tmpl b/data/templates/pppoe-server/chap-secrets.tmpl
new file mode 100644
index 000000000..808debccb
--- /dev/null
+++ b/data/templates/pppoe-server/chap-secrets.tmpl
@@ -0,0 +1,11 @@
+# username server password acceptable local IP addresses shaper
+{% for user in authentication['local-users'] %}
+{% if authentication['local-users'][user]['state'] == 'enabled' %}
+{% if (authentication['local-users'][user]['upload']) and (authentication['local-users'][user]['download']) %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}\t\
+{{authentication['local-users'][user]['download']}}/{{authentication['local-users'][user]['upload']}}
+{% else %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}
+{% endif %}
+{% endif %}
+{% endfor %}
diff --git a/data/templates/pppoe-server/pppoe.config.tmpl b/data/templates/pppoe-server/pppoe.config.tmpl
new file mode 100644
index 000000000..d44c0aa93
--- /dev/null
+++ b/data/templates/pppoe-server/pppoe.config.tmpl
@@ -0,0 +1,228 @@
+
+### generated by accel_pppoe.py ###
+[modules]
+log_syslog
+pppoe
+{% if authentication['mode'] == 'radius' %}
+radius
+{% endif %}
+ippool
+{% if ppp_options['ipv6'] != 'deny' %}
+ipv6pool
+ipv6_nd
+ipv6_dhcp
+{% endif %}
+chap-secrets
+auth_pap
+auth_chap_md5
+auth_mschap_v1
+auth_mschap_v2
+#pppd_compat
+shaper
+{% if snmp == 'enable' or snmp == 'enable-ma' %}
+net-snmp
+{% endif %}
+{% if limits %}
+connlimit
+{% endif %}
+
+[core]
+thread-count={{thread_cnt}}
+
+[log]
+syslog=accel-pppoe,daemon
+copy=1
+level=5
+
+{% if snmp == 'enable-ma' %}
+[snmp]
+master=1
+{% endif -%}
+
+[client-ip-range]
+disable
+
+{% if ppp_gw %}
+[ip-pool]
+gw-ip-address={{ppp_gw}}
+{% if client_ip_pool %}
+{{client_ip_pool}}
+{% endif -%}
+
+{% if client_ip_subnets %}
+{% for sn in client_ip_subnets %}
+{{sn}}
+{% endfor %}
+{% endif %}
+{% endif -%}
+
+{% if client_ipv6_pool %}
+[ipv6-pool]
+{% for prfx in client_ipv6_pool['prefix']: %}
+{{prfx}}
+{% endfor %}
+{% for prfx in client_ipv6_pool['delegate-prefix']: %}
+delegate={{prfx}}
+{% endfor %}
+{% endif %}
+
+{% if dns %}
+[dns]
+{% if dns[0] %}
+dns1={{dns[0]}}
+{% endif -%}
+{% if dns[1] %}
+dns2={{dns[1]}}
+{% endif -%}
+{% endif %}
+
+{% if dnsv6 %}
+[ipv6-dns]
+{% for srv in dnsv6: %}
+{{srv}}
+{% endfor %}
+{% endif %}
+
+{% if wins %}
+[wins]
+{% if wins[0] %}
+wins1={{wins[0]}}
+{% endif %}
+{% if wins[1] %}
+wins2={{wins[1]}}
+{% endif -%}
+{% endif -%}
+
+{% if authentication['mode'] == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/pppoe/chap-secrets
+{% endif -%}
+
+{% if authentication['mode'] == 'radius' %}
+[radius]
+{% for rsrv in authentication['radiussrv']: %}
+server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\
+req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\
+fail-time={{authentication['radiussrv'][rsrv]['fail-time']}}
+{% endfor %}
+{% if authentication['radiusopt']['timeout'] %}
+timeout={{authentication['radiusopt']['timeout']}}
+{% endif %}
+{% if authentication['radiusopt']['acct-timeout'] %}
+acct-timeout={{authentication['radiusopt']['acct-timeout']}}
+{% endif %}
+{% if authentication['radiusopt']['max-try'] %}
+max-try={{authentication['radiusopt']['max-try']}}
+{% endif %}
+{% if authentication['radiusopt']['nas-id'] %}
+nas-identifier={{authentication['radiusopt']['nas-id']}}
+{% endif %}
+{% if authentication['radiusopt']['nas-ip'] %}
+nas-ip-address={{authentication['radiusopt']['nas-ip']}}
+{% endif -%}
+{% if authentication['radiusopt']['dae-srv'] %}
+dae-server={{authentication['radiusopt']['dae-srv']['ip-addr']}}:\
+{{authentication['radiusopt']['dae-srv']['port']}},\
+{{authentication['radiusopt']['dae-srv']['secret']}}
+{% endif -%}
+gw-ip-address={{ppp_gw}}
+verbose=1
+
+{% if authentication['radiusopt']['shaper'] %}
+[shaper]
+verbose=1
+attr={{authentication['radiusopt']['shaper']['attr']}}
+{% if authentication['radiusopt']['shaper']['vendor'] %}
+vendor={{authentication['radiusopt']['shaper']['vendor']}}
+{% endif -%}
+{% endif -%}
+{% endif %}
+
+[ppp]
+verbose=1
+check-ip=1
+{% if not sesscrtl == 'disable' %}
+single-session={{sesscrtl}}
+{% endif -%}
+{% if ppp_options['ccp'] %}
+ccp=1
+{% endif %}
+{% if ppp_options['min-mtu'] %}
+min-mtu={{ppp_options['min-mtu']}}
+{% else %}
+min-mtu={{mtu}}
+{% endif %}
+{% if ppp_options['mru'] %}
+mru={{ppp_options['mru']}}
+{% endif %}
+{% if ppp_options['mppe'] %}
+mppe={{ppp_options['mppe']}}
+{% else %}
+mppe=prefer
+{% endif %}
+{% if ppp_options['lcp-echo-interval'] %}
+lcp-echo-interval={{ppp_options['lcp-echo-interval']}}
+{% else %}
+lcp-echo-interval=30
+{% endif %}
+{% if ppp_options['lcp-echo-timeout'] %}
+lcp-echo-timeout={{ppp_options['lcp-echo-timeout']}}
+{% endif %}
+{% if ppp_options['lcp-echo-failure'] %}
+lcp-echo-failure={{ppp_options['lcp-echo-failure']}}
+{% else %}
+lcp-echo-failure=3
+{% endif %}
+{% if ppp_options['ipv4'] %}
+ipv4={{ppp_options['ipv4']}}
+{% endif %}
+{% if client_ipv6_pool %}
+ipv6=allow
+{% endif %}
+
+{% if ppp_options['ipv6'] %}
+ipv6={{ppp_options['ipv6']}}
+{% if ppp_options['ipv6-intf-id'] %}
+ipv6-intf-id={{ppp_options['ipv6-intf-id']}}
+{% endif %}
+{% if ppp_options['ipv6-peer-intf-id'] %}
+ipv6-peer-intf-id={{ppp_options['ipv6-peer-intf-id']}}
+{% endif %}
+{% if ppp_options['ipv6-accept-peer-intf-id'] %}
+ipv6-accept-peer-intf-id={{ppp_options['ipv6-accept-peer-intf-id']}}
+{% endif %}
+{% endif %}
+mtu={{mtu}}
+
+[pppoe]
+verbose=1
+{% if concentrator %}
+ac-name={{concentrator}}
+{% endif %}
+{% if interface %}
+{% for int in interface %}
+interface={{int}}
+{% if interface[int]['vlans'] %}
+vlan-mon={{int}},{{interface[int]['vlans']|join(',')}}
+interface=re:{{int}}\.\d+
+{% endif %}
+{% endfor -%}
+{% endif -%}
+
+{% if svc_name %}
+service-name={{svc_name|join(',')}}
+{% endif -%}
+
+{% if pado_delay %}
+pado-delay={{pado_delay}}
+{% endif %}
+
+{% if limits %}
+[connlimit]
+limit={{limits['conn-limit']}}
+burst={{limits['burst']}}
+timeout={{limits['timeout']}}
+{% endif %}
+
+[cli]
+tcp=127.0.0.1:2001
diff --git a/data/templates/pppoe/ip-down.script.tmpl b/data/templates/pppoe/ip-down.script.tmpl
new file mode 100644
index 000000000..e76875f12
--- /dev/null
+++ b/data/templates/pppoe/ip-down.script.tmpl
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# As PPPoE is an "on demand" interface we need to re-configure it when it
+# becomes up
+if [ "$6" != "{{ intf }}" ]; then
+ exit
+fi
+
+# add some info to syslog
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+logger -t pppd[$DIALER_PID] "executing $0"
+
+# Determine if we are enslaved to a VRF, this is needed to properly insert
+# the default route
+VRF_NAME=""
+if [ -d /sys/class/net/{{ intf }}/upper_* ]; then
+ # Determine upper (VRF) interface
+ VRF=$(basename $(ls -d /sys/class/net/{{ intf }}/upper_*))
+ # Remove upper_ prefix from result string
+ VRF=${VRF#"upper_"}
+ # Populate variable to run in VR context
+ VRF_NAME="vrf ${VRF_NAME}"
+fi
+
+# Always delete default route when interface goes down
+vtysh -c "conf t" ${VRF_NAME} -c "no ip route 0.0.0.0/0 {{ intf }} ${VRF_NAME}"
diff --git a/data/templates/pppoe/ip-pre-up.script.tmpl b/data/templates/pppoe/ip-pre-up.script.tmpl
new file mode 100644
index 000000000..9a7ba7d57
--- /dev/null
+++ b/data/templates/pppoe/ip-pre-up.script.tmpl
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# As PPPoE is an "on demand" interface we need to re-configure it when it
+# becomes up
+
+if [ "$6" != "{{ intf }}" ]; then
+ exit
+fi
+
+# add some info to syslog
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+logger -t pppd[$DIALER_PID] "executing $0"
+
+echo "{{ description }}" > /sys/class/net/{{ intf }}/ifalias
+
+{% if vrf -%}
+logger -t pppd[$DIALER_PID] "configuring dialer interface $6 for VRF {{ vrf }}"
+ip link set dev {{ intf }} master {{ vrf }}
+{% endif %}
diff --git a/data/templates/pppoe/ip-up.script.tmpl b/data/templates/pppoe/ip-up.script.tmpl
new file mode 100644
index 000000000..4cc779914
--- /dev/null
+++ b/data/templates/pppoe/ip-up.script.tmpl
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+# As PPPoE is an "on demand" interface we need to re-configure it when it
+# becomes up
+if [ "$6" != "{{ intf }}" ]; then
+ exit
+fi
+
+set -x
+
+# add some info to syslog
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+logger -t pppd[$DIALER_PID] "executing $0"
+
+SED_OPT="ip route"
+VRF_NAME=""
+if [ -d /sys/class/net/{{ intf }}/upper_* ]; then
+ # Determine upper (VRF) interface
+ VRF=$(basename $(ls -d /sys/class/net/{{ intf }}/upper_*))
+ # Remove upper_ prefix from result string
+ VRF=${VRF#"upper_"}
+ # generate new SED command
+ SED_OPT="vrf ${VRF}"
+ # generate vtysh option
+ VRF_NAME="vrf ${VRF}"
+fi
+
+# Debian PPP version has no support for replacing an existing default route
+# thus we emulate this ba an ip-up script https://phabricator.vyos.net/T2220.
+{% if 'auto' in default_route -%}
+# only insert a new default route if there is no default route configured
+routes=$(vtysh -c "show running-config" | sed -n "/${SED_OPT}/,/!/p" | grep 0.0.0.0/0 | wc -l)
+if [ "$routes" -ne 0 ]; then
+ exit 1
+fi
+
+{% elif 'force' in default_route -%}
+# Retrieve current static default routes and remove it from the routing table
+vtysh -c "show running-config" | sed -n "/${SED_OPT}/,/!/p" | grep 0.0.0.0/0 | while read route ; do
+ vtysh -c "conf t" ${VTY_OPT} -c "no ${route} ${VRF_NAME}"
+done
+{% endif %}
+
+# Add default route to default or VRF routing table
+vtysh -c "conf t" ${VTY_OPT} -c "ip route 0.0.0.0/0 {{ intf }} ${VRF_NAME}"
+logger -t pppd[$DIALER_PID] "added default route via {{ intf }} ${VRF_NAME}"
+
diff --git a/data/templates/pppoe/ipv6-up.script.tmpl b/data/templates/pppoe/ipv6-up.script.tmpl
new file mode 100644
index 000000000..a4b08ddaf
--- /dev/null
+++ b/data/templates/pppoe/ipv6-up.script.tmpl
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# As PPPoE is an "on demand" interface we need to re-configure it when it
+# becomes up
+
+if [ "$6" != "{{ intf }}" ]; then
+ exit
+fi
+
+{% if ipv6_autoconf -%}
+# add some info to syslog
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+logger -t pppd[$DIALER_PID] "executing $0"
+logger -t pppd[$DIALER_PID] "configuring interface {{ intf }} via {{ source_interface }}"
+
+# Configure interface-specific Host/Router behaviour.
+# Note: It is recommended to have the same setting on all interfaces; mixed
+# router/host scenarios are rather uncommon. Possible values are:
+#
+# 0 Forwarding disabled
+# 1 Forwarding enabled
+#
+echo 1 > /proc/sys/net/ipv6/conf/{{ intf }}/forwarding
+
+# Accept Router Advertisements; autoconfigure using them.
+#
+# It also determines whether or not to transmit Router
+# Solicitations. If and only if the functional setting is to
+# accept Router Advertisements, Router Solicitations will be
+# transmitted. Possible values are:
+#
+# 0 Do not accept Router Advertisements.
+# 1 Accept Router Advertisements if forwarding is disabled.
+# 2 Overrule forwarding behaviour. Accept Router Advertisements
+# even if forwarding is enabled.
+#
+echo 2 > /proc/sys/net/ipv6/conf/{{ intf }}/accept_ra
+
+# Autoconfigure addresses using Prefix Information in Router Advertisements.
+echo 1 > /proc/sys/net/ipv6/conf/{{ intf }}/autoconfigure
+{% endif %}
diff --git a/data/templates/pppoe/peer.tmpl b/data/templates/pppoe/peer.tmpl
new file mode 100644
index 000000000..8651f12a5
--- /dev/null
+++ b/data/templates/pppoe/peer.tmpl
@@ -0,0 +1,63 @@
+### Autogenerated by interfaces-pppoe.py ###
+
+{% if description %}
+# {{ description }}
+{% endif %}
+
+# Require peer to provide the local IP address if it is not
+# specified explicitly in the config file.
+noipdefault
+
+# Don't show the password in logfiles:
+hide-password
+
+# Standard Link Control Protocol (LCP) parameters:
+lcp-echo-interval 20
+lcp-echo-failure 3
+
+# RFC 2516, paragraph 7 mandates that the following options MUST NOT be
+# requested and MUST be rejected if requested by the peer:
+# Address-and-Control-Field-Compression (ACFC)
+noaccomp
+
+# Asynchronous-Control-Character-Map (ACCM)
+default-asyncmap
+
+# Override any connect script that may have been set in /etc/ppp/options.
+connect /bin/true
+
+# Don't try to authenticate the remote node
+noauth
+
+# Don't try to proxy ARP for the remote endpoint. User can set proxy
+# arp entries up manually if they wish. More importantly, having
+# the "proxyarp" parameter set disables the "defaultroute" option.
+noproxyarp
+
+# Unlimited connection attempts
+maxfail 0
+
+plugin rp-pppoe.so
+{{ source_interface }}
+persist
+ifname {{ intf }}
+ipparam {{ intf }}
+debug
+logfile {{ logfile }}
+mtu {{ mtu }}
+mru {{ mtu }}
+user "{{ auth_username }}"
+password "{{ auth_password }}"
+{% if name_server -%}
+usepeerdns
+{% endif %}
+{% if ipv6_enable -%}
++ipv6
+ipv6cp-use-ipaddr
+{% endif %}
+{% if service_name -%}
+rp_pppoe_service "{{ service_name }}"
+{% endif %}
+{% if on_demand %}
+demand
+{% endif %}
diff --git a/data/templates/pptp/chap-secrets.tmpl b/data/templates/pptp/chap-secrets.tmpl
new file mode 100644
index 000000000..6bfa2d64e
--- /dev/null
+++ b/data/templates/pptp/chap-secrets.tmpl
@@ -0,0 +1,6 @@
+# username server password acceptable local IP addresses
+{% for user in authentication['local-users'] %}
+{% if authentication['local-users'][user]['state'] == 'enabled' %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}
+{% endif %}
+{% endfor %}
diff --git a/data/templates/pptp/pptp.config.tmpl b/data/templates/pptp/pptp.config.tmpl
new file mode 100644
index 000000000..2596507af
--- /dev/null
+++ b/data/templates/pptp/pptp.config.tmpl
@@ -0,0 +1,87 @@
+
+### generated by accel_pptp.py ###
+[modules]
+log_syslog
+pptp
+ippool
+chap-secrets
+{% if authentication['auth_proto'] %}
+{{ authentication['auth_proto'] }}
+{% else %}
+auth_mschap_v2
+{% endif %}
+{% if authentication['mode'] == 'radius' %}
+radius
+{% endif -%}
+
+[core]
+thread-count={{thread_cnt}}
+
+[log]
+syslog=accel-pptp,daemon
+copy=1
+level=5
+
+{% if dns %}
+[dns]
+{% if dns[0] %}
+dns1={{dns[0]}}
+{% endif %}
+{% if dns[1] %}
+dns2={{dns[1]}}
+{% endif %}
+{% endif %}
+
+{% if wins %}
+[wins]
+{% if wins[0] %}
+wins1={{wins[0]}}
+{% endif %}
+{% if wins[1] %}
+wins2={{wins[1]}}
+{% endif %}
+{% endif %}
+
+[pptp]
+ifname=pptp%d
+{% if outside_addr %}
+bind={{outside_addr}}
+{% endif %}
+verbose=1
+ppp-max-mtu={{mtu}}
+mppe={{authentication['mppe']}}
+echo-interval=10
+echo-failure=3
+
+
+[client-ip-range]
+0.0.0.0/0
+
+[ip-pool]
+tunnel={{client_ip_pool}}
+gw-ip-address={{gw_ip}}
+
+{% if authentication['mode'] == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/pptp/chap-secrets
+{% endif %}
+
+[ppp]
+verbose=5
+check-ip=1
+single-session=replace
+
+{% if authentication['mode'] == 'radius' %}
+[radius]
+{% for rsrv in authentication['radiussrv']: %}
+server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\
+req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\
+fail-time={{authentication['radiussrv'][rsrv]['fail-time']}}
+{% endfor %}
+timeout=30
+acct-timeout=30
+max-try=3
+{%endif %}
+
+[cli]
+tcp=127.0.0.1:2003
diff --git a/data/templates/router-advert/radvd.conf.tmpl b/data/templates/router-advert/radvd.conf.tmpl
new file mode 100644
index 000000000..2768f6f2e
--- /dev/null
+++ b/data/templates/router-advert/radvd.conf.tmpl
@@ -0,0 +1,37 @@
+### Autogenerated by service-router-advert.py ###
+
+{% for i in interfaces -%}
+interface {{ i.name }} {
+ IgnoreIfMissing on;
+ AdvDefaultPreference {{ i.default_preference }};
+ AdvManagedFlag {{ i.managed_flag }};
+ MaxRtrAdvInterval {{ i.interval_max }};
+{% if i.interval_min %}
+ MinRtrAdvInterval {{ i.interval_min }};
+{% endif %}
+ AdvReachableTime {{ i.reachable_time }};
+ AdvIntervalOpt {{ i.send_advert }};
+ AdvSendAdvert {{ i.send_advert }};
+{% if i.default_lifetime %}
+ AdvDefaultLifetime {{ i.default_lifetime }};
+{% endif %}
+{% if i.link_mtu %}
+ AdvLinkMTU {{ i.link_mtu }};
+{% endif %}
+ AdvOtherConfigFlag {{ i.other_config_flag }};
+ AdvRetransTimer {{ i.retrans_timer }};
+ AdvCurHopLimit {{ i.hop_limit }};
+{% for p in i.prefixes %}
+ prefix {{ p.prefix }} {
+ AdvAutonomous {{ p.autonomous_flag }};
+ AdvValidLifetime {{ p.valid_lifetime }};
+ AdvOnLink {{ p.on_link }};
+ AdvPreferredLifetime {{ p.preferred_lifetime }};
+ };
+{% endfor %}
+{% if i.name_server %}
+ RDNSS {{ i.name_server | join(" ") }} {
+ };
+{% endif %}
+};
+{% endfor -%}
diff --git a/data/templates/salt-minion/minion.tmpl b/data/templates/salt-minion/minion.tmpl
new file mode 100644
index 000000000..5e50d588c
--- /dev/null
+++ b/data/templates/salt-minion/minion.tmpl
@@ -0,0 +1,63 @@
+### Autogenerated by salt-minion.py ###
+
+##### Primary configuration settings #####
+##########################################
+
+# The hash_type is the hash to use when discovering the hash of a file on
+# the master server. The default is sha256, but md5, sha1, sha224, sha384 and
+# sha512 are also supported.
+#
+# WARNING: While md5 and sha1 are also supported, do not use them due to the
+# high chance of possible collisions and thus security breach.
+#
+# Prior to changing this value, the master should be stopped and all Salt
+# caches should be cleared.
+hash_type: {{ hash_type }}
+
+##### Logging settings #####
+##########################################
+# The location of the minion log file
+# The minion log can be sent to a regular file, local path name, or network
+# location. Remote logging works best when configured to use rsyslogd(8) (e.g.:
+# ``file:///dev/log``), with rsyslogd(8) configured for network logging. The URI
+# format is: <file|udp|tcp>://<host|socketpath>:<port-if-required>/<log-facility>
+#log_file: /var/log/salt/minion
+#log_file: file:///dev/log
+#log_file: udp://loghost:10514
+#
+log_file: {{ log_file }}
+
+# The level of messages to send to the console.
+# One of 'garbage', 'trace', 'debug', info', 'warning', 'error', 'critical'.
+#
+# The following log levels are considered INSECURE and may log sensitive data:
+# ['garbage', 'trace', 'debug']
+#
+# Default: 'warning'
+log_level: {{ log_level }}
+
+# Set the location of the salt master server, if the master server cannot be
+# resolved, then the minion will fail to start.
+master:
+{% for host in master -%}
+- {{ host }}
+{% endfor %}
+
+# The user to run salt
+user: {{ user }}
+
+# The directory to store the pki information in
+pki_dir: /config/salt/pki/minion
+
+# Explicitly declare the id for this minion to use, if left commented the id
+# will be the hostname as returned by the python call: socket.getfqdn()
+# Since salt uses detached ids it is possible to run multiple minions on the
+# same machine but with different ids, this can be useful for salt compute
+# clusters.
+id: {{ salt_id }}
+
+
+# The number of minutes between mine updates.
+mine_interval: {{ mine_interval }}
+
+verify_master_pubkey_sign: {{ verify_master_pubkey_sign }}
diff --git a/data/templates/snmp/etc.snmp.conf.tmpl b/data/templates/snmp/etc.snmp.conf.tmpl
new file mode 100644
index 000000000..159578906
--- /dev/null
+++ b/data/templates/snmp/etc.snmp.conf.tmpl
@@ -0,0 +1,4 @@
+### Autogenerated by snmp.py ###
+{% if trap_source -%}
+clientaddr {{ trap_source }}
+{% endif %}
diff --git a/data/templates/snmp/etc.snmpd.conf.tmpl b/data/templates/snmp/etc.snmpd.conf.tmpl
new file mode 100644
index 000000000..1659abf93
--- /dev/null
+++ b/data/templates/snmp/etc.snmpd.conf.tmpl
@@ -0,0 +1,118 @@
+### Autogenerated by snmp.py ###
+
+# non configurable defaults
+sysObjectID 1.3.6.1.4.1.44641
+sysServices 14
+master agentx
+agentXPerms 0777 0777
+pass .1.3.6.1.2.1.31.1.1.1.18 /opt/vyatta/sbin/if-mib-alias
+smuxpeer .1.3.6.1.2.1.83
+smuxpeer .1.3.6.1.2.1.157
+smuxsocket localhost
+
+# linkUp/Down configure the Event MIB tables to monitor
+# the ifTable for network interfaces being taken up or down
+# for making internal queries to retrieve any necessary information
+iquerySecName {{ vyos_user }}
+
+# Modified from the default linkUpDownNotification
+# to include more OIDs and poll more frequently
+notificationEvent linkUpTrap linkUp ifIndex ifDescr ifType ifAdminStatus ifOperStatus
+notificationEvent linkDownTrap linkDown ifIndex ifDescr ifType ifAdminStatus ifOperStatus
+monitor -r 10 -e linkUpTrap "Generate linkUp" ifOperStatus != 2
+monitor -r 10 -e linkDownTrap "Generate linkDown" ifOperStatus == 2
+
+########################
+# configurable section #
+########################
+
+# Default system description is VyOS version
+sysDescr VyOS {{ version }}
+
+{% if description %}
+# Description
+SysDescr {{ description }}
+{%- endif %}
+
+# Listen
+agentaddress unix:/run/snmpd.socket{% if listen_on %}{% for li in listen_on %},{{ li }}{% endfor %}{% else %},udp:161{% if ipv6_enabled %},udp6:161{% endif %}{% endif %}
+
+# SNMP communities
+{%- for c in communities %}
+
+{%- if c.network_v4 %}
+{%- for network in c.network_v4 %}
+{{ c.authorization }}community {{ c.name }} {{ network }}
+{%- endfor %}
+{%- elif not c.has_source %}
+{{ c.authorization }}community {{ c.name }}
+{%- endif %}
+
+{%- if c.network_v6 %}
+{%- for network in c.network_v6 %}
+{{ c.authorization }}community6 {{ c.name }} {{ network }}
+{%- endfor %}
+{%- elif not c.has_source %}
+{{ c.authorization }}community6 {{ c.name }}
+{%- endif %}
+
+{%- endfor %}
+
+{% if contact %}
+# system contact information
+SysContact {{ contact }}
+{%- endif %}
+
+{% if location %}
+# system location information
+SysLocation {{ location }}
+{%- endif %}
+
+{% if smux_peers -%}
+# additional smux peers
+{%- for sp in smux_peers %}
+smuxpeer {{ sp }}
+{%- endfor %}
+{%- endif %}
+
+{% if trap_targets -%}
+# if there is a problem - tell someone!
+{%- for t in trap_targets %}
+trap2sink {{ t.target }}{% if t.port -%}:{{ t.port }}{% endif %} {{ t.community }}
+{%- endfor %}
+{%- endif %}
+
+{%- if v3_enabled %}
+#
+# SNMPv3 stuff goes here
+#
+# views
+{%- for v in v3_views %}
+{%- for oid in v.oids %}
+view {{ v.name }} included .{{ oid.oid }}
+{%- endfor %}
+{%- endfor %}
+
+# access
+# context sec.model sec.level match read write notif
+{%- for g in v3_groups %}
+access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} {% if g.mode == 'ro' %}none{% else %}{{ g.view }}{% endif %} none
+{%- endfor %}
+
+# trap-target
+{%- for t in v3_traps %}
+trapsess -v 3 {{ '-Ci' if t.type == 'inform' }} -e {{ v3_engineid }} -u {{ t.secName }} -l {{ t.secLevel }} -a {{ t.authProtocol }} {% if t.authPassword %}-A {{ t.authPassword }}{% elif t.authMasterKey %}-3m {{ t.authMasterKey }}{% endif %} -x {{ t.privProtocol }} {% if t.privPassword %}-X {{ t.privPassword }}{% elif t.privMasterKey %}-3M {{ t.privMasterKey }}{% endif %} {{ t.ipProto }}:{{ t.ipAddr }}:{{ t.ipPort }}
+{%- endfor %}
+
+# group
+{%- for u in v3_users %}
+group {{ u.group }} usm {{ u.name }}
+{% endfor %}
+{%- endif %}
+
+{% if script_ext %}
+# extension scripts
+{%- for ext in script_ext|sort(attribute='name') %}
+extend {{ ext.name }} {{ ext.script }}
+{%- endfor %}
+{% endif %}
diff --git a/data/templates/snmp/usr.snmpd.conf.tmpl b/data/templates/snmp/usr.snmpd.conf.tmpl
new file mode 100644
index 000000000..9c0337fa8
--- /dev/null
+++ b/data/templates/snmp/usr.snmpd.conf.tmpl
@@ -0,0 +1,6 @@
+### Autogenerated by snmp.py ###
+{%- for u in v3_users %}
+{{ u.mode }}user {{ u.name }}
+{%- endfor %}
+
+rwuser {{ vyos_user }}
diff --git a/data/templates/snmp/var.snmpd.conf.tmpl b/data/templates/snmp/var.snmpd.conf.tmpl
new file mode 100644
index 000000000..0b8e9f291
--- /dev/null
+++ b/data/templates/snmp/var.snmpd.conf.tmpl
@@ -0,0 +1,16 @@
+### Autogenerated by snmp.py ###
+# user
+{%- for u in v3_users %}
+{%- if u.authOID == 'none' %}
+createUser {{ u.name }}
+{%- elif u.authPassword %}
+createUser {{ u.name }} {{ u.authProtocol | upper }} "{{ u.authPassword }}" {{ u.privProtocol | upper }} {{ u.privPassword }}
+{%- else %}
+usmUser 1 3 {{ v3_engineid }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} {{ u.authMasterKey }} {{ u.privOID }} {{ u.privMasterKey }} 0x
+{%- endif %}
+{%- endfor %}
+
+createUser {{ vyos_user }} MD5 "{{ vyos_user_pass }}" DES
+{%- if v3_engineid %}
+oldEngineID {{ v3_engineid }}
+{%- endif %}
diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl
new file mode 100644
index 000000000..5deb5232a
--- /dev/null
+++ b/data/templates/ssh/sshd_config.tmpl
@@ -0,0 +1,125 @@
+### Autogenerated by ssh.py ###
+
+# Non-configurable defaults
+Protocol 2
+HostKey /etc/ssh/ssh_host_rsa_key
+HostKey /etc/ssh/ssh_host_dsa_key
+HostKey /etc/ssh/ssh_host_ecdsa_key
+HostKey /etc/ssh/ssh_host_ed25519_key
+SyslogFacility AUTH
+LoginGraceTime 120
+StrictModes yes
+PubkeyAuthentication yes
+IgnoreRhosts yes
+HostbasedAuthentication no
+PermitEmptyPasswords no
+ChallengeResponseAuthentication no
+X11Forwarding yes
+X11DisplayOffset 10
+PrintMotd no
+PrintLastLog yes
+TCPKeepAlive yes
+Banner /etc/issue.net
+Subsystem sftp /usr/lib/openssh/sftp-server
+UsePAM yes
+HostKey /etc/ssh/ssh_host_rsa_key
+
+# Specifies whether sshd should look up the remote host name,
+# and to check that the resolved host name for the remote IP
+# address maps back to the very same IP address.
+UseDNS {{ host_validation }}
+
+# Specifies the port number that sshd listens on. The default is 22.
+# Multiple options of this type are permitted.
+{% if mport|length != 0 %}
+{% for p in mport %}
+Port {{ p }}
+{% endfor %}
+{% else %}
+Port {{ port }}
+{% endif %}
+
+# Gives the verbosity level that is used when logging messages from sshd
+LogLevel {{ log_level }}
+
+# Specifies whether root can log in using ssh
+PermitRootLogin no
+
+# Specifies whether password authentication is allowed
+PasswordAuthentication {{ password_authentication }}
+
+{% if listen_on %}
+# Specifies the local addresses sshd should listen on
+{% for a in listen_on %}
+ListenAddress {{ a }}
+{% endfor %}
+{{ "\n" }}
+{% endif %}
+
+{%- if ciphers %}
+# Specifies the ciphers allowed. Multiple ciphers must be comma-separated.
+#
+# NOTE: As of now, there is no 'multi' node for 'ciphers', thus we have only one :/
+Ciphers {{ ciphers | join(",") }}
+{{ "\n" }}
+{% endif %}
+
+{%- if mac %}
+# Specifies the available MAC (message authentication code) algorithms. The MAC
+# algorithm is used for data integrity protection. Multiple algorithms must be
+# comma-separated.
+#
+# NOTE: As of now, there is no 'multi' node for 'mac', thus we have only one :/
+MACs {{ mac | join(",") }}
+{{ "\n" }}
+{% endif %}
+
+{%- if key_exchange %}
+# Specifies the available KEX (Key Exchange) algorithms. Multiple algorithms must
+# be comma-separated.
+#
+# NOTE: As of now, there is no 'multi' node for 'key-exchange', thus we have only one :/
+KexAlgorithms {{ key_exchange | join(",") }}
+{{ "\n" }}
+{% endif %}
+
+{%- if allow_users %}
+# This keyword can be followed by a list of user name patterns, separated by spaces.
+# If specified, login is allowed only for user names that match one of the patterns.
+# Only user names are valid, a numerical user ID is not recognized.
+AllowUsers {{ allow_users | join(" ") }}
+{{ "\n" }}
+{% endif %}
+
+{%- if allow_groups %}
+# This keyword can be followed by a list of group name patterns, separated by spaces.
+# If specified, login is allowed only for users whose primary group or supplementary
+# group list matches one of the patterns. Only group names are valid, a numerical group
+# ID is not recognized.
+AllowGroups {{ allow_groups | join(" ") }}
+{{ "\n" }}
+{% endif %}
+
+{%- if deny_users %}
+# This keyword can be followed by a list of user name patterns, separated by spaces.
+# Login is disallowed for user names that match one of the patterns. Only user names
+# are valid, a numerical user ID is not recognized.
+DenyUsers {{ deny_users | join(" ") }}
+{{ "\n" }}
+{% endif %}
+
+{%- if deny_groups %}
+# This keyword can be followed by a list of group name patterns, separated by spaces.
+# Login is disallowed for users whose primary group or supplementary group list matches
+# one of the patterns. Only group names are valid, a numerical group ID is not recognized.
+DenyGroups {{ deny_groups | join(" ") }}
+{{ "\n" }}
+{% endif %}
+
+{%- if client_keepalive %}
+# Sets a timeout interval in seconds after which if no data has been received from the client,
+# sshd will send a message through the encrypted channel to request a response from the client.
+# The default is 0, indicating that these messages will not be sent to the client.
+# This option applies to protocol version 2 only.
+ClientAliveInterval {{ client_keepalive }}
+{% endif %}
diff --git a/data/templates/sstp/chap-secrets.tmpl b/data/templates/sstp/chap-secrets.tmpl
new file mode 100644
index 000000000..dd00d7bd0
--- /dev/null
+++ b/data/templates/sstp/chap-secrets.tmpl
@@ -0,0 +1,10 @@
+# username server password acceptable local IP addresses shaper
+{% for user in local_users %}
+{% if user.state == 'enabled' %}
+{% if user.upload and user.download %}
+{{ "%-12s" | format(user.name) }} * {{ "%-16s" | format(user.password) }} {{ "%-16s" | format(user.ip) }} {{ user.download }} / {{ user.upload }}
+{% else %}
+{{ "%-12s" | format(user.name) }} * {{ "%-16s" | format(user.password) }} {{ "%-16s" | format(user.ip) }}
+{% endif %}
+{% endif %}
+{% endfor %}
diff --git a/data/templates/sstp/sstp.config.tmpl b/data/templates/sstp/sstp.config.tmpl
new file mode 100644
index 000000000..19805358e
--- /dev/null
+++ b/data/templates/sstp/sstp.config.tmpl
@@ -0,0 +1,114 @@
+### generated by vpn_sstp.py ###
+[modules]
+log_syslog
+sstp
+shaper
+{% if auth_mode == 'local' %}
+chap-secrets
+{% elif auth_mode == 'radius' %}
+radius
+{% endif -%}
+ippool
+
+{% for proto in auth_proto %}
+{{proto}}
+{% endfor %}
+
+[core]
+thread-count={{thread_cnt}}
+
+[common]
+single-session=replace
+
+[log]
+syslog=accel-sstp,daemon
+copy=1
+level=5
+
+[client-ip-range]
+disable
+
+[sstp]
+verbose=1
+accept=ssl
+ssl-ca-file={{ ssl_ca }}
+ssl-pemfile={{ ssl_cert }}
+ssl-keyfile={{ ssl_key }}
+
+{% if client_ip_pool %}
+[ip-pool]
+gw-ip-address={{ client_gateway }}
+{% for subnet in client_ip_pool %}
+{{ subnet }}
+{% endfor %}
+{% endif %}
+
+{% if dnsv4 %}
+[dns]
+{% for dns in dnsv4 -%}
+dns{{ loop.index }}={{ dns }}
+{% endfor -%}
+{% endif %}
+
+{% if auth_mode == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/sstp/chap-secrets
+{% elif auth_mode == 'radius' %}
+[radius]
+verbose=1
+{% for r in radius_server %}
+server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }}
+{% endfor -%}
+
+acct-timeout={{ radius_acct_tmo }}
+timeout={{ radius_timeout }}
+max-try={{ radius_max_try }}
+
+{% if radius_nas_id %}
+nas-identifier={{ radius_nas_id }}
+{% endif -%}
+{% if radius_nas_ip %}
+nas-ip-address={{ radius_nas_ip }}
+{% endif -%}
+{% if radius_source_address %}
+bind={{ radius_source_address }}
+{% endif -%}
+
+
+{% if radius_dynamic_author %}
+dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }}
+{% endif -%}
+{% endif %}
+
+[ppp]
+verbose=1
+check-ip=1
+{% if mtu %}
+mtu={{ mtu }}
+{% endif -%}
+
+{% if ppp_mppe %}
+mppe={{ ppp_mppe }}
+{% endif -%}
+{% if ppp_echo_interval %}
+lcp-echo-interval={{ ppp_echo_interval }}
+{% endif -%}
+{% if ppp_echo_failure %}
+lcp-echo-failure={{ ppp_echo_failure }}
+{% endif -%}
+{% if ppp_echo_timeout %}
+lcp-echo-timeout={{ ppp_echo_timeout }}
+{% endif %}
+
+{% if radius_shaper_attr %}
+[shaper]
+verbose=1
+attr={{ radius_shaper_attr }}
+{% if radius_shaper_vendor %}
+vendor={{ radius_shaper_vendor }}
+{% endif -%}
+{% endif %}
+
+[cli]
+tcp=127.0.0.1:2005
+
diff --git a/data/templates/syslog/logrotate.tmpl b/data/templates/syslog/logrotate.tmpl
new file mode 100644
index 000000000..f758265e4
--- /dev/null
+++ b/data/templates/syslog/logrotate.tmpl
@@ -0,0 +1,12 @@
+{% for file in files %}
+{{files[file]['log-file']}} {
+ missingok
+ notifempty
+ create
+ rotate {{files[file]['max-files']}}
+ size={{files[file]['max-size']//1024}}k
+ postrotate
+ invoke-rc.d rsyslog rotate > /dev/null
+ endscript
+}
+{% endfor %}
diff --git a/data/templates/syslog/rsyslog.conf.tmpl b/data/templates/syslog/rsyslog.conf.tmpl
new file mode 100644
index 000000000..bc3f7667b
--- /dev/null
+++ b/data/templates/syslog/rsyslog.conf.tmpl
@@ -0,0 +1,44 @@
+## generated by syslog.py ##
+## file based logging
+{% if files['global']['marker'] -%}
+$ModLoad immark
+{% if files['global']['marker-interval'] %}
+$MarkMessagePeriod {{files['global']['marker-interval']}}
+{% endif %}
+{% endif -%}
+{% if files['global']['preserver_fqdn'] -%}
+$PreserveFQDN on
+{% endif -%}
+{% for file in files %}
+$outchannel {{file}},{{files[file]['log-file']}},{{files[file]['max-size']}},{{files[file]['action-on-max-size']}}
+{{files[file]['selectors']}} :omfile:${{file}}
+{% endfor %}
+{% if console %}
+## console logging
+{% for con in console %}
+{{console[con]['selectors']}} /dev/console
+{% endfor %}
+{% endif %}
+{% if hosts %}
+## remote logging
+{% for host in hosts %}
+{% if hosts[host]['proto'] == 'tcp' %}
+{% if hosts[host]['port'] %}
+{{hosts[host]['selectors']}} @@{{host}}:{{hosts[host]['port']}}
+{% else %}
+{{hosts[host]['selectors']}} @@{{host}}
+{% endif %}
+{% else %}
+{% if hosts[host]['port'] %}
+{{hosts[host]['selectors']}} @{{host}}:{{hosts[host]['port']}}
+{% else %}
+{{hosts[host]['selectors']}} @{{host}}
+{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if user %}
+{% for u in user %}
+{{user[u]['selectors']}} :omusrmsg:{{u}}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/system-login/pam_radius_auth.conf.tmpl b/data/templates/system-login/pam_radius_auth.conf.tmpl
new file mode 100644
index 000000000..6cff67867
--- /dev/null
+++ b/data/templates/system-login/pam_radius_auth.conf.tmpl
@@ -0,0 +1,13 @@
+# Automatically generated by VyOS
+# RADIUS configuration file
+{%- if radius_server %}
+# server[:port] shared_secret timeout (s) source_ip
+{% for s in radius_server %}
+{%- if not s.disabled -%}
+{{ s.address }}:{{ s.port }} {{ s.key }} {{ s.timeout }} {% if radius_source_address -%}{{ radius_source_address }}{% endif %}
+{% endif %}
+{%- endfor %}
+
+priv-lvl 15
+mapped_priv_user radius_priv_user
+{% endif %}
diff --git a/data/templates/tftp-server/default.tmpl b/data/templates/tftp-server/default.tmpl
new file mode 100644
index 000000000..18fee35d1
--- /dev/null
+++ b/data/templates/tftp-server/default.tmpl
@@ -0,0 +1,2 @@
+### Autogenerated by tftp_server.py ###
+DAEMON_ARGS="--listen --user tftp --address {% for a in listen-%}{{ a }}{% endfor %}{% if allow_upload %} --create --umask 000{% endif %} --secure {{ directory }}"
diff --git a/data/templates/vrf/vrf.conf.tmpl b/data/templates/vrf/vrf.conf.tmpl
new file mode 100644
index 000000000..761b0bb6f
--- /dev/null
+++ b/data/templates/vrf/vrf.conf.tmpl
@@ -0,0 +1,8 @@
+### Autogenerated by vrf.py ###
+#
+# Routing table ID to name mapping reference
+
+# id vrf name comment
+{% for vrf in vrf_add -%}
+{{ "%-10s" | format(vrf.table) }} {{ "%-16s" | format(vrf.name) }} # {{ vrf.description }}
+{% endfor -%}
diff --git a/data/templates/vrrp/daemon.tmpl b/data/templates/vrrp/daemon.tmpl
new file mode 100644
index 000000000..62d0ba63b
--- /dev/null
+++ b/data/templates/vrrp/daemon.tmpl
@@ -0,0 +1,5 @@
+# Autogenerated by VyOS
+# Options to pass to keepalived
+
+# DAEMON_ARGS are appended to the keepalived command-line
+DAEMON_ARGS="--snmp"
diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl
new file mode 100644
index 000000000..08b821f70
--- /dev/null
+++ b/data/templates/vrrp/keepalived.conf.tmpl
@@ -0,0 +1,97 @@
+# Autogenerated by VyOS
+# Do not edit this file, all your changes will be lost
+# on next commit or reboot
+
+global_defs {
+ dynamic_interfaces
+ script_user root
+ notify_fifo /run/keepalived_notify_fifo
+ notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py
+}
+
+{% for group in groups -%}
+
+{% if group.health_check_script -%}
+vrrp_script healthcheck_{{ group.name }} {
+ script "{{ group.health_check_script }}"
+ interval {{ group.health_check_interval }}
+ fall {{ group.health_check_count }}
+ rise 1
+
+}
+{% endif %}
+
+vrrp_instance {{ group.name }} {
+ {% if group.description -%}
+ # {{ group.description }}
+ {% endif -%}
+
+ state BACKUP
+ interface {{ group.interface }}
+ virtual_router_id {{ group.vrid }}
+ priority {{ group.priority }}
+ advert_int {{ group.advertise_interval }}
+
+ {% if group.preempt -%}
+ preempt_delay {{ group.preempt_delay }}
+ {% else -%}
+ nopreempt
+ {% endif -%}
+
+ {% if group.peer_address -%}
+ unicast_peer { {{ group.peer_address }} }
+ {% endif -%}
+
+ {% if group.hello_source -%}
+ {%- if group.peer_address -%}
+ unicast_src_ip {{ group.hello_source }}
+ {%- else -%}
+ mcast_src_ip {{ group.hello_source }}
+ {%- endif %}
+ {% endif -%}
+
+ {% if group.use_vmac and group.peer_address -%}
+ use_vmac {{group.interface}}v{{group.vrid}}
+ vmac_xmit_base
+ {% elif group.use_vmac -%}
+ use_vmac {{group.interface}}v{{group.vrid}}
+ {% endif -%}
+
+ {% if group.auth_password -%}
+ authentication {
+ auth_pass "{{ group.auth_password }}"
+ auth_type {{ group.auth_type }}
+ }
+ {% endif -%}
+
+ virtual_ipaddress {
+ {% for addr in group.virtual_addresses -%}
+ {{ addr }}
+ {% endfor -%}
+ }
+
+ {% if group.health_check_script -%}
+ track_script {
+ healthcheck_{{ group.name }}
+ }
+ {% endif -%}
+}
+
+{% endfor -%}
+
+{% for sync_group in sync_groups -%}
+vrrp_sync_group {{ sync_group.name }} {
+ group {
+ {% for member in sync_group.members -%}
+ {{ member }}
+ {% endfor -%}
+ }
+
+ {% if sync_group.conntrack_sync -%}
+ notify_master "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh master {{ sync_group.name }}"
+ notify_backup "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh backup {{ sync_group.name }}"
+ notify_fault "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh fault {{ sync_group.name }}"
+ {% endif -%}
+}
+
+{% endfor -%}
diff --git a/data/templates/wifi/cfg80211.conf.tmpl b/data/templates/wifi/cfg80211.conf.tmpl
new file mode 100644
index 000000000..b21bacc1e
--- /dev/null
+++ b/data/templates/wifi/cfg80211.conf.tmpl
@@ -0,0 +1,3 @@
+{%- if regdom -%}
+options cfg80211 ieee80211_regdom={{ regdom }}
+{% endif %}
diff --git a/data/templates/wifi/crda.tmpl b/data/templates/wifi/crda.tmpl
new file mode 100644
index 000000000..750ad86ee
--- /dev/null
+++ b/data/templates/wifi/crda.tmpl
@@ -0,0 +1,3 @@
+{%- if regdom -%}
+REGDOMAIN={{ regdom }}
+{% endif %}
diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl
new file mode 100644
index 000000000..031fb6c90
--- /dev/null
+++ b/data/templates/wifi/hostapd.conf.tmpl
@@ -0,0 +1,728 @@
+### Autogenerated by interfaces-wireless.py ###
+{% if description %}
+# Description: {{ description }}
+# User-friendly description of device; up to 32 octets encoded in UTF-8
+device_name={{ description | truncate(32, True) }}
+{% endif %}
+
+# AP netdevice name (without 'ap' postfix, i.e., wlan0 uses wlan0ap for
+# management frames with the Host AP driver); wlan0 with many nl80211 drivers
+# Note: This attribute can be overridden by the values supplied with the '-i'
+# command line parameter.
+interface={{ intf }}
+
+# Driver interface type (hostap/wired/none/nl80211/bsd);
+# default: hostap). nl80211 is used with all Linux mac80211 drivers.
+# Use driver=none if building hostapd as a standalone RADIUS server that does
+# not control any wireless/wired driver.
+driver=nl80211
+
+# Levels (minimum value for logged events):
+# 0 = verbose debugging
+# 1 = debugging
+# 2 = informational messages
+# 3 = notification
+# 4 = warning
+logger_syslog=-1
+logger_syslog_level=0
+logger_stdout=-1
+logger_stdout_level=0
+
+{%- if country_code %}
+
+# Country code (ISO/IEC 3166-1). Used to set regulatory domain.
+# Set as needed to indicate country in which device is operating.
+# This can limit available channels and transmit power.
+country_code={{ country_code }}
+
+# Enable IEEE 802.11d. This advertises the country_code and the set of allowed
+# channels and transmit power levels based on the regulatory limits. The
+# country_code setting must be configured with the correct country for
+# IEEE 802.11d functions.
+ieee80211d=1
+{% endif %}
+
+{%- if ssid %}
+
+# SSID to be used in IEEE 802.11 management frames
+ssid={{ ssid }}
+{% endif %}
+
+{%- if channel %}
+
+# Channel number (IEEE 802.11)
+# (default: 0, i.e., not set)
+# Please note that some drivers do not use this value from hostapd and the
+# channel will need to be configured separately with iwconfig.
+#
+# If CONFIG_ACS build option is enabled, the channel can be selected
+# automatically at run time by setting channel=acs_survey or channel=0, both of
+# which will enable the ACS survey based algorithm.
+channel={{ channel }}
+{% endif %}
+
+{%- if mode %}
+
+# Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz),
+# g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used
+# with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this
+# needs to be set to hw_mode=a. For IEEE 802.11ax (HE) on 6 GHz this needs
+# to be set to hw_mode=a. When using ACS (see channel parameter), a
+# special value "any" can be used to indicate that any support band can be used.
+# This special case is currently supported only with drivers with which
+# offloaded ACS is used.
+{% if 'n' in mode -%}
+hw_mode=g
+ieee80211n=1
+{% elif 'ac' in mode -%}
+hw_mode=a
+ieee80211h=1
+ieee80211ac=1
+{% else -%}
+hw_mode={{ mode }}
+{% endif %}
+{% endif %}
+
+# ieee80211w: Whether management frame protection (MFP) is enabled
+# 0 = disabled (default)
+# 1 = optional
+# 2 = required
+{% if 'disabled' in mgmt_frame_protection -%}
+ieee80211w=0
+{% elif 'optional' in mgmt_frame_protection -%}
+ieee80211w=1
+{% elif 'required' in mgmt_frame_protection -%}
+ieee80211w=2
+{% endif %}
+
+# ht_capab: HT capabilities (list of flags)
+# LDPC coding capability: [LDPC] = supported
+# Supported channel width set: [HT40-] = both 20 MHz and 40 MHz with secondary
+# channel below the primary channel; [HT40+] = both 20 MHz and 40 MHz
+# with secondary channel above the primary channel
+# (20 MHz only if neither is set)
+# Note: There are limits on which channels can be used with HT40- and
+# HT40+. Following table shows the channels that may be available for
+# HT40- and HT40+ use per IEEE 802.11n Annex J:
+# freq HT40- HT40+
+# 2.4 GHz 5-13 1-7 (1-9 in Europe/Japan)
+# 5 GHz 40,48,56,64 36,44,52,60
+# (depending on the location, not all of these channels may be available
+# for use)
+# Please note that 40 MHz channels may switch their primary and secondary
+# channels if needed or creation of 40 MHz channel maybe rejected based
+# on overlapping BSSes. These changes are done automatically when hostapd
+# is setting up the 40 MHz channel.
+# Spatial Multiplexing (SM) Power Save: [SMPS-STATIC] or [SMPS-DYNAMIC]
+# (SMPS disabled if neither is set)
+# HT-greenfield: [GF] (disabled if not set)
+# Short GI for 20 MHz: [SHORT-GI-20] (disabled if not set)
+# Short GI for 40 MHz: [SHORT-GI-40] (disabled if not set)
+# Tx STBC: [TX-STBC] (disabled if not set)
+# Rx STBC: [RX-STBC1] (one spatial stream), [RX-STBC12] (one or two spatial
+# streams), or [RX-STBC123] (one, two, or three spatial streams); Rx STBC
+# disabled if none of these set
+# HT-delayed Block Ack: [DELAYED-BA] (disabled if not set)
+# Maximum A-MSDU length: [MAX-AMSDU-7935] for 7935 octets (3839 octets if not
+# set)
+# DSSS/CCK Mode in 40 MHz: [DSSS_CCK-40] = allowed (not allowed if not set)
+# 40 MHz intolerant [40-INTOLERANT] (not advertised if not set)
+# L-SIG TXOP protection support: [LSIG-TXOP-PROT] (disabled if not set)
+{% if cap_ht %}
+ht_capab=
+{%- endif -%}
+
+{%- if cap_ht_40mhz_incapable -%}
+[40-INTOLERANT]
+{%- endif -%}
+
+{%- if cap_ht_delayed_block_ack -%}
+[DELAYED-BA]
+{%- endif -%}
+
+{%- if cap_ht_dsss_cck_40 -%}
+[DSSS_CCK-40]
+{%- endif -%}
+
+{%- if cap_ht_greenfield -%}
+[GF]
+{%- endif -%}
+
+{%- if cap_ht_ldpc -%}
+[LDPC]
+{%- endif -%}
+
+{%- if cap_ht_lsig_protection -%}
+[LSIG-TXOP-PROT]
+{%- endif -%}
+
+{%- if cap_ht_max_amsdu -%}
+[MAX-AMSDU-{{ cap_ht_max_amsdu }}]
+{%- endif -%}
+
+{%- if cap_ht_smps -%}
+[SMPS-{{ cap_ht_smps | upper }}]
+{%- endif -%}
+
+{%- if cap_ht_chan_set_width -%}
+{%- for csw in cap_ht_chan_set_width -%}
+[{{ csw | upper }}]
+{%- endfor -%}
+{%- endif -%}
+
+{%- if cap_ht_short_gi -%}
+{%- for gi in cap_ht_short_gi -%}
+[SHORT-GI-{{ gi }}]
+{%- endfor -%}
+{%- endif -%}
+
+{%- if cap_ht_stbc_tx -%}
+[TX-STBC]
+{%- endif -%}
+{%- if cap_ht_stbc_rx -%}
+[RX-STBC{{ cap_ht_stbc_rx }}]
+{%- endif %}
+
+# Required for full HT and VHT functionality
+wme_enabled=1
+
+{% if cap_ht_powersave -%}
+# WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD]
+# Enable this flag if U-APSD supported outside hostapd (eg., Firmware/driver)
+uapsd_advertisement_enabled=1
+{%- endif %}
+
+{% if cap_req_ht -%}
+# Require stations to support HT PHY (reject association if they do not)
+require_ht=1
+{% endif %}
+
+{%- if cap_vht_chan_set_width -%}
+vht_oper_chwidth={{ cap_vht_chan_set_width }}
+{%- endif %}
+
+# vht_capab: VHT capabilities (list of flags)
+#
+# vht_max_mpdu_len: [MAX-MPDU-7991] [MAX-MPDU-11454]
+# Indicates maximum MPDU length
+# 0 = 3895 octets (default)
+# 1 = 7991 octets
+# 2 = 11454 octets
+# 3 = reserved
+#
+# supported_chan_width: [VHT160] [VHT160-80PLUS80]
+# Indicates supported Channel widths
+# 0 = 160 MHz & 80+80 channel widths are not supported (default)
+# 1 = 160 MHz channel width is supported
+# 2 = 160 MHz & 80+80 channel widths are supported
+# 3 = reserved
+#
+# Rx LDPC coding capability: [RXLDPC]
+# Indicates support for receiving LDPC coded pkts
+# 0 = Not supported (default)
+# 1 = Supported
+#
+# Short GI for 80 MHz: [SHORT-GI-80]
+# Indicates short GI support for reception of packets transmitted with TXVECTOR
+# params format equal to VHT and CBW = 80Mhz
+# 0 = Not supported (default)
+# 1 = Supported
+#
+# Short GI for 160 MHz: [SHORT-GI-160]
+# Indicates short GI support for reception of packets transmitted with TXVECTOR
+# params format equal to VHT and CBW = 160Mhz
+# 0 = Not supported (default)
+# 1 = Supported
+#
+# Tx STBC: [TX-STBC-2BY1]
+# Indicates support for the transmission of at least 2x1 STBC
+# 0 = Not supported (default)
+# 1 = Supported
+#
+# Rx STBC: [RX-STBC-1] [RX-STBC-12] [RX-STBC-123] [RX-STBC-1234]
+# Indicates support for the reception of PPDUs using STBC
+# 0 = Not supported (default)
+# 1 = support of one spatial stream
+# 2 = support of one and two spatial streams
+# 3 = support of one, two and three spatial streams
+# 4 = support of one, two, three and four spatial streams
+# 5,6,7 = reserved
+#
+# SU Beamformer Capable: [SU-BEAMFORMER]
+# Indicates support for operation as a single user beamformer
+# 0 = Not supported (default)
+# 1 = Supported
+#
+# SU Beamformee Capable: [SU-BEAMFORMEE]
+# Indicates support for operation as a single user beamformee
+# 0 = Not supported (default)
+# 1 = Supported
+#
+# Compressed Steering Number of Beamformer Antennas Supported:
+# [BF-ANTENNA-2] [BF-ANTENNA-3] [BF-ANTENNA-4]
+# Beamformee's capability indicating the maximum number of beamformer
+# antennas the beamformee can support when sending compressed beamforming
+# feedback
+# If SU beamformer capable, set to maximum value minus 1
+# else reserved (default)
+#
+# Number of Sounding Dimensions:
+# [SOUNDING-DIMENSION-2] [SOUNDING-DIMENSION-3] [SOUNDING-DIMENSION-4]
+# Beamformer's capability indicating the maximum value of the NUM_STS parameter
+# in the TXVECTOR of a VHT NDP
+# If SU beamformer capable, set to maximum value minus 1
+# else reserved (default)
+#
+# MU Beamformer Capable: [MU-BEAMFORMER]
+# Indicates support for operation as an MU beamformer
+# 0 = Not supported or sent by Non-AP STA (default)
+# 1 = Supported
+#
+# VHT TXOP PS: [VHT-TXOP-PS]
+# Indicates whether or not the AP supports VHT TXOP Power Save Mode
+# or whether or not the STA is in VHT TXOP Power Save mode
+# 0 = VHT AP doesn't support VHT TXOP PS mode (OR) VHT STA not in VHT TXOP PS
+# mode
+# 1 = VHT AP supports VHT TXOP PS mode (OR) VHT STA is in VHT TXOP power save
+# mode
+#
+# +HTC-VHT Capable: [HTC-VHT]
+# Indicates whether or not the STA supports receiving a VHT variant HT Control
+# field.
+# 0 = Not supported (default)
+# 1 = supported
+#
+# Maximum A-MPDU Length Exponent: [MAX-A-MPDU-LEN-EXP0]..[MAX-A-MPDU-LEN-EXP7]
+# Indicates the maximum length of A-MPDU pre-EOF padding that the STA can recv
+# This field is an integer in the range of 0 to 7.
+# The length defined by this field is equal to
+# 2 pow(13 + Maximum A-MPDU Length Exponent) -1 octets
+#
+# VHT Link Adaptation Capable: [VHT-LINK-ADAPT2] [VHT-LINK-ADAPT3]
+# Indicates whether or not the STA supports link adaptation using VHT variant
+# HT Control field
+# If +HTC-VHTcapable is 1
+# 0 = (no feedback) if the STA does not provide VHT MFB (default)
+# 1 = reserved
+# 2 = (Unsolicited) if the STA provides only unsolicited VHT MFB
+# 3 = (Both) if the STA can provide VHT MFB in response to VHT MRQ and if the
+# STA provides unsolicited VHT MFB
+# Reserved if +HTC-VHTcapable is 0
+#
+# Rx Antenna Pattern Consistency: [RX-ANTENNA-PATTERN]
+# Indicates the possibility of Rx antenna pattern change
+# 0 = Rx antenna pattern might change during the lifetime of an association
+# 1 = Rx antenna pattern does not change during the lifetime of an association
+#
+# Tx Antenna Pattern Consistency: [TX-ANTENNA-PATTERN]
+# Indicates the possibility of Tx antenna pattern change
+# 0 = Tx antenna pattern might change during the lifetime of an association
+# 1 = Tx antenna pattern does not change during the lifetime of an association
+{% if cap_vht %}
+vht_capab=
+{%- endif -%}
+
+{%- if cap_vht_max_mpdu -%}
+[MAX-MPDU-{{ cap_vht_max_mpdu }}]
+{%- endif -%}
+
+{%- if cap_vht_max_mpdu_exp -%}
+[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}]
+{%- endif -%}
+
+{%- if cap_vht_chan_set_width -%}
+{%- if '2' in cap_vht_chan_set_width -%}
+[VHT160]
+{%- elif '3' in cap_vht_chan_set_width -%}
+[VHT160-80PLUS80]
+{%- endif -%}
+{%- endif -%}
+
+{%- if cap_vht_stbc_tx -%}
+[TX-STBC-2BY1]
+{%- endif -%}
+
+{%- if cap_vht_stbc_rx -%}
+[RX-STBC-{{ cap_vht_stbc_rx }}]
+{%- endif -%}
+
+{%- if cap_vht_link_adaptation -%}
+{%- if 'unsolicited' in cap_vht_link_adaptation -%}
+[VHT-LINK-ADAPT2]
+{%- elif 'both' in cap_vht_link_adaptation -%}
+[VHT-LINK-ADAPT3]
+{%- endif -%}
+{%- endif -%}
+
+{%- if cap_vht_short_gi -%}
+{%- for gi in cap_vht_short_gi -%}
+[SHORT-GI-{{ gi }}]
+{%- endfor -%}
+{%- endif -%}
+
+{%- if cap_vht_ldpc -%}
+[RXLDPC]
+{%- endif -%}
+
+{%- if cap_vht_tx_powersave -%}
+[VHT-TXOP-PS]
+{%- endif -%}
+
+{%- if cap_vht_vht_cf -%}
+[HTC-VHT]
+{%- endif -%}
+
+{%- if cap_vht_beamform -%}
+{%- for beamform in cap_vht_beamform -%}
+{%- if 'single-user-beamformer' in beamform -%}
+[SU-BEAMFORMER]
+{%- elif 'single-user-beamformee' in beamform -%}
+[SU-BEAMFORMEE]
+{%- elif 'multi-user-beamformer' in beamform -%}
+[MU-BEAMFORMER]
+{%- elif 'multi-user-beamformee' in beamform -%}
+[MU-BEAMFORMEE]
+{%- endif -%}
+{%- endfor -%}
+{%- endif -%}
+
+{%- if cap_vht_antenna_fixed -%}
+[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]
+{%- endif -%}
+
+{%- if cap_vht_antenna_cnt -%}
+{%- if cap_vht_antenna_cnt|int > 1 -%}
+{%- if cap_vht_beamform -%}
+{%- for beamform in cap_vht_beamform -%}
+{%- if 'single-user-beamformer' in beamform -%}
+{%- if cap_vht_antenna_cnt|int < 6 -%}
+[BF-ANTENNA-{{ cap_vht_antenna_cnt|int -1 }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt|int -1}}]
+{%- endif -%}
+{%- else -%}
+{%- if cap_vht_antenna_cnt|int < 5 -%}
+[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}]
+{%- endif -%}
+{%- endif -%}
+{%- endfor -%}
+{%- else -%}
+{%- if cap_vht_antenna_cnt|int < 5 -%}
+[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}]
+{%- endif -%}
+{%- endif -%}
+{%- endif -%}
+{%- endif %}
+
+# ieee80211n: Whether IEEE 802.11n (HT) is enabled
+# 0 = disabled (default)
+# 1 = enabled
+# Note: You will also need to enable WMM for full HT functionality.
+# Note: hw_mode=g (2.4 GHz) and hw_mode=a (5 GHz) is used to specify the band.
+{% if cap_req_vht -%}
+ieee80211n=0
+# Require stations to support VHT PHY (reject association if they do not)
+require_vht=1
+{% endif %}
+
+{% if cap_vht_center_freq_1 -%}
+# center freq = 5 GHz + (5 * index)
+# So index 42 gives center freq 5.210 GHz
+# which is channel 42 in 5G band
+vht_oper_centr_freq_seg0_idx={{ cap_vht_center_freq_1 }}
+{% endif %}
+
+{% if cap_vht_center_freq_2 -%}
+# center freq = 5 GHz + (5 * index)
+# So index 159 gives center freq 5.795 GHz
+# which is channel 159 in 5G band
+vht_oper_centr_freq_seg1_idx={{ cap_vht_center_freq_2 }}
+{% endif %}
+
+{% if disable_broadcast_ssid -%}
+# Send empty SSID in beacons and ignore probe request frames that do not
+# specify full SSID, i.e., require stations to know SSID.
+# default: disabled (0)
+# 1 = send empty (length=0) SSID in beacon and ignore probe request for
+# broadcast SSID
+# 2 = clear SSID (ASCII 0), but keep the original length (this may be required
+# with some clients that do not support empty SSID) and ignore probe
+# requests for broadcast SSID
+ignore_broadcast_ssid=1
+{% endif %}
+
+# Station MAC address -based authentication
+# Please note that this kind of access control requires a driver that uses
+# hostapd to take care of management frame processing and as such, this can be
+# used with driver=hostap or driver=nl80211, but not with driver=atheros.
+# 0 = accept unless in deny list
+# 1 = deny unless in accept list
+# 2 = use external RADIUS server (accept/deny lists are searched first)
+macaddr_acl=0
+
+{% if max_stations -%}
+# Maximum number of stations allowed in station table. New stations will be
+# rejected after the station table is full. IEEE 802.11 has a limit of 2007
+# different association IDs, so this number should not be larger than that.
+# (default: 2007)
+max_num_sta={{ max_stations }}
+{% endif %}
+
+{% if isolate_stations -%}
+# Client isolation can be used to prevent low-level bridging of frames between
+# associated stations in the BSS. By default, this bridging is allowed.
+ap_isolate=1
+{% endif %}
+
+{% if reduce_transmit_power -%}
+# Add Power Constraint element to Beacon and Probe Response frames
+# This config option adds Power Constraint element when applicable and Country
+# element is added. Power Constraint element is required by Transmit Power
+# Control. This can be used only with ieee80211d=1.
+# Valid values are 0..255.
+local_pwr_constraint={{ reduce_transmit_power }}
+{% endif %}
+
+{% if expunge_failing_stations -%}
+# Disassociate stations based on excessive transmission failures or other
+# indications of connection loss. This depends on the driver capabilities and
+# may not be available with all drivers.
+disassoc_low_ack=1
+{% endif %}
+
+{% if sec_wep -%}
+# IEEE 802.11 specifies two authentication algorithms. hostapd can be
+# configured to allow both of these or only one. Open system authentication
+# should be used with IEEE 802.1X.
+# Bit fields of allowed authentication algorithms:
+# bit 0 = Open System Authentication
+# bit 1 = Shared Key Authentication (requires WEP)
+auth_algs=2
+
+# WEP rekeying (disabled if key lengths are not set or are set to 0)
+# Key lengths for default/broadcast and individual/unicast keys:
+# 5 = 40-bit WEP (also known as 64-bit WEP with 40 secret bits)
+# 13 = 104-bit WEP (also known as 128-bit WEP with 104 secret bits)
+wep_key_len_broadcast=5
+wep_key_len_unicast=5
+
+# Static WEP key configuration
+#
+# The key number to use when transmitting.
+# It must be between 0 and 3, and the corresponding key must be set.
+# default: not set
+wep_default_key=0
+
+# The WEP keys to use.
+# A key may be a quoted string or unquoted hexadecimal digits.
+# The key length should be 5, 13, or 16 characters, or 10, 26, or 32
+# digits, depending on whether 40-bit (64-bit), 104-bit (128-bit), or
+# 128-bit (152-bit) WEP is used.
+# Only the default key must be supplied; the others are optional.
+{% if sec_wep_key -%}
+{% for key in sec_wep_key -%}
+wep_key{{ loop.index -1 }}={{ key}}
+{% endfor %}
+{%- endif %}
+
+{% elif sec_wpa -%}
+##### WPA/IEEE 802.11i configuration ##########################################
+
+# Enable WPA. Setting this variable configures the AP to require WPA (either
+# WPA-PSK or WPA-RADIUS/EAP based on other configuration). For WPA-PSK, either
+# wpa_psk or wpa_passphrase must be set and wpa_key_mgmt must include WPA-PSK.
+# Instead of wpa_psk / wpa_passphrase, wpa_psk_radius might suffice.
+# For WPA-RADIUS/EAP, ieee8021x must be set (but without dynamic WEP keys),
+# RADIUS authentication server must be configured, and WPA-EAP must be included
+# in wpa_key_mgmt.
+# This field is a bit field that can be used to enable WPA (IEEE 802.11i/D3.0)
+# and/or WPA2 (full IEEE 802.11i/RSN):
+# bit0 = WPA
+# bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled)
+{% if 'both' in sec_wpa_mode -%}
+wpa=3
+{%- elif 'wpa2' in sec_wpa_mode -%}
+wpa=2
+{%- elif 'wpa' in sec_wpa_mode -%}
+wpa=1
+{%- endif %}
+
+{% if sec_wpa_cipher -%}
+# Set of accepted cipher suites (encryption algorithms) for pairwise keys
+# (unicast packets). This is a space separated list of algorithms:
+# CCMP = AES in Counter mode with CBC-MAC (CCMP-128)
+# TKIP = Temporal Key Integrity Protocol
+# CCMP-256 = AES in Counter mode with CBC-MAC with 256-bit key
+# GCMP = Galois/counter mode protocol (GCMP-128)
+# GCMP-256 = Galois/counter mode protocol with 256-bit key
+# Group cipher suite (encryption algorithm for broadcast and multicast frames)
+# is automatically selected based on this configuration. If only CCMP is
+# allowed as the pairwise cipher, group cipher will also be CCMP. Otherwise,
+# TKIP will be used as the group cipher. The optional group_cipher parameter can
+# be used to override this automatic selection.
+{% if 'wpa2' in sec_wpa_mode -%}
+# Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value)
+rsn_pairwise={{ sec_wpa_cipher | join(" ") }}
+{% else -%}
+# Pairwise cipher for WPA (v1) (default: TKIP)
+wpa_pairwise={{ sec_wpa_cipher | join(" ") }}
+{%- endif -%}
+{% endif %}
+
+{% if sec_wpa_passphrase -%}
+# IEEE 802.11 specifies two authentication algorithms. hostapd can be
+# configured to allow both of these or only one. Open system authentication
+# should be used with IEEE 802.1X.
+# Bit fields of allowed authentication algorithms:
+# bit 0 = Open System Authentication
+# bit 1 = Shared Key Authentication (requires WEP)
+auth_algs=1
+
+# WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit
+# secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase
+# (8..63 characters) that will be converted to PSK. This conversion uses SSID
+# so the PSK changes when ASCII passphrase is used and the SSID is changed.
+wpa_passphrase={{ sec_wpa_passphrase }}
+
+# Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The
+# entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be
+# added to enable SHA256-based stronger algorithms.
+# WPA-PSK = WPA-Personal / WPA2-Personal
+# WPA-PSK-SHA256 = WPA2-Personal using SHA256
+wpa_key_mgmt=WPA-PSK
+
+{% elif sec_wpa_radius -%}
+##### IEEE 802.1X-2004 related configuration ##################################
+# Require IEEE 802.1X authorization
+ieee8021x=1
+
+# Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The
+# entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be
+# added to enable SHA256-based stronger algorithms.
+# WPA-EAP = WPA-Enterprise / WPA2-Enterprise
+# WPA-EAP-SHA256 = WPA2-Enterprise using SHA256
+wpa_key_mgmt=WPA-EAP
+
+{% if sec_wpa_radius_source -%}
+# RADIUS client forced local IP address for the access point
+# Normally the local IP address is determined automatically based on configured
+# IP addresses, but this field can be used to force a specific address to be
+# used, e.g., when the device has multiple IP addresses.
+radius_client_addr={{ sec_wpa_radius_source }}
+
+# The own IP address of the access point (used as NAS-IP-Address)
+own_ip_addr={{ sec_wpa_radius_source }}
+{% else %}
+# The own IP address of the access point (used as NAS-IP-Address)
+own_ip_addr=127.0.0.1
+{% endif %}
+
+{% for radius in sec_wpa_radius -%}
+{%- if not radius.disabled -%}
+# RADIUS authentication server
+auth_server_addr={{ radius.server }}
+auth_server_port={{ radius.port }}
+auth_server_shared_secret={{ radius.key }}
+{% if radius.acc_port -%}
+# RADIUS accounting server
+acct_server_addr={{ radius.server }}
+acct_server_port={{ radius.acc_port }}
+acct_server_shared_secret={{ radius.key }}
+{% endif %}
+{% endif %}
+{% endfor %}
+
+{% endif %}
+
+{% else %}
+# Open system
+auth_algs=1
+{% endif %}
+
+# TX queue parameters (EDCF / bursting)
+# tx_queue_<queue name>_<param>
+# queues: data0, data1, data2, data3
+# (data0 is the highest priority queue)
+# parameters:
+# aifs: AIFS (default 2)
+# cwmin: cwMin (1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191,
+# 16383, 32767)
+# cwmax: cwMax (same values as cwMin, cwMax >= cwMin)
+# burst: maximum length (in milliseconds with precision of up to 0.1 ms) for
+# bursting
+#
+# Default WMM parameters (IEEE 802.11 draft; 11-03-0504-03-000e):
+# These parameters are used by the access point when transmitting frames
+# to the clients.
+#
+# Low priority / AC_BK = background
+tx_queue_data3_aifs=7
+tx_queue_data3_cwmin=15
+tx_queue_data3_cwmax=1023
+tx_queue_data3_burst=0
+# Note: for IEEE 802.11b mode: cWmin=31 cWmax=1023 burst=0
+#
+# Normal priority / AC_BE = best effort
+tx_queue_data2_aifs=3
+tx_queue_data2_cwmin=15
+tx_queue_data2_cwmax=63
+tx_queue_data2_burst=0
+# Note: for IEEE 802.11b mode: cWmin=31 cWmax=127 burst=0
+#
+# High priority / AC_VI = video
+tx_queue_data1_aifs=1
+tx_queue_data1_cwmin=7
+tx_queue_data1_cwmax=15
+tx_queue_data1_burst=3.0
+# Note: for IEEE 802.11b mode: cWmin=15 cWmax=31 burst=6.0
+#
+# Highest priority / AC_VO = voice
+tx_queue_data0_aifs=1
+tx_queue_data0_cwmin=3
+tx_queue_data0_cwmax=7
+tx_queue_data0_burst=1.5
+
+# Default WMM parameters (IEEE 802.11 draft; 11-03-0504-03-000e):
+# for 802.11a or 802.11g networks
+# These parameters are sent to WMM clients when they associate.
+# The parameters will be used by WMM clients for frames transmitted to the
+# access point.
+#
+# note - txop_limit is in units of 32microseconds
+# note - acm is admission control mandatory flag. 0 = admission control not
+# required, 1 = mandatory
+# note - Here cwMin and cmMax are in exponent form. The actual cw value used
+# will be (2^n)-1 where n is the value given here. The allowed range for these
+# wmm_ac_??_{cwmin,cwmax} is 0..15 with cwmax >= cwmin.
+#
+wmm_enabled=1
+
+# Low priority / AC_BK = background
+wmm_ac_bk_cwmin=4
+wmm_ac_bk_cwmax=10
+wmm_ac_bk_aifs=7
+wmm_ac_bk_txop_limit=0
+wmm_ac_bk_acm=0
+# Note: for IEEE 802.11b mode: cWmin=5 cWmax=10
+#
+# Normal priority / AC_BE = best effort
+wmm_ac_be_aifs=3
+wmm_ac_be_cwmin=4
+wmm_ac_be_cwmax=10
+wmm_ac_be_txop_limit=0
+wmm_ac_be_acm=0
+# Note: for IEEE 802.11b mode: cWmin=5 cWmax=7
+#
+# High priority / AC_VI = video
+wmm_ac_vi_aifs=2
+wmm_ac_vi_cwmin=3
+wmm_ac_vi_cwmax=4
+wmm_ac_vi_txop_limit=94
+wmm_ac_vi_acm=0
+# Note: for IEEE 802.11b mode: cWmin=4 cWmax=5 txop_limit=188
+#
+# Highest priority / AC_VO = voice
+wmm_ac_vo_aifs=2
+wmm_ac_vo_cwmin=2
+wmm_ac_vo_cwmax=3
+wmm_ac_vo_txop_limit=47
+wmm_ac_vo_acm=0
+
diff --git a/data/templates/wifi/wpa_supplicant.conf.tmpl b/data/templates/wifi/wpa_supplicant.conf.tmpl
new file mode 100644
index 000000000..2784883f1
--- /dev/null
+++ b/data/templates/wifi/wpa_supplicant.conf.tmpl
@@ -0,0 +1,9 @@
+# WPA supplicant config
+network={
+ ssid="{{ ssid }}"
+{%- if sec_wpa_passphrase %}
+ psk="{{ sec_wpa_passphrase }}"
+{% else %}
+ key_mgmt=NONE
+{% endif %}
+}
diff --git a/data/templates/wwan/chat.tmpl b/data/templates/wwan/chat.tmpl
new file mode 100644
index 000000000..a3395c057
--- /dev/null
+++ b/data/templates/wwan/chat.tmpl
@@ -0,0 +1,6 @@
+ABORT 'NO DIAL TONE' ABORT 'NO ANSWER' ABORT 'NO CARRIER' ABORT DELAYED
+'' AT
+OK ATZ
+OK 'AT+CGDCONT=1,"IP","{{ apn }}"'
+OK ATD*99#
+CONNECT ''
diff --git a/data/templates/wwan/ip-down.script.tmpl b/data/templates/wwan/ip-down.script.tmpl
new file mode 100644
index 000000000..194f8d863
--- /dev/null
+++ b/data/templates/wwan/ip-down.script.tmpl
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+tty=$2
+
+# Only applicable for Wireless Modems (WWAN)
+if [ -z "$(echo $tty | egrep "tty(USB|ACM)")" ]; then
+ exit 0
+fi
+
+# Determine if we are enslaved to a VRF, this is needed to properly insert
+# the default route
+VRF_NAME=""
+if [ -d /sys/class/net/{{ intf }}/upper_* ]; then
+ # Determine upper (VRF) interface
+ VRF=$(basename $(ls -d /sys/class/net/{{ intf }}/upper_*))
+ # Remove upper_ prefix from result string
+ VRF=${VRF#"upper_"}
+ # Populate variable to run in VR context
+ VRF_NAME=" -c vrf ${VRF_NAME} "
+fi
+
+# Remove default route to either default or VRF routing table
+vtysh -c "conf t" ${VRF_NAME} -c "no ip route 0.0.0.0/0 {{ intf }} {{ metric }}"
+
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+logger -t pppd[$DIALER_PID] "removed default route via {{ intf }} metric {{ metric }}"
diff --git a/data/templates/wwan/ip-pre-up.script.tmpl b/data/templates/wwan/ip-pre-up.script.tmpl
new file mode 100644
index 000000000..7a17a1c71
--- /dev/null
+++ b/data/templates/wwan/ip-pre-up.script.tmpl
@@ -0,0 +1,23 @@
+#!/bin/sh
+# As WWAN is an "on demand" interface we need to re-configure it when it
+# becomes 'up'
+
+ipparam=$6
+
+# device name and metric are received using ipparam
+device=`echo "$ipparam"|awk '{ print $1 }'`
+
+if [ "$device" != "{{ intf }}" ]; then
+ exit
+fi
+
+# add some info to syslog
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+logger -t pppd[$DIALER_PID] "executing $0"
+
+echo "{{ description }}" > /sys/class/net/{{ intf }}/ifalias
+
+{% if vrf -%}
+logger -t pppd[$DIALER_PID] "configuring interface {{ intf }} for VRF {{ vrf }}"
+ip link set dev {{ intf }} master {{ vrf }}
+{% endif %}
diff --git a/data/templates/wwan/ip-up.script.tmpl b/data/templates/wwan/ip-up.script.tmpl
new file mode 100644
index 000000000..89e42a23a
--- /dev/null
+++ b/data/templates/wwan/ip-up.script.tmpl
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+tty=$2
+
+# Only applicable for Wireless Modems (WWAN)
+if [ -z "$(echo $tty | egrep "tty(USB|ACM)")" ]; then
+ exit 0
+fi
+
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+
+# Determine if we are enslaved to a VRF, this is needed to properly insert
+# the default route
+VRF_NAME=""
+if [ -d /sys/class/net/{{ intf }}/upper_* ]; then
+ # Determine upper (VRF) interface
+ VRF=$(basename $(ls -d /sys/class/net/{{ intf }}/upper_*))
+ # Remove upper_ prefix from result string
+ VRF=${VRF#"upper_"}
+ VRF_NAME="vrf ${VRF}"
+fi
+
+# Apply default route to either default or VRF routing table
+vtysh -c "conf t" -c "ip route 0.0.0.0/0 {{ intf }} ${VRF_NAME} {{ metric }}"
+logger -t pppd[$DIALER_PID] "added default route via {{ intf }} metric {{ metric }} ${VRF_NAME}"
diff --git a/data/templates/wwan/peer.tmpl b/data/templates/wwan/peer.tmpl
new file mode 100644
index 000000000..04ab4f844
--- /dev/null
+++ b/data/templates/wwan/peer.tmpl
@@ -0,0 +1,30 @@
+### Autogenerated by interfaces-wirelessmodem.py ###
+
+{% if description %}
+# {{ description }}
+{% endif %}
+ifname {{ intf }}
+ipparam {{ intf }}
+linkname {{ intf }}
+{% if name_server -%}
+usepeerdns
+{%- endif %}
+# physical device
+/dev/{{ device }}
+lcp-echo-failure 0
+115200
+debug
+logfile {{ logfile }}
+nodefaultroute
+ipcp-max-failure 4
+ipcp-accept-local
+ipcp-accept-remote
+noauth
+crtscts
+lock
+persist
+{% if on_demand -%}
+demand
+{%- endif %}
+
+connect '/usr/sbin/chat -v -t6 -f {{ chat_script }}'