From 77ca2fb0725f74eb006c3a0825f86d6ad064e48b Mon Sep 17 00:00:00 2001 From: vados-dev Date: Mon, 2 Mar 2026 22:00:20 +0300 Subject: [PATCH] Backup for MikroYellow sucessfully creatd at Mar 02 22:00:20. --- .../20260302/20260302220010-Binary.backup | Bin 0 -> 181082 bytes .../20260302/20260302220010-Config.rsc | 3946 +++++++++++++++++ .../20260302/20260302220010-Diff-Config.rsc | 4 + 3 files changed, 3950 insertions(+) create mode 100644 MikroYellow/20260302/20260302220010-Binary.backup create mode 100644 MikroYellow/20260302/20260302220010-Config.rsc create mode 100644 MikroYellow/history/20260302/20260302220010-Diff-Config.rsc diff --git a/MikroYellow/20260302/20260302220010-Binary.backup b/MikroYellow/20260302/20260302220010-Binary.backup new file mode 100644 index 0000000000000000000000000000000000000000..22315acc148b70bb674aeaf3b9423a4871e8bf31 GIT binary patch literal 181082 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

aj}F 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 b{E7c*Hv5(=DfLm)Kd=E}tsN_eNf+=xTAe_j literal 0 HcmV?d00001 diff --git a/MikroYellow/20260302/20260302220010-Config.rsc b/MikroYellow/20260302/20260302220010-Config.rsc new file mode 100644 index 0000000..299e214 --- /dev/null +++ b/MikroYellow/20260302/20260302220010-Config.rsc @@ -0,0 +1,3946 @@ +# 2026-03-02 22:00:10 by RouterOS 7.21.3 +# software id = A8L5-07YF +# +# model = RB952Ui-5ac2nD +# serial number = D3D50CEDCD38 +/interface bridge +add name=Bridge-LAN port-cost-mode=short +/interface wireless +# managed by CAPsMAN +# channel: 2442/20-Ce/gn(18dBm), SSID: vados, local forwarding +set [ find default-name=wlan1 ] disabled=no ssid=MikroTik +# managed by CAPsMAN +# channel: 5200/20-eCee/ac/P(21dBm), SSID: vados5g, local forwarding +set [ find default-name=wlan2 ] disabled=no ssid=MikroTik +/disk +add slot=tmpfs tmpfs-max-size=22369621 type=tmpfs +/interface list +add name=WAN +add name=LAN +/interface wireless security-profiles +set [ find default=yes ] supplicant-identity=MikroTik +/user group +add name=backup policy="telnet,ssh,ftp,read,write,policy,test,sniff,sensitive,\ + !local,!reboot,!winbox,!password,!web,!api,!romon,!rest-api" +/caps-man manager interface +set [ find default=yes ] forbid=yes +/certificate settings +set builtin-trust-store=fetch crl-store=system +/interface bridge port +add bridge=Bridge-LAN interface=ether1 internal-path-cost=10 path-cost=10 +add bridge=Bridge-LAN interface=ether2 internal-path-cost=10 path-cost=10 +add bridge=Bridge-LAN interface=ether3 internal-path-cost=10 path-cost=10 +add bridge=Bridge-LAN interface=ether4 internal-path-cost=10 path-cost=10 +add bridge=Bridge-LAN interface=ether5 internal-path-cost=10 path-cost=10 +add bridge=Bridge-LAN interface=wlan2 internal-path-cost=10 path-cost=10 +add bridge=Bridge-LAN interface=wlan1 internal-path-cost=10 path-cost=10 +/ip firewall connection tracking +set udp-timeout=10s +/ipv6 settings +set disable-ipv6=yes disable-link-local-address=yes +/interface list member +add interface=ether1 list=WAN +add interface=ether2 list=LAN +add interface=ether3 list=LAN +add interface=ether4 list=LAN +add interface=ether5 list=LAN +add interface=wlan2 list=LAN +add interface=wlan1 list=LAN +/interface ovpn-server server +add mac-address=FE:9B:F4:E6:40:D9 name=ovpn-server1 +/interface wifi capsman +set package-path="" require-peer-certificate=no +/interface wireless cap +# +set bridge=Bridge-LAN caps-man-certificate-common-names=CAPsMAN-A2A7DF3D5EE6 \ + certificate=CAP-488F5A8E1ABC discovery-interfaces=Bridge-LAN enabled=yes \ + interfaces=wlan1,wlan2 lock-to-caps-man=yes +/ip address +add address=10.30.30.86/24 interface=ether2 network=10.30.30.0 +/ip dns +set servers=10.30.30.81 +/ip hotspot profile +set [ find default=yes ] html-directory=hotspot +/ip ipsec profile +set [ find default=yes ] dpd-interval=2m dpd-maximum-failures=5 +/ip service +set ftp disabled=yes +set telnet disabled=yes +set www disabled=yes +set ssh port=2222 +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 publickey-authentication-options=verify-required \ + strong-crypto=yes +/ipv6 nd +set [ find default=yes ] advertise-dns=yes +/system clock +set time-zone-autodetect=no time-zone-name=Europe/Moscow +/system clock manual +set time-zone=+03:00 +/system identity +set name=MikroYellow +/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=10.30.30.10 +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 interval=1w name=BackupEveryWeek on-event=BackupAndUpdate policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-date=2025-07-07 start-time=04:39:32 +add comment="CloudFlare Certificate Update" interval=1w name=\ + Certificate_CloudFlare_Update on-event=Certificate_CloudFlare policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + start-date=2025-11-28 start-time=05:45:40 +add comment="Setup Global Environment for scripts" 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 +/system script +add dont-require-permissions=no name=BackupAndUpdate owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + \_Script name: BackupAndUpdate\r\ + \n#\r\ + \n# SCRIPT INFORMATION\r\ + \n#\r\ + \n# Script: Mikrotik RouterOS automatic backup & update\r\ + \n# Version: 25.04.15\r\ + \n# Created: 07/08/2018\r\ + \n# Updated: 15/04/2025\r\ + \n# Author: Alexander Tebiev\r\ + \n# Website: https://github.com/beeyev\r\ + \n# You can contact me by e-mail at tebiev@mail.com\r\ + \n#\r\ + \n# IMPORTANT!\r\ + \n# Minimum supported RouterOS version is v6.43.7\r\ + \n#\r\ + \n# --- MODIFY THIS SECTION AS NEEDED ---\r\ + \n# Notification e-mail\r\ + \n# (Make sure you have configured Email settings in Tools -> Email)\r\ + \n:local emailAddress \"vados@vados.ru\";\r\ + \n\r\ + \n# Script mode, possible values: backup, osupdate, osnotify.\r\ + \n# backup - Only backup will be performed. (default value, if none p\ + rovided)\r\ + \n#\r\ + \n# osupdate - Installs new RouterOS if available and creates backups b\ + efore/after update (ignores `forceBackup`)\r\ + \n# Sends email only when an update is found.\r\ + \n# Set `forceBackup` to true to always create backups, even\ + \_without updates\r\ + \n#\r\ + \n# osnotify - Sends email only if a new RouterOS update is found (no b\ + ackups)\r\ + \n# Set `forceBackup` to always create backups on every run\ + \r\ + \n:local scriptMode \"osupdate\"\r\ + \n\r\ + \n# Additional parameter if you set `scriptMode` to `osupdate` or `osnotif\ + y`\r\ + \n# Set `true` if you want the script to perform backup every time its fir\ + ed, whatever script mode is set.\r\ + \n:local forceBackup true\r\ + \n\r\ + \n# Backup encryption password, no encryption if no password.\r\ + \n:local backupPassword \"Peskar55\"\r\ + \n\r\ + \n# If true, passwords will be included in exported config.\r\ + \n:local sensitiveDataInConfig true\r\ + \n\r\ + \n## Update channel. Possible values: stable, long-term, testing, developm\ + ent\r\ + \n:local updateChannel \"stable\"\r\ + \n\r\ + \n# Installs patch updates only (scriptMode = \"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\r\ + \n# Include public IP info in email if set to true\r\ + \n:local detectPublicIpAddress true\r\ + \n\r\ + \n## Allow anonymous statistics collection. (script mode and generic non-s\ + ensitive device info)\r\ + \n:local anonStats false\r\ + \n\r\ + \n# !!! DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU\E2\80\99RE D\ + OING !!!\r\ + \n\r\ + \n:local scriptVersion \"25.04.15\"\r\ + \n\r\ + \n# default and fallback public IP detection services\r\ + \n:local ipAddressDetectServiceDefault \"https://ipv4.mikrotik.ovh/\"\r\ + \n:local ipAddressDetectServiceFallback \"https://api.ipify.org/\"\r\ + \n\r\ + \n#Script messages prefix\r\ + \n:local SMP \"Bkp&Upd:\"\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: `\$scriptMode`, Update channel: `\$update\ + Channel`, Force backup: `\$forceBackup`, Install only patch updates: `\$in\ + stallOnlyPatchUpdates`\"\r\ + \n\r\ + \n## vv FUNCTIONS vv ##\r\ + \n\r\ + \n# Returns currently running RouterOS version\r\ + \n# :put [\$FuncGetRunningOsVersion] # Output: 6.48.1\r\ + \n:local FuncGetRunningOsVersion do={\r\ + \n :local runningOsAndChannel [/system resource get version]\r\ + \n\r\ + \n :local spacePos [:find \$runningOsAndChannel \" \"]\r\ + \n :if ([:len \$spacePos] = 0) do={\r\ + \n :log error \"Bkp&Upd: Could not extract installed OS version string:\ + \_`\$runningOsAndChannel`.\"\r\ + \n :error \"Bkp&Upd: error, check logs\"\r\ + \n }\r\ + \n\r\ + \n :local versionOnly [:pick \$runningOsAndChannel 0 \$spacePos]\r\ + \n\r\ + \n :return \$versionOnly\r\ + \n}\r\ + \n\r\ + \n# Returns currently running RouterOS channel\r\ + \n# :put [\$FuncGetRunningOsChannel] # Output: stable\r\ + \n:local FuncGetRunningOsChannel do={\r\ + \n :local runningOsAndChannel [/system resource get version]\r\ + \n\r\ + \n :local open [:find \$runningOsAndChannel \"(\"]\r\ + \n :if ([:len \$open] = 0) do={\r\ + \n :log error \"Bkp&Upd: Could not extract installed OS channel from ve\ + rsion string: `\$runningOsAndChannel`.\"\r\ + \n :error \"Bkp&Upd: error, check logs\"\r\ + \n }\r\ + \n\r\ + \n :local rest [:pick \$runningOsAndChannel (\$open+1) [:len \$runningOsA\ + ndChannel]]\r\ + \n :local close [:find \$rest \")\"]\r\ + \n :local channel [:pick \$rest 0 \$close]\r\ + \n\r\ + \n :return \$channel\r\ + \n}\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\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\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\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\ + \n :return true\r\ + \n }\r\ + \n :return false\r\ + \n}\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 \"daily-backup\"]\r\ + \n:local FuncCreateBackups do={\r\ + \n :local backupName \$1\r\ + \n :local backupPassword \$2\r\ + \n :local sensitiveDataInConfig \$3\r\ + \n\r\ + \n #Script messages prefix\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 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 :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 \$backupPassword] = 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=\$backupPassword name=\$backupName\r\ + \n }\r\ + \n\r\ + \n :log info (\"\$SMP system backup created: `\$backupFileSys`\")\r\ + \n\r\ + \n ## Export config file\r\ + \n :if (\$sensitiveDataInConfig = 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\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\r\ + \n :delay 20s\r\ + \n\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\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\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` finished. Creat\ + ed backups, system: `\$backupFileSys`, config: `\$backupFileConfig`\")\r\ + \n\r\ + \n :return \$backupNames\r\ + \n}\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\r\ + \n :local emailTo \$1\r\ + \n :local emailSubject \$2\r\ + \n :local emailBody \$3\r\ + \n :local emailAttachments \$4\r\ + \n\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\r\ + \n :log info \"\$SMP Attempting to send email to `\$emailTo`\"\r\ + \n\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\r\ + \n :log info \"\$SMP Waiting for previous email to finish (status: \$[/\ + tool e-mail get last-status])...\"\r\ + \n :delay 1s\r\ + \n :set waitCounterPre (\$waitCounterPre + 1)\r\ + \n }\r\ + \n\r\ + \n # Send the email\r\ + \n :do {\r\ + \n /tool e-mail send to=\$emailTo subject=\$emailSubject body=\$emailBo\ + dy 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\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\r\ + \n # exit loop\r\ + \n :set waitCounter \$waitTimeout\r\ + \n } else={\r\ + \n :delay 1s\r\ + \n :set waitCounter (\$waitCounter + 1)\r\ + \n }\r\ + \n }\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 `\$emailTo`\"\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\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\r\ + \n:global buGlobalVarScriptStep\r\ + \n:local scriptStep \$buGlobalVarScriptStep\r\ + \n:do {/system script environment remove buGlobalVarScriptStep} on-error={\ + }\r\ + \n:if ([:len \$scriptStep] = 0) do={\r\ + \n :set scriptStep 1\r\ + \n}\r\ + \n## ^^ FUNCTIONS ^^ ##\r\ + \n\r\ + \n\r\ + \n#\r\ + \n# Initial validation\r\ + \n#\r\ + \n\r\ + \n## Check email settings\r\ + \n:if ([:len \$emailAddress] < 3) do={\r\ + \n :log error (\"\$SMP Parameter `\\\$emailAddress` is not set, or contai\ + ns invalid value. Script stopped.\")\r\ + \n :error \$exitErrorMessage\r\ + \n}\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\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\r\ + \n# Script mode validation\r\ + \n:if (\$scriptMode != \"backup\" and \$scriptMode != \"osupdate\" and \$s\ + criptMode != \"osnotify\") do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$scriptMode` is not set, or c\ + ontains invalid value: `\$scriptMode`. Script stopped.\")\r\ + \n :error \$exitErrorMessage\r\ + \n}\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\r\ + \n# Verify if script is set to install patch updates and if the update cha\ + nnel is valid\r\ + \n:if (\$scriptMode = \"osupdate\" and \$installOnlyPatchUpdates = true) d\ + o={\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 :local susRunningOsChannel [\$FuncGetRunningOsChannel]\r\ + \n\r\ + \n :if (\$susRunningOsChannel != \"stable\" and \$susRunningOsChannel != \ + \"long-term\") 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: `\$susRunningOsChannel`. Script stopped\")\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n#\r\ + \n# Get current date and time\r\ + \n#\r\ + \n:local rawTime [/system clock get time]\r\ + \n:local rawDate [/system clock get date]\r\ + \n\r\ + \n# Current time in specific format `hh-mm-ss`\r\ + \n:local currentTime ([:pick \$rawTime 0 2] . \"-\" . [:pick \$rawTime 3 5\ + ] . \"-\" . [:pick \$rawTime 6 8])\r\ + \n\r\ + \n# Current date `YYYY-MM-DD` or `YYYY-Mon-DD`\r\ + \n:local currentDate \"undefined\"\r\ + \n\r\ + \n# Check if the date is in the old format\r\ + \n:if ([:len [:tonum [:pick \$rawDate 0 1]]] = 0) do={\r\ + \n # Convert old format `nov/11/2023` > `2023-nov-11`\r\ + \n :set currentDate ([:pick \$rawDate 7 11] . \"-\" . [:pick \$rawDate 0 \ + 3] . \"-\" . [:pick \$rawDate 4 6])\r\ + \n} else={\r\ + \n # Use new format as is `YYYY-MM-DD`\r\ + \n :set currentDate \$rawDate\r\ + \n}\r\ + \n\r\ + \n:local currentDateTime (\$currentDate . \"-\" . \$currentTime)\r\ + \n\r\ + \n:local deviceBoardName [/system resource get board-name]\r\ + \n\r\ + \n## Check if it's a cloud hosted router\r\ + \n:local isCloudHostedRouter false;\r\ + \n:if ([:pick \$deviceBoardName 0 3] = \"CHR\" or [:pick \$deviceBoardName\ + \_0 3] = \"x86\") do={\r\ + \n :set isCloudHostedRouter true;\r\ + \n};\r\ + \n\r\ + \n:local deviceIdentityName [/system identity get name];\r\ + \n:local deviceIdentityNameShort [:pick \$deviceIdentityName 0 18]\r\ + \n\r\ + \n:local deviceRbModel \"CloudHostedRouter\";\r\ + \n:local deviceRbSerialNumber \"--\"\r\ + \n:local deviceRbCurrentFw \"--\"\r\ + \n:local deviceRbUpgradeFw \"--\"\r\ + \n\r\ + \n:if (\$isCloudHostedRouter = false) do={\r\ + \n :set deviceRbModel [/system routerboard get model]\r\ + \n :set deviceRbSerialNumber [/system routerboard get serial-number]\r\ + \n :set deviceRbCurrentFw [/system routerboard get current-firmware]\r\ + \n :set deviceRbUpgradeFw [/system routerboard get upgrade-firmware]\r\ + \n};\r\ + \n\r\ + \n:local runningOsChannel [\$FuncGetRunningOsChannel]\r\ + \n:local runningOsVersion [\$FuncGetRunningOsVersion]\r\ + \n:local deviceOsVerAndChannelRunning [/system resource get version]\r\ + \n\r\ + \n:local backupNameTemplate \"backup_v\$runningOsVersion_\$runningOsCh\ + annel_\$currentDateTime\"\r\ + \n:local backupNameBeforeUpdate \"backup_before_update_\$backupNameTemplat\ + e\"\r\ + \n:local backupNameAfterUpdate \"backup_after_update_\$backupNameTemplate\ + \"\r\ + \n\r\ + \n## Email body template\r\ + \n\r\ + \n:local mailSubjectPrefix \"\$SMP Device - `\$deviceIdentityNameShort`\"\ + \r\ + \n\r\ + \n:local mailBodyCopyright \"Mikrotik RouterOS automatic backup & update \ + (ver. \$scriptVersion) \\nhttps://github.com/beeyev/Mikrotik-RouterOS-auto\ + matic-backup-and-update\"\r\ + \n:local changelogUrl \"Check RouterOS changelog: https://mikrotik.com\ + /download/changelogs/\"\r\ + \n\r\ + \n:local mailBodyDeviceInfo \"\"\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"Device information:\")\ + \r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\n-------------------\ + --\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nName: \$deviceIdent\ + ityName\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nModel: \$deviceRbMo\ + del\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBoard: \$deviceBoar\ + dName\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nSerial number: \$de\ + viceRbSerialNumber\")\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: \$d\ + eviceRbCurrentFw\")\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 will be appended later if needed\r\ + \n\r\ + \n:local mailAttachments [:toarray \"\"]\r\ + \n\r\ + \n## IP address detection\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 # default values\r\ + \n :local publicIpAddress \"not-detected\"\r\ + \n :local telemetryDataQuery \"\"\r\ + \n\r\ + \n :if (\$detectPublicIpAddress = true or \$anonStats = true) do={\r\ + \n :if (\$anonStats = true) do={\r\ + \n :set telemetryDataQuery (\"\\\?mode=\" . \$scriptMode . \"&scriptv\ + er=\" . \$scriptVersion . \"&updatechannel=\" . \$updateChannel . \"&osver\ + =\" . \$runningOsVersion . \"&step=\" . \$scriptStep . \"&forcebackup=\" .\ + \_\$forceBackup . \"&onlypatchupdates=\" . \$installOnlyPatchUpdates . \"&\ + model=\" . \$deviceRbModel . \"&deviceboard=\" . \$deviceBoardName)\r\ + \n }\r\ + \n\r\ + \n :do {:set publicIpAddress ([/tool fetch http-method=\"get\" url=(\$i\ + pAddressDetectServiceDefault . \$telemetryDataQuery) output=user as-value]\ + ->\"data\")} on-error={\r\ + \n :if (\$detectPublicIpAddress = true) do={\r\ + \n :log warning \"\$SMP Failed to detect public IP using default se\ + rvice: `\$ipAddressDetectServiceDefault`\"\r\ + \n :log warning \"\$SMP Trying fallback service: `\$ipAddressDetect\ + ServiceFallback`\"\r\ + \n\r\ + \n :do {:set publicIpAddress ([/tool fetch http-method=\"get\" url=\ + \$ipAddressDetectServiceFallback output=user as-value]->\"data\")} on-erro\ + r={\r\ + \n :log warning \"\$SMP Could not detect public IP address using \ + fallback detection service: `\$ipAddressDetectServiceFallback`\"\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n # basic safety\r\ + \n :set publicIpAddress ([:pick \$publicIpAddress 0 15])\r\ + \n\r\ + \n :if (\$detectPublicIpAddress = true) do={\r\ + \n :set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nPublic IP add\ + ress: \$publicIpAddress\")\r\ + \n :log info \"\$SMP Public IP address detected: `\$publicIpAddress`\ + \"\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n## STEP 1: Create backups, check for new RouterOS, and send email\r\ + \n## Steps 2\E2\80\933 run only if auto-update is enabled and a new versio\ + n is available\r\ + \n:if (\$scriptStep = 1) do={\r\ + \n :local routerOsVersionAvailable \"0.0.0\"\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\r\ + \n :local mailSubjectPartAction \"\"\r\ + \n :local mailPtBodyAction \"\"\r\ + \n\r\ + \n :local mailPtSubjectBackup \"\"\r\ + \n :local mailPtBodyBackup \"\"\r\ + \n\r\ + \n # Checking for new version\r\ + \n :if (\$scriptMode = \"osupdate\" or \$scriptMode = \"osnotify\") 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 install\ + ed version is: `\$runningOsVersion`\")\r\ + \n /system package update check-for-updates\r\ + \n\r\ + \n # Wait to allow the system to check for updates\r\ + \n :delay 5s;\r\ + \n\r\ + \n :local packageUpdateStatus \"undefined\"\r\ + \n\r\ + \n :set routerOsVersionAvailable [/system package update get latest-ver\ + sion]\r\ + \n :set packageUpdateStatus [/system package update get status]\r\ + \n\r\ + \n :if (\$packageUpdateStatus = \"New version is available\") do={\r\ + \n :log info (\"\$SMP New RouterOS version is available: `\$routerOsV\ + ersionAvailable`\")\r\ + \n :set isNewOsUpdateAvailable true\r\ + \n :set isLatestOsAlreadyInstalled false\r\ + \n :set isUpdateCheckSucceeded true\r\ + \n :set isEmailNeedsToBeSent true\r\ + \n\r\ + \n :set mailSubjectPartAction \"New RouterOS available\"\r\ + \n :set mailPtBodyAction \"New RouterOS version is available, curren\ + t version: v\$runningOsVersion, new version: v\$routerOsVersionAvailable. \ + \\n\$changelogUrl\"\r\ + \n } else={\r\ + \n :if (\$packageUpdateStatus = \"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\$runningOsVersion`\")\r\ + \n :set isUpdateCheckSucceeded true\r\ + \n\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\$runningOsVersion`\"\r\ + \n } else={\r\ + \n :log error (\"\$SMP Failed to check for new RouterOS version. Pa\ + ckage check status: `\$packageUpdateStatus`\")\r\ + \n :set isEmailNeedsToBeSent true\r\ + \n\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: `\$packageUpdateStatus`\\n\\nPlea\ + se review the logs on the device for more details and verify internet conn\ + ectivity.\"\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n # Checking if the script needs to install new os version\r\ + \n :if (\$scriptMode = \"osupdate\" and \$isNewOsUpdateAvailable = true) \ + do={\r\ + \n :if (\$installOnlyPatchUpdates = true) do={\r\ + \n :if ([\$FuncIsPatchUpdateOnly \$runningOsVersion \$routerOsVersion\ + Available] = true) do={\r\ + \n :log info \"\$SMP New RouterOS version is available, and it is a\ + \_patch update. Current version: v\$runningOsVersion, new version: v\$rout\ + erOsVersionAvailable\"\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\$runningOsVersion, new ver\ + sion: v\$routerOsVersionAvailable\"\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\r\ + \n\r\ + \n # Checking If the script needs to create a backup\r\ + \n :if (\$forceBackup = true or \$scriptMode = \"backup\" or \$isOsNeedsT\ + oBeUpdated = true) do={\r\ + \n :log info (\"\$SMP Starting backup process.\")\r\ + \n\r\ + \n :set isEmailNeedsToBeSent true\r\ + \n\r\ + \n :local backupName \$backupNameTemplate\r\ + \n\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\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 '\$deviceIdentityName' is scheduled to upgrade RouterOS from \ + version v.\$runningOsVersion to version v.\$routerOsVersionAvailable (Upda\ + te channel: \$updateChannel)\")\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\r\ + \n :do {\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupName \$backupPas\ + sword \$sensitiveDataInConfig];\r\ + \n\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 #failed to create backup\r\ + \n :set isOsNeedsToBeUpdated false\r\ + \n\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\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\r\ + \n :if (\$isEmailNeedsToBeSent = true) do={\r\ + \n :log info \"\$SMP Preparing to send email...\"\r\ + \n\r\ + \n :local mailStep1Subject \$mailSubjectPrefix\r\ + \n :local mailStep1Body \"\"\r\ + \n\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\r\ + \n :set mailStep1Body (\$mailStep1Body . \$mailBodyDeviceInfo . \"\\n\\\ + n\" . \$mailBodyCopyright)\r\ + \n\r\ + \n # Send email with backups\r\ + \n :do {\$FuncSendEmailSafe \$emailAddress \$mailStep1Subject \$mailSte\ + p1Body \$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\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\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\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\r\ + \n :local scheduledCommand (\":delay 5s; /system scheduler remove BKP\ + UPD-NEXT-BOOT-TASK; :global buGlobalVarScriptStep \$nextStep; :global buGl\ + obalVarTargetOsVersion \\\"\$routerOsVersionAvailable\\\"; :delay 10s; /sy\ + stem script run BackupAndUpdate;\")\r\ + \n /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\$schedu\ + ledCommand start-time=startup interval=0\r\ + \n\r\ + \n /system package update install\r\ + \n } on-error={\r\ + \n # Failed to install new os version, remove the task\r\ + \n :do {/system scheduler remove BKPUPD-NEXT-BOOT-TASK} on-error={}\r\ + \n\r\ + \n :log error \"\$SMP Failed to install new RouterOS version. Please \ + check device logs for more details.\"\r\ + \n\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\r\ + \n # Send email with error\r\ + \n \$FuncSendEmailSafe \$emailAddress \$mailUpdateErrorSubject \$mail\ + UpdateErrorBody \"\"\r\ + \n\r\ + \n :error \$exitErrorMessage\r\ + \n }\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\r\ + \n :log info \"\$SMP Upgrading routerboard firmware from v.\$deviceRbCurr\ + entFw to v.\$deviceRbUpgradeFw\"\r\ + \n\r\ + \n /system routerboard upgrade\r\ + \n :delay 2s\r\ + \n\r\ + \n :log info \"\$SMP routerboard upgrade process was completed, going to \ + reboot in a moment!\";\r\ + \n\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 5s;\ + \_/system scheduler remove BKPUPD-NEXT-BOOT-TASK; :global buGlobalVarScrip\ + tStep 3; :global buGlobalVarTargetOsVersion \\\"\$buGlobalVarTargetOsVersi\ + on\\\"; :delay 10s; /system script run BackupAndUpdate;\" start-time=start\ + up interval=0\r\ + \n\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\r\ + \n :local targetOsVersion \$buGlobalVarTargetOsVersion\r\ + \n :do {/system script environment remove buGlobalVarTargetOsVersion} on-\ + error={}\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\r\ + \n :local mailStep3Subject \$mailSubjectPrefix\r\ + \n :local mailStep3Body \"\"\r\ + \n\r\ + \n :if (\$targetOsVersion = \$runningOsVersion) do={\r\ + \n :log info \"\$SMP Successfully verified new RouterOS version: target\ + : `\$targetOsVersion`, current: `\$runningOsVersion`\"\r\ + \n\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\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupNameAfterUpdate \$\ + backupPassword \$sensitiveDataInConfig];\r\ + \n } else={\r\ + \n :log error \"\$SMP Failed to verify new RouterOS version: target: `\ + \$targetOsVersion`, current: `\$runningOsVersion`\"\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update failed\")\r\ + \n\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: `\$runningOsVersion`\\nCheck device logs for \ + more details.\\n\\n\$mailBodyDeviceInfo\\n\\n\$mailBodyCopyright\")\r\ + \n }\r\ + \n\r\ + \n \$FuncSendEmailSafe \$emailAddress \$mailStep3Subject \$mailStep3Body \ + \$mailAttachments\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\r\ + \n :log info \"\$SMP Final report email sent successfully, and the script\ + \_has finished.\"\r\ + \n}\r\ + \n\r\ + \n:log info \"\$SMP the script has finished, script step: `\$scriptStep` \ + \\n\\n\"" +add comment="Certificates CloudFlare" dont-require-permissions=no name=\ + Certificate_CloudFlare owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + tool fetch url=https://cacerts.digicert.com/DigiCertGlobalRootCA.crt.pem\r\ + \n/certificate import file-name=DigiCertGlobalRootCA.crt.pem passphrase=\"\ + \"\r\ + \n/tool fetch url=https://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA25\ + 62020CA1-1.crt.pem\r\ + \n/certificate import file-name=DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt.\ + pem passphrase=\"\"\r\ + \n#/ip dns set allow-remote-requests=yes doh-max-concurrent-queries=100 do\ + h-max-server-connections=20 use-doh-server=https://security.cloudflare-dns\ + .com/dns-query verify-doh-cert=yes\r\ + \n#/ip dns static add address=1.1.1.1 name=security.cloudflare-dns.com\r\ + \n#/ip dns static add address=1.0.0.1 name=security.cloudflare-dns.com\r\ + \n#/ip dns static add address=2606:4700:4700::1111 name=security.cloudflar\ + e-dns.com type=AAAA\r\ + \n#/ip dns static add address=2606:4700:4700::1001 name=security.cloudflar\ + e-dns.com type=AAAA\r\ + \n#/ip dns set servers=\"\"" +add dont-require-permissions=no name=Amster-Backup-UpdateEmail owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by/// Vados\r\ + \n# RouterOS script: Amster-Backup-UpdateEmail\r\ + \n# Script comment: Update and Backup send notification to EMail\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:global LogPrint;\r\ + \n\r\ + \n# Add scheduler\r\ + \n:if ([ :len [ /system/scheduler/find where name=\$ScriptName ] ] = 0) do\ + ={\r\ + \n \$LogPrint warning \$ScriptName (\"SystemScheduler NOT SET!\");\r\ + \n /system/scheduler/add name=\$ScriptName on-event=\"/system/script { ru\ + n \$ScriptName; }\" comment=\"Scheduler for \$ScriptName\" interval=\"7d 0\ + 0:00:00\" policy=ftp,reboot,read,write,policy,test,password,sniff,sensitiv\ + e,romon start-time=\"03:41:27\"; \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:global getPubIp;\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.02.12\"\r\ + \n\r\ + \n# Include public IP info in email if set to true\r\ + \n:local includePubIpAddr true;\r\ + \n# default and fallback public IP detection services\r\ + \n:local ipAddrDefault \"https://ipv4.mikrotik.ovh/\";\r\ + \n:local ipAddrFallback \"https://api.ipify.org/\";\r\ + \n\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\r\ + \n# Returns currently running RouterOS version\r\ + \n# :put [\$FuncGetRunningOsVersion] # Output: 6.48.1\r\ + \n:local FuncGetRunningOsVersion do={\r\ + \n :local runningOsAndChannel [/system resource get version];\r\ + \n :local spacePos [:find \$runningOsAndChannel \" \"];\r\ + \n :if ([:len \$spacePos] = 0) do={\r\ + \n :log error \"\$SMP Could not extract installed OS version string: `\ + \$runningOsAndChannel`.\";\r\ + \n :error \"\$SMP error, check logs\";\r\ + \n }\r\ + \n :local versionOnly [:pick \$runningOsAndChannel 0 \$spacePos];\r\ + \n :return \$versionOnly;\r\ + \n}\r\ + \n# Returns currently running RouterOS channel\r\ + \n# :put [\$FuncGetRunningOsChannel] # Output: stable\r\ + \n:local FuncGetRunningOsChannel do={\r\ + \n :local runningOsAndChannel [/system resource get version];\r\ + \n :local open [:find \$runningOsAndChannel \"(\"];\r\ + \n :if ([:len \$open] = 0) do={\r\ + \n :log error \"\$SMP Could not extract installed OS channel from versi\ + on string: `\$runningOsAndChannel`.\";\r\ + \n :error \"\$SMP error, check logs\";\r\ + \n }\r\ + \n :local rest [:pick \$runningOsAndChannel (\$open+1) [:len \$runningOsA\ + ndChannel]];\r\ + \n :local close [:find \$rest \")\"];\r\ + \n :local channel [:pick \$rest 0 \$close];\r\ + \n :return \$channel;\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 {/system script environment remove buGlobalVarScriptStep} 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 :local susRunningOsChannel [\$FuncGetRunningOsChannel];\r\ + \n :if (\$susRunningOsChannel != \"stable\" and \$susRunningOsChannel != \ + \"long-term\") 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: `\$susRunningOsChannel`. Script stopped\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n}\r\ + \n#\r\ + \n:global MiniDateTimeStamp;\r\ + \n:local MDTSFix [\$MiniDateTimeStamp];\r\ + \n:global Identity;\r\ + \n:global IdentityShort;\r\ + \n:global devBoardName;\r\ + \n:global isCHR;\r\ + \n:global devRbModel;\r\ + \n:global devRbSerialNumber;\r\ + \n:global devRbCurrentFw;\r\ + \n:global devRbUpgradeFw;\r\ + \n\r\ + \n:local rawTime [/system clock get time];\r\ + \n:local rawDate [/system clock get date];\r\ + \n\r\ + \n:local runningOsChannel [\$FuncGetRunningOsChannel];\r\ + \n:local runningOsVersion [\$FuncGetRunningOsVersion];\r\ + \n:local deviceOsVerAndChannelRunning [/system resource get version];\r\ + \n\r\ + \n:local backupNameTemplate (\"backup_v\" . \$runningOsVersion . \"_\"\ + \_. \$runningOsChannel . \"_\" . \$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) \\nhttps://github.com/beeyev/Mikrotik-RouterOS-auto\ + matic-backup-and-update\";\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: \$devRbModel\ + \");\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: \$d\ + evRbCurrentFw\");\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\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## IP address detection\r\ + \n :local publicIpAddress \"not-detected\";\r\ + \n :if ([\$includePubIpAddr] = true) do={\r\ + \n :set publicIpAddress [ \$getPubIp \$ipAddrDefault ];\r\ + \n :if (([\$publicIpAddress] = false) || ([\$publicIpAddress] = \"not\ + -detected\")) do={\r\ + \n :log warning \"\$SMP Failed to detect public IP using default se\ + rvice: `\$ipAddrDefault`\";\r\ + \n :log warning \"\$SMP Trying fallback service: `\$ipAddrFallback`\ + \";\r\ + \n :set publicIpAddress [ \$getPubIp \$ipAddrFallback ];\r\ + \n :if (([\$publicIpAddress] = false) || ([\$publicIpAddress] = \"n\ + ot-detected\")) do={\r\ + \n :log warning \"\$SMP Could not detect public IP address using \ + fallback detection service: `\$ipAddrFallback`\";\r\ + \n }\r\ + \n }\r\ + \n :set publicIpAddress ([ :pick \$publicIpAddress 0 15]);\r\ + \n :set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nPublic IP addre\ + ss: \$publicIpAddress\");\r\ + \n :log info \"\$SMP Public IP address detected: `\$publicIpAddress`\";\ + \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 :global ROsVerAvail 0.0.0;\r\ + \n :global PkgUpdStatus \"undefined\";\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: `\$runningOsVersion`\");\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\$runningOsVersion, new version: v\$ROsVerAvail. \\n\$changelo\ + gUrl\";\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\$runningOsVersion`\");\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\$runningOsVersion`\";\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 \$runningOsVersion \$ROsVerAvail] = \ + true) do={\r\ + \n :log info \"\$SMP New RouterOS version is available, and it is a\ + \_patch update. Current version: v\$runningOsVersion, new version: v\$ROsV\ + erAvail\";\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\$runningOsVersion, new ver\ + sion: 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.\ + \$runningOsVersion to version v.\$ROsVerAvail (Update channel: \$updateCha\ + nnel)\");\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 # 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 /system/script/environment remove [find name=\"buGlobalVarTarg\ + etOsVersion\"];\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 /system/script/environment remove [find name=\"OldMode\"];\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Rem\ + ove 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=\"buGlobalVarTargetOsVe\ + rsion\"];\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 } 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 = \$runningOsVersion) do={\r\ + \n :log info \"\$SMP Successfully verified new RouterOS version: target\ + : `\$targetOsVersion`, current: `\$runningOsVersion`\";\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: `\$runningOsVersion`\";\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: `\$runningOsVersion`\\nCheck device logs for \ + more 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 script\ + \_has finished.\";\r\ + \n}\r\ + \n:log info \"\$SMP the script has finished, script step: `\$scriptStep` \ + \\n\\n\";\r\ + \n} do={\r\ + \n :global ExitError; \$ExitError \$ExitOK [ :jobname ] \$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="#\ + root.mnuc.backuper\r\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@10\ + .30.30.86.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.86.pub\";\r\ + \n#/usb1/mikro_bak_rsa.pub" +add dont-require-permissions=no name=testSetup owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + \r\ + \n:local newFolder \"tmpfs/scripts\";\r\ + \n\r\ + \n:local errorMsg;\r\ + \n\r\ + \n# :while (([:pick \$newFolder ([:len \$newFolder] - 1)]) = \"/\") do=\ + {\r\ + \n# :set newFolder [:pick \$newFolder 0 ([:len \$newFolder] - 1)]\r\ + \n# }\r\ + \n\r\ + \n# :while ([:pick \$newFolder 0] = \"/\") do={\r\ + \n# :set newFolder [:pick \$newFolder 1 [:len \$newFolder]]\r\ + \n# }\r\ + \n\r\ + \n\r\ + \n:if ([/file find name=\$newFolder] != \"\") do={\r\ + \n :set errorMsg (\$newFolder . \" already exists.\");\r\ + \n :log warning (\$0 . \" mkdir: \" . \$errorMsg);\r\ + \n :return \$newFolder;\r\ + \n}\r\ + \n\r\ + \n\r\ + \n#:put [ :len [ /file/find where name=\$Path ] ];\r\ + \n\r\ + \n:put [:len \$Path];\r\ + \n:put [ :pick \$Path ];\r\ + \n:put [ :pick \$Path ([ :len \$Path ] - 1) ];\r\ + \n\r\ + \n:if (\$Path != false) do={\r\ + \n:put \"TRUE\";\r\ + \n}" +add comment="Script for setup routeros scrypt 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 scrypt 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#:foreach Script in=[ /system/script/find where source~\"^#!rsc by Vados\ + \" ] do={\r\ + \n# if (\$ScriptUpdatesCRLF = true) do={\r\ + \n# /system/script/set source=[ :tocrlf [ get \$Script source ] ] \$Scri\ + pt;\r\ + \n# } else={\r\ + \n# /system/script/set source=[ :tolf [ get \$Script source ] ] \$Script\ + ;\r\ + \n# }\r\ + \n#}\r\ + \n# /import verbose=no \$ScriptName\r\ + \n:global SetupScript [ :jobname ];\r\ + \n:global RemoveMe;\r\ + \n:global InitSetup true;\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 \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 :local CertSettings [ /certificate/settings/get ];\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 \r\ + \n /tool/fetch (\$GlobalScriptsUrl . \"certs/\" . \$CertFileName) dst-p\ + ath=\$CertFileName as-value;\r\ + \n :delay 1s;\r\ + \n /certificate/import file-name=\$CertFileName passphrase=\"\";\r\ + \n :if ([ :len [ /certificate/find where fingerprint=\$CertFingerprint \ + ] ] != 1) do={\r\ + \n :error \"Something is wrong with your certificates!\";\r\ + \n };\r\ + \n :delay 1s;\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 :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 GlobalConfReady false;\r\ + \n# \\/ start edit\r\ + \n#\r\ + \n# Global Url For Amster scripts to fetch\r\ + \n# most be defined in Amster-Init-Script\r\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n:global ScriptsUrlSuffix \"\";\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\"; #: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# 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#\r\ + \n#\r\ + \n# requires RouterOS, version=7.21\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# The variables are received right here.\r\ + \n:global GlobalConfVersion 138;\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 GlobalEnvRemove;\r\ + \n:global MiniDateTimeStamp;\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 ExitError;\r\ + \n:global CleanFilePath;\r\ + \n:global CleanName;\r\ + \n:global CharacterReplace;\r\ + \n:global CharacterMultiply;\r\ + \n:global EitherOr;\r\ + \n:global FormatLine;\r\ + \n:global GetRandom20CharAlNum;\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 WaitDNSResolving;\r\ + \n:global WaitFullyConnected;\r\ + \n:global WaitTimeSync;\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 FetchLargeFiles;\r\ + \n:global FetchUserAgentStr;\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 LogPrint;\r\ + \n :global updateChannel;\r\ + \n :global PkgUpdStatus;\r\ + \n \$LogPrint debug \$0 (\"\\\$isInfo = \" . \$isInfo); \r\ + \n :local CurrentChannel [ /system/package/update/get channel ];\r\ + \n :if (\$CurrentChannel != \$updateChannel) do={\r\ + \n :retry command={/system/package/update set channel=\$updateChannel} \ + \_delay=300ms max=5 on-error={};\r\ + \n \$LogPrint debug \$0 (\"Set updates channel to: \$updateChannel\");\ + \r\ + \n }\r\ + \n \r\ + \n :local Update [ /system/package/update/get ];\r\ + \n :local Channel (\$Update->\"channel\");\r\ + \n \$LogPrint debug \$0 (\"Update Channel: \" . \$Channel . \".\");\r\ + \n \r\ + \n :local StatusPkg \"undefined\";\r\ + \n \$LogPrint debug \$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 \$LogPrint debug \$0 (\"\\\$StatusPkg = \" . \$StatusPkg);\r\ + \n :if (\$StatusPkg = \"New version is available\") do={ :set PkgUpdStatu\ + s true; } else={ :set PkgUpdStatus false; }\r\ + \n \$LogPrint debug \$0 (\"\\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if (\$isInfo != false || \$PkgUpdStatus = true) do={\r\ + \n \$LogPrint debug \$0 (\"\\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if ([:len \$arrDeviceInfo] = 0) do={\r\ + \n :global arrDeviceInfo ({});\r\ + \n :local arrDeviceInfo ({\"section\":{\"key\":\"\"; \"value\":\"\";};}\ + );\r\ + \n }\r\ + \n # else={:local arrDeviceInfo ({})}\r\ + \n :retry command={:set (\$arrDeviceInfo->\"devUpdates\") [[ :parse \"/\ + system/package/update/get\" ]]} delay=500ms max=5 on-error={:log error (\"\ + Error\")};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devCurrentChannel\"; \$Cu\ + rrentChannel;};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devCurrentVersion\"; (\$U\ + pdate->\"installed-version\");};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devCurrentChannelVersion\ + \"; [/system/resource/get version];};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devUpdateChannel\"; [\$up\ + dateChannel];};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devUpdateVersion\"; (\$Up\ + date->\"latest-version\");};\r\ + \n } else={\r\ + \n :return \$PkgUpdStatus;\r\ + \n }\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\r\ + \n :global devBoardName;\r\ + \n :global devModel;\r\ + \n \r\ + \n :global arrDeviceInfo [:toarray {\"section\"={\"key\"=\"\"; \"value\"\ + =\"\";} }];\r\ + \n #:do {:set (\$arrDeviceInfo->\"Updates\") [[ :parse \"/system/package\ + /update/get\" ]]} on-error={}\r\ + \n :global devRbRevision;\r\ + \n :global devRbSerialNumber;\r\ + \n :global devRbCurrentFw;\r\ + \n :global devRbUpgradeFw;\r\ + \n \r\ + \n :global devRunning;\r\ + \n :global devCurArch;\r\ + \n :global ROsVerCurrent;\r\ + \n :global ROsChannelCurrent;\r\ + \n :global ROsVerAvail;\r\ + \n\r\ + \n :global ParseCustom;\r\ + \n :global runningChannelVersion;\r\ + \n :global runningChannel;\r\ + \n :global runningVersion;\r\ + \n :global devBuildTime;\r\ + \n :global GetPubIp;\r\ + \n \r\ + \n :local License [/system/license/get];\r\ + \n :local Resource [/system/resource/get];\r\ + \n :set devBuildTime (\$Resource->\"build-time\");\r\ + \n :set runningChannelVersion (\$Resource->\"version\");\r\ + \n :set runningChannel [\$ParseCustom \$runningChannelVersion \"(\" \")\ + \" 1];\r\ + \n :set runningVersion [\$ParseCustom \$runningChannelVersion \" \"];\r\ + \n \r\ + \n :do {\r\ + \n :if ([:pick \$devBoardName 0 3] != \"CHR\" or [:pick \$devBoardName 0\ + \_3] != \"x86\") do={\r\ + \n :local RouterBoard;\r\ + \n :do {:set RouterBoard [[ :parse \"/system/routerboard/get\" ]]} on\ + -error={}\r\ + \n :set devModel (\$RouterBoard->\"model\");\r\ + \n :set devRbRevision (\$RouterBoard->\"revision\");\r\ + \n :set devRbSerialNumber (\$RouterBoard->\"serial-number\");\r\ + \n #:set devRbCurrentFw (\$RouterBoard->\"current-firmware\");\r\ + \n #:set devRbUpgradeFw (\$RouterBoard->\"upgrade-firmware\");\r\ + \n :if (\$devCurArch = \"mipsbe\" || \$devCurArch = \"arm\") do={\r\ + \n :global devRunning [ /system/device-mode/get mode ];\r\ + \n } else={\r\ + \n :global devRunning [/system/device-mode/get running];}\r\ + \n } else={\r\ + \n :set devModel \$devBoardName;\r\ + \n \r\ + \n :set devRbRevision false;\r\ + \n :set devRbSerialNumber false;\r\ + \n# :set devRbCurrentFw false;\r\ + \n# :set devRbUpgradeFw false;\r\ + \n #:set devCurrentFw \$runningVersion;#:set devUpgradeFw \$ROsVerAvail\ + ;\r\ + \n :set devRunning [/system/device-mode/get running];\r\ + \n }\r\ + \n } on-error={\r\ + \n \$LogPrint error \$0 (\"Error in Function getDevice!\");\r\ + \n :return false;\r\ + \n }\r\ + \n:return true;\r\ + \n}\r\ + \n\r\ + \n:set DeviceInfo do={\r\ + \n :global WaitArrDeviceInfo;\r\ + \n # \$WaitDeviceInfo;\r\ + \n :global devBoardName;\r\ + \n :global devCurArch;\r\ + \n :global devModel;\r\ + \n :global devRunning;\r\ + \n :global devRbRevision;\r\ + \n :global devRbSerialNumber;\r\ + \n :global PiblicIpAddress;\r\ + \n\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global IfThenElse;\r\ + \n :global FormatLine;\r\ + \n :local Return;\r\ + \n :local License [/system/license/get];\r\ + \n :local Resource [/system/resource/get];\r\ + \n :local Snmp [ /snmp/get ];\r\ + \n :local Update [ /system/package/update/get ];\r\ + \n \r\ + \n :set Return ([ \$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:\" . \$devBoardName ] . \"\\n\" . \\\r\ + \n [ \$FormatLine \" Arch:\" . \$devCurArch ] . \"\\n\" . \\\r\ + \n [ \$FormatLine \" Model:\" \$devModel ] . \\\r\ + \n [ \$IfThenElse ([ :len \$devRbRevision ] > 0) \\\r\ + \n (\" \" . \$devRbRevision) ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len \$devRbSerialNumber ] > 0) \\\r\ + \n ([ \$FormatLine \" Serial:\" . \$devRbSerialNumber ] . \"\\n\"\ + ) ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"nlevel\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License:\" (\"level \" . (\$License->\"nleve\ + l\")) ] . \"\\n\") ] . \"RouterOS:\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"level\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License:\" (\"level \" . (\$License->\"level\ + \")) ] . \"\\n\") ] . \\\r\ + \n [ \$FormatLine \"Running Channel\" \$runningChannel ] . \"\\n\" . \\\ + \r\ + \n [ \$FormatLine \" Installed:\" (\$Update->\"installed-version\") \ + ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :typeof (\$Update->\"latest-version\") ] != \"noth\ + ing\" && \\\r\ + \n \$Update->\"installed-version\" != \$Update->\"latest-version\")\ + \_\\\r\ + \n ([ \$FormatLine \" Available\" (\$Update->\"latest-version\") ]\ + \_. \"\\n\") ] . \\\r\ + \n [ \$IfThenElse (\$RouterBoard->\"routerboard\" = true && \\\r\ + \n \$RouterBoard->\"current-firmware\" != \$RouterBoard->\"upgrade-\ + firmware\") \\\r\ + \n ([ \$FormatLine \" Firmware\" (\$RouterBoard->\"current-firmwar\ + e\") ] . \"\\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: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: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 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# !!! The function is not ready !!!\r\ + \n:global FetchHttpInfo do={\r\ + \n :local Mac [ :tostr \$1 ];\r\ + \n :global CertificateAvailable;\r\ + \n :global IsMacLocallyAdministered;\r\ + \n :global LogPrint;\r\ + \n :local Url \"https://api.macvendors.com/\";\r\ + \n :if ([ \$IsMacLocallyAdministered \$Mac ] = true) do={:return \"locall\ + y administered\";}\r\ + \n :do {\r\ + \n :if ([ \$CertificateAvailable \"GTS Root R4\" \"fetch\" ] = false) d\ + o={\r\ + \n \$LogPrint warning \$0 (\"Downloading required certificate failed.\ + \");\r\ + \n :error false;\r\ + \n }\r\ + \n :local Vendor ([ /tool/fetch \r\ + \n (\$Url . [ :pick \$Mac 0 8 ]) output=user as-value ]->\"data\");\r\ + \n :return \$Vendor;\r\ + \n } on-error={\r\ + \n :onerror Err {\r\ + \n /tool/fetch check-certificate=yes-without-crl (\$Url) output=none \ + as-value;\r\ + \n \$LogPrint debug \$0 (\"The mac vendor is not known in database.\"\ + );\r\ + \n } do={ \$LogPrint warning \$0 (\"Failed getting mac vendor: \" . \$E\ + rr); }\r\ + \n :return \"unknown vendor\";\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set GetPubIp do={\r\ + \n :global DetectIpAddrDefault;\r\ + \n :global DetectIpAddrFallback;\r\ + \n :global LogPrint;\r\ + \n \r\ + \n :local PubIpAddr false;\r\ + \n :local Url \$DetectIpAddrDefault;\r\ + \n\r\ + \n# :retry command={abc} delay=1 max=2 on-error={:put \"got error\"} \r\ + \n :do {\r\ + \n \$LogPrint debug \$0 (\"Trying to detect public IP using default s\ + ervice: \" . \$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={\$LogPrint error [ :jobname ] (\"Fail\ + ed to detect public IP using default service: \" . \$Url . \".\")}\r\ + \n #:error \"Failed to detect public IP using default service: \$Url\ + .\"};\r\ + \n } on-error={\r\ + \n \$LogPrint error \$0 (\"Failed to detect public IP using default s\ + ervice: \" . \$Url . \".\");\r\ + \n :set Url \$DetectIpAddrFallBack;\r\ + \n \$LogPrint debug \$0 (\"Trying to detect public IP using fallback \ + service: \" . \$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={\$LogPrint error [ :jobname ] (\"Fai\ + led to detect public IP using fallback service: \" . \$Url . \".\")}\r\ + \n };\r\ + \n :if (\$PubIpAddr = false) do={\r\ + \n :set PubIpAddr \"not-detected\";\r\ + \n } else={\r\ + \n :set PubIpAddr [ :pick \$PubIpAddr 0 15];\r\ + \n }\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 :global CertificateDownload;\r\ + \n :global EitherOr;\r\ + \n :global LogPrint;\r\ + \n :global ParseKeyValueStore;\r\ + \n :set UseFor [ \$EitherOr \$UseFor \"undefined\" ];\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 \$LogPrint 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 \$LogPrint 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 \$LogPrint info \$0 (\"Certificate with CommonName '\" . \$CommonNam\ + e . \"' not available.\");\r\ + \n :if ([ \$CertificateDownload \$CommonName ] = false) do={:return fal\ + se}\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] > 1\ + ) do={\r\ + \n \$LogPrint 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 \$LogPrint info \$0 (\"Certificate chain for '\" . \$CommonName . \ + \"' is incomplete, missing '\" . \$IssuerCN . \"'.\");\r\ + \n :if ([ \$CertificateDownload \$IssuerCN ] = false) do={:return fal\ + se}\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 GlobalScriptsUrl;\r\ + \n :global GlobalScriptsUrlSuffix;\r\ + \n :global CertificateNameByCN;\r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global LogPrint;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n \r\ + \n \$LogPrint info \$0 (\"Downloading and importing certificate with Comm\ + onName '\" . \$CommonName . \"'.\");\r\ + \n \r\ + \n :local FileName ([ \$CleanName \$CommonName ] . \".pem\");\r\ + \n :do {\r\ + \n /tool/fetch check-certificate=yes-without-crl http-header-field=({ [\ + \_\$FetchUserAgentStr \$0 ] }) \\\r\ + \n (\$GlobalScriptsUrl . \"certs/\" . \$FileName . \$GlobalScriptsUrl\ + Suffix) dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n } on-error={\r\ + \n \$LogPrint warning \$0 (\"Failed downloading certificate with Common\ + Name '\" . \$CommonName . \\\r\ + \n \"' from repository! Trying fallback to mkcert.org...\");\r\ + \n :do {\r\ + \n :if ([ :len [ /certificate/find where common-name=\"ISRG Root X1\"\ + \_] ] = 0) do={\r\ + \n \$LogPrint error \$0 (\"Required certificate is not available.\"\ + );\r\ + \n :return false;\r\ + \n }\r\ + \n /tool/fetch check-certificate=yes-without-crl http-header-field=({\ + \_[ \$FetchUserAgentStr \$0 ] }) \\\r\ + \n \"https://mkcert.org/generate/\" http-data=[ :serialize to=json \ + ({ \$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 \$LogPrint warning \$0 (\"Failed downloading certificate with Comm\ + onName '\" . \$CommonName . \"'!\");\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n /certificate/import file-name=\$FileName passphrase=\"\" as-value;\r\ + \n :delay 1s;\r\ + \n \$RmFile \$FileName;\r\ + \n\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n /certificate/remove [ find where name~(\"^\" . \$FileName . \"_[0-9]\ + +\\\$\") ];\r\ + \n \$LogPrint warning \$0 (\"Certificate with CommonName '\" . \$Common\ + Name . \"' 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 :global LogPrint;\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 \$LogPrint 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 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 ScriptUpdatesCRLF;\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global CertificateAvailable;\r\ + \n :global CheckSumsVerify;\r\ + \n :global GlobalScriptsUrl;\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 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 . \"checksums.json\" . \$GlobalScrip\ + tsUrlSuffix);\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={:set NotificationMessage (\$NotificationMessage . \"\\n\\nNe\ + ws and changes are not available.\")}\r\ + \n\r\ + \n :set GlobalConfChanges;\r\ + \n :set GlobalConfMigration;\r\ + \n }\r\ + \n} do={:global ExitOnError; \$ExitOnError \$0 \$Err}\r\ + \n}\r\ + \n\r\ + \n:set FetchUserAgentStr do={\r\ + \n :local Caller [ :tostr \$1 ];\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 (https://amster.vados.ru/; main/3419/3ee\ + 19780)\");\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 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 GetRandomNumber do={\r\ + \n :global EitherOr;\r\ + \n :return [ :rndnum from=0 to=[ \$EitherOr [ :tonum \$1 ] 4294967295 ] ]\ + \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: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 :global LogPrint;\r\ + \n :if (\$ExitOK = \"false\") do={\r\ + \n \$LogPrint 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 MiniDateTimeStamp do={\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 :return ([\$yyyy] . [\$MM] . [\$dd] . [\$HH] . [\$mm] . [\$ss]);\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={:return false}\r\ + \n :local FileVal false;\r\ + \n :do {: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 :global LogPrint;\r\ + \n \$LogPrint debug \$0 (\"Removing directory: \". \$DirName);\r\ + \n :local DirVal [ \$FileGet \$DirName ];\r\ + \n :if (\$DirVal = false) do={\$LogPrint debug \$0 (\"... which does not \ + exist.\"); :return true};\r\ + \n :if (\$DirVal->\"type\" != \"directory\") do={\$LogPrint error \$0 (\"\ + Directory '\" . \$DirName . \"' is not a directory.\"); :return false};\r\ + \n :onerror Err {/file/remove \$DirName} do={\$LogPrint error \$0 (\"Remo\ + ving directory '\" . \$DirName . \"' failed: \" . \$Err); :return false};\ + \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 :global LogPrint;\r\ + \n \$LogPrint debug \$0 (\"Removing file: \". \$FileName);\r\ + \n :local FileVal [ \$FileGet \$FileName ];\r\ + \n :if (\$FileVal = false) do={\$LogPrint debug \$0 (\"... which does not\ + \_exist.\"); :return true}\r\ + \n :if (\$FileVal->\"type\" = \"directory\" || \$FileVal->\"type\" = \"di\ + sk\") do={\r\ + \n \$LogPrint error \$0 (\"File '\" . \$FileName . \"' is not a file.\"\ + ); :return false}\r\ + \n :onerror Err {\r\ + \n /file/remove \$FileName;\r\ + \n } do={\$LogPrint error \$0 (\"Removing file '\" . \$FileName . \"' fai\ + led: \" . \$Err); :return false}\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 :global LogPrint;\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 \$LogPrint info \$0 (\"The tmpfs is disabled, enabling.\");\r\ + \n /disk/enable \$TmpFs;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n \$LogPrint 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 \$LogPrint 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 \$LogPrint debug \$0 (\"Making directory: \" . \$Path);\r\ + \n :local PathVal [ \$FileGet \$Path ];\r\ + \n :if (\$PathVal->\"type\" = \"directory\") do={\r\ + \n \$LogPrint 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 \$LogPrint 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 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 [ :len\ + \_\$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 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 LogPrint;\r\ + \n :global VersionToNum;\r\ + \n :if (!(\$Required ~ \"^\\\\d+\\\\.\\\\d+((alpha|beta|rc|\\\\.)\\\\d+|)\ + \\\$\")) do={\r\ + \n \$LogPrint 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 \$LogPrint warning \$0 (\"This \" . [ \$IfThenElse ([ :pick \$Call\ + er 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: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: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 LogPrint;\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 \$LogPrint error \$0 (\"A script named '\" . \$Script . \"' does not\ + \_exist!\");\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 \$LogPrint error \$0 (\"More tickets than running scripts '\" . \$Sc\ + ript . \"', 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: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: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:set SymbolByUnicodeName do={\r\ + \n :local Name [ :tostr \$1 ];\r\ + \n :global EitherOr;\r\ + \n :global LogPrint;\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 \$LogPrint 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: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: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: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:set IsDNSResolving do={\r\ + \n :do {:resolve \"api.telegram.org\"} on-error={:return false}\r\ + \n :return true;\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: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# read parts of a large file\r\ + \n:set FetchLargeFiles do={\r\ + \n :local ScriptName [:tostr \$1];\r\ + \n :local fetchUrl [:tostr \$2];\r\ + \n :local CheckCert [:tostr \$3];\r\ + \n :local fetchMode [:tostr \$4];\r\ + \n :local fetchMethod [:tostr \$5];\r\ + \n :local FetchString \"undefined\";\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 #:set fetchMode [ \$IfThenElse (\$fetchMode != \"false\") [:tostr (\"m\ + ode=\" . \$fetchMode)] ];\r\ + \n #:set fetchMethod [\$IfThenElse (\$fetchMethod != \"false\") [:tostr (\ + \"http-method=\\\"\" . \$fetchMethod . \"\\\"\") ]];\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 :local FileName (\$DirName . \"/\" . [ \$CleanName \$0 ] . \"-\" . [ \ + \$GetRandom20CharAlNum ]);\r\ + \n #/tool/fetch check-certificate=\$CheckCert \$Url dst-path=\$FileName h\ + ttp-header-field=({ [ \$FetchUserAgentStr \$ScriptName ] }) as-value;\r\ + \n #:log warning (\"\\\$fUrl =\" . \$2 . ({[ \$fMode ]}));\r\ + \n :onerror Err {\r\ + \n :set FetchString (\"/tool/fetch \" . \\\r\ + \n [\$IfThenElse ([\$fetchMethod] != \"false\") (\"http-method=\\\"\"\ + \_. \$fetchMethod . \"\\\" \")] . \\\r\ + \n \"check-certificate=\" . \$CheckCert . \" url=(\\\"\" . \$fetchUrl\ + \_. \"\\\") \" . \\\r\ + \n [\$IfThenElse ([\$fetchMode] != \"false\") (\"mode=\" . \$fetchMod\ + e . \" \")] . \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$ScriptName ] }) as-va\ + lue);\r\ + \n :log warning (\"\\\$FetchSring = \" . \$FetchString);\r\ + \n :put [\$FetchString];\r\ + \n #:put [/tool/fetch \$fetchMode check-certificate=\$CheckCert url=(\$\ + fetchUrl) dst-path=(\$FileName) http-header-field=({ [ \$FetchUserAgentStr\ + \_\$ScriptName ] }) as-value];\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 \" . \$fetchUrl . \"\ + \_- \" . \$Err);\r\ + \n \$RmDir \$DirName;\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ \$WaitForFile \$FileName 5s ] = false) do={\r\ + \n \$LogPrint debug \$0 (\"The file downloaded from \" . \$fetchUrl . \ + \" did not show up.\");\r\ + \n :return false;\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# Global Environment clear. Use: \"\$GlobalEnvRemove True;\".\r\ + \n:set GlobalEnvRemove do={\r\ + \n :local doRmEnv [ :tostr \$1 ];\r\ + \n :global GlobalScriptsUrl;\r\ + \n :local ScriptsUrl;\r\ + \n :global LogPrint;\r\ + \n :if (\$doRmEnv != \"True\") do={\r\ + \n \$LogPrint error \"GlobalEnvRemove\" (\"This script is needed \\\"T\ + rue\\\" for confirm to remove global environment variables!\");\r\ + \n :return [];\r\ + \n :error true; \r\ + \n } else={\r\ + \n :set ScriptsUrl \$GlobalScriptsUrl;\r\ + \n :delay 5;\r\ + \n :foreach var in=[/system/script/environment print as-value] do={/sy\ + stem/script/environment remove (\$var->\".id\")}\r\ + \n :delay 5;\r\ + \n :global GlobalScriptsUrl \$ScriptsUrl;\r\ + \n \$LogPrint warning \"GlobalEnvRemove\" (\"Done!\");\r\ + \n :return [];\r\ + \n :error false;\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 \"Setup Global Environment for scripts\";\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#:local Resource [ /system/resource/get ];\r\ + \n#:log info (\$ScriptName . \" Loaded on \" . \$Resource->\"board-name\" \ + . \" with RouterOS \" . \$Resource->\"version\" . \" and Architecture \" .\ + \".\");\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# signal we are ready\r\ + \n:set GlobalFuncReady true;\r\ + \n:set ExitOK true;\r\ + \n:log warning (\"This script \$ScriptName Loaded on \" . \$devBoardName .\ + \_\"\\nRouterOS: \" . \$devCurVersion . \"\\nArchitecture: \" . \$devCurAr\ + ch . \"\\nIdentity: \" . \$Identity);\r\ + \n} do={:global ExitOnError; \$ExitOnError \$0 \$Err;}\r\ + \n\r\ + \n#} do={:global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err}\r\ + \n# log the successful load of the script, or an error if it failed\r\ + \n#:if ([\$GlobalFuncReady] = true && [\$GlobalConfReady] = true) do={\r\ + \n" +/tool e-mail +set certificate-verification=no from=MikroYellow port=\ + 587 server=smtp.mail.me.com tls=starttls user=vadossio@icloud.com diff --git a/MikroYellow/history/20260302/20260302220010-Diff-Config.rsc b/MikroYellow/history/20260302/20260302220010-Diff-Config.rsc new file mode 100644 index 0000000..2953c35 --- /dev/null +++ b/MikroYellow/history/20260302/20260302220010-Diff-Config.rsc @@ -0,0 +1,4 @@ +1c1 +< # 2026-03-02 22:00:10 by RouterOS 7.21.3 +--- +> # 2026-03-02 21:40:10 by RouterOS 7.21.3 -- 2.52.0