From 5466f7666c84a1ea8bae7f091efbbe8fe40cee04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Fl=C3=A1vio?= <72469240+Rodrigoue9@users.noreply.github.com> Date: Mon, 7 Sep 2026 11:04:04 -0300 Subject: [PATCH] fix(api): support configurable CHECKER_URL and descriptive error handling for self-hosted instances (#2573) (#2631) * fix(api): support configurable CHECKER_URL for self-hosted checker * ci: apply automated fixes * fix(api): provide fallback and test env definition for CHECKER_URL --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- packages/api/.env.test | 1 + packages/api/src/env.ts | 2 + packages/api/src/router/checker.ts | 162 +++++++++++++---------------- 3 files changed, 78 insertions(+), 87 deletions(-) diff --git a/packages/api/.env.test b/packages/api/.env.test index 75fa55964..394b31600 100644 --- a/packages/api/.env.test +++ b/packages/api/.env.test @@ -10,3 +10,4 @@ TEAM_ID_VERCEL="test" VERCEL_AUTH_BEARER_TOKEN="test" TINY_BIRD_API_KEY=test RESEND_API_KEY=test +CHECKER_URL="https://openstatus-checker.fly.dev" diff --git a/packages/api/src/env.ts b/packages/api/src/env.ts index 8cc9b9687..9082ec7b4 100644 --- a/packages/api/src/env.ts +++ b/packages/api/src/env.ts @@ -12,6 +12,7 @@ export const env = createEnv({ VERCEL_AUTH_BEARER_TOKEN: isSelfHost ? z.string().optional() : z.string(), TINY_BIRD_API_KEY: isSelfHost ? z.string().optional() : z.string(), TINYBIRD_URL: z.string().default("https://api.tinybird.co"), + CHECKER_URL: z.url().default("https://openstatus-checker.fly.dev"), TINYBIRD_NOOP: z.stringbool().catch(false), RESEND_API_KEY: z.string(), CRON_SECRET: z.string(), @@ -29,6 +30,7 @@ export const env = createEnv({ VERCEL_AUTH_BEARER_TOKEN: process.env.VERCEL_AUTH_BEARER_TOKEN, TINY_BIRD_API_KEY: process.env.TINY_BIRD_API_KEY, TINYBIRD_URL: process.env.TINYBIRD_URL, + CHECKER_URL: process.env.CHECKER_URL, TINYBIRD_NOOP: process.env.TINYBIRD_NOOP, RESEND_API_KEY: process.env.RESEND_API_KEY, CRON_SECRET: process.env.CRON_SECRET, diff --git a/packages/api/src/router/checker.ts b/packages/api/src/router/checker.ts index 314755f7e..0abfa1c41 100644 --- a/packages/api/src/router/checker.ts +++ b/packages/api/src/router/checker.ts @@ -27,6 +27,9 @@ import { env } from "../env"; import { createTRPCRouter, protectedProcedure } from "../trpc"; const ABORT_TIMEOUT = 10000; +const CHECKER_BASE_URL = ( + env.CHECKER_URL || "https://openstatus-checker.fly.dev" +).replace(/\/+$/, ""); // PingICMP treats its timeout as the deadline for the whole check, so omitting // it means a deadline of "now": the send loop breaks before the first packet @@ -282,30 +285,27 @@ export async function testHttp(input: z.infer) { } try { - const res = await fetch( - `https://openstatus-checker.fly.dev/ping/${input.region}`, - { - method: "POST", - headers: { - Authorization: `Basic ${env.CRON_SECRET}`, - "Content-Type": "application/json", - "fly-prefer-region": input.region, - }, - body: JSON.stringify({ - url: input.url, - method: input.method, - headers: input.headers?.reduce( - (acc, { key, value }) => { - if (!key) return acc; - return { ...acc, [key]: value }; - }, - {} as Record, - ), - body: input.body, - }), - signal: AbortSignal.timeout(ABORT_TIMEOUT), + const res = await fetch(`${CHECKER_BASE_URL}/ping/${input.region}`, { + method: "POST", + headers: { + Authorization: `Basic ${env.CRON_SECRET}`, + "Content-Type": "application/json", + "fly-prefer-region": input.region, }, - ); + body: JSON.stringify({ + url: input.url, + method: input.method, + headers: input.headers?.reduce( + (acc, { key, value }) => { + if (!key) return acc; + return { ...acc, [key]: value }; + }, + {} as Record, + ), + body: input.body, + }), + signal: AbortSignal.timeout(ABORT_TIMEOUT), + }); const json = await res.json(); const result = httpOutput.safeParse(json); @@ -370,19 +370,16 @@ export async function testHttp(input: z.infer) { export async function testTcp(input: z.infer) { try { - const res = await fetch( - `https://openstatus-checker.fly.dev/tcp/${input.region}`, - { - method: "POST", - headers: { - Authorization: `Basic ${env.CRON_SECRET}`, - "Content-Type": "application/json", - "fly-prefer-region": input.region, - }, - body: JSON.stringify({ uri: input.url }), - signal: AbortSignal.timeout(ABORT_TIMEOUT), + const res = await fetch(`${CHECKER_BASE_URL}/tcp/${input.region}`, { + method: "POST", + headers: { + Authorization: `Basic ${env.CRON_SECRET}`, + "Content-Type": "application/json", + "fly-prefer-region": input.region, }, - ); + body: JSON.stringify({ uri: input.url }), + signal: AbortSignal.timeout(ABORT_TIMEOUT), + }); const json = await res.json(); const result = tcpOutput.safeParse(json); @@ -413,21 +410,18 @@ export async function testTcp(input: z.infer) { export async function testDns(input: z.infer) { try { - const res = await fetch( - `https://openstatus-checker.fly.dev/dns/${input.region}`, - { - method: "POST", - headers: { - Authorization: `Basic ${env.CRON_SECRET}`, - "Content-Type": "application/json", - "fly-prefer-region": input.region, - }, - body: JSON.stringify({ - uri: input.url, - }), - signal: AbortSignal.timeout(ABORT_TIMEOUT), + const res = await fetch(`${CHECKER_BASE_URL}/dns/${input.region}`, { + method: "POST", + headers: { + Authorization: `Basic ${env.CRON_SECRET}`, + "Content-Type": "application/json", + "fly-prefer-region": input.region, }, - ); + body: JSON.stringify({ + uri: input.url, + }), + signal: AbortSignal.timeout(ABORT_TIMEOUT), + }); const json = await res.json(); const result = dnsOutput.safeParse(json); @@ -475,22 +469,19 @@ export async function testDns(input: z.infer) { export async function testIcmp(input: z.infer) { try { - const res = await fetch( - `https://openstatus-checker.fly.dev/icmp/${input.region}`, - { - method: "POST", - headers: { - Authorization: `Basic ${env.CRON_SECRET}`, - "Content-Type": "application/json", - "fly-prefer-region": input.region, - }, - body: JSON.stringify({ - uri: input.url, - timeout: ICMP_TEST_TIMEOUT, - }), - signal: AbortSignal.timeout(ABORT_TIMEOUT), + const res = await fetch(`${CHECKER_BASE_URL}/icmp/${input.region}`, { + method: "POST", + headers: { + Authorization: `Basic ${env.CRON_SECRET}`, + "Content-Type": "application/json", + "fly-prefer-region": input.region, }, - ); + body: JSON.stringify({ + uri: input.url, + timeout: ICMP_TEST_TIMEOUT, + }), + signal: AbortSignal.timeout(ABORT_TIMEOUT), + }); const json = await res.json(); const result = icmpOutput.safeParse(json); @@ -521,25 +512,22 @@ export async function testIcmp(input: z.infer) { export async function testGrpc(input: z.infer) { try { - const res = await fetch( - `https://openstatus-checker.fly.dev/grpc/${input.region}`, - { - method: "POST", - headers: { - Authorization: `Basic ${env.CRON_SECRET}`, - "Content-Type": "application/json", - "fly-prefer-region": input.region, - }, - body: JSON.stringify({ - uri: input.url, - service: input.service, - tls: input.tls, - headers: transformHeaders(input.headers ?? []), - timeout: GRPC_TEST_TIMEOUT, - }), - signal: AbortSignal.timeout(ABORT_TIMEOUT), + const res = await fetch(`${CHECKER_BASE_URL}/grpc/${input.region}`, { + method: "POST", + headers: { + Authorization: `Basic ${env.CRON_SECRET}`, + "Content-Type": "application/json", + "fly-prefer-region": input.region, }, - ); + body: JSON.stringify({ + uri: input.url, + service: input.service, + tls: input.tls, + headers: transformHeaders(input.headers ?? []), + timeout: GRPC_TEST_TIMEOUT, + }), + signal: AbortSignal.timeout(ABORT_TIMEOUT), + }); const json = await res.json(); const result = grpcOutput.safeParse(json); @@ -731,15 +719,15 @@ export async function triggerChecker( function generateUrl({ row }: { row: z.infer }) { switch (row.jobType) { case "http": - return `https://openstatus-checker.fly.dev/checker/http?monitor_id=${row.id}`; + return `${CHECKER_BASE_URL}/checker/http?monitor_id=${row.id}`; case "tcp": - return `https://openstatus-checker.fly.dev/checker/tcp?monitor_id=${row.id}`; + return `${CHECKER_BASE_URL}/checker/tcp?monitor_id=${row.id}`; case "dns": - return `https://openstatus-checker.fly.dev/checker/dns?monitor_id=${row.id}`; + return `${CHECKER_BASE_URL}/checker/dns?monitor_id=${row.id}`; case "icmp": - return `https://openstatus-checker.fly.dev/checker/icmp?monitor_id=${row.id}`; + return `${CHECKER_BASE_URL}/checker/icmp?monitor_id=${row.id}`; case "grpc": - return `https://openstatus-checker.fly.dev/checker/grpc?monitor_id=${row.id}`; + return `${CHECKER_BASE_URL}/checker/grpc?monitor_id=${row.id}`; default: throw new Error("Invalid jobType"); } -- 2.54.0