Skip to content

Fix self-hosted checker and workflow routing#1976

Open
aiokaizen wants to merge 3 commits intoopenstatusHQ:mainfrom
aiokaizen:fix/self-hosted-checker-workflows
Open

Fix self-hosted checker and workflow routing#1976
aiokaizen wants to merge 3 commits intoopenstatusHQ:mainfrom
aiokaizen:fix/self-hosted-checker-workflows

Conversation

@aiokaizen
Copy link

  • Self-host mode was still coupled to OpenStatus cloud infrastructure, so it broke in real deployments.
  • workflows crashed on startup because apps/workflows/src/cron/monitor.ts initialized Google Cloud Tasks even when no GCP_* config existed.
  • Monitor validation and trigger paths still called hosted checker URLs like openstatus-checker.fly.dev, so self-host monitor creation/execution was not
    actually local.
  • Checker status updates still assumed hosted workflows / Cloud Tasks instead of reporting back to the local stack.
  • I fixed workflows to skip Cloud Tasks initialization in self-host mode and only use it when valid GCP config exists.
  • I added explicit self-host routing envs: CHECKER_BASE_URL, CHECKER_REGION, WORKFLOWS_BASE_URL, and OPENSTATUS_WORKFLOWS_URL.
  • I changed self-host monitor execution to dispatch directly to the local checker service over HTTP instead of relying on Cloud Tasks.
  • I changed checker status updates to post directly back to local workflows in self-host mode.
  • I updated API/server checker paths so monitor test/trigger flows use the local checker instead of hosted Fly endpoints.
  • I updated Docker env/compose examples to include the missing Redis REST shim and proper self-host service wiring.
  • Result: self-hosted OpenStatus can boot and run checks locally without depending on hosted checker/workflow endpoints.
  • Known limitation: self-host is treated as a single-region checker deployment.
  • Also, 30s cloud-style split scheduling is not reproduced; 1m+ intervals are the safer self-host default.

@vercel
Copy link

vercel bot commented Mar 13, 2026

@aiokaizen is attempting to deploy a commit to the OpenStatus Team on Vercel.

A member of the Team first needs to authorize it.

@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openstatus-dashboard Ready Ready Preview, Comment Mar 13, 2026 7:06am
openstatus-status-page Ready Ready Preview, Comment Mar 13, 2026 7:06am
openstatus-web Ready Ready Preview, Comment Mar 13, 2026 7:06am

Request Review

@thibaultleouay thibaultleouay requested a review from Copilot March 13, 2026 13:34
}

func UpdateStatus(ctx context.Context, updateData UpdateData) error {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cloudProvider := env("CLOUD_PROVIDER", "fly")

We should do something like this instead

@thibaultleouay
Copy link
Member

Also for self host it's easier to use private location

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes self-hosted deployments by decoupling checker/workflow routing from OpenStatus cloud infrastructure and disabling Cloud Tasks usage when GCP config isn’t present.

Changes:

  • Added explicit self-host routing via env vars (checker/workflows base URLs + region) and updated API/server to call local checker endpoints.
  • Updated workflows to avoid initializing/using Google Cloud Tasks in self-host mode or without valid GCP config, and added a direct HTTP dispatch path to the checker.
  • Updated Docker Compose and env examples to include a Redis REST shim and self-host service wiring.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/api/src/router/checker.ts Routes checker calls to local base URL in self-host mode and normalizes region/base URL handling.
apps/server/src/routes/v1/check/http/post.ts Uses self-host checker routing for test runs instead of hosted Fly endpoints.
apps/server/src/libs/checker/utils.ts Builds checker URLs pointing to local checker service in self-host mode.
apps/workflows/src/env.ts Adds self-host routing env variables and SELF_HOST flag to workflows config.
apps/workflows/src/cron/monitor.ts Lazily constructs Cloud Tasks context only when configured; skips lifecycle workflow scheduling if unavailable.
apps/workflows/src/cron/checker.ts Adds direct (non-Cloud-Tasks) dispatch to checker for self-host / no-GCP configs.
apps/checker/checker/update.go Posts status updates directly back to workflows in self-host/custom workflows URL mode.
docker-compose.yaml Adds redis + redis-http services and wires new self-host env vars across services including checker.
docker-compose.github-packages.yaml Same as above but for GitHub Packages images.
.env.docker.example Updates env guidance for self-host routing and Redis REST shim defaults.
.env.docker-lightweight.example Adds missing self-host routing + Redis REST shim variables.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

c: Context,
) {
const timestamp = Date.now();
const selfHostRegion = env().CHECKER_REGION as Region;
Comment on lines +272 to +278
for (const row of monitors.data) {
const result = await db
.select()
.from(monitorStatusTable)
.where(eq(monitorStatusTable.monitorId, row.id))
.all();
const monitorStatus = z.array(selectMonitorStatusSchema).safeParse(result);
);
}

const allRequests = await Promise.allSettled(allResult);
Comment on lines +225 to +226
const selfHostRegion = env().CHECKER_REGION as Region;

Comment on lines +413 to +423
const response = await fetch(
`${getCheckerBaseUrl()}/checker/${row.jobType}?monitor_id=${row.id}`,
{
method: "POST",
headers: {
Authorization: `Basic ${env().CRON_SECRET}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
},
);
Comment on lines 37 to +65
payloadBuf := new(bytes.Buffer)

if os.Getenv("SELF_HOST") == "true" || os.Getenv("OPENSTATUS_WORKFLOWS_URL") != "" {
if err := json.NewEncoder(payloadBuf).Encode(updateData); err != nil {
log.Ctx(ctx).Error().Err(err).Msg("error while encoding update payload")
return err
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payloadBuf)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msg("error while creating update request")
return err
}
req.Header.Set("Authorization", basic)
req.Header.Set("Content-Type", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msg("error while posting update status directly")
return err
}
defer res.Body.Close()

if res.StatusCode < 200 || res.StatusCode >= 300 {
return fmt.Errorf("direct updateStatus failed with status %d", res.StatusCode)
}

return nil
}
req.Header.Set("Authorization", basic)
req.Header.Set("Content-Type", "application/json")

res, err := http.DefaultClient.Do(req)
- "8079:80"
environment:
- SRH_MODE=env
- SRH_TOKEN=${UPSTASH_REDIS_REST_TOKEN:-replace-with-a-long-random-secret}
Comment on lines +27 to +38
function isSelfHost() {
return process.env.SELF_HOST === "true";
}

function getCheckerBaseUrl() {
return (process.env.CHECKER_BASE_URL || "http://checker:8080").replace(
/\/$/,
"",
);
}

function getCheckerRegion(region: string) {
…table.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants