Could you share your config?
/export file=anynameyoulike
Remove serial and any other private info and post in between code tags by using the </> button.
Current config:
# 2024-10-19 08:57:00 by RouterOS 7.16.1
# software id = 80FK-6837
#
# model = RBD53iG-5HacD2HnD
# serial number = E72C0
/interface bridge add admin-mac=2C:C8:1B:95:46:68 auto-mac=no comment=defconf name=bridge port-cost-mode=short
/interface ethernet set [ find default-name=ether5 ] poe-out=off
/interface eoip add allow-fast-path=no disabled=yes ipsec-secret=secret mac-address=02:A6:C1:08:12:E2 name=eoip-tunnel-to-212 remote-address=XXXXXX.mynetname.net tunnel-id=629
/interface wireguard add listen-port=51821 mtu=1420 name=wireguard1 private-key=XXXXXgsBPktdV7TjYbIF+cqfiRjVU="
/interface list add comment=defconf name=WAN
/interface list add comment=defconf name=LAN
/interface lte apn set [ find default=yes ] ip-type=ipv4 use-network-apn=no
/interface wireless security-profiles set [ find default=yes ] supplicant-identity=MikroTik
/interface wireless security-profiles add authentication-types=wpa2-psk disable-pmkid=yes group-ciphers=tkip,aes-ccm mode=dynamic-keys name=629 supplicant-identity="" unicast-ciphers=tkip,aes-ccm wpa-pre-shared-key=blueberry1! wpa2-pre-shared-key=blueberry1!
/interface wireless security-profiles add authentication-types=wpa2-psk disable-pmkid=yes group-ciphers=tkip,aes-ccm mode=dynamic-keys name=Guest supplicant-identity="" unicast-ciphers=tkip,aes-ccm wpa2-pre-shared-key=blueberry
/interface wireless set [ find default-name=wlan1 ] band=2ghz-b/g/n channel-width=20/40mhz-eC country="united states2" disabled=no disconnect-timeout=15s distance=indoors frequency=2462 installation=indoor mode=ap-bridge security-profile=629 ssid=MikroTik-95466C-2.4ghz wireless-protocol=802.11
/interface wireless set [ find default-name=wlan2 ] band=5ghz-a/n/ac channel-width=20/40/80mhz-eCee disabled=no disconnect-timeout=15s distance=indoors frequency=auto installation=indoor mode=ap-bridge security-profile=629 ssid=MikroTik-95466D-5ghz wireless-protocol=802.11
/interface wireless add disabled=no keepalive-frames=disabled mac-address=2E:C8:1B:95:46:6C master-interface=wlan1 multicast-buffering=disabled name=Guest-wlan1 security-profile=Guest ssid=Guest wds-cost-range=0 wds-default-cost=0 wps-mode=disabled
/interface wireless add disabled=no keepalive-frames=disabled mac-address=2E:C8:1B:95:46:6D master-interface=wlan2 multicast-buffering=disabled name=Guest-wlan3 security-profile=Guest ssid=Guest wds-cost-range=0 wds-default-cost=0 wps-mode=disabled
/ip pool add name=default-dhcp ranges=192.168.20.10-192.168.20.140
/ip dhcp-server add address-pool=default-dhcp interface=bridge lease-script="\
\n/system\
\n:local cdate [clock get date] \
\n:local yyyy [:pick \$cdate 0 4]\
\n:local MM [:pick \$cdate 5 7]\
\n:local dd [:pick \$cdate 8 10]\
\n\
\n:local thistime [/system clock get time]\
\n:local thishour [:pick \$thistime 0 2]\
\n:local thisminute [:pick \$thistime 3 5]\
\n:local thissecond [:pick \$thistime 6 8]\
\n:local identitydatetime \"\$[identity get name]_\$yyyy-\$MM-\$dd_\$thishour:\$thisminute:\$thissecond\"\
\n:local datetime \"\$yyyy-\$MM-\$dd_\$thishour:\$thisminute:\$thissecond\"\
\n:local systemname \"\$[identity get name]\"\
\n\
\n:if (\$leaseBound=1) do={\
\n\
\n :if ([/ip dhcp-server lease find where dynamic mac-address=\$leaseActMAC]!=\"\") do={\
\n\
\n# :log info \"testing after condition DYNAMIC\"\
\n}\
\n\
\n:local recipient \"jXXXXX@domain.com\"\
\n\
\n :if ((\$leaseBound=1) && ([/ip dhcp-server lease find where dynamic mac-address=\$leaseActMAC]!=\"\") && ([/ip dhcp-server lease find where comment mac-address=\$leaseActMAC]=\"\")) do={\
\n\
\n# :log info \"testing after conditions BOUND and DYNAMIC and EMPTY COMMENT\" \
\n\
\n :tool e-mail send to=\$recipient subject=\"\$systemname DHCP Lease Assigned to \$leaseActMAC\" body=\"MAC address \$leaseActMAC received IP address \$leaseActIP with a hostname of \$[/ip/dhcp-server/lease/get value-name=host-name [find where mac-address=\$leaseActMAC]] from DHCP Server \$leaseServerName on \$datetime from \$systemname with comment \$[/ip/dhcp-server/lease/get value-name=comment [find where mac-address=\$leaseActMAC]]\"\
\n\
\n# :log info \"Sent DHCP alert for MAC \$leaseActMAC\"\
\n\
\n }\
\n}\
\n" lease-time=1d name=defconf
/ip smb users set [ find default=yes ] disabled=yes
/routing bgp template set default disabled=no output.network=bgp-networks
/routing ospf instance add disabled=no name=default-v2
/routing ospf area add disabled=yes instance=default-v2 name=backbone-v2
/system logging action set 3 remote=192.168.0.13
/system logging action add name=logserver remote=192.168.0.112 remote-port=51400 target=remote
/interface bridge port add bridge=bridge comment=defconf ingress-filtering=no interface=ether2 internal-path-cost=10 path-cost=10
/interface bridge port add bridge=bridge comment=defconf ingress-filtering=no interface=ether3 internal-path-cost=10 path-cost=10
/interface bridge port add bridge=bridge comment=defconf ingress-filtering=no interface=ether4 internal-path-cost=10 path-cost=10
/interface bridge port add bridge=bridge comment=defconf ingress-filtering=no interface=ether5 internal-path-cost=10 path-cost=10
/interface bridge port add bridge=bridge comment=defconf ingress-filtering=no interface=wlan1 internal-path-cost=10 path-cost=10
/interface bridge port add bridge=bridge comment=defconf ingress-filtering=no interface=wlan2 internal-path-cost=10 path-cost=10
/interface bridge port add bridge=bridge interface=Guest-wlan1 internal-path-cost=10 path-cost=10
/interface bridge port add bridge=bridge interface=Guest-wlan3 internal-path-cost=10 path-cost=10
/ip firewall connection tracking set udp-timeout=10s
/ip neighbor discovery-settings set discover-interface-list=all
/ipv6 settings set disable-ipv6=yes max-neighbor-entries=8192
/interface list member add comment=defconf interface=bridge list=LAN
/interface list member add comment=defconf interface=ether1 list=WAN
/interface list member add interface=wireguard1 list=LAN
/interface ovpn-server server set auth=sha1,md5
/interface wireguard peers add allowed-address=10.10.100.1/24,192.168.2.0/24 comment=212 endpoint-address=XXXXX.dyndns.org endpoint-port=51820 interface=wireguard1 name=212 persistent-keepalive=40s public-key=XXXXXUo/op1OqXrW4Ds="
/interface wireguard peers add allowed-address=10.10.100.60/32,192.168.1.0/24 comment=255 endpoint-address=XXXXX.dyndns.org endpoint-port=51835 interface=wireguard1 name=255 persistent-keepalive=40s public-key=XXXXXzZ0aWPK0PMwbRc="
/interface wireguard peers add allowed-address=10.10.90.0/24,192.168.88.0/24 comment="WG client on BI PC" endpoint-port=51820 interface=wireguard1 name=peer4 public-key=XXXXXUr5iBSC0jt9TV4="
/interface wireguard peers add allowed-address=10.10.100.8/32 comment=Laptop interface=wireguard1 name=peer5 public-key=XXXXXorKJBrljQqFSxc="
/interface wireguard peers add allowed-address=10.10.100.50/32,192.168.0.1/24 comment="355 hEX UDM " endpoint-address=XXXXX.dyndns.org endpoint-port=51833 interface=wireguard1 name=355 persistent-keepalive=40s public-key=XXXXXrycOWg5omLZq3g="
/interface wireguard peers add allowed-address=192.168.40.0/24,10.10.100.2/32,10.10.100.40/32 comment=371 endpoint-address=XXXXX.dyndns.org endpoint-port=52820 interface=wireguard1 name=371 persistent-keepalive=40s public-key=XXXXXLAxn4pMzU5lohI="
/interface wireguard peers add allowed-address=10.0.0.1/24,10.10.100.15/24 comment=AX disabled=yes endpoint-address=10.0.0.1 endpoint-port=51860 interface=wireguard1 name=ax persistent-keepalive=40s public-key=XXXXXtpEw54Qql3LZ04="
/interface wireless access-list add comment="629 Emporia Vue" interface=wlan1 mac-address=10:52:1C:B9:83:84 vlan-mode=no-tag
/interface wireless access-list add comment="629 Sonoff TH10 Temp" interface=wlan1 mac-address=3C:61:05:E2:4A:C4 vlan-mode=no-tag
/interface wireless access-list add comment=Diego mac-address=92:A5:3E:1F:79:99 vlan-mode=no-tag
/ip address add address=192.168.20.1/24 comment=defconf interface=bridge network=192.168.20.0
/ip address add address=10.10.100.12/24 interface=wireguard1 network=10.10.100.0
/ip address add address=192.168.1.100/24 disabled=yes interface=bridge network=192.168.1.0
/ip address add address=192.168.0.1/24 disabled=yes interface=bridge network=192.168.0.0
/ip cloud set ddns-enabled=yes ddns-update-interval=1h
/ip dhcp-client add comment=defconf interface=ether1
/ip dhcp-server lease add address=192.168.20.62 comment="629 Sonoff TH10 Temp" mac-address=3C:61:05:E2:4A:C4 server=defconf
/ip dhcp-server lease add address=192.168.20.61 client-id=1:10:52:1c:b9:83:84 comment="629 Emporia Vue" mac-address=10:52:1C:B9:83:84 server=defconf
/ip dhcp-server lease add address=192.168.20.68 client-id=ff:32:2b:9f:d4:0:3:0:1:44:61:32:2b:9f:d4 comment="630 Ecobee\?" mac-address=44:61:32:2B:9F:D4 server=defconf
/ip dhcp-server lease add address=192.168.20.78 comment=tasmota-DEA607-1543 mac-address=C4:5B:BE:DE:A6:07 server=defconf
/ip dhcp-server lease add address=192.168.20.79 comment=tasmota-DECE8D-3725 mac-address=C4:5B:BE:DE:CE:8D server=defconf
/ip dhcp-server lease add address=192.168.20.80 comment=tasmota-DED149-4425 mac-address=C4:5B:BE:DE:D1:49 server=defconf
/ip dhcp-server lease add address=192.168.20.81 comment=tasmota-DED215-4629 mac-address=C4:5B:BE:DE:D2:15 server=defconf
/ip dhcp-server lease add address=192.168.20.82 comment=tasmota-DED2A9-4777 mac-address=C4:5B:BE:DE:D2:A9 server=defconf
/ip dhcp-server lease add address=192.168.20.83 comment=tasmota-DECE23-3619 mac-address=C4:5B:BE:DE:CE:23 server=defconf
/ip dhcp-server lease add address=192.168.20.84 comment=tasmota-DED00C-4108 mac-address=C4:5B:BE:DE:D0:0C server=defconf
/ip dhcp-server lease add address=192.168.20.30 comment="630 Emporia Vue" mac-address=A8:48:FA:96:5B:CC server=defconf
/ip dhcp-server lease add address=192.168.20.24 comment=630-9-E323D6-0982 mac-address=C4:5B:BE:E3:23:D6 server=defconf
/ip dhcp-server lease add address=192.168.20.23 comment=tasmota-E37507-5383 mac-address=C4:5B:BE:E3:75:07 server=defconf
/ip dhcp-server lease add address=192.168.20.20 comment=tasmota-E37588-5512 mac-address=C4:5B:BE:E3:75:88 server=defconf
/ip dhcp-server lease add address=192.168.20.99 mac-address=20:1F:3B:88:0D:4E server=defconf
/ip dhcp-server lease add address=192.168.20.14 client-id=1:e2:2e:79:50:90:85 comment=Franklin mac-address=E2:2E:79:50:90:85 server=defconf
/ip dhcp-server lease add address=192.168.20.16 client-id=1:72:a3:6b:d:75:7c comment=Esteban mac-address=72:A3:6B:0D:75:7C server=defconf
/ip dhcp-server lease add address=192.168.20.18 client-id=1:c0:49:ef:60:7b:28 comment="630 THR316 5 Water" mac-address=C0:49:EF:60:7B:28 server=defconf
/ip dhcp-server lease add address=192.168.20.10 client-id=1:8c:aa:ce:62:44:68 mac-address=8C:AA:CE:62:44:68 server=defconf
/ip dhcp-server lease add address=192.168.20.15 client-id=1:e2:db:8f:a5:7f:e5 mac-address=E2:DB:8F:A5:7F:E5 server=defconf
/ip dhcp-server lease add address=192.168.20.26 client-id=1:e2:a4:51:cd:e8:76 mac-address=E2:A4:51:CD:E8:76 server=defconf
/ip dhcp-server lease add address=192.168.20.33 comment="Bath heater 2" mac-address=A8:80:55:F1:DC:74 server=defconf
/ip dhcp-server lease add address=192.168.20.35 comment="Bathroom heater" mac-address=50:8B:B9:3C:3F:AF server=defconf
/ip dhcp-server lease add address=192.168.20.19 client-id=1:a2:29:38:62:d9:c9 mac-address=A2:29:38:62:D9:C9 server=defconf
/ip dhcp-server lease add address=192.168.20.12 client-id=1:3c:e9:e:8a:3a:10 comment="630 THR316 Boiler" mac-address=3C:E9:0E:8A:3A:10 server=defconf
/ip dhcp-server lease add address=192.168.20.11 comment="630 Vue" mac-address=A8:48:FA:A0:4E:CC server=defconf
/ip dhcp-server lease add address=192.168.20.25 client-id=1:d2:d9:25:ce:84:92 mac-address=D2:D9:25:CE:84:92 server=defconf
/ip dhcp-server lease add address=192.168.20.27 client-id=ff:32:80:3:ba:0:3:0:1:44:61:32:80:3:ba comment=Ecobee mac-address=44:61:32:80:03:BA server=defconf
/ip dhcp-server lease add address=192.168.20.63 client-id=1:94:e7:b:29:30:e7 mac-address=94:E7:0B:29:30:E7 server=defconf
/ip dhcp-server lease add address=192.168.20.32 client-id=1:92:a5:3e:1f:79:99 comment="Diego phone" mac-address=92:A5:3E:1F:79:99 server=defconf
/ip dhcp-server network add address=192.168.20.0/24 comment=defconf dns-server=192.168.20.1 gateway=192.168.20.1
/ip dns set allow-remote-requests=yes servers=1.1.1.1,8.8.8.8,9.9.9.9,8.8.4.4
/ip dns static add address=192.168.20.1 comment=defconf name=629.local type=A
/ip dns static add address=10.10.100.12 name=629.10.10.100.12.local type=A
/ip dns static add address=192.168.20.99 comment="automatic-from-dhcp (magic comment)" name=Chromecast.629.local ttl=15m type=A
/ip dns static add address=192.168.20.30 comment="automatic-from-dhcp (magic comment)" name=Emporia.629.local ttl=15m type=A
/ip dns static add address=192.168.20.62 comment="automatic-from-dhcp (magic comment)" name=ESP_E24AC4.629.local ttl=15m type=A
/ip dns static add address=192.168.20.69 comment="automatic-from-dhcp (magic comment)" name=A54-de-Franklin.629.local ttl=15m type=A
/ip dns static add address=192.168.20.57 comment="automatic-from-dhcp (magic comment)" name=Tab-A7-Lite-de-Franklin.629.local ttl=15m type=A
/ip dns static add address=192.168.20.68 comment="automatic-from-dhcp (magic comment)" name=630.629.local ttl=15m type=A
/ip dns static add address=192.168.20.78 comment="automatic-from-dhcp (magic comment)" name=tasmota-DEA607-1543.629.local ttl=15m type=A
/ip dns static add address=192.168.20.79 comment="automatic-from-dhcp (magic comment)" name=tasmota-DECE8D-3725.629.local ttl=15m type=A
/ip dns static add address=192.168.20.80 comment="automatic-from-dhcp (magic comment)" name=tasmota-DED149-4425.629.local ttl=15m type=A
/ip dns static add address=192.168.20.81 comment="automatic-from-dhcp (magic comment)" name=tasmota-DED215-4629.629.local ttl=15m type=A
/ip dns static add address=192.168.20.82 comment="automatic-from-dhcp (magic comment)" name=tasmota-DED2A9-4777.629.local ttl=15m type=A
/ip dns static add address=192.168.20.83 comment="automatic-from-dhcp (magic comment)" name=tasmota-DECE23-3619.629.local ttl=15m type=A
/ip dns static add address=192.168.20.84 comment="automatic-from-dhcp (magic comment)" name=tasmota-DED00C-4108.629.local ttl=15m type=A
/ip dns static add address=192.168.20.27 comment="automatic-from-dhcp (magic comment)" name=espressif.629.local ttl=15m type=A
/ip dns static add address=192.168.20.24 comment="automatic-from-dhcp (magic comment)" name=tasmota-E323D6-0982.629.local ttl=15m type=A
/ip dns static add address=192.168.20.23 comment="automatic-from-dhcp (magic comment)" name=tasmota-E37507-5383.629.local ttl=15m type=A
/ip dns static add address=192.168.20.20 comment="automatic-from-dhcp (magic comment)" name=tasmota-E37588-5512.629.local ttl=15m type=A
/ip dns static add address=192.168.20.28 comment="automatic-from-dhcp (magic comment)" name=Andres-s-S23-Ultra.629.local ttl=15m type=A
/ip dns static add address=192.168.20.17 comment="automatic-from-dhcp (magic comment)" name=Esteban-s-S23-Ultra.629.local ttl=15m type=A
/ip firewall address-list add address=XXXXX.dyndns.org list=XXXXX
/ip firewall address-list add address=XXXXX.dyndns.org list=212
/ip firewall address-list add address=subnet_1 list=external-access
/ip firewall address-list add address=subnet_2 list=external-access
/ip firewall address-list add address=subnet_XX list=external-access
/ip firewall address-list add address=10.0.100.5 list=external-access
/ip firewall address-list add address=10.0.100.6 list=external-access
/ip firewall address-list add address=IP-local-admin-destkop list=authorized
/ip firewall address-list add address=IP-local-admin-laptop list=authorized
/ip firewall address-list add address=XXXXX.dyndns.org list=dynamic-WANIP
/ip firewall address-list add address=192.168.0.0/16 list=admin
/ip firewall address-list add address=10.10.100.0/24 list=admin
/ip firewall filter add action=accept chain=input comment="REMOVE\?" disabled=yes src-address-list=212
/ip firewall filter add action=accept chain=input dst-port=8291 protocol=tcp src-address-list=212
/ip firewall filter add action=accept chain=input comment="defconf: accept established,related,untracked" connection-state=established,related,untracked
/ip firewall filter add action=drop chain=input comment="defconf: drop invalid" connection-state=invalid
/ip firewall filter add action=accept chain=input comment="defconf: accept ICMP" protocol=icmp
/ip firewall filter add action=accept chain=input comment="GRE for EoIP" protocol=gre
/ip firewall filter add action=accept chain=input comment="allow incoming wireguard connections" dst-port=51821 protocol=udp
/ip firewall filter add action=accept chain=input comment="defconf: accept to local loopback (for CAPsMAN)" dst-address=127.0.0.1
/ip firewall filter add action=accept chain=input comment="allow LAN originated" in-interface-list=LAN
/ip firewall filter add action=accept chain=input src-address-list=admin
/ip firewall filter add action=drop chain=input comment="drop all else"
/ip firewall filter add action=accept chain=forward comment="defconf: accept in ipsec policy" disabled=yes ipsec-policy=in,ipsec
/ip firewall filter add action=accept chain=forward comment="defconf: accept out ipsec policy" disabled=yes ipsec-policy=out,ipsec
/ip firewall filter add action=fasttrack-connection chain=forward comment="defconf: fasttrack" connection-state=established,related hw-offload=yes
/ip firewall filter add action=accept chain=forward comment="defconf: accept established,related, untracked" connection-state=established,related,untracked
/ip firewall filter add action=accept chain=forward comment="For new cameras" disabled=yes dst-address=192.168.1.0/24 log=yes
/ip firewall filter add action=accept chain=forward comment="For new cameras" disabled=yes src-address=192.168.1.0/24
/ip firewall filter add action=accept chain=forward in-interface-list=LAN out-interface-list=WAN
/ip firewall filter add action=accept chain=forward dst-address=10.10.100.0/24
/ip firewall filter add action=accept chain=forward comment="Allow WG to subnet" dst-address=192.168.0.0/16 in-interface=wireguard1
/ip firewall filter add action=accept chain=forward comment="Allow wireguard to subnet" disabled=yes dst-address=192.168.20.0/24 in-interface=wireguard1
/ip firewall filter add action=accept chain=forward comment="Allow subnet to enter WG" out-interface=wireguard1
/ip firewall filter add action=accept chain=forward comment="Allow wireguard to subnet" in-interface=wireguard1
/ip firewall filter add action=accept chain=input comment="Alow wireguard to router" in-interface=wireguard1
/ip firewall filter add action=accept chain=forward in-interface=wireguard1 out-interface=wireguard1
/ip firewall filter add action=accept chain=forward in-interface=wireguard1 protocol=udp
/ip firewall filter add action=drop chain=forward comment="defconf: drop invalid" connection-state=invalid
/ip firewall filter 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="defconf: masquerade" ipsec-policy=out,none out-interface-list=WAN
/ip ipsec profile set [ find default=yes ] dpd-interval=2m dpd-maximum-failures=5
/ip kid-control add fri=0s-1d mon=0s-1d name=Monitor sat=0s-1d sun=0s-1d thu=0s-1d tue=0s-1d wed=0s-1d
/ip route add disabled=no distance=1 dst-address=192.168.2.0/24 gateway=wireguard1 routing-table=main scope=30 suppress-hw-offload=no target-scope=10
/ip route add disabled=no distance=1 dst-address=192.168.0.0/24 gateway=wireguard1 pref-src="" routing-table=main scope=30 suppress-hw-offload=no target-scope=10
/ip route add disabled=no distance=1 dst-address=192.168.1.0/24 gateway=wireguard1 pref-src="" routing-table=main scope=30 suppress-hw-offload=no target-scope=10
/ip route add disabled=no distance=1 dst-address=192.168.5.0/24 gateway=wireguard1 routing-table=main scope=30 suppress-hw-offload=no target-scope=10
/ip route add disabled=no dst-address=192.168.40.0/24 gateway=wireguard1 routing-table=main suppress-hw-offload=no
/ip service set www-ssl disabled=no
/ip smb shares set [ find default=yes ] directory=/pub
/ip ssh set forwarding-enabled=both
/routing bfd configuration add disabled=no
/snmp set enabled=yes trap-version=2
/system clock set time-zone-name=America/New_York
/system identity set name=629hAPac3
/system leds set 0 interface=wlan1 leds=led1,led2,led3,led4,led5 type=wireless-signal-strength
/system leds set 1 leds=poe-led type=poe-out
/system logging set 0 disabled=yes
/system logging add topics=event
/system logging add topics=account
/system logging add disabled=yes topics=firewall
/system logging add disabled=yes topics=wireless
/system logging add disabled=yes topics=interface
/system logging add topics=mqtt
/system logging add disabled=yes topics=wireguard
/system logging add topics=watchdog
/system logging add action=logserver prefix="XXXXX5A MikroTik" topics=hotspot
/system logging add action=logserver prefix="XXXXX5A MikroTik" topics=!debug,!packet,!snmp
/system logging add disabled=yes topics=dhcp
/system logging add action=logserver prefix="XXXXX5A MikroTik" topics=info
/system logging add disabled=yes topics=fetch
/system logging add topics=info
/system logging add action=remote prefix="192.168.20.1 " topics=info
/system note set show-at-login=no
/system ntp client set enabled=yes
/system ntp client servers add address=0.north-america.pool.ntp.org
/system ntp client servers add address=3.pool.ntp.org
/system scheduler add disabled=yes interval=5d name=dyndns on-event=dyndns policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2023-03-11 start-time=07:40:42
/system scheduler add interval=2d name=export-download on-event=export-download policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2023-03-11 start-time=07:41:14
/system scheduler add interval=4w2d name="log create upload" on-event=create-upload-log policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2023-03-11 start-time=09:43:08
/system scheduler add disabled=yes interval=10m name=WG-iface-restart on-event=WG-iface-restart policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2023-03-11 start-time=14:37:25
/system scheduler add interval=2w name=IPlist on-event=IPlist policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2023-04-10 start-time=06:50:50
/system scheduler 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
/system scheduler add interval=2w name=dynamic-data-rextended on-event=dynamic-data-rextended policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2023-09-30 start-time=02:58:29
/system scheduler add interval=2w name=dhcpleasesftp on-event=dhcpleasesftp policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2024-01-09 start-time=18:26:37
/system scheduler add interval=2w name=DynDNS on-event=DynDNS policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2022-10-18 start-time=02:00:00
/system scheduler add disabled=yes interval=5m name=Data_to_Splunk on-event=Data_to_Splunk_using_Syslog policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=2024-08-31 start-time=04:45:39
/system script add dont-require-permissions=no name=export-download owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\r\
\n\r\
\n/system\r\
\n:local cdate [clock get date] \r\
\n:local yyyy [:pick \$cdate 0 4]\r\
\n:local MM [:pick \$cdate 5 7]\r\
\n:local dd [:pick \$cdate 8 10]\r\
\n:local identitydate \"\$[identity get name]_\$yyyy-\$MM-\$dd\"\r\
\n/export show-sensitive file=\"\$identitydate\"\r\
\n\r\
\n/tool fetch upload=yes mode=ftp ascii=no src-path=\"/\$[\$identitydate].rsc\" dst-path=\"/mikrotik-backups/\$[\$identitydate].rsc\" address=192.168.2.22 port=21 user=mikrotik password=XXXXX\r\
\n\r\
\n/file remove \"\$[\$identitydate].rsc\"\r\
\n\r\
\n:log info (\"Uploaded rsc backup to 192.168.2.22 as \".\$identitydate)\r\
\n"
/system script add dont-require-permissions=no name=create-upload-log owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\r\
\n/system\r\
\n:local cdate [clock get date] \r\
\n:local yyyy [:pick \$cdate 0 4]\r\
\n:local MM [:pick \$cdate 5 7]\r\
\n:local dd [:pick \$cdate 8 10]\r\
\n:local identitydate \"\$[identity get name]_\$yyyy-\$MM-\$dd\"\r\
\n\r\
\n/log print file=\"\$identitydate.log\"\r\
\n\r\
\n/tool fetch upload=yes mode=ftp ascii=no src-path=\"/\$identitydate.log.txt\" dst-path=\"/mikrotik-backups/\$identitydate.log.txt\" address=192.168.2.22 port=21 user=mikrotik password=XXXXX\r\
\n\r\
\n/file remove \"\$identitydate.log.txt\"\r\
\n\r\
\n\r\
\n"
/system script add dont-require-permissions=no name=script1 owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\r\
\n:global nowdate do={\r\
\n /system clock\r\
\n :local vdate [get date]\r\
\n :local vtime [get time]\r\
\n :local yyyy [:pick \$vdate 7 11]\r\
\n :local M ([:find \"xxanebarprayunulugepctovecANEBARPRAYUNULUGEPCTOVEC\" [:pick \$vdate 1 3] -1] / 2); :if (\$M>12) do={:set M (\$M - 12)}\r\
\n :local MM [:pick \"0\$M\" 1 3]\r\
\n :local dd [:pick \$vdate 4 6]\r\
\n :local HH [:pick \$vtime 0 2]\r\
\n :local mm [:pick \$vtime 3 5]\r\
\n :local ss [:pick \$vtime 6 8]\r\
\n\r\
\n :return \"\$yyyy-\$MM-\$dd \$HH:\$mm:\$ss\"\r\
\n\r\
\n}\r\
\n\r\
\n/log print file=\"629log-\$[\$nowdate]\"\r\
\n\r\
\n/tool fetch upload=yes mode=ftp ascii=no src-path=\"/log\$[\$nowdate].txt\" dst-path=\"/mikrotik-backups/629log-\$[\$nowdate].txt\" address=192.168.2.22 port=21 user=mikrotik password=XXXXX\r\
\n\r\
\n\r\
\n"
/system script add dont-require-permissions=no name=WG-iface-restart owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":foreach i in=[/interface/wireguard/peers/find where disabled=no endpoint-address~\"[a-z]\\\$\"] do={\r\
\n :local LastHandshake [/interface/wireguard/peers/get \$i last-handshake]\r\
\n :if (([:tostr \$LastHandshake] = \"\") or (\$LastHandshake > [:totime \"5m\"])) do={\r\
\n \r\
\n :log info \"WG-iface-restart script found WG peers with last handshake greater than 5 minutes; then reset the endpoint-address to reload dns of endpoint\"\r\
\n\r\
\n /interface/wireguard/peers/set \$i endpoint-address=[/interface/wireguard/peers/get \$i endpoint-address]\r\
\n\r\
\n :local endpoint [/interface/wireguard/peers/get \$i endpoint-address]\r\
\n :log info \"WG-iface-restart script found WG peer with last handshake greater than 5 minutes; then reset the endpoint-address to reload dns of endpoint: \$endpoint\"\r\
\n }\r\
\n}\r\
\n\r\
\n"
/system script add dont-require-permissions=no name="DHCP to DNS" owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="# SPDX-License-Identifier: CC0-1.0\
\n\r\
\n\r\r\r\r\
\n\r\
\n\r\r:local domains [:toarray \"629.local\"]\
\n\r\
\n\r\r:local dnsttl \"15m\"\
\n\r\
\n\r\r\
\n\r\
\n\r\r:local magiccomment \"automatic-from-dhcp (magic comment)\"\
\n\r\
\n\r\r:local activehosts [:toarray \"\"]\
\n\r\
\n\r\r\
\n\r\
\n\r\r:foreach lease in [/ip dhcp-server lease find] do={\
\n\r\
\n\r\r :local hostname [/ip dhcp-server lease get value-name=host-name \$lease]\
\n\r\
\n\r\r :local hostaddr [/ip dhcp-server lease get value-name=address \$lease]\
\n\r\
\n\r\r\
\n\r\
\n\r\r :if ([:len \$hostname] > 0) do={\
\n\r\
\n\r\r :foreach domain in \$domains do={\
\n\r\
\n\r\r :local regdomain \"\$hostname.\$domain\"\
\n\r\
\n\r\r :set activehosts (\$activehosts, \$regdomain)\
\n\r\
\n\r\r\
\n\r\
\n\r\r :if ([:len [/ip dns static find where name=\$regdomain]] = 0) do={\
\n\r\
\n\r\r /ip dns static add name=\$regdomain address=\$hostaddr comment=\$magiccomment ttl=\$dnsttl\
\n\r\
\n\r\r } else={\
\n\r\
\n\r\r :if ([:len [/ip dns static find where name=\$regdomain comment=\$magiccomment]] = 1) do={\
\n\r\
\n\r\r /ip dns static set address=\$hostaddr [/ip dns static find name=\$regdomain comment=\$magiccomment]\
\n\r\
\n\r\r }\
\n\r\
\n\r\r }\
\n\r\
\n\r\r }\
\n\r\
\n\r\r }\
\n\r\
\n\r\r}\
\n\r\
\n\r\r\
\n\r\
\n\r\r:foreach dnsentry in [/ip dns static find where comment=\$magiccomment] do={\
\n\r\
\n\r\r :local hostname [/ip dns static get value-name=name \$dnsentry]\
\n\r\
\n\r\r :if ([:type [:find \$activehosts \$hostname]] = \"nil\") do={\
\n\r\
\n\r\r /ip dns static remove \$dnsentry\
\n\r\
\n\r\r }\
\n\r\
\n\r\r}\
\n\r\
\n\r\r"
/system script add dont-require-permissions=no name="Dynamic Data" owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\r\
\n/system\r\
\n:local cdate [clock get date] \r\
\n:local yyyy [:pick \$cdate 0 4]\r\
\n:local MM [:pick \$cdate 5 7]\r\
\n:local dd [:pick \$cdate 8 10]\r\
\n:local identitydate \"\$[identity get name]_\$yyyy-\$MM-\$dd\"\r\
\n\r\
\n:local fileup \"dynamicdata-\$[\$identitydate].txt\"\r\
\n\r\
\n/file remove \"\$[\$fileup]\"\r\
\n\r\
\n/ip/cloud print file=\"\$fileup\"\r\
\n\r\
\n/ip/dhcp-server/lease print file=\"dhcpleases-\$[\$identitydate]\"\r\
\n\r\
\n/interface/bridge/host print file=\"629-bridgehosts-\$[\$identitydate]\"\r\
\n\r\
\n/interface/wireless/registration-table print file=\"629-wirelessregistrations-\$[\$identitydate]\"\r\
\n\r\
\n:local filecontents [/file get \$fileup contents]\r\
\n:set filecontents (\$filecontents . \"\\r\\n\")\r\
\n/file set \$fileup contents=\$filecontents\r\
\n\r\
\n/tool fetch upload=yes mode=ftp ascii=no src-path=\"/fileup.txt\" dst-path=\"/mikrotik-backups/fileup.txt\" address=192.168.2.22 port=21 user=mikrotik password=XXXXX\r\
\n"
/system script add dont-require-permissions=no name=global-config owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#!rsc by RouterOS\
\n# RouterOS script: global-config\
\n# Copyright (c) 2013-2023 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-dns')\
\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 settings\
\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 address\
\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# This is whether or not to send Telegram messages with fixed-width font.\
\n:global TelegramFixedWidthFont true;\
\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# It is possible to override e-mail, Telegram and Matrix setting for every\
\n# script. This is done in arrays, where 'Override' is appended to the\
\n# 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\
\n# This defines the settings for firewall address-lists (fw-addr-lists).\
\n:global FwAddrLists {\
\n# \"allow\"={\
\n# { url=\"https://eworm.de/ros/fw-addr-lists/allow\";\
\n# cert=\"R3\" };\
\n# };\
\n \"block\"={\
\n# { url=\"https://eworm.de/ros/fw-addr-lists/block\";\
\n# cert=\"R3\" };\
\n { url=\"https://feodotracker.abuse.ch/downloads/ipblocklist_recommended.txt\";\
\n cert=\"GlobalSign Atlas R3 DV TLS CA 2022 Q3\" };\
\n { url=\"https://sslbl.abuse.ch/blacklist/sslipblacklist.txt\";\
\n cert=\"GlobalSign Atlas R3 DV TLS CA 2022 Q3\" };\
\n { url=\"https://www.dshield.org/block.txt\"; cidr=\"/24\";\
\n cert=\"R3\" };\
\n# { url=\"https://www.spamhaus.org/drop/drop.txt\";\
\n# cert=\"Cloudflare Inc ECC CA-3\" };\
\n# { url=\"https://www.spamhaus.org/drop/edrop.txt\";\
\n# cert=\"Cloudflare Inc ECC CA-3\" };\
\n };\
\n};\
\n:global FwAddrListTimeOut 1d;\
\n\
\n# This defines what log messages to filter or include by topic or message\
\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)\";\
\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 even\
\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 appended.\
\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# 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.cgi\";\
\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/script/run leds-toggle-mode;\";\
\n 2=\":global Identity; :global SendNotification; :global SymbolForNotification; \\\$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# Enable this to fetch scripts from given url.\
\n:global ScriptUpdatesFetch true;\
\n:global ScriptUpdatesBaseUrl \"https://git.eworm.de/cgit/routeros-scripts/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-scripts/raw/main/\";\
\n#:global ScriptUpdatesBaseUrl \"https://gitlab.com/eworm-de/routeros-scripts/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 charge\
\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\
\n# Warning: Do *NOT* copy this code to overlay!\
\n:do {\
\n /system/script/run global-config-overlay;\
\n} on-error={\
\n :log error (\"Loading configuration from overlay failed!\");\
\n}\
\n"
/system script add dont-require-permissions=no name=global-config-overlay owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="# Overlay for global configuration by RouterOS Scripts\
\n# Copyright (c) 2013-2023 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-configuration\
\n\
\n# Copy relevant configuration from global-config, paste and modify it here.\
\n# https://git.eworm.de/cgit/routeros-scripts/about/global-config.rsc\
\n\
\n\
\n# End of global-config-overlay\
\n"
/system script add dont-require-permissions=no name=global-functions owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#!rsc by RouterOS\
\n# RouterOS script: global-functions\
\n# Copyright (c) 2013-2023 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.9beta4\
\n#\
\n# global functions\
\n# https://git.eworm.de/cgit/routeros-scripts/about/\
\n\
\n:local 0 \"global-functions\";\
\n\
\n# expected configuration version\
\n:global ExpectedConfigVersion 105;\
\n\
\n# global variables not to be changed by user\
\n:global GlobalFunctionsReady false;\
\n:global FetchUserAgent (\"User-Agent: Mikrotik/\" . [ /system/resource/get version ] . \" Fetch\");\
\n:global Identity [ /system/identity/get name ];\
\n\
\n# global functions\
\n:global CertificateAvailable;\
\n:global CertificateDownload;\
\n:global CertificateNameByCN;\
\n:global CharacterReplace;\
\n:global CleanFilePath;\
\n:global DeviceInfo;\
\n:global Dos2Unix;\
\n:global DownloadPackage;\
\n:global EitherOr;\
\n:global EscapeForRegEx;\
\n:global FormatLine;\
\n:global GetMacVendor;\
\n:global GetRandom20CharAlNum;\
\n:global GetRandom20CharHex;\
\n:global GetRandomNumber;\
\n:global Grep;\
\n:global HexToNum;\
\n:global IfThenElse;\
\n:global IsDefaultRouteReachable;\
\n:global IsDNSResolving;\
\n:global IsFullyConnected;\
\n:global IsMacLocallyAdministered;\
\n:global IsTimeSync;\
\n:global LogPrintExit2;\
\n:global MkDir;\
\n:global NotificationFunctions;\
\n:global ParseDate;\
\n:global ParseKeyValueStore;\
\n:global PrettyPrint;\
\n:global RandomDelay;\
\n:global Read;\
\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# check and download required certificate\
\n:set CertificateAvailable do={\
\n :local CommonName [ :tostr \$1 ];\
\n\
\n :global CertificateDownload;\
\n :global LogPrintExit2;\
\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 \$LogPrintExit2 warning \$0 (\"This system has low free flash space but \" . \\\
\n \"is configured to download certificate CRLs to system!\") false;\
\n }\
\n\
\n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0) do={\
\n \$LogPrintExit2 info \$0 (\"Certificate with CommonName \\\"\" . \$CommonName . \"\\\" not available.\") false;\
\n :if ([ \$CertificateDownload \$CommonName ] = false) do={\
\n :return false;\
\n }\
\n }\
\n\
\n :local CertVal [ /certificate/get [ find where common-name=\$CommonName ] ];\
\n :while ((\$CertVal->\"akid\") != \"\" && (\$CertVal->\"akid\") != (\$CertVal->\"skid\")) do={\
\n :if ([ :len [ /certificate/find where skid=(\$CertVal->\"akid\") ] ] = 0) do={\
\n \$LogPrintExit2 info \$0 (\"Certificate chain for \\\"\" . \$CommonName . \\\
\n \"\\\" is incomplete, missing \\\"\" . ([ \$ParseKeyValueStore (\$CertVal->\"issuer\") ]->\"CN\") . \"\\\".\") false;\
\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 FetchUserAgent;\
\n :global ScriptUpdatesBaseUrl;\
\n :global ScriptUpdatesUrlSuffix;\
\n\
\n :global CertificateNameByCN;\
\n :global LogPrintExit2;\
\n :global UrlEncode;\
\n :global WaitForFile;\
\n\
\n \$LogPrintExit2 info \$0 (\"Downloading and importing certificate with \" . \\\
\n \"CommonName \\\"\" . \$CommonName . \"\\\".\") false;\
\n :do {\
\n :local LocalFileName (\$CommonName . \".pem\");\
\n :local UrlFileName ([ \$UrlEncode \$CommonName ] . \".pem\");\
\n /tool/fetch check-certificate=yes-without-crl http-header-field=\$FetchUserAgent \\\
\n (\$ScriptUpdatesBaseUrl . \"certs/\" . \$UrlFileName . \$ScriptUpdatesUrlSuffix) \\\
\n dst-path=\$LocalFileName as-value;\
\n \$WaitForFile \$LocalFileName;\
\n /certificate/import file-name=\$LocalFileName passphrase=\"\" as-value;\
\n /file/remove \$LocalFileName;\
\n\
\n :foreach Cert in=[ /certificate/find where name~(\"^\" . \$LocalFileName . \"_[0-9]+\\\$\") ] do={\
\n \$CertificateNameByCN [ /certificate/get \$Cert common-name ];\
\n }\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"Failed importing certificate with \" . \\\
\n \"CommonName \\\"\" . \$CommonName . \"\\\"!\") false;\
\n :return false;\
\n }\
\n :delay 1s;\
\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 CharacterReplace;\
\n\
\n :local Cert [ /certificate/find where common-name=\$CommonName ];\
\n /certificate/set \$Cert \\\
\n name=[ \$CharacterReplace [ \$CharacterReplace [ \$CharacterReplace \$CommonName \"'\" \"-\" ] \" \" \"-\" ] \"---\" \"-\" ];\
\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# get readable device info\
\n:set DeviceInfo do={\
\n :global ExpectedConfigVersion;\
\n :global Identity;\
\n\
\n :global IfThenElse;\
\n :global FormatLine;\
\n\
\n :local Resource [ /system/resource/get ];\
\n :local RouterBoard;\
\n :do {\
\n :set RouterBoard [[ :parse \"/system/routerboard/get\" ]];\
\n } on-error={ }\
\n :local License [ /system/license/get ];\
\n :local Update [ /system/package/update/get ];\
\n\
\n :return ( \\\
\n [ \$FormatLine \"Hostname\" \$Identity ] . \"\\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\") ] != \"nothing\" && \\\
\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-firmware\") ] . \"\\n\") ] . \\\
\n \"RouterOS-Scripts:\\n\" . \\\
\n [ \$FormatLine \" Version\" \$ExpectedConfigVersion ]);\
\n}\
\n\
\n# convert line endings, DOS -> UNIX\
\n:set Dos2Unix do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :global CharacterReplace;\
\n\
\n :return [ \$CharacterReplace \$Input (\"\\r\\n\") (\"\\n\") ];\
\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 LogPrintExit2;\
\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/update/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 \$LogPrintExit2 warning \$0 (\"Failed creating directory, not downloading package.\") false;\
\n :return false;\
\n }\
\n\
\n :if ([ :len [ /file/find where name=\$PkgDest type=\"package\" ] ] > 0) do={\
\n \$LogPrintExit2 info \$0 (\"Package file \" . \$PkgName . \" already exists.\") false;\
\n :return true;\
\n }\
\n\
\n :if ([ \$CertificateAvailable \"R3\" ] = false) do={\
\n \$LogPrintExit2 error \$0 (\"Downloading required certificate failed.\") true;\
\n }\
\n\
\n :local Url (\"https://upgrade.mikrotik.com/routeros/\" . \$PkgVer . \"/\" . \$PkgFile);\
\n \$LogPrintExit2 info \$0 (\"Downloading package file '\" . \$PkgName . \"'...\") false;\
\n \$LogPrintExit2 debug \$0 (\"... from url: \" . \$Url) false;\
\n :local Retry 3;\
\n :while (\$Retry > 0) do={\
\n :do {\
\n /tool/fetch check-certificate=yes-without-crl \$Url dst-path=\$PkgDest;\
\n \$WaitForFile \$PkgDest;\
\n\
\n :if ([ /file/get [ find where name=\$PkgDest ] type ] = \"package\") do={\
\n :return true;\
\n }\
\n } on-error={\
\n \$LogPrintExit2 debug \$0 (\"Downloading package file failed.\") false;\
\n }\
\n\
\n /file/remove [ find where name=\$PkgDest ];\
\n :set Retry (\$Retry - 1);\
\n }\
\n\
\n \$LogPrintExit2 warning \$0 (\"Downloading package file '\" . \$PkgName . \"' failed.\") false;\
\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 :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# format a line for output\
\n:set FormatLine do={\
\n :local Key [ :tostr \$1 ];\
\n :local Values [ :toarray \$2 ];\
\n :local Indent [ :tonum \$3 ];\
\n :local Spaces \" \";\
\n :local Return \"\";\
\n\
\n :global EitherOr;\
\n :global FormatLine;\
\n\
\n :set Indent [ \$EitherOr \$Indent 16 ];\
\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 ] . (\$Values->0));\
\n } else={\
\n :set Return (\$Return . [ :pick \$Spaces 0 (\$Indent - [ :len \$Return ]) ] . (\$Values->0));\
\n }\
\n :foreach Value in=[ :pick \$Values 1 [ :len \$Values ] ] do={\
\n :set Return (\$Return . \"\\n\" . [ \$FormatLine \"\" ({\$Value}) \$Indent ]);\
\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 LogPrintExit2;\
\n\
\n :if ([ \$IsMacLocallyAdministered \$Mac ] = true) do={\
\n :return \"locally administered\";\
\n }\
\n\
\n :do {\
\n :if ([ \$CertificateAvailable \"R3\" ] = false) do={\
\n \$LogPrintExit2 warning \$0 (\"Downloading required certificate failed.\") true;\
\n }\
\n :local Vendor ([ /tool/fetch check-certificate=yes-without-crl \\\
\n (\"https://api.macvendors.com/\" . [ :pick \$Mac 0 8 ]) output=user as-value ]->\"data\");\
\n :return \$Vendor;\
\n } on-error={\
\n :do {\
\n /tool/fetch check-certificate=yes-without-crl (\"https://api.macvendors.com/\") \\\
\n output=none as-value;\
\n \$LogPrintExit2 debug \$0 (\"The mac vendor is not known in database.\") false;\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"Failed getting mac vendor.\") false;\
\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=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\" ];\
\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=\"0123456789abcdef\" ];\
\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 :local Hex \"0123456789abcdef0123456789ABCDEF\";\
\n :local Multi 1;\
\n :local Return 0;\
\n\
\n :for I from=([ :len \$Input ] - 1) to=0 do={\
\n :set Return (\$Return + (([ :find \$Hex [ :pick \$Input \$I ] ] % 16) * \$Multi));\
\n :set Multi (\$Multi * 16);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# mimic conditional/ternary operator (condition \? consequent : alternative)\
\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 routing-table=main ] ] > 0) do={\
\n :return true;\
\n }\
\n :return false;\
\n}\
\n\
\n# check if DNS is resolving\
\n:set IsDNSResolving do={\
\n :global CharacterReplace;\
\n\
\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 resolving, 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 LogPrintExit2;\
\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 :if ([ :typeof \$IsTimeSyncResetNtp ] = \"nothing\") do={\
\n :set IsTimeSyncResetNtp 0s;\
\n }\
\n :local Uptime [ /system/resource/get uptime ];\
\n :if (\$Uptime - \$IsTimeSyncResetNtp < 3m) do={\
\n :return false;\
\n }\
\n\
\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 \$LogPrintExit2 debug \$0 (\"No ntp client configured, relying on RTC for CHR free license and x86.\") false;\
\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 \$LogPrintExit2 debug \$0 (\"No time source configured! Returning gracefully...\") false;\
\n :return true;\
\n}\
\n\
\n# log and print with same text, optionally exit\
\n:set LogPrintExit2 do={\
\n :local Severity [ :tostr \$1 ];\
\n :local Name [ :tostr \$2 ];\
\n :local Message [ :tostr \$3 ];\
\n :local Exit [ :tostr \$4 ];\
\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 :if (\$Exit = \"true\") do={\
\n :error (\"Hard error to exit.\");\
\n }\
\n}\
\n\
\n# create directory\
\n:set MkDir do={\
\n :local Path [ :tostr \$1 ];\
\n\
\n :global CharacterReplace;\
\n :global CleanFilePath;\
\n :global GetRandom20CharAlNum;\
\n :global LogPrintExit2;\
\n :global WaitForFile;\
\n\
\n :local MkTmpfs do={\
\n :global LogPrintExit2;\
\n :global WaitForFile;\
\n\
\n :if ([ :len [ /disk/find where slot=tmpfs type=tmpfs ] ] = 1) do={\
\n :return true;\
\n }\
\n\
\n \$LogPrintExit2 info \$0 (\"Creating disk of type tmpfs.\") false;\
\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 \$LogPrintExit2 warning \$0 (\"Creating disk of type tmpfs failed!\") false;\
\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 \$LogPrintExit2 warning \$0 (\"Making directory '\" . \$Path . \"' failed!\") false;\
\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 :if ([ :pick \$Date 4 5 ] != \"-\") do={\
\n :local Months { \"jan\"=1; \"feb\"=2; \"mar\"=3; \"apr\"=4; \"may\"=5; \"jun\"=6;\
\n \"jul\"=7; \"aug\"=8; \"sep\"=9; \"oct\"=10; \"nov\"=11; \"dec\"=12 };\
\n\
\n :return ({ \"year\"=[ :tonum [ :pick \$Date 7 11 ] ];\
\n \"month\"=(\$Months->[ :pick \$Date 0 3 ]);\
\n \"day\"=[ :tonum [ :pick \$Date 4 6 ] ] });\
\n }\
\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 :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 :set (\$Result->[ :pick \$KeyValue 0 [ :find \$KeyValue \"=\" ] ]) \\\
\n [ :pick \$KeyValue ([ :find \$KeyValue \"=\" ] + 1) [ :len \$KeyValue ] ];\
\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 :local Input [ :tostr \$1 ];\
\n\
\n :global Unix2Dos;\
\n\
\n :put [ \$Unix2Dos \$Input ];\
\n}\
\n\
\n# delay a random amount of seconds\
\n:set RandomDelay do={\
\n :global EitherOr;\
\n :global GetRandomNumber;\
\n\
\n :delay ([ \$GetRandomNumber \$1 ] . [ \$EitherOr \$2 \"s\" ]);\
\n}\
\n\
\n# read input from user\
\n:set Read do={\
\n :return;\
\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 LogPrintExit2;\
\n :global VersionToNum;\
\n\
\n :if (!(\$Required ~ \"^\\\\d+\\\\.\\\\d+((alpha|beta|rc|\\\\.)\\\\d+|)\\\$\")) do={\
\n \$LogPrintExit2 error \$0 (\"No valid RouterOS version: \" . \$Required) false;\
\n :return false;\
\n }\
\n\
\n :if ([ \$VersionToNum \$Required ] > [ \$VersionToNum [ /system/package/update/get installed-version ] ]) do={\
\n :if (\$Warn = \"true\") do={\
\n \$LogPrintExit2 warning \$0 (\"This \" . [ \$IfThenElse ([ :pick \$Caller 0 ] = (\"\\\$\")) \"function\" \"script\" ] . \\\
\n \" '\" . \$Caller . \"' (at least specific functionality) requires RouterOS \" . \$Required . \". Please update!\") false;\
\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 LogPrintExit2;\
\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->\"parent\") ] ];\
\n }\
\n :if ((\$Job->\"type\") = \"login\") do={\
\n \$LogPrintExit2 debug \$0 (\"Script \" . \$Script . \" started from terminal.\") false;\
\n :return true;\
\n }\
\n }\
\n \$LogPrintExit2 debug \$0 (\"Script \" . \$Script . \" NOT started from terminal.\") false;\
\n\
\n :return false;\
\n}\
\n\
\n# install new scripts, update existing scripts\
\n:set ScriptInstallUpdate do={\
\n :local Scripts [ :toarray \$1 ];\
\n :local NewComment [ :tostr \$2 ];\
\n\
\n :global ExpectedConfigVersion;\
\n :global FetchUserAgent;\
\n :global Identity;\
\n :global IDonate;\
\n :global NoNewsAndChangesNotification;\
\n :global NotificationsWithSymbols;\
\n :global ScriptUpdatesBaseUrl;\
\n :global ScriptUpdatesFetch;\
\n :global ScriptUpdatesUrlSuffix;\
\n\
\n :global CertificateAvailable;\
\n :global EitherOr;\
\n :global Grep;\
\n :global IfThenElse;\
\n :global LogPrintExit2;\
\n :global ParseKeyValueStore;\
\n :global RequiredRouterOS;\
\n :global SendNotification2;\
\n :global SymbolForNotification;\
\n :global ValidateSyntax;\
\n\
\n :if ([ \$CertificateAvailable \"R3\" ] = false) do={\
\n \$LogPrintExit2 warning \$0 (\"Downloading certificate failed, trying without.\") false;\
\n }\
\n\
\n :if ([ \$CertificateAvailable \"E1\" ] = false) do={\
\n \$LogPrintExit2 warning \$0 (\"Downloading certificate failed, trying without.\") false;\
\n }\
\n\
\n :foreach Script in=\$Scripts do={\
\n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\
\n \$LogPrintExit2 info \$0 (\"Adding new script: \" . \$Script) false;\
\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 RouterOS\\r\?\\n\" ] do={\
\n :local ScriptVal [ /system/script/get \$Script ];\
\n :local ScriptFile [ /file/find where name=(\"script-updates/\" . \$ScriptVal->\"name\") . \".rsc\" ];\
\n :local SourceNew;\
\n :if ([ :len \$ScriptFile ] > 0) do={\
\n :set SourceNew [ /file/get \$ScriptFile contents ];\
\n /file/remove \$ScriptFile;\
\n }\
\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 \$LogPrintExit2 warning \$0 (\"Policies differ for script '\" . \$ScriptVal->\"name\" . \\\
\n \"' and its scheduler '\" . \$SchedulerVal->\"name\" . \"'!\") false;\
\n }\
\n }\
\n\
\n :if ([ :len \$SourceNew ] = 0 && \$ScriptUpdatesFetch = true) do={\
\n :local Comment [ \$ParseKeyValueStore (\$ScriptVal->\"comment\") ];\
\n :if (!(\$Comment->\"ignore\" = true)) do={\
\n :do {\
\n :local BaseUrl \$ScriptUpdatesBaseUrl;\
\n :local UrlSuffix \$ScriptUpdatesUrlSuffix;\
\n :if ([ :typeof (\$Comment->\"base-url\") ] = \"str\") do={ :set BaseUrl (\$Comment->\"base-url\"); }\
\n :if ([ :typeof (\$Comment->\"url-suffix\") ] = \"str\") do={ :set UrlSuffix (\$Comment->\"url-suffix\"); }\
\n :local Url (\$BaseUrl . \$ScriptVal->\"name\" . \".rsc\" . \$UrlSuffix);\
\n\
\n \$LogPrintExit2 debug \$0 (\"Fetching script '\" . \$ScriptVal->\"name\" . \"' from url: \" . \$Url) false;\
\n :local Result [ /tool/fetch check-certificate=yes-without-crl http-header-field=\$FetchUserAgent \\\
\n \$Url output=user as-value ];\
\n :if (\$Result->\"status\" = \"finished\") do={\
\n :set SourceNew (\$Result->\"data\");\
\n }\
\n } on-error={\
\n :if (\$ScriptVal->\"source\" = \"#!rsc by RouterOS\\n\") do={\
\n \$LogPrintExit2 warning \$0 (\"Failed fetching script '\" . \$ScriptVal->\"name\" . \\\
\n \"', removing dummy. Typo on installation\?\") false;\
\n /system/script/remove \$Script;\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"Failed fetching script '\" . \$ScriptVal->\"name\" . \"'!\") false;\
\n }\
\n }\
\n }\
\n }\
\n\
\n :if ([ :len \$SourceNew ] > 0) do={\
\n :if (\$SourceNew != \$ScriptVal->\"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 \$LogPrintExit2 info \$0 (\"Updating script: \" . \$ScriptVal->\"name\") false;\
\n /system/script/set owner=(\$ScriptVal->\"name\") source=\$SourceNew \$Script;\
\n :if (\$ScriptVal->\"name\" = \"global-config\") do={\
\n :set ReloadGlobalConfig true;\
\n }\
\n :if (\$ScriptVal->\"name\" = \"global-functions\" || \$ScriptVal->\"name\" ~ (\"^mod/.\")) do={\
\n :set ReloadGlobalFunctions true;\
\n }\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"Syntax validation for script '\" . \$ScriptVal->\"name\" . \\\
\n \"' failed! Ignoring!\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"The script '\" . \$ScriptVal->\"name\" . \"' requires RouterOS \" . \\\
\n \$Required . \", which is not met by your installation. Ignoring!\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"Looks like new script '\" . \$ScriptVal->\"name\" . \\\
\n \"' is not valid (missing shebang). Ignoring!\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 debug \$0 (\"Script '\" . \$ScriptVal->\"name\" . \"' did not change.\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 debug \$0 (\"No update for script '\" . \$ScriptVal->\"name\" . \"'.\") false;\
\n }\
\n }\
\n\
\n :if (\$ReloadGlobalFunctions = true) do={\
\n \$LogPrintExit2 info \$0 (\"Reloading global functions.\") false;\
\n :do {\
\n /system/script/run global-functions;\
\n } on-error={\
\n \$LogPrintExit2 error \$0 (\"Reloading global functions failed!\") false;\
\n }\
\n }\
\n\
\n :if (\$ReloadGlobalConfig = true) do={\
\n \$LogPrintExit2 info \$0 (\"Reloading global configuration.\") false;\
\n :do {\
\n /system/script/run global-config;\
\n } on-error={\
\n \$LogPrintExit2 error \$0 (\"Reloading global configuration failed!\" . \\\
\n \" Syntax error or missing overlay\?\") false;\
\n }\
\n }\
\n\
\n :if (\$ExpectedConfigVersionBefore > \$ExpectedConfigVersion) do={\
\n \$LogPrintExit2 warning \$0 (\"The configuration version decreased from \" . \\\
\n \$ExpectedConfigVersionBefore . \" to \" . \$ExpectedConfigVersion . \\\
\n \". Installed an older version\?\") false;\
\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 \$LogPrintExit2 debug \$0 (\"Fetching news, changes and migration: \" . \$Url) false;\
\n :local Result [ /tool/fetch check-certificate=yes-without-crl http-header-field=\$FetchUserAgent \\\
\n \$Url output=user as-value ];\
\n :if (\$Result->\"status\" = \"finished\") do={\
\n :set ChangeLogCode (\$Result->\"data\");\
\n }\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"Failed fetching news, changes and migration!\") false;\
\n }\
\n\
\n :if ([ :len \$ChangeLogCode ] > 0) do={\
\n :if ([ \$ValidateSyntax \$ChangeLogCode ] = true) do={\
\n :do {\
\n [ :parse \$ChangeLogCode ];\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"The changelog failed to run!\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"The changelog failed syntax validation!\") false;\
\n }\
\n }\
\n\
\n :if ([ :len \$GlobalConfigMigration ] > 0) do={\
\n :for I from=(\$ExpectedConfigVersionBefore + 1) to=\$ExpectedConfigVersion do={\
\n :local Migration (\$GlobalConfigMigration->[ :tostr \$I ]);\
\n :if ([ :typeof \$Migration ] = \"str\") do={\
\n :if ([ \$ValidateSyntax \$Migration ] = true) do={\
\n \$LogPrintExit2 info \$0 (\"Applying migration for change \" . \$I . \": \" . \$Migration) false;\
\n :do {\
\n [ :parse \$Migration ];\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"Migration code for change \" . \$I . \" failed to run!\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"Migration code for change \" . \$I . \" failed syntax validation!\") false;\
\n }\
\n }\
\n }\
\n }\
\n\
\n :local NotificationMessage (\"The configuration version on \" . \$Identity . \" increased \" . \\\
\n \"to \" . \$ExpectedConfigVersion . \", current configuration may need modification. \" . \\\
\n \"Please review and update global-config-overlay, then re-run global-config.\");\
\n \$LogPrintExit2 info \$0 (\$NotificationMessage) false;\
\n\
\n :if ([ :len \$GlobalConfigChanges ] > 0) do={\
\n :set NotificationMessage (\$NotificationMessage . \"\\n\\nChanges:\");\
\n :for I from=(\$ExpectedConfigVersionBefore + 1) to=\$ExpectedConfigVersion do={\
\n :local Change (\$GlobalConfigChanges->[ :tostr \$I ]);\
\n :set NotificationMessage (\$NotificationMessage . \"\\n \" . \\\
\n [ \$IfThenElse (\$NotificationsWithSymbols = true) (\"\\E2\\97\\8F\") \"*\" ] . \" \" . \$Change);\
\n \$LogPrintExit2 info \$0 (\"Change \" . \$I . \": \" . \$Change) false;\
\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 this is \" . \\\
\n \"of value for you or your business please consider a donation.\");\
\n :set Link \"https://git.eworm.de/cgit/routeros-scripts/about/#donate\";\
\n }\
\n\
\n \$SendNotification2 ({ origin=\$0; \\\
\n subject=([ \$SymbolForNotification \"pushpin\" ] . \"News and configuration changes\"); \\\
\n message=\$NotificationMessage; link=\$Link });\
\n }\
\n\
\n :set GlobalConfigChanges;\
\n :set GlobalConfigMigration;\
\n }\
\n}\
\n\
\n# lock script against multiple invocation\
\n:set ScriptLock do={\
\n :local Script [ :tostr \$1 ];\
\n :local DoReturn \$2;\
\n :local WaitMax ([ :tonum \$3 ] * 10);\
\n\
\n :global GetRandom20CharAlNum;\
\n :global IfThenElse;\
\n :global LogPrintExit2;\
\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) do={ :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 true; }\
\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 \$LogPrintExit2 error \$0 (\"A script named '\" . \$Script . \"' does not exist!\") true;\
\n }\
\n\
\n :if ([ \$JobCount \$Script ] = 0) do={\
\n \$LogPrintExit2 error \$0 (\"No script '\" . \$Script . \"' is running!\") true;\
\n }\
\n\
\n :if ([ \$TicketCount \$Script ] >= [ \$JobCount \$Script ]) do={\
\n \$LogPrintExit2 error \$0 (\"More tickets than running scripts '\" . \$Script . \"', resetting!\") false;\
\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 && ([ \$IsFirstTicket \$Script \$MyTicket ] = false || [ \$TicketCount \$Script ] < [ \$JobCount \$Script ])) do={\
\n :set WaitCount (\$WaitCount + 1);\
\n :delay 100ms;\
\n }\
\n\
\n :if ([ \$IsFirstTicket \$Script \$MyTicket ] = true && [ \$TicketCount \$Script ] = [ \$JobCount \$Script ]) do={\
\n \$RemoveTicket \$Script \$MyTicket;\
\n \$CleanupTickets \$Script;\
\n :return false;\
\n }\
\n\
\n \$RemoveTicket \$Script \$MyTicket;\
\n \$LogPrintExit2 info \$0 (\"Script '\" . \$Script . \"' started more than once\" . [ \$IfThenElse (\$WaitCount > 0) \\\
\n \" and timed out waiting for lock\" \"\" ] . \"... Aborting.\") [ \$IfThenElse (\$DoReturn = true) false true ];\
\n :return true;\
\n}\
\n\
\n# send notification via NotificationFunctions - expects at least two string arguments\
\n:set SendNotification do={\
\n :global SendNotification2;\
\n\
\n \$SendNotification2 ({ subject=\$1; message=\$2; link=\$3; silent=\$4 });\
\n}\
\n\
\n# send notification via NotificationFunctions - expects one array argument\
\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 Symbols {\
\n \"abacus\"=\"\\F0\\9F\\A7\\AE\";\
\n \"alarm-clock\"=\"\\E2\\8F\\B0\";\
\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 \"high-voltage-sign\"=\"\\E2\\9A\\A1\";\
\n \"incoming-envelope\"=\"\\F0\\9F\\93\\A8\";\
\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 \"sparkles\"=\"\\E2\\9C\\A8\";\
\n \"speech-balloon\"=\"\\F0\\9F\\92\\AC\";\
\n \"up-arrow\"=\"\\E2\\AC\\86\";\
\n \"warning-sign\"=\"\\E2\\9A\\A0\";\
\n \"white-heavy-check-mark\"=\"\\E2\\9C\\85\"\
\n }\
\n\
\n :return ((\$Symbols->\$1) . \"\\EF\\B8\\8F\");\
\n}\
\n\
\n# return symbol for notification\
\n:set SymbolForNotification do={\
\n :global NotificationsWithSymbols;\
\n :global SymbolByUnicodeName;\
\n\
\n :if (\$NotificationsWithSymbols != true) do={\
\n :return \"\";\
\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 :local Input [ :tostr \$1 ];\
\n\
\n :global CharacterReplace;\
\n\
\n :return [ \$CharacterReplace [ \$CharacterReplace \$Input \\\
\n (\"\\n\") (\"\\r\\n\") ] (\"\\r\\r\\n\") (\"\\r\\n\") ];\
\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={ \"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 = \"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 + (\$Value * \$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\
\n :set FileName [ \$CleanFilePath \$FileName ];\
\n :local I 1;\
\n :local Delay ([ :totime [ \$EitherOr \$WaitTime 2s ] ] / 20);\
\n\
\n :while ([ :len [ /file/find where name=\$FileName ] ] = 0) do={\
\n :if (\$I >= 20) 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 \$LogPrintExit2 error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' failed to run.\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' failed syntax validation, skipping.\") false;\
\n }\
\n}\
\n\
\n# signal we are ready\
\n:set GlobalFunctionsReady true;\
\n"
/system script add dont-require-permissions=no name=netwatch-notify owner=netwatch-notify policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#!rsc by RouterOS\
\n# RouterOS script: netwatch-notify\
\n# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# monitor netwatch and send notifications\
\n# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-notify.md\
\n\
\n:local 0 \"netwatch-notify\";\
\n:global GlobalFunctionsReady;\
\n:while (\$GlobalFunctionsReady != true) do={ :delay 500ms; }\
\n\
\n:global NetwatchNotify;\
\n\
\n:global EitherOr;\
\n:global IfThenElse;\
\n:global IsDNSResolving;\
\n:global LogPrintExit2;\
\n:global ParseKeyValueStore;\
\n:global ScriptFromTerminal;\
\n:global ScriptLock;\
\n:global SendNotification2;\
\n:global SymbolForNotification;\
\n\
\n:local NetwatchNotifyHook do={\
\n :local Name [ :tostr \$1 ];\
\n :local Type [ :tostr \$2 ];\
\n :local State [ :tostr \$3 ];\
\n :local Hook [ :tostr \$4 ];\
\n\
\n :global LogPrintExit2;\
\n :global ValidateSyntax;\
\n\
\n :if ([ \$ValidateSyntax \$Hook ] = true) do={\
\n :do {\
\n [ :parse \$Hook ];\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"The \" . \$State . \"-hook for \" . \$Type . \" '\" . \$Name . \\\
\n \"' failed to run.\") false;\
\n :return (\"The hook failed to run.\");\
\n }\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"The \" . \$State . \"-hook for \" . \$Type . \" '\" . \$Name . \\\
\n \"' failed syntax validation.\") false;\
\n :return (\"The hook failed syntax validation.\");\
\n }\
\n\
\n \$LogPrintExit2 info \$0 (\"Ran hook on \" . \$Type . \" '\" . \$Name . \"' \" . \$State . \": \" . \\\
\n \$Hook) false;\
\n :return (\"Ran hook:\\n\" . \$Hook);\
\n}\
\n\
\n\$ScriptLock \$0;\
\n\
\n:local ScriptFromTerminalCached [ \$ScriptFromTerminal \$0 ];\
\n\
\n:if ([ :typeof \$NetwatchNotify ] = \"nothing\") do={\
\n :set NetwatchNotify ({});\
\n}\
\n\
\n:foreach Host in=[ /tool/netwatch/find where comment~\"notify\" !disabled status!=\"unknown\" ] do={\
\n :local HostVal [ /tool/netwatch/get \$Host ];\
\n :local Type [ \$IfThenElse (\$HostVal->\"type\" ~ \"^(https\?-get|tcp-conn)\\\$\") \"service\" \"host\" ];\
\n :local HostInfo [ \$ParseKeyValueStore (\$HostVal->\"comment\") ];\
\n :local HostDetails (\$HostVal->\"host\" . \\\
\n [ \$IfThenElse ([ :len (\$HostInfo->\"resolve\") ] > 0) (\", \" . \$HostInfo->\"resolve\") ]);\
\n\
\n :if (\$HostInfo->\"notify\" = true && \$HostInfo->\"disabled\" != true) do={\
\n :local Name [ \$EitherOr (\$HostInfo->\"name\") (\$HostVal->\"name\") ];\
\n\
\n :local Metric { \"count-down\"=0; \"count-up\"=0; \"notified\"=false; \"resolve-failcnt\"=0 };\
\n :if ([ :typeof (\$NetwatchNotify->\$Name) ] = \"array\") do={\
\n :set \$Metric (\$NetwatchNotify->\$Name);\
\n }\
\n\
\n :if ([ :typeof (\$HostInfo->\"resolve\") ] = \"str\") do={\
\n :if ([ \$IsDNSResolving ] = true) do={\
\n :do {\
\n :local Resolve [ :resolve (\$HostInfo->\"resolve\") ];\
\n :if (\$Resolve != \$HostVal->\"host\") do={\
\n \$LogPrintExit2 info \$0 (\"Name '\" . \$HostInfo->\"resolve\" . [ \$IfThenElse \\\
\n (\$HostInfo->\"resolve\" != \$HostInfo->\"name\") (\"' for \" . \$Type . \" '\" . \\\
\n \$HostInfo->\"name\") \"\" ] . \"' resolves to different address \" . \$Resolve . \\\
\n \", updating.\") false;\
\n /tool/netwatch/set host=\$Resolve \$Host;\
\n :set (\$Metric->\"resolve-failcnt\") 0;\
\n }\
\n } on-error={\
\n :set (\$Metric->\"resolve-failcnt\") (\$Metric->\"resolve-failcnt\" + 1);\
\n :if (\$Metric->\"resolve-failcnt\" = 3) do={\
\n \$LogPrintExit2 warning \$0 (\"Resolving name '\" . \$HostInfo->\"resolve\" . [ \$IfThenElse \\\
\n (\$HostInfo->\"resolve\" != \$HostInfo->\"name\") (\"' for \" . \$Type . \" '\" . \\\
\n \$HostInfo->\"name\") \"\" ] . \"' failed.\") false;\
\n }\
\n }\
\n }\
\n }\
\n\
\n :if (\$HostVal->\"status\" = \"up\") do={\
\n :local CountDown (\$Metric->\"count-down\");\
\n :if (\$CountDown > 0) do={\
\n \$LogPrintExit2 info \$0 \\\
\n (\"The \" . \$Type . \" '\" . \$Name . \"' (\" . \$HostDetails . \") is up.\") false;\
\n :set (\$Metric->\"count-down\") 0;\
\n }\
\n :set (\$Metric->\"count-up\") (\$Metric->\"count-up\" + 1);\
\n :if (\$Metric->\"notified\" = true) do={\
\n :local Message (\"The \" . \$Type . \" '\" . \$Name . \"' (\" . \$HostDetails . \\\
\n \") is up since \" . \$HostVal->\"since\" . \".\\n\" . \\\
\n \"It was down for \" . \$CountDown . \" checks since \" . (\$Metric->\"since\") . \".\");\
\n :if ([ :typeof (\$HostInfo->\"note\") ] = \"str\") do={\
\n :set Message (\$Message . \"\\n\\nNote:\\n\" . (\$HostInfo->\"note\"));\
\n }\
\n :if ([ :typeof (\$HostInfo->\"up-hook\") ] = \"str\") do={\
\n :set Message (\$Message . \"\\n\\n\" . [ \$NetwatchNotifyHook \$Name \$Type \"up\" \\\
\n (\$HostInfo->\"up-hook\") ]);\
\n }\
\n \$SendNotification2 ({ origin=\$0; silent=(\$HostInfo->\"silent\"); \\\
\n subject=([ \$SymbolForNotification \"white-heavy-check-mark\" ] . \"Netwatch Notify: \" . \\\
\n \$Name . \" up\"); \\\
\n message=\$Message });\
\n }\
\n :set (\$Metric->\"notified\") false;\
\n :set (\$Metric->\"parent\") (\$HostInfo->\"parent\");\
\n :set (\$Metric->\"since\");\
\n } else={\
\n :set (\$Metric->\"count-down\") (\$Metric->\"count-down\" + 1);\
\n :set (\$Metric->\"count-up\") 0;\
\n :set (\$Metric->\"parent\") (\$HostInfo->\"parent\");\
\n :set (\$Metric->\"since\") (\$HostVal->\"since\");\
\n :local CountDown [ \$IfThenElse ([ :tonum (\$HostInfo->\"count\") ] > 0) (\$HostInfo->\"count\") 5 ];\
\n :local Parent (\$HostInfo->\"parent\");\
\n :local ParentUp false;\
\n :while ([ :len \$Parent ] > 0) do={\
\n :set CountDown (\$CountDown + 1);\
\n :set Parent (\$NetwatchNotify->\$Parent->\"parent\");\
\n }\
\n :set Parent (\$HostInfo->\"parent\");\
\n :local ParentNotified false;\
\n :while (\$ParentNotified = false && [ :len \$Parent ] > 0) do={\
\n :set ParentNotified [ \$IfThenElse ((\$NetwatchNotify->\$Parent->\"notified\") = true) \\\
\n true false ];\
\n :set ParentUp (\$NetwatchNotify->\$Parent->\"count-up\");\
\n :if (\$ParentNotified = false) do={\
\n :set Parent (\$NetwatchNotify->\$Parent->\"parent\");\
\n }\
\n }\
\n :if (\$Metric->\"notified\" = false || \$Metric->\"count-down\" % 120 = 0 || \\\
\n \$ScriptFromTerminalCached = true) do={\
\n \$LogPrintExit2 [ \$IfThenElse (\$HostInfo->\"no-down-notification\" != true) info debug ] \$0 \\\
\n (\"The \" . \$Type . \" '\" . \$Name . \"' (\" . \$HostDetails . \") is down for \" . \\\
\n \$Metric->\"count-down\" . \" checks, \" . [ \$IfThenElse (\$ParentNotified = false) [ \$IfThenElse \\\
\n (\$Metric->\"notified\" = true) (\"already notified.\") (\$CountDown - \$Metric->\"count-down\" . \\\
\n \" to go.\") ] (\"parent \" . \$Type . \" \" . \$Parent . \" is down.\") ]) false;\
\n }\
\n :if (((\$CountDown * 2) - (\$Metric->\"count-down\" * 3)) / 2 = 0 && \\\
\n [ :typeof (\$HostInfo->\"pre-down-hook\") ] = \"str\") do={\
\n \$NetwatchNotifyHook \$Name \$Type \"pre-down\" (\$HostInfo->\"pre-down-hook\");\
\n }\
\n :if (\$ParentNotified = false && \$Metric->\"count-down\" >= \$CountDown && \\\
\n (\$ParentUp = false || \$ParentUp > 2) && \$Metric->\"notified\" != true) do={\
\n :local Message (\"The \" . \$Type . \" '\" . \$Name . \"' (\" . \$HostDetails . \\\
\n \") is down since \" . \$HostVal->\"since\" . \".\");\
\n :if ([ :typeof (\$HostInfo->\"note\") ] = \"str\") do={\
\n :set Message (\$Message . \"\\n\\nNote:\\n\" . (\$HostInfo->\"note\"));\
\n }\
\n :if ([ :typeof (\$HostInfo->\"down-hook\") ] = \"str\") do={\
\n :set Message (\$Message . \"\\n\\n\" . [ \$NetwatchNotifyHook \$Name \$Type \"down\" \\\
\n (\$HostInfo->\"down-hook\") ]);\
\n }\
\n :if (\$HostInfo->\"no-down-notification\" != true) do={\
\n \$SendNotification2 ({ origin=\$0; silent=(\$HostInfo->\"silent\"); \\\
\n subject=([ \$SymbolForNotification \"cross-mark\" ] . \"Netwatch Notify: \" . \\\
\n \$Name . \" down\"); \\\
\n message=\$Message });\
\n }\
\n :set (\$Metric->\"notified\") true;\
\n }\
\n }\
\n :set (\$NetwatchNotify->\$Name) {\
\n \"count-down\"=(\$Metric->\"count-down\");\
\n \"count-up\"=(\$Metric->\"count-up\");\
\n \"notified\"=(\$Metric->\"notified\");\
\n \"parent\"=(\$Metric->\"parent\");\
\n \"resolve-failcnt\"=(\$Metric->\"resolve-failcnt\");\
\n \"since\"=(\$Metric->\"since\") };\
\n }\
\n}\
\n"
/system script add dont-require-permissions=no name="Dynamic Data New" owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\r\
\n/system\r\
\n:local cdate [clock get date] \r\
\n:local yyyy [:pick \$cdate 0 4]\r\
\n:local MM [:pick \$cdate 5 7]\r\
\n:local dd [:pick \$cdate 8 10]\r\
\n:local identitydate \"\$[identity get name]_\$yyyy-\$MM-\$dd\"\r\
\n\r\
\n:local fileup \"\$identitydate-dynamicdata\"\r\
\n\r\
\n/ip/cloud print file=cloud\r\
\n/ip/dhcp-server/lease print file=leases\r\
\n/interface/bridge/host print file=hosts\r\
\n/interface/wireless/registration-table print file=registrations\r\
\n\r\
\n:local cloudcontents [/file get cloud.txt contents]\r\
\n:local leasescontents [/file get leases.txt contents]\r\
\n:local hostscontents [/file get hosts.txt contents]\r\
\n:local registrationscontents [/file get registrations.txt contents]\r\
\n\r\
\n:local fileupcontents \"\$cloudcontents . \$leasescontents . \$hostscontents . \$registrationscontents\"\r\
\n\r\
\n/file print file=\"\$fileup\" \r\
\n/file set \$fileup contents=\$fileupcontents\r\
\n\r\
\n#/tool fetch upload=yes mode=ftp ascii=no src-path=\"/\$fileup\" dst-path=\"/mikrotik-backups/\$fileup\" address=192.168.2.22 port=21 user=mikrotik password=XXXXX\r\
\n\r\
\n/file remove [find name=\$fileup]\r\
\n\r\
\n#/file remove \"\$fileup\"\r\
\n"
/system script add dont-require-permissions=no name=dynamic-data-rextended owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/system\r\
\n:local identitydate \"\$[identity get name]_\$[clock get date]\"\r\
\n:local stringexec \"/system iden print; :put \\\"\\\\r\\\\n\\\"; /ip cloud pri; :put \\\"\\\\r\\\\n\\\"; /ip dhcp-server lease pri det; :put \\\"\\\\r\\\\n\\\"; /int bridge host pri det\"\r\
\n\r\
\n:if ([:len [/system package find where name=\"wifiwave2\"]] > 1) do={\r\
\n :set stringexec \"\$stringexec; :put \\\"\\\\r\\\\n\\\" /int wifiwave2 reg pri det\"\r\
\n} \r\
\n\r\
\n:if ([:len [/system package find where name=\"wifiwave2\"]] > 1) do={\r\
\n :set stringexec \"\$stringexec; :put \\\"\\\\r\\\\n\\\" /int wireless reg pri det\"\r\
\n}\r\
\n\r\
\n\r\
\n/file remove [find where name=tmpresults.txt]\r\
\n:delay 1s\r\
\n:execute \$stringexec file=tmpresults.txt\r\
\n:delay 2s\r\
\n\r\
\n/tool fetch upload=yes mode=ftp ascii=no address=192.168.2.22 port=21 user=mikrotik password=XXXXX \\\r\
\n src-path=tmpresults.txt dst-path=\"/mikrotik-backups/\$identitydate-dynamicdata.txt\"\r\
\n\r\
\n/file remove [find where name=tmpresults.txt]"
/system script add dont-require-permissions=no name=dhcpleasesftp owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\r\
\n/file remove [find where name=temp3.txt]\r\
\n\r\
\n/system\r\
\n\r\
\n:local identitydate \"\$[identity get name]\"\r\
\n\r\
\n:local stringexec \"/ip dhcp-server lease; :foreach i in=[find] do={ :put ([get \\\$i address].\\\",\\\".[get \\\$i comment].\\\",\\\",[get \\\$i mac-address].\\\",\\\".[get \\\$i host-name] ) }\"\r\
\n\r\
\n\r\
\n:execute \$stringexec file=temp3\r\
\n\r\
\n:delay 60\r\
\n\r\
\n/tool fetch address=192.168.2.22 port=21 user=mikrotik password=XXXXX src-path=temp3.txt mode=ftp dst-path=\"/mikrotik-backups/\$identitydate-leases.txt\" upload=yes ascii=no\r\
\n\r\
\n\r\
\n\r\
\n\r\
\n"
/system script add dont-require-permissions=no name=DynDNS owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\r\
\n/system\r\
\n:local cdate [clock get date] \r\
\n:local yyyy [:pick \$cdate 0 4]\r\
\n:local MM [:pick \$cdate 5 7]\r\
\n:local dd [:pick \$cdate 8 10]\r\
\n:local identitydate \"\$[identity get name]_\$yyyy-\$MM-\$dd\"\r\
\n#/export show-sensitive file=\"\$identitydate\"\r\
\n\r\
\n# Export public IP and mail it\r\
\n\r\
\n#/ip/address print file=\"\$identitydate-IP\"\r\
\n\r\
\n#/tool fetch upload=yes mode=ftp ascii=no src-path=\"\$[\$identitydate]-IP.txt\" dst-path=\"/mikrotik-backups/\$[\$identitydate]-IP.txt\" address=192.168.2.22 port=21 user=mikrotik password=XXXXX\r\
\n\r\
\n#/file remove \"\$identitydate-IP.txt\"\r\
\n\r\
\n# Set needed variables\r\
\n\t:local username \"josephXXXXX\"\r\
\n\t:local clientkey XXXXX788e206873aa78bc3\"\r\
\n\t:local hostname \"XXXXX.dyndns.org\"\r\
\n\r\
\n\t:global dyndnsForce\r\
\n\t:global previousIP\r\
\n\r\
\n# get the current IP address from the internet (in case of double-nat)\r\
\n\t/tool fetch mode=http address=\"checkip.dyndns.org\" src-path=\"/\" dst-path=\"/dyndns.checkip.html\"\r\
\n\t:delay 1\r\
\n\t:local result [/file get dyndns.checkip.html contents]\r\
\n\r\
\n# parse the current IP result\r\
\n\t:local resultLen [:len \$result]\r\
\n\t:local startLoc [:find \$result \": \" -1]\r\
\n\t:set startLoc (\$startLoc + 2)\r\
\n\t:local endLoc [:find \$result \"</body>\" -1]\r\
\n\t:local currentIP [:pick \$result \$startLoc \$endLoc]\r\
\n\t:log info \"UpdateDynDNS: currentIP = \$currentIP\"\r\
\n\r\
\n# Remove the # on next line to force an update every single time - useful for debugging,\r\
\n# but you could end up getting blacklisted by DynDNS!\r\
\n\r\
\n#:set dyndnsForce true\r\
\n\r\
\n# Determine if dyndns update is needed\r\
\n# more dyndns updater request details https://help.dyn.com/remote-access-api/perform-update/\r\
\n\t:log info \"UpdateDynDNS: previousIP = \$previousIP\"\r\
\n\t:if (\$dyndnsForce = true) do={ :log warning \"UpdateDynDNS: Forced update on\" }\r\
\n\r\
\n\t:if ((\$currentIP != \$previousIP) || (\$dyndnsForce = true)) do={\r\
\n\t\t:set dyndnsForce false\r\
\n\t\t:set previousIP \$currentIP\r\
\n\r\
\n\t\t/tool fetch mode=https \\\r\
\n\t\turl=\"https://\$username:\$clientkey XXXXXg/v3/update\?hostname=\$hostname&myip=\$currentIP\" \\ \r\
\n\t\tdst-path=\"/dyndns.txt\"\r\
\n\r\
\n\t\t:delay 1\r\
\n\t\t:local result [/file get dyndns.txt contents]\r\
\n\t\t:log info (\"UpdateDynDNS: Dyndns update needed\")\r\
\n\t\t:log info (\"UpdateDynDNS: Dyndns Update Result: \".\$result)\r\
\n\t\t:put (\"Dyndns Update Result: \".\$result)\r\
\n\r\
\n /ip/address print file=\"\$identitydate-IP\"\r\
\n\r\
\n /tool fetch upload=yes mode=ftp ascii=no src-path=\"\$[\$identitydate]-IP.txt\" dst-path=\"/mikrotik-backups/\$[\$identitydate]-IP.txt\" address=192.168.2.22 port=21 user=mikrotik password=XXXXX\r\
\n\r\
\n /file remove \"\$identitydate-IP.txt\"\r\
\n\r\
\n\r\
\n\t} else={\r\
\n\t\t:log info (\"UpdateDynDNS: No dyndns update needed\")\r\
\n\t}\r\
\n\r\
\n"
/system script add dont-require-permissions=no name=Data_to_Splunk_using_Syslog owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="# Collect information from Mikrotik RouterOS\r\
\n# Jotne 2024\r\
\n# Script name=Data_to_Splunk_using_Syslog\r\
\n:log info message=\"script=version ver=5.6\"\r\
\n# ----------------------------------\r\
\n\r\
\n# Auto update syslog server. 5.3-5.4.\r\
\n# Change <your syslog dns name> to the dns of your syslog server.\r\
\n# The update is disabled by default. Remove the # from the two next line to use it.\r\
\n\r\
\n#:local mySyslog [resolve <your syslog dns name>]\r\
\n#/system/logging/action/set [find where name=\"logserver\"] remote=\$mySyslog\r\
\n\r\
\n\r\
\n# What data to collect. Set to false to skip the section \r\
\n# ----------------------------------\r\
\n:local SystemResource true\r\
\n:local SystemInformation true\r\
\n:local SystemHealth true\r\
\n:local TrafficData true\r\
\n:local AccountData true\r\
\n:local uPnP true\r\
\n:local Wireless true\r\
\n:local AddressLists true\r\
\n:local DHCP true\r\
\n:local Neighbor true\r\
\n:local InterfaceData true\r\
\n:local CmdHistory true\r\
\n:local CAPsMANN false\r\
\n\r\
\n:local Routing true\r\
\n:local OSPF false\r\
\n:local BGP false\r\
\n\r\
\n:local PPP true\r\
\n:local IPSEC true\r\
\n\r\
\n# Get RouterOS main version (used to run different script on different version)\r\
\n:local train [:tonum [:pick [/system resource get version] 0 1]] \r\
\n\r\
\n# Collect system resource\r\
\n# ----------------------------------\r\
\n:if (\$SystemResource) do={\r\
\n\t/system resource\r\
\n\t:local cpuload [get cpu-load]\r\
\n\t:local freemem ([get free-memory]/1048576)\r\
\n\t:local totmem ([get total-memory]/1048576)\r\
\n\t:local freehddspace ([get free-hdd-space]/1048576)\r\
\n\t:local totalhddspace ([get total-hdd-space]/1048576)\r\
\n\t:local up [get uptime]\r\
\n\t:local sector [get write-sect-total]\r\
\n\t:log info message=\"script=resource free_memory=\$freemem MB total_memory=\$totmem MB free_hdd_space=\$freehddspace MB total_hdd_space=\$totalhddspace MB cpu_load=\$cpuload uptime=\$up write-sect-total=\$sector\"\r\
\n}\r\
\n\r\
\n\r\
\n# Make some part only run every hours\r\
\n# ----------------------------------\r\
\n:global Hour\r\
\n:local run false\r\
\n:local hour [:pick [/system clock get time] 0 2]\r\
\n:if (\$Hour != \$hour) do={\r\
\n\t:global Hour \$hour\r\
\n\t:set run true\r\
\n}\r\
\n\r\
\n\r\
\n# Get NTP status\r\
\n# ----------------------------------\r\
\n:local ntpstatus \"\"\r\
\n:if ([:len [/system package find where !disabled and name=ntp]] > 0 or [:tonum [:pick [/system resource get version] 0 1]] > 6) do={\r\
\n :set ntpstatus [/system ntp client get status]\r\
\n} else={\r\
\n :if ([:typeof [/system ntp client get last-update-from]] = \"nil\") do={\r\
\n :set ntpstatus \"using-local-clock\"\r\
\n } else={\r\
\n :set ntpstatus \"synchronized\"\r\
\n }\r\
\n}\r\
\n:log info message=\"script=ntp status=\$ntpstatus\" \r\
\n\r\
\n\r\
\n# Get interface traffic data for all interface\r\
\n# ----------------------------------\r\
\n:if (\$TrafficData) do={\r\
\n\t:foreach id in=[/interface find] do={\r\
\n\t\t:local output \"\$[/interface print stats as-value where .id=\$id]\"\r\
\n\t\t:set ( \"\$output\"->\"script\" ) \"if_traffic\"\r\
\n\t\t:log info message=\"\$output\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n\r\
\n# Get traffic data v2 (Kid Control)\r\
\n# ----------------------------------\r\
\n:if (\$AccountData) do={\r\
\n\t:foreach logline in=[/ip kid-control device find] do={\r\
\n\t\t:local output \"\$[/ip kid-control device get \$logline]\"\r\
\n\t\t:set ( \"\$output\"->\"script\" ) \"kids\"\r\
\n\t\t:log info message=\"\$output\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n\r\
\n# Finding dynmaic lines used in uPnP\r\
\n# ----------------------------------\r\
\n:if (\$uPnP) do={\r\
\n\t:foreach logline in=[/ip firewall nat find where dynamic=yes and comment~\"^upnp \"] do={\r\
\n\t\t:local output \"\$[/ip firewall nat print as-value from=\$logline]\"\r\
\n\t\t:set ( \"\$output\"->\"script\" ) \"upnp\"\r\
\n\t\t:log info message=\"\$output\" \r\
\n\t}\r\
\n}\r\
\n\r\
\n\r\
\n# Collect system information 5.5 added ID for non routerBoard 5.6 Remvoed serial\r\
\n# ----------------------------------\r\
\n:local model na\r\
\n:local ffirmware na\r\
\n:local cfirmware na\r\
\n:local ufirmware na\r\
\n:if (\$SystemInformation and \$run) do={\r\
\n\t:local version ([/system resource get version])\r\
\n\t:local board ([/system resource get board-name])\r\
\n\t:local identity ([/system identity get name])\r\
\n\t:do {\r\
\n\t\t:if (\$board!=\"CHR\" OR \$board!=\"x86\") do={\r\
\n\t\t\t/system routerboard\r\
\n\t\t\t:set model ([get model])\r\
\n\t\t\t:set ffirmware ([get factory-firmware])\r\
\n\t\t\t:set cfirmware ([get current-firmware])\r\
\n\t\t\t:set ufirmware ([get upgrade-firmware])\r\
\n\t\t}\r\
\n\t} on-error={}\r\
\n\t:log info message=\"script=sysinfo version=\\\"\$version\\\" board-name=\\\"\$board\\\" model=\\\"\$model\\\" identity=\\\"\$identity\\\" factory-firmware=\\\"\$ffirmware\\\" current-firmware=\\\"\$cfirmware\\\" upgrade-firmware=\\\"\$ufirmware\\\"\"\r\
\n}\r\
\n\r\
\n\r\
\n# Collect system health\r\
\n# ----------------------------------\r\
\n:if (\$train > 6 and \$SystemHealth) do={\r\
\n\t# New version (RouterOS >6)\r\
\n\t:foreach id in=[/system health find] do={\r\
\n\t\t:local health \"\$[/system health get \$id]\"\r\
\n\t\t:set ( \"\$health\"->\"script\" ) \"health\"\r\
\n\t\t:log info message=\"\$health\"\r\
\n\t}\r\
\n} else={\r\
\n\t# Old version (RouterOS 6 or older)\r\
\n\t:if (!([/system health get]~\"(state=disabled|^\\\$)\")) do={\r\
\n\t\t:local health \"\$[/system health get]\"\r\
\n\t\t:set ( \"\$health\"->\"script\" ) \"health\"\r\
\n\t\t:log info message=\"\$health\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n\r\
\n\r\
\n# Sends wireless client data to log server \r\
\n# ----------------------------------\r\
\n:if (\$Wireless && [:len [/int find where type=wlan]]>0) do={\r\
\n\t/interface wireless registration-table\r\
\n\t:foreach i in=[find] do={\r\
\n\t\t:log info message=\".id=\$i;ap=\$([get \$i ap]);interface=\$([get \$i interface]);mac-address=\$([get \$i mac-address]);signal-strength=\$([get \$i signal-strength]);tx-rate=\$([get \$i tx-rate]);uptime=\$([get \$i uptime]);script=wifi\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n\r\
\n# Count IP in address-lists\r\
\n#----------------------------------\r\
\n:if (\$AddressLists) do={\r\
\n\t:local array [ :toarray \"\" ]\r\
\n\t:local addrcntdyn [:toarray \"\"] \r\
\n\t:local addrcntstat [:toarray \"\"] \r\
\n\t:local test\r\
\n\t:foreach id in=[/ip firewall address-list find] do={\r\
\n\t\t:local rec [/ip firewall address-list get \$id]\r\
\n\t\t:local listname (\$rec->\"list\")\r\
\n\t\t:local listdynamic (\$rec->\"dynamic\")\r\
\n\t\t:if (!(\$array ~ \$listname)) do={ :set array (\$array , \$listname) }\r\
\n\t\t:if (\$listdynamic = true) do={\r\
\n\t\t\t:set (\$addrcntdyn->\$listname) (\$addrcntdyn->\$listname+1)\r\
\n\t\t} else={\r\
\n\t\t\t:set (\$addrcntstat->\$listname) (\$addrcntstat->\$listname+1)}\r\
\n\t}\r\
\n\t:foreach k in=\$array do={\r\
\n\t\t:log info message=(\"script=address_lists list=\$k dynamic=\".((\$addrcntdyn->\$k)+0).\" static=\".((\$addrcntstat->\$k)+0))}\r\
\n}\r\
\n\r\
\n\r\
\n# Get MNDP (CDP) Neighbors\r\
\n# ----------------------------------\r\
\n:if (\$Neighbor and \$run) do={\r\
\n\t:foreach neighborID in=[/ip neighbor find] do={\r\
\n\t\t:local nb [/ip neighbor get \$neighborID]\r\
\n\t\t:local id [:pick (\"\$nb\"->\".id\") 1 99]\r\
\n\t\t:foreach key,value in=\$nb do={\r\
\n\t\t\t:local newline [:find \$value \"\\n\"]\r\
\n\t\t\t:if ([\$newline]>0) do={\r\
\n\t\t\t\t:set value [:pick \$value 0 \$newline]\r\
\n\t\t\t}\r\
\n\t\t\t:log info message=\"script=neighbor nid=\$id \$key=\\\"\$value\\\"\"\r\
\n\t\t}\r\
\n\t}\r\
\n}\r\
\n\r\
\n\r\
\n# Collect DHCP Pool information\r\
\n# ----------------------------------\r\
\n:if (\$DHCP and \$run) do={\r\
\n\t/ip pool {\r\
\n\t\t:local poolname\r\
\n\t\t:local pooladdresses\r\
\n\t\t:local poolused\r\
\n\t\t:local minaddress\r\
\n\t\t:local maxaddress\r\
\n\t\t:local findindex\r\
\n\r\
\n# Iterate through IP Pools\r\
\n\t\t:foreach pool in=[find] do={\r\
\n\t\t\t:set poolname [get \$pool name]\r\
\n\t\t\t:set pooladdresses 0\r\
\n\t\t\t:set poolused 0\r\
\n\r\
\n# Iterate through current pool's IP ranges\r\
\n\t\t\t:foreach range in=[:toarray [get \$pool range]] do={\r\
\n\r\
\n# Get min and max addresses\r\
\n\t\t\t\t:set findindex [:find [:tostr \$range] \"-\"]\r\
\n\t\t\t\t:if ([:len \$findindex] > 0) do={\r\
\n\t\t\t\t\t:set minaddress [:pick [:tostr \$range] 0 \$findindex]\r\
\n\t\t\t\t\t:set maxaddress [:pick [:tostr \$range] (\$findindex + 1) [:len [:tostr \$range]]]\r\
\n\t\t\t\t} else={\r\
\n\t\t\t\t\t:set minaddress [:tostr \$range]\r\
\n\t\t\t\t\t:set maxaddress [:tostr \$range]\r\
\n\t\t\t\t}\r\
\n\r\
\n# Calculate number of ip in one range\r\
\n\t\t\t\t:set pooladdresses (\$maxaddress - \$minaddress)\r\
\n\r\
\n# /foreach range\r\
\n\t\t\t}\r\
\n\r\
\n# Test if pools is used in DHCP or VPN and show leases used\r\
\n\t\t\t:local dname [/ip dhcp-server find where address-pool=\$poolname]\r\
\n\t\t\t:if ([:len \$dname] = 0) do={\r\
\n# No DHCP server found, assume VPN\r\
\n\t\t\t\t:set poolused [:len [used find pool=[:tostr \$poolname]]]\r\
\n\t\t\t} else={\r\
\n# DHCP server found, count leases\r\
\n\t\t\t\t:local dname [/ip dhcp-server get [find where address-pool=\$poolname] name]\r\
\n\t\t\t\t:set poolused [:len [/ip dhcp-server lease find where server=\$dname]]}\r\
\n\r\
\n# Send data\r\
\n\t\t\t:log info message=(\"script=pool pool=\$poolname used=\$poolused total=\$pooladdresses\")\r\
\n\r\
\n# /foreach pool\r\
\n\t\t}\r\
\n# /ip pool\r\
\n\t}\r\
\n}\r\
\n\r\
\n\r\
\n# Get detailed command history RouterOS >= v7\r\
\n# ----------------------------------\r\
\n:if (\$train > 6 and \$CmdHistory) do={\r\
\n\t:global cmd\r\
\n\t:local f 0\r\
\n\t:foreach i in=[/system history find] do={\r\
\n\t\t:if (\$i = \$cmd) do={ :set f 1 }\r\
\n\t\t:if (\$f != 1) do={\r\
\n\t\t\t:log info message=\"StartCMD\"\r\
\n\t\t\t:log info message=[/system history get \$i]\r\
\n\t\t\t:log info message=\"EndCMD\"\r\
\n\t\t}\r\
\n\t}\r\
\n\t:global cmd [:pick [/system history find] 0]\r\
\n}\r\
\n\r\
\n\r\
\n# Test if CAPsMANN is installed and run script 5.5\r\
\n# ----------------------------------\r\
\n:if ( ([:len [/interface find where type=\"cap\"]] > 0) and \$CAPsMANN) do={ \r\
\n\t/system script run CAPsMANN\r\
\n}\r\
\n\r\
\n\r\
\n\r\
\n# Collect routing information\r\
\n# ----------------------------------\r\
\n:if (\$Routing) do={\r\
\n\t/ip route\r\
\n\t:foreach id in=[find] do={\r\
\n\t\t:local route \"\$[get \$id]\"\r\
\n\t\t:set ( \"\$route\"->\"script\" ) \"route\"\r\
\n\t\t:log info message=\"\$route\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n:if (\$OSPF) do={\r\
\n\t/routing ospf neighbor\r\
\n\t:foreach id in=[find] do={\r\
\n\t\t:local ospf \"\$[get \$id]\"\r\
\n\t\t:set ( \"\$ospf\"->\"script\" ) \"ospf\"\r\
\n\t\t:log info message=\"\$ospf\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n:if (\$BGP) do={\r\
\n\t/routing bgp session\r\
\n\t:foreach id in=[find] do={\r\
\n\t\t:local bgp \"\$[get \$id]\"\r\
\n\t\t:set ( \"\$bgp\"->\"script\" ) \"bgp\"\r\
\n\t\t:log info message=\"\$bgp\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n\r\
\n# Collect PPP/IPSEC\r\
\n# ----------------------------------\r\
\n:if (\$PPP) do={\r\
\n\t/ppp active\r\
\n\t:foreach id in=[find] do={\r\
\n\t\t:local ppp \"\$[get \$id]\"\r\
\n\t\t:set ( \"\$ppp\"->\"script\" ) \"ppp\"\r\
\n\t\t:log info message=\"\$ppp\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n:if (\$IPSEC) do={\r\
\n\t/ip ipsec active-peers\r\
\n\t:foreach id in=[find] do={\r\
\n\t\t:local ipsec \"\$[get \$id]\"\r\
\n\t\t:set ( \"\$ipsec\"->\"script\" ) \"ipsec\"\r\
\n\t\t:log info message=\"\$ipsec\"\r\
\n\t}\r\
\n}\r\
\n\r\
\n# End Script\r\
\n\r\
\n"
/system script add comment="for Splunk" dont-require-permissions=yes name=Netwatch owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="####################################\r\
\n# Netwatch script\r\
\n#\r\
\n# Used as both up and down script\r\
\n# Created Jotne 2021 v1.5\r\
\n#\r\
\n####################################\r\
\n:local Host \$host\r\
\n/tool netwatch\r\
\n:local Status [get [find where host=\"\$Host\"] status]\r\
\n:local Comment [get [find where host=\"\$Host\"] comment]\r\
\n:local Interval [get [find where host=\"\$Host\"] interval]\r\
\n:local Since [get [find where host=\"\$Host\"] since]\r\
\n:log info \"script=netwatch watch_host=\$Host comment=\\\"\$Comment\\\" status=\$Status interval=\$Interval since=\\\"\$Since\\\"\""
/system watchdog set auto-send-supout=yes ping-start-after-boot=15m ping-timeout=10m send-email-from=jXXXXX@domain.com send-email-to=jXXXXX@domain.com watch-address=192.168.2.2
/tool e-mail set from=jXXXXX@domain.com password=XXXXX port=587 server=smtp.gmail.com tls=starttls user=jXXXXX@domain.com
/tool graphing interface add
/tool graphing interface add interface=wireguard1
/tool graphing interface add interface=bridge
/tool graphing queue add
/tool graphing resource add
/tool netwatch add comment=Netwatch-8.8.4.4-this-box disabled=no down-script=":local thisBox [/system identity get name];\r\
\n\r\
\n:tool e-mail send to=jXXXXX@domain.com subject=\"\$thisBox DOWN\" body=( \"\$thisBox DOWN to 1.1.1.1\" )" host=8.8.4.4 http-codes="" interval=5m name=Netwatch-8.8.4.4-this-box test-script="" type=simple up-script=":local thisBox [/system identity get name];\r\
\n\r\
\n:tool e-mail send to=jXXXXX@domain.com subject=\"\$thisBox UP\" body=( \"\$thisBox UP to 1.1.1.1\" )"
/tool netwatch add disabled=no down-script=":local thisBox [/system identity get name];\r\
\n\r\
\n:tool e-mail send to=jXXXXX@domain.com subject=\"\$thisBox DOWN\" body=( [ :system clock get date ] . \" \" . [ :system clock get time ] . \"\$thisBox DOWN to 192.168.0.21\" )" host=192.168.20.21 http-codes="" interval=30s name=Netwatch-192.168.20.21-this-box test-script="" type=simple up-script=":local thisBox [/system identity get name];\r\
\n\r\
\n:tool e-mail send to=jXXXXX@domain.com subject=\"\$thisBox DOWN\" body=( [ :system clock get date ] . \" \" . [ :system clock get time ] . \"\$thisBox UP to 192.168.0.21\" )"
/tool netwatch add disabled=no down-script=":local thisBox [/system identity get name];\r\
\n\r\
\n:tool e-mail send to=jXXXXX@domain.com subject=\"\$thisBox DOWN\" body=( \"\$thisBox DOWN to 192.168.0.5\" )" host=192.168.20.5 http-codes="" interval=30s name=Netwatch-192.168.20.5-this-box test-script="" type=simple up-script=":local thisBox [/system identity get name];\r\
\n\r\
\n:tool e-mail send to=jXXXXX@domain.com subject=\"\$thisBox UP\" body=( \"\$thisBox UP to 192.168.0.5\" )"
/tool netwatch add disabled=no down-script=":local thisBox [/system identity get name];\r\
\n\r\
\n:tool e-mail send to=jXXXXX@domain.com subject=\"\$thisBox DOWN\" body=( \"\$thisBox DOWN to 192.168.0.22\" )" host=192.168.20.22 http-codes="" interval=30s name=Netwatch-192.168.20.22-this-box test-script="" type=simple up-script=":local thisBox [/system identity get name];\r\
\n\r\
\n:tool e-mail send to=jXXXXX@domain.com subject=\"\$thisBox UP\" body=( \"\$thisBox UP to 192.168.0.22\" )"
/tool netwatch add comment=WG-Tunnel-355 disabled=no down-script=Netwatch host=192.168.0.11 http-codes="" interval=30s name=Netwatch-192.168.0.11 test-script="" type=simple up-script=Netwatch
/tool netwatch add comment=WG-Tunnel-212 disabled=no down-script=Netwatch host=192.168.2.2 http-codes="" interval=30s name=Netwatch-192.168.2.2 test-script="" type=simple up-script=Netwatch
/tool netwatch add comment=Netwatch-1.1.1.1 disabled=no down-script=Netwatch host=1.1.1.1 http-codes="" interval=1m name=Netwatch-1.1.1.1 test-script="" type=simple up-script=Netwatch
/tool romon set enabled=yes
/tool sniffer set filter-mac-address=92:A5:3E:1F:79:99/FF:FF:FF:FF:FF:FF filter-operator-between-entries=and memory-limit=10000KiB