From 595026c458ca51cca8d24316a39aa0f63044d023 Mon Sep 17 00:00:00 2001 From: vados-dev Date: Tue, 3 Mar 2026 09:00:19 +0300 Subject: [PATCH] Backup for MikroYellow sucessfully creatd at Mar 03 09:00:19. --- .../20260303/20260303090009-Binary.backup | Bin 0 -> 181502 bytes .../20260303/20260303090009-Config.rsc | 3960 +++++++++++++++++ .../20260303/20260303090009-Diff-Config.rsc | 4 + 3 files changed, 3964 insertions(+) create mode 100644 MikroYellow/20260303/20260303090009-Binary.backup create mode 100644 MikroYellow/20260303/20260303090009-Config.rsc create mode 100644 MikroYellow/history/20260303/20260303090009-Diff-Config.rsc diff --git a/MikroYellow/20260303/20260303090009-Binary.backup b/MikroYellow/20260303/20260303090009-Binary.backup new file mode 100644 index 0000000000000000000000000000000000000000..ef59e1543d121e39ac6fc2f39a64e1bc128c2ca7 GIT binary patch literal 181502 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 literal 0 HcmV?d00001 diff --git a/MikroYellow/20260303/20260303090009-Config.rsc b/MikroYellow/20260303/20260303090009-Config.rsc new file mode 100644 index 0000000..c8db890 --- /dev/null +++ b/MikroYellow/20260303/20260303090009-Config.rsc @@ -0,0 +1,3960 @@ +# 2026-03-03 09:00:09 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:global tmpfs 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 :global tmpfs;\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 \"Thr tmpfs variable is true. Checking for tmpfs/scripts director\ + y...\";\r\ + \n :if (\$tmpfs = true && [/file find name=\"tmpfs/scripts\"] != \"\") do\ + ={\r\ + \n :log debug (\$0 . \"Directory tmpfs/scripts exsist.\");\r\ + \n :return \$true;\r\ + \n } else={\r\ + \n :log error (\$0 . \"Directory tmpfs/scripts does not exsist. Remove \ + global variable tmpfs!\");\r\ + \n :global tmpfs (a);\r\ + \n return false;\r\ + \n}\r\ + \n :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/20260303/20260303090009-Diff-Config.rsc b/MikroYellow/history/20260303/20260303090009-Diff-Config.rsc new file mode 100644 index 0000000..c7fde1d --- /dev/null +++ b/MikroYellow/history/20260303/20260303090009-Diff-Config.rsc @@ -0,0 +1,4 @@ +1c1 +< # 2026-03-03 09:00:09 by RouterOS 7.21.3 +--- +> # 2026-03-03 08:40:10 by RouterOS 7.21.3 -- 2.52.0