From 79d0dccb43ef3e8702ea9c6551e7765ed5f42ae8 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 21 Apr 2026 02:43:52 -0500 Subject: [PATCH 1/5] docs(fractal): add FractalCronJob CRD to architecture overview Document the new FractalCronJob resource, its spec fields, status, and note that FractalService serviceType: cronJob is deprecated in favor of the dedicated CRD. --- fractal/architecture.md | 227 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 fractal/architecture.md diff --git a/fractal/architecture.md b/fractal/architecture.md new file mode 100644 index 0000000..ddb80cc --- /dev/null +++ b/fractal/architecture.md @@ -0,0 +1,227 @@ +# Fractal Architecture Overview + +Fractal is a Kubernetes-native application deployment platform — a FOSS alternative to Railway. It consists of three components: the **CLI**, the **operator**, and the **dashboard**. + +## Cluster Architecture + +### Reference deployment (Omni production) + +| Component | Technology | Purpose | +|-----------|-----------|---------| +| OS | Talos Linux | Immutable, API-managed, no SSH, minimal attack surface | +| CNI | Cilium | eBPF networking, replaces kube-proxy, Gateway API, Hubble observability | +| Ingress | Cilium Gateway API | Native Gateway API with eBPF acceleration, L7 visibility | +| Storage | Hetzner CSI (hcloud-csi) | Network-attached SSD persistent volumes | +| TLS | cert-manager + Let's Encrypt | DNS-01 via Cloudflare, auto-provisioned wildcard certs | +| DNS/CDN | Cloudflare | `*.fractal.omni.dev` wildcard, proxied | +| Load balancer | Hetzner LB | TCP forwarding to Cilium Gateway NodePorts (80->30640, 443->32596) | +| Databases | CloudNativePG | HA Postgres with streaming replication, auto-failover, PgBouncer | + +### Compute (Omni production) + +| Node | Hetzner Type | Specs | Cost | +|------|-------------|-------|------| +| control-1 | CPX31 | 4 vCPU, 8 GB RAM, 160 GB | ~$15/mo | +| control-2 | CPX31 | 4 vCPU, 8 GB RAM, 160 GB | ~$15/mo | +| control-3 | CPX31 | 4 vCPU, 8 GB RAM, 160 GB | ~$15/mo | +| Hetzner LB | LB11 | | ~$6/mo | + +~18 GB usable for workloads after system overhead. + +## Custom Resource Definitions (CRDs) + +Fractal defines the following CRDs in the `fractal.omni.dev` API group (version `v1alpha1`): + +### FractalProject (`fproj`) + +Top-level organizational unit. Each project maps to a Kubernetes namespace (`fractal-{name}`). + +**Spec:** +- `displayName` -- human-readable name +- `description` -- optional description +- `owner` -- owner email or HIDRA user ID + +**Status:** +- `phase` -- `Pending`, `Active`, `Suspended`, `Deleting` +- `namespace` -- created namespace name +- `serviceCount` -- number of services in the project + +### FractalService (`fsvc`) + +A deployable application within a project. + +**Spec:** +- `project` -- parent project name +- `source` -- either `git` (url + branch) or `image` (repository + tag) +- `build` -- build mode (`auto`, `dockerfile`, `none`) + optional Dockerfile path +- `deploy` -- replicas, env, resources, health check, volumes, schedule, autoscale +- `expose` -- port, domain, TLS +- `serviceType` -- `web`, `worker`, `cronJob`, `staticSite` (`cronJob` is deprecated in favor of the dedicated `FractalCronJob` CRD) + +**Status:** +- `phase` -- `Pending`, `Building`, `Deploying`, `Running`, `Failed` +- `readyReplicas` / `desiredReplicas` +- `lastBuildTime`, `currentImage`, `url` + +### FractalCronJob (`fcj`) + +A scheduled task within a project. Reconciled into a Kubernetes CronJob. + +**Spec:** +- `project` -- parent project name +- `source` -- either `git` (url + branch) or `image` (repository + tag) +- `build` -- build mode (`auto`, `dockerfile`, `none`) +- `schedule` -- cron expression (e.g. "0 3 * * *") +- `command`, `args` -- container entrypoint overrides +- `env` -- environment variables (literal or secret refs) +- `resources` -- CPU/memory requirements +- `concurrencyPolicy` -- `allow`, `forbid`, or `replace` +- `startingDeadlineSeconds` -- grace period for missed schedules +- `suspend` -- pause scheduling without deleting the resource +- `backoffLimit` -- retries before marking a job as failed + +**Status:** +- `phase` -- `Pending`, `Building`, `Active`, `Suspended` +- `lastScheduleTime`, `lastSuccessfulTime` +- `currentImage`, `activeJobs` + +### Planned CRDs + +| CRD | Purpose | Backed by | +|-----|---------|-----------| +| `FractalDatabase` | Managed Postgres databases | CloudNativePG Cluster + K8s Secret | +| `FractalCache` | Managed cache instances | Valkey/Dragonfly StatefulSet | + +## Reconciliation flow + +### FractalProject controller + +1. Receive `FractalProject` create/update event +2. Create the target namespace (`fractal-{name}`) if it doesn't exist +3. Count `FractalService` resources in the namespace +4. Update status (`Active`, namespace, service count) + +On delete (via finalizer): +1. Delete all `FractalService` resources in the namespace +2. Delete the namespace + +### FractalService controller + +1. Receive `FractalService` create/update event +2. **Build phase** (git sources only): + - Create a Kaniko `Job` to build the container image + - Monitor Job status; requeue until complete + - For localhost registries, pass `--insecure` to Kaniko +3. **Deploy phase** (by service type): + - **Web / Worker / StaticSite**: Apply `Deployment`, optional `Service`, optional `HTTPRoute` + - **CronJob**: Apply `CronJob` (no Service/HTTPRoute) +4. **PVCs**: Create `PersistentVolumeClaim` resources before workloads +5. **HPA**: Apply `HorizontalPodAutoscaler` if autoscale config is present +6. **TLS**: Auto-create cert-manager `Certificate` per HTTPRoute when TLS is enabled +7. Update status (phase, replica counts, current image, URL) +8. Requeue: 300s if Running, 10s if Deploying/Building + +On delete (via finalizer): +1. Delete Deployment, build Job, CronJob, HPA, Service, HTTPRoute, Certificate, and PVCs + +## Kubernetes resources per service type + +| Service type | Deployment | CronJob | Service | HTTPRoute | HPA | PVCs | Certificate | +|-------------|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| Web | x | | x (if expose) | x (if domain) | x (if autoscale) | x (if volumes) | x (if TLS) | +| Worker | x | | | | x (if autoscale) | x (if volumes) | | +| CronJob | | x | | | | | | +| StaticSite | x | | x (if expose) | x (if domain) | x (if autoscale) | x (if volumes) | x (if TLS) | + +## Installation modes + +| Mode | Command | Cluster | Registry | Use case | +|------|---------|---------|----------|----------| +| Quick start | `fractal install --distribution k3s` | k3s | Configurable | Local dev, homelab, single-node | +| Production | `fractal install --distribution talos --provider hetzner` | Talos Linux | Configurable | Teams running real workloads | +| BYOC | `fractal install --existing-cluster` | Any K8s | Configurable | GKE, EKS, AKS, self-managed | +| Local dev | `fractal install --distribution k3d` | k3d + local registry | `k3d-{name}-registry.localhost:5000` | Local development | + +All modes install: CRDs, operator, dashboard, CloudNativePG, cert-manager, metrics-server (each if not already present). + +Full provisioning modes (Talos/k3s) additionally install: Cilium CNI, CSI driver, Gateway API CRDs, DNS configuration. + +## Platform stack + +Components installed by `fractal install` (full provisioning): + +``` +Cilium -> Gateway API CRDs -> hcloud-csi -> cert-manager -> metrics-server -> CloudNativePG -> fractal-operator +``` + +The operator and dashboard run in the `fractal-system` namespace. + +## Dashboard data flow + +The dashboard is a React SPA served by nginx. + +**In-cluster (production):** +1. Browser loads static assets from nginx +2. API requests (`/api/*`, `/apis/*`) are proxied by nginx to the Kubernetes API server +3. Nginx reads the ServiceAccount token and sets the `Authorization` header +4. The dashboard queries `FractalProject` and `FractalService` resources via the K8s API + +**Local development:** +1. `bun dev` starts Vite dev server +2. Vite proxies `/api` and `/apis` to `kubectl proxy` on `localhost:8001` +3. `kubectl proxy` authenticates with the user's kubeconfig + +## Metrics and monitoring + +The operator exposes Prometheus metrics on port 8081 at `/metrics`: + +| Metric | Type | Description | +|--------|------|-------------| +| `reconcile_total` | counter | Total reconciliation attempts (labels: `resource`, `result`) | +| `reconcile_duration_seconds` | histogram | Reconciliation latency (labels: `resource`, `result`) | +| `active_services` | gauge | Number of active FractalService resources | +| `active_builds` | gauge | Number of active build jobs | + +A `ServiceMonitor` and Grafana dashboard are provided in the operator manifests. + +## Control plane layering + +``` +Pulumi (Mosaic) -> cloud resources (nodes, LB, DNS, Stripe, GitHub) +Flux / Pulumi -> cluster platform components (operators, CNI, cert-manager, monitoring) +Fractal operator -> application workloads (projects, services, databases, caches) +``` + +Clean separation: Pulumi owns infrastructure, Flux/Pulumi owns platform, Fractal owns apps. + +## Component diagram + +``` + +-----------+ + | fractal | + | CLI | + +-----+-----+ + | + kubectl / kube API + | + +-----v-----+ + | Kubernetes| + | API Server| + +-----+-----+ + | + +--------------+--------------+ + | | + +------v------+ +-------v-------+ + | FractalProject | | FractalService | + | Controller | | Controller | + +------+------+ +-------+-------+ + | | + create namespace create Deployment, + Service, HTTPRoute, + Certificate, HPA, PVC + | | + +------v------------------------------v------+ + | fractal-system | + | operator | dashboard | metrics service | + +---------------------------------------------+ +``` From 1c61bc5cacefaf1ee045829a3251dd2d6075b4ca Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Sat, 16 May 2026 11:21:42 -0600 Subject: [PATCH 2/5] chore: extend org-wide Renovate config Switches `extends` from `config:recommended` to `github>omnidotdev/.github` so this repo inherits the org-wide Renovate policy (weekend-only schedule, grouped non-major updates, auto-merge for low-risk bumps). --- renovate.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 80bde9f..b90b806 100644 --- a/renovate.json +++ b/renovate.json @@ -1,4 +1,7 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended", "customManagers:biomeVersions"] + "extends": [ + "github>omnidotdev/.github", + "customManagers:biomeVersions" + ] } From 130276a3211eb4e42251d60fafad67687fd13ef4 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 18 May 2026 12:42:47 -0600 Subject: [PATCH 3/5] docs(blink): add getting-started, link from index --- .../docs/kindred/blink/getting-started.mdx | 57 +++++++++++++ content/docs/kindred/blink/index.mdx | 81 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 content/docs/kindred/blink/getting-started.mdx create mode 100644 content/docs/kindred/blink/index.mdx diff --git a/content/docs/kindred/blink/getting-started.mdx b/content/docs/kindred/blink/getting-started.mdx new file mode 100644 index 0000000..6000c5d --- /dev/null +++ b/content/docs/kindred/blink/getting-started.mdx @@ -0,0 +1,57 @@ +--- +title: Getting Started +description: Sign up for Blink, create your first profile, add links, and share your branded landing page. +--- + +This guide walks you from a brand-new account to a published profile in under five minutes. If you intend to run Blink on your own hardware, jump to [Self-Hosting](/kindred/blink/self-hosting). + +## Create an account + +1. Visit [blink.omni.dev](https://blink.omni.dev) and click **Sign In**. +2. You will be redirected to `identity.omni.dev` to authenticate with Google, GitHub, or any OIDC provider configured by your organization. +3. After signing in, you land on the Blink dashboard. A default organization is created for you on first sign-in. + +## Create your first profile + +A profile is the public page people see when they visit `blink.omni.dev/u/`. + +1. From the dashboard, click **New Profile**. +2. Pick a **username** (this becomes the URL slug), a **display name**, and optionally a short bio. +3. Upload an avatar if you have one handy. +4. Click **Save**. Your profile is now live at `https://blink.omni.dev/u/`. + +Free accounts can have unlimited profiles, but each profile is capped at five links. Profiles above the free tier inherit your organization's plan. + +## Add links + +1. Open your profile and click **Add link**. +2. Choose the link **type** (regular link, short link, or section header). +3. Paste the destination URL, set a label, and optionally an icon. +4. Drag to reorder. Toggle visibility per link. + +Short links count against your plan's monthly quota (`max_short_links_per_month`); regular links count against `max_links`. See [Pricing](/kindred/blink/pricing) for current limits. + +## Customize the look + +Pro and Team plans unlock custom themes: + +- Open profile settings, click **Theme**, and pick a preset or define your own colors, fonts, and background. +- Team plans can also configure a **custom domain** (e.g., `links.yourbrand.com`) and remove the "Powered by Blink" footer. + +Free profiles always show the Blink footer and use the default theme. + +## Generate a QR code + +1. From the dashboard or any link's detail page, click **Generate QR**. +2. Pick the size, error-correction level, and optional logo overlay. +3. Download the SVG or PNG. The QR resolves through Blink so you can swap the destination later without reprinting. + +## Sharing and analytics + +Every profile has a canonical URL (`blink.omni.dev/u/`) and an OG image for previews in social apps. Analytics (per-link click counts, geo, referrers) ship on Pro and Team plans. The Free tier shows a "Coming soon" placeholder on the analytics page. + +## Next steps + +- [Pricing](/kindred/blink/pricing): plan comparison and what each entitlement unlocks +- [API Reference](/kindred/blink/api): GraphQL endpoint, REST helpers, webhooks +- [Self-Hosting](/kindred/blink/self-hosting): run Blink inside your own infrastructure diff --git a/content/docs/kindred/blink/index.mdx b/content/docs/kindred/blink/index.mdx new file mode 100644 index 0000000..32cd459 --- /dev/null +++ b/content/docs/kindred/blink/index.mdx @@ -0,0 +1,81 @@ +--- +title: 🖇️ Blink +description: One link to bind them all. Centralize your online identity, share branded links, and manage QR codes from a single dashboard. +--- + +import { ProductOverview } from "@/components/docs"; +import app from "@/lib/config/app.config"; +import { FaIdCard, FaLink, FaQrcode } from "react-icons/fa"; + +, + className: "bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-300", + }, + { + label: "URL Shortener", + icon: , + className: "bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-300", + }, + { + label: "QR Codes", + icon: , + className: "bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-300", + }, + ]} + links={[ + { + href: "https://blink.omni.dev", + label: "Visit Website", + type: "website", + }, + { + href: `${app.socials.github}/blink`, + label: "Visit Repository", + type: "repository", + }, + ]} + alerts={[ + { + title: "What is Blink?", + description: + "A unified link-in-bio platform with branded short links, custom themes, and per-organization workspaces. Apache 2.0 licensed and self-hostable.", + }, + ]} +/> + +**Omni Blink** is a link-in-bio and short-link platform. Create a single shareable profile that points at every place you live online, generate branded QR codes, and shorten links with custom domains. + +## Why Blink? + +- **One profile, many facets**: a single public page that organizes every link you want to share +- **Branded short links**: per-organization short links with optional custom domains +- **Custom themes**: pick a preset or design your own to match your brand +- **Workspaces**: organizations and team seats backed by Omni's identity platform +- **Self-hostable**: ship it inside your own infrastructure with the official Compose stack + + + + Sign up, create your first profile, add links, and publish in under five minutes + + + Run Blink inside your own infrastructure with Docker Compose + + + Free, Pro, and Team plans with entitlements enforced by Aether + + + GraphQL endpoint, REST helpers, authentication, and rate limits + + + +## Architecture + +Blink runs as two services backed by PostgreSQL: + +- `blink-api`: Elysia + PostGraphile GraphQL backend with Drizzle migrations +- `blink-app`: TanStack Start frontend (React 19) served as the public-facing application + +The hosted version uses Omni's Identity (`identity.omni.dev`) for OAuth, Aether for billing entitlements, Warden for authorization, and the Fractal cluster for deployment. Self-hosters can wire any subset of these or run Blink standalone. From 603d3f156aa78fd4f7ebab6c08b8b16d0e57fa41 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 18 May 2026 21:42:52 -0600 Subject: [PATCH 4/5] docs(blink): add api, pricing, self-hosting; refresh getting-started + index for launch --- content/docs/kindred/blink/api.mdx | 69 ++++++++++++++ .../docs/kindred/blink/getting-started.mdx | 2 +- content/docs/kindred/blink/index.mdx | 4 +- content/docs/kindred/blink/pricing.mdx | 40 ++++++++ content/docs/kindred/blink/self-hosting.mdx | 92 +++++++++++++++++++ 5 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 content/docs/kindred/blink/api.mdx create mode 100644 content/docs/kindred/blink/pricing.mdx create mode 100644 content/docs/kindred/blink/self-hosting.mdx diff --git a/content/docs/kindred/blink/api.mdx b/content/docs/kindred/blink/api.mdx new file mode 100644 index 0000000..58b0a5f --- /dev/null +++ b/content/docs/kindred/blink/api.mdx @@ -0,0 +1,69 @@ +--- +title: API Reference +description: GraphQL endpoint, REST helpers, authentication, and rate limits for the Blink API. +--- + +The Blink API is served by a single Elysia process. It exposes a GraphQL endpoint, a small set of REST helpers, and standard health probes. + +## Base URL + +Hosted: `https://api.blink.omni.dev` + +Self-hosted: whatever `API_BASE_URL` is set to in your Compose environment (default `http://localhost:4000`). + +## Authentication + +Authenticated requests carry a JWT bearer token in the `Authorization` header: + +``` +Authorization: Bearer +``` + +Tokens are issued by Omni Identity (`identity.omni.dev`) via the OAuth 2.0 authorization code flow with PKCE. The frontend handles token acquisition and refresh; programmatic clients can mint tokens via the standard Identity API. + +Public read paths (such as `/api/profile/by-username/:username/branding`) do not require authentication. + +## GraphQL + +Endpoint: `POST /graphql` + +Schema introspection is **disabled in production**. To explore the schema, run `bun graphql:generate` in the API service to write the SDL to disk, or query a development instance where introspection is enabled. + +Common operations: + +- `query profileByUsername(username: String!)` to fetch a public profile and its tree +- `mutation createProfile`, `updateProfile`, `deleteProfile` +- `mutation createLink`, `updateLink`, `deleteLink` +- `mutation createTreeNode`, `updateTreeNode`, `deleteTreeNode` + +All write mutations enforce Aether entitlements and Warden authorization checks. + +## REST Helpers + +| Method | Path | Auth | Purpose | +|--------|------|------|---------| +| `GET` | `/health` | none | Liveness probe | +| `GET` | `/ready` | none | Readiness probe (checks DB) | +| `GET` | `/api/profile/by-username/:username/branding` | none | Returns `{ removeBranding: boolean }` for a public profile's organization | +| `GET` | `/api/entitlements/check?feature=&organizationId=` | bearer | Returns `{ allowed: boolean }` for the given feature key | +| `GET` | `/api/billing/prices` | none | Returns the live Stripe price catalog for Blink, including marketing features per tier | +| `POST` | `/api/billing/checkout` | bearer | Creates a Stripe Checkout session for the requested tier and returns the redirect URL | +| `POST` | `/api/billing/portal` | bearer | Creates a Stripe Customer Portal session for self-serve subscription management | + +## Webhooks + +The API receives webhooks from related services. Each path verifies its own HMAC signature. + +| Path | Source | Purpose | +|------|--------|---------| +| `/webhooks/entitlements` | Aether | Invalidate cached entitlements on plan changes | +| `/webhooks/authz` | Vortex | Invalidate authorization tuples on org membership changes | +| `/webhooks/idp` | Gatekeeper | Sync user records on identity events | + +## Rate Limiting + +The API rate limits at **100 requests per minute per IP** (Elysia rate-limit plugin). Authenticated endpoints inherit the same limit. For higher throughput, contact us about Enterprise plans. + +## CORS + +The API only accepts cross-origin requests from origins listed in `CORS_ALLOWED_ORIGINS`. The hosted app (`https://blink.omni.dev`) is allowed by default in production. diff --git a/content/docs/kindred/blink/getting-started.mdx b/content/docs/kindred/blink/getting-started.mdx index 6000c5d..6e181d9 100644 --- a/content/docs/kindred/blink/getting-started.mdx +++ b/content/docs/kindred/blink/getting-started.mdx @@ -20,7 +20,7 @@ A profile is the public page people see when they visit `blink.omni.dev/u/`. -Free accounts can have unlimited profiles, but each profile is capped at five links. Profiles above the free tier inherit your organization's plan. +Free organizations are capped at ten links total across all profiles, plus 10 short links per month. Pro raises the link cap to 20 and 100 short links per month, Team removes both caps. See [Pricing](/kindred/blink/pricing) for the full breakdown. ## Add links diff --git a/content/docs/kindred/blink/index.mdx b/content/docs/kindred/blink/index.mdx index 32cd459..f37e60a 100644 --- a/content/docs/kindred/blink/index.mdx +++ b/content/docs/kindred/blink/index.mdx @@ -1,6 +1,6 @@ --- title: 🖇️ Blink -description: One link to bind them all. Centralize your online identity, share branded links, and manage QR codes from a single dashboard. +description: One link, infinite facets. Centralize your online identity, share branded links, and manage QR codes from a single dashboard. --- import { ProductOverview } from "@/components/docs"; @@ -32,7 +32,7 @@ import { FaIdCard, FaLink, FaQrcode } from "react-icons/fa"; type: "website", }, { - href: `${app.socials.github}/blink`, + href: `${app.socials.github}/blink-stack`, label: "Visit Repository", type: "repository", }, diff --git a/content/docs/kindred/blink/pricing.mdx b/content/docs/kindred/blink/pricing.mdx new file mode 100644 index 0000000..bf44b9e --- /dev/null +++ b/content/docs/kindred/blink/pricing.mdx @@ -0,0 +1,40 @@ +--- +title: Pricing +description: Tier breakdown and entitlement enforcement for Blink. +--- + +Blink offers a free tier for individuals, paid tiers for power users and teams, and self-hosting for anyone who would rather run the stack themselves. Entitlements are enforced at the API by Aether and surfaced through the app. + +## Plans + +Pricing and tier definitions are owned by the [omni-api product catalog](https://github.com/omnidotdev/omni-api/blob/master/services/api/src/lib/db/catalog/planConfigs.ts) under the `blink` key. The values below mirror that source of truth. + +| Plan | Monthly | Yearly | Links | Short links per month | Custom themes | Custom domain | Remove "Powered by Blink" | Analytics | +|------|---------|--------|-------|-----------------------|----------------|----------------|---------------------------|-----------| +| Free | $0 | $0 | 10 | 10 | No | No | No | No | +| Pro | $3 / mo | $27 / yr | 20 | 100 | Yes | No | No | Basic | +| Team | $8 / mo | $72 / yr | Unlimited | Unlimited | Yes | Yes | Yes | Advanced | + +Yearly billing applies a 25% discount over the monthly rate. For enterprise terms, dedicated support, or on-prem deployments, [contact us](mailto:support@omni.dev). + +## Entitlement Enforcement + +Each entitlement is enforced server-side. The relevant feature keys map to checks in `services/blink-api/src/lib/entitlements`: + +| Feature key | Where it's enforced | +|-------------|---------------------| +| `max_links` | Link create mutation | +| `max_short_links_per_month` | Short link create mutation | +| `custom_themes` | Profile update mutation when `customTheme` is set; also drives the analytics gate | +| `remove_branding` | Public profile renderer (hides the badge) | +| `custom_domain` | Custom domain settings tab | + +When Aether is unreachable, the API falls back to free-tier defaults rather than failing requests. This keeps the app responsive during partial outages. + +## Self-Hosting + +Self-hosters do not pay anything to use Blink. The Apache 2.0 license covers commercial use, modification, and redistribution. Aether integration is optional. See [self-hosting](/kindred/blink/self-hosting) for setup. + +## Billing + +Hosted plans are billed monthly via Aether. Invoices, seat changes, and cancellations are handled in the Omni Console. diff --git a/content/docs/kindred/blink/self-hosting.mdx b/content/docs/kindred/blink/self-hosting.mdx new file mode 100644 index 0000000..4e294fc --- /dev/null +++ b/content/docs/kindred/blink/self-hosting.mdx @@ -0,0 +1,92 @@ +--- +title: Self-Hosting +description: Run Blink inside your own infrastructure with Docker Compose. +--- + +Blink ships an Apache 2.0 licensed reference deployment via Docker Compose. The metarepo at [`omnidotdev/blink-stack`](https://github.com/omnidotdev/blink-stack) provides everything needed to bring up the full stack. + +## Prerequisites + +- Docker (or any OCI runtime that speaks `compose` v2) +- A registered OAuth application if you want federated sign-in +- A reachable public hostname for the app and API if not running on localhost + +## Services + +The bundled `compose.yaml` defines three services. `api` and `app` are built from source on first `docker compose up`; subsequent runs use the cached images. + +| Service | Image | Default Port | +|---------|-------|--------------| +| `db` | `postgres:18-alpine` | `5432` | +| `api` | `blink-api:latest` (built from `./services/blink-api`) | `4000` | +| `app` | `blink-app:latest` (built from `./services/blink-app`) | `3000` | + +The API runs migrations on boot, then serves GraphQL at `/graphql` and REST helpers under `/api/`. The app is a TanStack Start server that proxies API calls to the API service. + +## Required Environment Variables + +Configure these in a `.env` next to `compose.yaml`. A starter file is included as `.env.local.template`. + +| Variable | Purpose | +|----------|---------| +| `DB_PASSWORD` | PostgreSQL password used by the API to connect to `db` | +| `AUTH_SECRET` | Session signing secret. Generate with `openssl rand -hex 32` | + +## Optional Environment Variables + +| Variable | Purpose | +|----------|---------| +| `APP_BASE_URL` | Public URL of the app, used for CORS and OAuth callbacks. Defaults to `http://localhost:3000` | +| `API_BASE_URL` | URL the app uses to reach the API. Defaults to `http://api:4000` | +| `DB_NAME`, `DB_USER`, `DB_PORT` | Postgres overrides | +| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | Enable Google OAuth | +| `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` | Enable GitHub OAuth | +| `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` | Generic OIDC provider | + +If no OAuth provider is configured, sign-in features are disabled. The app boots successfully with only `DB_PASSWORD` and `AUTH_SECRET` set. + +## Running + +```sh +git clone https://github.com/omnidotdev/blink-stack.git +cd blink-stack +cp services.yaml.template services.yaml +cp .env.local.template .env +# edit .env to set DB_PASSWORD and AUTH_SECRET +docker compose up -d --build +``` + +The `--build` flag is only needed the first time; subsequent `docker compose up` runs reuse the cached images. + +The API is reachable at `http://localhost:4000`, the app at `http://localhost:3000`. + +## Diagnostics + +Verify each service is healthy: + +```sh +curl http://localhost:4000/health +curl http://localhost:4000/ready +curl http://localhost:3000 +``` + +`GET /ready` returns `503` if the database is not reachable. Logs are available via `docker compose logs -f api app`. + +## Volumes + +`db_data` is the only declared volume and persists Postgres state. Back it up before any image upgrade. + +## Optional Integrations + +Blink can integrate with other Omni services if those URLs are configured: + +- `BILLING_BASE_URL` and `BILLING_SERVICE_API_KEY`: enable Aether-backed entitlements (otherwise free-tier defaults apply) +- `AUTHZ_API_URL`: enable Warden authorization checks (otherwise allow-by-default within the app) +- `VORTEX_API_URL`: enable cross-product event delivery +- `MEILISEARCH_URL` and `MEILISEARCH_MASTER_KEY`: enable search indexing + +If these are unset, the API logs a warning at boot and continues with the corresponding feature disabled. + +## License + +Blink is licensed under Apache 2.0. See `LICENSE.md` in the metarepo for details. From 3d2bc1165b6db5eaffdbeeaf98c0c9fa14397921 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 00:09:24 +0000 Subject: [PATCH 5/5] chore(deps): update dependency typescript to v6 --- bun.lock | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 529aac7..3cb6166 100644 --- a/bun.lock +++ b/bun.lock @@ -42,7 +42,7 @@ "nitro": "3.0.1-alpha.1", "tailwindcss": "^4.1.18", "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3", + "typescript": "^6.0.0", "vite-tsconfig-paths": "^6.0.3", }, }, @@ -1252,7 +1252,7 @@ "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], diff --git a/package.json b/package.json index d212832..40fbfab 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "nitro": "3.0.1-alpha.1", "tailwindcss": "^4.1.18", "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3", + "typescript": "^6.0.0", "vite-tsconfig-paths": "^6.0.3" }, "trustedDependencies": [