Script to update RouterOS after X days of release

I’ve had a script to check and install updates but I added functionality to update only after the release has been out for a period of time.
I do this to avoid installing a release that has bugs and has to be released again or pulled.

I run this script daily so it can count the days from the last update as well as the fw update script in case the RouterOS came with a FW update (usually does). I run RouterOSupdate then FirmwareInstall 15min later


I call this script RouterOSupdate

#This script looks to see if there is a RouterOS update available, if so, it starts counting in variable DaysSinceRelease until the release has been out for "DaysToWait" days or more.
#the variable is also saved in a system file to survive reboots that may occur.
#This script should run daily to keep the DaysSinceRelease variable up to date.
#G. Racino 01Oct2023

#days to wait after a new release before updating this device.
:local DaysToWait 30
:global LatestVer [/system package update get latest-version]

/system package update set channel=stable
/system package update
check-for-updates once
:delay 5s;

#initialize files if they do not exist
:if ([/file find name="DaysSinceRelease.txt"] = "") do={:log info "DaysSinceRelease file does not exists, creating"; /file add name=DaysSinceRelease.txt;/file set "DaysSinceRelease.txt" contents="0";delay 2s}
:global DaysSinceRelease [/file get [/file find name=DaysSinceRelease.txt] contents];

:if ([/file find name="CountingVer.txt"] = "") do={:log info "CountingVer file does not exists, creating"; /file add name=CountingVer.txt;/file set "CountingVer.txt" contents=[/system package update get installed-version];delay 2s}
:global CountingVer [/file get [/file find name=CountingVer.txt] contents];

:local newdays $DaysSinceRelease
:set $newdays ($newdays + 1)

:log info "~~~~~~~~~~RouterOS update check:  Latest Ver=$LatestVer, Installed Ver=$CountingVer ~~~~~~~~~~~"

:if ([/system package update get status] = "New version is available") do={

   :if (([/system package update get status] = "New version is available") && ($newdays <= $DaysToWait) && ($CountingVer = $LatestVer)) do={
   :log info "~~~~~~~~~New RouterOS release is out for $newdays so NOT updating this device until release has been out for $DaysToWait days~~~~~~~~~~~~~~~~" 
   :set $DaysSinceRelease $newdays;
   /file set "DaysSinceRelease.txt" contents=$newdays
   } else={

   :if ( ([/system package update get status] = "New version is available") && ($newdays > $DaysToWait) && ($CountingVer != $LatestVer)) do={
   :log info "~~~~~~~~~RouterOS version changed, need to update on next check.  Waiting period of $DaysToWait met. Wait time was $newdays of $DaysToWait, latest version is $LatestVer, tracking version was $CountingVer"
   :set CountingVer $LatestVer
   /file set "CountingVer.txt" content=$LatestVer
   /file set "DaysSinceRelease.txt" content=0
   :set $DaysSinceRelease 0
   } else={

   :if ( ([/system package update get status] = "New version is available") && ($newdays > $DaysToWait)  && ($CountingVer = $LatestVer)) do={
   :log info "~~~~~~~~~~New RouterOS release is out for $newdays so updating this device~~~~~~~~~"; 
   :set $DaysSinceRelease 0;
   /file set "DaysSinceRelease.txt" contents="0"
   :delay 3s;
   /system package update install 
   }
   }}

} else={
:log info "~~~~~~~~~~RouterOS update check: No new updates.~~~~~~~~~~~~"
/file set "DaysSinceRelease.txt" content="0"
:set $DaysSinceRelease 0
}

and this one is the fw update I call FirmwareInstall

## script to update RouterOS firmware
## check for updated firmware
:log info ("Checking for FW upgrade...")
/system routerboard
   :if ( [get current-firmware] != [get upgrade-firmware]) do={ 
      ## New version of firmware available, let's upgrade
      :log info ("Upgrading firmware on router $[/system identity get name] from $[/system routerboard get current-firmware] to $[/system routerboard get upgrade-firmware]")
      :delay 3s;
      upgrade

      ## Wait for upgrade, then reboot
      :delay 8s;
      /system reboot

   } else={

   :log info ("No Router FW upgrade found")

   }

}

Nice idea for a scripting exercise, but just to let you know - MikroTik does not do this. There is no re-release or version pull. We always release a new version.

instead of working with a file, you can use a layer7 to save a variable

/ip fire layer7-protocol add/set/get

It’s all nonsense. And even just 30 days is ridiculous.

The only thing that is actually valid when you want to proceed with the mass update is to publish on a machine/server a file or a web page where the minimum version to which the device must be updated is written, and the script will automatically download the specific file for machine architecture, and related additional packages, and will reboot to apply the update.

But what’s the point of saving a file or value in layer 7 just to know how many days ago that version was released?
For example, reading just data inside
https://upgrade.mikrotik.com/routeros/LATEST.7
You obtain “7.11.2 1693490147” and if you split at the space, you have version number and build time
1693490147 = Thu Aug 31 2023 13:55:47 GMT+0000 (GMT)
So, just compare actual time with build time…
1696238651 - 1693490147 = 2748504
2748504 / 86400 = ~31 days

I had an easy scheduled script every 30 days;
/system package update check-for-updates once
:if ([get status] = “New version is available”) do={

  • backup
  • then “install”

instead of making things complicated and updating X days after release date, I’ll be more concerned to not update to the latest version, as @rextended teached time ago, never update production devices without having tried first.
This is what I have experienced and can confirm the good practice, having a scheduled update every X day or after X days after release will not cover you against critical bugs, if you want to auto-update, better at least to find a way to download the previous version.

I understand blind updating can be a problem. I’m just trying to protect from a release that had a short life and had serious bugs that got fixed in days or weeks.
Feature changes that can break things, yes, have to test that stuff, and I do.

Yes, I know. my intention was to wait until a new version was out for X days before updating. Its been my observation that if there was a big problem, a new one gets released pretty quick to correct any issues.

That was my first attempt but I could not get the build time to return in a script call
:global LatestVer [/system package update get latest-version]
This only returns the version number, no build timestamp.

Ah now I get it. If version X is latest version and after 30 days the same version is STILL the latest version, then upgrade? It makes sense, but also this situation will not be common, as we release new versons for other reasons too, not just because of problems.

I still stand by what I said, if you are on stable channel, there is no need to worry, MikroTik is not releasing potentially broken versions in there. We first test them in less stable channels

Is there a command similar to
:global LatestVer [/system package update get latest-version]
that also returns the build date?

When executing the command:

:put [/system/package/update/ get]
channel=stable;installed-version=7.11.2;latest-version=7.11.2;status=System is already up to date

No compilation date available, I have only seen it in the changelog itself:

What’s new in 7.11.2 (2023-Aug-31 16:55):

*) dhcp - fixed DHCP server “authoritative” and “delay-threshold” settings (introduced in v7.11.1);
But I don’t know how we could get that date from the text itself.

BR.

There would be a solution to get the build date:

/system package update
:local latest [get latest-version]
:local changeLog ([/tool fetch “http://upgrade.mikrotik.com/routeros/$latest/CHANGELOG” output=user as-value] → “data”)
:local dateEx [:pick $changeLog ([:find $changeLog “(”]+1) [:find $changeLog “):”]]
:put $dateEx

Output: 2023-Aug-31 16:55
Edited for simplified version

that is one way to do it. Thanks.

Nice! Should not be more useful to pick just the date and compare it with the current date [/system clock get date], than if the date is X days after the release, install?

Yeah, was starting to work on that but have not worked out the :pick of only the date without time. Seems pick is a little finicky. and not sure the math will work in that format.

Trouble is the date format in the changelog is different than that in the router

“2023-Aug-31 16:55” has to be converted to “2023-08-31 16:55”

found a script from @rextended to convert to epoch to do the math but even that does not like the “Aug” format.
the datetime2epoch script is here http://forum.mikrotik.com/t/i-did-it-script-to-compute-unix-time/68576/30

Simple converter: (based on a @rextended script)


:global date2ymd do={
    :local dtime [:tostr $1]
    :local yyyy; :local MM; :local dd; :local HH
    # Date format: 2023-Aug-31 16:55
    :if ([:len $dtime] = 17) do={
        :set yyyy [:pick $dtime 0 4]; :set dd [:pick $dtime 9 11]; :set HH [:pick $dtime 12 17]
        :set MM   ([:find "xxanebarprayunulugepctovec" [:pick $dtime 6 8] -1]/2); :if ($MM < 10) do={:set MM "0$MM"}
    }
    :return "$yyyy-$MM-$dd $HH"
}

[admin@MikroTik] > :put [$date2ymd “2023-Aug-31 16:55”]
2023-08-31 16:55

Ok, that is rather helpful.

I’ve reworked the script to use current date and the log date to determine days since release.
I’m naming it “RouterOSupdateV2”
I just put the two functions in the script but they could be in a “run at boot” script to make them more global.

#This script looks to see if there is a RouterOS update available, if so, it starts counting in variable DaysSinceRelease until the release has been out for "DaysToWait" days or more.
#the variable is also saved in a system file to survive reboots that may occur.
#This script should run daily to keep the DaysSinceRelease variable up to date.
#G. Racino 01Oct2023
#Updated 12Oct2023 to directly access release date from MT web site release log. WIth help from @rextended and @diamuxin from MT forum

#days to wait after a new release before updating this device.
:local DaysToWait 30
:local DSR

/system package update set channel=stable
/system package update
check-for-updates once
:delay 5s;
:global LatestVer [/system package update get latest-version]
:global InstVer [/system package update get installed-version]

:log info "~~~~~~~~~~RouterOS update check:  Latest Ver=$LatestVer, Installed Ver=$InstVer ~~~~~~~~~~~"

#The two below functions are used to convert the time stamps to a format that can be use in a simple subtract formula.
#They are from @rextended on MT forum
#http://forum.mikrotik.com/t/i-did-it-script-to-compute-unix-time/68576/28
:global datetime2epoch do={
    :local dtime [:tostr $1]
    /system clock
    :local cyear [get date] ; :if ($cyear ~ "....-..-..") do={:set cyear [:pick $cyear 0 4]} else={:set cyear [:pick $cyear 7 11]}
    :if (([:len $dtime] = 10) or ([:len $dtime] = 11)) do={:set dtime "$dtime 00:00:00"}
    :if ([:len $dtime] = 15) do={:set dtime "$[:pick $dtime 0 6]/$cyear $[:pick $dtime 7 15]"}
    :if ([:len $dtime] = 14) do={:set dtime "$cyear-$[:pick $dtime 0 5] $[:pick $dtime 6 14]"}
    :if ([:len $dtime] =  8) do={:set dtime "$[get date] $dtime"}
    :if ([:tostr $1] = "") do={:set dtime ("$[get date] $[get time]")}
    :local vdate [:pick $dtime 0 [:find $dtime " " -1]]
    :local vtime [:pick $dtime ([:find $dtime " " -1] + 1) [:len $dtime]]
    :local vgmt  [get gmt-offset]; :if ($vgmt > 0x7FFFFFFF) do={:set vgmt ($vgmt - 0x100000000)}
    :if ($vgmt < 0) do={:set vgmt ($vgmt * -1)}
    :local arrm  [:toarray "0,0,31,59,90,120,151,181,212,243,273,304,334"]
    :local vdoff [:toarray "0,4,5,7,8,10"]
    :local MM    [:pick $vdate ($vdoff->2) ($vdoff->3)]
    :local M     [:tonum $MM]
    :if ($vdate ~ ".../../....") do={
        :set vdoff [:toarray "7,11,1,3,4,6"]
        :set M     ([:find "xxanebarprayunulugepctovecANEBARPRAYUNULUGEPCTOVEC" [:pick $vdate ($vdoff->2) ($vdoff->3)] -1] / 2)
        :if ($M>12) do={:set M ($M - 12)}
    }
    :local yyyy  [:pick $vdate ($vdoff->0) ($vdoff->1)] ; :if ((($yyyy - 1968) % 4) = 0) do={:set ($arrm->1) -1; :set ($arrm->2) 30}
    :local totd  ((($yyyy - 1970) * 365) + (($yyyy - 1968) / 4) + ($arrm->$M) + ([:pick $vdate ($vdoff->4) ($vdoff->5)] - 1))
    :return      (((((($totd * 24) + [:pick $vtime 0 2]) * 60) + [:pick $vtime 3 5]) * 60) + [:pick $vtime 6 8] - $vgmt)
}

#located http://forum.mikrotik.com/t/script-to-update-routeros-after-x-days-of-release/170000/18
:global date2ymd do={
    :local dtime [:tostr $1]
    :local yyyy; :local MM; :local dd; :local HH
    # Date format: 2023-Aug-31 16:55
    :if ([:len $dtime] = 17) do={
        :set yyyy [:pick $dtime 0 4]; :set dd [:pick $dtime 9 11]; :set HH [:pick $dtime 12 17]
        :set MM   ([:find "xxanebarprayunulugepctovec" [:pick $dtime 6 8] -1]/2); :if ($MM < 10) do={:set MM "0$MM"}
    }
    :return "$yyyy-$MM-$dd $HH"
}

#Check release date of new version to see if it is DaysToWait days old before installing
:if ([/system package update get status] = "New version is available") do={

      # Get the Changelog of the latest version of the package and save it in a text file.

      :local VerChangeLog ([/tool fetch "http://upgrade.mikrotik.com/routeros/$LatestVer/CHANGELOG" output=user as-value] -> "data")

      :local dateEx [:pick $VerChangeLog ([:find $VerChangeLog "("]+1) ([:find $VerChangeLog "):"]) ]
      :local currDate [/system clock get date]

      #:log info "dateEx=$dateEx  currDate=$currDate"
      :local EPOCHdateEx [$datetime2epoch [$date2ymd $dateEx]]
      :local EPOCHcurrDate [$datetime2epoch $currDate]

      :set DSR (([:tonum $EPOCHcurrDate]-[:tonum $EPOCHdateEx])/86400)

      :if ($DSR <= $DaysToWait) do={
      :log info "~~~~~~~~~New RouterOS release is out for $DSR days so NOT updating this device until release has been out for $DaysToWait days~~~~~~~~~~~~~~~~" 
      } else={
      :log info "~~~~~~~~~~New RouterOS release is out for $DSR days so updating this device~~~~~~~~~"; 
      :delay 3s;
      /system package update install 
      }
} else={
:log info "~~~~~~~~~~RouterOS update check: No new update available.~~~~~~~~~~~~"
}

I think the script is not well designed. In this part:


:if ($DSR <= $DaysToWait) do={
    :log info "~~~~~~~~~ New RouterOS release is out for $DSR so NOT updating this device until release has been out for $DaysToWait days ~~~~~~~~~~~~~~~~" 
    :set $DaysSinceRelease $newdays;
    /file set "DaysSinceRelease.txt" contents=$newdays
} else={...}

a) The local variable $DaysSinceRelease is not declared initially and neither is $newdays.

b) Is the variable $DSR the same as $DaysSinceRelease? You have to declare it with the same name.

c) You don’t create correctly the file with the content of $newdays, it should be like this:


local filename "DaysSinceRelease.txt"
/file print file="$filename"; :delay 5s; set $filename contents=$newdays

Check the script because as it is it does not work.