6.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
LabGraph is a HomeLab infrastructure documentation engine. It maps physical hardware to virtual services using a graph model (Nodes + Edges). The stack is Django + Celery on the backend, Next.js 14 + DaisyUI on the frontend, all orchestrated via Docker Compose.
Running the Stack
# Start everything (from repo root)
docker compose up -d
# Include Flower (Celery monitoring at localhost:5555)
docker compose --profile dev-tools up -d
# View logs
docker compose logs -f web
docker compose logs -f celery-worker
# Rebuild after Dockerfile or requirements.txt changes
docker compose build
docker compose up -d
The web service runs backend/scripts/entrypoint.sh which runs migrate → collectstatic → gunicorn.
Backend Commands (inside container)
# Run migrations
docker compose exec web python manage.py migrate
# Create superuser
docker compose exec web python manage.py createsuperuser
# Make migrations after model changes
docker compose exec web python manage.py makemigrations
# Open Django shell
docker compose exec web python manage.py shell
# Inspect Celery workers
docker compose exec celery-worker celery -A config.celery inspect ping
docker compose exec celery-worker celery -A config.celery inspect active
Frontend Commands
cd frontend
npm run dev # Dev server at localhost:3000
npm run type-check # TypeScript check (tsc --noEmit)
npm run lint # ESLint
npm run build # Production build
Environment Setup
Copy .env.example to .env (repo root). Copy frontend/.env.local.example to frontend/.env.local.
Required variables that have no defaults and will crash on startup if missing:
SECRET_KEY— Django secret key (python3 -c "import secrets; print(secrets.token_urlsafe(50))")POSTGRES_PASSWORD— PostgreSQL password (used directly by docker-compose)REDIS_PASSWORD— Redis password (used directly by docker-compose)NEXTAUTH_SECRET— NextAuth JWT signing key (openssl rand -base64 32)
Critical: DATABASE_URL, REDIS_URL, and CELERY_BROKER_URL must use the Docker service hostnames db and redis, not localhost. The Redis URL format is redis://:PASSWORD@redis:6379/0 (colon before password, no username).
Architecture
Backend (backend/)
Settings are split into config/settings/base.py (shared) → config/settings/development.py / production.py. Settings are loaded via django-environ from the root .env file (BASE_DIR.parent / ".env"). Never import base directly — always use development or production.
Celery is configured in config/celery.py and referenced by docker-compose as -A config.celery. Three queues: discovery (nmap/Proxmox scans), heartbeat (ICMP/TCP liveness), default (maintenance). Beat uses DatabaseScheduler, so periodic tasks are stored in the DB and can be edited at runtime via admin.
Graph model in apps/core/models.py:
Node— any infrastructure component (location → hardware → hypervisor → VM/container → application). All have UUID PKs, anode_typeTextChoices enum,status, optionalip_address, and ametadataJSONField for per-type flexible data.Edge— directed source→target relationship withedge_type(parent_child, network, dependency, physical). Unique constraint on(source, target, edge_type).Network— VLAN/subnet records (separate from Nodes).WikiPage— OneToOne to Node, stores raw Markdown;rendered_html()converts via theMarkdownpackage.HeartbeatLog— high-volume time-series; pruned nightly bytasks/maintenance.py.
Input validation uses Pydantic v2 schemas (apps/core/schemas.py) at the API boundary before data reaches the ORM. DRF serializers (to be added in Phase 2) handle output.
Celery tasks live in tasks/ (not inside an app). Phase 1 stubs are in tasks/discovery.py, tasks/heartbeat.py, tasks/maintenance.py. Autodiscovery is configured for the tasks package.
RLS: apps/core/apps.py connects a post_migrate signal that enables PostgreSQL Row-Level Security on core_node. The handler checks information_schema.tables first — it fires after every app's migrations, not just after core.
Health check at GET /api/health/ (no auth required) checks DB (SELECT 1) and Redis (cache round-trip). Returns {"status": "ok"|"degraded", "services": {...}, "version": "1.0.0"}. Used by docker-compose healthcheck.
OpenAPI schema at GET /api/schema/, Swagger UI at GET /api/docs/.
Frontend (frontend/)
Next.js 14 App Router. TypeScript strict mode with noUncheckedIndexedAccess and exactOptionalPropertyTypes — avoid any.
Auth flow: NextAuth (src/lib/auth.ts) uses CredentialsProvider. In development, any non-empty credentials are accepted (stub). Phase 2 wires authorize() to POST /api/auth/login/ on the Django backend. NEXTAUTH_SECRET must be stable in .env.local — changing it invalidates all existing sessions.
API client (src/lib/api.ts): all backend calls go through apiFetch() which validates responses against Zod schemas before returning. Throws ApiError on non-2xx, ZodError on schema mismatch.
Types (src/types/index.ts): Zod schemas are the source of truth; TypeScript types are inferred with z.infer<>. The schemas mirror the Django models exactly — keep them in sync when models change.
Routing: next.config.mjs proxies /backend/* → Django. Route groups: (auth) for login, (dashboard) for protected pages. Auth guard is done server-side with getServerSession(authOptions) + redirect().
Styling: Tailwind CSS + DaisyUI. Theme is set via html[data-theme] in layout.tsx. DaisyUI component classes (e.g. btn, card, alert) — no custom CSS unless DaisyUI can't cover it.
Phase Status
- Phase 1 ✅ Complete — Docker stack, Django models, Celery stubs, Next.js scaffold
- Phase 2 🔄 In Progress — DRF ViewSets, discovery scrapers (Proxmox/nmap), heartbeat (icmplib), Django auth endpoint
- Phase 3 ⏳ Pending — Nested inventory list, React Flow topology graph, Markdown wiki editor
Key Constraints
- The
requirements.txtlives inbackend/(Docker build context is./backend). next.config.tsis not supported in Next.js 14 — usenext.config.mjs.- YAML
>folded scalars in docker-compose do NOT fold newlines on more-indented continuation lines — use a shell script instead of multi-linesh -ccommands. HeartbeatLogis high-volume — always filter bynodeand use the(node, -timestamp)index; never do full-table scans.Node.metadatais a free-form JSONField — validate its contents with the Pydantic schemas inapps/core/schemas.pybefore writing.