# RouterOS 7.23.7 target. Import ONLY after reviewing and enrolling a lab device. # Replace token privately; the script source is a credential. Do not export/share it. /system script add name=pcsrt-poll policy=read,write,test,reboot dont-require-permissions=no source={ :local pcsrtBase "https://mikrotik.pcsrt.com" :local pcsrtToken "REPLACE_WITH_DEVICE_TOKEN" :local pcsrtLte "lte1" # Must match the server enrollment. Empty means VPN enable is forbidden. :local pcsrtVpn "" :if ([/system script job print count-only as-value where script=[:jobname]] > 1) do={ :return "" } :global pcsrtPendingId :global pcsrtPendingResult :local pcsrtPost do={ :local pcsrtReply [/tool fetch url=($pBase.$pPath) http-method=post http-header-field=("Authorization: Bearer ".$pToken.",Content-Type: application/json") http-data=[:serialize to=json value=$pBody options=json.no-string-conversion] check-certificate=yes http-max-redirect-count=0 idle-timeout=10s duration=20s output=user as-value] :return [:deserialize from=json value=($pcsrtReply->"data") options=json.no-string-conversion] } :do { # Result retries never repeat the command. If RAM is lost the server shows uncertainty. :if ([:len $pcsrtPendingId] > 0) do={ :do { :local pcsrtSent [$pcsrtPost pBase=$pcsrtBase pToken=$pcsrtToken pPath=("/api/device/jobs/".$pcsrtPendingId."/result") pBody=$pcsrtPendingResult] :set pcsrtPendingId "" :set pcsrtPendingResult } on-error={ :log warning "PCSRT: result delivery pending" } } :local pcsrtMetrics [:toarray ""] :set ($pcsrtMetrics->"uptime_seconds") ([:tonsec [/system resource get uptime]] / 1000000000) :set ($pcsrtMetrics->"cpu_percent") [/system resource get cpu-load] :set ($pcsrtMetrics->"memory_free") [/system resource get free-memory] :set ($pcsrtMetrics->"memory_total") [/system resource get total-memory] :set ($pcsrtMetrics->"version") [/system resource get version] :local pcsrtSchedule [/system scheduler find where name="pcsrt-poll"] :local pcsrtInterval 60 :if ([:len $pcsrtSchedule] = 1) do={ :set pcsrtInterval ([:tonsec [/system scheduler get $pcsrtSchedule interval]] / 1000000000) } :set ($pcsrtMetrics->"interval") $pcsrtInterval :set ($pcsrtMetrics->"rx_bytes") 0 :set ($pcsrtMetrics->"tx_bytes") 0 :set ($pcsrtMetrics->"operator") "" :set ($pcsrtMetrics->"connection") "unavailable" :local pcsrtLteId [/interface lte find where name=$pcsrtLte] :if ([:len $pcsrtLteId] = 1) do={ :set ($pcsrtMetrics->"rx_bytes") [/interface get $pcsrtLteId rx-byte] :set ($pcsrtMetrics->"tx_bytes") [/interface get $pcsrtLteId tx-byte] :do { :local pcsrtRadio [/interface lte monitor [:pick $pcsrtLteId 0] once as-value] :set ($pcsrtMetrics->"operator") [:tostr ($pcsrtRadio->"current-operator")] :set ($pcsrtMetrics->"connection") [:tostr ($pcsrtRadio->"status")] :foreach pcsrtField in={"rssi";"rsrp";"rsrq";"sinr"} do={ :set ($pcsrtMetrics->$pcsrtField) [:tostr ($pcsrtRadio->$pcsrtField)] } } on-error={ :set ($pcsrtMetrics->"connection") "monitor-error" } } :local pcsrtResponse [$pcsrtPost pBase=$pcsrtBase pToken=$pcsrtToken pPath="/api/device/checkin" pBody=$pcsrtMetrics] :if ([:len $pcsrtPendingId] > 0) do={ :return "" } :local pcsrtJob ($pcsrtResponse->"job") :if ([:typeof $pcsrtJob] != "array") do={ :return "" } :local pcsrtJobId ($pcsrtJob->"id") :local pcsrtKind ($pcsrtJob->"kind") :local pcsrtArgs ($pcsrtJob->"payload") :local pcsrtNow ([:tonsec [:timestamp]] / 1000000000) :if ($pcsrtNow >= ($pcsrtJob->"expires")) do={ :return "" } :local pcsrtPermit [$pcsrtPost pBase=$pcsrtBase pToken=$pcsrtToken pPath=("/api/device/jobs/".$pcsrtJobId."/claim") pBody=[:toarray ""]] :if (($pcsrtPermit->"execute") != true) do={ :return "" } # Only this unique, successfully received permit allows execution. No automatic claim retry. :set pcsrtPendingId $pcsrtJobId :set pcsrtPendingResult {"status"="failed";"message"="Command failed; inspect device locally"} :local pcsrtDoReboot false :do { # Do not execute a permit held beyond expiration or after a stalled fetch. :if (([:tonsec [:timestamp]] / 1000000000) >= ($pcsrtJob->"expires")) do={ :error "expired" } :local pcsrtKnown false :if ($pcsrtKind = "diagnostics") do={ :set pcsrtKnown true :set ($pcsrtPendingResult->"diagnostics") $pcsrtMetrics } :if ($pcsrtKind = "set_interval") do={ :set pcsrtKnown true :local pcsrtSeconds ($pcsrtArgs->"seconds") :if (([:typeof $pcsrtSeconds] != "num") || ($pcsrtSeconds < 30) || ($pcsrtSeconds > 3600) || ([:len $pcsrtSchedule] != 1)) do={ :error "interval" } /system scheduler set $pcsrtSchedule interval=[:totime ($pcsrtSeconds."s")] } :if ($pcsrtKind = "configure") do={ :set pcsrtKnown true :local pcsrtIdentity ($pcsrtArgs->"identity") :local pcsrtComment ($pcsrtArgs->"lte_comment") # Values are passed as data, never parsed as RouterOS code. :if ([:typeof $pcsrtComment] != "nil") do={ :if (([:len $pcsrtComment] > 128) || ([:len $pcsrtLteId] != 1)) do={ :error "comment" } } :if ([:typeof $pcsrtIdentity] != "nil") do={ :if (([:len $pcsrtIdentity] < 1) || ([:len $pcsrtIdentity] > 64)) do={ :error "identity" } /system identity set name=$pcsrtIdentity } :if ([:typeof $pcsrtComment] != "nil") do={ /interface set $pcsrtLteId comment=$pcsrtComment } } :if ($pcsrtKind = "enable_support_vpn") do={ :set pcsrtKnown true :if (([:len $pcsrtVpn] = 0) || (($pcsrtArgs->"interface") != $pcsrtVpn)) do={ :error "VPN not approved" } :local pcsrtVpnId [/interface find where name=$pcsrtVpn] :if ([:len $pcsrtVpnId] != 1) do={ :error "VPN missing" } :local pcsrtVpnType [/interface get $pcsrtVpnId type] :if (($pcsrtVpnType != "wg") && ($pcsrtVpnType != "wireguard") && ($pcsrtVpnType != "l2tp-out") && ($pcsrtVpnType != "sstp-out") && ($pcsrtVpnType != "ovpn-out")) do={ :error "VPN type not allowed" } /interface enable $pcsrtVpnId } :if ($pcsrtKind = "reboot") do={ :set pcsrtKnown true; :set pcsrtDoReboot true } :if (!$pcsrtKnown) do={ :error "unsupported command" } :set ($pcsrtPendingResult->"status") "succeeded" :set ($pcsrtPendingResult->"message") "Command applied" :if ($pcsrtDoReboot) do={ :set ($pcsrtPendingResult->"status") "reboot_requested" :set ($pcsrtPendingResult->"message") "Reboot requested; completion must be checked from subsequent telemetry" } } on-error={ :set pcsrtDoReboot false } :do { :local pcsrtSent [$pcsrtPost pBase=$pcsrtBase pToken=$pcsrtToken pPath=("/api/device/jobs/".$pcsrtPendingId."/result") pBody=$pcsrtPendingResult] :set pcsrtPendingId "" :set pcsrtPendingResult } on-error={ :log warning "PCSRT: result delivery pending" } :if ($pcsrtDoReboot) do={ /system reboot } } on-error={ # Never log fetch errors: they may include headers or script source. :log warning "PCSRT: poll failed; check connectivity, certificate, clock and enrollment" } } # Review/import poll.rsc first. This scheduler runs every 60 seconds after boot. # start-time=startup with a nonzero interval does not execute at boot itself. /system scheduler add name=pcsrt-poll interval=1m start-time=startup on-event=pcsrt-poll policy=read,write,test,reboot disabled=yes # Enable ONLY when the device rollout is approved: # /system scheduler enable [find where name="pcsrt-poll"] # /system script run pcsrt-poll # 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=""