Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.docker-lightweight.example
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ NODE_ENV=production
# [REQUIRED] Public URL for the application
NEXT_PUBLIC_URL=http://localhost:3002

# Self-hosted checker and workflows routing.
UPSTASH_REDIS_REST_URL=http://redis-http:80
UPSTASH_REDIS_REST_TOKEN=replace-with-a-long-random-secret
CHECKER_BASE_URL=http://checker:8080
CHECKER_REGION=ams
WORKFLOWS_BASE_URL=http://workflows:3000
OPENSTATUS_WORKFLOWS_URL=http://workflows:3000
OPENSTATUS_INGEST_URL=http://server:3000



# DEVELOPMENT & TESTING
Expand Down
26 changes: 12 additions & 14 deletions .env.docker.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ DATABASE_AUTH_TOKEN=

# REDIS & QUEUE
# ============================================================================
# Redis (optional) - for caching
UPSTASH_REDIS_REST_URL=http://localhost:6379
UPSTASH_REDIS_REST_TOKEN=placeholder
# Self-hosted Redis REST shim used by workflows/dashboard/server.
UPSTASH_REDIS_REST_URL=http://redis-http:80
UPSTASH_REDIS_REST_TOKEN=replace-with-a-long-random-secret

# QStash (optional - for background jobs)
QSTASH_CURRENT_SIGNING_KEY=
Expand Down Expand Up @@ -62,15 +62,7 @@ AUTH_GITHUB_SECRET=
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=

# GOOGLE CLOUD
# ============================================================================
# Google Cloud Platform (optional - for scheduled tasks)
GCP_PROJECT_ID=your-value
GCP_LOCATION=your-value
GCP_CLIENT_EMAIL=your-value
GCP_PRIVATE_KEY=your-value

# Cron secret for scheduled jobs
# Cron secret for scheduled jobs and checker callbacks.
CRON_SECRET=your-random-cron-secret

# API KEYS
Expand Down Expand Up @@ -133,11 +125,17 @@ NODE_ENV=production
# [REQUIRED] Public URL for the application
NEXT_PUBLIC_URL=http://localhost:3002

# Self-hosted checker and workflows routing.
CHECKER_BASE_URL=http://checker:8080
CHECKER_REGION=ams
WORKFLOWS_BASE_URL=http://workflows:3000

# Screenshot service (optional)
SCREENSHOT_SERVICE_URL=

# External services
OPENSTATUS_INGEST_URL=https://openstatus-private-location.fly.dev
# Private-location and checker callbacks stay on the internal Docker network.
OPENSTATUS_WORKFLOWS_URL=http://workflows:3000
OPENSTATUS_INGEST_URL=http://server:3000

# DEVELOPMENT & TESTING
# ============================================================================
Expand Down
37 changes: 35 additions & 2 deletions apps/checker/checker/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"

Expand All @@ -27,10 +28,42 @@ type UpdateData struct {
}

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

url := "https://openstatus-workflows.fly.dev/updateStatus"
url := os.Getenv("OPENSTATUS_WORKFLOWS_URL")
if url == "" {
url = "https://openstatus-workflows.fly.dev"
}
url = strings.TrimRight(url, "/") + "/updateStatus"
basic := "Basic " + os.Getenv("CRON_SECRET")
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
}
Comment on lines +45 to +57
defer res.Body.Close()

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

return nil
}

c := os.Getenv("GCP_PRIVATE_KEY")
c = strings.ReplaceAll(c, "\\n", "\n")
opts := &auth.Options2LO{
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openstatus/dashboard",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
Expand Down
7 changes: 2 additions & 5 deletions apps/dashboard/src/app/(dashboard)/notifications/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ import { columns } from "@/components/data-table/notifications/columns";
import { FormSheetNotifier } from "@/components/forms/notifications/sheet";
import { DataTable } from "@/components/ui/data-table/data-table";
import { config } from "@/data/notifications.client";
import { getDashboardPublicUrl } from "@/lib/public-url";
import { useTRPC } from "@/lib/trpc/client";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useQueryStates } from "nuqs";
import { searchParamsParsers } from "./search-params";

// FIXME: WARNING we are using the `web` api url here
const BASE_URL =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://www.openstatus.dev";
const BASE_URL = getDashboardPublicUrl();

export function Client() {
const trpc = useTRPC();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
FormCardSeparator,
FormCardTitle,
} from "@/components/forms/form-card";
import { getDashboardPublicUrl } from "@/lib/public-url";
import { useTRPC } from "@/lib/trpc/client";
import { allPlans } from "@openstatus/db/src/schema/plan/config";
import type { Limits } from "@openstatus/db/src/schema/plan/schema";
Expand All @@ -36,10 +37,7 @@ import { useEffect, useMemo, useTransition } from "react";
import { toast } from "sonner";
import { searchParamsParsers } from "./search-params";

const BASE_URL =
process.env.NODE_ENV === "production"
? "https://app.openstatus.dev"
: "http://localhost:3000";
const BASE_URL = getDashboardPublicUrl();

function calculateTotalRequests(limits: Limits) {
const monitors = limits.monitors;
Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/src/app/(dashboard)/settings/general/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import { FormApiKey } from "@/components/forms/settings/form-api-key";
import { FormMembers } from "@/components/forms/settings/form-members";
import { FormSlug } from "@/components/forms/settings/form-slug";
import { FormWorkspace } from "@/components/forms/settings/form-workspace";
import { getDashboardPublicUrl } from "@/lib/public-url";
import { useTRPC } from "@/lib/trpc/client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

const BASE_URL = "https://app.openstatus.dev/invite";
const BASE_URL = `${getDashboardPublicUrl()}/invite`;

export default function Page() {
const trpc = useTRPC();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@ import {
FormCardTitle,
FormCardUpgrade,
} from "@/components/forms/form-card";
import { getDashboardPublicUrl } from "@/lib/public-url";
import { useTRPC } from "@/lib/trpc/client";
import { Badge } from "@openstatus/ui/components/ui/badge";
import { Button } from "@openstatus/ui/components/ui/button";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Lock } from "lucide-react";
import { useRouter } from "next/navigation";

const SERVER_URL =
process.env.NODE_ENV === "production"
? "https://api.openstatus.dev"
: "http://localhost:3000";
const SERVER_URL = getDashboardPublicUrl();

interface SlackIntegrationCardProps {
locked?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "@openstatus/ui/components/ui/table";

import { config as featureGroups, plans } from "@/data/plans";
import { getDashboardPublicUrl } from "@/lib/public-url";
import { getStripe } from "@/lib/stripe";
import { useTRPC } from "@/lib/trpc/client";
import { cn } from "@/lib/utils";
Expand All @@ -27,10 +28,7 @@ import { Badge } from "@openstatus/ui/components/ui/badge";
import { useCookieState } from "@openstatus/ui/hooks/use-cookie-state";
import { useMutation, useQuery } from "@tanstack/react-query";

const BASE_URL =
process.env.NODE_ENV === "production"
? "https://app.openstatus.dev"
: "http://localhost:3000";
const BASE_URL = getDashboardPublicUrl();

export function DataTable({ restrictTo }: { restrictTo?: WorkspacePlan[] }) {
const [currency] = useCookieState("x-currency", "USD");
Expand Down
6 changes: 6 additions & 0 deletions apps/dashboard/src/lib/public-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const DEFAULT_DASHBOARD_PUBLIC_URL = "http://localhost:3000";

export function getDashboardPublicUrl() {
const baseUrl = process.env.NEXT_PUBLIC_URL || DEFAULT_DASHBOARD_PUBLIC_URL;
return baseUrl.replace(/\/$/, "");
}
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openstatus/server",
"version": "0.0.1",
"version": "0.0.2",
"description": "",
"type": "module",
"main": "src/index.ts",
Expand Down
15 changes: 15 additions & 0 deletions apps/server/src/libs/checker/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import {
transformHeaders,
} from "@openstatus/utils";

function isSelfHost() {
return process.env.SELF_HOST === "true";
}

function getCheckerBaseUrl() {
return (process.env.CHECKER_BASE_URL || "http://checker:8080").replace(
/\/$/,
"",
);
}
Comment on lines +10 to +19

export function getCheckerPayload(
monitor: z.infer<typeof selectMonitorSchema>,
status: z.infer<typeof selectMonitorSchema>["status"],
Expand Down Expand Up @@ -72,6 +83,10 @@ export function getCheckerUrl(
data: false,
},
): string {
if (isSelfHost()) {
return `${getCheckerBaseUrl()}/checker/${monitor.jobType}?monitor_id=${monitor.id}&trigger=${opts.trigger}&data=${opts.data}`;
}

switch (monitor.jobType) {
case "http":
return `https://openstatus-checker.fly.dev/checker/http?monitor_id=${monitor.id}&trigger=${opts.trigger}&data=${opts.data}`;
Expand Down
10 changes: 7 additions & 3 deletions apps/server/src/routes/slack/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,13 @@ function getRedirectUri(c: Context): string {
}

function getDashboardUrl(): string {
return env.NODE_ENV === "production"
? "https://app.openstatus.dev"
: "http://localhost:3000";
return (
process.env.DASHBOARD_PUBLIC_URL ||
process.env.NEXT_PUBLIC_URL ||
(env.NODE_ENV === "production"
? "https://app.openstatus.dev"
: "http://localhost:3000")
).replace(/\/$/, "");
}

function encodeState(state: OAuthState): string {
Expand Down
27 changes: 25 additions & 2 deletions apps/server/src/routes/v1/check/http/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ import {
ResponseSchema,
} from "./schema";

function isSelfHost() {
return process.env.SELF_HOST === "true";
}

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

function getCheckerRegion(region: string) {
if (!isSelfHost()) {
return region;
}

return process.env.CHECKER_REGION || "ams";
}
Comment on lines +20 to +37

const postRoute = createRoute({
method: "post",
tags: ["check"],
Expand Down Expand Up @@ -69,11 +88,15 @@ export function registerHTTPPostCheck(api: typeof checkApi) {
for (let count = 0; count < input.runCount; count++) {
const currentFetch = [];
for (const region of input.regions) {
const r = fetch(`https://openstatus-checker.fly.dev/ping/${region}`, {
const targetRegion = getCheckerRegion(region);
const targetUrl = isSelfHost()
? `${getCheckerBaseUrl()}/ping/${targetRegion}`
: `https://openstatus-checker.fly.dev/ping/${targetRegion}`;
const r = fetch(targetUrl, {
headers: {
Authorization: `Basic ${env.CRON_SECRET}`,
"Content-Type": "application/json",
"fly-prefer-region": region,
...(isSelfHost() ? {} : { "fly-prefer-region": targetRegion }),
},
method: "POST",
body: JSON.stringify({
Expand Down
2 changes: 1 addition & 1 deletion apps/status-page/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openstatus/status-page",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"scripts": {
"env": "bun env.ts",
Expand Down
7 changes: 4 additions & 3 deletions apps/status-page/src/lib/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ export const getValidCustomDomain = (req: NextRequest | Request) => {
const url = "nextUrl" in req ? req.nextUrl.clone() : new URL(req.url);
const headers = req.headers;
const host = headers.get("x-forwarded-host");
const effectiveHost = host ?? url.host;

let prefix = "";
let type: "hostname" | "pathname";

const hostnames = host?.split(/[.:]/) ?? url.host.split(/[.:]/);
const hostnames = effectiveHost.split(/[.:]/);
const pathnames = url.pathname.split("/");

const subdomain = getValidSubdomain(url.host);
const subdomain = getValidSubdomain(effectiveHost);
console.log({
hostnames,
pathnames,
Expand All @@ -67,7 +68,7 @@ export const getValidCustomDomain = (req: NextRequest | Request) => {
if (
hostnames.length > 2 &&
hostnames[0] !== "www" &&
!url.host.endsWith(".vercel.app")
!effectiveHost.endsWith(".vercel.app")
) {
prefix = hostnames[0].toLowerCase();
type = "hostname";
Expand Down
7 changes: 4 additions & 3 deletions apps/status-page/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ export default auth(async (req) => {
const cookies = req.cookies;
const headers = req.headers;
const host = headers.get("x-forwarded-host");
const effectiveHost = host ?? url.host;

let prefix = "";
let type: "hostname" | "pathname";

const hostnames = host?.split(/[.:]/) ?? url.host.split(/[.:]/);
const hostnames = effectiveHost.split(/[.:]/);
const pathnames = url.pathname.split("/");

const subdomain = getValidSubdomain(url.host);
const subdomain = getValidSubdomain(effectiveHost);
console.log({
hostnames,
pathnames,
Expand All @@ -32,7 +33,7 @@ export default auth(async (req) => {
if (
hostnames.length > 2 &&
hostnames[0] !== "www" &&
!url.host.endsWith(".vercel.app")
!effectiveHost.endsWith(".vercel.app")
) {
prefix = hostnames[0].toLowerCase();
type = "hostname";
Expand Down
Loading
Loading