diff --git a/apps/loopover-ui/src/routeTree.gen.ts b/apps/loopover-ui/src/routeTree.gen.ts index 737dfd2c8b..c3719d822d 100644 --- a/apps/loopover-ui/src/routeTree.gen.ts +++ b/apps/loopover-ui/src/routeTree.gen.ts @@ -33,6 +33,7 @@ import { Route as DocsSelfHostingReesAnalyzersRouteImport } from './routes/docs. import { Route as DocsSelfHostingReesRouteImport } from './routes/docs.self-hosting-rees' import { Route as DocsSelfHostingRagRouteImport } from './routes/docs.self-hosting-rag' import { Route as DocsSelfHostingQuickstartRouteImport } from './routes/docs.self-hosting-quickstart' +import { Route as DocsSelfHostingUnifiedAmsOrbRouteImport } from './routes/docs.self-hosting-unified-ams-orb' import { Route as DocsSelfHostingOperationsRouteImport } from './routes/docs.self-hosting-operations' import { Route as DocsSelfHostingGithubAppRouteImport } from './routes/docs.self-hosting-github-app' import { Route as DocsSelfHostingDocsAuditRouteImport } from './routes/docs.self-hosting-docs-audit' @@ -196,6 +197,12 @@ const DocsSelfHostingQuickstartRoute = path: '/self-hosting-quickstart', getParentRoute: () => DocsRoute, } as any) +const DocsSelfHostingUnifiedAmsOrbRoute = + DocsSelfHostingUnifiedAmsOrbRouteImport.update({ + id: '/self-hosting-unified-ams-orb', + path: '/self-hosting-unified-ams-orb', + getParentRoute: () => DocsRoute, + } as any) const DocsSelfHostingOperationsRoute = DocsSelfHostingOperationsRouteImport.update({ id: '/self-hosting-operations', @@ -444,6 +451,7 @@ export interface FileRoutesByFullPath { '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute + '/docs/self-hosting-unified-ams-orb': typeof DocsSelfHostingUnifiedAmsOrbRoute '/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute '/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute '/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute @@ -505,6 +513,7 @@ export interface FileRoutesByTo { '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute + '/docs/self-hosting-unified-ams-orb': typeof DocsSelfHostingUnifiedAmsOrbRoute '/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute '/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute '/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute @@ -570,6 +579,7 @@ export interface FileRoutesById { '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute + '/docs/self-hosting-unified-ams-orb': typeof DocsSelfHostingUnifiedAmsOrbRoute '/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute '/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute '/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute @@ -961,6 +971,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DocsSelfHostingQuickstartRouteImport parentRoute: typeof DocsRoute } + '/docs/self-hosting-unified-ams-orb': { + id: '/docs/self-hosting-unified-ams-orb' + path: '/self-hosting-unified-ams-orb' + fullPath: '/docs/self-hosting-unified-ams-orb' + preLoaderRoute: typeof DocsSelfHostingUnifiedAmsOrbRouteImport + parentRoute: typeof DocsRoute + } '/docs/self-hosting-operations': { id: '/docs/self-hosting-operations' path: '/self-hosting-operations' @@ -1303,6 +1320,7 @@ interface DocsRouteChildren { DocsSelfHostingGithubAppRoute: typeof DocsSelfHostingGithubAppRoute DocsSelfHostingOperationsRoute: typeof DocsSelfHostingOperationsRoute DocsSelfHostingQuickstartRoute: typeof DocsSelfHostingQuickstartRoute + DocsSelfHostingUnifiedAmsOrbRoute: typeof DocsSelfHostingUnifiedAmsOrbRoute DocsSelfHostingRagRoute: typeof DocsSelfHostingRagRoute DocsSelfHostingReesRoute: typeof DocsSelfHostingReesRoute DocsSelfHostingReesAnalyzersRoute: typeof DocsSelfHostingReesAnalyzersRoute @@ -1341,6 +1359,7 @@ const DocsRouteChildren: DocsRouteChildren = { DocsSelfHostingGithubAppRoute: DocsSelfHostingGithubAppRoute, DocsSelfHostingOperationsRoute: DocsSelfHostingOperationsRoute, DocsSelfHostingQuickstartRoute: DocsSelfHostingQuickstartRoute, + DocsSelfHostingUnifiedAmsOrbRoute: DocsSelfHostingUnifiedAmsOrbRoute, DocsSelfHostingRagRoute: DocsSelfHostingRagRoute, DocsSelfHostingReesRoute: DocsSelfHostingReesRoute, DocsSelfHostingReesAnalyzersRoute: DocsSelfHostingReesAnalyzersRoute, diff --git a/apps/loopover-ui/src/routes/docs.index.tsx b/apps/loopover-ui/src/routes/docs.index.tsx index 1ba6b3192d..eeb2461f0e 100644 --- a/apps/loopover-ui/src/routes/docs.index.tsx +++ b/apps/loopover-ui/src/routes/docs.index.tsx @@ -72,6 +72,7 @@ const AUDIENCES: Audience[] = [ primary: { to: "/docs/beta-onboarding", label: "Beta onboarding" }, links: [ { to: "/docs/maintainer-self-hosting", label: "Self-host reviews" }, + { to: "/docs/self-hosting-unified-ams-orb", label: "Unified ORB + AMS" }, { to: "/docs/self-hosting-docs-audit", label: "Self-host docs audit" }, { to: "/docs/maintainer-install-trust", label: "Install & trust guide" }, { to: "/docs/github-app", label: "GitHub App configuration" }, diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-unified-ams-orb.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-unified-ams-orb.tsx new file mode 100644 index 0000000000..43af1f6f77 --- /dev/null +++ b/apps/loopover-ui/src/routes/docs.self-hosting-unified-ams-orb.tsx @@ -0,0 +1,155 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; + +import { DocsPage } from "@/components/site/docs-page"; +import { Callout, CodeBlock, FeatureRow } from "@/components/site/primitives"; + +const DESCRIPTION = + "Run ORB (self-hosted PR review) and AMS (the autonomous miner) together on one host — shared state, aligned .env, and working AMS Grafana panels in fleet mode."; + +export const Route = createFileRoute("/docs/self-hosting-unified-ams-orb")({ + head: () => ({ + meta: [ + { title: "Unified ORB + AMS self-host — LoopOver docs" }, + { name: "description", content: DESCRIPTION }, + { property: "og:title", content: "Unified ORB + AMS self-host — LoopOver docs" }, + { property: "og:description", content: DESCRIPTION }, + { property: "og:url", content: "/docs/self-hosting-unified-ams-orb" }, + ], + links: [{ rel: "canonical", href: "/docs/self-hosting-unified-ams-orb" }], + }), + component: UnifiedAmsOrb, +}); + +function UnifiedAmsOrb() { + return ( + +

Two compose files are involved, and they are deliberately separate:

+ +

+ Standing both up on one host is just two docker compose invocations. The one + thing that needs care is where AMS keeps its SQLite state, because the ORB{" "} + ams-observability exporter reads that state from a host directory — and fleet + mode does not write there by default. +

+ +

1. Bring up ORB

+

+ Follow the Self-hosting quickstart to + configure ORB's .env and boot the stack. For this combined setup, enable both + the observability profile (Prometheus, Alertmanager, Loki, and Grafana) and the{" "} + ams-observability profile (the ams-reporting-exporter that feeds + AMS data into Grafana): +

+ + + Grafana itself ships under --profile observability; the AMS exporter that + populates its AMS datasources ships under --profile ams-observability. Enable + both, or the AMS panels stay empty even though the rest of the observability stack is up. + + +

2. Bring up AMS in fleet mode

+

+ Fleet mode reads credentials from an env file and runs the continuous worker loop. Build and + start it from the repo root: +

+ +

+ On its own this works — but the miner's SQLite ledgers now live in a Docker{" "} + named volume (miner-data), whose real host path is a + Docker-managed internal detail. The ORB exporter, meanwhile, reads the ledgers from a host + directory (default ~/.config/loopover-miner), so the two never line up on their + own and the Grafana AMS datasources stay silently empty. +

+ +

3. Bridge the state so AMS panels populate

+

+ The AMS package ships an opt-in override that relocates the fleet miner's{" "} + /data/miner state onto the same host directory the exporter reads — + using the same LOOPOVER_MINER_CONFIG_DIR variable and default, so there is no{" "} + docker volume inspect archaeology. Copy the example (it is gitignored) and run + all three compose files together with both profiles: +

+ +

+ The override bind-mounts /data/miner to{" "} + ${"{LOOPOVER_MINER_CONFIG_DIR:-~/.config/loopover-miner}"} — Compose merges by + container path, so this replaces the base file's miner-data{" "} + named-volume mount for the same target rather than adding a second one. The exporter's own + bind is the same source:{" "} + ${"{LOOPOVER_MINER_CONFIG_DIR:-~/.config/loopover-miner}"}:/ams-ledgers:ro. + Both sides now read one location. +

+ + Leave LOOPOVER_MINER_CONFIG_DIR unset on both to use the shared default, or set + it once so both the fleet miner and the ORB exporter follow it. If only one side sets it, + they diverge again and the AMS panels go empty — this is the mismatch the fleet-mode bridge + exists to close. + + +

4. Verify the AMS panels

+ + + This is the fleet-mode bridge that #5805 introduced; the AMS-deployment + reference in packages/loopover-miner/DEPLOYMENT.md ("Running fleet mode + alongside ORB's ams-observability profile") documents the same override for a + package-internal audience. Laptop-mode AMS already writes to{" "} + ~/.config/loopover-miner directly and needs no override — only fleet mode's + named volume does. + +
+ ); +}