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