jcoffey-dev e10289ee36 Grow the demo fleet to fifty hosts and a real Windows tier
Twelve hosts running six services read as somebody's side project. Fifty
hosts across twenty-one services read as an estate, which is what a
visitor is trying to see themselves in.

Thirty-one Linux, eighteen Windows, one Linux host whose agent is gone.
The proportions are the point: Windows now carries Active Directory, IIS,
SQL Server, Exchange, file shares, Remote Desktop, print, WSUS and SCCM
rather than appearing as a Security channel on one box. Linux gains a
load-balancer tier, an outbound proxy, MySQL beside Postgres, RabbitMQ,
Elasticsearch, three Kubernetes nodes, CI, Vault, OpenLDAP, BIND and a
backup server.

Fifteen new generators, each writing what the real daemon writes --
HAProxy's timing quintuple, MySQL slow-query blocks, W3C extended format
for IIS, kubelet PLEG lines, BIND query logging with NXDOMAIN, Squid's
TCP_DENIED, SQL Server deadlock and I/O-stall messages -- with the
structured fields carried in attributes so both halves of the query
language have something to work on.

Three things found while doing it, each of which would have shipped as a
quiet wrongness:

linuxHosts() decided Windows by `service == "eventlog"`. That held while
eventlog was the only Windows role; with IIS and SQL Server on Windows it
would have given every one of them a journald system stream -- sshd and
UFW lines on a Windows box. It now decides from `os`.

worker-02 lost its filling disk when the fleet was rewritten, which is
the story worker-disk-filling thresholds on. Restored at its original
rate, with a comment saying why it cannot move.

Five dashboard panels were written as `timechart`, which this query
language does not have -- its stages are where/stats/sort/fields/head/
tail. They are raw ClickHouse SQL now, which the reference recommends for
exactly this, and a `pie` panel was dropped before it shipped because the
web's VizType union has no such member even though the API accepts one.

Five new dashboards: platform/Kubernetes, directory and DNS, messaging
and search, the Windows server estate, and edge/proxy. Every panel was
checked against the language's real stage list and the web's real viz
types.

Volume roughly quadruples: about 316 records/minute at rate-scale 1, and
1.9M per nightly reset at the demo's own settings against about 0.5M
before. ClickHouse will not notice; reset time and disk on the demo box
might, so the README says so and names RATE_SCALE as the lever.
2026-09-04 15:40:08 -07:00
2026-09-03 11:12:35 -07:00
2026-08-21 20:53:32 -07:00
2026-09-03 11:12:35 -07:00
2026-09-03 11:12:35 -07:00
2026-09-03 11:12:35 -07:00
2026-09-03 11:12:35 -07:00
2026-09-03 11:12:35 -07:00

Cairn OBS

Open-core, Kubernetes-native log aggregation and observability.
Built to match Splunk on capability while winning on cost-per-GB,
with honest multi-tenant RBAC and a modern language stack.

Licensed AGPLv3 in its entirety — including enterprise/. See Licensing.

What it does

Logs flow from a statically-linked Rust edge agent through Redpanda into a Go ingest pipeline, landing in ClickHouse for analytics and Tantivy for full-text search. One query language spans both stores, compiling to a single execution plan:

service=api | where status>=500 | stats count by host | sort -count
message:"connection refused" | stats count by host

Raw ClickHouse SQL stays available as an escape hatch and compiles to the same IR, so performance doesn't depend on which syntax you write.

On top of that sit dashboards, an alerting evaluator with threshold and absence rules, a CLI (cairnobsctl), a Terraform provider, and AI-assisted query authoring that runs against a self-hosted Ollama model by default — no cloud dependency.

Architecture

Component Stack
Edge agent Rust, musl static target
Transport Redpanda (Kafka API)
Ingest / parse Go
Analytical store ClickHouse
Full-text index Tantivy (Rust)
Control plane / API Go, gRPC + REST gateway
Control-plane metadata PostgreSQL
Frontend SvelteKit + TypeScript
Deployment Kubernetes Operator (kubebuilder), Helm, docker-compose

PostgreSQL is scoped strictly to control-plane config — dashboards, panels, alert rules and state, notification targets, delivery log — because those need row-level locking and transactional read-modify-write that ClickHouse's MergeTree family doesn't provide. Log data itself never touches it.

Full spec: docs/architecture.md. Read it before changing any component; the storage/query split in particular is deliberate.

Repository layout

Monorepo, one top-level directory per component, each with its own README.md, unit tests, and Dockerfile.

agent/      Rust edge agent (Linux + Windows)
transport/  Redpanda topics and schemas
ingest/     Go ingest and parse pipeline
storage/    ClickHouse schema and migrations
search/     Tantivy full-text index service
api/        Control plane, query compiler, RBAC
alerting/   Rule evaluator and notification delivery
metadata/   PostgreSQL schema and migrations
web/        SvelteKit frontend
cli/        cairnobsctl
proto/      gRPC service definitions
terraform/  Terraform provider
enterprise/ SSO, multi-tenancy, per-tenant provisioning
deploy/     Helm charts and Kubernetes operator
docs/       Architecture, design docs, per-phase runbooks
hack/       Development scripts

enterprise/ stays a separate module that core never imports from. Since the Phase 6 relicensing that boundary is architectural rather than legal — it keeps core buildable and deployable standalone, and keeps tenant resolution server-side.

Running locally

docker compose up

The web UI comes up on http://localhost:3000 and the API on :8080; alerting is on :8081 and enterprise auth on :8082. The agent connects to ingest over mTLS gRPC on :4317. search is reachable only on the compose network — it publishes no host port.

COMPOSE_PROFILES in .env selects the query-serving binary — single-tenant (default) or enterprise for the multi-tenant path. They're mutually exclusive, the same choice Helm's enterprise.enabled flag makes for a real cluster. Override per invocation:

COMPOSE_PROFILES=enterprise docker compose up

Kubernetes deployment via the Helm chart in deploy/.

Status

Built in phases; each has a runbook in docs/ recording how it was verified. Full per-phase detail is in docs/status.md.

Phase Scope Status
0 Agent → Redpanda → ingest → ClickHouse, queryable end-to-end Shipped
1 Windows Event Log + journald, SQL and full-text paths Shipped
2 Unified query language across both stores Shipped
3 Dashboards, alert rules, notification delivery Shipped
4 RBAC, tenant isolation, audit logging, per-tenant ClickHouse Shipped
5 Frontend redesign and design system Shipped
6 License compliance audit and remediation Shipped
7 AI-assisted query authoring Shipped

Phase 4 is shipped, and the environment that proved it is gone. Every control the phase defines was verified against real infrastructure at least once — a docker-compose stack with real ClickHouse and Postgres, a local kind cluster, and both SSO protocols against a real Auth0 tenant — finding eight bugs that no amount of Docker-free testing could have caught. The prototype VPS was retired on 2026-09-04, so that verification is a record rather than something you can re-run: see docs/phase-4-runbook.md.

Two limits worth stating plainly. SSO has been tried against one IdP, not two, and no production-grade cluster has run this. And demo.cairnobs.org is not evidence for any of it — the demo runs the single-tenant profile, so it exercises the OSS path and says nothing about RBAC or tenant isolation.

The Windows agent code (EvtSubscribe, ETW, service registration) has never run on real Windows — no Windows toolchain existed in the build environment. ETW additionally sits behind a feature flag, since it needs elevated privileges. Details in agent/README.md.

Terraform provider coverage is partial by necessity: dashboards and panels have full CRUD, while alert rules and notification targets are create/destroy only, because alerting exposes no PUT /rules/{id} or PUT /targets/{id} to update against. Tenant and RBAC resources are disclosed future work — terraform/README.md accounts for exactly what exists.

Contributing

  • Conventional commits. Every change should be a logically complete, independently revertible unit.
  • Rust: cargo clippy --all-targets -- -D warnings must pass.
  • Go: go vet and golangci-lint, no globals for shared state.
  • Every UI action must map to a documented REST/gRPC call — no UI-only logic. The CLI and Terraform provider are first-class, not afterthoughts.
  • Prefer boring, well-understood dependencies. This is infrastructure software; operators need to trust it.

Licensing

Copyright (C) 2026 Coffey Labs.

AGPLv3, no exceptions — see LICENSE. enterprise/ was under a commercial-license stub from Phase 4 through Phase 5; Phase 6 relicensed it to match core. The full record and its business-model consequences are in docs/compliance/license-audit-report.md.

The default AI deployment uses qwen2.5-coder (Apache-2.0) via Ollama, chosen specifically to keep that license purity intact. The cloud adapter is pluggable, opt-in, and off by default.

S
Description
Imported from github.com during the 2026-09-20 standup (local dir: cairnobs)
Readme AGPL-3.0
2.1 MiB
Languages
Go 72.5%
Svelte 12.1%
Rust 8%
TypeScript 4%
Shell 1.3%
Other 2%