From 675e5d699072a325cb3a772478ffb4292da3c204 Mon Sep 17 00:00:00 2001 From: vados-dev Date: Mon, 2 Mar 2026 22:40:19 +0300 Subject: [PATCH] Backup for MikroYellow sucessfully creatd at Mar 02 22:40:19. --- .../20260302/20260302224009-Binary.backup | Bin 0 -> 181082 bytes .../20260302/20260302224009-Config.rsc | 3946 +++++++++++++++++ .../20260302/20260302224009-Diff-Config.rsc | 4 + 3 files changed, 3950 insertions(+) create mode 100644 MikroYellow/20260302/20260302224009-Binary.backup create mode 100644 MikroYellow/20260302/20260302224009-Config.rsc create mode 100644 MikroYellow/history/20260302/20260302224009-Diff-Config.rsc diff --git a/MikroYellow/20260302/20260302224009-Binary.backup b/MikroYellow/20260302/20260302224009-Binary.backup new file mode 100644 index 0000000000000000000000000000000000000000..5c94435457ccd13fc06f7036337f5f5e0c09d323 GIT binary patch literal 181082 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 literal 0 HcmV?d00001 diff --git a/MikroYellow/20260302/20260302224009-Config.rsc b/MikroYellow/20260302/20260302224009-Config.rsc new file mode 100644 index 0000000..4eb43ec --- /dev/null +++ b/MikroYellow/20260302/20260302224009-Config.rsc @@ -0,0 +1,3946 @@ +# 2026-03-02 22:40: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/20260302224009-Diff-Config.rsc b/MikroYellow/history/20260302/20260302224009-Diff-Config.rsc new file mode 100644 index 0000000..b8e2974 --- /dev/null +++ b/MikroYellow/history/20260302/20260302224009-Diff-Config.rsc @@ -0,0 +1,4 @@ +1c1 +< # 2026-03-02 22:40:10 by RouterOS 7.21.3 +--- +> # 2026-03-02 22:20:10 by RouterOS 7.21.3 -- 2.52.0