From 53a37e9c03a278ca09e4a341c037758ebd8724e1 Mon Sep 17 00:00:00 2001 From: vados-dev Date: Sun, 28 Jun 2026 06:00:04 +0300 Subject: [PATCH] Backup for MikroNUC created successfully at Jun 28 06:00:04. --- .../20260628/20260628050003-Config.rsc | 3692 +++++++++++++ MikroMaster/tmp/Diff-Config.rsc | 203 +- .../20260628/20260628060001-Binary.backup | Bin 0 -> 357430 bytes MikroNUC/20260628/20260628060001-Config.rsc | 4826 +++++++++++++++++ .../20260628/20260628060001-Diff-Config.rsc | 143 + 5 files changed, 8701 insertions(+), 163 deletions(-) create mode 100644 MikroMaster/20260628/20260628050003-Config.rsc create mode 100644 MikroNUC/20260628/20260628060001-Binary.backup create mode 100644 MikroNUC/20260628/20260628060001-Config.rsc create mode 100644 MikroNUC/history/20260628/20260628060001-Diff-Config.rsc diff --git a/MikroMaster/20260628/20260628050003-Config.rsc b/MikroMaster/20260628/20260628050003-Config.rsc new file mode 100644 index 0000000..3b46f8c --- /dev/null +++ b/MikroMaster/20260628/20260628050003-Config.rsc @@ -0,0 +1,3692 @@ +# 2026-06-28 05:00:07 by RouterOS 7.23.1 +# software id = A8L5-07YF +# +# model = RB952Ui-5ac2nD +# serial number = D3D50CEDCD38 +/interface bridge +add admin-mac=48:8F:5A:8E:1A:BF auto-mac=no dhcpv6-agent-circuit-id=\ + "\$(INTERFACE):\$(VID)" dhcpv6-agent-remote-id="\$(BRIDGEMAC)" name=\ + Bridge-LAN port-cost-mode=short +/interface ethernet +set [ find default-name=ether1 ] comment="WAN - Unet" loop-protect=off name=\ + "ether1 - Unet" rx-flow-control=auto tx-flow-control=auto +set [ find default-name=ether2 ] comment=LAN-2 +set [ find default-name=ether3 ] advertise="10M-baseT-half,10M-baseT-full,100M\ + -baseT-half,100M-baseT-full,1G-baseT-full,1G-baseX" comment=LAN-3 +set [ find default-name=ether4 ] comment=LAN-4 +set [ find default-name=ether5 ] advertise="10M-baseT-half,10M-baseT-full,100M\ + -baseT-half,100M-baseT-full,1G-baseT-full,1G-baseX" comment=LAN-5 \ + loop-protect=on poe-out=off rx-flow-control=auto tx-flow-control=auto +/interface wireless +# managed by CAPsMAN +# channel: 2422/20-Ce/gn(18dBm), SSID: vados, local forwarding +set [ find default-name=wlan1 ] country=belarus disabled=no mode=ap-bridge \ + ssid=vados wireless-protocol=802.11 +# managed by CAPsMAN +# channel: 5200/20-eCee/ac/P(21dBm), SSID: vados5g, local forwarding +set [ find default-name=wlan2 ] country=belarus disabled=no mode=ap-bridge \ + ssid=vados5g wireless-protocol=802.11 +/interface wireguard +add comment=WG disabled=yes listen-port=13231 mtu=1320 name=WG +/interface list +add include=static name=LAN +add name=Internet +add include=Internet name=WAN +/interface wireless security-profiles +set [ find default=yes ] authentication-types=wpa-psk,wpa2-psk mode=\ + dynamic-keys supplicant-identity=MikroTik +/iot wiliot servers +set *1 address=mqtt.us-east-2.prod.wiliot.cloud name="Wiliot US East" +/ip dhcp-server option +add code=12 name=HostName +add code=119 force=yes name=domain-search-list value="0x03'WORKGROUP'0x00" +/ip dhcp-server option sets +add name=HP1505n options=HostName +add name=StartLife1 options=HostName +add name=amsterDev options=HostName +add name=MiTV options=HostName +add name=miller options=HostName +add name=amster options=HostName +add name=garage options=HostName +add name=domain-search-set options=domain-search-list +add name=VADOS-LEN options=HostName +add name=VADOS-NUC options=HostName +add name=MikroNUC options=HostName +add name=master options=HostName +add name=MikroYellow options=HostName +add name="=\"Client ID\"" options=HostName +add name=iVados options=HostName +/ip dns forwarders +add dns-servers=1.0.0.1,1.1.1.1 name=blsp.at verify-doh-cert=no +add comment=Kra dns-servers=1.0.0.1,1.1.1.1 name=slon2.to verify-doh-cert=no +add dns-servers=1.1.1.1,1.0.0.1 name=slon3.at verify-doh-cert=no +add dns-servers=1.0.0.1,1.1.1.1 name=slon3.cc verify-doh-cert=no +add dns-servers=1.1.1.1,1.0.0.1 name=bs2web.at verify-doh-cert=no +add dns-servers=1.1.1.1,1.0.0.1 name=bs2best.at verify-doh-cert=no +add dns-servers=1.0.0.1,1.1.1.1 name=psrms.is verify-doh-cert=no +/ip ipsec proposal +set [ find default=yes ] disabled=yes +/ip pool +add name=dhcp ranges=10.30.30.83-10.30.30.254 +/ip dhcp-server +add add-arp=yes address-pool=dhcp always-broadcast=yes bootp-support=none \ + dhcp-option-set=domain-search-set interface=Bridge-LAN lease-time=10m \ + name=masterDHCP server-address=10.30.30.81 +/ip smb users +set [ find default=yes ] disabled=yes +add disabled=yes name=vados +/ipv6 dhcp-client option +add code=119 name=domain-search-list value="0x03'WORKGROUP'0x00" +add code=12 name=HostName value="\$(HOSTNAME)" +add code=61 name=DUID value=0x00030001488f5a8e1abc +add code=61 name=clientid +/ipv6 dhcp-server option +add code=119 name=domain-search-list value="0x03'WORKGROUP'0x00" +add code=12 name=HostName value="\$(HOSTNAME)" +add code=61 name=CLIENT_DUID +add code=61 name=CLIENT_MAC +/ppp profile +set *FFFFFFFE use-upnp=no +/routing bgp template +set default disabled=no output.network=bgp-networks +/routing table +add disabled=no fib name=To-VPN +/system logging action +add email-start-tls=yes email-to=vados@vados.ru name=email target=email +/system script +add dont-require-permissions=no name=fetchCustom owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\ + global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n\r\ + \n#/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@1\ + 0.30.30.86.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.86.pub\";\ + \n\r\ + \n#:local Script AM-setup\ + \n;\r\ + \n#/system/script/add name=\$Script owner=\$Script source=([ /tool/fetch c\ + heck-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \".r\ + sc\") output=user as-value ]->\"data\");\r\ + \n\ + \n#/usb1/mikro_bak_rsa.pub\ + \n\ + \n\r\ + \n#\ + \n\r\ + \n#/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@1\ + 0.30.30.10.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.10.pub\";\ + \n\r\ + \n#\r\ + \n\ + \n#root.mnuc.backuper\ + \n\ + \n\r\ + \n/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@10\ + .30.30.81.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.81.pub\";\r\ + \n\ + \n#:local Script AM-GlobalConfig;\ + \n\r\ + \n#/system/script/add name=\$Script owner=\$Script source=([ /tool/fetch c\ + heck-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \".r\ + sc\") output=user as-value ]->\"data\");\r\ + \n\ + \n#/usb1/mikro_bak_rsa.pub\ + \n#:local FetchFile AM-DOHCert.rsc\ + \n\ + \n\r\ + \n\r\ + \n#\ + \n:global MkDir;\ + \n\r\ + \n\ + \n#:local FetchFile Binary.backup\ + \n:local localDir \"\";\ + \n\ + \n\r\ + \n#\$MkDir \$localDir;\r\ + \n\ + \n#\ + \n/tool/fetch mode=https url=(\$GlobalScriptsUrl . \$FetchFile) dst-path=(\ + \$localDir . \$FetchFile);\r\ + \n" +add comment="Global Config Script" dont-require-permissions=no name=\ + AM-GlobalConfig owner=AM-GlobalConfig policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-GlobalConfig\r\ + \n# Script comment: Global Config Script\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# Warning: Dont touch this line!\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global GlobalConfReady false;\r\ + \n#\r\ + \n#\r\ + \n# Global Url For Amster scripts to fetch\r\ + \n# most be defined in AM-Init-Script or AM-setup scripts\r\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n:global ScriptsUrlSuffix (a);\r\ + \n:global CheckSumsVerify true;\r\ + \n\r\ + \n:global ScriptUpdatesCRLF true;\r\ + \n:global CommentsInScripts true;\r\ + \n\r\ + \n# Debug output:\r\ + \n:global PrintDebug true; #false;\r\ + \n\r\ + \n# Debug output for specific script:\r\ + \n#:set (\$PrintDebugOverride->\"AM-GlobalFunc\") true;\r\ + \n\r\ + \n# Debug logs\r\ + \n# Other actions (disk, email, remote or support) can be used as well.\r\ + \n# I do not recommend using echo - use debug output instead (https://rsc.\ + eworm.de/main/DEBUG.html#debug-output).\r\ + \n#/system/logging/add topics=script,debug action=memory;\r\ + \n\r\ + \n:global MikrotikUpgradeUrl \"https://upgrade.mikrotik.com/routeros\";\r\ + \n:global Domain \"WORKGROUP\";\r\ + \n\r\ + \n:global EmailGeneralTo \"vados@vados.ru\";\r\ + \n#:global EmailGeneralCc \"\";\r\ + \n\r\ + \n# \$ScriptInstallUpdate mod/notification-telegram\r\ + \n:global TelegramTokenId \"8390868324:AAG3_d1hfVR2M6pGAgDK6ZvhOe4J97nyopw\ + \";\r\ + \n:global TelegramChatId \"-1003631309063\"; #\"454403445\";\r\ + \n# Use this to send notifications to a specific topic in group.\r\ + \n#:global TelegramThreadId \"30\";\r\ + \n# Using telegram-chat you have to define trusted chat ids (not group ids\ + !)\r\ + \n# or user names. Groups allow to chat with devices simultaneously.\r\ + \n:global TelegramChatIdsTrusted {\r\ + \n \"454403445\"\r\ + \n \"-1003631309063\"\r\ + \n \"-5124482807\"\r\ + \n \"1087968824\";\r\ + \n};\r\ + \n# \"-5294757340\";\r\ + \n\r\ + \n:global TelegramChatGroups \"(all)\";\r\ + \n#:global TelegramChatGroups \"(all|home|office)\";\r\ + \n\r\ + \n:global NotificationFunctions {\r\ + \n \"email\"\r\ + \n \"telegram\";\r\ + \n};\r\ + \n# Toggle this to disable symbols in notifications.\r\ + \n:global NotificationsWithSymbols true;\r\ + \n:global TerminalColorOutput true;\r\ + \n\r\ + \n:global DetectIpAddrDefault \"https://ipv4.mikrotik.ovh/\";\r\ + \n:global DetectIpAddrFallback \"https://api.ipify.org/\";\r\ + \n\r\ + \n## Update channel. Possible values: stable, long-term, testing, developm\ + ent\r\ + \n:global updateChannel \"stable\";\r\ + \n\r\ + \n:global CertRenewUrl (\$GlobalScriptsUrl . \"certs/\" . \$Identity . \"/\ + \");\r\ + \n:global CertRenewTime 2w;\r\ + \n:global CertRenewPass (a); #{\r\ + \n# \"\";\r\ + \n# \"v3ry-s3cr3t\";\r\ + \n# \"4n0th3r-s3cr3t\";\r\ + \n#};\r\ + \n:global CertWarnTime 1w;\r\ + \n:global CertIssuedExportPass (a); # {\r\ + \n# \"\";\r\ + \n# \"cert1-cn\"=\"v3ry-s3cr3t\";\r\ + \n# \"cert2-cn\"=\"4n0th3r-s3cr3t\";\r\ + \n#};\r\ + \n\r\ + \n# Script BackupAndUpdate mode, possible values: backup,\r\ + \n# updateOnce - Update once as \"osnotify\" and back \"osnotify\" mode (i\ + n terminal run :set OSUpdateMode updateOnce; \$OSUpdateMode;). \r\ + \n# osupdate - Update if available and creates backups before/after update\ + \_(ignores `forceBackup`)\r\ + \n# Set `forceBackup` to true to always create backups, even\ + \_without updates\r\ + \n# osnotify - Set `forceBackup` to always create backups on every run\r\ + \n:global DevInfoIncludeIP true;\r\ + \n:global BackupAndUpdateMode \"osnotify\"\r\ + \n\r\ + \n:global forceBackup true;\r\ + \n:global BackupPassword \"Peskar55\";\r\ + \n\r\ + \n# SFTP Backup Variables\r\ + \n:global BackupRandomDelay 0;\r\ + \n:global BackupUploadUrl \"sftp://amster.vados.ru/upload/\";\r\ + \n:global BackupUploadUser \$Identity;\r\ + \n:global BackupUploadPass \"root.mnuc.backuper\";\r\ + \n\r\ + \n:global BackupSendBinary true;\r\ + \n:global BackupSendExport true;\r\ + \n:global BackupSendGlobalConfig true;\r\ + \n:global BackupSendScripts true;\r\ + \n\r\ + \n# Remove local file after uploading if no errors\r\ + \n:global BackupRmLocal true;\r\ + \n\r\ + \n# Local Backups Root Directory \r\ + \n:global BackupLocalRoot \"tmpfs\";\r\ + \n\r\ + \n# Encrypt Backup\r\ + \n:global BackupEncrypt true;\r\ + \n\r\ + \n# Sensitive information in Backups\r\ + \n:global BackupSens true;\r\ + \n\r\ + \n# User Export\r\ + \n:global BackupUser true;\r\ + \n\r\ + \n# License Export (not for CHR, will silently skip)\r\ + \n:global BackupLicense false;\r\ + \n\r\ + \n# SSH Keys\r\ + \n:global BackupSshKeys true;\r\ + \n\r\ + \n# Certificate Export\r\ + \n:global BackupCerts true;\r\ + \n\r\ + \n# Certificate Password\r\ + \n:global BackupCertsPasswd \"\";\r\ + \n\r\ + \n# User-Manager Export\r\ + \n:global BackupUserMan false;\r\ + \n\r\ + \n# The Dude Export\r\ + \n:global BackupDude false;\r\ + \n\r\ + \n# User Files to export, comma-separated string or array of strings\r\ + \n# User Files are not removed on backup\r\ + \n# Any directory paths will be removed (/ -> _) on remote file\r\ + \n# Nonpresent files are silently skipped\r\ + \n:global BackupUserList \"autosupout.rif,autosupout.old.rif\";\r\ + \n\r\ + \n# load functions and any custom scripts\r\ + \n# [ /system/script/find where name=\"global-config-ov\ + erlay\" ], \\\r\ + \n# [ /system/script/find where name~\"^global-config-o\ + verlay\\\\.d/.\" ]\r\ + \n#\r\ + \n#:foreach Script in=([ /system/script/find where name=\"Amster-TGglobalV\ + ars\" ], [ /system/script/find where name=\"Amster-TGglobalFunc\" ]) do={\ + \r\ + \n# :onerror Err {\r\ + \n# /system/script/run \$Script;\r\ + \n# } do={\r\ + \n# :log error (\"Loading configuration from configs and functions\"\ + \_. [ /system/script/get \$Script name ] . \" failed: \" . \$Err);\r\ + \n# }\r\ + \n#}\r\ + \n#:log info \"\\ndevMode = \$devMode\";\r\ + \n#:log info \"\\ndevRunning = \$devRunning\";\r\ + \n:foreach Script in=[ /system/script/find where source~\"^#!rsc by Vados \ + GlobalRUN\\r\?\\n\" ] do={\r\ + \n :onerror Err {\r\ + \n /system/script/run \$Script;\r\ + \n } do={\r\ + \n :log error (\"Run script \" . [ /system/script/get \$Script name \ + ] . \" width marker \\\" GlobalRUN\\\" failed: \" . \$Err);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# signal we are ready\r\ + \n:set GlobalConfReady true;\r\ + \n:while (\$GlobalConfReady != true) do={:delay 500ms};\r\ + \n" +add comment="Global functions" dont-require-permissions=no name=AM-GlobalFunc \ + owner=AM-GlobalFunc policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-GglobalFunc\r\ + \n# Script comment: Global functions\r\ + \n# Copyright (c) 2005-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch, scheduler\r\ + \n:local ExitOK false;\r\ + \n:local ScriptName [ :jobname ];\r\ + \n#:global checkRunJob \$SriptName;\r\ + \n#:global fixCRLF;\r\ + \n#:if (\$fixCRLF != false) do={\r\ + \n#:foreach fixScript in=([ /system/script/find where name~\"*\" ]) do={/s\ + ystem/script/set source=[ :tocrlf [ get \$fixScript source ] ] \$fixScript\ + }\r\ + \n#:if (\$fixCRLF != \"LF\") do={ /system/script/set source=[ :tocrlf [ g\ + et \$fixScript source ] ] \$fixScript};};\r\ + \n#}\r\ + \n:global GlobalFuncReady false;\r\ + \n\r\ + \n# Git commit id & info, expected configuration version\r\ + \n:global CommitId \"main/700/f14f095d\";\r\ + \n:global CommitTAG \"unknown\";\r\ + \n:global CommitInfo \"Autocommit in repo: ros.vados.ru, generated by owne\ + r: root, in 13:30:03 22.06.2026.\";\r\ + \n\r\ + \n# The variables are received right here.\r\ + \n:global GlobalConfVersion 140;\r\ + \n\r\ + \n:global SumsDirAndFileName \"tmp/checksums.json\";\r\ + \n\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global IdentityShort [:pick \$Identity 0 18];\r\ + \n\r\ + \n:global devBoardName [/system/resource/get board-name];\r\ + \n:global devMode [/system/device-mode/get mode];\r\ + \n:global devCurArch [/system/resource/get architecture-name];\r\ + \n:global devCurVersion [/system/resource/get version];\r\ + \n\r\ + \n# Variables defined in AM-GlobalConfig.rsc\r\ + \n:global TerminalColorOutput;\r\ + \n:global CommentsInScripts;\r\ + \n:global DetectIpAddrDefault;\r\ + \n:global DetectIpAddrFallback;\r\ + \n:global updateChannel;\r\ + \n:global Domain;\r\ + \n:global EmailGeneralTo;\r\ + \n:global BackupAndUpdateMode;\r\ + \n\r\ + \n# Single functions, without nested functions.\r\ + \n:global MiniDateTimeStamp;\r\ + \n:global MicroDateTimeStamp;\r\ + \n\r\ + \n# Include globals \$PrintDebug and \$PrintDebugOverride\r\ + \n:global LogPrint;\r\ + \n\r\ + \n# DeviceInfo and Updates\r\ + \n:global IsCheckUpdates;\r\ + \n:global WaitCheckUpdates;\r\ + \n:global GetPubIp;\r\ + \n\r\ + \n:global GetArrDeviceInfo;\r\ + \n:global WaitArrDeviceInfo;\r\ + \n\r\ + \n:global devModel;\r\ + \n:global devRunningMode;\r\ + \n\r\ + \n:global devRbRevision;\r\ + \n:global devRbSerialNumber;\r\ + \n:global devRbVerAvail;\r\ + \n\r\ + \n:global runningChannelVersion;\r\ + \n:global runningChannel;\r\ + \n:global runningVersion;\r\ + \n\r\ + \n:global devBuildTime;\r\ + \n:global ROsVerAvail;\r\ + \n:global PkgUpdStatus;\r\ + \n:global currentUpdateChannel;\r\ + \n\r\ + \n:global ScriptInstallUpdate;\r\ + \n\r\ + \n:global DeviceInfo;\r\ + \n\r\ + \n:global OldMode;\r\ + \n\r\ + \n:global ParseKeyValueStore;\r\ + \n:global RandomDelay;\r\ + \n:global GetRandomNumber;\r\ + \n:global EscapeForRegEx;\r\ + \n:global CharacterMultiply;\r\ + \n:global CharacterReplace;\r\ + \n:global CleanFilePath;\r\ + \n:global CleanName;\r\ + \n\r\ + \n:global CommitBrief;\r\ + \n\r\ + \n:global EitherOr;\r\ + \n:global FormatLine;\r\ + \n:global GetRandom20CharAlNum;\r\ + \n:global GetRandom20CharHex;\r\ + \n:global GetRandomNumber;\r\ + \n:global Grep;\r\ + \n:global IfThenElse;\r\ + \n:global MAX;\r\ + \n:global MIN;\r\ + \n:global HumanReadableNum;\r\ + \n\r\ + \n:global IsFullyConnected;\r\ + \n:global IsDNSResolving;\r\ + \n:global IsTimeSync;\r\ + \n:global IsTimeSyncCached;\r\ + \n:global IsTimeSyncResetNtp;\r\ + \n\r\ + \n:global MkDir;\r\ + \n:global RmDir;\r\ + \n:global RmFile;\r\ + \n:global FileGet;\r\ + \n:global Unix2Dos;\r\ + \n:global UrlEncode;\r\ + \n:global WaitForFile;\r\ + \n:global ScriptLock;\r\ + \n:global SendTelegram;\r\ + \n:global NotificationFunctions;\r\ + \n:global SendNotification;\r\ + \n:global SendNotification2;\r\ + \n:global SymbolForNotification;\r\ + \n:global SymbolByUnicodeName;\r\ + \n:global ValidateSyntax;\r\ + \n:global CheckSumsVerify;\r\ + \n:global GlobalConfChanges;\r\ + \n:global GlobalConfMigration;\r\ + \n:global VersionToNum;\r\ + \n:global RequiredRouterOS;\r\ + \n:global ExitError;\r\ + \n:global ExitOnError;\r\ + \n\r\ + \n:global FetchHugeFiles;\r\ + \n:global FetchUserAgentStr;\r\ + \n:global FileExists;\r\ + \n:global CertificateAvailable;\r\ + \n:global CertificateDownload;\r\ + \n:global CertificateNameByCN;\r\ + \n:global CharacterMultiply;\r\ + \n\r\ + \n:global InspectVar;\r\ + \n:global InspectVarRet;\r\ + \n:global ParseCustom;\r\ + \n\r\ + \n:set IsCheckUpdates do={\r\ + \n :local isInfo [:tobool \$1];\r\ + \n :global updateChannel;\r\ + \n :global PkgUpdStatus;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n :global arrDeviceInfo;\r\ + \n :log info (\$0 . \" \\\$isInfo = \" . \$isInfo); \r\ + \n :local CurrentChannel [ /system/package/update/get channel ];\r\ + \n :if ([ :len \$updateChannel ] > 0 && \$CurrentChannel != \$updateChann\ + el) do={\r\ + \n :retry command={/system/package/update set channel=\$updateChannel} \ + \_delay=300ms max=5 on-error={};\r\ + \n :log info (\$0 . \" Set updates channel to: \$updateChannel\");\r\ + \n }\r\ + \n :local Update [ /system/package/update/get ];\r\ + \n :local Channel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set currentUpdateChannel \$Channel;\r\ + \n :log info (\$0 . \" Update Channel: \" . \$Channel . \".\");\r\ + \n :local StatusPkg \"undefined\";\r\ + \n :log info (\$0 . \" Checking for updates...\");\r\ + \n :retry command={:set \$StatusPkg ([/system/package/update/check-for-up\ + dates without-paging as-value]->\"status\")} \\\r\ + \n delay=300ms max=5 on-error={};\r\ + \n :log info (\$0 . \" \\\$StatusPkg = \" . \$StatusPkg);\r\ + \n :if (\$StatusPkg = \"New version is available\") do={ :set PkgUpdStatu\ + s true; } else={ :set PkgUpdStatus false; }\r\ + \n :set ROsVerAvail [ :tostr [ /system/package/update/get latest-version \ + ] ];\r\ + \n :log info (\$0 . \" \\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if (\$isInfo != false || \$PkgUpdStatus = true) do={\r\ + \n :log info (\$0 . \" \\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if ([ :typeof \$arrDeviceInfo ] != \"array\") do={\r\ + \n :set arrDeviceInfo [:toarray \$arrDeviceInfo];\r\ + \n }\r\ + \n :retry command={:set (\$arrDeviceInfo->\"devUpdates\") [ /system/pac\ + kage/update/get ]} delay=500ms max=5 on-error={:log error (\"Error\")};\r\ + \n :return true;\r\ + \n } else={\r\ + \n :return \$PkgUpdStatus;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set WaitCheckUpdates do={\r\ + \n :global IsCheckUpdates;\r\ + \n :global PkgUpdStatus;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n :global arrDeviceInfo;\r\ + \n :local Status \"\";\r\ + \n :local Update;\r\ + \n :local WaitCount 0;\r\ + \n :if ([ \$IsCheckUpdates true ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n :set Status [ :tostr [ /system/package/update/get status ] ];\r\ + \n :while ((\$Status = \"\" || \$Status = \"finding out latest version...\ + \") && \$WaitCount < 60) do={\r\ + \n :delay 1s;\r\ + \n :set WaitCount (\$WaitCount + 1);\r\ + \n :set Status [ :tostr [ /system/package/update/get status ] ];\r\ + \n }\r\ + \n :set Update [ /system/package/update/get ];\r\ + \n :set currentUpdateChannel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set ROsVerAvail [ :tostr (\$Update->\"latest-version\") ];\r\ + \n :if ((\$Update->\"status\") = \"New version is available\") do={\r\ + \n :set PkgUpdStatus true;\r\ + \n } else={\r\ + \n :set PkgUpdStatus false;\r\ + \n }\r\ + \n :if ([ :typeof \$arrDeviceInfo ] != \"array\") do={\r\ + \n :set arrDeviceInfo [:toarray \$arrDeviceInfo];\r\ + \n }\r\ + \n :set (\$arrDeviceInfo->\"devUpdates\") \$Update;\r\ + \n :if (\$Status = \"\" || \$Status = \"finding out latest version...\") \ + do={\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set WaitArrDeviceInfo do={\r\ + \n :global GetArrDeviceInfo;\r\ + \n :while ([ \$GetArrDeviceInfo ] = false) do={\r\ + \n :delay 1s;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set GetArrDeviceInfo do={\r\ + \n :global LogPrint;\r\ + \n :global arrDeviceInfo;\r\ + \n :global devBoardName;\r\ + \n :global devModel;\r\ + \n :global devRunning;\r\ + \n :global devCurArch;\r\ + \n :global devRbRevision;\r\ + \n :global devRbSerialNumber;\r\ + \n :global devBuildTime;\r\ + \n :global runningChannelVersion;\r\ + \n :global runningChannel;\r\ + \n :global runningVersion;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n\r\ + \n :local DeviceInfo;\r\ + \n :local Resource [/system/resource/get];\r\ + \n :local Update [ /system/package/update/get ];\r\ + \n :local VersionString [ :tostr (\$Resource->\"version\") ];\r\ + \n :local ChannelStart [ :find \$VersionString \" (\" ];\r\ + \n :local RouterBoard;\r\ + \n :local IsRouterBoard false;\r\ + \n :local RunningMode \"\";\r\ + \n\r\ + \n :set devCurArch [ :tostr (\$Resource->\"architecture-name\") ];\r\ + \n :set devBuildTime [ :tostr (\$Resource->\"build-time\") ];\r\ + \n :set runningChannelVersion \$VersionString;\r\ + \n :set runningVersion \$VersionString;\r\ + \n :set runningChannel \"\";\r\ + \n\r\ + \n :if ([ :typeof \$ChannelStart ] = \"num\" && \$ChannelStart >= 0) do=\ + {\r\ + \n :set runningVersion [ :pick \$VersionString 0 \$ChannelStart ];\r\ + \n :local ChannelRest [ :pick \$VersionString (\$ChannelStart + 2) [ :\ + len \$VersionString ] ];\r\ + \n :local ChannelEnd [ :find \$ChannelRest \")\" ];\r\ + \n :if ([ :typeof \$ChannelEnd ] = \"num\" && \$ChannelEnd >= 0) do={\ + \r\ + \n :set runningChannel [ :pick \$ChannelRest 0 \$ChannelEnd ];\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if ([:pick \$devBoardName 0 3] != \"CHR\" or [:pick \$devBoardName 0\ + \_3] != \"x86\") do={\r\ + \n :do {\r\ + \n :set RouterBoard [[ :parse \"/system/routerboard/get\" ]];\r\ + \n# :set RouterBoard [ /system/routerboard/get ];\r\ + \n :if ((\$RouterBoard->\"routerboard\") = true) do={\r\ + \n :set IsRouterBoard true;\r\ + \n }\r\ + \n } on-error={}\r\ + \n }\r\ + \n\r\ + \n :do {\r\ + \n :set RunningMode [ /system/device-mode/get mode ];\r\ + \n } on-error={\r\ + \n :do {\r\ + \n :set RunningMode [ /system/device-mode/get running ];\r\ + \n } on-error={\r\ + \n :set RunningMode \"\";\r\ + \n }\r\ + \n }\r\ + \n :set devRunning \$RunningMode;\r\ + \n\r\ + \n :if (\$IsRouterBoard = true) do={\r\ + \n :set devModel [ :tostr (\$RouterBoard->\"model\") ];\r\ + \n :set devRbRevision [ :tostr (\$RouterBoard->\"revision\") ];\r\ + \n :set devRbSerialNumber [ :tostr (\$RouterBoard->\"serial-number\") \ + ];\r\ + \n } else={\r\ + \n :set devModel \$devBoardName;\r\ + \n :set devRbRevision \"\";\r\ + \n :set devRbSerialNumber \"\";\r\ + \n }\r\ + \n\r\ + \n :set currentUpdateChannel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set ROsVerAvail [ :tostr (\$Update->\"latest-version\") ];\r\ + \n\r\ + \n :set (\$DeviceInfo->\"board-name\") \$devBoardName;\r\ + \n :set (\$DeviceInfo->\"architecture-name\") \$devCurArch;\r\ + \n :set (\$DeviceInfo->\"model\") \$devModel;\r\ + \n :set (\$DeviceInfo->\"revision\") \$devRbRevision;\r\ + \n :set (\$DeviceInfo->\"serial-number\") \$devRbSerialNumber;\r\ + \n :set (\$DeviceInfo->\"running-mode\") \$devRunning;\r\ + \n :set (\$DeviceInfo->\"build-time\") \$devBuildTime;\r\ + \n :set (\$DeviceInfo->\"running-channel-version\") \$runningChannelVers\ + ion;\r\ + \n :set (\$DeviceInfo->\"running-version\") \$runningVersion;\r\ + \n :set (\$DeviceInfo->\"running-channel\") \$runningChannel;\r\ + \n :set (\$DeviceInfo->\"routerboard\") \$IsRouterBoard;\r\ + \n :set (\$DeviceInfo->\"current-firmware\") [ :tostr (\$RouterBoard->\"\ + current-firmware\") ];\r\ + \n :set (\$DeviceInfo->\"upgrade-firmware\") [ :tostr (\$RouterBoard->\"\ + upgrade-firmware\") ];\r\ + \n :set (\$DeviceInfo->\"package-channel\") \$currentUpdateChannel;\r\ + \n :set (\$DeviceInfo->\"installed-version\") [ :tostr (\$Update->\"inst\ + alled-version\") ];\r\ + \n :set (\$DeviceInfo->\"latest-version\") \$ROsVerAvail;\r\ + \n :set (\$DeviceInfo->\"update-status\") [ :tostr (\$Update->\"status\"\ + ) ];\r\ + \n\r\ + \n :set arrDeviceInfo \$DeviceInfo;\r\ + \n :return \$DeviceInfo;\r\ + \n}\r\ + \n\r\ + \n:set DeviceInfo do={\r\ + \n :global GetArrDeviceInfo;\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global IfThenElse;\r\ + \n :global FormatLine;\r\ + \n :global CommitBrief;\r\ + \n\r\ + \n :local License [/system/license/get];\r\ + \n :local Snmp [ /snmp/get ];\r\ + \n :local Info [ \$GetArrDeviceInfo ];\r\ + \n\r\ + \n :if ([ :typeof \$Info ] != \"array\") do={\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :return ( \\\r\ + \n [ \$FormatLine \"Hostname\" \$Identity ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Snmp->\"location\") ] > 0) \\\r\ + \n ([ \$FormatLine \"Location\" (\$Snmp->\"location\") ] . \"\\n\") ]\ + \_. \\\r\ + \n [ \$IfThenElse ([ :len (\$Snmp->\"contact\") ] > 0) \\\r\ + \n ([ \$FormatLine \"Contact\" (\$Snmp->\"contact\") ] . \"\\n\") ] .\ + \_\\\r\ + \n \"Hardware:\\n\" . \\\r\ + \n [ \$FormatLine \" Board\" (\$Info->\"board-name\") ] . \"\\n\" . \ + \\\r\ + \n [ \$FormatLine \" Arch\" (\$Info->\"architecture-name\") ] . \"\\\ + n\" . \\\r\ + \n [ \$FormatLine \" Model\" (\$Info->\"model\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"revision\") ] > 0) \\\r\ + \n (\" \" . (\$Info->\"revision\")) ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"serial-number\") ] > 0) \\\r\ + \n ([ \$FormatLine \" Serial\" (\$Info->\"serial-number\") ] . \"\ + \\n\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"nlevel\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License\" (\"level \" . (\$License->\"nlevel\ + \")) ] . \"\\n\") ] . \\\r\ + \n \"RouterOS:\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"level\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License\" (\"level \" . (\$License->\"level\ + \")) ] . \"\\n\") ] . \\\r\ + \n [ \$FormatLine \" Channel\" (\$Info->\"package-channel\") ] . \"\ + \\n\" . \\\r\ + \n [ \$FormatLine \" Installed\" (\$Info->\"installed-version\") ] .\ + \_\"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"running-channel\") ] > 0) \\\r\ + \n ([ \$FormatLine \" Running\" ((\$Info->\"running-version\") . \ + \" (\" . (\$Info->\"running-channel\") . \")\") ] . \"\\n\") \\\r\ + \n ([ \$FormatLine \" Running\" (\$Info->\"running-version\") ] . \ + \"\\n\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"latest-version\") ] > 0 && \\\r\ + \n (\$Info->\"installed-version\") != (\$Info->\"latest-version\"))\ + \_\\\r\ + \n ([ \$FormatLine \" Available\" (\$Info->\"latest-version\") ] .\ + \_\"\\n\") ] . \\\r\ + \n [ \$IfThenElse ((\$Info->\"routerboard\") = true && \\\r\ + \n (\$Info->\"current-firmware\") != (\$Info->\"upgrade-firmware\")\ + ) \\\r\ + \n ([ \$FormatLine \" Firmware\" ((\$Info->\"current-firmware\") .\ + \_\" -> \" . (\$Info->\"upgrade-firmware\")) ] . \"\\n\") ] . \\\r\ + \n \"RouterOS-Scripts:\\n\" . \\\r\ + \n [ \$FormatLine \" Commit\" [ \$CommitBrief ] ] . \"\\n\" . \\\r\ + \n [ \$FormatLine \" Version\" \$GlobalConfVersion ]);\r\ + \n}\r\ + \n\r\ + \n:if ([ :typeof \$NotificationFunctions ] != \"array\") do={:set Notifica\ + tionFunctions ({})}\r\ + \n\r\ + \n:set GetRandom20CharAlNum do={\r\ + \n :global EitherOr;\r\ + \n :return [ :rndstr length=[ \$EitherOr [ :tonum \$1 ] 20 ] from=\"ABCDE\ + FGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\" ];\r\ + \n}\r\ + \n\r\ + \n:set GetRandom20CharHex do={:global EitherOr; :return [ :rndstr length=[\ + \_\$EitherOr [ :tonum \$1 ] 20 ] from=\"0123456789abcdef\" ]; }\r\ + \n:set GetRandomNumber do={:global EitherOr; :return [ :rndnum from=0 to=[\ + \_\$EitherOr [ :tonum \$1 ] 4294967295 ] ]; }\r\ + \n\r\ + \n:global checkRunJob;\r\ + \n:set checkRunJob do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLock; \r\ + \n :if ([ \$ScriptLock \$Script ] = false) do={:return false}\r\ + \n :foreach Job in=[ /system/script/job/find where script=\$Script ] do={\ + :set Job [ /system/script/job/get \$Job ];\r\ + \n :while ([ :typeof (\$Job->\"parent\") ] = \"id\") do={:set Job [ /sy\ + stem/script/job/get [ find where .id=(\$Job->\"parent\") ] ]}\r\ + \n :if ((\$Job->\"type\") = \"login\") do={:return true};}; :return fa\ + lse; \r\ + \n}\r\ + \n\r\ + \n:set InspectVar do={\r\ + \n :global InspectVarRet;\r\ + \n :put [ :tocrlf [ \$InspectVarRet \$1 ] ];\r\ + \n}\r\ + \n\r\ + \n:set CommitBrief do={\r\ + \n :global CommitId;\r\ + \n :global CommitInfo;\r\ + \n :if (\$CommitId = \"unknown\") do={\r\ + \n :return \"unknown\";\r\ + \n }\r\ + \n :return (\$CommitInfo . \"/\" . [ :pick \$CommitId 0 8 ]);\r\ + \n}\r\ + \n\r\ + \n:set InspectVarRet do={\r\ + \n :local Input \$1;\r\ + \n :local Level (0 + [ :tonum \$2 ]);\r\ + \n :global CharacterReplace;\r\ + \n :global IfThenElse;\r\ + \n :global InspectVarRet;\r\ + \n :local IndentReturn do={\r\ + \n :local Prefix [ :tostr \$1 ];\r\ + \n :local Value [ :tostr \$2 ];\r\ + \n :local Level [ :tonum \$3 ];\r\ + \n :global CharacterMultiply;\r\ + \n :return ([ \$CharacterMultiply \" \" (\$Level * 2) ] . \"-\" . \$Pre\ + fix . \"-> \" . \$Value);\r\ + \n }\r\ + \n :local TypeOf [ :typeof \$Input ];\r\ + \n :local Len [ :len \$Input ];\r\ + \n :local Return [ \$IndentReturn \"type\" \$TypeOf \$Level ];\r\ + \n :if (\$TypeOf = \"array\") do={\r\ + \n :foreach Key,Value in=\$Input do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"key\" \$Key (\$Level + 1) ] . \"\\n\" . \\\r\ + \n [ \$InspectVarRet \$Value (\$Level + 2) ]);\r\ + \n }\r\ + \n } else={\r\ + \n :if (\$TypeOf = \"str\") do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"len\" \$Len \$Level ]);\r\ + \n :if ([ :typeof [ :find \$Input (\"\\r\") ] ] = \"num\") do={\r\ + \n :set Input [ \$CharacterReplace \$Input (\"\\r\") \"\" ];\r\ + \n }\r\ + \n :if ([ :typeof [ :find \$Input (\"\\n\") ] ] = \"num\") do={\r\ + \n :set Input [ \$CharacterReplace \$Input (\"\\n\") \" \" ];\r\ + \n }\r\ + \n }\r\ + \n :if (\$TypeOf != \"nothing\") do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"value\" [ \$IfThenElse ([ :len \$Input ] > 80)\ + \_\\\r\ + \n ([ :pick \$Input 0 77 ] . \"...\") \$Input ] \$Level ]);\r\ + \n }\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set GetPubIp do={\r\ + \n :global DetectIpAddrDefault;\r\ + \n :global DetectIpAddrFallback;\r\ + \n\r\ + \n :local PubIpAddr false;\r\ + \n :local Url \$DetectIpAddrDefault;\r\ + \n :local JobName [ :jobname ];\r\ + \n\r\ + \n :do {\r\ + \n :log debug (\$0 . \" Trying to detect public IP using default serv\ + ice: \" . \$Url . \"...\");\r\ + \n :retry command={:set PubIpAddr ([ /tool/fetch http-method=\"get\"\ + \_check-certificate=yes-without-crl \$Url output=user as-value ]->\"data\"\ + )} \\\r\ + \n delay=50ms max=5 on-error={:log error (\$JobName . \" Failed to \ + detect public IP using default service: \" . \$Url . \".\")}\r\ + \n } on-error={\r\ + \n :log error (\$0 . \" Failed to detect public IP using default serv\ + ice: \" . \$Url . \".\");\r\ + \n :set Url \$DetectIpAddrFallBack;\r\ + \n :log debug (\$0 . \" Trying to detect public IP using fallback ser\ + vice: \" . \$Url . \"...\");\r\ + \n :retry command={:set PubIpAddr ([ /tool/fetch http-method=\"get\"\ + \_check-certificate=yes-without-crl \$Url output=user as-value ]->\"data\"\ + )} \\\r\ + \n delay=200ms max=5 on-error={:log error (\$JobName . \" Failed to\ + \_detect public IP using fallback service: \" . \$Url . \".\")}\r\ + \n };\r\ + \n :local IpLen ([ :len \$PubIpAddr ] -1);\r\ + \n :if (\$PubIpAddr = false) do={:set PubIpAddr \"not-detected\"} else=\ + {:set PubIpAddr [:pick \$PubIpAddr 0 \$IpLen]}\r\ + \n :return \$PubIpAddr;\r\ + \n}\r\ + \n\r\ + \n:set FormatLine do={\r\ + \n :local Key [ :tostr \$1 ];\r\ + \n :local Value [ :tostr \$2 ];\r\ + \n :local Indent [ :tonum \$3 ];\r\ + \n :local Spaces;\r\ + \n :local Return \"\";\r\ + \n :global CharacterMultiply;\r\ + \n :global EitherOr;\r\ + \n :set Indent [ \$EitherOr \$Indent 16 ];\r\ + \n :local Spaces [ \$CharacterMultiply \" \" \$Indent ];\r\ + \n :if ([ :len \$Key ] > 0) do={ :set Return (\$Key . \":\")}\r\ + \n :if ([ :len \$Key ] > (\$Indent - 2)) do={:set Return (\$Return . \"\\\ + n\" . [ :pick \$Spaces 0 \$Indent ] . \$Value);\r\ + \n } else={:set Return (\$Return . [ :pick \$Spaces 0 (\$Indent - [ :len \ + \$Return ]) ] . \$Value)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ParseCustom do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Start [:find [ \$Str ] [:tostr \$2]];\r\ + \n :local End [ :tostr \$3 ];\r\ + \n :local Num [ :tonum \$4 ];\r\ + \n :local Return;\r\ + \n :if ([:len \$Num] = 0) do={:set Num \"0\"}\r\ + \n :local rest [:pick \$Str (\$Start+\$Num) [:len \$Str]]; \r\ + \n :if ([:len \$End ] = 0) do={:set Return [:pick \$Str \$Num \$Start]} e\ + lse={\r\ + \n :set End [:find \$rest \$End];\r\ + \n :set Return [:pick \$rest 0 \$End];\r\ + \n }\r\ + \n:return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set LogPrint do={\r\ + \n :local Severity [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Message [ :tostr \$3 ];\r\ + \n :global PrintDebug;\r\ + \n :global PrintDebugOverride;\r\ + \n :global EitherOr;\r\ + \n :local Debug [ \$EitherOr (\$PrintDebugOverride->\$Name) \$PrintDebug \ + ];\r\ + \n :local PrintSeverity do={\r\ + \n :global TerminalColorOutput;\r\ + \n :if (\$TerminalColorOutput != true) do={:return \$1}\r\ + \n :local Color { debug=96; info=97; warning=93; error=91 };\r\ + \n :return (\"\\1B[\" . \$Color->\$1 . \"m\" . \$1 . \"\\1B[0m\");\r\ + \n}\r\ + \n :local Log ([ \$EitherOr \$Name \"\" ] . \": \" . \$Message);\ + \r\ + \n :if (\$Severity ~ (\"^(custom|debug|error|info)\\\$\")) do={\r\ + \n :if (\$Severity = \"debug\") do={:log debug \$Log}\r\ + \n :if (\$Severity = \"error\") do={:log error \$Log}\r\ + \n :if (\$Severity = \"info\" ) do={:log info \$Log}\r\ + \n } else={:log warning \$Log; :set Severity \"warning\"}\r\ + \n :if (\$Severity != \"debug\" || \$Debug = true) do={:put ([ \$PrintSev\ + erity \$Severity ] . \": \" . \$Message)}\r\ + \n}\r\ + \n\r\ + \n:set CertificateAvailable do={\r\ + \n :local CommonName [ :tostr \$1 ];\r\ + \n :local UseFor [ :tostr \$2 ];\r\ + \n\r\ + \n :global CertificateDownload;\r\ + \n :global ParseKeyValueStore;\r\ + \n\r\ + \n :if ([ :len \$UseFor ] = 0) do={\r\ + \n :log warning (\$0 . \"The intended use is undefined!\");\r\ + \n :set UseFor \"undefined\";\r\ + \n }\r\ + \n :if ([ /system/resource/get free-hdd-space ] < 8388608 && \\\r\ + \n [ /certificate/settings/get crl-download ] = true && \\\r\ + \n [ /certificate/settings/get crl-store ] = \"system\") do={\r\ + \n :log warning (\$0 . \"This system has low free flash space but \" . \ + \\\r\ + \n \"is configured to download certificate CRLs to system!\");\r\ + \n }\r\ + \n :if ([ :len \$CommonName ] = 0) do={\r\ + \n :log warning (\$0 . \"No CommonName given!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if (((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" || \\\ + \r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \$UseFor || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\r\ + \n [ :len [ /certificate/builtin/find where common-name=\$CommonName\ + \_] ] > 0) do={\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n :log info (\$0 . \"Certificate with CommonName '\" . \$CommonName . \ + \"' not available.\");\r\ + \n :if ([ \$CertificateDownload \$CommonName ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] > 1\ + ) do={\r\ + \n :log info (\$0 . \"There are \" . \$CertCount . \" Certificates with\ + \_CommonName '\" . \$CommonName . \"'. Should be ok.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :local CertVal [ /certificate/get [ find where common-name=\$CommonNam\ + e ] ];\r\ + \n :while ((\$CertVal->\"akid\") != \"\" && (\$CertVal->\"akid\") != (\$C\ + ertVal->\"skid\")) do={\r\ + \n :if ([ :len [ /certificate/find where skid=(\$CertVal->\"akid\") ] ]\ + \_= 0) do={\r\ + \n :local IssuerCN ([ \$ParseKeyValueStore (\$CertVal->\"issuer\") ]-\ + >\"CN\");\r\ + \n :log info (\$0 . \"Certificate chain for '\" . \$CommonName . \"' \ + is incomplete, missing '\" . \$IssuerCN . \"'.\");\r\ + \n :if ([ \$CertificateDownload \$IssuerCN ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :set CertVal [ /certificate/get [ find where skid=(\$CertVal->\"akid\ + \") ] ];\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CertificateDownload do={\r\ + \n :local CommonName [ :tostr \$1 ];\r\ + \n :global ScriptUpdatesBaseUrl;\r\ + \n :global ScriptUpdatesUrlSuffix;\r\ + \n :global CertificateNameByCN;\r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :log info (\$0 . \"Downloading and importing certificate with \" . \\\ + \r\ + \n \"CommonName '\" . \$CommonName . \"'.\");\r\ + \n :local FileName ([ \$CleanName \$CommonName ] . \".pem\");\r\ + \n :do {\r\ + \n /tool/fetch check-certificate=yes-without-crl mode=https http-header\ + -field=({ [ \$FetchUserAgentStr \$0 ] }) \\\r\ + \n (\$ScriptUpdatesBaseUrl . \"certs/\" . \$FileName . \$ScriptUpdate\ + sUrlSuffix) \\\r\ + \n dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n } on-error={\r\ + \n :log warning (\$0 . \" Failed downloading certificate with CommonNam\ + e '\" . \$CommonName . \\\r\ + \n \"' from repository! Trying fallback to mkcert.org...\");\r\ + \n :do {\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if ([ :len [ /certificate/find where common-name=\"ISRG Root X1\"\ + \_] ] = 0 && \\\r\ + \n !(((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" \ + || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \"fetch\" || \ + \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\ + \r\ + \n [ :len [ /certificate/builtin/find where common-name=\"ISRG\ + \_Root X1\" ] ] > 0)) do={\r\ + \n :log error (\$0 . \" Required certificate is not available.\");\ + \r\ + \n :return false;\r\ + \n }\r\ + \n /tool/fetch check-certificate=yes-without-crl mode=https http-head\ + er-field=({ [ \$FetchUserAgentStr \$0 ] }) \\\r\ + \n url=\"https://mkcert.org/generate/\" http-data=[ :serialize to=jso\ + n ({ \$CommonName }) ] \\\r\ + \n dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n :if ([ /file/get \$FileName size ] = 0) do={\r\ + \n \$RmFile \$FileName;\r\ + \n :error false;\r\ + \n }\r\ + \n } on-error={\r\ + \n :log error (\$0 . \" Failed downloading certificate with CommonNam\ + e '\" . \$CommonName . \"'!\");\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n /certificate/import file-name=\$FileName passphrase=\"\" as-value;\r\ + \n :delay 1s;\r\ + \n \$RmFile \$FileName;\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n /certificate/remove [ find where name~(\"^\" . \$FileName . \"_[0-9]\ + +\\\$\") ];\r\ + \n :log warning (\$0 . \" Certificate with CommonName '\" . \$CommonNam\ + e . \"' still unavailable!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :foreach Cert in=[ /certificate/find where name~(\"^\" . \$FileName . \ + \"_[0-9]+\\\$\") ] do={\r\ + \n \$CertificateNameByCN [ /certificate/get \$Cert common-name ];\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CertificateNameByCN do={\r\ + \n :local Match [ :tostr \$1 ];\r\ + \n :global CleanName;\r\ + \n :local Cert ([ /certificate/find where (common-name=\$Match or fingerp\ + rint=\$Match or name=\$Match) ]->0);\r\ + \n :if ([ :len \$Cert ] = 0) do={\r\ + \n :log warning (\$0 .\" No matching certificate found.\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local CommonName [ /certificate/get \$Cert common-name ];\r\ + \n /certificate/set \$Cert name=[ \$CleanName \$CommonName ];\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CharacterMultiply do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Num [ :tonum \$2 ];\r\ + \n :if (\$Num = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :for I from=1 to=\$Num do={:set Return (\$Return . \$Str)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set FileExists do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :local Type [ :tostr \$2 ];\r\ + \n :global FileGet;\r\ + \n :local FileVal [ \$FileGet \$FileName ];\r\ + \n :if (\$FileVal = false) do={ :return false; }\r\ + \n :if ([ :len (\$FileVal->\"size\") ] = 0) do={ :return false; }\r\ + \n :if ([ :len \$Type ] = 0 || \$FileVal->\"type\" = \$Type) do={ :return\ + \_true; }\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set Grep do={\r\ + \n :local Input ([ :tostr \$1 ] . \"\\n\");\r\ + \n :local Pattern [ :tostr \$2 ];\r\ + \n :if ([ :typeof [ :find \$Input \$Pattern ] ] = \"nil\") do={:return []\ + }\r\ + \n :do {\r\ + \n :local Line [ :pick \$Input 0 [ :find \$Input \"\\n\" ] ];\r\ + \n :if ([ :typeof [ :find \$Line \$Pattern ] ] = \"num\") do={:return \ + \$Line}\r\ + \n :set Input [ :pick \$Input ([ :find \$Input \"\\n\" ] + 1) [ :len \$\ + Input ] ];\r\ + \n } while=([ :len \$Input ] > 0);\r\ + \n :return [];\r\ + \n}\r\ + \n\r\ + \n:set HumanReadableNum do={\r\ + \n :local Input [ :tonum \$1 ];\r\ + \n :local Base [ :tonum \$2 ];\r\ + \n :global EitherOr;\r\ + \n :global IfThenElse;\r\ + \n :local Prefix \"kMGTPE\";\r\ + \n :local Pow 1;\r\ + \n :set Base [ \$EitherOr \$Base 1024 ];\r\ + \n :local Bin [ \$IfThenElse (\$Base = 1024) \"i\" \"\" ];\r\ + \n :if (\$Input < \$Base) do={:return \$Input}\r\ + \n :for I from=0 to=[ :len \$Prefix ] do={\r\ + \n :set Pow (\$Pow * \$Base);\r\ + \n :if (\$Input / \$Base < \$Pow) do={\r\ + \n :set Prefix [ :pick \$Prefix \$I ];\r\ + \n :local Tmp1 (\$Input * 100 / \$Pow);\r\ + \n :local Tmp2 (\$Tmp1 / 100);\r\ + \n :if (\$Tmp2 >= 100) do={:return (\$Tmp2 . \$Prefix . \$Bin)}\r\ + \n :return (\$Tmp2 . \".\" . \\\r\ + \n [ :pick \$Tmp1 [ :len \$Tmp2 ] ([ :len \$Tmp1 ] - [ :len \$Tmp\ + 2 ] + 1) ] . \\\r\ + \n \$Prefix . \$Bin);\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set CleanFilePath do={\r\ + \n :local Path [ :tostr \$1 ];\r\ + \n :global CharacterReplace;\r\ + \n :while (\$Path ~ \"//\") do={ :set \$Path [ \$CharacterReplace \$Path \ + \"//\" \"/\" ]; }\r\ + \n :if ([ :pick \$Path 0 ] = \"/\") do={ :set Path [ :pick \$Path 1 [ :le\ + n \$Path ] ]; }\r\ + \n :if ([ :pick \$Path ([ :len \$Path ] - 1) ] = \"/\") do={ :set Path [ \ + :pick \$Path 0 ([ :len \$Path ] - 1) ]; }\r\ + \n :return \$Path;\r\ + \n}\r\ + \n\r\ + \n:set CleanName do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :local Return \"\";\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :if ([ :typeof [ find \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr\ + stuvwxyz0123456789\" \$Char ] ] = \"nil\") do={\r\ + \n :do {:if ([ :len \$Return ] = 0) do={:error true};\r\ + \n :if ([ :pick \$Return ([ :len \$Return ] - 1) ] = \"-\") do={:er\ + ror true}; :set Char \"-\"} on-error={:set Char \"\"}; \r\ + \n };\r\ + \n :set Return (\$Return . \$Char)};\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set RandomDelay do={\r\ + \n :local Time [ :tonum \$1 ];\r\ + \n :local Unit [ :tostr \$2 ];\r\ + \n :global EitherOr;\r\ + \n :global GetRandomNumber;\r\ + \n :global MAX;\r\ + \n :if (\$Time = 0) do={:return false}\r\ + \n :delay ([ \$MAX 10 [ \$GetRandomNumber ([ :tonsec [ :totime (\$Time . \ + [ \$EitherOr \$Unit \"s\" ]) ] ] / 1000000) ] ] . \"ms\");\r\ + \n}\r\ + \n\r\ + \n:set EscapeForRegEx do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :if ([ :len \$Input ] = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :local Chars (\"^.[]\\\$()|*+\?{}\\\\\");\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :if ([ :find \$Chars \$Char ]) do={:set Char (\"\\\\\" . \$Char)}\r\ + \n :set Return (\$Return . \$Char);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ExitError do={\r\ + \n :local ExitOK [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Error [ :tostr \$3 ];\r\ + \n :global IfThenElse;\r\ + \n :if (\$ExitOK = \"false\") do={\r\ + \n :log error (\$Name [ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\\\$\") \ + \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\ + ror\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \ + \$Error) \".\" ]);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set ExitOnError do={\r\ + \n :local Name [ :tostr \$1 ];\r\ + \n :local Error [ :tostr \$2 ];\r\ + \n :global IfThenElse;\r\ + \n :log error (\$Name . \" \" . [ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\ + \\\$\") \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with erro\ + r\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \$E\ + rror) \".\" ]);\r\ + \n}\r\ + \n\r\ + \n:set MiniDateTimeStamp do={\r\ + \n :local mikro [ :tobool \$1 ];\r\ + \n /system/clock;\r\ + \n :local vdate [get date];\r\ + \n :local vtime [get time];\r\ + \n :local vdoff [:toarray \"0,4,5,7,8,10\"];\r\ + \n :local MM [:pick \$vdate (\$vdoff->2) (\$vdoff->3)];\r\ + \n :local M [:tonum \$MM];\r\ + \n :if (\$vdate ~ \".../../....\") do={\r\ + \n :set vdoff [:toarray \"7,11,1,3,4,6\"];\r\ + \n :set M ([:find \"xxanebarprayunulugepctovecANEBARPRAYUNULUGE\ + PCTOVEC\" [:pick \$vdate (\$vdoff->2) (\$vdoff->3)] -1] / 2);\r\ + \n :if (\$M>12) do={:set M (\$M - 12)}\r\ + \n :set MM [:pick (100 + \$M) 1 3];\r\ + \n }\r\ + \n :local yyyy [:pick \$vdate (\$vdoff->0) (\$vdoff->1)];\r\ + \n :local dd [:pick \$vdate (\$vdoff->4) (\$vdoff->5)];\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 :local Return;\r\ + \n :if (\$mikro = true) do={\r\ + \n :set Return ([:pick \$yyyy 2 4] . [:pick \$dd 0 1] . [:pick \$HH 0\ + \_1] . [:pick \$mm 3 4] . [:pick \$ss 6 7]);\r\ + \n } else={\r\ + \n :set Return ([\$yyyy] . [\$MM] . [\$dd] . [\$HH] . [\$mm] . [\$ss]);\ + \r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set WaitForFile do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :local WaitTime [ :totime \$2 ];\r\ + \n :global CleanFilePath;\r\ + \n :global EitherOr;\r\ + \n :global MAX;\r\ + \n :set FileName [ \$CleanFilePath \$FileName ];\r\ + \n :local Delay ([ \$MAX [ \$EitherOr \$WaitTime 2s ] 100ms ] / 9);\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/get \$FileName;\r\ + \n :return true;\r\ + \n } delay=\$Delay max=10;\r\ + \n } on-error={ }\r\ + \n :while ([ :len [ /file/find where name=\$FileName ] ] > 0) do={\r\ + \n :do {\r\ + \n /file/get \$FileName;\r\ + \n :return true;\r\ + \n } on-error={ }\r\ + \n :delay \$Delay;\r\ + \n :set Delay (\$Delay * 3 / 2);\r\ + \n }\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set FileGet do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :global WaitForFile;\r\ + \n :if ([ \$WaitForFile \$FileName 0s ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n :local FileVal false;\r\ + \n :do {\r\ + \n :set FileVal [ /file/get \$FileName ];\r\ + \n } on-error={ }\r\ + \n :return \$FileVal;\r\ + \n}\r\ + \n\r\ + \n:set RmDir do={\r\ + \n :local DirName [ :tostr \$1 ];\r\ + \n :global FileGet;\r\ + \n :log debug (\$0 . \" Removing directory: \". \$DirName);\r\ + \n :local DirVal [ \$FileGet \$DirName ];\r\ + \n :if (\$DirVal = false) do={\r\ + \n :log debug (\$0 . \" ... which does not exist.\");\r\ + \n :return true;\r\ + \n };\r\ + \n :if (\$DirVal->\"type\" != \"directory\") do={\r\ + \n :log error (\$0 . \" Directory '\" . \$DirName . \"' is not a direct\ + ory.\");\r\ + \n :return false;\r\ + \n };\r\ + \n :onerror Err {\r\ + \n /file/remove \$DirName;\r\ + \n } do={\r\ + \n :log error (\$0 . \" Removing directory '\" . \$DirName . \"' failed\ + : \" . \$Err);\r\ + \n :return false;\r\ + \n };\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set RmFile do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :global FileGet;\r\ + \n :log debug (\$0 . \" Removing file: \". \$FileName);\r\ + \n :local FileVal [ \$FileGet \$FileName ];\r\ + \n :if (\$FileVal = false) do={\r\ + \n :log debug (\$0 . \" ... which does not exist.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :if (\$FileVal->\"type\" = \"directory\" || \$FileVal->\"type\" = \"di\ + sk\") do={\r\ + \n :log error (\$0 . \" File '\" . \$FileName . \"' is not a file.\");\ + \r\ + \n :return false;\r\ + \n }\r\ + \n :onerror Err {\r\ + \n /file/remove \$FileName;\r\ + \n } do={\r\ + \n :log error (\$0 . \" Removing file '\" . \$FileName . \"' failed: \"\ + \_. \$Err); :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set MkDir do={\r\ + \n :local Path [ :tostr \$1 ];\r\ + \n :global CleanFilePath;\r\ + \n :global FileGet;\r\ + \n :global RmDir;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :local MkTmpfs do={\r\ + \n :global WaitForFile;\r\ + \n :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ];\r\ + \n :if ([ :len \$TmpFs ] = 1) do={\r\ + \n :if ([ /disk/get \$TmpFs disabled ] = true) do={\r\ + \n :log info (\$0 . \" The tmpfs is disabled, enabling.\");\r\ + \n /disk/enable \$TmpFs;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :log info (\$0 . \" Creating disk of type tmpfs.\");\r\ + \n \$RmDir \"tmpfs\";\r\ + \n :onerror Err {\r\ + \n /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource\ + /get total-memory ] / 3);\r\ + \n \$WaitForFile \"tmpfs\";\r\ + \n } do={\r\ + \n :log warning (\$0 . \" Creating disk of type tmpfs failed: \" . \$\ + Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :set Path [ \$CleanFilePath \$Path ];\r\ + \n :if (\$Path = \"\") do={\r\ + \n :return true;\r\ + \n }\r\ + \n :log debug (\$0 . \" Making directory: \" . \$Path);\r\ + \n :local PathVal [ \$FileGet \$Path ];\r\ + \n :if (\$PathVal->\"type\" = \"directory\") do={\r\ + \n :log debug (\$0 . \" ... which already exists.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ :pick \$Path 0 5 ] = \"tmpfs\") do={\r\ + \n :if ([ \$MkTmpfs ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :onerror Err {\r\ + \n /file/add type=\"directory\" name=\$Path;\r\ + \n \$WaitForFile \$Path;\r\ + \n } do={\r\ + \n :log warning (\$0 . \" Making directory '\" . \$Path . \"' failed: \ + \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set MAX do={:if (\$1 > \$2) do={:return \$1}\r\ + \n :return \$2;\r\ + \n}\r\ + \n:set MIN do={:if (\$1 < \$2) do={:return \$1}\r\ + \n :return \$2;\r\ + \n}\r\ + \n\r\ + \n:set CharacterMultiply do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Num [ :tonum \$2 ];\r\ + \n :if (\$Num = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :for I from=1 to=\$Num do={:set Return (\$Return . \$Str)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set CharacterReplace do={\r\ + \n :local String [ :tostr \$1 ];\r\ + \n :local ReplaceFrom [ :tostr \$2 ];\r\ + \n :local ReplaceWith [ :tostr \$3 ];\r\ + \n :local Return \"\";\r\ + \n :if (\$ReplaceFrom = \"\") do={:return \$String}\r\ + \n :while ([ :typeof [ :find \$String \$ReplaceFrom ] ] != \"nil\") do={\ + \r\ + \n :local Pos [ :find \$String \$ReplaceFrom ];\r\ + \n :set Return (\$Return . [ :pick \$String 0 \$Pos ] . \$ReplaceWith);\ + \r\ + \n :set String [ :pick \$String (\$Pos + [ :len \$ReplaceFrom ]) [ :len\ + \_\$String ] ];\r\ + \n }\r\ + \n :return (\$Return . \$String);\r\ + \n}\r\ + \n\r\ + \n:set IfThenElse do={\r\ + \n :if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] = true) do={:return \ + \$2};\r\ + \n :return \$3;\r\ + \n}\r\ + \n\r\ + \n:set EitherOr do={\r\ + \n :global IfThenElse;\r\ + \n :if ([ :typeof \$1 ] = \"num\") do={:return [ \$IfThenElse (\$1 != 0) \ + \$1 \$2 ]}\r\ + \n :if ([ :typeof \$1 ] = \"time\") do={:return [ \$IfThenElse (\$1 > 0s)\ + \_\$1 \$2 ]}\r\ + \n# this works for boolean values, literal ones with parentheses\r\ + \n :return [ \$IfThenElse ([ :len [ :tostr \$1 ] ] > 0) \$1 \$2 ];\r\ + \n}\r\ + \n\r\ + \n:set ParseKeyValueStore do={\r\ + \n :local Source \$1;\r\ + \n :if ([ :pick \$Source 0 1 ] = \"{\") do={\r\ + \n :do {\r\ + \n :return [ :deserialize from=json \$Source ];\r\ + \n } on-error={ }\r\ + \n } \r\ + \n :if ([ :typeof \$Source ] != \"array\") do={:set Source [ :tostr \$1 ]\ + }\r\ + \n :local Result ({});\r\ + \n :foreach KeyValue in=[ :toarray \$Source ] do={\r\ + \n :if ([ :find \$KeyValue \"=\" ]) do={\r\ + \n :local Key [ :pick \$KeyValue 0 [ :find \$KeyValue \"=\" ] ];\r\ + \n :local Value [ :pick \$KeyValue ([ :find \$KeyValue \"=\" ] + 1) [\ + \_:len \$KeyValue ] ];\r\ + \n :if (\$Value=\"true\") do={:set Value true}\r\ + \n :if (\$Value=\"false\") do={:set Value false}\r\ + \n :set (\$Result->\$Key) \$Value;\r\ + \n } else={:set (\$Result->\$KeyValue) true}\r\ + \n }\r\ + \n :return \$Result;\r\ + \n}\r\ + \n:set RequiredRouterOS do={\r\ + \n :local Caller [ :tostr \$1 ];\r\ + \n :local Required [ :tostr \$2 ];\r\ + \n :local Warn [ :tostr \$3 ];\r\ + \n :global IfThenElse;\r\ + \n :global VersionToNum;\r\ + \n :if (!(\$Required ~ \"^\\\\d+\\\\.\\\\d+((alpha|beta|rc|\\\\.)\\\\d+|)\ + \\\$\")) do={\r\ + \n :log error (\$0 . \" No valid RouterOS version: \" . \$Required);\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ \$VersionToNum \$Required ] > [ \$VersionToNum [ /system/packag\ + e/update/get installed-version ] ]) do={\r\ + \n :if (\$Warn = \"true\") do={\r\ + \n :log warning (\$0 . \" This \" . [ \$IfThenElse ([ :pick \$Caller \ + 0 ] = (\"\\\$\")) \"function\" \"script\" ] . \\\r\ + \n \" '\" . \$Caller . \"' (at least specific functionality) requir\ + es RouterOS \" . \$Required . \". Please update!\");\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set ValidateSyntax do={\r\ + \n :local Code [ :tostr \$1 ];\r\ + \n :global LogPrint;\r\ + \n :onerror Err {\r\ + \n [ :parse (\":local Validate do={\\n\" . \$Code . \"\\n}\") ];\r\ + \n } do={\r\ + \n \$LogPrint debug \$0 (\"Valdation failed: \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set VersionToNum do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :local Multi 0x1000000;\r\ + \n :local Return 0;\r\ + \n :global CharacterReplace;\r\ + \n :set Input [ \$CharacterReplace \$Input \".\" \",\" ];\r\ + \n :foreach I in={ \"zero\"; \"alpha\"; \"beta\"; \"rc\" } do={\r\ + \n :set Input [ \$CharacterReplace \$Input \$I (\",\" . \$I . \",\") ];\ + \r\ + \n }\r\ + \n :foreach Value in=([ :toarray \$Input ], 0) do={\r\ + \n :local Num [ :tonum \$Value ];\r\ + \n :if (\$Multi = 0x100) do={\r\ + \n :if ([ :typeof \$Num ] = \"num\") do={\r\ + \n :set Return (\$Return + 0xff00);\r\ + \n :set Multi (\$Multi / 0x100);\r\ + \n } else={\r\ + \n :if (\$Value = \"zero\") do={ }\r\ + \n :if (\$Value = \"alpha\") do={ :set Return (\$Return + 0x3f00); \ + }\r\ + \n :if (\$Value = \"beta\") do={ :set Return (\$Return + 0x5f00); }\ + \r\ + \n :if (\$Value = \"rc\") do={ :set Return (\$Return + 0x7f00); }\r\ + \n }\r\ + \n }\r\ + \n :if ([ :typeof \$Num ] = \"num\") do={ :set Return (\$Return + (\$Va\ + lue * \$Multi)); }\r\ + \n :set Multi (\$Multi / 0x100);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ScriptLock do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local WaitMax [ :totime \$2 ];\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global ScriptLockOrder;\r\ + \n :if ([ :typeof \$ScriptLockOrder ] = \"nothing\") do={\r\ + \n :set ScriptLockOrder ({});\r\ + \n }\r\ + \n :if ([ :typeof (\$ScriptLockOrder->\$Script) ] = \"nothing\") do={\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n }\r\ + \n :local JobCount do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :return [ :len [ /system/script/job/find where script=\$Script ] ];\ + \r\ + \n }\r\ + \n :local TicketCount do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :local Count 0;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\") do={:set Count (\$Count \ + + 1)}\r\ + \n }\r\ + \n :return \$Count;\r\ + \n }\r\ + \n :local IsFirstTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Check [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if (\$Ticket = \$Check) do={:return true}\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\" && \$Ticket != \$Check) d\ + o={:return false}\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :local AddTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Add [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :while (true) do={\r\ + \n :local Pos [ :len (\$ScriptLockOrder->\$Script) ];\r\ + \n :set (\$ScriptLockOrder->\$Script->\$Pos) \$Add;\r\ + \n :delay 10ms;\r\ + \n :if ((\$ScriptLockOrder->\$Script->\$Pos) = \$Add) do={:return tru\ + e}\r\ + \n }\r\ + \n }\r\ + \n :local RemoveTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Remove [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Id,Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :while ((\$ScriptLockOrder->\$Script->\$Id) = \$Remove) do={\r\ + \n :set (\$ScriptLockOrder->\$Script->\$Id);\r\ + \n :delay 10ms;\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n :local CleanupTickets do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\") do={:return false}\r\ + \n }\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n }\r\ + \n :if ([ :typeof \$WaitMax ] = \"nil\" ) do={:set WaitMax 0s}\r\ + \n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\r\ + \n :log error (\$0 . \" A script named '\" . \$Script . \"' does not ex\ + ist!\");\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$JobCount \$Script ] = 0) do={\r\ + \n \$LogPrint error \$0 (\"No script '\" . \$Script . \"' is running!\"\ + );\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$TicketCount \$Script ] >= [ \$JobCount \$Script ]) do={\r\ + \n :log error (\$0 . \" More tickets than running scripts '\" . \$Scrip\ + t . \"', resetting!\");\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n /system/script/job/remove [ find where script=\$Script ];\r\ + \n }\r\ + \n :local MyTicket [ \$GetRandom20CharAlNum 6 ];\r\ + \n \$AddTicket \$Script \$MyTicket;\r\ + \n :local WaitInterval (\$WaitMax / 20);\r\ + \n :local WaitTime \$WaitMax;\r\ + \n :while (\$WaitTime > 0 && \\\r\ + \n ([ \$IsFirstTicket \$Script \$MyTicket ] = false || \\\r\ + \n [ \$TicketCount \$Script ] < [ \$JobCount \$Script ])) do={\r\ + \n :set WaitTime (\$WaitTime - \$WaitInterval);\r\ + \n :delay \$WaitInterval;\r\ + \n }\r\ + \n :if ([ \$IsFirstTicket \$Script \$MyTicket ] = true && \\\r\ + \n [ \$TicketCount \$Script ] = [ \$JobCount \$Script ]) do={\r\ + \n \$RemoveTicket \$Script \$MyTicket;\r\ + \n \$CleanupTickets \$Script;\r\ + \n :return true;\r\ + \n }\r\ + \n \$RemoveTicket \$Script \$MyTicket;\r\ + \n \$LogPrint debug \$0 (\"Script '\" . \$Script . \"' started more than \ + once\" . \\\r\ + \n [ \$IfThenElse (\$WaitTime < \$WaitMax) \" and timed out waiting for\ + \_lock\" \"\" ] . \"...\");\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set SendNotification do={ :onerror Err {\r\ + \n :global SendNotification2;\r\ + \n \$SendNotification2 ({ origin=\$0; subject=\$1; message=\$2; link=\$3;\ + \_silent=\$4 });\r\ + \n} do={:global ExitOnError; \$ExitOnError \$0 \$Err}\r\ + \n}\r\ + \n\r\ + \n:set SendNotification2 do={\r\ + \n :local Notification \$1;\r\ + \n :global NotificationFunctions;\r\ + \n :foreach FunctionName,Discard in=\$NotificationFunctions do={\r\ + \n (\$NotificationFunctions->\$FunctionName) (\"\\\$NotificationFunctio\ + ns->\\\"\" . \$FunctionName . \"\\\"\") \$Notification;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set SymbolByUnicodeName do={\r\ + \n :local Name [ :tostr \$1 ];\r\ + \n :global EitherOr;\r\ + \n :global SymbolsExtra;\r\ + \n :local Symbols ({\r\ + \n \"abacus\"=\"\\F0\\9F\\A7\\AE\";\r\ + \n \"alarm-clock\"=\"\\E2\\8F\\B0\";\r\ + \n \"arrow-down\"=\"\\E2\\AC\\87\";\r\ + \n \"arrow-up\"=\"\\E2\\AC\\86\";\r\ + \n \"calendar\"=\"\\F0\\9F\\93\\85\";\r\ + \n \"card-file-box\"=\"\\F0\\9F\\97\\83\";\r\ + \n \"chart-decreasing\"=\"\\F0\\9F\\93\\89\";\r\ + \n \"chart-increasing\"=\"\\F0\\9F\\93\\88\";\r\ + \n \"cloud\"=\"\\E2\\98\\81\";\r\ + \n \"cross-mark\"=\"\\E2\\9D\\8C\";\r\ + \n \"earth\"=\"\\F0\\9F\\8C\\8D\";\r\ + \n \"fire\"=\"\\F0\\9F\\94\\A5\";\r\ + \n \"floppy-disk\"=\"\\F0\\9F\\92\\BE\";\r\ + \n \"gear\"=\"\\E2\\9A\\99\";\r\ + \n \"heart\"=\"\\E2\\99\\A5\";\r\ + \n \"high-voltage-sign\"=\"\\E2\\9A\\A1\";\r\ + \n \"incoming-envelope\"=\"\\F0\\9F\\93\\A8\";\r\ + \n \"information\"=\"\\E2\\84\\B9\";\r\ + \n \"large-orange-circle\"=\"\\F0\\9F\\9F\\A0\";\r\ + \n \"large-red-circle\"=\"\\F0\\9F\\94\\B4\";\r\ + \n \"link\"=\"\\F0\\9F\\94\\97\";\r\ + \n \"lock-with-ink-pen\"=\"\\F0\\9F\\94\\8F\";\r\ + \n \"memo\"=\"\\F0\\9F\\93\\9D\";\r\ + \n \"mobile-phone\"=\"\\F0\\9F\\93\\B1\";\r\ + \n \"pushpin\"=\"\\F0\\9F\\93\\8C\";\r\ + \n \"scissors\"=\"\\E2\\9C\\82\";\r\ + \n \"scroll\"=\"\\F0\\9F\\93\\9C\";\r\ + \n \"smiley-partying-face\"=\"\\F0\\9F\\A5\\B3\";\r\ + \n \"smiley-smiling-face\"=\"\\E2\\98\\BA\";\r\ + \n \"smiley-winking-face-with-tongue\"=\"\\F0\\9F\\98\\9C\";\r\ + \n \"sparkles\"=\"\\E2\\9C\\A8\";\r\ + \n \"speech-balloon\"=\"\\F0\\9F\\92\\AC\";\r\ + \n \"star\"=\"\\E2\\AD\\90\";\r\ + \n \"warning-sign\"=\"\\E2\\9A\\A0\";\r\ + \n \"white-heavy-check-mark\"=\"\\E2\\9C\\85\"\r\ + \n }, \$SymbolsExtra);\r\ + \n :local Magic [ :pick [ /system/clock/get date ] 4 10 ];\r\ + \n :local Special {\r\ + \n \"information-04-01\"=\"\\F0\\9F\\9A\\BB\";\r\ + \n \"large-orange-circle-04-01\"=\"\\F0\\9F\\8D\\8A\";\r\ + \n \"large-orange-circle-10-31\"=\"\\F0\\9F\\8E\\83\";\r\ + \n \"large-red-circle-04-01\"=\"\\F0\\9F\\8D\\92\" };\r\ + \n :if ([ :len (\$Symbols->\$Name) ] = 0) do={\r\ + \n :log warning (\$0 . \"No symbol available for name '\" . \$Name . \"\ + '!\");\r\ + \n :return \"\";\r\ + \n }\r\ + \n :return ([ \$EitherOr (\$Special->(\$Name . \$Magic)) (\$Symbols->\$Na\ + me) ] . \"\\EF\\B8\\8F\");\r\ + \n}\r\ + \n\r\ + \n:set SymbolForNotification do={\r\ + \n :global NotificationsWithSymbols;\r\ + \n :global SymbolByUnicodeName;\r\ + \n :global IfThenElse;\r\ + \n :if (\$NotificationsWithSymbols != true) do={:return [ \$IfThenElse ([\ + \_:len \$2 ] > 0) ([ :tostr \$2 ] . \" \") \"\" ]}\r\ + \n :local Return \"\";\r\ + \n :foreach Symbol in=[ :toarray \$1 ] do={:set Return (\$Return . [ \$Sy\ + mbolByUnicodeName \$Symbol ])}\r\ + \n :return (\$Return . \" \");\r\ + \n}\r\ + \n\r\ + \n:set Unix2Dos do={:return [ :tocrlf [ :tostr \$1 ] ]}\r\ + \n:set UrlEncode do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :if ([ :len \$Input ] = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :local Chars (\"\\n\\r !\\\"#\\\$%&'()*+,:;<=>\?@[\\\\]^`{|}~\");\r\ + \n :local Subs { \"%0A\"; \"%0D\"; \"%20\"; \"%21\"; \"%22\"; \"%23\"; \"\ + %24\"; \"%25\"; \"%26\"; \"%27\";\r\ + \n \"%28\"; \"%29\"; \"%2A\"; \"%2B\"; \"%2C\"; \"%3A\"; \"%3B\"; \ + \"%3C\"; \"%3D\"; \"%3E\"; \"%3F\";\r\ + \n \"%40\"; \"%5B\"; \"%5C\"; \"%5D\"; \"%5E\"; \"%60\"; \"%7B\"; \ + \"%7C\"; \"%7D\"; \"%7E\" };\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :local Replace [ :find \$Chars \$Char ];\r\ + \n :if ([ :typeof \$Replace ] = \"num\") do={:set Char (\$Subs->\$Repla\ + ce)}\r\ + \n :set Return (\$Return . \$Char);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set IsDNSResolving do={\r\ + \n :do {:resolve \"api.telegram.org\"} on-error={:return false}\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set IsFullyConnected do={\r\ + \n :global IsDNSResolving;\r\ + \n :global IsTimeSync;\r\ + \n :if ([ \$IsDNSResolving ] = false) do={:return false};\r\ + \n :if ([ \$IsTimeSync ] = false) do={:return false};\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set IsTimeSync do={\r\ + \n :global IsTimeSyncCached;\r\ + \n :global IsTimeSyncResetNtp;\r\ + \n :if (\$IsTimeSyncCached = true) do={:return true}\r\ + \n :if ([ /system/ntp/client/get enabled ] = true) do={\r\ + \n :if ([ /system/ntp/client/get status ] = \"synchronized\") do={\r\ + \n :set IsTimeSyncCached true;\r\ + \n :return true;\r\ + \n }\r\ + \n :local Uptime [ /system/resource/get uptime ];\r\ + \n :if ([ :typeof \$IsTimeSyncResetNtp ] = \"nothing\") do={\r\ + \n :set IsTimeSyncResetNtp \$Uptime;\r\ + \n }\r\ + \n :if (\$Uptime - \$IsTimeSyncResetNtp < 3m) do={:return false}\r\ + \n :log warning \"\$0 The ntp client is configured, but did not sync.\"\ + ;\r\ + \n :set IsTimeSyncResetNtp \$Uptime;\r\ + \n /system/ntp/client/set enabled=no;\r\ + \n :delay 20ms;\r\ + \n /system/ntp/client/set enabled=yes;\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ /system/license/get ]->\"level\" = \"free\" || \\\r\ + \n [ /system/resource/get ]->\"board-name\" = \"x86\") do={\r\ + \n :log debug \"\$0 No ntp client configured, relying on RTC for CHR fr\ + ee license and x86.\";\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ /ip/cloud/get update-time ] = true) do={\r\ + \n :if ([ :typeof [ /ip/cloud/get public-address ] ] = \"ip\") do={\r\ + \n :set IsTimeSyncCached true;\r\ + \n :return true;\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :log debug \"\$0 No time source configured! Returning gracefully...\";\ + \r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set FetchHugeFiles do={\r\ + \n :local ScriptName [ :tostr \$1 ];\r\ + \n :local Url [ :tostr \$2 ];\r\ + \n :local CheckCert [ :tostr \$3 ];\r\ + \n\r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global MkDir;\r\ + \n :global RmDir;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :set CheckCert [ \$IfThenElse (\$CheckCert = \"false\") \"no\" \"yes-w\ + ithout-crl\" ];\r\ + \n\r\ + \n :local DirName (\"tmpfs/\" . [ \$CleanName \$ScriptName ]);\r\ + \n :if ([ \$MkDir \$DirName ] = false) do={\r\ + \n \$LogPrint error \$0 (\"Failed creating directory!\");\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :local FileName (\$DirName . \"/\" . [ \$CleanName \$0 ] . \"-\" . [ \ + \$GetRandom20CharAlNum ]);\r\ + \n :onerror Err {\r\ + \n /tool/fetch check-certificate=\$CheckCert \$Url dst-path=\$FileName \ + \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$ScriptName ] }) as-va\ + lue;\r\ + \n } do={\r\ + \n :if ([ \$WaitForFile \$FileName 500ms ] = true) do={\r\ + \n \$RmFile \$FileName;\r\ + \n }\r\ + \n \$LogPrint debug \$0 (\"Failed downloading from \" . \$Url . \" - \"\ + \_. \$Err);\r\ + \n \$RmDir \$DirName;\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$WaitForFile \$FileName 5s ] = false) do={\r\ + \n \$LogPrint debug \$0 (\"The file downloaded from \" . \$Url . \" did\ + \_not show up.\");\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :local FileSize [ /file/get \$FileName size ];\r\ + \n :local Return \"\";\r\ + \n :local VarSize 0;\r\ + \n :while (\$VarSize != \$FileSize) do={\r\ + \n :set Return (\$Return . ([ /file/read offset=\$VarSize chunk-size=32\ + 768 file=\$FileName as-value ]->\"data\"));\r\ + \n :set FileSize [ /file/get \$FileName size ];\r\ + \n :set VarSize [ :len \$Return ];\r\ + \n :if (\$VarSize > \$FileSize) do={\r\ + \n :delay 100ms;\r\ + \n }\r\ + \n }\r\ + \n \$RmDir \$DirName;\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set FetchUserAgentStr do={\r\ + \n :local Caller [ :tostr \$1 ];\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global CommitBrief;\r\ + \n :global IfThenElse;\r\ + \n :local Resource [ /system/resource/get ];\r\ + \n :return (\"User-Agent: Mikrotik/\" . \$Resource->\"version\" . \" \" .\ + \_\$Resource->\"architecture-name\" . \\\r\ + \n \" \" . \$Caller . \"/Fetch (\" . \$GlobalScriptsUrl . \"; \" . [ \$\ + CommitBrief ] . \")\");\r\ + \n}\r\ + \n\r\ + \n:set ScriptInstallUpdate do={ :onerror Err {\r\ + \n :local Scripts [ :toarray \$1 ];\r\ + \n :local NewComment [ :tostr \$2 ];\r\ + \n :global InitSetup;\r\ + \n\r\ + \n :global CommitId;\r\ + \n :global ScriptUpdatesCRLF;\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global CertificateAvailable;\r\ + \n :global CheckSumsVerify;\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global SumsDirAndFileName;\r\ + \n :global GlobalScriptsUrlSuffix;\r\ + \n :global EitherOr;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global Grep;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global ParseKeyValueStore;\r\ + \n :global RequiredRouterOS;\r\ + \n :global SendNotification2;\r\ + \n :global SymbolForNotification;\r\ + \n :global ValidateSyntax;\r\ + \n :global CommentsInScripts;\r\ + \n\r\ + \n :if ([ \$CertificateAvailable \"Root YE\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading certificate failed, trying wit\ + hout.\");\r\ + \n }\r\ + \n\r\ + \n :foreach Script in=\$Scripts do={\r\ + \n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\ + \r\ + \n \$LogPrint info \$0 (\"Adding new script: \" . \$Script);\r\ + \n /system/script/add name=\$Script owner=\$Script source=\"#!rsc by \ + Vados\\n\" comment=\$NewComment;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :local CommitIdBefore \$CommitId;\r\ + \n :local GlobalConfVersionBefore \$GlobalConfVersion;\r\ + \n :local ReloadGlobal false;\r\ + \n :local DeviceMode [ /system/device-mode/get ];\r\ + \n\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :local CheckSums ({});\r\ + \n :do {\r\ + \n :local Url (\$GlobalScriptsUrl . \$SumsDirAndFileName . \$GlobalScr\ + iptsUrlSuffix);\r\ + \n \$LogPrint debug \$0 (\"Fetching checksums from url: \" . \$Url);\ + \r\ + \n :set CheckSums [ :deserialize from=json ([ /tool/fetch check-certi\ + ficate=yes-without-crl \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url output\ + =user as-value ]->\"data\") ];\r\ + \n } on-error={ \$LogPrint warning \$0 (\"Err = \" . \$Err); }\r\ + \n }\r\ + \n\r\ + \n :foreach Script in=[ /system/script/find where source~\"^#!rsc by Vado\ + s\" ] do={\r\ + \n :local ScriptVal [ /system/script/get \$Script ];\r\ + \n :local ScriptInfo [ \$ParseKeyValueStore (\$ScriptVal->\"comment\") \ + ];\r\ + \n :local SourceNew;\r\ + \n\r\ + \n :foreach Scheduler in=[ /system/scheduler/find where on-event~(\"\\\ + \\b\" . \$ScriptVal->\"name\" . \"\\\\b\") ] do={\r\ + \n :local SchedulerVal [ /system/scheduler/get \$Scheduler ];\r\ + \n :if (\$ScriptVal->\"policy\" != \$SchedulerVal->\"policy\") do={\r\ + \n \$LogPrint warning \$0 (\"Policies differ for script '\" . \$Scr\ + iptVal->\"name\" . \"' and its scheduler '\" . \$SchedulerVal->\"name\" . \ + \"'!\");\r\ + \n }\r\ + \n }\r\ + \n :do {\r\ + \n :if (\$ScriptInfo->\"ignore\" = true) do={\r\ + \n \$LogPrint debug \$0 (\"Ignoring script '\" . \$ScriptVal->\"nam\ + e\" . \"', as requested.\");\r\ + \n :error true;\r\ + \n }\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :local CheckSum (\$CheckSums->(\$ScriptVal->\"name\"));\r\ + \n :if ([ :len (\$ScriptInfo->\"base-url\") ] = 0 && [ :len (\$Scri\ + ptInfo->\"url-suffix\") ] = 0 && \\\r\ + \n [ :convert transform=md5 to=hex [ :tolf (\$ScriptVal->\"sou\ + rce\") ] ] = \$CheckSum) do={\r\ + \n \$LogPrint debug \$0 (\"Checksum for script '\" . \$ScriptVal-\ + >\"name\" . \"' matches, ignoring.\");\r\ + \n :error true;\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len (\$ScriptInfo->\"certificate\") ] > 0) do={\r\ + \n :if ([ \$CertificateAvailable (\$ScriptInfo->\"certificate\") \"\ + fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading certificate failed, tryi\ + ng without.\");\r\ + \n }\r\ + \n }\r\ + \n :onerror Err {\r\ + \n :local BaseUrl [ \$EitherOr (\$ScriptInfo->\"base-url\") \$Globa\ + lScriptsUrl ];\r\ + \n :local UrlSuffix [ \$EitherOr (\$ScriptInfo->\"url-suffix\") \$G\ + lobalScriptsUrlSuffix ];\r\ + \n :local Url (\$BaseUrl . \$ScriptVal->\"name\" . \".rsc\" . \$Url\ + Suffix);\r\ + \n \$LogPrint warning \$0 (\"Fetching script '\" . (\$ScriptVal->\"\ + name\") . \"' from url: \" . \$Url);\r\ + \n :local Result [ /tool/fetch check-certificate=yes-without-crl \\\ + \r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url outp\ + ut=user as-value ];\r\ + \n :if (\$Result->\"status\" = \"finished\") do={\r\ + \n :set SourceNew [ :tolf (\$Result->\"data\") ];\r\ + \n } \r\ + \n } do={\r\ + \n \$LogPrint warning \$0 (\"Failed fetching script '\" . \$ScriptV\ + al->\"name\" . \"': \" . \$Err);\r\ + \n :if (\$Err != \"Fetch failed with status 404\") do={\r\ + \n :error false;\r\ + \n }\r\ + \n :if (\$ScriptVal->\"source\" = \"#!rsc by Vados\") do={\r\ + \n \$LogPrint warning \$0 (\"Removing dummy. Typo on installation\ + \?\");\r\ + \n /system/script/remove \$Script;\r\ + \n :error false;\r\ + \n }\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :if ([ :len (\$ScriptInfo->\"base-url\") ] = 0 && [ :len (\$Sc\ + riptInfo->\"url-suffix\") ] = 0 && \\\r\ + \n [ :len \$CheckSum ] = 0) do={\r\ + \n \$LogPrint warning \$0 (\"Added the script manually\? Skip u\ + pdates with 'ignore=true' in comment.\");\r\ + \n }\r\ + \n }\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$SourceNew ] = 0) do={\r\ + \n \$LogPrint debug \$0 (\"No update for script '\" . (\$ScriptVal-\ + >\"name\") . \"'.\");\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :local SourceCRLF [ :tocrlf \$SourceNew ];\r\ + \n :if (\$InitSetup != true && ((\$ScriptVal->\"name\") != \"AM-Globa\ + lConfig\" || (\$ScriptVal->\"name\") != \"AM-GlobalFunc\")) do={\r\ + \n :if (\$SourceNew = (\$ScriptVal->\"source\") || \$SourceCRLF = (\ + \$ScriptVal->\"source\")) do={\r\ + \n \$LogPrint debug \$0 (\"Script '\" . (\$ScriptVal->\"name\") \ + . \"' source did not change. End of lines set to CRLF.\");\r\ + \n if (\$ScriptUpdatesCRLF = false) do={\r\ + \n :put [ :tolf \$SourceNew ];\r\ + \n \$LogPrint debug \$0 (\"Script '\" . (\$ScriptVal->\"name\"\ + ) . \"' source update end of lines to LF.\");\r\ + \n }\r\ + \n :error false;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if ([ :pick \$SourceNew 0 14 ] != \"#!rsc by Vados\") do={\r\ + \n \$LogPrint warning \$0 (\"Looks like new script '\" . \$ScriptVa\ + l->\"name\" . \"' is not valid (missing shebang = '\$shebang') Ignoring!\"\ + );\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if (\$CommentsInScripts = true) do={\r\ + \n :local CommentLine [ \$Grep \$SourceNew (\"\\23 Script comment:\ + \_\") ];\r\ + \n :if ([ :len \$CommentLine ] = 0) do={\r\ + \n \$LogPrint debug \$0 (\"The search in new script '\" . (\$Scr\ + iptVal->\"name\") . \"' for comment in script body, is not found result Ig\ + noring!\");\r\ + \n :error false;\r\ + \n } else={\r\ + \n :if ([ :len (\$ScriptVal->\"comment\")] = 0 || [ :len (\$Scri\ + ptVal->\"comment\")] != [ :pick \$CommentLine ([ :find \$CommentLine \":\"\ + ] + 2) [ :len \$CommentLine] ]) do={\r\ + \n \$LogPrint debug \$0 (\"Exsiting comment for script '\" . (\ + \$ScriptVal->\"name\") . \"' is '\" . (\$ScriptVal->\"comment\") . \"'.\")\ + ;\r\ + \n :set (\$ScriptVal->\"comment\") [ :pick \$CommentLine ([ :f\ + ind \$CommentLine \":\"] + 2) [ :len \$CommentLine] ];\r\ + \n \$LogPrint debug \$0 (\"New comment (\" . (\$ScriptVal->\"c\ + omment\") . \") for script '\" . (\$ScriptVal->\"name\") . \"' has been ad\ + ded!\");\r\ + \n } else={\r\ + \n \$LogPrint debug \$0 (\"Comment for script '\" . (\$ScriptV\ + al->\"name\") . \"' did not change.\");\r\ + \n :error false;\r\ + \n }\r\ + \n } \r\ + \n }\r\ + \n\r\ + \n :local RequiredROS ([ \$ParseKeyValueStore [ \$Grep \$SourceNew (\ + \"\\23 requires RouterOS, \") ] ]->version);\r\ + \n :if ([ \$RequiredRouterOS \$0 [ \$EitherOr \$RequiredROS \"0.0\" ]\ + \_false ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"The script '\" . (\$ScriptVal->\"name\ + \") . \"' requires RouterOS \" . \\\r\ + \n \$RequiredROS . \", which is not met by your installation. I\ + gnoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n :local RequiredDM [ \$ParseKeyValueStore [ \$Grep \$SourceNew (\"\ + \\23 requires device-mode, \") ] ];\r\ + \n :local MissingDM ({});\r\ + \n :foreach Feature,Value in=\$RequiredDM do={\r\ + \n :if ([ :typeof (\$DeviceMode->\$Feature) ] = \"bool\" && (\$Devi\ + ceMode->\$Feature) = false) do={\r\ + \n :set MissingDM (\$MissingDM, \$Feature);\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len \$MissingDM ] > 0) do={\r\ + \n \$LogPrint warning \$0 (\"The script '\" . (\$ScriptVal->\"name\ + \") . \"' requires disabled \" . \\\r\ + \n \"device-mode features (\" . [ :tostr \$MissingDM ] . \"). I\ + gnoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$ValidateSyntax \$SourceNew ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Syntax validation for script '\" . (\$\ + ScriptVal->\"name\") . \"' failed! Ignoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n \r\ + \n :log info (\"Updating script: \" . (\$ScriptVal->\"name\") . \" Sc\ + ript comment: \" . (\$ScriptVal->\"comment\"));\r\ + \n\r\ + \n /system/script/set owner=(\$ScriptVal->\"name\") comment=(\$Script\ + Val->\"comment\") \\\r\ + \n source=[ \$IfThenElse (\$ScriptUpdatesCRLF = true) \$SourceCRL\ + F \$SourceNew ] \$Script;\r\ + \n :if (\$ScriptVal->\"name\" = \"AM-GlobalConfig\" || \\\r\ + \n \$ScriptVal->\"name\" = \"AM-GlobalFunc\" || \\\r\ + \n \$ScriptVal->\"name\" ~ (\"^(AM-GlobalFunc\\\\.d|mod)/.\")) d\ + o={\r\ + \n :set ReloadGlobal true;\r\ + \n }\r\ + \n } on-error={ }\r\ + \n }\r\ + \n :if (\$ReloadGlobal = true) do={\r\ + \n \$LogPrint info \$0 (\"Reloading global configuration and functions.\ + \");\r\ + \n :onerror Err {\r\ + \n :global InitSetup (a);\r\ + \n /system/script {run AM-GlobalConfig; run AM-GlobalFunc;};\r\ + \n } do={\$LogPrint error \$0 (\"Reloading global configuration and fun\ + ctions failed! \" . \$Err)}\r\ + \n }\r\ + \n :if (\$GlobalConfVersionBefore > \$GlobalConfVersion) do={\r\ + \n \$LogPrint warning \$0 (\"The configuration version decreased from \ + \" . \\\r\ + \n \$GlobalConfVersionBefore . \" to \" . \$GlobalConfVersion . \\\r\ + \n \". Installed an older version\?\");\r\ + \n }\r\ + \n :if (\$GlobalConfVersionBefore < \$GlobalConfVersion) do={\r\ + \n :global GlobalConfChanges;\r\ + \n :global GlobalConfMigration;\r\ + \n :local ChangeLogCode;\r\ + \n :if ([ :len \$ChangeLogCode ] > 0) do={\r\ + \n :if ([ \$ValidateSyntax \$ChangeLogCode ] = true) do={\r\ + \n :onerror Err {\r\ + \n [ :parse \$ChangeLogCode ];\r\ + \n } do={\$LogPrint warning \$0 (\"The changelog failed to run: \" \ + . \$Err)}\r\ + \n } else={\$LogPrint warning \$0 (\"The changelog failed syntax vali\ + dation!\")}\r\ + \n }\r\ + \n :if ([ :len \$GlobalConfMigration ] > 0) do={\r\ + \n :for I from=(\$GlobalConfVersionBefore + 1) to=\$GlobalConfVersion\ + \_do={\r\ + \n :local Migration (\$GlobalConfMigration->[ :tostr \$I ]);\r\ + \n :do {\r\ + \n :if ([ :typeof \$Migration ] != \"str\") do={\r\ + \n \$LogPrint debug \$0 (\"Migration code for change \" . \$I .\ + \_\" is not available.\");\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$ValidateSyntax \$Migration ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Migration code for change \" . \$I\ + \_. \" failed syntax validation!\");\r\ + \n :error false}\r\ + \n \$LogPrint info \$0 (\"Applying migration for change \" . \$I \ + . \": \" . \$Migration);\r\ + \n :onerror Err {\r\ + \n [ :parse \$Migration ];\r\ + \n } do={\$LogPrint warning \$0 (\"Migration code for change \" .\ + \_\$I . \" failed to run: \" . \$Err)}\r\ + \n } on-error={ }\r\ + \n }\r\ + \n }\r\ + \n :local NotificationMessage (\"The configuration version on \" . \$Id\ + entity . \" increased \" . \\\r\ + \n \"to \" . \$GlobalConfVersion . \", current configuration may nee\ + d modification. \" . \\\r\ + \n \"Please review and update global-config-overlay, then re-run glo\ + bal-config.\");\r\ + \n \$LogPrint info \$0 (\$NotificationMessage);\r\ + \n :if ([ :len \$GlobalConfChanges ] > 0) do={\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n\\nChanges:\ + \");\r\ + \n :for I from=(\$GlobalConfVersionBefore + 1) to=\$GlobalConfVersion\ + \_do={\r\ + \n :local Change (\$GlobalConfChanges->[ :tostr \$I ]);\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n \" . \\\ + \r\ + \n [ \$SymbolForNotification \"pushpin\" \"*\" ] . \$Change);\r\ + \n \$LogPrint info \$0 (\"Change \" . \$I . \": \" . \$Change);\r\ + \n }\r\ + \n } else={\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n\\nNews and\ + \_changes are not available.\");\r\ + \n\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$GlobalConfChanges ] > 0) do={ :set GlobalConfChanges; \ + } else={ :global GlobalConfChanges (a); }\r\ + \n :if ([ :len \$GlobalConfMigration ] > 0) do={ :set GlobalConfMigrati\ + on; } else={ :global GlobalConfMigration (a); }\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n\r\ + \n# load modules\r\ + \n:foreach Script in=[ /system/script/find where name ~ \"^(global-functio\ + ns\\\\.d|mod)/.\" ] do={\r\ + \n :local ScriptVal [ /system/script/get \$Script ];\r\ + \n :if ([ \$ValidateSyntax (\$ScriptVal->\"source\") ] = true) do={\r\ + \n :onerror Err {\r\ + \n /system/script/run \$Script;\r\ + \n } do={\r\ + \n \$LogPrint error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' f\ + ailed to run: \" . \$Err);\r\ + \n }\r\ + \n } else={\r\ + \n \$LogPrint error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' fai\ + led syntax validation, skipping.\");\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# Add and fix global scheduler\r\ + \n:put \"Add or/and fix Global scheduler...\";\r\ + \n:local OnEvent \"/system/script { run AM-GlobalConfig; run AM-GlobalFunc\ + ; }\";\r\ + \n:local Comment \"Every Day Set Global Environment Variables\";\r\ + \n:if ([ :len [ /system/scheduler/find where name=\"AM-SetGlobalEnv\" ] ] \ + = 0) do={\r\ + \n /system/scheduler/add name=\"AM-SetGlobalEnv\" comment=\$Comment on-ev\ + ent=\$OnEvent start-time=startup;\r\ + \n}\r\ + \n:if ([ /system/scheduler/get \"AM-SetGlobalEnv\" comment ] != \$Comment)\ + \_do={\r\ + \n /system/scheduler/set \"AM-SetGlobalEnv\" comment=\$Comment;\r\ + \n}\r\ + \n:if ([ /system/scheduler/get \"AM-SetGlobalEnv\" on-event ] != \$OnEvent\ + ) do={\r\ + \n /system/scheduler/set \"AM-SetGlobalEnv\" on-event=\$OnEvent;\r\ + \n}\r\ + \n# Add Update Script scheduler\r\ + \n:put \"Add or/and fix Update Script scheduler\";\r\ + \n:set OnEvent \":global ScriptInstallUpdate; \\\$ScriptInstallUpdate;\";\ + \r\ + \n:set Comment \"Every Day Check For Update Scripts\";\r\ + \n:if ([ :len [ /system/scheduler/find where name=\"AM-ScriptInstallUpdate\ + \" ] ] = 0) do={\r\ + \n/system/scheduler/add name=\"AM-ScriptInstallUpdate\" comment=\$Comment \ + start-time=startup interval=1d on-event=\$OnEvent;\r\ + \n}\r\ + \n\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady;\r\ + \n :retry { :if (\$GlobalConfReady != true) do={ :error (\"Global configs\ + \_not ready.\"); }; } delay=100ms max=5;\r\ + \n\r\ + \n:log warning (\"This script \$ScriptName Loaded \" . \\\r\ + \n [ \$IfThenElse (\$CommitId != \"unknown\") ([ \$CommitBrief ] . \" \ + \") ] . \\\r\ + \n \"on \" . \$devBoardName . \"\\nRouterOS: \" . \$devCurVersion . \"\ + \\nArchitecture: \" . \$devCurArch . \"\\nIdentity: \" . \$Identity);\r\ + \n # signal we are ready\r\ + \n:set GlobalFuncReady true;\r\ + \n:set ExitOK true;\r\ + \n\r\ + \n:log info (\$0 . \"Global configs ready!\");\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n}\r\ + \n" +add comment=\ + "Lookup IP addresses for RKN list and add them to firewall address-list" \ + dont-require-permissions=no name=AM-RKN-dnsLookup owner=AM-RKN-dnsLookup \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by Vados\r\ + \n# RouterOS script: AM-RKN-dnsLookup\r\ + \n# Script comment: Lookup IP addresses for RKN list and add them to firew\ + all address-list\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n# Script description: This script is intended to be run periodically, e.\ + g. once a day, to update the IP addresses in the firewall address-list bas\ + ed on the RKN list.\r\ + \n# It resolves the domain names in the RKN list and a\ + dds the corresponding IP addresses to the specified firewall address-list.\ + \r\ + \n# The script also handles CNAME records by following\ + \_them until it finds A records.\r\ + \n# Usage:\r\ + \n# /system/script { run AM-RKN-dnsLookup; }\r\ + \n:local ScriptName [ :jobname ];\r\ + \n\r\ + \n:global dnsLookupReady false;\r\ + \n:if ([:typeof \$dnsLookupReady] != \"boolean\") do={ :set dnsLookupReady\ + \_true; }\r\ + \nwhile (!\$dnsLookupReady) do={ :nothing; }\r\ + \n:set dnsLookupReady false;\r\ + \n\r\ + \n:local arrRKN {\r\ + \n \"telegra.ph\"\r\ + \n \"grani.ru\"\r\ + \n \"xhamster.com\"\r\ + \n \"lostfilm.tv\"\r\ + \n \"rutracker.org\"\r\ + \n \"lurkmore.to\"\r\ + \n \"dou.ua\"\r\ + \n \"youporn.com\"\r\ + \n \"pornolab.net\"\r\ + \n \"crimerussia.com\"\r\ + \n \"telegram.org\"\r\ + \n \"api.telegram.org\"\r\ + \n \"facebook.com\"\r\ + \n \"instagram.com\"\r\ + \n \"x.com\"\r\ + \n \"twitter.com\"\r\ + \n \"youtube.com\"\r\ + \n \"googleapis.com\"\r\ + \n \"youtu.be\"\r\ + \n \"googlevideo.com\"\r\ + \n \"gstatic.com\"\r\ + \n \"googleusercontent.com\"\r\ + \n \"nhacmp3youtube.com\"\r\ + \n \"telegram.me\"\r\ + \n \"t.me\";}\r\ + \n\r\ + \n:local ListRKN \"RKN-dnsLookup\";\r\ + \n:local ListComment (\"ListRKN managed by \" . \$ScriptName);\r\ + \n:foreach aListItem in=[/ip/firewall/address-list find list=\$ListRKN] do\ + ={\r\ + \n /ip firewall address-list remove \$aListItem;\r\ + \n}\r\ + \n:foreach aItem in=\$arrRKN do={\r\ + \n# :do {\r\ + \n :retry delay=500ms max=5 on-error={:log error \"dns resolve fail\ + ure [ \$aItem - resolve ip ]\"} command={:resolve \$aItem}\r\ + \n# } on-error={\r\ + \n# :log error \"dns resolve failure [ \$arItem - resolve ip ]\";\r\ + \n# }\r\ + \n :foreach dnsRecord in=[/ip dns cache all find where (name=\$aItem)] \ + do={\r\ + \n :if ([/ip dns cache all get \$dnsRecord type]=\"A\") do={\r\ + \n :local newAddress [/ip dns cache all get \$dnsRecord data];\ + \r\ + \n :do {\r\ + \n /ip firewall address-list add list=\$ListRKN address=\$newA\ + ddress comment=\$aItem;\r\ + \n :log warning \"Successfully adding ip to list A record: \ + \$dnsRecord, address: \$newAddress domain: \$aItem\";\r\ + \n } on-error={\r\ + \n :log error \"error when adding ip to list A record: \$dn\ + sRecord, address: \$newAddress domain: \$aItem\";\r\ + \n }\r\ + \n }\r\ + \n :if ([/ip dns cache all get \$dnsRecord type]=\"CNAME\") do={\r\ + \n :local cname;\r\ + \n :local nextCname;\r\ + \n :local cnameAddress;\r\ + \n :set cname [/ip dns cache all find where (name=\$aItem && ty\ + pe=\"CNAME\")];\r\ + \n :set nextCname [/ip dns cache all find where (name=[/ip dns \ + cache all get \$cname data] && type=\"CNAME\")];\r\ + \n :while (\$nextCname != \"\") do={\r\ + \n :set cname \$nextCname;\r\ + \n :set nextCname [/ip dns cache all find where (name=[/ip \ + dns cache all get \$cname data] && type=\"CNAME\")];\r\ + \n }\r\ + \n :foreach aRecord in=[/ip dns cache all find where (name=[/ip\ + \_dns cache all get \$cname data] && type=\"A\")] do={\r\ + \n :do {\r\ + \n :set cnameAddress [/ip dns cache all get \$aRecord d\ + ata];\r\ + \n /ip firewall address-list add list=\$ListRKN address\ + =\$cnameAddress comment=\$aItem;\r\ + \n :log warning \"Succesfully adding ip to list CNAME r\ + ecord: \$cname, A record: \$aRecord, address: \$newAddress, from domain: \ + \$aItem.\";\r\ + \n# /ip firewall raw add action=add-dst-to-address-list\ + \_address-list=\$ListRKN address-list-timeout=none-dynamic chain=preroutin\ + g comment=\$aItem content=\$aItem;\r\ + \n } on-error={\r\ + \n :log error \"error when adding ip to list CNAME reco\ + rd: \$cname, A record: \$aRecord, address: \$newAddress, from domain: \$aI\ + tem.\";\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:if ([ :len [ /system/scheduler/find where name=\$ScriptName ] ] = 0) do\ + ={\r\ + \n :log error (\$ScriptName . \"System Scheduler for script: \" . \$Scri\ + ptName . \" is NOT SET! Fix It Now!\");\r\ + \n /system/scheduler/add name=\$ScriptName on-event=\"/system/script { \ + run \$ScriptName; }\" comment=\"Update RKN DNS Looup\" interval=00:30:00 p\ + olicy=\"ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon\"\ + \_start-time=startup;\r\ + \n }\r\ + \n:set dnsLookupReady true;\r\ + \ndelay 2s; \r\ + \n:global dnsLookupReady (a);\r\ + \n" +add dont-require-permissions=no name="renameLists " owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\ + local oldName \"Test\";\ + \n:local newName \"Test-IP\";\ + \n/ip firewall address-list\ + \n:foreach i in=[find list=\$oldName] do={\ + \nset \$i list=\$newName;\ + \n}\ + \n" +add dont-require-permissions=no name=IPv6-Settings owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + #############Script Settings##################\ + \n\ + \n:local \"IF-TUN\" \"6to4-tun\"\ + \n:local \"IF-LOC\" \"Bridge-LAN\"\ + \n:local \"POOL-6TO4\" \"poolV6-local\"\ + \n:local \"force-update\" false\ + \n############ use then static ip #######################\ + \n:local \"lease-address\" [/ip address get [find interface=\$\"IF-LOC\"] \ + address]\ + \n#:local \"interface\" \"ext\"\ + \n:local bound 0\ + \n\ + \n###############################################\ + \n\ + \n:local 6to4prefix do={\ + \n :global dec2hex\ + \n :local ipv4 \$1\ + \n :local ipv6 \"2002:\"\ + \n :for i from=0 to=2 step=1 do={ \ + \n :if ( \$i = 2) do={\ + \n :set ipv6 (\$ipv6.\":\")\ + \n }\ + \n :local fp [:find \$ipv4 \".\"]\ + \n :if ( \$fp > 0 ) do={\ + \n :set ipv6 (\$ipv6.[\$dec2hex [:pick \$ipv4 0 \$fp]])\ + \n :set ipv4 [:pick \$ipv4 (\$fp + 1 ) [:len \$ipv4]]\ + \n }\ + \n }\ + \n :return (\$ipv6.[\$dec2hex \$ipv4])\ + \n}\ + \n:local \"str_replace\" do={\ + \n :local \"str-pos\" [:find \$1 \$2]\ + \n :local \"str-len\" [:len \$2]\ + \n :local str \"\"\ + \n :local rest \$1\ + \n :while (\$\"str-pos\" >= 0) do={\ + \n :local t [:pick \$rest 0 \$\"str-pos\"]\ + \n :if ( \$4 > 0 ) do={\ + \n :for i from=1 to=(\$4 - [:len \$t]) step=1 do={\ + \n :set str (\$str.\$5)\ + \n }\ + \n }\ + \n :set str (\$str.\$t.\$3)\ + \n :set rest [:pick \$rest (\$\"str-pos\"+\$\"str-len\") [:len \$re\ + st]]\ + \n :set \"str-pos\" [:find \$rest \$2]\ + \n }\ + \n :return (\$str.\$rest)\ + \n}\ + \n\ + \n:if (\$bound=1) do={\ + \n :local t [:find \$\"lease-address\" \"/\"]\ + \n\ + \n :if ( \$t >= 0 ) do={\ + \n :set \"lease-address\" [:pick \$\"lease-address\" 0 \$t]\ + \n }\ + \n\ + \n :if ( [/interface 6to4 print count-only where name=\$\"IF-TUN\"] = 0\ + ) do={\ + \n /interface 6to4 add !keepalive local-address=\$\"lease-address\"\ + \_mtu=1280 name=\$\"IF-TUN\" remote-address=10.30.30.0\ + \n :set \"force-update\" true\ + \n }\ + \n\ + \n :local \"IPv4-TUN\" [/interface 6to4 get [find name=\$\"IF-TUN\"] lo\ + cal-address];\ + \n\ + \n :if ( \$\"lease-address\" != \$\"IPv4-TUN\" || \$\"force-update\" = \ + true) do={\ + \n /interface 6to4 set [find name=\$\"IF-TUN\"] local-address=\$\"l\ + ease-address\"\ + \n /ipv6 address remove [find interface=\$\"IF-TUN\"]\ + \n /ipv6 address remove [find interface=\$\"IF-LOC\"]\ + \n /ipv6 route remove [find gateway ~ \$\"IF-TUN\" and dst-address=\ + \"::/0\"]\ + \n :local \"pool-prefix\" [\$6to4prefix \$\"lease-address\"]\ + \n :if ( [/ipv6 pool print count-only where name=\$\"POOL-6TO4\"] =\ + \_0) do={ \ + \n /ipv6 pool add name=\$\"POOL-6TO4\" prefix=(\$\"pool-prefix\ + \".\":1::/64\") prefix-length=64\ + \n } else={\ + \n /ipv6 pool set [/ipv6 pool find where name=\$\"POOL-6TO4\"] \ + prefix=(\$\"pool-prefix\".\":1::/64\")\ + \n }\ + \n /ipv6 address add address=(\$\"pool-prefix\".\"::\".[\$MAC2EUI64\ + \_[/interface get [find name=\$\"interface\"] mac-address]].\"/3\") interf\ + ace=\$\"IF-TUN\" advertise=no\ + \n /ipv6 address add eui-64=yes from-pool=\$\"POOL-6TO4\" interface\ + =\$\"IF-LOC\" advertise=yes\ + \n /ipv6 route add dst-address=::/0 gateway=(\"::10.30.30.81%\".\$\ + \"IF-TUN\")\ + \n :local t [/ipv6 address get [find interface=\$\"IF-LOC\"] addres\ + s]\ + \n :set t [:pick \$t 0 [:find \$t \"/\"]]\ + \n :if ( [/ipv6 dhcp-server option print count-only where name=DNS]\ + \_= 0) do={\ + \n /ipv6 dhcp-server option add code=23 name=DNS value=(\"0x\".\ + [\$\"str_replace\" \$t \":\" \"\" 4 \"0\"])\ + \n } else={\ + \n /ipv6 dhcp-server option set [/ipv6 dhcp-server option find \ + where name=DNS] value=(\"0x\".[\$\"str_replace\" \$t \":\" \"\" 4 \"0\"])\ + \n }\ + \n :if ( [/ipv6 dhcp-server print count-only where interface=\$\"IF\ + -LOC\"] = 0) do={\ + \n /ipv6 dhcp-server add address-pool=\$\"POOL-6TO4\" dhcp-opti\ + on=DNS interface=\$\"IF-LOC\" lease-time=1d name=\$\"IF-LOC\"\ + \n }\ + \n :if ( [ipv6 nd print count-only where interface=\$\"IF-LOC\"] = \ + 0) do={\ + \n /ipv6 nd add advertise-dns=no interface=\$\"IF-LOC\" other-c\ + onfiguration=yes\ + \n }\ + \n }\ + \n}" +add dont-require-permissions=no name=IPv6-GlobalFunc owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\ + global MAC2EUI64 do={\ + \n :local EUI64 \"\"\ + \n :local MAC \$1\ + \n :local HEX2DEC \"0123456789ABCDEF\"\ + \n :for i from=0 to=5 step=1 do={\ + \n :if ( \$i = 0 ) do={\ + \n :set EUI64 [:pick \$MAC 0]\ + \n :set EUI64 (\$EUI64.[:tostr [:pick \$\"HEX2DEC\" ([:find \$\ + \"HEX2DEC\" [:pick \$MAC 1]] ^ 2)]])\ + \n }\ + \n :if ( \$i = 1 || \$i = 3) do={\ + \n :set EUI64 (\$EUI64.\":\")\ + \n }\ + \n :if ( \$i = 2 ) do={\ + \n :set EUI64 (\$EUI64.\"FF:FE\")\ + \n }\ + \n :if ( [:find \$MAC \":\"] = 2 ) do={\ + \n :set MAC [:pick \$MAC 3 [:len \$MAC]]\ + \n :set EUI64 (\$EUI64.[:pick \$MAC 0 2])\ + \n }\ + \n }\ + \n :return (\$EUI64)\ + \n}\ + \n\ + \n:global dec2hex do={\ + \n :local hex \"\"\ + \n :local dec [:tonum \$1]\ + \n :for i from=0 to=4 step=4 do={\ + \n :set hex ([:pick \"0123456789ABCDEF\" ((\$dec>>\$i)&0xf) (((\$de\ + c>>\$i)&0xf)+1)].\$hex)\ + \n }\ + \n :return ([:tostr \$hex])\ + \n}" +/user group +add name=backup policy="ssh,read,write,policy,test,web,sniff,sensitive,api,res\ + t-api,!local,!telnet,!ftp,!reboot,!winbox,!password,!romon" +/ip smb +set enabled=no interfaces=lo +/interface bridge port +add bridge=Bridge-LAN interface=ether5 +add bridge=Bridge-LAN interface=ether2 +add bridge=Bridge-LAN interface=ether4 trusted=yes +add bridge=Bridge-LAN interface=ether3 +add bridge=Bridge-LAN interface=wlan2 +add bridge=Bridge-LAN interface=wlan1 +/interface bridge settings +set use-ip-firewall=yes +/ip firewall connection tracking +set udp-timeout=10s +/ip neighbor discovery-settings +set discover-interface-list=LAN lldp-med=no lldp-poe-power=no +/ip settings +set accept-redirects=yes ipv4-multipath-hash-policy=l4 max-neighbor-entries=\ + 8192 +/ipv6 settings +set disable-ipv6=yes disable-link-local-address=yes forward=no \ + max-neighbor-entries=8192 multipath-hash-policy=l4 +/interface detect-internet +set detect-interface-list=WAN internet-interface-list=Internet \ + lan-interface-list=LAN request-interval=1m wan-interface-list=WAN +/interface l2tp-server server +set authentication=mschap2 use-ipsec=yes +/interface list member +add interface=ether4 list=WAN +add interface="ether1 - Unet" list=Internet +add disabled=yes interface=WG list=WAN +add interface=Bridge-LAN list=LAN +/interface wireguard peers +add allowed-address=0.0.0.0/0 client-allowed-address=::/0 disabled=yes \ + endpoint-address=109.107.177.79 endpoint-port=29185 interface=WG name=\ + VDSina persistent-keepalive=10s public-key=\ + "zuRsO+dL/j6awaBWZ/NgnHR+bCNUjRWbaHFpdzkNnw4=" +/interface wireless cap +# +set bridge=Bridge-LAN caps-man-addresses=10.30.30.10 \ + caps-man-certificate-common-names=CAPsMAN-51B25E507D07 certificate=\ + CAP-488F5A8E1ABC discovery-interfaces=Bridge-LAN enabled=yes interfaces=\ + wlan2,wlan1 lock-to-caps-man=yes +/ip address +add address=10.30.30.81/24 interface=Bridge-LAN network=10.30.30.0 +add address=10.8.0.2/24 disabled=yes interface=WG network=10.8.0.0 +/ip cloud +set update-time=no +/ip dhcp-client +add add-default-route=special-classless default-route-distance=4 \ + default-route-tables=main:4 dhcp-options=clientid_duid,clientid \ + interface="ether1 - Unet" name=client1 use-broadcast=never use-peer-dns=\ + no use-peer-ntp=no +/ip dhcp-server config +set accounting=no +/ip dhcp-server lease +add address=10.30.30.101 client-id=1:48:8f:5a:8e:ee:a8 mac-address=\ + 48:8F:5A:8E:EE:A8 server=masterDHCP +add address=10.30.30.107 client-id=1:f0:de:f1:91:d:1e mac-address=\ + F0:DE:F1:91:0D:1E server=masterDHCP +add address=10.30.30.83 client-id=1:0:80:77:ec:43:86 mac-address=\ + 00:80:77:EC:43:86 server=masterDHCP +add address=10.30.30.70 dhcp-option=domain-search-list,HostName \ + dhcp-option-set=HP1505n mac-address=00:1E:0B:1A:88:8D server=masterDHCP +add address=10.30.30.50 client-id=1:4:7c:16:36:f5:37 mac-address=\ + 04:7C:16:36:F5:37 server=masterDHCP +add address=10.30.30.71 client-id=1:10:2d:41:2c:20:c2 mac-address=\ + 10:2D:41:2C:20:C2 server=masterDHCP +add address=10.30.30.61 client-id=1:10:8:c1:81:59:5f mac-address=\ + 10:08:C1:81:59:5F server=masterDHCP +add address=10.30.30.72 client-id=1:10:38:1f:d2:5f:13 mac-address=\ + 10:38:1F:D2:5F:13 server=masterDHCP +add address=10.30.30.99 client-id=1:10:b1:df:e9:d0:d5 mac-address=\ + 10:B1:DF:E9:D0:D5 server=masterDHCP +add address=10.30.30.111 client-id=1:fe:2:52:be:3f:16 mac-address=\ + FE:02:52:BE:3F:16 server=masterDHCP +add address=10.30.30.92 client-id=1:d4:6d:6d:fd:e5:49 mac-address=\ + D4:6D:6D:FD:E5:49 server=masterDHCP +add address=10.30.30.93 client-id=1:7e:8b:1e:cb:b8:cb mac-address=\ + CC:42:10:12:46:94 server=masterDHCP +add address=10.30.30.94 client-id=1:50:cc:f8:85:96:a mac-address=\ + 50:CC:F8:85:96:0A server=masterDHCP +add address=10.30.30.82 client-id=1:e4:18:6b:58:34:ab mac-address=\ + E4:18:6B:58:34:AB server=masterDHCP +add address=10.30.30.75 client-id=1:0:27:15:41:97:a9 mac-address=\ + 00:27:15:41:97:A9 server=masterDHCP +add address=10.30.30.77 dhcp-option=HostName dhcp-option-set=StartLife1 \ + mac-address=C4:82:E1:03:49:10 server=masterDHCP +add address=10.30.30.73 client-id=1:10:38:1f:82:81:f2 mac-address=\ + 10:38:1F:82:81:F2 server=masterDHCP +add address=10.30.30.95 client-id=1:88:ae:dd:60:39:54 dhcp-option=HostName \ + dhcp-option-set=VADOS-NUC mac-address=88:AE:DD:60:39:54 server=masterDHCP +add address=10.30.30.98 client-id=1:c8:5b:76:d8:cc:bf dhcp-option=HostName \ + dhcp-option-set=VADOS-LEN mac-address=C8:5B:76:D8:CC:BF server=masterDHCP +add address=10.30.30.96 client-id=iPhone mac-address=4A:91:ED:0E:1F:39 \ + server=masterDHCP +add address=10.30.30.74 client-id=MiTV dhcp-option=HostName dhcp-option-set=\ + MiTV mac-address=20:72:A9:D3:34:36 server=masterDHCP +add address=10.30.30.30 dhcp-option=HostName dhcp-option-set=miller \ + mac-address=F0:75:5D:1E:5F:71 server=masterDHCP +add address=10.30.30.100 client-id=iVados dhcp-option=HostName \ + dhcp-option-set=iVados mac-address=DE:4D:72:F3:1A:9F server=masterDHCP +/ip dhcp-server network +add address=10.30.30.0/24 caps-manager=10.30.30.10 dhcp-option=\ + domain-search-list dhcp-option-set=domain-search-set dns-server=\ + 10.30.30.81,10.30.30.33 domain=WORKGROUP gateway=10.30.30.81 netmask=24 \ + ntp-server=10.30.30.81,10.30.30.10 wins-server=10.30.30.95,10.30.30.98 +/ip dns +set allow-remote-requests=yes cache-max-ttl=1d doh-max-concurrent-queries=100 \ + doh-max-server-connections=20 servers=109.107.177.79,10.30.30.33 \ + verify-doh-cert=yes +/ip dns static +add address=109.107.177.79 comment="VDSina Server DNS" name=ns1.vados.ru \ + type=A +add address=109.107.177.79 name=vds.vados.ru type=A +add comment="Local DNS server for vados.ru" disabled=yes ns=10.30.30.81 \ + regexp=*.vados.ru type=NS +add comment="*.vados.ru Regexp" disabled=yes forward-to=10.30.30.81 regexp=\ + "\".*\\\\.vados\\\\.ru\"" type=FWD +add comment="*.30.30.10.in-addr.arpa Regexp" disabled=yes forward-to=\ + 10.30.30.81 regexp="\".*\\\\.30\\\\.30\\\\.10\\\\.in-addr\\\\.arpa" type=\ + FWD +add address=10.30.30.81 comment=NS.vados.ru.local disabled=yes name=\ + ns.vados.ru.local type=A +add comment="DNS ns.vados.ru.local" disabled=yes match-subdomain=yes name=\ + vados.ru ns=ns.vados.ru.local type=NS +add address=10.30.30.33 comment=reg.vados.ru name=reg.vados.ru type=A +add address=8.8.8.8 comment="Google-dns IPv4 1" match-subdomain=yes name=\ + dns.google type=A +add address=8.8.4.4 comment="Google-dns IPv4 2" name=dns.google type=A +add address=10.30.30.33 comment=AMSTER name=amster.vados.ru type=A +add address=10.30.30.33 name=ros.vados.ru type=A +add address=10.30.30.95 name=vados-nuc type=A +add address=10.30.30.33 name=amster-nuc type=A +add address=10.30.30.33 name=amster type=A +add address=10.30.30.33 name=amster-reg type=A +add address=10.30.30.33 disabled=yes name=mng.vados.ru type=A +add address=10.30.30.33 name=mgr.vados.ru type=A +add address=10.30.30.33 name=prt.vados.ru type=A +add address=10.30.30.33 disabled=yes match-subdomain=yes name=\ + mikrots.vados.ru type=A +add address=10.30.30.30 comment=MILLER name=miller.vados.ru type=A +add address=10.30.30.30 name=MILLER-LEN type=A +add address=10.30.30.30 name=miller type=A +add address=1.0.0.1 comment="CloudFlare-doh IPv4 2" disabled=yes name=\ + security.cloudflare-dns.com type=A +add address=1.1.1.1 comment="CloudFlare-doh IPv4 1" disabled=yes name=\ + security.cloudflare-dns.com type=A +add address=9.9.9.9 comment="Cuad9-dns IPv4 1" disabled=yes name=\ + dns.quad9.net type=A +add address=149.112.112.112 comment="Cuad9-dns IPv4 2" disabled=yes name=\ + dns.quad9.net type=A +add address=195.133.25.16 comment="DNS Comss" disabled=yes name=dns.comss.one \ + type=A +add address=10.30.30.81 comment=LocalNet name=MikroVados type=A +add address=10.30.30.10 name=MikroNUC type=A +add address=10.30.30.86 disabled=yes name=MikroYellow type=A +add address=10.30.30.85 disabled=yes name=MikroMic type=A +add address=10.30.30.95 name=VADOS-NUC type=A +add address=10.30.30.98 name=VADOS-LEN type=A +add address=91.192.189.2 comment=Unet-DNS disabled=yes name=ns1.unet.ws type=\ + A +add address=10.30.30.20 disabled=yes name=garage type=A +/ip firewall address-list +add address=91.215.120.206 list=TRUSTED +add address=185.71.70.78 disabled=yes list=TRUSTED +add address=176.53.203.171 disabled=yes list=TRUSTED +add address=176.53.203.170 disabled=yes list=TRUSTED +add address=176.53.202.194 disabled=yes list=TRUSTED +add address=8.8.4.0/24 list=YouTube +add address=8.8.8.0/24 list=YouTube +add address=8.34.208.0/20 list=YouTube +add address=8.35.192.0/20 list=YouTube +add address=23.236.48.0/20 list=YouTube +add address=23.251.128.0/19 list=YouTube +add address=34.0.0.0/15 list=YouTube +add address=34.2.0.0/16 list=YouTube +add address=34.3.0.0/23 list=YouTube +add address=34.3.3.0/24 list=YouTube +add address=34.3.4.0/24 list=YouTube +add address=34.3.8.0/21 list=YouTube +add address=34.3.16.0/20 list=YouTube +add address=34.3.32.0/19 list=YouTube +add address=34.3.64.0/18 list=YouTube +add address=34.4.0.0/14 list=YouTube +add address=34.8.0.0/13 list=YouTube +add address=34.16.0.0/12 list=YouTube +add address=34.32.0.0/11 list=YouTube +add address=34.64.0.0/10 list=YouTube +add address=34.128.0.0/10 list=YouTube +add address=35.184.0.0/13 list=YouTube +add address=35.192.0.0/14 list=YouTube +add address=35.196.0.0/15 list=YouTube +add address=35.198.0.0/16 list=YouTube +add address=35.199.0.0/17 list=YouTube +add address=35.199.128.0/18 list=YouTube +add address=35.200.0.0/13 list=YouTube +add address=35.208.0.0/12 list=YouTube +add address=35.224.0.0/12 list=YouTube +add address=35.240.0.0/13 list=YouTube +add address=57.140.192.0/18 list=YouTube +add address=64.15.112.0/20 list=YouTube +add address=64.233.160.0/19 list=YouTube +add address=66.22.228.0/23 list=YouTube +add address=66.102.0.0/20 list=YouTube +add address=66.249.64.0/19 list=YouTube +add address=70.32.128.0/19 list=YouTube +add address=72.14.192.0/18 list=YouTube +add address=74.125.0.0/16 list=YouTube +add address=104.154.0.0/15 list=YouTube +add address=104.196.0.0/14 list=YouTube +add address=104.237.160.0/19 list=YouTube +add address=107.167.160.0/19 list=YouTube +add address=107.178.192.0/18 list=YouTube +add address=108.59.80.0/20 list=YouTube +add address=108.170.192.0/18 list=YouTube +add address=108.177.0.0/17 list=YouTube +add address=130.211.0.0/16 list=YouTube +add address=136.22.160.0/20 list=YouTube +add address=136.22.176.0/21 list=YouTube +add address=136.22.184.0/23 list=YouTube +add address=136.22.186.0/24 list=YouTube +add address=142.250.0.0/15 list=YouTube +add address=146.148.0.0/17 list=YouTube +add address=152.65.208.0/22 list=YouTube +add address=152.65.214.0/23 list=YouTube +add address=152.65.218.0/23 list=YouTube +add address=152.65.222.0/23 list=YouTube +add address=152.65.224.0/19 list=YouTube +add address=162.120.128.0/17 list=YouTube +add address=162.216.148.0/22 list=YouTube +add address=162.222.176.0/21 list=YouTube +add address=172.110.32.0/21 list=YouTube +add address=172.217.0.0/16 list=YouTube +add address=172.253.0.0/16 list=YouTube +add address=173.194.0.0/16 list=YouTube +add address=173.255.112.0/20 list=YouTube +add address=192.158.28.0/22 list=YouTube +add address=192.178.0.0/15 list=YouTube +add address=193.186.4.0/24 list=YouTube +add address=199.36.154.0/23 list=YouTube +add address=199.36.156.0/24 list=YouTube +add address=199.192.112.0/22 list=YouTube +add address=199.223.232.0/21 list=YouTube +add address=207.223.160.0/20 list=YouTube +add address=208.65.152.0/22 list=YouTube +add address=208.68.108.0/22 list=YouTube +add address=208.81.188.0/22 list=YouTube +add address=208.117.224.0/19 list=YouTube +add address=209.85.128.0/17 list=YouTube +add address=216.58.192.0/19 list=YouTube +add address=216.73.80.0/20 list=YouTube +add address=216.239.32.0/19 list=YouTube +add address=2ip.ru list=Test-IP +add address=wg.vados.ru comment=VDSIna list=TRUSTED +add address=10.8.0.0/24 comment="WG VDSIna" disabled=yes list=TRUSTED +add address=ikus.pesc.ru comment=ikus.pesc.ru list=NoVdsina +add address=love.ru comment=love.ru list=NoVdsina +add address=gosuslugi.ru comment=gosuslugi.ru list=NoVdsina +add address=pochta.ru comment=pochta.ru list=NoVdsina +add address=vdsina.com list=NoVdsina +add address=vdsina.ru list=NoVdsina +add address=youla.ru list=NoVdsina +add address=kupiprodai.ru list=NoVdsina +add address=myip.ru list=Test-IP +add address=geoip.co list=NoVdsina +add address=comp-lenta.ru list=NoVdsina +add address=128.85.102.70 comment=update.microsoft.com list=\ + windowsupdateslist +add address=wu-b-net.trafficmanager.net. comment=\ + ctldl.windowsupdate.com.delivery.microsoft.com list=windowsupdateslist +add address=ctldl.windowsupdate.com.delivery.microsoft.com. comment=\ + ctldl.windowsupdate.com list=windowsupdateslist +add address=redir.update.msft.com.trafficmanager.net. comment=\ + update.microsoft.com list=windowsupdateslist +add address=geo.prod.do.dsp.trafficmanager.net. comment=\ + geo.prod.do.dsp.mp.microsoft.com list=windowsupdateslist +add address=13.74.139.29 comment=array605.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=kv801.prod.do.dsp.mp.microsoft.com.edgekey.net. comment=\ + kv801.prod.do.dsp.mp.microsoft.com list=windowsupdateslist +add address=e12437.d.akamaiedge.net. comment=\ + kv801.prod.do.dsp.mp.microsoft.com.edgekey.net list=windowsupdateslist +add address=displaycatalog-rp.md.mp.microsoft.com.akadns.net. comment=\ + displaycatalog.mp.microsoft.com list=windowsupdateslist +add address=rp-consumer-prod-displaycatalog-geomap.trafficmanager.net. \ + comment=displaycatalog-rp.md.mp.microsoft.com.akadns.net list=\ + windowsupdateslist +add address=sls.update.microsoft.com. comment=slscr.update.microsoft.com \ + list=windowsupdateslist +add address=glb.sls.prod.dcat.dsp.trafficmanager.net. comment=\ + sls.update.microsoft.com list=windowsupdateslist +add address=fe3.delivery.mp.microsoft.com. comment=\ + fe3cr.delivery.mp.microsoft.com list=windowsupdateslist +add address=glb.cws.prod.dcat.dsp.trafficmanager.net. comment=\ + fe3.delivery.mp.microsoft.com list=windowsupdateslist +add address=fe2cr.update.msft.com.trafficmanager.net. comment=\ + fe2cr.update.microsoft.com list=windowsupdateslist +add address=download.windowsupdate.com.delivery.microsoft.com. comment=\ + download.windowsupdate.com list=windowsupdateslist +add address=wu-f-net.trafficmanager.net. comment=\ + download.windowsupdate.com.delivery.microsoft.com list=windowsupdateslist +add address=tsfe.trafficmanager.net. comment=\ + tsfe.trafficshaping.dsp.mp.microsoft.com list=windowsupdateslist +add address=edge.ds-c7110-microsoft.global.dns.qwilted-cds.cqloud.com. \ + comment=9.au.download.windowsupdate.com list=windowsupdateslist +add address=52.158.227.125 comment=array812.prod.do.dsp.mp.microsoft.com \ + list=windowsupdateslist +add address=au.download.windowsupdate.com.delivery.microsoft.com. comment=\ + au.download.windowsupdate.com list=windowsupdateslist +add address=a767.dspw65.akamai.net. comment=\ + download.windowsupdate.com.edgesuite.net list=windowsupdateslist +add address=52.151.58.88 comment=array810.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=consumer-licensing-aks2aks.md.mp.microsoft.com.akadns.net. \ + comment=licensing.mp.microsoft.com list=windowsupdateslist +add address=licensing-prod-frontdoor-geomap.trafficmanager.net. comment=\ + consumer-licensing-aks2aks.md.mp.microsoft.com.akadns.net list=\ + windowsupdateslist +add address=star.f.dl.delivery.mp.microsoft.com.delivery.microsoft.com. \ + comment=msedge.f.dl.delivery.mp.microsoft.com list=windowsupdateslist +add address=cdp-f-nlu-net.trafficmanager.net. comment=\ + star.f.dl.delivery.mp.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=star.b.tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com. \ + comment=msedge.b.tlu.dl.delivery.mp.microsoft.com list=windowsupdateslist +add address=cdp-f-tlu-net.trafficmanager.net. comment=\ + star.b.tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=storeedgefd.xbetservices.akadns.net. comment=\ + storeedgefd.dsx.mp.microsoft.com list=windowsupdateslist +add address=\ + storeedgefd.dsx.mp.microsoft.com.edgekey.net.globalredir.akadns.net. \ + comment=storeedgefd.dsx.mp.microsoft.com.edgekey.net list=\ + windowsupdateslist +add address=e16646.dscg.akamaiedge.net. comment=\ + storeedgefd.dsx.mp.microsoft.com.edgekey.net.globalredir.akadns.net list=\ + windowsupdateslist +add address=40.69.76.189 comment=array621.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=bg.microsoft.map.fastly.net. comment=\ + 3.au.download.windowsupdate.com list=windowsupdateslist +add list=windowsupdateslist +add address=40.69.75.33 comment=array613.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=star.sf.tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com. \ + comment=msedgeextensions.sf.tlu.dl.delivery.mp.microsoft.com list=\ + windowsupdateslist +add address=cdp-f-ssl-tlu-net.trafficmanager.net. comment=\ + star.sf.tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=a2033.dscd.akamai.net. comment=\ + star.sb.tlu.dl.delivery.mp.microsoft.com.edgesuite.net list=\ + windowsupdateslist +add address=13.74.138.242 comment=array602.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=dl.delivery.mp.microsoft.com.delivery.microsoft.com. comment=\ + dl.delivery.mp.microsoft.com list=windowsupdateslist +add address=dcat-f-nlu-net.trafficmanager.net. comment=\ + dl.delivery.mp.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=1d.tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com. \ + comment=1D.tlu.dl.delivery.mp.microsoft.com list=windowsupdateslist +add address=dcat-b-tlu-net.trafficmanager.net. comment=\ + 1d.tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=a122.dscg3.akamai.net. comment=\ + tlu.dl.delivery.mp.microsoft.com.edgesuite.net list=windowsupdateslist +add address=geover.prod.do.dsp.mp.microsoft.com.edgekey.net. comment=\ + geover.prod.do.dsp.mp.microsoft.com list=windowsupdateslist +add address=e10370.d.akamaiedge.net. comment=\ + geover.prod.do.dsp.mp.microsoft.com.edgekey.net list=windowsupdateslist +add address=52.232.182.21 comment=array511.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=40.70.77.95 comment=array519.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=a1683.dscd.akamai.net. comment=\ + dl.delivery.mp.microsoft.com-c.edgesuite.net list=windowsupdateslist +add address=consumer-collections-aks2eap.md.mp.microsoft.com.akadns.net. \ + comment=collections.mp.microsoft.com list=windowsupdateslist +add address=a1847.dscd.akamai.net. comment=\ + wildcard.f.tlu.dl.delivery.mp.microsoft.com.edgesuite.net list=\ + windowsupdateslist +add address=13.74.142.6 comment=array607.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=au.download.windowsupdate.com.edgesuite.net. comment=\ + 2.au.download.windowsupdate.com list=windowsupdateslist +add address=a767.dscg3.akamai.net. comment=\ + au.download.windowsupdate.com.edgesuite.net list=windowsupdateslist +add address=52.156.123.84 comment=array818.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=40.69.78.254 comment=array610.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=4.au.download.windowsupdate.com.delivery.microsoft.com. comment=\ + 4.au.download.windowsupdate.com list=windowsupdateslist +add address=cl-glcb907925.gcdn.co. comment=\ + 4.au.download.windowsupdate.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=52.167.167.111 comment=array510.prod.do.dsp.mp.microsoft.com \ + list=windowsupdateslist +add address=40.69.70.43 comment=array623.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=tsfe.trafficshaping.dsp.mp.microsoft.com.delivery.microsoft.com. \ + comment=tsfe.trafficshaping.dsp.mp.microsoft.com list=windowsupdateslist +add address=ts-frontend.trafficmanager.net. comment=\ + tsfe.trafficshaping.dsp.mp.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=40.69.75.248 comment=array616.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=40.69.74.84 comment=array615.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=sds.trafficmanager.net. comment=software-download.microsoft.com \ + list=windowsupdateslist +add address=40.69.74.80 comment=array611.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.175.242.182 comment=array804.prod.do.dsp.mp.microsoft.com \ + list=windowsupdateslist +add address=dlc-shim.trafficmanager.net. comment=download.microsoft.com list=\ + windowsupdateslist +add address=e12671.dscd.akamaiedge.net. comment=\ + download.microsoft.com.edgekey.net list=windowsupdateslist +add address=20.187.55.34 comment=array808.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.167.167.231 comment=array513.prod.do.dsp.mp.microsoft.com \ + list=windowsupdateslist +add address=52.167.222.13 comment=array518.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=kv601.prod.do.dsp.mp.microsoft.com.edgekey.net. comment=\ + kv601.prod.do.dsp.mp.microsoft.com list=windowsupdateslist +add address=e12358.d.akamaiedge.net. comment=\ + kv601.prod.do.dsp.mp.microsoft.com.edgekey.net list=windowsupdateslist +add address=tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com. comment=\ + tlu.dl.delivery.mp.microsoft.com list=windowsupdateslist +add address=dcat-f-tlu-net.trafficmanager.net. comment=\ + tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=a1856.dspw65.akamai.net. comment=\ + tlu.dl.delivery.mp.microsoft.com-c.edgesuite.net list=windowsupdateslist +add address=52.167.164.252 comment=array506.prod.do.dsp.mp.microsoft.com \ + list=windowsupdateslist +add address=13.74.137.54 comment=array601.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=kv501.prod.do.dsp.mp.microsoft.com.edgekey.net. comment=\ + kv501.prod.do.dsp.mp.microsoft.com list=windowsupdateslist +add address=collections-prod-frontdoor-geomap.trafficmanager.net. comment=\ + consumer-collections-aks2eap.md.mp.microsoft.com.akadns.net list=\ + windowsupdateslist +add address=52.167.160.173 comment=array509.prod.do.dsp.mp.microsoft.com \ + list=windowsupdateslist +add address=40.70.77.148 comment=array516.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.167.200.126 comment=array507.prod.do.dsp.mp.microsoft.com \ + list=windowsupdateslist +add address=52.167.167.59 comment=array508.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=40.69.76.172 comment=array620.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=smtp.mail.me.com comment=smtp.mail.me.com list=NoVdsina +add address=40.70.76.5 comment=array514.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=13.74.186.119 comment=array618.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.177.176.186 comment=array520.prod.do.dsp.mp.microsoft.com \ + list=windowsupdateslist +add address=40.70.76.27 comment=array515.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.167.160.43 comment=array503.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.110 comment=array817.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=13.74.142.8 comment=array614.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=13.74.138.254 comment=array603.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.167.167.96 comment=array501.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=40.69.76.215 comment=array622.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=13.74.143.200 comment=array606.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.111 comment=array818.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.112 comment=array819.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=13.74.137.53 comment=array608.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.98 comment=array805.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.96 comment=array803.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.104 comment=array811.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.101 comment=array808.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.99 comment=array806.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.17 comment=array802.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.97 comment=array804.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.103 comment=array810.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.16 comment=array801.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=40.70.77.177 comment=array517.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.167.164.84 comment=array512.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.105 comment=array812.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.107 comment=array814.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.232.161.82 comment=array502.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.108 comment=array815.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=52.167.161.91 comment=array505.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.102 comment=array809.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.109 comment=array816.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.106 comment=array813.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.117 comment=array622.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.107 comment=array612.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.108 comment=array613.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.105 comment=array610.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.113 comment=array618.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.116 comment=array621.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.98 comment=array603.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.114 comment=array619.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.111 comment=array616.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.97 comment=array602.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.106 comment=array611.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.110 comment=array615.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.119 comment=array624.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.135 comment=array514.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.97 comment=array503.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.99 comment=array604.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.61 comment=array504.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.131 comment=array510.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.132 comment=array511.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.96 comment=array601.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.104 comment=array609.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.138 comment=array517.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.60 comment=array501.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.139 comment=array518.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.118 comment=array623.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.113 comment=array820.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.103 comment=array608.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.154.7.100 comment=array807.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.129 comment=array508.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.134 comment=array513.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.62 comment=array505.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.137 comment=array516.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.101 comment=array606.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.96 comment=array502.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.130 comment=array509.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.112 comment=array617.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.100 comment=array605.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.115 comment=array620.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.136 comment=array515.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=purchase.md.mp.microsoft.com.akadns.net. comment=\ + purchase.mp.microsoft.com list=windowsupdateslist +add address=e110219.a.akamaiedge.net. comment=\ + purchase.mp.microsoft.com.edgekey.net list=windowsupdateslist +add address=72.153.5.140 comment=array519.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.141 list=windowsupdateslist +add address=72.153.5.63 comment=array506.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.128 comment=array507.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.102 comment=array607.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=storesdk.xbetservices.akadns.net. comment=\ + storesdk.dsx.mp.microsoft.com list=windowsupdateslist +add address=e16646.g.akamaiedge.net. comment=\ + storesdk.dsx.mp.microsoft.com.edgekey.net list=windowsupdateslist +add address=software-download.microsoft.com.delivery.microsoft.com. comment=\ + software-download.microsoft.com list=windowsupdateslist +add address=download.prss.microsoft.com.akamaized.net. comment=\ + software-download.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=72.145.35.109 comment=array614.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=72.153.5.133 comment=array512.prod.do.dsp.mp.microsoft.com list=\ + windowsupdateslist +add address=213.180.192.0/19 comment=YandexDisk list=NoVdsina +add address=87.250.250.50 comment=YandexDisk list=NoVdsina +add address=consumer-prod-bcfd-v7-afd.trafficmanager.net. comment=\ + displaycatalog.mp.microsoft.com list=windowsupdateslist +add address=purchase-europeeap.md.mp.microsoft.com.akadns.net. comment=\ + purchase.md.mp.microsoft.com.akadns.net list=windowsupdateslist +add address=ne-az-conspurchase-mp.trafficmanager.net. comment=\ + purchase-europeeap.md.mp.microsoft.com.akadns.net list=windowsupdateslist +add address=storeedgefd.dsx.mp.microsoft.com.edgekey.net. comment=\ + cdn.storeedgefd.dsx.mp.microsoft.com list=windowsupdateslist +add address=star.f.tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com. \ + comment=msedge.f.tlu.dl.delivery.mp.microsoft.com list=windowsupdateslist +add address=a1996.dscd.akamai.net. comment=\ + wildcard.f.dl.delivery.mp.microsoft.com.edgesuite.net list=\ + windowsupdateslist +add address=cp801.prod.do.dsp.mp.microsoft.com.edgekey.net. comment=\ + cp801.prod.do.dsp.mp.microsoft.com list=windowsupdateslist +add address=glb.tas02.sls-prod.dcat.dsp.mp.microsoft.com. comment=\ + tas02.sls.update.microsoft.com list=windowsupdateslist +add address=cp601.prod.do.dsp.mp.microsoft.com.edgekey.net. comment=\ + cp601.prod.do.dsp.mp.microsoft.com list=windowsupdateslist +add address=star.sb.tlu.dl.delivery.mp.microsoft.com.delivery.microsoft.com. \ + comment=msedgeextensions.sb.tlu.dl.delivery.mp.microsoft.com list=\ + windowsupdateslist +add address=star.sf.dl.delivery.mp.microsoft.com.delivery.microsoft.com. \ + comment=msedge.sf.dl.delivery.mp.microsoft.com list=windowsupdateslist +add address=cdp-f-ssl-nlu-net.trafficmanager.net. comment=\ + star.sf.dl.delivery.mp.microsoft.com.delivery.microsoft.com list=\ + windowsupdateslist +add address=tm-pifd-prod-gbl-weight.trafficmanager.net. comment=\ + paymentinstruments.mp.microsoft.com list=windowsupdateslist +add address=10.30.30.33 comment=amster list=NoWG +add address=10.30.30.10 comment=MikroNUC disabled=yes list=NoWG +add address=89.110.101.17 comment=!!!VDSINA!!!! disabled=yes list=NoWG +add address=wg.vados.ru comment=VDSina-vpn list=VDSina +add address=80.85.244.9 list=TRUSTED +add address=disc801.prod.do.dsp.mp.microsoft.com.edgekey.net. comment=\ + disc801.prod.do.dsp.mp.microsoft.com list=windowsupdateslist +add address=185.71.70.78 disabled=yes list=TRUSTED +add address=176.53.203.171 disabled=yes list=TRUSTED +add address=176.53.203.170 disabled=yes list=TRUSTED +add address=176.53.202.194 disabled=yes list=TRUSTED +add address=89.110.101.17 comment="!!!VDSina Server!!!" list=NoVdsina +add address=dns.quad9.net comment=Quad9-dns disabled=yes list=Trusted-DNS +add address=dns.google comment=Google-dns list=Trusted-DNS +add address=security.cloudflare-dns.com comment=CloudFlare-dns list=\ + Trusted-DNS +add address=time.google.com comment=Google-time list=Trusted-NTP +add address=0.ru.pool.ntp.org comment=ru.pool.ntp.org disabled=yes list=\ + Trusted-NTP +add address=1.ru.pool.ntp.org disabled=yes list=Trusted-NTP +add address=2.ru.pool.ntp.org comment=2.ru.pool.ntp.org disabled=yes list=\ + Trusted-NTP +add address=3.ru.pool.ntp.org comment=3.ru.pool.ntp.org disabled=yes list=\ + Trusted-NTP +add address=cp.vdsina.com comment=VDSina-vpn list=VDSina +add address=vdsina.ru comment=VDSina-vpn list=VDSina +add address=ns.vados.ru comment=VDSina-vpn list=VDSina +add address=vdsina.com comment=VDSina-vpn list=VDSina +add address=cp.vdsina.ru comment=VDSina-vpn list=VDSina +add address=ns1.vdsina.ru comment=VDSina-vpn list=VDSina +add address=10.30.30.0/24 comment="Local Network" list=Local-Net +add address=10.8.0.0/24 comment="WireGuard Network" list=Local-Net +add address=146.120.64.208/29 comment="External Net" list=Unet +add address=esia.gosuslugi.ru comment=esia.gosuslugi.ru list=NoVdsina +add address=lk.gosuslugi.ru comment=lk.gosuslugi.ru list=NoVdsina +add address=trudvsem.ru comment=trudvsem.ru list=NoVdsina +add address=10.30.30.30 comment=miller disabled=yes list=NoWG +add address=10.30.30.33 comment="Direct connet to DNS servers" list=\ + Direct-DNS +add address=109.107.177.79 list=VPN-Server +add address=10.30.30.10 list=AWG-Proxy +/ip firewall filter +add action=accept chain=input disabled=yes dst-port=18880 in-interface=\ + Bridge-LAN protocol=tcp src-address=10.30.30.0/24 +add action=add-src-to-address-list address-list="Honeypot Hacker" \ + address-list-timeout=12h chain=input comment="block honeypot DNS" \ + connection-state=new dst-port=53 in-interface="ether1 - Unet" protocol=\ + udp src-address-list=!Trusted-DNS +add action=add-src-to-address-list address-list="Honeypot Hacker" \ + address-list-timeout=4w2d chain=input connection-state=new dst-port=53 \ + in-interface="ether1 - Unet" protocol=tcp src-address-list=!Trusted-DNS +add action=add-src-to-address-list address-list="Honeypot Hacker" \ + address-list-timeout=4w2d chain=input comment=\ + "block honeypot ssh rdp winbox" connection-state=new dst-port=22,8291 \ + in-interface="ether1 - Unet" protocol=tcp src-address-list=!TRUSTED +add action=add-src-to-address-list address-list="Honeypot Hacker" \ + address-list-timeout=4w2d chain=input comment="block honeypot asterisk" \ + connection-state=new dst-port=5060 in-interface="ether1 - Unet" protocol=\ + udp +add action=add-src-to-address-list address-list="Honeypot Hacker" \ + address-list-timeout=4w2d chain=input comment="block honeypot NTP" \ + connection-state=new dst-port=123 in-interface="ether1 - Unet" protocol=\ + udp +add action=fasttrack-connection chain=forward comment=\ + "FastTrack for non-VPN connections" connection-mark=!VPN_No_FastTrack \ + connection-state=established,related,new disabled=yes +add action=accept chain=input disabled=yes dst-port=29185 in-interface=\ + "ether1 - Unet" protocol=tcp +add action=accept chain=input disabled=yes dst-port=29185 in-interface=\ + "ether1 - Unet" protocol=udp +add action=accept chain=input comment="tcp Accept input DNS queries from LAN" \ + dst-port=53 protocol=tcp src-address=10.30.30.0/24 +add action=accept chain=input comment="udp Accept input DNS queries" \ + dst-port=53 protocol=udp src-address=10.30.30.0/24 +add action=accept chain=output comment="accept WireGuard" disabled=yes \ + dst-address-list=VPN-Server out-interface="ether1 - Unet" protocol=udp \ + src-port=13231 +add action=accept chain=output disabled=yes dst-address-list=VPN-Server \ + dst-port=29185 out-interface="ether1 - Unet" protocol=udp +add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \ + connection-state=established,related,new disabled=yes routing-mark=\ + !To-VPN +add action=accept chain=output disabled=yes protocol=tcp src-port=443 +add action=accept chain=output disabled=yes src-address=127.0.0.1 +add action=accept chain=forward comment="defconf: accept in ipsec policy" \ + disabled=yes ipsec-policy=in,ipsec +add action=accept chain=forward comment="defconf: accept out ipsec policy" \ + disabled=yes ipsec-policy=out,ipsec +add action=accept chain=forward comment=\ + "defconf: accept established,related, untracked" connection-state=\ + established,related,untracked +add action=drop chain=forward comment="defconf: drop invalid" \ + connection-state=invalid in-interface="ether1 - Unet" src-address=\ + !10.30.30.0/24 +add action=drop chain=forward comment=\ + "defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat \ + connection-state=new in-interface="ether1 - Unet" src-address-list=\ + !VDSina +add action=drop chain=forward comment=\ + "Filter Rule to block FB address LIST : )" disabled=yes dst-address-list=\ + facebook +add action=drop chain=forward comment=\ + "Filter Rule to block YOUTUBE address LIST : )" disabled=yes \ + dst-address-list=YouTube +add action=accept chain=input in-interface="ether1 - Unet" protocol=icmp \ + src-address-list=VDSina +add action=accept chain=input connection-state=established disabled=yes +add action=accept chain=input connection-state=related disabled=yes +/ip firewall mangle +add action=mark-routing chain=prerouting comment="Mark Routing main for NoWG" \ + disabled=yes new-routing-mark=main src-address-list=NoWG +add action=mark-routing chain=prerouting comment=\ + "Mark Routing main for NoVdsina" disabled=yes dst-address-list=NoVdsina \ + new-routing-mark=main src-address=10.30.30.0/24 +add action=mark-routing chain=prerouting comment=\ + "Mark Routing main for VDSina" disabled=yes dst-address-list=VPN-Server \ + new-routing-mark=main src-address=10.30.30.0/24 +add action=mark-routing chain=prerouting comment=\ + "Mark Routing main for MikroNuc" disabled=yes dst-address-list=AWG-Proxy \ + new-routing-mark=main src-address=10.30.30.0/24 +add action=mark-routing chain=prerouting comment=\ + "!!!ERROR Mark Routing main for Unet!!!" disabled=yes dst-address-list=\ + Unet new-routing-mark=main passthrough=no +add action=mark-routing chain=prerouting comment="Test-IP sites" disabled=yes \ + dst-address=!10.30.30.0/24 dst-address-list=Test-IP new-routing-mark=\ + To-VPN src-address=10.30.30.0/24 +add action=mark-routing chain=prerouting comment=FuckRKN disabled=yes \ + dst-address=!10.30.30.0/24 dst-address-list=RKN-dnsLookup \ + new-routing-mark=To-VPN passthrough=no src-address=10.30.30.0/24 +add action=mark-routing chain=prerouting comment="AWG for All sites" \ + disabled=yes dst-address=!10.30.30.0/24 dst-address-list=!NoVdsina \ + new-routing-mark=*401 routing-mark=!main src-address=10.30.30.0/24 \ + src-address-list=!NoWG +add action=mark-routing chain=prerouting comment="VDSIna for All sites" \ + disabled=yes dst-address=!10.30.30.0/24 dst-address-list=!NoVdsina \ + new-routing-mark=To-VPN routing-mark=!main src-address=10.30.30.0/24 \ + src-address-list=!NoWG +add action=mark-connection chain=prerouting comment=\ + "Mark VPN connections to exclude from FastTrack" disabled=yes \ + new-connection-mark=VPN_No_FastTrack routing-mark=*401 +add action=change-mss chain=forward comment="MSS on WG" disabled=yes new-mss=\ + 1380 out-interface=*16 protocol=tcp routing-mark=*401 tcp-flags=syn \ + tcp-mss=1381-65535 +add action=mark-routing chain=prerouting comment="VPN Network" disabled=yes \ + dst-address=10.8.0.0/24 new-routing-mark=*401 src-address=10.30.30.0/24 +/ip firewall nat +add action=dst-nat chain=dstnat comment=amster.vados.ruhttps dst-port=443 \ + fragment=no in-interface="ether1 - Unet" protocol=tcp routing-mark=main \ + to-addresses=10.30.30.33 to-ports=443 +add action=dst-nat chain=dstnat comment="amster.vados.ru For Certbot HTTP" \ + dst-port=80 in-interface="ether1 - Unet" protocol=tcp to-addresses=\ + 10.30.30.33 to-ports=80 +add action=redirect chain=dstnat comment="udp Redirect DNS in NAT" disabled=\ + yes dst-port=53 protocol=udp src-address=10.30.30.0/24 src-address-list=\ + !NoWG to-addresses=10.30.30.33 to-ports=53 +add action=redirect chain=dstnat comment="tcp Redirect DNS in NAT" disabled=\ + yes dst-port=53 protocol=tcp src-address=10.30.30.0/24 src-address-list=\ + !NoWG to-addresses=10.30.30.33 to-ports=53 +add action=dst-nat chain=dstnat comment=\ + "53/tcp dst-nat to amster.loc. From VDSina Server" dst-port=53 \ + in-interface="ether1 - Unet" protocol=tcp src-address-list=VPN-Server \ + to-addresses=10.30.30.33 to-ports=53 +add action=dst-nat chain=dstnat comment=\ + "53/udp dst-nat to amster.loc. From VDSina Server" dst-port=53 protocol=\ + udp src-address-list=VPN-Server to-addresses=10.30.30.33 to-ports=53 +add action=dst-nat chain=dstnat disabled=yes dst-port=44333 in-interface=\ + "ether1 - Unet" protocol=udp src-address=!10.30.30.0/24 to-addresses=\ + 10.30.30.33 to-ports=44333 +add action=masquerade chain=srcnat comment="Default NAT" out-interface=\ + "ether1 - Unet" routing-mark=!To-VPN src-address=10.30.30.0/24 +add action=dst-nat chain=dstnat comment="amster.vados.ru 3x-ui" disabled=yes \ + dst-port=2904 in-interface="ether1 - Unet" protocol=tcp to-addresses=\ + 10.30.30.33 to-ports=2904 +add action=dst-nat chain=dstnat comment=reg.vados.ru disabled=yes dst-port=\ + 5000 in-interface="ether1 - Unet" protocol=tcp to-addresses=10.30.30.33 \ + to-ports=5000 +add action=dst-nat chain=dstnat comment="Docker swarm" disabled=yes dst-port=\ + 2377 in-interface="ether1 - Unet" protocol=tcp to-addresses=10.30.30.33 \ + to-ports=2377 +add action=dst-nat chain=dstnat comment=npm dst-port=81 in-interface=\ + "ether1 - Unet" protocol=tcp routing-mark=To-VPN to-addresses=10.30.30.33 \ + to-ports=81 +add action=dst-nat chain=dstnat comment=SSH dst-port=22 in-interface=\ + "ether1 - Unet" protocol=tcp src-address-list=TRUSTED to-addresses=\ + 10.30.30.33 to-ports=22 +add action=dst-nat chain=dstnat comment="portainer agent" disabled=yes \ + dst-port=2377 in-interface="ether1 - Unet" protocol=tcp to-addresses=\ + 10.30.30.33 to-ports=2377 +add action=dst-nat chain=dstnat disabled=yes dst-port=22 in-interface=\ + "ether1 - Unet" protocol=tcp routing-mark=!To-VPN to-addresses=\ + 10.30.30.33 to-ports=22 +add action=masquerade chain=srcnat comment=WG disabled=yes dst-address-list=\ + !NoVdsina out-interface=WG routing-mark=To-VPN src-address-list=!NoWG +add action=dst-nat chain=dstnat comment="Yandex.DNS Family" disabled=yes \ + dst-port=53 in-interface=Bridge-LAN protocol=udp src-address-list=KIDS \ + time=6h-15m,sun,mon,tue,wed,thu,fri,sat to-addresses=77.88.8.3 +add action=dst-nat chain=dstnat disabled=yes dst-port=53 in-interface=\ + Bridge-LAN protocol=tcp src-address-list=KIDS time=\ + 6h-15m,sun,mon,tue,wed,thu,fri,sat to-addresses=77.88.8.3 +/ip firewall raw +add action=drop chain=prerouting comment="Drop Honeypot Hackers" \ + in-interface="ether1 - Unet" src-address-list="Honeypot Hacker" +add action=accept chain=prerouting comment="Trusted DNS" disabled=yes \ + in-interface-list=WAN src-address-list=Trusted-DNS +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=\ + extras.getpagespeed.com content=extras.getpagespeed.com disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=linux.duke.edu \ + content=linux.duke.edu disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=youtube.com \ + disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=\ + googleapis.com disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=ggpht.com \ + disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=youtu.be \ + disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=ytimg.com \ + disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=\ + googlevideo.com disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=gstatic.com \ + disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=\ + googleusercontent.com disabled=yes +add action=add-dst-to-address-list address-list=FuckRKN address-list-timeout=\ + none-dynamic chain=prerouting comment="YouTube raw" content=\ + nhacmp3youtube.com disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=rutube.ru \ + content=rutube.ru disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment="me.com raw" \ + content=me.com disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=\ + "icloud.com raw" content=icloud.com disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=\ + "smtp.mail.me.com raw" content=smtp.mail.me.com disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=\ + "smtp.mail.me.com.akadns.net raw" content=smtp.mail.me.com.akadns.net \ + disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=\ + "api.db-ip.com raw" content=api.db-ip.com disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=\ + esia.gosuslugi.ru content=esia.gosuslugi.ru disabled=yes +add action=add-dst-to-address-list address-list=NoVdsina \ + address-list-timeout=none-dynamic chain=prerouting comment=gosuslugi.ru \ + content=gosuslugi.ru disabled=yes +/ip firewall service-port +set ftp disabled=yes +set tftp disabled=yes +set irc disabled=no +set sip disabled=yes +set pptp disabled=yes +set rtsp disabled=no +/ip ipsec settings +set accounting=no +/ip ipsec policy +set 0 disabled=yes dst-address=0.0.0.0/0 src-address=0.0.0.0/0 +/ip ipsec profile +set [ find default=yes ] dpd-interval=2m dpd-maximum-failures=5 +/ip proxy +set cache-administrator="" max-cache-size=none port=80,443 src-address=\ + 0.0.0.0 +/ip route +add check-gateway=none disabled=yes distance=1 dst-address=0.0.0.0/0 gateway=\ + WG routing-table=To-VPN scope=30 target-scope=10 +/ip service +set ftp disabled=yes +set telnet disabled=yes +set reverse-proxy disabled=yes +set ssh address=10.30.30.0/24 port=2222 +set winbox address=10.30.30.0/24 +set api address=10.30.30.0/24 disabled=yes +set api-ssl address=10.30.30.0/24 disabled=yes +set www-ssl certificate=Root-YE port=18443 +set www address=10.30.30.0/24 disabled=yes port=18880 +/ip service webserver +set acme-plain=no crl-plain=no graphs-plain=no graphs-secure=no index-plain=\ + no index-secure=no rest-plain=no rest-secure=no scep-plain=no \ + webfig-plain=no webfig-secure=no +/ip ssh +set forwarding-enabled=both publickey-authentication-options=verify-required \ + strong-crypto=yes +/ip upnp +set enabled=yes +/ip upnp interfaces +add interface=Bridge-LAN type=internal +add forced-ip=146.120.64.211 interface="ether1 - Unet" type=external +add forced-ip=109.107.177.79 interface=*E type=external +add interface=lo type=internal +add interface=wlan2 type=internal +add interface=wlan1 type=internal +/ipv6 dhcp-client +add add-default-route=yes disabled=yes interface=*F rapid-commit=no request=\ + info use-interface-duid=yes validate-server-duid=no +/ipv6 dhcp-server option sets +add name=domain-search-set options=domain-search-list +add name="=\"Client ID\"" options=HostName +/ipv6 firewall address-list +add address=::/128 comment="defconf: unspecified address" list=bad_ipv6 +add address=::1/128 comment="defconf: lo" list=bad_ipv6 +add address=fec0::/10 comment="defconf: site-local" list=bad_ipv6 +add address=::ffff:0.0.0.0/96 comment="defconf: ipv4-mapped" list=bad_ipv6 +add address=::/96 comment="defconf: ipv4 compat" list=bad_ipv6 +add address=100::/64 comment="defconf: discard only " list=bad_ipv6 +add address=2001:db8::/32 comment="defconf: documentation" list=bad_ipv6 +add address=2001:10::/28 comment="defconf: ORCHID" list=bad_ipv6 +add address=3ffe::/16 comment="defconf: 6bone" list=bad_ipv6 +/ipv6 firewall filter +add action=accept chain=input comment=\ + "defconf: accept established,related,untracked" connection-state=\ + established,related,untracked +add action=drop chain=input comment="defconf: drop invalid" connection-state=\ + invalid +add action=accept chain=input comment="defconf: accept ICMPv6" protocol=\ + icmpv6 +add action=accept chain=input comment="defconf: accept UDP traceroute" \ + dst-port=33434-33534 protocol=udp +add action=accept chain=input comment=\ + "defconf: accept DHCPv6-Client prefix delegation." dst-port=546 protocol=\ + udp src-address=fe80::/10 +add action=accept chain=input comment="defconf: accept IKE" dst-port=500,4500 \ + protocol=udp +add action=accept chain=input comment="defconf: accept ipsec AH" protocol=\ + ipsec-ah +add action=accept chain=input comment="defconf: accept ipsec ESP" protocol=\ + ipsec-esp +add action=accept chain=input comment=\ + "defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec +add action=drop chain=input comment=\ + "defconf: drop everything else not coming from LAN" in-interface-list=\ + !LAN +add action=accept chain=forward comment=\ + "defconf: accept established,related,untracked" connection-state=\ + established,related,untracked +add action=drop chain=forward comment="defconf: drop invalid" \ + connection-state=invalid +add action=drop chain=forward comment=\ + "defconf: drop packets with bad src ipv6" src-address-list=bad_ipv6 +add action=drop chain=forward comment=\ + "defconf: drop packets with bad dst ipv6" dst-address-list=bad_ipv6 +add action=drop chain=forward comment="defconf: rfc4890 drop hop-limit=1" \ + hop-limit=equal:1 protocol=icmpv6 +add action=accept chain=forward comment="defconf: accept ICMPv6" protocol=\ + icmpv6 +add action=accept chain=forward comment="defconf: accept HIP" protocol=139 +add action=accept chain=forward comment="defconf: accept IKE" dst-port=\ + 500,4500 protocol=udp +add action=accept chain=forward comment="defconf: accept ipsec AH" protocol=\ + ipsec-ah +add action=accept chain=forward comment="defconf: accept ipsec ESP" protocol=\ + ipsec-esp +add action=accept chain=forward comment=\ + "defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec +add action=drop chain=forward comment=\ + "defconf: drop everything else not coming from LAN" in-interface-list=\ + !LAN +/ipv6 nd +set [ find default=yes ] advertise-dns=yes disabled=yes interface=Bridge-LAN \ + managed-address-configuration=yes other-configuration=yes pref64=::/64 +/ipv6 nd prefix +add autonomous=no disabled=yes interface=Bridge-LAN on-link=no prefix=none +/ipv6 nd prefix default +set dhcp6-pd-preferred=yes +/ipv6 nd proxy +add disabled=yes interface=lo +/ipv6 nd settings +set router-advertisement-route-distance=100 +/routing settings +set single-process=no +/system clock +set time-zone-name=Europe/Moscow +/system clock manual +set time-zone=+03:00 +/system identity +set name=MikroMaster +/system logging +add action=email disabled=yes regex="* added|changed|removed *" topics=\ + system,info +/system ntp client +set enabled=yes +/system ntp server +set enabled=yes manycast=yes multicast=yes use-local-clock=yes +/system ntp client servers +add address=0.ru.pool.ntp.org +add address=1.ru.pool.ntp.org +add address=2.ru.pool.ntp.org +add address=3.ru.pool.ntp.org +add address=time.google.com disabled=yes +/system package update +set check-certificate=yes-without-crl ip-version=ipv4 +/system scheduler +add comment="Every Day Set Global Environment Variables" name=AM-SetGlobalEnv \ + on-event="/system/script { run AM-GlobalConfig; run AM-GlobalFunc; }" \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment="Every Day Check For Update Scripts" interval=1d name=\ + AM-ScriptInstallUpdate on-event=\ + ":global ScriptInstallUpdate; \$ScriptInstallUpdate;" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment="Update RKN DNS Looup" disabled=yes interval=20m name=\ + AM-RKN-dnsLookup on-event="/system/script { run AM-RKN-dnsLookup; }" \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +/system watchdog +set watchdog-timer=no +/tool e-mail +set certificate-verification=no from=MikroVados port=587 \ + server=smtp.mail.me.com tls=starttls user=vadossio@icloud.com +/tool mac-server +set allowed-interface-list=LAN +/tool mac-server mac-winbox +set allowed-interface-list=LAN +/tool netwatch +add certificate=DigiCert-Global-Root-CA check-certificate=no comment="doh, doh\ + -url=https://security.cloudflare-dns.com/dns-query, notify, name=CloudFlar\ + e-doh IPv4 1" disabled=yes down-script="" host=1.1.1.1 http-codes="" \ + interval=10s name="CloudFlare-doh IPv4 1" port=443 test-script="" type=\ + https-get up-script="" +add check-certificate=no comment="doh-fallback, doh-url=https://security.cloud\ + flare-dns.com/dns-query, doh-cert=DigiCert Global Root CA, notify, name=Cl\ + oudFlare-doh IPv4" disabled=yes down-script="" host=1.0.0.1 http-codes="" \ + name="CloudFlare-doh IPv4" port=443 test-script="" type=https-get \ + up-script="" +add comment="dns, notify, name=Google-DNS IPv4" disabled=yes dns-server=\ + 8.8.8.8 down-script="" host=dns.google http-codes="" name=\ + "Google-DNS IPv4" test-script="" type=dns up-script="" +add certificate=GTS-Root-R4 check-certificate=yes comment="doh-fallback, doh-u\ + rl=https://dns.google/dns-query, doh-cert=GTS Root R1, notify, name=Google\ + -DOH IPv4" disabled=yes dns-server=8.8.4.4 down-script="" host=dns.google \ + http-codes="" name="Google-DOH IPv4" port=443 record-type=NS src-address=\ + "" test-script="" type=dns up-script="" +add comment="dns-fallback, notify, name=Cuad9-DOH IPv4" disabled=yes \ + dns-server=9.9.9.9 down-script="" host=dns9.quad9.net http-codes="" \ + interval=10s name="Cuad9-DOH IPv4" src-address=146.120.64.211 \ + test-script="" timeout=800ms type=dns up-script="" +add certificate=DigiCert-Assured-ID-Root-CA comment="doh-fallback, doh-url=htt\ + ps://dns.quad9.net/dns-query, name=Cuad9-DOH IPv4" disabled=yes \ + down-script="" host=149.112.112.112 http-codes="" interval=10s name=\ + "Cuad9-DOH IPv4" port=443 test-script="" type=https-get up-script="" +add comment="dns-fallback, notify, name=ns1.unet.ws" disabled=yes \ + down-script="" host=91.192.189.2 http-codes="" interval=10s name=\ + ns1.unet.ws test-script="" type=simple up-script="" +add check-certificate=yes comment=\ + "doh, doh-url=https://vds.vados.ru/vds-query, name=vds.vados.ru" \ + disabled=yes down-script="" host=109.107.177.79 http-codes="" name=\ + vds.vados.ru port=443 test-script="" type=https-get up-script="" +/user aaa +set accounting=no diff --git a/MikroMaster/tmp/Diff-Config.rsc b/MikroMaster/tmp/Diff-Config.rsc index a81bca7..e99473b 100644 --- a/MikroMaster/tmp/Diff-Config.rsc +++ b/MikroMaster/tmp/Diff-Config.rsc @@ -1,163 +1,40 @@ -22,23c22,24 -< set [ find default-name=wlan1 ] country=belarus mode=ap-bridge ssid=vados \ -< wireless-protocol=802.11 ---- -> # channel: 2442/20-Ce/gn(18dBm), SSID: vados, local forwarding -> set [ find default-name=wlan1 ] country=belarus disabled=no mode=ap-bridge \ -> ssid=vados wireless-protocol=802.11 -25,26c26,28 -< set [ find default-name=wlan2 ] country=belarus mode=ap-bridge ssid=vados5g \ -< wireless-protocol=802.11 ---- -> # channel: 5260/20-Ceee/ac/DP(18dBm), SSID: vados5g, local forwarding -> set [ find default-name=wlan2 ] country=belarus disabled=no mode=ap-bridge \ -> ssid=vados5g wireless-protocol=802.11 -28c30 -< add comment=WG disabled=yes listen-port=13231 mtu=1320 name=WG ---- -> add comment=WG listen-port=13231 mtu=1320 name=WG -36,37d37 -< /iot wiliot servers -< set *1 address=mqtt.us-east-2.prod.wiliot.cloud name="Wiliot US East" -92d91 -< add disabled=no fib name=To-AWG -2578a2578 -> # ipv6 neighbor configuration has changed, please restart the device in order to apply the new settings -2580a2581 -> # ipv6 neighbor configuration has changed, please restart the device in order to apply the new settings -2582a2584 -> # ipv6 neighbor configuration has changed, please restart the device in order to apply the new settings -2584a2587 -> # ipv6 neighbor configuration has changed, please restart the device in order to apply the new settings -2587a2591 -> # ipv6 neighbor configuration has changed, please restart the device in order to apply the new settings -2591c2595 -< set detect-interface-list=WAN internet-interface-list=Internet \ ---- -> set detect-interface-list=static internet-interface-list=Internet \ -2596,2598d2599 -< add interface=ether4 list=WAN -< add interface="ether1 - Unet" list=Internet -< add disabled=yes interface=WG list=WAN -2599a2601,2602 -> add interface="ether1 - Unet" list=Internet -> add interface=WG list=WAN -2601,2603c2604,2606 -< add allowed-address=0.0.0.0/0 client-allowed-address=::/0 disabled=yes \ -< endpoint-address=109.107.177.79 endpoint-port=29185 interface=WG name=\ -< VDSina persistent-keepalive=10s public-key=\ ---- -> add allowed-address=0.0.0.0/0 client-allowed-address=::/0 endpoint-address=\ -> 109.107.177.79 endpoint-port=29185 interface=WG name=VDSina \ -> persistent-keepalive=10s public-key=\ -2613c2616 -< add address=10.8.0.2/24 disabled=yes interface=WG network=10.8.0.0 ---- -> add address=10.8.0.2/24 interface=WG network=10.8.0.0 -2617,2620c2620,2622 -< add add-default-route=special-classless default-route-distance=4 \ -< default-route-tables=main:4 dhcp-options=clientid_duid,clientid \ -< interface="ether1 - Unet" name=client1 use-broadcast=never use-peer-dns=\ -< no use-peer-ntp=no ---- -> add add-default-route=special-classless default-route-distance=2 \ -> default-route-tables=main:2 dhcp-options=clientid,clientid_duid \ -> interface="ether1 - Unet" name=client1 use-peer-dns=no use-peer-ntp=no -2677,2678c2679 -< doh-max-server-connections=20 servers=10.30.30.33,109.107.177.79 \ -< verify-doh-cert=yes ---- -> doh-max-server-connections=20 servers=8.8.4.4,8.8.8.8 verify-doh-cert=yes -3227c3228 -< add address=10.30.30.33 comment=amster list=NoWG ---- -> add address=10.30.30.33 comment=amster disabled=yes list=NoWG -3267d3267 -< add address=10.30.30.10 list=AWG-Proxy -3292c3292 -< connection-state=established,related,new ---- -> connection-state=established,related,new disabled=yes -3336,3337d3335 -< add action=mark-routing chain=prerouting comment="Mark Routing main for NoWG" \ -< disabled=yes new-routing-mark=main src-address-list=NoWG -3339c3337 -< "Mark Routing main for NoVdsina" disabled=yes dst-address-list=NoVdsina \ ---- -> "Mark Routing main for NoVdsina" dst-address-list=NoVdsina \ -3342,3345c3340 -< "Mark Routing main for VDSina" disabled=yes dst-address-list=VPN-Server \ -< new-routing-mark=main src-address=10.30.30.0/24 -< add action=mark-routing chain=prerouting comment=\ -< "Mark Routing main for MikroNuc" disabled=yes dst-address-list=AWG-Proxy \ ---- -> "Mark Routing main for VDSina" dst-address-list=VPN-Server \ -3356,3359c3351,3352 -< add action=mark-routing chain=prerouting comment="AWG for All sites" \ -< disabled=yes dst-address=!10.30.30.0/24 dst-address-list=!NoVdsina \ -< new-routing-mark=To-AWG routing-mark=!main src-address=10.30.30.0/24 \ -< src-address-list=!NoWG ---- -> add action=mark-routing chain=prerouting comment="Mark Routing main for NoWG" \ -> disabled=yes new-routing-mark=main src-address-list=NoWG -3361,3363c3354,3355 -< disabled=yes dst-address=!10.30.30.0/24 dst-address-list=!NoVdsina \ -< new-routing-mark=To-VPN routing-mark=!main src-address=10.30.30.0/24 \ -< src-address-list=!NoWG ---- -> dst-address=!10.30.30.0/24 dst-address-list=!NoVdsina new-routing-mark=\ -> To-VPN src-address=10.30.30.0/24 src-address-list=!NoWG -3365,3371c3357,3363 -< "Mark VPN connections to exclude from FastTrack" disabled=yes \ -< new-connection-mark=VPN_No_FastTrack routing-mark=To-AWG -< add action=change-mss chain=forward comment="MSS on WG" disabled=yes new-mss=\ -< 1380 out-interface=*16 protocol=tcp routing-mark=To-AWG tcp-flags=syn \ -< tcp-mss=1381-65535 -< add action=mark-routing chain=prerouting comment="VPN Network" disabled=yes \ -< dst-address=10.8.0.0/24 new-routing-mark=To-AWG src-address=10.30.30.0/24 ---- -> "Mark VPN connections to exclude from FastTrack" new-connection-mark=\ -> VPN_No_FastTrack routing-mark=To-VPN -> add action=change-mss chain=forward comment="MSS on WG" new-mss=1380 \ -> out-interface=WG protocol=tcp routing-mark=To-VPN tcp-flags=syn tcp-mss=\ -> 1381-65535 -> add action=mark-routing chain=prerouting comment="VPN Network" dst-address=\ -> 10.8.0.0/24 new-routing-mark=To-VPN src-address=10.30.30.0/24 -3373,3378d3364 -< add action=dst-nat chain=dstnat comment=amster.vados.ruhttps dst-port=443 \ -< fragment=no in-interface="ether1 - Unet" protocol=tcp routing-mark=main \ -< to-addresses=10.30.30.33 to-ports=443 -< add action=dst-nat chain=dstnat comment="amster.vados.ru For Certbot HTTP" \ -< dst-port=80 in-interface="ether1 - Unet" protocol=tcp to-addresses=\ -< 10.30.30.33 to-ports=80 -3396a3383,3388 -> add action=dst-nat chain=dstnat comment=amster.vados.ruhttps dst-address=\ -> 146.120.64.211 dst-port=443 fragment=no in-interface="ether1 - Unet" \ -> protocol=tcp routing-mark=main to-addresses=10.30.30.33 to-ports=443 -> add action=dst-nat chain=dstnat comment="amster.vados.ru For Certbot HTTP" \ -> dst-port=80 in-interface="ether1 - Unet" protocol=tcp to-addresses=\ -> 10.30.30.33 to-ports=80 -3418,3419c3410,3411 -< add action=masquerade chain=srcnat comment=WG disabled=yes dst-address-list=\ -< !NoVdsina out-interface=WG routing-mark=To-VPN src-address-list=!NoWG ---- -> add action=masquerade chain=srcnat comment=WG dst-address-list=!NoVdsina \ -> out-interface=WG routing-mark=To-VPN src-address-list=!NoWG -3506c3498 -< add check-gateway=none disabled=yes distance=1 dst-address=0.0.0.0/0 gateway=\ ---- -> add check-gateway=none disabled=no distance=1 dst-address=0.0.0.0/0 gateway=\ -3517c3509 -< set www address=10.30.30.0/24 disabled=yes port=18880 ---- -> set www address=10.30.30.0/24 port=18880 -3520,3521c3512,3513 -< no index-secure=no rest-plain=no rest-secure=no scep-plain=no \ -< webfig-plain=no webfig-secure=no ---- -> no index-secure=no rest-secure=no scep-plain=no webfig-plain=no \ -> webfig-secure=no -3632c3624 -< set check-certificate=yes-without-crl ip-version=ipv4 ---- -> set check-certificate=yes-without-crl +22,24c22,23 +< # channel: 2422/20-Ce/gn(18dBm), SSID: vados, local forwarding +< set [ find default-name=wlan1 ] country=belarus disabled=no mode=ap-bridge \ +< ssid=vados wireless-protocol=802.11 +--- +> set [ find default-name=wlan1 ] country=belarus mode=ap-bridge ssid=vados \ +> wireless-protocol=802.11 +26,28c25,26 +< # channel: 5200/20-eCee/ac/P(21dBm), SSID: vados5g, local forwarding +< set [ find default-name=wlan2 ] country=belarus disabled=no mode=ap-bridge \ +< ssid=vados5g wireless-protocol=802.11 +--- +> set [ find default-name=wlan2 ] country=belarus mode=ap-bridge ssid=vados5g \ +> wireless-protocol=802.11 +93a92 +> add disabled=no fib name=To-AWG +2678c2677 +< doh-max-server-connections=20 servers=109.107.177.79,10.30.30.33 \ +--- +> doh-max-server-connections=20 servers=10.30.30.33,109.107.177.79 \ +3293c3292 +< connection-state=established,related,new disabled=yes +--- +> connection-state=established,related,new +3359c3358 +< new-routing-mark=*401 routing-mark=!main src-address=10.30.30.0/24 \ +--- +> new-routing-mark=To-AWG routing-mark=!main src-address=10.30.30.0/24 \ +3367c3366 +< new-connection-mark=VPN_No_FastTrack routing-mark=*401 +--- +> new-connection-mark=VPN_No_FastTrack routing-mark=To-AWG +3369c3368 +< 1380 out-interface=*16 protocol=tcp routing-mark=*401 tcp-flags=syn \ +--- +> 1380 out-interface=*16 protocol=tcp routing-mark=To-AWG tcp-flags=syn \ +3372c3371 +< dst-address=10.8.0.0/24 new-routing-mark=*401 src-address=10.30.30.0/24 +--- +> dst-address=10.8.0.0/24 new-routing-mark=To-AWG src-address=10.30.30.0/24 diff --git a/MikroNUC/20260628/20260628060001-Binary.backup b/MikroNUC/20260628/20260628060001-Binary.backup new file mode 100644 index 0000000000000000000000000000000000000000..f7df061298f6d19819b5d611a0e3b12b19586a56 GIT binary patch literal 357430 zcmeEv2V4_N*Y|7^AfZSX5X8^~OJLIz6{RCelV&fe5D5g60HP>}4LkPUd+#0FwfBa- z_l~_Qe802V04m<=^M3bvp8I{T`_rA1Wy+Z|GiUyD&g@R{in(hAdJ|-h5YqAkauiyf zN|T8rgfN5<0~s!A4cepzZB~Q!Cg>Orh7bc|{G5O^nF@VCj#lYRW*>k z5DG*w94bOfFl3HmI5dRl2q6lF5N3udFmqIaFjT=nCa8j5Sy@@hKouUy232^_G9;=@ zTY`lOI%H}B(d-RX)M~Qzs?5}YY-ws%fJ~WcN!GO)SuljnPz=WkRbcjz4$8plql!jO zH6B7SW^#p6lC9R)q%FwW$rU*&nL^is%shk4oPrSgO41E$(610eyUFLT5klKZ`Zg7z z3>3pjLl{Qzfgy|$O+gsL(h^||)EsW62=kz*HFShE3X0)?SmX$!Lkm8pA_nF}Lkz4g z4Ka{24KYkyXo%hvLx@I0Y*RxqkEm7T*BXUUpfHSrFuKN*hAa_A|5905SyiN|LZ7D4 zvTYFNVJ8#FC1Spi&k_p6QWlRV5#b2)uvOyCg#2cFc{8@68DH5<#ADR@6gVFk!?C2H zVuapM5r$|8Ss`fl3iO(aun$zkKp)}u32y$t7u4~p2{P2Nx_U%yjdiqx_H(5n2K5z4 z!X1S0mWmh@qVg}YySjHDL{#2D)~tNPU#UEYsJvLo=L&EImnD_+6fB;SC#kja{8}r| z|8G`)aCPMe@2=7Eub}dghL%Ur@-=#zb&f)x#tLp9$w~=JRAov?{-01`oLZw8j!$I; z!<|`KL0@4X)AJb>5v}-5YyD!6>}Hw&aEfg?6*Z#7aGn~1$5hnFJcdJNuX+wWmxyP# zy$1iUf&YkFhKW^f>W{3up^7;MF3q5W0IMTS5PCvI^a?)F!91GA25U0Y5)!i`qO=iN zv2Bxd{0>}BlwOt-ksZfQSG7r2rMBxN74ti)!vh~tkw;TrXzRS#G-;ep%;5%v=f#Gl zX2*1B(?On^7R`y`Cg=6(5TC0Jj!j8ztqUZTuT&{FDkM-^G*SmZNJB9kP`px|LYrgM zSl>s0=duh*rb;Q1DP+o2P_LlHLD9ek%BKNIhZD~Y3XNh=s6;!FN(NOc)CMSq11Zf= zbqvtKj2nc$1+6MilVt_S)G9@$zPlz%uhL}dU^aMDSy>rLmiM-@vND*Y-w|5GaP371 zMUs3#eK1r(hjdUtAO#^0JG{42>FpO99Tgs%)IBUYDy&hJp0_fTsgie>XRGAF-(>?u zR%K(0_r}>(*~A2gR_8)D=Fo;JAu28YH+XLj-4@ zGiszUl}cpk*;z1Ct7US%CR>&U{SvN-8es&A;bbC&^+E{gkYV(x5JKrNNdtmVAVjB| zA*6&Ksc_T4EemcvK>}n4xacslpq1nr^{WSn2!UdN|1kuv!H@!} zvT_8>Oob{nO{&o{WtvQ-Dm7ax0WHW(ljxYTG)ZQvLf({_rB+CE3Z_<}(@V5^ran!< zG^}Hos!XOvD_3ZldJR*Om8H%j{+OxAW$F}qSWfA>AcV;g0=*478?!-(=)(30VGam^ zP6GvoIU|JCGbnj<93jjdA*=yHm?sHT8X<%=MhIhp3dRiGNK7or5NeJP0<{h6#g+)6 zRtTXWgs@#1QI<5Th_1e$Wt7BgBwrkV-idCPEX0Gzih}`XB@oR8TJ{PY1gG zq_ttudWr~X=(II5%(ibZ!{iAev9disuxiRMt)4oF;SaM1OaKfrG)IWO#ez^3^!Nw> z=!U;rAw=)-7$KNo5VAoq!PvqCgPh>z0$d%2n=3*Lb2o(O&dvzchuA>~oq*UUA>t__ zJ)$r{c1I9mv^WY+Y!EsIArrc&Ax#sA-V|;LP#}4=t3ij?pu=j=p*84`8gv{nu}Lyx zYCMasK}Xe~Ti2ldYS6wlXzv=dR}H!jxlr$wt;kmB^Rg7W-sDJ5g5gZU<*ppCgU(fe{6!#TMRMKI}9;6?}7S&A^IsZ0!TBk zkMQIZ+&*K7Vg3a(;QFs*Sb-r%VI|x;QV@f)gJS4LJ1K}hX9$LNQ4qb@9-%!HLoyF4 zqJx}0sfNFMQ4N0ws5Ye;LOxVOyoL~uhLA6W2vPi~22Np0q6~!vd@Wf!x6xF@FdGNdc(}1J#Ax9Ubqs)d;22^!urUL-6CuK2YIW}em}JzFUH^8OQvTP< z)P}J4WGeJ5((d*53zz!O6E5vP(_w9LRa%8wq0b%xz8oe${Q>7TBe}`;FpSL|5EXkf4O)znBlb&d5%1Q@KYJ>XWd9)LD&t&aEzuwqek& znAaIp5J;7Q9h?vb^BUneAS`Tl@Pv>P;Ryj7${|?GYqNCFN(TF&;Q}ui6ap6*sH~8| zmky~ZHGeb&mJPCGsWqvpDia`>tl)x>MVbJ?gH3nf!~nm9bh0b?0`TFkQjpsjt0X1)0?z3A_iel z1ZF(IY~evysUuUVNbNHE z^w+xn2V6@|UG4mcdvZI*M2KyZP%rBF>R0xQmc~syBplJt1Nyg#M4q9_td$$!nXs?c z#?9eGS`|0oqp8VFYe~N6=x(!brn^*}cWFGf?V^unj|TYlzu90)xr=(qrF#Z$R+tYx z?p42yXPnhFntY?7u~Pt0-n zJUFss=tAD;OS9UBdA3@6&vMU^X{X-W-*`U8z)kON4W6`U{luxo$pxc#UG}yFZs;+p zbgd>fITZXKKQz{N;sL*2Rbzd_kO?qEt7fS0CYvFy{lDH4c8u=wSdIdfpT@5k?Du9< zbJh5_Y02R>U@BzWT<$kmAM9wCzc?{+P@@#pa$ty&0StAQO47kZ1l=z{B9Rz7zR^nx zAvi2ejJ^Y+QxaXCFo%N0+{W3(%OUXcjSbg&x8pLt9DQVs5HhZ&v$PteN=+ykxNeeN z7{Z$cy8gLReS4L^yx5Xzd+-5xMR-K$zHCMwhxp45Sx72Lox0K>`yOllq= z4-XJbmDP)AAW#7gs0bKrM2*y+JCBOw1RAZNkBjUtxNc~8fyq!V3VD|=Y}^mlOQQ?W_wLn$Of4issEv^ZeQTs)jxy4qdyO>cULy@M z;*j}(H^@k@!XOT8?LaUC5ZgLnwjeq%pbc=38{AOfp<-Zl;3qVOF&Q|mzDWjNn(vcQ zs*+)3X+I>R)RGLm--gmCKTAgaHW`JO(5jMAf0m3^OEOwjGTP6QnbeZZq$-&S#~{fc z>Sp?FG8k@#YMK5lh1t(im_e%&NCG-LbO~c;gDzpDA(D}Xc+gWDAVg;k%upHWLF}7y_eIOGO_au^=B%rHJgKmlpUY`l%a|n8qGysifBshYo5_01p z@N5i>8L!gR2k?G+-DJMV!pW3znotVZewaja6`dN zO_~g#Ay29l(OTG5F2A*Mf6rcP(mvDcd*!~EeYLOUnJDp?d(ls%XFlRv?em{GhYe=G zeHwe=P>1tt-gz)T7wqLO+b_(~l)HFe-N6hL$(owHjn(zseeRHal-@q(D4rS5^-8*fzbmizfvJH)W)cO22LFOa-IEv&8ntk3M|~=2SYkJM1%m=gb-9;8sYwqz4^_Ql8}hCbF+IM z?l|^f8{Ep2(RfJfA@8j)%)|uYl5h2_BW`cdvzEpJK`?9=+zB3A*xP(B9ySwqH>hH# zsubS}2~;yKV6)i<%?zrU%dYYBKfyu~=2^4imw!30?KkSroCu>)(&8{!lyTO;1gxPf9EX{U4cY5B=$lJa>-%R6vh@$rx zQPzD!vq8J&E^8CrujR?O?&_RwbJ_~W&bsP(KjVZ$z0p07RvcgNAa^?0xzQ}nu2xolbX{KJ5A+edY1wbAuql;6Cp%WT@U_v`AuiZ+imTk@<;V7HA0^Go)w z_N7oUgw2=XlJDl8A%8aaP_M`HQ%*;3SH75k+tqdN4GA2~Mb_Ht{to#U5Q%Ff+2>v4Aeu9=+~FVBs>llW$|>{F}7 zS2iebNR{VLX9kaY#oG8dx6SOC9d0-2xI4(}8OyIIyk%a}xQ0`17Pej@eE#BmUXQxh zS3In9Jaa{ulPDnS@~!ZejkgLCPd1A;eeeHpU;8Mv#KZX_FMhJx(c#g> zVSBcU)VIf~hgZtC=K8e9ce{GezjgJ|jmZ3s8@%Zz_*2#ni(TQn;KRFY4THUAytUCf4a0JfSn+^;6$Z+xPGE#w${u?|a>E@*Rh_4lzd; z+q|m#c)8~bwx_K`Rnl-(*Z`qzm`R zu3nEgeOfeG^Gb&a^QSclijbXH&cpC0){EcHnoYVCBtY!fL8ksKZo5c_wR27Rd zgh$4bNnov{`zGRd!wS|bksKIBcnDOz0j)VM#)WhEbGSp;#%#1|?l0w`#seV_CQX3z za0^pLk0GtGAz#%L3^y}1NgZH`VL08?6bMQ+MX7p5{cd0q5VYiF)=|I7s>%8Kue(1r z?3=iJ<#EN=Z8J<~48PDRbm+5~=#=g&_w3(j!u0QWy0XpNIgKx_nYi+>f1BnP7QfRJ zH~NCN@*cE(@8-Va&6=(rnx4abhLRK3b)0o~>#5f_9#~gg`>=1TjdlQo*8Sw^G%aXPc)&DKu=n8W@qDRS&mzoAP+iL*y^jmYS7z<1Dq zvi>7S&Yzy};6U+HVSeM!FOoZnv^E!!{mE$Em1RQyl12kmGjd9=JNGRqJ96@QD)lR^ zlSkdPm5*E9xLWk~$(xSjysW$%PxOgCGI#9#vdt@o>es)_m_PN($8$Rd1Z8e&Sfc2_ zM43jf*U2Sng%yemjMRil(OyV(V?o?>?Dy2#$(JPq2+4?k% zR;ABlvVIM-%V)G997|3Q3up{1zOOE+)FY87VckJ#GuQ*v#I%T6pYwxHCL7x3{e! z!=_*~^=Gs300WTr`&CI2wo)-J#z|u-*h+56Nd)LFx z^WPUnb$H}C(0Kt(98RSqh|X)emBT9cFu+?SyK*ju(c*F&0@>ne2NR`h$=&6;&y%SedKS_$1WXJWPf*4tEJR|pXTix?%1>7y2aj2E)FI~9`9X3 zo6tNWpJVkJDLoI3Qe_B6+B$DOyUwl02GJ+V$P;I;MVMVTZ~3Ha{-ONOYnmi37$;S= z^D&Xs3mY)9>C(_iUN=@W8!)X-qy276A9Z!#G;0=aE+9cjQUa<@z0x)B+{eisQ>l`d z{hOPzvsrVQqvI4qH@sgoqx9mVfKyjGm7mm))NK%jTsG%z4cR=vA$-Tr8z zP5h1GXpPq$K{RXpYKk4QaEt=Gzx2@ z&6|rQ2Wh&XXIh^*OJ_fwwLHykYWo9Y?3yta>L}bZ{Vr$zGtt9~^4CrKJA9!$ zo_)O1EY0bPXNic_XVmGWm#RD6xkvg0_BFqAq@cU_-tFvd`*WPRCz`$ph+g@A?Sm6> zOZH7~lVrNTpx-&V^=-F`{!Uv~bbD7;cU>sMGUnal^bJ!7DUS3hzI{7vXP5Vn-QE(O zMyH3wK2XBbD4zkZsuEE3_b~rEmC^5E{(G4J*M@mDPUG>}JY34<@PrDfSSAz6_)?w{ zmkXtQE-q#(g<#;qMPh|O!IucQ3XYH`mdm*knMBMMixhGRfzxW!u2o)T3QqWqe|ee) z%=P1Zp9#CptD!*#c%n({Kw3+Cf8&6Tk9I|IfytSwJ_@~tnWT`VWolsal0wH!^bhv; z0Bbe=%?@0W_ zkBgIzCJl)XU-~iXPrz3-*rMkgKWbL#{civ9XWL6kZx0eiWWWCbo7Li~UbJMT=Lsxu z(fms3sn#<`bknZs9d_rc+iJ#FuhyS-G~Ty0nYPhp{wUw;7DcjC<<~8RdBL4M*R7cr z++|VL&9YOuN4?IXo9dK+TPiE7OUrh742ZvEx^PM5MB%Ar6}Q*FoyrOyH2hu&a^-i^_*k3Q`y0`%~9&U4UHSKCOO>{ zCzZB7o8vok8q0Li;iuv=%dgCyX5mZWH(DDs@xjN3Gm?v4BF=B-z3qQOJnfR`WxMxr zO#ys?wlLZWL4z2<*YCmor^@=5l+W+M{d;i#*9Lbrao`lPhON9cFjK{-83IcM~remuhmnjY|aRPSz zd@cr8^j}Qr0L6Ze3<%s6AN|bA;H5SpZDE!uF)&t<%WR|2>J)iaC_2zUF+)}QJSP+v zn3SeqCMSj`Bqby#CK(}>AD}JbDL^g43koCNU=#ZWm;JR9yU<11mN1B&sWceGYz!Zo zF$d&z&-bUu%edLPh6lSz6-%sy#NZkRF_k8>+9dWJ5W8)H7nxkgi>L=k?=|){1OGDr zNV_GjrSdv_Dy!z?XK8})0mU_7S#qM$7)G}@uNEZRfpBcn%sPahy}rMkd19Y*Ces?4QGG5#`5yhZNX%NHM1rQ7}|!r*IO~ z*?Wb){?v;0Ryog(`<{(n89z{Rdl@r^!ICqso)|X!Sm4s9?@sQy`tt1B^L>SWwoM{0 ziw~4GbJ@^+(T;vI=gUGi<-A(jyvf)Pw>77p)@7D3THb!BKkU3=Y?)MkrC`44%VOuA zC7vxxE9_qkb?E!tY`%arHPkapA+;r2sq-%#B*fon;6+L`&muVX| zGXswlc=A^DZ=R;a!X99;5sA~eJ>E~)nJ6sLg}&zOO?-Z3S%p4UbufKokMY;{9kjf0 zi?iz3_~z|;PfQ(A9+*;Lc}ivNYW>=$VLCVCeP-k*UFg03brveuHp=G=k|ht@XSHX1 z67FKZ;%qK+njPcUZ8!SvzCrt>B`v?|WdUpB6^#2GLi|JPMQ?cF z6WQ?-&3$}X!-zKV1J+&N*?aSsRL5I~+YfTLmYshj-FCivvj5{Q2mI(XAMuttO_sij zp6q7U$|~XQv*!Ut*40?G8k#2N?dr6wNt=^Ng|{wF-#zoyI;!&M576`nyJ8KjY6-hr zDH!TfmTz_PJ=QOHs=`vYKVkOK4=1OZ7Pwvxb(}qmlH7ih!^?ygzC$PYJ!{Ym6VA)| zk{G{x(DVc8vD-#^#LXL>EFJq=<#@5Q_|Cp%7GIw)dDJ$!u_-enxpyO%^6q$-w%)eM zmEO}byESf7ZV@xP;5{aq;4m@NjPbe0(sH-ub|dFZ>$Z4G&yE=*TG2lrIvu{+*->6% zXRY(|-+$XX_DhGSFW!V#c&>jEF#61;aEZyD$(kiO+A$+W3Rds3GC9#})cr-82I@t< zr28+P8mP5=nQA-Ca)y1hmUpA{T(29;dR@Df_P%{5|u`hyS>sRnfx6O%ETG9;(yzy`b}vn?63+hHLfAX5^w)r>>rR8d&ye zd#huE&ua>nvTkgpynj1++D886y{T)4wpr>o%WHIH7vJ<-YvNv;l_USMjzf;M1ARP|f`|JMJ%_5W}E z|6kGn)yp}BP|THyr8q|^7fB>+u7bmsaoHS>NFtL-aS@v>7Vx-IAx|t5iA7=oPYRnI z!*pYpyK7!d_$fK^oa5rho62WbR+D=61(?Y6q*iApU1q%^BXCwIG&qo`$8| zcdvN;{Jq)w#yz(z!-izTE{90&ck8!M3SLBMg%@G_$@QB_a!To|ZVUMQy$h_*Ef3e$ zdaaf0O;5{$cxBx%+|?Rd7c0m1h?? zbQJWSNewNxu~%!auPRjfX@?!UN75PZy5 zrjj-)ZF`_*Yf;k>uAs2d%FzBvYGR9m}FaMNYFo%MRm7V{PkJ>UT{_2u*QS+W1ym~sSzzN%OXvmn@k;P4xOj|m9 z|7o=2L(=VO_d4gV_jLk*2wm1_%!O`j1c<*on6_%$JMp$>B=>g*`&T zRNKg@F$3}We7-;`Q*!u_EuYJmaV0!lDd$V1Jid_g^LJh%x&P8TFV}u`XPq;O3ry5Y zv`UpiE&s;BYuIGh6by>h5r}S9ZBhLDcG+3}>$~h&Rc!rB+w5+EfA<<*L?Nf8TDIAZ z`PPp3i`(pca4&-m(XEQHZv;y2xZ~is!IapjI@(`|^{=e9T-EgJ!|Q#uXU^CVmCI?A@^$Oek=Hyw zX|MW9CYQhKH0Wuj%hq8{uMX;bPX9T*`~G1IZ4!n>B){2`?RLN5Zqll@ACk50DJ~zJ zdUbr_?q{aoKeP2t^@cYK*1VPr^TzFd8quxbRz_m9boKED7P9TlW`6#Xho(sMCmR+8 z$zHG1_H?nDdZesQ_A%k-5ySR$d?_vHzP`Hx|FC#-Oim~FoNm0`4=1He9lriW!GhyE zu5#G?cxN0#wjDqD#8EX9rZ%6} zplGnTEXQO*w_ASWGo)P^f6Q4@wkBKjlGk(Q-Pyy3G;DXj>FEQk%!$l49SVrMhdDN&-)rBhN1f z`yOS;!!4Qz4S3%#pYwLgkq)E#H7+mdsFQyb=)3vMeXBI_FAzH}ZTvZY#OT4QH-EPM z?)UIFZmRxwDy-kb|M&3!uMPidNXL;W*fJ?DRf^>trC7nnIbtzaDOGUTd^VpW<4QSl zIWCh(!3r=|U5VlF7s^S5=x3desm>t8FoEx8Wx&hG#K&fd?`Hkv z-3F)7;CNk3a4ah{n8jy@aQNYTTo{H6tr5bP&|b0!HxFL(43NAHjF8J#h;+j7b7BSZT< z`ntn5quV-hd51%7JFV}NG1!Xv>K*lU-!-x%VfQn8yV%`IO%_Ew(n+_3KSw}snh(biooSGQ}o@Z~tal`qR>Py2a)c^-3msxU8Jz53zSW>r$~Xv1?i2@5Tj-^v4byVnrvybFxmen3%*U0A^Hwuf=R7#`xx+!htB#h9>s3~Kh+m`{^4#Z* zj46qpk+N&-)I(d$$|K$-kLbR1uHviH?&JASa@DBJB*8PzGcTOiXN2O+>?N=A`*E+I z&o{r8(7tqZ!y9p@9Gjo4yVt8zo3?-47Rhq`@0}dBF2`G*)px*5vx9Nd>gO#zw|@Q3 zz-D33>v78JB|Lo8@y(hYhi1KRpFjA@j&k*gg>APjFP{5#Y;)7CWwHfNM`m?<#&X?q zZnw!wcGv->#i7u1MZ0(IXy>}1c`^HS%8dfl8C--#B;q`lOOz?R%F#U7tw8H7rZJ%X#HXnOYUw{9H z4^#H^i^^2l_}heBK7fWu{Bx82do8xqTFzQxKfLAQyWVPDONV)jf|D7YOb>Nf$GXy7 z^X5{I_%HNlXFYGsDjB>cma%x!al4@}EGND7+%%%1V731M+O+QWtN+*?_mS;x@ups# zyvbLlUR~aI-0>D?`_qIEvLCm-Gc*0jyP3~hZQ?q=I=AN$-1o@&=@=S9Z$ z^Oddw#ar>Kd3pZ5CoCH59=Wscn-t5y)_szx&kroUEyWyX7KUs&KVGTcp< z_pr|19CPl)u{^=8Emv2!y&bZ&evpcyKmJE9zcl*&yVbW|FCF>#$jlBeMmcFGwcB9N zsccF+J!YOos>SLl@8)%7KV25p_`Tb}Vci->4NKQD(`=`tDwhp>e(Hp$TEvk>RB%w$ZjI!ntf7F6{J6dHfY<+L938 zq3Nc_n@7CGJB;@o>JeFFI`aI3MKqh`9_uF`dp|U$_3TDtpOWh6RimW+gH4USv|)#rAsC zYwm)&I_vrLbGv#wo{H9M%Obs9bzf6@Jh8!(2dxks&8e_wE#A`P^RCt>1c~E%9KFUK zT^xF}JI$?6tHK41@7^E1s;qOvA+1Z+y&rt}y~Cv^U(-WR%zU`(bGPYP>#tsGw&!D? z@LO#rF#Cx&7ROI(G%37^^MZT$!e+r0q3wdT^$H^AV|VY|v+w&hBWRrayPjj$b{qU@ zWuGKR?;d8qyk^=|!<6Ye-u}3v)1r6H&y~E_%*+m_ZJaRqy9Op+S@In=Wk=4gJ8cg3 zT`}TCK+(xe`doo2WqE-+wrcZ-4hyf&^jfi-R_Xcn?T)pf*r4GQ&!u55FD`a?BQ`gG zH+_$TJMHNa{o8@>gFW%?qObG5o(?I! z(Q5CSM=RP-oV-f5u8yvI+oj{h6L#&AO-UJ``P^u+L-{k?<*jvJyEdf1pOSdTM<7^* z{n43y!FuqS{oJ=#THLr%>eqYKn)j}vyS=S8pMIfiJ8o6K?G{1e0kgjL>-|COG-+E; zTVl<$s-nu!3SSU48d`skl;0x-tFkWqZyzaRek0xANcT6={f%`0pFq0AM{k8fiA=6k zibY(ZL@F0>MI3=lB9!4cE|iF*A}L!!e3n|G;Bxu6QYvJN6k?@7z)`TdGO>hfaBbCw zq^j&7Hf(-4myh#tk%%YcMu^#jE9)n=f~(&fFyKfWTq@$B8W`w90q17=KLRbKxYyJG+BR+fQU+1?u z?`7WNo%e>lc|81F?&88kUDuJ>Z{A%QzeuHUEhil#s9U>+ZQt{t<*2CR zXRaqnCtkZe&@$mxNcY!w1J~xIS8m;2hJ1at7b#EIqf8pMA_uhCpQe+^r4{_LAY`tV2HI!~JE=6~|Uvq7!u%ssy?VE0IF z>B|#ty}foV=!~Of7m|~1?x5_+GOG+2ePE33&V55BpK3Sai>2v<*KR`(TTIsmwS2WV z%sJ`K_Oht3l(oE@HqVE5dhs^!eC*r1W37U=b&_Q^b1yy>-`Bkn+vMWO!vXDY1lO}I ze;EDl$|tGdM%KnxCAa$Jvr-y(do^?`(Fe`l?09lYulH$r*|!hBzdEn?(Q|c=ojrDJ z_!kq(JM|x{9jOIH^u@yW)01FqSZf4p)^4yZGGjI-j>& z;_#|**U7er=bh^GbSBGg&gEMzGd8`w*TuhJ(~;5p$4bpNNAWhz4LkAalB-kEZSkWA zXIf??9#-97$erZ#;Ay*NV-Cmeo%B#FyU&=#o=i;@1(*4(Xw|&@)~5CEW$ex;Zuw+i z+nMkp{7ILO$0ijXe9-0U$ZdfG9u42UW{0dt9X|b>n{TIs&pWppvAKQM4-5O>y}o1o z)E$!D_vd#GwOP>7>%tSKPowvY8-*+OZrt54YVY8nbe-zs>&0~rSZS;s1HVSCzpseP zv~IeOK1$|53GSx8^jnUh3|!Tbuir?CXGf*lp|q*9Dt#KvIZ@JZpgQlUg-Pd8cU^Xe z1+Kf&!@T*pROYncw{3GH77ocS+j8sFqjMpQ{>S&HZSU@U!*AxkKK{43%3fX8XYK3} z98x#%{TTJEdl%L9G~|$HH;a)g^7Q}U3!u@ z=I~C|FkhW_@piEqo$X?~!-7sN+2Fh?f1kAA>&Kg})IKfreJwT&>VML0;*JscVC=H5 zW_#qGqswm6tUE^Jj(2=Gv-ww9i+g^$lRd8mJs)$+E_+}8^5q9E^y!xp8}%mXX6$Bc zSq^p3il)2%xUq?OU$0!8*rxB6mv2VQh)I6guFSvi&Qbfw7wtB3o5`p8JuSa?aD zYAZpHe!cJqZBrY!*&bdT-Pq;R#Y^dCH=c*ZJQ_A5G^%6ZBkw1kRO!O@A)Iw(QGUG- zPJgmFon@UmWpC*l&x7Yr47@D~-Gs$+o0K@-n30VJZEQ5T)9u~CzFxMEURmb`IN#{v zyfv0TH=(g}Udy9HEhmf~_(T;FvTLD@c|tlqV5#qf%<@24Ko7TkPltSml>+*cX1#)X z-M>0~;0E=hT;5uz!l9HM14H7DT5Mf-vEG*<2j(yBzc1Witw_M*zjf%+*3>Xg3=1D; z{h8VN;`Bp(ZF%dIzU!W^nfT3D6l)Ld-fmiidvSQx80_Dw8y2_r{)??r~N)% z-0H1+e0Zek{fgxFvA!L>i%rX~HduM)Qlk&I>rx*Mh(8;{ETz)NPZ#}}_2T!i`8{lY zV)XmpK5Pu8jNcgeHwONVfq!G*e>w&xOc{Kf%M%NfJSAH$;fkd)nLx;AbJ#LDu9S*o zBCeRLWJ@^`t{hk33YkP8l`91jHe14#;S#BwV|eYqHrxfKj9T#)d=b+|7#zms;%tE^ zL=+N&^9b`st+=?4*>{^W{uSGX-M?vY3LbJ|S5TUOrRieK^i(t+kf^FQ1Y-S1g+Xz=1V?~q2!zK>m3 z#`km4FBr6V)l$*=gB$q;SJ{g{RSb21(JbO{)*1s!gR}{Hb^z=^M zr%zTe{2pbegf4o#{9LPTU9TOUs18Bh3-2kKFYD$o>2R)nM83)mxzu+MaH>n^i)PX2l~XML#XOP$$;GiRTx zX#KJg#UU_%*rA=hANF6dvz65WC>S9WLYzEbfpN%#qxobg zB*SL#o@NWUwIq&JudHlEhCy&%UpvfjkT1ADh?CU~XG;@D|C&Q8Co&)7!PG9~Z#bYD zb0dGiLA7wwDsjT97eb6eIHua%hkViyGV+64Be)TVr~3m%oaGFsdz;A>N=dd_&x4bx zxp1?F8*zel6F6p*yqeaai8HQkAX;6xi4h#${Ou)zKd@@~194)u0RHM%%U>`3zulU2 z=$*gPpbR*{oM^MZw>9m^)?_I%W!k(f_(Ib^qhX;R=`u62STapUMs}u3uTtoYM+Pis85-v;;%0D24+EvqK;Ip?g`O3d|HC*eFe$er*jAi38mofvP@r-2@>I)2wW% zS|ua8yo2$eaGgS%qtFIuHH44^z{_jH5GAfw2%%QQdD0aa4G{;e)8X$JLX0>#z}?E* zFElzTJT|F&OmJu;B5ycG(_E%jDKholGbFOQ-xXyjsk=_0hl+vAEX?l&r*ZnYf9>c*9Dh5qs`1+UO40mAEG@i}J`I){y zM4?pI1zvH}N2`QYI04zhf$8+BD7JDbUKh*BeDe`rwEeP5YJ;c&_a+q2K~Yk`h`KWf`sX zi1v7Ioa@^@E}?B?LR@mZFWwu+t9Z2gmPfTpr-w#YWo2nJYUr!LSYwhI2pgjqjs-%+ z6lCu1KFQqO{Sd*HWtFZshofqtl8{fXS|*XHjYpV^YgEukgCesfpTi;EVidzMM-|Af z1ToAhB}7-FP)iWKxU#a6R)Xkjh`~|8z|2b!12ciD(o2ZR;U@{#8WJ*k6Jmi>$OKgo zI*~|dOEjTDr9yTv-hpwMG8HEU5LAQlNRo!})1RcFb&5!OD27lFis4XtAch%cgBTdq z1~E__8^o}-hMNsMBZOuLe}EP=>8)dNB55|r!z4!{*XSb2^z`cVu0P3y@=YduA`>$r z6Mkxc6vLtSK*dz_;*B@53H3qvSRX_yM3_k-!XR(Tw`@^>Eh^-gh4nymW-LNPHKLk4!t$sbS!N=^QW?o~vVZi0H4$DzUwacIt>IJEms9J(12U&V!44}?J( zL{%wJ5dBXI0+xYC+BZBB<$qgsf&)0B9vU)Dk||TBs>#M|R--XlH5!xn^gcpFV>TzB zgEBPI&@GHKC_^I+%Fvi69As~NZbyOVh8Dnx7BC^}Y7W&zMo|)7p{6KloJbazRh6d?l;cFp!bh!|90#G<#36(-S3;wAbtn5b&TqEyAA8>2wa z%vH;D3eaoDQUQFx1*O7j1Fn1$kT95Je_@hi4x=zZnK4P7W!hWEN>s>bOyprM#^KBM z@Sy@?+y$bTACptQO-^ecAIzyOF-!(>jh@{*AR}GEA{S@InW!GgB!u-0dfTw3F(-xt z2};SjN+c4QGSvwF{K=#c!mLe$8OsFTWrLX#L_jQzJZw93X!h;DI6%bu+*Z$Xq8& zQ^>Q`3N2&?3Uxi1IY@dVNe3W=W|B0_s<8fri;WPvNYbfPNK4G(m_KG%wXmVG8L;*Q zW>~!tqQ6MTV7X0Bl^}!0;W-Lz9y3gm$4pdIed{DVGe@P>WM(Kb^~@BBRwa?D6*?A% zJemdQ@^pGdMu1MHRb}a!eVN+qOlEKl`JJLrO{P+n+Wgyx5!sot=FEOdggmT+V_1m_ z@|4x^%$$usvP>+xIT)fZ8y4IXxVHrE{cGpmvzq(RG=(gk8KKcKle6S-I#nXUA;C4L zS*ltim8i*QQJFfuM6EW&YtHmG#QgRta9bC+C35-ZZ!>?D>44gqX4b}Zgd|s_0EXy=nif6T5H@2F zZT#R`IMK!gFaeKGEgXd!#3dy9=nt(d$&@ogB(n7EET&G8DQ9MCYJ7@WuVIGANK|Tn zr~~WUI#hRELvog(JA`LQRBE!fItIto@<+fm5|{~$+VOjck0FFn5rQQt!wNJ)9oT7W zMK`L-I%cp0fyvC!j#(P5o;Y$7E+w(sH=+*=0q&yE08Nfm^rEd za~5JE1l7Q-5yDImqIaMm!Zb#Osv4Fxb)ZaJvS3il2GvApM`w8K0=tXr0p$j_`eXzT zgcwv$!W2i$FQ8q`9pU(CwL+p(ung*l0k*NsY*|?A(0GGFM+hmP1&wWETvi zG%S>iG}O~b1GYBOfUS-62nx`|iW8Zk3c>^fhD#=+W+Rv*L|5jkRi+3zd!wTy4~7eH zfn#WNTyj`MbZ|mAD=MB9ESCf4gs}fIb4tz38=4wde}7Nr)S8+5Q9qE`{ymw~YG&?j zVw{Qpp3F_Y&3piv?f*FQ`ZXKWv}WdwO{eUxnfw23gPPUMys7mo;?NJ{^%t321D;dp zG-|Nu$q1Za9HdLgEFfHBuqYM-phHV2$j)FSqGzP9jRRN)q461=PNTsy2tnc68k8;_ zlqhJv7N%gsZw7{RL`Cp~^WJvLHp#Os~-C>8OJ60Hq^Df4Akd zN{tY`j(mU$Lltxo6X2J+7(zb+1%PWb$U=b-vxm8tmAO|lwMJG2`M|`-?96P|QzOj; zvTJiIb25UWcUGcSR{c9drB*=nZ=b+Y@&B;*uD_9FS$hj$U}lhvVP-^bL?l_9?rHOH7fC!i7KPNL z*|7V@)D)D%G#i_<ybnN8!s$4f#OG$M=mQ^?CVP(A~p1oW`{YN z-?#mnp}A>)++0wQz#lTu_#4GSkiQ9$Ph`XB8`kEKDasLnAbImWBch|ljcgV%C-rwk z___7H-@IZ8+{;P z7A^}`w6tm7oBinR+i275Gmtd^zM;Ns-wp~zfbO7ur06?`luj^e3&1A#{T`j)FnA8r zNiysbs5HfW6ZMO7v_BX`pv(K?+&Ct2NXxxhnu^|#HN1{Bd}9R-n|M6tN$OBlG)SxH zewCM1VeD$6wM#XyT)40qCQW1UiW^V3f*fl@fldy{9qq$u)2 zc;S(uUsxeec6VRw8eS)DpG``7ZEcG(=ul;^Ng3tlcE5B;C@!BJfEx_Ce`TJ%x9B4 z9uLox(#(s=u-jp4$!mK3x|o2`tVF&TB-X>-V5%+XW?m-6FsxrDg_*@=d6pOB@YBn< zEKOOAdNB6a8Cy6AV@No&?Gf|<)dx$6uv=Tq0vLFYKt`3j9ar6HK3tcWCHSf9miE7Y z>9XPqM)-1?;Gx+0al=lcG%X?5&Bq5e8@#JZa$3ahzqz1l*f-W7jb1x%V$2IUmi- zDxY#Q;O5>-Q8|rK9UC&hh%o4^GIrg^xTq}Prh~$7jf#%W8p#1$`bjqSnpzK=dOn}> z$80_|om=6@-K3fq7{sS&Ej={dH}@w=W;)O3)3;`52COO5SkO>3h~86C!9`L6%2_%( zVHmp|3a$7I^_PsawO18y-)@?E7l69n2g4IIiKftaTvTu0n`BZZkXAy!G;*Ha4grH! ziB6eBYwb~yfmi7Lr=Q#}2nVKPaX}+=o0E_b%$trssh1O#iIyU>N^;xhfp0otyHo%q z6VlfQc4xS|wZ+|iCG<{nj(NWkPJBK~GH+HyRU*(uIq2-|yflyU`GgF|CV5vC@d(>7 zau6hA^J34y1)e1nb6Dh4U1FxJiZnaAZ~jz={-WH<#n=Xy(vsFuzErn=Jxi&|Wyi9ZnGUY@|#3H;hGE7|pN?%kL zC`M7GFDZ<-?}-=wv)@%-_y@`h2dh1iY};8{i6h15jsL~}{hF;y{7HfsXed_(>$f1_ z&a$L15q$=kxR#!#MOp36nbXlS+p_O0V>ZTBa9u;_d7rc!d3{5x5L5U`a{g63nJ0Ty zUO*2=Mcs;RO<&K(k9NO&%0mr52Br?K1zzA@6OhcHeVJi~Cc>0N=G$+X%OB+Vgv5y% zQb)qTl?d0~Z#s%xCr)R~jTLxL_%_bOI!y3~Ihd!DD$RNoF7E7=7}t=L936nK_e32r z(Y-Dv)MUAW6!N`4d4fAwA~P;4R!yP;ug?>DZCmnzNT;OTLnG<&PS>wO`p@jRh|6Rc z*#^jwc@UTB2rS24E_#-jB0(djI!=t&3pv8rPLfvU;2e9%qoklWeX|4aVmU7ole~*Z z)#Th%$7yNuENr72$rz(0la{7D&d;*G%x*9wpp%E970esoUWEYCJd@E<>is`?5{bC3 z&k+MXEAkm4=!R%^*#lloMBbZWl&s!5JRHU2+XsWe+U>jRtAoLvQ5@e|k5|`LS7NrNX-@YGSJs7Qfb*4qztqmVAJ^N|L` zfYq`lR zIFO?K!*egtLD>KvC4&!Ldviq1{QoO&lnjY@KXm1_aoNuZQy-_jwes~ZO3fZ$dPJe_*>Ub+2Kg$Fn;K{qR1Y5VOc){z|#eIV~hgUJ!@n9@9V86)<)=;t@C7v%`r&{qfz-!Fc=pWT3Nl4R8o&NGDr3n$d!u}wRhu22sm)HBoyL=NyxEc2+3fW3tDhzBD*9ub z#P}T_$rr6noYXbw0I+WY*6KWwLx2W_vHHoLtCs%L;S1xrh;W z!MRTgP49uZ;i!zXCvO->D`c~nCS@5PCBtstKt^_K9OYqdhDHIyAeWYwHsQ?1GpOW$8vt1fv%Y3~6oUgqy7d_P1RbV!1 zD>+K5TG#x|R!8Tf0*4UJ@c$A>h)_81UGfhWDzW3OS;$mz8IZIE{zIIFS;LXZToXz9O z{XLG3ew>yk0Kw!1#NK>nVzaxo)5Cjnn#Shl$~Y}gNJFU3XUUM_NDTeIH;v!*%Ji4X zaBZ+-%1K@gi+Fm|8tunvVWJfdJQ>BaavEp7nNS!MK{kY9*g|)PoSx~S9p^H6XPIjw zMmB>TAU!nm5*WETpB)wPIO(DBn<9=r{bVK0s-!rKN6E@rdYEe0w8fO}*&xyZfZ7Ik zGDHsu>p+veX`HQ0<19W(iVv!NLq$s&Pm`WFJJ>(L+W=>_Ra3S?Mms3t=WLUZ%I1xy zG!Tc3l#Zge9DHCNC-Ci=d@?p;S=KbO>|qLbpfr_&3~KMYNM;DihD;2kAn;7~WV^-` zqPzXo^<^_IQoJySd4Zmj!knc=GD*sk>^ye{&%XGju@h37xFE8f!PAl-5>c1QEG`fS zA8A#Xnurng&8uTrg3^)zi7=mM@oBPVOPJ%hGzUqNnQ?lWj*~HHInAovjH%M7(zeR` zT)+2cqkI|>bkrXG?!}|1kE(W<4~BR9t7|YW*+p07N-g*k z`79mvXUDT2SHrcz+O4&9vbSnn6;6$&RWhnz^GdVfHztaUX<%|`c~IugEq^!Fe4=+7 zcfP)?kcp=0tUO4f_ue+yX|YNo445T3-yqcTgm}Gj;fsf3B7|qeX_ge16|{83gEFGL zE2M0~@iwdc!b*ycj=JO4Dd@Z7&y`o5tS*MV(9?XJ42dGhr7RB|($<)+i!&q@#qEmg zbVUK_xp%z)h<-LZiTsY7u@}tkiVe8!ystqER-TYsNMTk%1QT)JyvR&!ZVhfN<1hmZ zy>Ue=TJblH&+&y9Frg4atehpI^e|1v0qtLppu-cs*sMd|n*0B{sw{s+RhF$C9I~Fz zd>!Ta-~a7jAoLOur_fD;xXT}C4TN6WAJ9ksn?DG*T>%4N4fH;a3{ijh$XdB7wfFTm zh=YZv&Z0(aup*ACkMh}hkscjaraM|PYlFe<-r8X84)6C2N9O;L`dA*YKM3`N`;?Y5 zEh`5GgSru5R19tqwG+tVzbU^Z$SJ8QO48WjZb*)<$ABIEb4 z`+?}OtSa#PX(x!*i>_cfzCm&>z=3a}kG_0LqJjKtva$2OUmu#ocv6Zl4Rr3rlF(h6 zeT;FXW=%o-MN9YUK;W2VGJCW-Ed<3MWQwEKTv)}`8o!!-otq|Xi3tRMt7q>tDGu^7 z8D_a|g~1G*&vtv9WK~+7TeWm0l{XZ&gY`{84mzpe!!j2&hN4B-AiX0#Y}eE7JbRIi z5?Lx*8cuO)RNb}ufp;B552QzPFbXTvyVD(n3{>icwvG{EM~(#Zv$bvMLka>K4a3(ZKEqyw~4@(Nh2g^#VYyce~vV z(7PvRAbSs@(4ik^qvO0Nqa_I>D63>lu>=T0w9h4tP*gy)WWF;IX`Ub5z|chTWD+fz zKm5Z0fH#JM(%zWBpDrWuZP+mJsSBM3?$I8ce-F%HNzZ=h=){Xk@dnT-Jtsolk?OlN zsd-p!FySJV6`3TG#=eCYbCm^H!!3 zSbdK4@GbFdc4`u3w$G?%Snxv^WY9J=gQoKO(5!}2sbOP#`827HF_md)&Wb!cGH?W$ z^L$?Tvs+T^F+2aPp=jsi6;3DF9jJ`))pYih21gbs4^A>Qu}JW2Qq7TexCKPz1Yip` zxLrDb!v)7_iTnW^Aqgf?8!H?Wbeg3b!7jlNq@}I5Y?3TKKvY2A^PO`y^d%9HgdAnV zqj5q$TN0+Ey%l8sA{!2H?2Lpr35c88pSRORtHrqs{0PiIDzk{xngAH{-q87|;p$*8 zEzLB3H(VVs%k<7KkcweUXLm~Cj2aawQxDh104C8GmIR(USKx=+2Pa(El2)D>=i^btZ(I*$A_CAkAVL%47m=E1pGLM$b`8?9VLBl|RO#hqES3a-Ai<<=P;4gCrM-(_ zODy49ikoF@aLIyRx)sdZ5QG}Nn2gzh55=05TaG~LLEUws3!_OsIw1<7lr?V`e5_I& zS$6glBE_23!sa!!rwl5R^g7rDmA!}OJ=z4bW`J%k#eN9IRdHXd^)CtLL#P`V_xQoqcuW|j znYe#;j00p4TGiBkBEbc|76@KG>Q3hXh!56x7d#)dJq8Dn3AgMbgz##!LDS4awQU>e zDf5?ZI?a3 zA{v{9&c`CVUIdNIWqebuj>GbUpxH>hIS@zOZ3)MB529>Hf6iy@!9I;A!}S3UBlHr? z%&;RB$NDXnTdz=&0-o)oVu*LN?G*+98=<#c}zY$91u;9=0yyb4}o$x z&qny+v;=U8+vpnL0vk|VoU3A#cnSSfNheX*Z-J3dw} zZ4m{IBHiHzg|CEBfkgf-g)F9Ix zaw7T-Xr4s>9o(w_+V5Y@t?JiM7@)hM8uGG6%bEW>2Q=Qy5kMpcD zRX!gb^CFt0S#sU^Ce&nqMK6dO#%>ai&#i_I_btHX?=bq4Cza1Js6!QzBxJuu$!T6z zSn1>BFwK&&Nhx6^C|M3AB1%gQ;M#(Nn>%7GUgB)kRpJVZH-VRuex@Xbd-G}e6^?eD z>yF8F$@P7=oouRX3(Ql??M%AT0vPY(gj#g=u0Hr;cwKMxuKh$dL>>398WM3 zguqWq=KgNSHT2>%DT;KA6Z4jcX;-k+ga_wc(b0?Q7^h;GE1E70H_L`{_AL|tLabF` z%J~e3=9bJT&MbcB4gWHmqxQaNN1 zxy-%sz=mu{aP$A@3-uI=2JJ=;2Z{F6I>+q!0?tg(}2?3SOa86hNcZ%;Hb#w zGfb{xS37=nTqN-rU9S(c1E}yi>fQ)3Ip_J@9LG3FoC|LO)x+(CGvk#>$EE3lM5!PA zyuJhs=LMz0ARANJH_re?Y2wKwKSNVgm#q98cv6}&oz5p!oF)0ZoSgTYIA=taucUVp zv>u8eck4*xcehv9Zr$2g+Zf#C7psGfyZ7#FY^+fb9Q5%UsCs+t-mSa0@2=k>Y_#_; z!-%@^WU>@cnqn1D`lY@-&Zo(@`Qc$YN~nfw*gxC^hC(WojEi^$gRAp_?ikot`O(qD z(So=hOnE*%$R{Nx-6!MH!=>M4`O~yI-jg~t*855VM;_&qym&ze{j`XK|8auY!)fk2tZROu=vFqUj@JwSEvUoq4@wJlw_5LRyDaC)_z7iX@_GgFp*v;=JhIi^D2s+| z5McYf#$OizgVtDfOZwuiY|H|mf*qI5tUiT&)^HgWd2%Syqo;G9Bkm(EO_Y~J;^!QU z%jDzUA&Sj()651yk!ge^`-?pWUW8ZSaYG-*|Es zpO@01_P30kVMe?(pI6c$hvf^7Os~D!*5*u}@m3QkbL8|mImOAB?QArekCW|}$_Usd|ZkO&IJ{4s~{T`jyv0RzMy?tQj`FpKh5T&K2tRZAJ-w; zOS199G{ZKw#)P?BeCLI__`BOiq(_TOwP?}2mo;oOLSU{@vc<_BXanh?$#UM8Hc>tO zlCqBq+?XPM!H^^FG_R0GR8FPThfam5jPw7Zktcj*m^4$brC+^X_mZqktMn96@Q1Jx zkZB^#6p(roDDT&&No6G0i_#~rt1r`0l9h>+X4MD#=|^AeF0;9%OeaZ(>=WflI$Nr@ zr}h19$===<<|oN{6U_1$ul?>l3*-WTQDR{gU0Zdis1kh~J0%%I@9ppzmIa-m$WELE zqd|DXny@Z;0S0%JEQ2lwH&5ERiWfhBs zj--V1J;N!I8p|n@;>)zGOcc*6$di0t^^5dy+54GK#(2f5#|=Zr>a10bq)eQhn-L`| zbDMYZK8BC;yYZIIx0kcGowsT|w&V5k)1;Wh=aG5449o6EwfSB2^VUZ9_xt0OKAj4p zWW{=LMRbC;guqR3C3(ZEXWYeCae*-04od>_;BZ?Kmf27=?h|FlBm#qYq~Z@g8id&n1g6wCPb@Vs?FE!WxUc+1qn)i*h|JLcf zUm)T5?H3z#>IM1KVXWQtPvm|Y*tuY;4d1{}`%nJS6%Dn#Y5@nBi%@jkPHWc!$~0`W z|6Go{EIS904K950F0Eet#7Zl1G4U8E^r05HZfz{&gx{TKf1Dqf&K@oiLUG?Rb(p?G zVj+4(u~+f}G{7CI{Mk)pss7(E!rO;dss(F_3;i?oiDf5S<2`)pONJeC=3cOZw5Y@E|TXRZw zI5Lvn9Sl|uS8p8-?%k&JQwjhVd$sk52n7mCbra{4fj3l1@|eFWj02l!5Bol(`RNMg^{@s73;`-gR) zR^idS*eXWHu1?U8e|EF2yGQdvF>5;w8W3{h0p4(X*P6G+O;mb>lTvO>E%rEX#0^J@ z_szGoiIHx>SGB`=pV@lhWz=P6E(Ut9pl@e?m3tp}Cm7fTi1Kcjy)=Un({g_7o+$f| z$#BEP*nROxc_+=%$8nXwsnnn*Xoz~ z5xIf{>c@0Ul9o6w`*nf=|zq>gD`k8)I<|Jxi*s0#_NQBkHc! zRQ;Q)D5@TgDk)~hiK=y27(qAqbg-M8rkWdBW|-dZB}E!fo>TspT5|_qCB+uZU_onP zhFn<_Ud`}(^Mk7q0RM2FPR5vWug2~RI2IKI1^ea65%RNDaW(gmKbjXsl8LX;k+bWB z#cJRV3I-O;gRAypGHzhBHM^6-=NYHGu{=vE{?I`6lX4W#lBapGn;boP=T&)h93zQu zQtZqpRXUsa&Dn3e$!rpje5^g1Byk2Fk8Bx!b9gT*iW?qFyN5-Z90u5UlES_D!l#L+ zWOey6r3@H4T05?_0btq!DS4gNK7Wz;1GFC-IC)ki{<3$;9KEJ@wtnXS+D4d`LiNRb z8fVZSV8LaG+{f+m>3lLdf0Sn#=?z}J?eg*Sy@^?-ir#@=wgoIapcZv!0Uf}1m#AoZTv-I8C}9E>f-Xs`u-%SW#q#0zN4C7=7LXa{x|YUJdC7qEbCPs()KPV2pelo z5U&O{6%@r&d-o#Kw~n-);(CHF=0^$U*awO}z_PS;lw{Rj{uqWUWKL3A&71HvWdSBxzhb z#Os2+>N<@3QWF;L!vb;P3WUPPy^m`WTLQTFBo-eoG5Z~XSWboVui7bGmRIVSbVdxx zj4X$e|Bx*pYe8?j%DVi?Ot!d(GaMr*PAt*ykFoy=#XOP^WxSye#Q~O=JX9?$&cYnRZyg(A>xJqCu=lqYf8@eC zuFDBCB#{rwwuO;57S>g}ble0d$MQoYG4ggKhR^AdJ0>`+CyOL34F1fXo3IteiySRz zvKvBwH>`pb)5)o9v7)6*7P_8BB|9peM)lbbVMh*SalWEx$+9FlHYHYDoZNJ`nYg23 z!35AObHuIKfa@DSC%>$wSlbsmdE%1+(~s0?V?aRxk79%O!KCmP=7GGX*!f312G>e5 zj{RaZ8e}yk-@2ucPEAGwW$gzc6{+e0)I%j$W&*?2Rv0rJx3a{hP8Zr#bF74j@)`)Y zz;B!!0pCKL1TRqK9K;tLrGAxxd2C2T$c2nY z_zck5MkEfom&rJff?6wu9L~u{aA>b>l?zv3)uIRhbf54SuD?dXI!yr-5Yq>7FKRYe zCYSIoRy_dBC45Sh3SLg0Ap00nb;rdQEvS%r@ttk}5)>O)k6c^IU0Q5Upk#MytX^Qj zQV$nQz!e)U*a0aJp5YCh3*!qWhA?SA`3B?07no_76Z+-F+1V&9=HSNrz*Ocz)n+XV zKl>&u?@MS9bLOvwMM99IdTYJ5)gRXORydh=&?yUpz})3t!}x)F*xIe!BFUwRtMe+Y zA*0x%OQ(F6quur%M55?fZliTYkEXPvs7hx9J7J5F>FP0Ht;V}^ax(~Oc>LR?_r+r? zwjR68$;}`!<-ih_*Emt?Suli#Gr=2-G+>EkK@&)%Gp0jkwYsh3RKtS|6kUbnb7?u< ztau}?aoAuQ=&qw|K8<+K$$KoGp$GP&I_CL|x*9ldjNDTfPUFCCw74x*Aa!n}qg}e` zuDywF2VPqpgSlCVl|4dgojeWqy!W}S8VM_=ql2(?l-U3;l_8+UxY-^(8(Ap48x4d#OG zDz*nuIpHNa@vvu#j9%#0THo%rYSkRJd1T#=*KO~CB%Y;SC~Fo}Q{TX0dkYu6Rr($N ze5(c1twpJqDuY&*&mbJ+ZH`C<-)~(Ph1aTL)MGr0+m(f+O@}FGr16#pX3A?$u|e8Qh(JG-MF%AOjf8Q)+bFL&@!{SBWB^EqP^~JRU(hQpZe_qBmsng&D86@?7Spr10EVqypw8l3%BnKjLMEEE|0*rFl)a#3 zu><-@YFoIA2W@kPdsF0%+OG1pau>kWSl%6vAuNW5t-d|GgKvYxenj_rZ7p7Hy`K@cjs zpmrbO%+yX_8@Z%&%J)B_o2M@6`w?yi7*$@^e%+Ryqc7rO43>1ijtzfE&|65MLBp7( zy5OWtSC_Sbpfs<6HYs|gc1Uu14XIQ`Wto*l2o=~p;o86`Zn5sWVY;H2dB)(C`>9Lh zMrSqB0iIc2d&6rs(lU>W1}sat_aMq9I5c_%`UMbD4ORxKCOB!f=8(dFX*wNNf?w=4 z$jY%OGH2qt_paCO_X?ymwgAQ!+>7}4kl&Potwc~?EE0T$bjEhUw92^;rKpflF>X>jnpHJmOelm zy$2y@Z0oCaHZ3!dZ{f;f z-K=KL@}skC^jC{ELRxrx;qEu@mz>^bgZ{jL!G;FtMISm%=8d^uWaF|b%p}QwXo&R#My0(7n_MN*M_t?<45c)5YcLcktp9+q<&;8?o zb^tJt00Y(n!m`C6hfQ7y0AL2E(}n|6$yDvbyYIJ29a)I)7LRfeyu&HK(Wbw76Tz4suR#Rx?v<~=k~ z3Zo>Db9wtgmeYPZ9=5djRt%GB3gs^00OcD4CizhcMVfl_JcKxEV|N9-T`;=nF_kV} z&&{NUFWC4#4&{@?g|u0e`-M9}8D51zlv_I9zN*myf7@nTf%YmdapNd$XvhGmC4pEy zp5vQ(b;R}~??&0ssUN%A4DS7;G#-?%R_x`ri-FnjIb(>VT{wdBA=y3ErIgL)7&?Q? zZl*7xUYhQ}eCDyJHE!11b84u!3+Hwpoo*QJYMYZHo~>-0WYum$xtJA1yNi8UB)E9Q zZuIT4v?acxTi(4AYmVgoL3smOcO`^m?1;R8Q7Aul3LwOE)LO=1m(lGo&VB@!4M|SQ zHQW)v`z8{R+aW+X2!ojbL-*=@mb}1i^SUfMJTbv8{L3T*IO-B=DIo}i(&ZzRSDNWa zF9NKzO(ZH`aB&8Xv4`ZTeCj93`7$Fq&4$vwn=7&Db_53G5T)6yi+LVG4ZxG+JTjf1 zB{bbFdG9;wKi0Oy^xIP zfw_B^DUrjmY|Z-gGNt*3ECD-^DSE8Rqc>$h?T6xM(u76%CA@=iWZ_hnPKUOT{K_-1 z*^fvU)$v<#hwrE#W=DUVA0VI}#PN&#fY14>A+_dEMHnY%-_ro;SH(G^*(fm^GI5#N z{9uxf%=Syk5;&HmTxC+=7@5Qz12V)L+W=vDz_XG1atC1lyC`FEGgJtE+U~Pxv+O5(j-xsQUiV)I(z``otWUIm_ z-}i9exP&h=3PoX&cmrj*4UI3*XzlKq*gmVX7!FLUECpq5J>VstrR6m zEAd{F$@58!iG`+su$d3k~k_`w$%aO`<7^O)sjqI*pdJY1|TAM!=v|*+zi$ zYwb~yfmaCDAa${F*?<>lXl-NWkYk>%@YFLd_30xy8*&TX01EDQlCq4C5`T+m=S_4g zms&Yk;qJAb!Vf;{BIlPpQJm=6=K$@An>&De$v@~HQVVr$M{oI~z}udDK{fnN%V%iW zF3!wO#VRWo0zBnbHQ%sOH@tUelg)rXPj+(cyE1^52p<8t*RW-*1FOD>ccnK zv301W-h<96c+r$z(Vr+Vn9?yYpS-`!k08H1wn@_(eJ(bW2guO`u13b|j?5@)DlFJ6 zs=hPb=;vL|OZF{kr{B_Hns0IBacRF31-CcWDi2LWRb(IHX*QiN^CQMpOC9RKuOn&i zFRBA@BEvf@>W7YGx_)#8(l^0Aecfb+ZwDO7N8uMF=Iux9t$0LqFpH@(paj@}fclW& zTnlY@58~#%bGp{6;s};k1#DC~-`~1$A-^t>RiJe#6BklTjl1x~I~MHBPp!!gr~zMw zN@pJDMG?`b72C3_L2{cktCDP-AlW%xG-nS$x;xQ`4NAN91CmVZ41gv+3evG07D>`O z9*=t^R1@>od~P<@H#Y7JHbNg#?f%l>Zp=|J>FLe(G%`B!6x@S)S#`RGmG}RA*L$!b z4@l7>CoSb(cyk<=2FbKB7;`v@%VWdCIhb?mGwWxJQrv69mv$t)H_Fd4$}#Be*UY2c zFFCdNd08b>MYWGi7H}SUU1>(2=RQRqrKd@zjXRTH5Tat`KgjPxZz}yhZqU8*{kclo z-+t@RZLV6q{q|hZ%WA}P>D{?*&Fj6nmMu6e+o0ygtk$~o?pVUfZ+As+*p1bsy!GrK zcK6r^Cwuqm-xSehc~##;Hz{+cO|Pscn5`|%zhz1J5&JuZ-+>^Bl>E`V@jEIL8`<}S zhX=JgV(Uk4`7$14CgnlXCEGYe36q6~BtBt%DVF+X?>L`N#<(*gKk=~Sd2~ok;^B!@ zNQ&yK81cWsZNu^+F$6Y^uxwVmLByneol!*BlEl4|&~ua9-1L5lVJmJbKQO7iSQDlAP17cCX3{n6uqTQY1zv+i>qe^ys-9 zp|`?n5pj=><20k=_w8hzA}jwWpUx&pl`NZSTGCYuT-d^g72sy`5~{bg7SMb<)i-Rz z*iKxPG%i5CM{XezdOp6UE@4@e(x~GGEt-qyBLD?APQ^>}KZxF{XUpCWC8Q>jG zsC&3-r8y-eK`me)HJ_Yon(nE_JYg1|CA?z@?9aaK_i{@wq6>#~0}UIBkCS+u6urYV znT&_sZ?Iu&pwe^@mhs+m5Ol@aAI>yj1ZK(=Vx@LieT@f@;&$G>7RP1PL(b=+tEM(+ zq7Gx|47bdfyUCS_+dKZ_iMyKmss>@zuv#bb;;C~}e3O#~BY9+*R(WxLU2Xo>c3I_S zdIEglstn(x9~>Ui<@I6e6mdOIk6+A{eVGw$@9jS0%d-oun>r}y$jf8nwxq{&_qIo~31Rcp17AUg5Bc%|S;fl)dn(n1>a3o^+o*4k(+gS{z$++ywsC7; z<&DWsTp{yr)8wo+C{Z1QJpJ!v$%GwqjW3t7D7Yy>Exd;kEvuChrd{UuZ%k8&@qPC;s1gY4J>MDKxww+*=jD=4-#vMq(&K;@g;10r{8 z9myR0gT<_lzE!q^+smB=*DvF$N(%4nw?nlIUNZgELajPlQRBT*90DP-5u$&?PIOOa zmM)Rd7R`lWO11Xs6qm~su?QGys{zYgt-cBil_OSAi$V*40Jb>!1fx%2pkO{_S^Px- zdvC~aGqfB{KSSAM)@rd4cnj^5Pi!_sWGZ`^pBag*1>d7i0~Z&)Sskp^{KOAa&{Gh%#zluzc*#U*rY)wlXUTtY`bVjKY_}1T%)v?co1ElBQ|w>H_0lSNIcpf0>_!djxL9hVU}Y z%J0s$)nY6cdNrM`QbYi}Qa>`Qg8`aHv(m9&a?Nx*RAB`Ly%NNyUQK6f$XtR?!OEga zYuKF&Abrz$m}WN2%CZkzFzN?(4m@@N5&%lx)aP4T2{;nRAK#i@y?_mHwOax_HF#vi zG;^WD4KfoP5E--Uq)+bxzQyFKnv06gAaYymr{Dy~*pjZS2sh#h6z6zy@`@Kw4QEU2uV3E!cX z1J>gdmYUANi=hScER`3l4YU&NP?QDIShNdE^_V_~FfQw@qXwTRe*7z+DWb^trl?|* zlNjZ+YodD@tS8}RdkKlG!E1Q57Eh{Fv$(L$xQXqlm}Ij9x5vapxQTj^zgs2#!ylse z92jFXYJqEMpV}E>7mnjXGHzNr;9>zKc!~4vM5zjf3so12pVwI`v1uG&r0SI{(N^hG zJ46+%Y0fOKOqu#_=&+>sun^!Bc(A$dZ2iov4zRFm&C?WAc~&MP9!7-$b-C?+D8*OO81tTF&_Sj@Q+gCJ>(Qtc!fY6c?%bkr%u{s(irH4%DQZ1j^m<4lt};0+x<>= z>DwQD_Tz8f@9%4JVo#<23s{@1A~6iIutYwQ-vbN08}0AY?w=`9OuP8Jh+2srczTs{ zwNUI6P4iwyIX=Aj$ztY3R(d~v%F--jl(NM8-|O*Jb>)d9wDgKBsPUx4mLTAFF0Prn zuc<^n*Op?Chulm<0{(*^A5%(A&i( z<1^FQSu(3;UD2gXmnZ)G{QP_od;@PfR_oI-9xtB9TVBQ6^@?A7;a4Qe8W^*t;0avQ zX`|G-F}G`_%Cb@Fjxlt%8DWpdXyt{&D6bMfTF|O|zA#ccB`mF(TZkCccjGa5v;XFc zFY0fmQ?nk{Dob<6+_h)tG!(K$5Wb;aseKSTdhU$J{O=cE@W0b3|67*YBG2yq3q+;o zCbz3Y5M`}mR@ZmzLyDlb(*bGr8q7J^#Rd9Ys{&}1JYe1ffi0+ZH`uV*@#ON|E^ue?WlS_1f?OHb zkIcH`Pk~C-v^@fRSXve6g%svQzle5^J_!_2j@yF;%m7mx7;I%J$t)>k;&%1GfJxYW z^-yZ*Py)DG9Ff9?{QX#n5H;h5+p6i~w4n+;P76^2J%cO{To59t_YR+#h4T z`u)C%`lcfd3$4pyNsGEAT>F(C|dF1zQT932a3z3Q5Gcxgi9=3hXnT~Uqo&hszsOznau!@n81 z1-j4c zn$*@tXaw0JI-?u&>SRV{N~<+Ac=}?E0K>EXI$?Nv2j3;3eegu=oV=RO4xKB}#uV12 zh;sf{)7ev;O&0rz~|=gZ+gR;+DyA%$<$X68FEk zg9|||g6t@Wb)`Fp8I-iN)t60@#Rro#J91P7_Cy1rPYIdjpYrKXJT#GbYLn@GBfjMa z+L}j25>rSd8aDYMp@YG39d}rkMFgd0c4;GK1u!0u7oeD$=uP8yJ$O%HE^a+WQsq@V z=}nVqUf_)Aidk1qS&JwNVe)yjpt=!LvUtEP>xI<|hf*g(EWR7q<^=`yR@O;fwPhn z`@yF_LQtLh>T@M*o5oB6rOC3H1Qm0R059R&TR$VN%>=e;9+-|A6@=CEkxBatcZ6!D zvz_hdLFLb{SXs>Cu=WNQ=+@=Ozw}`(c7&R=rlyD0FIsUTQf(A;d+^gDmvh#k10^W* z)0BOS?I*RxkgicY#7+b#YTa=aJJSptlaJe;*5f;52ti}moKlh-8|5Ly$pF91A#24k z&{|)K*|)klhm~akQg;B4Ib?6&f&pZxB%oHLXuT?&{UzuA5=Wjx_*-`)iPW}n9KI^# z0Vd_pvD3hvE5OaZFfy0yfmm2EPe}VDjmes61ACO6oV8Hz@2m%Xszt^1xWGcLfXMY+ z;#6ox#MF$|omI^5U=aY)Egt984fv|dli-Td^-ICH^5L_czVPBxF7!Apbst6^WlsiSr54%=vdAM#{ z+ptKn8a-!BX*K}@XoDgQ+p;|dEn^UzoMRZ#Vr6xMu*m)4E#Z!a;+H3R+^j&aT@CO% z@DpIX_W)olNjebWi0%T+ma+uHk^_QgQN=j7>Pc@sx}Zs~?L5dFJc=ii1n1KO(qI}; z()hD^iu25_gmuYriJ!&=>t^-^!yx?cx&q*FK|_xH!2?eF*TPq!OS zX2KYuWn=0a)oUp2{Z_@q2zW5%TR(LLHy4P_BOA;TQBm1%G7fVQ9X{z10GLs}OD}ywK>0vrD+E6@6tMesOFn&shd^Ac?&a<#IH)|a)jXg(I1E5iHf8ZSxv zX7Fy6;$PUeVOawu#FTe}xVk#b2n5s9jwk|z_5`XiOXLee5~F2JuxMq_HiXYNCi-Pk zVjG_RkSL>~_ZMx{KkH7lpSWloZPgN1fTtCeyoXuXY%!7Hm9G-<1 z#DvlK+<=O7mKcoHA>*~SnnjMPzJ|s~cXS#XYuY4#+O+4-%;4SOp&vb}KH~YLO5sAL ze^;dUhS>3%cj67ahh~7G)kRqof+)rFBgGa1xK03G&BvyHvv>+CQVCrc$xMQRUNA?ax zmdk3Cf9%Sj0vn?MV&2bk-N4BQ|D_T9&~v-LuzT^ zgc9oMe=;6&A}1e@X|Dem$tpiCir7-Z_^= z;vdrR6iuUPg~58IsN%k(m8n%>m7veYkK68B^N$W2%{F^MucP0 zOZxo4go#uTV;Jfb4RDB}i28ygb#HVz*QfpS1+yMZyRJO~%7d@R1JEJs4o-vld~|ro zm$h(}fk4Oxsbm(8;|kO(Z2rQDM1BO={vB%;3={lLaRy*+NLOjuEo{!}PYYWXa&uLe zRb1f2#FUm7mQ|jO5*P`@0#?w@6u|+J zCM>F?%SsK#t8Nz&!2UNq2_t#WRS4^A#8)mqPRmhTjIm!?aFv>Nj+BzEt@j>u{3e&k zUb>*R#J%(BK|XnS{yIxX`Iz&FDQt=uxyHh|6{#7;jXDK` zm)vamW80|kD?B9F3zjb$iOU{2A*mdG!+M^>RDDq@& zf8*)?!-2*C!RwTtQLj7J_6TetTgIwF^{_{T4b9yj(MZn+-0&9gK^L>M*6SgP{uXzn$CZ)4vFWfm7F zlf)Z^H}mBzNk+%LgLpE@a~~0F`&-^|*!%a?Gh1EfC-RoiFiO<^V3#Fy`dux%#vU~fp zu41aE+4&n=pSIvM?0iTVxdeBlyh~bU6>EQ z2A%_v?zaqkn=@7^8fyBZCfQii_!P3}*$^;{+u+)SfFU{L9v6EL3kOedGI!oWJJM!(aIkYR{#1H4T^V;tRoSEOx_6u`;Kf1Bs`Qabl>@NN2v*r7npAR4W z_>bP~@4x-|pMLZ0`|tD|y>N>8L214*(GLe(a6tZW@R>aE5_)Fez>;5@9*(H-1^!>{`H9dS?8DQ{Bm7hG63sOxQG>c((Ek;>^9fA&2?^b zo!eaJ4i~x0MecHuyIkb%6U$oug7OF`SyN;lY?d6q17~un83kzao4y2fBD3Yn<$1#9 z8TG+W<_BOVQc`^h3)TZCD4@;)2G*8*V7q+$d~Y`?^9i5B(z{3z^mQM1Axusa6UDQ% zUnP^|sEDV?9~)-#Xqfc$jqG(Dr`s-{&L@-eM|qYdBkWRbf5PNKaCouUPcS0voo6F| z4RyjA&WI73smZS49o-^xh_-i?N;1Zl5!qInW6@8K;?Z#uUQ4@eKkmY$^}L$7QFYu$ zuGtz~2~d1vraI!XYPK?(q)AqhuE3`&+7EDum|ak_tm10Uhee|DJR2Psd6xb%(R+xk z^8#erfLIX4%eRI-yd=o2(faj_oVZTc32xz-^Vdbo$MvnKl$8{CzX>(e$`1yKEE#ZlC8qYJI#G$%Jme%rmq5>JjOIkG|OD z8&L#SGI+h)xZ_YBE_tB|T{0;aSw0`r8k^6ck@tv(!NfuQff-cPS#fl}F=2JSjk6bz z$3;?N!I2D8X4d=dDsDgf4eqWDsJBiiuxgNuc+@qOoE`6k4=;;Pl;x3$ zYIm)2s2U-O(XGdjyL=Rla_5H$VZBL{#0^5;9X?&D$BgSzm6xnDW_w;LNl^`JmYm)z zk`bk`7o~Lp-|^tqqo$^7`*&Be`}u4UEsEQ}Vd7lmz0q;C1WjIizk!E&y2Q36rMQhl zrJHfDcHx1KawIJe?jrDmF?A;3KWSV}U~Wqmi6(o0!sRVg8$q(WQ zFH&455^#82;gMDki!?b5-q$TLN)aM@7b!*DhKSp4^tw!n-qul)Rrk#fvS(H43Ey{u zJ>9$*CA|mHX;PGFo`K_WVln??T#R6|998pzlk|J8LMiRy4Q7(@@F>p?B}hH^x{Vus(<;297_u2>4C2aN z&KT4pjb?8?Ex$^N^f0Vk$9qr_bsne1RyJ0?6MU}M-J2gCrs0i8YT^v)H5N(m70J4T z7j@zm`s$m;5VO|q9dhHVX8cztk9KASGZNos@Ud3lx(?Gj1<>=Ud`s!a9$=wn+|04mEd%%!%48J zpA-eQVZbUW!K|eaiPDT)*9UEe+4Z4h(u_!Cq5fU;^KdobjGMR0nJ&3S$ppriwQ7r` znM#jV`SNRRXNSnd*=cfTSfdG4POKqo&>lUqB*zDApC@NR+5w9?PR4T_+ayPigH2xh zA(+VccA8|>cSyf`Kxp*4XWI~*Yx@UudxgV-gGAe{qe8nAY$#BQ5Mh@=f3tj&j?N=e z!~J%95A>6-l;c@I7BA6I3Y$oJc$gGqlj7+Pg1Q*Vc9b{COqEupDJ{4f118|tYq&i$ z9@)>yKjGPK;ysL9On>h|lpbYyLAJNLNh=(gw<+946)=G-3})HHC7r8G$|~`0yul;k zuz`6?pw@Y@yn~gb7B)!@F~Xs#hyl3?bAifX zalvT@u*?2f&Ek93Ekti63LrkL=@lY&!?E)u&C>F?;en${un1d`%Nt~37PcMSiiqXp z_4J++ap3WdhzU3`zX2DcjW)QSdY zhU3RBEp5JqPeK!`X-*e$%76}CPR#jqdfvy4UO7BUlKz0g34d%2*8DitrbuU%9ySoj zyqUQApset^Q0NH?+U1eQIKCIjMfMmA;EUC6XT$S;LZ6m_!i!5qcQeN;>i1Bfks@oKXsFOBP>3)ov>!lK;T>A78RdPGj! z%L^lDJPOW$Se$yu9M)R0TFkfK>Ii6Di^?@At92%r0kDEx>ybo~NDS`X+Wkkzi2LoE zCm9UpI6aV*2JE%U4WJ4q%NS2>_DqCrb{~E2;~p)tVJ@06e!ho6v1{5S{#dHqG)HmS z3h`98mc`;HlEP{knAKZy{_hfY>&6%`+b{F{q%@QCB=O8icB=zs>PdbU{E{@8Gvsuq zX^9AWQywP=adu>e=4MC!zPV&{mlAG{E?syu)eXzwf{ll&a#CJy2<*_!hTdA}M*C~) zYPFNy^nHX(y7gPh-d-~cNVnEb&uot=&m+q$}0r^*Wj4HZQZJIKf9 z%Zhfov=ry_kcO)ry^Ez>?Jfa%d`=v~W{nOkzrOtztI0n%&BWmqyN+XCLZ&|YAR;AF zkBx~mP0{@bxwh6^Zawd7ExD0>93^`gfAX&6lT!lA)nx(@OI!N|L#LUk^CF-atZGr6 z?-2)9y2#TyeDDGtIeCp10oDh_gUqg`k2+)Y?v-5ZdNNO>XUOqRT3qYsG zE-9gb9hPB_cLMXiOVRN(iQ%?gwh5jrDjx3yhES)~N(dUR#Zb2djs|i=l41r~EGh5C zqEam`Ex`_Wj|X+f{=IB$Cw7$HaA;OT8 zv`3^KJG%j!211G8R)13^GW}X%E8js=+F@n)kq9!8Wr6`>>EiFR4dcvfxZVJuS7q!}W_@yYWNC{r&#< z+i5;tTIsiJ>ww(A``Ko$51eGQ(CL3XPA0V zdrL5%3k`L9#$pEnAL-F1vR1g4K-ngrGf<MZp>IIy^V_Iz zBpxUk8ze56jFUq9R?C`53Z|Y$b29MHTcGg=t^p~o{v+D-YcyH&`%Zeq^d-_uBs5q1 zGCx9^O#Q^Z3AKp9m>>n(#F&(BaQvWU;`+=|LJ`;dmFWZ`?bK=3 zc)?L(P*sKF(>R^rK9}a6rB&<2V<*^yyg{rEh=uA2o&~hwYF{t}A;310@hF;r^bEWK z^saj+Otxk-J{mIB1>TNAA$bdG*y-YB=r2TDIW+Bv^Tk0IFk@Hga^x&to#>=_cb=VH zU2RP2{xReo&ZAijn7S% zP+FTY2TrDW2EpR1>ycz%E|SwUIU_|t%%7|%^tjDlewq}M_BIR@*@U%=_B*P(1iddtpYku!)%Yq26I2GI42T#`X zYxJc-TUyUE@4#paTu1-yC^=lB^rX4|xN+a_~8m|s`L643JdG~qN`?Q5F z?A4a?`bB0XD|J=(dU9DlCY;=7T-`7Tz%yM6F?KVF2L z@JZ`m_M-LKuB8!W@KXr07tGd0XSnIJelUp9y1v&gWWqhl3Rqd_UUrxb7lpSQ*&)PT z>|I%;0E+OX1rsh8xBfn^tA?mwHswh=n|VP;nv&}VWo4zEU;0kBg*HO&(@)6Z1}C%0 zixn-#tHl+Q(~L5?FR~{HJ2X*i9?x<70*=CGsqp?q_6^mRyo(uoROy~VqrW_Jj-T-Xz9z}nqKP(#ElY{ml2**!iG z77aNSU}U5p4ve;Ak;Tw3v9h^Hja+t07trd0(nMjvgw95!+>?1zaJ**I>FbJ*FyodCp9L+<%^&2;G-zNKJ zkW2syd5SP#k})!|bd>tbJVr2$ryJJ|mY8T9eV{mxd3KWJXIZpFZYKUj$JN-B&7LEx zW=E>*kCW4fIGwLal=d_A9EnObPEH@q3*A$b-n!+TH494N0~gUfgnZVYDrRmNWPgh% z<}4vCitr{E^)Xsufn)58Et1v;DHA`@SHzapD%}~N=h~c5yX;nU@>40{N}9>1pZw0X zKfnIF(yrAg{_Ee^S^I-)|L=eLYuEnjHJ-cs6ixSNURL?^2iN}mT6OK(wUcYte(>Ao zpQMv(*M4yDSO0_b*RK8f5B}C~Ub{BEcI|h5?a!`V`@ugt`L%0D*REavty7$ZIlOl5 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 A6951J literal 0 HcmV?d00001 diff --git a/MikroNUC/20260628/20260628060001-Config.rsc b/MikroNUC/20260628/20260628060001-Config.rsc new file mode 100644 index 0000000..0c3bc84 --- /dev/null +++ b/MikroNUC/20260628/20260628060001-Config.rsc @@ -0,0 +1,4826 @@ +# 2026-06-28 06:00:03 by RouterOS 7.23.1 +# system id = NmJcEM+8XAP +# +/caps-man channel +add band=2ghz-g/n extension-channel=disabled frequency=2422 name=2G-3 \ + reselect-interval=20m save-selected=yes +add band=5ghz-a/n/ac extension-channel=XXXX frequency=5660 name=5G-5660 \ + save-selected=yes +add band=5ghz-a/n/ac extension-channel=XXXXXXXX frequency=5160,5200,5280 \ + name=5G-5160-5280 reselect-interval=30m save-selected=yes +add band=5ghz-n/ac extension-channel=XXXX frequency=5180 name=5G-36 \ + reselect-interval=30m save-selected=yes +add band=5ghz-n/ac extension-channel=XXXX frequency=5200 name=5G-40 \ + reselect-interval=30m save-selected=yes +add band=5ghz-n/ac extension-channel=XXXX frequency=5220 name=5G-44 \ + reselect-interval=30m save-selected=yes +add band=5ghz-n/ac extension-channel=XXXX frequency=5240 name=5G-48 \ + reselect-interval=30m save-selected=yes +add band=2ghz-g/n extension-channel=disabled frequency=2442 name=2G-7 \ + reselect-interval=20m save-selected=yes +add band=2ghz-g/n extension-channel=disabled frequency=2462 name=2G-11 \ + reselect-interval=20m save-selected=yes +/interface ethernet +set [ find default-name=ether1 ] disable-running-check=no name=ether2 +/interface veth +add address=172.19.77.106/30 container-mac-address=5E:0C:7C:E8:98:12 dhcp=no \ + gateway=172.19.77.105 gateway6="" mac-address=5E:0C:7C:E8:98:11 name=\ + veth-AWG-VDSina +/interface wireguard +add listen-port=13822 mtu=1420 name=wg-AWG-VDSina +/caps-man security +add authentication-types=wpa2-psk encryption=aes-ccm group-encryption=aes-ccm \ + group-key-update=30m name=xs-Sec +add authentication-types=wpa2-psk encryption=aes-ccm group-encryption=aes-ccm \ + group-key-update=30m name=WPA2 +/disk +add slot=ram tmpfs-max-size=100000000 type=tmpfs +/interface list +add include=dynamic,static name=LAN +add exclude=LAN name=WAN +add exclude=LAN include=WAN name=Internet +/interface wireless security-profiles +set [ find default=yes ] supplicant-identity=MikroMIC +/iot wiliot servers +set *1 address=mqtt.us-east-2.prod.wiliot.cloud name="Wiliot US East" +/ip dhcp-server option +add code=12 name=HostName +add code=119 force=yes name=domain-search-list value="0x03'WORKGROUP'0x00" +/ip dhcp-server option sets +add name=P1505n options=HostName +add name=StartLife1 options=HostName +add name=amsterDev options=HostName +add name=MiTV options=HostName +add name=miller options=HostName +add name=amster options=HostName +add name=garage options=HostName +add name=domain-search-set options=domain-search-list +/ip pool +add name=dhcp ranges=10.30.30.150-10.30.30.200 +/system logging action +add email-start-tls=yes email-to=vados@vados.ru name=email target=email +/system script +add dont-require-permissions=no name=test owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + \r\ + \n#:local ScriptName [ :jobname ];\r\ + \n:local expSrc;\r\ + \n:local FileName;\r\ + \n:local FilePath;\r\ + \n:local DirPath \"tmpfs/\";\r\ + \n:global WaitForFile;\r\ + \n:global IfThenElse;\r\ + \n\r\ + \n:foreach Script in=([ /system/script find where name~\"Amster-GlobalConf\ + *\" ]) do={\r\ + \n:local ScriptVal [ /system/script/get \$Script ];\r\ + \n\r\ + \n:local expName [ (\$ScriptVal->\"name\") ];\r\ + \n#[/system/script get \$expScript name];\r\ + \n:delay 2s;\r\ + \n\r\ + \n:set expSrc [/system/script print where name=\$expName];\r\ + \n/system/script print where name=\$expName;\r\ + \n#[:put [/system/script get \$expScript source]];\r\ + \n:put [\$expSrc];\r\ + \n:delay 2s;\r\ + \n:set FileName \$expName;\r\ + \n:set FilePath (\$DirPath . \$FileName);\r\ + \n\r\ + \n#:log warning \"\\n----------------------\\n\\\$expSrc = \$expSrc\\n____\ + ______________________________\";\r\ + \n:log info \"\\n\\\$FileName = '\$FileName'\";\r\ + \n:log info \"\\n\\\$FilePath = \$FilePath\";\r\ + \n#:log warning {/system/script/get source=[/system/script/get \$expScript\ + \_source];};\r\ + \n\r\ + \n#\$[/system script get [/system script find name=\"\$FileName\"] source]\ + \r\ + \n\r\ + \n#:local gettingScript \"\$FileName\";\r\ + \n#:execute script=\"[[:parse \\\"[:parse { :put (\"\$[/system script get \ + [/system script find name=\$FileName] source]\"); } \\\"]]\"; \\\r\ + \n\r\ + \n#:execute script={:put \"[:parse \\\"[:parse [/system/script get [/syste\ + m/script find name=\$gettingScript] source];\\\"]]\";} \\\r\ + \n# file=(\$FilePath . \".rsc\\00\");\r\ + \n:do {\r\ + \n:local fName (\$FilePath . \".rsc\");\r\ + \n#:delay 3s;\r\ + \n#/file add name=\$fName;\r\ + \n:put [:tostr [\$expSrc]];\r\ + \n/file set \$fName contents=[:put [\$expSrc]];\r\ + \n:delay 3s;\r\ + \n\$WaitForFile \$fName;\r\ + \n(\$FilePath . \".rsc\");\r\ + \n#:delay 2s;\r\ + \n\r\ + \n} on-error={\r\ + \n:log error \"\\nERROR \\\$FilePath = \$FilePath\";\r\ + \n}\r\ + \n#\$WaitForFile (\$FilePath . \".rsc\");\r\ + \n#:execute script={ :put [/system/script get [:put \$FileName] source]; }\ + ;\r\ + \n#/system/script export \$Sscript file=\$FilePath;\r\ + \n#/system/script/export [:put [/system/script/get \$expScript source]] fi\ + le=\$FilePath;\r\ + \n#:execute script={ :put [/system/script/get \$expScript source]; } \\\r\ + \n\r\ + \n#:log warning \"\\n\\\$expScript = \$expScript\";\r\ + \n# \\\r\ + \n# file=(\$FilePath . \".rsc\\00\");\r\ + \n#script={:put [/system/script/get \$FileName source];} file=(\$FilePath\ + \_. \".rsc\\00\");\r\ + \n#/system script export \$src file=(\$FilePath . \$FileName);\r\ + \n}\r\ + \n\r\ + \n:log warning \"\\nComplete!\";\r\ + \n\r\ + \n # :execute script={ :put [ /system/script/get Amster-GlobalConfig sou\ + rce ]; } \\\r\ + \n # file=(\$FilePath . \".conf\\00\");\r\ + \n # \$WaitForFile (\$FilePath . \".conf\");\r\ + \n # :onerror Err {\r\ + \n # /tool/fetch upload=yes url=(\$BackupUploadUrl . \"/\" . \$FileName\ + \_. \".conf\") \\\r\ + \n # user=\$BackupUploadUser password=\$BackupUploadPass src-path=(\ + \$FilePath . \".conf\");\r\ + \n # :set ConfigFile [ /file/get (\$FilePath . \".conf\") ];\r\ + \n # :set (\$ConfigFile->\"name\") (\$FileName . \".conf\");\r\ + \n " +add dont-require-permissions=no name=test1 owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\ + \r\ + \n#:local runningVer [:find [/system/resource/get version] \"(\"];\r\ + \n\r\ + \n:local runningChannel;\r\ + \n:local runningVersion;\r\ + \n:local ParseCustom;\r\ + \n\r\ + \n:set ParseCustom do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Start [:find [ \$Str ] [:tostr \$2]];\r\ + \n :local End [ :tostr \$3 ];\r\ + \n :local Num [ :tonum \$4 ];\r\ + \n :local Return;\r\ + \n\r\ + \n :if ([:len \$Num] = 0) do={:set Num \"0\"}\r\ + \n :local rest [:pick \$Str (\$Start+\$Num) [:len \$Str]]; \r\ + \n :if ([:len \$End ] = 0) do={:set Return [:pick \$Str \$Num \$Start]} e\ + lse={\r\ + \n :set End [:find \$rest \$End];\r\ + \n :set Return [:pick \$rest 0 \$End];\r\ + \n }\r\ + \n:return \$Return;\r\ + \n}\r\ + \n \r\ + \n:global runningChannel [\$ParseCustom [/system/resource/get version] \"(\ + \" \")\" 1];\r\ + \n:log warning [\$runningChannel];\r\ + \n:set runningVersion [\$ParseCustom [/system/resource/get version] \" \" \ + \")\" 2];\r\ + \n:log warning [\$runningVersion];\r\ + \n\r\ + \n\r\ + \n\r\ + \n# :if ([:len \$open] = 0) do={\r\ + \n# :log error \"\$SMP Could not extract installed OS channel from vers\ + ion string: `\$runningOsAndChannel`.\";\r\ + \n# :error \"\$SMP error, check logs\";\r\ + \n# }\r\ + \n# :local rest [:pick \$runningOsAndChannel (\$open+1) [:len \$runningOs\ + AndChannel]];\r\ + \n# :local close [:find \$rest \")\"];\r\ + \n# :local channel [:pick \$rest 0 \$close];\r\ + \n# :return \$channel;\r\ + \n#}" +add dont-require-permissions=no name=01-Amster-Init-Script owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="# Amster Initial Script\r\ + \n#\r\ + \n#:global GlobalEnvRemove true; /system/script {run Amster-GlobalEnvRemov\ + e; };\r\ + \n#:delay 1s;\r\ + \n:#global ReloadGlobal true; \r\ + \n\r\ + \n:global AmScriptsUrl \"https://ros.vados.ru/\";\r\ + \n:global defOwner \"vados\";\r\ + \n:global AmFirstRun;\r\ + \n\r\ + \n:set AmFirstRun do={\r\ + \n :global AmScriptsUrl;\r\ + \n :global defOwner;\r\ + \n #:global ParseKeyValueStore;\r\ + \n #:local AmUpdCRLF true;\r\ + \n #:local IfThenElse;\r\ + \n #:set IfThenElse do={:if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1\ + \_] = true) do={:return \$2}; :return \$3}\r\ + \n #/certificate/settings/set builtin-trust-store=fetch;\r\ + \n #/tool/fetch \"\$scriptsUrl/certs/Root-YE.pem\" dst-path=\"root-ye.p\ + em\";\r\ + \n #/certificate/import file-name=\"root-ye.pem\" passphrase=\"\";\r\ + \n # For basic verification we rename the certificate and print it by f\ + ingerprint. Make sure exactly this one certificate (\"Root-YE\") is shown.\ + \r\ + \n #/certificate/set name=\"Root-YE\" [ find where common-name=\"Root Y\ + E\" ];\r\ + \n #/certificate/print proplist=name,fingerprint where fingerprint=\"e1\ + 4ffcad5b0025731006caa43a121a22d8e9700f4fb9cf852f02a708aa5d5666\";\r\ + \n #:log info \"\\nAmScriptsUrl = \$AmScriptsUrl\";\r\ + \n :local Scripts {\r\ + \n \"Amster-GlobalConfig\"\r\ + \n \"Amster-GlobalFunc\";\r\ + \n# \"Amster-SFTP-backup\"\r\ + \n# \"Amster-BkpEmail-and-upd\";\r\ + \n };\r\ + \n# /system/script/set owner=(\$ScriptVal->\"name\") \\\r\ + \n# source=[ \$IfThenElse (\$ScriptUpdatesCRLF = true) \$SourceCRLF \$S\ + ourceNew ] \$Script;\r\ + \n :foreach Script in=\$Scripts do={\r\ + \n :put \"Installing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$defOwner source=([ /too\ + l/fetch (\"\$AmScriptsUrl\" . \$Script . \".rsc\") output=user as-value ]-\ + >\"data\");\r\ + \n /system/script/set source=[ :tocrlf [ get \$Script source ] ] \$\ + Script;\r\ + \n };\r\ + \n /system/script { run Amster-GlobalConfig; run Amster-GlobalFunc; }\r\ + \n}\r\ + \n:global AmUpdRun;\r\ + \n:set AmUpdRun do={\r\ + \n #:local UpdScript [ :tostr \$1 ];\r\ + \n #:global ParseKeyValueStore;\r\ + \n :global AmScriptsUrl;\r\ + \n :global defOwner;\r\ + \n :local Scripts {\r\ + \n# \"Amster-GlobalConfig\"\r\ + \n# \"Amster-GlobalFunc\"\r\ + \n# \"Amster-GlobalRemove\"\r\ + \n \"Amster-backup-SFTP\"\r\ + \n \"Amster-SFTP-backup\"\r\ + \n \"Amster-BkpEmail-and-upd\"\r\ + \n \"Amster-TG-Bot\"\r\ + \n \"Amster-TG-Notification\";\r\ + \n };\r\ + \n :foreach Script in=\$Scripts do={\r\ + \n :put \"updating \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$defOwner source=([ /too\ + l/fetch (\"\$AmScriptsUrl\" . \$Script . \".rsc\") output=user as-value ]-\ + >\"data\");\r\ + \n /system/script/set source=[ :tocrlf [ get \$Script source ] ] \$\ + Script;\r\ + \n };\r\ + \n# /system/script/add name=\$Script owner=\$AmOwner source=([ /tool/fe\ + tch check-certificate=yes-without-crl (\"\$AmScriptsUrl\" . \$Script . \".\ + rsc\") output=user as-value ]->\"data\"); };\r\ + \n}\r\ + \n\r\ + \n\$AmFirstRun;\r\ + \n#\$AmUpdRun;\r\ + \n\r\ + \n#\$setUpdExist;\r\ + \n#:global setUpdExist;\r\ + \n#:global ParseKeyValueStore;\r\ + \n#:set setUpdExist do={\r\ + \n#:do {\r\ + \n#:global ParseKeyValueStore;\r\ + \n# :foreach Script in=[ /system/script/find where source~\"^#!rsc by Vad\ + os\\r\?\\n#comment=*\" ] do={\r\ + \n# :local ScriptVal [ /system/script/get \$Script ];\r\ + \n# :local ScriptInfo [ \$ParseKeyValueStore (\$ScriptVal->\"#comment\"\ + ) ];\r\ + \n# :local scriptName (\$ScriptVal->\"name\");\r\ + \n#:log warning (\"\\n\\\$scriptName = \". \$scriptName);\r\ + \n#: log warning (\"\\n\\\$ScriptInfo = \". \$ScriptInfo);\r\ + \n# }\r\ + \n#} on-error={}\r\ + \n#}\r\ + \n#\$name;\$owner;\$policy;\$dont-require-permissions;\$last-started;\$run\ + -count;\$source;\$invalid;\$comment;\$.id;\$.nextid;\$.dead;\$.about;\r\ + \n#;(evl / (evl /docommand=;(evl / (evl /foreachcounter=\$Script;do=;(evl \ + / (evl /localname=\$ScriptVal;value=(evl (evl /system/script/getnumber=\$S\ + cript))) (evl /localname=\$ScriptInfo;value=(evl (<%% \$ParseKeyValueStore\ + \_(> \$ParseKeyValueStore);(-> \$ScriptVal comment)))) (evl /localname=\$s\ + criptName) (evl /setname=\$scriptName;value=(-> #\$ScriptVal name)) (evl /\ + log/errormessage=(. \r\ + \n# \$ ScriptInfo = \$ScriptInfo)));in=(evl (evl /system/script/findwhere\ + =\$name;\$owner;\$policy;\$dont-require-permissions;\$last-started;\$run-c\ + ount;\$source;\$invalid;\$comment;\$.id;\$.nextid;\$.dead;\$.about;(~ \$so\ + urce (. ^#!rsc by Vados \r\ + \n# \? \r\ + \n#));5))));on-error=;(evl /)))\r\ + \n" +add dont-require-permissions=no name="Download cAP's Packages" owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="# +------------------------------+\r\ + \n# | |\r\ + \n# | Mikrotik RouterOS Scripts |\r\ + \n# | _Download cAP's Packages_ |\r\ + \n# | |\r\ + \n# +------------------------------+\r\ + \n# https://github.com/sebastian13/mikrotik-routeros-scripts\r\ + \n#\r\ + \n\r\ + \n# Set a directory path providing the required space\r\ + \n# Add a microSD card https://wiki.mikrotik.com/wiki/Manual:System/Disks\ + \r\ + \n# or setup a RAM-Disk via '/disk/add type=tmpfs tmpfs-max-size=20M slot=\ + ram'\r\ + \n:local dir \"/tmpfs/capsman-packages\";\r\ + \n\r\ + \n\r\ + \n# Set Package Path if required, set policy to none if using 'upgrade-cap\ + s' script\r\ + \n/interface/wifi/capsman/set package-path=\$dir upgrade-policy=none;\r\ + \n/caps-man/manager/set package-path=\$dir upgrade-policy=none;\r\ + \n#require-same-version;\r\ + \n\r\ + \n:local installed [/system package get value-name=version [find where nam\ + e=\"routeros\"]]\r\ + \n\r\ + \n# Delete old downloads\r\ + \n# /file/remove [ find type=package name~\"capsman*\" ]\r\ + \n\r\ + \n# Starting from RouterOS version 7.13, drivers for older wireless and 60\ + GHz interfaces are\r\ + \n# part of the separate \"wireless\" package. The existing \"wifiwave2\" \ + package has been divided\r\ + \n# into distinct packages: \"wifi-qcom\" and \"wifi-qcom-ac\".\r\ + \n# RouterOS >=7.12 is required to upgrade to 7.13.\r\ + \n# https://forum.mikrotik.com/viewtopic.php\?t=202423\r\ + \n\r\ + \n:foreach architecture in={ \"arm\";\r\ + \n# \"arm64\";\r\ + \n \"x86_64\";\r\ + \n \"mipsbe\"} do={\r\ + \n :foreach package in={ \"routeros\";\r\ + \n# \"wifi-qcom-ac\";\r\ + \n# \"wifi-qcom\";\r\ + \n \"container\";\r\ + \n \"wireless\"} do={ :do {\r\ + \n :put \"\"\r\ + \n :put \"Download \$package \$installed for \$architecture\"\r\ + \n /tool fetch mode=https \\\r\ + \n url=\"https://download.mikrotik.com/routeros/\$installed/\$packa\ + ge-\$installed-\$architecture.npk\" \\\r\ + \n dst-path=\"\$dir/\$package-\$installed-\$architecture.npk\"\r\ + \n } on-error={ :put \"Error downloading the file. On a 404, note t\ + hat the package may not exist for the specified architecture!\" }\r\ + \n }\r\ + \n}\r\ + \n" +add comment="Script for setup routeros script system" \ + dont-require-permissions=no name=AM-setup owner=AM-setup policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-setup\r\ + \n# Script comment: Script for setup routeros script system\r\ + \n# Copyright (c) 2005-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n# :local ScriptName [ :jobname ];\r\ + \n# Additional commands:\r\ + \n#\r\ + \n# For scripts:\r\ + \n#:global NewScript \"script_name\"; \r\ + \n#:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n#/system/script/add name=\$NewScript owner=\$NewScript source=([/tool/fe\ + tch check-certificate=yes-without-crl (\$GlobalScriptsUrl . \$ScriptAdd . \ + \".rsc\") output=user as-value ]->\"data\");\r\ + \n#:global NewScript (a);\r\ + \n#:global ScriptInstallUpdate; \$ScriptInstallUpdate;\r\ + \n#\r\ + \n# For run from terminal:\r\ + \n#/system/script/add name=AM-setup owner=AM-Setup source=([/tool/fetch ch\ + eck-certificate=yes-without-crl \"https://ros.vados.ru/AM-setup.rsc\" outp\ + ut=user as-value ]->\"data\");\r\ + \n#\r\ + \n\r\ + \n:global ScriptUpdatesCRLF \"LF\";\r\ + \n#:if (\$ScriptUpdatesCRLF != false) do={\r\ + \n# :foreach Script in=[ /system/script/find where source~\"^#!rsc by Vad\ + os\" ] do={\r\ + \n# if (\$ScriptUpdatesCRLF = true) do={\r\ + \n# /system/script/set source=[ :tocrlf [ get \$Script source ] ] \$S\ + cript;\r\ + \n# } else={\r\ + \n# /system/script/set source=[ :tolf [ get \$Script source ] ] \$Scr\ + ipt;\r\ + \n# }\r\ + \n# }\r\ + \n#}\r\ + \n\r\ + \n# /import verbose=no \$ScriptName\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global SetupScript [ :jobname ];\r\ + \n:global RemoveMe true;\r\ + \n:global RunOnce (a);\r\ + \n:global InitSetup true;\r\ + \n:global tmpfs false;\r\ + \n\r\ + \n{\r\ + \n :local RmMe [:tobool \$1];\r\ + \n\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global SetupScript;\r\ + \n :global RemoveMe;\r\ + \n :global InitSetup;\r\ + \n :global tmpfs;\r\ + \n\r\ + \n :local UrlSuffix \$Identity;\r\ + \n :local UrlDir \"certs/\";\r\ + \n :local CertUrl (\$GlobalScriptsUrl . \$UrlDir . \$UrlSuffix .\"/\"); \ + \r\ + \n :local CertCommonName \"Root YE\";\r\ + \n :local CertFileName \"Root-YE.pem\";\r\ + \n :local CertFingerprint \"e14ffcad5b0025731006caa43a121a22d8e9700f4fb9c\ + f852f02a708aa5d5666\";\r\ + \n\r\ + \n :if (\$SetupScript = false) do={\r\ + \n :global SetupScript \"AM-setup\";\r\ + \n }\r\ + \n :if (\$RemoveMe != false) do={\r\ + \n :set RemoveMe true;\r\ + \n }\r\ + \n\r\ + \n :log warning (\"\\n\\\$GlobalScriptsUrl = \" . \$GlobalScriptsUrl);\r\ + \n \r\ + \n\r\ + \n :if (!(((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" ||\ + \_\\\r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \"fetch\" || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\r\ + \n [ :len [ /certificate/builtin/find where common-name=\$CertComm\ + onName ] ] > 0)) do={\r\ + \n :put \"Importing certificate...\";\r\ + \n /tool/fetch (\$CertUrl . \$CertFileName) dst-path=\$CertFileName\ + \_as-value;\r\ + \n :delay 1s;\r\ + \n /certificate/import file-name=\$CertFileName passphrase=\"\";\r\ + \n /certificate/set name=\"Root-YE\" [ find where common-name=\"Roo\ + t YE\" ];\r\ + \n :delay 1s;\r\ + \n };\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if ([ :len [ /certificate/find where fingerprint=\$CertFingerprint ] \ + ] != 1) do={\r\ + \n :error \"Something is wrong with your certificates!\";\r\ + \n };\r\ + \n\r\ + \n:local MkTmpfs do={\r\ + \n :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ];\r\ + \n :if ([ :len \$TmpFs ] = 1) do={\r\ + \n :if ([ /disk/get \$TmpFs disabled ] = true) do={\r\ + \n :log info (\"The tmpfs is disabled, enabling.\");\r\ + \n /disk/enable \$TmpFs;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :log info (\"Try to removing directory 'tmpfs'.\");\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/remove tmpfs;\r\ + \n :return true;\r\ + \n } delay=100ms max=10;\r\ + \n } on-error={ }\r\ + \n# \$RmDir \"tmpfs\";\r\ + \n :onerror Err {\r\ + \n /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource/g\ + et total-memory ] / 3);\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/get tmpfs;\r\ + \n :return true;\r\ + \n } delay=222ms max=10;\r\ + \n } on-error={ }\r\ + \n } do={\r\ + \n :log warning (\"Creating disk of type tmpfs failed: \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n# :return true;\r\ + \n}\r\ + \n:if (\$tmpfs = true) do={\r\ + \n :put \"Tmpfs variable is true. Checking for tmpfs/scripts directory...\ + \";\r\ + \n :if ([/file find name=\"tmpfs/scripts\"] != \"\") do={\r\ + \n :log info (\$0 . \"Directory tmpfs/scripts exsist.\");\r\ + \n :return \$true;\r\ + \n } else={\r\ + \n :log warning (\$0 . \"Directory tmpfs/scripts does't exsist.\");\r\ + \n :if ([ \$MkTmpfs ] = false) do={\r\ + \n :log error (\$0 . \"Directory tmpfs is not created!\");\r\ + \n :return false;\r\ + \n } else={\r\ + \n :onerror Err {\r\ + \n /file/add type=\"directory\" name=/tmpfs/scripts;\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/get /tmpfs/scripts;\r\ + \n :return true;\r\ + \n } delay=100ms max=10;\r\ + \n } on-error={ }\r\ + \n } do={\r\ + \n :log warning (\$0 . \"Making directory 'scripts' failed: \" . \$\ + Err);\r\ + \n :return false;\r\ + \n }\r\ + \n# :return true;\r\ + \n# [ /file/get \$FileName ]\r\ + \n }\r\ + \n# :log error (\$0 . \"Directory tmpfs/scripts does not exsist. Remove\ + \_global variable tmpfs!\");\r\ + \n# :global tmpfs (a);\r\ + \n# return false;\r\ + \n }\r\ + \n}\r\ + \n :put \"Renaming AM-GlobalConfig, if exists...\";\r\ + \n :local ConfOldName (\"AM-GlobalConfig-\" . [ /system/clock/get date ] \ + . [ /system/clock/get time ] . \".rsc\");\r\ + \n /system/script/set name=\$ConfOldName [ find where name=\"AM-GlobalCon\ + fig\" ];\r\ + \n :foreach Script in={ \"AM-GlobalConfig\"; \"AM-GlobalFunc\" } do={\r\ + \n :put \"Installing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$Script source=([ /tool/fetc\ + h check-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \ + \".rsc\") output=user as-value ]->\"data\");\r\ + \n \r\ + \n /system/script run \$Script;\r\ + \n :delay 2s;\r\ + \n };\r\ + \n\r\ + \n :local Script \"mod/AM-Certs-By-Ident\";\r\ + \n \r\ + \n :put \"Installing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$Script source=([ /tool/fetch \ + check-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \".\ + rsc\") output=user as-value ]->\"data\");\r\ + \n /system/script run \$Script;\r\ + \n :global tmpAddCerts;\r\ + \n :while ([ \$tmpAddCerts ] = true) do={\r\ + \n :delay 1s;\r\ + \n }\r\ + \n :delay 1s;\r\ + \n :put \"Removing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n\r\ + \n :if ([ :len [ /certificate/find where fingerprint=\$CertFingerprint ] \ + ] > 0) do={\r\ + \n :put \"Renaming certificate by its common-name...\";\r\ + \n :global CertificateNameByCN;\r\ + \n \$CertificateNameByCN \$CertFingerprint;\r\ + \n };\r\ + \n\r\ + \n :if (\$RemoveMe = true) do={\r\ + \n :put \"Add Scheduler for remove this script...\";\r\ + \n :local OnEvent (\":delay 30s;\\n\" . \"/system/script/remove [ find \ + where name=\\\"\$SetupScript\\\" ];\\n\" . \\\r\ + \n \":delay 2s;\\n\" . \"/system/scheduler/remove [ find where name=\\\ + \"_RemoveSetup\\\" ];\\n\" . \\\r\ + \n \":delay 2s;\\n\" . \":global RemoveMe (a);\\n\");\r\ + \n :local Comment \"_RemoveSetup tmp scheduler for remove setup script.\ + \";\r\ + \n /system/scheduler/add name=\"_RemoveSetup\" comment=\$Comment start-\ + time=startup interval=30s on-event=\$OnEvent;\r\ + \n :log warning (\$0 . \"\\nI've done everything, and now I'm tired..\ + . I'm leaving.\\nThere will be an error below, this is normal.\");\r\ + \n }\r\ + \n :delay 1;\r\ + \n :put \"Loading configuration and functions...\";\r\ + \n :do { \r\ + \n :global ScriptInstallUpdate; \$ScriptInstallUpdate;\r\ + \n :put \"Normal removing temporary global environment variables.\";\ + \r\ + \n :global SetupScript (a);\r\ + \n :global InitSetup (a);\r\ + \n :global RemoveMe (a);\r\ + \n } on-error={\r\ + \n :local ErrorMsg \"Loading configuration and functions error!\";\r\ + \n :log error (\$0 . \$ErrorMsg . :error);\r\ + \n :error \$ErrorMsg;\r\ + \n }\r\ + \n}\r\ + \n" +add comment="Global Config Script" dont-require-permissions=no name=\ + AM-GlobalConfig owner=AM-GlobalConfig policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-GlobalConfig\r\ + \n# Script comment: Global Config Script\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# Warning: Dont touch this line!\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global GlobalConfReady false;\r\ + \n#\r\ + \n#\r\ + \n# Global Url For Amster scripts to fetch\r\ + \n# most be defined in AM-Init-Script or AM-setup scripts\r\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n:global ScriptsUrlSuffix (a);\r\ + \n:global CheckSumsVerify true;\r\ + \n\r\ + \n:global ScriptUpdatesCRLF true;\r\ + \n:global CommentsInScripts true;\r\ + \n\r\ + \n# Debug output:\r\ + \n:global PrintDebug true; #false;\r\ + \n\r\ + \n# Debug output for specific script:\r\ + \n#:set (\$PrintDebugOverride->\"AM-GlobalFunc\") true;\r\ + \n\r\ + \n# Debug logs\r\ + \n# Other actions (disk, email, remote or support) can be used as well.\r\ + \n# I do not recommend using echo - use debug output instead (https://rsc.\ + eworm.de/main/DEBUG.html#debug-output).\r\ + \n#/system/logging/add topics=script,debug action=memory;\r\ + \n\r\ + \n:global MikrotikUpgradeUrl \"https://upgrade.mikrotik.com/routeros\";\r\ + \n:global Domain \"WORKGROUP\";\r\ + \n\r\ + \n:global EmailGeneralTo \"vados@vados.ru\";\r\ + \n#:global EmailGeneralCc \"\";\r\ + \n\r\ + \n# \$ScriptInstallUpdate mod/notification-telegram\r\ + \n:global TelegramTokenId \"8390868324:AAG3_d1hfVR2M6pGAgDK6ZvhOe4J97nyopw\ + \";\r\ + \n:global TelegramChatId \"-1003631309063\"; #\"454403445\";\r\ + \n# Use this to send notifications to a specific topic in group.\r\ + \n#:global TelegramThreadId \"30\";\r\ + \n# Using telegram-chat you have to define trusted chat ids (not group ids\ + !)\r\ + \n# or user names. Groups allow to chat with devices simultaneously.\r\ + \n:global TelegramChatIdsTrusted {\r\ + \n \"454403445\"\r\ + \n \"-1003631309063\"\r\ + \n \"-5124482807\"\r\ + \n \"1087968824\";\r\ + \n};\r\ + \n# \"-5294757340\";\r\ + \n\r\ + \n:global TelegramChatGroups \"(all)\";\r\ + \n#:global TelegramChatGroups \"(all|home|office)\";\r\ + \n\r\ + \n:global NotificationFunctions {\r\ + \n \"email\"\r\ + \n \"telegram\";\r\ + \n};\r\ + \n# Toggle this to disable symbols in notifications.\r\ + \n:global NotificationsWithSymbols true;\r\ + \n:global TerminalColorOutput true;\r\ + \n\r\ + \n:global DetectIpAddrDefault \"https://ipv4.mikrotik.ovh/\";\r\ + \n:global DetectIpAddrFallback \"https://api.ipify.org/\";\r\ + \n\r\ + \n## Update channel. Possible values: stable, long-term, testing, developm\ + ent\r\ + \n:global updateChannel \"stable\";\r\ + \n\r\ + \n:global CertRenewUrl (\$GlobalScriptsUrl . \"certs/\" . \$Identity . \"/\ + \");\r\ + \n:global CertRenewTime 2w;\r\ + \n:global CertRenewPass (a); #{\r\ + \n# \"\";\r\ + \n# \"v3ry-s3cr3t\";\r\ + \n# \"4n0th3r-s3cr3t\";\r\ + \n#};\r\ + \n:global CertWarnTime 1w;\r\ + \n:global CertIssuedExportPass (a); # {\r\ + \n# \"\";\r\ + \n# \"cert1-cn\"=\"v3ry-s3cr3t\";\r\ + \n# \"cert2-cn\"=\"4n0th3r-s3cr3t\";\r\ + \n#};\r\ + \n\r\ + \n# Script BackupAndUpdate mode, possible values: backup,\r\ + \n# updateOnce - Update once as \"osnotify\" and back \"osnotify\" mode (i\ + n terminal run :set OSUpdateMode updateOnce; \$OSUpdateMode;). \r\ + \n# osupdate - Update if available and creates backups before/after update\ + \_(ignores `forceBackup`)\r\ + \n# Set `forceBackup` to true to always create backups, even\ + \_without updates\r\ + \n# osnotify - Set `forceBackup` to always create backups on every run\r\ + \n:global DevInfoIncludeIP true;\r\ + \n:global BackupAndUpdateMode \"osnotify\"\r\ + \n\r\ + \n:global forceBackup true;\r\ + \n:global BackupPassword \"Peskar55\";\r\ + \n\r\ + \n# SFTP Backup Variables\r\ + \n:global BackupRandomDelay 0;\r\ + \n:global BackupUploadUrl \"sftp://amster.vados.ru/upload/\";\r\ + \n:global BackupUploadUser \$Identity;\r\ + \n:global BackupUploadPass \"root.mnuc.backuper\";\r\ + \n\r\ + \n:global BackupSendBinary true;\r\ + \n:global BackupSendExport true;\r\ + \n:global BackupSendGlobalConfig true;\r\ + \n:global BackupSendScripts true;\r\ + \n\r\ + \n# Remove local file after uploading if no errors\r\ + \n:global BackupRmLocal true;\r\ + \n\r\ + \n# Local Backups Root Directory \r\ + \n:global BackupLocalRoot \"tmpfs\";\r\ + \n\r\ + \n# Encrypt Backup\r\ + \n:global BackupEncrypt true;\r\ + \n\r\ + \n# Sensitive information in Backups\r\ + \n:global BackupSens true;\r\ + \n\r\ + \n# User Export\r\ + \n:global BackupUser true;\r\ + \n\r\ + \n# License Export (not for CHR, will silently skip)\r\ + \n:global BackupLicense false;\r\ + \n\r\ + \n# SSH Keys\r\ + \n:global BackupSshKeys true;\r\ + \n\r\ + \n# Certificate Export\r\ + \n:global BackupCerts true;\r\ + \n\r\ + \n# Certificate Password\r\ + \n:global BackupCertsPasswd \"\";\r\ + \n\r\ + \n# User-Manager Export\r\ + \n:global BackupUserMan false;\r\ + \n\r\ + \n# The Dude Export\r\ + \n:global BackupDude false;\r\ + \n\r\ + \n# User Files to export, comma-separated string or array of strings\r\ + \n# User Files are not removed on backup\r\ + \n# Any directory paths will be removed (/ -> _) on remote file\r\ + \n# Nonpresent files are silently skipped\r\ + \n:global BackupUserList \"autosupout.rif,autosupout.old.rif\";\r\ + \n\r\ + \n# load functions and any custom scripts\r\ + \n# [ /system/script/find where name=\"global-config-ov\ + erlay\" ], \\\r\ + \n# [ /system/script/find where name~\"^global-config-o\ + verlay\\\\.d/.\" ]\r\ + \n#\r\ + \n#:foreach Script in=([ /system/script/find where name=\"Amster-TGglobalV\ + ars\" ], [ /system/script/find where name=\"Amster-TGglobalFunc\" ]) do={\ + \r\ + \n# :onerror Err {\r\ + \n# /system/script/run \$Script;\r\ + \n# } do={\r\ + \n# :log error (\"Loading configuration from configs and functions\"\ + \_. [ /system/script/get \$Script name ] . \" failed: \" . \$Err);\r\ + \n# }\r\ + \n#}\r\ + \n#:log info \"\\ndevMode = \$devMode\";\r\ + \n#:log info \"\\ndevRunning = \$devRunning\";\r\ + \n:foreach Script in=[ /system/script/find where source~\"^#!rsc by Vados \ + GlobalRUN\\r\?\\n\" ] do={\r\ + \n :onerror Err {\r\ + \n /system/script/run \$Script;\r\ + \n } do={\r\ + \n :log error (\"Run script \" . [ /system/script/get \$Script name \ + ] . \" width marker \\\" GlobalRUN\\\" failed: \" . \$Err);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# signal we are ready\r\ + \n:set GlobalConfReady true;\r\ + \n:while (\$GlobalConfReady != true) do={:delay 500ms};\r\ + \n" +add comment="Global functions" dont-require-permissions=no name=AM-GlobalFunc \ + owner=AM-GlobalFunc policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-GglobalFunc\r\ + \n# Script comment: Global functions\r\ + \n# Copyright (c) 2005-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch, scheduler\r\ + \n:local ExitOK false;\r\ + \n:local ScriptName [ :jobname ];\r\ + \n#:global checkRunJob \$SriptName;\r\ + \n#:global fixCRLF;\r\ + \n#:if (\$fixCRLF != false) do={\r\ + \n#:foreach fixScript in=([ /system/script/find where name~\"*\" ]) do={/s\ + ystem/script/set source=[ :tocrlf [ get \$fixScript source ] ] \$fixScript\ + }\r\ + \n#:if (\$fixCRLF != \"LF\") do={ /system/script/set source=[ :tocrlf [ g\ + et \$fixScript source ] ] \$fixScript};};\r\ + \n#}\r\ + \n:global GlobalFuncReady false;\r\ + \n\r\ + \n# Git commit id & info, expected configuration version\r\ + \n:global CommitId \"main/700/f14f095d\";\r\ + \n:global CommitTAG \"unknown\";\r\ + \n:global CommitInfo \"Autocommit in repo: ros.vados.ru, generated by owne\ + r: root, in 13:30:03 22.06.2026.\";\r\ + \n\r\ + \n# The variables are received right here.\r\ + \n:global GlobalConfVersion 140;\r\ + \n\r\ + \n:global SumsDirAndFileName \"tmp/checksums.json\";\r\ + \n\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global IdentityShort [:pick \$Identity 0 18];\r\ + \n\r\ + \n:global devBoardName [/system/resource/get board-name];\r\ + \n:global devMode [/system/device-mode/get mode];\r\ + \n:global devCurArch [/system/resource/get architecture-name];\r\ + \n:global devCurVersion [/system/resource/get version];\r\ + \n\r\ + \n# Variables defined in AM-GlobalConfig.rsc\r\ + \n:global TerminalColorOutput;\r\ + \n:global CommentsInScripts;\r\ + \n:global DetectIpAddrDefault;\r\ + \n:global DetectIpAddrFallback;\r\ + \n:global updateChannel;\r\ + \n:global Domain;\r\ + \n:global EmailGeneralTo;\r\ + \n:global BackupAndUpdateMode;\r\ + \n\r\ + \n# Single functions, without nested functions.\r\ + \n:global MiniDateTimeStamp;\r\ + \n:global MicroDateTimeStamp;\r\ + \n\r\ + \n# Include globals \$PrintDebug and \$PrintDebugOverride\r\ + \n:global LogPrint;\r\ + \n\r\ + \n# DeviceInfo and Updates\r\ + \n:global IsCheckUpdates;\r\ + \n:global WaitCheckUpdates;\r\ + \n:global GetPubIp;\r\ + \n\r\ + \n:global GetArrDeviceInfo;\r\ + \n:global WaitArrDeviceInfo;\r\ + \n\r\ + \n:global devModel;\r\ + \n:global devRunningMode;\r\ + \n\r\ + \n:global devRbRevision;\r\ + \n:global devRbSerialNumber;\r\ + \n:global devRbVerAvail;\r\ + \n\r\ + \n:global runningChannelVersion;\r\ + \n:global runningChannel;\r\ + \n:global runningVersion;\r\ + \n\r\ + \n:global devBuildTime;\r\ + \n:global ROsVerAvail;\r\ + \n:global PkgUpdStatus;\r\ + \n:global currentUpdateChannel;\r\ + \n\r\ + \n:global ScriptInstallUpdate;\r\ + \n\r\ + \n:global DeviceInfo;\r\ + \n\r\ + \n:global OldMode;\r\ + \n\r\ + \n:global ParseKeyValueStore;\r\ + \n:global RandomDelay;\r\ + \n:global GetRandomNumber;\r\ + \n:global EscapeForRegEx;\r\ + \n:global CharacterMultiply;\r\ + \n:global CharacterReplace;\r\ + \n:global CleanFilePath;\r\ + \n:global CleanName;\r\ + \n\r\ + \n:global CommitBrief;\r\ + \n\r\ + \n:global EitherOr;\r\ + \n:global FormatLine;\r\ + \n:global GetRandom20CharAlNum;\r\ + \n:global GetRandom20CharHex;\r\ + \n:global GetRandomNumber;\r\ + \n:global Grep;\r\ + \n:global IfThenElse;\r\ + \n:global MAX;\r\ + \n:global MIN;\r\ + \n:global HumanReadableNum;\r\ + \n\r\ + \n:global IsFullyConnected;\r\ + \n:global IsDNSResolving;\r\ + \n:global IsTimeSync;\r\ + \n:global IsTimeSyncCached;\r\ + \n:global IsTimeSyncResetNtp;\r\ + \n\r\ + \n:global MkDir;\r\ + \n:global RmDir;\r\ + \n:global RmFile;\r\ + \n:global FileGet;\r\ + \n:global Unix2Dos;\r\ + \n:global UrlEncode;\r\ + \n:global WaitForFile;\r\ + \n:global ScriptLock;\r\ + \n:global SendTelegram;\r\ + \n:global NotificationFunctions;\r\ + \n:global SendNotification;\r\ + \n:global SendNotification2;\r\ + \n:global SymbolForNotification;\r\ + \n:global SymbolByUnicodeName;\r\ + \n:global ValidateSyntax;\r\ + \n:global CheckSumsVerify;\r\ + \n:global GlobalConfChanges;\r\ + \n:global GlobalConfMigration;\r\ + \n:global VersionToNum;\r\ + \n:global RequiredRouterOS;\r\ + \n:global ExitError;\r\ + \n:global ExitOnError;\r\ + \n\r\ + \n:global FetchHugeFiles;\r\ + \n:global FetchUserAgentStr;\r\ + \n:global FileExists;\r\ + \n:global CertificateAvailable;\r\ + \n:global CertificateDownload;\r\ + \n:global CertificateNameByCN;\r\ + \n:global CharacterMultiply;\r\ + \n\r\ + \n:global InspectVar;\r\ + \n:global InspectVarRet;\r\ + \n:global ParseCustom;\r\ + \n\r\ + \n:set IsCheckUpdates do={\r\ + \n :local isInfo [:tobool \$1];\r\ + \n :global updateChannel;\r\ + \n :global PkgUpdStatus;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n :global arrDeviceInfo;\r\ + \n :log info (\$0 . \" \\\$isInfo = \" . \$isInfo); \r\ + \n :local CurrentChannel [ /system/package/update/get channel ];\r\ + \n :if ([ :len \$updateChannel ] > 0 && \$CurrentChannel != \$updateChann\ + el) do={\r\ + \n :retry command={/system/package/update set channel=\$updateChannel} \ + \_delay=300ms max=5 on-error={};\r\ + \n :log info (\$0 . \" Set updates channel to: \$updateChannel\");\r\ + \n }\r\ + \n :local Update [ /system/package/update/get ];\r\ + \n :local Channel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set currentUpdateChannel \$Channel;\r\ + \n :log info (\$0 . \" Update Channel: \" . \$Channel . \".\");\r\ + \n :local StatusPkg \"undefined\";\r\ + \n :log info (\$0 . \" Checking for updates...\");\r\ + \n :retry command={:set \$StatusPkg ([/system/package/update/check-for-up\ + dates without-paging as-value]->\"status\")} \\\r\ + \n delay=300ms max=5 on-error={};\r\ + \n :log info (\$0 . \" \\\$StatusPkg = \" . \$StatusPkg);\r\ + \n :if (\$StatusPkg = \"New version is available\") do={ :set PkgUpdStatu\ + s true; } else={ :set PkgUpdStatus false; }\r\ + \n :set ROsVerAvail [ :tostr [ /system/package/update/get latest-version \ + ] ];\r\ + \n :log info (\$0 . \" \\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if (\$isInfo != false || \$PkgUpdStatus = true) do={\r\ + \n :log info (\$0 . \" \\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if ([ :typeof \$arrDeviceInfo ] != \"array\") do={\r\ + \n :set arrDeviceInfo [:toarray \$arrDeviceInfo];\r\ + \n }\r\ + \n :retry command={:set (\$arrDeviceInfo->\"devUpdates\") [ /system/pac\ + kage/update/get ]} delay=500ms max=5 on-error={:log error (\"Error\")};\r\ + \n :return true;\r\ + \n } else={\r\ + \n :return \$PkgUpdStatus;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set WaitCheckUpdates do={\r\ + \n :global IsCheckUpdates;\r\ + \n :global PkgUpdStatus;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n :global arrDeviceInfo;\r\ + \n :local Status \"\";\r\ + \n :local Update;\r\ + \n :local WaitCount 0;\r\ + \n :if ([ \$IsCheckUpdates true ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n :set Status [ :tostr [ /system/package/update/get status ] ];\r\ + \n :while ((\$Status = \"\" || \$Status = \"finding out latest version...\ + \") && \$WaitCount < 60) do={\r\ + \n :delay 1s;\r\ + \n :set WaitCount (\$WaitCount + 1);\r\ + \n :set Status [ :tostr [ /system/package/update/get status ] ];\r\ + \n }\r\ + \n :set Update [ /system/package/update/get ];\r\ + \n :set currentUpdateChannel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set ROsVerAvail [ :tostr (\$Update->\"latest-version\") ];\r\ + \n :if ((\$Update->\"status\") = \"New version is available\") do={\r\ + \n :set PkgUpdStatus true;\r\ + \n } else={\r\ + \n :set PkgUpdStatus false;\r\ + \n }\r\ + \n :if ([ :typeof \$arrDeviceInfo ] != \"array\") do={\r\ + \n :set arrDeviceInfo [:toarray \$arrDeviceInfo];\r\ + \n }\r\ + \n :set (\$arrDeviceInfo->\"devUpdates\") \$Update;\r\ + \n :if (\$Status = \"\" || \$Status = \"finding out latest version...\") \ + do={\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set WaitArrDeviceInfo do={\r\ + \n :global GetArrDeviceInfo;\r\ + \n :while ([ \$GetArrDeviceInfo ] = false) do={\r\ + \n :delay 1s;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set GetArrDeviceInfo do={\r\ + \n :global LogPrint;\r\ + \n :global arrDeviceInfo;\r\ + \n :global devBoardName;\r\ + \n :global devModel;\r\ + \n :global devRunning;\r\ + \n :global devCurArch;\r\ + \n :global devRbRevision;\r\ + \n :global devRbSerialNumber;\r\ + \n :global devBuildTime;\r\ + \n :global runningChannelVersion;\r\ + \n :global runningChannel;\r\ + \n :global runningVersion;\r\ + \n :global currentUpdateChannel;\r\ + \n :global ROsVerAvail;\r\ + \n\r\ + \n :local DeviceInfo;\r\ + \n :local Resource [/system/resource/get];\r\ + \n :local Update [ /system/package/update/get ];\r\ + \n :local VersionString [ :tostr (\$Resource->\"version\") ];\r\ + \n :local ChannelStart [ :find \$VersionString \" (\" ];\r\ + \n :local RouterBoard;\r\ + \n :local IsRouterBoard false;\r\ + \n :local RunningMode \"\";\r\ + \n\r\ + \n :set devCurArch [ :tostr (\$Resource->\"architecture-name\") ];\r\ + \n :set devBuildTime [ :tostr (\$Resource->\"build-time\") ];\r\ + \n :set runningChannelVersion \$VersionString;\r\ + \n :set runningVersion \$VersionString;\r\ + \n :set runningChannel \"\";\r\ + \n\r\ + \n :if ([ :typeof \$ChannelStart ] = \"num\" && \$ChannelStart >= 0) do=\ + {\r\ + \n :set runningVersion [ :pick \$VersionString 0 \$ChannelStart ];\r\ + \n :local ChannelRest [ :pick \$VersionString (\$ChannelStart + 2) [ :\ + len \$VersionString ] ];\r\ + \n :local ChannelEnd [ :find \$ChannelRest \")\" ];\r\ + \n :if ([ :typeof \$ChannelEnd ] = \"num\" && \$ChannelEnd >= 0) do={\ + \r\ + \n :set runningChannel [ :pick \$ChannelRest 0 \$ChannelEnd ];\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if ([:pick \$devBoardName 0 3] != \"CHR\" or [:pick \$devBoardName 0\ + \_3] != \"x86\") do={\r\ + \n :do {\r\ + \n :set RouterBoard [[ :parse \"/system/routerboard/get\" ]];\r\ + \n# :set RouterBoard [ /system/routerboard/get ];\r\ + \n :if ((\$RouterBoard->\"routerboard\") = true) do={\r\ + \n :set IsRouterBoard true;\r\ + \n }\r\ + \n } on-error={}\r\ + \n }\r\ + \n\r\ + \n :do {\r\ + \n :set RunningMode [ /system/device-mode/get mode ];\r\ + \n } on-error={\r\ + \n :do {\r\ + \n :set RunningMode [ /system/device-mode/get running ];\r\ + \n } on-error={\r\ + \n :set RunningMode \"\";\r\ + \n }\r\ + \n }\r\ + \n :set devRunning \$RunningMode;\r\ + \n\r\ + \n :if (\$IsRouterBoard = true) do={\r\ + \n :set devModel [ :tostr (\$RouterBoard->\"model\") ];\r\ + \n :set devRbRevision [ :tostr (\$RouterBoard->\"revision\") ];\r\ + \n :set devRbSerialNumber [ :tostr (\$RouterBoard->\"serial-number\") \ + ];\r\ + \n } else={\r\ + \n :set devModel \$devBoardName;\r\ + \n :set devRbRevision \"\";\r\ + \n :set devRbSerialNumber \"\";\r\ + \n }\r\ + \n\r\ + \n :set currentUpdateChannel [ :tostr (\$Update->\"channel\") ];\r\ + \n :set ROsVerAvail [ :tostr (\$Update->\"latest-version\") ];\r\ + \n\r\ + \n :set (\$DeviceInfo->\"board-name\") \$devBoardName;\r\ + \n :set (\$DeviceInfo->\"architecture-name\") \$devCurArch;\r\ + \n :set (\$DeviceInfo->\"model\") \$devModel;\r\ + \n :set (\$DeviceInfo->\"revision\") \$devRbRevision;\r\ + \n :set (\$DeviceInfo->\"serial-number\") \$devRbSerialNumber;\r\ + \n :set (\$DeviceInfo->\"running-mode\") \$devRunning;\r\ + \n :set (\$DeviceInfo->\"build-time\") \$devBuildTime;\r\ + \n :set (\$DeviceInfo->\"running-channel-version\") \$runningChannelVers\ + ion;\r\ + \n :set (\$DeviceInfo->\"running-version\") \$runningVersion;\r\ + \n :set (\$DeviceInfo->\"running-channel\") \$runningChannel;\r\ + \n :set (\$DeviceInfo->\"routerboard\") \$IsRouterBoard;\r\ + \n :set (\$DeviceInfo->\"current-firmware\") [ :tostr (\$RouterBoard->\"\ + current-firmware\") ];\r\ + \n :set (\$DeviceInfo->\"upgrade-firmware\") [ :tostr (\$RouterBoard->\"\ + upgrade-firmware\") ];\r\ + \n :set (\$DeviceInfo->\"package-channel\") \$currentUpdateChannel;\r\ + \n :set (\$DeviceInfo->\"installed-version\") [ :tostr (\$Update->\"inst\ + alled-version\") ];\r\ + \n :set (\$DeviceInfo->\"latest-version\") \$ROsVerAvail;\r\ + \n :set (\$DeviceInfo->\"update-status\") [ :tostr (\$Update->\"status\"\ + ) ];\r\ + \n\r\ + \n :set arrDeviceInfo \$DeviceInfo;\r\ + \n :return \$DeviceInfo;\r\ + \n}\r\ + \n\r\ + \n:set DeviceInfo do={\r\ + \n :global GetArrDeviceInfo;\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global IfThenElse;\r\ + \n :global FormatLine;\r\ + \n :global CommitBrief;\r\ + \n\r\ + \n :local License [/system/license/get];\r\ + \n :local Snmp [ /snmp/get ];\r\ + \n :local Info [ \$GetArrDeviceInfo ];\r\ + \n\r\ + \n :if ([ :typeof \$Info ] != \"array\") do={\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :return ( \\\r\ + \n [ \$FormatLine \"Hostname\" \$Identity ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Snmp->\"location\") ] > 0) \\\r\ + \n ([ \$FormatLine \"Location\" (\$Snmp->\"location\") ] . \"\\n\") ]\ + \_. \\\r\ + \n [ \$IfThenElse ([ :len (\$Snmp->\"contact\") ] > 0) \\\r\ + \n ([ \$FormatLine \"Contact\" (\$Snmp->\"contact\") ] . \"\\n\") ] .\ + \_\\\r\ + \n \"Hardware:\\n\" . \\\r\ + \n [ \$FormatLine \" Board\" (\$Info->\"board-name\") ] . \"\\n\" . \ + \\\r\ + \n [ \$FormatLine \" Arch\" (\$Info->\"architecture-name\") ] . \"\\\ + n\" . \\\r\ + \n [ \$FormatLine \" Model\" (\$Info->\"model\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"revision\") ] > 0) \\\r\ + \n (\" \" . (\$Info->\"revision\")) ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"serial-number\") ] > 0) \\\r\ + \n ([ \$FormatLine \" Serial\" (\$Info->\"serial-number\") ] . \"\ + \\n\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"nlevel\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License\" (\"level \" . (\$License->\"nlevel\ + \")) ] . \"\\n\") ] . \\\r\ + \n \"RouterOS:\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"level\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License\" (\"level \" . (\$License->\"level\ + \")) ] . \"\\n\") ] . \\\r\ + \n [ \$FormatLine \" Channel\" (\$Info->\"package-channel\") ] . \"\ + \\n\" . \\\r\ + \n [ \$FormatLine \" Installed\" (\$Info->\"installed-version\") ] .\ + \_\"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"running-channel\") ] > 0) \\\r\ + \n ([ \$FormatLine \" Running\" ((\$Info->\"running-version\") . \ + \" (\" . (\$Info->\"running-channel\") . \")\") ] . \"\\n\") \\\r\ + \n ([ \$FormatLine \" Running\" (\$Info->\"running-version\") ] . \ + \"\\n\") ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$Info->\"latest-version\") ] > 0 && \\\r\ + \n (\$Info->\"installed-version\") != (\$Info->\"latest-version\"))\ + \_\\\r\ + \n ([ \$FormatLine \" Available\" (\$Info->\"latest-version\") ] .\ + \_\"\\n\") ] . \\\r\ + \n [ \$IfThenElse ((\$Info->\"routerboard\") = true && \\\r\ + \n (\$Info->\"current-firmware\") != (\$Info->\"upgrade-firmware\")\ + ) \\\r\ + \n ([ \$FormatLine \" Firmware\" ((\$Info->\"current-firmware\") .\ + \_\" -> \" . (\$Info->\"upgrade-firmware\")) ] . \"\\n\") ] . \\\r\ + \n \"RouterOS-Scripts:\\n\" . \\\r\ + \n [ \$FormatLine \" Commit\" [ \$CommitBrief ] ] . \"\\n\" . \\\r\ + \n [ \$FormatLine \" Version\" \$GlobalConfVersion ]);\r\ + \n}\r\ + \n\r\ + \n:if ([ :typeof \$NotificationFunctions ] != \"array\") do={:set Notifica\ + tionFunctions ({})}\r\ + \n\r\ + \n:set GetRandom20CharAlNum do={\r\ + \n :global EitherOr;\r\ + \n :return [ :rndstr length=[ \$EitherOr [ :tonum \$1 ] 20 ] from=\"ABCDE\ + FGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\" ];\r\ + \n}\r\ + \n\r\ + \n:set GetRandom20CharHex do={:global EitherOr; :return [ :rndstr length=[\ + \_\$EitherOr [ :tonum \$1 ] 20 ] from=\"0123456789abcdef\" ]; }\r\ + \n:set GetRandomNumber do={:global EitherOr; :return [ :rndnum from=0 to=[\ + \_\$EitherOr [ :tonum \$1 ] 4294967295 ] ]; }\r\ + \n\r\ + \n:global checkRunJob;\r\ + \n:set checkRunJob do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLock; \r\ + \n :if ([ \$ScriptLock \$Script ] = false) do={:return false}\r\ + \n :foreach Job in=[ /system/script/job/find where script=\$Script ] do={\ + :set Job [ /system/script/job/get \$Job ];\r\ + \n :while ([ :typeof (\$Job->\"parent\") ] = \"id\") do={:set Job [ /sy\ + stem/script/job/get [ find where .id=(\$Job->\"parent\") ] ]}\r\ + \n :if ((\$Job->\"type\") = \"login\") do={:return true};}; :return fa\ + lse; \r\ + \n}\r\ + \n\r\ + \n:set InspectVar do={\r\ + \n :global InspectVarRet;\r\ + \n :put [ :tocrlf [ \$InspectVarRet \$1 ] ];\r\ + \n}\r\ + \n\r\ + \n:set CommitBrief do={\r\ + \n :global CommitId;\r\ + \n :global CommitInfo;\r\ + \n :if (\$CommitId = \"unknown\") do={\r\ + \n :return \"unknown\";\r\ + \n }\r\ + \n :return (\$CommitInfo . \"/\" . [ :pick \$CommitId 0 8 ]);\r\ + \n}\r\ + \n\r\ + \n:set InspectVarRet do={\r\ + \n :local Input \$1;\r\ + \n :local Level (0 + [ :tonum \$2 ]);\r\ + \n :global CharacterReplace;\r\ + \n :global IfThenElse;\r\ + \n :global InspectVarRet;\r\ + \n :local IndentReturn do={\r\ + \n :local Prefix [ :tostr \$1 ];\r\ + \n :local Value [ :tostr \$2 ];\r\ + \n :local Level [ :tonum \$3 ];\r\ + \n :global CharacterMultiply;\r\ + \n :return ([ \$CharacterMultiply \" \" (\$Level * 2) ] . \"-\" . \$Pre\ + fix . \"-> \" . \$Value);\r\ + \n }\r\ + \n :local TypeOf [ :typeof \$Input ];\r\ + \n :local Len [ :len \$Input ];\r\ + \n :local Return [ \$IndentReturn \"type\" \$TypeOf \$Level ];\r\ + \n :if (\$TypeOf = \"array\") do={\r\ + \n :foreach Key,Value in=\$Input do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"key\" \$Key (\$Level + 1) ] . \"\\n\" . \\\r\ + \n [ \$InspectVarRet \$Value (\$Level + 2) ]);\r\ + \n }\r\ + \n } else={\r\ + \n :if (\$TypeOf = \"str\") do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"len\" \$Len \$Level ]);\r\ + \n :if ([ :typeof [ :find \$Input (\"\\r\") ] ] = \"num\") do={\r\ + \n :set Input [ \$CharacterReplace \$Input (\"\\r\") \"\" ];\r\ + \n }\r\ + \n :if ([ :typeof [ :find \$Input (\"\\n\") ] ] = \"num\") do={\r\ + \n :set Input [ \$CharacterReplace \$Input (\"\\n\") \" \" ];\r\ + \n }\r\ + \n }\r\ + \n :if (\$TypeOf != \"nothing\") do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"value\" [ \$IfThenElse ([ :len \$Input ] > 80)\ + \_\\\r\ + \n ([ :pick \$Input 0 77 ] . \"...\") \$Input ] \$Level ]);\r\ + \n }\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set GetPubIp do={\r\ + \n :global DetectIpAddrDefault;\r\ + \n :global DetectIpAddrFallback;\r\ + \n\r\ + \n :local PubIpAddr false;\r\ + \n :local Url \$DetectIpAddrDefault;\r\ + \n :local JobName [ :jobname ];\r\ + \n\r\ + \n :do {\r\ + \n :log debug (\$0 . \" Trying to detect public IP using default serv\ + ice: \" . \$Url . \"...\");\r\ + \n :retry command={:set PubIpAddr ([ /tool/fetch http-method=\"get\"\ + \_check-certificate=yes-without-crl \$Url output=user as-value ]->\"data\"\ + )} \\\r\ + \n delay=50ms max=5 on-error={:log error (\$JobName . \" Failed to \ + detect public IP using default service: \" . \$Url . \".\")}\r\ + \n } on-error={\r\ + \n :log error (\$0 . \" Failed to detect public IP using default serv\ + ice: \" . \$Url . \".\");\r\ + \n :set Url \$DetectIpAddrFallBack;\r\ + \n :log debug (\$0 . \" Trying to detect public IP using fallback ser\ + vice: \" . \$Url . \"...\");\r\ + \n :retry command={:set PubIpAddr ([ /tool/fetch http-method=\"get\"\ + \_check-certificate=yes-without-crl \$Url output=user as-value ]->\"data\"\ + )} \\\r\ + \n delay=200ms max=5 on-error={:log error (\$JobName . \" Failed to\ + \_detect public IP using fallback service: \" . \$Url . \".\")}\r\ + \n };\r\ + \n :local IpLen ([ :len \$PubIpAddr ] -1);\r\ + \n :if (\$PubIpAddr = false) do={:set PubIpAddr \"not-detected\"} else=\ + {:set PubIpAddr [:pick \$PubIpAddr 0 \$IpLen]}\r\ + \n :return \$PubIpAddr;\r\ + \n}\r\ + \n\r\ + \n:set FormatLine do={\r\ + \n :local Key [ :tostr \$1 ];\r\ + \n :local Value [ :tostr \$2 ];\r\ + \n :local Indent [ :tonum \$3 ];\r\ + \n :local Spaces;\r\ + \n :local Return \"\";\r\ + \n :global CharacterMultiply;\r\ + \n :global EitherOr;\r\ + \n :set Indent [ \$EitherOr \$Indent 16 ];\r\ + \n :local Spaces [ \$CharacterMultiply \" \" \$Indent ];\r\ + \n :if ([ :len \$Key ] > 0) do={ :set Return (\$Key . \":\")}\r\ + \n :if ([ :len \$Key ] > (\$Indent - 2)) do={:set Return (\$Return . \"\\\ + n\" . [ :pick \$Spaces 0 \$Indent ] . \$Value);\r\ + \n } else={:set Return (\$Return . [ :pick \$Spaces 0 (\$Indent - [ :len \ + \$Return ]) ] . \$Value)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ParseCustom do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Start [:find [ \$Str ] [:tostr \$2]];\r\ + \n :local End [ :tostr \$3 ];\r\ + \n :local Num [ :tonum \$4 ];\r\ + \n :local Return;\r\ + \n :if ([:len \$Num] = 0) do={:set Num \"0\"}\r\ + \n :local rest [:pick \$Str (\$Start+\$Num) [:len \$Str]]; \r\ + \n :if ([:len \$End ] = 0) do={:set Return [:pick \$Str \$Num \$Start]} e\ + lse={\r\ + \n :set End [:find \$rest \$End];\r\ + \n :set Return [:pick \$rest 0 \$End];\r\ + \n }\r\ + \n:return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set LogPrint do={\r\ + \n :local Severity [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Message [ :tostr \$3 ];\r\ + \n :global PrintDebug;\r\ + \n :global PrintDebugOverride;\r\ + \n :global EitherOr;\r\ + \n :local Debug [ \$EitherOr (\$PrintDebugOverride->\$Name) \$PrintDebug \ + ];\r\ + \n :local PrintSeverity do={\r\ + \n :global TerminalColorOutput;\r\ + \n :if (\$TerminalColorOutput != true) do={:return \$1}\r\ + \n :local Color { debug=96; info=97; warning=93; error=91 };\r\ + \n :return (\"\\1B[\" . \$Color->\$1 . \"m\" . \$1 . \"\\1B[0m\");\r\ + \n}\r\ + \n :local Log ([ \$EitherOr \$Name \"\" ] . \": \" . \$Message);\ + \r\ + \n :if (\$Severity ~ (\"^(custom|debug|error|info)\\\$\")) do={\r\ + \n :if (\$Severity = \"debug\") do={:log debug \$Log}\r\ + \n :if (\$Severity = \"error\") do={:log error \$Log}\r\ + \n :if (\$Severity = \"info\" ) do={:log info \$Log}\r\ + \n } else={:log warning \$Log; :set Severity \"warning\"}\r\ + \n :if (\$Severity != \"debug\" || \$Debug = true) do={:put ([ \$PrintSev\ + erity \$Severity ] . \": \" . \$Message)}\r\ + \n}\r\ + \n\r\ + \n:set CertificateAvailable do={\r\ + \n :local CommonName [ :tostr \$1 ];\r\ + \n :local UseFor [ :tostr \$2 ];\r\ + \n\r\ + \n :global CertificateDownload;\r\ + \n :global ParseKeyValueStore;\r\ + \n\r\ + \n :if ([ :len \$UseFor ] = 0) do={\r\ + \n :log warning (\$0 . \"The intended use is undefined!\");\r\ + \n :set UseFor \"undefined\";\r\ + \n }\r\ + \n :if ([ /system/resource/get free-hdd-space ] < 8388608 && \\\r\ + \n [ /certificate/settings/get crl-download ] = true && \\\r\ + \n [ /certificate/settings/get crl-store ] = \"system\") do={\r\ + \n :log warning (\$0 . \"This system has low free flash space but \" . \ + \\\r\ + \n \"is configured to download certificate CRLs to system!\");\r\ + \n }\r\ + \n :if ([ :len \$CommonName ] = 0) do={\r\ + \n :log warning (\$0 . \"No CommonName given!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if (((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" || \\\ + \r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \$UseFor || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\r\ + \n [ :len [ /certificate/builtin/find where common-name=\$CommonName\ + \_] ] > 0) do={\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n :log info (\$0 . \"Certificate with CommonName '\" . \$CommonName . \ + \"' not available.\");\r\ + \n :if ([ \$CertificateDownload \$CommonName ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] > 1\ + ) do={\r\ + \n :log info (\$0 . \"There are \" . \$CertCount . \" Certificates with\ + \_CommonName '\" . \$CommonName . \"'. Should be ok.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :local CertVal [ /certificate/get [ find where common-name=\$CommonNam\ + e ] ];\r\ + \n :while ((\$CertVal->\"akid\") != \"\" && (\$CertVal->\"akid\") != (\$C\ + ertVal->\"skid\")) do={\r\ + \n :if ([ :len [ /certificate/find where skid=(\$CertVal->\"akid\") ] ]\ + \_= 0) do={\r\ + \n :local IssuerCN ([ \$ParseKeyValueStore (\$CertVal->\"issuer\") ]-\ + >\"CN\");\r\ + \n :log info (\$0 . \"Certificate chain for '\" . \$CommonName . \"' \ + is incomplete, missing '\" . \$IssuerCN . \"'.\");\r\ + \n :if ([ \$CertificateDownload \$IssuerCN ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :set CertVal [ /certificate/get [ find where skid=(\$CertVal->\"akid\ + \") ] ];\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CertificateDownload do={\r\ + \n :local CommonName [ :tostr \$1 ];\r\ + \n :global ScriptUpdatesBaseUrl;\r\ + \n :global ScriptUpdatesUrlSuffix;\r\ + \n :global CertificateNameByCN;\r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :log info (\$0 . \"Downloading and importing certificate with \" . \\\ + \r\ + \n \"CommonName '\" . \$CommonName . \"'.\");\r\ + \n :local FileName ([ \$CleanName \$CommonName ] . \".pem\");\r\ + \n :do {\r\ + \n /tool/fetch check-certificate=yes-without-crl mode=https http-header\ + -field=({ [ \$FetchUserAgentStr \$0 ] }) \\\r\ + \n (\$ScriptUpdatesBaseUrl . \"certs/\" . \$FileName . \$ScriptUpdate\ + sUrlSuffix) \\\r\ + \n dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n } on-error={\r\ + \n :log warning (\$0 . \" Failed downloading certificate with CommonNam\ + e '\" . \$CommonName . \\\r\ + \n \"' from repository! Trying fallback to mkcert.org...\");\r\ + \n :do {\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if ([ :len [ /certificate/find where common-name=\"ISRG Root X1\"\ + \_] ] = 0 && \\\r\ + \n !(((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" \ + || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \"fetch\" || \ + \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\ + \r\ + \n [ :len [ /certificate/builtin/find where common-name=\"ISRG\ + \_Root X1\" ] ] > 0)) do={\r\ + \n :log error (\$0 . \" Required certificate is not available.\");\ + \r\ + \n :return false;\r\ + \n }\r\ + \n /tool/fetch check-certificate=yes-without-crl mode=https http-head\ + er-field=({ [ \$FetchUserAgentStr \$0 ] }) \\\r\ + \n url=\"https://mkcert.org/generate/\" http-data=[ :serialize to=jso\ + n ({ \$CommonName }) ] \\\r\ + \n dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n :if ([ /file/get \$FileName size ] = 0) do={\r\ + \n \$RmFile \$FileName;\r\ + \n :error false;\r\ + \n }\r\ + \n } on-error={\r\ + \n :log error (\$0 . \" Failed downloading certificate with CommonNam\ + e '\" . \$CommonName . \"'!\");\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n /certificate/import file-name=\$FileName passphrase=\"\" as-value;\r\ + \n :delay 1s;\r\ + \n \$RmFile \$FileName;\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n /certificate/remove [ find where name~(\"^\" . \$FileName . \"_[0-9]\ + +\\\$\") ];\r\ + \n :log warning (\$0 . \" Certificate with CommonName '\" . \$CommonNam\ + e . \"' still unavailable!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :foreach Cert in=[ /certificate/find where name~(\"^\" . \$FileName . \ + \"_[0-9]+\\\$\") ] do={\r\ + \n \$CertificateNameByCN [ /certificate/get \$Cert common-name ];\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CertificateNameByCN do={\r\ + \n :local Match [ :tostr \$1 ];\r\ + \n :global CleanName;\r\ + \n :local Cert ([ /certificate/find where (common-name=\$Match or fingerp\ + rint=\$Match or name=\$Match) ]->0);\r\ + \n :if ([ :len \$Cert ] = 0) do={\r\ + \n :log warning (\$0 .\" No matching certificate found.\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local CommonName [ /certificate/get \$Cert common-name ];\r\ + \n /certificate/set \$Cert name=[ \$CleanName \$CommonName ];\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CharacterMultiply do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Num [ :tonum \$2 ];\r\ + \n :if (\$Num = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :for I from=1 to=\$Num do={:set Return (\$Return . \$Str)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set FileExists do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :local Type [ :tostr \$2 ];\r\ + \n :global FileGet;\r\ + \n :local FileVal [ \$FileGet \$FileName ];\r\ + \n :if (\$FileVal = false) do={ :return false; }\r\ + \n :if ([ :len (\$FileVal->\"size\") ] = 0) do={ :return false; }\r\ + \n :if ([ :len \$Type ] = 0 || \$FileVal->\"type\" = \$Type) do={ :return\ + \_true; }\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set Grep do={\r\ + \n :local Input ([ :tostr \$1 ] . \"\\n\");\r\ + \n :local Pattern [ :tostr \$2 ];\r\ + \n :if ([ :typeof [ :find \$Input \$Pattern ] ] = \"nil\") do={:return []\ + }\r\ + \n :do {\r\ + \n :local Line [ :pick \$Input 0 [ :find \$Input \"\\n\" ] ];\r\ + \n :if ([ :typeof [ :find \$Line \$Pattern ] ] = \"num\") do={:return \ + \$Line}\r\ + \n :set Input [ :pick \$Input ([ :find \$Input \"\\n\" ] + 1) [ :len \$\ + Input ] ];\r\ + \n } while=([ :len \$Input ] > 0);\r\ + \n :return [];\r\ + \n}\r\ + \n\r\ + \n:set HumanReadableNum do={\r\ + \n :local Input [ :tonum \$1 ];\r\ + \n :local Base [ :tonum \$2 ];\r\ + \n :global EitherOr;\r\ + \n :global IfThenElse;\r\ + \n :local Prefix \"kMGTPE\";\r\ + \n :local Pow 1;\r\ + \n :set Base [ \$EitherOr \$Base 1024 ];\r\ + \n :local Bin [ \$IfThenElse (\$Base = 1024) \"i\" \"\" ];\r\ + \n :if (\$Input < \$Base) do={:return \$Input}\r\ + \n :for I from=0 to=[ :len \$Prefix ] do={\r\ + \n :set Pow (\$Pow * \$Base);\r\ + \n :if (\$Input / \$Base < \$Pow) do={\r\ + \n :set Prefix [ :pick \$Prefix \$I ];\r\ + \n :local Tmp1 (\$Input * 100 / \$Pow);\r\ + \n :local Tmp2 (\$Tmp1 / 100);\r\ + \n :if (\$Tmp2 >= 100) do={:return (\$Tmp2 . \$Prefix . \$Bin)}\r\ + \n :return (\$Tmp2 . \".\" . \\\r\ + \n [ :pick \$Tmp1 [ :len \$Tmp2 ] ([ :len \$Tmp1 ] - [ :len \$Tmp\ + 2 ] + 1) ] . \\\r\ + \n \$Prefix . \$Bin);\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set CleanFilePath do={\r\ + \n :local Path [ :tostr \$1 ];\r\ + \n :global CharacterReplace;\r\ + \n :while (\$Path ~ \"//\") do={ :set \$Path [ \$CharacterReplace \$Path \ + \"//\" \"/\" ]; }\r\ + \n :if ([ :pick \$Path 0 ] = \"/\") do={ :set Path [ :pick \$Path 1 [ :le\ + n \$Path ] ]; }\r\ + \n :if ([ :pick \$Path ([ :len \$Path ] - 1) ] = \"/\") do={ :set Path [ \ + :pick \$Path 0 ([ :len \$Path ] - 1) ]; }\r\ + \n :return \$Path;\r\ + \n}\r\ + \n\r\ + \n:set CleanName do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :local Return \"\";\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :if ([ :typeof [ find \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr\ + stuvwxyz0123456789\" \$Char ] ] = \"nil\") do={\r\ + \n :do {:if ([ :len \$Return ] = 0) do={:error true};\r\ + \n :if ([ :pick \$Return ([ :len \$Return ] - 1) ] = \"-\") do={:er\ + ror true}; :set Char \"-\"} on-error={:set Char \"\"}; \r\ + \n };\r\ + \n :set Return (\$Return . \$Char)};\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set RandomDelay do={\r\ + \n :local Time [ :tonum \$1 ];\r\ + \n :local Unit [ :tostr \$2 ];\r\ + \n :global EitherOr;\r\ + \n :global GetRandomNumber;\r\ + \n :global MAX;\r\ + \n :if (\$Time = 0) do={:return false}\r\ + \n :delay ([ \$MAX 10 [ \$GetRandomNumber ([ :tonsec [ :totime (\$Time . \ + [ \$EitherOr \$Unit \"s\" ]) ] ] / 1000000) ] ] . \"ms\");\r\ + \n}\r\ + \n\r\ + \n:set EscapeForRegEx do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :if ([ :len \$Input ] = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :local Chars (\"^.[]\\\$()|*+\?{}\\\\\");\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :if ([ :find \$Chars \$Char ]) do={:set Char (\"\\\\\" . \$Char)}\r\ + \n :set Return (\$Return . \$Char);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ExitError do={\r\ + \n :local ExitOK [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Error [ :tostr \$3 ];\r\ + \n :global IfThenElse;\r\ + \n :if (\$ExitOK = \"false\") do={\r\ + \n :log error (\$Name [ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\\\$\") \ + \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\ + ror\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \ + \$Error) \".\" ]);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set ExitOnError do={\r\ + \n :local Name [ :tostr \$1 ];\r\ + \n :local Error [ :tostr \$2 ];\r\ + \n :global IfThenElse;\r\ + \n :log error (\$Name . \" \" . [ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\ + \\\$\") \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with erro\ + r\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \$E\ + rror) \".\" ]);\r\ + \n}\r\ + \n\r\ + \n:set MiniDateTimeStamp do={\r\ + \n :local mikro [ :tobool \$1 ];\r\ + \n /system/clock;\r\ + \n :local vdate [get date];\r\ + \n :local vtime [get time];\r\ + \n :local vdoff [:toarray \"0,4,5,7,8,10\"];\r\ + \n :local MM [:pick \$vdate (\$vdoff->2) (\$vdoff->3)];\r\ + \n :local M [:tonum \$MM];\r\ + \n :if (\$vdate ~ \".../../....\") do={\r\ + \n :set vdoff [:toarray \"7,11,1,3,4,6\"];\r\ + \n :set M ([:find \"xxanebarprayunulugepctovecANEBARPRAYUNULUGE\ + PCTOVEC\" [:pick \$vdate (\$vdoff->2) (\$vdoff->3)] -1] / 2);\r\ + \n :if (\$M>12) do={:set M (\$M - 12)}\r\ + \n :set MM [:pick (100 + \$M) 1 3];\r\ + \n }\r\ + \n :local yyyy [:pick \$vdate (\$vdoff->0) (\$vdoff->1)];\r\ + \n :local dd [:pick \$vdate (\$vdoff->4) (\$vdoff->5)];\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 :local Return;\r\ + \n :if (\$mikro = true) do={\r\ + \n :set Return ([:pick \$yyyy 2 4] . [:pick \$dd 0 1] . [:pick \$HH 0\ + \_1] . [:pick \$mm 3 4] . [:pick \$ss 6 7]);\r\ + \n } else={\r\ + \n :set Return ([\$yyyy] . [\$MM] . [\$dd] . [\$HH] . [\$mm] . [\$ss]);\ + \r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set WaitForFile do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :local WaitTime [ :totime \$2 ];\r\ + \n :global CleanFilePath;\r\ + \n :global EitherOr;\r\ + \n :global MAX;\r\ + \n :set FileName [ \$CleanFilePath \$FileName ];\r\ + \n :local Delay ([ \$MAX [ \$EitherOr \$WaitTime 2s ] 100ms ] / 9);\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/get \$FileName;\r\ + \n :return true;\r\ + \n } delay=\$Delay max=10;\r\ + \n } on-error={ }\r\ + \n :while ([ :len [ /file/find where name=\$FileName ] ] > 0) do={\r\ + \n :do {\r\ + \n /file/get \$FileName;\r\ + \n :return true;\r\ + \n } on-error={ }\r\ + \n :delay \$Delay;\r\ + \n :set Delay (\$Delay * 3 / 2);\r\ + \n }\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set FileGet do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :global WaitForFile;\r\ + \n :if ([ \$WaitForFile \$FileName 0s ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n :local FileVal false;\r\ + \n :do {\r\ + \n :set FileVal [ /file/get \$FileName ];\r\ + \n } on-error={ }\r\ + \n :return \$FileVal;\r\ + \n}\r\ + \n\r\ + \n:set RmDir do={\r\ + \n :local DirName [ :tostr \$1 ];\r\ + \n :global FileGet;\r\ + \n :log debug (\$0 . \" Removing directory: \". \$DirName);\r\ + \n :local DirVal [ \$FileGet \$DirName ];\r\ + \n :if (\$DirVal = false) do={\r\ + \n :log debug (\$0 . \" ... which does not exist.\");\r\ + \n :return true;\r\ + \n };\r\ + \n :if (\$DirVal->\"type\" != \"directory\") do={\r\ + \n :log error (\$0 . \" Directory '\" . \$DirName . \"' is not a direct\ + ory.\");\r\ + \n :return false;\r\ + \n };\r\ + \n :onerror Err {\r\ + \n /file/remove \$DirName;\r\ + \n } do={\r\ + \n :log error (\$0 . \" Removing directory '\" . \$DirName . \"' failed\ + : \" . \$Err);\r\ + \n :return false;\r\ + \n };\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set RmFile do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :global FileGet;\r\ + \n :log debug (\$0 . \" Removing file: \". \$FileName);\r\ + \n :local FileVal [ \$FileGet \$FileName ];\r\ + \n :if (\$FileVal = false) do={\r\ + \n :log debug (\$0 . \" ... which does not exist.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :if (\$FileVal->\"type\" = \"directory\" || \$FileVal->\"type\" = \"di\ + sk\") do={\r\ + \n :log error (\$0 . \" File '\" . \$FileName . \"' is not a file.\");\ + \r\ + \n :return false;\r\ + \n }\r\ + \n :onerror Err {\r\ + \n /file/remove \$FileName;\r\ + \n } do={\r\ + \n :log error (\$0 . \" Removing file '\" . \$FileName . \"' failed: \"\ + \_. \$Err); :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set MkDir do={\r\ + \n :local Path [ :tostr \$1 ];\r\ + \n :global CleanFilePath;\r\ + \n :global FileGet;\r\ + \n :global RmDir;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :local MkTmpfs do={\r\ + \n :global WaitForFile;\r\ + \n :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ];\r\ + \n :if ([ :len \$TmpFs ] = 1) do={\r\ + \n :if ([ /disk/get \$TmpFs disabled ] = true) do={\r\ + \n :log info (\$0 . \" The tmpfs is disabled, enabling.\");\r\ + \n /disk/enable \$TmpFs;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :log info (\$0 . \" Creating disk of type tmpfs.\");\r\ + \n \$RmDir \"tmpfs\";\r\ + \n :onerror Err {\r\ + \n /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource\ + /get total-memory ] / 3);\r\ + \n \$WaitForFile \"tmpfs\";\r\ + \n } do={\r\ + \n :log warning (\$0 . \" Creating disk of type tmpfs failed: \" . \$\ + Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :set Path [ \$CleanFilePath \$Path ];\r\ + \n :if (\$Path = \"\") do={\r\ + \n :return true;\r\ + \n }\r\ + \n :log debug (\$0 . \" Making directory: \" . \$Path);\r\ + \n :local PathVal [ \$FileGet \$Path ];\r\ + \n :if (\$PathVal->\"type\" = \"directory\") do={\r\ + \n :log debug (\$0 . \" ... which already exists.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ :pick \$Path 0 5 ] = \"tmpfs\") do={\r\ + \n :if ([ \$MkTmpfs ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :onerror Err {\r\ + \n /file/add type=\"directory\" name=\$Path;\r\ + \n \$WaitForFile \$Path;\r\ + \n } do={\r\ + \n :log warning (\$0 . \" Making directory '\" . \$Path . \"' failed: \ + \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set MAX do={:if (\$1 > \$2) do={:return \$1}\r\ + \n :return \$2;\r\ + \n}\r\ + \n:set MIN do={:if (\$1 < \$2) do={:return \$1}\r\ + \n :return \$2;\r\ + \n}\r\ + \n\r\ + \n:set CharacterMultiply do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Num [ :tonum \$2 ];\r\ + \n :if (\$Num = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :for I from=1 to=\$Num do={:set Return (\$Return . \$Str)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set CharacterReplace do={\r\ + \n :local String [ :tostr \$1 ];\r\ + \n :local ReplaceFrom [ :tostr \$2 ];\r\ + \n :local ReplaceWith [ :tostr \$3 ];\r\ + \n :local Return \"\";\r\ + \n :if (\$ReplaceFrom = \"\") do={:return \$String}\r\ + \n :while ([ :typeof [ :find \$String \$ReplaceFrom ] ] != \"nil\") do={\ + \r\ + \n :local Pos [ :find \$String \$ReplaceFrom ];\r\ + \n :set Return (\$Return . [ :pick \$String 0 \$Pos ] . \$ReplaceWith);\ + \r\ + \n :set String [ :pick \$String (\$Pos + [ :len \$ReplaceFrom ]) [ :len\ + \_\$String ] ];\r\ + \n }\r\ + \n :return (\$Return . \$String);\r\ + \n}\r\ + \n\r\ + \n:set IfThenElse do={\r\ + \n :if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] = true) do={:return \ + \$2};\r\ + \n :return \$3;\r\ + \n}\r\ + \n\r\ + \n:set EitherOr do={\r\ + \n :global IfThenElse;\r\ + \n :if ([ :typeof \$1 ] = \"num\") do={:return [ \$IfThenElse (\$1 != 0) \ + \$1 \$2 ]}\r\ + \n :if ([ :typeof \$1 ] = \"time\") do={:return [ \$IfThenElse (\$1 > 0s)\ + \_\$1 \$2 ]}\r\ + \n# this works for boolean values, literal ones with parentheses\r\ + \n :return [ \$IfThenElse ([ :len [ :tostr \$1 ] ] > 0) \$1 \$2 ];\r\ + \n}\r\ + \n\r\ + \n:set ParseKeyValueStore do={\r\ + \n :local Source \$1;\r\ + \n :if ([ :pick \$Source 0 1 ] = \"{\") do={\r\ + \n :do {\r\ + \n :return [ :deserialize from=json \$Source ];\r\ + \n } on-error={ }\r\ + \n } \r\ + \n :if ([ :typeof \$Source ] != \"array\") do={:set Source [ :tostr \$1 ]\ + }\r\ + \n :local Result ({});\r\ + \n :foreach KeyValue in=[ :toarray \$Source ] do={\r\ + \n :if ([ :find \$KeyValue \"=\" ]) do={\r\ + \n :local Key [ :pick \$KeyValue 0 [ :find \$KeyValue \"=\" ] ];\r\ + \n :local Value [ :pick \$KeyValue ([ :find \$KeyValue \"=\" ] + 1) [\ + \_:len \$KeyValue ] ];\r\ + \n :if (\$Value=\"true\") do={:set Value true}\r\ + \n :if (\$Value=\"false\") do={:set Value false}\r\ + \n :set (\$Result->\$Key) \$Value;\r\ + \n } else={:set (\$Result->\$KeyValue) true}\r\ + \n }\r\ + \n :return \$Result;\r\ + \n}\r\ + \n:set RequiredRouterOS do={\r\ + \n :local Caller [ :tostr \$1 ];\r\ + \n :local Required [ :tostr \$2 ];\r\ + \n :local Warn [ :tostr \$3 ];\r\ + \n :global IfThenElse;\r\ + \n :global VersionToNum;\r\ + \n :if (!(\$Required ~ \"^\\\\d+\\\\.\\\\d+((alpha|beta|rc|\\\\.)\\\\d+|)\ + \\\$\")) do={\r\ + \n :log error (\$0 . \" No valid RouterOS version: \" . \$Required);\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ \$VersionToNum \$Required ] > [ \$VersionToNum [ /system/packag\ + e/update/get installed-version ] ]) do={\r\ + \n :if (\$Warn = \"true\") do={\r\ + \n :log warning (\$0 . \" This \" . [ \$IfThenElse ([ :pick \$Caller \ + 0 ] = (\"\\\$\")) \"function\" \"script\" ] . \\\r\ + \n \" '\" . \$Caller . \"' (at least specific functionality) requir\ + es RouterOS \" . \$Required . \". Please update!\");\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set ValidateSyntax do={\r\ + \n :local Code [ :tostr \$1 ];\r\ + \n :global LogPrint;\r\ + \n :onerror Err {\r\ + \n [ :parse (\":local Validate do={\\n\" . \$Code . \"\\n}\") ];\r\ + \n } do={\r\ + \n \$LogPrint debug \$0 (\"Valdation failed: \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set VersionToNum do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :local Multi 0x1000000;\r\ + \n :local Return 0;\r\ + \n :global CharacterReplace;\r\ + \n :set Input [ \$CharacterReplace \$Input \".\" \",\" ];\r\ + \n :foreach I in={ \"zero\"; \"alpha\"; \"beta\"; \"rc\" } do={\r\ + \n :set Input [ \$CharacterReplace \$Input \$I (\",\" . \$I . \",\") ];\ + \r\ + \n }\r\ + \n :foreach Value in=([ :toarray \$Input ], 0) do={\r\ + \n :local Num [ :tonum \$Value ];\r\ + \n :if (\$Multi = 0x100) do={\r\ + \n :if ([ :typeof \$Num ] = \"num\") do={\r\ + \n :set Return (\$Return + 0xff00);\r\ + \n :set Multi (\$Multi / 0x100);\r\ + \n } else={\r\ + \n :if (\$Value = \"zero\") do={ }\r\ + \n :if (\$Value = \"alpha\") do={ :set Return (\$Return + 0x3f00); \ + }\r\ + \n :if (\$Value = \"beta\") do={ :set Return (\$Return + 0x5f00); }\ + \r\ + \n :if (\$Value = \"rc\") do={ :set Return (\$Return + 0x7f00); }\r\ + \n }\r\ + \n }\r\ + \n :if ([ :typeof \$Num ] = \"num\") do={ :set Return (\$Return + (\$Va\ + lue * \$Multi)); }\r\ + \n :set Multi (\$Multi / 0x100);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ScriptLock do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local WaitMax [ :totime \$2 ];\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global ScriptLockOrder;\r\ + \n :if ([ :typeof \$ScriptLockOrder ] = \"nothing\") do={\r\ + \n :set ScriptLockOrder ({});\r\ + \n }\r\ + \n :if ([ :typeof (\$ScriptLockOrder->\$Script) ] = \"nothing\") do={\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n }\r\ + \n :local JobCount do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :return [ :len [ /system/script/job/find where script=\$Script ] ];\ + \r\ + \n }\r\ + \n :local TicketCount do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :local Count 0;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\") do={:set Count (\$Count \ + + 1)}\r\ + \n }\r\ + \n :return \$Count;\r\ + \n }\r\ + \n :local IsFirstTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Check [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if (\$Ticket = \$Check) do={:return true}\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\" && \$Ticket != \$Check) d\ + o={:return false}\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :local AddTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Add [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :while (true) do={\r\ + \n :local Pos [ :len (\$ScriptLockOrder->\$Script) ];\r\ + \n :set (\$ScriptLockOrder->\$Script->\$Pos) \$Add;\r\ + \n :delay 10ms;\r\ + \n :if ((\$ScriptLockOrder->\$Script->\$Pos) = \$Add) do={:return tru\ + e}\r\ + \n }\r\ + \n }\r\ + \n :local RemoveTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Remove [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Id,Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :while ((\$ScriptLockOrder->\$Script->\$Id) = \$Remove) do={\r\ + \n :set (\$ScriptLockOrder->\$Script->\$Id);\r\ + \n :delay 10ms;\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n :local CleanupTickets do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\") do={:return false}\r\ + \n }\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n }\r\ + \n :if ([ :typeof \$WaitMax ] = \"nil\" ) do={:set WaitMax 0s}\r\ + \n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\r\ + \n :log error (\$0 . \" A script named '\" . \$Script . \"' does not ex\ + ist!\");\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$JobCount \$Script ] = 0) do={\r\ + \n \$LogPrint error \$0 (\"No script '\" . \$Script . \"' is running!\"\ + );\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$TicketCount \$Script ] >= [ \$JobCount \$Script ]) do={\r\ + \n :log error (\$0 . \" More tickets than running scripts '\" . \$Scrip\ + t . \"', resetting!\");\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n /system/script/job/remove [ find where script=\$Script ];\r\ + \n }\r\ + \n :local MyTicket [ \$GetRandom20CharAlNum 6 ];\r\ + \n \$AddTicket \$Script \$MyTicket;\r\ + \n :local WaitInterval (\$WaitMax / 20);\r\ + \n :local WaitTime \$WaitMax;\r\ + \n :while (\$WaitTime > 0 && \\\r\ + \n ([ \$IsFirstTicket \$Script \$MyTicket ] = false || \\\r\ + \n [ \$TicketCount \$Script ] < [ \$JobCount \$Script ])) do={\r\ + \n :set WaitTime (\$WaitTime - \$WaitInterval);\r\ + \n :delay \$WaitInterval;\r\ + \n }\r\ + \n :if ([ \$IsFirstTicket \$Script \$MyTicket ] = true && \\\r\ + \n [ \$TicketCount \$Script ] = [ \$JobCount \$Script ]) do={\r\ + \n \$RemoveTicket \$Script \$MyTicket;\r\ + \n \$CleanupTickets \$Script;\r\ + \n :return true;\r\ + \n }\r\ + \n \$RemoveTicket \$Script \$MyTicket;\r\ + \n \$LogPrint debug \$0 (\"Script '\" . \$Script . \"' started more than \ + once\" . \\\r\ + \n [ \$IfThenElse (\$WaitTime < \$WaitMax) \" and timed out waiting for\ + \_lock\" \"\" ] . \"...\");\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set SendNotification do={ :onerror Err {\r\ + \n :global SendNotification2;\r\ + \n \$SendNotification2 ({ origin=\$0; subject=\$1; message=\$2; link=\$3;\ + \_silent=\$4 });\r\ + \n} do={:global ExitOnError; \$ExitOnError \$0 \$Err}\r\ + \n}\r\ + \n\r\ + \n:set SendNotification2 do={\r\ + \n :local Notification \$1;\r\ + \n :global NotificationFunctions;\r\ + \n :foreach FunctionName,Discard in=\$NotificationFunctions do={\r\ + \n (\$NotificationFunctions->\$FunctionName) (\"\\\$NotificationFunctio\ + ns->\\\"\" . \$FunctionName . \"\\\"\") \$Notification;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set SymbolByUnicodeName do={\r\ + \n :local Name [ :tostr \$1 ];\r\ + \n :global EitherOr;\r\ + \n :global SymbolsExtra;\r\ + \n :local Symbols ({\r\ + \n \"abacus\"=\"\\F0\\9F\\A7\\AE\";\r\ + \n \"alarm-clock\"=\"\\E2\\8F\\B0\";\r\ + \n \"arrow-down\"=\"\\E2\\AC\\87\";\r\ + \n \"arrow-up\"=\"\\E2\\AC\\86\";\r\ + \n \"calendar\"=\"\\F0\\9F\\93\\85\";\r\ + \n \"card-file-box\"=\"\\F0\\9F\\97\\83\";\r\ + \n \"chart-decreasing\"=\"\\F0\\9F\\93\\89\";\r\ + \n \"chart-increasing\"=\"\\F0\\9F\\93\\88\";\r\ + \n \"cloud\"=\"\\E2\\98\\81\";\r\ + \n \"cross-mark\"=\"\\E2\\9D\\8C\";\r\ + \n \"earth\"=\"\\F0\\9F\\8C\\8D\";\r\ + \n \"fire\"=\"\\F0\\9F\\94\\A5\";\r\ + \n \"floppy-disk\"=\"\\F0\\9F\\92\\BE\";\r\ + \n \"gear\"=\"\\E2\\9A\\99\";\r\ + \n \"heart\"=\"\\E2\\99\\A5\";\r\ + \n \"high-voltage-sign\"=\"\\E2\\9A\\A1\";\r\ + \n \"incoming-envelope\"=\"\\F0\\9F\\93\\A8\";\r\ + \n \"information\"=\"\\E2\\84\\B9\";\r\ + \n \"large-orange-circle\"=\"\\F0\\9F\\9F\\A0\";\r\ + \n \"large-red-circle\"=\"\\F0\\9F\\94\\B4\";\r\ + \n \"link\"=\"\\F0\\9F\\94\\97\";\r\ + \n \"lock-with-ink-pen\"=\"\\F0\\9F\\94\\8F\";\r\ + \n \"memo\"=\"\\F0\\9F\\93\\9D\";\r\ + \n \"mobile-phone\"=\"\\F0\\9F\\93\\B1\";\r\ + \n \"pushpin\"=\"\\F0\\9F\\93\\8C\";\r\ + \n \"scissors\"=\"\\E2\\9C\\82\";\r\ + \n \"scroll\"=\"\\F0\\9F\\93\\9C\";\r\ + \n \"smiley-partying-face\"=\"\\F0\\9F\\A5\\B3\";\r\ + \n \"smiley-smiling-face\"=\"\\E2\\98\\BA\";\r\ + \n \"smiley-winking-face-with-tongue\"=\"\\F0\\9F\\98\\9C\";\r\ + \n \"sparkles\"=\"\\E2\\9C\\A8\";\r\ + \n \"speech-balloon\"=\"\\F0\\9F\\92\\AC\";\r\ + \n \"star\"=\"\\E2\\AD\\90\";\r\ + \n \"warning-sign\"=\"\\E2\\9A\\A0\";\r\ + \n \"white-heavy-check-mark\"=\"\\E2\\9C\\85\"\r\ + \n }, \$SymbolsExtra);\r\ + \n :local Magic [ :pick [ /system/clock/get date ] 4 10 ];\r\ + \n :local Special {\r\ + \n \"information-04-01\"=\"\\F0\\9F\\9A\\BB\";\r\ + \n \"large-orange-circle-04-01\"=\"\\F0\\9F\\8D\\8A\";\r\ + \n \"large-orange-circle-10-31\"=\"\\F0\\9F\\8E\\83\";\r\ + \n \"large-red-circle-04-01\"=\"\\F0\\9F\\8D\\92\" };\r\ + \n :if ([ :len (\$Symbols->\$Name) ] = 0) do={\r\ + \n :log warning (\$0 . \"No symbol available for name '\" . \$Name . \"\ + '!\");\r\ + \n :return \"\";\r\ + \n }\r\ + \n :return ([ \$EitherOr (\$Special->(\$Name . \$Magic)) (\$Symbols->\$Na\ + me) ] . \"\\EF\\B8\\8F\");\r\ + \n}\r\ + \n\r\ + \n:set SymbolForNotification do={\r\ + \n :global NotificationsWithSymbols;\r\ + \n :global SymbolByUnicodeName;\r\ + \n :global IfThenElse;\r\ + \n :if (\$NotificationsWithSymbols != true) do={:return [ \$IfThenElse ([\ + \_:len \$2 ] > 0) ([ :tostr \$2 ] . \" \") \"\" ]}\r\ + \n :local Return \"\";\r\ + \n :foreach Symbol in=[ :toarray \$1 ] do={:set Return (\$Return . [ \$Sy\ + mbolByUnicodeName \$Symbol ])}\r\ + \n :return (\$Return . \" \");\r\ + \n}\r\ + \n\r\ + \n:set Unix2Dos do={:return [ :tocrlf [ :tostr \$1 ] ]}\r\ + \n:set UrlEncode do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :if ([ :len \$Input ] = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :local Chars (\"\\n\\r !\\\"#\\\$%&'()*+,:;<=>\?@[\\\\]^`{|}~\");\r\ + \n :local Subs { \"%0A\"; \"%0D\"; \"%20\"; \"%21\"; \"%22\"; \"%23\"; \"\ + %24\"; \"%25\"; \"%26\"; \"%27\";\r\ + \n \"%28\"; \"%29\"; \"%2A\"; \"%2B\"; \"%2C\"; \"%3A\"; \"%3B\"; \ + \"%3C\"; \"%3D\"; \"%3E\"; \"%3F\";\r\ + \n \"%40\"; \"%5B\"; \"%5C\"; \"%5D\"; \"%5E\"; \"%60\"; \"%7B\"; \ + \"%7C\"; \"%7D\"; \"%7E\" };\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :local Replace [ :find \$Chars \$Char ];\r\ + \n :if ([ :typeof \$Replace ] = \"num\") do={:set Char (\$Subs->\$Repla\ + ce)}\r\ + \n :set Return (\$Return . \$Char);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set IsDNSResolving do={\r\ + \n :do {:resolve \"api.telegram.org\"} on-error={:return false}\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set IsFullyConnected do={\r\ + \n :global IsDNSResolving;\r\ + \n :global IsTimeSync;\r\ + \n :if ([ \$IsDNSResolving ] = false) do={:return false};\r\ + \n :if ([ \$IsTimeSync ] = false) do={:return false};\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set IsTimeSync do={\r\ + \n :global IsTimeSyncCached;\r\ + \n :global IsTimeSyncResetNtp;\r\ + \n :if (\$IsTimeSyncCached = true) do={:return true}\r\ + \n :if ([ /system/ntp/client/get enabled ] = true) do={\r\ + \n :if ([ /system/ntp/client/get status ] = \"synchronized\") do={\r\ + \n :set IsTimeSyncCached true;\r\ + \n :return true;\r\ + \n }\r\ + \n :local Uptime [ /system/resource/get uptime ];\r\ + \n :if ([ :typeof \$IsTimeSyncResetNtp ] = \"nothing\") do={\r\ + \n :set IsTimeSyncResetNtp \$Uptime;\r\ + \n }\r\ + \n :if (\$Uptime - \$IsTimeSyncResetNtp < 3m) do={:return false}\r\ + \n :log warning \"\$0 The ntp client is configured, but did not sync.\"\ + ;\r\ + \n :set IsTimeSyncResetNtp \$Uptime;\r\ + \n /system/ntp/client/set enabled=no;\r\ + \n :delay 20ms;\r\ + \n /system/ntp/client/set enabled=yes;\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ /system/license/get ]->\"level\" = \"free\" || \\\r\ + \n [ /system/resource/get ]->\"board-name\" = \"x86\") do={\r\ + \n :log debug \"\$0 No ntp client configured, relying on RTC for CHR fr\ + ee license and x86.\";\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ /ip/cloud/get update-time ] = true) do={\r\ + \n :if ([ :typeof [ /ip/cloud/get public-address ] ] = \"ip\") do={\r\ + \n :set IsTimeSyncCached true;\r\ + \n :return true;\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :log debug \"\$0 No time source configured! Returning gracefully...\";\ + \r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set FetchHugeFiles do={\r\ + \n :local ScriptName [ :tostr \$1 ];\r\ + \n :local Url [ :tostr \$2 ];\r\ + \n :local CheckCert [ :tostr \$3 ];\r\ + \n\r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global MkDir;\r\ + \n :global RmDir;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :set CheckCert [ \$IfThenElse (\$CheckCert = \"false\") \"no\" \"yes-w\ + ithout-crl\" ];\r\ + \n\r\ + \n :local DirName (\"tmpfs/\" . [ \$CleanName \$ScriptName ]);\r\ + \n :if ([ \$MkDir \$DirName ] = false) do={\r\ + \n \$LogPrint error \$0 (\"Failed creating directory!\");\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :local FileName (\$DirName . \"/\" . [ \$CleanName \$0 ] . \"-\" . [ \ + \$GetRandom20CharAlNum ]);\r\ + \n :onerror Err {\r\ + \n /tool/fetch check-certificate=\$CheckCert \$Url dst-path=\$FileName \ + \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$ScriptName ] }) as-va\ + lue;\r\ + \n } do={\r\ + \n :if ([ \$WaitForFile \$FileName 500ms ] = true) do={\r\ + \n \$RmFile \$FileName;\r\ + \n }\r\ + \n \$LogPrint debug \$0 (\"Failed downloading from \" . \$Url . \" - \"\ + \_. \$Err);\r\ + \n \$RmDir \$DirName;\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$WaitForFile \$FileName 5s ] = false) do={\r\ + \n \$LogPrint debug \$0 (\"The file downloaded from \" . \$Url . \" did\ + \_not show up.\");\r\ + \n :return false;\r\ + \n }\r\ + \n\r\ + \n :local FileSize [ /file/get \$FileName size ];\r\ + \n :local Return \"\";\r\ + \n :local VarSize 0;\r\ + \n :while (\$VarSize != \$FileSize) do={\r\ + \n :set Return (\$Return . ([ /file/read offset=\$VarSize chunk-size=32\ + 768 file=\$FileName as-value ]->\"data\"));\r\ + \n :set FileSize [ /file/get \$FileName size ];\r\ + \n :set VarSize [ :len \$Return ];\r\ + \n :if (\$VarSize > \$FileSize) do={\r\ + \n :delay 100ms;\r\ + \n }\r\ + \n }\r\ + \n \$RmDir \$DirName;\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set FetchUserAgentStr do={\r\ + \n :local Caller [ :tostr \$1 ];\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global CommitBrief;\r\ + \n :global IfThenElse;\r\ + \n :local Resource [ /system/resource/get ];\r\ + \n :return (\"User-Agent: Mikrotik/\" . \$Resource->\"version\" . \" \" .\ + \_\$Resource->\"architecture-name\" . \\\r\ + \n \" \" . \$Caller . \"/Fetch (\" . \$GlobalScriptsUrl . \"; \" . [ \$\ + CommitBrief ] . \")\");\r\ + \n}\r\ + \n\r\ + \n:set ScriptInstallUpdate do={ :onerror Err {\r\ + \n :local Scripts [ :toarray \$1 ];\r\ + \n :local NewComment [ :tostr \$2 ];\r\ + \n :global InitSetup;\r\ + \n\r\ + \n :global CommitId;\r\ + \n :global ScriptUpdatesCRLF;\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global CertificateAvailable;\r\ + \n :global CheckSumsVerify;\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global SumsDirAndFileName;\r\ + \n :global GlobalScriptsUrlSuffix;\r\ + \n :global EitherOr;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global Grep;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global ParseKeyValueStore;\r\ + \n :global RequiredRouterOS;\r\ + \n :global SendNotification2;\r\ + \n :global SymbolForNotification;\r\ + \n :global ValidateSyntax;\r\ + \n :global CommentsInScripts;\r\ + \n\r\ + \n :if ([ \$CertificateAvailable \"Root YE\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading certificate failed, trying wit\ + hout.\");\r\ + \n }\r\ + \n\r\ + \n :foreach Script in=\$Scripts do={\r\ + \n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\ + \r\ + \n \$LogPrint info \$0 (\"Adding new script: \" . \$Script);\r\ + \n /system/script/add name=\$Script owner=\$Script source=\"#!rsc by \ + Vados\\n\" comment=\$NewComment;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :local CommitIdBefore \$CommitId;\r\ + \n :local GlobalConfVersionBefore \$GlobalConfVersion;\r\ + \n :local ReloadGlobal false;\r\ + \n :local DeviceMode [ /system/device-mode/get ];\r\ + \n\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :local CheckSums ({});\r\ + \n :do {\r\ + \n :local Url (\$GlobalScriptsUrl . \$SumsDirAndFileName . \$GlobalScr\ + iptsUrlSuffix);\r\ + \n \$LogPrint debug \$0 (\"Fetching checksums from url: \" . \$Url);\ + \r\ + \n :set CheckSums [ :deserialize from=json ([ /tool/fetch check-certi\ + ficate=yes-without-crl \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url output\ + =user as-value ]->\"data\") ];\r\ + \n } on-error={ \$LogPrint warning \$0 (\"Err = \" . \$Err); }\r\ + \n }\r\ + \n\r\ + \n :foreach Script in=[ /system/script/find where source~\"^#!rsc by Vado\ + s\" ] do={\r\ + \n :local ScriptVal [ /system/script/get \$Script ];\r\ + \n :local ScriptInfo [ \$ParseKeyValueStore (\$ScriptVal->\"comment\") \ + ];\r\ + \n :local SourceNew;\r\ + \n\r\ + \n :foreach Scheduler in=[ /system/scheduler/find where on-event~(\"\\\ + \\b\" . \$ScriptVal->\"name\" . \"\\\\b\") ] do={\r\ + \n :local SchedulerVal [ /system/scheduler/get \$Scheduler ];\r\ + \n :if (\$ScriptVal->\"policy\" != \$SchedulerVal->\"policy\") do={\r\ + \n \$LogPrint warning \$0 (\"Policies differ for script '\" . \$Scr\ + iptVal->\"name\" . \"' and its scheduler '\" . \$SchedulerVal->\"name\" . \ + \"'!\");\r\ + \n }\r\ + \n }\r\ + \n :do {\r\ + \n :if (\$ScriptInfo->\"ignore\" = true) do={\r\ + \n \$LogPrint debug \$0 (\"Ignoring script '\" . \$ScriptVal->\"nam\ + e\" . \"', as requested.\");\r\ + \n :error true;\r\ + \n }\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :local CheckSum (\$CheckSums->(\$ScriptVal->\"name\"));\r\ + \n :if ([ :len (\$ScriptInfo->\"base-url\") ] = 0 && [ :len (\$Scri\ + ptInfo->\"url-suffix\") ] = 0 && \\\r\ + \n [ :convert transform=md5 to=hex [ :tolf (\$ScriptVal->\"sou\ + rce\") ] ] = \$CheckSum) do={\r\ + \n \$LogPrint debug \$0 (\"Checksum for script '\" . \$ScriptVal-\ + >\"name\" . \"' matches, ignoring.\");\r\ + \n :error true;\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len (\$ScriptInfo->\"certificate\") ] > 0) do={\r\ + \n :if ([ \$CertificateAvailable (\$ScriptInfo->\"certificate\") \"\ + fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading certificate failed, tryi\ + ng without.\");\r\ + \n }\r\ + \n }\r\ + \n :onerror Err {\r\ + \n :local BaseUrl [ \$EitherOr (\$ScriptInfo->\"base-url\") \$Globa\ + lScriptsUrl ];\r\ + \n :local UrlSuffix [ \$EitherOr (\$ScriptInfo->\"url-suffix\") \$G\ + lobalScriptsUrlSuffix ];\r\ + \n :local Url (\$BaseUrl . \$ScriptVal->\"name\" . \".rsc\" . \$Url\ + Suffix);\r\ + \n \$LogPrint warning \$0 (\"Fetching script '\" . (\$ScriptVal->\"\ + name\") . \"' from url: \" . \$Url);\r\ + \n :local Result [ /tool/fetch check-certificate=yes-without-crl \\\ + \r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url outp\ + ut=user as-value ];\r\ + \n :if (\$Result->\"status\" = \"finished\") do={\r\ + \n :set SourceNew [ :tolf (\$Result->\"data\") ];\r\ + \n } \r\ + \n } do={\r\ + \n \$LogPrint warning \$0 (\"Failed fetching script '\" . \$ScriptV\ + al->\"name\" . \"': \" . \$Err);\r\ + \n :if (\$Err != \"Fetch failed with status 404\") do={\r\ + \n :error false;\r\ + \n }\r\ + \n :if (\$ScriptVal->\"source\" = \"#!rsc by Vados\") do={\r\ + \n \$LogPrint warning \$0 (\"Removing dummy. Typo on installation\ + \?\");\r\ + \n /system/script/remove \$Script;\r\ + \n :error false;\r\ + \n }\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :if ([ :len (\$ScriptInfo->\"base-url\") ] = 0 && [ :len (\$Sc\ + riptInfo->\"url-suffix\") ] = 0 && \\\r\ + \n [ :len \$CheckSum ] = 0) do={\r\ + \n \$LogPrint warning \$0 (\"Added the script manually\? Skip u\ + pdates with 'ignore=true' in comment.\");\r\ + \n }\r\ + \n }\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$SourceNew ] = 0) do={\r\ + \n \$LogPrint debug \$0 (\"No update for script '\" . (\$ScriptVal-\ + >\"name\") . \"'.\");\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :local SourceCRLF [ :tocrlf \$SourceNew ];\r\ + \n :if (\$InitSetup != true && ((\$ScriptVal->\"name\") != \"AM-Globa\ + lConfig\" || (\$ScriptVal->\"name\") != \"AM-GlobalFunc\")) do={\r\ + \n :if (\$SourceNew = (\$ScriptVal->\"source\") || \$SourceCRLF = (\ + \$ScriptVal->\"source\")) do={\r\ + \n \$LogPrint debug \$0 (\"Script '\" . (\$ScriptVal->\"name\") \ + . \"' source did not change. End of lines set to CRLF.\");\r\ + \n if (\$ScriptUpdatesCRLF = false) do={\r\ + \n :put [ :tolf \$SourceNew ];\r\ + \n \$LogPrint debug \$0 (\"Script '\" . (\$ScriptVal->\"name\"\ + ) . \"' source update end of lines to LF.\");\r\ + \n }\r\ + \n :error false;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if ([ :pick \$SourceNew 0 14 ] != \"#!rsc by Vados\") do={\r\ + \n \$LogPrint warning \$0 (\"Looks like new script '\" . \$ScriptVa\ + l->\"name\" . \"' is not valid (missing shebang = '\$shebang') Ignoring!\"\ + );\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if (\$CommentsInScripts = true) do={\r\ + \n :local CommentLine [ \$Grep \$SourceNew (\"\\23 Script comment:\ + \_\") ];\r\ + \n :if ([ :len \$CommentLine ] = 0) do={\r\ + \n \$LogPrint debug \$0 (\"The search in new script '\" . (\$Scr\ + iptVal->\"name\") . \"' for comment in script body, is not found result Ig\ + noring!\");\r\ + \n :error false;\r\ + \n } else={\r\ + \n :if ([ :len (\$ScriptVal->\"comment\")] = 0 || [ :len (\$Scri\ + ptVal->\"comment\")] != [ :pick \$CommentLine ([ :find \$CommentLine \":\"\ + ] + 2) [ :len \$CommentLine] ]) do={\r\ + \n \$LogPrint debug \$0 (\"Exsiting comment for script '\" . (\ + \$ScriptVal->\"name\") . \"' is '\" . (\$ScriptVal->\"comment\") . \"'.\")\ + ;\r\ + \n :set (\$ScriptVal->\"comment\") [ :pick \$CommentLine ([ :f\ + ind \$CommentLine \":\"] + 2) [ :len \$CommentLine] ];\r\ + \n \$LogPrint debug \$0 (\"New comment (\" . (\$ScriptVal->\"c\ + omment\") . \") for script '\" . (\$ScriptVal->\"name\") . \"' has been ad\ + ded!\");\r\ + \n } else={\r\ + \n \$LogPrint debug \$0 (\"Comment for script '\" . (\$ScriptV\ + al->\"name\") . \"' did not change.\");\r\ + \n :error false;\r\ + \n }\r\ + \n } \r\ + \n }\r\ + \n\r\ + \n :local RequiredROS ([ \$ParseKeyValueStore [ \$Grep \$SourceNew (\ + \"\\23 requires RouterOS, \") ] ]->version);\r\ + \n :if ([ \$RequiredRouterOS \$0 [ \$EitherOr \$RequiredROS \"0.0\" ]\ + \_false ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"The script '\" . (\$ScriptVal->\"name\ + \") . \"' requires RouterOS \" . \\\r\ + \n \$RequiredROS . \", which is not met by your installation. I\ + gnoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n :local RequiredDM [ \$ParseKeyValueStore [ \$Grep \$SourceNew (\"\ + \\23 requires device-mode, \") ] ];\r\ + \n :local MissingDM ({});\r\ + \n :foreach Feature,Value in=\$RequiredDM do={\r\ + \n :if ([ :typeof (\$DeviceMode->\$Feature) ] = \"bool\" && (\$Devi\ + ceMode->\$Feature) = false) do={\r\ + \n :set MissingDM (\$MissingDM, \$Feature);\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len \$MissingDM ] > 0) do={\r\ + \n \$LogPrint warning \$0 (\"The script '\" . (\$ScriptVal->\"name\ + \") . \"' requires disabled \" . \\\r\ + \n \"device-mode features (\" . [ :tostr \$MissingDM ] . \"). I\ + gnoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$ValidateSyntax \$SourceNew ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Syntax validation for script '\" . (\$\ + ScriptVal->\"name\") . \"' failed! Ignoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n \r\ + \n :log info (\"Updating script: \" . (\$ScriptVal->\"name\") . \" Sc\ + ript comment: \" . (\$ScriptVal->\"comment\"));\r\ + \n\r\ + \n /system/script/set owner=(\$ScriptVal->\"name\") comment=(\$Script\ + Val->\"comment\") \\\r\ + \n source=[ \$IfThenElse (\$ScriptUpdatesCRLF = true) \$SourceCRL\ + F \$SourceNew ] \$Script;\r\ + \n :if (\$ScriptVal->\"name\" = \"AM-GlobalConfig\" || \\\r\ + \n \$ScriptVal->\"name\" = \"AM-GlobalFunc\" || \\\r\ + \n \$ScriptVal->\"name\" ~ (\"^(AM-GlobalFunc\\\\.d|mod)/.\")) d\ + o={\r\ + \n :set ReloadGlobal true;\r\ + \n }\r\ + \n } on-error={ }\r\ + \n }\r\ + \n :if (\$ReloadGlobal = true) do={\r\ + \n \$LogPrint info \$0 (\"Reloading global configuration and functions.\ + \");\r\ + \n :onerror Err {\r\ + \n :global InitSetup (a);\r\ + \n /system/script {run AM-GlobalConfig; run AM-GlobalFunc;};\r\ + \n } do={\$LogPrint error \$0 (\"Reloading global configuration and fun\ + ctions failed! \" . \$Err)}\r\ + \n }\r\ + \n :if (\$GlobalConfVersionBefore > \$GlobalConfVersion) do={\r\ + \n \$LogPrint warning \$0 (\"The configuration version decreased from \ + \" . \\\r\ + \n \$GlobalConfVersionBefore . \" to \" . \$GlobalConfVersion . \\\r\ + \n \". Installed an older version\?\");\r\ + \n }\r\ + \n :if (\$GlobalConfVersionBefore < \$GlobalConfVersion) do={\r\ + \n :global GlobalConfChanges;\r\ + \n :global GlobalConfMigration;\r\ + \n :local ChangeLogCode;\r\ + \n :if ([ :len \$ChangeLogCode ] > 0) do={\r\ + \n :if ([ \$ValidateSyntax \$ChangeLogCode ] = true) do={\r\ + \n :onerror Err {\r\ + \n [ :parse \$ChangeLogCode ];\r\ + \n } do={\$LogPrint warning \$0 (\"The changelog failed to run: \" \ + . \$Err)}\r\ + \n } else={\$LogPrint warning \$0 (\"The changelog failed syntax vali\ + dation!\")}\r\ + \n }\r\ + \n :if ([ :len \$GlobalConfMigration ] > 0) do={\r\ + \n :for I from=(\$GlobalConfVersionBefore + 1) to=\$GlobalConfVersion\ + \_do={\r\ + \n :local Migration (\$GlobalConfMigration->[ :tostr \$I ]);\r\ + \n :do {\r\ + \n :if ([ :typeof \$Migration ] != \"str\") do={\r\ + \n \$LogPrint debug \$0 (\"Migration code for change \" . \$I .\ + \_\" is not available.\");\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$ValidateSyntax \$Migration ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Migration code for change \" . \$I\ + \_. \" failed syntax validation!\");\r\ + \n :error false}\r\ + \n \$LogPrint info \$0 (\"Applying migration for change \" . \$I \ + . \": \" . \$Migration);\r\ + \n :onerror Err {\r\ + \n [ :parse \$Migration ];\r\ + \n } do={\$LogPrint warning \$0 (\"Migration code for change \" .\ + \_\$I . \" failed to run: \" . \$Err)}\r\ + \n } on-error={ }\r\ + \n }\r\ + \n }\r\ + \n :local NotificationMessage (\"The configuration version on \" . \$Id\ + entity . \" increased \" . \\\r\ + \n \"to \" . \$GlobalConfVersion . \", current configuration may nee\ + d modification. \" . \\\r\ + \n \"Please review and update global-config-overlay, then re-run glo\ + bal-config.\");\r\ + \n \$LogPrint info \$0 (\$NotificationMessage);\r\ + \n :if ([ :len \$GlobalConfChanges ] > 0) do={\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n\\nChanges:\ + \");\r\ + \n :for I from=(\$GlobalConfVersionBefore + 1) to=\$GlobalConfVersion\ + \_do={\r\ + \n :local Change (\$GlobalConfChanges->[ :tostr \$I ]);\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n \" . \\\ + \r\ + \n [ \$SymbolForNotification \"pushpin\" \"*\" ] . \$Change);\r\ + \n \$LogPrint info \$0 (\"Change \" . \$I . \": \" . \$Change);\r\ + \n }\r\ + \n } else={\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n\\nNews and\ + \_changes are not available.\");\r\ + \n\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$GlobalConfChanges ] > 0) do={ :set GlobalConfChanges; \ + } else={ :global GlobalConfChanges (a); }\r\ + \n :if ([ :len \$GlobalConfMigration ] > 0) do={ :set GlobalConfMigrati\ + on; } else={ :global GlobalConfMigration (a); }\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n\r\ + \n# load modules\r\ + \n:foreach Script in=[ /system/script/find where name ~ \"^(global-functio\ + ns\\\\.d|mod)/.\" ] do={\r\ + \n :local ScriptVal [ /system/script/get \$Script ];\r\ + \n :if ([ \$ValidateSyntax (\$ScriptVal->\"source\") ] = true) do={\r\ + \n :onerror Err {\r\ + \n /system/script/run \$Script;\r\ + \n } do={\r\ + \n \$LogPrint error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' f\ + ailed to run: \" . \$Err);\r\ + \n }\r\ + \n } else={\r\ + \n \$LogPrint error \$0 (\"Module '\" . \$ScriptVal->\"name\" . \"' fai\ + led syntax validation, skipping.\");\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# Add and fix global scheduler\r\ + \n:put \"Add or/and fix Global scheduler...\";\r\ + \n:local OnEvent \"/system/script { run AM-GlobalConfig; run AM-GlobalFunc\ + ; }\";\r\ + \n:local Comment \"Every Day Set Global Environment Variables\";\r\ + \n:if ([ :len [ /system/scheduler/find where name=\"AM-SetGlobalEnv\" ] ] \ + = 0) do={\r\ + \n /system/scheduler/add name=\"AM-SetGlobalEnv\" comment=\$Comment on-ev\ + ent=\$OnEvent start-time=startup;\r\ + \n}\r\ + \n:if ([ /system/scheduler/get \"AM-SetGlobalEnv\" comment ] != \$Comment)\ + \_do={\r\ + \n /system/scheduler/set \"AM-SetGlobalEnv\" comment=\$Comment;\r\ + \n}\r\ + \n:if ([ /system/scheduler/get \"AM-SetGlobalEnv\" on-event ] != \$OnEvent\ + ) do={\r\ + \n /system/scheduler/set \"AM-SetGlobalEnv\" on-event=\$OnEvent;\r\ + \n}\r\ + \n# Add Update Script scheduler\r\ + \n:put \"Add or/and fix Update Script scheduler\";\r\ + \n:set OnEvent \":global ScriptInstallUpdate; \\\$ScriptInstallUpdate;\";\ + \r\ + \n:set Comment \"Every Day Check For Update Scripts\";\r\ + \n:if ([ :len [ /system/scheduler/find where name=\"AM-ScriptInstallUpdate\ + \" ] ] = 0) do={\r\ + \n/system/scheduler/add name=\"AM-ScriptInstallUpdate\" comment=\$Comment \ + start-time=startup interval=1d on-event=\$OnEvent;\r\ + \n}\r\ + \n\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady;\r\ + \n :retry { :if (\$GlobalConfReady != true) do={ :error (\"Global configs\ + \_not ready.\"); }; } delay=100ms max=5;\r\ + \n\r\ + \n:log warning (\"This script \$ScriptName Loaded \" . \\\r\ + \n [ \$IfThenElse (\$CommitId != \"unknown\") ([ \$CommitBrief ] . \" \ + \") ] . \\\r\ + \n \"on \" . \$devBoardName . \"\\nRouterOS: \" . \$devCurVersion . \"\ + \\nArchitecture: \" . \$devCurArch . \"\\nIdentity: \" . \$Identity);\r\ + \n # signal we are ready\r\ + \n:set GlobalFuncReady true;\r\ + \n:set ExitOK true;\r\ + \n\r\ + \n:log info (\$0 . \"Global configs ready!\");\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n}\r\ + \n" +add dont-require-permissions=no name=fetchCustom owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + \r\ + \n#/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@1\ + 0.30.30.10.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.10.pub\";\r\ + \n#\r\ + \n#root.mnuc.backuper\r\ + \n\r\ + \n#/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@1\ + 0.30.30.81.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.81.pub\";\r\ + \n#:local Script AM-GlobalConfig;\r\ + \n#/system/script/add name=\$Script owner=\$Script source=([ /tool/fetch c\ + heck-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \".r\ + sc\") output=user as-value ]->\"data\");\r\ + \n#/usb1/mikro_bak_rsa.pub\r\ + \n#:local FetchFile AM-DOHCert.rsc\r\ + \n\r\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/tmp/\";\r\ + \n:global MkDir;\r\ + \n\r\ + \n:local FetchFile Binary.backup\r\ + \n:local localDir \"[usb1]/scripts/\";\r\ + \n\r\ + \n\$MkDir \$localDir;\r\ + \n\r\ + \n/tool/fetch mode=https url=(\$GlobalScriptsUrl . \$FetchFile) dst-path=(\ + \$localDir . \$FetchFile);\r\ + \n" +add dont-require-permissions=yes name=MkTmpfs owner=vados policy=\ + ftp,read,write,policy,test,sniff,sensitive source=\ + ":global MkDir;\r\ + \n\r\ + \n\$MkDir /usb1/tmpfs;" +add dont-require-permissions=no name=CreateContainer owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\ + local contIF \"veth1\";\r\ + \n:local contIP \"10.10.10.10/24\";\r\ + \n:local contGW \"10.10.10.1\";\r\ + \n#/interface/veth/add name=\$contIF address=\$contIP gateway=\$contGW;\r\ + \n:local contBridgeIF \"Bridge-DOC\";\r\ + \n:local contBridgeIP \"10.10.10.1/24\";\r\ + \n/interface/bridge/add name=\$contBridgeIF;\r\ + \n/ip/address/add address=\$contBridgeIP interface=\$contBridgeIF;\r\ + \n/interface/bridge/port/add bridge=\$contBridgeIF interface=\$contIF;\r\ + \n/container/config/set ram-high=768M registry-url=https://registry-1.dock\ + er.io tmpdir=sata1/tmp\r\ + \n" +add dont-require-permissions=no name=carkeeperbot owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + container/mounts/add list=carkeeper_configs/.profile src=sata1/carkeeper/c\ + onfigs/.profile dst=/root/.ashrc;\r\ + \n/container/mounts/add list=carkeeper_configs/php.ini src=sata1/carkeeper\ + /configs/php.ini dst=/etc/php82/php.ini;\r\ + \n#/container/mounts/add list=carkeeper_configs src=sata1/carkeeper/config\ + s dst=/configs;\r\ + \n/container/mounts/add list=carkeeper_app src=sata1/carkeeper/app dst=/ap\ + p;\r\ + \n/container/mounts/add list=carkeeper_scripts src=sata1/carkeeper/scripts\ + \_dst=/scripts;\r\ + \n/container/mounts/add list=carkeeper_configs/sshd_config src=sata1/carke\ + eper/configs/sshd_config dst=/etc/ssh/sshd_config;\r\ + \n\r\ + \n/container/mounts/add list=carkeeper_tmp src=sata1/carkeeper/tmp dst=/va\ + r/tmp;\r\ + \n/container/mounts/add list=carkeeper_sync src=sata1/carkeeper/sync dst=/\ + var/sync;\r\ + \n" +add comment="Enable or disable ip addresses based on bridge port state" \ + dont-require-permissions=no name=mod/AM-IPbridge owner=mod/AM-IPbridge \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by Vados\r\ + \n# RouterOS script: mod/AM-IPbridge\r\ + \n# Script comment: Enable or disable ip addresses based on bridge port st\ + ate\r\ + \n# Copyright (c) 2005-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n:local ScriptName [ :jobname ];\r\ + \n\r\ + \n# Add this Script scheduler\r\ + \n:if ([ :len [ /system/scheduler/find where name=\$ScriptName ] ] = 0) do\ + ={\r\ + \n:log warning (\"SystemScheduler for script: \" . \$ScriptName . \" is NO\ + T SET! Fix It Now!\");\r\ + \n:local OnEvent \"/system/script/run \$ScriptName;\";\r\ + \n:local Comment \"Every Day Check For Fake Bridges\";\r\ + \n/system/scheduler/add name=\$ScriptName comment=\$Comment start-time=sta\ + rtup interval=1d on-event=\$OnEvent;\r\ + \n}\r\ + \n\r\ + \n:foreach Bridge in=[ /interface/bridge/find ] do={\r\ + \n :local BrName [ /interface/bridge/get \$Bridge name ];\r\ + \n :if ([ :len [ /interface/bridge/port/find where bridge=\$BrName ] ] > \ + 0) do={\r\ + \n :if ([ :len [ /interface/bridge/port/find where bridge=\$BrName and \ + inactive=no ] ] = 0) do={\r\ + \n /ip/address/disable [ find where !dynamic interface=\$BrName ];\r\ + \n } else={\r\ + \n /ip/address/enable [ find where !dynamic interface=\$BrName ];\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n" +add comment="Use Telegram to chat with your Router and send commands" \ + dont-require-permissions=no name=mod/AM-TG-Bot owner=mod/AM-TG-Bot \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by Vados\r\ + \n# RouterOS script: mod/AM-TG-Bot\r\ + \n# Script comment: Use Telegram to chat with your Router and send command\ + s\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch\r\ + \n:local ExitOK false;\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady; :global GlobalFuncReady;\r\ + \n :retry { :if (\$GlobalConfReady != true || \$GlobalFuncReady != true) \ + \\\r\ + \n do={ :error (\"Global config and/or functions not ready.\"); }; } \ + delay=500ms max=50;\r\ + \n :local ScriptName [ :jobname ];\r\ + \n\r\ + \n :global Identity;\r\ + \n :global TelegramChatActive;\r\ + \n :global TelegramChatGroups;\r\ + \n :global TelegramChatId;\r\ + \n :global TelegramChatIdsTrusted;\r\ + \n :global TelegramChatOffset;\r\ + \n :global TelegramChatRunTime;\r\ + \n :global TelegramMessageIDs;\r\ + \n :global TelegramRandomDelay;\r\ + \n :global TelegramTokenId;\r\ + \n\r\ + \n :global CertificateAvailable;\r\ + \n :global EitherOr;\r\ + \n :global EscapeForRegEx;\r\ + \n :global FileExists;\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global LogPrintVerbose;\r\ + \n :global MAX;\r\ + \n :global MIN;\r\ + \n :global MkDir;\r\ + \n :global RandomDelay;\r\ + \n :global RmDir;\r\ + \n :global ScriptLock;\r\ + \n :global SendTelegram2;\r\ + \n :global SymbolForNotification;\r\ + \n :global ValidateSyntax;\r\ + \n :global WaitForFile;\r\ + \n :global WaitFullyConnected;\r\ + \n\r\ + \n :if ([ \$ScriptLock \$ScriptName ] = false) do={\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n \$WaitFullyConnected;\r\ + \n\r\ + \n :if ([ :len [ /system/scheduler/find where name=\$ScriptName ] ] = 0) \ + do={\r\ + \n /system/scheduler/add start-time=startup interval=30s name=\$ScriptN\ + ame on-event=\"/system/script/run \$ScriptName;\" comment=\"Telegram Chat \ + Bot\";\r\ + \n }\r\ + \n :if ([ :typeof \$TelegramChatOffset ] != \"array\") do={\r\ + \n :set TelegramChatOffset { 0; 0; 0 };\r\ + \n }\r\ + \n :if ([ :typeof \$TelegramRandomDelay ] != \"num\") do={\r\ + \n :set TelegramRandomDelay 0;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$CertificateAvailable \"Go Daddy Root Certificate Authority - \ + G2\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$ScriptName (\"Downloading required certificate \ + failed.\");\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n \$RandomDelay \$TelegramRandomDelay;\r\ + \n\r\ + \n :local Data false;\r\ + \n :for I from=1 to=4 do={\r\ + \n :if (\$Data = false) do={\r\ + \n :onerror Err {\r\ + \n :set Data ([ /tool/fetch check-certificate=yes-without-crl outpu\ + t=user \\\r\ + \n (\"https://api.telegram.org/bot\" . \$TelegramTokenId . \"/get\ + Updates\?offset=\" . \\\r\ + \n \$TelegramChatOffset->0 . \"&allowed_updates=%5B%22message%22%\ + 5D\") as-value ]->\"data\");\r\ + \n :set TelegramRandomDelay [ \$MAX 0 (\$TelegramRandomDelay - 1) ]\ + ;\r\ + \n } do={\r\ + \n :if (\$I < 4) do={\r\ + \n \$LogPrint debug \$ScriptName (\"Fetch failed, \" . \$I . \". \ + try: \" . \$Err);\r\ + \n :set TelegramRandomDelay [ \$MIN 15 (\$TelegramRandomDelay + 5\ + ) ];\r\ + \n :delay ((\$I * \$I) . \"s\");\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if (\$Data = false) do={\r\ + \n \$LogPrint warning \$ScriptName (\"Failed getting updates.\");\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :local JSON [ :deserialize from=json value=\$Data ];\r\ + \n :local UpdateID 0;\r\ + \n :local Uptime [ /system/resource/get uptime ];\r\ + \n :foreach Update in=(\$JSON->\"result\") do={\r\ + \n :set UpdateID (\$Update->\"update_id\");\r\ + \n \$LogPrintVerbose debug \$ScriptName (\"Update \" . \$UpdateID . \":\ + \_\" . [ :serialize to=json \$Update ]);\r\ + \n\r\ + \n :local Message (\$Update->\"message\");\r\ + \n :local IsAnyReply ([ :typeof (\$Message->\"reply_to_message\") ] = \ + \"array\");\r\ + \n :local IsMyReply (\$TelegramMessageIDs->[ :tostr (\$Message->\"reply\ + _to_message\"->\"message_id\") ]);\r\ + \n :if ((\$IsMyReply = 1 || \$TelegramChatOffset->0 > 0 || \$Uptime > 5\ + m) && \$UpdateID >= \$TelegramChatOffset->2) do={\r\ + \n :local Trusted false;\r\ + \n :local Chat (\$Message->\"chat\");\r\ + \n :local From (\$Message->\"from\");\r\ + \n :local Command (\$Message->\"text\");\r\ + \n :local ThreadId [ \$IfThenElse (\$Message->\"is_topic_message\") (\ + \$Message->\"message_thread_id\") \"\" ];\r\ + \n\r\ + \n :foreach IdsTrusted in=(\$TelegramChatId, \$TelegramChatIdsTrusted\ + ) do={\r\ + \n :if (\$From->\"id\" = \$IdsTrusted || \\\r\ + \n \$From->\"username\" = \$IdsTrusted || \\\r\ + \n \$Chat->\"id\" = \$IdsTrusted) do={\r\ + \n :set Trusted true;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if (\$Trusted = true) do={\r\ + \n :local Done false;\r\ + \n :if (\$Command = \"\?\") do={\r\ + \n \$LogPrint info \$ScriptName (\"Sending notice for update \" .\ + \_\$UpdateID . \".\");\r\ + \n \$SendTelegram2 ({ origin=\$ScriptName; chatid=(\$Chat->\"id\"\ + ); silent=true; \\\r\ + \n replyto=(\$Message->\"message_id\"); threadid=\$ThreadId; \\\ + \r\ + \n subject=([ \$SymbolForNotification \"speech-balloon\" ] . \"\ + Telegram Chat\"); \\\r\ + \n message=([ \$IfThenElse ([ :len (\$From->\"first_name\") ] >\ + \_0) (\"Hello \" . (\$From->\"first_name\") . \"!\\n\\n\") ] . \\\r\ + \n \"Online\" . [ \$IfThenElse \$TelegramChatActive \" (and a\ + ctive!)\" ] . \", awaiting your commands!\") });\r\ + \n :set Done true;\r\ + \n }\r\ + \n :if (\$Done = false && [ :pick \$Command 0 1 ] = \"!\") do={\r\ + \n :if (\$Command ~ (\"^! *(\" . [ \$EscapeForRegEx \$Identity ] \ + . \"|@\" . \$TelegramChatGroups . \")\\\$\")) do={\r\ + \n :set TelegramChatActive true;\r\ + \n } else={\r\ + \n :set TelegramChatActive false;\r\ + \n }\r\ + \n \$LogPrint info \$ScriptName (\"Now \" . [ \$IfThenElse \$Tele\ + gramChatActive \"active\" \"passive\" ] . \\\r\ + \n \" from update \" . \$UpdateID . \"!\");\r\ + \n :set Done true;\r\ + \n }\r\ + \n :if (\$Done = false && (\$IsMyReply = 1 || (\$IsAnyReply = false\ + \_&& \\\r\ + \n \$TelegramChatActive = true)) && [ :len \$Command ] > 0) do\ + ={\r\ + \n :if ([ \$ValidateSyntax \$Command ] = true) do={\r\ + \n :local State \"\";\r\ + \n :local File (\"tmpfs/telegram-chat/\" . [ \$GetRandom20CharA\ + lNum 6 ]);\r\ + \n :if ([ \$MkDir \"tmpfs/telegram-chat\" ] = false) do={\r\ + \n \$LogPrint error \$ScriptName (\"Failed creating directory\ + !\");\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n \$LogPrint info \$ScriptName (\"Running command from update \ + \" . \$UpdateID . \": \" . \$Command);\r\ + \n :execute script=(\":do {\\n\" . \$Command . \"\\n} on-error=\ + { /file/add name=\\\"\" . \$File . \".failed\\\" };\" . \\\r\ + \n \"/file/add name=\\\"\" . \$File . \".done\\\"\") file=(\$\ + File . \"\\00\");\r\ + \n :if ([ \$WaitForFile (\$File . \".done\") [ \$EitherOr \$Tel\ + egramChatRunTime 20s ] ] = false) do={\r\ + \n :set State ([ \$SymbolForNotification \"warning-sign\" ] .\ + \_\"The command did not finish, still running in background.\\n\\n\");\r\ + \n }\r\ + \n :if ([ \$FileExists (\$File . \".failed\") ] = true) do={\r\ + \n :set State ([ \$SymbolForNotification \"cross-mark\" ] . \ + \"The command failed with an error!\\n\\n\");\r\ + \n }\r\ + \n :local Content ([ /file/read chunk-size=32768 file=\$File as\ + -value ]->\"data\");\r\ + \n \$SendTelegram2 ({ origin=\$ScriptName; chatid=(\$Chat->\"id\ + \"); silent=true; \\\r\ + \n replyto=(\$Message->\"message_id\"); threadid=\$ThreadId; \ + \\\r\ + \n subject=([ \$SymbolForNotification \"speech-balloon\" ] . \ + \"Telegram Chat\"); \\\r\ + \n message=([ \$SymbolForNotification \"gear\" ] . \"Command:\ + \\n\" . \$Command . \"\\n\\n\" . \\\r\ + \n \$State . [ \$IfThenElse ([ :len \$Content ] > 0) \\\r\ + \n ([ \$SymbolForNotification \"memo\" ] . \"Output:\\n\" .\ + \_\$Content) \\\r\ + \n ([ \$SymbolForNotification \"memo\" ] . \"No output.\") \ + ]) });\r\ + \n \$RmDir \"tmpfs/telegram-chat\";\r\ + \n } else={\r\ + \n \$LogPrint info \$ScriptName (\"The command from update \" .\ + \_\$UpdateID . \" failed syntax validation!\");\r\ + \n \$SendTelegram2 ({ origin=\$ScriptName; chatid=(\$Chat->\"id\ + \"); silent=false; \\\r\ + \n replyto=(\$Message->\"message_id\"); threadid=\$ThreadId; \ + \\\r\ + \n subject=([ \$SymbolForNotification \"speech-balloon\" ] . \ + \"Telegram Chat\"); \\\r\ + \n message=([ \$SymbolForNotification \"gear\" ] . \"Command:\ + \\n\" . \$Command . \"\\n\\n\" . \\\r\ + \n [ \$SymbolForNotification \"cross-mark\" ] . \"The comma\ + nd failed syntax validation!\") });\r\ + \n }\r\ + \n }\r\ + \n } else={\r\ + \n :local MessageText (\"Received a message from untrusted contact \ + \" . \\\r\ + \n [ \$IfThenElse ([ :len (\$From->\"username\") ] = 0) \"without\ + \_username\" (\"'\" . \$From->\"username\" . \"'\") ] . \\\r\ + \n \" (ID \" . \$From->\"id\" . \") in update \" . \$UpdateID . \ + \"!\");\r\ + \n :if (\$Command ~ (\"^! *\" . [ \$EscapeForRegEx \$Identity ] . \ + \"\\\$\")) do={\r\ + \n \$LogPrint warning \$ScriptName \$MessageText;\r\ + \n \$SendTelegram2 ({ origin=\$ScriptName; chatid=(\$Chat->\"id\"\ + ); silent=false; \\\r\ + \n replyto=(\$Message->\"message_id\"); threadid=\$ThreadId; \\\ + \r\ + \n subject=([ \$SymbolForNotification \"speech-balloon\" ] . \"\ + Telegram Chat\"); \\\r\ + \n message=(\"You are not trusted.\") });\r\ + \n } else={\r\ + \n \$LogPrint info \$ScriptName \$MessageText;\r\ + \n }\r\ + \n }\r\ + \n } else={\r\ + \n \$LogPrint debug \$ScriptName (\"Already handled update \" . \$Upd\ + ateID . \".\");\r\ + \n }\r\ + \n }\r\ + \n :set TelegramChatOffset ([ :pick \$TelegramChatOffset 1 3 ], \\\r\ + \n [ \$IfThenElse (\$UpdateID >= \$TelegramChatOffset->2) (\$UpdateID +\ + \_1) (\$TelegramChatOffset->2) ]);\r\ + \n} do={\r\ + \n :global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err;\r\ + \n}\r\ + \n" +add comment="Script send Notifications in Telegram" dont-require-permissions=\ + no name=mod/AM-TG-Notifications owner=mod/AM-TG-Notifications policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: mod/AM-TG-Notifications\r\ + \n# Script comment: Script send Notifications in Telegram\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch, scheduler\r\ + \n:global FlushTelegramQueue;\r\ + \n:global GetTelegramChatId;\r\ + \n:global NotificationFunctions;\r\ + \n:global PurgeTelegramQueue;\r\ + \n:global SendTelegram;\r\ + \n:global SendTelegram2;\r\ + \n# flush telegram queue\r\ + \n:set FlushTelegramQueue do={ :onerror Err {\r\ + \n :global TelegramQueue;\r\ + \n :global TelegramMessageIDs;\r\ + \n :global CertificateAvailable;\r\ + \n :global IsFullyConnected;\r\ + \n :global LogPrint;\r\ + \n :if ([ \$IsFullyConnected ] = false) do={\r\ + \n \$LogPrint debug \$0 (\"System is not fully connected, not flushing.\ + \");\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ \$CertificateAvailable \"Go Daddy Root Certificate Authority - \ + G2\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading required certificate failed.\"\ + );\r\ + \n :return false;\r\ + \n }\r\ + \n :local AllDone true;\r\ + \n :local QueueLen [ :len \$TelegramQueue ];\r\ + \n :if ([ :len [ /system/scheduler/find where name=\"_FlushTelegramQueue\ + \" ] ] > 0 && \$QueueLen = 0) do={\r\ + \n \$LogPrint warning \$0 (\"Flushing Telegram messages from scheduler,\ + \_but queue is empty.\");\r\ + \n }\r\ + \n :foreach Id,Message in=\$TelegramQueue do={\r\ + \n :if ([ :typeof \$Message ] = \"array\" ) do={\r\ + \n :onerror Err {\r\ + \n :local Data ([ /tool/fetch check-certificate=yes-without-crl out\ + put=user http-method=post \\\r\ + \n (\"https://api.telegram.org/bot\" . (\$Message->\"tokenid\") .\ + \_\"/sendMessage\") \\\r\ + \n http-data=(\$Message->\"http-data\") as-value ]->\"data\");\r\ + \n :set (\$TelegramQueue->\$Id);\r\ + \n :set (\$TelegramMessageIDs->[ :tostr ([ :deserialize from=json v\ + alue=\$Data ]->\"result\"->\"message_id\") ]) 1;\r\ + \n } do={\r\ + \n \$LogPrint debug \$0 (\"Sending queued Telegram message failed: \ + \" . \$Err);\r\ + \n :set AllDone false;\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n :if (\$AllDone = true && \$QueueLen = [ :len \$TelegramQueue ]) do={\r\ + \n /system/scheduler/remove [ find where name=\"_FlushTelegramQueue\" ]\ + ;\r\ + \n :set TelegramQueue;\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n# get the chat id\r\ + \n:set GetTelegramChatId do={ :onerror Err {\r\ + \n :global TelegramTokenId;\r\ + \n :global CertificateAvailable;\r\ + \n :global LogPrint;\r\ + \n :if ([ \$CertificateAvailable \"Go Daddy Root Certificate Authority - \ + G2\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading required certificate failed.\"\ + );\r\ + \n :return false;\r\ + \n }\r\ + \n :local Data;\r\ + \n :onerror Err {\r\ + \n :set Data ([ /tool/fetch check-certificate=yes-without-crl output=us\ + er \\\r\ + \n (\"https://api.telegram.org/bot\" . \$TelegramTokenId . \"/getUpd\ + ates\?offset=0\" . \\\r\ + \n \"&allowed_updates=%5B%22message%22%5D\") as-value ]->\"data\");\ + \r\ + \n } do={\r\ + \n \$LogPrint warning \$0 (\"Fetching data failed: \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :local JSON [ :deserialize from=json value=\$Data ];\r\ + \n :local Count [ :len (\$JSON->\"result\") ];\r\ + \n :if (\$Count = 0) do={\r\ + \n \$LogPrint info \$0 (\"No message received.\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local Message (\$JSON->\"result\"->(\$Count - 1)->\"message\");\r\ + \n \$LogPrint info \$0 (\"The chat id is: \" . (\$Message->\"chat\"->\"id\ + \"));\r\ + \n :if ((\$Message->\"is_topic_message\") = true) do={\r\ + \n \$LogPrint info \$0 (\"The thread id is: \" . (\$Message->\"message_\ + thread_id\"));\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n# send notification via telegram - expects one array argument\r\ + \n:set (\$NotificationFunctions->\"telegram\") do={\r\ + \n :local Notification \$1;\r\ + \n :global Identity;\r\ + \n :global IdentityExtra;\r\ + \n :global TelegramChatId;\r\ + \n :global TelegramChatIdOverride;\r\ + \n :global TelegramMessageIDs;\r\ + \n :global TelegramQueue;\r\ + \n :global TelegramThreadId;\r\ + \n :global TelegramThreadIdOverride;\r\ + \n :global TelegramTokenId;\r\ + \n :global TelegramTokenIdOverride;\r\ + \n :global CertificateAvailable;\r\ + \n :global CharacterReplace;\r\ + \n :global EitherOr;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global ProtocolStrip;\r\ + \n :global SymbolForNotification;\r\ + \n :global UrlEncode;\r\ + \n :local EscapeMD do={\r\ + \n :local Text [ :tostr \$1 ];\r\ + \n :local Mode [ :tostr \$2 ];\r\ + \n :local Excl [ :tostr \$3 ];\r\ + \n :global CharacterReplace;\r\ + \n :global IfThenElse;\r\ + \n :local Chars {\r\ + \n \"body\"={ \"\\\\\"; \"`\" };\r\ + \n \"plain\"={ \"_\"; \"*\"; \"[\"; \"]\"; \"(\"; \")\"; \"~\"; \"`\"\ + ; \">\";\r\ + \n \"#\"; \"+\"; \"-\"; \"=\"; \"|\"; \"{\"; \"}\"; \".\"; \ + \"!\" };\r\ + \n }\r\ + \n :foreach Char in=(\$Chars->\$Mode) do={\r\ + \n :if ([ :typeof [ :find \$Excl \$Char ] ] = \"nil\") do={\r\ + \n :set Text [ \$CharacterReplace \$Text \$Char (\"\\\\\" . \$Char)\ + \_];\r\ + \n }\r\ + \n }\r\ + \n :if (\$Mode = \"body\") do={:return (\"```\\n\" . \$Text . \"\\n```\ + \")}\r\ + \n :return \$Text;\r\ + \n }\r\ + \n :local ChatId [ \$EitherOr (\$Notification->\"chatid\") \\\r\ + \n [ \$EitherOr (\$TelegramChatIdOverride->(\$Notification->\"origin\")\ + ) \$TelegramChatId ] ];\r\ + \n :local ThreadId [ \$EitherOr (\$Notification->\"threadid\") \\\r\ + \n [ \$EitherOr (\$TelegramThreadIdOverride->(\$Notification->\"origin\ + \")) \\\r\ + \n [ \$IfThenElse ([ :len (\$TelegramChatIdOverride->(\$Notification->\ + \"origin\")) ] = 0) \$TelegramThreadId ] ] ];\r\ + \n :local TokenId [ \$EitherOr (\$TelegramTokenIdOverride->(\$Notificatio\ + n->\"origin\")) \$TelegramTokenId ];\r\ + \n :if ([ :len \$TokenId ] = 0 || [ :len \$ChatId ] = 0) do={:return fals\ + e}\r\ + \n :if ([ :typeof \$TelegramMessageIDs ] = \"nothing\") do={:set Telegram\ + MessageIDs ({})}\r\ + \n :local Truncated false;\r\ + \n :local Text (\"*__\" . [ \$EscapeMD (\"[\" . \$IdentityExtra . \$Ident\ + ity . \"] \" . \\\r\ + \n (\$Notification->\"subject\")) \"plain\" ] . \"__*\\n\\n\");\r\ + \n :local LenSubject [ :len \$Text ];\r\ + \n :local LenMessage [ :len (\$Notification->\"message\") ];\r\ + \n :local LenLink ([ :len (\$Notification->\"link\") ] * 2);\r\ + \n :local LenSum (\$LenSubject + \$LenMessage + \$LenLink);\r\ + \n :if (\$LenSum > 3968) do={\r\ + \n :set Text (\$Text . [ \$EscapeMD ([ :pick (\$Notification->\"message\ + \") 0 (3840 - \$LenSubject - \$LenLink) ] . \"...\") \"body\" ]);\r\ + \n :set Truncated true;\r\ + \n } else={:set Text (\$Text . [ \$EscapeMD (\$Notification->\"message\")\ + \_\"body\" ])}\r\ + \n :if (\$LenLink > 0) do={\r\ + \n :set Text (\$Text . \"\\n\" . [ \$SymbolForNotification \"link\" ] .\ + \_\\\r\ + \n \"[\" . [ \$EscapeMD [ \$ProtocolStrip (\$Notification->\"link\") \ + ] \"plain\" ] . \"]\" . \\\r\ + \n \"(\" . [ \$EscapeMD (\$Notification->\"link\") \"plain\" ] . \")\ + \");\r\ + \n }\r\ + \n :if (\$Truncated = true) do={\r\ + \n :set Text (\$Text . \"\\n\" . [ \$SymbolForNotification \"scissors\"\ + \_] . \\\r\ + \n [ \$EscapeMD (\"The message was too long and has been truncated, c\ + ut off _\" . \\\r\ + \n ((\$LenSum - [ :len \$Text ]) * 100 / \$LenSum) . \"%_!\") \"plain\ + \" \"_\" ]);\r\ + \n }\r\ + \n :local HTTPData (\"chat_id=\" . \$ChatId . \"&disable_notification=\" \ + . (\$Notification->\"silent\") . \\\r\ + \n \"&reply_to_message_id=\" . (\$Notification->\"replyto\") . \"&mes\ + sage_thread_id=\" . \$ThreadId . \\\r\ + \n \"&disable_web_page_preview=true&parse_mode=MarkdownV2\");\r\ + \n :onerror Err {\r\ + \n :if ([ \$CertificateAvailable \"Go Daddy Root Certificate Authority \ + - G2\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading required certificate failed.\ + \");\r\ + \n :error false;\r\ + \n }\r\ + \n :local Data ([ /tool/fetch check-certificate=yes-without-crl output=\ + user http-method=post \\\r\ + \n (\"https://api.telegram.org/bot\" . \$TokenId . \"/sendMessage\") \ + \\\r\ + \n http-data=(\$HTTPData . \"&text=\" . [ \$UrlEncode \$Text ]) as-va\ + lue ]->\"data\");\r\ + \n :set (\$TelegramMessageIDs->[ :tostr ([ :deserialize from=json value\ + =\$Data ]->\"result\"->\"message_id\") ]) 1;\r\ + \n } do={\r\ + \n \$LogPrint info \$0 (\"Failed sending Telegram notification: \" . \$\ + Err . \" - Queuing...\");\r\ + \n :if ([ :typeof \$TelegramQueue ] = \"nothing\") do={:set TelegramQue\ + ue ({})}\r\ + \n :set Text (\$Text . \"\\n\" . [ \$SymbolForNotification \"alarm-cloc\ + k\" ] . \\\r\ + \n [ \$EscapeMD (\"This message was queued since _\" . [ /system/cloc\ + k/get date ] . \\\r\ + \n \" \" . [ /system/clock/get time ] . \"_ and may be obsolete.\") \ + \"plain\" \"_\" ]);\r\ + \n :set (\$TelegramQueue->[ :len \$TelegramQueue ]) { tokenid=\$TokenId\ + ;\r\ + \n http-data=(\$HTTPData . \"&text=\" . [ \$UrlEncode \$Text ]) };\r\ + \n :if ([ :len [ /system/scheduler/find where name=\"_FlushTelegramQueu\ + e\" ] ] = 0) do={\r\ + \n /system/scheduler/add name=\"_FlushTelegramQueue\" interval=1m sta\ + rt-time=startup \\\r\ + \n on-event=(\":global FlushTelegramQueue; \\\$FlushTelegramQueue;\ + \");\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n# purge the Telegram queue\r\ + \n:set PurgeTelegramQueue do={\r\ + \n :global TelegramQueue;\r\ + \n /system/scheduler/remove [ find where name=\"_FlushTelegramQueue\" ];\ + \r\ + \n :set TelegramQueue;\r\ + \n}\r\ + \n# send notification via telegram - expects at least two string arguments\ + \r\ + \n:set SendTelegram do={ :onerror Err {\r\ + \n :global SendTelegram2;\r\ + \n \$SendTelegram2 ({ origin=\$0; subject=\$1; message=\$2; link=\$3; sil\ + ent=\$4 });\r\ + \n} do={\r\ + \n :global ExitOnError; \$ExitOnError \$0 \$Err;\r\ + \n} }\r\ + \n# send notification via telegram - expects one array argument\r\ + \n:set SendTelegram2 do={\r\ + \n :local Notification \$1;\r\ + \n :global NotificationFunctions;\r\ + \n (\$NotificationFunctions->\"telegram\") (\"\\\$NotificationFunctions->\ + \\\"telegram\\\"\") \$Notification;\r\ + \n}\r\ + \n" +add dont-require-permissions=no name=testIfThenElseAndEitherOr owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source=":local ExitOK false;\r\ + \n:local ScriptName [ :jobname ];\r\ + \n\r\ + \n:local ExitError;\r\ + \n#:local IfThenElse;\r\ + \n#:local EitherOr;\r\ + \n\r\ + \n:local one 11;\r\ + \n:local two 8;\r\ + \n#:log warning ([ \$IfThenElse (\$one != 0) \$one \$two ]);\r\ + \n:local Test; # [ :totime \"22s\" ];\r\ + \n\r\ + \n:local IfThenElse do={:if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] \ + = true) do={:return \$2}; :return \$3;}\r\ + \n:local EitherOr do={\r\ + \n :if ([ :typeof \$1 ] = \"num\") do={:if ((\$1 != 0) = true) do={:retur\ + n \$1} else={:return \$2};}\r\ + \n :if ([ :typeof \$1 ] = \"time\") do={:if ((\$1 > 0s) = true) do={:retu\ + rn \$1} else={:return \$2};}\r\ + \n :if ([ :len [ :tostr \$1 ] ] > 0) do={:return \$1} else={:return \$2};\ + \r\ + \n}\r\ + \n\r\ + \n:log warning ([ \$IfThenElse (\$one != 0) \$one \$two ]);\r\ + \n\r\ + \n:set Test [ \$EitherOr \$Test \"\" ];\r\ + \n:log warning (\"\\\$ Test = \" . \$Test);\r\ + \n\r\ + \n:set ExitError do={\r\ + \n :local ExitOK [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Error [ :tostr \$3 ];\r\ + \n :local IfThenElse;\r\ + \n :if (\$ExitOK = \"false\") do={\r\ + \n :log error (\$Name [ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\\\$\") \ + \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\ + ror\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \ + \$Error) \".\" ]);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n\r\ + \n#:log warning ([ \$IfThenElse (\$one != 0) \$one \$two ]);\r\ + \n\r\ + \n:set ExitOK true;\r\ + \n\$ExitError \$ExitOK \$ScriptName \"Err test custom\";" +add comment="Update and Backup send notification to EMail" \ + dont-require-permissions=no name=AM-Backup-UpdateEmail owner=\ + AM-Backup-UpdateEmail policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-Backup-UpdateEmail\r\ + \n# Script comment: Update and Backup send notification to EMail\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version>=6.43.7\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n# Updated: 15/04/2025\r\ + \n# Website: https://github.com/beeyev\r\ + \n# Notification e-mail (Make sure you have configured Email settings in T\ + ools -> Email)\r\ + \n:local ExitOK false;\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady; :global GlobalFuncReady;\r\ + \n :retry { :if (\$GlobalConfReady != true || \$GlobalFuncReady != true) \ + \\\r\ + \n do={ :error (\"Global config and/or functions not ready.\"); }; } \ + delay=500ms max=50;\r\ + \n :local ScriptName [ :jobname ];\r\ + \n\r\ + \n:local IfThenElse do={:if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] \ + = true) do={:return \$2}; :return \$3;}\r\ + \n:local EitherOr do={\r\ + \n :if ([ :typeof \$1 ] = \"num\") do={:if ((\$1 != 0) = true) do={:retur\ + n \$1} else={:return \$2};}\r\ + \n :if ([ :typeof \$1 ] = \"time\") do={:if ((\$1 > 0s) = true) do={:retu\ + rn \$1} else={:return \$2};}\r\ + \n :if ([ :len [ :tostr \$1 ] ] > 0) do={:return \$1} else={:return \$2};\ + \r\ + \n}\r\ + \n:local ExitError do={\r\ + \n :local ExitOK [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Error [ :tostr \$3 ];\r\ + \n :if (\$ExitOK = \"false\") do={:log error (\$Name [ \$IfThenElse ([ :p\ + ick \$Name 0 1 ] = \"\\\$\") \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\ + ror\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \ + \$Error) \".\" ]);}\r\ + \n}\r\ + \n\r\ + \n:global LogPrint;\r\ + \n:global EditSchedulers;\r\ + \n:global DevInfoIncludeIP;\r\ + \n\r\ + \n:local SchedInterval \"7d\";\r\ + \n:local SchedStartTime \"03:41:27\";\r\ + \n:local SchedComment \"Every 7 Day Check For Update and Backup send notif\ + ication to EMail.\";\r\ + \n\r\ + \n# Function to add or edit schedulers for scripts, to ensure they are set\ + \_up correctly\r\ + \n# How to use:\r\ + \n# Call this function with the following parameters:\r\ + \n# 1. \$1 - Scheduler name (usually same as script name) [*]\r\ + \n# 2. \$2 - Interval (e.g. \"1d\" (default is \"1d\")) [**]\r\ + \n# 3. \$3 - Start Time (e.g. \"00:00:00\", default is current time) [**]\ + \r\ + \n# 4. \$4 - Comment (optional, default: \"Scheduler for script: \")\ + \_[**]\r\ + \n# 5. \$5 - On Event command (e.g. \"/system/script { run ; }\" (de\ + fault is \"/system/script { run \$SchedName; }\")) [**]\r\ + \n# 6. \$6 - Policy (e.g. \"ftp,reboot,read,write,policy,test,password,sni\ + ff,sensitive,romon\" (default is the same)) [**]\r\ + \n# 7. \$7 - Start Date (e.g. \"jan/01/1970\", default is current date) [*\ + *]\r\ + \n# Notes:\r\ + \n# * - Scheduler name is required, and is used to identify the scheduler\ + \_to create or edit. It is usually the same as the script name (\$ScriptNa\ + me).\r\ + \n# ** - If optional parameters are not provided, default values will be u\ + sed.\r\ + \n# This allows for quick setup with minimal parameters, while still \ + providing flexibility for customization.\r\ + \n# Example usage:\r\ + \n# :if ([ \$EditSchedulers \"my-script\" ] = false) do={:set ExitOK false\ + ; :error true;}\r\ + \n# :if ([ \$EditSchedulers \"my-script\" \"1d\" \"03:41:27\" \"My custom \ + scheduler comment\" false false \"jan/01/1970\" ] = false) do={:set ExitOK\ + \_false; :error true;}\r\ + \n\r\ + \n:set EditSchedulers do={\r\ + \n :local SchedName [ :tostr \$1 ];\r\ + \n :if ([ :len \$SchedName ] = 0) do={ :return false; }\r\ + \n :local Interval [ :tostr \$2 ];\r\ + \n :if ([ :len \$Interval ] = 0) do={ :set Interval \"1d\"; }\r\ + \n :local StartTime [ :tostr \$3 ];\r\ + \n :if ([ :len \$StartTime ] = 0) do={ :set StartTime [/system/clock/get \ + time]; }\r\ + \n :local Comment [ :tostr \$4 ];\r\ + \n :if ([ :len \$Comment ] = 0) do={ :set Comment \"Scheduler for script:\ + \_\$SchedName\"; }\r\ + \n :local OnEvent [ :tostr \$5 ];\r\ + \n :if ([ :len \$OnEvent ] = 0) do={ :set OnEvent \"/system/script { run \ + \$SchedName; }\"; }\r\ + \n :local Policy [ :tostr \$6 ];\r\ + \n :if ([ :len \$Policy ] = 0) do={ :set Policy \"ftp,reboot,read,write,p\ + olicy,test,password,sniff,sensitive,romon\"; }\r\ + \n :local StartDate [ :tostr \$7 ];\r\ + \n :if ([ :len \$StartDate ] = 0) do={ :set StartDate [/system/clock/get \ + date]; }\r\ + \n\r\ + \n :onerror Err {\r\ + \n # Check if scheduler exists, if not - create it\r\ + \n :if ([ :len [ /system/scheduler/find where name=\$SchedName ] ] = 0)\ + \_do={\r\ + \n :log warning (\$SchedName . \"System Scheduler for script: \" . \$\ + SchedName . \" is NOT SET! Fix It Now!\");\r\ + \n /system/scheduler/add comment=\$Comment interval=\$Interval name=\ + \$SchedName on-event=\$OnEvent policy=\$Policy start-date=\$StartDate star\ + t-time=\$StartTime;\r\ + \n }\r\ + \n # Check if scheduler parameters are correct, if not - fix them\r\ + \n :if ([ /system/scheduler/get \$SchedName comment ] != \$Comment) do=\ + {\r\ + \n /system/scheduler/set \$SchedName comment=\$Comment;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName interval ] != \$Interval) d\ + o={\r\ + \n /system/scheduler/set \$SchedName interval=\$Interval;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName on-event ] != \$OnEvent) do\ + ={\r\ + \n /system/scheduler/set \$SchedName on-event=\$OnEvent;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName policy ] != \$Policy) do={\ + \r\ + \n /system/scheduler/set \$SchedName policy=\$Policy;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName start-date ] != \$StartDate\ + ) do={\r\ + \n /system/scheduler/set \$SchedName start-date=\$StartDate;\r\ + \n }\r\ + \n :if ([ /system/scheduler/get \$SchedName start-time ] != \$StartTime\ + ) do={\r\ + \n /system/scheduler/set \$SchedName start-time=\$StartTime;\r\ + \n }\r\ + \n } do={:log error (\$SchedName . \"Failed to add/modify scheduler for '\ + \" . \$SchedName . \"' failed: \" . \$Err); :return false};\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:if ([ \$EditSchedulers \$ScriptName \$SchedInterval \$SchedStartTime \$\ + SchedComment ] = false) do={\r\ + \n :log error (\$ScriptName . \"Failed to add/modify scheduler for \" . \ + \$ScriptName . \"!\");\r\ + \n :set ExitOK true;\r\ + \n :error true;\r\ + \n}\r\ + \n\r\ + \n# Local Function CheckOSUpdateMode\r\ + \n:local CheckOSUpdateMode;\r\ + \n:set CheckOSUpdateMode do={\r\ + \n :local NewMode [ :tostr \$1 ];\r\ + \n :if ([ :len \$NewMode ] = 0) do={\r\ + \n :log error (\$0 . \"/nFunction /\$CheckOSUpdateMode received empty v\ + alue!\");\r\ + \n :return false;\r\ + \n :error true;\r\ + \n }\r\ + \n\r\ + \n :global BackupAndUpdateMode;\r\ + \n :global OldMode [ :tostr \$BackupAndUpdateMode ];\r\ + \n :if (\$NewMode != \$BackupAndUpdateMode && \$NewMode = \"updateOnce\")\ + \_do={\r\ + \n :set BackupAndUpdateMode [ :tostr \$NewMode ];\r\ + \n :log info (\$0 . \"/nOk. /\$BackupAndUpdateMode set to: '\" . \$NewM\ + ode . \"'.\\nYou're a good boy! ))\\nI'm launching the update in 5 seconds\ + .\");\r\ + \n :delay 5;\r\ + \n /system/script { run \$ScriptName; };\r\ + \n } else={\r\ + \n :log warning (\$0 . \"/nFunction /\$CheckOSUpdateMode received value: \ + '\" . \$NewMode . \"', but is may be only \\\"updateOnce\\\"!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n :error false;\r\ + \n}\r\ + \n\r\ + \n# :if ([ :len [ /system/scheduler/find where name=\"running-from-backup\ + -partition\" ] ] > 0) do={\r\ + \n# \$LogPrint warning \$ScriptName (\"Running from backup partition, r\ + efusing to act.\");\r\ + \n# :set ExitOK true;\r\ + \n# :error false;\r\ + \n# }\r\ + \n\r\ + \n:global EmailGeneralTo;\r\ + \n:global BackupAndUpdateMode;\r\ + \n:global forceBackup;\r\ + \n:global BackupPassword;\r\ + \n:global BackupSens;\r\ + \n:global updateChannel;\r\ + \n\r\ + \n:global DevInfoIncludeIP;\r\ + \n\r\ + \n# Installs patch updates only (BackupAndUpdateMode = \"osupdate\").\r\ + \n# Works for `stable` and `long-term` channels.\r\ + \n# Updates only if MAJOR.MINOR match (e.g. 6.43.2 > 6.43.6 allowed, 6.44.\ + 1 skipped).\r\ + \n# Sends info if a newer (non-patch) version is found.\r\ + \n:local installOnlyPatchUpdates false;\r\ + \n:local scriptVersion \"26.04.30\";\r\ + \n:local copyrightUrl \"https://www.github.com/vados-dev/\";\r\ + \n#Script messages prefix\r\ + \n:local SMP \"BkpAndUpdate:\";\r\ + \n\r\ + \n:local exitErrorMessage \"\$SMP script stopped due to an error. Please c\ + heck logs for more details.\";\r\ + \n:log info \"\\n\\n\$SMP Script \\\"Mikrotik RouterOS automatic backup & \ + update\\\" v.\$scriptVersion started.\";\r\ + \n:log info \"\$SMP Script Mode: `\$BackupAndUpdateMode`, Update channel: \ + `\$updateChannel`, Force backup: `\$forceBackup`, Install only patch updat\ + es: `\$installOnlyPatchUpdates`\";\r\ + \n\r\ + \n## vv FUNCTIONS vv ##\r\ + \n:global WaitCheckUpdates; \$WaitCheckUpdates;\r\ + \n:global MiniDateTimeStamp;\r\ + \n:local MDTSFix [\$MiniDateTimeStamp];\r\ + \n:global Identity;\r\ + \n:global IdentityShort;\r\ + \n:global devBoardName;\r\ + \n:global runningVersion;\r\ + \n:global runningChannel;\r\ + \n:global devModel;\r\ + \n:global devRbSerialNumber;\r\ + \n:global ROsVerAvail;\r\ + \n:global PkgUpdStatus;\r\ + \n\r\ + \n# Checks if two RouterOS version strings differ only by the patch versio\ + n\r\ + \n# :put [\$FuncIsPatchUpdateOnly \"6.2.1\" \"6.2.4\"] # Output: true\r\ + \n# :put [\$FuncIsPatchUpdateOnly \"6.2.1\" \"6.3.1\"] # Output: false\r\ + \n:local FuncIsPatchUpdateOnly do={\r\ + \n :local ver1 \$1;\r\ + \n :local ver2 \$2;\r\ + \n # Extract the major and minor components from a version\r\ + \n :local extractMajorMinor do={\r\ + \n :local ver \$1;\r\ + \n :local dot1 [:find \$ver \".\"];\r\ + \n :if (\$dot1 = -1) do={:return \$ver}\r\ + \n :local major [:pick \$ver 0 \$dot1];\r\ + \n :local rest [:pick \$ver (\$dot1 + 1) [:len \$ver]];\r\ + \n :local dot2 [:find \$rest \".\"];\r\ + \n :local minor \$rest;\r\ + \n :if (\$dot2 >= 0) do={:set minor [:pick \$rest 0 \$dot2]}\r\ + \n :return (\$major . \".\" . \$minor);\r\ + \n }\r\ + \n\r\ + \n# Compare the major and minor components of both version strings\r\ + \n:if ([\$extractMajorMinor \$ver1] = [\$extractMajorMinor \$ver2]) do={:r\ + eturn true}\r\ + \n :return false;\r\ + \n}\r\ + \n# Creates backups and returns array of names\r\ + \n# Possible arguments:\r\ + \n# \$1 - file name, without extension\r\ + \n# \$2 - password (optional)\r\ + \n# \$3 - sensitive data in config (optional, default: false)\r\ + \n# Example:\r\ + \n#:put [\$FuncCreateBackups \$backupName]\r\ + \n:local FuncCreateBackups do={\r\ + \n :local backupName [ :tostr \$1 ];\r\ + \n :local BackPass \$2;\r\ + \n :local BackSens \$3;\r\ + \n\r\ + \n #Script messages prefix\r\ + \n :local SMP \"BkpAndUpdate:\";\r\ + \n :local exitErrorMessage \"\$SMP script stopped due to an error. Please\ + \_check logs for more details.\";\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` started, input:\ + \_`\$backupName`\");\r\ + \n\r\ + \n # validate required parameter: backupName\r\ + \n :if ([:typeof \$backupName] != \"str\" or [:len \$backupName] = 0) do=\ + {\r\ + \n :log error \"\$SMP parameter 'backupName' is required and must be a \ + non-empty string\";\r\ + \n# :log warning \"\$SMP parameter '\\\$backupName' is required and mus\ + t be a non-empty string.\\nI set '\\\$backupName' to 'default-backup'\";\r\ + \n# :set \$backupName \"default-backup\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n\r\ + \n :local backupFileSys \"\$backupName.backup\";\r\ + \n :local backupFileConfig \"\$backupName.rsc\";\r\ + \n :local backupNames {\$backupFileSys;\$backupFileConfig};\r\ + \n\r\ + \n ## Perform system backup\r\ + \n :if ([:len \$BackPass] = 0) do={\r\ + \n :log info (\"\$SMP starting backup without password, backup name: `\ + \$backupName`\");\r\ + \n /system backup save dont-encrypt=yes name=\$backupName;\r\ + \n } else={\r\ + \n :log info (\"\$SMP starting backup with password, backup name: `\$ba\ + ckupName`\");\r\ + \n /system backup save password=\$BackPass name=\$backupName;\r\ + \n }\r\ + \n :log info (\"\$SMP system backup created: `\$backupFileSys`\");\r\ + \n ## Export config file\r\ + \n :if (\$BackSens = true) do={\r\ + \n :log info (\"\$SMP starting export config with sensitive data, backu\ + p name: `\$backupName`\");\r\ + \n # Since RouterOS v7 it needs to be explicitly set that we want to ex\ + port sensitive data\r\ + \n :if ([:pick [/system resource get version] 0 1] < 7) do={\r\ + \n :execute \"/export compact terse file=\$backupName\";\r\ + \n } else={\r\ + \n :execute \"/export compact show-sensitive terse file=\$backupName\ + \";\r\ + \n }\r\ + \n } else={\r\ + \n :log info (\"\$SMP starting export config without sensitive data, ba\ + ckup name: `\$backupName`\");\r\ + \n /export compact hide-sensitive terse file=\$backupName;\r\ + \n }\r\ + \n :log info (\"\$SMP Config export complete: `\$backupFileConfig`\");\r\ + \n :log info (\"\$SMP Waiting a little to ensure backup files are written\ + \");\r\ + \n :delay 20;\r\ + \n :if ([:len [/file find name=\$backupFileSys]] > 0) do={\r\ + \n :log info (\"\$SMP system backup file successfully saved to the file\ + \_system: `\$backupFileSys`\");\r\ + \n } else={\r\ + \n :log error (\"\$SMP system backup was not created, file does not exi\ + st: `\$backupFileSys`\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :if ([:len [/file find name=\$backupFileConfig]] > 0) do={\r\ + \n :log info (\"\$SMP config backup file successfully saved to the file\ + \_system: `\$backupFileConfig`\");\r\ + \n } else={\r\ + \n :log error (\"\$SMP config backup was not created, file does not exi\ + st: `\$backupFileConfig`\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` finished. Creat\ + ed backups, system: `\$backupFileSys`, config: `\$backupFileConfig`\")\r\ + \n :return \$backupNames;\r\ + \n}\r\ + \n# Sends an email\r\ + \n# Parameters:\r\ + \n# \$1 - to (email address)\r\ + \n# \$2 - subject\r\ + \n# \$3 - body\r\ + \n# \$4 - file attachments (optional; pass \"\" if not needed)\r\ + \n#\r\ + \n# Example:\r\ + \n# \$FuncSendEmailSafe \"admin@domain.com\" \"Backup Done\" \"Backup comp\ + lete.\" \"backup1.backup\"\r\ + \n:local FuncSendEmailSafe do={\r\ + \n :global EmailGeneralTo;\r\ + \n :local emailSubject \$2;\r\ + \n :local emailBody \$3;\r\ + \n :local emailAttachments \$4;\r\ + \n :local SMP \"Bkp&Upd:\";\r\ + \n :local exitErrorMessage \"\$SMP script stopped due to an error. Please\ + \_check logs for more details.\";\r\ + \n :log info \"\$SMP Attempting to send email to `\$EmailGeneralTo`\";\r\ + \n # SAFETY: wait for any previously queued email to finish\r\ + \n :local waitTimeoutPre 60;\r\ + \n :local waitCounterPre 0;\r\ + \n :while (([/tool e-mail get last-status] = \"resolving-dns\" or [/tool \ + e-mail get last-status] = \"in-progress\")) do={\r\ + \n :if (\$waitCounterPre >= \$waitTimeoutPre) do={\r\ + \n :log error \"\$SMP Email send aborted: previous send did not compl\ + ete after \$waitTimeoutPre seconds\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :log info \"\$SMP Waiting for previous email to finish (status: \$[/\ + tool e-mail get last-status])...\";\r\ + \n :delay 1; \r\ + \n :set waitCounterPre (\$waitCounterPre + 1);\r\ + \n }\r\ + \n # Send the email\r\ + \n :do {\r\ + \n /tool e-mail send to=\$EmailGeneralTo subject=\$emailSubject body=\$\ + emailBody file=\$emailAttachments;\r\ + \n } on-error={\r\ + \n :log error \"\$SMP Email send command failed to execute. Check logs \ + and verify email settings.\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n # Wait for send status to change from \"in-progress\" / \"resolving-dn\ + s\"\r\ + \n :local waitTimeout 60;\r\ + \n :local waitCounter 0;\r\ + \n :local emailStatus \"\";\r\ + \n :log info \"\$SMP Waiting for email to be sent, timeout in `\$waitTime\ + out` seconds...\";\r\ + \n :while (\$waitCounter < \$waitTimeout) do={\r\ + \n :set emailStatus [/tool e-mail get last-status];\r\ + \n :if (\$emailStatus != \"in-progress\" and \$emailStatus != \"resolvi\ + ng-dns\") do={\r\ + \n :log info \"\$SMP Email send status received: \$emailStatus\";\r\ + \n # exit loop\r\ + \n :set waitCounter \$waitTimeout;\r\ + \n } else={:delay 1; :set waitCounter (\$waitCounter + 1)}\r\ + \n }\r\ + \n # Final decision based on last status\r\ + \n :if (\$emailStatus = \"succeeded\") do={\r\ + \n :log info \"\$SMP Email successfully sent to `\$EmailGeneralTo`\";\ + \r\ + \n } else={\r\ + \n :log error \"\$SMP Email failed to send. Status: `\$emailStatus`. Ch\ + eck logs for more details and verify email settings.\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n}\r\ + \n# Global variable to track current update step\r\ + \n# They need to be initialized here first to be available in the script\r\ + \n:global buGlobalVarTargetOsVersion;\r\ + \n:global buGlobalVarScriptStep;\r\ + \n:local scriptStep \$buGlobalVarScriptStep;\r\ + \n:do {\r\ + \n /system/script/environment remove buGlobalVarScriptStep;\r\ + \n} on-error={}\r\ + \n:if ([:len \$scriptStep] = 0) do={\r\ + \n :set scriptStep 1;\r\ + \n}\r\ + \n## ^^ FUNCTIONS ^^ ##\r\ + \n#\r\ + \n# Initial validation\r\ + \n## Check email settings\r\ + \n:if ([:len \$EmailGeneralTo] < 3) do={\r\ + \n :log error (\"\$SMP Parameter `\\\$EmailGeneralTo` is not set, or cont\ + ains invalid value. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Values will be defined later in the script\r\ + \n:local emailServer \"\";\r\ + \n:local emailFromAddress [/tool e-mail get from];\r\ + \n:log info \"\$SMP Validating email settings...\";\r\ + \n:do {\r\ + \n :set emailServer [/tool e-mail get server];\r\ + \n} on-error={\r\ + \n # This is a workaround for the RouterOS v7.12 and older versions\r\ + \n :set emailServer [/tool e-mail get address];\r\ + \n}\r\ + \n:if (\$emailServer = \"0.0.0.0\") do={\r\ + \n :log error (\"\$SMP Email server address is not correct: `\$emailServe\ + r`, check `Tools -> Email`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n:if ([:len \$emailFromAddress] < 3) do={\r\ + \n :log error (\"\$SMP Email configuration FROM address is not correct: `\ + \$emailFromAddress`, check `Tools -> Email`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Script mode validation\r\ + \n:if (\$BackupAndUpdateMode != \"backup\" and \$BackupAndUpdateMode != \"\ + osupdate\" and \$BackupAndUpdateMode != \"updateOnce\" and \$BackupAndUpda\ + teMode != \"osnotify\") do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$BackupAndUpdateMode` is not \ + set, or contains invalid value: `\$BackupAndUpdateMode`. Script stopped.\"\ + );\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Update channel validation\r\ + \n:if (\$updateChannel != \"stable\" and \$updateChannel != \"long-term\" \ + and \$updateChannel != \"testing\" and \$updateChannel != \"development\")\ + \_do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$updateChannel` is not set, o\ + r contains invalid value: `\$updateChannel`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Verify if script is set to install patch updates and if the update cha\ + nnel is valid\r\ + \n:if ((\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \"\ + updateOnce\") and \$installOnlyPatchUpdates = true) do={\r\ + \n :if (\$updateChannel != \"stable\" and \$updateChannel != \"long-term\ + \") do={\r\ + \n :log error (\"\$SMP Patch-only updates enabled, but update channel `\ + \$updateChannel` is invalid. Only `stable` and `long-term` are supported. \ + Script stopped\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n \r\ + \n :if (\$runningChannel != \"stable\" and \$runningChannel != \"long-ter\ + m\") do={\r\ + \n :log error (\"\$SMP Script is set to install only patch updates, but\ + \_the installed RouterOS version is not from `stable` or `long-term` chann\ + el: `\$runningChannel`. Script stopped\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n}\r\ + \n#\r\ + \n\r\ + \n:local rawTime [/system clock get time];\r\ + \n:local rawDate [/system clock get date];\r\ + \n\r\ + \n:local deviceOsVerAndChannelRunning [/system/resource/get version];\r\ + \n\r\ + \n:local backupNameTemplate (\"backup_v\" . \$runningVersion . \"_\" .\ + \_\$runningChannel . \"_\" . \$MDTSFix);\r\ + \n:local backupNameBeforeUpdate (\$backupNameTemplate . \"_before_update\"\ + );\r\ + \n:local backupNameAfterUpdate (\$backupNameTemplate . \"_after_update\")\ + ;\r\ + \n\r\ + \n## Email body template\r\ + \n:local mailSubjectPrefix \"\$SMP Device - `\$IdentityShort`\";\r\ + \n:local mailBodyCopyright \"Mikrotik RouterOS automatic backup & update \ + (ver. \$scriptVersion) \\n\$copyrightUrl\";\r\ + \n:local changelogUrl \"Check RouterOS changelog: https://mikrotik.com\ + /download/changelogs/\";\r\ + \n:local mailBodyDeviceInfo \"\";\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"Device information:\")\ + ;\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\n-------------------\ + --\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nName: \$Identity\")\ + ;\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nModel: \$devModel\"\ + );\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBoard: \$devBoardNa\ + me\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nSerial number: \$de\ + vRbSerialNumber\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nRouterOS version: v\ + \$deviceOsVerAndChannelRunning\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBuild time: \$[/sys\ + tem/resource/get build-time]\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nRouterboard FW Avai\ + lable: \$ROsVerAvail\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nDevice date-time: \ + \$rawDate \$rawTime (\$[/system/clock/get time-zone-name ])\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nUptime: \$[/system/\ + resource/get uptime]\");\r\ + \n## IP address detection\r\ + \n:if ([ \$DevInfoIncludeIP ] = true) do={\r\ + \n :global GetPubIp;\r\ + \n :local PublicIpAddress [ \$GetPubIp ];\r\ + \n :set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nPublic IP address\ + : \" . \$PublicIpAddress . \".\");\r\ + \n :log info \"\$SMP Public IP address detected: \$PublicIpAddress\";\r\ + \n} \r\ + \n\r\ + \n:local mailAttachments [:toarray \"\"];\r\ + \n:if (\$scriptStep = 1 or \$scriptStep = 3) do={\r\ + \n :if (\$scriptStep = 3) do={\r\ + \n :log info (\"\$SMP Waiting for one minute before continuing to the f\ + inal step.\");\r\ + \n :delay 1m;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n## STEP 1: Create backups, check for new RouterOS, and send email\r\ + \n## Steps 2\C3\A23 run only if auto-update is enabled and a new version i\ + s available\r\ + \n:if (\$scriptStep = 1) do={\r\ + \n :global BackupPassword;\r\ + \n :global BackupSens;\r\ + \n :local isNewOsUpdateAvailable false;\r\ + \n :local isLatestOsAlreadyInstalled true;\r\ + \n :local isOsNeedsToBeUpdated false;\r\ + \n :local isUpdateCheckSucceeded false;\r\ + \n :local isEmailNeedsToBeSent false;\r\ + \n :local mailSubjectPartAction \"\";\r\ + \n :local mailPtBodyAction \"\";\r\ + \n :local mailPtSubjectBackup \"\";\r\ + \n :local mailPtBodyBackup \"\";\r\ + \n # Checking for new version\r\ + \n :if (\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \ + \"osnotify\" or \$BackupAndUpdateMode = \"updateOnce\") do={\r\ + \n :log info (\"\$SMP Setting update channel to `\$updateChannel`\");\r\ + \n /system/package/update/set channel=\$updateChannel;\r\ + \n :log info (\"\$SMP Checking for new RouterOS version. Current instal\ + led version is: `\$runningVersion`\");\r\ + \n \$LogPrint debug \$ScriptName (\"Checking for updates...\");\r\ + \n \r\ + \n# /system/package/update/check-for-updates without-paging as-value;\ + \r\ + \n# /system/package/update check-for-updates;\r\ + \n :delay 5s;\r\ + \n :set PkgUpdStatus [ /system/package/update/get status ];\r\ + \n :if (\$PkgUpdStatus = \"New version is available\") do={\r\ + \n :set ROsVerAvail [/system/package/update/get latest-version];\r\ + \n }\r\ + \n\r\ + \n :if (\$PkgUpdStatus = \"New version is available\") do={\r\ + \n :log info (\"\$SMP New RouterOS version is available: `\$ROsVerAva\ + il`\");\r\ + \n :set isNewOsUpdateAvailable true;\r\ + \n :set isLatestOsAlreadyInstalled false;\r\ + \n :set isUpdateCheckSucceeded true;\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :set mailSubjectPartAction \"New RouterOS available\";\r\ + \n :set mailPtBodyAction \"New RouterOS version is available, curren\ + t version: v\$runningVersion, new version: v\$ROsVerAvail. \\n\$changelogU\ + rl\";\r\ + \n } else={\r\ + \n :if (\$PkgUpdStatus = \"System is already up to date\") do={\r\ + \n :log info (\"\$SMP No new RouterOS version is available, the lat\ + est version is already installed: `v\$runningVersion`\");\r\ + \n :set isUpdateCheckSucceeded true;\r\ + \n :set mailSubjectPartAction \"No os update available\";\r\ + \n :set mailPtBodyAction \"No new RouterOS version is available, t\ + he latest version is already installed: `v\$runningVersion`\";\r\ + \n } else={\r\ + \n :log error (\"\$SMP Failed to check for new RouterOS version. Pa\ + ckage check status: `\$PkgUpdStatus`\");\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :set mailSubjectPartAction \"Error unable to check new os versio\ + n\";\r\ + \n :set mailPtBodyAction \"An error occurred while checking for a \ + new RouterOS version.\\nStatus returned: `\$PkgUpdStatus`\\n\\nPlease revi\ + ew the logs on the device for more details and verify internet connectivit\ + y.\";\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n # Checking if the script needs to install new os version\r\ + \n :if ((\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \ + \"updateOnce\") and \$isNewOsUpdateAvailable = true) do={\r\ + \n :if (\$installOnlyPatchUpdates = true) do={\r\ + \n :if ([\$FuncIsPatchUpdateOnly \$runningVersion \$ROsVerAvail] = tr\ + ue) do={\r\ + \n :log info \"\$SMP New RouterOS version is available, and it is a\ + \_patch update. Current version: v\$runningVersion, new version: v\$ROsVer\ + Avail\";\r\ + \n :set isOsNeedsToBeUpdated true;\r\ + \n } else={\r\ + \n :log info \"\$SMP The script will not install this update, becau\ + se it is not a patch update. Current version: v\$runningVersion, new versi\ + on: v\$ROsVerAvail\";\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nThis update wil\ + l not be installed, because the script is set to install only patch update\ + s.\");\r\ + \n }\r\ + \n } else={\r\ + \n :set isOsNeedsToBeUpdated true;\r\ + \n }\r\ + \n }\r\ + \n # Checking If the script needs to create a backup\r\ + \n :if (\$forceBackup = true or \$BackupAndUpdateMode = \"backup\" or \$i\ + sOsNeedsToBeUpdated = true) do={\r\ + \n :log info (\"\$SMP Starting backup process.\");\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :local backupName \$backupNameTemplate;\r\ + \n # This means it's the first step where we create a backup before the\ + \_update process\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\r\ + \n :set backupName \$backupNameBeforeUpdate;\r\ + \n #Email body if the purpose of the script is to update the device\r\ + \n :set mailSubjectPartAction \"Update preparation\";\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nThe update proces\ + s for device '\$Identity' is scheduled to upgrade RouterOS from version v.\ + \$runningVersion to version v.\$ROsVerAvail (Update channel: \$updateChann\ + el)\");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nPlease note: The \ + update will proceed only after a successful backup.\");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nA final report wi\ + th detailed information will be sent once the update process is completed.\ + \");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nIf you do not rec\ + eive a second email within the next 10 minutes, there may be an issue. Ple\ + ase check your device logs for further information.\");\r\ + \n }\r\ + \n :do {\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupName \$BackupPas\ + sword \$BackupSens];\r\ + \n :set mailPtSubjectBackup \"Backup created\";\r\ + \n :set mailPtBodyBackup \"System backups have been successfully crea\ + ted and attached to this email.\";\r\ + \n } on-error={\r\ + \n :set isOsNeedsToBeUpdated false;\r\ + \n :set mailPtSubjectBackup \"Backup failed\";\r\ + \n :set mailPtBodyBackup \"The script failed to create backups. Pleas\ + e check device logs for more details.\";\r\ + \n :log warning \"\$SMP Backup creation failed. Update process will b\ + e canceled if automatic update is enabled\";\r\ + \n }\r\ + \n }\r\ + \n :if (\$isEmailNeedsToBeSent = true) do={\r\ + \n :log info \"\$SMP Preparing to send email...\";\r\ + \n :local mailStep1Subject \$mailSubjectPrefix;\r\ + \n :local mailStep1Body \"\";\r\ + \n # subject\r\ + \n :if (\$mailSubjectPartAction != \"\") do={:set mailStep1Subject (\$\ + mailStep1Subject . \" - \" . \$mailSubjectPartAction)}\r\ + \n :if (\$mailPtSubjectBackup != \"\") do={:set mailStep1Subject (\$ma\ + ilStep1Subject . \" - \" . \$mailPtSubjectBackup)}\r\ + \n # body\r\ + \n :if (\$mailPtBodyAction != \"\") do={:set mailStep1Body (\$mailStep1\ + Body . \$mailPtBodyAction . \"\\n\\n\")}\r\ + \n :if (\$mailPtBodyBackup != \"\") do={:set mailStep1Body (\$mailStep1\ + Body . \$mailPtBodyBackup . \"\\n\\n\")}\r\ + \n :set mailStep1Body (\$mailStep1Body . \$mailBodyDeviceInfo . \"\\n__\ + ________\\n\" . \$mailBodyCopyright);\r\ + \n :set mailStep1Body (\$mailStep1Body . \"\\n[*** TEST START ***]\\n\\\ + \$DeviceInfo: \" . [ \$DeviceInfo ] . \"\\n[*** TEST END ***]\\n\");\r\ + \n # Send email with backups\r\ + \n :do {\$FuncSendEmailSafe \$EmailGeneralTo \$mailStep1Subject \$mailS\ + tep1Body \$mailAttachments} on-error={\r\ + \n :set isOsNeedsToBeUpdated false;\r\ + \n :log error \"\$SMP The script will not proceed with the update pro\ + cess, because the email was not sent.\";\r\ + \n }\r\ + \n }\r\ + \n :if ([:len \$mailAttachments] > 0) do={\r\ + \n :log info \"\$SMP Cleaning up backup files from the file system...\"\ + ;\r\ + \n /file remove \$mailAttachments;\r\ + \n :delay 2s;\r\ + \n }\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\r\ + \n :global OldMode;\r\ + \n :log info \"\$SMP everything is ready to install new RouterOS, going\ + \_to start the update process and reboot the device.\";\r\ + \n :do {\r\ + \n :local nextStep 2;\r\ + \n :if (\$isCloudHostedRouter = true) do={\r\ + \n :log info \"\$SMP The device is a cloud hosted router, the secon\ + d step updating the Routerboard firmware will be skipped.\";\r\ + \n :set nextStep 3;\r\ + \n }\r\ + \n :local scheduledCommand (\":delay 5s; /system/scheduler remove BKP\ + UPD-NEXT-BOOT-TASK; \\\r\ + \n :global buGlobalVarScriptStep \$nextStep; :global buGlobalVarTarg\ + etOsVersion \\\"\$ROsVerAvail\\\"; \\\r\ + \n :delay 10s; /system/script { run \$ScriptName; }\");\r\ + \n /system/scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\$schedu\ + ledCommand start-time=startup interval=0;\r\ + \n /system/package/update install;\r\ + \n } on-error={\r\ + \n # Failed to install new os version, remove the task and variables\ + \r\ + \n :do {\r\ + \n /system/scheduler remove BKPUPD-NEXT-BOOT-TASK;\r\ + \n :set BackupAndUpdateMode \$OldMode;\r\ + \n :delay 2;\r\ + \n :do {\r\ + \n :global buGlobalVarTargetOsVersion (a);\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \"ERROR Remove\ + \_global variable \\\$buGlobalVarTargetOsVersion failed!\");\r\ + \n }\r\ + \n# :if ([ :len \$OldMode ] > 0) do={\r\ + \n# :do {\r\ + \n# :global OldMode (a);\r\ + \n# } on-error={\r\ + \n# :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Re\ + move global variable \\\$OldMode failed!\");\r\ + \n# }\r\ + \n# }\r\ + \n } on-error={\r\ + \n :log error \"\$SMP Failed to install new RouterOS version. Pleas\ + e check device logs for more details. \\\r\ + \n \\nAnd Failed to remove task and variables! Check it!\";\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Remove \ + task and global variables!\");\r\ + \n }\r\ + \n :log error \"\$SMP Failed to install new RouterOS version. Please \ + check device logs for more details.\";\r\ + \n :local mailUpdateErrorSubject (\$mailSubjectPrefix . \" - Update f\ + ailed\");\r\ + \n :local mailUpdateErrorBody \"The script was unable to install new \ + RouterOS version. Please check device logs for more details.\";\r\ + \n # Send email with error\r\ + \n \$FuncSendEmailSafe \$EmailGeneralTo \$mailUpdateErrorSubject \$ma\ + ilUpdateErrorBody \"\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n## STEP 2: (Post-reboot) Upgrade RouterBOARD firmware\r\ + \n## Runs only if auto-update is enabled and a new RouterOS version was fo\ + und\r\ + \n:if (\$scriptStep = 2) do={\r\ + \n :log info \"\$SMP The script is in the second step, updating Routerboa\ + rd firmware.\";\r\ + \n :log info \"\$SMP Upgrading routerboard firmware from v.\$deviceRbCurr\ + entFw to v.\$deviceRbUpgradeFw\";\r\ + \n /system routerboard upgrade;\r\ + \n :delay 2;\r\ + \n :log info \"\$SMP routerboard upgrade process was completed, going to \ + reboot in a moment!\";\r\ + \n ## Set task to send final report on the next boot\r\ + \n /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\":delay 2; \ + :global buGlobalVarScriptStep 3; \\\r\ + \n :global buGlobalVarTargetOsVersion \\\"\$buGlobalVarTargetOsVersion\\\ + \"; :delay 10s; /system/script { run \$ScriptName; }; \\ \r\ + \n :delay 5s; /system/scheduler remove BKPUPD-NEXT-BOOT-TASK;\" start-ti\ + me=startup interval=0;\r\ + \n :delay 2;\r\ + \n /system reboot;\r\ + \n}\r\ + \n\r\ + \n## STEP 3: Final report (after second reboot, with delay).\r\ + \n## Runs only if auto-update is enabled and a new RouterOS version was fo\ + und.\r\ + \n:if (\$scriptStep = 3) do={\r\ + \n :log info (\"\$SMP The script is in the third step, sending final repo\ + rt.\");\r\ + \n :local targetOsVersion \$buGlobalVarTargetOsVersion;\r\ + \n :do {\r\ + \n# /system/script/environment remove [find name=\"buGlobalVarTargetOsV\ + ersion\"];\r\ + \n :global buGlobalVarTargetOsVersion (a);\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Remove glob\ + al variable \\\$buGlobalVarTargetOsVersion failed!\");\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$OldMode ] != 0) do={\r\ + \n :do {\r\ + \n# /system/script/environment remove [find name=\"OldMode\"];\r\ + \n :global OldMode (a);\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR! Remove\ + \_global variable \\\$OldMode failed!\");\r\ + \n }\r\ + \n }\r\ + \n :if ([:len \$targetOsVersion] = 0) do={\r\ + \n :log warning \"\$SMP Something is wrong, the script was unable to ge\ + t the target updated OS version from the global variable.\";\r\ + \n }\r\ + \n :local mailStep3Subject \$mailSubjectPrefix;\r\ + \n :local mailStep3Body \"\";\r\ + \n :if (\$targetOsVersion = \$runningVersion) do={\r\ + \n :log info \"\$SMP Successfully verified new RouterOS version: target\ + : `\$targetOsVersion`, current: `\$runningVersion`\";\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update completed - \ + Backup created\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"RouterOS and routerboard upg\ + rade process was completed\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"\\nNew RouterOS version: v.\ + \$targetOsVersion, routerboard firmware: v.\$deviceRbCurrentFw\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"\\n\$changelogUrl\\nBackups \ + of the upgraded system are in the attachment of this email.\\n\\n\$mailBod\ + yDeviceInfo\\n\\n\$mailBodyCopyright\");\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupNameAfterUpdate \$\ + BackupPassword \$BackupSens];\r\ + \n } else={\r\ + \n :log error \"\$SMP Failed to verify new RouterOS version: target: `\ + \$targetOsVersion`, current: `\$runningVersion`\";\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update failed\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"The script was unable to ver\ + ify that the new RouterOS version was installed, target version: `\$target\ + OsVersion`, current version: `\$runningVersion`\\nCheck device logs for mo\ + re details.\\n\\n\$mailBodyDeviceInfo\\n\\n\$mailBodyCopyright\");\r\ + \n }\r\ + \n \$FuncSendEmailSafe \$EmailGeneralTo \$mailStep3Subject \$mailStep3Bod\ + y \$mailAttachments;\r\ + \n :if ([:len \$mailAttachments] > 0) do={\r\ + \n :log info \"\$SMP Cleaning up backup files from the file system...\"\ + ;\r\ + \n /file remove \$mailAttachments;\r\ + \n :delay 2;\r\ + \n }\r\ + \n :log info (\"\$SMP Final report email sent successfully, and the scrip\ + t has finished.\");\r\ + \n}\r\ + \n:log info (\"\$SMP the script has finished, script step: `\$scriptStep` \ + \\n\\n\");\r\ + \n\r\ + \n} do={:global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err}\r\ + \n" +add dont-require-permissions=no name=testGetPubIP owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\ + global DevInfoIncludeIP;\r\ + \n:global CharacterReplace;\r\ + \n:global EscapeForRegEx;\r\ + \n:global UrlEncode;\r\ + \n\r\ + \n:if ([ \$DevInfoIncludeIP ] = true) do={\r\ + \n :global GetPubIp;\r\ + \n :local Char (\"\\n\\r\");\r\ + \n :local PublicIpAddress [\$GetPubIp];\r\ + \n# :if ([ :pick \$PublicIpAddress ([ :len \$PublicIpAddress ] - 2) ] = \ + \"\\n\") do={:error true}; :set error falseon-error={:set PublicIpAddress \ + (\$PublicIpAddress . Char)};\r\ + \n#:if ([ :pick \$PublicIpAddress ([ :len \$PublicIpAddress ] - 1) ] = \"/\ + n\") do={\r\ + \n#:log warning \"N is = //n\"; \r\ + \n# :set PublicIpAddress [ \$CharacterReplace \$PublicIpAddress \$Char \"\ + \" ];\r\ + \n#}\r\ + \n# :set PublicIpAddress [ \$UrlEncode \$PublicIpAddress ];\r\ + \n#:local IpLen ([ :len \$PublicIpAddress ] -1);\r\ + \n#:set PublicIpAddress [:pick \$PublicIpAddress 0 \$IpLen];\r\ + \n :log warning \"Public IP address detected: \$PublicIpAddress , len = \ + \$IpLen\";\r\ + \n}\r\ + \n" +add comment="download, import and update firewall address-lists" \ + dont-require-permissions=no name=AM-FwAddrLists owner=AM-FwAddrLists \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by Vados\r\ + \n# RouterOS script: AM-FwAddrLists\r\ + \n# Script comment: download, import and update firewall address-lists\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# https://ros.vados.ru/doc/fw-addr-lists.md\r\ + \n:local ExitOK false;\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady; :global GlobalFuncReady;\r\ + \n :retry { :if (\$GlobalConfReady != true || \$GlobalFuncReady != true) \ + \\\r\ + \n do={ :error (\"Global config and/or functions not ready.\"); }; } \ + delay=500ms max=50;\r\ + \n :local ScriptName [ :jobname ];\r\ + \n\r\ + \n :global GlobalScriptsUrl;\r\ + \n :local ListsDirName (\"fw-addr-lists.d/\" . [ /system identity get nam\ + e ] . \"/\");\r\ + \n :local ListsUrl (\$GlobalScriptsUrl . \$ListsDirName);\r\ + \n :local ListsIndex \"index.lst\";\r\ + \n :local AddrListsTimeOut 1d;\r\ + \n :local CheckCerts false;\r\ + \n\r\ + \n :global CertificateAvailable;\r\ + \n :global EitherOr;\r\ + \n :global FetchHugeFiles;\r\ + \n :global HumanReadableNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global NetMask4;\r\ + \n :global NetMask6;\r\ + \n :global ScriptLock;\r\ + \n \r\ + \n :local FindDelim do={\r\ + \n :local ValidChars \"0123456789.:/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi\ + jklmnopqrstuvwxyz-\";\r\ + \n :for I from=0 to=[ :len \$1 ] do={\r\ + \n :if ([ :typeof [ :find \$ValidChars [ :pick (\$1 . \" \") \$I ] ] \ + ] != \"num\") do={\r\ + \n :return \$I;\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :local GetBranch do={\r\ + \n :global EitherOr;\r\ + \n :return [ :pick [ :convert transform=md5 to=hex [ :pick \$1 0 [ \$Ei\ + therOr [ :find \$1 \"/\" ] [ :len \$1 ] ] ] ] 0 2 ];\r\ + \n }\r\ + \n \r\ + \n :if ([ \$ScriptLock \$ScriptName ] = false) do={\r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :local ListComment (\"managed by \" . \$ScriptName);\r\ + \n :local Url (\$ListsUrl . \$ListsIndex);\r\ + \n :local tmpIndex false;\r\ + \n\r\ + \n :for I from=1 to=5 do={\r\ + \n :if (\$tmpIndex = false) do={\r\ + \n :set tmpIndex [ :tolf [ \$FetchHugeFiles \$ScriptName \$Url \$Chec\ + kCerts ] ];\r\ + \n :if (\$tmpIndex = false && \$I < 5) do={\r\ + \n :log warning (\$ScriptName . \" Failed downloading address-list \ + index, \" . \$I . \". try from: \" . \$Url);\r\ + \n :delay ((\$I * \$I) . \"s\");\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n :if (\$tmpIndex = false) do={\r\ + \n :log warning (\$ScriptName . \" Failed downloading address-list inde\ + x from: \" . \$Url);\r\ + \n :set tmpIndex \"\";\r\ + \n }\r\ + \n\r\ + \n :foreach List in=[ :deserialize \$tmpIndex delimiter=\"\\n\" from=dsv \ + options=dsv.plain ] do={\r\ + \n# :set List (\$List->0);\r\ + \n :local ListName;\r\ + \n :local Delim [ \$FindDelim \$List ];\r\ + \n :if (\$Delim = 0) do={ :set List \"\"; };\r\ + \n :log warning (\$ScriptName . \" Parsing address-list index entry: \"\ + \_. \$List);\r\ + \n :if ([ :len \$List ] = 0) do={\r\ + \n :log error (\$ScriptName . \" Failed parsing address-list index en\ + try: \" . \$List);\r\ + \n } else={\r\ + \n :set ListName [ :pick \$List 0 \$Delim ];\r\ + \n }\r\ + \n :log warning (\$ScriptName . \" Parsing address-list index entry: \"\ + \_. \$ListName . \" - Ok.\");\r\ + \n :local ListUrl (\$ListsUrl . \$ListName);\r\ + \n :local CntAdd 0;\r\ + \n :local CntRenew 0;\r\ + \n :local CntRemove 0;\r\ + \n :local IPv4Addresses ({});\r\ + \n :local IPv6Addresses ({});\r\ + \n :local Failure false;\r\ + \n :local Data false;\r\ + \n :local TimeOut \$AddrListTimeOut;\r\ + \n\r\ + \n :for I from=1 to=5 do={\r\ + \n :if (\$Data = false) do={\r\ + \n :set Data [ :tolf [ \$FetchHugeFiles \$ScriptName \$ListUrl \$Ch\ + eckCerts ] ];\r\ + \n :if (\$Data = false && \$I < 5) do={\r\ + \n :log warning (\$ScriptName . \" Failed downloading for list '\ + \" . \$ListName . \"', \" . \$I . \". try from: \" . \$ListUrl);\r\ + \n :delay ((\$I * \$I) . \"s\");\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if (\$Data = false) do={\r\ + \n :set Data \"\";\r\ + \n :set Failure true;\r\ + \n :log warning (\$ScriptName . \" Failed downloading for list '\" . \ + \$ListName . \"' from: \" . \$ListUrl);\r\ + \n } else={\r\ + \n :log warning (\$ScriptName . \" Downloaded \" . [ \$HumanReadableN\ + um [ :len \$Data ] 1024 ] . \"B for list '\" . \$ListName . \"' from: \" .\ + \_\$ListUrl);\r\ + \n }\r\ + \n\r\ + \n :foreach Line in=[ :deserialize \$Data delimiter=\"\\n\" from=dsv op\ + tions=dsv.plain ] do={\r\ + \n :set Line (\$Line->0);\r\ + \n :local Address;\r\ + \n :if ([ :pick \$Line 0 1 ] = \"{\" && [ :pick \$Line ([ :len \$Line\ + \_] - 1) ] = \"}\") do={\r\ + \n :do {\r\ + \n :set Address [ :tostr ([ :deserialize from=json \$Line ]->\"ci\ + dr\") ];\r\ + \n } on-error={ }\r\ + \n } else={\r\ + \n :set Address ([ :pick \$Line 0 [ \$FindDelim \$Line ] ] . (\$L\ + ist->\"cidr\"));\r\ + \n }\r\ + \n :do {\r\ + \n :local Branch;\r\ + \n :if (\$Address ~ \"^[0-9]{1,3}\\\\.[0-9]{1,3}\\\\.[0-9]{1,3}\\\\\ + .[0-9]{1,3}(/[0-9]{1,2})\?\\\$\") do={\r\ + \n :local Net \$Address;\r\ + \n :local CIDR 32;\r\ + \n :local Slash [ :find \$Address \"/\" ];\r\ + \n :if ([ :typeof \$Slash ] = \"num\") do={\r\ + \n :set Net [ :toip [ :pick \$Address 0 \$Slash ] ]\r\ + \n :set CIDR [ :pick \$Address (\$Slash + 1) [ :len \$Address ]\ + \_];\r\ + \n :set Address [ :tostr (([ :toip \$Net ] & [ \$NetMask4 \$CID\ + R ]) . [ \$IfThenElse (\$CIDR < 32) (\"/\" . \$CIDR) ]) ];\r\ + \n }\r\ + \n :set Branch [ \$GetBranch \$Address ];\r\ + \n :set (\$IPv4Addresses->\$Branch->\$Address) \$TimeOut;\r\ + \n }\r\ + \n :if (\$Address ~ \"^[0-9a-zA-Z]*:[0-9a-zA-Z:\\\\.]+(/[0-9]{1,3})\ + \?\\\$\") do={\r\ + \n :local Net \$Address;\r\ + \n :local CIDR 128;\r\ + \n :local Slash [ :find \$Address \"/\" ];\r\ + \n :if ([ :typeof \$Slash ] = \"num\") do={\r\ + \n :set Net [ :toip6 [ :pick \$Address 0 \$Slash ] ]\r\ + \n :set CIDR [ :pick \$Address (\$Slash + 1) [ :len \$Address ]\ + \_];\r\ + \n }\r\ + \n :set Address (([ :toip6 \$Net ] & [ \$NetMask6 \$CIDR ]) . \"/\ + \" . \$CIDR);\r\ + \n :set Branch [ \$GetBranch \$Address ];\r\ + \n :set (\$IPv6Addresses->\$Branch->\$Address) \$TimeOut;\r\ + \n }\r\ + \n :if (\$Address ~ \"^[\\\\.a-zA-Z0-9-]+\\\\.[a-zA-Z]{2,}\\\$\") d\ + o={\r\ + \n :set Branch [ \$GetBranch \$Address ];\r\ + \n :set (\$IPv4Addresses->\$Branch->\$Address) \$TimeOut;\r\ + \n :set (\$IPv6Addresses->\$Branch->\$Address) \$TimeOut;\r\ + \n }\r\ + \n } on-error={ }\r\ + \n }\r\ + \n \r\ + \n\r\ + \n :foreach Entry in=[ /ip/firewall/address-list/find where \\\r\ + \n list=\$ListName comment=\$ListComment ] do={\r\ + \n :local Address [ /ip/firewall/address-list/get \$Entry address ];\ + \r\ + \n :local Branch [ \$GetBranch \$Address ];\r\ + \n :local TimeOut (\$IPv4Addresses->\$Branch->\$Address);\r\ + \n :if ([ :typeof \$TimeOut ] = \"time\") do={\r\ + \n :log warning (\$ScriptName . \" Renewing IPv4 address \" . \$Add\ + ress . \\\r\ + \n \" in list '\" . \$ListName . \"' with \" . \$TimeOut . \".\ + \");\r\ + \n /ip/firewall/address-list/set \$Entry timeout=\$TimeOut;\r\ + \n :set (\$IPv4Addresses->\$Branch->\$Address);\r\ + \n :set CntRenew (\$CntRenew + 1);\r\ + \n } else={\r\ + \n :if (\$Failure = false) do={\r\ + \n :log warning (\$ScriptName . \" Removing IPv4 address \" . \$A\ + ddress . \\\r\ + \n \" from list '\" . \$ListName . \".\");\r\ + \n /ip/firewall/address-list/remove \$Entry;\r\ + \n :set CntRemove (\$CntRemove + 1);\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :foreach Entry in=[ /ipv6/firewall/address-list/find where \\\r\ + \n list=\$ListName comment=\$ListComment ] do={\r\ + \n :local Address [ /ipv6/firewall/address-list/get \$Entry address ]\ + ;\r\ + \n :local Branch [ \$GetBranch \$Address ];\r\ + \n :local TimeOut (\$IPv6Addresses->\$Branch->\$Address);\r\ + \n :if ([ :typeof \$TimeOut ] = \"time\") do={\r\ + \n :log warning (\$ScriptName . \" Renewing IPv6 address \" . \$Add\ + ress . \\\r\ + \n \" in list '\" . \$ListName . \"' with \" . \$TimeOut . \".\ + \");\r\ + \n /ipv6/firewall/address-list/set \$Entry timeout=\$TimeOut;\r\ + \n :set (\$IPv6Addresses->\$Branch->\$Address);\r\ + \n :set CntRenew (\$CntRenew + 1);\r\ + \n } else={\r\ + \n :if (\$Failure = false) do={\r\ + \n :log warning (\$ScriptName . \" Removing IPv6 address \" . \$A\ + ddress . \\\r\ + \n \" from list '\" . \$ListName . \".\");\r\ + \n /ipv6/firewall/address-list/remove \$Entry;\r\ + \n :set CntRemove (\$CntRemove + 1);\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :foreach BranchName,Branch in=\$IPv4Addresses do={\r\ + \n :log warning (\$ScriptName . \" Handling branch: \" . \$BranchName\ + );\r\ + \n :foreach Address,Timeout in=\$Branch do={\r\ + \n :log warning (\$ScriptName . \" Adding IPv4 address \" . \$Addre\ + ss . \\\r\ + \n \" to list '\" . \$ListName . \"' with \" . \$Timeout . \".\ + \");\r\ + \n :onerror Err {\r\ + \n /ip/firewall/address-list/add list=\$ListName comment=\$ListCo\ + mment \\\r\ + \n address=\$Address timeout=\$Timeout;\r\ + \n :set CntAdd (\$CntAdd + 1);\r\ + \n } do={\r\ + \n :log warning (\$ScriptName . \" Failed to add IPv4 address \" \ + . \$Address . \\\r\ + \n \" to list '\" . \$ListName . \"': \" . \$Err);\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :foreach BranchName,Branch in=\$IPv6Addresses do={\r\ + \n :log warning (\$ScriptName . \" Handling branch: \" . \$BranchName\ + );\r\ + \n :foreach Address,Timeout in=\$Branch do={\r\ + \n :log warning (\$ScriptName . \" Adding IPv6 address \" . \$Addre\ + ss . \\\r\ + \n \" to list '\" . \$ListName . \"' with \" . \$Timeout . \".\ + \");\r\ + \n :onerror Err {\r\ + \n /ipv6/firewall/address-list/add list=\$ListName comment=\$List\ + Comment \\\r\ + \n address=\$Address timeout=\$Timeout;\r\ + \n :set CntAdd (\$CntAdd + 1);\r\ + \n } do={\r\ + \n :log warning (\$ScriptName . \" Failed to add IPv6 address \" \ + . \$Address . \\\r\ + \n \" to list '\" . \$ListName . \"': \" . \$Err);\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :log info (\$ScriptName . \" list: \" . \$ListName . \\\r\ + \n \" (\" . [ \$HumanReadableNum (\$CntAdd + \$CntRenew) 1000 ] . \ + \")\" . \\\r\ + \n \" -- added: \" . [ \$HumanReadableNum \$CntAdd 1000 ] . \\\r\ + \n \" - renewed: \" . [ \$HumanReadableNum \$CntRenew 1000 ] . \\\r\ + \n \" - removed: \" . [ \$HumanReadableNum \$CntRemove 1000 ]);\r\ + \n }\r\ + \n} do={\r\ + \n :global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err;\r\ + \n}\r\ + \n" +add dont-require-permissions=no name=awg-env owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + container/envs/add list=awg-proxy-env key=AWG_LISTEN value=\":55055\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_REMOTE value=\"109.107.17\ + 7.79:55055\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_JC value=\"9\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_JMIN value=\"105\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_JMAX value=\"920\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_S1 value=\"90\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_S2 value=\"136\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_S3 value=\"45\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_S4 value=\"27\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_H1 value=\"472437748-4724\ + 38637\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_H2 value=\"707808381-7078\ + 09250\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_H3 value=\"1609078552-160\ + 9079495\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_H4 value=\"1012329484-101\ + 2329741\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I1 value=\"\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I2 value=\"\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I3 value=\"\ + \"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I4 value=\"\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_I5 value=\"\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_SERVER_PUB value=\"o5IgCl\ + 1/G6xbl6j7sPaP4ZNYREeYOmqZjKbm+v0b9jU=\"\ + \n/container/envs/add list=awg-proxy-env key=AWG_CLIENT_PUB value=[/interf\ + ace/wireguard/get [find name=wg-awg-proxy] public-key]" +add dont-require-permissions=no name=awg-container-add owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + container/add file=sata1/tmp/awg-proxy-amd64.tar.gz interface=veth-awg-pro\ + xy envlist=awg-proxy-env \\\ + \n hostname=awg-proxy root-dir=sata1/awg-proxy logging=yes shm-size=4M \ + start-on-boot=yes\ + \n/container/start [find where tag~\"awg-proxy\"]" +/user group +add name=backup policy="telnet,ssh,ftp,read,write,policy,test,sniff,sensitive,\ + !local,!reboot,!winbox,!password,!web,!api,!romon,!rest-api" +/app +set cinny firewall-redirects=8094:80:tcp:web +set goaway container-command-lines=goaway:none:docker.io/pommee/goaway:latest +set home-assistant container-command-lines=\ + home-assistant:none:lscr.io/linuxserver/homeassistant +set n8n firewall-redirects=5678:5678:tcp:web +set nextcloud container-command-lines="db:none:docker.io/postgres:17,redis:non\ + e:docker.io/valkey/valkey:/bin/sh -c 'valkey-server --port 6379 --appendon\ + ly yes --requirepass \$VALKEY_PASSWORD',server:none:docker.io/nextcloud:ap\ + ache" +set pihole environment="pihole:FTLCONF_dns_listeningMode=all,pihole:FTLCONF_we\ + bserver_api_password=password" +set redlib firewall-redirects=8087:8080:tcp:web +set solr container-command-lines=solr:none:docker.io/solr:latest +set uptime-kuma container-command-lines=\ + uptime-kuma:none:docker.io/louislam/uptime-kuma:1 +/app settings +set auto-update=yes disk=*7 lan-bridge=*3 registry-mirrors="https://reg.vados.\ + ru:5000/:https://reg.vados.ru/,https://registry-1.docker.io" +/caps-man aaa +set called-format=ssid +/caps-man access-list +add action=reject allow-signal-out-of-range=10s disabled=no interface=all \ + signal-range=-120..-80 ssid-regexp="" +add action=accept allow-signal-out-of-range=10s disabled=no interface=all \ + signal-range=-79..120 ssid-regexp="" +/caps-man configuration +add channel.band=2ghz-b/g/n country=no_country_set datapath=xs-DataPath mode=\ + ap name=xs-2G rx-chains=0,1,2,3 security=xs-Sec ssid=vados tx-chains=\ + 0,1,2,3 +add channel.band=5ghz-a/n/ac country=no_country_set datapath=xs-DataPath \ + installation=any mode=ap name=xs-5G rx-chains=0,1,2,3 security=xs-Sec \ + ssid=vados5g tx-chains=0,1,2,3 +add channel.band=2ghz-b/g/n .control-channel-width=20mhz .extension-channel=\ + XX .frequency=2422,2442,2462 .reselect-interval=20m country=belarus \ + datapath=DataPath datapath.client-to-client-forwarding=yes \ + .local-forwarding=yes hw-protection-mode=rts-cts installation=any name=2G \ + rx-chains=0,1,2,3 security=WPA2 ssid=vados tx-chains=0,1,2,3 +add channel.band=5ghz-a/n/ac .control-channel-width=20mhz .extension-channel=\ + XXXX .frequency=5180,5200,5220,5240,5260 .reselect-interval=20m \ + .skip-dfs-channels=yes country=belarus datapath=DataPath \ + datapath.client-to-client-forwarding=yes .local-forwarding=yes \ + hw-protection-mode=rts-cts installation=any name=5G security=WPA2 ssid=\ + vados5g +/caps-man datapath +add bridge=*3 client-to-client-forwarding=yes local-forwarding=yes name=\ + xs-DataPath +add bridge=*3 client-to-client-forwarding=yes local-forwarding=yes name=\ + DataPath +/caps-man manager +set ca-certificate=CAPsMAN-CA-51B25E507D07 certificate=CAPsMAN-51B25E507D07 \ + enabled=yes require-peer-certificate=yes +/caps-man manager interface +set [ find default=yes ] forbid=yes +add disabled=no interface=*DF +/caps-man provisioning +add action=create-dynamic-enabled hw-supported-modes=g,gn,b \ + master-configuration=2G name-format=prefix-identity name-prefix=2G +add action=create-dynamic-enabled hw-supported-modes=an,ac,a \ + master-configuration=5G name-format=prefix-identity name-prefix=5G +add action=create-dynamic-enabled disabled=yes hw-supported-modes=ac,an,a \ + master-configuration=2G name-format=prefix-identity name-prefix=5ghz-ac +add action=create-dynamic-enabled disabled=yes hw-supported-modes=an \ + master-configuration=5G name-format=prefix-identity name-prefix=5ghz-an +/certificate settings +set crl-store=system +/container config +set layer-dir=/sata1 memory-high=200.0MiB memory-max=4096.0MiB registry-url=\ + https://registry-1.docker.io tmpdir=/sata1/pull username=amsternl +/ip smb +set enabled=no +/ipv6 settings +# ipv6 neighbor configuration has changed, please restart the device in order to apply the new settings +set allow-fast-path=no disable-ipv6=yes disable-link-local-address=yes +/interface detect-internet +set detect-interface-list=WAN internet-interface-list=Internet \ + lan-interface-list=LAN wan-interface-list=WAN +/interface wifi cap +set discovery-interfaces=*DF +/interface wifi capsman +set package-path=/tmpfs/capsman-packages upgrade-policy=none +/interface wireguard peers +add allowed-address=0.0.0.0/0 endpoint-address=172.19.77.106 endpoint-port=\ + 51820 interface=wg-AWG-VDSina name=peer19 persistent-keepalive=10s \ + public-key="o5IgCl1/G6xbl6j7sPaP4ZNYREeYOmqZjKbm+v0b9jU=" +/ip address +add address=10.30.30.10/24 interface=ether2 network=10.30.30.0 +/ip dhcp-client +add disabled=yes interface=ether2 name=client1 +/ip dhcp-server +add add-arp=yes address-pool=dhcp always-broadcast=yes bootp-support=none \ + dhcp-option-set=domain-search-set disabled=yes interface=*3 lease-script=\ + lease-script lease-time=10m name=nucDHCP server-address=10.30.30.10 +/ip dns +set allow-remote-requests=yes doh-max-concurrent-queries=100 \ + doh-max-server-connections=20 servers=109.107.177.79,8.8.4.4 \ + verify-doh-cert=yes +/ip firewall nat +add action=masquerade chain=srcnat src-address=172.19.77.104/30 +/ip firewall service-port +set ftp disabled=yes +set tftp disabled=yes +set sip disabled=yes +set pptp disabled=yes +set rtsp disabled=no +/ip route +add check-gateway=none disabled=no distance=1 dst-address=0.0.0.0/0 gateway=\ + 10.30.30.81 routing-table=main scope=30 target-scope=10 +/ip service +set ftp disabled=yes +set telnet disabled=yes +set www disabled=yes +set reverse-proxy disabled=yes +set ssh port=2222 +set www-ssl port=4443 +set api disabled=yes +set api-ssl disabled=yes +/ip service webserver +set acme-plain=no crl-plain=no graphs-plain=no graphs-secure=no index-plain=\ + no index-secure=no rest-plain=no rest-secure=no scep-plain=no \ + webfig-plain=no webfig-secure=no +/ip ssh +set forwarding-enabled=both password-authentication=no \ + publickey-authentication-options=verify-required strong-crypto=yes +/ip upnp +set enabled=yes +/ip upnp interfaces +add interface=*3 type=external +add interface=*B5 type=internal +/ipv6 firewall address-list +add address=2400:cb00::/32 list=CLOUDFLARE-IP-Address +add address=2606:4700::/32 list=CLOUDFLARE-IP-Address +add address=2803:f800::/32 list=CLOUDFLARE-IP-Address +add address=2405:b500::/32 list=CLOUDFLARE-IP-Address +add address=2405:8100::/32 list=CLOUDFLARE-IP-Address +add address=2a06:98c0::/29 list=CLOUDFLARE-IP-Address +add address=2c0f:f248::/32 list=CLOUDFLARE-IP-Address +/system clock +set time-zone-autodetect=no time-zone-name=Europe/Moscow +/system identity +set name=MikroNUC +/system note +set show-at-login=no +/system ntp client +set enabled=yes +/system ntp server +set enabled=yes manycast=yes multicast=yes +/system ntp client servers +add address=10.30.30.81 +add address=0.ru.pool.ntp.org +add address=1.ru.pool.ntp.org +add address=2.ru.pool.ntp.org +add address=3.ru.pool.ntp.org +/system scheduler +add comment="Every Day Set Global Environment Variables" disabled=yes name=\ + AM-SetGlobalEnv on-event=\ + "/system/script { run AM-GlobalConfig; run AM-GlobalFunc; }" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment="Every Day Check For Update Scripts" disabled=yes interval=1d \ + name=AM-ScriptInstallUpdate on-event=\ + ":global ScriptInstallUpdate; \$ScriptInstallUpdate;" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment="Every Day Check For Fake Bridges" disabled=yes interval=1d name=\ + mod/AM-IPbridge on-event="/system/script/run mod/AM-IPbridge;" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment="Telegram Chat Bot" disabled=yes interval=30s name=mod/AM-TG-Bot \ + on-event="/system/script/run mod/AM-TG-Bot;" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-time=startup +add comment=\ + "Every 7 Day Check For Update and Backup send notification to EMail." \ + disabled=yes interval=1w name=AM-Backup-UpdateEmail on-event=\ + "/system/script { run AM-Backup-UpdateEmail; }" policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-date=2026-06-25 start-time=03:41:27 +/tool e-mail +set certificate-verification=no from=MikroNUC port=587 \ + server=smtp.mail.me.com tls=starttls user=vadossio@icloud.com +/tool netwatch +add disabled=yes down-script=\ + ":local msg \"Host MikroYellow is DOWN\";\r\ + \n\$TGSendMessage \$msg;" host=10.30.30.86 http-codes="" interval=10s \ + test-script="" type=simple up-script=\ + ":global TGSendMessage;\r\ + \n\$TGSendMessage \"Host MikroYellow is UP\";" +/user aaa +set accounting=no diff --git a/MikroNUC/history/20260628/20260628060001-Diff-Config.rsc b/MikroNUC/history/20260628/20260628060001-Diff-Config.rsc new file mode 100644 index 0000000..6ab4dd3 --- /dev/null +++ b/MikroNUC/history/20260628/20260628060001-Diff-Config.rsc @@ -0,0 +1,143 @@ +22a23,24 +> /interface bridge +> add name=Bridge-LAN +26,28c28,29 +< add address=172.19.77.106/30 container-mac-address=5E:0C:7C:E8:98:12 dhcp=no \ +< gateway=172.19.77.105 gateway6="" mac-address=5E:0C:7C:E8:98:11 name=\ +< veth-AWG-VDSina +--- +> add address=172.18.9.106/30 container-mac-address=00:00:00:00:00:00 dhcp=no \ +> gateway=172.18.9.105 gateway6="" name=veth-awg-proxy +30c31 +< add listen-port=13822 mtu=1420 name=wg-AWG-VDSina +--- +> add disabled=yes listen-port=13241 mtu=1420 name=wg-awg-proxy +59a61,62 +> /routing table +> add disabled=no fib name=To-AWG +4633a4637,4676 +> add comment=awg-proxy dont-require-permissions=no name=awg-proxy-uninstall \ +> owner=vados policy=\ +> ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="\ +> \n :put \"Uninstallimg AmneziaWG...\"\ +> \n :log info \"Uninstallimg AmneziaWG...\"\ +> \n :local prevDNS \"\"\ +> \n :do { :set prevDNS [/container/envs/get [find where list=\"awg-proxy-e\ +> nv\" key=\"AWG_PREV_DNS\"] value] } on-error={}\ +> \n :local prevTmpdir \"\"\ +> \n :do { :set prevTmpdir [/container/envs/get [find where list=\"awg-prox\ +> y-env\" key=\"AWG_PREV_TMPDIR\"] value] } on-error={}\ +> \n /ip/route/remove [find where comment=awg-proxy-tunnel]\ +> \n /ip/route/remove [find where comment=awg-proxy-dns]\ +> \n /container/stop [find where interface=veth-awg-proxy]\ +> \n :delay 7s\ +> \n /container/remove [find where interface=veth-awg-proxy]\ +> \n /container/envs/remove [find where list=\"awg-proxy-env\"]\ +> \n :if (\$prevDNS != \"\") do={\ +> \n :do { /ip/dns/set servers=\"\$prevDNS\" } on-error={}\ +> \n :put (\"DNS restored to: \$prevDNS\")\ +> \n }\ +> \n /container/config set tmpdir=\$prevTmpdir\ +> \n :put (\"Container tmpdir restored to: \$prevTmpdir\")\ +> \n /ip/firewall/nat/remove [find where out-interface=\"wg-awg-proxy\"]\ +> \n /ip/firewall/nat/remove [find where src-address=\"172.18.9.104/30\"]\ +> \n /ip/address/remove [find where address=\"172.18.9.105/30\"]\ +> \n /interface/veth/remove [find where name=\"veth-awg-proxy\"]\ +> \n /interface/wireguard/peers/remove [find where interface=\"wg-awg-proxy\ +> \"]\ +> \n /ip/address/remove [find where interface=\"wg-awg-proxy\"]\ +> \n /interface/wireguard/remove [find where name=\"wg-awg-proxy\"]\ +> \n :do { /file/remove [find where name~\"awg-proxy.+tar\"] } on-error={}\ +> \n :do { /file/remove [find where name=\"sata1/awg-proxy\"] } on-error={}\ +> \n :do { /file/remove [find where name=\"sata1/ru_ranges_timeout.rsc\"] }\ +> \_on-error={}\ +> \n :do { /file/remove [find where name~\"sata1/pull\"] } on-error={}\ +> \n :put \"Uninstall AmneziaWG Proxy complete!\"\ +> \n :log info \"Uninstall AmneziaWG Proxy complete!\"\ +> \n /system/script/remove [find where name=awg-proxy-uninstall]\ +> \n" +4691c4734 +< add disabled=no interface=*DF +--- +> add disabled=no interface=Bridge-LAN +4706,4707c4749,4776 +< /ip smb +< set enabled=no +--- +> /container envs +> add key=AWG_CLIENT_PUB list=awg-proxy-env value=\ +> "NAonhRSuFIrFpNKTs5X32ItcvFuO1kiJUigQZb95YlE=" +> add key=AWG_H1 list=awg-proxy-env value=472437748-472438637 +> add key=AWG_H2 list=awg-proxy-env value=707808381-707809250 +> add key=AWG_H3 list=awg-proxy-env value=1609078552-1609079495 +> add key=AWG_H4 list=awg-proxy-env value=1012329484-1012329741 +> add key=AWG_I1 list=awg-proxy-env value=\ +> "" +> add key=AWG_I2 list=awg-proxy-env value="" +> add key=AWG_I3 list=awg-proxy-env value="" +> add key=AWG_I4 list=awg-proxy-env value="" +> add key=AWG_I5 list=awg-proxy-env value="" +> add key=AWG_JC list=awg-proxy-env value=9 +> add key=AWG_JMAX list=awg-proxy-env value=920 +> add key=AWG_JMIN list=awg-proxy-env value=105 +> add key=AWG_LISTEN list=awg-proxy-env value=:51820 +> add key=AWG_PREV_DNS list=awg-proxy-env value=10.30.30.33 +> add key=AWG_PREV_TMPDIR list=awg-proxy-env value="" +> add key=AWG_REMOTE list=awg-proxy-env value=109.107.177.79:55055 +> add key=AWG_S1 list=awg-proxy-env value=90 +> add key=AWG_S2 list=awg-proxy-env value=136 +> add key=AWG_S3 list=awg-proxy-env value=45 +> add key=AWG_S4 list=awg-proxy-env value=27 +> add key=AWG_SERVER_PUB list=awg-proxy-env value=\ +> "o5IgCl1/G6xbl6j7sPaP4ZNYREeYOmqZjKbm+v0b9jU=" +> /interface bridge port +> add bridge=Bridge-LAN interface=ether2 +4709,4710c4778,4779 +< # ipv6 neighbor configuration has changed, please restart the device in order to apply the new settings +< set allow-fast-path=no disable-ipv6=yes disable-link-local-address=yes +--- +> set allow-fast-path=no disable-ipv6=yes disable-link-local-address=yes \ +> forward=no +4713a4783,4787 +> /interface list member +> add interface=lo list=LAN +> add interface=*D9 list=Internet +> add interface=ether2 list=WAN +> add interface=*3 list=LAN +4715c4789 +< set discovery-interfaces=*DF +--- +> set discovery-interfaces=Bridge-LAN +4719,4720c4793,4794 +< add allowed-address=0.0.0.0/0 endpoint-address=172.19.77.106 endpoint-port=\ +< 51820 interface=wg-AWG-VDSina name=peer19 persistent-keepalive=10s \ +--- +> add allowed-address=0.0.0.0/0 endpoint-address=172.18.9.106 endpoint-port=\ +> 51820 interface=wg-awg-proxy name=peer14 persistent-keepalive=10s \ +4724,4725c4798,4799 +< /ip dhcp-client +< add disabled=yes interface=ether2 name=client1 +--- +> add address=172.18.9.105/30 interface=veth-awg-proxy network=172.18.9.104 +> add address=10.9.0.3 interface=wg-awg-proxy network=10.9.0.3 +4732,4733c4806,4808 +< doh-max-server-connections=20 servers=109.107.177.79,8.8.4.4 \ +< verify-doh-cert=yes +--- +> doh-max-server-connections=20 servers=10.9.0.1 verify-doh-cert=yes +> /ip dns static +> add address=109.107.177.79 name=vds.vados.ru type=A +4735c4810 +< add action=masquerade chain=srcnat src-address=172.19.77.104/30 +--- +> add action=masquerade chain=srcnat src-address=172.18.9.104/30 +4744a4820,4821 +> add comment=awg-proxy-dns distance=1 dst-address=10.9.0.1/32 gateway=\ +> wg-awg-proxy +4747d4823 +< set telnet disabled=yes +4749d4824 +< set reverse-proxy disabled=yes +4775a4851,4852 +> /system gps +> set set-system-time=yes -- 2.52.0