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

0Iu}jwsts7d9 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 6.43.6 allowed, 6.44.\ + 1 skipped).\r\ + \n# Sends info if a newer (non-patch) version is found.\r\ + \n:local installOnlyPatchUpdates false\r\ + \n\r\ + \n# Include public IP info in email if set to true\r\ + \n:local detectPublicIpAddress true\r\ + \n\r\ + \n## Allow anonymous statistics collection. (script mode and generic non-s\ + ensitive device info)\r\ + \n:local anonStats false\r\ + \n\r\ + \n# !!! DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU\E2\80\99RE D\ + OING !!!\r\ + \n\r\ + \n:local scriptVersion \"25.04.15\"\r\ + \n\r\ + \n# default and fallback public IP detection services\r\ + \n:local ipAddressDetectServiceDefault \"https://ipv4.mikrotik.ovh/\"\r\ + \n:local ipAddressDetectServiceFallback \"https://api.ipify.org/\"\r\ + \n\r\ + \n#Script messages prefix\r\ + \n:local SMP \"Bkp&Upd:\"\r\ + \n\r\ + \n:local exitErrorMessage \"\$SMP script stopped due to an error. Please c\ + heck logs for more details.\"\r\ + \n:log info \"\\n\\n\$SMP Script \\\"Mikrotik RouterOS automatic backup & \ + update\\\" v.\$scriptVersion started.\"\r\ + \n:log info \"\$SMP Script Mode: `\$scriptMode`, Update channel: `\$update\ + Channel`, Force backup: `\$forceBackup`, Install only patch updates: `\$in\ + stallOnlyPatchUpdates`\"\r\ + \n\r\ + \n## vv FUNCTIONS vv ##\r\ + \n\r\ + \n# Returns currently running RouterOS version\r\ + \n# :put [\$FuncGetRunningOsVersion] # Output: 6.48.1\r\ + \n:local FuncGetRunningOsVersion do={\r\ + \n :local runningOsAndChannel [/system resource get version]\r\ + \n\r\ + \n :local spacePos [:find \$runningOsAndChannel \" \"]\r\ + \n :if ([:len \$spacePos] = 0) do={\r\ + \n :log error \"Bkp&Upd: Could not extract installed OS version string:\ + \_`\$runningOsAndChannel`.\"\r\ + \n :error \"Bkp&Upd: error, check logs\"\r\ + \n }\r\ + \n\r\ + \n :local versionOnly [:pick \$runningOsAndChannel 0 \$spacePos]\r\ + \n\r\ + \n :return \$versionOnly\r\ + \n}\r\ + \n\r\ + \n# Returns currently running RouterOS channel\r\ + \n# :put [\$FuncGetRunningOsChannel] # Output: stable\r\ + \n:local FuncGetRunningOsChannel do={\r\ + \n :local runningOsAndChannel [/system resource get version]\r\ + \n\r\ + \n :local open [:find \$runningOsAndChannel \"(\"]\r\ + \n :if ([:len \$open] = 0) do={\r\ + \n :log error \"Bkp&Upd: Could not extract installed OS channel from ve\ + rsion string: `\$runningOsAndChannel`.\"\r\ + \n :error \"Bkp&Upd: error, check logs\"\r\ + \n }\r\ + \n\r\ + \n :local rest [:pick \$runningOsAndChannel (\$open+1) [:len \$runningOsA\ + ndChannel]]\r\ + \n :local close [:find \$rest \")\"]\r\ + \n :local channel [:pick \$rest 0 \$close]\r\ + \n\r\ + \n :return \$channel\r\ + \n}\r\ + \n\r\ + \n# Checks if two RouterOS version strings differ only by the patch versio\ + n\r\ + \n# :put [\$FuncIsPatchUpdateOnly \"6.2.1\" \"6.2.4\"] # Output: true\r\ + \n# :put [\$FuncIsPatchUpdateOnly \"6.2.1\" \"6.3.1\"] # Output: false\r\ + \n:local FuncIsPatchUpdateOnly do={\r\ + \n :local ver1 \$1\r\ + \n :local ver2 \$2\r\ + \n\r\ + \n # Extract the major and minor components from a version\r\ + \n :local extractMajorMinor do={\r\ + \n :local ver \$1\r\ + \n :local dot1 [:find \$ver \".\"]\r\ + \n :if (\$dot1 = -1) do={ :return \$ver }\r\ + \n\r\ + \n :local major [:pick \$ver 0 \$dot1]\r\ + \n :local rest [:pick \$ver (\$dot1 + 1) [:len \$ver]]\r\ + \n :local dot2 [:find \$rest \".\"]\r\ + \n :local minor \$rest\r\ + \n :if (\$dot2 >= 0) do={ :set minor [:pick \$rest 0 \$dot2] }\r\ + \n\r\ + \n :return (\$major . \".\" . \$minor)\r\ + \n }\r\ + \n\r\ + \n # Compare the major and minor components of both version strings\r\ + \n :if ([\$extractMajorMinor \$ver1] = [\$extractMajorMinor \$ver2]) do={\ + \r\ + \n :return true\r\ + \n }\r\ + \n :return false\r\ + \n}\r\ + \n\r\ + \n# Creates backups and returns array of names\r\ + \n# Possible arguments:\r\ + \n# \$1 - file name, without extension\r\ + \n# \$2 - password (optional)\r\ + \n# \$3 - sensitive data in config (optional, default: false)\r\ + \n# Example:\r\ + \n# :put [\$FuncCreateBackups \"daily-backup\"]\r\ + \n:local FuncCreateBackups do={\r\ + \n :local backupName \$1\r\ + \n :local backupPassword \$2\r\ + \n :local sensitiveDataInConfig \$3\r\ + \n\r\ + \n #Script messages prefix\r\ + \n :local SMP \"Bkp&Upd:\"\r\ + \n :local exitErrorMessage \"\$SMP script stopped due to an error. Please\ + \_check logs for more details.\"\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` started, input:\ + \_`\$backupName`\")\r\ + \n\r\ + \n # validate required parameter: backupName\r\ + \n :if ([:typeof \$backupName] != \"str\" or [:len \$backupName] = 0) do=\ + {\r\ + \n :log error \"\$SMP parameter 'backupName' is required and must be a \ + non-empty string\"\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n\r\ + \n :local backupFileSys \"\$backupName.backup\"\r\ + \n :local backupFileConfig \"\$backupName.rsc\"\r\ + \n :local backupNames {\$backupFileSys;\$backupFileConfig}\r\ + \n\r\ + \n ## Perform system backup\r\ + \n :if ([:len \$backupPassword] = 0) do={\r\ + \n :log info (\"\$SMP starting backup without password, backup name: `\ + \$backupName`\")\r\ + \n /system backup save dont-encrypt=yes name=\$backupName\r\ + \n } else={\r\ + \n :log info (\"\$SMP starting backup with password, backup name: `\$ba\ + ckupName`\")\r\ + \n /system backup save password=\$backupPassword name=\$backupName\r\ + \n }\r\ + \n\r\ + \n :log info (\"\$SMP system backup created: `\$backupFileSys`\")\r\ + \n\r\ + \n ## Export config file\r\ + \n :if (\$sensitiveDataInConfig = true) do={\r\ + \n :log info (\"\$SMP starting export config with sensitive data, backu\ + p name: `\$backupName`\")\r\ + \n # Since RouterOS v7 it needs to be explicitly set that we want to ex\ + port sensitive data\r\ + \n :if ([:pick [/system resource get version] 0 1] < 7) do={\r\ + \n :execute \"/export compact terse file=\$backupName\"\r\ + \n } else={\r\ + \n :execute \"/export compact show-sensitive terse file=\$backupName\ + \"\r\ + \n }\r\ + \n } else={\r\ + \n :log info (\"\$SMP starting export config without sensitive data, ba\ + ckup name: `\$backupName`\")\r\ + \n /export compact hide-sensitive terse file=\$backupName\r\ + \n }\r\ + \n\r\ + \n :log info (\"\$SMP Config export complete: `\$backupFileConfig`\")\r\ + \n :log info (\"\$SMP Waiting a little to ensure backup files are written\ + \")\r\ + \n\r\ + \n :delay 20s\r\ + \n\r\ + \n :if ([:len [/file find name=\$backupFileSys]] > 0) do={\r\ + \n :log info (\"\$SMP system backup file successfully saved to the file\ + \_system: `\$backupFileSys`\")\r\ + \n } else={\r\ + \n :log error (\"\$SMP system backup was not created, file does not exi\ + st: `\$backupFileSys`\")\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n\r\ + \n :if ([:len [/file find name=\$backupFileConfig]] > 0) do={\r\ + \n :log info (\"\$SMP config backup file successfully saved to the file\ + \_system: `\$backupFileConfig`\")\r\ + \n } else={\r\ + \n :log error (\"\$SMP config backup was not created, file does not exi\ + st: `\$backupFileConfig`\")\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` finished. Creat\ + ed backups, system: `\$backupFileSys`, config: `\$backupFileConfig`\")\r\ + \n\r\ + \n :return \$backupNames\r\ + \n}\r\ + \n\r\ + \n# Sends an email\r\ + \n# Parameters:\r\ + \n# \$1 - to (email address)\r\ + \n# \$2 - subject\r\ + \n# \$3 - body\r\ + \n# \$4 - file attachments (optional; pass \"\" if not needed)\r\ + \n#\r\ + \n# Example:\r\ + \n# \$FuncSendEmailSafe \"admin@domain.com\" \"Backup Done\" \"Backup comp\ + lete.\" \"backup1.backup\"\r\ + \n:local FuncSendEmailSafe do={\r\ + \n\r\ + \n :local emailTo \$1\r\ + \n :local emailSubject \$2\r\ + \n :local emailBody \$3\r\ + \n :local emailAttachments \$4\r\ + \n\r\ + \n :local SMP \"Bkp&Upd:\"\r\ + \n :local exitErrorMessage \"\$SMP script stopped due to an error. Please\ + \_check logs for more details.\"\r\ + \n\r\ + \n :log info \"\$SMP Attempting to send email to `\$emailTo`\"\r\ + \n\r\ + \n # SAFETY: wait for any previously queued email to finish\r\ + \n :local waitTimeoutPre 60\r\ + \n :local waitCounterPre 0\r\ + \n :while (([/tool e-mail get last-status] = \"resolving-dns\" or [/tool \ + e-mail get last-status] = \"in-progress\")) do={\r\ + \n :if (\$waitCounterPre >= \$waitTimeoutPre) do={\r\ + \n :log error \"\$SMP Email send aborted: previous send did not compl\ + ete after \$waitTimeoutPre seconds\"\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n\r\ + \n :log info \"\$SMP Waiting for previous email to finish (status: \$[/\ + tool e-mail get last-status])...\"\r\ + \n :delay 1s\r\ + \n :set waitCounterPre (\$waitCounterPre + 1)\r\ + \n }\r\ + \n\r\ + \n # Send the email\r\ + \n :do {\r\ + \n /tool e-mail send to=\$emailTo subject=\$emailSubject body=\$emailBo\ + dy file=\$emailAttachments\r\ + \n } on-error={\r\ + \n :log error \"\$SMP Email send command failed to execute. Check logs \ + and verify email settings.\"\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n\r\ + \n # Wait for send status to change from \"in-progress\" / \"resolving-dn\ + s\"\r\ + \n :local waitTimeout 60\r\ + \n :local waitCounter 0\r\ + \n :local emailStatus \"\"\r\ + \n :log info \"\$SMP Waiting for email to be sent, timeout in `\$waitTime\ + out` seconds...\"\r\ + \n :while (\$waitCounter < \$waitTimeout) do={\r\ + \n :set emailStatus [/tool e-mail get last-status]\r\ + \n :if (\$emailStatus != \"in-progress\" and \$emailStatus != \"resolvi\ + ng-dns\") do={\r\ + \n :log info \"\$SMP Email send status received: \$emailStatus\"\r\ + \n\r\ + \n # exit loop\r\ + \n :set waitCounter \$waitTimeout\r\ + \n } else={\r\ + \n :delay 1s\r\ + \n :set waitCounter (\$waitCounter + 1)\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n # Final decision based on last status\r\ + \n :if (\$emailStatus = \"succeeded\") do={\r\ + \n :log info \"\$SMP Email successfully sent to `\$emailTo`\"\r\ + \n } else={\r\ + \n :log error \"\$SMP Email failed to send. Status: `\$emailStatus`. Ch\ + eck logs for more details and verify email settings.\"\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# Global variable to track current update step\r\ + \n# They need to be initialized here first to be available in the script\r\ + \n:global buGlobalVarTargetOsVersion\r\ + \n\r\ + \n:global buGlobalVarScriptStep\r\ + \n:local scriptStep \$buGlobalVarScriptStep\r\ + \n:do {/system script environment remove buGlobalVarScriptStep} on-error={\ + }\r\ + \n:if ([:len \$scriptStep] = 0) do={\r\ + \n :set scriptStep 1\r\ + \n}\r\ + \n## ^^ FUNCTIONS ^^ ##\r\ + \n\r\ + \n\r\ + \n#\r\ + \n# Initial validation\r\ + \n#\r\ + \n\r\ + \n## Check email settings\r\ + \n:if ([:len \$emailAddress] < 3) do={\r\ + \n :log error (\"\$SMP Parameter `\\\$emailAddress` is not set, or contai\ + ns invalid value. Script stopped.\")\r\ + \n :error \$exitErrorMessage\r\ + \n}\r\ + \n\r\ + \n# Values will be defined later in the script\r\ + \n:local emailServer \"\"\r\ + \n:local emailFromAddress [/tool e-mail get from]\r\ + \n\r\ + \n:log info \"\$SMP Validating email settings...\"\r\ + \n:do {\r\ + \n :set emailServer [/tool e-mail get server]\r\ + \n} on-error={\r\ + \n # This is a workaround for the RouterOS v7.12 and older versions\r\ + \n :set emailServer [/tool e-mail get address]\r\ + \n}\r\ + \n:if (\$emailServer = \"0.0.0.0\") do={\r\ + \n :log error (\"\$SMP Email server address is not correct: `\$emailServe\ + r`, check `Tools -> Email`. Script stopped.\");\r\ + \n :error \$exitErrorMessage\r\ + \n}\r\ + \n:if ([:len \$emailFromAddress] < 3) do={\r\ + \n :log error (\"\$SMP Email configuration FROM address is not correct: `\ + \$emailFromAddress`, check `Tools -> Email`. Script stopped.\");\r\ + \n :error \$exitErrorMessage\r\ + \n}\r\ + \n\r\ + \n# Script mode validation\r\ + \n:if (\$scriptMode != \"backup\" and \$scriptMode != \"osupdate\" and \$s\ + criptMode != \"osnotify\") do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$scriptMode` is not set, or c\ + ontains invalid value: `\$scriptMode`. Script stopped.\")\r\ + \n :error \$exitErrorMessage\r\ + \n}\r\ + \n\r\ + \n# Update channel validation\r\ + \n:if (\$updateChannel != \"stable\" and \$updateChannel != \"long-term\" \ + and \$updateChannel != \"testing\" and \$updateChannel != \"development\")\ + \_do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$updateChannel` is not set, o\ + r contains invalid value: `\$updateChannel`. Script stopped.\")\r\ + \n :error \$exitErrorMessage\r\ + \n}\r\ + \n\r\ + \n# Verify if script is set to install patch updates and if the update cha\ + nnel is valid\r\ + \n:if (\$scriptMode = \"osupdate\" and \$installOnlyPatchUpdates = true) d\ + o={\r\ + \n :if (\$updateChannel != \"stable\" and \$updateChannel != \"long-term\ + \") do={\r\ + \n :log error (\"\$SMP Patch-only updates enabled, but update channel `\ + \$updateChannel` is invalid. Only `stable` and `long-term` are supported. \ + Script stopped\")\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n\r\ + \n :local susRunningOsChannel [\$FuncGetRunningOsChannel]\r\ + \n\r\ + \n :if (\$susRunningOsChannel != \"stable\" and \$susRunningOsChannel != \ + \"long-term\") do={\r\ + \n :log error (\"\$SMP Script is set to install only patch updates, but\ + \_the installed RouterOS version is not from `stable` or `long-term` chann\ + el: `\$susRunningOsChannel`. Script stopped\")\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n#\r\ + \n# Get current date and time\r\ + \n#\r\ + \n:local rawTime [/system clock get time]\r\ + \n:local rawDate [/system clock get date]\r\ + \n\r\ + \n# Current time in specific format `hh-mm-ss`\r\ + \n:local currentTime ([:pick \$rawTime 0 2] . \"-\" . [:pick \$rawTime 3 5\ + ] . \"-\" . [:pick \$rawTime 6 8])\r\ + \n\r\ + \n# Current date `YYYY-MM-DD` or `YYYY-Mon-DD`\r\ + \n:local currentDate \"undefined\"\r\ + \n\r\ + \n# Check if the date is in the old format\r\ + \n:if ([:len [:tonum [:pick \$rawDate 0 1]]] = 0) do={\r\ + \n # Convert old format `nov/11/2023` > `2023-nov-11`\r\ + \n :set currentDate ([:pick \$rawDate 7 11] . \"-\" . [:pick \$rawDate 0 \ + 3] . \"-\" . [:pick \$rawDate 4 6])\r\ + \n} else={\r\ + \n # Use new format as is `YYYY-MM-DD`\r\ + \n :set currentDate \$rawDate\r\ + \n}\r\ + \n\r\ + \n:local currentDateTime (\$currentDate . \"-\" . \$currentTime)\r\ + \n\r\ + \n:local deviceBoardName [/system resource get board-name]\r\ + \n\r\ + \n## Check if it's a cloud hosted router\r\ + \n:local isCloudHostedRouter false;\r\ + \n:if ([:pick \$deviceBoardName 0 3] = \"CHR\" or [:pick \$deviceBoardName\ + \_0 3] = \"x86\") do={\r\ + \n :set isCloudHostedRouter true;\r\ + \n};\r\ + \n\r\ + \n:local deviceIdentityName [/system identity get name];\r\ + \n:local deviceIdentityNameShort [:pick \$deviceIdentityName 0 18]\r\ + \n\r\ + \n:local deviceRbModel \"CloudHostedRouter\";\r\ + \n:local deviceRbSerialNumber \"--\"\r\ + \n:local deviceRbCurrentFw \"--\"\r\ + \n:local deviceRbUpgradeFw \"--\"\r\ + \n\r\ + \n:if (\$isCloudHostedRouter = false) do={\r\ + \n :set deviceRbModel [/system routerboard get model]\r\ + \n :set deviceRbSerialNumber [/system routerboard get serial-number]\r\ + \n :set deviceRbCurrentFw [/system routerboard get current-firmware]\r\ + \n :set deviceRbUpgradeFw [/system routerboard get upgrade-firmware]\r\ + \n};\r\ + \n\r\ + \n:local runningOsChannel [\$FuncGetRunningOsChannel]\r\ + \n:local runningOsVersion [\$FuncGetRunningOsVersion]\r\ + \n:local deviceOsVerAndChannelRunning [/system resource get version]\r\ + \n\r\ + \n:local backupNameTemplate \"backup_v\$runningOsVersion_\$runningOsCh\ + annel_\$currentDateTime\"\r\ + \n:local backupNameBeforeUpdate \"backup_before_update_\$backupNameTemplat\ + e\"\r\ + \n:local backupNameAfterUpdate \"backup_after_update_\$backupNameTemplate\ + \"\r\ + \n\r\ + \n## Email body template\r\ + \n\r\ + \n:local mailSubjectPrefix \"\$SMP Device - `\$deviceIdentityNameShort`\"\ + \r\ + \n\r\ + \n:local mailBodyCopyright \"Mikrotik RouterOS automatic backup & update \ + (ver. \$scriptVersion) \\nhttps://github.com/beeyev/Mikrotik-RouterOS-auto\ + matic-backup-and-update\"\r\ + \n:local changelogUrl \"Check RouterOS changelog: https://mikrotik.com\ + /download/changelogs/\"\r\ + \n\r\ + \n:local mailBodyDeviceInfo \"\"\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"Device information:\")\ + \r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\n-------------------\ + --\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nName: \$deviceIdent\ + ityName\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nModel: \$deviceRbMo\ + del\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBoard: \$deviceBoar\ + dName\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nSerial number: \$de\ + viceRbSerialNumber\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nRouterOS version: v\ + \$deviceOsVerAndChannelRunning\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBuild time: \$[/sys\ + tem resource get build-time]\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nRouterboard FW: \$d\ + eviceRbCurrentFw\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nDevice date-time: \ + \$rawDate \$rawTime (\$[/system clock get time-zone-name ])\")\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nUptime: \$[/system \ + resource get uptime]\")\r\ + \n# IP address will be appended later if needed\r\ + \n\r\ + \n:local mailAttachments [:toarray \"\"]\r\ + \n\r\ + \n## IP address detection\r\ + \n:if (\$scriptStep = 1 or \$scriptStep = 3) do={\r\ + \n :if (\$scriptStep = 3) do={\r\ + \n :log info (\"\$SMP Waiting for one minute before continuing to the f\ + inal step.\")\r\ + \n :delay 1m\r\ + \n }\r\ + \n # default values\r\ + \n :local publicIpAddress \"not-detected\"\r\ + \n :local telemetryDataQuery \"\"\r\ + \n\r\ + \n :if (\$detectPublicIpAddress = true or \$anonStats = true) do={\r\ + \n :if (\$anonStats = true) do={\r\ + \n :set telemetryDataQuery (\"\\\?mode=\" . \$scriptMode . \"&scriptv\ + er=\" . \$scriptVersion . \"&updatechannel=\" . \$updateChannel . \"&osver\ + =\" . \$runningOsVersion . \"&step=\" . \$scriptStep . \"&forcebackup=\" .\ + \_\$forceBackup . \"&onlypatchupdates=\" . \$installOnlyPatchUpdates . \"&\ + model=\" . \$deviceRbModel . \"&deviceboard=\" . \$deviceBoardName)\r\ + \n }\r\ + \n\r\ + \n :do {:set publicIpAddress ([/tool fetch http-method=\"get\" url=(\$i\ + pAddressDetectServiceDefault . \$telemetryDataQuery) output=user as-value]\ + ->\"data\")} on-error={\r\ + \n :if (\$detectPublicIpAddress = true) do={\r\ + \n :log warning \"\$SMP Failed to detect public IP using default se\ + rvice: `\$ipAddressDetectServiceDefault`\"\r\ + \n :log warning \"\$SMP Trying fallback service: `\$ipAddressDetect\ + ServiceFallback`\"\r\ + \n\r\ + \n :do {:set publicIpAddress ([/tool fetch http-method=\"get\" url=\ + \$ipAddressDetectServiceFallback output=user as-value]->\"data\")} on-erro\ + r={\r\ + \n :log warning \"\$SMP Could not detect public IP address using \ + fallback detection service: `\$ipAddressDetectServiceFallback`\"\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n # basic safety\r\ + \n :set publicIpAddress ([:pick \$publicIpAddress 0 15])\r\ + \n\r\ + \n :if (\$detectPublicIpAddress = true) do={\r\ + \n :set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nPublic IP add\ + ress: \$publicIpAddress\")\r\ + \n :log info \"\$SMP Public IP address detected: `\$publicIpAddress`\ + \"\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n## STEP 1: Create backups, check for new RouterOS, and send email\r\ + \n## Steps 2\E2\80\933 run only if auto-update is enabled and a new versio\ + n is available\r\ + \n:if (\$scriptStep = 1) do={\r\ + \n :local routerOsVersionAvailable \"0.0.0\"\r\ + \n :local isNewOsUpdateAvailable false\r\ + \n :local isLatestOsAlreadyInstalled true\r\ + \n :local isOsNeedsToBeUpdated false\r\ + \n :local isUpdateCheckSucceeded false\r\ + \n :local isEmailNeedsToBeSent false\r\ + \n\r\ + \n :local mailSubjectPartAction \"\"\r\ + \n :local mailPtBodyAction \"\"\r\ + \n\r\ + \n :local mailPtSubjectBackup \"\"\r\ + \n :local mailPtBodyBackup \"\"\r\ + \n\r\ + \n # Checking for new version\r\ + \n :if (\$scriptMode = \"osupdate\" or \$scriptMode = \"osnotify\") do={\ + \r\ + \n log info (\"\$SMP Setting update channel to `\$updateChannel`\")\r\ + \n /system package update set channel=\$updateChannel\r\ + \n log info (\"\$SMP Checking for new RouterOS version. Current install\ + ed version is: `\$runningOsVersion`\")\r\ + \n /system package update check-for-updates\r\ + \n\r\ + \n # Wait to allow the system to check for updates\r\ + \n :delay 5s;\r\ + \n\r\ + \n :local packageUpdateStatus \"undefined\"\r\ + \n\r\ + \n :set routerOsVersionAvailable [/system package update get latest-ver\ + sion]\r\ + \n :set packageUpdateStatus [/system package update get status]\r\ + \n\r\ + \n :if (\$packageUpdateStatus = \"New version is available\") do={\r\ + \n :log info (\"\$SMP New RouterOS version is available: `\$routerOsV\ + ersionAvailable`\")\r\ + \n :set isNewOsUpdateAvailable true\r\ + \n :set isLatestOsAlreadyInstalled false\r\ + \n :set isUpdateCheckSucceeded true\r\ + \n :set isEmailNeedsToBeSent true\r\ + \n\r\ + \n :set mailSubjectPartAction \"New RouterOS available\"\r\ + \n :set mailPtBodyAction \"New RouterOS version is available, curren\ + t version: v\$runningOsVersion, new version: v\$routerOsVersionAvailable. \ + \\n\$changelogUrl\"\r\ + \n } else={\r\ + \n :if (\$packageUpdateStatus = \"System is already up to date\") do=\ + {\r\ + \n :log info (\"\$SMP No new RouterOS version is available, the lat\ + est version is already installed: `v\$runningOsVersion`\")\r\ + \n :set isUpdateCheckSucceeded true\r\ + \n\r\ + \n :set mailSubjectPartAction \"No os update available\"\r\ + \n :set mailPtBodyAction \"No new RouterOS version is available, t\ + he latest version is already installed: `v\$runningOsVersion`\"\r\ + \n } else={\r\ + \n :log error (\"\$SMP Failed to check for new RouterOS version. Pa\ + ckage check status: `\$packageUpdateStatus`\")\r\ + \n :set isEmailNeedsToBeSent true\r\ + \n\r\ + \n :set mailSubjectPartAction \"Error unable to check new os versio\ + n\"\r\ + \n :set mailPtBodyAction \"An error occurred while checking for a \ + new RouterOS version.\\nStatus returned: `\$packageUpdateStatus`\\n\\nPlea\ + se review the logs on the device for more details and verify internet conn\ + ectivity.\"\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n # Checking if the script needs to install new os version\r\ + \n :if (\$scriptMode = \"osupdate\" and \$isNewOsUpdateAvailable = true) \ + do={\r\ + \n :if (\$installOnlyPatchUpdates = true) do={\r\ + \n :if ([\$FuncIsPatchUpdateOnly \$runningOsVersion \$routerOsVersion\ + Available] = true) do={\r\ + \n :log info \"\$SMP New RouterOS version is available, and it is a\ + \_patch update. Current version: v\$runningOsVersion, new version: v\$rout\ + erOsVersionAvailable\"\r\ + \n :set isOsNeedsToBeUpdated true\r\ + \n } else={\r\ + \n :log info \"\$SMP The script will not install this update, becau\ + se it is not a patch update. Current version: v\$runningOsVersion, new ver\ + sion: v\$routerOsVersionAvailable\"\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nThis update wil\ + l not be installed, because the script is set to install only patch update\ + s.\")\r\ + \n }\r\ + \n } else={\r\ + \n :set isOsNeedsToBeUpdated true\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n\r\ + \n # Checking If the script needs to create a backup\r\ + \n :if (\$forceBackup = true or \$scriptMode = \"backup\" or \$isOsNeedsT\ + oBeUpdated = true) do={\r\ + \n :log info (\"\$SMP Starting backup process.\")\r\ + \n\r\ + \n :set isEmailNeedsToBeSent true\r\ + \n\r\ + \n :local backupName \$backupNameTemplate\r\ + \n\r\ + \n # This means it's the first step where we create a backup before the\ + \_update process\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\r\ + \n :set backupName \$backupNameBeforeUpdate\r\ + \n\r\ + \n #Email body if the purpose of the script is to update the device\r\ + \n :set mailSubjectPartAction \"Update preparation\"\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nThe update proces\ + s for device '\$deviceIdentityName' is scheduled to upgrade RouterOS from \ + version v.\$runningOsVersion to version v.\$routerOsVersionAvailable (Upda\ + te channel: \$updateChannel)\")\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nPlease note: The \ + update will proceed only after a successful backup.\")\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nA final report wi\ + th detailed information will be sent once the update process is completed.\ + \")\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nIf you do not rec\ + eive a second email within the next 10 minutes, there may be an issue. Ple\ + ase check your device logs for further information.\")\r\ + \n }\r\ + \n\r\ + \n :do {\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupName \$backupPas\ + sword \$sensitiveDataInConfig];\r\ + \n\r\ + \n :set mailPtSubjectBackup \"Backup created\"\r\ + \n :set mailPtBodyBackup \"System backups have been successfully crea\ + ted and attached to this email.\"\r\ + \n } on-error={\r\ + \n #failed to create backup\r\ + \n :set isOsNeedsToBeUpdated false\r\ + \n\r\ + \n :set mailPtSubjectBackup \"Backup failed\"\r\ + \n :set mailPtBodyBackup \"The script failed to create backups. Pleas\ + e check device logs for more details.\"\r\ + \n\r\ + \n :log warning \"\$SMP Backup creation failed. Update process will b\ + e canceled if automatic update is enabled\"\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if (\$isEmailNeedsToBeSent = true) do={\r\ + \n :log info \"\$SMP Preparing to send email...\"\r\ + \n\r\ + \n :local mailStep1Subject \$mailSubjectPrefix\r\ + \n :local mailStep1Body \"\"\r\ + \n\r\ + \n # subject\r\ + \n :if (\$mailSubjectPartAction != \"\") do={:set mailStep1Subject (\$\ + mailStep1Subject . \" - \" . \$mailSubjectPartAction)}\r\ + \n :if (\$mailPtSubjectBackup != \"\") do={:set mailStep1Subject (\$ma\ + ilStep1Subject . \" - \" . \$mailPtSubjectBackup)}\r\ + \n # body\r\ + \n :if (\$mailPtBodyAction != \"\") do={:set mailStep1Body (\$mailStep1\ + Body . \$mailPtBodyAction . \"\\n\\n\")}\r\ + \n :if (\$mailPtBodyBackup != \"\") do={:set mailStep1Body (\$mailStep1\ + Body . \$mailPtBodyBackup . \"\\n\\n\")}\r\ + \n\r\ + \n :set mailStep1Body (\$mailStep1Body . \$mailBodyDeviceInfo . \"\\n\\\ + n\" . \$mailBodyCopyright)\r\ + \n\r\ + \n # Send email with backups\r\ + \n :do {\$FuncSendEmailSafe \$emailAddress \$mailStep1Subject \$mailSte\ + p1Body \$mailAttachments} on-error={\r\ + \n :set isOsNeedsToBeUpdated false\r\ + \n :log error \"\$SMP The script will not proceed with the update pro\ + cess, because the email was not sent.\"\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if ([:len \$mailAttachments] > 0) do={\r\ + \n :log info \"\$SMP Cleaning up backup files from the file system...\"\ + \r\ + \n /file remove \$mailAttachments;\r\ + \n :delay 2s;\r\ + \n }\r\ + \n\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\r\ + \n :log info \"\$SMP everything is ready to install new RouterOS, going\ + \_to start the update process and reboot the device.\"\r\ + \n :do {\r\ + \n :local nextStep 2\r\ + \n :if (\$isCloudHostedRouter = true) do={\r\ + \n :log info \"\$SMP The device is a cloud hosted router, the secon\ + d step updating the Routerboard firmware will be skipped.\"\r\ + \n :set nextStep 3\r\ + \n }\r\ + \n\r\ + \n :local scheduledCommand (\":delay 5s; /system scheduler remove BKP\ + UPD-NEXT-BOOT-TASK; :global buGlobalVarScriptStep \$nextStep; :global buGl\ + obalVarTargetOsVersion \\\"\$routerOsVersionAvailable\\\"; :delay 10s; /sy\ + stem script run BackupAndUpdate;\")\r\ + \n /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\$schedu\ + ledCommand start-time=startup interval=0\r\ + \n\r\ + \n /system package update install\r\ + \n } on-error={\r\ + \n # Failed to install new os version, remove the task\r\ + \n :do {/system scheduler remove BKPUPD-NEXT-BOOT-TASK} on-error={}\r\ + \n\r\ + \n :log error \"\$SMP Failed to install new RouterOS version. Please \ + check device logs for more details.\"\r\ + \n\r\ + \n :local mailUpdateErrorSubject (\$mailSubjectPrefix . \" - Update f\ + ailed\")\r\ + \n :local mailUpdateErrorBody \"The script was unable to install new \ + RouterOS version. Please check device logs for more details.\"\r\ + \n\r\ + \n # Send email with error\r\ + \n \$FuncSendEmailSafe \$emailAddress \$mailUpdateErrorSubject \$mail\ + UpdateErrorBody \"\"\r\ + \n\r\ + \n :error \$exitErrorMessage\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n## STEP 2: (Post-reboot) Upgrade RouterBOARD firmware\r\ + \n## Runs only if auto-update is enabled and a new RouterOS version was fo\ + und\r\ + \n:if (\$scriptStep = 2) do={\r\ + \n :log info \"\$SMP The script is in the second step, updating Routerboa\ + rd firmware.\"\r\ + \n\r\ + \n :log info \"\$SMP Upgrading routerboard firmware from v.\$deviceRbCurr\ + entFw to v.\$deviceRbUpgradeFw\"\r\ + \n\r\ + \n /system routerboard upgrade\r\ + \n :delay 2s\r\ + \n\r\ + \n :log info \"\$SMP routerboard upgrade process was completed, going to \ + reboot in a moment!\";\r\ + \n\r\ + \n ## Set task to send final report on the next boot\r\ + \n /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\":delay 5s;\ + \_/system scheduler remove BKPUPD-NEXT-BOOT-TASK; :global buGlobalVarScrip\ + tStep 3; :global buGlobalVarTargetOsVersion \\\"\$buGlobalVarTargetOsVersi\ + on\\\"; :delay 10s; /system script run BackupAndUpdate;\" start-time=start\ + up interval=0\r\ + \n\r\ + \n /system reboot;\r\ + \n}\r\ + \n\r\ + \n## STEP 3: Final report (after second reboot, with delay).\r\ + \n## Runs only if auto-update is enabled and a new RouterOS version was fo\ + und.\r\ + \n:if (\$scriptStep = 3) do={\r\ + \n :log info (\"\$SMP The script is in the third step, sending final repo\ + rt.\")\r\ + \n\r\ + \n :local targetOsVersion \$buGlobalVarTargetOsVersion\r\ + \n :do {/system script environment remove buGlobalVarTargetOsVersion} on-\ + error={}\r\ + \n :if ([:len \$targetOsVersion] = 0) do={\r\ + \n :log warning \"\$SMP Something is wrong, the script was unable to ge\ + t the target updated OS version from the global variable.\"\r\ + \n }\r\ + \n\r\ + \n :local mailStep3Subject \$mailSubjectPrefix\r\ + \n :local mailStep3Body \"\"\r\ + \n\r\ + \n :if (\$targetOsVersion = \$runningOsVersion) do={\r\ + \n :log info \"\$SMP Successfully verified new RouterOS version: target\ + : `\$targetOsVersion`, current: `\$runningOsVersion`\"\r\ + \n\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update completed - \ + Backup created\")\r\ + \n :set mailStep3Body (\$mailStep3Body . \"RouterOS and routerboard upg\ + rade process was completed\")\r\ + \n :set mailStep3Body (\$mailStep3Body . \"\\nNew RouterOS version: v.\ + \$targetOsVersion, routerboard firmware: v.\$deviceRbCurrentFw\")\r\ + \n :set mailStep3Body (\$mailStep3Body . \"\\n\$changelogUrl\\nBackups \ + of the upgraded system are in the attachment of this email.\\n\\n\$mailBod\ + yDeviceInfo\\n\\n\$mailBodyCopyright\")\r\ + \n\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupNameAfterUpdate \$\ + backupPassword \$sensitiveDataInConfig];\r\ + \n } else={\r\ + \n :log error \"\$SMP Failed to verify new RouterOS version: target: `\ + \$targetOsVersion`, current: `\$runningOsVersion`\"\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update failed\")\r\ + \n\r\ + \n :set mailStep3Body (\$mailStep3Body . \"The script was unable to ver\ + ify that the new RouterOS version was installed, target version: `\$target\ + OsVersion`, current version: `\$runningOsVersion`\\nCheck device logs for \ + more details.\\n\\n\$mailBodyDeviceInfo\\n\\n\$mailBodyCopyright\")\r\ + \n }\r\ + \n\r\ + \n \$FuncSendEmailSafe \$emailAddress \$mailStep3Subject \$mailStep3Body \ + \$mailAttachments\r\ + \n\r\ + \n :if ([:len \$mailAttachments] > 0) do={\r\ + \n :log info \"\$SMP Cleaning up backup files from the file system...\"\ + \r\ + \n /file remove \$mailAttachments;\r\ + \n :delay 2s;\r\ + \n }\r\ + \n\r\ + \n :log info \"\$SMP Final report email sent successfully, and the script\ + \_has finished.\"\r\ + \n}\r\ + \n\r\ + \n:log info \"\$SMP the script has finished, script step: `\$scriptStep` \ + \\n\\n\"" +add comment="Certificates CloudFlare" dont-require-permissions=no name=\ + Certificate_CloudFlare owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\ + tool fetch url=https://cacerts.digicert.com/DigiCertGlobalRootCA.crt.pem\r\ + \n/certificate import file-name=DigiCertGlobalRootCA.crt.pem passphrase=\"\ + \"\r\ + \n/tool fetch url=https://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA25\ + 62020CA1-1.crt.pem\r\ + \n/certificate import file-name=DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt.\ + pem passphrase=\"\"\r\ + \n#/ip dns set allow-remote-requests=yes doh-max-concurrent-queries=100 do\ + h-max-server-connections=20 use-doh-server=https://security.cloudflare-dns\ + .com/dns-query verify-doh-cert=yes\r\ + \n#/ip dns static add address=1.1.1.1 name=security.cloudflare-dns.com\r\ + \n#/ip dns static add address=1.0.0.1 name=security.cloudflare-dns.com\r\ + \n#/ip dns static add address=2606:4700:4700::1111 name=security.cloudflar\ + e-dns.com type=AAAA\r\ + \n#/ip dns static add address=2606:4700:4700::1001 name=security.cloudflar\ + e-dns.com type=AAAA\r\ + \n#/ip dns set servers=\"\"" +add dont-require-permissions=no name=Amster-Backup-UpdateEmail owner=vados \ + policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ + source="#!rsc by/// Vados\r\ + \n# RouterOS script: Amster-Backup-UpdateEmail\r\ + \n# Script comment: Update and Backup send notification to EMail\r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version>=6.43.7\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n# Updated: 15/04/2025\r\ + \n# Website: https://github.com/beeyev\r\ + \n# Notification e-mail (Make sure you have configured Email settings in T\ + ools -> Email)\r\ + \n:local ExitOK false;\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady; :global GlobalFuncReady;\r\ + \n :retry { :if (\$GlobalConfReady != true || \$GlobalFuncReady != true) \ + \\\r\ + \n do={ :error (\"Global config and/or functions not ready.\"); }; } \ + delay=500ms max=50;\r\ + \n :local ScriptName [ :jobname ];\r\ + \n\r\ + \n:global LogPrint;\r\ + \n\r\ + \n# Add scheduler\r\ + \n:if ([ :len [ /system/scheduler/find where name=\$ScriptName ] ] = 0) do\ + ={\r\ + \n \$LogPrint warning \$ScriptName (\"SystemScheduler NOT SET!\");\r\ + \n /system/scheduler/add name=\$ScriptName on-event=\"/system/script { ru\ + n \$ScriptName; }\" comment=\"Scheduler for \$ScriptName\" interval=\"7d 0\ + 0:00:00\" policy=ftp,reboot,read,write,policy,test,password,sniff,sensitiv\ + e,romon start-time=\"03:41:27\"; \r\ + \n :set ExitOK true;\r\ + \n :error false;\r\ + \n}\r\ + \n\r\ + \n:global EmailGeneralTo;\r\ + \n:global BackupAndUpdateMode;\r\ + \n:global forceBackup;\r\ + \n:global BackupPassword;\r\ + \n:global BackupSens;\r\ + \n:global updateChannel;\r\ + \n:global getPubIp;\r\ + \n\r\ + \n# Installs patch updates only (BackupAndUpdateMode = \"osupdate\").\r\ + \n# Works for `stable` and `long-term` channels.\r\ + \n# Updates only if MAJOR.MINOR match (e.g. 6.43.2 > 6.43.6 allowed, 6.44.\ + 1 skipped).\r\ + \n# Sends info if a newer (non-patch) version is found.\r\ + \n:local installOnlyPatchUpdates false\r\ + \n:local scriptVersion \"26.02.12\"\r\ + \n\r\ + \n# Include public IP info in email if set to true\r\ + \n:local includePubIpAddr true;\r\ + \n# default and fallback public IP detection services\r\ + \n:local ipAddrDefault \"https://ipv4.mikrotik.ovh/\";\r\ + \n:local ipAddrFallback \"https://api.ipify.org/\";\r\ + \n\r\ + \n#Script messages prefix\r\ + \n:local SMP \"BkpAndUpdate:\"\r\ + \n\r\ + \n:local exitErrorMessage \"\$SMP script stopped due to an error. Please c\ + heck logs for more details.\"\r\ + \n:log info \"\\n\\n\$SMP Script \\\"Mikrotik RouterOS automatic backup & \ + update\\\" v.\$scriptVersion started.\"\r\ + \n:log info \"\$SMP Script Mode: `\$BackupAndUpdateMode`, Update channel: \ + `\$updateChannel`, Force backup: `\$forceBackup`, Install only patch updat\ + es: `\$installOnlyPatchUpdates`\"\r\ + \n\r\ + \n## vv FUNCTIONS vv ##\r\ + \n\r\ + \n# Returns currently running RouterOS version\r\ + \n# :put [\$FuncGetRunningOsVersion] # Output: 6.48.1\r\ + \n:local FuncGetRunningOsVersion do={\r\ + \n :local runningOsAndChannel [/system resource get version];\r\ + \n :local spacePos [:find \$runningOsAndChannel \" \"];\r\ + \n :if ([:len \$spacePos] = 0) do={\r\ + \n :log error \"\$SMP Could not extract installed OS version string: `\ + \$runningOsAndChannel`.\";\r\ + \n :error \"\$SMP error, check logs\";\r\ + \n }\r\ + \n :local versionOnly [:pick \$runningOsAndChannel 0 \$spacePos];\r\ + \n :return \$versionOnly;\r\ + \n}\r\ + \n# Returns currently running RouterOS channel\r\ + \n# :put [\$FuncGetRunningOsChannel] # Output: stable\r\ + \n:local FuncGetRunningOsChannel do={\r\ + \n :local runningOsAndChannel [/system resource get version];\r\ + \n :local open [:find \$runningOsAndChannel \"(\"];\r\ + \n :if ([:len \$open] = 0) do={\r\ + \n :log error \"\$SMP Could not extract installed OS channel from versi\ + on string: `\$runningOsAndChannel`.\";\r\ + \n :error \"\$SMP error, check logs\";\r\ + \n }\r\ + \n :local rest [:pick \$runningOsAndChannel (\$open+1) [:len \$runningOsA\ + ndChannel]];\r\ + \n :local close [:find \$rest \")\"];\r\ + \n :local channel [:pick \$rest 0 \$close];\r\ + \n :return \$channel;\r\ + \n}\r\ + \n# Checks if two RouterOS version strings differ only by the patch versio\ + n\r\ + \n# :put [\$FuncIsPatchUpdateOnly \"6.2.1\" \"6.2.4\"] # Output: true\r\ + \n# :put [\$FuncIsPatchUpdateOnly \"6.2.1\" \"6.3.1\"] # Output: false\r\ + \n:local FuncIsPatchUpdateOnly do={\r\ + \n :local ver1 \$1;\r\ + \n :local ver2 \$2;\r\ + \n # Extract the major and minor components from a version\r\ + \n :local extractMajorMinor do={\r\ + \n :local ver \$1;\r\ + \n :local dot1 [:find \$ver \".\"];\r\ + \n :if (\$dot1 = -1) do={:return \$ver}\r\ + \n :local major [:pick \$ver 0 \$dot1];\r\ + \n :local rest [:pick \$ver (\$dot1 + 1) [:len \$ver]];\r\ + \n :local dot2 [:find \$rest \".\"];\r\ + \n :local minor \$rest;\r\ + \n :if (\$dot2 >= 0) do={:set minor [:pick \$rest 0 \$dot2]}\r\ + \n :return (\$major . \".\" . \$minor);\r\ + \n }\r\ + \n\r\ + \n# Compare the major and minor components of both version strings\r\ + \n:if ([\$extractMajorMinor \$ver1] = [\$extractMajorMinor \$ver2]) do={:r\ + eturn true}\r\ + \n :return false;\r\ + \n}\r\ + \n# Creates backups and returns array of names\r\ + \n# Possible arguments:\r\ + \n# \$1 - file name, without extension\r\ + \n# \$2 - password (optional)\r\ + \n# \$3 - sensitive data in config (optional, default: false)\r\ + \n# Example:\r\ + \n#:put [\$FuncCreateBackups \$backupName]\r\ + \n:local FuncCreateBackups do={\r\ + \n :local backupName [ :tostr \$1 ];\r\ + \n :local BackPass \$2;\r\ + \n :local BackSens \$3;\r\ + \n\r\ + \n #Script messages prefix\r\ + \n :local SMP \"BkpAndUpdate:\";\r\ + \n :local exitErrorMessage \"\$SMP script stopped due to an error. Please\ + \_check logs for more details.\";\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` started, input:\ + \_`\$backupName`\");\r\ + \n\r\ + \n # validate required parameter: backupName\r\ + \n :if ([:typeof \$backupName] != \"str\" or [:len \$backupName] = 0) do=\ + {\r\ + \n :log error \"\$SMP parameter 'backupName' is required and must be a \ + non-empty string\";\r\ + \n# :log warning \"\$SMP parameter '\\\$backupName' is required and mus\ + t be a non-empty string.\\nI set '\\\$backupName' to 'default-backup'\";\r\ + \n# :set \$backupName \"default-backup\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n\r\ + \n :local backupFileSys \"\$backupName.backup\";\r\ + \n :local backupFileConfig \"\$backupName.rsc\";\r\ + \n :local backupNames {\$backupFileSys;\$backupFileConfig};\r\ + \n\r\ + \n ## Perform system backup\r\ + \n :if ([:len \$BackPass] = 0) do={\r\ + \n :log info (\"\$SMP starting backup without password, backup name: `\ + \$backupName`\");\r\ + \n /system backup save dont-encrypt=yes name=\$backupName;\r\ + \n } else={\r\ + \n :log info (\"\$SMP starting backup with password, backup name: `\$ba\ + ckupName`\");\r\ + \n /system backup save password=\$BackPass name=\$backupName;\r\ + \n }\r\ + \n :log info (\"\$SMP system backup created: `\$backupFileSys`\");\r\ + \n ## Export config file\r\ + \n :if (\$BackSens = true) do={\r\ + \n :log info (\"\$SMP starting export config with sensitive data, backu\ + p name: `\$backupName`\");\r\ + \n # Since RouterOS v7 it needs to be explicitly set that we want to ex\ + port sensitive data\r\ + \n :if ([:pick [/system resource get version] 0 1] < 7) do={\r\ + \n :execute \"/export compact terse file=\$backupName\";\r\ + \n } else={\r\ + \n :execute \"/export compact show-sensitive terse file=\$backupName\ + \";\r\ + \n }\r\ + \n } else={\r\ + \n :log info (\"\$SMP starting export config without sensitive data, ba\ + ckup name: `\$backupName`\");\r\ + \n /export compact hide-sensitive terse file=\$backupName;\r\ + \n }\r\ + \n :log info (\"\$SMP Config export complete: `\$backupFileConfig`\");\r\ + \n :log info (\"\$SMP Waiting a little to ensure backup files are written\ + \");\r\ + \n :delay 20;\r\ + \n :if ([:len [/file find name=\$backupFileSys]] > 0) do={\r\ + \n :log info (\"\$SMP system backup file successfully saved to the file\ + \_system: `\$backupFileSys`\");\r\ + \n } else={\r\ + \n :log error (\"\$SMP system backup was not created, file does not exi\ + st: `\$backupFileSys`\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :if ([:len [/file find name=\$backupFileConfig]] > 0) do={\r\ + \n :log info (\"\$SMP config backup file successfully saved to the file\ + \_system: `\$backupFileConfig`\");\r\ + \n } else={\r\ + \n :log error (\"\$SMP config backup was not created, file does not exi\ + st: `\$backupFileConfig`\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :log info (\"\$SMP global function `FuncCreateBackups` finished. Creat\ + ed backups, system: `\$backupFileSys`, config: `\$backupFileConfig`\")\r\ + \n :return \$backupNames;\r\ + \n}\r\ + \n# Sends an email\r\ + \n# Parameters:\r\ + \n# \$1 - to (email address)\r\ + \n# \$2 - subject\r\ + \n# \$3 - body\r\ + \n# \$4 - file attachments (optional; pass \"\" if not needed)\r\ + \n#\r\ + \n# Example:\r\ + \n# \$FuncSendEmailSafe \"admin@domain.com\" \"Backup Done\" \"Backup comp\ + lete.\" \"backup1.backup\"\r\ + \n:local FuncSendEmailSafe do={\r\ + \n :global EmailGeneralTo;\r\ + \n :local emailSubject \$2;\r\ + \n :local emailBody \$3;\r\ + \n :local emailAttachments \$4;\r\ + \n :local SMP \"Bkp&Upd:\";\r\ + \n :local exitErrorMessage \"\$SMP script stopped due to an error. Please\ + \_check logs for more details.\";\r\ + \n :log info \"\$SMP Attempting to send email to `\$EmailGeneralTo`\";\r\ + \n # SAFETY: wait for any previously queued email to finish\r\ + \n :local waitTimeoutPre 60;\r\ + \n :local waitCounterPre 0;\r\ + \n :while (([/tool e-mail get last-status] = \"resolving-dns\" or [/tool \ + e-mail get last-status] = \"in-progress\")) do={\r\ + \n :if (\$waitCounterPre >= \$waitTimeoutPre) do={\r\ + \n :log error \"\$SMP Email send aborted: previous send did not compl\ + ete after \$waitTimeoutPre seconds\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :log info \"\$SMP Waiting for previous email to finish (status: \$[/\ + tool e-mail get last-status])...\";\r\ + \n :delay 1; \r\ + \n :set waitCounterPre (\$waitCounterPre + 1);\r\ + \n }\r\ + \n # Send the email\r\ + \n :do {\r\ + \n /tool e-mail send to=\$EmailGeneralTo subject=\$emailSubject body=\$\ + emailBody file=\$emailAttachments;\r\ + \n } on-error={\r\ + \n :log error \"\$SMP Email send command failed to execute. Check logs \ + and verify email settings.\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n # Wait for send status to change from \"in-progress\" / \"resolving-dn\ + s\"\r\ + \n :local waitTimeout 60;\r\ + \n :local waitCounter 0;\r\ + \n :local emailStatus \"\";\r\ + \n :log info \"\$SMP Waiting for email to be sent, timeout in `\$waitTime\ + out` seconds...\";\r\ + \n :while (\$waitCounter < \$waitTimeout) do={\r\ + \n :set emailStatus [/tool e-mail get last-status];\r\ + \n :if (\$emailStatus != \"in-progress\" and \$emailStatus != \"resolvi\ + ng-dns\") do={\r\ + \n :log info \"\$SMP Email send status received: \$emailStatus\";\r\ + \n # exit loop\r\ + \n :set waitCounter \$waitTimeout;\r\ + \n } else={:delay 1; :set waitCounter (\$waitCounter + 1)}\r\ + \n }\r\ + \n # Final decision based on last status\r\ + \n :if (\$emailStatus = \"succeeded\") do={\r\ + \n :log info \"\$SMP Email successfully sent to `\$EmailGeneralTo`\";\ + \r\ + \n } else={\r\ + \n :log error \"\$SMP Email failed to send. Status: `\$emailStatus`. Ch\ + eck logs for more details and verify email settings.\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n}\r\ + \n# Global variable to track current update step\r\ + \n# They need to be initialized here first to be available in the script\r\ + \n:global buGlobalVarTargetOsVersion;\r\ + \n:global buGlobalVarScriptStep;\r\ + \n:local scriptStep \$buGlobalVarScriptStep;\r\ + \n:do {/system script environment remove buGlobalVarScriptStep} on-error={\ + }\r\ + \n:if ([:len \$scriptStep] = 0) do={\r\ + \n :set scriptStep 1;\r\ + \n}\r\ + \n## ^^ FUNCTIONS ^^ ##\r\ + \n#\r\ + \n# Initial validation\r\ + \n## Check email settings\r\ + \n:if ([:len \$EmailGeneralTo] < 3) do={\r\ + \n :log error (\"\$SMP Parameter `\\\$EmailGeneralTo` is not set, or cont\ + ains invalid value. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Values will be defined later in the script\r\ + \n:local emailServer \"\";\r\ + \n:local emailFromAddress [/tool e-mail get from];\r\ + \n:log info \"\$SMP Validating email settings...\";\r\ + \n:do {\r\ + \n :set emailServer [/tool e-mail get server];\r\ + \n} on-error={\r\ + \n # This is a workaround for the RouterOS v7.12 and older versions\r\ + \n :set emailServer [/tool e-mail get address];\r\ + \n}\r\ + \n:if (\$emailServer = \"0.0.0.0\") do={\r\ + \n :log error (\"\$SMP Email server address is not correct: `\$emailServe\ + r`, check `Tools -> Email`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n:if ([:len \$emailFromAddress] < 3) do={\r\ + \n :log error (\"\$SMP Email configuration FROM address is not correct: `\ + \$emailFromAddress`, check `Tools -> Email`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Script mode validation\r\ + \n:if (\$BackupAndUpdateMode != \"backup\" and \$BackupAndUpdateMode != \"\ + osupdate\" and \$BackupAndUpdateMode != \"updateOnce\" and \$BackupAndUpda\ + teMode != \"osnotify\") do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$BackupAndUpdateMode` is not \ + set, or contains invalid value: `\$BackupAndUpdateMode`. Script stopped.\"\ + );\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Update channel validation\r\ + \n:if (\$updateChannel != \"stable\" and \$updateChannel != \"long-term\" \ + and \$updateChannel != \"testing\" and \$updateChannel != \"development\")\ + \_do={\r\ + \n :log error (\"\$SMP Script parameter `\\\$updateChannel` is not set, o\ + r contains invalid value: `\$updateChannel`. Script stopped.\");\r\ + \n :error \$exitErrorMessage;\r\ + \n}\r\ + \n# Verify if script is set to install patch updates and if the update cha\ + nnel is valid\r\ + \n:if ((\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \"\ + updateOnce\") and \$installOnlyPatchUpdates = true) do={\r\ + \n :if (\$updateChannel != \"stable\" and \$updateChannel != \"long-term\ + \") do={\r\ + \n :log error (\"\$SMP Patch-only updates enabled, but update channel `\ + \$updateChannel` is invalid. Only `stable` and `long-term` are supported. \ + Script stopped\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n :local susRunningOsChannel [\$FuncGetRunningOsChannel];\r\ + \n :if (\$susRunningOsChannel != \"stable\" and \$susRunningOsChannel != \ + \"long-term\") do={\r\ + \n :log error (\"\$SMP Script is set to install only patch updates, but\ + \_the installed RouterOS version is not from `stable` or `long-term` chann\ + el: `\$susRunningOsChannel`. Script stopped\");\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n}\r\ + \n#\r\ + \n:global MiniDateTimeStamp;\r\ + \n:local MDTSFix [\$MiniDateTimeStamp];\r\ + \n:global Identity;\r\ + \n:global IdentityShort;\r\ + \n:global devBoardName;\r\ + \n:global isCHR;\r\ + \n:global devRbModel;\r\ + \n:global devRbSerialNumber;\r\ + \n:global devRbCurrentFw;\r\ + \n:global devRbUpgradeFw;\r\ + \n\r\ + \n:local rawTime [/system clock get time];\r\ + \n:local rawDate [/system clock get date];\r\ + \n\r\ + \n:local runningOsChannel [\$FuncGetRunningOsChannel];\r\ + \n:local runningOsVersion [\$FuncGetRunningOsVersion];\r\ + \n:local deviceOsVerAndChannelRunning [/system resource get version];\r\ + \n\r\ + \n:local backupNameTemplate (\"backup_v\" . \$runningOsVersion . \"_\"\ + \_. \$runningOsChannel . \"_\" . \$MDTSFix);\r\ + \n:local backupNameBeforeUpdate (\$backupNameTemplate . \"_before_update\"\ + );\r\ + \n:local backupNameAfterUpdate (\$backupNameTemplate . \"_after_update\")\ + ;\r\ + \n\r\ + \n## Email body template\r\ + \n:local mailSubjectPrefix \"\$SMP Device - `\$IdentityShort`\";\r\ + \n:local mailBodyCopyright \"Mikrotik RouterOS automatic backup & update \ + (ver. \$scriptVersion) \\nhttps://github.com/beeyev/Mikrotik-RouterOS-auto\ + matic-backup-and-update\";\r\ + \n:local changelogUrl \"Check RouterOS changelog: https://mikrotik.com\ + /download/changelogs/\";\r\ + \n:local mailBodyDeviceInfo \"\";\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"Device information:\")\ + ;\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\n-------------------\ + --\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nName: \$Identity\")\ + ;\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nModel: \$devRbModel\ + \");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBoard: \$devBoardNa\ + me\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nSerial number: \$de\ + vRbSerialNumber\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nRouterOS version: v\ + \$deviceOsVerAndChannelRunning\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nBuild time: \$[/sys\ + tem resource get build-time]\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nRouterboard FW: \$d\ + evRbCurrentFw\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nDevice date-time: \ + \$rawDate \$rawTime (\$[/system clock get time-zone-name ])\");\r\ + \n:set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nUptime: \$[/system \ + resource get uptime]\");\r\ + \n\r\ + \n:local mailAttachments [:toarray \"\"];\r\ + \n:if (\$scriptStep = 1 or \$scriptStep = 3) do={\r\ + \n :if (\$scriptStep = 3) do={\r\ + \n :log info (\"\$SMP Waiting for one minute before continuing to the f\ + inal step.\");\r\ + \n :delay 1m;\r\ + \n }\r\ + \n## IP address detection\r\ + \n :local publicIpAddress \"not-detected\";\r\ + \n :if ([\$includePubIpAddr] = true) do={\r\ + \n :set publicIpAddress [ \$getPubIp \$ipAddrDefault ];\r\ + \n :if (([\$publicIpAddress] = false) || ([\$publicIpAddress] = \"not\ + -detected\")) do={\r\ + \n :log warning \"\$SMP Failed to detect public IP using default se\ + rvice: `\$ipAddrDefault`\";\r\ + \n :log warning \"\$SMP Trying fallback service: `\$ipAddrFallback`\ + \";\r\ + \n :set publicIpAddress [ \$getPubIp \$ipAddrFallback ];\r\ + \n :if (([\$publicIpAddress] = false) || ([\$publicIpAddress] = \"n\ + ot-detected\")) do={\r\ + \n :log warning \"\$SMP Could not detect public IP address using \ + fallback detection service: `\$ipAddrFallback`\";\r\ + \n }\r\ + \n }\r\ + \n :set publicIpAddress ([ :pick \$publicIpAddress 0 15]);\r\ + \n :set mailBodyDeviceInfo (\$mailBodyDeviceInfo . \"\\nPublic IP addre\ + ss: \$publicIpAddress\");\r\ + \n :log info \"\$SMP Public IP address detected: `\$publicIpAddress`\";\ + \r\ + \n } \r\ + \n}\r\ + \n\r\ + \n## STEP 1: Create backups, check for new RouterOS, and send email\r\ + \n## Steps 2\C3\A23 run only if auto-update is enabled and a new version i\ + s available\r\ + \n:if (\$scriptStep = 1) do={\r\ + \n :global BackupPassword;\r\ + \n :global BackupSens;\r\ + \n :global ROsVerAvail 0.0.0;\r\ + \n :global PkgUpdStatus \"undefined\";\r\ + \n :local isNewOsUpdateAvailable false;\r\ + \n :local isLatestOsAlreadyInstalled true;\r\ + \n :local isOsNeedsToBeUpdated false;\r\ + \n :local isUpdateCheckSucceeded false;\r\ + \n :local isEmailNeedsToBeSent false;\r\ + \n :local mailSubjectPartAction \"\";\r\ + \n :local mailPtBodyAction \"\";\r\ + \n :local mailPtSubjectBackup \"\";\r\ + \n :local mailPtBodyBackup \"\";\r\ + \n # Checking for new version\r\ + \n :if (\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \ + \"osnotify\" or \$BackupAndUpdateMode = \"updateOnce\") do={\r\ + \n :log info (\"\$SMP Setting update channel to `\$updateChannel`\");\r\ + \n /system/package/update/set channel=\$updateChannel;\r\ + \n :log info (\"\$SMP Checking for new RouterOS version. Current instal\ + led version is: `\$runningOsVersion`\");\r\ + \n /system/package/update check-for-updates;\r\ + \n :delay 5s;\r\ + \n :set PkgUpdStatus [ /system/package/update/get status ];\r\ + \n :if (\$PkgUpdStatus = \"New version is available\") do={\r\ + \n :set ROsVerAvail [/system/package/update/get latest-version];\r\ + \n }\r\ + \n\r\ + \n :if (\$PkgUpdStatus = \"New version is available\") do={\r\ + \n :log info (\"\$SMP New RouterOS version is available: `\$ROsVerAva\ + il`\");\r\ + \n :set isNewOsUpdateAvailable true;\r\ + \n :set isLatestOsAlreadyInstalled false;\r\ + \n :set isUpdateCheckSucceeded true;\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :set mailSubjectPartAction \"New RouterOS available\";\r\ + \n :set mailPtBodyAction \"New RouterOS version is available, curren\ + t version: v\$runningOsVersion, new version: v\$ROsVerAvail. \\n\$changelo\ + gUrl\";\r\ + \n } else={\r\ + \n :if (\$PkgUpdStatus = \"System is already up to date\") do={\r\ + \n :log info (\"\$SMP No new RouterOS version is available, the lat\ + est version is already installed: `v\$runningOsVersion`\");\r\ + \n :set isUpdateCheckSucceeded true;\r\ + \n :set mailSubjectPartAction \"No os update available\";\r\ + \n :set mailPtBodyAction \"No new RouterOS version is available, t\ + he latest version is already installed: `v\$runningOsVersion`\";\r\ + \n } else={\r\ + \n :log error (\"\$SMP Failed to check for new RouterOS version. Pa\ + ckage check status: `\$PkgUpdStatus`\");\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :set mailSubjectPartAction \"Error unable to check new os versio\ + n\";\r\ + \n :set mailPtBodyAction \"An error occurred while checking for a \ + new RouterOS version.\\nStatus returned: `\$PkgUpdStatus`\\n\\nPlease revi\ + ew the logs on the device for more details and verify internet connectivit\ + y.\";\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n # Checking if the script needs to install new os version\r\ + \n :if ((\$BackupAndUpdateMode = \"osupdate\" or \$BackupAndUpdateMode = \ + \"updateOnce\") and \$isNewOsUpdateAvailable = true) do={\r\ + \n :if (\$installOnlyPatchUpdates = true) do={\r\ + \n :if ([\$FuncIsPatchUpdateOnly \$runningOsVersion \$ROsVerAvail] = \ + true) do={\r\ + \n :log info \"\$SMP New RouterOS version is available, and it is a\ + \_patch update. Current version: v\$runningOsVersion, new version: v\$ROsV\ + erAvail\";\r\ + \n :set isOsNeedsToBeUpdated true;\r\ + \n } else={\r\ + \n :log info \"\$SMP The script will not install this update, becau\ + se it is not a patch update. Current version: v\$runningOsVersion, new ver\ + sion: v\$ROsVerAvail\";\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nThis update wil\ + l not be installed, because the script is set to install only patch update\ + s.\");\r\ + \n }\r\ + \n } else={\r\ + \n :set isOsNeedsToBeUpdated true;\r\ + \n }\r\ + \n }\r\ + \n # Checking If the script needs to create a backup\r\ + \n :if (\$forceBackup = true or \$BackupAndUpdateMode = \"backup\" or \$i\ + sOsNeedsToBeUpdated = true) do={\r\ + \n :log info (\"\$SMP Starting backup process.\");\r\ + \n :set isEmailNeedsToBeSent true;\r\ + \n :local backupName \$backupNameTemplate;\r\ + \n # This means it's the first step where we create a backup before the\ + \_update process\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\r\ + \n :set backupName \$backupNameBeforeUpdate;\r\ + \n #Email body if the purpose of the script is to update the device\r\ + \n :set mailSubjectPartAction \"Update preparation\";\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nThe update proces\ + s for device '\$Identity' is scheduled to upgrade RouterOS from version v.\ + \$runningOsVersion to version v.\$ROsVerAvail (Update channel: \$updateCha\ + nnel)\");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nPlease note: The \ + update will proceed only after a successful backup.\");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nA final report wi\ + th detailed information will be sent once the update process is completed.\ + \");\r\ + \n :set mailPtBodyAction (\$mailPtBodyAction . \"\\nIf you do not rec\ + eive a second email within the next 10 minutes, there may be an issue. Ple\ + ase check your device logs for further information.\");\r\ + \n }\r\ + \n :do {\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupName \$BackupPas\ + sword \$BackupSens];\r\ + \n :set mailPtSubjectBackup \"Backup created\";\r\ + \n :set mailPtBodyBackup \"System backups have been successfully crea\ + ted and attached to this email.\";\r\ + \n } on-error={\r\ + \n :set isOsNeedsToBeUpdated false;\r\ + \n :set mailPtSubjectBackup \"Backup failed\";\r\ + \n :set mailPtBodyBackup \"The script failed to create backups. Pleas\ + e check device logs for more details.\";\r\ + \n :log warning \"\$SMP Backup creation failed. Update process will b\ + e canceled if automatic update is enabled\";\r\ + \n }\r\ + \n }\r\ + \n :if (\$isEmailNeedsToBeSent = true) do={\r\ + \n :log info \"\$SMP Preparing to send email...\";\r\ + \n :local mailStep1Subject \$mailSubjectPrefix;\r\ + \n :local mailStep1Body \"\";\r\ + \n # subject\r\ + \n :if (\$mailSubjectPartAction != \"\") do={:set mailStep1Subject (\$\ + mailStep1Subject . \" - \" . \$mailSubjectPartAction)}\r\ + \n :if (\$mailPtSubjectBackup != \"\") do={:set mailStep1Subject (\$ma\ + ilStep1Subject . \" - \" . \$mailPtSubjectBackup)}\r\ + \n # body\r\ + \n :if (\$mailPtBodyAction != \"\") do={:set mailStep1Body (\$mailStep1\ + Body . \$mailPtBodyAction . \"\\n\\n\")}\r\ + \n :if (\$mailPtBodyBackup != \"\") do={:set mailStep1Body (\$mailStep1\ + Body . \$mailPtBodyBackup . \"\\n\\n\")}\r\ + \n :set mailStep1Body (\$mailStep1Body . \$mailBodyDeviceInfo . \"\\n\\\ + n\" . \$mailBodyCopyright);\r\ + \n # Send email with backups\r\ + \n :do {\$FuncSendEmailSafe \$EmailGeneralTo \$mailStep1Subject \$mailS\ + tep1Body \$mailAttachments} on-error={\r\ + \n :set isOsNeedsToBeUpdated false;\r\ + \n :log error \"\$SMP The script will not proceed with the update pro\ + cess, because the email was not sent.\";\r\ + \n }\r\ + \n }\r\ + \n :if ([:len \$mailAttachments] > 0) do={\r\ + \n :log info \"\$SMP Cleaning up backup files from the file system...\"\ + ;\r\ + \n /file remove \$mailAttachments;\r\ + \n :delay 2s;\r\ + \n }\r\ + \n :if (\$isOsNeedsToBeUpdated = true) do={\r\ + \n :global OldMode;\r\ + \n :log info \"\$SMP everything is ready to install new RouterOS, going\ + \_to start the update process and reboot the device.\";\r\ + \n :do {\r\ + \n :local nextStep 2;\r\ + \n :if (\$isCloudHostedRouter = true) do={\r\ + \n :log info \"\$SMP The device is a cloud hosted router, the secon\ + d step updating the Routerboard firmware will be skipped.\";\r\ + \n :set nextStep 3;\r\ + \n }\r\ + \n :local scheduledCommand (\":delay 5s; /system/scheduler remove BKP\ + UPD-NEXT-BOOT-TASK; \\\r\ + \n :global buGlobalVarScriptStep \$nextStep; :global buGlobalVarTarg\ + etOsVersion \\\"\$ROsVerAvail\\\"; \\\r\ + \n :delay 10s; /system/script { run \$ScriptName; }\");\r\ + \n /system/scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\$schedu\ + ledCommand start-time=startup interval=0;\r\ + \n /system/package/update install;\r\ + \n } on-error={\r\ + \n # Failed to install new os version, remove the task and variables\ + \r\ + \n :do {\r\ + \n /system/scheduler remove BKPUPD-NEXT-BOOT-TASK;\r\ + \n :set BackupAndUpdateMode \$OldMode;\r\ + \n :delay 2;\r\ + \n :do {\r\ + \n /system/script/environment remove [find name=\"buGlobalVarTarg\ + etOsVersion\"];\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \"ERROR Remove\ + \_global variable \\\$buGlobalVarTargetOsVersion failed!\");\r\ + \n }\r\ + \n :if ([ :len \$OldMode ] > 0) do={\r\ + \n :do {\r\ + \n /system/script/environment remove [find name=\"OldMode\"];\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Rem\ + ove global variable \\\$OldMode failed!\");\r\ + \n }\r\ + \n }\r\ + \n } on-error={\r\ + \n :log error \"\$SMP Failed to install new RouterOS version. Pleas\ + e check device logs for more details. \\\r\ + \n \\nAnd Failed to remove task and variables! Check it!\";\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Remove \ + task and global variables!\");\r\ + \n }\r\ + \n :log error \"\$SMP Failed to install new RouterOS version. Please \ + check device logs for more details.\";\r\ + \n :local mailUpdateErrorSubject (\$mailSubjectPrefix . \" - Update f\ + ailed\");\r\ + \n :local mailUpdateErrorBody \"The script was unable to install new \ + RouterOS version. Please check device logs for more details.\";\r\ + \n # Send email with error\r\ + \n \$FuncSendEmailSafe \$EmailGeneralTo \$mailUpdateErrorSubject \$ma\ + ilUpdateErrorBody \"\";\r\ + \n :error \$exitErrorMessage;\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n## STEP 2: (Post-reboot) Upgrade RouterBOARD firmware\r\ + \n## Runs only if auto-update is enabled and a new RouterOS version was fo\ + und\r\ + \n:if (\$scriptStep = 2) do={\r\ + \n :log info \"\$SMP The script is in the second step, updating Routerboa\ + rd firmware.\";\r\ + \n :log info \"\$SMP Upgrading routerboard firmware from v.\$deviceRbCurr\ + entFw to v.\$deviceRbUpgradeFw\";\r\ + \n /system routerboard upgrade;\r\ + \n :delay 2;\r\ + \n :log info \"\$SMP routerboard upgrade process was completed, going to \ + reboot in a moment!\";\r\ + \n ## Set task to send final report on the next boot\r\ + \n /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=\":delay 2; \ + :global buGlobalVarScriptStep 3; \\\r\ + \n :global buGlobalVarTargetOsVersion \\\"\$buGlobalVarTargetOsVersion\\\ + \"; :delay 10s; /system/script { run \$ScriptName; }; \\ \r\ + \n :delay 5s; /system/scheduler remove BKPUPD-NEXT-BOOT-TASK;\" start-ti\ + me=startup interval=0;\r\ + \n :delay 2;\r\ + \n /system reboot;\r\ + \n}\r\ + \n\r\ + \n## STEP 3: Final report (after second reboot, with delay).\r\ + \n## Runs only if auto-update is enabled and a new RouterOS version was fo\ + und.\r\ + \n:if (\$scriptStep = 3) do={\r\ + \n :log info (\"\$SMP The script is in the third step, sending final repo\ + rt.\");\r\ + \n :local targetOsVersion \$buGlobalVarTargetOsVersion;\r\ + \n :do {\r\ + \n /system/script/environment remove [find name=\"buGlobalVarTargetOsVe\ + rsion\"];\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR Remove glob\ + al variable \\\$buGlobalVarTargetOsVersion failed!\");\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$OldMode ] != 0) do={\r\ + \n :do {\r\ + \n /system/script/environment remove [find name=\"OldMode\"];\r\ + \n } on-error={\r\ + \n :set \$mailSubjectPrefix (\$mailSubjectPrefix . \" ERROR! Remove\ + \_global variable \\\$OldMode failed!\");\r\ + \n }\r\ + \n }\r\ + \n :if ([:len \$targetOsVersion] = 0) do={\r\ + \n :log warning \"\$SMP Something is wrong, the script was unable to ge\ + t the target updated OS version from the global variable.\";\r\ + \n }\r\ + \n :local mailStep3Subject \$mailSubjectPrefix;\r\ + \n :local mailStep3Body \"\";\r\ + \n :if (\$targetOsVersion = \$runningOsVersion) do={\r\ + \n :log info \"\$SMP Successfully verified new RouterOS version: target\ + : `\$targetOsVersion`, current: `\$runningOsVersion`\";\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update completed - \ + Backup created\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"RouterOS and routerboard upg\ + rade process was completed\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"\\nNew RouterOS version: v.\ + \$targetOsVersion, routerboard firmware: v.\$deviceRbCurrentFw\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"\\n\$changelogUrl\\nBackups \ + of the upgraded system are in the attachment of this email.\\n\\n\$mailBod\ + yDeviceInfo\\n\\n\$mailBodyCopyright\");\r\ + \n :set mailAttachments [\$FuncCreateBackups \$backupNameAfterUpdate \$\ + BackupPassword \$BackupSens];\r\ + \n } else={\r\ + \n :log error \"\$SMP Failed to verify new RouterOS version: target: `\ + \$targetOsVersion`, current: `\$runningOsVersion`\";\r\ + \n :set mailStep3Subject (\$mailStep3Subject . \" - Update failed\");\r\ + \n :set mailStep3Body (\$mailStep3Body . \"The script was unable to ver\ + ify that the new RouterOS version was installed, target version: `\$target\ + OsVersion`, current version: `\$runningOsVersion`\\nCheck device logs for \ + more details.\\n\\n\$mailBodyDeviceInfo\\n\\n\$mailBodyCopyright\");\r\ + \n }\r\ + \n \$FuncSendEmailSafe \$EmailGeneralTo \$mailStep3Subject \$mailStep3Bod\ + y \$mailAttachments;\r\ + \n :if ([:len \$mailAttachments] > 0) do={\r\ + \n :log info \"\$SMP Cleaning up backup files from the file system...\"\ + ;\r\ + \n /file remove \$mailAttachments;\r\ + \n :delay 2;\r\ + \n }\r\ + \n :log info \"\$SMP Final report email sent successfully, and the script\ + \_has finished.\";\r\ + \n}\r\ + \n:log info \"\$SMP the script has finished, script step: `\$scriptStep` \ + \\n\\n\";\r\ + \n} do={\r\ + \n :global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err;\r\ + \n}\r\ + \n" +add dont-require-permissions=no name=fetchCustom owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + root.mnuc.backuper\r\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n/tool/fetch mode=https url=(\$GlobalScriptsUrl . \".bak/mikro_bak_rsa@10\ + .30.30.86.pub\") dst-path=\"/mikro_bak_rsa@10.30.30.86.pub\";\r\ + \n#/usb1/mikro_bak_rsa.pub" +add dont-require-permissions=no name=testSetup owner=vados policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + \r\ + \n:local newFolder \"tmpfs/scripts\";\r\ + \n\r\ + \n:local errorMsg;\r\ + \n\r\ + \n# :while (([:pick \$newFolder ([:len \$newFolder] - 1)]) = \"/\") do=\ + {\r\ + \n# :set newFolder [:pick \$newFolder 0 ([:len \$newFolder] - 1)]\r\ + \n# }\r\ + \n\r\ + \n# :while ([:pick \$newFolder 0] = \"/\") do={\r\ + \n# :set newFolder [:pick \$newFolder 1 [:len \$newFolder]]\r\ + \n# }\r\ + \n\r\ + \n\r\ + \n:if ([/file find name=\$newFolder] != \"\") do={\r\ + \n :set errorMsg (\$newFolder . \" already exists.\");\r\ + \n :log warning (\$0 . \" mkdir: \" . \$errorMsg);\r\ + \n :return \$newFolder;\r\ + \n}\r\ + \n\r\ + \n\r\ + \n#:put [ :len [ /file/find where name=\$Path ] ];\r\ + \n\r\ + \n:put [:len \$Path];\r\ + \n:put [ :pick \$Path ];\r\ + \n:put [ :pick \$Path ([ :len \$Path ] - 1) ];\r\ + \n\r\ + \n:if (\$Path != false) do={\r\ + \n:put \"TRUE\";\r\ + \n}" +add comment="Script for setup routeros scrypt system" \ + dont-require-permissions=no name=AM-setup owner=AM-setup policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-setup\r\ + \n# Script comment: Script for setup routeros scrypt system\r\ + \n# Copyright (c) 2005-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.19\r\ + \n# requires device-mode, fetch\r\ + \n#\r\ + \n# :local ScriptName [ :jobname ];\r\ + \n# Additional commands:\r\ + \n#\r\ + \n# For scripts:\r\ + \n#:global NewScript \"script_name\"; \r\ + \n#:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n#/system/script/add name=\$NewScript owner=\$NewScript source=([/tool/fe\ + tch check-certificate=yes-without-crl (\$GlobalScriptsUrl . \$ScriptAdd . \ + \".rsc\") output=user as-value ]->\"data\");\r\ + \n#:global NewScript (a);\r\ + \n#:global ScriptInstallUpdate; \$ScriptInstallUpdate;\r\ + \n#\r\ + \n# For run from terminal:\r\ + \n#/system/script/add name=AM-setup owner=AM-Setup source=([/tool/fetch ch\ + eck-certificate=yes-without-crl \"https://ros.vados.ru/AM-setup.rsc\" outp\ + ut=user as-value ]->\"data\");\r\ + \n#\r\ + \n#:foreach Script in=[ /system/script/find where source~\"^#!rsc by Vados\ + \" ] do={\r\ + \n# if (\$ScriptUpdatesCRLF = true) do={\r\ + \n# /system/script/set source=[ :tocrlf [ get \$Script source ] ] \$Scri\ + pt;\r\ + \n# } else={\r\ + \n# /system/script/set source=[ :tolf [ get \$Script source ] ] \$Script\ + ;\r\ + \n# }\r\ + \n#}\r\ + \n# /import verbose=no \$ScriptName\r\ + \n:global SetupScript [ :jobname ];\r\ + \n:global RemoveMe;\r\ + \n:global InitSetup true;\r\ + \n\r\ + \n{\r\ + \n :local RmMe [:tobool \$1];\r\ + \n\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global SetupScript;\r\ + \n :global RemoveMe;\r\ + \n :global InitSetup;\r\ + \n \r\ + \n :local CertCommonName \"Root YE\";\r\ + \n :local CertFileName \"Root-YE.pem\";\r\ + \n :local CertFingerprint \"e14ffcad5b0025731006caa43a121a22d8e9700f4fb9c\ + f852f02a708aa5d5666\";\r\ + \n\r\ + \n :if (\$SetupScript = false) do={\r\ + \n :global SetupScript \"AM-setup\";\r\ + \n }\r\ + \n :if (\$RemoveMe != false) do={\r\ + \n :set RemoveMe true;\r\ + \n }\r\ + \n \r\ + \n :log warning (\"\\n\\\$GlobalScriptsUrl = \" . \$GlobalScriptsUrl);\r\ + \n \r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if (!(((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" || \ + \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \"fetch\" || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\r\ + \n [ :len [ /certificate/builtin/find where common-name=\$CertComm\ + onName ] ] > 0)) do={\r\ + \n :put \"Importing certificate...\";\r\ + \n \r\ + \n /tool/fetch (\$GlobalScriptsUrl . \"certs/\" . \$CertFileName) dst-p\ + ath=\$CertFileName as-value;\r\ + \n :delay 1s;\r\ + \n /certificate/import file-name=\$CertFileName passphrase=\"\";\r\ + \n :if ([ :len [ /certificate/find where fingerprint=\$CertFingerprint \ + ] ] != 1) do={\r\ + \n :error \"Something is wrong with your certificates!\";\r\ + \n };\r\ + \n :delay 1s;\r\ + \n };\r\ + \n :put \"Renaming AM-GlobalConfig, if exists...\";\r\ + \n :local ConfOldName (\"AM-GlobalConfig-\" . [ /system/clock/get date ] \ + . [ /system/clock/get time ] . \".rsc\");\r\ + \n /system/script/set name=\$ConfOldName [ find where name=\"AM-GlobalCon\ + fig\" ];\r\ + \n :foreach Script in={ \"AM-GlobalConfig\"; \"AM-GlobalFunc\" } do={\r\ + \n :put \"Installing \$Script...\";\r\ + \n /system/script/remove [ find where name=\$Script ];\r\ + \n /system/script/add name=\$Script owner=\$Script source=([ /tool/fetc\ + h check-certificate=yes-without-crl url=(\$GlobalScriptsUrl . \$Script . \ + \".rsc\") output=user as-value ]->\"data\");\r\ + \n \r\ + \n /system/script run \$Script;\r\ + \n :delay 2s;\r\ + \n };\r\ + \n\r\ + \n :if ([ :len [ /certificate/find where fingerprint=\$CertFingerprint ] \ + ] > 0) do={\r\ + \n :put \"Renaming certificate by its common-name...\";\r\ + \n :global CertificateNameByCN;\r\ + \n \$CertificateNameByCN \$CertFingerprint;\r\ + \n };\r\ + \n\r\ + \n :if (\$RemoveMe = true) do={\r\ + \n :put \"Add Scheduler for remove this script...\";\r\ + \n :local OnEvent (\":delay 30s;\\n\" . \"/system/script/remove [ find \ + where name=\\\"\$SetupScript\\\" ];\\n\" . \\\r\ + \n \":delay 2s;\\n\" . \"/system/scheduler/remove [ find where name=\\\ + \"_RemoveSetup\\\" ];\\n\" . \\\r\ + \n \":delay 2s;\\n\" . \":global RemoveMe (a);\\n\");\r\ + \n :local Comment \"_RemoveSetup tmp scheduler for remove setup script.\ + \";\r\ + \n /system/scheduler/add name=\"_RemoveSetup\" comment=\$Comment start-\ + time=startup interval=30s on-event=\$OnEvent;\r\ + \n :log warning (\$0 . \"\\nI've done everything, and now I'm tired..\ + . I'm leaving.\\nThere will be an error below, this is normal.\");\r\ + \n }\r\ + \n :delay 1;\r\ + \n :put \"Loading configuration and functions...\";\r\ + \n :do { \r\ + \n :global ScriptInstallUpdate; \$ScriptInstallUpdate;\r\ + \n :put \"Normal removing temporary global environment variables.\";\ + \r\ + \n :global SetupScript (a);\r\ + \n :global InitSetup (a);\r\ + \n :global RemoveMe (a);\r\ + \n } on-error={\r\ + \n :local ErrorMsg \"Loading configuration and functions error!\";\r\ + \n :log error (\$0 . \$ErrorMsg . :error);\r\ + \n :error \$ErrorMsg;\r\ + \n }\r\ + \n}\r\ + \n" +add comment="Global Config Script" dont-require-permissions=no name=\ + AM-GlobalConfig owner=AM-GlobalConfig policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-GlobalConfig\r\ + \n# Script comment: Global Config Script\r\ + \n# Copyright (c) 2007-2026 Vados \r\ + \n#\r\ + \n#\r\ + \n# Warning: Dont touch this line!\r\ + \n:global GlobalConfReady false;\r\ + \n# \\/ start edit\r\ + \n#\r\ + \n# Global Url For Amster scripts to fetch\r\ + \n# most be defined in Amster-Init-Script\r\ + \n:global GlobalScriptsUrl \"https://ros.vados.ru/\";\r\ + \n:global ScriptsUrlSuffix \"\";\r\ + \n:global CheckSumsVerify true;\r\ + \n\r\ + \n:global ScriptUpdatesCRLF true;\r\ + \n:global CommentsInScripts true;\r\ + \n\r\ + \n# Debug output:\r\ + \n:global PrintDebug true; #false;\r\ + \n\r\ + \n# Debug output for specific script:\r\ + \n:set (\$PrintDebugOverride->\"AM-GlobalFunc\") true;\r\ + \n\r\ + \n# Debug logs\r\ + \n# Other actions (disk, email, remote or support) can be used as well.\r\ + \n# I do not recommend using echo - use debug output instead (https://rsc.\ + eworm.de/main/DEBUG.html#debug-output).\r\ + \n#/system/logging/add topics=script,debug action=memory;\r\ + \n\r\ + \n:global MikrotikUpgradeUrl \"https://upgrade.mikrotik.com/routeros\";\r\ + \n:global Domain \"WORKGROUP\";\r\ + \n\r\ + \n:global EmailGeneralTo \"vados@vados.ru\"; #:global EmailGeneralCc \"\";\ + \r\ + \n\r\ + \n# \$ScriptInstallUpdate mod/notification-telegram\r\ + \n:global TelegramTokenId \"8390868324:AAG3_d1hfVR2M6pGAgDK6ZvhOe4J97nyopw\ + \";\r\ + \n:global TelegramChatId \"-1003631309063\"; #\"454403445\";\r\ + \n# Use this to send notifications to a specific topic in group.\r\ + \n#:global TelegramThreadId \"30\";\r\ + \n# Using telegram-chat you have to define trusted chat ids (not group ids\ + !)\r\ + \n# or user names. Groups allow to chat with devices simultaneously.\r\ + \n:global TelegramChatIdsTrusted {\r\ + \n \"454403445\"\r\ + \n \"-1003631309063\"\r\ + \n \"-5124482807\"\r\ + \n \"1087968824\";\r\ + \n};\r\ + \n# \"-5294757340\";\r\ + \n\r\ + \n:global TelegramChatGroups \"(all)\";\r\ + \n#:global TelegramChatGroups \"(all|home|office)\";\r\ + \n\r\ + \n:global NotificationFunctions {\r\ + \n \"email\"\r\ + \n \"telegram\";\r\ + \n};\r\ + \n# Toggle this to disable symbols in notifications.\r\ + \n:global NotificationsWithSymbols true;\r\ + \n:global TerminalColorOutput true;\r\ + \n\r\ + \n:global DetectIpAddrDefault \"https://ipv4.mikrotik.ovh/\";\r\ + \n:global DetectIpAddrFallback \"https://api.ipify.org/\";\r\ + \n\r\ + \n## Update channel. Possible values: stable, long-term, testing, developm\ + ent\r\ + \n:global updateChannel \"stable\";\r\ + \n\r\ + \n# Script BackupAndUpdate mode, possible values: backup,\r\ + \n# updateOnce - Update once as \"osnotify\" and back \"osnotify\" mode (i\ + n terminal run :set OSUpdateMode updateOnce; \$OSUpdateMode;). \r\ + \n# osupdate - Update if available and creates backups before/after update\ + \_(ignores `forceBackup`)\r\ + \n# Set `forceBackup` to true to always create backups, even\ + \_without updates\r\ + \n# osnotify - Set `forceBackup` to always create backups on every run\r\ + \n:global DevInfoIncludeIP true;\r\ + \n:global BackupAndUpdateMode \"osnotify\"\r\ + \n\r\ + \n:global forceBackup true;\r\ + \n:global BackupPassword \"Peskar55\";\r\ + \n\r\ + \n# SFTP Backup Variables\r\ + \n:global BackupRandomDelay 0;\r\ + \n:global BackupUploadUrl \"sftp://amster.vados.ru/upload/\";\r\ + \n:global BackupUploadUser \$Identity;\r\ + \n:global BackupUploadPass \"root.mnuc.backuper\";\r\ + \n\r\ + \n:global BackupSendBinary true;\r\ + \n:global BackupSendExport true;\r\ + \n:global BackupSendGlobalConfig true;\r\ + \n:global BackupSendScripts true;\r\ + \n\r\ + \n# Remove local file after uploading if no errors\r\ + \n:global BackupRmLocal true;\r\ + \n\r\ + \n# Local Backups Root Directory \r\ + \n:global BackupLocalRoot \"tmpfs\";\r\ + \n\r\ + \n# Encrypt Backup\r\ + \n:global BackupEncrypt true;\r\ + \n\r\ + \n# Sensitive information in Backups\r\ + \n:global BackupSens true;\r\ + \n\r\ + \n# User Export\r\ + \n:global BackupUser true;\r\ + \n\r\ + \n# License Export (not for CHR, will silently skip)\r\ + \n:global BackupLicense false;\r\ + \n\r\ + \n# SSH Keys\r\ + \n:global BackupSshKeys true;\r\ + \n\r\ + \n# Certificate Export\r\ + \n:global BackupCerts true;\r\ + \n\r\ + \n# Certificate Password\r\ + \n:global BackupCertsPasswd \"\";\r\ + \n\r\ + \n# User-Manager Export\r\ + \n:global BackupUserMan false;\r\ + \n\r\ + \n# The Dude Export\r\ + \n:global BackupDude false;\r\ + \n\r\ + \n# User Files to export, comma-separated string or array of strings\r\ + \n# User Files are not removed on backup\r\ + \n# Any directory paths will be removed (/ -> _) on remote file\r\ + \n# Nonpresent files are silently skipped\r\ + \n:global BackupUserList \"autosupout.rif,autosupout.old.rif\";\r\ + \n\r\ + \n# load functions and any custom scripts\r\ + \n# [ /system/script/find where name=\"global-config-ov\ + erlay\" ], \\\r\ + \n# [ /system/script/find where name~\"^global-config-o\ + verlay\\\\.d/.\" ]\r\ + \n#\r\ + \n#:foreach Script in=([ /system/script/find where name=\"Amster-TGglobalV\ + ars\" ], [ /system/script/find where name=\"Amster-TGglobalFunc\" ]) do={\ + \r\ + \n# :onerror Err {\r\ + \n# /system/script/run \$Script;\r\ + \n# } do={\r\ + \n# :log error (\"Loading configuration from configs and functions\"\ + \_. [ /system/script/get \$Script name ] . \" failed: \" . \$Err);\r\ + \n# }\r\ + \n#}\r\ + \n#:log info \"\\ndevMode = \$devMode\";\r\ + \n#:log info \"\\ndevRunning = \$devRunning\";\r\ + \n:foreach Script in=[ /system/script/find where source~\"^#!rsc by Vados \ + GlobalRUN\\r\?\\n\" ] do={\r\ + \n :onerror Err {\r\ + \n /system/script/run \$Script;\r\ + \n } do={\r\ + \n :log error (\"Run script \" . [ /system/script/get \$Script name \ + ] . \" width marker \\\" GlobalRUN\\\" failed: \" . \$Err);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# signal we are ready\r\ + \n:set GlobalConfReady true;\r\ + \n:while (\$GlobalConfReady != true) do={:delay 500ms};\r\ + \n" +add comment="Global functions" dont-require-permissions=no name=AM-GlobalFunc \ + owner=AM-GlobalFunc policy=\ + ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\ + !rsc by Vados\r\ + \n# RouterOS script: AM-GglobalFunc\r\ + \n# Script comment: Global functions\r\ + \n#\r\ + \n#\r\ + \n# requires RouterOS, version=7.21\r\ + \n# requires device-mode, fetch, scheduler\r\ + \n:local ExitOK false;\r\ + \n:local ScriptName [ :jobname ];\r\ + \n#:global checkRunJob \$SriptName;\r\ + \n#:global fixCRLF;\r\ + \n#:if (\$fixCRLF != false) do={\r\ + \n#:foreach fixScript in=([ /system/script/find where name~\"*\" ]) do={/s\ + ystem/script/set source=[ :tocrlf [ get \$fixScript source ] ] \$fixScript\ + }\r\ + \n#:if (\$fixCRLF != \"LF\") do={ /system/script/set source=[ :tocrlf [ g\ + et \$fixScript source ] ] \$fixScript};};\r\ + \n#}\r\ + \n:global GlobalFuncReady false;\r\ + \n\r\ + \n# The variables are received right here.\r\ + \n:global GlobalConfVersion 138;\r\ + \n\r\ + \n:global Identity [/system/identity/get name];\r\ + \n:global IdentityShort [:pick \$Identity 0 18];\r\ + \n\r\ + \n:global devBoardName [/system/resource/get board-name];\r\ + \n:global devMode [/system/device-mode/get mode];\r\ + \n:global devCurArch [/system/resource/get architecture-name];\r\ + \n:global devCurVersion [/system/resource/get version];\r\ + \n\r\ + \n# Variables defined in AM-GlobalConfig.rsc\r\ + \n:global TerminalColorOutput;\r\ + \n:global CommentsInScripts;\r\ + \n:global DetectIpAddrDefault;\r\ + \n:global DetectIpAddrFallback;\r\ + \n:global updateChannel;\r\ + \n:global Domain;\r\ + \n:global EmailGeneralTo;\r\ + \n:global BackupAndUpdateMode;\r\ + \n\r\ + \n# Single functions, without nested functions.\r\ + \n:global GlobalEnvRemove;\r\ + \n:global MiniDateTimeStamp;\r\ + \n\r\ + \n# Include globals \$PrintDebug and \$PrintDebugOverride\r\ + \n:global LogPrint;\r\ + \n\r\ + \n# DeviceInfo and Updates\r\ + \n:global IsCheckUpdates;\r\ + \n:global WaitCheckUpdates;\r\ + \n:global GetPubIp;\r\ + \n\r\ + \n:global GetArrDeviceInfo;\r\ + \n:global WaitArrDeviceInfo;\r\ + \n\r\ + \n:global devModel;\r\ + \n:global devRunningMode;\r\ + \n\r\ + \n:global devRbRevision;\r\ + \n:global devRbSerialNumber;\r\ + \n:global devRbVerAvail;\r\ + \n\r\ + \n:global runningChannelVersion;\r\ + \n:global runningChannel;\r\ + \n:global runningVersion;\r\ + \n\r\ + \n:global devBuildTime;\r\ + \n#:global ROsVerAvail;\r\ + \n:global PkgUpdStatus;\r\ + \n:global currentUpdateChannel;\r\ + \n\r\ + \n:global ScriptInstallUpdate;\r\ + \n\r\ + \n:global DeviceInfo;\r\ + \n\r\ + \n:global OldMode;\r\ + \n\r\ + \n:global ParseKeyValueStore;\r\ + \n:global RandomDelay;\r\ + \n:global GetRandomNumber;\r\ + \n:global EscapeForRegEx;\r\ + \n:global ExitError;\r\ + \n:global CleanFilePath;\r\ + \n:global CleanName;\r\ + \n:global CharacterReplace;\r\ + \n:global CharacterMultiply;\r\ + \n:global EitherOr;\r\ + \n:global FormatLine;\r\ + \n:global GetRandom20CharAlNum;\r\ + \n:global Grep;\r\ + \n:global IfThenElse;\r\ + \n:global MAX;\r\ + \n:global MIN;\r\ + \n:global HumanReadableNum;\r\ + \n\r\ + \n:global IsFullyConnected;\r\ + \n:global IsDNSResolving;\r\ + \n:global IsTimeSync;\r\ + \n:global IsTimeSyncCached;\r\ + \n:global IsTimeSyncResetNtp;\r\ + \n\r\ + \n:global WaitDNSResolving;\r\ + \n:global WaitFullyConnected;\r\ + \n:global WaitTimeSync;\r\ + \n:global MkDir;\r\ + \n:global RmDir;\r\ + \n:global RmFile;\r\ + \n:global FileGet;\r\ + \n:global Unix2Dos;\r\ + \n:global UrlEncode;\r\ + \n:global WaitForFile;\r\ + \n:global ScriptLock;\r\ + \n:global SendTelegram;\r\ + \n:global NotificationFunctions;\r\ + \n:global SendNotification;\r\ + \n:global SendNotification2;\r\ + \n:global SymbolForNotification;\r\ + \n:global SymbolByUnicodeName;\r\ + \n:global ValidateSyntax;\r\ + \n:global CheckSumsVerify;\r\ + \n:global GlobalConfChanges;\r\ + \n:global GlobalConfMigration;\r\ + \n:global VersionToNum;\r\ + \n:global RequiredRouterOS;\r\ + \n:global FetchLargeFiles;\r\ + \n:global FetchUserAgentStr;\r\ + \n:global CertificateAvailable;\r\ + \n:global CertificateDownload;\r\ + \n:global CertificateNameByCN;\r\ + \n:global CharacterMultiply;\r\ + \n\r\ + \n:global InspectVar;\r\ + \n:global InspectVarRet;\r\ + \n:global ParseCustom;\r\ + \n\r\ + \n:set IsCheckUpdates do={\r\ + \n :local isInfo [:tobool \$1];\r\ + \n :global LogPrint;\r\ + \n :global updateChannel;\r\ + \n :global PkgUpdStatus;\r\ + \n \$LogPrint debug \$0 (\"\\\$isInfo = \" . \$isInfo); \r\ + \n :local CurrentChannel [ /system/package/update/get channel ];\r\ + \n :if (\$CurrentChannel != \$updateChannel) do={\r\ + \n :retry command={/system/package/update set channel=\$updateChannel} \ + \_delay=300ms max=5 on-error={};\r\ + \n \$LogPrint debug \$0 (\"Set updates channel to: \$updateChannel\");\ + \r\ + \n }\r\ + \n \r\ + \n :local Update [ /system/package/update/get ];\r\ + \n :local Channel (\$Update->\"channel\");\r\ + \n \$LogPrint debug \$0 (\"Update Channel: \" . \$Channel . \".\");\r\ + \n \r\ + \n :local StatusPkg \"undefined\";\r\ + \n \$LogPrint debug \$0 (\"Checking for updates...\");\r\ + \n :retry command={:set \$StatusPkg ([/system/package/update/check-for-up\ + dates without-paging as-value]->\"status\")} \\\r\ + \n delay=300ms max=5 on-error={};\r\ + \n \$LogPrint debug \$0 (\"\\\$StatusPkg = \" . \$StatusPkg);\r\ + \n :if (\$StatusPkg = \"New version is available\") do={ :set PkgUpdStatu\ + s true; } else={ :set PkgUpdStatus false; }\r\ + \n \$LogPrint debug \$0 (\"\\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if (\$isInfo != false || \$PkgUpdStatus = true) do={\r\ + \n \$LogPrint debug \$0 (\"\\\$PkgUpdStatus = \" . \$PkgUpdStatus);\r\ + \n :if ([:len \$arrDeviceInfo] = 0) do={\r\ + \n :global arrDeviceInfo ({});\r\ + \n :local arrDeviceInfo ({\"section\":{\"key\":\"\"; \"value\":\"\";};}\ + );\r\ + \n }\r\ + \n # else={:local arrDeviceInfo ({})}\r\ + \n :retry command={:set (\$arrDeviceInfo->\"devUpdates\") [[ :parse \"/\ + system/package/update/get\" ]]} delay=500ms max=5 on-error={:log error (\"\ + Error\")};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devCurrentChannel\"; \$Cu\ + rrentChannel;};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devCurrentVersion\"; (\$U\ + pdate->\"installed-version\");};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devCurrentChannelVersion\ + \"; [/system/resource/get version];};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devUpdateChannel\"; [\$up\ + dateChannel];};\r\ + \n #:set (\$arrDeviceInfo->\"devUpdates\") {\"devUpdateVersion\"; (\$Up\ + date->\"latest-version\");};\r\ + \n } else={\r\ + \n :return \$PkgUpdStatus;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set WaitArrDeviceInfo do={\r\ + \n :global GetArrDeviceInfo;\r\ + \n :while ([ \$GetArrDeviceInfo ] = false) do={\r\ + \n :delay 1s;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set GetArrDeviceInfo do={\r\ + \n :global LogPrint;\r\ + \n\r\ + \n :global devBoardName;\r\ + \n :global devModel;\r\ + \n \r\ + \n :global arrDeviceInfo [:toarray {\"section\"={\"key\"=\"\"; \"value\"\ + =\"\";} }];\r\ + \n #:do {:set (\$arrDeviceInfo->\"Updates\") [[ :parse \"/system/package\ + /update/get\" ]]} on-error={}\r\ + \n :global devRbRevision;\r\ + \n :global devRbSerialNumber;\r\ + \n :global devRbCurrentFw;\r\ + \n :global devRbUpgradeFw;\r\ + \n \r\ + \n :global devRunning;\r\ + \n :global devCurArch;\r\ + \n :global ROsVerCurrent;\r\ + \n :global ROsChannelCurrent;\r\ + \n :global ROsVerAvail;\r\ + \n\r\ + \n :global ParseCustom;\r\ + \n :global runningChannelVersion;\r\ + \n :global runningChannel;\r\ + \n :global runningVersion;\r\ + \n :global devBuildTime;\r\ + \n :global GetPubIp;\r\ + \n \r\ + \n :local License [/system/license/get];\r\ + \n :local Resource [/system/resource/get];\r\ + \n :set devBuildTime (\$Resource->\"build-time\");\r\ + \n :set runningChannelVersion (\$Resource->\"version\");\r\ + \n :set runningChannel [\$ParseCustom \$runningChannelVersion \"(\" \")\ + \" 1];\r\ + \n :set runningVersion [\$ParseCustom \$runningChannelVersion \" \"];\r\ + \n \r\ + \n :do {\r\ + \n :if ([:pick \$devBoardName 0 3] != \"CHR\" or [:pick \$devBoardName 0\ + \_3] != \"x86\") do={\r\ + \n :local RouterBoard;\r\ + \n :do {:set RouterBoard [[ :parse \"/system/routerboard/get\" ]]} on\ + -error={}\r\ + \n :set devModel (\$RouterBoard->\"model\");\r\ + \n :set devRbRevision (\$RouterBoard->\"revision\");\r\ + \n :set devRbSerialNumber (\$RouterBoard->\"serial-number\");\r\ + \n #:set devRbCurrentFw (\$RouterBoard->\"current-firmware\");\r\ + \n #:set devRbUpgradeFw (\$RouterBoard->\"upgrade-firmware\");\r\ + \n :if (\$devCurArch = \"mipsbe\" || \$devCurArch = \"arm\") do={\r\ + \n :global devRunning [ /system/device-mode/get mode ];\r\ + \n } else={\r\ + \n :global devRunning [/system/device-mode/get running];}\r\ + \n } else={\r\ + \n :set devModel \$devBoardName;\r\ + \n \r\ + \n :set devRbRevision false;\r\ + \n :set devRbSerialNumber false;\r\ + \n# :set devRbCurrentFw false;\r\ + \n# :set devRbUpgradeFw false;\r\ + \n #:set devCurrentFw \$runningVersion;#:set devUpgradeFw \$ROsVerAvail\ + ;\r\ + \n :set devRunning [/system/device-mode/get running];\r\ + \n }\r\ + \n } on-error={\r\ + \n \$LogPrint error \$0 (\"Error in Function getDevice!\");\r\ + \n :return false;\r\ + \n }\r\ + \n:return true;\r\ + \n}\r\ + \n\r\ + \n:set DeviceInfo do={\r\ + \n :global WaitArrDeviceInfo;\r\ + \n # \$WaitDeviceInfo;\r\ + \n :global devBoardName;\r\ + \n :global devCurArch;\r\ + \n :global devModel;\r\ + \n :global devRunning;\r\ + \n :global devRbRevision;\r\ + \n :global devRbSerialNumber;\r\ + \n :global PiblicIpAddress;\r\ + \n\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global IfThenElse;\r\ + \n :global FormatLine;\r\ + \n :local Return;\r\ + \n :local License [/system/license/get];\r\ + \n :local Resource [/system/resource/get];\r\ + \n :local Snmp [ /snmp/get ];\r\ + \n :local Update [ /system/package/update/get ];\r\ + \n \r\ + \n :set Return ([ \$FormatLine \"Hostname:\" \$Identity ] . \"\\n\" . \\\ + \r\ + \n [ \$IfThenElse ([ :len (\$Snmp->\"location\") ] > 0) \\\r\ + \n ([ \$FormatLine \"Location:\" (\$Snmp->\"location\") ] . \"\\n\") \ + ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$Snmp->\"contact\") ] > 0) \\\r\ + \n ([ \$FormatLine \"Contact:\" (\$Snmp->\"contact\") ] . \"\\n\") ] \ + . \\\r\ + \n \"Hardware:\\n\" . \\\r\ + \n [ \$FormatLine \" Board:\" . \$devBoardName ] . \"\\n\" . \\\r\ + \n [ \$FormatLine \" Arch:\" . \$devCurArch ] . \"\\n\" . \\\r\ + \n [ \$FormatLine \" Model:\" \$devModel ] . \\\r\ + \n [ \$IfThenElse ([ :len \$devRbRevision ] > 0) \\\r\ + \n (\" \" . \$devRbRevision) ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len \$devRbSerialNumber ] > 0) \\\r\ + \n ([ \$FormatLine \" Serial:\" . \$devRbSerialNumber ] . \"\\n\"\ + ) ] . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"nlevel\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License:\" (\"level \" . (\$License->\"nleve\ + l\")) ] . \"\\n\") ] . \"RouterOS:\\n\" . \\\r\ + \n [ \$IfThenElse ([ :len (\$License->\"level\") ] > 0) \\\r\ + \n ([ \$FormatLine \" License:\" (\"level \" . (\$License->\"level\ + \")) ] . \"\\n\") ] . \\\r\ + \n [ \$FormatLine \"Running Channel\" \$runningChannel ] . \"\\n\" . \\\ + \r\ + \n [ \$FormatLine \" Installed:\" (\$Update->\"installed-version\") \ + ] . \"\\n\" . \\\r\ + \n [ \$IfThenElse ([ :typeof (\$Update->\"latest-version\") ] != \"noth\ + ing\" && \\\r\ + \n \$Update->\"installed-version\" != \$Update->\"latest-version\")\ + \_\\\r\ + \n ([ \$FormatLine \" Available\" (\$Update->\"latest-version\") ]\ + \_. \"\\n\") ] . \\\r\ + \n [ \$IfThenElse (\$RouterBoard->\"routerboard\" = true && \\\r\ + \n \$RouterBoard->\"current-firmware\" != \$RouterBoard->\"upgrade-\ + firmware\") \\\r\ + \n ([ \$FormatLine \" Firmware\" (\$RouterBoard->\"current-firmwar\ + e\") ] . \"\\n\") ] . \\ \r\ + \n \"RouterOS-Scripts:\\n\" . \\\r\ + \n [ \$FormatLine \" Commit\" [ \$CommitBrief ] ] . \"\\n\" . \\\r\ + \n [ \$FormatLine \" Version\" \$GlobalConfVersion ]);\r\ + \n}\r\ + \n\r\ + \n:if ([ :typeof \$NotificationFunctions ] != \"array\") do={:set Notifica\ + tionFunctions ({})}\r\ + \n:set GetRandom20CharAlNum do={\r\ + \n :global EitherOr;\r\ + \n :return [ :rndstr length=[ \$EitherOr [ :tonum \$1 ] 20 ] from=\"ABCDE\ + FGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\" ];\r\ + \n}\r\ + \n\r\ + \n:global checkRunJob;\r\ + \n:set checkRunJob do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLock; \r\ + \n :if ([ \$ScriptLock \$Script ] = false) do={:return false}\r\ + \n :foreach Job in=[ /system/script/job/find where script=\$Script ] do={\ + :set Job [ /system/script/job/get \$Job ];\r\ + \n :while ([ :typeof (\$Job->\"parent\") ] = \"id\") do={:set Job [ /sy\ + stem/script/job/get [ find where .id=(\$Job->\"parent\") ] ]}\r\ + \n :if ((\$Job->\"type\") = \"login\") do={:return true};}; :return fa\ + lse; \r\ + \n}\r\ + \n\r\ + \n:set InspectVar do={\r\ + \n :global InspectVarRet;\r\ + \n :put [ :tocrlf [ \$InspectVarRet \$1 ] ];\r\ + \n}\r\ + \n\r\ + \n:set InspectVarRet do={\r\ + \n :local Input \$1;\r\ + \n :local Level (0 + [ :tonum \$2 ]);\r\ + \n :global CharacterReplace;\r\ + \n :global IfThenElse;\r\ + \n :global InspectVarRet;\r\ + \n :local IndentReturn do={\r\ + \n :local Prefix [ :tostr \$1 ];\r\ + \n :local Value [ :tostr \$2 ];\r\ + \n :local Level [ :tonum \$3 ];\r\ + \n :global CharacterMultiply;\r\ + \n :return ([ \$CharacterMultiply \" \" (\$Level * 2) ] . \"-\" . \$Pre\ + fix . \"-> \" . \$Value);\r\ + \n }\r\ + \n :local TypeOf [ :typeof \$Input ];\r\ + \n :local Len [ :len \$Input ];\r\ + \n :local Return [ \$IndentReturn \"type\" \$TypeOf \$Level ];\r\ + \n :if (\$TypeOf = \"array\") do={\r\ + \n :foreach Key,Value in=\$Input do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"key\" \$Key (\$Level + 1) ] . \"\\n\" . \\\r\ + \n [ \$InspectVarRet \$Value (\$Level + 2) ]);\r\ + \n }\r\ + \n } else={\r\ + \n :if (\$TypeOf = \"str\") do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"len\" \$Len \$Level ]);\r\ + \n :if ([ :typeof [ :find \$Input (\"\\r\") ] ] = \"num\") do={\r\ + \n :set Input [ \$CharacterReplace \$Input (\"\\r\") \"\" ];\r\ + \n }\r\ + \n :if ([ :typeof [ :find \$Input (\"\\n\") ] ] = \"num\") do={\r\ + \n :set Input [ \$CharacterReplace \$Input (\"\\n\") \" \" ];\r\ + \n }\r\ + \n }\r\ + \n :if (\$TypeOf != \"nothing\") do={\r\ + \n :set \$Return (\$Return . \"\\n\" . \\\r\ + \n [ \$IndentReturn \"value\" [ \$IfThenElse ([ :len \$Input ] > 80)\ + \_\\\r\ + \n ([ :pick \$Input 0 77 ] . \"...\") \$Input ] \$Level ]);\r\ + \n }\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n# !!! The function is not ready !!!\r\ + \n:global FetchHttpInfo do={\r\ + \n :local Mac [ :tostr \$1 ];\r\ + \n :global CertificateAvailable;\r\ + \n :global IsMacLocallyAdministered;\r\ + \n :global LogPrint;\r\ + \n :local Url \"https://api.macvendors.com/\";\r\ + \n :if ([ \$IsMacLocallyAdministered \$Mac ] = true) do={:return \"locall\ + y administered\";}\r\ + \n :do {\r\ + \n :if ([ \$CertificateAvailable \"GTS Root R4\" \"fetch\" ] = false) d\ + o={\r\ + \n \$LogPrint warning \$0 (\"Downloading required certificate failed.\ + \");\r\ + \n :error false;\r\ + \n }\r\ + \n :local Vendor ([ /tool/fetch \r\ + \n (\$Url . [ :pick \$Mac 0 8 ]) output=user as-value ]->\"data\");\r\ + \n :return \$Vendor;\r\ + \n } on-error={\r\ + \n :onerror Err {\r\ + \n /tool/fetch check-certificate=yes-without-crl (\$Url) output=none \ + as-value;\r\ + \n \$LogPrint debug \$0 (\"The mac vendor is not known in database.\"\ + );\r\ + \n } do={ \$LogPrint warning \$0 (\"Failed getting mac vendor: \" . \$E\ + rr); }\r\ + \n :return \"unknown vendor\";\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set GetPubIp do={\r\ + \n :global DetectIpAddrDefault;\r\ + \n :global DetectIpAddrFallback;\r\ + \n :global LogPrint;\r\ + \n \r\ + \n :local PubIpAddr false;\r\ + \n :local Url \$DetectIpAddrDefault;\r\ + \n\r\ + \n# :retry command={abc} delay=1 max=2 on-error={:put \"got error\"} \r\ + \n :do {\r\ + \n \$LogPrint debug \$0 (\"Trying to detect public IP using default s\ + ervice: \" . \$Url . \"...\");\r\ + \n :retry command={:set PubIpAddr ([ /tool/fetch http-method=\"get\"\ + \_check-certificate=yes-without-crl \$Url output=user as-value ]->\"data\"\ + )} \\\r\ + \n delay=50ms max=5 on-error={\$LogPrint error [ :jobname ] (\"Fail\ + ed to detect public IP using default service: \" . \$Url . \".\")}\r\ + \n #:error \"Failed to detect public IP using default service: \$Url\ + .\"};\r\ + \n } on-error={\r\ + \n \$LogPrint error \$0 (\"Failed to detect public IP using default s\ + ervice: \" . \$Url . \".\");\r\ + \n :set Url \$DetectIpAddrFallBack;\r\ + \n \$LogPrint debug \$0 (\"Trying to detect public IP using fallback \ + service: \" . \$Url . \"...\");\r\ + \n :retry command={:set PubIpAddr ([ /tool/fetch http-method=\"get\"\ + \_check-certificate=yes-without-crl \$Url output=user as-value ]->\"data\"\ + )} \\\r\ + \n delay=200ms max=5 on-error={\$LogPrint error [ :jobname ] (\"Fai\ + led to detect public IP using fallback service: \" . \$Url . \".\")}\r\ + \n };\r\ + \n :if (\$PubIpAddr = false) do={\r\ + \n :set PubIpAddr \"not-detected\";\r\ + \n } else={\r\ + \n :set PubIpAddr [ :pick \$PubIpAddr 0 15];\r\ + \n }\r\ + \n :return \$PubIpAddr;\r\ + \n}\r\ + \n\r\ + \n:set FormatLine do={\r\ + \n :local Key [ :tostr \$1 ];\r\ + \n :local Value [ :tostr \$2 ];\r\ + \n :local Indent [ :tonum \$3 ];\r\ + \n :local Spaces;\r\ + \n :local Return \"\";\r\ + \n :global CharacterMultiply;\r\ + \n :global EitherOr;\r\ + \n :set Indent [ \$EitherOr \$Indent 16 ];\r\ + \n :local Spaces [ \$CharacterMultiply \" \" \$Indent ];\r\ + \n :if ([ :len \$Key ] > 0) do={ :set Return (\$Key . \":\")}\r\ + \n :if ([ :len \$Key ] > (\$Indent - 2)) do={:set Return (\$Return . \"\\\ + n\" . [ :pick \$Spaces 0 \$Indent ] . \$Value);\r\ + \n } else={:set Return (\$Return . [ :pick \$Spaces 0 (\$Indent - [ :len \ + \$Return ]) ] . \$Value)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ParseCustom do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Start [:find [ \$Str ] [:tostr \$2]];\r\ + \n :local End [ :tostr \$3 ];\r\ + \n :local Num [ :tonum \$4 ];\r\ + \n :local Return;\r\ + \n :if ([:len \$Num] = 0) do={:set Num \"0\"}\r\ + \n :local rest [:pick \$Str (\$Start+\$Num) [:len \$Str]]; \r\ + \n :if ([:len \$End ] = 0) do={:set Return [:pick \$Str \$Num \$Start]} e\ + lse={\r\ + \n :set End [:find \$rest \$End];\r\ + \n :set Return [:pick \$rest 0 \$End];\r\ + \n }\r\ + \n:return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set LogPrint do={\r\ + \n :local Severity [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Message [ :tostr \$3 ];\r\ + \n :global PrintDebug;\r\ + \n :global PrintDebugOverride;\r\ + \n :global EitherOr;\r\ + \n :local Debug [ \$EitherOr (\$PrintDebugOverride->\$Name) \$PrintDebug \ + ];\r\ + \n :local PrintSeverity do={\r\ + \n :global TerminalColorOutput;\r\ + \n :if (\$TerminalColorOutput != true) do={:return \$1}\r\ + \n :local Color { debug=96; info=97; warning=93; error=91 };\r\ + \n :return (\"\\1B[\" . \$Color->\$1 . \"m\" . \$1 . \"\\1B[0m\");\r\ + \n}\r\ + \n :local Log ([ \$EitherOr \$Name \"\" ] . \": \" . \$Message);\ + \r\ + \n :if (\$Severity ~ (\"^(custom|debug|error|info)\\\$\")) do={\r\ + \n :if (\$Severity = \"debug\") do={:log debug \$Log}\r\ + \n :if (\$Severity = \"error\") do={:log error \$Log}\r\ + \n :if (\$Severity = \"info\" ) do={:log info \$Log}\r\ + \n } else={:log warning \$Log; :set Severity \"warning\"}\r\ + \n :if (\$Severity != \"debug\" || \$Debug = true) do={:put ([ \$PrintSev\ + erity \$Severity ] . \": \" . \$Message)}\r\ + \n}\r\ + \n\r\ + \n:set CertificateAvailable do={\r\ + \n :local CommonName [ :tostr \$1 ];\r\ + \n :local UseFor [ :tostr \$2 ];\r\ + \n :global CertificateDownload;\r\ + \n :global EitherOr;\r\ + \n :global LogPrint;\r\ + \n :global ParseKeyValueStore;\r\ + \n :set UseFor [ \$EitherOr \$UseFor \"undefined\" ];\r\ + \n :if ([ /system/resource/get free-hdd-space ] < 8388608 && \\\r\ + \n [ /certificate/settings/get crl-download ] = true && \\\r\ + \n [ /certificate/settings/get crl-store ] = \"system\") do={\r\ + \n \$LogPrint warning \$0 (\"This system has low free flash space but \ + \" . \\\r\ + \n \"is configured to download certificate CRLs to system!\");\r\ + \n }\r\ + \n :if ([ :len \$CommonName ] = 0) do={\r\ + \n \$LogPrint warning \$0 (\"No CommonName given!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local CertSettings [ /certificate/settings/get ];\r\ + \n :if (((\$CertSettings->\"builtin-trust-anchors\") = \"trusted\" || \\\ + \r\ + \n (\$CertSettings->\"builtin-trust-store\") ~ \$UseFor || \\\r\ + \n (\$CertSettings->\"builtin-trust-store\") = \"all\") && \\\r\ + \n [ :len [ /certificate/builtin/find where common-name=\$CommonName\ + \_] ] > 0) do={\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n \$LogPrint info \$0 (\"Certificate with CommonName '\" . \$CommonNam\ + e . \"' not available.\");\r\ + \n :if ([ \$CertificateDownload \$CommonName ] = false) do={:return fal\ + se}\r\ + \n }\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] > 1\ + ) do={\r\ + \n \$LogPrint info \$0 (\"There are \" . \$CertCount . \" Certificates \ + with CommonName '\" . \$CommonName . \"'. Should be ok.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :local CertVal [ /certificate/get [ find where common-name=\$CommonNam\ + e ] ];\r\ + \n :while ((\$CertVal->\"akid\") != \"\" && (\$CertVal->\"akid\") != (\$C\ + ertVal->\"skid\")) do={\r\ + \n :if ([ :len [ /certificate/find where skid=(\$CertVal->\"akid\") ] ]\ + \_= 0) do={\r\ + \n :local IssuerCN ([ \$ParseKeyValueStore (\$CertVal->\"issuer\") ]-\ + >\"CN\");\r\ + \n \$LogPrint info \$0 (\"Certificate chain for '\" . \$CommonName . \ + \"' is incomplete, missing '\" . \$IssuerCN . \"'.\");\r\ + \n :if ([ \$CertificateDownload \$IssuerCN ] = false) do={:return fal\ + se}\r\ + \n }\r\ + \n :set CertVal [ /certificate/get [ find where skid=(\$CertVal->\"akid\ + \") ] ];\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CertificateDownload do={\r\ + \n :local CommonName [ :tostr \$1 ];\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global GlobalScriptsUrlSuffix;\r\ + \n :global CertificateNameByCN;\r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global LogPrint;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n \r\ + \n \$LogPrint info \$0 (\"Downloading and importing certificate with Comm\ + onName '\" . \$CommonName . \"'.\");\r\ + \n \r\ + \n :local FileName ([ \$CleanName \$CommonName ] . \".pem\");\r\ + \n :do {\r\ + \n /tool/fetch check-certificate=yes-without-crl http-header-field=({ [\ + \_\$FetchUserAgentStr \$0 ] }) \\\r\ + \n (\$GlobalScriptsUrl . \"certs/\" . \$FileName . \$GlobalScriptsUrl\ + Suffix) dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n } on-error={\r\ + \n \$LogPrint warning \$0 (\"Failed downloading certificate with Common\ + Name '\" . \$CommonName . \\\r\ + \n \"' from repository! Trying fallback to mkcert.org...\");\r\ + \n :do {\r\ + \n :if ([ :len [ /certificate/find where common-name=\"ISRG Root X1\"\ + \_] ] = 0) do={\r\ + \n \$LogPrint error \$0 (\"Required certificate is not available.\"\ + );\r\ + \n :return false;\r\ + \n }\r\ + \n /tool/fetch check-certificate=yes-without-crl http-header-field=({\ + \_[ \$FetchUserAgentStr \$0 ] }) \\\r\ + \n \"https://mkcert.org/generate/\" http-data=[ :serialize to=json \ + ({ \$CommonName }) ] \\\r\ + \n dst-path=\$FileName as-value;\r\ + \n \$WaitForFile \$FileName;\r\ + \n :if ([ /file/get \$FileName size ] = 0) do={\r\ + \n \$RmFile \$FileName;\r\ + \n :error false;\r\ + \n }\r\ + \n } on-error={\r\ + \n \$LogPrint warning \$0 (\"Failed downloading certificate with Comm\ + onName '\" . \$CommonName . \"'!\");\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n /certificate/import file-name=\$FileName passphrase=\"\" as-value;\r\ + \n :delay 1s;\r\ + \n \$RmFile \$FileName;\r\ + \n\r\ + \n :if ([ :len [ /certificate/find where common-name=\$CommonName ] ] = 0\ + ) do={\r\ + \n /certificate/remove [ find where name~(\"^\" . \$FileName . \"_[0-9]\ + +\\\$\") ];\r\ + \n \$LogPrint warning \$0 (\"Certificate with CommonName '\" . \$Common\ + Name . \"' still unavailable!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :foreach Cert in=[ /certificate/find where name~(\"^\" . \$FileName . \ + \"_[0-9]+\\\$\") ] do={\r\ + \n \$CertificateNameByCN [ /certificate/get \$Cert common-name ];\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CertificateNameByCN do={\r\ + \n :local Match [ :tostr \$1 ];\r\ + \n :global CleanName;\r\ + \n :global LogPrint;\r\ + \n :local Cert ([ /certificate/find where (common-name=\$Match or fingerp\ + rint=\$Match or name=\$Match) ]->0);\r\ + \n :if ([ :len \$Cert ] = 0) do={\r\ + \n \$LogPrint warning \$0 (\"No matching certificate found.\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local CommonName [ /certificate/get \$Cert common-name ];\r\ + \n /certificate/set \$Cert name=[ \$CleanName \$CommonName ];\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set CharacterMultiply do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Num [ :tonum \$2 ];\r\ + \n :if (\$Num = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :for I from=1 to=\$Num do={:set Return (\$Return . \$Str)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set ScriptInstallUpdate do={ :onerror Err {\r\ + \n :local Scripts [ :toarray \$1 ];\r\ + \n :local NewComment [ :tostr \$2 ];\r\ + \n :global InitSetup;\r\ + \n\r\ + \n :global ScriptUpdatesCRLF;\r\ + \n :global GlobalConfVersion;\r\ + \n :global Identity;\r\ + \n :global CertificateAvailable;\r\ + \n :global CheckSumsVerify;\r\ + \n :global GlobalScriptsUrl;\r\ + \n :global GlobalScriptsUrlSuffix;\r\ + \n :global EitherOr;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global Grep;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global ParseKeyValueStore;\r\ + \n :global RequiredRouterOS;\r\ + \n :global SendNotification2;\r\ + \n :global SymbolForNotification;\r\ + \n :global ValidateSyntax;\r\ + \n :global CommentsInScripts;\r\ + \n\r\ + \n :if ([ \$CertificateAvailable \"Root YE\" \"fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading certificate failed, trying wit\ + hout.\");\r\ + \n }\r\ + \n\r\ + \n :foreach Script in=\$Scripts do={\r\ + \n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\ + \r\ + \n \$LogPrint info \$0 (\"Adding new script: \" . \$Script);\r\ + \n /system/script/add name=\$Script owner=\$Script source=\"#!rsc by \ + Vados\\n\" comment=\$NewComment;\r\ + \n }\r\ + \n }\r\ + \n \r\ + \n :local GlobalConfVersionBefore \$GlobalConfVersion;\r\ + \n :local ReloadGlobal false;\r\ + \n :local DeviceMode [ /system/device-mode/get ];\r\ + \n \r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :local CheckSums ({});\r\ + \n :do {\r\ + \n :local Url (\$GlobalScriptsUrl . \"checksums.json\" . \$GlobalScrip\ + tsUrlSuffix);\r\ + \n \$LogPrint debug \$0 (\"Fetching checksums from url: \" . \$Url);\ + \r\ + \n :set CheckSums [ :deserialize from=json ([ /tool/fetch check-certi\ + ficate=yes-without-crl \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url output\ + =user as-value ]->\"data\") ];\r\ + \n } on-error={ \$LogPrint warning \$0 (\"Err = \" . \$Err); }\r\ + \n }\r\ + \n\r\ + \n :foreach Script in=[ /system/script/find where source~\"^#!rsc by Vado\ + s\" ] do={\r\ + \n :local ScriptVal [ /system/script/get \$Script ];\r\ + \n :local ScriptInfo [ \$ParseKeyValueStore (\$ScriptVal->\"comment\") \ + ];\r\ + \n :local SourceNew;\r\ + \n\r\ + \n :foreach Scheduler in=[ /system/scheduler/find where on-event~(\"\\\ + \\b\" . \$ScriptVal->\"name\" . \"\\\\b\") ] do={\r\ + \n :local SchedulerVal [ /system/scheduler/get \$Scheduler ];\r\ + \n :if (\$ScriptVal->\"policy\" != \$SchedulerVal->\"policy\") do={\r\ + \n \$LogPrint warning \$0 (\"Policies differ for script '\" . \$Scr\ + iptVal->\"name\" . \"' and its scheduler '\" . \$SchedulerVal->\"name\" . \ + \"'!\");\r\ + \n }\r\ + \n }\r\ + \n :do {\r\ + \n :if (\$ScriptInfo->\"ignore\" = true) do={\r\ + \n \$LogPrint debug \$0 (\"Ignoring script '\" . \$ScriptVal->\"nam\ + e\" . \"', as requested.\");\r\ + \n :error true;\r\ + \n }\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :local CheckSum (\$CheckSums->(\$ScriptVal->\"name\"));\r\ + \n :if ([ :len (\$ScriptInfo->\"base-url\") ] = 0 && [ :len (\$Scri\ + ptInfo->\"url-suffix\") ] = 0 && \\\r\ + \n [ :convert transform=md5 to=hex [ :tolf (\$ScriptVal->\"sou\ + rce\") ] ] = \$CheckSum) do={\r\ + \n \$LogPrint debug \$0 (\"Checksum for script '\" . \$ScriptVal-\ + >\"name\" . \"' matches, ignoring.\");\r\ + \n :error true;\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len (\$ScriptInfo->\"certificate\") ] > 0) do={\r\ + \n :if ([ \$CertificateAvailable (\$ScriptInfo->\"certificate\") \"\ + fetch\" ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Downloading certificate failed, tryi\ + ng without.\");\r\ + \n }\r\ + \n }\r\ + \n :onerror Err {\r\ + \n :local BaseUrl [ \$EitherOr (\$ScriptInfo->\"base-url\") \$Globa\ + lScriptsUrl ];\r\ + \n :local UrlSuffix [ \$EitherOr (\$ScriptInfo->\"url-suffix\") \$G\ + lobalScriptsUrlSuffix ];\r\ + \n :local Url (\$BaseUrl . \$ScriptVal->\"name\" . \".rsc\" . \$Url\ + Suffix);\r\ + \n \$LogPrint warning \$0 (\"Fetching script '\" . (\$ScriptVal->\"\ + name\") . \"' from url: \" . \$Url);\r\ + \n :local Result [ /tool/fetch check-certificate=yes-without-crl \\\ + \r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$0 ] }) \$Url outp\ + ut=user as-value ];\r\ + \n :if (\$Result->\"status\" = \"finished\") do={\r\ + \n :set SourceNew [ :tolf (\$Result->\"data\") ];\r\ + \n } \r\ + \n } do={\r\ + \n \$LogPrint warning \$0 (\"Failed fetching script '\" . \$ScriptV\ + al->\"name\" . \"': \" . \$Err);\r\ + \n :if (\$Err != \"Fetch failed with status 404\") do={\r\ + \n :error false;\r\ + \n }\r\ + \n :if (\$ScriptVal->\"source\" = \"#!rsc by Vados\") do={\r\ + \n \$LogPrint warning \$0 (\"Removing dummy. Typo on installation\ + \?\");\r\ + \n /system/script/remove \$Script;\r\ + \n :error false;\r\ + \n }\r\ + \n :if (\$CheckSumsVerify = true) do={\r\ + \n :if ([ :len (\$ScriptInfo->\"base-url\") ] = 0 && [ :len (\$Sc\ + riptInfo->\"url-suffix\") ] = 0 && \\\r\ + \n [ :len \$CheckSum ] = 0) do={\r\ + \n \$LogPrint warning \$0 (\"Added the script manually\? Skip u\ + pdates with 'ignore=true' in comment.\");\r\ + \n }\r\ + \n }\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if ([ :len \$SourceNew ] = 0) do={\r\ + \n \$LogPrint debug \$0 (\"No update for script '\" . (\$ScriptVal-\ + >\"name\") . \"'.\");\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :local SourceCRLF [ :tocrlf \$SourceNew ];\r\ + \n :if (\$InitSetup != true && ((\$ScriptVal->\"name\") != \"AM-Globa\ + lConfig\" || (\$ScriptVal->\"name\") != \"AM-GlobalFunc\")) do={\r\ + \n :if (\$SourceNew = (\$ScriptVal->\"source\") || \$SourceCRLF = (\ + \$ScriptVal->\"source\")) do={\r\ + \n \$LogPrint debug \$0 (\"Script '\" . (\$ScriptVal->\"name\") \ + . \"' source did not change. End of lines set to CRLF.\");\r\ + \n if (\$ScriptUpdatesCRLF = false) do={\r\ + \n :put [ :tolf \$SourceNew ];\r\ + \n \$LogPrint debug \$0 (\"Script '\" . (\$ScriptVal->\"name\"\ + ) . \"' source update end of lines to LF.\");\r\ + \n }\r\ + \n :error false;\r\ + \n }\r\ + \n }\r\ + \n\r\ + \n :if ([ :pick \$SourceNew 0 14 ] != \"#!rsc by Vados\") do={\r\ + \n \$LogPrint warning \$0 (\"Looks like new script '\" . \$ScriptVa\ + l->\"name\" . \"' is not valid (missing shebang = '\$shebang') Ignoring!\"\ + );\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if (\$CommentsInScripts = true) do={\r\ + \n :local CommentLine [ \$Grep \$SourceNew (\"\\23 Script comment:\ + \_\") ];\r\ + \n :if ([ :len \$CommentLine ] = 0) do={\r\ + \n \$LogPrint debug \$0 (\"The search in new script '\" . (\$Scr\ + iptVal->\"name\") . \"' for comment in script body, is not found result Ig\ + noring!\");\r\ + \n :error false;\r\ + \n } else={\r\ + \n :if ([ :len (\$ScriptVal->\"comment\")] = 0 || [ :len (\$Scri\ + ptVal->\"comment\")] != [ :pick \$CommentLine ([ :find \$CommentLine \":\"\ + ] + 2) [ :len \$CommentLine] ]) do={\r\ + \n \$LogPrint debug \$0 (\"Exsiting comment for script '\" . (\ + \$ScriptVal->\"name\") . \"' is '\" . (\$ScriptVal->\"comment\") . \"'.\")\ + ;\r\ + \n :set (\$ScriptVal->\"comment\") [ :pick \$CommentLine ([ :f\ + ind \$CommentLine \":\"] + 2) [ :len \$CommentLine] ];\r\ + \n \$LogPrint debug \$0 (\"New comment (\" . (\$ScriptVal->\"c\ + omment\") . \") for script '\" . (\$ScriptVal->\"name\") . \"' has been ad\ + ded!\");\r\ + \n } else={\r\ + \n \$LogPrint debug \$0 (\"Comment for script '\" . (\$ScriptV\ + al->\"name\") . \"' did not change.\");\r\ + \n :error false;\r\ + \n }\r\ + \n } \r\ + \n }\r\ + \n\r\ + \n :local RequiredROS ([ \$ParseKeyValueStore [ \$Grep \$SourceNew (\ + \"\\23 requires RouterOS, \") ] ]->version);\r\ + \n :if ([ \$RequiredRouterOS \$0 [ \$EitherOr \$RequiredROS \"0.0\" ]\ + \_false ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"The script '\" . (\$ScriptVal->\"name\ + \") . \"' requires RouterOS \" . \\\r\ + \n \$RequiredROS . \", which is not met by your installation. I\ + gnoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n :local RequiredDM [ \$ParseKeyValueStore [ \$Grep \$SourceNew (\"\ + \\23 requires device-mode, \") ] ];\r\ + \n :local MissingDM ({});\r\ + \n :foreach Feature,Value in=\$RequiredDM do={\r\ + \n :if ([ :typeof (\$DeviceMode->\$Feature) ] = \"bool\" && (\$Devi\ + ceMode->\$Feature) = false) do={\r\ + \n :set MissingDM (\$MissingDM, \$Feature);\r\ + \n }\r\ + \n }\r\ + \n :if ([ :len \$MissingDM ] > 0) do={\r\ + \n \$LogPrint warning \$0 (\"The script '\" . (\$ScriptVal->\"name\ + \") . \"' requires disabled \" . \\\r\ + \n \"device-mode features (\" . [ :tostr \$MissingDM ] . \"). I\ + gnoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n\r\ + \n :if ([ \$ValidateSyntax \$SourceNew ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Syntax validation for script '\" . (\$\ + ScriptVal->\"name\") . \"' failed! Ignoring!\");\r\ + \n :error false;\r\ + \n }\r\ + \n \r\ + \n :log info (\"Updating script: \" . (\$ScriptVal->\"name\") . \" Sc\ + ript comment: \" . (\$ScriptVal->\"comment\"));\r\ + \n\r\ + \n /system/script/set owner=(\$ScriptVal->\"name\") comment=(\$Script\ + Val->\"comment\") \\\r\ + \n source=[ \$IfThenElse (\$ScriptUpdatesCRLF = true) \$SourceCRL\ + F \$SourceNew ] \$Script;\r\ + \n :if (\$ScriptVal->\"name\" = \"AM-GlobalConfig\" || \\\r\ + \n \$ScriptVal->\"name\" = \"AM-GlobalFunc\" || \\\r\ + \n \$ScriptVal->\"name\" ~ (\"^(AM-GlobalFunc\\\\.d|mod)/.\")) d\ + o={\r\ + \n :set ReloadGlobal true;\r\ + \n }\r\ + \n } on-error={ }\r\ + \n }\r\ + \n :if (\$ReloadGlobal = true) do={\r\ + \n \$LogPrint info \$0 (\"Reloading global configuration and functions.\ + \");\r\ + \n :onerror Err {\r\ + \n :global InitSetup (a);\r\ + \n /system/script {run AM-GlobalConfig; run AM-GlobalFunc;};\r\ + \n } do={\$LogPrint error \$0 (\"Reloading global configuration and fun\ + ctions failed! \" . \$Err)}\r\ + \n }\r\ + \n :if (\$GlobalConfVersionBefore > \$GlobalConfVersion) do={\r\ + \n \$LogPrint warning \$0 (\"The configuration version decreased from \ + \" . \\\r\ + \n \$GlobalConfVersionBefore . \" to \" . \$GlobalConfVersion . \\\r\ + \n \". Installed an older version\?\");\r\ + \n }\r\ + \n :if (\$GlobalConfVersionBefore < \$GlobalConfVersion) do={\r\ + \n :global GlobalConfChanges;\r\ + \n :global GlobalConfMigration;\r\ + \n :local ChangeLogCode;\r\ + \n :if ([ :len \$ChangeLogCode ] > 0) do={\r\ + \n :if ([ \$ValidateSyntax \$ChangeLogCode ] = true) do={\r\ + \n :onerror Err {\r\ + \n [ :parse \$ChangeLogCode ];\r\ + \n } do={\$LogPrint warning \$0 (\"The changelog failed to run: \" \ + . \$Err)}\r\ + \n } else={\$LogPrint warning \$0 (\"The changelog failed syntax vali\ + dation!\")}\r\ + \n }\r\ + \n :if ([ :len \$GlobalConfMigration ] > 0) do={\r\ + \n :for I from=(\$GlobalConfVersionBefore + 1) to=\$GlobalConfVersion\ + \_do={\r\ + \n :local Migration (\$GlobalConfMigration->[ :tostr \$I ]);\r\ + \n :do {\r\ + \n :if ([ :typeof \$Migration ] != \"str\") do={\r\ + \n \$LogPrint debug \$0 (\"Migration code for change \" . \$I .\ + \_\" is not available.\");\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$ValidateSyntax \$Migration ] = false) do={\r\ + \n \$LogPrint warning \$0 (\"Migration code for change \" . \$I\ + \_. \" failed syntax validation!\");\r\ + \n :error false}\r\ + \n \$LogPrint info \$0 (\"Applying migration for change \" . \$I \ + . \": \" . \$Migration);\r\ + \n :onerror Err {\r\ + \n [ :parse \$Migration ];\r\ + \n } do={\$LogPrint warning \$0 (\"Migration code for change \" .\ + \_\$I . \" failed to run: \" . \$Err)}\r\ + \n } on-error={ }\r\ + \n }\r\ + \n }\r\ + \n :local NotificationMessage (\"The configuration version on \" . \$Id\ + entity . \" increased \" . \\\r\ + \n \"to \" . \$GlobalConfVersion . \", current configuration may nee\ + d modification. \" . \\\r\ + \n \"Please review and update global-config-overlay, then re-run glo\ + bal-config.\");\r\ + \n \$LogPrint info \$0 (\$NotificationMessage);\r\ + \n :if ([ :len \$GlobalConfChanges ] > 0) do={\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n\\nChanges:\ + \");\r\ + \n :for I from=(\$GlobalConfVersionBefore + 1) to=\$GlobalConfVersion\ + \_do={\r\ + \n :local Change (\$GlobalConfChanges->[ :tostr \$I ]);\r\ + \n :set NotificationMessage (\$NotificationMessage . \"\\n \" . \\\ + \r\ + \n [ \$SymbolForNotification \"pushpin\" \"*\" ] . \$Change);\r\ + \n \$LogPrint info \$0 (\"Change \" . \$I . \": \" . \$Change);\r\ + \n }\r\ + \n } else={:set NotificationMessage (\$NotificationMessage . \"\\n\\nNe\ + ws and changes are not available.\")}\r\ + \n\r\ + \n :set GlobalConfChanges;\r\ + \n :set GlobalConfMigration;\r\ + \n }\r\ + \n} do={:global ExitOnError; \$ExitOnError \$0 \$Err}\r\ + \n}\r\ + \n\r\ + \n:set FetchUserAgentStr do={\r\ + \n :local Caller [ :tostr \$1 ];\r\ + \n :global IfThenElse;\r\ + \n :local Resource [ /system/resource/get ];\r\ + \n :return (\"User-Agent: Mikrotik/\" . \$Resource->\"version\" . \" \" .\ + \_\$Resource->\"architecture-name\" . \\\r\ + \n \" \" . \$Caller . \"/Fetch (https://amster.vados.ru/; main/3419/3ee\ + 19780)\");\r\ + \n}\r\ + \n\r\ + \n:set Grep do={\r\ + \n :local Input ([ :tostr \$1 ] . \"\\n\");\r\ + \n :local Pattern [ :tostr \$2 ];\r\ + \n :if ([ :typeof [ :find \$Input \$Pattern ] ] = \"nil\") do={:return []\ + }\r\ + \n :do {\r\ + \n :local Line [ :pick \$Input 0 [ :find \$Input \"\\n\" ] ];\r\ + \n :if ([ :typeof [ :find \$Line \$Pattern ] ] = \"num\") do={:return \ + \$Line}\r\ + \n :set Input [ :pick \$Input ([ :find \$Input \"\\n\" ] + 1) [ :len \$\ + Input ] ];\r\ + \n } while=([ :len \$Input ] > 0);\r\ + \n :return [];\r\ + \n}\r\ + \n\r\ + \n:set HumanReadableNum do={\r\ + \n :local Input [ :tonum \$1 ];\r\ + \n :local Base [ :tonum \$2 ];\r\ + \n :global EitherOr;\r\ + \n :global IfThenElse;\r\ + \n :local Prefix \"kMGTPE\";\r\ + \n :local Pow 1;\r\ + \n :set Base [ \$EitherOr \$Base 1024 ];\r\ + \n :local Bin [ \$IfThenElse (\$Base = 1024) \"i\" \"\" ];\r\ + \n :if (\$Input < \$Base) do={:return \$Input}\r\ + \n :for I from=0 to=[ :len \$Prefix ] do={\r\ + \n :set Pow (\$Pow * \$Base);\r\ + \n :if (\$Input / \$Base < \$Pow) do={\r\ + \n :set Prefix [ :pick \$Prefix \$I ];\r\ + \n :local Tmp1 (\$Input * 100 / \$Pow);\r\ + \n :local Tmp2 (\$Tmp1 / 100);\r\ + \n :if (\$Tmp2 >= 100) do={:return (\$Tmp2 . \$Prefix . \$Bin)}\r\ + \n :return (\$Tmp2 . \".\" . \\\r\ + \n [ :pick \$Tmp1 [ :len \$Tmp2 ] ([ :len \$Tmp1 ] - [ :len \$Tmp\ + 2 ] + 1) ] . \\\r\ + \n \$Prefix . \$Bin);\r\ + \n }\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set CleanName do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :local Return \"\";\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :if ([ :typeof [ find \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr\ + stuvwxyz0123456789\" \$Char ] ] = \"nil\") do={\r\ + \n :do {:if ([ :len \$Return ] = 0) do={:error true};\r\ + \n :if ([ :pick \$Return ([ :len \$Return ] - 1) ] = \"-\") do={:er\ + ror true}; :set Char \"-\"} on-error={:set Char \"\"}; \r\ + \n };\r\ + \n :set Return (\$Return . \$Char)};\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set GetRandomNumber do={\r\ + \n :global EitherOr;\r\ + \n :return [ :rndnum from=0 to=[ \$EitherOr [ :tonum \$1 ] 4294967295 ] ]\ + \r\ + \n}\r\ + \n\r\ + \n:set RandomDelay do={\r\ + \n :local Time [ :tonum \$1 ];\r\ + \n :local Unit [ :tostr \$2 ];\r\ + \n :global EitherOr;\r\ + \n :global GetRandomNumber;\r\ + \n :global MAX;\r\ + \n :if (\$Time = 0) do={:return false}\r\ + \n :delay ([ \$MAX 10 [ \$GetRandomNumber ([ :tonsec [ :totime (\$Time . \ + [ \$EitherOr \$Unit \"s\" ]) ] ] / 1000000) ] ] . \"ms\");\r\ + \n}\r\ + \n\r\ + \n:set EscapeForRegEx do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :if ([ :len \$Input ] = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :local Chars (\"^.[]\\\$()|*+\?{}\\\\\");\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :if ([ :find \$Chars \$Char ]) do={:set Char (\"\\\\\" . \$Char)}\r\ + \n :set Return (\$Return . \$Char);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n:set ExitError do={\r\ + \n :local ExitOK [ :tostr \$1 ];\r\ + \n :local Name [ :tostr \$2 ];\r\ + \n :local Error [ :tostr \$3 ];\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :if (\$ExitOK = \"false\") do={\r\ + \n \$LogPrint error \$Name ([ \$IfThenElse ([ :pick \$Name 0 1 ] = \"\\\ + \$\") \\\r\ + \n \"Function\" \"Script\" ] . \" '\" . \$Name . \"' exited with er\ + ror\" . \\\r\ + \n [ \$IfThenElse (!(\$Error ~ \"^(|true|false)\\\$\")) (\": \" . \ + \$Error) \".\" ]);\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n:set MiniDateTimeStamp do={\r\ + \n /system/clock;\r\ + \n :local vdate [get date];\r\ + \n :local vtime [get time];\r\ + \n :local vdoff [:toarray \"0,4,5,7,8,10\"];\r\ + \n :local MM [:pick \$vdate (\$vdoff->2) (\$vdoff->3)];\r\ + \n :local M [:tonum \$MM];\r\ + \n :if (\$vdate ~ \".../../....\") do={\r\ + \n :set vdoff [:toarray \"7,11,1,3,4,6\"];\r\ + \n :set M ([:find \"xxanebarprayunulugepctovecANEBARPRAYUNULUGE\ + PCTOVEC\" [:pick \$vdate (\$vdoff->2) (\$vdoff->3)] -1] / 2);\r\ + \n :if (\$M>12) do={:set M (\$M - 12)}\r\ + \n :set MM [:pick (100 + \$M) 1 3];\r\ + \n }\r\ + \n :local yyyy [:pick \$vdate (\$vdoff->0) (\$vdoff->1)];\r\ + \n :local dd [:pick \$vdate (\$vdoff->4) (\$vdoff->5)];\r\ + \n :local HH [:pick \$vtime 0 2];\r\ + \n :local mm [:pick \$vtime 3 5];\r\ + \n :local ss [:pick \$vtime 6 8];\r\ + \n :return ([\$yyyy] . [\$MM] . [\$dd] . [\$HH] . [\$mm] . [\$ss]);\r\ + \n}\r\ + \n\r\ + \n:set WaitForFile do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :local WaitTime [ :totime \$2 ];\r\ + \n :global CleanFilePath;\r\ + \n :global EitherOr;\r\ + \n :global MAX;\r\ + \n :set FileName [ \$CleanFilePath \$FileName ];\r\ + \n :local Delay ([ \$MAX [ \$EitherOr \$WaitTime 2s ] 100ms ] / 9);\r\ + \n :do {\r\ + \n :retry {\r\ + \n /file/get \$FileName;\r\ + \n :return true;\r\ + \n } delay=\$Delay max=10;\r\ + \n } on-error={ }\r\ + \n :while ([ :len [ /file/find where name=\$FileName ] ] > 0) do={\r\ + \n :do {\r\ + \n /file/get \$FileName;\r\ + \n :return true;\r\ + \n } on-error={ }\r\ + \n :delay \$Delay;\r\ + \n :set Delay (\$Delay * 3 / 2);\r\ + \n }\r\ + \n :return false;\r\ + \n}\r\ + \n\r\ + \n:set FileGet do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :global WaitForFile;\r\ + \n :if ([ \$WaitForFile \$FileName 0s ] = false) do={:return false}\r\ + \n :local FileVal false;\r\ + \n :do {:set FileVal [ /file/get \$FileName ];\r\ + \n } on-error={ }\r\ + \n :return \$FileVal;\r\ + \n}\r\ + \n\r\ + \n:set RmDir do={\r\ + \n :local DirName [ :tostr \$1 ];\r\ + \n :global FileGet;\r\ + \n :global LogPrint;\r\ + \n \$LogPrint debug \$0 (\"Removing directory: \". \$DirName);\r\ + \n :local DirVal [ \$FileGet \$DirName ];\r\ + \n :if (\$DirVal = false) do={\$LogPrint debug \$0 (\"... which does not \ + exist.\"); :return true};\r\ + \n :if (\$DirVal->\"type\" != \"directory\") do={\$LogPrint error \$0 (\"\ + Directory '\" . \$DirName . \"' is not a directory.\"); :return false};\r\ + \n :onerror Err {/file/remove \$DirName} do={\$LogPrint error \$0 (\"Remo\ + ving directory '\" . \$DirName . \"' failed: \" . \$Err); :return false};\ + \r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set RmFile do={\r\ + \n :local FileName [ :tostr \$1 ];\r\ + \n :global FileGet;\r\ + \n :global LogPrint;\r\ + \n \$LogPrint debug \$0 (\"Removing file: \". \$FileName);\r\ + \n :local FileVal [ \$FileGet \$FileName ];\r\ + \n :if (\$FileVal = false) do={\$LogPrint debug \$0 (\"... which does not\ + \_exist.\"); :return true}\r\ + \n :if (\$FileVal->\"type\" = \"directory\" || \$FileVal->\"type\" = \"di\ + sk\") do={\r\ + \n \$LogPrint error \$0 (\"File '\" . \$FileName . \"' is not a file.\"\ + ); :return false}\r\ + \n :onerror Err {\r\ + \n /file/remove \$FileName;\r\ + \n } do={\$LogPrint error \$0 (\"Removing file '\" . \$FileName . \"' fai\ + led: \" . \$Err); :return false}\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set MkDir do={\r\ + \n :local Path [ :tostr \$1 ];\r\ + \n :global CleanFilePath;\r\ + \n :global FileGet;\r\ + \n :global RmDir;\r\ + \n :global WaitForFile;\r\ + \n :global LogPrint;\r\ + \n :local MkTmpfs do={\r\ + \n :global WaitForFile;\r\ + \n :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ];\r\ + \n :if ([ :len \$TmpFs ] = 1) do={\r\ + \n :if ([ /disk/get \$TmpFs disabled ] = true) do={\r\ + \n \$LogPrint info \$0 (\"The tmpfs is disabled, enabling.\");\r\ + \n /disk/enable \$TmpFs;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n \$LogPrint info \$0 (\"Creating disk of type tmpfs.\");\r\ + \n \$RmDir \"tmpfs\";\r\ + \n :onerror Err {\r\ + \n /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource\ + /get total-memory ] / 3);\r\ + \n \$WaitForFile \"tmpfs\";\r\ + \n } do={\r\ + \n \$LogPrint warning \$0 (\"Creating disk of type tmpfs failed: \" .\ + \_\$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n }\r\ + \n :set Path [ \$CleanFilePath \$Path ];\r\ + \n :if (\$Path = \"\") do={\r\ + \n :return true;\r\ + \n }\r\ + \n \$LogPrint debug \$0 (\"Making directory: \" . \$Path);\r\ + \n :local PathVal [ \$FileGet \$Path ];\r\ + \n :if (\$PathVal->\"type\" = \"directory\") do={\r\ + \n \$LogPrint debug \$0 (\"... which already exists.\");\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ :pick \$Path 0 5 ] = \"tmpfs\") do={\r\ + \n :if ([ \$MkTmpfs ] = false) do={\r\ + \n :return false;\r\ + \n }\r\ + \n }\r\ + \n :onerror Err {\r\ + \n /file/add type=\"directory\" name=\$Path;\r\ + \n \$WaitForFile \$Path;\r\ + \n } do={\r\ + \n \$LogPrint warning \$0 (\"Making directory '\" . \$Path . \"' failed\ + : \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n:set MAX do={:if (\$1 > \$2) do={:return \$1}\r\ + \n :return \$2;\r\ + \n}\r\ + \n:set MIN do={:if (\$1 < \$2) do={:return \$1}\r\ + \n :return \$2;\r\ + \n}\r\ + \n\r\ + \n:set CharacterMultiply do={\r\ + \n :local Str [ :tostr \$1 ];\r\ + \n :local Num [ :tonum \$2 ];\r\ + \n :if (\$Num = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :for I from=1 to=\$Num do={:set Return (\$Return . \$Str)}\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n:set CharacterReplace do={\r\ + \n :local String [ :tostr \$1 ];\r\ + \n :local ReplaceFrom [ :tostr \$2 ];\r\ + \n :local ReplaceWith [ :tostr \$3 ];\r\ + \n :local Return \"\";\r\ + \n :if (\$ReplaceFrom = \"\") do={:return \$String}\r\ + \n :while ([ :typeof [ :find \$String \$ReplaceFrom ] ] != \"nil\") do={\ + \r\ + \n :local Pos [ :find \$String \$ReplaceFrom ];\r\ + \n :set Return (\$Return . [ :pick \$String 0 \$Pos ] . \$ReplaceWith);\ + \r\ + \n :set String [ :pick \$String (\$Pos + [ :len \$ReplaceFrom ]) [ :len\ + \_\$String ] ];\r\ + \n }\r\ + \n :return (\$Return . \$String);\r\ + \n}\r\ + \n\r\ + \n:set CleanFilePath do={\r\ + \n :local Path [ :tostr \$1 ];\r\ + \n :global CharacterReplace;\r\ + \n :while (\$Path ~ \"//\") do={:set \$Path [ \$CharacterReplace \$Path \ + \"//\" \"/\" ]};\r\ + \n :if ([ :pick \$Path 0 ] = \"/\") do={:set Path [ :pick \$Path 1 [ :len\ + \_\$Path ] ]};\r\ + \n :if ([ :pick \$Path ([ :len \$Path ] - 1) ] = \"/\") do={:set Path [ :\ + pick \$Path 0 ([ :len \$Path ] - 1) ]};\r\ + \n :return \$Path;\r\ + \n}\r\ + \n\r\ + \n:set IfThenElse do={\r\ + \n :if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] = true) do={:return \ + \$2};\r\ + \n :return \$3;\r\ + \n}\r\ + \n\r\ + \n:set EitherOr do={\r\ + \n :global IfThenElse;\r\ + \n :if ([ :typeof \$1 ] = \"num\") do={:return [ \$IfThenElse (\$1 != 0) \ + \$1 \$2 ]}\r\ + \n :if ([ :typeof \$1 ] = \"time\") do={:return [ \$IfThenElse (\$1 > 0s)\ + \_\$1 \$2 ]}\r\ + \n# this works for boolean values, literal ones with parentheses\r\ + \n :return [ \$IfThenElse ([ :len [ :tostr \$1 ] ] > 0) \$1 \$2 ];\r\ + \n}\r\ + \n\r\ + \n:set ParseKeyValueStore do={\r\ + \n :local Source \$1;\r\ + \n :if ([ :pick \$Source 0 1 ] = \"{\") do={\r\ + \n :do {\r\ + \n :return [ :deserialize from=json \$Source ];\r\ + \n } on-error={ }\r\ + \n } \r\ + \n :if ([ :typeof \$Source ] != \"array\") do={:set Source [ :tostr \$1 ]\ + }\r\ + \n :local Result ({});\r\ + \n :foreach KeyValue in=[ :toarray \$Source ] do={\r\ + \n :if ([ :find \$KeyValue \"=\" ]) do={\r\ + \n :local Key [ :pick \$KeyValue 0 [ :find \$KeyValue \"=\" ] ];\r\ + \n :local Value [ :pick \$KeyValue ([ :find \$KeyValue \"=\" ] + 1) [\ + \_:len \$KeyValue ] ];\r\ + \n :if (\$Value=\"true\") do={:set Value true}\r\ + \n :if (\$Value=\"false\") do={:set Value false}\r\ + \n :set (\$Result->\$Key) \$Value;\r\ + \n } else={:set (\$Result->\$KeyValue) true}\r\ + \n }\r\ + \n :return \$Result;\r\ + \n}\r\ + \n:set RequiredRouterOS do={\r\ + \n :local Caller [ :tostr \$1 ];\r\ + \n :local Required [ :tostr \$2 ];\r\ + \n :local Warn [ :tostr \$3 ];\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global VersionToNum;\r\ + \n :if (!(\$Required ~ \"^\\\\d+\\\\.\\\\d+((alpha|beta|rc|\\\\.)\\\\d+|)\ + \\\$\")) do={\r\ + \n \$LogPrint error \$0 (\"No valid RouterOS version: \" . \$Required);\ + \r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ \$VersionToNum \$Required ] > [ \$VersionToNum [ /system/packag\ + e/update/get installed-version ] ]) do={\r\ + \n :if (\$Warn = \"true\") do={\r\ + \n \$LogPrint warning \$0 (\"This \" . [ \$IfThenElse ([ :pick \$Call\ + er 0 ] = (\"\\\$\")) \"function\" \"script\" ] . \\\r\ + \n \" '\" . \$Caller . \"' (at least specific functionality) requir\ + es RouterOS \" . \$Required . \". Please update!\");\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n:set VersionToNum do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :local Multi 0x1000000;\r\ + \n :local Return 0;\r\ + \n :global CharacterReplace;\r\ + \n :set Input [ \$CharacterReplace \$Input \".\" \",\" ];\r\ + \n :foreach I in={ \"zero\"; \"alpha\"; \"beta\"; \"rc\" } do={\r\ + \n :set Input [ \$CharacterReplace \$Input \$I (\",\" . \$I . \",\") ];\ + \r\ + \n }\r\ + \n :foreach Value in=([ :toarray \$Input ], 0) do={\r\ + \n :local Num [ :tonum \$Value ];\r\ + \n :if (\$Multi = 0x100) do={\r\ + \n :if ([ :typeof \$Num ] = \"num\") do={\r\ + \n :set Return (\$Return + 0xff00);\r\ + \n :set Multi (\$Multi / 0x100);\r\ + \n } else={\r\ + \n :if (\$Value = \"zero\") do={ }\r\ + \n :if (\$Value = \"alpha\") do={ :set Return (\$Return + 0x3f00); \ + }\r\ + \n :if (\$Value = \"beta\") do={ :set Return (\$Return + 0x5f00); }\ + \r\ + \n :if (\$Value = \"rc\") do={ :set Return (\$Return + 0x7f00); }\r\ + \n }\r\ + \n }\r\ + \n :if ([ :typeof \$Num ] = \"num\") do={ :set Return (\$Return + (\$Va\ + lue * \$Multi)); }\r\ + \n :set Multi (\$Multi / 0x100);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n:set ScriptLock do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local WaitMax [ :totime \$2 ];\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global ScriptLockOrder;\r\ + \n :if ([ :typeof \$ScriptLockOrder ] = \"nothing\") do={\r\ + \n :set ScriptLockOrder ({});\r\ + \n }\r\ + \n :if ([ :typeof (\$ScriptLockOrder->\$Script) ] = \"nothing\") do={\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n }\r\ + \n :local JobCount do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :return [ :len [ /system/script/job/find where script=\$Script ] ];\ + \r\ + \n }\r\ + \n :local TicketCount do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :local Count 0;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\") do={:set Count (\$Count \ + + 1)}\r\ + \n }\r\ + \n :return \$Count;\r\ + \n }\r\ + \n :local IsFirstTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Check [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if (\$Ticket = \$Check) do={:return true}\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\" && \$Ticket != \$Check) d\ + o={:return false}\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :local AddTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Add [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :while (true) do={\r\ + \n :local Pos [ :len (\$ScriptLockOrder->\$Script) ];\r\ + \n :set (\$ScriptLockOrder->\$Script->\$Pos) \$Add;\r\ + \n :delay 10ms;\r\ + \n :if ((\$ScriptLockOrder->\$Script->\$Pos) = \$Add) do={:return tru\ + e}\r\ + \n }\r\ + \n }\r\ + \n :local RemoveTicket do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :local Remove [ :tostr \$2 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Id,Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :while ((\$ScriptLockOrder->\$Script->\$Id) = \$Remove) do={\r\ + \n :set (\$ScriptLockOrder->\$Script->\$Id);\r\ + \n :delay 10ms;\r\ + \n }\r\ + \n }\r\ + \n }\r\ + \n :local CleanupTickets do={\r\ + \n :local Script [ :tostr \$1 ];\r\ + \n :global ScriptLockOrder;\r\ + \n :foreach Ticket in=(\$ScriptLockOrder->\$Script) do={\r\ + \n :if ([ :typeof \$Ticket ] != \"nothing\") do={:return false}\r\ + \n }\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n }\r\ + \n :if ([ :typeof \$WaitMax ] = \"nil\" ) do={:set WaitMax 0s}\r\ + \n :if ([ :len [ /system/script/find where name=\$Script ] ] = 0) do={\r\ + \n \$LogPrint error \$0 (\"A script named '\" . \$Script . \"' does not\ + \_exist!\");\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$JobCount \$Script ] = 0) do={\r\ + \n \$LogPrint error \$0 (\"No script '\" . \$Script . \"' is running!\"\ + );\r\ + \n :error false;\r\ + \n }\r\ + \n :if ([ \$TicketCount \$Script ] >= [ \$JobCount \$Script ]) do={\r\ + \n \$LogPrint error \$0 (\"More tickets than running scripts '\" . \$Sc\ + ript . \"', resetting!\");\r\ + \n :set (\$ScriptLockOrder->\$Script) ({});\r\ + \n /system/script/job/remove [ find where script=\$Script ];\r\ + \n }\r\ + \n :local MyTicket [ \$GetRandom20CharAlNum 6 ];\r\ + \n \$AddTicket \$Script \$MyTicket;\r\ + \n :local WaitInterval (\$WaitMax / 20);\r\ + \n :local WaitTime \$WaitMax;\r\ + \n :while (\$WaitTime > 0 && \\\r\ + \n ([ \$IsFirstTicket \$Script \$MyTicket ] = false || \\\r\ + \n [ \$TicketCount \$Script ] < [ \$JobCount \$Script ])) do={\r\ + \n :set WaitTime (\$WaitTime - \$WaitInterval);\r\ + \n :delay \$WaitInterval;\r\ + \n }\r\ + \n :if ([ \$IsFirstTicket \$Script \$MyTicket ] = true && \\\r\ + \n [ \$TicketCount \$Script ] = [ \$JobCount \$Script ]) do={\r\ + \n \$RemoveTicket \$Script \$MyTicket;\r\ + \n \$CleanupTickets \$Script;\r\ + \n :return true;\r\ + \n }\r\ + \n \$RemoveTicket \$Script \$MyTicket;\r\ + \n \$LogPrint debug \$0 (\"Script '\" . \$Script . \"' started more than \ + once\" . \\\r\ + \n [ \$IfThenElse (\$WaitTime < \$WaitMax) \" and timed out waiting for\ + \_lock\" \"\" ] . \"...\");\r\ + \n :return false;\r\ + \n}\r\ + \n:set SendNotification do={ :onerror Err {\r\ + \n :global SendNotification2;\r\ + \n \$SendNotification2 ({ origin=\$0; subject=\$1; message=\$2; link=\$3;\ + \_silent=\$4 });\r\ + \n} do={:global ExitOnError; \$ExitOnError \$0 \$Err}\r\ + \n}\r\ + \n:set SendNotification2 do={\r\ + \n :local Notification \$1;\r\ + \n :global NotificationFunctions;\r\ + \n :foreach FunctionName,Discard in=\$NotificationFunctions do={\r\ + \n (\$NotificationFunctions->\$FunctionName) (\"\\\$NotificationFunctio\ + ns->\\\"\" . \$FunctionName . \"\\\"\") \$Notification;\r\ + \n }\r\ + \n}\r\ + \n:set SymbolByUnicodeName do={\r\ + \n :local Name [ :tostr \$1 ];\r\ + \n :global EitherOr;\r\ + \n :global LogPrint;\r\ + \n :global SymbolsExtra;\r\ + \n :local Symbols ({\r\ + \n \"abacus\"=\"\\F0\\9F\\A7\\AE\";\r\ + \n \"alarm-clock\"=\"\\E2\\8F\\B0\";\r\ + \n \"arrow-down\"=\"\\E2\\AC\\87\";\r\ + \n \"arrow-up\"=\"\\E2\\AC\\86\";\r\ + \n \"calendar\"=\"\\F0\\9F\\93\\85\";\r\ + \n \"card-file-box\"=\"\\F0\\9F\\97\\83\";\r\ + \n \"chart-decreasing\"=\"\\F0\\9F\\93\\89\";\r\ + \n \"chart-increasing\"=\"\\F0\\9F\\93\\88\";\r\ + \n \"cloud\"=\"\\E2\\98\\81\";\r\ + \n \"cross-mark\"=\"\\E2\\9D\\8C\";\r\ + \n \"earth\"=\"\\F0\\9F\\8C\\8D\";\r\ + \n \"fire\"=\"\\F0\\9F\\94\\A5\";\r\ + \n \"floppy-disk\"=\"\\F0\\9F\\92\\BE\";\r\ + \n \"gear\"=\"\\E2\\9A\\99\";\r\ + \n \"heart\"=\"\\E2\\99\\A5\";\r\ + \n \"high-voltage-sign\"=\"\\E2\\9A\\A1\";\r\ + \n \"incoming-envelope\"=\"\\F0\\9F\\93\\A8\";\r\ + \n \"information\"=\"\\E2\\84\\B9\";\r\ + \n \"large-orange-circle\"=\"\\F0\\9F\\9F\\A0\";\r\ + \n \"large-red-circle\"=\"\\F0\\9F\\94\\B4\";\r\ + \n \"link\"=\"\\F0\\9F\\94\\97\";\r\ + \n \"lock-with-ink-pen\"=\"\\F0\\9F\\94\\8F\";\r\ + \n \"memo\"=\"\\F0\\9F\\93\\9D\";\r\ + \n \"mobile-phone\"=\"\\F0\\9F\\93\\B1\";\r\ + \n \"pushpin\"=\"\\F0\\9F\\93\\8C\";\r\ + \n \"scissors\"=\"\\E2\\9C\\82\";\r\ + \n \"scroll\"=\"\\F0\\9F\\93\\9C\";\r\ + \n \"smiley-partying-face\"=\"\\F0\\9F\\A5\\B3\";\r\ + \n \"smiley-smiling-face\"=\"\\E2\\98\\BA\";\r\ + \n \"smiley-winking-face-with-tongue\"=\"\\F0\\9F\\98\\9C\";\r\ + \n \"sparkles\"=\"\\E2\\9C\\A8\";\r\ + \n \"speech-balloon\"=\"\\F0\\9F\\92\\AC\";\r\ + \n \"star\"=\"\\E2\\AD\\90\";\r\ + \n \"warning-sign\"=\"\\E2\\9A\\A0\";\r\ + \n \"white-heavy-check-mark\"=\"\\E2\\9C\\85\"\r\ + \n }, \$SymbolsExtra);\r\ + \n :local Magic [ :pick [ /system/clock/get date ] 4 10 ];\r\ + \n :local Special {\r\ + \n \"information-04-01\"=\"\\F0\\9F\\9A\\BB\";\r\ + \n \"large-orange-circle-04-01\"=\"\\F0\\9F\\8D\\8A\";\r\ + \n \"large-orange-circle-10-31\"=\"\\F0\\9F\\8E\\83\";\r\ + \n \"large-red-circle-04-01\"=\"\\F0\\9F\\8D\\92\" };\r\ + \n :if ([ :len (\$Symbols->\$Name) ] = 0) do={\r\ + \n \$LogPrint warning \$0 (\"No symbol available for name '\" . \$Name \ + . \"'!\");\r\ + \n :return \"\";\r\ + \n }\r\ + \n :return ([ \$EitherOr (\$Special->(\$Name . \$Magic)) (\$Symbols->\$Na\ + me) ] . \"\\EF\\B8\\8F\");\r\ + \n}\r\ + \n:set SymbolForNotification do={\r\ + \n :global NotificationsWithSymbols;\r\ + \n :global SymbolByUnicodeName;\r\ + \n :global IfThenElse;\r\ + \n :if (\$NotificationsWithSymbols != true) do={:return [ \$IfThenElse ([\ + \_:len \$2 ] > 0) ([ :tostr \$2 ] . \" \") \"\" ]}\r\ + \n :local Return \"\";\r\ + \n :foreach Symbol in=[ :toarray \$1 ] do={:set Return (\$Return . [ \$Sy\ + mbolByUnicodeName \$Symbol ])}\r\ + \n :return (\$Return . \" \");\r\ + \n}\r\ + \n:set Unix2Dos do={:return [ :tocrlf [ :tostr \$1 ] ]}\r\ + \n:set UrlEncode do={\r\ + \n :local Input [ :tostr \$1 ];\r\ + \n :if ([ :len \$Input ] = 0) do={:return \"\"}\r\ + \n :local Return \"\";\r\ + \n :local Chars (\"\\n\\r !\\\"#\\\$%&'()*+,:;<=>\?@[\\\\]^`{|}~\");\r\ + \n :local Subs { \"%0A\"; \"%0D\"; \"%20\"; \"%21\"; \"%22\"; \"%23\"; \"\ + %24\"; \"%25\"; \"%26\"; \"%27\";\r\ + \n \"%28\"; \"%29\"; \"%2A\"; \"%2B\"; \"%2C\"; \"%3A\"; \"%3B\"; \ + \"%3C\"; \"%3D\"; \"%3E\"; \"%3F\";\r\ + \n \"%40\"; \"%5B\"; \"%5C\"; \"%5D\"; \"%5E\"; \"%60\"; \"%7B\"; \ + \"%7C\"; \"%7D\"; \"%7E\" };\r\ + \n :for I from=0 to=([ :len \$Input ] - 1) do={\r\ + \n :local Char [ :pick \$Input \$I ];\r\ + \n :local Replace [ :find \$Chars \$Char ];\r\ + \n :if ([ :typeof \$Replace ] = \"num\") do={:set Char (\$Subs->\$Repla\ + ce)}\r\ + \n :set Return (\$Return . \$Char);\r\ + \n }\r\ + \n :return \$Return;\r\ + \n}\r\ + \n:set ValidateSyntax do={\r\ + \n :local Code [ :tostr \$1 ];\r\ + \n :global LogPrint;\r\ + \n :onerror Err {\r\ + \n [ :parse (\":local Validate do={\\n\" . \$Code . \"\\n}\") ];\r\ + \n } do={\r\ + \n \$LogPrint debug \$0 (\"Valdation failed: \" . \$Err);\r\ + \n :return false;\r\ + \n }\r\ + \n :return true;\r\ + \n}\r\ + \n:set IsDNSResolving do={\r\ + \n :do {:resolve \"api.telegram.org\"} on-error={:return false}\r\ + \n :return true;\r\ + \n}\r\ + \n:set IsFullyConnected do={\r\ + \n :global IsDNSResolving;\r\ + \n :global IsTimeSync;\r\ + \n :if ([ \$IsDNSResolving ] = false) do={:return false};\r\ + \n :if ([ \$IsTimeSync ] = false) do={:return false};\r\ + \n :return true;\r\ + \n}\r\ + \n:set IsTimeSync do={\r\ + \n :global IsTimeSyncCached;\r\ + \n :global IsTimeSyncResetNtp;\r\ + \n :if (\$IsTimeSyncCached = true) do={:return true}\r\ + \n :if ([ /system/ntp/client/get enabled ] = true) do={\r\ + \n :if ([ /system/ntp/client/get status ] = \"synchronized\") do={\r\ + \n :set IsTimeSyncCached true;\r\ + \n :return true;\r\ + \n }\r\ + \n :local Uptime [ /system/resource/get uptime ];\r\ + \n :if ([ :typeof \$IsTimeSyncResetNtp ] = \"nothing\") do={\r\ + \n :set IsTimeSyncResetNtp \$Uptime;\r\ + \n }\r\ + \n :if (\$Uptime - \$IsTimeSyncResetNtp < 3m) do={:return false}\r\ + \n :log warning \"\$0 The ntp client is configured, but did not sync.\"\ + ;\r\ + \n :set IsTimeSyncResetNtp \$Uptime;\r\ + \n /system/ntp/client/set enabled=no;\r\ + \n :delay 20ms;\r\ + \n /system/ntp/client/set enabled=yes;\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ /system/license/get ]->\"level\" = \"free\" || \\\r\ + \n [ /system/resource/get ]->\"board-name\" = \"x86\") do={\r\ + \n :log debug \"\$0 No ntp client configured, relying on RTC for CHR fr\ + ee license and x86.\";\r\ + \n :return true;\r\ + \n }\r\ + \n :if ([ /ip/cloud/get update-time ] = true) do={\r\ + \n :if ([ :typeof [ /ip/cloud/get public-address ] ] = \"ip\") do={\r\ + \n :set IsTimeSyncCached true;\r\ + \n :return true;\r\ + \n }\r\ + \n :return false;\r\ + \n }\r\ + \n :log debug \"\$0 No time source configured! Returning gracefully...\";\ + \r\ + \n :return true;\r\ + \n}\r\ + \n\r\ + \n# read parts of a large file\r\ + \n:set FetchLargeFiles do={\r\ + \n :local ScriptName [:tostr \$1];\r\ + \n :local fetchUrl [:tostr \$2];\r\ + \n :local CheckCert [:tostr \$3];\r\ + \n :local fetchMode [:tostr \$4];\r\ + \n :local fetchMethod [:tostr \$5];\r\ + \n :local FetchString \"undefined\";\r\ + \n \r\ + \n :global CleanName;\r\ + \n :global FetchUserAgentStr;\r\ + \n :global GetRandom20CharAlNum;\r\ + \n :global IfThenElse;\r\ + \n :global LogPrint;\r\ + \n :global MkDir;\r\ + \n :global RmDir;\r\ + \n :global RmFile;\r\ + \n :global WaitForFile;\r\ + \n\r\ + \n :set CheckCert [ \$IfThenElse (\$CheckCert = \"false\") \"no\" \"yes-w\ + ithout-crl\" ];\r\ + \n #:set fetchMode [ \$IfThenElse (\$fetchMode != \"false\") [:tostr (\"m\ + ode=\" . \$fetchMode)] ];\r\ + \n #:set fetchMethod [\$IfThenElse (\$fetchMethod != \"false\") [:tostr (\ + \"http-method=\\\"\" . \$fetchMethod . \"\\\"\") ]];\r\ + \n \r\ + \n :local DirName (\"tmpfs/\" . [ \$CleanName \$ScriptName ]);\r\ + \n :if ([ \$MkDir \$DirName ] = false) do={\r\ + \n \$LogPrint error \$0 (\"Failed creating directory!\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local FileName (\$DirName . \"/\" . [ \$CleanName \$0 ] . \"-\" . [ \ + \$GetRandom20CharAlNum ]);\r\ + \n #/tool/fetch check-certificate=\$CheckCert \$Url dst-path=\$FileName h\ + ttp-header-field=({ [ \$FetchUserAgentStr \$ScriptName ] }) as-value;\r\ + \n #:log warning (\"\\\$fUrl =\" . \$2 . ({[ \$fMode ]}));\r\ + \n :onerror Err {\r\ + \n :set FetchString (\"/tool/fetch \" . \\\r\ + \n [\$IfThenElse ([\$fetchMethod] != \"false\") (\"http-method=\\\"\"\ + \_. \$fetchMethod . \"\\\" \")] . \\\r\ + \n \"check-certificate=\" . \$CheckCert . \" url=(\\\"\" . \$fetchUrl\ + \_. \"\\\") \" . \\\r\ + \n [\$IfThenElse ([\$fetchMode] != \"false\") (\"mode=\" . \$fetchMod\ + e . \" \")] . \\\r\ + \n http-header-field=({ [ \$FetchUserAgentStr \$ScriptName ] }) as-va\ + lue);\r\ + \n :log warning (\"\\\$FetchSring = \" . \$FetchString);\r\ + \n :put [\$FetchString];\r\ + \n #:put [/tool/fetch \$fetchMode check-certificate=\$CheckCert url=(\$\ + fetchUrl) dst-path=(\$FileName) http-header-field=({ [ \$FetchUserAgentStr\ + \_\$ScriptName ] }) as-value];\r\ + \n } do={\r\ + \n :if ([ \$WaitForFile \$FileName 500ms ] = true) do={\r\ + \n \$RmFile \$FileName;\r\ + \n }\r\ + \n \$LogPrint debug \$0 (\"Failed downloading from \" . \$fetchUrl . \"\ + \_- \" . \$Err);\r\ + \n \$RmDir \$DirName;\r\ + \n :return false;\r\ + \n }\r\ + \n :if ([ \$WaitForFile \$FileName 5s ] = false) do={\r\ + \n \$LogPrint debug \$0 (\"The file downloaded from \" . \$fetchUrl . \ + \" did not show up.\");\r\ + \n :return false;\r\ + \n }\r\ + \n :local FileSize [ /file/get \$FileName size ];\r\ + \n :local Return \"\";\r\ + \n :local VarSize 0;\r\ + \n :while (\$VarSize != \$FileSize) do={\r\ + \n :set Return (\$Return . ([ /file/read offset=\$VarSize chunk-size=32\ + 768 file=\$FileName as-value ]->\"data\"));\r\ + \n :set FileSize [ /file/get \$FileName size ];\r\ + \n :set VarSize [ :len \$Return ];\r\ + \n :if (\$VarSize > \$FileSize) do={\r\ + \n :delay 100ms;\r\ + \n }\r\ + \n }\r\ + \n \$RmDir \$DirName;\r\ + \n :return \$Return;\r\ + \n}\r\ + \n\r\ + \n# Global Environment clear. Use: \"\$GlobalEnvRemove True;\".\r\ + \n:set GlobalEnvRemove do={\r\ + \n :local doRmEnv [ :tostr \$1 ];\r\ + \n :global GlobalScriptsUrl;\r\ + \n :local ScriptsUrl;\r\ + \n :global LogPrint;\r\ + \n :if (\$doRmEnv != \"True\") do={\r\ + \n \$LogPrint error \"GlobalEnvRemove\" (\"This script is needed \\\"T\ + rue\\\" for confirm to remove global environment variables!\");\r\ + \n :return [];\r\ + \n :error true; \r\ + \n } else={\r\ + \n :set ScriptsUrl \$GlobalScriptsUrl;\r\ + \n :delay 5;\r\ + \n :foreach var in=[/system/script/environment print as-value] do={/sy\ + stem/script/environment remove (\$var->\".id\")}\r\ + \n :delay 5;\r\ + \n :global GlobalScriptsUrl \$ScriptsUrl;\r\ + \n \$LogPrint warning \"GlobalEnvRemove\" (\"Done!\");\r\ + \n :return [];\r\ + \n :error false;\r\ + \n }\r\ + \n}\r\ + \n\r\ + \n# Add and fix global scheduler\r\ + \n:put \"Add or/and fix Global scheduler...\";\r\ + \n:local OnEvent \"/system/script { run AM-GlobalConfig; run AM-GlobalFunc\ + ; }\";\r\ + \n:local Comment \"Setup Global Environment for scripts\";\r\ + \n:if ([ :len [ /system/scheduler/find where name=\"AM-SetGlobalEnv\" ] ] \ + = 0) do={\r\ + \n /system/scheduler/add name=\"AM-SetGlobalEnv\" comment=\$Comment on-ev\ + ent=\$OnEvent start-time=startup;\r\ + \n}\r\ + \n:if ([ /system/scheduler/get \"AM-SetGlobalEnv\" comment ] != \$Comment)\ + \_do={\r\ + \n /system/scheduler/set \"AM-SetGlobalEnv\" comment=\$Comment;\r\ + \n}\r\ + \n:if ([ /system/scheduler/get \"AM-SetGlobalEnv\" on-event ] != \$OnEvent\ + ) do={\r\ + \n /system/scheduler/set \"AM-SetGlobalEnv\" on-event=\$OnEvent;\r\ + \n}\r\ + \n# Add Update Script scheduler\r\ + \n:put \"Add or/and fix Update Script scheduler\";\r\ + \n:set OnEvent \":global ScriptInstallUpdate; \\\$ScriptInstallUpdate;\";\ + \r\ + \n:set Comment \"Every Day Check For Update Scripts\";\r\ + \n:if ([ :len [ /system/scheduler/find where name=\"AM-ScriptInstallUpdate\ + \" ] ] = 0) do={\r\ + \n/system/scheduler/add name=\"AM-ScriptInstallUpdate\" comment=\$Comment \ + start-time=startup interval=1d on-event=\$OnEvent;\r\ + \n}\r\ + \n\r\ + \n#:local Resource [ /system/resource/get ];\r\ + \n#:log info (\$ScriptName . \" Loaded on \" . \$Resource->\"board-name\" \ + . \" with RouterOS \" . \$Resource->\"version\" . \" and Architecture \" .\ + \".\");\r\ + \n\r\ + \n:onerror Err {\r\ + \n :global GlobalConfReady;\r\ + \n :retry { :if (\$GlobalConfReady != true) do={ :error (\"Global configs\ + \_not ready.\"); }; } delay=100ms max=5;\r\ + \n\r\ + \n# signal we are ready\r\ + \n:set GlobalFuncReady true;\r\ + \n:set ExitOK true;\r\ + \n:log warning (\"This script \$ScriptName Loaded on \" . \$devBoardName .\ + \_\"\\nRouterOS: \" . \$devCurVersion . \"\\nArchitecture: \" . \$devCurAr\ + ch . \"\\nIdentity: \" . \$Identity);\r\ + \n} do={:global ExitOnError; \$ExitOnError \$0 \$Err;}\r\ + \n\r\ + \n#} do={:global ExitError; \$ExitError \$ExitOK [ :jobname ] \$Err}\r\ + \n# log the successful load of the script, or an error if it failed\r\ + \n#:if ([\$GlobalFuncReady] = true && [\$GlobalConfReady] = true) do={\r\ + \n" +/tool e-mail +set certificate-verification=no from=MikroYellow port=\ + 587 server=smtp.mail.me.com tls=starttls user=vadossio@icloud.com diff --git a/MikroYellow/history/20260302/20260302232010-Diff-Config.rsc b/MikroYellow/history/20260302/20260302232010-Diff-Config.rsc new file mode 100644 index 0000000..e0e03f0 --- /dev/null +++ b/MikroYellow/history/20260302/20260302232010-Diff-Config.rsc @@ -0,0 +1,4 @@ +1c1 +< # 2026-03-02 23:20:10 by RouterOS 7.21.3 +--- +> # 2026-03-02 23:00:10 by RouterOS 7.21.3 -- 2.52.0