The runbook opened with `ALTER USER default IDENTIFIED BY ...` and claimed the image entrypoint reads CLICKHOUSE_PASSWORD only when it initialises an empty data volume. Both claims were wrong, and following them would have failed at the first command. `SELECT name, storage FROM system.users` reports `default | users_xml`: the user is defined by a config file, and ClickHouse rejects ALTER USER against that storage. There is no in-database password to change. The password lives in /etc/clickhouse-server/users.d/default-user.xml, which the entrypoint regenerates on *every* container start -- that path is in the container filesystem, and only /var/lib/clickhouse is a volume. The file's mtime matches the container's StartedAt to the second, before and after a rotation. So the env var is the source of truth and recreating the container is what applies it -- close to the inverse of what was written. /opt/sentry's own docker-compose.override.yml header had recorded the correct mechanism during the 2026-08-19 rotation. The runbook was drafted without reading it. Noted at the top of the new Verification status section, since a runbook that was wrong once should say so. Procedure rewritten around what was actually executed against proto on 2026-08-23 and verified: back up first, generate hex on the box so the value never reaches a terminal or transcript, sed only indented KEY: value lines so the header comment is untouched, `docker compose config -q` before touching anything live, then recreate. Verification now checks that the OLD password is rejected -- read from the backup rather than retyped -- not merely that the new one works. Rollback rewritten to restore the backup, since there is no ALTER to reverse. Every shell block syntax-checked with bash -n.
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 | In progress |
| 5 | Frontend redesign and design system | Shipped |
| 6 | License compliance audit and remediation | Shipped |
| 7 | AI-assisted query authoring | Shipped |
Phase 4 is not shipped. The code is built and tested, but the environment
lost Docker and database access partway through, so only the audit-logging
guarantees were confirmed against a live database. The rest compiles and skips
cleanly when no live database is configured, but is otherwise unverified — see
the verification-status section of
docs/phase-4-runbook.md.
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 warningsmust pass. - Go:
go vetandgolangci-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
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.