fix(launcher): показывать ошибку ingest MikroTik и повторять без src-address
Docker / build (push) Failing after 23s

RouterOS скрывает HTTP 4xx как fetch error. ros-5 печатает код и тело ответа, затем повторяет POST без src-address.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Denozordec
2026-08-26 14:09:19 +07:00
co-authored by Cursor
parent ecd053b832
commit d7bcd0d4ec
6 changed files with 74 additions and 26 deletions
+2 -2
View File
@@ -94,7 +94,7 @@ vps-tracker/
Ручная проверка с VPS: `curl -fsSL https://vt.shnt.top/cc | bash` (тот же контейнер, Traefik dual Host).
Раз в сутки: `curl -fsSL https://vt.shnt.top/cc | bash -s -- --daily` (cron, свежий HMAC-токен на каждый запуск).
MikroTik 7.22+: `:global vt-srcIp "1.2.3.4"; /tool fetch url="https://vt.shnt.top/cc.rsc" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc` (ежедневно: `?daily=1`; все пробы через `src-address` этого IP).
MikroTik 7.22+: `:global "vt-srcIp" "1.2.3.4"; /tool fetch url="https://vt.shnt.top/cc.rsc" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc` (ежедневно: `?daily=1`; все пробы через `src-address` этого IP).
- **Vendor:** `apps/api/scripts/censorcheck/censorcheck.sh` (pin SHA `12c5839`, MIT)
- **Launcher:** `GET /cc` минтит HMAC ingest-токен (TTL 20 мин); по `/etc/os-release` ставит `jq`/`dig`/`column` без prompt; прогресс-бар в stderr; `GET /cc/vendor` — скрипт (LF); `--daily` / `--remove-daily`
@@ -107,7 +107,7 @@ MikroTik 7.22+: `:global vt-srcIp "1.2.3.4"; /tool fetch url="https://vt.shnt.to
Ручная проверка с VPS: `curl -fsSL https://vt.shnt.top/ic | bash`.
Раз в сутки: `curl -fsSL https://vt.shnt.top/ic | bash -s -- --daily`.
MikroTik 7.22+: `:global vt-srcIp "1.2.3.4"; /tool fetch url="https://vt.shnt.top/ic.rsc" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc` (`?daily=1`; пробы через тот же `vt-srcIp`).
MikroTik 7.22+: `:global "vt-srcIp" "1.2.3.4"; /tool fetch url="https://vt.shnt.top/ic.rsc" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc` (`?daily=1`; пробы через тот же `vt-srcIp`).
- **Vendor:** `apps/api/scripts/ipregion/ipregion.sh` (pin SHA `7d1c25c`, MIT, [vernette/ipregion](https://github.com/vernette/ipregion))
- **Launcher:** `GET /ic` минтит HMAC ingest-токен (тот же `CENSORCHECK_INGEST_SECRET`); `GET /ic/vendor` — pinned скрипт (LF); `--daily` / `--remove-daily`
+30 -9
View File
@@ -1,12 +1,12 @@
# VPS Tracker — blocking launcher for RouterOS 7.22+
# First run: :global vt-srcIp "1.2.3.4"; /tool fetch url="__VT_API_URL__/cc.rsc" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc
# First run: :global "vt-srcIp" "1.2.3.4"; /tool fetch url="__VT_API_URL__/cc.rsc" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc
# HTTPS GET only (no DPI/SNI). Ingest: POST /api/integrations/censorcheck/runs
:local vtApi "__VT_API_URL__"
:local vtToken "__VT_INGEST_TOKEN__"
:local vtDaily "__VT_DAILY__"
:local vtRemove "__VT_REMOVE_DAILY__"
:local launcherVer "ros-4"
:local launcherVer "ros-5"
:local schedName "vt-cc"
:local dstFile "vt-cc.rsc"
@@ -37,11 +37,12 @@
:global "vt-srcIp"
:if (([:typeof $"vt-srcIp"] != "str") or ([:len $"vt-srcIp"] = 0)) do={
:put "Задайте исходный IP и повторите импорт:"
:put ":global vt-srcIp \"1.2.3.4\""
:put ":global \"vt-srcIp\" \"1.2.3.4\""
:error "vt-srcIp не задан"
}
:local srcIp $"vt-srcIp"
:put ("vt-srcIp: " . $srcIp)
:local ingestOk false
:local r
:local publicIp ""
@@ -156,13 +157,33 @@
results=$results\
}
:local json [:serialize to=json value=$payload]
:local hdrs ("Content-Type: application/json,Authorization: Bearer " . $vtToken)
:put "Отправляю ingest..."
:do {
:local hdrs ("Content-Type:application/json,Authorization:Bearer " . $vtToken)
:put ("Отправляю ingest (" . [:len $json] . " B)...")
:set ingestOk false
:onerror err,attr in={
:set r [/tool fetch url=($vtApi . "/api/integrations/censorcheck/runs") src-address=$srcIp http-method=post http-header-field=$hdrs http-data=$json output=user as-value]
:put ($r->"data")
} on-error={
:put "API недоступен (fetch error)"
:set ingestOk true
} do={
:put ("ingest: " . $err)
:if (([:typeof $attr] = "array") and ([:typeof ($attr->"code")] != "nil")) do={
:put ("code: " . ($attr->"code"))
}
:if (([:typeof $attr] = "array") and ([:typeof ($attr->"data")] = "str") and ([:len ($attr->"data")] > 0)) do={
:put ($attr->"data")
}
}
:if ($ingestOk = false) do={
:put "повтор ingest без src-address..."
:onerror err2,attr2 in={
:set r [/tool fetch url=($vtApi . "/api/integrations/censorcheck/runs") http-method=post http-header-field=$hdrs http-data=$json output=user as-value]
:put ($r->"data")
} do={
:put ("ingest retry: " . $err2)
:if (([:typeof $attr2] = "array") and ([:typeof ($attr2->"data")] = "str") and ([:len ($attr2->"data")] > 0)) do={
:put ($attr2->"data")
}
}
}
:if ($vtDaily = "yes") do={
@@ -175,7 +196,7 @@
:if ($hour < 10) do={ :set hh ("0" . $hour) }
:if ($minute < 10) do={ :set mm ("0" . $minute) }
:local startTime ($hh . ":" . $mm . ":00")
:local ev (":global vt-srcIp \"" . $srcIp . "\"; /tool fetch url=" . $vtApi . "/cc.rsc dst-path=" . $dstFile . "; /import file-name=" . $dstFile)
:local ev (":global \"vt-srcIp\" \"" . $srcIp . "\"; /tool fetch url=" . $vtApi . "/cc.rsc dst-path=" . $dstFile . "; /import file-name=" . $dstFile)
:do { /system scheduler remove [find name=$schedName] } on-error={}
/system scheduler add name=$schedName interval=1d start-time=$startTime on-event=$ev policy=read,write,test,policy comment="vps-tracker vt-cc"
:put ("Ежедневная проверка: каждый день в " . $startTime . " (" . $schedName . ")")
+30 -9
View File
@@ -1,12 +1,12 @@
# VPS Tracker — GeoIP launcher for RouterOS 7.22+
# First run: :global vt-srcIp "1.2.3.4"; /tool fetch url="__VT_API_URL__/ic.rsc" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc
# First run: :global "vt-srcIp" "1.2.3.4"; /tool fetch url="__VT_API_URL__/ic.rsc" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc
# Primary GeoIP JSON + Cloudflare CDN. Ingest: POST /api/integrations/ipregion/runs
:local vtApi "__VT_API_URL__"
:local vtToken "__VT_INGEST_TOKEN__"
:local vtDaily "__VT_DAILY__"
:local vtRemove "__VT_REMOVE_DAILY__"
:local launcherVer "ros-4"
:local launcherVer "ros-5"
:local schedName "vt-ic"
:local dstFile "vt-ic.rsc"
@@ -37,11 +37,12 @@
:global "vt-srcIp"
:if (([:typeof $"vt-srcIp"] != "str") or ([:len $"vt-srcIp"] = 0)) do={
:put "Задайте исходный IP и повторите импорт:"
:put ":global vt-srcIp \"1.2.3.4\""
:put ":global \"vt-srcIp\" \"1.2.3.4\""
:error "vt-srcIp не задан"
}
:local srcIp $"vt-srcIp"
:put ("vt-srcIp: " . $srcIp)
:local ingestOk false
:local r
:local j
@@ -273,13 +274,33 @@
results=$results\
}
:local json [:serialize to=json value=$payload]
:local hdrs ("Content-Type: application/json,Authorization: Bearer " . $vtToken)
:put "Отправляю ingest..."
:do {
:local hdrs ("Content-Type:application/json,Authorization:Bearer " . $vtToken)
:put ("Отправляю ingest (" . [:len $json] . " B)...")
:set ingestOk false
:onerror err,attr in={
:set r [/tool fetch url=($vtApi . "/api/integrations/ipregion/runs") src-address=$srcIp http-method=post http-header-field=$hdrs http-data=$json output=user as-value]
:put ($r->"data")
} on-error={
:put "API недоступен (fetch error)"
:set ingestOk true
} do={
:put ("ingest: " . $err)
:if (([:typeof $attr] = "array") and ([:typeof ($attr->"code")] != "nil")) do={
:put ("code: " . ($attr->"code"))
}
:if (([:typeof $attr] = "array") and ([:typeof ($attr->"data")] = "str") and ([:len ($attr->"data")] > 0)) do={
:put ($attr->"data")
}
}
:if ($ingestOk = false) do={
:put "повтор ingest без src-address..."
:onerror err2,attr2 in={
:set r [/tool fetch url=($vtApi . "/api/integrations/ipregion/runs") http-method=post http-header-field=$hdrs http-data=$json output=user as-value]
:put ($r->"data")
} do={
:put ("ingest retry: " . $err2)
:if (([:typeof $attr2] = "array") and ([:typeof ($attr2->"data")] = "str") and ([:len ($attr2->"data")] > 0)) do={
:put ($attr2->"data")
}
}
}
:if ($vtDaily = "yes") do={
@@ -292,7 +313,7 @@
:if ($hour < 10) do={ :set hh ("0" . $hour) }
:if ($minute < 10) do={ :set mm ("0" . $minute) }
:local startTime ($hh . ":" . $mm . ":00")
:local ev (":global vt-srcIp \"" . $srcIp . "\"; /tool fetch url=" . $vtApi . "/ic.rsc dst-path=" . $dstFile . "; /import file-name=" . $dstFile)
:local ev (":global \"vt-srcIp\" \"" . $srcIp . "\"; /tool fetch url=" . $vtApi . "/ic.rsc dst-path=" . $dstFile . "; /import file-name=" . $dstFile)
:do { /system scheduler remove [find name=$schedName] } on-error={}
/system scheduler add name=$schedName interval=1d start-time=$startTime on-event=$ev policy=read,write,test,policy comment="vps-tracker vt-ic"
:put ("Ежедневная проверка: каждый день в " . $startTime . " (" . $schedName . ")")
+8 -2
View File
@@ -128,13 +128,16 @@ describe('GET /cc.rsc RouterOS launcher', () => {
expect(res.body).toContain('https://vt.shnt.top')
expect(res.body).toContain('/tool fetch')
expect(res.body).toContain('/api/integrations/censorcheck/runs')
expect(res.body).toContain('ros-4')
expect(res.body).toContain('ros-5')
expect(res.body).toContain(':local vtDaily "no"')
expect(res.body).toContain(':local vtRemove "no"')
expect(res.body).toContain('/system scheduler')
expect(res.body).toContain('youtube.com')
expect(res.body).toContain('vt-srcIp')
expect(res.body).toContain(':global "vt-srcIp"')
expect(res.body).toContain('src-address=$srcIp')
expect(res.body).toContain(':onerror')
expect(res.body).toContain('повтор ingest без src-address')
expect(res.body).not.toContain('mark-routing')
expect(res.body).not.toContain('vtIface')
expect(res.body).not.toContain('\r')
@@ -185,12 +188,15 @@ describe('GET /ic.rsc RouterOS launcher', () => {
expect(res.body).toContain('https://vt.shnt.top')
expect(res.body).toContain('/tool fetch')
expect(res.body).toContain('/api/integrations/ipregion/runs')
expect(res.body).toContain('ros-4')
expect(res.body).toContain('ros-5')
expect(res.body).toContain(':local vtDaily "no"')
expect(res.body).toContain('ipinfo.io')
expect(res.body).toContain('cloudflare cdn')
expect(res.body).toContain('vt-srcIp')
expect(res.body).toContain(':global "vt-srcIp"')
expect(res.body).toContain('src-address=$srcIp')
expect(res.body).toContain(':onerror')
expect(res.body).toContain('повтор ingest без src-address')
expect(res.body).not.toContain('mark-routing')
expect(res.body).not.toContain('vtIface')
expect(res.body).not.toContain('\r')
+2 -2
View File
@@ -59,9 +59,9 @@ export const CENSORCHECK_STATUS_LABELS: Record<string, string> = {
export const LAUNCHER_CMD = 'curl -fsSL https://vt.shnt.top/cc | bash'
export const LAUNCHER_CMD_DAILY = 'curl -fsSL https://vt.shnt.top/cc | bash -s -- --daily'
export const LAUNCHER_CMD_ROS =
':global vt-srcIp "1.2.3.4"; /tool fetch url="https://vt.shnt.top/cc.rsc" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc'
':global "vt-srcIp" "1.2.3.4"; /tool fetch url="https://vt.shnt.top/cc.rsc" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc'
export const LAUNCHER_CMD_ROS_DAILY =
':global vt-srcIp "1.2.3.4"; /tool fetch url="https://vt.shnt.top/cc.rsc?daily=1" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc'
':global "vt-srcIp" "1.2.3.4"; /tool fetch url="https://vt.shnt.top/cc.rsc?daily=1" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc'
export function formatVpsResources(vcpu: number, ramGb: number, diskGb: number): string {
return `${vcpu} vCPU / ${ramGb} GB / ${diskGb} GB`
+2 -2
View File
@@ -58,9 +58,9 @@ export const IPREGION_STATUS_LABELS: Record<string, string> = {
export const LAUNCHER_CMD = 'curl -fsSL https://vt.shnt.top/ic | bash'
export const LAUNCHER_CMD_DAILY = 'curl -fsSL https://vt.shnt.top/ic | bash -s -- --daily'
export const LAUNCHER_CMD_ROS =
':global vt-srcIp "1.2.3.4"; /tool fetch url="https://vt.shnt.top/ic.rsc" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc'
':global "vt-srcIp" "1.2.3.4"; /tool fetch url="https://vt.shnt.top/ic.rsc" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc'
export const LAUNCHER_CMD_ROS_DAILY =
':global vt-srcIp "1.2.3.4"; /tool fetch url="https://vt.shnt.top/ic.rsc?daily=1" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc'
':global "vt-srcIp" "1.2.3.4"; /tool fetch url="https://vt.shnt.top/ic.rsc?daily=1" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc'
export function formatVpsResources(vcpu: number, ramGb: number, diskGb: number): string {
return `${vcpu} vCPU / ${ramGb} GB / ${diskGb} GB`