Kataster connects an infrastructure inventory with customer ownership, provider costs, contracts, and billing. It is built for a small hosting operation: domains, DNS, mail, hosts, containers, cloud projects, and certificates in one register.
Connectors discover and enrich resources. The operator can see who owns them,
what they cost, which checks need attention, and whether a customer is ready to
be invoiced. The interface is German. The name comes from the German land
register; LFIO_ environment variables retain the former product's compatibility
prefix.
MIT licensed. Public source, private deployments. This repository contains no production inventory, customer database, or provider credentials. See the publication boundary.
- Discover: AWS, SSM, Mailcow, Hetzner Cloud, Railway, WordPress, Checkmk, and HTTP connectors feed inventory or health checks.
- Assign: give each resource an owner, retain its lifecycle, and record ownership changes instead of losing history when infrastructure disappears.
- Account: allocate provider cost pools to resources, maintain contract positions, and see customer margin separately from internal overhead.
- Invoice: check billing readiness, create invoice snapshots, and keep the original lines and recipient details fixed after creation.
The background worker handles checks and synchronization. The German operator interface keeps inventory, operational attention, costs, and billing connected. MCP offers scoped access to the same register, with read-only tokens by default.
These are captures of the application, not design mockups. Every customer, domain, email address, and amount is fictional. The demo has 8 customers, 32 resources, EUR 284 in monthly provider costs, and EUR 772 in contract charges. These are demonstration figures, not production totals or capacity claims.
The screenshot demo deliberately runs without a worker or external provider connections. Its worker and stale-check warnings show the real application behavior when monitoring is not running.
See the reproducible demo for data and capture boundaries.
TanStack Start (Vite) + Router + Query + Form, Tailwind v4 + shadcn/ui, oRPC, better-auth, Drizzle ORM + PostgreSQL, BullMQ + Redis, Bun. Biome + Vitest. Deployed on Railway.
- Connectors declare configuration and health or discovery behavior under
src/server/connectors/. The registry includes AWS, SSM, Mailcow, Hetzner Cloud, Railway, WordPress, Checkmk, and HTTP. - Connections hold encrypted credentials (AES-256-GCM). Configure an API key once, reference it from many assets.
- Resources are Configuration Items with an owner, a lifecycle status, and an audit trail. Connectors discover and enrich them; nothing is deleted, stale objects are decommissioned.
- Customers are CRM records with contacts, billing fields, and contract positions. Cost is attributed per resource and compared with what each customer pays.
- A worker process runs scheduled checks via BullMQ and writes results to Postgres.
Requires Bun, a PostgreSQL database, and Redis.
bun install
cp .env.example .env # fill in DATABASE_URL, REDIS_URL, secrets
bun run icons # generate the favicon set
bun run db:migrate:prod # apply the committed migrations
bun run dev # web app on http://localhost:3000
bun run worker # background check worker (separate terminal)Public registration is disabled. Provision a trusted operator from a terminal
using the same DATABASE_URL, with a password of 12 to 128 characters supplied
on stdin:
bash -c 'read -rsp "Operator password: " taskPassword; printf "%s" "$taskPassword"' \
| bun run user:create [email protected] --name='Operator'The command creates a new account transactionally and refuses to overwrite an existing account. It never prints the password. Every provisioned operator can access the entire register: this is a single-organization tool, not a customer portal with tenant isolation. Provision accounts only for trusted operators.
See .env.example. LFIO_ENCRYPTION_KEY must be a stable 32-byte base64 key. AWS and other API credentials are never set via env. They are configured in the app and stored encrypted.
POST /api/mcp is a stateless MCP (Model Context Protocol) server for inventory, customers, margin, billing readiness, and incidents. Tokens are read-only by default. Write-scoped tokens can assign resource owners and change pricing or provider costs, with every write attempt recorded in the MCP audit log.
Generate a token in the app under Settings → MCP access tokens (shown once), then connect:
claude mcp add kataster --transport http https://<your-app>/api/mcp \
--header "Authorization: Bearer <token>"LFIO_MCP_TOKEN still works as an optional read-only env fallback. Set LFIO_MCP_ENV_ACCESS=write only when that token requires mutation tools. Tools: overview, list_customers, list_resources, margin_summary, billing_readiness, customer_statements, list_contract_positions, list_incidents, assign_resource_owner, apply_owner_suggestions, upsert_contract_position, set_customer_price (one-off adjustment), upsert_provider_cost.
Railway, via the multi-stage Dockerfile. The app and worker run the same image with different start commands. Postgres and Redis are Railway services wired in with reference variables. Per-service start, migration, and healthcheck settings are configured in Railway because the shared image has different web and worker requirements.
The web service runs bun run server.ts, migrates with
bun run db:migrate:prod before deployment, and uses /api/health. The worker
runs bun run src/worker/index.ts. Preserve existing database volumes and the
encryption key during upgrades; replacing the key makes stored credentials
unreadable.
bun run testFor a complete source check, build first to generate the route tree, then run
bun run lint, bun run typecheck, bun run test, and bunx drizzle-kit check.
CI also builds the production Docker image and scans Git history for secrets.


