# 2025-01-20 09:48:07 by RouterOS 7.17
# software id = REDACTED
#
# model = RB4011iGS+
# serial number = REDACTED
/interface bridge
add admin-mac=REDACTED auto-mac=no comment=defconf name=bridge
/interface ethernet
set [ find default-name=ether3 ] comment="b\C3\BCro"
set [ find default-name=ether6 ] comment=knx
/interface wireguard
add listen-port=13232 mtu=1420 name=REDACTED1
add listen-port=21841 mtu=1420 name=REDACTED1
add listen-port=13231 mtu=1420 name=wireguard1
/interface vlan
add interface=bridge name=guest vlan-id=300
add interface=bridge name=home vlan-id=200
add interface=bridge name=mgmt vlan-id=100
add interface=sfp-sfpplus1 name=sfpv7 vlan-id=7
/interface bonding
add mode=802.3ad name=nas slaves=ether7,ether8
add mode=balance-xor name=switch slaves=ether9,ether10 transmit-hash-policy=\
layer-2-and-3
/interface pppoe-client
add add-default-route=yes allow=pap,chap,mschap2 disabled=no interface=sfpv7 \
name=telekom use-peer-dns=yes user=REDACTED
/interface list
add comment=defconf name=WAN
add comment=defconf name=LAN
add name=GUEST
/interface wifi datapath
add bridge=bridge comment=defconf disabled=no name=capdp
add bridge=bridge comment=guest disabled=no name=guest-datapath vlan-id=300
/interface wifi security
add authentication-types=wpa2-psk,wpa3-psk connect-priority=0 disabled=no \
name=family-sec
add authentication-types=wpa2-psk,wpa3-psk disabled=no name=guest-sec
/interface wifi configuration
add channel.reselect-interval=10m..30m comment=family disabled=no mode=ap \
name=family security=family-sec security.connect-priority=0 .ft=yes \
.ft-over-ds=yes ssid=BuddhasBlessedBunch
add comment=guest country=Germany datapath=guest-datapath disabled=yes mode=\
ap name=guest security=guest-sec ssid=BuddhasBlessedGuests
/ip kid-control
add disabled=yes mon=9h-21h name="Stop Internet "
/ip pool
add name=dhcp ranges=192.168.88.100-192.168.88.254
add name=mgmt ranges=192.168.90.100-192.168.90.254
add name=home ranges=192.168.91.100-192.168.91.254
add name=guest ranges=192.168.92.100-192.168.92.254
/ip dhcp-server
add address-pool=dhcp interface=bridge lease-script=dhcp-to-dns lease-time=2h \
name=defconf
add address-pool=mgmt interface=mgmt lease-script=dhcp-to-dns name=mgmt
add address-pool=home interface=home lease-script=dhcp-to-dns name=home
add address-pool=guest interface=guest lease-script=dhcp-to-dns name=guest
/port
set 0 name=serial0
set 1 name=serial1
/system logging action
set 3 remote=nas.lan
/interface bridge port
add bridge=bridge comment=defconf interface=ether2
add bridge=bridge comment=defconf interface=ether3
add bridge=bridge comment=defconf interface=ether4
add bridge=bridge comment=defconf interface=ether5
add bridge=bridge comment=defconf interface=ether6
add bridge=bridge comment=defconf interface=ether1
add bridge=bridge interface=nas
add bridge=bridge interface=switch
/ip neighbor discovery-settings
set discover-interface-list=LAN
/interface bridge vlan
add bridge=bridge comment=mgmt tagged="ether1,ether2,ether3,ether4,ether5,ethe\
r6,ether7,ether8,ether9,ether10,bridge" vlan-ids=100
add bridge=bridge comment=home tagged="ether1,ether2,ether3,ether4,ether5,ethe\
r6,ether7,ether8,ether9,ether10,bridge" vlan-ids=200
add bridge=bridge comment=guest tagged="ether1,ether2,ether3,ether4,ether5,eth\
er6,ether7,ether8,ether9,ether10,bridge" vlan-ids=300
/interface list member
add comment=defconf interface=bridge list=LAN
add interface=sfp-sfpplus1 list=WAN
add comment=wireguard interface=wireguard1 list=LAN
add comment=REDACTED interface=REDACTED1 list=LAN
add interface=sfpv7 list=WAN
add interface=telekom list=WAN
/interface ovpn-server server
add mac-address=FE:97:BD:F3:AB:5D name=ovpn-server1
/interface wifi capsman
set ca-certificate=auto enabled=yes package-path="" require-peer-certificate=\
no upgrade-policy=suggest-same-version
/interface wifi provisioning
add action=create-dynamic-enabled comment=5ghz disabled=no \
master-configuration=family slave-configurations=guest supported-bands=\
5ghz-ax
add action=create-dynamic-enabled comment=2ghx disabled=no \
master-configuration=family slave-configurations=guest supported-bands=\
2ghz-ax
/interface wireguard peers
add allowed-address=192.168.87.10/32 client-address=192.168.87.10/32 \
client-dns=192.168.87.1 client-endpoint=REDACTEDREDACTED \
interface=wireguard1 name=pixel preshared-key=\
"REDACTED" private-key=\
"REDACTED" public-key=\
"zA3KVqkFdyeYs8SeH5bBty5q8a6aqZjHmywineHN0EQ="
add allowed-address=192.168.87.11/32 client-address=192.168.87.11/32 \
client-dns=192.168.87.1 client-endpoint=REDACTEDREDACTED \
interface=wireguard1 name=tuxedo preshared-key=\
"REDACTED" private-key=\
"REDACTED" public-key=\
"hYoazZbXFmbE148jFN8s6v0D3cRCkTawVtaaXySosEE="
add allowed-address=192.168.87.12/32 client-address=192.168.87.12/32 \
client-dns=192.168.87.1 client-endpoint=REDACTEDREDACTED \
interface=wireguard1 name=travelrouter preshared-key=\
"REDACTED" private-key=\
"REDACTED" public-key=\
"PaIB5Rp1hI1pRtadUrtSDAFEOI//urx6fhApJaZqrDM="
add allowed-address=192.168.87.13/32 client-address=192.168.87.13/32 \
client-dns=192.168.87.1 client-endpoint=REDACTEDREDACTED \
interface=wireguard1 name=iphone preshared-key=\
"REDACTED" private-key=\
"REDACTED" public-key=\
"FHB9LwPAgM7MoR2pSOqO1RnvaAXy77XkpJ4Mo62qPis="
add allowed-address=192.168.86.11/32 client-address=192.168.86.11/32 \
client-dns=192.168.86.1 client-endpoint=REDACTEDREDACTED \
interface=REDACTED1 name=rieckmanns preshared-key=\
"REDACTED" private-key=\
"REDACTED" public-key=\
"HJl4GXRRVzEdIlCNxw7c2k0oADNBxtkpxnT+C6h45Ss="
add allowed-address=0.0.0.0/0 endpoint-address=REDACTED endpoint-port=\
51026 interface=REDACTED1 name=REDACTED public-key=\
"Tq7MDaNyunVrfko5mLMWoN8rZ08hSJOCpJEPUfQcHVo="
/ip address
add address=192.168.88.1/24 comment=defconf interface=bridge network=\
192.168.88.0
add address=192.168.87.1/24 comment=wireguard interface=wireguard1 network=\
192.168.87.0
add address=10.102.6.2/24 comment=REDACTED interface=REDACTED1 network=\
10.102.6.0
add address=192.168.86.1/24 comment=REDACTED interface=REDACTED1 network=\
192.168.86.0
add address=192.168.90.1/24 comment=mgmt interface=mgmt network=192.168.90.0
add address=192.168.91.1/24 comment=home interface=home network=192.168.91.0
add address=192.168.92.1/24 comment=guest interface=guest network=\
192.168.92.0
add address=192.168.1.2/24 comment=luleey interface=sfp-sfpplus1 network=\
192.168.1.0
/ip dhcp-server network
add address=192.168.88.0/24 comment=defconf dns-server=192.168.88.14 domain=\
lan gateway=192.168.88.1 netmask=24
add address=192.168.90.0/24 comment=mgmt dns-server=192.168.90.1 gateway=\
192.168.90.1
add address=192.168.91.0/24 comment=home dns-server=192.168.90.1 gateway=\
192.168.91.1
add address=192.168.92.0/24 comment=guest dns-server=192.168.90.1 gateway=\
192.168.92.1
/ip dns
set allow-remote-requests=yes servers=9.9.9.9
/ip dns static
add address=192.168.88.110 comment=\
"managed by dhcp-to-dns, macaddress=00:F6:20:4C:5E:0A, server=defconf" \
name=00-F6-20-4C-5E-0A.lan ttl=5m type=A
add address=192.168.88.103 comment=\
"managed by dhcp-to-dns, macaddress=48:A9:8A:A2:9D:29, server=defconf" \
name=48-A9-8A-A2-9D-29.lan ttl=5m type=A
add address=192.168.88.179 comment=\
"managed by dhcp-to-dns, macaddress=68:A4:0E:A4:14:C3, server=defconf" \
name=68-A4-0E-A4-14-C3.lan ttl=5m type=A
add address=192.168.88.108 comment=\
"managed by dhcp-to-dns, macaddress=84:0D:8E:B7:69:A0, server=defconf" \
name=84-0D-8E-B7-69-A0.lan ttl=5m type=A
add address=192.168.88.105 comment=\
"managed by dhcp-to-dns, macaddress=74:4D:BD:8A:19:00, server=defconf" \
name=74-4D-BD-8A-19-00.lan ttl=5m type=A
add address=192.168.88.113 comment=\
"managed by dhcp-to-dns, macaddress=58:E4:88:42:34:58, server=defconf" \
name=58-E4-88-42-34-58.lan ttl=5m type=A
add address=192.168.88.101 comment=\
"managed by dhcp-to-dns, macaddress=10:DA:43:00:AD:D9, server=defconf" \
name=10-DA-43-00-AD-D9.lan ttl=5m type=A
add address=192.168.88.254 comment=\
"managed by dhcp-to-dns, macaddress=CC:1B:E0:80:FB:09, server=defconf" \
name=CC-1B-E0-80-FB-09.lan ttl=5m type=A
add address=192.168.88.112 comment=\
"managed by dhcp-to-dns, macaddress=F0:EF:86:19:80:61, server=defconf" \
name=F0-EF-86-19-80-61.lan ttl=5m type=A
add address=192.168.88.107 comment=\
"managed by dhcp-to-dns, macaddress=98:F4:AB:B8:64:E1, server=defconf" \
name=98-F4-AB-B8-64-E1.lan ttl=5m type=A
add address=192.168.88.106 comment=\
"managed by dhcp-to-dns, macaddress=92:66:00:A0:AD:52, server=defconf" \
name=92-66-00-A0-AD-52.lan ttl=5m type=A
add address=192.168.88.111 comment=\
"managed by dhcp-to-dns, macaddress=F0:EF:86:16:D8:0C, server=defconf" \
name=F0-EF-86-16-D8-0C.lan ttl=5m type=A
add address=192.168.88.133 comment=\
"managed by dhcp-to-dns, macaddress=BC:24:11:32:1A:58, server=defconf" \
name=BC-24-11-32-1A-58.lan ttl=5m type=A
add address=192.168.88.123 comment=\
"managed by dhcp-to-dns, macaddress=BC:24:11:1D:7E:77, server=defconf" \
name=BC-24-11-1D-7E-77.lan ttl=5m type=A
add address=192.168.88.128 comment=\
"managed by dhcp-to-dns, macaddress=BC:24:11:5D:F2:F2, server=defconf" \
name=BC-24-11-5D-F2-F2.lan ttl=5m type=A
add address=192.168.88.181 comment=\
"managed by dhcp-to-dns, macaddress=56:E8:1C:8E:C6:DA, server=defconf" \
name=56-E8-1C-8E-C6-DA.lan ttl=5m type=A
add address=192.168.88.174 comment=\
"managed by dhcp-to-dns, macaddress=9C:2D:CD:B7:D5:DF, server=defconf" \
name=9C-2D-CD-B7-D5-DF.lan ttl=5m type=A
add cname=00-F6-20-4C-5E-0A.lan comment=\
"managed by dhcp-to-dns, macaddress=00:F6:20:4C:5E:0A, server=defconf" \
name=Chromecast-Ultra.lan ttl=5m type=CNAME
add cname=48-A9-8A-A2-9D-29.lan comment=\
"managed by dhcp-to-dns, macaddress=48:A9:8A:A2:9D:29, server=defconf" \
name=AP-Hauswirtschaftsraum.lan ttl=5m type=CNAME
add cname=68-A4-0E-A4-14-C3.lan comment=\
"managed by dhcp-to-dns, macaddress=68:A4:0E:A4:14:C3, server=defconf" \
name=SIEMENS-EX877NX68E-68A40EA414C3.lan ttl=5m type=CNAME
add cname=84-0D-8E-B7-69-A0.lan comment=\
"managed by dhcp-to-dns, macaddress=84:0D:8E:B7:69:A0, server=defconf" \
name=tasmota-B769A0-2464.lan ttl=5m type=CNAME
add cname=74-4D-BD-8A-19-00.lan comment=\
"managed by dhcp-to-dns, macaddress=74:4D:BD:8A:19:00, server=defconf" \
name=ems-esp.lan ttl=5m type=CNAME
add cname=CC-1B-E0-80-FB-09.lan comment=\
"managed by dhcp-to-dns, macaddress=CC:1B:E0:80:FB:09, server=defconf" \
name=KNX-IPIF-80FB09.lan ttl=5m type=CNAME
add cname=F0-EF-86-19-80-61.lan comment=\
"managed by dhcp-to-dns, macaddress=F0:EF:86:19:80:61, server=defconf" \
name=Google-Home-Mini.lan ttl=5m type=CNAME
add cname=98-F4-AB-B8-64-E1.lan comment=\
"managed by dhcp-to-dns, macaddress=98:F4:AB:B8:64:E1, server=defconf" \
name=shellyplug-s-B864E1.lan ttl=5m type=CNAME
add cname=92-66-00-A0-AD-52.lan comment=\
"managed by dhcp-to-dns, macaddress=92:66:00:A0:AD:52, server=defconf" \
name=MacBookPro.lan ttl=5m type=CNAME
add cname=BC-24-11-32-1A-58.lan comment=\
"managed by dhcp-to-dns, macaddress=BC:24:11:32:1A:58, server=defconf" \
name=zigbee2mqtt.lan ttl=5m type=CNAME
add cname=BC-24-11-1D-7E-77.lan comment=\
"managed by dhcp-to-dns, macaddress=BC:24:11:1D:7E:77, server=defconf" \
name=paperless-ngx.lan ttl=5m type=CNAME
add cname=BC-24-11-5D-F2-F2.lan comment=\
"managed by dhcp-to-dns, macaddress=BC:24:11:5D:F2:F2, server=defconf" \
name=recyclarr.lan ttl=5m type=CNAME
add cname=56-E8-1C-8E-C6-DA.lan comment=\
"managed by dhcp-to-dns, macaddress=56:E8:1C:8E:C6:DA, server=defconf" \
name=Pixel-6.lan ttl=5m type=CNAME
add cname=9C-2D-CD-B7-D5-DF.lan comment=\
"managed by dhcp-to-dns, macaddress=9C:2D:CD:B7:D5:DF, server=defconf" \
name=DESKTOP-B0UL83K.lan ttl=5m type=CNAME
add address=192.168.88.116 comment=\
"managed by dhcp-to-dns, macaddress=82:20:AC:54:BB:7F, server=defconf" \
name=82-20-AC-54-BB-7F.lan ttl=5m type=A
add address=192.168.88.164 comment=\
"managed by dhcp-to-dns, macaddress=E0:F6:B5:E3:A0:CF, server=defconf" \
name=E0-F6-B5-E3-A0-CF.lan ttl=5m type=A
add cname=82-20-AC-54-BB-7F.lan comment=\
"managed by dhcp-to-dns, macaddress=82:20:AC:54:BB:7F, server=defconf" \
name=iPhone.lan ttl=5m type=CNAME
add address=192.168.88.100 comment=\
"managed by dhcp-to-dns, macaddress=3C:A3:08:C2:81:43, server=defconf" \
name=3C-A3-08-C2-81-43.lan ttl=5m type=A
add cname=3C-A3-08-C2-81-43.lan comment=\
"managed by dhcp-to-dns, macaddress=3C:A3:08:C2:81:43, server=defconf" \
name=SoundTouch-20.lan ttl=5m type=CNAME
add address=192.168.88.194 comment=\
"managed by dhcp-to-dns, macaddress=8C:17:59:72:4D:EA, server=defconf" \
name=8C-17-59-72-4D-EA.lan ttl=5m type=A
add address=192.168.88.175 comment=\
"managed by dhcp-to-dns, macaddress=A4:83:E7:CA:88:9A, server=defconf" \
name=A4-83-E7-CA-88-9A.lan ttl=5m type=A
add cname=A4-83-E7-CA-88-9A.lan comment=\
"managed by dhcp-to-dns, macaddress=A4:83:E7:CA:88:9A, server=defconf" \
name=Julias-MBP.lan ttl=5m type=CNAME
add address=192.168.88.104 comment=\
"managed by dhcp-to-dns, macaddress=CE:A3:95:75:39:A4, server=defconf" \
name=CE-A3-95-75-39-A4.lan ttl=5m type=A
add address=192.168.88.115 comment=\
"managed by dhcp-to-dns, macaddress=5A:05:ED:04:D0:0E, server=defconf" \
name=5A-05-ED-04-D0-0E.lan ttl=5m type=A
add cname=5A-05-ED-04-D0-0E.lan comment=\
"managed by dhcp-to-dns, macaddress=5A:05:ED:04:D0:0E, server=defconf" \
name=Watch.lan ttl=5m type=CNAME
add address=192.168.88.119 comment=\
"managed by dhcp-to-dns, macaddress=4A:1B:F2:31:39:96, server=defconf" \
name=4A-1B-F2-31-39-96.lan ttl=5m type=A
add cname=4A-1B-F2-31-39-96.lan comment=\
"managed by dhcp-to-dns, macaddress=4A:1B:F2:31:39:96, server=defconf" \
name=iPad.lan ttl=5m type=CNAME
add address=192.168.88.120 comment=\
"managed by dhcp-to-dns, macaddress=DE:8C:E0:F5:51:8F, server=defconf" \
name=DE-8C-E0-F5-51-8F.lan ttl=5m type=A
add address=192.168.88.102 comment=\
"managed by dhcp-to-dns, macaddress=48:A9:8A:A2:8F:9C, server=defconf" \
name=48-A9-8A-A2-8F-9C.lan ttl=5m type=A
add cname=48-A9-8A-A2-8F-9C.lan comment=\
"managed by dhcp-to-dns, macaddress=48:A9:8A:A2:8F:9C, server=defconf" \
name=AP-Dachboden.lan ttl=5m type=CNAME
add address=192.168.88.138 comment=\
"managed by dhcp-to-dns, macaddress=A4:42:3B:6D:4F:57, server=defconf" \
name=A4-42-3B-6D-4F-57.lan ttl=5m type=A
add cname=A4-42-3B-6D-4F-57.lan comment=\
"managed by dhcp-to-dns, macaddress=A4:42:3B:6D:4F:57, server=defconf" \
name=LAPTOP-RLPTO8GK.lan ttl=5m type=CNAME
add disabled=yes name="--- dhcp-to-dns above ---" type=NXDOMAIN
add address=192.168.88.1 comment=defconf name=router.lan type=A
add address=192.168.88.3 name=proxmox2.lan type=A
add address=192.168.88.6 name=nas.lan type=A
add address=192.168.88.7 name=REDACTED.lan type=A
add address=192.168.88.8 name=REDACTED.lan type=A
add address=192.168.88.11 name=box.lan type=A
add address=192.168.88.12 name=REDACTED.lan type=A
add address=192.168.88.14 name=homeassistant.lan type=A
add address=192.168.88.16 name=REDACTED.lan type=A
add address=192.168.88.17 name=zigbeecoordinator.lan type=A
add address=192.168.88.20 name=proxmox3.lan type=A
add address=192.168.88.21 name=proxmox.lan type=A
# bad CNAME data
add cname=nginx.lan. name=REDACTED.REDACTED type=CNAME
# bad CNAME data
add cname=nginx.lan. name=REDACTED.REDACTED type=CNAME
# bad CNAME data
add cname=nginx.lan. name=REDACTED.REDACTED type=CNAME
# bad CNAME data
add cname=nginx.lan. name=REDACTED.REDACTED type=CNAME
# bad CNAME data
add cname=homeassistant.lan. name=mqtt.lan type=CNAME
# bad CNAME data
add cname=homeassistant.lan. name=REDACTED.lan type=CNAME
# bad CNAME data
add cname=homeassistant.lan. name=REDACTED.lan type=CNAME
# bad CNAME data
add cname=homeassistant.lan. name=REDACTED.lan type=CNAME
# bad CNAME data
add cname=homeassistant.lan. name=nginx.lan type=CNAME
/ip firewall address-list
add address=192.168.88.7 comment=REDACTED list=REDACTED
add address=192.168.88.12 comment=REDACTED list=REDACTED
add address=192.168.88.13 comment=nginx list=REDACTED
/ip firewall filter
add action=jump chain=forward comment="jump to kid-control rules" \
jump-target=kid-control
add action=accept chain=input comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
invalid
add action=accept chain=input comment="defconf: accept ICMP" protocol=icmp
add action=accept chain=input comment=\
"defconf: accept to local loopback (for CAPsMAN)" dst-address=127.0.0.1
add action=accept chain=input comment="allow Wireguard" dst-port=13231 \
protocol=udp
add action=accept chain=input comment="allow REDACTED (Wireguard)" dst-port=\
13232 protocol=udp
add action=accept chain=input comment="allow REDACTED (Wireguard )" dst-port=\
21841 protocol=udp
add action=drop chain=input comment="defconf: drop all not coming from LAN" \
in-interface-list=!LAN
add action=accept chain=forward comment="defconf: accept in ipsec policy" \
ipsec-policy=in,ipsec
add action=accept chain=forward comment="defconf: accept out ipsec policy" \
ipsec-policy=out,ipsec
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
connection-state=established,related hw-offload=yes
add action=accept chain=forward comment=\
"defconf: accept established,related, untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="allow REDACTED access only" \
dst-address-list=!REDACTED in-interface=REDACTED1
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat \
connection-state=new in-interface-list=WAN
/ip firewall nat
add action=masquerade chain=srcnat comment=luleey disabled=yes out-interface=\
sfp-sfpplus1
add action=masquerade chain=srcnat comment="defconf: masquerade" \
ipsec-policy=out,none out-interface-list=WAN
add action=masquerade chain=srcnat comment=REDACTED out-interface=REDACTED1
/ip ipsec profile
set [ find default=yes ] dpd-interval=2m dpd-maximum-failures=5
/ip kid-control device
add mac-address=92:66:00:A0:AD:52 name="MacBook Pro " user="Stop Internet "
/ipv6 dhcp-client
add add-default-route=yes interface=telekom pool-name=pool-ipv6 request=\
prefix
/ipv6 firewall address-list
add address=::/128 comment="defconf: unspecified address" list=bad_ipv6
add address=::1/128 comment="defconf: lo" list=bad_ipv6
add address=fec0::/10 comment="defconf: site-local" list=bad_ipv6
add address=::ffff:0.0.0.0/96 comment="defconf: ipv4-mapped" list=bad_ipv6
add address=::/96 comment="defconf: ipv4 compat" list=bad_ipv6
add address=100::/64 comment="defconf: discard only " list=bad_ipv6
add address=2001:db8::/32 comment="defconf: documentation" list=bad_ipv6
add address=2001:10::/28 comment="defconf: ORCHID" list=bad_ipv6
add address=3ffe::/16 comment="defconf: 6bone" list=bad_ipv6
/ipv6 firewall filter
add action=accept chain=input comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
invalid
add action=accept chain=input comment="defconf: accept ICMPv6" protocol=\
icmpv6
add action=accept chain=input comment="defconf: accept UDP traceroute" \
dst-port=33434-33534 protocol=udp
add action=accept chain=input comment=\
"defconf: accept DHCPv6-Client prefix delegation." dst-port=546 protocol=\
udp src-address=fe80::/10
add action=accept chain=input comment="defconf: accept IKE" dst-port=500,4500 \
protocol=udp
add action=accept chain=input comment="defconf: accept ipsec AH" protocol=\
ipsec-ah
add action=accept chain=input comment="defconf: accept ipsec ESP" protocol=\
ipsec-esp
add action=accept chain=input comment=\
"defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec
add action=drop chain=input comment=\
"defconf: drop everything else not coming from LAN" in-interface-list=\
!LAN
add action=accept chain=forward comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop packets with bad src ipv6" src-address-list=bad_ipv6
add action=drop chain=forward comment=\
"defconf: drop packets with bad dst ipv6" dst-address-list=bad_ipv6
add action=drop chain=forward comment="defconf: rfc4890 drop hop-limit=1" \
hop-limit=equal:1 protocol=icmpv6
add action=accept chain=forward comment="defconf: accept ICMPv6" protocol=\
icmpv6
add action=accept chain=forward comment="defconf: accept HIP" protocol=139
add action=accept chain=forward comment="defconf: accept IKE" dst-port=\
500,4500 protocol=udp
add action=accept chain=forward comment="defconf: accept ipsec AH" protocol=\
ipsec-ah
add action=accept chain=forward comment="defconf: accept ipsec ESP" protocol=\
ipsec-esp
add action=accept chain=forward comment=\
"defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec
add action=drop chain=forward comment=\
"defconf: drop everything else not coming from LAN" in-interface-list=\
!LAN
/system clock
set time-zone-name=Europe/Berlin
/system identity
set name=Router
/system logging
add action=remote topics=critical,error,warning
/system note
set show-at-login=no
/system ntp client
set enabled=yes
/system ntp client servers
add address=0.de.pool.ntp.org
add address=1.de.pool.ntp.org
add address=2.de.pool.ntp.org
add address=3.de.pool.ntp.org
/system routerboard settings
set auto-upgrade=yes enter-setup-on=delete-key
/system scheduler
add comment=strato interval=1h name=dyndns on-event=\
"/system script run dyndns" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2024-10-20 start-time=17:57:15
add name=global-scripts on-event=\
"/system/script { run global-config; run global-functions; }" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
add interval=1d name=ScriptInstallUpdate on-event=\
":global ScriptInstallUpdate; \$ScriptInstallUpdate;" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
add interval=15m name=dhcp-to-dns on-event="/system/script/run dhcp-to-dns;" \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
/system script
add comment=strato dont-require-permissions=no name=dyndns owner=admin \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source=":global ddnsuser \"REDACTED\"\
\n:global ddnspass \"REDACTED\"\
\n:global theinterface \"telekom\"\
\n:global ddnshost1 \"REDACTEDREDACTED\"\
\n\
\n:global ipddns\
\n:global ipfresh [/ip address get [find where interface=\$theinterface] v\
alue-name=address]\
\n\
\n:if ([ :typeof \$ipfresh ] = nil ) do={\
\n :log info (\"DynDNS: No ip address on \$theinterface.\")\
\n} else={\
\n :for i from=( [:len \$ipfresh] - 1) to=0 do={ \
\n :if ( [:pick \$ipfresh \$i] = \"/\") do={\
\n :set ipfresh [:pick \$ipfresh 0 \$i];\
\n }\
\n }\
\n :if (\$ipddns != \$ipfresh) do={\
\n :log info (\"DynDNS: IP-DynDNS = \$ipddns\")\
\n :log info (\"DynDNS: IP-Fresh = \$ipfresh\")\
\n :log info (\"DynDNS: Update IP needed. Sending UPDATE...!\")\
\n :global str1 \"/nic/update\\\?hostname=\$ddnshost1&myip=\$ipfresh\"\
\n /tool fetch address=dyndns.strato.com src-path=\$str1 user=\$ddnsuse\
r password=\$ddnspass mode=https dst-path=(\"/DynDNS.\$ddnshost1\")\
\n :delay 1\
\n :global str1 [/file find name=\"DynDNS.\$ddnshost1\"];\
\n /file remove \$str1\
\n :global ipddns \$ipfresh\
\n :log info \"DynDNS: IP updated to \$ipfresh!\"\
\n } else={\
\n :log info \"DynDNS: dont need changes\";\
\n }\
\n}"
add dont-require-permissions=no name=global-config owner=global-config \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source="#!rsc by RouterOS\
\n# RouterOS script: global-config\
\n# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# global configuration\
\n# https://git.eworm.de/cgit/routeros-scripts/about/\
\n\
\n# Set this to 'true' to disable news and change notifications.\
\n:global NoNewsAndChangesNotification false;\
\n\
\n# Add extra text (or emojis) in notification tags.\
\n:global IdentityExtra \"\";\
\n\
\n# This is used in DNS scripts ('ipsec-to-dns' and fallback in 'dhcp-to-d\
ns')\
\n# and backup scripts for file names.\
\n:global Domain \"example.com\";\
\n\
\n# You can send e-mail notifications. Configure the system's mail setting\
s\
\n# (/tool/e-mail), then install the module:\
\n# \$ScriptInstallUpdate mod/notification-email\
\n# The to-address needs to be filled; cc-address can be empty, one addres\
s\
\n# or a comma separated list of addresses.\
\n:global EmailGeneralTo \"\";\
\n:global EmailGeneralCc \"\";\
\n#:global EmailGeneralTo \"mail@example.com\";\
\n#:global EmailGeneralCc \"another@example.com,third@example.com\";\
\n\
\n# You can send Telegram notifications. Register a bot\
\n# and add the token and chat ids here, then install the module:\
\n# \$ScriptInstallUpdate mod/notification-telegram\
\n:global TelegramTokenId \"\";\
\n:global TelegramChatId \"\";\
\n#:global TelegramTokenId \"123456:ABCDEF-GHI\";\
\n#:global TelegramChatId \"12345678\";\
\n# Using telegram-chat you have to define trusted chat ids (not group ids\
!)\
\n# or user names. Groups allow to chat with devices simultaneously.\
\n#:global TelegramChatIdsTrusted {\
\n# \"12345678\";\
\n# \"example_user\";\
\n#};\
\n:global TelegramChatGroups \"(all)\";\
\n#:global TelegramChatGroups \"(all|home|office)\";\
\n\
\n# You can send Matrix notifications. Configure these settings and\
\n# install the module:\
\n# \$ScriptInstallUpdate mod/notification-matrix\
\n:global MatrixHomeServer \"\";\
\n:global MatrixAccessToken \"\";\
\n:global MatrixRoom \"\";\
\n#:global MatrixHomeServer \"matrix.org\";\
\n#:global MatrixAccessToken \"123456ABCDEFGHI...\";\
\n#:global MatrixRoom \"!example:matrix.org\";\
\n\
\n# You can send Ntfy notifications. Configure these settings and\
\n# install the module:\
\n# \$ScriptInstallUpdate mod/notification-ntfy\
\n:global NtfyServer \"ntfy.sh\";\
\n:global NtfyServerUser [];\
\n:global NtfyServerPass [];\
\n:global NtfyServerToken [];\
\n:global NtfyTopic \"\";\
\n\
\n# It is possible to override e-mail, Telegram, Matrix and Ntfy setting\
\n# for every script. This is done in arrays, where 'Override' is appended\
\n# to the variable name, like this:\
\n#:global EmailGeneralToOverride {\
\n# \"check-certificates\"=\"override@example.com\";\
\n# \"backup-email\"=\"backup@example.com\";\
\n#}\
\n\
\n# Toggle this to disable symbols in notifications.\
\n:global NotificationsWithSymbols true;\
\n# Toggle this to disable color output in terminal/cli.\
\n:global TerminalColorOutput true;\
\n\
\n# This defines what backups to generate and what password to use.\
\n:global BackupSendBinary false;\
\n:global BackupSendExport true;\
\n:global BackupSendGlobalConfig true;\
\n:global BackupPassword \"v3ry-s3cr3t\";\
\n:global BackupRandomDelay 0;\
\n# These credentials are used to upload backup and config export files.\
\n# SFTP authentication is tricky, you may have to limit authentication\
\n# methods for your SSH server.\
\n:global BackupUploadUrl \"sftp://example.com/backup/\";\
\n:global BackupUploadUser \"mikrotik\";\
\n:global BackupUploadPass \"v3ry-s3cr3t\";\
\n# Copy the RouterOS installation to backup partition before feature upda\
te.\
\n:global BackupPartitionCopyBeforeFeatureUpdate false;\
\n\
\n# This defines the settings for firewall address-lists (fw-addr-lists).\
\n:global FwAddrLists {\
\n# \"allow\"={\
\n# { url=\"https://git.eworm.de/cgit/routeros-scripts/plain/fw-addr-li\
sts.d/allow\";\
\n# cert=\"ISRG Root X2\"; timeout=1w };\
\n# };\
\n \"block\"={\
\n# { url=\"https://git.eworm.de/cgit/routeros-scripts/plain/fw-addr-li\
sts.d/block\";\
\n# cert=\"ISRG Root X2\" };\
\n { url=\"https://feodotracker.abuse.ch/downloads/ipblocklist_recommen\
ded.txt\";\
\n cert=\"GlobalSign\" };\
\n { url=\"https://sslbl.abuse.ch/blacklist/sslipblacklist.txt\";\
\n cert=\"GlobalSign\" };\
\n { url=\"https://www.dshield.org/block.txt\"; cidr=\"/24\";\
\n cert=\"ISRG Root X1\" };\
\n { url=\"https://lists.blocklist.de/lists/strongips.txt\";\
\n cert=\"Certum Trusted Network CA\" };\
\n# { url=\"https://www.spamhaus.org/drop/drop_v4.json\";\
\n# cert=\"ISRG Root X1\" };\
\n# { url=\"https://www.spamhaus.org/drop/drop_v6.json\";\
\n# cert=\"ISRG Root X1\" };\
\n };\
\n# \"mikrotik\"={\
\n# { url=\"https://git.eworm.de/cgit/routeros-scripts/plain/fw-addr-li\
sts.d/mikrotik\";\
\n# cert=\"ISRG Root X2\"; timeout=1w };\
\n# };\
\n};\
\n:global FwAddrListTimeOut 1d;\
\n\
\n# This defines what log messages to filter or include by topic or messag\
e\
\n# text. Regular expressions are supported. Do *NOT* set an empty string,\
\n# that will filter or include everything!\
\n# These are filters, so excluding messages from forwarding.\
\n:global LogForwardFilter \"(debug|info|packet|raw)\";\
\n:global LogForwardFilterMessage [];\
\n#:global LogForwardFilterMessage \"message text\";\
\n#:global LogForwardFilterMessage \"(message text|another text|...)\";\
\n# ... and another setting with reverse logic. This includes messages eve\
n\
\n# if filtered above.\
\n:global LogForwardInclude [];\
\n:global LogForwardIncludeMessage [];\
\n#:global LogForwardInclude \"account\";\
\n#:global LogForwardIncludeMessage \"message text\";\
\n\
\n# Specify an address to enable auto update to version assumed safe.\
\n# The configured channel (bugfix, current, release-candidate) is appende\
d.\
\n:global SafeUpdateUrl \"\";\
\n#:global SafeUpdateUrl \"https://example.com/ros/safe-update/\";\
\n# Allow to install patch updates automatically.\
\n:global SafeUpdatePatch false;\
\n# Allow to install updates automatically if seen in neighbor list.\
\n:global SafeUpdateNeighbor false;\
\n:global SafeUpdateNeighborIdentity \"\";\
\n# Install *ALL* updates automatically!\
\n# Set to all upper-case \"Yes, please!\" to enable.\
\n:global SafeUpdateAll \"no\";\
\n\
\n# Defer the reboot for night on automatic (non-interactive) update\
\n:global PackagesUpdateDeferReboot false;\
\n\
\n# These thresholds control when to send health notification\
\n# on temperature and voltage.\
\n:global CheckHealthTemperature {\
\n temperature=50;\
\n cpu-temperature=70;\
\n board-temperature1=50;\
\n board-temperature2=50;\
\n};\
\n# This is deviation on recovery threshold against notification flooding.\
\n:global CheckHealthTemperatureDeviation 3;\
\n:global CheckHealthVoltageLow 115;\
\n:global CheckHealthVoltagePercent 10;\
\n\
\n# Access-list entries matching this comment are updated\
\n# with daily pseudo-random PSK.\
\n:global DailyPskMatchComment \"Daily PSK\";\
\n:global DailyPskQrCodeUrl \"https://www.eworm.de/cgi-bin/cqrlogo-wifi.cg\
i\";\
\n:global DailyPskSecrets {\
\n { \"Abusive\"; \"Aggressive\"; \"Bored\"; \"Chemical\"; \"Cold\";\
\n \"Cruel\"; \"Curved\"; \"Delightful\"; \"Discreet\"; \"Elite\";\
\n \"Evasive\"; \"Faded\"; \"Flat\"; \"Future\"; \"Grandiose\";\
\n \"Hanging\"; \"Humorous\"; \"Interesting\"; \"Magenta\";\
\n \"Magnificent\"; \"Numerous\"; \"Optimal\"; \"Pathetic\";\
\n \"Possessive\"; \"Remarkable\"; \"Rightful\"; \"Ruthless\";\
\n \"Stale\"; \"Unusual\"; \"Useless\"; \"Various\" };\
\n { \"Adhesive\"; \"Amusing\"; \"Astonishing\"; \"Frantic\";\
\n \"Kindhearted\"; \"Limping\"; \"Roasted\"; \"Robust\";\
\n \"Staking\"; \"Thundering\"; \"Ultra\"; \"Unreal\" };\
\n { \"Belief\"; \"Button\"; \"Curtain\"; \"Edge\"; \"Jewel\";\
\n \"String\"; \"Whistle\" }\
\n};\
\n\
\n# Specify how to assemble DNS names in ipsec-to-dns.\
\n:global HostNameInZone true;\
\n:global PrefixInZone true;\
\n\
\n# Run different commands with multiple mode-button presses.\
\n:global ModeButton {\
\n 1=\"/system/leds/settings/set all-leds-off=(({ \\\"never\\\"=\\\"immed\
iate\\\"; \\\"immediate\\\"=\\\"never\\\" })->[ get all-leds-off ]);\";\
\n 2=\":global Identity; :global SendNotification; :global SymbolForNotif\
ication; \\\$SendNotification ([ \\\$SymbolForNotification \\\"earth\\\" ]\
\_. \\\"Hello...\\\") (\\\"Hello world, \\\" . \\\$Identity . \\\" calling\
!\\\");\";\
\n 3=\"/system/shutdown;\";\
\n 4=\"/system/reboot;\";\
\n 5=\":global BridgePortVlan; \\\$BridgePortVlan alt;\";\
\n# add more here...\
\n};\
\n# This led gives visual feedback if type is 'on' or 'off'.\
\n:global ModeButtonLED \"user-led\";\
\n\
\n# Run commands on SMS action.\
\n:global SmsAction {\
\n bridge-port-vlan-alt=\":global BridgePortVlan; \\\$BridgePortVlan alt;\
\";\
\n reboot=\"/system/reboot;\";\
\n shutdown=\"/system/shutdown;\";\
\n# add more here...\
\n};\
\n\
\n# Run commands by hooking into SMS forward.\
\n:global SmsForwardHooks {\
\n { match=\"magic string\";\
\n allowed-number=\"12345678\";\
\n command=\"/system/script/run ...\" };\
\n# add more here...\
\n};\
\n\
\n# This is the address used to send gps data to.\
\n:global GpsTrackUrl \"https://example.com/index.php\";\
\n\
\n# This is the base url to fetch scripts from.\
\n:global ScriptUpdatesBaseUrl \"https://git.eworm.de/cgit/routeros-script\
s/plain/\";\
\n# alternative urls - main: stable code - next: currently in development\
\n#:global ScriptUpdatesBaseUrl \"https://raw.githubusercontent.com/eworm-\
de/routeros-scripts/main/\";\
\n#:global ScriptUpdatesBaseUrl \"https://raw.githubusercontent.com/eworm-\
de/routeros-scripts/next/\";\
\n#:global ScriptUpdatesBaseUrl \"https://gitlab.com/eworm-de/routeros-scr\
ipts/raw/main/\";\
\n#:global ScriptUpdatesBaseUrl \"https://gitlab.com/eworm-de/routeros-scr\
ipts/raw/next/\";\
\n:global ScriptUpdatesUrlSuffix \"\";\
\n# use next branch with default url (git.eworm.de)\
\n#:global ScriptUpdatesUrlSuffix \"\?h=next\";\
\n\
\n# Use this for defaults with \$ScriptRunOnce\
\n# Install module with:\
\n# \$ScriptInstallUpdate mod/scriptrunonce\
\n:global ScriptRunOnceBaseUrl \"\";\
\n:global ScriptRunOnceUrlSuffix \"\";\
\n\
\n# This project is developed in private spare time and usage is free of c\
harge\
\n# for you. If you like the scripts and think this is of value for you or\
\_your\
\n# business please consider a donation:\
\n# https://git.eworm.de/cgit/routeros-scripts/about/#donate\
\n# Enable this to silence donation hint.\
\n:global IDonate false;\
\n\
\n# Use this for certificate auto-renew\
\n:global CertRenewUrl \"\";\
\n#:global CertRenewUrl \"https://example.com/certificates/\";\
\n:global CertRenewTime 3w;\
\n:global CertRenewPass {\
\n \"v3ry-s3cr3t\";\
\n \"4n0th3r-s3cr3t\";\
\n};\
\n:global CertWarnTime 2w;\
\n:global CertIssuedExportPass {\
\n \"cert1-cn\"=\"v3ry-s3cr3t\";\
\n \"cert2-cn\"=\"4n0th3r-s3cr3t\";\
\n};\
\n\
\n# load custom settings from overlay and snippets\
\n# Warning: Do *NOT* copy this code to overlay!\
\n:foreach Script in=([ /system/script/find where name=\"global-config-ove\
rlay\" ], \\\
\n [ /system/script/find where name~\"^global-config-ov\
erlay.d/\" ]) do={\
\n :do {\
\n /system/script/run \$Script;\
\n } on-error={\
\n :log error (\"Loading configuration from overlay or snippet \" . \\\
\n [ /system/script/get \$Script name ] . \" failed!\");\
\n }\
\n}\
\n"
add dont-require-permissions=no name=global-config-overlay owner=\
global-config-overlay policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
\_Overlay for global configuration by RouterOS Scripts\
\n# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# global configuration, custom overlay\
\n# https://git.eworm.de/cgit/routeros-scripts/about/#editing-configuratio\
n\
\n\
\n# Copy relevant configuration from global-config, paste and modify it he\
re.\
\n# https://git.eworm.de/cgit/routeros-scripts/about/global-config.rsc\
\n\
\n\
\n# End of global-config-overlay\
\n"
add dont-require-permissions=no name=global-functions owner=global-functions \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source="#!rsc by RouterOS\
\n# RouterOS script: global-functions\
\n# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de>\
\n# Michael Gisbers <michael@gisbers.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# requires RouterOS, version=7.14\
\n#\
\n# global functions\
\n# https://git.eworm.de/cgit/routeros-scripts/about/\
\n\
\n:local ScriptName [ :jobname ];\
\n\
\n# expected configuration version\
\n:global ExpectedConfigVersion 131;\
\n\
\n# global variables not to be changed by user\
\n:global GlobalFunctionsReady false;\
\n:global Identity [ /system/identity/get name ];\
\n\
\n# global functions\
\n:global AlignRight;\
\n:global CertificateAvailable;\
\n:global CertificateDownload;\
\n:global CertificateNameByCN;\
\n:global CharacterMultiply;\
\n:global CharacterReplace;\
\n:global CleanFilePath;\
\n:global CleanName;\
\n:global DeviceInfo;\
\n:global Dos2Unix;\
\n:global DownloadPackage;\
\n:global EitherOr;\
\n:global EscapeForRegEx;\
\n:global ExitError;\
\n:global FetchHuge;\
\n:global FetchUserAgentStr;\
\n:global FormatLine;\
\n:global FormatMultiLines;\
\n:global GetMacVendor;\
\n:global GetRandom20CharAlNum;\
\n:global GetRandom20CharHex;\
\n:global GetRandomNumber;\
\n:global Grep;\
\n:global HexToNum;\
\n:global HumanReadableNum;\
\n:global IfThenElse;\
\n:global IsDefaultRouteReachable;\
\n:global IsDNSResolving;\
\n:global IsFullyConnected;\
\n:global IsMacLocallyAdministered;\
\n:global IsTimeSync;\
\n:global LogPrint;\
\n:global LogPrintOnce;\
\n:global MAX;\
\n:global MIN;\
\n:global MkDir;\
\n:global NotificationFunctions;\
\n:global ParseDate;\
\n:global ParseKeyValueStore;\
\n:global PrettyPrint;\
\n:global ProtocolStrip;\
\n:global RandomDelay;\
\n:global RequiredRouterOS;\
\n:global ScriptFromTerminal;\
\n:global ScriptInstallUpdate;\
\n:global ScriptLock;\
\n:global SendNotification;\
\n:global SendNotification2;\
\n:global SymbolByUnicodeName;\
\n:global SymbolForNotification;\
\n:global Unix2Dos;\
\n:global UrlEncode;\
\n:global ValidateSyntax;\
\n:global VersionToNum;\
\n:global WaitDefaultRouteReachable;\
\n:global WaitDNSResolving;\
\n:global WaitForFile;\
\n:global WaitFullyConnected;\
\n:global WaitTimeSync;\
\n\
\n# align string to the right\
\n:set AlignRight do={\
\n :local Input [ :tostr \$1 ];\
\n :local Len [ :tonum \$2 ];\
\n\
\n :global CharacterMultiply;\
\n :global EitherOr;\
\n\
\n :set Len [ \$EitherOr \$Len 8 ];\
\n :local Spaces [ \$CharacterMultiply \" \" \$Len ];\
\n\
\n :return ([ :pick \$Spaces 0 (\$Len - [ :len \$Input ]) ] . \$Input);\
\n}\
\n\
\n# check and download required certificate\
\n:set CertificateAvailable do={\
\n :local CommonName [ :tostr \$1 ];\
\n\
\n :global CertificateDownload;\
\n :global LogPrint;\
\n :global ParseKeyValueStore;\
\n\
\n :if ([ /system/resource/get free-hdd-space ] < 8388608 && \\\
\n [ /certificate/settings/get crl-download ] = true && \\\
\n [ /certificate/settings/get crl-store ] = \"system\") do={\
\n \$LogPrint warning \$0 (\"This system has low free flash space but \
\" . \\\
\n \"is configured to download certificate CRLs to system!\");\
\n }\
\n\
\n :if ([ :len \$CommonName ] = 0) do={\
\n \$LogPrint warning \$0 (\"No CommonName given!\");\
\n :return false;\
\n }\
\n\
\n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\
) do={\
\n \$LogPrint info \$0 (\"Certificate with CommonName '\" . \$CommonNam\
e . \"' not available.\");\
\n :if ([ \$CertificateDownload \$CommonName ] = false) do={\
\n :return false;\
\n }\
\n }\
\n\
\n :local CertVal [ /certificate/get [ find where common-name=\$CommonNam\
e ] ];\
\n :while ((\$CertVal->\"akid\") != \"\" && (\$CertVal->\"akid\") != (\$C\
ertVal->\"skid\")) do={\
\n :if ([ :len [ /certificate/find where skid=(\$CertVal->\"akid\") ] ]\
\_= 0) do={\
\n \$LogPrint info \$0 (\"Certificate chain for '\" . \$CommonName . \
\\\
\n \"' is incomplete, missing '\" . ([ \$ParseKeyValueStore (\$Cert\
Val->\"issuer\") ]->\"CN\") . \"'.\");\
\n :if ([ \$CertificateDownload \$CommonName ] = false) do={\
\n :return false;\
\n }\
\n }\
\n :set CertVal [ /certificate/get [ find where skid=(\$CertVal->\"akid\
\") ] ];\
\n }\
\n :return true;\
\n}\
\n\
\n# download and import certificate\
\n:set CertificateDownload do={\
\n :local CommonName [ :tostr \$1 ];\
\n\
\n :global ScriptUpdatesBaseUrl;\
\n :global ScriptUpdatesUrlSuffix;\
\n\
\n :global CertificateAvailable;\
\n :global CertificateNameByCN;\
\n :global CleanName;\
\n :global FetchUserAgentStr;\
\n :global LogPrint;\
\n :global WaitForFile;\
\n\
\n \$LogPrint info \$0 (\"Downloading and importing certificate with \" .\
\_\\\
\n \"CommonName '\" . \$CommonName . \"'.\");\
\n :local FileName ([ \$CleanName \$CommonName ] . \".pem\");\
\n :do {\
\n /tool/fetch check-certificate=yes-without-crl http-header-field=({ [\
\_\$FetchUserAgentStr \$0 ] }) \\\
\n (\$ScriptUpdatesBaseUrl . \"certs/\" . \$FileName . \$ScriptUpdate\
sUrlSuffix) \\\
\n dst-path=\$FileName as-value;\
\n \$WaitForFile \$FileName;\
\n } on-error={\
\n \$LogPrint warning \$0 (\"Failed downloading certificate with Common\
Name '\" . \$CommonName . \\\
\n \"' from repository! Trying fallback to mkcert.org...\");\
\n :do {\
\n :if ([ \$CertificateAvailable \"ISRG Root X1\" ] = false) do={\
\n \$LogPrint error \$0 (\"Downloading required certificate failed.\
\");\
\n :return false;\
\n }\
\n /tool/fetch check-certificate=yes-without-crl http-header-field=({\
\_[ \$FetchUserAgentStr \$0 ] }) \\\
\n \"https://mkcert.org/generate/\" http-data=[ :serialize to=json \
({ \$CommonName }) ] \\\
\n dst-path=\$FileName as-value;\
\n \$WaitForFile \$FileName;\
\n :if ([ /file/get \$FileName size ] = 0) do={\
\n /file/remove \$FileName;\
\n :error false;\
\n }\
\n } on-error={\
\n \$LogPrint warning \$0 (\"Failed downloading certificate with Comm\
onName '\" . \$CommonName . \"'!\");\
\n :return false;\
\n }\
\n }\
\n\
\n /certificate/import file-name=\$FileName passphrase=\"\" as-value;\
\n :delay 1s;\
\n /file/remove [ find where name=\$FileName ];\
\n\
\n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\
) do={\
\n /certificate/remove [ find where name~(\"^\" . \$FileName . \"_[0-9]\
+\\\$\") ];\
\n \$LogPrint warning \$0 (\"Certificate with CommonName '\" . \$Common\
Name . \"' still unavailable!\");\
\n :return false;\
\n }\
\n\
\n :foreach Cert in=[ /certificate/find where name~(\"^\" . \$FileName . \
\"_[0-9]+\\\$\") ] do={\
\n \$CertificateNameByCN [ /certificate/get \$Cert common-name ];\
\n }\
\n :return true;\
\n}\
\n\
\n# name a certificate by its common-name\
\n:set CertificateNameByCN do={\
\n :local CommonName [ :tostr \$1 ];\
\n\
\n :global CleanName;\
\n\
\n :local Cert [ /certificate/find where common-name=\$CommonName ];\
\n /certificate/set \$Cert name=[ \$CleanName \$CommonName ];\
\n}\
\n\
\n# multiply given character(s)\
\n:set CharacterMultiply do={\
\n :local Return \"\";\
\n :for I from=1 to=\$2 do={\
\n :set Return (\$Return . \$1);\
\n }\
\n :return \$Return;\
\n}\
\n\
\n# character replace\
\n:set CharacterReplace do={\
\n :local String [ :tostr \$1 ];\
\n :local ReplaceFrom [ :tostr \$2 ];\
\n :local ReplaceWith [ :tostr \$3 ];\
\n :local Return \"\";\
\n\
\n :if (\$ReplaceFrom = \"\") do={\
\n :return \$String;\
\n }\
\n\
\n :while ([ :typeof [ :find \$String \$ReplaceFrom ] ] != \"nil\") do={\
\n :local Pos [ :find \$String \$ReplaceFrom ];\
\n :set Return (\$Return . [ :pick \$String 0 \$Pos ] . \$ReplaceWith);\
\n :set String [ :pick \$String (\$Pos + [ :len \$ReplaceFrom ]) [ :len\
\_\$String ] ];\
\n }\
\n\
\n :return (\$Return . \$String);\
\n}\
\n\
\n# clean file path\
\n:set CleanFilePath do={\
\n :local Path [ :tostr \$1 ];\
\n\
\n :global CharacterReplace;\
\n\
\n :while (\$Path ~ \"//\") do={\
\n :set \$Path [ \$CharacterReplace \$Path \"//\" \"/\" ];\
\n }\
\n :if ([ :pick \$Path 0 ] = \"/\") do={\
\n :set Path [ :pick \$Path 1 [ :len \$Path ] ];\
\n }\
\n :if ([ :pick \$Path ([ :len \$Path ] - 1) ] = \"/\") do={\
\n :set Path [ :pick \$Path 0 ([ :len \$Path ] - 1) ];\
\n }\
\n\
\n :return \$Path;\
\n}\
\n\
\n# clean name for DNS, file and more\
\n:set CleanName do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :local Return \"\";\
\n\
\n :for I from=0 to=([ :len \$Input ] - 1) do={\
\n :local Char [ :pick \$Input \$I ];\
\n :if ([ :typeof [ find \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr\
stuvwxyz0123456789\" \$Char ] ] = \"nil\") do={\
\n :do {\
\n :if ([ :len \$Return ] = 0) do={\
\n :error true;\
\n }\
\n :if ([ :pick \$Return ([ :len \$Return ] - 1) ] = \"-\") do={\
\n :error true;\
\n }\
\n :set Char \"-\";\
\n } on-error={\
\n :set Char \"\";\
\n }\
\n }\
\n :set Return (\$Return . \$Char);\
\n }\
\n :return \$Return;\
\n}\
\n\
\n# get readable device info\
\n:set DeviceInfo do={\
\n :global ExpectedConfigVersion;\
\n :global Identity;\
\n\
\n :global IfThenElse;\
\n :global FormatLine;\
\n\
\n :local License [ /system/license/get ];\
\n :local Resource [ /system/resource/get ];\
\n :local RouterBoard;\
\n :do {\
\n :set RouterBoard [[ :parse \"/system/routerboard/get\" ]];\
\n } on-error={ }\
\n :local Snmp [ /snmp/get ];\
\n :local Update [ /system/package/update/get ];\
\n\
\n :return ( \\\
\n [ \$FormatLine \"Hostname\" \$Identity ] . \"\\n\" . \\\
\n [ \$IfThenElse ([ :len (\$Snmp->\"location\") ] > 0) \\\
\n ([ \$FormatLine \"Location\" (\$Snmp->\"location\") ] . \"\\n\") ]\
\_. \\\
\n [ \$IfThenElse ([ :len (\$Snmp->\"contact\") ] > 0) \\\
\n ([ \$FormatLine \"Contact\" (\$Snmp->\"contact\") ] . \"\\n\") ] .\
\_\\\
\n [ \$FormatLine \"Board name\" (\$Resource->\"board-name\") ] . \"\\n\
\" . \\\
\n [ \$FormatLine \"Architecture\" (\$Resource->\"architecture-name\") \
] . \"\\n\" . \\\
\n [ \$IfThenElse (\$RouterBoard->\"routerboard\" = true) \\\
\n ([ \$FormatLine \"Model\" (\$RouterBoard->\"model\") ] . \\\
\n [ \$IfThenElse ([ :len (\$RouterBoard->\"revision\") ] > 0) \\\
\n (\" \" . \$RouterBoard->\"revision\") ] . \"\\n\" . \\\
\n [ \$FormatLine \"Serial number\" (\$RouterBoard->\"serial-number\
\") ] . \"\\n\") ] . \\\
\n [ \$IfThenElse ([ :len (\$License->\"level\") ] > 0) \\\
\n ([ \$FormatLine \"License\" (\$License->\"level\") ] . \"\\n\") ] \
. \\\
\n \"RouterOS:\\n\" . \\\
\n [ \$FormatLine \" Channel\" (\$Update->\"channel\") ] . \"\\n\" .\
\_\\\
\n [ \$FormatLine \" Installed\" (\$Update->\"installed-version\") ]\
\_. \"\\n\" . \\\
\n [ \$IfThenElse ([ :typeof (\$Update->\"latest-version\") ] != \"noth\
ing\" && \\\
\n \$Update->\"installed-version\" != \$Update->\"latest-version\")\
\_\\\
\n ([ \$FormatLine \" Available\" (\$Update->\"latest-version\") ]\
\_. \"\\n\") ] . \\\
\n [ \$IfThenElse (\$RouterBoard->\"routerboard\" = true && \\\
\n \$RouterBoard->\"current-firmware\" != \$RouterBoard->\"upgrade-\
firmware\") \\\
\n ([ \$FormatLine \" Firmware\" (\$RouterBoard->\"current-firmwar\
e\") ] . \"\\n\") ] . \\\
\n \"RouterOS-Scripts:\\n\" . \\\
\n [ \$FormatLine \" Version\" \$ExpectedConfigVersion ]);\
\n}\
\n\
\n# convert line endings, DOS -> UNIX\
\n:set Dos2Unix do={\
\n :return [ :tolf [ :tostr \$1 ] ];\
\n}\
\n\
\n# download package from upgrade server\
\n:set DownloadPackage do={\
\n :local PkgName [ :tostr \$1 ];\
\n :local PkgVer [ :tostr \$2 ];\
\n :local PkgArch [ :tostr \$3 ];\
\n :local PkgDir [ :tostr \$4 ];\
\n\
\n :global CertificateAvailable;\
\n :global CleanFilePath;\
\n :global LogPrint;\
\n :global MkDir;\
\n :global WaitForFile;\
\n\
\n :if ([ :len \$PkgName ] = 0) do={ :return false; }\
\n :if ([ :len \$PkgVer ] = 0) do={ :set PkgVer [ /system/package/updat\
e/get installed-version ]; }\
\n :if ([ :len \$PkgArch ] = 0) do={ :set PkgArch [ /system/resource/get \
architecture-name ]; }\
\n\
\n :if (\$PkgName = \"system\") do={ :set PkgName \"routeros\"; }\
\n\
\n :local PkgFile (\$PkgName . \"-\" . \$PkgVer . \"-\" . \$PkgArch . \".\
npk\");\
\n :if (\$PkgArch = \"x86_64\") do={ :set PkgFile (\$PkgName . \"-\" . \$\
PkgVer . \".npk\"); }\
\n :local PkgDest [ \$CleanFilePath (\$PkgDir . \"/\" . \$PkgFile) ];\
\n\
\n :if ([ \$MkDir \$PkgDir ] = false) do={\
\n \$LogPrint warning \$0 (\"Failed creating directory, not downloading\
\_package.\");\
\n :return false;\
\n }\
\n\
\n :if ([ :len [ /file/find where name=\$PkgDest type=\"package\" ] ] > 0\
) do={\
\n \$LogPrint info \$0 (\"Package file \" . \$PkgName . \" already exis\
ts.\");\
\n :return true;\
\n }\
\n\
\n :if ([ \$CertificateAvailable \"ISRG Root X1\" ] = false) do={\
\n \$LogPrint error \$0 (\"Downloading required certificate failed.\");\
\n :return false;\
\n }\
\n\
\n :local Url (\"https://upgrade.mikrotik.com/routeros/\" . \$PkgVer . \"\
/\" . \$PkgFile);\
\n \$LogPrint info \$0 (\"Downloading package file '\" . \$PkgName . \"'.\
..\");\
\n \$LogPrint debug \$0 (\"... from url: \" . \$Url);\
\n :local Retry 3;\
\n :while (\$Retry > 0) do={\
\n :do {\
\n /tool/fetch check-certificate=yes-without-crl \$Url dst-path=\$Pkg\
Dest;\
\n \$WaitForFile \$PkgDest;\
\n\
\n :if ([ /file/get [ find where name=\$PkgDest ] type ] = \"package\
\") do={\
\n :return true;\
\n }\
\n } on-error={\
\n \$LogPrint debug \$0 (\"Downloading package file failed.\");\
\n }\
\n\
\n /file/remove [ find where name=\$PkgDest ];\
\n :set Retry (\$Retry - 1);\
\n }\
\n\
\n \$LogPrint warning \$0 (\"Downloading package file '\" . \$PkgName . \
\"' failed.\");\
\n :return false;\
\n}\
\n\
\n# return either first (if \"true\") or second\
\n:set EitherOr do={\
\n :global IfThenElse;\
\n\
\n :if ([ :typeof \$1 ] = \"num\") do={\
\n :return [ \$IfThenElse (\$1 != 0) \$1 \$2 ];\
\n }\
\n :if ([ :typeof \$1 ] = \"time\") do={\
\n :return [ \$IfThenElse (\$1 > 0s) \$1 \$2 ];\
\n }\
\n # this works for boolean values, literal ones with parentheses\
\n :return [ \$IfThenElse ([ :len [ :tostr \$1 ] ] > 0) \$1 \$2 ];\
\n}\
\n\
\n# escape for regular expression\
\n:set EscapeForRegEx do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :if ([ :len \$Input ] = 0) do={\
\n :return \"\";\
\n }\
\n\
\n :local Return \"\";\
\n :local Chars (\"^.[]\\\$()|*+\?{}\\\\\");\
\n\
\n :for I from=0 to=([ :len \$Input ] - 1) do={\
\n :local Char [ :pick \$Input \$I ];\
\n :if ([ :find \$Chars \$Char ]) do={\
\n :set Char (\"\\\\\" . \$Char);\
\n }\
\n :set Return (\$Return . \$Char);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# simple macro to print error message on unintentional error\
\n:set ExitError do={\
\n :local ExitOK [ :tostr \$1 ];\
\n :local Name [ :tostr \$2 ];\
\n\
\n :global IfThenElse;\
\n :global LogPrint; \
\n\
\n :if (\$ExitOK = \"false\") do={\
\n \$LogPrint error \$Name ([ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\\\
\$\") \\\
\n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\
ror.\");\
\n }\
\n}\
\n\
\n# fetch huge data to file, read in chunks\
\n:set FetchHuge do={\
\n :local ScriptName [ :tostr \$1 ];\
\n :local Url [ :tostr \$2 ];\
\n :local CheckCert [ :tostr \$3 ];\
\n\
\n :global CleanName;\
\n :global FetchUserAgentStr;\
\n :global GetRandom20CharAlNum;\
\n :global IfThenElse;\
\n :global LogPrint;\
\n :global MkDir;\
\n :global WaitForFile;\
\n\
\n :set CheckCert [ \$IfThenElse (\$CheckCert = \"false\") \"no\" \"yes-w\
ithout-crl\" ];\
\n\
\n :local DirName (\"tmpfs/\" . [ \$CleanName \$ScriptName ]);\
\n :if ([ \$MkDir \$DirName ] = false) do={\
\n \$LogPrint error \$0 (\"Failed creating directory!\");\
\n :return false;\
\n }\
\n\
\n :local FileName (\$DirName . \"/\" . [ \$CleanName \$0 ] . \"-\" . [ \
\$GetRandom20CharAlNum ]);\
\n :do {\
\n /tool/fetch check-certificate=\$CheckCert \$Url dst-path=\$FileName \
\\\
\n http-header-field=({ [ \$FetchUserAgentStr \$ScriptName ] }) as-va\
lue;\
\n } on-error={\
\n :if ([ \$WaitForFile \$FileName 500ms ] = true) do={\
\n /file/remove \$FileName;\
\n }\
\n \$LogPrint debug \$0 (\"Failed downloading from: \" . \$Url);\
\n /file/remove \$DirName;\
\n :return false;\
\n }\
\n \$WaitForFile \$FileName;\
\n\
\n :local FileSize [ /file/get \$FileName size ];\
\n :local Return \"\";\
\n :local VarSize 0;\
\n :while (\$VarSize != \$FileSize) do={\
\n :set Return (\$Return . ([ /file/read offset=\$VarSize chunk-size=32\
768 file=\$FileName as-value ]->\"data\"));\
\n :set FileSize [ /file/get \$FileName size ];\
\n :set VarSize [ :len \$Return ];\
\n :if (\$VarSize > \$FileSize) do={\
\n :delay 100ms;\
\n }\
\n }\
\n /file/remove \$DirName;\
\n :return \$Return;\
\n}\
\n\
\n# generate user agent string for fetch\
\n:set FetchUserAgentStr do={\
\n :local Caller [ :tostr \$1 ];\
\n\
\n :local Resource [ /system/resource/get ];\
\n\
\n :return (\"User-Agent: Mikrotik/\" . \$Resource->\"version\" . \" \" .\
\_\\\
\n \$Resource->\"architecture-name\" . \" \" . \$Caller . \"/Fetch (htt\
ps://rsc.eworm.de/)\");\
\n}\
\n\
\n# format a line for output\
\n:set FormatLine do={\
\n :local Key [ :tostr \$1 ];\
\n :local Value [ :tostr \$2 ];\
\n :local Indent [ :tonum \$3 ];\
\n :local Spaces;\
\n :local Return \"\";\
\n\
\n :global CharacterMultiply;\
\n :global EitherOr;\
\n\
\n :set Indent [ \$EitherOr \$Indent 16 ];\
\n :local Spaces [ \$CharacterMultiply \" \" \$Indent ];\
\n\
\n :if ([ :len \$Key ] > 0) do={ :set Return (\$Key . \":\"); }\
\n :if ([ :len \$Key ] > (\$Indent - 2)) do={\
\n :set Return (\$Return . \"\\n\" . [ :pick \$Spaces 0 \$Indent ] . \$\
Value);\
\n } else={\
\n :set Return (\$Return . [ :pick \$Spaces 0 (\$Indent - [ :len \$Retu\
rn ]) ] . \$Value);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# format multiple lines for output\
\n:set FormatMultiLines do={\
\n :local Key [ :tostr \$1 ];\
\n :local Values [ :toarray \$2 ];\
\n :local Indent [ :tonum \$3 ];\
\n :local Return;\
\n\
\n :global FormatLine;\
\n\
\n :set Return [ \$FormatLine \$Key (\$Values->0) \$Indent ];\
\n :foreach Value in=[ :pick \$Values 1 [ :len \$Values ] ] do={\
\n :set Return (\$Return . \"\\n\" . [ \$FormatLine \"\" \$Value \$Inde\
nt ]);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# get MAC vendor\
\n:set GetMacVendor do={\
\n :local Mac [ :tostr \$1 ];\
\n\
\n :global CertificateAvailable;\
\n :global IsMacLocallyAdministered;\
\n :global LogPrint;\
\n\
\n :if ([ \$IsMacLocallyAdministered \$Mac ] = true) do={\
\n :return \"locally administered\";\
\n }\
\n\
\n :do {\
\n :if ([ \$CertificateAvailable \"GTS Root R4\" ] = false) do={\
\n \$LogPrint warning \$0 (\"Downloading required certificate failed.\
\");\
\n :error false;\
\n }\
\n :local Vendor ([ /tool/fetch check-certificate=yes-without-crl \\\
\n (\"https://api.macvendors.com/\" . [ :pick \$Mac 0 8 ]) output=u\
ser as-value ]->\"data\");\
\n :return \$Vendor;\
\n } on-error={\
\n :do {\
\n /tool/fetch check-certificate=yes-without-crl (\"https://api.macve\
ndors.com/\") \\\
\n output=none as-value;\
\n \$LogPrint debug \$0 (\"The mac vendor is not known in database.\"\
);\
\n } on-error={\
\n \$LogPrint warning \$0 (\"Failed getting mac vendor.\");\
\n }\
\n :return \"unknown vendor\";\
\n }\
\n}\
\n\
\n# generate random 20 chars alphabetical (A-Z & a-z) and numerical (0-9)\
\n:set GetRandom20CharAlNum do={\
\n :global EitherOr;\
\n\
\n :return [ :rndstr length=[ \$EitherOr [ :tonum \$1 ] 20 ] from=\"ABCDE\
FGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\" ];\
\n}\
\n\
\n# generate random 20 chars hex (0-9 and a-f)\
\n:set GetRandom20CharHex do={\
\n :global EitherOr;\
\n\
\n :return [ :rndstr length=[ \$EitherOr [ :tonum \$1 ] 20 ] from=\"01234\
56789abcdef\" ];\
\n}\
\n\
\n# generate random number\
\n:set GetRandomNumber do={\
\n :global EitherOr;\
\n\
\n :return [ :rndnum from=0 to=[ \$EitherOr [ :tonum \$1 ] 4294967295 ] ]\
;\
\n}\
\n\
\n# return first line that matches a pattern\
\n:set Grep do={\
\n :local Input ([ :tostr \$1 ] . \"\\n\");\
\n :local Pattern [ :tostr \$2 ];\
\n\
\n :if ([ :typeof [ :find \$Input \$Pattern ] ] = \"nil\") do={\
\n :return [];\
\n }\
\n\
\n :do {\
\n :local Line [ :pick \$Input 0 [ :find \$Input \"\\n\" ] ];\
\n :if ([ :typeof [ :find \$Line \$Pattern ] ] = \"num\") do={\
\n :return \$Line;\
\n }\
\n :set Input [ :pick \$Input ([ :find \$Input \"\\n\" ] + 1) [ :len \$\
Input ] ];\
\n } while=([ :len \$Input ] > 0);\
\n\
\n :return [];\
\n}\
\n\
\n# convert from hex (string) to num\
\n:set HexToNum do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :global HexToNum;\
\n\
\n :if ([ :pick \$Input 0 ] = \"*\") do={\
\n :return [ \$HexToNum [ :pick \$Input 1 [ :len \$Input ] ] ];\
\n }\
\n\
\n :return [ :tonum (\"0x\" . \$Input) ];\
\n}\
\n\
\n# return human readable number\
\n:set HumanReadableNum do={\
\n :local Input [ :tonum \$1 ];\
\n :local Base [ :tonum \$2 ];\
\n\
\n :global EitherOr;\
\n :global IfThenElse;\
\n\
\n :local Prefix \"kMGTPE\";\
\n :local Pow 1;\
\n\
\n :set Base [ \$EitherOr \$Base 1024 ];\
\n :local Bin [ \$IfThenElse (\$Base = 1024) \"i\" \"\" ];\
\n\
\n :if (\$Input < \$Base) do={\
\n :return \$Input;\
\n }\
\n\
\n :for I from=0 to=[ :len \$Prefix ] do={\
\n :set Pow (\$Pow * \$Base);\
\n :if (\$Input / \$Base < \$Pow) do={\
\n :set Prefix [ :pick \$Prefix \$I ];\
\n :local Tmp1 (\$Input * 100 / \$Pow);\
\n :local Tmp2 (\$Tmp1 / 100);\
\n :if (\$Tmp2 >= 100) do={\
\n :return (\$Tmp2 . \$Prefix . \$Bin);\
\n }\
\n :return (\$Tmp2 . \".\" . \\\
\n [ :pick \$Tmp1 [ :len \$Tmp2 ] ([ :len \$Tmp1 ] - [ :len \$Tmp\
2 ] + 1) ] . \\\
\n \$Prefix . \$Bin);\
\n }\
\n }\
\n}\
\n\
\n# mimic conditional/ternary operator (condition \? consequent : alternat\
ive)\
\n:set IfThenElse do={\
\n :if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] = true) do={\
\n :return \$2;\
\n }\
\n :return \$3;\
\n}\
\n\
\n# check if default route is reachable\
\n:set IsDefaultRouteReachable do={\
\n :if ([ :len [ /ip/route/find where dst-address=0.0.0.0/0 active routin\
g-table=main ] ] > 0) do={\
\n :return true;\
\n }\
\n :return false;\
\n}\
\n\
\n# check if DNS is resolving\
\n:set IsDNSResolving do={\
\n :do {\
\n :resolve \"low-ttl.eworm.de\";\
\n } on-error={\
\n :return false;\
\n }\
\n :return true;\
\n}\
\n\
\n# check if system is is fully connected (default route reachable, DNS re\
solving, time sync)\
\n:set IsFullyConnected do={\
\n :global IsDefaultRouteReachable;\
\n :global IsDNSResolving;\
\n :global IsTimeSync;\
\n\
\n :if ([ \$IsDefaultRouteReachable ] = false) do={\
\n :return false;\
\n }\
\n :if ([ \$IsDNSResolving ] = false) do={\
\n :return false;\
\n }\
\n :if ([ \$IsTimeSync ] = false) do={\
\n :return false;\
\n }\
\n :return true;\
\n}\
\n\
\n# check if mac address is locally administered\
\n:set IsMacLocallyAdministered do={\
\n :if ([ :tonum (\"0x\" . [ :pick \$1 0 [ :find \$1 \":\" ] ]) ] & 2 = 2\
) do={\
\n :return true;\
\n }\
\n :return false;\
\n}\
\n\
\n# check if system time is sync\
\n:set IsTimeSync do={\
\n :global IsTimeSyncCached;\
\n :global IsTimeSyncResetNtp;\
\n\
\n :global LogPrintOnce;\
\n\
\n :if (\$IsTimeSyncCached = true) do={\
\n :return true;\
\n }\
\n\
\n :if ([ /system/ntp/client/get enabled ] = true) do={\
\n :if ([ /system/ntp/client/get status ] = \"synchronized\") do={\
\n :set IsTimeSyncCached true;\
\n :return true;\
\n }\
\n\
\n :local Uptime [ /system/resource/get uptime ];\
\n :if ([ :typeof \$IsTimeSyncResetNtp ] = \"nothing\") do={\
\n :set IsTimeSyncResetNtp \$Uptime;\
\n }\
\n :if (\$Uptime - \$IsTimeSyncResetNtp < 3m) do={\
\n :return false;\
\n }\
\n\
\n \$LogPrintOnce warning \$0 (\"The ntp client is configured, but did \
not sync.\");\
\n :set IsTimeSyncResetNtp \$Uptime;\
\n /system/ntp/client/set enabled=no;\
\n :delay 20ms;\
\n /system/ntp/client/set enabled=yes;\
\n :return false;\
\n }\
\n\
\n :if ([ /system/license/get ]->\"level\" = \"free\" || \\\
\n [ /system/resource/get ]->\"board-name\" = \"x86\") do={\
\n \$LogPrintOnce debug \$0 (\"No ntp client configured, relying on RTC\
\_for CHR free license and x86.\");\
\n :return true;\
\n }\
\n\
\n :if ([ /ip/cloud/get update-time ] = true) do={\
\n :if ([ :typeof [ /ip/cloud/get public-address ] ] = \"ip\") do={\
\n :set IsTimeSyncCached true;\
\n :return true;\
\n }\
\n :return false;\
\n }\
\n\
\n \$LogPrintOnce debug \$0 (\"No time source configured! Returning grace\
fully...\");\
\n :return true;\
\n}\
\n\
\n# log and print with same text\
\n:set LogPrint do={\
\n :local Severity [ :tostr \$1 ];\
\n :local Name [ :tostr \$2 ];\
\n :local Message [ :tostr \$3 ];\
\n\
\n :global PrintDebug;\
\n :global PrintDebugOverride;\
\n\
\n :global EitherOr;\
\n\
\n :local Debug [ \$EitherOr (\$PrintDebugOverride->\$Name) \$PrintDebug \
];\
\n\
\n :local PrintSeverity do={\
\n :global TerminalColorOutput;\
\n\
\n :if (\$TerminalColorOutput != true) do={\
\n :return \$1;\
\n }\
\n\
\n :local Color { debug=96; info=97; warning=93; error=91 };\
\n :return (\"\\1B[\" . \$Color->\$1 . \"m\" . \$1 . \"\\1B[0m\");\
\n }\
\n\
\n :local Log ([ \$EitherOr \$Name \"<unknown>\" ] . \": \" . \$Message);\
\n :if (\$Severity ~ (\"^(debug|error|info)\\\$\")) do={\
\n :if (\$Severity = \"debug\") do={ :log debug \$Log; }\
\n :if (\$Severity = \"error\") do={ :log error \$Log; }\
\n :if (\$Severity = \"info\" ) do={ :log info \$Log; }\
\n } else={\
\n :log warning \$Log;\
\n :set Severity \"warning\";\
\n }\
\n\
\n :if (\$Severity != \"debug\" || \$Debug = true) do={\
\n :put ([ \$PrintSeverity \$Severity ] . \": \" . \$Message);\
\n }\
\n}\
\n\
\n# log and print, once until reboot\
\n:set LogPrintOnce do={\
\n :local Severity [ :tostr \$1 ];\
\n :local Name [ :tostr \$2 ];\
\n :local Message [ :tostr \$3 ];\
\n\
\n :global LogPrint;\
\n\
\n :global LogPrintOnceMessages;\
\n\
\n :if ([ :typeof \$LogPrintOnceMessages ] = \"nothing\") do={\
\n :set LogPrintOnceMessages ({});\
\n }\
\n\
\n :if (\$LogPrintOnceMessages->\$Message = 1) do={\
\n :return false;\
\n }\
\n\
\n :if ([ :len [ /log/find where message=(\$Name . \": \" . \$Message) ] \
] > 0) do={\
\n \$LogPrint warning \$0 \\\
\n (\"The message is already in log, scripting subsystem may have cra\
shed before!\");\
\n }\
\n\
\n :set (\$LogPrintOnceMessages->\$Message) 1;\
\n \$LogPrint \$Severity \$Name \$Message;\
\n :return true;\
\n}\
\n\
\n# get max value\
\n:set MAX do={\
\n :if (\$1 > \$2) do={ :return \$1; }\
\n :return \$2;\
\n}\
\n\
\n# get min value\
\n:set MIN do={\
\n :if (\$1 < \$2) do={ :return \$1; }\
\n :return \$2;\
\n}\
\n\
\n# create directory\
\n:set MkDir do={\
\n :local Path [ :tostr \$1 ];\
\n\
\n :global CleanFilePath;\
\n :global LogPrint;\
\n :global WaitForFile;\
\n\
\n :local MkTmpfs do={\
\n :global LogPrint;\
\n :global WaitForFile;\
\n\
\n :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ];\
\n :if ([ :len \$TmpFs ] = 1) do={\
\n :if ([ /disk/get \$TmpFs disabled ] = true) do={\
\n \$LogPrint info \$0 (\"The tmpfs is disabled, enabling.\");\
\n /disk/enable \$TmpFs;\
\n }\
\n :return true;\
\n }\
\n\
\n \$LogPrint info \$0 (\"Creating disk of type tmpfs.\");\
\n /file/remove [ find where name=\"tmpfs\" type=\"directory\" ];\
\n :do {\
\n /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource\
/get total-memory ] / 3);\
\n \$WaitForFile \"tmpfs\";\
\n } on-error={\
\n \$LogPrint warning \$0 (\"Creating disk of type tmpfs failed!\");\
\n :return false;\
\n }\
\n :return true;\
\n }\
\n\
\n :set Path [ \$CleanFilePath \$Path ];\
\n\
\n :if (\$Path = \"\") do={\
\n :return true;\
\n }\
\n\
\n :if ([ :len [ /file/find where name=\$Path type=\"directory\" ] ] = 1)\
\_do={\
\n :return true;\
\n }\
\n\
\n :if ([ :pick \$Path 0 5 ] = \"tmpfs\") do={\
\n :if ([ \$MkTmpfs ] = false) do={\
\n :return false;\
\n }\
\n }\
\n\
\n :do {\
\n :local File (\$Path . \"/file\");\
\n /file/add name=\$File;\
\n \$WaitForFile \$File;\
\n /file/remove \$File;\
\n } on-error={\
\n \$LogPrint warning \$0 (\"Making directory '\" . \$Path . \"' failed\
!\");\
\n :return false;\
\n }\
\n\
\n :return true;\
\n}\
\n\
\n# prepare NotificationFunctions array\
\n:if ([ :typeof \$NotificationFunctions ] != \"array\") do={\
\n :set NotificationFunctions ({});\
\n}\
\n\
\n# parse the date and return a named array\
\n:set ParseDate do={\
\n :local Date [ :tostr \$1 ];\
\n\
\n :return ({ \"year\"=[ :tonum [ :pick \$Date 0 4 ] ];\
\n \"month\"=[ :tonum [ :pick \$Date 5 7 ] ];\
\n \"day\"=[ :tonum [ :pick \$Date 8 10 ] ] });\
\n}\
\n\
\n# parse key value store\
\n:set ParseKeyValueStore do={\
\n :local Source \$1;\
\n\
\n :if ([ :pick \$Source 0 1 ] = \"{\") do={\
\n :do {\
\n :return [ :deserialize from=json \$Source ];\
\n } on-error={ }\
\n }\
\n\
\n :if ([ :typeof \$Source ] != \"array\") do={\
\n :set Source [ :tostr \$1 ];\
\n }\
\n :local Result ({});\
\n :foreach KeyValue in=[ :toarray \$Source ] do={\
\n :if ([ :find \$KeyValue \"=\" ]) do={\
\n :local Key [ :pick \$KeyValue 0 [ :find \$KeyValue \"=\" ] ];\
\n :local Value [ :pick \$KeyValue ([ :find \$KeyValue \"=\" ] + 1) [\
\_:len \$KeyValue ] ];\
\n :if (\$Value=\"true\") do={ :set Value true; }\
\n :if (\$Value=\"false\") do={ :set Value false; }\
\n :set (\$Result->\$Key) \$Value;\
\n } else={\
\n :set (\$Result->\$KeyValue) true;\
\n }\
\n }\
\n :return \$Result;\
\n}\
\n\
\n# print lines with trailing carriage return\
\n:set PrettyPrint do={\
\n :put [ :tocrlf [ :tostr \$1 ] ];\
\n}\
\n\
\n# strip protocol from from url string\
\n:set ProtocolStrip do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :local Pos [ :find \$Input \"://\" ];\
\n :if ([ :typeof \$Pos ] = \"nil\") do={\
\n :return \$Input;\
\n }\
\n :return [ :pick \$Input (\$Pos + 3) [ :len \$Input ] ];\
\n}\
\n\
\n# delay a random amount of seconds\
\n:set RandomDelay do={\
\n :local Time [ :tonum \$1 ];\
\n :local Unit [ :tostr \$2 ];\
\n\
\n :global EitherOr;\
\n :global GetRandomNumber;\
\n :global MAX;\
\n\
\n :if (\$Time = 0) do={\
\n :return false;\
\n }\
\n\
\n :delay ([ \$MAX 10 [ \$GetRandomNumber ([ :tonsec [ :totime (\$Time . \
[ \$EitherOr \$Unit \"s\" ]) ] ] / 1000000) ] ] . \"ms\");\
\n}\
\n\
\n# check for required RouterOS version\
\n:set RequiredRouterOS do={\
\n :local Caller [ :tostr \$1 ];\
\n :local Required [ :tostr \$2 ];\
\n :local Warn [ :tostr \$3 ];\
\n\
\n :global IfThenElse;\
\n :global LogPrint;\
\n :global VersionToNum;\
\n\
\n :if (!(\$Required ~ \"^\\\\d+\\\\.\\\\d+((alpha|beta|rc|\\\\.)\\\\d+|)\
\\\$\")) do={\
\n \$LogPrint error \$0 (\"No valid RouterOS version: \" . \$Required);\
\n :return false;\
\n }\
\n\
\n :if ([ \$VersionToNum \$Required ] > [ \$VersionToNum [ /system/packag\
e/update/get installed-version ] ]) do={\
\n :if (\$Warn = \"true\") do={\
\n \$LogPrint warning \$0 (\"This \" . [ \$IfThenElse ([ :pick \$Call\
er 0 ] = (\"\\\$\")) \"function\" \"script\" ] . \\\
\n \" '\" . \$Caller . \"' (at least specific functionality) requir\
es RouterOS \" . \$Required . \". Please update!\");\
\n }\
\n :return false;\
\n }\
\n :return true;\
\n}\
\n\
\n# check if script is run from terminal\
\n:set ScriptFromTerminal do={\
\n :local Script [ :tostr \$1 ];\
\n\
\n :global LogPrint;\
\n :global ScriptLock;\
\n\
\n :if ([ \$ScriptLock \$Script ] = false) do={\
\n :return false;\
\n }\
\n\
\n :foreach Job in=[ /system/script/job/find where script=\$Script ] do={\
\n :set Job [ /system/script/job/get \$Job ];\
\n :while ([ :typeof (\$Job->\"parent\") ] = \"id\") do={\
\n :set Job [ /system/script/job/get [ find where .id=(\$Job->\"paren\
t\") ] ];\
\n }\
\n :if ((\$Job->\"type\") = \"login\") do={\
\n \$LogPrint debug \$0 (\"Script \" . \$Script . \" started from ter\
minal.\");\
\n :return true;\
\n }\
\n }\
\n\
\n \$LogPrint debug \$0 (\"Script \" . \$Script . \" NOT started from ter\
minal.\");\
\n :return false;\
\n}\
\n\
\n# install new scripts, update existing scripts\
\n:set ScriptInstallUpdate do={ :do {\
\n :local Scripts [ :toarray \$1 ];\
\n :local NewComment [ :tostr \$2 ];\
\n\
\n :global ExpectedConfigVersion;\
\n :global Identity;\
\n :global IDonate;\
\n :global NoNewsAndChangesNotification;\
\n :global ScriptUpdatesBaseUrl;\
\n :global ScriptUpdatesCRLF;\
\n :global ScriptUpdatesUrlSuffix;\
\n\
\n :global CertificateAvailable;\
\n :global EitherOr;\
\n :global FetchUserAgentStr;\
\n :global Grep;\
\n :global IfThenElse;\
\n :global LogPrint;\
\n :global LogPrintOnce;\
\n :global ParseKeyValueStore;\
\n :global RequiredRouterOS;\
\n :global SendNotification2;\
\n :global SymbolForNotification;\
\n :global ValidateSyntax;\
\n\
\n :if ([ \$CertificateAvailable \"ISRG Root X2\" ] = false) do={\
\n \$LogPrint warning \$0 (\"Downloading certificate failed, trying wit\
hout.\");\
\n }\
\n\
\n :foreach Script in=\$Scripts do={\
\n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\
\n \$LogPrint info \$0 (\"Adding new script: \" . \$Script);\
\n /system/script/add name=\$Script owner=\$Script source=\"#!rsc by \
RouterOS\\n\" comment=\$NewComment;\
\n }\
\n }\
\n\
\n :local ExpectedConfigVersionBefore \$ExpectedConfigVersion;\
\n :local ReloadGlobalFunctions false;\
\n :local ReloadGlobalConfig false;\
\n\
\n :foreach Script in=[ /system/script/find where source~\"^#!rsc by Rout\
erOS\\r\?\\n\" ] do={\
\n :local ScriptVal [ /system/script/get \$Script ];\
\n :local ScriptInfo [ \$ParseKeyValueStore (\$ScriptVal->\"comment\") \
];\
\n :local SourceNew;\
\n\
\n :foreach Scheduler in=[ /system/scheduler/find where on-event~(\"\\\
\\b\" . \$ScriptVal->\"name\" . \"\\\\b\") ] do={\
\n :local SchedulerVal [ /system/scheduler/get \$Scheduler ];\
\n :if (\$ScriptVal->\"policy\" != \$SchedulerVal->\"policy\") do={\
\n \$LogPrint warning \$0 (\"Policies differ for script '\" . \$Scr\
iptVal->\"name\" . \\\
\n \"' and its scheduler '\" . \$SchedulerVal->\"name\" . \"'!\")\
;\
\n }\
\n }\
\n\
\n :if (!(\$ScriptInfo->\"ignore\" = true)) do={\
\n :do {\
\n :local BaseUrl [ \$EitherOr (\$ScriptInfo->\"base-url\") \$Scrip\
tUpdatesBaseUrl ];\
\n :local UrlSuffix [ \$EitherOr (\$ScriptInfo->\"url-suffix\") \$S\
criptUpdatesUrlSuffix ];\
\n :local Url (\$BaseUrl . \$ScriptVal->\"name\" . \".rsc\" . \$Url\
Suffix);\
\n \$LogPrint debug \$0 (\"Fetching script '\" . \$ScriptVal->\"nam\
e\" . \"' from url: \" . \$Url);\
\n :local Result [ /tool/fetch check-certificate=yes-without-crl \\\
\n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url outp\
ut=user as-value ];\
\n :if (\$Result->\"status\" = \"finished\") do={\
\n :set SourceNew [ :tolf (\$Result->\"data\") ];\
\n }\
\n } on-error={\
\n :if (\$ScriptVal->\"source\" = \"#!rsc by RouterOS\\n\") do={\
\n \$LogPrint warning \$0 (\"Failed fetching script '\" . \$Scrip\
tVal->\"name\" . \\\
\n \"', removing dummy. Typo on installation\?\");\
\n /system/script/remove \$Script;\
\n } else={\
\n \$LogPrint warning \$0 (\"Failed fetching script '\" . \$Scrip\
tVal->\"name\" . \"'!\");\
\n }\
\n }\
\n }\
\n\
\n :if ([ :len \$SourceNew ] > 0) do={\
\n :local SourceCRLF [ :tocrlf \$SourceNew ];\
\n :if (\$SourceNew != \$ScriptVal->\"source\" && \$SourceCRLF != \$S\
criptVal->\"source\") do={\
\n :if ([ :pick \$SourceNew 0 18 ] = \"#!rsc by RouterOS\\n\") do={\
\n :local Required ([ \$ParseKeyValueStore [ \$Grep \$SourceNew (\
\"\\23 requires RouterOS, \") ] ]->\"version\");\
\n :if ([ \$RequiredRouterOS \$0 [ \$EitherOr \$Required \"0.0\" \
] false ] = true) do={\
\n :if ([ \$ValidateSyntax \$SourceNew ] = true) do={\
\n \$LogPrint info \$0 (\"Updating script: \" . \$ScriptVal->\
\"name\");\
\n /system/script/set owner=(\$ScriptVal->\"name\") \\\
\n source=[ \$IfThenElse (\$ScriptUpdatesCRLF = true) \$S\
ourceCRLF \$SourceNew ] \$Script;\
\n :if (\$ScriptVal->\"name\" = \"global-config\") do={\
\n :set ReloadGlobalConfig true;\
\n }\
\n :if (\$ScriptVal->\"name\" = \"global-functions\" || \$Scr\
iptVal->\"name\" ~ (\"^mod/.\")) do={\
\n :set ReloadGlobalFunctions true;\
\n }\
\n } else={\
\n \$LogPrint warning \$0 (\"Syntax validation for script '\"\
\_. \$ScriptVal->\"name\" . \\\
\n \"' failed! Ignoring!\");\
\n }\
\n } else={\
\n \$LogPrintOnce warning \$0 (\"The script '\" . \$ScriptVal->\
\"name\" . \"' requires RouterOS \" . \\\
\n \$Required . \", which is not met by your installation. Ig\
noring!\");\
\n }\
\n } else={\
\n \$LogPrint warning \$0 (\"Looks like new script '\" . \$Script\
Val->\"name\" . \\\
\n \"' is not valid (missing shebang). Ignoring!\");\
\n }\
\n } else={\
\n \$LogPrint debug \$0 (\"Script '\" . \$ScriptVal->\"name\" . \"\
' did not change.\");\
\n }\
\n } else={\
\n \$LogPrint debug \$0 (\"No update for script '\" . \$ScriptVal->\"\
name\" . \"'.\");\
\n }\
\n }\
\n\
\n :if (\$ReloadGlobalFunctions = true) do={\
\n \$LogPrint info \$0 (\"Reloading global functions.\");\
\n :do {\
\n /system/script/run global-functions;\
\n } on-error={\
\n \$LogPrint error \$0 (\"Reloading global functions failed!\");\
\n }\
\n }\
\n\
\n :if (\$ReloadGlobalConfig = true) do={\
\n \$LogPrint info \$0 (\"Reloading global configuration.\");\
\n :do {\
\n /system/script/run global-config;\
\n } on-error={\
\n \$LogPrint error \$0 (\"Reloading global configuration failed!\" .\
\_\\\
\n \" Syntax error or missing overlay\?\");\
\n }\
\n }\
\n\
\n :if (\$ExpectedConfigVersionBefore > \$ExpectedConfigVersion) do={\
\n \$LogPrint warning \$0 (\"The configuration version decreased from \
\" . \\\
\n \$ExpectedConfigVersionBefore . \" to \" . \$ExpectedConfigVersion\
\_. \\\
\n \". Installed an older version\?\");\
\n }\
\n\
\n :if (\$ExpectedConfigVersionBefore < \$ExpectedConfigVersion) do={\
\n :global GlobalConfigChanges;\
\n :global GlobalConfigMigration;\
\n :local ChangeLogCode;\
\n\
\n :do {\
\n :local Url (\$ScriptUpdatesBaseUrl . \"news-and-changes.rsc\" . \$\
ScriptUpdatesUrlSuffix);\
\n \$LogPrint debug \$0 (\"Fetching news, changes and migration: \" .\
\_\$Url);\
\n :local Result [ /tool/fetch check-certificate=yes-without-crl \\\
\n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url output\
=user as-value ];\
\n :if (\$Result->\"status\" = \"finished\") do={\
\n :set ChangeLogCode (\$Result->\"data\");\
\n }\
\n } on-error={\
\n \$LogPrint warning \$0 (\"Failed fetching news, changes and migrat\
ion!\");\
\n }\
\n\
\n :if ([ :len \$ChangeLogCode ] > 0) do={\
\n :if ([ \$ValidateSyntax \$ChangeLogCode ] = true) do={\
\n :do {\
\n [ :parse \$ChangeLogCode ];\
\n } on-error={\
\n \$LogPrint warning \$0 (\"The changelog failed to run!\");\
\n }\
\n } else={\
\n \$LogPrint warning \$0 (\"The changelog failed syntax validation\
!\");\
\n }\
\n }\
\n\
\n :if ([ :len \$GlobalConfigMigration ] > 0) do={\
\n :for I from=(\$ExpectedConfigVersionBefore + 1) to=\$ExpectedConfi\
gVersion do={\
\n :local Migration (\$GlobalConfigMigration->[ :tostr \$I ]);\
\n :if ([ :typeof \$Migration ] = \"str\") do={\
\n :if ([ \$ValidateSyntax \$Migration ] = true) do={\
\n \$LogPrint info \$0 (\"Applying migration for change \" . \$\
I . \": \" . \$Migration);\
\n :do {\
\n [ :parse \$Migration ];\
\n } on-error={\
\n \$LogPrint warning \$0 (\"Migration code for change \" . \
\$I . \" failed to run!\");\
\n }\
\n } else={\
\n \$LogPrint warning \$0 (\"Migration code for change \" . \$I\
\_. \" failed syntax validation!\");\
\n }\
\n }\
\n }\
\n }\
\n\
\n :local NotificationMessage (\"The configuration version on \" . \$Id\
entity . \" increased \" . \\\
\n \"to \" . \$ExpectedConfigVersion . \", current configuration may\
\_need modification. \" . \\\
\n \"Please review and update global-config-overlay, then re-run glo\
bal-config.\");\
\n \$LogPrint info \$0 (\$NotificationMessage);\
\n\
\n :if ([ :len \$GlobalConfigChanges ] > 0) do={\
\n :set NotificationMessage (\$NotificationMessage . \"\\n\\nChanges:\
\");\
\n :for I from=(\$ExpectedConfigVersionBefore + 1) to=\$ExpectedConfi\
gVersion do={\
\n :local Change (\$GlobalConfigChanges->[ :tostr \$I ]);\
\n :set NotificationMessage (\$NotificationMessage . \"\\n \" . \\\
\n [ \$SymbolForNotification \"pushpin\" \"*\" ] . \$Change);\
\n \$LogPrint info \$0 (\"Change \" . \$I . \": \" . \$Change);\
\n }\
\n } else={\
\n :set NotificationMessage (\$NotificationMessage . \"\\n\\nNews and\
\_changes are not available.\");\
\n }\
\n\
\n :if (\$NoNewsAndChangesNotification != true) do={\
\n :local Link;\
\n :if (\$IDonate != true) do={\
\n :set NotificationMessage (\$NotificationMessage . \\\
\n \"\\n\\n==== donation hint ====\\n\" . \\\
\n \"This project is developed in private spare time and usage is\
\_\" . \\\
\n \"free of charge for you. If you like the scripts and think th\
is is \" . \\\
\n \"of value for you or your business please consider a donation\
.\");\
\n :set Link \"https://rsc.eworm.de/#donate\";\
\n }\
\n\
\n \$SendNotification2 ({ origin=\$0; \\\
\n subject=([ \$SymbolForNotification \"pushpin\" ] . \"News and co\
nfiguration changes\"); \\\
\n message=\$NotificationMessage; link=\$Link });\
\n }\
\n\
\n :set GlobalConfigChanges;\
\n :set GlobalConfigMigration;\
\n }\
\n} on-error={\
\n :global ExitError; \$ExitError false \$0;\
\n} }\
\n\
\n# lock script against multiple invocation\
\n:set ScriptLock do={\
\n :local Script [ :tostr \$1 ];\
\n :local WaitMax ([ :tonum \$3 ] * 10);\
\n\
\n :global GetRandom20CharAlNum;\
\n :global IfThenElse;\
\n :global LogPrint;\
\n\
\n :global ScriptLockOrder;\
\n :if ([ :typeof \$ScriptLockOrder ] = \"nothing\") do={\
\n :set ScriptLockOrder ({});\
\n }\
\n :if ([ :typeof (\$ScriptLockOrder->\$Script) ] = \"nothing\") do={\
\n :set (\$ScriptLockOrder->\$Script) ({});\
\n }\
\n\
\n :local JobCount do={\
\n :local Script [ :tostr \$1 ];\
\n\
\n :return [ :len [ /system/script/job/find where script=\$Script ] ];\
\n }\
\n\
\n :local TicketCount do={\
\n :local Script [ :tostr \$1 ];\
\n\
\n :global ScriptLockOrder;\
\n\
\n :local Count 0;\
\n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\
\n :if ([ :typeof \$Ticket ] != \"nothing\") do={\
\n :set Count (\$Count + 1);\
\n }\
\n }\
\n :return \$Count;\
\n }\
\n\
\n :local IsFirstTicket do={\
\n :local Script [ :tostr \$1 ];\
\n :local Check [ :tostr \$2 ];\
\n\
\n :global ScriptLockOrder;\
\n\
\n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\
\n :if (\$Ticket = \$Check) do={ :return true; }\
\n :if ([ :typeof \$Ticket ] != \"nothing\" && \$Ticket != \$Check) d\
o={ :return false; }\
\n }\
\n :return false;\
\n }\
\n\
\n :local AddTicket do={\
\n :local Script [ :tostr \$1 ];\
\n :local Add [ :tostr \$2 ];\
\n\
\n :global ScriptLockOrder;\
\n\
\n :while (true) do={\
\n :local Pos [ :len (\$ScriptLockOrder->\$Script) ];\
\n :set (\$ScriptLockOrder->\$Script->\$Pos) \$Add;\
\n :delay 10ms;\
\n :if ((\$ScriptLockOrder->\$Script->\$Pos) = \$Add) do={ :return tr\
ue; }\
\n }\
\n }\
\n\
\n :local RemoveTicket do={\
\n :local Script [ :tostr \$1 ];\
\n :local Remove [ :tostr \$2 ];\
\n\
\n :global ScriptLockOrder;\
\n\
\n :foreach Id,Ticket in=(\$ScriptLockOrder->\$Script) do={\
\n :while ((\$ScriptLockOrder->\$Script->\$Id) = \$Remove) do={\
\n :set (\$ScriptLockOrder->\$Script->\$Id);\
\n :delay 10ms;\
\n }\
\n }\
\n }\
\n\
\n :local CleanupTickets do={\
\n :local Script [ :tostr \$1 ];\
\n\
\n :global ScriptLockOrder;\
\n\
\n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\
\n :if ([ :typeof \$Ticket ] != \"nothing\") do={\
\n :return false;\
\n }\
\n }\
\n\
\n :set (\$ScriptLockOrder->\$Script) ({});\
\n }\
\n\
\n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\
\n \$LogPrint error \$0 (\"A script named '\" . \$Script . \"' does not\
\_exist!\");\
\n :error false;\
\n }\
\n\
\n :if ([ \$JobCount \$Script ] = 0) do={\
\n \$LogPrint error \$0 (\"No script '\" . \$Script . \"' is running!\"\
);\
\n :error false;\
\n }\
\n\
\n :if ([ \$TicketCount \$Script ] >= [ \$JobCount \$Script ]) do={\
\n \$LogPrint error \$0 (\"More tickets than running scripts '\" . \$Sc\
ript . \"', resetting!\");\
\n :set (\$ScriptLockOrder->\$Script) ({});\
\n /system/script/job/remove [ find where script=\$Script ];\
\n }\
\n\
\n :local MyTicket [ \$GetRandom20CharAlNum 6 ];\
\n \$AddTicket \$Script \$MyTicket;\
\n\
\n :local WaitCount 0;\
\n :while (\$WaitMax > \$WaitCount && \\\
\n ([ \$IsFirstTicket \$Script \$MyTicket ] = false || \\\
\n [ \$TicketCount \$Script ] < [ \$JobCount \$Script ])) do={\
\n :set WaitCount (\$WaitCount + 1);\
\n :delay 100ms;\
\n }\
\n\
\n :if ([ \$IsFirstTicket \$Script \$MyTicket ] = true && \\\
\n [ \$TicketCount \$Script ] = [ \$JobCount \$Script ]) do={\
\n \$RemoveTicket \$Script \$MyTicket;\
\n \$CleanupTickets \$Script;\
\n :return true;\
\n }\
\n\
\n \$RemoveTicket \$Script \$MyTicket;\
\n \$LogPrint debug \$0 (\"Script '\" . \$Script . \"' started more than \
once\" . \\\
\n [ \$IfThenElse (\$WaitCount > 0) \" and timed out waiting for lock\"\
\_\"\" ] . \"...\");\
\n :return false;\
\n}\
\n\
\n# send notification via NotificationFunctions - expects at least two str\
ing arguments\
\n:set SendNotification do={ :do {\
\n :global SendNotification2;\
\n\
\n \$SendNotification2 ({ origin=\$0; subject=\$1; message=\$2; link=\$3;\
\_silent=\$4 });\
\n} on-error={\
\n :global ExitError; \$ExitError false \$0;\
\n} }\
\n\
\n# send notification via NotificationFunctions - expects one array argume\
nt\
\n:set SendNotification2 do={\
\n :local Notification \$1;\
\n\
\n :global NotificationFunctions;\
\n\
\n :foreach FunctionName,Discard in=\$NotificationFunctions do={\
\n (\$NotificationFunctions->\$FunctionName) \\\
\n (\"\\\$NotificationFunctions->\\\"\" . \$FunctionName . \"\\\"\") \
\\\
\n \$Notification;\
\n }\
\n}\
\n\
\n# return UTF-8 symbol for unicode name\
\n:set SymbolByUnicodeName do={\
\n :local Name [ :tostr \$1 ];\
\n\
\n :global LogPrintOnce;\
\n\
\n :local Symbols {\
\n \"abacus\"=\"\\F0\\9F\\A7\\AE\";\
\n \"alarm-clock\"=\"\\E2\\8F\\B0\";\
\n \"arrow-down\"=\"\\E2\\AC\\87\";\
\n \"arrow-up\"=\"\\E2\\AC\\86\";\
\n \"calendar\"=\"\\F0\\9F\\93\\85\";\
\n \"card-file-box\"=\"\\F0\\9F\\97\\83\";\
\n \"chart-decreasing\"=\"\\F0\\9F\\93\\89\";\
\n \"chart-increasing\"=\"\\F0\\9F\\93\\88\";\
\n \"cloud\"=\"\\E2\\98\\81\";\
\n \"cross-mark\"=\"\\E2\\9D\\8C\";\
\n \"earth\"=\"\\F0\\9F\\8C\\8D\";\
\n \"fire\"=\"\\F0\\9F\\94\\A5\";\
\n \"floppy-disk\"=\"\\F0\\9F\\92\\BE\";\
\n \"gear\"=\"\\E2\\9A\\99\";\
\n \"heart\"=\"\\E2\\99\\A5\";\
\n \"high-voltage-sign\"=\"\\E2\\9A\\A1\";\
\n \"incoming-envelope\"=\"\\F0\\9F\\93\\A8\";\
\n \"information\"=\"\\E2\\84\\B9\";\
\n \"large-orange-circle\"=\"\\F0\\9F\\9F\\A0\";\
\n \"large-red-circle\"=\"\\F0\\9F\\94\\B4\";\
\n \"link\"=\"\\F0\\9F\\94\\97\";\
\n \"lock-with-ink-pen\"=\"\\F0\\9F\\94\\8F\";\
\n \"memo\"=\"\\F0\\9F\\93\\9D\";\
\n \"mobile-phone\"=\"\\F0\\9F\\93\\B1\";\
\n \"pushpin\"=\"\\F0\\9F\\93\\8C\";\
\n \"scissors\"=\"\\E2\\9C\\82\";\
\n \"smiley-partying-face\"=\"\\F0\\9F\\A5\\B3\";\
\n \"smiley-smiling-face\"=\"\\E2\\98\\BA\";\
\n \"smiley-winking-face-with-tongue\"=\"\\F0\\9F\\98\\9C\";\
\n \"sparkles\"=\"\\E2\\9C\\A8\";\
\n \"speech-balloon\"=\"\\F0\\9F\\92\\AC\";\
\n \"star\"=\"\\E2\\AD\\90\";\
\n \"warning-sign\"=\"\\E2\\9A\\A0\";\
\n \"white-heavy-check-mark\"=\"\\E2\\9C\\85\"\
\n }\
\n\
\n :if ([ :len (\$Symbols->\$Name) ] = 0) do={\
\n \$LogPrintOnce warning \$0 (\"No symbol available for name '\" . \$N\
ame . \"'!\");\
\n :return \"\";\
\n }\
\n\
\n :return ((\$Symbols->\$Name) . \"\\EF\\B8\\8F\");\
\n}\
\n\
\n# return symbol for notification\
\n:set SymbolForNotification do={\
\n :global NotificationsWithSymbols;\
\n :global SymbolByUnicodeName;\
\n :global IfThenElse;\
\n\
\n :if (\$NotificationsWithSymbols != true) do={\
\n :return [ \$IfThenElse ([ :len \$2 ] > 0) ([ :tostr \$2 ] . \" \") \
\"\" ];\
\n }\
\n :local Return \"\";\
\n :foreach Symbol in=[ :toarray \$1 ] do={\
\n :set Return (\$Return . [ \$SymbolByUnicodeName \$Symbol ]);\
\n }\
\n :return (\$Return . \" \");\
\n}\
\n\
\n# convert line endings, UNIX -> DOS\
\n:set Unix2Dos do={\
\n :return [ :tocrlf [ :tostr \$1 ] ];\
\n}\
\n\
\n# url encoding\
\n:set UrlEncode do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :if ([ :len \$Input ] = 0) do={\
\n :return \"\";\
\n }\
\n\
\n :local Return \"\";\
\n :local Chars (\"\\n\\r !\\\"#\\\$%&'()*+,:;<=>\?@[\\\\]^`{|}~\");\
\n :local Subs { \"%0A\"; \"%0D\"; \"%20\"; \"%21\"; \"%22\"; \"%23\"; \"\
%24\"; \"%25\"; \"%26\"; \"%27\";\
\n \"%28\"; \"%29\"; \"%2A\"; \"%2B\"; \"%2C\"; \"%3A\"; \"%3B\"; \
\"%3C\"; \"%3D\"; \"%3E\"; \"%3F\";\
\n \"%40\"; \"%5B\"; \"%5C\"; \"%5D\"; \"%5E\"; \"%60\"; \"%7B\"; \
\"%7C\"; \"%7D\"; \"%7E\" };\
\n\
\n :for I from=0 to=([ :len \$Input ] - 1) do={\
\n :local Char [ :pick \$Input \$I ];\
\n :local Replace [ :find \$Chars \$Char ];\
\n\
\n :if ([ :typeof \$Replace ] = \"num\") do={\
\n :set Char (\$Subs->\$Replace);\
\n }\
\n :set Return (\$Return . \$Char);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# basic syntax validation\
\n:set ValidateSyntax do={\
\n :local Code [ :tostr \$1 ];\
\n\
\n :do {\
\n [ :parse (\":local Validate do={\\n\" . \$Code . \"\\n}\") ];\
\n } on-error={\
\n :return false;\
\n }\
\n :return true;\
\n}\
\n\
\n# convert version string to numeric value\
\n:set VersionToNum do={\
\n :local Input [ :tostr \$1 ];\
\n :local Multi 0x1000000;\
\n :local Return 0;\
\n\
\n :global CharacterReplace;\
\n\
\n :set Input [ \$CharacterReplace \$Input \".\" \",\" ];\
\n :foreach I in={ \"zero\"; \"alpha\"; \"beta\"; \"rc\" } do={\
\n :set Input [ \$CharacterReplace \$Input \$I (\",\" . \$I . \",\") ];\
\n }\
\n\
\n :foreach Value in=([ :toarray \$Input ], 0) do={\
\n :local Num [ :tonum \$Value ];\
\n :if (\$Multi = 0x100) do={\
\n :if ([ :typeof \$Num ] = \"num\") do={\
\n :set Return (\$Return + 0xff00);\
\n :set Multi (\$Multi / 0x100);\
\n } else={\
\n :if (\$Value = \"zero\") do={ }\
\n :if (\$Value = \"alpha\") do={ :set Return (\$Return + 0x3f00); \
}\
\n :if (\$Value = \"beta\") do={ :set Return (\$Return + 0x5f00); }\
\n :if (\$Value = \"rc\") do={ :set Return (\$Return + 0x7f00); }\
\n }\
\n }\
\n :if ([ :typeof \$Num ] = \"num\") do={ :set Return (\$Return + (\$Va\
lue * \$Multi)); }\
\n :set Multi (\$Multi / 0x100);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# wait for default route to be reachable\
\n:set WaitDefaultRouteReachable do={\
\n :global IsDefaultRouteReachable;\
\n\
\n :while ([ \$IsDefaultRouteReachable ] = false) do={\
\n :delay 1s;\
\n }\
\n}\
\n\
\n# wait for DNS to resolve\
\n:set WaitDNSResolving do={\
\n :global IsDNSResolving;\
\n\
\n :while ([ \$IsDNSResolving ] = false) do={\
\n :delay 1s;\
\n }\
\n}\
\n\
\n# wait for file to be available\
\n:set WaitForFile do={\
\n :local FileName [ :tostr \$1 ];\
\n :local WaitTime [ :totime \$2 ];\
\n\
\n :global CleanFilePath;\
\n :global EitherOr;\
\n :global MAX;\
\n\
\n :set FileName [ \$CleanFilePath \$FileName ];\
\n :local I 1;\
\n :local Delay ([ \$MAX [ \$EitherOr \$WaitTime 2s ] 100ms ] / 10);\
\n\
\n :while ([ :len [ /file/find where name=\$FileName ] ] = 0) do={\
\n :if (\$I >= 10) do={\
\n :return false;\
\n }\
\n :delay \$Delay;\
\n :set I (\$I + 1);\
\n }\
\n :return true;\
\n}\
\n\
\n# wait to be fully connected (default route is reachable, time is sync, \
DNS resolves)\
\n:set WaitFullyConnected do={\
\n :global WaitDefaultRouteReachable;\
\n :global WaitDNSResolving;\
\n :global WaitTimeSync;\
\n\
\n \$WaitDefaultRouteReachable;\
\n \$WaitTimeSync;\
\n \$WaitDNSResolving;\
\n}\
\n\
\n# wait for time to become synced\
\n:set WaitTimeSync do={\
\n :global IsTimeSync;\
\n\
\n :while ([ \$IsTimeSync ] = false) do={\
\n :delay 1s;\
\n }\
\n}\
\n\
\n# load modules\
\n:foreach Script in=[ /system/script/find where name ~ \"^mod/.\" ] do={\
\n :local ScriptVal [ /system/script/get \$Script ];\
\n :if ([ \$ValidateSyntax (\$ScriptVal->\"source\") ] = true) do={\
\n :do {\
\n /system/script/run \$Script;\
\n } on-error={\
\n \$LogPrint error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' f\
ailed to run.\");\
\n }\
\n } else={\
\n \$LogPrint error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' fai\
led syntax validation, skipping.\");\
\n }\
\n}\
\n\
\n# Log success\
\n:local Resource [ /system/resource/get ];\
\n\$LogPrintOnce info \$ScriptName (\"Loaded on \" . \$Resource->\"board-n\
ame\" . \\\
\n \" with RouterOS \" . \$Resource->\"version\" . \".\");\
\n\
\n# signal we are ready\
\n:set GlobalFunctionsReady true;\
\n"
add dont-require-permissions=no name=dhcp-to-dns owner=dhcp-to-dns policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
!rsc by RouterOS\
\n# RouterOS script: dhcp-to-dns\
\n# Copyright (c) 2013-2025 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# provides: lease-script, order=20\
\n# requires RouterOS, version=7.16\
\n#\
\n# check DHCP leases and add/remove/update DNS entries\
\n# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-to-dns.md\
\n\
\n:global GlobalFunctionsReady;\
\n:while (\$GlobalFunctionsReady != true) do={ :delay 500ms; }\
\n\
\n:local ExitOK false;\
\n:do {\
\n :local ScriptName [ :jobname ];\
\n\
\n :global Domain;\
\n :global Identity;\
\n\
\n :global CleanName;\
\n :global EitherOr;\
\n :global IfThenElse;\
\n :global LogPrint;\
\n :global LogPrintOnce;\
\n :global ParseKeyValueStore;\
\n :global ScriptLock;\
\n\
\n :if ([ \$ScriptLock \$ScriptName 10 ] = false) do={\
\n :set ExitOK true;\
\n :error false;\
\n }\
\n\
\n :local Ttl 5m;\
\n :local CommentPrefix (\"managed by \" . \$ScriptName);\
\n :local CommentString (\"--- \" . \$ScriptName . \" above ---\");\
\n\
\n :if ([ :len [ /ip/dns/static/find where (name=\$CommentString or (comm\
ent=\$CommentString and name=-)) type=NXDOMAIN disabled ] ] = 0) do={\
\n /ip/dns/static/add name=\$CommentString type=NXDOMAIN disabled=yes;\
\n \$LogPrint warning \$ScriptName (\"Added disabled static dns record \
with name '\" . \$CommentString . \"'.\");\
\n }\
\n :local PlaceBefore ([ /ip/dns/static/find where (name=\$CommentString \
or (comment=\$CommentString and name=-)) type=NXDOMAIN disabled ]->0);\
\n\
\n :foreach DnsRecord in=[ /ip/dns/static/find where comment~(\"^\" . \$C\
ommentPrefix . \"\\\\b\") type=A ] do={\
\n :local DnsRecordVal [ /ip/dns/static/get \$DnsRecord ];\
\n :local DnsRecordInfo [ \$ParseKeyValueStore (\$DnsRecordVal->\"comme\
nt\") ];\
\n :local MacInServer (\$DnsRecordInfo->\"macaddress\" . \" in \" . \$D\
nsRecordInfo->\"server\");\
\n\
\n :if ([ :len [ /ip/dhcp-server/lease/find where active-mac-address=(\
\$DnsRecordInfo->\"macaddress\") \\\
\n active-address=(\$DnsRecordVal->\"address\") server=(\$DnsRecor\
dInfo->\"server\") status=bound ] ] > 0) do={\
\n \$LogPrint debug \$ScriptName (\"Lease for \" . \$MacInServer . \"\
\_(\" . \$DnsRecordVal->\"name\" . \") still exists. Not deleting record.\
\");\
\n } else={\
\n :local Found false;\
\n \$LogPrint info \$ScriptName (\"Lease expired for \" . \$MacInServ\
er . \", deleting record (\" . \$DnsRecordVal->\"name\" . \").\");\
\n /ip/dns/static/remove \$DnsRecord;\
\n /ip/dns/static/remove [ find where type=CNAME comment=(\$DnsRecord\
Val->\"comment\") ];\
\n }\
\n }\
\n\
\n :foreach Lease in=[ /ip/dhcp-server/lease/find where status=bound ] do\
={\
\n :local LeaseVal;\
\n :do {\
\n :set LeaseVal [ /ip/dhcp-server/lease/get \$Lease ];\
\n :if ([ :len [ /ip/dhcp-server/lease/find where active-mac-address=\
(\$LeaseVal->\"active-mac-address\") status=bound ] ] > 1) do={\
\n \$LogPrintOnce info \$ScriptName (\"Multiple bound leases found \
for mac-address \" . (\$LeaseVal->\"active-mac-address\") . \"!\");\
\n }\
\n } on-error={\
\n \$LogPrint debug \$ScriptName (\"A lease just vanished, ignoring.\
\");\
\n }\
\n\
\n :if ([ :len (\$LeaseVal->\"active-address\") ] > 0) do={\
\n :local Comment (\$CommentPrefix . \", macaddress=\" . \$LeaseVal->\
\"active-mac-address\" . \", server=\" . \$LeaseVal->\"server\");\
\n :local MacDash [ \$CleanName (\$LeaseVal->\"active-mac-address\") \
];\
\n :local HostName [ \$CleanName [ \$EitherOr ([ \$ParseKeyValueStore\
\_(\$LeaseVal->\"comment\") ]->\"hostname\") (\$LeaseVal->\"host-name\") ]\
\_];\
\n :local Network [ /ip/dhcp-server/network/find where (\$LeaseVal->\
\"active-address\") in address ];\
\n :local NetworkVal;\
\n :if ([ :len \$Network ] > 0) do={\
\n :set NetworkVal [ /ip/dhcp-server/network/get (\$Network->0) ];\
\n }\
\n :local NetworkInfo [ \$ParseKeyValueStore (\$NetworkVal->\"comment\
\") ];\
\n :local NetDomain ([ \$IfThenElse ([ :len (\$NetworkInfo->\"name-ex\
tra\") ] > 0) (\$NetworkInfo->\"name-extra\" . \".\") ] . \\\
\n [ \$EitherOr [ \$EitherOr (\$NetworkInfo->\"domain\") (\$Network\
Val->\"domain\") ] \$Domain ]);\
\n :local FullA (\$MacDash . \".\" . \$NetDomain);\
\n :local FullCN (\$HostName . \".\" . \$NetDomain);\
\n :local MacInServer (\$LeaseVal->\"active-mac-address\" . \" in \" \
. \$LeaseVal->\"server\");\
\n\
\n :local DnsRecord [ /ip/dns/static/find where comment=\$Comment typ\
e=A ];\
\n :if ([ :len \$DnsRecord ] > 0) do={\
\n :local DnsRecordVal [ /ip/dns/static/get \$DnsRecord ];\
\n\
\n :if (\$DnsRecordVal->\"address\" = \$LeaseVal->\"active-address\
\" && \$DnsRecordVal->\"name\" = \$FullA) do={\
\n \$LogPrint debug \$ScriptName (\"The A record for \" . \$MacIn\
Server . \" (\" . \$FullA . \") does not need updating.\");\
\n } else={\
\n \$LogPrint info \$ScriptName (\"Updating A record for \" . \$M\
acInServer . \" (\" . \$FullA . \" -> \" . \$LeaseVal->\"active-address\" \
. \").\");\
\n /ip/dns/static/set address=(\$LeaseVal->\"active-address\") na\
me=\$FullA \$DnsRecord;\
\n }\
\n\
\n :local CName [ /ip/dns/static/find where comment=\$Comment type=\
CNAME ];\
\n :if ([ :len \$CName ] > 0) do={\
\n :local CNameVal [ /ip/dns/static/get \$CName ];\
\n :if (\$CNameVal->\"name\" != \$FullCN || \$CNameVal->\"cname\"\
\_!= \$FullA) do={\
\n \$LogPrint info \$ScriptName (\"Deleting CNAME record with w\
rong data for \" . \$MacInServer . \".\");\
\n /ip/dns/static/remove \$CName;\
\n }\
\n }\
\n :if ([ :len \$HostName ] > 0 && [ :len [ /ip/dns/static/find whe\
re name=\$FullCN type=CNAME ] ] = 0) do={\
\n \$LogPrint info \$ScriptName (\"Adding CNAME record for \" . \
\$MacInServer . \" (\" . \$FullCN . \" -> \" . \$FullA . \").\");\
\n /ip/dns/static/add name=\$FullCN type=CNAME cname=\$FullA ttl=\
\$Ttl comment=\$Comment place-before=\$PlaceBefore;\
\n }\
\n\
\n } else={\
\n \$LogPrint info \$ScriptName (\"Adding A record for \" . \$MacIn\
Server . \" (\" . \$FullA . \" -> \" . \$LeaseVal->\"active-address\" . \"\
).\");\
\n /ip/dns/static/add name=\$FullA type=A address=(\$LeaseVal->\"ac\
tive-address\") ttl=\$Ttl comment=\$Comment place-before=\$PlaceBefore;\
\n :if ([ :len \$HostName ] > 0 && [ :len [ /ip/dns/static/find whe\
re name=\$FullCN type=CNAME ] ] = 0) do={\
\n \$LogPrint info \$ScriptName (\"Adding CNAME record for \" . \
\$MacInServer . \" (\" . \$FullCN . \" -> \" . \$FullA . \").\");\
\n /ip/dns/static/add name=\$FullCN type=CNAME cname=\$FullA ttl=\
\$Ttl comment=\$Comment place-before=\$PlaceBefore;\
\n }\
\n }\
\n\
\n :if ([ :len [ /ip/dns/static/find where name=\$FullA type=A ] ] > \
1) do={\
\n \$LogPrintOnce warning \$ScriptName (\"The name '\" . \$FullA . \
\"' appeared in more than one A record!\");\
\n }\
\n } else={\
\n \$LogPrint debug \$ScriptName (\"No address available... Ignoring.\
\");\
\n }\
\n }\
\n} on-error={\
\n :global ExitError; \$ExitError \$ExitOK [ :jobname ];\
\n}\
\n"
/tool e-mail
set from="Router <mikrotik@REDACTED>" port=587 server=\
smtp.protonmail.ch tls=yes user=mikrotik@REDACTED
/tool graphing interface
add allow-address=192.168.88.0/24 interface=sfp-sfpplus1
add allow-address=192.168.88.0/24 interface=nas
add allow-address=192.168.88.0/24 interface=switch
/tool graphing resource
add allow-address=192.168.88.0/24
/tool mac-server
set allowed-interface-list=LAN
/tool mac-server mac-winbox
set allowed-interface-list=LAN