# Script name: BackupAndUpdate # #----------SCRIPT INFORMATION--------------------------------------------------- # # Script: Mikrotik RouterOS automatic backup & update # Version: 24.06.04 # Created: 07/08/2018 # Updated: 04/06/2024 # Author: Alexander Tebiev # Website: https://github.com/beeyev # You can contact me by e-mail at tebiev@mail.com # # IMPORTANT! # Minimum supported RouterOS version is v6.43.7 # #----------MODIFY THIS SECTION AS NEEDED---------------------------------------- ## Notification e-mail ## (Make sure you have configured Email settings in Tools -> Email) :local emailAddress "zzt.tzz@gmail.com" ## Script mode, possible values: backup, osupdate, osnotify. # backup - Only backup will be performed. (default value, if none provided) # # osupdate - The script will install a new RouterOS version if it is available. # It will also create backups before and after update process (it does not matter what value `forceBackup` is set to) # Email will be sent only if a new RouterOS version is available. # Change parameter `forceBackup` if you need the script to create backups every time when it runs (even when no updates were found). # # osnotify - The script will send email notifications only (without backups) if a new RouterOS update is available. # Change parameter `forceBackup` if you need the script to create backups every time when it runs. :local scriptMode "osupdate" ## Additional parameter if you set `scriptMode` to `osupdate` or `osnotify` # Set `true` if you want the script to perform backup every time its fired, whatever script mode is set. :local forceBackup false ## Backup encryption password, no encryption if no password. :local backupPassword "" ## If true, passwords will be included in exported config. :local sensitiveDataInConfig true ## Update channel. Possible values: stable, long-term, testing, development :local updateChannel "stable" ## Installs only patch versions of RouterOS updates. ## Works only if you set scriptMode to "osupdate" ## Means that new update will be installed only if MAJOR and MINOR version numbers remained the same as currently installed RouterOS. ## Example: v6.43.6 => major.minor.PATCH ## Script will send information if new version is greater than just patch. :local installOnlyPatchUpdates false ## If true, device public IP address information will be included into the email message :local detectPublicIpAddress true ## Allow anonymous statistics collection. (script mode, device model, OS version) :local allowAnonymousStatisticsCollection true ##------------------------------------------------------------------------------------------## # !!!! DO NOT CHANGE ANYTHING BELOW THIS LINE, IF YOU ARE NOT SURE WHAT YOU ARE DOING !!!! # ##------------------------------------------------------------------------------------------## #Script messages prefix :local SMP "Bkp&Upd:" :log info "\n$SMP script \"Mikrotik RouterOS automatic backup & update\" started." :log info "$SMP Script Mode: `$scriptMode`, forceBackup: `$forceBackup`" :local scriptVersion "24.06.04" ########### # Get current system time and date :local rawTime [/system clock get time] :local rawDate [/system clock get date] # Current time in specific format `hh-mm-ss` :local currentTime ([:pick $rawTime 0 2] . "-" . [:pick $rawTime 3 5] . "-" . [:pick $rawTime 6 8]) # Current date `YYYY-MM-DD` or `YYYY-Mon-DD`, will be defined later in the script :local currentDate "undefined" # Check if the date is in the old format, it should not start with a number :if ([:len [:tonum [:pick $rawDate 0 1]]] = 0) do={ # Convert old format `nov/11/2023` → `2023-nov-11` :set currentDate ([:pick $rawDate 7 11] . "-" . [:pick $rawDate 0 3] . "-" . [:pick $rawDate 4 6]) } else={ # Use new format as is `YYYY-MM-DD` :set currentDate $rawDate } # Combine date and time → `YYYY-MM-DD-hh-mm-ss` or `YYYY-Mon-11-hh-mm-ss` :local currentDateTime ($currentDate . "-" . $currentTime) :local isSoftBased false :local boardName [/system resource get board-name] # Check if board name contains "CHR" or starts with "x86" :if ([:len [:find $boardName "CHR"]] > 0 or [:pick $boardName 0 3] = "x86") do={ :set isSoftBased true } ############### vvvvvvvvv GLOBALS vvvvvvvvv ############### # Function converts standard mikrotik build versions intto the number. # Possible arguments: paramOsVer # Example: # :put [$buGlobalFuncGetOsVerNum paramOsVer="6.49.2"] # Result will be: 64902 # :put [$buGlobalFuncGetOsVerNum paramOsVer=[/system routerboard get current-firmware]] :global buGlobalFuncGetOsVerNum do={ #Script messages prefix :local SMP "Bkp&Upd:" :log info ("$SMP global function buGlobalFuncGetOsVerNum started. Input: `$paramOsVer`") :local osVer $paramOsVer :local allowedChars "0123456789." :local i 0 :local c "" if ([:len $osVer] < 1 or [:len $osVer] > 10) do={ :local errMesg "$SMP getOsVerNum: invalid version string length, given version: `$osVer`" :log error $errMesg :error $errMesg } # validate that each character is a digit or a dot :for i from=0 to=([:len $osVer] - 1) do={ :set c [:pick $osVer $i] :if ([:len [:find $allowedChars $c]] = 0) do={ :local errMesg "$SMP invalid version string, invalid character: `$c`, given version: `$osVer`" :log error $errMesg :error $errMesg } } :local major "" :local minor "00" :local patch "00" :if ([:find $osVer "."] >= 0) do={ :set major [:pick $osVer 0 [:find $osVer "."]] :local rest [:pick $osVer ([:find $osVer "."] + 1) [:len $osVer]] :if ([:find $rest "."] >= 0) do={ :set minor [:pick $rest 0 [:find $rest "."]] :set patch [:pick $rest ([:find $rest "."] + 1) [:len $rest]] } else={ :set minor $rest } } else={:set major $osVer} :if ([:len $minor] = 1) do={:set minor ("0" . $minor)} :if ([:len $patch] = 1) do={:set patch ("0" . $patch)} :local result ($major . $minor . $patch) :log info ("$SMP global function buGlobalFuncGetOsVerNum finished. Result: `$result`") :return $result } # Function creates backups (system and config) and returns array of names of created files. # Possible arguments: # `backupName` | string | backup file name, without extension! # `backupPassword` | string | # `sensitiveDataInConfig` | boolean | # Example: # :put [$buGlobalFuncCreateBackups backupName="daily-backup"] :global buGlobalFuncCreateBackups do={ #Script messages prefix :local SMP "Bkp&Upd:" :log info ("$SMP global function `buGlobalFuncCreateBackups` started, input: `$backupName`") # validate required parameter: backupName :if ([:typeof $backupName] != "str" or [:len $backupName] = 0) do={ :local errMesg "$SMP parameter 'backupName' is required and must be a non-empty string" :log error $errMesg :error $errMesg } :local backupFileSys "$backupName.backup" :local backupFileConfig "$backupName.rsc" :local backupNames {$backupFileSys;$backupFileConfig} ## Perform system backup :if ([:len $backupPassword] = 0) do={ :log info ("$SMP starting backup without password, backup name: `$backupName`") /system backup save dont-encrypt=yes name=$backupName } else={ :log info ("$SMP starting backup with password, backup name: `$backupName`") /system backup save password=$backupPassword name=$backupName } :log info ("$SMP system backup created: `$backupFileSys`") ## Export config file :if ($sensitiveDataInConfig = true) do={ :log info ("$SMP starting export config with sensitive data, backup name: `$backupName`") # Since RouterOS v7 it needs to be explicitly set that we want to export sensitive data :if ([:pick [/system package update get installed-version] 0 1] < 7) do={ :execute "/export compact terse file=$backupName" } else={ :execute "/export compact show-sensitive terse file=$backupName" } } else={ :log info ("$SMP starting export config without sensitive data, backup name: `$backupName`") /export compact hide-sensitive terse file=$backupName } :log info ("$SMP Config export complete: `$backupFileConfig`") :log info ("$SMP Waiting a little to ensure file is written") :delay 20s :log info ("$SMP global function `buGlobalFuncCreateBackups` finished. Created backups: `$backupNames`") :return $backupNames } # Global variable to track current update step :global buGlobalVarUpdateStep ############### ^^^^^^^^^ GLOBALS ^^^^^^^^^ ############### :local deviceOsVerInst [/system package update get installed-version]; :local deviceOsVerInstNum [$buGlobalFuncGetOsVerNum paramOsVer=$deviceOsVerInst]; :local deviceIdentityName [/system identity get name]; :local deviceIdentityNameShort [:pick $deviceIdentityName 0 18] :local deviceUpdateChannel [/system package update get channel]; :local deviceRbModel "CloudHostedRouter"; :local deviceRbSerialNumber "--"; :local deviceRbCurrentFw "--"; :local deviceRbUpgradeFw "--"; :if ($isSoftBased = false) do={ :set deviceRbModel [/system routerboard get model]; :set deviceRbSerialNumber [/system routerboard get serial-number]; :set deviceRbCurrentFw [/system routerboard get current-firmware]; :set deviceRbUpgradeFw [/system routerboard get upgrade-firmware]; }; :local mailBodyDeviceInfo "\n\nDevice information: \nIdentity: $deviceIdentityName \nModel: $deviceRbModel \nSerial number: $deviceRbSerialNumber \nCurrent RouterOS: $deviceOsVerInst ($[/system package update get channel]) $[/system resource get build-time] \nCurrent routerboard FW: $deviceRbCurrentFw \nDevice uptime: $[/system resource get uptime]" # default and fallback public IP detection services :local ipAddressDetectServiceDefault "https://ipv4.mikrotik.ovh/" :local ipAddressDetectServiceFallback "https://api.ipify.org/" # default values, to be set later :local publicIpAddress "not-detected" :local telemetryDataQuery "" :local updateStep $buGlobalVarUpdateStep :do {/system script environment remove buGlobalVarUpdateStep} on-error={} :if ([:len $updateStep] = 0) do={ :set updateStep 1 } ## IP address detection & anonymous statistics collection :if ($updateStep = 1 or $updateStep = 3) do={ :if ($updateStep = 3) do={ :log info ("$SMP Waiting for one minute before continuing to the final step.") :delay 1m } :if ($detectPublicIpAddress = true or $allowAnonymousStatisticsCollection = true) do={ :if ($allowAnonymousStatisticsCollection = true) do={ :set telemetryDataQuery ("\?mode=" . $scriptMode . "&osver=" . $deviceOsVerInst . "&model=" . $deviceRbModel. "&step=" . $updateStep) } :do {:set publicIpAddress ([/tool fetch http-method="get" url=($ipAddressDetectServiceDefault . $telemetryDataQuery) output=user as-value]->"data")} on-error={ :if ($detectPublicIpAddress = true) do={ :log warning "$SMP Could not detect public IP address using default detection service: `$ipAddressDetectServiceDefault`" :log warning "$SMP Trying to detect public ip using fallback detection service: `$ipAddressDetectServiceFallback`" :do { :set publicIpAddress ([/tool fetch http-method="get" url=$ipAddressDetectServiceFallback output=user as-value]->"data") } on-error={ :log warning "$SMP Could not detect public IP address using fallback detection service: `$ipAddressDetectServiceFallback`" } } } :set publicIpAddress ([:pick $publicIpAddress 0 15]) :if ($detectPublicIpAddress = true) do={ # truncate IP to max 15 characters (basic safety) :set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nPublic IP address: $publicIpAddress") :log info "$SMP Public IP address detected: `$publicIpAddress`" } } } # Remove functions from global environment to keep it fresh and clean. :do {/system script environment remove buGlobalFuncGetOsVerNum} on-error={} :do {/system script environment remove buGlobalFuncCreateBackups} on-error={}