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