# PCSRT HTTPS command console, agent v2. Not an SSH shell or VPN. # Review on a lab router. Import once; existing telemetry poll.rsc is unchanged. # Configure the same device token privately. Do not publish the configured copy. /system script add name=pcsrt-support policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon dont-require-permissions=no source={ :local pcsrtBase "https://mikrotik.pcsrt.com" :local pcsrtToken "REPLACE_WITH_DEVICE_TOKEN" :local pcsrtLte "lte1" :if ([/system script job print count-only as-value where script=[:jobname]] > 1) do={ :return "" } :global pcsrtSupportLastGood :local pcsrtGuard [/system scheduler find where name="pcsrt-recovery"] :local pcsrtUndo [/system script find where name="pcsrt-undo"] :global pcsrtSupportId :global pcsrtSupportOutput :global pcsrtSupportStatus :local pcsrtSchedule [/system scheduler find where name="pcsrt-support"] :do { :local pcsrtBody {"version"="2";"result_id"="";"status"="succeeded";"output"=""} :if (([:len $pcsrtGuard] = 1) && ([:len $pcsrtUndo] = 1)) do={ :set ($pcsrtBody->"guard_id") [/system script get $pcsrtUndo comment] :set ($pcsrtBody->"guard_state") [/system scheduler get $pcsrtGuard comment] } :if ([:len $pcsrtSupportId] > 0) do={ :set ($pcsrtBody->"result_id") $pcsrtSupportId :set ($pcsrtBody->"status") $pcsrtSupportStatus :set ($pcsrtBody->"output") $pcsrtSupportOutput } :local pcsrtFetch [/tool fetch url=($pcsrtBase."/api/device/support") http-method=post http-header-field=("Authorization: Bearer ".$pcsrtToken.",Content-Type: application/json") http-data=[:serialize to=json value=$pcsrtBody options=json.no-string-conversion] check-certificate=yes http-max-redirect-count=0 idle-timeout=10s duration=15s output=user as-value] :local pcsrtReply [:deserialize from=json value=($pcsrtFetch->"data") options=json.no-string-conversion] :if ([:typeof ($pcsrtReply->"active")] != "bool") do={ :error "Invalid response" } :set pcsrtSupportLastGood [/system resource get uptime] :if (([:len $pcsrtGuard] = 1) && ([:len $pcsrtUndo] = 1)) do={ :local pcsrtCommit ($pcsrtReply->"commit_id") :if (([:len $pcsrtCommit] > 0) && ($pcsrtCommit = [/system script get $pcsrtUndo comment])) do={ :if ([/system scheduler get $pcsrtGuard comment] = "armed") do={ /system scheduler set $pcsrtGuard comment="committed" /system script set $pcsrtUndo source=":return \"\"" } } } :set pcsrtSupportId "" :set pcsrtSupportOutput "" :set pcsrtSupportStatus "succeeded" :local pcsrtActive (($pcsrtReply->"active") = true) # Change scheduler only on transitions, avoiding configuration writes every poll. :local pcsrtNext 30s :if ($pcsrtActive) do={ :set pcsrtNext 2s } :if ([:len $pcsrtSchedule] = 1) do={ :if ([/system scheduler get $pcsrtSchedule interval] != $pcsrtNext) do={ /system scheduler set $pcsrtSchedule interval=$pcsrtNext } } :if (!$pcsrtActive) do={ :return "" } :local pcsrtQuery ($pcsrtReply->"query") :if ([:typeof $pcsrtQuery] != "array") do={ :return "" } :local pcsrtKind ($pcsrtQuery->"kind") :if ($pcsrtKind = "script") do={ # Offers are repeatable; execution requires a NEW one-time claim response. :local pcsrtClaim [/tool fetch url=($pcsrtBase."/api/device/support/".($pcsrtQuery->"id")."/claim") http-method=post http-header-field=("Authorization: Bearer ".$pcsrtToken.",Content-Type: application/json") http-data="{}" check-certificate=yes http-max-redirect-count=0 idle-timeout=10s duration=15s output=user as-value] :local pcsrtPermit [:deserialize from=json value=($pcsrtClaim->"data") options=json.no-string-conversion] :if (($pcsrtPermit->"execute") != true) do={ :return "" } :set pcsrtSupportId ($pcsrtQuery->"id") :set pcsrtSupportStatus "failed" :set pcsrtSupportOutput "Execution failed or could not be prepared; inspect router state before retrying." :do { :local pcsrtRun [:parse ($pcsrtPermit->"command")] :local pcsrtRollback ($pcsrtPermit->"rollback") :if ([:len $pcsrtRollback] > 0) do={ :if (([:len $pcsrtGuard] != 1) || ([:len $pcsrtUndo] != 1)) do={ :error "Recovery not installed" } :if ([/system scheduler get $pcsrtGuard comment] = "armed") do={ :error "Recovery already armed" } :local pcsrtValidate [:parse $pcsrtRollback] /system script set $pcsrtUndo source=$pcsrtRollback comment=$pcsrtSupportId :set pcsrtSupportLastGood [/system resource get uptime] /system scheduler set $pcsrtGuard disabled=no comment="armed" } :local pcsrtValue [$pcsrtRun] :set pcsrtSupportOutput "Script completed without a returned value. Use :return for browser output." :if ([:typeof $pcsrtValue] != "nil") do={ :set pcsrtSupportOutput [:serialize to=json value=$pcsrtValue options=json.no-string-conversion] } :if ([:len $pcsrtSupportOutput] > 2000) do={ :set pcsrtSupportOutput ([:pick $pcsrtSupportOutput 0 2000]." [output truncated]") } :set pcsrtSupportStatus "succeeded" } on-error={ # Never echo script source, tokens or exception contents into logs. :set pcsrtSupportStatus "failed" } :return "" } :set pcsrtSupportId ($pcsrtQuery->"id") :do { :local pcsrtResult [:toarray ""] :local pcsrtKnown false :if ($pcsrtKind = "resources") do={ :set pcsrtKnown true :foreach pcsrtField in={"version";"uptime";"cpu-load";"free-memory";"total-memory";"board-name";"architecture-name"} do={ :set ($pcsrtResult->$pcsrtField) [:tostr [/system resource get $pcsrtField]] } } :if ($pcsrtKind = "identity") do={ :set pcsrtKnown true :set ($pcsrtResult->"name") [/system identity get name] } :if ($pcsrtKind = "lte") do={ :set pcsrtKnown true :local pcsrtLteIds [/interface lte find where name=$pcsrtLte] :if ([:len $pcsrtLteIds] != 1) do={ :error "LTE interface missing" } :local pcsrtRadio [/interface lte monitor [:pick $pcsrtLteIds 0] once as-value] :foreach pcsrtField in={"status";"current-operator";"rssi";"rsrp";"rsrq";"sinr";"data-class";"session-uptime"} do={ :set ($pcsrtResult->$pcsrtField) [:tostr ($pcsrtRadio->$pcsrtField)] } } :if ($pcsrtKind = "interfaces") do={ :set pcsrtKnown true :local pcsrtCount 0 :foreach pcsrtItem in=[/interface find] do={ :if ($pcsrtCount < 20) do={ :local pcsrtRow [:toarray ""] :foreach pcsrtField in={"name";"type";"running";"disabled";"rx-byte";"tx-byte"} do={ :set ($pcsrtRow->$pcsrtField) [:tostr [/interface get $pcsrtItem $pcsrtField]] } :set ($pcsrtResult->("item".$pcsrtCount)) $pcsrtRow :set pcsrtCount ($pcsrtCount + 1) } } } :if ($pcsrtKind = "addresses") do={ :set pcsrtKnown true :local pcsrtCount 0 :foreach pcsrtItem in=[/ip address find] do={ :if ($pcsrtCount < 20) do={ :local pcsrtRow [:toarray ""] :foreach pcsrtField in={"address";"interface";"disabled"} do={ :set ($pcsrtRow->$pcsrtField) [:tostr [/ip address get $pcsrtItem $pcsrtField]] } :set ($pcsrtResult->("item".$pcsrtCount)) $pcsrtRow :set pcsrtCount ($pcsrtCount + 1) } } } :if ($pcsrtKind = "routes") do={ :set pcsrtKnown true :local pcsrtCount 0 :foreach pcsrtItem in=[/ip route find] do={ :if ($pcsrtCount < 20) do={ :local pcsrtRow [:toarray ""] :foreach pcsrtField in={"dst-address";"gateway";"active"} do={ :set ($pcsrtRow->$pcsrtField) [:tostr [/ip route get $pcsrtItem $pcsrtField]] } :set ($pcsrtResult->("item".$pcsrtCount)) $pcsrtRow :set pcsrtCount ($pcsrtCount + 1) } } } :if (!$pcsrtKnown) do={ :error "Unknown diagnostic" } :set pcsrtSupportOutput [:serialize to=json value=$pcsrtResult options=json.no-string-conversion] :if ([:len $pcsrtSupportOutput] > 2000) do={ :set pcsrtSupportOutput ([:pick $pcsrtSupportOutput 0 2000]." [output truncated]") } :set pcsrtSupportStatus "succeeded" } on-error={ :set pcsrtSupportStatus "failed" :set pcsrtSupportOutput "Diagnostic failed; check interface name and script permissions locally." } # Result is submitted on the next short poll. Only diagnostics can execute again. } on-error={ :if ([:len $pcsrtSchedule] = 1) do={ :if ([/system scheduler get $pcsrtSchedule interval] != 30s) do={ /system scheduler set $pcsrtSchedule interval=30s } } :log warning "PCSRT support: check connectivity, certificate and enrollment" } } /system scheduler add name=pcsrt-support interval=30s start-time=startup on-event=pcsrt-support policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon disabled=yes # The watchdog has no token and makes no network requests. The poll updates an # in-memory heartbeat only after a verified, authenticated HTTPS response. /system script add name=pcsrt-undo policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon dont-require-permissions=no comment="" source={ :return "" } /system script add name=pcsrt-recovery policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon dont-require-permissions=no source={ :if ([/system script job print count-only as-value where script=[:jobname]] > 1) do={ :return "" } :local pcsrtGuard [/system scheduler find where name="pcsrt-recovery"] :if ([/system scheduler get $pcsrtGuard comment] != "armed") do={ :return "" } :global pcsrtSupportLastGood :local pcsrtUptime [/system resource get uptime] # After reboot globals are gone: allow up to five minutes from boot to reconnect. :if ([:typeof $pcsrtSupportLastGood] != "time") do={ :set pcsrtSupportLastGood 0s } :if (($pcsrtUptime - $pcsrtSupportLastGood) < 5m) do={ :return "" } # Mark attempted BEFORE running: never repeatedly execute an uncertain undo. /system scheduler set $pcsrtGuard comment="rollback_failed" :do { /system script run pcsrt-undo /system scheduler set $pcsrtGuard comment="rolled_back" /system script set [find where name="pcsrt-undo"] source=":return \"\"" :log warning "PCSRT recovery: undo script completed; verify connectivity" } on-error={ :log warning "PCSRT recovery: undo failed; local inspection required" } } /system scheduler add name=pcsrt-recovery interval=10s start-time=startup on-event=pcsrt-recovery policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon disabled=yes comment=""