diff --git a/AGENTS.md b/AGENTS.md index f89b90a..efa19ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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` diff --git a/apps/api/scripts/censorcheck/launcher.rsc b/apps/api/scripts/censorcheck/launcher.rsc index 83a1ae4..f762784 100644 --- a/apps/api/scripts/censorcheck/launcher.rsc +++ b/apps/api/scripts/censorcheck/launcher.rsc @@ -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 . ")") diff --git a/apps/api/scripts/ipregion/launcher.rsc b/apps/api/scripts/ipregion/launcher.rsc index 5f1910d..febf186 100644 --- a/apps/api/scripts/ipregion/launcher.rsc +++ b/apps/api/scripts/ipregion/launcher.rsc @@ -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 . ")") diff --git a/apps/api/src/routes/launcher.test.ts b/apps/api/src/routes/launcher.test.ts index 71d97d5..07df917 100644 --- a/apps/api/src/routes/launcher.test.ts +++ b/apps/api/src/routes/launcher.test.ts @@ -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') diff --git a/apps/web/src/components/censorcheck/types.ts b/apps/web/src/components/censorcheck/types.ts index f310dd1..69562c7 100644 --- a/apps/web/src/components/censorcheck/types.ts +++ b/apps/web/src/components/censorcheck/types.ts @@ -59,9 +59,9 @@ export const CENSORCHECK_STATUS_LABELS: Record = { 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` diff --git a/apps/web/src/components/ipregion/types.ts b/apps/web/src/components/ipregion/types.ts index 379daf3..360aaa5 100644 --- a/apps/web/src/components/ipregion/types.ts +++ b/apps/web/src/components/ipregion/types.ts @@ -58,9 +58,9 @@ export const IPREGION_STATUS_LABELS: Record = { 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`