From a1fa295e4ed767ce0f09930b88989f534503079c Mon Sep 17 00:00:00 2001 From: Alexander <326840+beeyev@users.noreply.github.com> Date: Tue, 29 Nov 2022 12:54:32 +0100 Subject: [PATCH 01/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1ee748..0d23ce5 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ You will see the script working process in the log window. If the script finishe ## Contributors - - [DJ5KP](mailto:dj5kp@dj5kp.de), website: [dj5kp.de](http://dj5kp.de/) + - DJ5KP, website: [dj5kp.de](http://dj5kp.de/) ## License From 267a49829d5be17ff9e041bfaf15f4b0008b51c3 Mon Sep 17 00:00:00 2001 From: Dale Maunder Date: Wed, 12 Jul 2023 11:47:56 +1000 Subject: [PATCH 02/27] Basic spelling and grammatical fixes --- BackupAndUpdate.rsc | 48 ++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index fc126bb..fe03916 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -21,12 +21,12 @@ ## 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 if it is available. -# It will also create backups before and after update process (does not matter what value is set to `forceBackup`) +# 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 notification only (without backups) if a new RouterOS is available. +# 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"; @@ -38,7 +38,7 @@ :local backupPassword "" ## If true, passwords will be included in exported config. -:local sensetiveDataInConfig true; +:local sensitiveDataInConfig true; ## Update channel. Possible values: stable, long-term, testing, development :local updateChannel "stable"; @@ -76,7 +76,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik # Possible arguments: paramOsVer # Example: # :put [$buGlobalFuncGetOsVerNum paramOsVer=[/system routerboard get current-RouterOS]]; -# result will be: 64301, because current RouterOS version is: 6.43.1 +# Result will be: 64301, because current RouterOS version is: 6.43.1 :global buGlobalFuncGetOsVerNum do={ :local osVer $paramOsVer; :local osVerNum; @@ -136,7 +136,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik # Possible arguments: # `backupName` | string | backup file name, without extension! # `backupPassword` | string | -# `sensetiveDataInConfig` | boolean | +# `sensitiveDataInConfig` | boolean | # Example: # :put [$buGlobalFuncCreateBackups name="daily-backup"]; :global buGlobalFuncCreateBackups do={ @@ -155,8 +155,8 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :log info ("$SMP System backup created. $backupFileSys"); ## Export config file - :if ($sensetiveDataInConfig = true) do={ - # since RouterOS v7 it needs to be set precise that we want to export sensitive data + :if ($sensitiveDataInConfig = true) do={ + # 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={ @@ -177,7 +177,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :local scriptVersion "22.11.12"; -#Current date time in format: 2020jan15-221324 +#Current date time in format: yyyymmmdd-hhMMss. E.g. 2020jan15-221324 :local dateTime ([:pick [/system clock get date] 7 11] . [:pick [/system clock get date] 0 3] . [:pick [/system clock get date] 4 6] . "-" . [:pick [/system clock get time] 0 2] . [:pick [/system clock get time] 3 5] . [:pick [/system clock get time] 6 8]); :local isSoftBased false; @@ -234,7 +234,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik ## STEP ONE: Creating backups, checking for new RouterOs version and sending email with backups, -## steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. +## Steps 2 and 3 are fired only if script is set to automatically update device and if a new RouterOs version is available. :if ($updateStep = 1) do={ :log info ("$SMP Performing the first step."); @@ -246,7 +246,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :delay 5s; :set deviceOsVerAvail [/system package update get latest-version]; - # If there is a problem getting information about available RouterOS from server + # If there is a problem getting information about available RouterOS versions from server :if ([:len $deviceOsVerAvail] = 0) do={ :log warning ("$SMP There is a problem getting information about new RouterOS from server."); :set mailSubject ($mailSubject . " Error: No data about new RouterOS!") @@ -275,18 +275,18 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :set isSendEmailRequired true; } - # if new OS version is available to install + # If a new OS version is available to install if ($isOsUpdateAvailable = true and $isSendEmailRequired = true) do={ - # If we only need to notify about new available version + # If we only need to notify about a new available version if ($scriptMode = "osnotify") do={ :set mailSubject ($mailSubject . " New RouterOS is available! v.$deviceOsVerAvail.") :set mailBody ($mailBody . "New RouterOS version is available to install: v.$deviceOsVerAvail ($updateChannel) \r\n$changelogUrl") } - # if we need to initiate RouterOs update process + # If we need to initiate RouterOS update process if ($scriptMode = "osupdate") do={ :set isOsNeedsToBeUpdated true; - # if we need to install only patch updates + # If we need to install only patch updates :if ($installOnlyPatchUpdates = true) do={ #Check if Major and Minor builds are the same. :if ([:pick $deviceOsVerInstNum 0 ([:len $deviceOsVerInstNum]-2)] = [:pick $deviceOsVerAvailNum 0 ([:len $deviceOsVerAvailNum]-2)]) do={ @@ -324,17 +324,17 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :set mailSubject ($mailSubject . " Backup was created."); :set mailBody ($mailBody . "System backups were created and attached to this email."); - :set mailAttachments [$buGlobalFuncCreateBackups backupName=$backupNameFinal backupPassword=$backupPassword sensetiveDataInConfig=$sensetiveDataInConfig]; + :set mailAttachments [$buGlobalFuncCreateBackups backupName=$backupNameFinal backupPassword=$backupPassword sensitiveDataInConfig=$sensitiveDataInConfig]; } else={ :log info ("$SMP There is no need to create a backup."); } - # Combine fisrst step email + # Combine first step email :set mailBody ($mailBody . $mailBodyDeviceInfo . $mailBodyCopyright); } ## STEP TWO: (after first reboot) routerboard firmware upgrade -## steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. +## Steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. :if ($updateStep = 2) do={ :log info ("$SMP Performing the second step."); ## RouterOS is the latest, let's check for upgraded routerboard firmware @@ -358,7 +358,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik } ## STEP THREE: Last step (after second reboot) sending final report -## steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. +## Steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. :if ($updateStep = 3) do={ :log info ("$SMP Performing the third step."); :log info "Bkp&Upd: RouterOS and routerboard upgrade process was completed. New RouterOS version: v.$deviceOsVerInst, routerboard firmware: v.$deviceRbCurrentFw."; @@ -367,7 +367,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :delay 1m; :set mailSubject ($mailSubject . " RouterOS Upgrade is completed, new version: v.$deviceOsVerInst!"); :set mailBody "RouterOS and routerboard upgrade process was completed. \r\nNew RouterOS version: v.$deviceOsVerInst, routerboard firmware: v.$deviceRbCurrentFw. \r\n$changelogUrl \r\n\r\nBackups of the upgraded system are in the attachment of this email. $mailBodyDeviceInfo $mailBodyCopyright"; - :set mailAttachments [$buGlobalFuncCreateBackups backupName=$backupNameAfterUpd backupPassword=$backupPassword sensetiveDataInConfig=$sensetiveDataInConfig]; + :set mailAttachments [$buGlobalFuncCreateBackups backupName=$backupNameAfterUpd backupPassword=$backupPassword sensitiveDataInConfig=$sensitiveDataInConfig]; } # Remove functions from global environment to keep it fresh and clean. @@ -377,7 +377,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik ## ## SENDING EMAIL ## -# Trying to send email with backups in attachment. +# Trying to send email with backups as attachments. :if ($isSendEmailRequired = true) do={ :log info "$SMP Sending email message, it will take around half a minute..."; @@ -409,20 +409,20 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik } -# Fire RouterOs update process +# Fire RouterOS update process if ($isOsNeedsToBeUpdated = true) do={ :if ($isSoftBased = false) do={ ## Set scheduled task to upgrade routerboard firmware on the next boot, task will be deleted when upgrade is done. (That is why you should keep original script name) /system scheduler add name=BKPUPD-UPGRADE-ON-NEXT-BOOT on-event=":delay 5s; /system scheduler remove BKPUPD-UPGRADE-ON-NEXT-BOOT; :global buGlobalVarUpdateStep 2; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0; } else= { - ## If the scrip is executed on CHR, step 2 will be skipped + ## If the script is executed on CHR, step 2 will be skipped /system scheduler add name=BKPUPD-UPGRADE-ON-NEXT-BOOT on-event=":delay 5s; /system scheduler remove BKPUPD-UPGRADE-ON-NEXT-BOOT; :global buGlobalVarUpdateStep 3; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0; }; :log info "$SMP everything is ready to install new RouterOS, going to reboot in a moment!" - ## command is reincarnation of the "upgrade" command - doing exactly the same but under a different name + ## Command is reincarnation of the "upgrade" command - doing exactly the same but under a different name /system package update install; } From 65afc007caa10f636c25405c2b7fd6f4a85fd3ba Mon Sep 17 00:00:00 2001 From: Alexander Tebiev Date: Sat, 11 Nov 2023 19:50:38 +0100 Subject: [PATCH 03/27] small improvements --- BackupAndUpdate.rsc | 54 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index fe03916..150d5c2 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -60,11 +60,28 @@ :log info "\r\n$SMP script \"Mikrotik RouterOS automatic backup & update\" started."; :log info "$SMP Script Mode: $scriptMode, forceBackup: $forceBackup"; -#Check proper email config -:if ([:len $emailAddress] = 0 or [:len [/tool e-mail get address]] = 0 or [:len [/tool e-mail get from]] = 0) do={ - :log error ("$SMP Email configuration is not correct, please check Tools -> Email. Script stopped."); +# Check email settings +:if ([:len $emailAddress] = 0) do={ + :log error ("$SMP \$emailAddress variable is empty. Script stopped."); :error "$SMP bye!"; } +:local emailServer "" +:do { + :set emailServer [/tool e-mail get server]; +} on-error={ + # Old of getting email server before the RouterOS v7.12 + :log info "$SMP Checking email server using old command `/tool e-mail get address`"; + :set emailServer [/tool e-mail get address]; +} +:if ($emailServer = "0.0.0.0") do={ + :log error ("$SMP Email server address is not correct, please check Tools -> Email. Script stopped."); + :error "$SMP bye!"; +} +:if ([:len [/tool e-mail get from]] = 0 or [/tool e-mail get from] = "<>") do={ + :log error ("$SMP Email configuration FROM address is not correct, please check Tools -> Email. Script stopped."); + :error "$SMP bye!"; +} + #Check if proper identity name is set if ([:len [/system identity get name]] = 0 or [/system identity get name] = "MikroTik") do={ @@ -156,12 +173,14 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik ## Export config file :if ($sensitiveDataInConfig = true) do={ - # 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"; + :do { + # Since RouterOS v7 it needs to be explicitly set that we want to export sensitive data + /export compact show-sensitive terse file=$backupName; + } on-error={ + :log info "$SMP Exporting config file with sensitive data using old way `/export compact terse file`"; + /export compact terse file=$backupName; } + } else={ /export compact hide-sensitive terse file=$backupName; } @@ -177,13 +196,24 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :local scriptVersion "22.11.12"; -#Current date time in format: yyyymmmdd-hhMMss. E.g. 2020jan15-221324 -:local dateTime ([:pick [/system clock get date] 7 11] . [:pick [/system clock get date] 0 3] . [:pick [/system clock get date] 4 6] . "-" . [:pick [/system clock get time] 0 2] . [:pick [/system clock get time] 3 5] . [:pick [/system clock get time] 6 8]); +# Current time `hhMMss` +:local currentTime ([:pick [/system clock get time] 0 2] . [:pick [/system clock get time] 3 5] . [:pick [/system clock get time] 6 8]); + +:local currentDateTime ("-" . $currentTime); + +# Detect old date format, Example: `nov/11/2023` +:if ([:len [:tonum [:pick [/system clock get date] 0 1]]] = 0) do={ + :set currentDateTime ([:pick [/system clock get date] 7 11] . [:pick [/system clock get date] 0 3] . [:pick [/system clock get date] 4 6] . "-" . $currentTime); +} +# New date format, Example: `nov/11/2023` +else={ + :set currentDateTime ([/system clock get date] . "-" . $currentTime); +}; :local isSoftBased false; :if ([/system resource get board-name] = "CHR" or [/system resource get board-name] = "x86") do={ :set isSoftBased true; -} +}; :local deviceOsVerInst [/system package update get installed-version]; :local deviceOsVerInstNum [$buGlobalFuncGetOsVerNum paramOsVer=$deviceOsVerInst]; @@ -218,7 +248,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :local mailBodyCopyright "\r\n\r\nMikrotik RouterOS automatic backup & update (ver. $scriptVersion) \r\nhttps://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update"; :local changelogUrl ("Check RouterOS changelog: https://mikrotik.com/download/changelogs/" . $updateChannel . "-release-tree"); -:local backupName "v$deviceOsVerInst_$deviceUpdateChannel_$dateTime"; +:local backupName "v$deviceOsVerInst_$deviceUpdateChannel_$currentDateTime"; :local backupNameBeforeUpd "backup_before_update_$backupName"; :local backupNameAfterUpd "backup_after_update_$backupName"; From f705d4bbf63d7b459e99b6f977655260081d1bc5 Mon Sep 17 00:00:00 2001 From: Alexander Tebiev Date: Sat, 11 Nov 2023 20:53:44 +0100 Subject: [PATCH 04/27] small fixes --- BackupAndUpdate.rsc | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index 150d5c2..74e4c36 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -3,9 +3,9 @@ #----------SCRIPT INFORMATION--------------------------------------------------- # # Script: Mikrotik RouterOS automatic backup & update -# Version: 22.11.12 +# Version: 23.11.11 # Created: 07/08/2018 -# Updated: 12/11/2022 +# Updated: 11/11/2023 # Author: Alexander Tebiev # Website: https://github.com/beeyev # You can contact me by e-mail at tebiev@mail.com @@ -173,14 +173,12 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik ## Export config file :if ($sensitiveDataInConfig = true) do={ - :do { - # Since RouterOS v7 it needs to be explicitly set that we want to export sensitive data - /export compact show-sensitive terse file=$backupName; - } on-error={ - :log info "$SMP Exporting config file with sensitive data using old way `/export compact terse file`"; - /export compact terse file=$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={ /export compact hide-sensitive terse file=$backupName; } @@ -194,19 +192,18 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :global buGlobalVarUpdateStep; ############### ^^^^^^^^^ GLOBALS ^^^^^^^^^ ############### -:local scriptVersion "22.11.12"; +:local scriptVersion "23.11.11"; -# Current time `hhMMss` -:local currentTime ([:pick [/system clock get time] 0 2] . [:pick [/system clock get time] 3 5] . [:pick [/system clock get time] 6 8]); +# Current time `hh-mm-ss` +:local currentTime ([:pick [/system clock get time] 0 2] . "-" . [:pick [/system clock get time] 3 5] . "-" . [:pick [/system clock get time] 6 8]); :local currentDateTime ("-" . $currentTime); # Detect old date format, Example: `nov/11/2023` :if ([:len [:tonum [:pick [/system clock get date] 0 1]]] = 0) do={ :set currentDateTime ([:pick [/system clock get date] 7 11] . [:pick [/system clock get date] 0 3] . [:pick [/system clock get date] 4 6] . "-" . $currentTime); -} -# New date format, Example: `nov/11/2023` -else={ +} else={ + # New date format, Example: `2023-11-11` :set currentDateTime ([/system clock get date] . "-" . $currentTime); }; From b149ed57a4102606a1a7d919c7b77a255bcea49e Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:50:04 +0100 Subject: [PATCH 05/27] v23.11.25 (#46) v23.11.25 (#46) --- BackupAndUpdate.rsc | 55 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index 74e4c36..fa4d6a8 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -3,9 +3,9 @@ #----------SCRIPT INFORMATION--------------------------------------------------- # # Script: Mikrotik RouterOS automatic backup & update -# Version: 23.11.11 +# Version: 23.11.25 # Created: 07/08/2018 -# Updated: 11/11/2023 +# Updated: 25/11/2023 # Author: Alexander Tebiev # Website: https://github.com/beeyev # You can contact me by e-mail at tebiev@mail.com @@ -50,6 +50,12 @@ ## 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 !!!! # ##------------------------------------------------------------------------------------------## @@ -192,7 +198,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :global buGlobalVarUpdateStep; ############### ^^^^^^^^^ GLOBALS ^^^^^^^^^ ############### -:local scriptVersion "23.11.11"; +:local scriptVersion "23.11.25"; # Current time `hh-mm-ss` :local currentTime ([:pick [/system clock get time] 0 2] . "-" . [:pick [/system clock get time] 3 5] . "-" . [:pick [/system clock get time] 6 8]); @@ -253,12 +259,49 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :local mailAttachments [:toarray ""]; +:local ipAddressDetectServiceDefault "https://ipv4.mikrotik.ovh/" +:local ipAddressDetectServiceFallback "https://api.ipify.org/" +: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); + } + + :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." + :log warning "$SMP Trying to detect public ip using fallback detection service." + + :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." + } + } + } + + :if ($detectPublicIpAddress = true) do={ + # Always truncate the string for safety measures + :set publicIpAddress ([:pick $publicIpAddress 0 15]) + :set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\r\nPublic IP address: " . $publicIpAddress); + } + } +} + ## STEP ONE: Creating backups, checking for new RouterOs version and sending email with backups, ## Steps 2 and 3 are fired only if script is set to automatically update device and if a new RouterOs version is available. @@ -330,7 +373,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik if ($isOsNeedsToBeUpdated = true) do={ :log info ("$SMP New RouterOS is going to be installed! v.$deviceOsVerInst -> v.$deviceOsVerAvail"); :set mailSubject ($mailSubject . " New RouterOS is going to be installed! v.$deviceOsVerInst -> v.$deviceOsVerAvail."); - :set mailBody ($mailBody . "Your Mikrotik will be updated to the new RouterOS version from v.$deviceOsVerInst to v.$deviceOsVerAvail (Update channel: $updateChannel) \r\nFinal report with the detailed information will be sent when update process is completed. \r\nIf you have not received second email in the next 10 minutes, then probably something went wrong. (Check your device logs)"); + :set mailBody ($mailBody . "Your Mikrotik will be updated to the new RouterOS version from v.$deviceOsVerInst to v.$deviceOsVerAvail (Update channel: $updateChannel) \r\nA final report with detailed information will be sent once the update process is completed. \r\nIf you do not receive a second email within the next 10 minutes, there may be an issue. Please check your device logs for further information."); #!! There is more code connected to this part and first step at the end of the script. } @@ -386,12 +429,12 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik ## STEP THREE: Last step (after second reboot) sending final report ## Steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. +## This step is executed after some delay :if ($updateStep = 3) do={ :log info ("$SMP Performing the third step."); :log info "Bkp&Upd: RouterOS and routerboard upgrade process was completed. New RouterOS version: v.$deviceOsVerInst, routerboard firmware: v.$deviceRbCurrentFw."; ## Small delay in case mikrotik needs some time to initialize connections - :log info "$SMP The final email with report and backups of upgraded system will be sent in a minute."; - :delay 1m; + :log info "$SMP Sending the final email with report and backups."; :set mailSubject ($mailSubject . " RouterOS Upgrade is completed, new version: v.$deviceOsVerInst!"); :set mailBody "RouterOS and routerboard upgrade process was completed. \r\nNew RouterOS version: v.$deviceOsVerInst, routerboard firmware: v.$deviceRbCurrentFw. \r\n$changelogUrl \r\n\r\nBackups of the upgraded system are in the attachment of this email. $mailBodyDeviceInfo $mailBodyCopyright"; :set mailAttachments [$buGlobalFuncCreateBackups backupName=$backupNameAfterUpd backupPassword=$backupPassword sensitiveDataInConfig=$sensitiveDataInConfig]; From b459aa499e570cc10ff4d5f2c1306073c754aedb Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:03:40 +0100 Subject: [PATCH 06/27] Update README.md --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0d23ce5..e6e8d6d 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,19 @@ # Mikrotik RouterOS automatic backup and update -This script provides an ability to create Mikrotik's daily backups to email. You can also enable automatic RouterOS upgrade or leave only notifications about new firmware versions. +This script allows you to generate daily backups of MikroTik and send them to an email address. You can also choose to enable automatic RouterOS upgrades or receive notifications exclusively for new firmware versions. > 💡 If you have any ideas about the script or you just want to share your opinion, you are welcome to [Discussions](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/discussions), or you can open an [issue](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/issues) if you found a bug. ## Features: -- Ability to choose script operating mode according to your needs. *(Read below)* -- Script creates backups of the whole system and exported config. -- You can set a preferred update channel. -- If automatic updates are enabled, you can set script to install only patch versions of RouterOS updates. *This means if the current RouterOS version is v6.43.6, the script will automatically install v6.43.7 (new patch version) but not v6.44.0 (new minor version), for example.* -- Script includes primary information about the device into the email message. So you can easily find the backup you need among multiple devices. -- For safety purposes, an automatic update process will not be started if script could not send backups to email. -- Routerboard firmware can be automatically upgraded according to the installed RouterOS version. - +- Choose the script's operating mode based on your specific requirements. (See details below) +- The script generates backups of the entire system and exports the configuration. +- Customize your preferred update channel. +- When automatic updates are enabled, you can configure the script to install only patch versions of RouterOS updates. *For example, if the current RouterOS version is v6.43.6, the script will automatically install v6.43.7 (a new patch version) but not v6.44.0 (a new minor version).* +- The script includes essential device information into the email message, making it easy to identify the required backup among multiple devices. +- As a safety measure, the automatic update process will not initiate if the script is unable to send backups via email. +- Routerboard firmware can be upgraded automatically based on the installed RouterOS version. ## Script operating modes: **Backups only** - script creates system and config backups and sends them to specified email as an attachment. Using email account as storage for your backups. From 23e31ae3fd2e83ca983a978b2687b20dcc928e14 Mon Sep 17 00:00:00 2001 From: deniska-666 <137393345+deniska-666@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:21:38 +0100 Subject: [PATCH 07/27] Update BackupAndUpdate.rsc --- BackupAndUpdate.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index fa4d6a8..cad2f47 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -417,7 +417,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik ## Wait until the upgrade is completed :delay 5s; :log info "$SMP routerboard upgrade process was completed, going to reboot in a moment!"; - ## Set scheduled task to send final report on the next boot, task will be deleted when is is done. (That is why you should keep original script name) + ## Set scheduled task to send final report on the next boot, task will be deleted when it is done. (That is why you should keep original script name) /system scheduler add name=BKPUPD-FINAL-REPORT-ON-NEXT-BOOT on-event=":delay 5s; /system scheduler remove BKPUPD-FINAL-REPORT-ON-NEXT-BOOT; :global buGlobalVarUpdateStep 3; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0; ## Reboot system to boot with new firmware /system reboot; From 05dd597d97b8832606022a035b17e7f7cee3d8ab Mon Sep 17 00:00:00 2001 From: deniska-666 <137393345+deniska-666@users.noreply.github.com> Date: Fri, 22 Mar 2024 22:19:27 +0100 Subject: [PATCH 08/27] Update BackupAndUpdate.rsc --- BackupAndUpdate.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index cad2f47..6d72f9e 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -422,7 +422,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik ## Reboot system to boot with new firmware /system reboot; } else={ - :log info "$SMP It appers that your routerboard is already up to date, skipping this step."; + :log info "$SMP It appears that your routerboard is already up to date, skipping this step."; :set updateStep 3; }; } @@ -453,7 +453,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :log info "$SMP Sending email message, it will take around half a minute..."; :do {/tool e-mail send to=$emailAddress subject=$mailSubject body=$mailBody file=$mailAttachments;} on-error={ :delay 5s; - :log error "$SMP could not send email message ($[/tool e-mail get last-status]). Going to try it again in a while." + :log error "$SMP could not send email message ($[/tool e-mail get last-status]). Will attempt redelivery shortly." :delay 5m; From 4f8bdc4d86ba22d2aa8b265c40bbd019c71ad4b6 Mon Sep 17 00:00:00 2001 From: deniska-666 <137393345+deniska-666@users.noreply.github.com> Date: Fri, 22 Mar 2024 22:29:01 +0100 Subject: [PATCH 09/27] Update BackupAndUpdate.rsc --- BackupAndUpdate.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index 6d72f9e..4c18fd5 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -450,7 +450,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik # Trying to send email with backups as attachments. :if ($isSendEmailRequired = true) do={ - :log info "$SMP Sending email message, it will take around half a minute..."; + :log info "$SMP Dispatching email message; estimated completion within 30 seconds."; :do {/tool e-mail send to=$emailAddress subject=$mailSubject body=$mailBody file=$mailAttachments;} on-error={ :delay 5s; :log error "$SMP could not send email message ($[/tool e-mail get last-status]). Will attempt redelivery shortly." @@ -459,7 +459,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :do {/tool e-mail send to=$emailAddress subject=$mailSubject body=$mailBody file=$mailAttachments;} on-error={ :delay 5s; - :log error "$SMP could not send email message ($[/tool e-mail get last-status]) for the second time." + :log error "$SMP failed to send email message ($[/tool e-mail get last-status]) for the second time." if ($isOsNeedsToBeUpdated = true) do={ :set isOsNeedsToBeUpdated false; From c76f77b1f7d91df6ba5eb7b10b2e47b2e7af5f6c Mon Sep 17 00:00:00 2001 From: deniska-666 <137393345+deniska-666@users.noreply.github.com> Date: Fri, 22 Mar 2024 22:48:23 +0100 Subject: [PATCH 10/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6e8d6d..ae66b5c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This script allows you to generate daily backups of MikroTik and send them to an ##### 1. Configure parameters Take the [script](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/BackupAndUpdate.rsc) and configure it's parameters at the begining of the file. This is not difficult because all parameters are well commented. -**Important!** Don't forget to provide correct email address for backups and pay attention a `scriptMode` variable. +**Important!** Don't forget to provide correct email address for backups and pay attention to `scriptMode` variable. ##### 2. Create new script System -> Scripts [Add] From f2e38f0db152cf18a773d80e2d1bc1508e6ae28e Mon Sep 17 00:00:00 2001 From: deniska-666 <137393345+deniska-666@users.noreply.github.com> Date: Fri, 22 Mar 2024 22:51:18 +0100 Subject: [PATCH 11/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae66b5c..adfc7dc 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This script allows you to generate daily backups of MikroTik and send them to an ## How to use > ❗️ **Important** -> Make sure that your device identity does not contain any spaces or specific symbols! `System -> Identity` +> Ensure your device identity is free from spaces and special characters! `System -> Identity` ##### 1. Configure parameters Take the [script](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/BackupAndUpdate.rsc) and configure it's parameters at the begining of the file. From 0aaca7eda2f7395cd376b5e32e3f8b344ec2a18d Mon Sep 17 00:00:00 2001 From: deniska-666 <137393345+deniska-666@users.noreply.github.com> Date: Sat, 23 Mar 2024 12:37:22 +0100 Subject: [PATCH 12/27] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index adfc7dc..282c498 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ This script allows you to generate daily backups of MikroTik and send them to an ## Features: -- Choose the script's operating mode based on your specific requirements. (See details below) -- The script generates backups of the entire system and exports the configuration. -- Customize your preferred update channel. -- When automatic updates are enabled, you can configure the script to install only patch versions of RouterOS updates. *For example, if the current RouterOS version is v6.43.6, the script will automatically install v6.43.7 (a new patch version) but not v6.44.0 (a new minor version).* -- The script includes essential device information into the email message, making it easy to identify the required backup among multiple devices. -- As a safety measure, the automatic update process will not initiate if the script is unable to send backups via email. +- Select the script's operational mode according to your specific needs (details provided below). +- This script is designed to create full system backups and export configurations. +- Customize the update channel according to your preference. +- With automatic updates activated, the script can be set to apply only patch updates for RouterOS. For instance, should the current RouterOS version be v6.43.6, the script will autonomously upgrade to v6.43.7 (a patch update), while avoiding v6.44.0 (a minor update).* +- The script also incorporates vital device details in the email alerts, facilitating easy identification of the necessary backup among several devices. +- For added security, the script is programmed to stop the automatic update process if it fails to dispatch backups via email. - Routerboard firmware can be upgraded automatically based on the installed RouterOS version. ## Script operating modes: From 4bd912658b48928531331f5377c67c76240d791b Mon Sep 17 00:00:00 2001 From: deniska-666 <137393345+deniska-666@users.noreply.github.com> Date: Sat, 23 Mar 2024 13:37:44 +0100 Subject: [PATCH 13/27] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pay attention to "execute the script by typing `/system script run BackupAndUpdate;` in the Terminal." i might have messed with the command as i don't really know how ' " / works ❤️ --- README.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 282c498..23ce2aa 100644 --- a/README.md +++ b/README.md @@ -16,29 +16,29 @@ This script allows you to generate daily backups of MikroTik and send them to an - Routerboard firmware can be upgraded automatically based on the installed RouterOS version. ## Script operating modes: -**Backups only** - script creates system and config backups and sends them to specified email as an attachment. Using email account as storage for your backups. -**Backups and notifications about new RouterOS release** - Except backups, script also checks for new RouterOS firmware release and provides this information in the email. -**Backups and automatic RouterOS upgrade** - Script makes a backup, then checks for new RouterOS version, and if new firmware released, script will initiate upgrade process. By the end, you receive two emails. The first one contains system backups of the previous RouterOS version, the second message will be sent when the upgrade process is done (including backups of the updated system). +**Backups only** - The script generates system and configuration backups and forwards them to a specified email as attachments. It uses your email account as a storage for these backups. +**Backups and notifications about new RouterOS release** - In addition to creating backups, the script also monitors for any new releases of RouterOS firmware and communicates this information via email. +**Backups and automatic RouterOS upgrade** - The script begins by creating a backup, followed by a check for any new versions of RouterOS. If a newer firmware version is detected, the script initiates the upgrade process. Upon completion, two emails are sent: the first includes the system backups from the prior RouterOS version, and the second, sent post-upgrade, contains backups of the updated system. ## How to use > ❗️ **Important** -> Ensure your device identity is free from spaces and special characters! `System -> Identity` +> Ensure your device identity does not contain spaces and special characters! `System -> Identity` ##### 1. Configure parameters Take the [script](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/BackupAndUpdate.rsc) and configure it's parameters at the begining of the file. -This is not difficult because all parameters are well commented. +This step is straightforward as all parameters are well-commented. **Important!** Don't forget to provide correct email address for backups and pay attention to `scriptMode` variable. ##### 2. Create new script System -> Scripts [Add] -**Important!** Script name has to be `BackupAndUpdate` -Put the script which you configured earlier into the source area. +**Important!** Script name must be `BackupAndUpdate` +Insert the script which you configured earlier into the source area. ![](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/howto/script-name.png) ##### 3. Configure mail server Tools -> Email -Set your email server parameters. If you don't have one, i recommend to use [smtp2go.com](https://smtp2go.com "smtp2go.com") service, it allows sending a thousand emails per month for free. +Configure your email server parameters. If you don't have one, i recommend using the [smtp2go.com](https://smtp2go.com "smtp2go.com") service, which allows sending a thousand emails per month for free. ![](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/howto/email-config.png) To check email settings, send a test message by running the following command in terminal: @@ -49,7 +49,7 @@ To check email settings, send a test message by running the following command in ##### 4. Create scheduled task System -> Scheduler [Add] Name: `Backup And Update` -Start Time: `03:10:00` (the start time has to be different for all your mikrotik device in a chain) +Start Time: `03:10:00` (the start time has to be different for all your mikrotik devices in a chain) Interval: `1d 00:00:00` On Event: `/system script run BackupAndUpdate;` ![](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/howto/scheduler-task.png) @@ -59,9 +59,14 @@ Or you can use this command to create the task: /system scheduler add name="Firmware Updater" on-event="/system script run BackupAndUpdate;" start-time=03:10:00 interval=1d comment="" disabled=no ``` ##### 5. Test the script -When everything is done, you need to make sure that the script is working correctly. -To do so, open a New Terminal and Log window in your WinBox, then run the script manually by executing this command `/system script run BackupAndUpdate;` in Terminal. -You will see the script working process in the log window. If the script finished without errors, check your email, there is a fresh message with backups from your MikroTik waiting for you 🎉 +Once everything is set up, it's important to verify that the script is functioning properly. +To do this, open a New Terminal and a Log window in your WinBox, then manually execute the script by typing `/system script run BackupAndUpdate;` in the Terminal. +You will see the script the script's operation in the log window. If the script completes without any errors, check your email. You'll find a new message with backups from your MikroTik awaiting you. 🎉 + + + + + ## Contributors From 3a1aae13b9c4a63d47045dc8727e837212b2e3a5 Mon Sep 17 00:00:00 2001 From: deniska-666 <137393345+deniska-666@users.noreply.github.com> Date: Sat, 23 Mar 2024 14:07:43 +0100 Subject: [PATCH 14/27] Update BackupAndUpdate.rsc --- BackupAndUpdate.rsc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index 4c18fd5..9a91ca6 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -43,7 +43,7 @@ ## Update channel. Possible values: stable, long-term, testing, development :local updateChannel "stable"; -## Install only patch versions of RouterOS updates. +## 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 @@ -341,7 +341,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik }; if ($forceBackup = true) do={ - # In this case the script will always send email, because it has to create backups + # In this case the script will always send an email, because it has to create backups :set isSendEmailRequired true; } @@ -396,7 +396,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :set mailAttachments [$buGlobalFuncCreateBackups backupName=$backupNameFinal backupPassword=$backupPassword sensitiveDataInConfig=$sensitiveDataInConfig]; } else={ - :log info ("$SMP There is no need to create a backup."); + :log info ("$SMP Creating a backup is not necessary."); } # Combine first step email From a5a5ae6bd48682ca6db7634d8a5d738d5fb3aa5a Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:19:02 +0200 Subject: [PATCH 15/27] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e6e8d6d..977d8b3 100644 --- a/README.md +++ b/README.md @@ -63,16 +63,18 @@ When everything is done, you need to make sure that the script is working correc To do so, open a New Terminal and Log window in your WinBox, then run the script manually by executing this command `/system script run BackupAndUpdate;` in Terminal. You will see the script working process in the log window. If the script finished without errors, check your email, there is a fresh message with backups from your MikroTik waiting for you 🎉 -## Contributors - +## Acknowledgements +I would like to extend my sincere gratitude to the following individuals who have contributed to this project: - DJ5KP, website: [dj5kp.de](http://dj5kp.de/) +Special thanks to the talented people who are working at [MikroTik](https://mikrotik.com) for their contributions in creating such outstanding products. + ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. --- -If you love this project, please consider giving me a ⭐ +If you love this project, please buy more mikrotiks ;) and consider giving me a ⭐ [__Buy me a coffee! :coffee:__](https://www.buymeacoffee.com/beeyev) From 8676bdd908692a7311af2fc5672cbdedc89439ae Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:53:32 +0200 Subject: [PATCH 16/27] bugfix - cloud based detection --- BackupAndUpdate.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index 9a91ca6..e542d2b 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -214,7 +214,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik }; :local isSoftBased false; -:if ([/system resource get board-name] = "CHR" or [/system resource get board-name] = "x86") do={ +:if ([:pick [/system resource get board-name] 0 3] = "CHR" or [/system resource get board-name] = "x86") do={ :set isSoftBased true; }; From 25df3b88f10fa1212f699e03f40fa513fb9dd3f3 Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:01:04 +0200 Subject: [PATCH 17/27] Update BackupAndUpdate.rsc --- BackupAndUpdate.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index e542d2b..8e207be 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -3,9 +3,9 @@ #----------SCRIPT INFORMATION--------------------------------------------------- # # Script: Mikrotik RouterOS automatic backup & update -# Version: 23.11.25 +# Version: 24.06.04 # Created: 07/08/2018 -# Updated: 25/11/2023 +# Updated: 04/06/2024 # Author: Alexander Tebiev # Website: https://github.com/beeyev # You can contact me by e-mail at tebiev@mail.com From 750f655c7c3769d42d85b5ddf82f66a6bfaaad43 Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Fri, 7 Jun 2024 20:04:28 +0200 Subject: [PATCH 18/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ea1dc9..c0e24e4 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Insert the script which you configured earlier into the source area. ##### 3. Configure mail server Tools -> Email -Configure your email server parameters. If you don't have one, i recommend using the [smtp2go.com](https://smtp2go.com "smtp2go.com") service, which allows sending a thousand emails per month for free. +Configure your email server parameters. If you don't have one, i recommend using the [smtp2go.com](https://smtp2go.com "smtp2go.com") service, which allows sending a thousand emails per month for free. ![](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/howto/email-config.png) To check email settings, send a test message by running the following command in terminal: From 532a099778c729574093f9f15a52244910a081d3 Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Mon, 17 Jun 2024 09:59:22 +0200 Subject: [PATCH 19/27] Update BackupAndUpdate.rsc --- BackupAndUpdate.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index 8e207be..5d629c7 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -214,7 +214,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik }; :local isSoftBased false; -:if ([:pick [/system resource get board-name] 0 3] = "CHR" or [/system resource get board-name] = "x86") do={ +:if ([:pick [/system resource get board-name] 0 3] = "CHR" or [:pick [/system resource get board-name] 0 3] = "x86") do={ :set isSoftBased true; }; From 6aafe7d8c3e7814fd90b6fe24931d34607beaa5a Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:26:47 +0200 Subject: [PATCH 20/27] Update BackupAndUpdate.rsc --- BackupAndUpdate.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index 5d629c7..b6ed63a 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -198,7 +198,7 @@ if ([:len [/system identity get name]] = 0 or [/system identity get name] = "Mik :global buGlobalVarUpdateStep; ############### ^^^^^^^^^ GLOBALS ^^^^^^^^^ ############### -:local scriptVersion "23.11.25"; +:local scriptVersion "24.06.04"; # Current time `hh-mm-ss` :local currentTime ([:pick [/system clock get time] 0 2] . "-" . [:pick [/system clock get time] 3 5] . "-" . [:pick [/system clock get time] 6 8]); From 6c7bda99940d6a52c58772ba87f39efccada8c31 Mon Sep 17 00:00:00 2001 From: Alexander Tebiev Date: Sun, 30 Mar 2025 18:14:17 +0200 Subject: [PATCH 21/27] update gitignore file --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2771fe3..1ea45ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -/ignore \ No newline at end of file +ignore/ +.idea/ +.vscode/ From 0a89615c4404d644291cde0a39759b2698f9ad9f Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Sat, 12 Apr 2025 21:02:33 +0200 Subject: [PATCH 22/27] Update FUNDING.yml --- .github/FUNDING.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index a888ef0..b77fe0e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,4 @@ # These are supported funding model platforms -custom: https://buymeacoffee.com/beeyev +ko_fi: beeyev +liberapay: beeyev +buy_me_a_coffee: beeyev From 5f839c943c0a472a68c6855b9e0f4337be9ef1eb Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Sat, 12 Apr 2025 22:31:55 +0200 Subject: [PATCH 23/27] v23.11.25 (#64) v23.11.25 (#64) --- BackupAndUpdate.rsc | 946 ++++++++++++++++++++++++++++---------------- 1 file changed, 596 insertions(+), 350 deletions(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index b6ed63a..b66b094 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -3,9 +3,9 @@ #----------SCRIPT INFORMATION--------------------------------------------------- # # Script: Mikrotik RouterOS automatic backup & update -# Version: 24.06.04 +# Version: 25.04.12 # Created: 07/08/2018 -# Updated: 04/06/2024 +# Updated: 12/04/2025 # Author: Alexander Tebiev # Website: https://github.com/beeyev # You can contact me by e-mail at tebiev@mail.com @@ -15,7 +15,7 @@ # #----------MODIFY THIS SECTION AS NEEDED---------------------------------------- ## Notification e-mail -## (Make sure you have configurated Email settings in Tools -> Email) +## (Make sure you have configured Email settings in Tools -> Email) :local emailAddress "yourmail@example.com"; ## Script mode, possible values: backup, osupdate, osnotify. @@ -28,472 +28,718 @@ # # 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"; +: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 it's fired, whatever script mode is set. -:local forceBackup false; +# 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; +:local sensitiveDataInConfig true ## Update channel. Possible values: stable, long-term, testing, development -:local updateChannel "stable"; +: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 +## Install only patch updates (requires scriptMode = "osupdate") +## Works only for `stable` and `long-term` channels. +## Update will run only if MAJOR and MINOR versions match the current one +## Example: current = v6.43.2 → v6.43.6 = allowed, v6.44.1 = skipped ## Script will send information if new version is greater than just patch. -:local installOnlyPatchUpdates false; +:local installOnlyPatchUpdates false ## If true, device public IP address information will be included into the email message -:local detectPublicIpAddress true; +:local detectPublicIpAddress true -## Allow anonymous statistics collection. (script mode, device model, OS version) -:local allowAnonymousStatisticsCollection true; +## Allow anonymous statistics collection. (script mode and generic non-sensitive device info) +:local allowAnonymousStatisticsCollection true ##------------------------------------------------------------------------------------------## # !!!! DO NOT CHANGE ANYTHING BELOW THIS LINE, IF YOU ARE NOT SURE WHAT YOU ARE DOING !!!! # ##------------------------------------------------------------------------------------------## +:local scriptVersion "25.04.12" + +# default and fallback public IP detection services +:local ipAddressDetectServiceDefault "https://ipv4.mikrotik.ovh/" +:local ipAddressDetectServiceFallback "https://api.ipify.org/" + #Script messages prefix :local SMP "Bkp&Upd:" -:log info "\r\n$SMP script \"Mikrotik RouterOS automatic backup & update\" started."; -:log info "$SMP Script Mode: $scriptMode, forceBackup: $forceBackup"; +:local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details." +:log info "\n\n$SMP Script \"Mikrotik RouterOS automatic backup & update\" v.$scriptVersion started." +:log info "$SMP Script Mode: `$scriptMode`, Update channel: `$updateChannel`, Force backup: `$forceBackup`, Install only patch updates: `$installOnlyPatchUpdates`" -# Check email settings -:if ([:len $emailAddress] = 0) do={ - :log error ("$SMP \$emailAddress variable is empty. Script stopped."); - :error "$SMP bye!"; -} -:local emailServer "" -:do { - :set emailServer [/tool e-mail get server]; -} on-error={ - # Old of getting email server before the RouterOS v7.12 - :log info "$SMP Checking email server using old command `/tool e-mail get address`"; - :set emailServer [/tool e-mail get address]; -} -:if ($emailServer = "0.0.0.0") do={ - :log error ("$SMP Email server address is not correct, please check Tools -> Email. Script stopped."); - :error "$SMP bye!"; -} -:if ([:len [/tool e-mail get from]] = 0 or [/tool e-mail get from] = "<>") do={ - :log error ("$SMP Email configuration FROM address is not correct, please check Tools -> Email. Script stopped."); - :error "$SMP bye!"; -} +############### vvvvvvvvv FUNCTIONS vvvvvvvvv ############### - -#Check if proper identity name is set -if ([:len [/system identity get name]] = 0 or [/system identity get name] = "MikroTik") do={ - :log warning ("$SMP Please set identity name of your device (System -> Identity), keep it short and informative."); -}; - -############### vvvvvvvvv GLOBALS vvvvvvvvv ############### -# Function converts standard mikrotik build versions to the number. -# Possible arguments: paramOsVer +# Function: FuncGetRunningOsVersion +# ---------------------------- +# Returns currently running RouterOS version +# # Example: -# :put [$buGlobalFuncGetOsVerNum paramOsVer=[/system routerboard get current-RouterOS]]; -# Result will be: 64301, because current RouterOS version is: 6.43.1 -:global buGlobalFuncGetOsVerNum do={ - :local osVer $paramOsVer; - :local osVerNum; - :local osVerMicroPart; - :local zro 0; - :local tmp; +# :put [$FuncGetRunningOsVersion] # Output: 6.48.1 +:local FuncGetRunningOsVersion do={ + :local runningOsAndChannel [/system resource get version] - # Replace word `beta` with dot - :local isBetaPos [:tonum [:find $osVer "beta" 0]]; - :if ($isBetaPos > 1) do={ - :set osVer ([:pick $osVer 0 $isBetaPos] . "." . [:pick $osVer ($isBetaPos + 4) [:len $osVer]]); - } - # Replace word `rc` with dot - :local isRcPos [:tonum [:find $osVer "rc" 0]]; - :if ($isRcPos > 1) do={ - :set osVer ([:pick $osVer 0 $isRcPos] . "." . [:pick $osVer ($isRcPos + 2) [:len $osVer]]); + :local spacePos [:find $runningOsAndChannel " "] + :if ([:len $spacePos] = 0) do={ + :log error "Bkp&Upd: Could not extract installed OS version string: `$runningOsAndChannel`. Script stopped." + :error "Bkp&Upd: script stopped due to an error. Please check logs for more details." } - :local dotPos1 [:find $osVer "." 0]; + :local versionOnly [:pick $runningOsAndChannel 0 $spacePos] - :if ($dotPos1 > 0) do={ - - # AA - :set osVerNum [:pick $osVer 0 $dotPos1]; - - :local dotPos2 [:find $osVer "." $dotPos1]; - #Taking minor version, everything after first dot - :if ([:len $dotPos2] = 0) do={:set tmp [:pick $osVer ($dotPos1+1) [:len $osVer]];} - #Taking minor version, everything between first and second dots - :if ($dotPos2 > 0) do={:set tmp [:pick $osVer ($dotPos1+1) $dotPos2];} - - # AA 0B - :if ([:len $tmp] = 1) do={:set osVerNum "$osVerNum$zro$tmp";} - # AA BB - :if ([:len $tmp] = 2) do={:set osVerNum "$osVerNum$tmp";} - - :if ($dotPos2 > 0) do={ - :set tmp [:pick $osVer ($dotPos2+1) [:len $osVer]]; - # AA BB 0C - :if ([:len $tmp] = 1) do={:set osVerNum "$osVerNum$zro$tmp";} - # AA BB CC - :if ([:len $tmp] = 2) do={:set osVerNum "$osVerNum$tmp";} - } else={ - # AA BB 00 - :set osVerNum "$osVerNum$zro$zro"; - } - } else={ - # AA 00 00 - :set osVerNum "$osVer$zro$zro$zro$zro"; - } - - :return $osVerNum; + :return $versionOnly } +# Function: FuncGetRunningOsChannel +# ---------------------------- +# Returns currently running RouterOS channel (stable, long-term, testing, development) +# +# Example: +# :put [$FuncGetRunningOsChannel] # Output: stable +:local FuncGetRunningOsChannel do={ + :local runningOsAndChannel [/system resource get version] + :local errorMessage "Bkp&Upd: Could not extract installed OS channel from version string: `$runningOsAndChannel`. Script stopped." + :local exitErrorMessage "Bkp&Upd: script stopped due to an error. Please check logs for more details." -# Function creates backups (system and config) and returns array with names + :local open [:find $runningOsAndChannel "("] + :if ([:len $open] = 0) do={ + :log error ($errorMessage . " (1)") + :error $exitErrorMessage + } + + :local rest [:pick $runningOsAndChannel ($open+1) [:len $runningOsAndChannel]] + + :local close [:find $rest ")"] + :if ([:len $close] = 0) do={ + :log error ($errorMessage . " (2)") + :error $exitErrorMessage + } + + :local channel [:pick $rest 0 $close] + :if ([:len $channel] = 0) do={ + :log error ($errorMessage . " (3)") + :error $exitErrorMessage + } + + :return $channel +} + +# Function: FuncIsPatchUpdateOnly +# ---------------------------- +# Determines if two RouterOS version strings differ only by the patch version. +# +# Parameters: +# `version1` | string | The first version string (e.g., "6.2.1"). +# `version2` | string | The second version string (e.g., "6.2.4"). +# +# Returns: +# boolean | true if only the patch versions differ; false otherwise. +# +# Example: +# :put [$FuncIsPatchUpdateOnly "6.2.1" "6.2.4"] # Output: true +# :put [$FuncIsPatchUpdateOnly "6.2.1" "6.3.1"] # Output: false +:local FuncIsPatchUpdateOnly do={ + :local ver1 $1 + :local ver2 $2 + + # Internal function to extract the major and minor components from a version string. + :local extractMajorMinor do={ + :local ver $1 + :local dot1 [:find $ver "."] + :if ($dot1 = -1) do={ :return $ver } + + :local major [:pick $ver 0 $dot1] + :local rest [:pick $ver ($dot1 + 1) [:len $ver]] + :local dot2 [:find $rest "."] + :local minor $rest + :if ($dot2 >= 0) do={ :set minor [:pick $rest 0 $dot2] } + + :return ($major . "." . $minor) + } + + # Compare the major and minor components of both version strings. + :if ([$extractMajorMinor $ver1] = [$extractMajorMinor $ver2]) do={ + :return true + } + :return false +} + +# 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 name="daily-backup"]; -:global buGlobalFuncCreateBackups do={ - :log info ("$SMP Global function \"buGlobalFuncCreateBackups\" was fired."); +# :put [$FuncCreateBackups "daily-backup"] +:local FuncCreateBackups do={ + :local backupName $1 + :local backupPassword $2 + :local sensitiveDataInConfig $3 - :local backupFileSys "$backupName.backup"; - :local backupFileConfig "$backupName.rsc"; - :local backupNames {$backupFileSys;$backupFileConfig}; + #Script messages prefix + :local SMP "Bkp&Upd:" + :local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details." + :log info ("$SMP global function `FuncCreateBackups` started, input: `$backupName`") - ## Make system backup - :if ([:len $backupPassword] = 0) do={ - /system backup save dont-encrypt=yes name=$backupName; - } else={ - /system backup save password=$backupPassword name=$backupName; + # validate required parameter: backupName + :if ([:typeof $backupName] != "str" or [:len $backupName] = 0) do={ + :log error "$SMP parameter 'backupName' is required and must be a non-empty string" + :error $exitErrorMessage } - :log info ("$SMP System backup created. $backupFileSys"); - ## Export config file + :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"; + :if ([:pick [/system resource get version] 0 1] < 7) do={ + :execute "/export compact terse file=$backupName" } else={ - :execute "/export compact show-sensitive terse file=$backupName"; + :execute "/export compact show-sensitive terse file=$backupName" } } else={ - /export compact hide-sensitive terse file=$backupName; + :log info ("$SMP starting export config without sensitive data, backup name: `$backupName`") + /export compact hide-sensitive terse file=$backupName } - :log info ("$SMP Config file was exported. $backupFileConfig, the script execution will be paused for a moment."); - #Delay after creating backups - :delay 20s; - :return $backupNames; + :log info ("$SMP Config export complete: `$backupFileConfig`") + :log info ("$SMP Waiting a little to ensure backup files are written") + + :delay 20s + + :if ([:len [/file find name=$backupFileSys]] > 0) do={ + :log info ("$SMP system backup file successfully saved to the file system: `$backupFileSys`") + } else={ + :log error ("$SMP system backup was not created, file does not exist: `$backupFileSys`") + :error $exitErrorMessage + } + + :if ([:len [/file find name=$backupFileConfig]] > 0) do={ + :log info ("$SMP config backup file successfully saved to the file system: `$backupFileConfig`") + } else={ + :log error ("$SMP config backup was not created, file does not exist: `$backupFileConfig`") + :error $exitErrorMessage + } + + :log info ("$SMP global function `FuncCreateBackups` finished. Created backups, system: `$backupFileSys`, config: `$backupFileConfig`") + + :return $backupNames } -:global buGlobalVarUpdateStep; -############### ^^^^^^^^^ GLOBALS ^^^^^^^^^ ############### +# Function: FuncSendEmailSafe +# --------------------------- +# Sends an email and checks if it was sent successfully. +# +# Parameters: +# $1 - to (email address) +# $2 - subject +# $3 - body +# $4 - file attachments (optional; pass "" if not needed) +# +# Example: +# :do { +# $FuncSendEmailSafe "admin@domain.com" "Backup Done" "Backup complete." "backup1.backup" +# } on-error={ +# :log error "Email failed to send" +# } +:local FuncSendEmailSafe do={ -:local scriptVersion "24.06.04"; + :local emailTo $1 + :local emailSubject $2 + :local emailBody $3 + :local emailAttachments $4 -# Current time `hh-mm-ss` -:local currentTime ([:pick [/system clock get time] 0 2] . "-" . [:pick [/system clock get time] 3 5] . "-" . [:pick [/system clock get time] 6 8]); + :local SMP "Bkp&Upd:" + :local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details." -:local currentDateTime ("-" . $currentTime); + :log info "$SMP Attempting to send email to `$emailTo`" -# Detect old date format, Example: `nov/11/2023` -:if ([:len [:tonum [:pick [/system clock get date] 0 1]]] = 0) do={ - :set currentDateTime ([:pick [/system clock get date] 7 11] . [:pick [/system clock get date] 0 3] . [:pick [/system clock get date] 4 6] . "-" . $currentTime); + # SAFETY: wait for any previously queued email to finish + :local waitTimeoutPre 60 + :local waitCounterPre 0 + :while (([/tool e-mail get last-status] = "resolving-dns" or [/tool e-mail get last-status] = "in-progress")) do={ + :if ($waitCounterPre >= $waitTimeoutPre) do={ + :log error "$SMP Email send aborted: previous send did not complete after $waitTimeoutPre seconds" + :error $exitErrorMessage + } + + :log info "$SMP Waiting for previous email to finish (status: $[/tool e-mail get last-status])..." + :delay 1s + :set waitCounterPre ($waitCounterPre + 1) + } + + # Send the email + :do { + /tool e-mail send to=$emailTo subject=$emailSubject body=$emailBody file=$emailAttachments + } on-error={ + :log error "$SMP Email send command failed to execute. Check logs and verify email settings." + :error $exitErrorMessage + } + + # Wait for send status to change from "in-progress" / "resolving-dns" + :local waitTimeout 60 + :local waitCounter 0 + :local emailStatus "" + :log info "$SMP Waiting for email to be sent, timeout in `$waitTimeout` seconds..." + :while ($waitCounter < $waitTimeout) do={ + :set emailStatus [/tool e-mail get last-status] + :if ($emailStatus != "in-progress" and $emailStatus != "resolving-dns") do={ + :log info "$SMP Email send status received: $emailStatus" + + # exit loop + :set waitCounter $waitTimeout + } else={ + :delay 1s + :set waitCounter ($waitCounter + 1) + } + } + + # Final decision based on last status + :if ($emailStatus = "succeeded") do={ + :log info "$SMP Email successfully sent to `$emailTo`" + } else={ + :log error "$SMP Email failed to send. Status: `$emailStatus`. Check logs for more details and verify email settings." + :error $exitErrorMessage + } +} + +# Global variable to track current update step +# They need to be initialized here first to be available in the script +:global buGlobalVarTargetOsVersion + +:global buGlobalVarScriptStep +:local scriptStep $buGlobalVarScriptStep +:do {/system script environment remove buGlobalVarScriptStep} on-error={} +:if ([:len $scriptStep] = 0) do={ + :set scriptStep 1 +} +############### ^^^^^^^^^ FUNCTIONS ^^^^^^^^^ ############### + + +# +# Initial validation +# + +## Check email settings +:if ([:len $emailAddress] < 3) do={ + :log error ("$SMP Script parameter `\$emailAddress` is not set, or contains invalid value. Script stopped.") + :error $exitErrorMessage +} + +# Values will be defined later in the script +:local emailServer "" +:local emailFromAddress [/tool e-mail get from] + +:log info "$SMP Validating email settings..." +:do { + :set emailServer [/tool e-mail get server] +} on-error={ + # This is a workaround for the RouterOS v7.12 and older versions + :set emailServer [/tool e-mail get address] +} +:if ($emailServer = "0.0.0.0") do={ + :log error ("$SMP Email server address is not correct: `$emailServer`, please check `Tools -> Email`. Script stopped."); + :error $exitErrorMessage +} +:if ([:len $emailFromAddress] < 3) do={ + :log error ("$SMP Email configuration FROM address is not correct: `$emailFromAddress`, please check `Tools -> Email`. Script stopped."); + :error $exitErrorMessage +} + +# Script mode validation +:if ($scriptMode != "backup" and $scriptMode != "osupdate" and $scriptMode != "osnotify") do={ + :log error ("$SMP Script parameter `\$scriptMode` is not set, or contains invalid value: `$scriptMode`. Script stopped.") + :error $exitErrorMessage +} + +# Update channel validation +:if ($updateChannel != "stable" and $updateChannel != "long-term" and $updateChannel != "testing" and $updateChannel != "development") do={ + :log error ("$SMP Script parameter `\$updateChannel` is not set, or contains invalid value: `$updateChannel`. Script stopped.") + :error $exitErrorMessage +} + +# Check if the script is set to install only patch updates and if the update channel is valid +:if ($scriptMode = "osupdate" and $installOnlyPatchUpdates = true) do={ + :if ($updateChannel != "stable" and $updateChannel != "long-term") do={ + :log error ("$SMP Script is set to install only patch updates, but the update channel is not valid: `$updateChannel`. Only `stable` and `long-term` channels supported. Script stopped.") + :error $exitErrorMessage + } + + :local susRunningOsChannel [$FuncGetRunningOsChannel] + + :if ($susRunningOsChannel != "stable" and $susRunningOsChannel != "long-term") do={ + :log error ("$SMP Script is set to install only patch updates, but the installed RouterOS version is not from `stable` or `long-term` channel: `$susRunningOsChannel`. Script stopped.") + :error $exitErrorMessage + } +} + +# +# Get current system date and time +# +: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={ - # New date format, Example: `2023-11-11` - :set currentDateTime ([/system clock get date] . "-" . $currentTime); + # Use new format as is `YYYY-MM-DD` + :set currentDate $rawDate +} + +## Combine date and time → `YYYY-MM-DD-hh-mm-ss` or `YYYY-Mon-DD-hh-mm-ss` +:local currentDateTime ($currentDate . "-" . $currentTime) + +##### + +:local deviceBoardName [/system resource get board-name] + +## Check if it's a cloud hosted router or a hardware based device +:local isCloudHostedRouter false; +:if ([:pick $deviceBoardName 0 3] = "CHR" or [:pick $deviceBoardName 0 3] = "x86") do={ + :set isCloudHostedRouter true; }; -:local isSoftBased false; -:if ([:pick [/system resource get board-name] 0 3] = "CHR" or [:pick [/system resource get board-name] 0 3] = "x86") do={ - :set isSoftBased true; -}; - -:local deviceOsVerInst [/system package update get installed-version]; -:local deviceOsVerInstNum [$buGlobalFuncGetOsVerNum paramOsVer=$deviceOsVerInst]; -:local deviceOsVerAvail ""; -:local deviceOsVerAvailNum 0; :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 "--"; +: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]; +:if ($isCloudHostedRouter = 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 isOsUpdateAvailable false; -:local isOsNeedsToBeUpdated false; +:local runningOsChannel [$FuncGetRunningOsChannel] +:local runningOsVersion [$FuncGetRunningOsVersion] +:local deviceOsVerAndChannelRunning [/system resource get version] -:local isSendEmailRequired true; +:local backupNameTemplate "backup_v$runningOsVersion_$runningOsChannel_$currentDateTime" +:local backupNameBeforeUpdate "backup_before_update_$backupNameTemplate" +:local backupNameAfterUpdate "backup_after_update_$backupNameTemplate" -:local mailSubject "$SMP Device - $deviceIdentityNameShort."; -:local mailBody ""; +## Email body template -:local mailBodyDeviceInfo "\r\n\r\nDevice information: \r\nIdentity: $deviceIdentityName \r\nModel: $deviceRbModel \r\nSerial number: $deviceRbSerialNumber \r\nCurrent RouterOS: $deviceOsVerInst ($[/system package update get channel]) $[/system resource get build-time] \r\nCurrent routerboard FW: $deviceRbCurrentFw \r\nDevice uptime: $[/system resource get uptime]"; -:local mailBodyCopyright "\r\n\r\nMikrotik RouterOS automatic backup & update (ver. $scriptVersion) \r\nhttps://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update"; -:local changelogUrl ("Check RouterOS changelog: https://mikrotik.com/download/changelogs/" . $updateChannel . "-release-tree"); +:local mailSubjectPrefix "$SMP Device - `$deviceIdentityNameShort`" -:local backupName "v$deviceOsVerInst_$deviceUpdateChannel_$currentDateTime"; -:local backupNameBeforeUpd "backup_before_update_$backupName"; -:local backupNameAfterUpd "backup_after_update_$backupName"; +:local mailBodyCopyright "Mikrotik RouterOS automatic backup & update (ver. $scriptVersion) \nhttps://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update" +:local changelogUrl "Check RouterOS changelog: https://mikrotik.com/download/changelogs/" -:local backupNameFinal $backupName; -:local mailAttachments [:toarray ""]; +:local mailBodyDeviceInfo "" +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "Device information:") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\n---------------------") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nName: $deviceIdentityName") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nModel: $deviceRbModel") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nBoard: $deviceBoardName") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nSerial number: $deviceRbSerialNumber") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nRouterOS version: v$deviceOsVerAndChannelRunning") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nBuild time: $[/system resource get build-time]") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nRouterboard FW: $deviceRbCurrentFw") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nDevice date-time: $rawDate $rawTime ($[/system clock get time-zone-name ])") +:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nUptime: $[/system resource get uptime]") +# IP address will be appended later if needed - -:local ipAddressDetectServiceDefault "https://ipv4.mikrotik.ovh/" -:local ipAddressDetectServiceFallback "https://api.ipify.org/" -: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; -} +:local mailAttachments [:toarray ""] ## 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 ($scriptStep = 1 or $scriptStep = 3) do={ + :if ($scriptStep = 3) do={ + :log info ("$SMP Waiting for one minute before continuing to the final step.") + :delay 1m } + # default values, to be set later + :local publicIpAddress "not-detected" + :local telemetryDataQuery "" :if ($detectPublicIpAddress = true or $allowAnonymousStatisticsCollection = true) do={ :if ($allowAnonymousStatisticsCollection = true) do={ - :set telemetryDataQuery ("\?mode=" . $scriptMode . "&osver=" . $deviceOsVerInst . "&model=" . $deviceRbModel); + :set telemetryDataQuery ("\?mode=" . $scriptMode . "&scriptver=" . $scriptVersion . "&updatechannel=" . $updateChannel . "&osver=" . $runningOsVersion . "&step=" . $scriptStep . "&forcebackup=" . $forceBackup . "&onlypatchupdates=" . $installOnlyPatchUpdates . "&model=" . $deviceRbModel . "&deviceboard=" . $deviceBoardName) } - :do {:set publicIpAddress ([/tool fetch http-method="get" url=($ipAddressDetectServiceDefault . $telemetryDataQuery) output=user as-value]->"data");} on-error={ - + :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." - :log warning "$SMP Trying to detect public ip using fallback detection service." - - :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." + :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={ - # Always truncate the string for safety measures - :set publicIpAddress ([:pick $publicIpAddress 0 15]) - :set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\r\nPublic IP address: " . $publicIpAddress); + # truncate IP to max 15 characters (basic safety) + :set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nPublic IP address: $publicIpAddress") + :log info "$SMP Public IP address detected: `$publicIpAddress`" } } } - ## STEP ONE: Creating backups, checking for new RouterOs version and sending email with backups, ## Steps 2 and 3 are fired only if script is set to automatically update device and if a new RouterOs version is available. -:if ($updateStep = 1) do={ - :log info ("$SMP Performing the first step."); +:if ($scriptStep = 1) do={ + :local routerOsVersionAvailable "0.0.0" + :local isNewOsUpdateAvailable false + :local isLatestOsAlreadyInstalled true + :local isOsNeedsToBeUpdated false + :local isUpdateCheckSucceeded false + :local isEmailNeedsToBeSent false + + :local mailSubjectPartAction "" + :local mailPtBodyAction "" + + :local mailPtSubjectBackup "" + :local mailPtBodyBackup "" # Checking for new RouterOS version - if ($scriptMode = "osupdate" or $scriptMode = "osnotify") do={ - log info ("$SMP Checking for new RouterOS version. Current version is: $deviceOsVerInst"); - /system package update set channel=$updateChannel; - /system package update check-for-updates; + :if ($scriptMode = "osupdate" or $scriptMode = "osnotify") do={ + log info ("$SMP Setting update channel to `$updateChannel`") + /system package update set channel=$updateChannel + log info ("$SMP Checking for new RouterOS version. Current installed version is: `$runningOsVersion`") + /system package update check-for-updates + + # Wait for 5 seconds to allow the system to check for updates :delay 5s; - :set deviceOsVerAvail [/system package update get latest-version]; - # If there is a problem getting information about available RouterOS versions from server - :if ([:len $deviceOsVerAvail] = 0) do={ - :log warning ("$SMP There is a problem getting information about new RouterOS from server."); - :set mailSubject ($mailSubject . " Error: No data about new RouterOS!") - :set mailBody ($mailBody . "Error occured! \r\nMikrotik couldn't get any information about new RouterOS from server! \r\nWatch additional information in device logs.") + :local packageUpdateStatus "undefined" + + :set routerOsVersionAvailable [/system package update get latest-version] + :set packageUpdateStatus [/system package update get status] + + :if ($packageUpdateStatus = "New version is available") do={ + :log info ("$SMP New RouterOS version is available: `$routerOsVersionAvailable`") + :set isNewOsUpdateAvailable true + :set isLatestOsAlreadyInstalled false + :set isUpdateCheckSucceeded true + :set isEmailNeedsToBeSent true + + :set mailSubjectPartAction "New RouterOS available" + :set mailPtBodyAction "New RouterOS version is available, current version: v$runningOsVersion, new version: v$routerOsVersionAvailable. \n$changelogUrl" } else={ - #Get numeric version of OS - :set deviceOsVerAvailNum [$buGlobalFuncGetOsVerNum paramOsVer=$deviceOsVerAvail]; + :if ($packageUpdateStatus = "System is already up to date") do={ + :log info ("$SMP No new RouterOS version is available, the latest version is already installed: `v$runningOsVersion`") + :set isUpdateCheckSucceeded true - # Checking if OS on server is greater than installed one. - :if ($deviceOsVerAvailNum > $deviceOsVerInstNum) do={ - :set isOsUpdateAvailable true; - :log info ("$SMP New RouterOS is available! $deviceOsVerAvail"); + :set mailSubjectPartAction "No os update available" + :set mailPtBodyAction "No new RouterOS version is available, the latest version is already installed: `v$runningOsVersion`" } else={ - :set isSendEmailRequired false; - :log info ("$SMP System is already up to date."); - :set mailSubject ($mailSubject . " No new OS updates."); - :set mailBody ($mailBody . "Your system is up to date."); + :log error ("$SMP Failed to check for new RouterOS version. Package check status: `$packageUpdateStatus`") + :set isEmailNeedsToBeSent true + + :set mailSubjectPartAction "Error unable to check new os version" + :set mailPtBodyAction "An error occurred while checking for a new RouterOS version.\nStatus returned: `$packageUpdateStatus`\n\nPlease review the logs on the device for more details and verify internet connectivity." } - }; - } else={ - :set scriptMode "backup"; - }; - - if ($forceBackup = true) do={ - # In this case the script will always send an email, because it has to create backups - :set isSendEmailRequired true; - } - - # If a new OS version is available to install - if ($isOsUpdateAvailable = true and $isSendEmailRequired = true) do={ - # If we only need to notify about a new available version - if ($scriptMode = "osnotify") do={ - :set mailSubject ($mailSubject . " New RouterOS is available! v.$deviceOsVerAvail.") - :set mailBody ($mailBody . "New RouterOS version is available to install: v.$deviceOsVerAvail ($updateChannel) \r\n$changelogUrl") - } - - # If we need to initiate RouterOS update process - if ($scriptMode = "osupdate") do={ - :set isOsNeedsToBeUpdated true; - # If we need to install only patch updates - :if ($installOnlyPatchUpdates = true) do={ - #Check if Major and Minor builds are the same. - :if ([:pick $deviceOsVerInstNum 0 ([:len $deviceOsVerInstNum]-2)] = [:pick $deviceOsVerAvailNum 0 ([:len $deviceOsVerAvailNum]-2)]) do={ - :log info ("$SMP New patch version of RouterOS firmware is available."); - } else={ - :log info ("$SMP New major or minor version of RouterOS firmware is available. You need to update it manually."); - :set mailSubject ($mailSubject . " New RouterOS: v.$deviceOsVerAvail needs to be installed manually."); - :set mailBody ($mailBody . "New major or minor RouterOS version is available to install: v.$deviceOsVerAvail ($updateChannel). \r\nYou chose to automatically install only patch updates, so this major update you need to install manually. \r\n$changelogUrl"); - :set isOsNeedsToBeUpdated false; - } - } - - #Check again, because this variable could be changed during checking for installing only patch updats - if ($isOsNeedsToBeUpdated = true) do={ - :log info ("$SMP New RouterOS is going to be installed! v.$deviceOsVerInst -> v.$deviceOsVerAvail"); - :set mailSubject ($mailSubject . " New RouterOS is going to be installed! v.$deviceOsVerInst -> v.$deviceOsVerAvail."); - :set mailBody ($mailBody . "Your Mikrotik will be updated to the new RouterOS version from v.$deviceOsVerInst to v.$deviceOsVerAvail (Update channel: $updateChannel) \r\nA final report with detailed information will be sent once the update process is completed. \r\nIf you do not receive a second email within the next 10 minutes, there may be an issue. Please check your device logs for further information."); - #!! There is more code connected to this part and first step at the end of the script. - } - } } - ## Checking If the script needs to create a backup - :log info ("$SMP Checking If the script needs to create a backup."); - if ($forceBackup = true or $scriptMode = "backup" or $isOsNeedsToBeUpdated = true) do={ - :log info ("$SMP Creating system backups."); - if ($isOsNeedsToBeUpdated = true) do={ - :set backupNameFinal $backupNameBeforeUpd; - }; - if ($scriptMode != "backup") do={ - :set mailBody ($mailBody . "\r\n\r\n"); - }; - - :set mailSubject ($mailSubject . " Backup was created."); - :set mailBody ($mailBody . "System backups were created and attached to this email."); - - :set mailAttachments [$buGlobalFuncCreateBackups backupName=$backupNameFinal backupPassword=$backupPassword sensitiveDataInConfig=$sensitiveDataInConfig]; - } else={ - :log info ("$SMP Creating a backup is not necessary."); + # Checking if the script needs to install new RouterOS version + :if ($scriptMode = "osupdate" and $isNewOsUpdateAvailable = true) do={ + :if ($installOnlyPatchUpdates = true) do={ + :if ([$FuncIsPatchUpdateOnly $runningOsVersion $routerOsVersionAvailable] = true) do={ + :log info "$SMP New RouterOS version is available, and it is a patch update. Current version: v$runningOsVersion, new version: v$routerOsVersionAvailable" + :set isOsNeedsToBeUpdated true + } else={ + :log info "$SMP The script will not install this update, because it is not a patch update. Current version: v$runningOsVersion, new version: v$routerOsVersionAvailable" + :set mailPtBodyAction ($mailPtBodyAction . "\nThis update will not be installed, because the script is set to install only patch updates.") + } + } else={ + :set isOsNeedsToBeUpdated true + } } - # Combine first step email - :set mailBody ($mailBody . $mailBodyDeviceInfo . $mailBodyCopyright); + + # Checking If the script needs to create a backup + :if ($forceBackup = true or $scriptMode = "backup" or $isOsNeedsToBeUpdated = true) do={ + :log info ("$SMP Starting backup process.") + + :set isEmailNeedsToBeSent true + + :local backupName $backupNameTemplate + + # This means it's the first step where we create a backup before the update process + :if ($isOsNeedsToBeUpdated = true) do={ + :set backupName $backupNameBeforeUpdate + + #Email body if the purpose of the script is to update the device + :set mailSubjectPartAction "Update preparation" + :set mailPtBodyAction ($mailPtBodyAction . "\nThe update process for device '$deviceIdentityName' is scheduled to upgrade RouterOS from version v.$runningOsVersion to version v.$routerOsVersionAvailable (Update channel: $updateChannel)") + :set mailPtBodyAction ($mailPtBodyAction . "\nPlease note: The update will proceed only after a successful backup.") + :set mailPtBodyAction ($mailPtBodyAction . "\nA final report with detailed information will be sent once the update process is completed.") + :set mailPtBodyAction ($mailPtBodyAction . "\nIf you do not receive a second email within the next 10 minutes, there may be an issue. Please check your device logs for further information.") + } + + :do { + :set mailAttachments [$FuncCreateBackups $backupName $backupPassword $sensitiveDataInConfig]; + + :set mailPtSubjectBackup "Backup created" + :set mailPtBodyBackup "System backups have been successfully created and attached to this email." + } on-error={ + #failed to create backup + :set isOsNeedsToBeUpdated false + + :set mailPtSubjectBackup "Backup failed" + :set mailPtBodyBackup "The script failed to create backups. Please check device logs for more details." + + :log warning "$SMP Failed to create backup files. Update process will be cancelled, if the script is set to update the device." + } + } + + :if ($isEmailNeedsToBeSent = true) do={ + :log info "$SMP Preparing to send email..." + + :local mailStep1Subject $mailSubjectPrefix + :local mailStep1Body "" + + # Assemble email subject + :if ($mailSubjectPartAction != "") do={:set mailStep1Subject ($mailStep1Subject . " - " . $mailSubjectPartAction)} + :if ($mailPtSubjectBackup != "") do={:set mailStep1Subject ($mailStep1Subject . " - " . $mailPtSubjectBackup)} + # Assemble email body + :if ($mailPtBodyAction != "") do={:set mailStep1Body ($mailStep1Body . $mailPtBodyAction . "\n\n")} + :if ($mailPtBodyBackup != "") do={:set mailStep1Body ($mailStep1Body . $mailPtBodyBackup . "\n\n")} + + :set mailStep1Body ($mailStep1Body . $mailBodyDeviceInfo . "\n\n" . $mailBodyCopyright) + + # Send email with backup files attached + :do {$FuncSendEmailSafe $emailAddress $mailStep1Subject $mailStep1Body $mailAttachments} on-error={ + :set isOsNeedsToBeUpdated false + :log error "$SMP The script will not proceed with the update process, because the email was not sent." + #:error $exitErrorMessage + } + } + + :if ([:len $mailAttachments] > 0) do={ + :log info "$SMP Cleaning up backup files from the file system..." + /file remove $mailAttachments; + :delay 2s; + } + + :if ($isOsNeedsToBeUpdated = true) do={ + :log info "$SMP everything is ready to install new RouterOS, going to start the update process and reboot the device." + :do { + :local nextStep 2 + :if ($isCloudHostedRouter = true) do={ + :log info "$SMP The device is a cloud hosted router, the second step updating the Routerboard firmware will be skipped." + :set nextStep 3 + } + + :local scheduledCommand (":delay 5s; /system scheduler remove BKPUPD-NEXT-BOOT-TASK; :global buGlobalVarScriptStep $nextStep; :global buGlobalVarTargetOsVersion \"$routerOsVersionAvailable\"; :delay 10s; /system script run BackupAndUpdate;") + /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=$scheduledCommand start-time=startup interval=0 + + /system package update install + } on-error={ + # Failed to install new RouterOS version, remove the scheduled task + :do {/system scheduler remove BKPUPD-NEXT-BOOT-TASK} on-error={} + + :log error "$SMP Failed to install new RouterOS version. Please check device logs for more details." + + :local mailUpdateErrorSubject ($mailSubjectPrefix . " - Update failed") + :local mailUpdateErrorBody "The script was unable to install new RouterOS version. Please check device logs for more details." + + # Send email with error message + $FuncSendEmailSafe $emailAddress $mailUpdateErrorSubject $mailUpdateErrorBody "" + + :error $exitErrorMessage + } + } } ## STEP TWO: (after first reboot) routerboard firmware upgrade ## Steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. -:if ($updateStep = 2) do={ - :log info ("$SMP Performing the second step."); - ## RouterOS is the latest, let's check for upgraded routerboard firmware - if ($deviceRbCurrentFw != $deviceRbUpgradeFw) do={ - :set isSendEmailRequired false; - :delay 10s; - :log info "$SMP Upgrading routerboard firmware from v.$deviceRbCurrentFw to v.$deviceRbUpgradeFw"; - ## Start the upgrading process - /system routerboard upgrade; - ## Wait until the upgrade is completed - :delay 5s; - :log info "$SMP routerboard upgrade process was completed, going to reboot in a moment!"; - ## Set scheduled task to send final report on the next boot, task will be deleted when it is done. (That is why you should keep original script name) - /system scheduler add name=BKPUPD-FINAL-REPORT-ON-NEXT-BOOT on-event=":delay 5s; /system scheduler remove BKPUPD-FINAL-REPORT-ON-NEXT-BOOT; :global buGlobalVarUpdateStep 3; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0; - ## Reboot system to boot with new firmware - /system reboot; - } else={ - :log info "$SMP It appears that your routerboard is already up to date, skipping this step."; - :set updateStep 3; - }; +:if ($scriptStep = 2) do={ + :log info "$SMP The script is in the second step, updating Routerboard firmware." + + :log info "$SMP Upgrading routerboard firmware from v.$deviceRbCurrentFw to v.$deviceRbUpgradeFw" + + /system routerboard upgrade + ## Wait until the upgrade is completed + :delay 2s + :log info "$SMP routerboard upgrade process was completed, going to reboot in a moment!"; + + ## Set scheduled task to send final report on the next boot, task will be deleted when it is done. (That is why you should keep original script name) + /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=":delay 5s; /system scheduler remove BKPUPD-NEXT-BOOT-TASK; :global buGlobalVarScriptStep 3; :global buGlobalVarTargetOsVersion \"$buGlobalVarTargetOsVersion\"; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0 + + ## Reboot system to boot with new firmware + /system reboot; } ## STEP THREE: Last step (after second reboot) sending final report ## Steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. ## This step is executed after some delay -:if ($updateStep = 3) do={ - :log info ("$SMP Performing the third step."); - :log info "Bkp&Upd: RouterOS and routerboard upgrade process was completed. New RouterOS version: v.$deviceOsVerInst, routerboard firmware: v.$deviceRbCurrentFw."; - ## Small delay in case mikrotik needs some time to initialize connections - :log info "$SMP Sending the final email with report and backups."; - :set mailSubject ($mailSubject . " RouterOS Upgrade is completed, new version: v.$deviceOsVerInst!"); - :set mailBody "RouterOS and routerboard upgrade process was completed. \r\nNew RouterOS version: v.$deviceOsVerInst, routerboard firmware: v.$deviceRbCurrentFw. \r\n$changelogUrl \r\n\r\nBackups of the upgraded system are in the attachment of this email. $mailBodyDeviceInfo $mailBodyCopyright"; - :set mailAttachments [$buGlobalFuncCreateBackups backupName=$backupNameAfterUpd backupPassword=$backupPassword sensitiveDataInConfig=$sensitiveDataInConfig]; -} +:if ($scriptStep = 3) do={ + :log info ("$SMP The script is in the third step, sending final report.") -# 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={} - -## -## SENDING EMAIL -## -# Trying to send email with backups as attachments. - -:if ($isSendEmailRequired = true) do={ - :log info "$SMP Dispatching email message; estimated completion within 30 seconds."; - :do {/tool e-mail send to=$emailAddress subject=$mailSubject body=$mailBody file=$mailAttachments;} on-error={ - :delay 5s; - :log error "$SMP could not send email message ($[/tool e-mail get last-status]). Will attempt redelivery shortly." - - :delay 5m; - - :do {/tool e-mail send to=$emailAddress subject=$mailSubject body=$mailBody file=$mailAttachments;} on-error={ - :delay 5s; - :log error "$SMP failed to send email message ($[/tool e-mail get last-status]) for the second time." - - if ($isOsNeedsToBeUpdated = true) do={ - :set isOsNeedsToBeUpdated false; - :log warning "$SMP script is not going to initialise update process due to inability to send backups to email." - } - } + :local targetOsVersion $buGlobalVarTargetOsVersion + :do {/system script environment remove buGlobalVarTargetOsVersion} on-error={} + :if ([:len $targetOsVersion] = 0) do={ + :log warning "$SMP Something is wrong, the script was unable to get the target updated OS version from the global variable." } - :delay 30s; + :local mailStep3Subject $mailSubjectPrefix + :local mailStep3Body "" - :if ([:len $mailAttachments] > 0 and [/tool e-mail get last-status] = "succeeded") do={ - :log info "$SMP File system cleanup." + :if ($targetOsVersion = $runningOsVersion) do={ + :log info "$SMP The script has successfully verified that the new RouterOS version was installed, target version: `$targetOsVersion`, current version: `$runningOsVersion`" + + :set mailStep3Subject ($mailStep3Subject . " - Update completed - Backup created") + :set mailStep3Body ($mailStep3Body . "RouterOS and routerboard upgrade process was completed") + :set mailStep3Body ($mailStep3Body . "\nNew RouterOS version: v.$targetOsVersion, routerboard firmware: v.$deviceRbCurrentFw") + :set mailStep3Body ($mailStep3Body . "\n$changelogUrl") + :set mailStep3Body ($mailStep3Body . "\nBackups of the upgraded system are in the attachment of this email.") + :set mailStep3Body ($mailStep3Body . "\n\n" . $mailBodyDeviceInfo . "\n\n" . $mailBodyCopyright) + + :set mailAttachments [$FuncCreateBackups $backupNameAfterUpdate $backupPassword $sensitiveDataInConfig]; + } else={ + :log error "$SMP The script was unable to verify that the new RouterOS version was installed, target version: `$targetOsVersion`, current version: `$runningOsVersion`" + :set mailStep3Subject ($mailStep3Subject . " - Update failed") + + :set mailStep3Body ($mailStep3Body . "The script was unable to verify that the new RouterOS version was installed, target version: `$targetOsVersion`, current version: `$runningOsVersion`") + :set mailStep3Body ($mailStep3Body . "\nPlease check device logs for more details.") + :set mailStep3Body ($mailStep3Body . "\n\n" . $mailBodyDeviceInfo . "\n\n" . $mailBodyCopyright) + } + + $FuncSendEmailSafe $emailAddress $mailStep3Subject $mailStep3Body $mailAttachments + + :if ([:len $mailAttachments] > 0) do={ + :log info "$SMP Cleaning up backup files from the file system..." /file remove $mailAttachments; :delay 2s; } + :log info "$SMP Final report email sent successfully, and the script has finished." } - -# Fire RouterOS update process -if ($isOsNeedsToBeUpdated = true) do={ - - :if ($isSoftBased = false) do={ - ## Set scheduled task to upgrade routerboard firmware on the next boot, task will be deleted when upgrade is done. (That is why you should keep original script name) - /system scheduler add name=BKPUPD-UPGRADE-ON-NEXT-BOOT on-event=":delay 5s; /system scheduler remove BKPUPD-UPGRADE-ON-NEXT-BOOT; :global buGlobalVarUpdateStep 2; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0; - } else= { - ## If the script is executed on CHR, step 2 will be skipped - /system scheduler add name=BKPUPD-UPGRADE-ON-NEXT-BOOT on-event=":delay 5s; /system scheduler remove BKPUPD-UPGRADE-ON-NEXT-BOOT; :global buGlobalVarUpdateStep 3; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0; - }; - - - :log info "$SMP everything is ready to install new RouterOS, going to reboot in a moment!" - ## Command is reincarnation of the "upgrade" command - doing exactly the same but under a different name - /system package update install; -} - -:log info "$SMP script \"Mikrotik RouterOS automatic backup & update\" completed it's job.\r\n"; +:log info "$SMP the script has finished, script step: `$scriptStep` \n\n" From 28426129d5a38dec8b54c3e19eb970245783c4dc Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Mon, 14 Apr 2025 16:48:10 +0200 Subject: [PATCH 24/27] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c0e24e4..5895a34 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This script allows you to generate daily backups of MikroTik and send them to an email address. You can also choose to enable automatic RouterOS upgrades or receive notifications exclusively for new firmware versions. - +> ‼️Use [WinBox 4.0](https://mikrotik.com/download), to update the script > 💡 If you have any ideas about the script or you just want to share your opinion, you are welcome to [Discussions](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/discussions), or you can open an [issue](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/issues) if you found a bug. @@ -21,6 +21,8 @@ This script allows you to generate daily backups of MikroTik and send them to an **Backups and automatic RouterOS upgrade** - The script begins by creating a backup, followed by a check for any new versions of RouterOS. If a newer firmware version is detected, the script initiates the upgrade process. Upon completion, two emails are sent: the first includes the system backups from the prior RouterOS version, and the second, sent post-upgrade, contains backups of the updated system. ## How to use +> ‼️Use [WinBox 4.0](https://mikrotik.com/download), to update the script + > ❗️ **Important** > Ensure your device identity does not contain spaces and special characters! `System -> Identity` From 288b2d9d4f7619a74c573f137d8bff7e97819579 Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Tue, 15 Apr 2025 21:36:49 +0200 Subject: [PATCH 25/27] 25.04.15 (#68) 25.04.15 (#68) --- .editorconfig | 2 +- BackupAndUpdate.rsc | 965 +++++++++++++++++++++----------------------- README.md | 4 - 3 files changed, 457 insertions(+), 514 deletions(-) diff --git a/.editorconfig b/.editorconfig index 160403d..172a51a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ insert_final_newline = true [*.rsc] indent_style = space -indent_size = 4 +indent_size = 2 [*.{yml,yaml,sls}] indent_style = space diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index b66b094..d70f770 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -1,11 +1,11 @@ # Script name: BackupAndUpdate # -#----------SCRIPT INFORMATION--------------------------------------------------- +# SCRIPT INFORMATION # # Script: Mikrotik RouterOS automatic backup & update -# Version: 25.04.12 +# Version: 25.04.15 # Created: 07/08/2018 -# Updated: 12/04/2025 +# Updated: 15/04/2025 # Author: Alexander Tebiev # Website: https://github.com/beeyev # You can contact me by e-mail at tebiev@mail.com @@ -13,54 +13,50 @@ # 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) +# --- MODIFY THIS SECTION AS NEEDED --- +# Notification e-mail +# (Make sure you have configured Email settings in Tools -> Email) :local emailAddress "yourmail@example.com"; -## Script mode, possible values: backup, osupdate, osnotify. +# 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). +# osupdate - Installs new RouterOS if available and creates backups before/after update (ignores `forceBackup`) +# Sends email only when an update is found. +# Set `forceBackup` to true to always create backups, even without updates # -# 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. +# osnotify - Sends email only if a new RouterOS update is found (no backups) +# Set `forceBackup` to always create backups on every run :local scriptMode "osupdate" -## Additional parameter if you set `scriptMode` to `osupdate` or `osnotify` +# 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. +# Backup encryption password, no encryption if no password. :local backupPassword "" -## If true, passwords will be included in exported config. +# If true, passwords will be included in exported config. :local sensitiveDataInConfig true ## Update channel. Possible values: stable, long-term, testing, development :local updateChannel "stable" -## Install only patch updates (requires scriptMode = "osupdate") -## Works only for `stable` and `long-term` channels. -## Update will run only if MAJOR and MINOR versions match the current one -## Example: current = v6.43.2 → v6.43.6 = allowed, v6.44.1 = skipped -## Script will send information if new version is greater than just patch. +# Installs patch updates only (scriptMode = "osupdate"). +# Works for `stable` and `long-term` channels. +# Updates only if MAJOR.MINOR match (e.g. 6.43.2 → 6.43.6 allowed, 6.44.1 skipped). +# Sends info if a newer (non-patch) version is found. :local installOnlyPatchUpdates false -## If true, device public IP address information will be included into the email message +# Include public IP info in email if set to true :local detectPublicIpAddress true ## Allow anonymous statistics collection. (script mode and generic non-sensitive device info) -:local allowAnonymousStatisticsCollection true +:local anonStats true -##------------------------------------------------------------------------------------------## -# !!!! DO NOT CHANGE ANYTHING BELOW THIS LINE, IF YOU ARE NOT SURE WHAT YOU ARE DOING !!!! # -##------------------------------------------------------------------------------------------## +# !!! DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU’RE DOING !!! -:local scriptVersion "25.04.12" +:local scriptVersion "25.04.15" # default and fallback public IP detection services :local ipAddressDetectServiceDefault "https://ipv4.mikrotik.ovh/" @@ -73,253 +69,215 @@ :log info "\n\n$SMP Script \"Mikrotik RouterOS automatic backup & update\" v.$scriptVersion started." :log info "$SMP Script Mode: `$scriptMode`, Update channel: `$updateChannel`, Force backup: `$forceBackup`, Install only patch updates: `$installOnlyPatchUpdates`" -############### vvvvvvvvv FUNCTIONS vvvvvvvvv ############### +## vv FUNCTIONS vv ## -# Function: FuncGetRunningOsVersion -# ---------------------------- # Returns currently running RouterOS version -# -# Example: # :put [$FuncGetRunningOsVersion] # Output: 6.48.1 :local FuncGetRunningOsVersion do={ - :local runningOsAndChannel [/system resource get version] + :local runningOsAndChannel [/system resource get version] - :local spacePos [:find $runningOsAndChannel " "] - :if ([:len $spacePos] = 0) do={ - :log error "Bkp&Upd: Could not extract installed OS version string: `$runningOsAndChannel`. Script stopped." - :error "Bkp&Upd: script stopped due to an error. Please check logs for more details." - } + :local spacePos [:find $runningOsAndChannel " "] + :if ([:len $spacePos] = 0) do={ + :log error "Bkp&Upd: Could not extract installed OS version string: `$runningOsAndChannel`." + :error "Bkp&Upd: error, check logs" + } - :local versionOnly [:pick $runningOsAndChannel 0 $spacePos] + :local versionOnly [:pick $runningOsAndChannel 0 $spacePos] - :return $versionOnly + :return $versionOnly } -# Function: FuncGetRunningOsChannel -# ---------------------------- -# Returns currently running RouterOS channel (stable, long-term, testing, development) -# -# Example: +# Returns currently running RouterOS channel # :put [$FuncGetRunningOsChannel] # Output: stable :local FuncGetRunningOsChannel do={ - :local runningOsAndChannel [/system resource get version] - :local errorMessage "Bkp&Upd: Could not extract installed OS channel from version string: `$runningOsAndChannel`. Script stopped." - :local exitErrorMessage "Bkp&Upd: script stopped due to an error. Please check logs for more details." + :local runningOsAndChannel [/system resource get version] - :local open [:find $runningOsAndChannel "("] - :if ([:len $open] = 0) do={ - :log error ($errorMessage . " (1)") - :error $exitErrorMessage - } + :local open [:find $runningOsAndChannel "("] + :if ([:len $open] = 0) do={ + :log error "Bkp&Upd: Could not extract installed OS channel from version string: `$runningOsAndChannel`." + :error "Bkp&Upd: error, check logs" + } - :local rest [:pick $runningOsAndChannel ($open+1) [:len $runningOsAndChannel]] + :local rest [:pick $runningOsAndChannel ($open+1) [:len $runningOsAndChannel]] + :local close [:find $rest ")"] + :local channel [:pick $rest 0 $close] - :local close [:find $rest ")"] - :if ([:len $close] = 0) do={ - :log error ($errorMessage . " (2)") - :error $exitErrorMessage - } - - :local channel [:pick $rest 0 $close] - :if ([:len $channel] = 0) do={ - :log error ($errorMessage . " (3)") - :error $exitErrorMessage - } - - :return $channel + :return $channel } -# Function: FuncIsPatchUpdateOnly -# ---------------------------- -# Determines if two RouterOS version strings differ only by the patch version. -# -# Parameters: -# `version1` | string | The first version string (e.g., "6.2.1"). -# `version2` | string | The second version string (e.g., "6.2.4"). -# -# Returns: -# boolean | true if only the patch versions differ; false otherwise. -# -# Example: +# Checks if two RouterOS version strings differ only by the patch version # :put [$FuncIsPatchUpdateOnly "6.2.1" "6.2.4"] # Output: true # :put [$FuncIsPatchUpdateOnly "6.2.1" "6.3.1"] # Output: false :local FuncIsPatchUpdateOnly do={ - :local ver1 $1 - :local ver2 $2 + :local ver1 $1 + :local ver2 $2 - # Internal function to extract the major and minor components from a version string. - :local extractMajorMinor do={ - :local ver $1 - :local dot1 [:find $ver "."] - :if ($dot1 = -1) do={ :return $ver } + # Extract the major and minor components from a version + :local extractMajorMinor do={ + :local ver $1 + :local dot1 [:find $ver "."] + :if ($dot1 = -1) do={ :return $ver } - :local major [:pick $ver 0 $dot1] - :local rest [:pick $ver ($dot1 + 1) [:len $ver]] - :local dot2 [:find $rest "."] - :local minor $rest - :if ($dot2 >= 0) do={ :set minor [:pick $rest 0 $dot2] } + :local major [:pick $ver 0 $dot1] + :local rest [:pick $ver ($dot1 + 1) [:len $ver]] + :local dot2 [:find $rest "."] + :local minor $rest + :if ($dot2 >= 0) do={ :set minor [:pick $rest 0 $dot2] } - :return ($major . "." . $minor) - } + :return ($major . "." . $minor) + } - # Compare the major and minor components of both version strings. - :if ([$extractMajorMinor $ver1] = [$extractMajorMinor $ver2]) do={ - :return true - } - :return false + # Compare the major and minor components of both version strings + :if ([$extractMajorMinor $ver1] = [$extractMajorMinor $ver2]) do={ + :return true + } + :return false } -# Function creates backups (system and config) and returns array of names of created files. +# Creates backups and returns array of names # Possible arguments: -# `backupName` | string | backup file name, without extension! -# `backupPassword` | string | -# `sensitiveDataInConfig` | boolean | +# $1 - file name, without extension +# $2 - password (optional) +# $3 - sensitive data in config (optional, default: false) # Example: # :put [$FuncCreateBackups "daily-backup"] :local FuncCreateBackups do={ - :local backupName $1 - :local backupPassword $2 - :local sensitiveDataInConfig $3 + :local backupName $1 + :local backupPassword $2 + :local sensitiveDataInConfig $3 - #Script messages prefix - :local SMP "Bkp&Upd:" - :local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details." - :log info ("$SMP global function `FuncCreateBackups` started, input: `$backupName`") + #Script messages prefix + :local SMP "Bkp&Upd:" + :local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details." + :log info ("$SMP global function `FuncCreateBackups` started, input: `$backupName`") - # validate required parameter: backupName - :if ([:typeof $backupName] != "str" or [:len $backupName] = 0) do={ - :log error "$SMP parameter 'backupName' is required and must be a non-empty string" - :error $exitErrorMessage - } + # validate required parameter: backupName + :if ([:typeof $backupName] != "str" or [:len $backupName] = 0) do={ + :log error "$SMP parameter 'backupName' is required and must be a non-empty string" + :error $exitErrorMessage + } - :local backupFileSys "$backupName.backup" - :local backupFileConfig "$backupName.rsc" - :local backupNames {$backupFileSys;$backupFileConfig} + :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 + ## 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 resource get version] 0 1] < 7) do={ + :execute "/export compact terse file=$backupName" } else={ - :log info ("$SMP starting backup with password, backup name: `$backupName`") - /system backup save password=$backupPassword name=$backupName + :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 system backup created: `$backupFileSys`") + :log info ("$SMP Config export complete: `$backupFileConfig`") + :log info ("$SMP Waiting a little to ensure backup files are written") - ## 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 resource get 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 - } + :delay 20s - :log info ("$SMP Config export complete: `$backupFileConfig`") - :log info ("$SMP Waiting a little to ensure backup files are written") + :if ([:len [/file find name=$backupFileSys]] > 0) do={ + :log info ("$SMP system backup file successfully saved to the file system: `$backupFileSys`") + } else={ + :log error ("$SMP system backup was not created, file does not exist: `$backupFileSys`") + :error $exitErrorMessage + } - :delay 20s + :if ([:len [/file find name=$backupFileConfig]] > 0) do={ + :log info ("$SMP config backup file successfully saved to the file system: `$backupFileConfig`") + } else={ + :log error ("$SMP config backup was not created, file does not exist: `$backupFileConfig`") + :error $exitErrorMessage + } - :if ([:len [/file find name=$backupFileSys]] > 0) do={ - :log info ("$SMP system backup file successfully saved to the file system: `$backupFileSys`") - } else={ - :log error ("$SMP system backup was not created, file does not exist: `$backupFileSys`") - :error $exitErrorMessage - } + :log info ("$SMP global function `FuncCreateBackups` finished. Created backups, system: `$backupFileSys`, config: `$backupFileConfig`") - :if ([:len [/file find name=$backupFileConfig]] > 0) do={ - :log info ("$SMP config backup file successfully saved to the file system: `$backupFileConfig`") - } else={ - :log error ("$SMP config backup was not created, file does not exist: `$backupFileConfig`") - :error $exitErrorMessage - } - - :log info ("$SMP global function `FuncCreateBackups` finished. Created backups, system: `$backupFileSys`, config: `$backupFileConfig`") - - :return $backupNames + :return $backupNames } -# Function: FuncSendEmailSafe -# --------------------------- -# Sends an email and checks if it was sent successfully. -# +# Sends an email # Parameters: -# $1 - to (email address) -# $2 - subject -# $3 - body -# $4 - file attachments (optional; pass "" if not needed) +# $1 - to (email address) +# $2 - subject +# $3 - body +# $4 - file attachments (optional; pass "" if not needed) # # Example: -# :do { -# $FuncSendEmailSafe "admin@domain.com" "Backup Done" "Backup complete." "backup1.backup" -# } on-error={ -# :log error "Email failed to send" -# } +# $FuncSendEmailSafe "admin@domain.com" "Backup Done" "Backup complete." "backup1.backup" :local FuncSendEmailSafe do={ - :local emailTo $1 - :local emailSubject $2 - :local emailBody $3 - :local emailAttachments $4 + :local emailTo $1 + :local emailSubject $2 + :local emailBody $3 + :local emailAttachments $4 - :local SMP "Bkp&Upd:" - :local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details." + :local SMP "Bkp&Upd:" + :local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details." - :log info "$SMP Attempting to send email to `$emailTo`" + :log info "$SMP Attempting to send email to `$emailTo`" - # SAFETY: wait for any previously queued email to finish - :local waitTimeoutPre 60 - :local waitCounterPre 0 - :while (([/tool e-mail get last-status] = "resolving-dns" or [/tool e-mail get last-status] = "in-progress")) do={ - :if ($waitCounterPre >= $waitTimeoutPre) do={ - :log error "$SMP Email send aborted: previous send did not complete after $waitTimeoutPre seconds" - :error $exitErrorMessage - } - - :log info "$SMP Waiting for previous email to finish (status: $[/tool e-mail get last-status])..." - :delay 1s - :set waitCounterPre ($waitCounterPre + 1) + # SAFETY: wait for any previously queued email to finish + :local waitTimeoutPre 60 + :local waitCounterPre 0 + :while (([/tool e-mail get last-status] = "resolving-dns" or [/tool e-mail get last-status] = "in-progress")) do={ + :if ($waitCounterPre >= $waitTimeoutPre) do={ + :log error "$SMP Email send aborted: previous send did not complete after $waitTimeoutPre seconds" + :error $exitErrorMessage } - # Send the email - :do { - /tool e-mail send to=$emailTo subject=$emailSubject body=$emailBody file=$emailAttachments - } on-error={ - :log error "$SMP Email send command failed to execute. Check logs and verify email settings." - :error $exitErrorMessage - } + :log info "$SMP Waiting for previous email to finish (status: $[/tool e-mail get last-status])..." + :delay 1s + :set waitCounterPre ($waitCounterPre + 1) + } - # Wait for send status to change from "in-progress" / "resolving-dns" - :local waitTimeout 60 - :local waitCounter 0 - :local emailStatus "" - :log info "$SMP Waiting for email to be sent, timeout in `$waitTimeout` seconds..." - :while ($waitCounter < $waitTimeout) do={ - :set emailStatus [/tool e-mail get last-status] - :if ($emailStatus != "in-progress" and $emailStatus != "resolving-dns") do={ - :log info "$SMP Email send status received: $emailStatus" + # Send the email + :do { + /tool e-mail send to=$emailTo subject=$emailSubject body=$emailBody file=$emailAttachments + } on-error={ + :log error "$SMP Email send command failed to execute. Check logs and verify email settings." + :error $exitErrorMessage + } - # exit loop - :set waitCounter $waitTimeout - } else={ - :delay 1s - :set waitCounter ($waitCounter + 1) - } - } + # Wait for send status to change from "in-progress" / "resolving-dns" + :local waitTimeout 60 + :local waitCounter 0 + :local emailStatus "" + :log info "$SMP Waiting for email to be sent, timeout in `$waitTimeout` seconds..." + :while ($waitCounter < $waitTimeout) do={ + :set emailStatus [/tool e-mail get last-status] + :if ($emailStatus != "in-progress" and $emailStatus != "resolving-dns") do={ + :log info "$SMP Email send status received: $emailStatus" - # Final decision based on last status - :if ($emailStatus = "succeeded") do={ - :log info "$SMP Email successfully sent to `$emailTo`" + # exit loop + :set waitCounter $waitTimeout } else={ - :log error "$SMP Email failed to send. Status: `$emailStatus`. Check logs for more details and verify email settings." - :error $exitErrorMessage + :delay 1s + :set waitCounter ($waitCounter + 1) } + } + + # Final decision based on last status + :if ($emailStatus = "succeeded") do={ + :log info "$SMP Email successfully sent to `$emailTo`" + } else={ + :log error "$SMP Email failed to send. Status: `$emailStatus`. Check logs for more details and verify email settings." + :error $exitErrorMessage + } } # Global variable to track current update step @@ -330,9 +288,9 @@ :local scriptStep $buGlobalVarScriptStep :do {/system script environment remove buGlobalVarScriptStep} on-error={} :if ([:len $scriptStep] = 0) do={ - :set scriptStep 1 + :set scriptStep 1 } -############### ^^^^^^^^^ FUNCTIONS ^^^^^^^^^ ############### +## ^^ FUNCTIONS ^^ ## # @@ -341,8 +299,8 @@ ## Check email settings :if ([:len $emailAddress] < 3) do={ - :log error ("$SMP Script parameter `\$emailAddress` is not set, or contains invalid value. Script stopped.") - :error $exitErrorMessage + :log error ("$SMP Parameter `\$emailAddress` is not set, or contains invalid value. Script stopped.") + :error $exitErrorMessage } # Values will be defined later in the script @@ -351,110 +309,107 @@ :log info "$SMP Validating email settings..." :do { - :set emailServer [/tool e-mail get server] + :set emailServer [/tool e-mail get server] } on-error={ - # This is a workaround for the RouterOS v7.12 and older versions - :set emailServer [/tool e-mail get address] + # This is a workaround for the RouterOS v7.12 and older versions + :set emailServer [/tool e-mail get address] } :if ($emailServer = "0.0.0.0") do={ - :log error ("$SMP Email server address is not correct: `$emailServer`, please check `Tools -> Email`. Script stopped."); - :error $exitErrorMessage + :log error ("$SMP Email server address is not correct: `$emailServer`, check `Tools -> Email`. Script stopped."); + :error $exitErrorMessage } :if ([:len $emailFromAddress] < 3) do={ - :log error ("$SMP Email configuration FROM address is not correct: `$emailFromAddress`, please check `Tools -> Email`. Script stopped."); - :error $exitErrorMessage + :log error ("$SMP Email configuration FROM address is not correct: `$emailFromAddress`, check `Tools -> Email`. Script stopped."); + :error $exitErrorMessage } # Script mode validation :if ($scriptMode != "backup" and $scriptMode != "osupdate" and $scriptMode != "osnotify") do={ - :log error ("$SMP Script parameter `\$scriptMode` is not set, or contains invalid value: `$scriptMode`. Script stopped.") - :error $exitErrorMessage + :log error ("$SMP Script parameter `\$scriptMode` is not set, or contains invalid value: `$scriptMode`. Script stopped.") + :error $exitErrorMessage } # Update channel validation :if ($updateChannel != "stable" and $updateChannel != "long-term" and $updateChannel != "testing" and $updateChannel != "development") do={ - :log error ("$SMP Script parameter `\$updateChannel` is not set, or contains invalid value: `$updateChannel`. Script stopped.") - :error $exitErrorMessage + :log error ("$SMP Script parameter `\$updateChannel` is not set, or contains invalid value: `$updateChannel`. Script stopped.") + :error $exitErrorMessage } -# Check if the script is set to install only patch updates and if the update channel is valid +# Verify if script is set to install patch updates and if the update channel is valid :if ($scriptMode = "osupdate" and $installOnlyPatchUpdates = true) do={ - :if ($updateChannel != "stable" and $updateChannel != "long-term") do={ - :log error ("$SMP Script is set to install only patch updates, but the update channel is not valid: `$updateChannel`. Only `stable` and `long-term` channels supported. Script stopped.") - :error $exitErrorMessage - } + :if ($updateChannel != "stable" and $updateChannel != "long-term") do={ + :log error ("$SMP Patch-only updates enabled, but update channel `$updateChannel` is invalid. Only `stable` and `long-term` are supported. Script stopped") + :error $exitErrorMessage + } - :local susRunningOsChannel [$FuncGetRunningOsChannel] + :local susRunningOsChannel [$FuncGetRunningOsChannel] - :if ($susRunningOsChannel != "stable" and $susRunningOsChannel != "long-term") do={ - :log error ("$SMP Script is set to install only patch updates, but the installed RouterOS version is not from `stable` or `long-term` channel: `$susRunningOsChannel`. Script stopped.") - :error $exitErrorMessage - } + :if ($susRunningOsChannel != "stable" and $susRunningOsChannel != "long-term") do={ + :log error ("$SMP Script is set to install only patch updates, but the installed RouterOS version is not from `stable` or `long-term` channel: `$susRunningOsChannel`. Script stopped") + :error $exitErrorMessage + } } # -# Get current system date and time +# Get current date and time # :local rawTime [/system clock get time] :local rawDate [/system clock get date] -## Current time in specific format `hh-mm-ss` +# 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 +# Current date `YYYY-MM-DD` or `YYYY-Mon-DD` :local currentDate "undefined" -## Check if the date is in the old format, it should not start with a number +# Check if the date is in the old format :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]) + # 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 + # Use new format as is `YYYY-MM-DD` + :set currentDate $rawDate } -## Combine date and time → `YYYY-MM-DD-hh-mm-ss` or `YYYY-Mon-DD-hh-mm-ss` :local currentDateTime ($currentDate . "-" . $currentTime) -##### - :local deviceBoardName [/system resource get board-name] -## Check if it's a cloud hosted router or a hardware based device +## Check if it's a cloud hosted router :local isCloudHostedRouter false; :if ([:pick $deviceBoardName 0 3] = "CHR" or [:pick $deviceBoardName 0 3] = "x86") do={ - :set isCloudHostedRouter true; + :set isCloudHostedRouter true; }; -:local deviceIdentityName [/system identity get name]; +:local deviceIdentityName [/system identity get name]; :local deviceIdentityNameShort [:pick $deviceIdentityName 0 18] -:local deviceRbModel "CloudHostedRouter"; -:local deviceRbSerialNumber "--" -:local deviceRbCurrentFw "--" -:local deviceRbUpgradeFw "--" +:local deviceRbModel "CloudHostedRouter"; +:local deviceRbSerialNumber "--" +:local deviceRbCurrentFw "--" +:local deviceRbUpgradeFw "--" :if ($isCloudHostedRouter = 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] + :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 runningOsChannel [$FuncGetRunningOsChannel] :local runningOsVersion [$FuncGetRunningOsVersion] :local deviceOsVerAndChannelRunning [/system resource get version] -:local backupNameTemplate "backup_v$runningOsVersion_$runningOsChannel_$currentDateTime" -:local backupNameBeforeUpdate "backup_before_update_$backupNameTemplate" -:local backupNameAfterUpdate "backup_after_update_$backupNameTemplate" +:local backupNameTemplate "backup_v$runningOsVersion_$runningOsChannel_$currentDateTime" +:local backupNameBeforeUpdate "backup_before_update_$backupNameTemplate" +:local backupNameAfterUpdate "backup_after_update_$backupNameTemplate" ## Email body template -:local mailSubjectPrefix "$SMP Device - `$deviceIdentityNameShort`" +:local mailSubjectPrefix "$SMP Device - `$deviceIdentityNameShort`" -:local mailBodyCopyright "Mikrotik RouterOS automatic backup & update (ver. $scriptVersion) \nhttps://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update" -:local changelogUrl "Check RouterOS changelog: https://mikrotik.com/download/changelogs/" +:local mailBodyCopyright "Mikrotik RouterOS automatic backup & update (ver. $scriptVersion) \nhttps://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update" +:local changelogUrl "Check RouterOS changelog: https://mikrotik.com/download/changelogs/" :local mailBodyDeviceInfo "" :set mailBodyDeviceInfo ($mailBodyDeviceInfo . "Device information:") @@ -472,274 +427,266 @@ :local mailAttachments [:toarray ""] -## IP address detection & anonymous statistics collection +## IP address detection :if ($scriptStep = 1 or $scriptStep = 3) do={ - :if ($scriptStep = 3) do={ - :log info ("$SMP Waiting for one minute before continuing to the final step.") - :delay 1m + :if ($scriptStep = 3) do={ + :log info ("$SMP Waiting for one minute before continuing to the final step.") + :delay 1m + } + # default values + :local publicIpAddress "not-detected" + :local telemetryDataQuery "" + + :if ($detectPublicIpAddress = true or $anonStats = true) do={ + :if ($anonStats = true) do={ + :set telemetryDataQuery ("\?mode=" . $scriptMode . "&scriptver=" . $scriptVersion . "&updatechannel=" . $updateChannel . "&osver=" . $runningOsVersion . "&step=" . $scriptStep . "&forcebackup=" . $forceBackup . "&onlypatchupdates=" . $installOnlyPatchUpdates . "&model=" . $deviceRbModel . "&deviceboard=" . $deviceBoardName) } - # default values, to be set later - :local publicIpAddress "not-detected" - :local telemetryDataQuery "" - :if ($detectPublicIpAddress = true or $allowAnonymousStatisticsCollection = true) do={ - :if ($allowAnonymousStatisticsCollection = true) do={ - :set telemetryDataQuery ("\?mode=" . $scriptMode . "&scriptver=" . $scriptVersion . "&updatechannel=" . $updateChannel . "&osver=" . $runningOsVersion . "&step=" . $scriptStep . "&forcebackup=" . $forceBackup . "&onlypatchupdates=" . $installOnlyPatchUpdates . "&model=" . $deviceRbModel . "&deviceboard=" . $deviceBoardName) - } - - :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`" + :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 Failed to detect public IP using default service: `$ipAddressDetectServiceDefault`" + :log warning "$SMP Trying fallback 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`" } + } } + + # basic safety + :set publicIpAddress ([:pick $publicIpAddress 0 15]) + + :if ($detectPublicIpAddress = true) do={ + :set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nPublic IP address: $publicIpAddress") + :log info "$SMP Public IP address detected: `$publicIpAddress`" + } + } } -## STEP ONE: Creating backups, checking for new RouterOs version and sending email with backups, -## Steps 2 and 3 are fired only if script is set to automatically update device and if a new RouterOs version is available. +## STEP 1: Create backups, check for new RouterOS, and send email +## Steps 2–3 run only if auto-update is enabled and a new version is available :if ($scriptStep = 1) do={ - :local routerOsVersionAvailable "0.0.0" - :local isNewOsUpdateAvailable false - :local isLatestOsAlreadyInstalled true - :local isOsNeedsToBeUpdated false - :local isUpdateCheckSucceeded false - :local isEmailNeedsToBeSent false + :local routerOsVersionAvailable "0.0.0" + :local isNewOsUpdateAvailable false + :local isLatestOsAlreadyInstalled true + :local isOsNeedsToBeUpdated false + :local isUpdateCheckSucceeded false + :local isEmailNeedsToBeSent false - :local mailSubjectPartAction "" - :local mailPtBodyAction "" + :local mailSubjectPartAction "" + :local mailPtBodyAction "" - :local mailPtSubjectBackup "" - :local mailPtBodyBackup "" + :local mailPtSubjectBackup "" + :local mailPtBodyBackup "" - # Checking for new RouterOS version - :if ($scriptMode = "osupdate" or $scriptMode = "osnotify") do={ - log info ("$SMP Setting update channel to `$updateChannel`") - /system package update set channel=$updateChannel - log info ("$SMP Checking for new RouterOS version. Current installed version is: `$runningOsVersion`") - /system package update check-for-updates + # Checking for new version + :if ($scriptMode = "osupdate" or $scriptMode = "osnotify") do={ + log info ("$SMP Setting update channel to `$updateChannel`") + /system package update set channel=$updateChannel + log info ("$SMP Checking for new RouterOS version. Current installed version is: `$runningOsVersion`") + /system package update check-for-updates - # Wait for 5 seconds to allow the system to check for updates - :delay 5s; + # Wait to allow the system to check for updates + :delay 5s; - :local packageUpdateStatus "undefined" + :local packageUpdateStatus "undefined" - :set routerOsVersionAvailable [/system package update get latest-version] - :set packageUpdateStatus [/system package update get status] + :set routerOsVersionAvailable [/system package update get latest-version] + :set packageUpdateStatus [/system package update get status] - :if ($packageUpdateStatus = "New version is available") do={ - :log info ("$SMP New RouterOS version is available: `$routerOsVersionAvailable`") - :set isNewOsUpdateAvailable true - :set isLatestOsAlreadyInstalled false - :set isUpdateCheckSucceeded true - :set isEmailNeedsToBeSent true + :if ($packageUpdateStatus = "New version is available") do={ + :log info ("$SMP New RouterOS version is available: `$routerOsVersionAvailable`") + :set isNewOsUpdateAvailable true + :set isLatestOsAlreadyInstalled false + :set isUpdateCheckSucceeded true + :set isEmailNeedsToBeSent true - :set mailSubjectPartAction "New RouterOS available" - :set mailPtBodyAction "New RouterOS version is available, current version: v$runningOsVersion, new version: v$routerOsVersionAvailable. \n$changelogUrl" - } else={ - :if ($packageUpdateStatus = "System is already up to date") do={ - :log info ("$SMP No new RouterOS version is available, the latest version is already installed: `v$runningOsVersion`") - :set isUpdateCheckSucceeded true - - :set mailSubjectPartAction "No os update available" - :set mailPtBodyAction "No new RouterOS version is available, the latest version is already installed: `v$runningOsVersion`" - } else={ - :log error ("$SMP Failed to check for new RouterOS version. Package check status: `$packageUpdateStatus`") - :set isEmailNeedsToBeSent true - - :set mailSubjectPartAction "Error unable to check new os version" - :set mailPtBodyAction "An error occurred while checking for a new RouterOS version.\nStatus returned: `$packageUpdateStatus`\n\nPlease review the logs on the device for more details and verify internet connectivity." - } - } - } - - # Checking if the script needs to install new RouterOS version - :if ($scriptMode = "osupdate" and $isNewOsUpdateAvailable = true) do={ - :if ($installOnlyPatchUpdates = true) do={ - :if ([$FuncIsPatchUpdateOnly $runningOsVersion $routerOsVersionAvailable] = true) do={ - :log info "$SMP New RouterOS version is available, and it is a patch update. Current version: v$runningOsVersion, new version: v$routerOsVersionAvailable" - :set isOsNeedsToBeUpdated true - } else={ - :log info "$SMP The script will not install this update, because it is not a patch update. Current version: v$runningOsVersion, new version: v$routerOsVersionAvailable" - :set mailPtBodyAction ($mailPtBodyAction . "\nThis update will not be installed, because the script is set to install only patch updates.") - } - } else={ - :set isOsNeedsToBeUpdated true - } - } - - - # Checking If the script needs to create a backup - :if ($forceBackup = true or $scriptMode = "backup" or $isOsNeedsToBeUpdated = true) do={ - :log info ("$SMP Starting backup process.") + :set mailSubjectPartAction "New RouterOS available" + :set mailPtBodyAction "New RouterOS version is available, current version: v$runningOsVersion, new version: v$routerOsVersionAvailable. \n$changelogUrl" + } else={ + :if ($packageUpdateStatus = "System is already up to date") do={ + :log info ("$SMP No new RouterOS version is available, the latest version is already installed: `v$runningOsVersion`") + :set isUpdateCheckSucceeded true + :set mailSubjectPartAction "No os update available" + :set mailPtBodyAction "No new RouterOS version is available, the latest version is already installed: `v$runningOsVersion`" + } else={ + :log error ("$SMP Failed to check for new RouterOS version. Package check status: `$packageUpdateStatus`") :set isEmailNeedsToBeSent true - :local backupName $backupNameTemplate - - # This means it's the first step where we create a backup before the update process - :if ($isOsNeedsToBeUpdated = true) do={ - :set backupName $backupNameBeforeUpdate - - #Email body if the purpose of the script is to update the device - :set mailSubjectPartAction "Update preparation" - :set mailPtBodyAction ($mailPtBodyAction . "\nThe update process for device '$deviceIdentityName' is scheduled to upgrade RouterOS from version v.$runningOsVersion to version v.$routerOsVersionAvailable (Update channel: $updateChannel)") - :set mailPtBodyAction ($mailPtBodyAction . "\nPlease note: The update will proceed only after a successful backup.") - :set mailPtBodyAction ($mailPtBodyAction . "\nA final report with detailed information will be sent once the update process is completed.") - :set mailPtBodyAction ($mailPtBodyAction . "\nIf you do not receive a second email within the next 10 minutes, there may be an issue. Please check your device logs for further information.") - } - - :do { - :set mailAttachments [$FuncCreateBackups $backupName $backupPassword $sensitiveDataInConfig]; - - :set mailPtSubjectBackup "Backup created" - :set mailPtBodyBackup "System backups have been successfully created and attached to this email." - } on-error={ - #failed to create backup - :set isOsNeedsToBeUpdated false - - :set mailPtSubjectBackup "Backup failed" - :set mailPtBodyBackup "The script failed to create backups. Please check device logs for more details." - - :log warning "$SMP Failed to create backup files. Update process will be cancelled, if the script is set to update the device." - } + :set mailSubjectPartAction "Error unable to check new os version" + :set mailPtBodyAction "An error occurred while checking for a new RouterOS version.\nStatus returned: `$packageUpdateStatus`\n\nPlease review the logs on the device for more details and verify internet connectivity." + } } + } - :if ($isEmailNeedsToBeSent = true) do={ - :log info "$SMP Preparing to send email..." - - :local mailStep1Subject $mailSubjectPrefix - :local mailStep1Body "" - - # Assemble email subject - :if ($mailSubjectPartAction != "") do={:set mailStep1Subject ($mailStep1Subject . " - " . $mailSubjectPartAction)} - :if ($mailPtSubjectBackup != "") do={:set mailStep1Subject ($mailStep1Subject . " - " . $mailPtSubjectBackup)} - # Assemble email body - :if ($mailPtBodyAction != "") do={:set mailStep1Body ($mailStep1Body . $mailPtBodyAction . "\n\n")} - :if ($mailPtBodyBackup != "") do={:set mailStep1Body ($mailStep1Body . $mailPtBodyBackup . "\n\n")} - - :set mailStep1Body ($mailStep1Body . $mailBodyDeviceInfo . "\n\n" . $mailBodyCopyright) - - # Send email with backup files attached - :do {$FuncSendEmailSafe $emailAddress $mailStep1Subject $mailStep1Body $mailAttachments} on-error={ - :set isOsNeedsToBeUpdated false - :log error "$SMP The script will not proceed with the update process, because the email was not sent." - #:error $exitErrorMessage - } - } - - :if ([:len $mailAttachments] > 0) do={ - :log info "$SMP Cleaning up backup files from the file system..." - /file remove $mailAttachments; - :delay 2s; - } - - :if ($isOsNeedsToBeUpdated = true) do={ - :log info "$SMP everything is ready to install new RouterOS, going to start the update process and reboot the device." - :do { - :local nextStep 2 - :if ($isCloudHostedRouter = true) do={ - :log info "$SMP The device is a cloud hosted router, the second step updating the Routerboard firmware will be skipped." - :set nextStep 3 - } - - :local scheduledCommand (":delay 5s; /system scheduler remove BKPUPD-NEXT-BOOT-TASK; :global buGlobalVarScriptStep $nextStep; :global buGlobalVarTargetOsVersion \"$routerOsVersionAvailable\"; :delay 10s; /system script run BackupAndUpdate;") - /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=$scheduledCommand start-time=startup interval=0 - - /system package update install - } on-error={ - # Failed to install new RouterOS version, remove the scheduled task - :do {/system scheduler remove BKPUPD-NEXT-BOOT-TASK} on-error={} - - :log error "$SMP Failed to install new RouterOS version. Please check device logs for more details." - - :local mailUpdateErrorSubject ($mailSubjectPrefix . " - Update failed") - :local mailUpdateErrorBody "The script was unable to install new RouterOS version. Please check device logs for more details." - - # Send email with error message - $FuncSendEmailSafe $emailAddress $mailUpdateErrorSubject $mailUpdateErrorBody "" - - :error $exitErrorMessage - } - } -} - -## STEP TWO: (after first reboot) routerboard firmware upgrade -## Steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. -:if ($scriptStep = 2) do={ - :log info "$SMP The script is in the second step, updating Routerboard firmware." - - :log info "$SMP Upgrading routerboard firmware from v.$deviceRbCurrentFw to v.$deviceRbUpgradeFw" - - /system routerboard upgrade - ## Wait until the upgrade is completed - :delay 2s - :log info "$SMP routerboard upgrade process was completed, going to reboot in a moment!"; - - ## Set scheduled task to send final report on the next boot, task will be deleted when it is done. (That is why you should keep original script name) - /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=":delay 5s; /system scheduler remove BKPUPD-NEXT-BOOT-TASK; :global buGlobalVarScriptStep 3; :global buGlobalVarTargetOsVersion \"$buGlobalVarTargetOsVersion\"; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0 - - ## Reboot system to boot with new firmware - /system reboot; -} - -## STEP THREE: Last step (after second reboot) sending final report -## Steps 2 and 3 are fired only if script is set to automatically update device and if new RouterOs is available. -## This step is executed after some delay -:if ($scriptStep = 3) do={ - :log info ("$SMP The script is in the third step, sending final report.") - - :local targetOsVersion $buGlobalVarTargetOsVersion - :do {/system script environment remove buGlobalVarTargetOsVersion} on-error={} - :if ([:len $targetOsVersion] = 0) do={ - :log warning "$SMP Something is wrong, the script was unable to get the target updated OS version from the global variable." - } - - :local mailStep3Subject $mailSubjectPrefix - :local mailStep3Body "" - - :if ($targetOsVersion = $runningOsVersion) do={ - :log info "$SMP The script has successfully verified that the new RouterOS version was installed, target version: `$targetOsVersion`, current version: `$runningOsVersion`" - - :set mailStep3Subject ($mailStep3Subject . " - Update completed - Backup created") - :set mailStep3Body ($mailStep3Body . "RouterOS and routerboard upgrade process was completed") - :set mailStep3Body ($mailStep3Body . "\nNew RouterOS version: v.$targetOsVersion, routerboard firmware: v.$deviceRbCurrentFw") - :set mailStep3Body ($mailStep3Body . "\n$changelogUrl") - :set mailStep3Body ($mailStep3Body . "\nBackups of the upgraded system are in the attachment of this email.") - :set mailStep3Body ($mailStep3Body . "\n\n" . $mailBodyDeviceInfo . "\n\n" . $mailBodyCopyright) - - :set mailAttachments [$FuncCreateBackups $backupNameAfterUpdate $backupPassword $sensitiveDataInConfig]; + # Checking if the script needs to install new os version + :if ($scriptMode = "osupdate" and $isNewOsUpdateAvailable = true) do={ + :if ($installOnlyPatchUpdates = true) do={ + :if ([$FuncIsPatchUpdateOnly $runningOsVersion $routerOsVersionAvailable] = true) do={ + :log info "$SMP New RouterOS version is available, and it is a patch update. Current version: v$runningOsVersion, new version: v$routerOsVersionAvailable" + :set isOsNeedsToBeUpdated true + } else={ + :log info "$SMP The script will not install this update, because it is not a patch update. Current version: v$runningOsVersion, new version: v$routerOsVersionAvailable" + :set mailPtBodyAction ($mailPtBodyAction . "\nThis update will not be installed, because the script is set to install only patch updates.") + } } else={ - :log error "$SMP The script was unable to verify that the new RouterOS version was installed, target version: `$targetOsVersion`, current version: `$runningOsVersion`" - :set mailStep3Subject ($mailStep3Subject . " - Update failed") + :set isOsNeedsToBeUpdated true + } + } - :set mailStep3Body ($mailStep3Body . "The script was unable to verify that the new RouterOS version was installed, target version: `$targetOsVersion`, current version: `$runningOsVersion`") - :set mailStep3Body ($mailStep3Body . "\nPlease check device logs for more details.") - :set mailStep3Body ($mailStep3Body . "\n\n" . $mailBodyDeviceInfo . "\n\n" . $mailBodyCopyright) + + # Checking If the script needs to create a backup + :if ($forceBackup = true or $scriptMode = "backup" or $isOsNeedsToBeUpdated = true) do={ + :log info ("$SMP Starting backup process.") + + :set isEmailNeedsToBeSent true + + :local backupName $backupNameTemplate + + # This means it's the first step where we create a backup before the update process + :if ($isOsNeedsToBeUpdated = true) do={ + :set backupName $backupNameBeforeUpdate + + #Email body if the purpose of the script is to update the device + :set mailSubjectPartAction "Update preparation" + :set mailPtBodyAction ($mailPtBodyAction . "\nThe update process for device '$deviceIdentityName' is scheduled to upgrade RouterOS from version v.$runningOsVersion to version v.$routerOsVersionAvailable (Update channel: $updateChannel)") + :set mailPtBodyAction ($mailPtBodyAction . "\nPlease note: The update will proceed only after a successful backup.") + :set mailPtBodyAction ($mailPtBodyAction . "\nA final report with detailed information will be sent once the update process is completed.") + :set mailPtBodyAction ($mailPtBodyAction . "\nIf you do not receive a second email within the next 10 minutes, there may be an issue. Please check your device logs for further information.") } - $FuncSendEmailSafe $emailAddress $mailStep3Subject $mailStep3Body $mailAttachments + :do { + :set mailAttachments [$FuncCreateBackups $backupName $backupPassword $sensitiveDataInConfig]; - :if ([:len $mailAttachments] > 0) do={ - :log info "$SMP Cleaning up backup files from the file system..." - /file remove $mailAttachments; - :delay 2s; + :set mailPtSubjectBackup "Backup created" + :set mailPtBodyBackup "System backups have been successfully created and attached to this email." + } on-error={ + #failed to create backup + :set isOsNeedsToBeUpdated false + + :set mailPtSubjectBackup "Backup failed" + :set mailPtBodyBackup "The script failed to create backups. Please check device logs for more details." + + :log warning "$SMP Backup creation failed. Update process will be canceled if automatic update is enabled" } + } - :log info "$SMP Final report email sent successfully, and the script has finished." + :if ($isEmailNeedsToBeSent = true) do={ + :log info "$SMP Preparing to send email..." + + :local mailStep1Subject $mailSubjectPrefix + :local mailStep1Body "" + + # subject + :if ($mailSubjectPartAction != "") do={:set mailStep1Subject ($mailStep1Subject . " - " . $mailSubjectPartAction)} + :if ($mailPtSubjectBackup != "") do={:set mailStep1Subject ($mailStep1Subject . " - " . $mailPtSubjectBackup)} + # body + :if ($mailPtBodyAction != "") do={:set mailStep1Body ($mailStep1Body . $mailPtBodyAction . "\n\n")} + :if ($mailPtBodyBackup != "") do={:set mailStep1Body ($mailStep1Body . $mailPtBodyBackup . "\n\n")} + + :set mailStep1Body ($mailStep1Body . $mailBodyDeviceInfo . "\n\n" . $mailBodyCopyright) + + # Send email with backups + :do {$FuncSendEmailSafe $emailAddress $mailStep1Subject $mailStep1Body $mailAttachments} on-error={ + :set isOsNeedsToBeUpdated false + :log error "$SMP The script will not proceed with the update process, because the email was not sent." + } + } + + :if ([:len $mailAttachments] > 0) do={ + :log info "$SMP Cleaning up backup files from the file system..." + /file remove $mailAttachments; + :delay 2s; + } + + :if ($isOsNeedsToBeUpdated = true) do={ + :log info "$SMP everything is ready to install new RouterOS, going to start the update process and reboot the device." + :do { + :local nextStep 2 + :if ($isCloudHostedRouter = true) do={ + :log info "$SMP The device is a cloud hosted router, the second step updating the Routerboard firmware will be skipped." + :set nextStep 3 + } + + :local scheduledCommand (":delay 5s; /system scheduler remove BKPUPD-NEXT-BOOT-TASK; :global buGlobalVarScriptStep $nextStep; :global buGlobalVarTargetOsVersion \"$routerOsVersionAvailable\"; :delay 10s; /system script run BackupAndUpdate;") + /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=$scheduledCommand start-time=startup interval=0 + + /system package update install + } on-error={ + # Failed to install new os version, remove the task + :do {/system scheduler remove BKPUPD-NEXT-BOOT-TASK} on-error={} + + :log error "$SMP Failed to install new RouterOS version. Please check device logs for more details." + + :local mailUpdateErrorSubject ($mailSubjectPrefix . " - Update failed") + :local mailUpdateErrorBody "The script was unable to install new RouterOS version. Please check device logs for more details." + + # Send email with error + $FuncSendEmailSafe $emailAddress $mailUpdateErrorSubject $mailUpdateErrorBody "" + + :error $exitErrorMessage + } + } +} + +## STEP 2: (Post-reboot) Upgrade RouterBOARD firmware +## Runs only if auto-update is enabled and a new RouterOS version was found +:if ($scriptStep = 2) do={ + :log info "$SMP The script is in the second step, updating Routerboard firmware." + + :log info "$SMP Upgrading routerboard firmware from v.$deviceRbCurrentFw to v.$deviceRbUpgradeFw" + + /system routerboard upgrade + :delay 2s + + :log info "$SMP routerboard upgrade process was completed, going to reboot in a moment!"; + + ## Set task to send final report on the next boot + /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=":delay 5s; /system scheduler remove BKPUPD-NEXT-BOOT-TASK; :global buGlobalVarScriptStep 3; :global buGlobalVarTargetOsVersion \"$buGlobalVarTargetOsVersion\"; :delay 10s; /system script run BackupAndUpdate;" start-time=startup interval=0 + + /system reboot; +} + +## STEP 3: Final report (after second reboot, with delay). +## Runs only if auto-update is enabled and a new RouterOS version was found. +:if ($scriptStep = 3) do={ + :log info ("$SMP The script is in the third step, sending final report.") + + :local targetOsVersion $buGlobalVarTargetOsVersion + :do {/system script environment remove buGlobalVarTargetOsVersion} on-error={} + :if ([:len $targetOsVersion] = 0) do={ + :log warning "$SMP Something is wrong, the script was unable to get the target updated OS version from the global variable." + } + + :local mailStep3Subject $mailSubjectPrefix + :local mailStep3Body "" + + :if ($targetOsVersion = $runningOsVersion) do={ + :log info "$SMP Successfully verified new RouterOS version: target: `$targetOsVersion`, current: `$runningOsVersion`" + + :set mailStep3Subject ($mailStep3Subject . " - Update completed - Backup created") + :set mailStep3Body ($mailStep3Body . "RouterOS and routerboard upgrade process was completed") + :set mailStep3Body ($mailStep3Body . "\nNew RouterOS version: v.$targetOsVersion, routerboard firmware: v.$deviceRbCurrentFw") + :set mailStep3Body ($mailStep3Body . "\n$changelogUrl\nBackups of the upgraded system are in the attachment of this email.\n\n$mailBodyDeviceInfo\n\n$mailBodyCopyright") + + :set mailAttachments [$FuncCreateBackups $backupNameAfterUpdate $backupPassword $sensitiveDataInConfig]; + } else={ + :log error "$SMP Failed to verify new RouterOS version: target: `$targetOsVersion`, current: `$runningOsVersion`" + :set mailStep3Subject ($mailStep3Subject . " - Update failed") + + :set mailStep3Body ($mailStep3Body . "The script was unable to verify that the new RouterOS version was installed, target version: `$targetOsVersion`, current version: `$runningOsVersion`\nCheck device logs for more details.\n\n$mailBodyDeviceInfo\n\n$mailBodyCopyright") + } + + $FuncSendEmailSafe $emailAddress $mailStep3Subject $mailStep3Body $mailAttachments + + :if ([:len $mailAttachments] > 0) do={ + :log info "$SMP Cleaning up backup files from the file system..." + /file remove $mailAttachments; + :delay 2s; + } + + :log info "$SMP Final report email sent successfully, and the script has finished." } :log info "$SMP the script has finished, script step: `$scriptStep` \n\n" diff --git a/README.md b/README.md index 5895a34..e4b945b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Mikrotik RouterOS automatic backup and update This script allows you to generate daily backups of MikroTik and send them to an email address. You can also choose to enable automatic RouterOS upgrades or receive notifications exclusively for new firmware versions. - -> ‼️Use [WinBox 4.0](https://mikrotik.com/download), to update the script > 💡 If you have any ideas about the script or you just want to share your opinion, you are welcome to [Discussions](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/discussions), or you can open an [issue](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/issues) if you found a bug. @@ -21,8 +19,6 @@ This script allows you to generate daily backups of MikroTik and send them to an **Backups and automatic RouterOS upgrade** - The script begins by creating a backup, followed by a check for any new versions of RouterOS. If a newer firmware version is detected, the script initiates the upgrade process. Upon completion, two emails are sent: the first includes the system backups from the prior RouterOS version, and the second, sent post-upgrade, contains backups of the updated system. ## How to use -> ‼️Use [WinBox 4.0](https://mikrotik.com/download), to update the script - > ❗️ **Important** > Ensure your device identity does not contain spaces and special characters! `System -> Identity` From dd10256c941fc64fe14152ae2cd018c0e4ef9f1c Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Sun, 20 Apr 2025 23:32:46 +0900 Subject: [PATCH 26/27] docs: update README.md begining -> beginning --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4b945b..afee68c 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This script allows you to generate daily backups of MikroTik and send them to an > Ensure your device identity does not contain spaces and special characters! `System -> Identity` ##### 1. Configure parameters -Take the [script](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/BackupAndUpdate.rsc) and configure it's parameters at the begining of the file. +Take the [script](https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update/raw/master/BackupAndUpdate.rsc) and configure it's parameters at the beginning of the file. This step is straightforward as all parameters are well-commented. **Important!** Don't forget to provide correct email address for backups and pay attention to `scriptMode` variable. From d1cd2dff3d4d52471d619b5c62de423630691c0f Mon Sep 17 00:00:00 2001 From: Alexander Tebiev <326840+beeyev@users.noreply.github.com> Date: Sat, 19 Jul 2025 12:53:56 +0200 Subject: [PATCH 27/27] issue #72 --- BackupAndUpdate.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BackupAndUpdate.rsc b/BackupAndUpdate.rsc index d70f770..8d3d5d3 100644 --- a/BackupAndUpdate.rsc +++ b/BackupAndUpdate.rsc @@ -189,7 +189,7 @@ :log info ("$SMP Config export complete: `$backupFileConfig`") :log info ("$SMP Waiting a little to ensure backup files are written") - :delay 20s + :delay 40s :if ([:len [/file find name=$backupFileSys]] > 0) do={ :log info ("$SMP system backup file successfully saved to the file system: `$backupFileSys`")