Configuration restoration (cloning) in MikroTik devices is a disaster. While restoring a simple configuration without additional “plugins” is usually unproblematic, using OVPN can lead to failure even when restoring a .backup file to the same model device (due to missing keys). For the ‘container’ package, don’t count on its settings being restored correctly either.
Since recent ROS7 versions, the manufacturer has enabled import error trapping, which – combined with line-by-line parsing of the file – finally allows loading such a backup regardless of anything.
When cloning settings to a second device, this mode can be a significant facilitator. The imperfectR.rsc script enables automated import of .cert and .key files alongside the backup.rsc file. As usual, it starts with a 15-second pause to allow the device to fully emerge from its deep sleep state (it still happens that a backup.rsc file loaded conventionally via reset configuration fails with errors like missing interfaces, etc.).
# 07.07.2025
# imperfectR.rsc V0.1 *ROS 7.18+*
# based on: Jonathan Cutrer perfectrestore V0.1.1
#
# Export via "/export terse file=backup show-sensitive"
# CLI ="/system reset-configuration no-defaults=yes run-after-reset=flash/imperfectR.rsc"
# dodano import certyfikatow .crt z przywroceniem nazwy pliku po imporcie oraz plikow .key
# mozliwosc importu z bledami
# max file size 61437B
:local predelay 15
:local targetfile "flash/backup.rsc"
#pobranie pliku do zmiennej
:local fileContents [/file get $targetfile contents]
:local fileLen [ :len $fileContents ]
:if ($fileLen = 0) do={
:put "File size error"
:return 0
}
/tool romon set enabled=yes
/system identity set name=imperfectR
# Wait for interfaces to initialize
:local sec
:do {
:put ($predelay - $sec)
:beep frequency=2000 length=25ms;
:delay 1s
:set sec ($sec+1)
} while ($sec < $predelay)
# Setup temporary logging to disk
/system logging action remove [/system logging action find where name=imperfectrestore]
/system logging remove [/system logging find where action=imperfectrestore]
/system logging action add disk-file-count=1 disk-file-name=flash/imperfectrestore.log disk-lines-per-file=4096 name=imperfectrestore target=disk
#/system logging add action=imperfectrestore topics=system,info
#/system logging add action=imperfectrestore topics=script,info
#/system logging add action=imperfectrestore topics=warning
/system logging add action=imperfectrestore topics=error
/system logging add action=imperfectrestore topics=critical
#/system logging add action=imperfectrestore topics=debug
#----------------------- CERT/KEY importer ----------------------------
#automatyczny import wszystkich certyfikatow we flash o nazwach cert1.crt,cert2.crt,cert3.crt...
#petla while wykorzystuje wlasciwosc usuwania certyfikatu po jego imporcie
#import rozpoczyna sie od ostatniego indexu pliku, konczy gdy nie ma wiecej plikow o wskazanym rozszerzeniu
:local itt
:local itn
/file
:foreach item in=[find] do={
:set itt ("$[get $item type]")
:set itn ("$[get $item name]")
:if ( $itt = ".crt file") do={
#crt import beep
:beep frequency=1000 length=50ms;:delay 50ms;:beep frequency=1500 length=50ms;:delay 200ms;
:put $itn
/certificate/import file-name=$itn
}
}
:foreach item in=[find] do={
:set itt ("$[get $item type]")
:set itn ("$[get $item name]")
:if ( $itt = ".key file") do={
#key import beep
:beep frequency=2000 length=50ms;:delay 50ms;:beep frequency=2500 length=50ms;:delay 200ms;
:put $itn
/certificate/import file-name=$itn
}
}
#-----------------------------------------------------------------------
#ten skrypt kopiuje nazwa common-name do nazwy pliku importowanego certyfikatu,
#bo annoing mikrotik podczs importu zmienia nazwe pliku dodajac jakies smieci _0 do nazwy
:local idx 0
:foreach item in=[/certificate find] do={
:local result [/certificate print as-value]
:local h1 ([$result as-value]->$idx)
:local h2 ([$h1 as-value]->"common-name")
/certificate set $idx name=($h2."")
:set idx ($idx + 1)
}
#---------------------------------------------------------------------
#min liczniki polozenia parsowanych ciagow
:local lastEnd 0
:local lastEnd2 0
:local lineEnd 0
:local lineEnd2 0
:local remBegin 0
:local execBegin
#licznik wszystkich linii
:local line 0
#licznik linii przetwarzanych (nie komentarzy)
:local EXline 0
#licznik linii komentarzy
:local REMline 0
#licznik bledow
:local error 0
#linia do wykonania
:local ExeLine
#przygotowanie pliku posredniczacego
/file print file=linExec.txt
:do {
:beep frequency=(($line * 10) + 200) length=25ms;
:delay 25ms
#znajdowanie delimiterow (pewne uproszczenie powoduje nieparsowanie pierwszej linii - zwykle jest zakomentowana)
#:set remBegin 1
:if ($line > 0) do={
:local tmp [:find $fileContents "\n" $lastEnd ] ;
:if ( $tmp > $lineEnd ) do={
:set lineEnd ($tmp)
}
:set remBegin [:find $fileContents "\n#" $lastEnd ] ;
#:set execBegin [:find $fileContents "\n:" $lastEnd ] ;
:set execBegin [:find $fileContents "\n/" $lastEnd ] ;
}
:set lineEnd2 [:find $fileContents "\n/" $lineEnd ] ;
#debuger
#:put "file length $fileLen"
#:put "parsed lines $line"
#:put "command lines $EXline"
#:put "end $lineEnd"
#:put "rem $remBegin"
#:put "exe $execBegin"
#:put "errors $error"
#:put "lastend $lastEnd"
#:put "line $line"
#parser
:if ((($remBegin > 0) || ($line = 0)) && ($remBegin <= $lineEnd)) do={
#parsowanie komentarzy wymaga oczekiwania zwyklego konca \ n
:set lineEnd2 [:find $fileContents "\n" $lineEnd ] ;
:set $execBegin 0
:set REMline ($REMline+1)
:set $RemLine [:pick $fileContents $remBegin $lineEnd2 ]
:put $RemLine
}
:set line ($line+1)
:if (($execBegin>0) && ($execBegin <= $lineEnd)) do={
:set $remBegin 0
:set EXline ($EXline+1)
#pobranie linii wycietej z fileContent
:set $ExeLine [:pick $fileContents $execBegin $lineEnd2 ]
:put $ExeLine
#zapisanie jednej linii przed jej importem do pliku
/file set linExec.txt contents=$ExeLine
:local E1 0
#error handler nie pozwoli zakonczyc przetwarzania w razie bledow
:onerror e in={ :import file-name=linExec.txt } do={
:set error ($error +1)
:set E1 1
#podmiana fragmentu komunikatu bledu za fraza 'line"' bo tam zawsze line2, wiec zamieniamy to na numer linii z rozkazem pobrany ze zmiennej EXline
:set e ([:pick $e 0 [:find $e "; line"]])
:put "Fail.$error: $e * command:$EXline line:$line *)"
:log error ("Fail.$error: $e * command:$EXline line:$line *)")
#identyfikator urzadzenia bedzie informowal o poprawnosci importu wraz z podaniem ilosci bledow
/system identity set name="imperfectR E.$error"}
:if ( $E1=0 ) do={
:put "at line:$line"
}
}
:set lastEnd2 ($lastEnd)
:set lastEnd ($lineEnd+1)
#detekcja konca pliku gdy brak znaku konca w ostatniej linii
:if ($lastEnd=$lastEnd2) do={
:set lastEnd ($fileLen)
}
} while (($lastEnd < ($fileLen -1)) && $line < 1500)
:put "\nfile length $fileLen"
:put "parsed lines $line"
:put "remark lines $REMline"
:put "command lines $EXline"
:put "errors $error"
#skasowanie niepotrzebnego pliku
/file remove linExec.txt
:delay 1s
:if ($error=0) do={
#mario
:local A1 { 659; 659;20; 659; 20;523; 659; 20; 784}
:local A2 { 100; 100;20; 100; 20; 100; 150; 20; 200}
:local A3 { 40; 40; 80; 40; 80; 40; 40; 80; 200}
:local idx 0
:local freq
:local time
:local pause
:do {
:set freq ($A1->$idx)
:set time ($A2->$idx)
:set pause ($A3->$idx)
:beep frequency=$freq length=($time."ms");:delay (($pause+ $time)."ms");
:set idx ($idx + 1)
} while ($idx<9)
#opcjonalnie mozna to wylaczyc, jezeli w przypadku bezblednego importu chcemy zachowac identyfikator z importowanego pliku
#/system identity set name=imperfectR_OK
# Teardown temporary logging to disk
/system logging remove [/system logging find where action=imperfectrestore]
/system logging action remove [/system logging action find where name=imperfectrestore]
} else={
:beep frequency=100 length=500ms;
:local percent (($error *100 / $EXline *100 ) / 100)
/system identity set name="imperfectR $percent%"}
}