# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Additional Instructions - personal overrides: @~/.claude/CLAUDE.md ## Commands ```bash # Build go build -o crowdsec-dashy ./cmd/server # Production build (static binary, stripped) CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o crowdsec-dashy ./cmd/server # Run ./crowdsec-dashy # Lint / vet go vet ./... # Tests (none yet — project is in early build phases) go test ./... # Docker docker compose up -d docker compose build ``` ## Architecture **Go 1.26. Zero external Go dependencies.** Stdlib only (`net/http`, `html/template`, `os/exec`, `encoding/json`). ### Data access — two parallel layers | Layer | Package | Coverage | |---|---|---| | CrowdSec LAPI (REST) | `internal/crowdsec/lapi.go` | Decisions CRUD, Alerts, health | | `cscli` exec wrapper | `internal/crowdsec/cli.go` | Bouncers, Machines, Hub, Metrics | All shared structs (Decision, Alert, Bouncer, Machine, HubItem, MetricsSection, etc.) live in `internal/crowdsec/types.go`. Bouncers, machines, hub, and metrics are **not** exposed via REST — they require `cscli` hitting the database directly. Features that need `cscli` check `Deps.CLIAvailable` and render an inline warning banner when the binary is absent. ### Package layout ``` cmd/server/main.go entrypoint — loads config, authenticates LAPI, starts HTTP server internal/config/config.go env-var loading; CscliAvailable() stat-checks the binary path internal/crowdsec/ types.go all shared structs lapi.go REST client (JWT auth, auto-retry on 401) cli.go cscli exec wrapper (strict allow-list) internal/handlers/ renderer.go Renderer, PageData, Deps, SidebarNav dashboard.go / api.go / ... one file per page + JSON API handler internal/middleware/middleware.go BasicAuth, Logger, SecureHeaders, Recovery internal/router/router.go constructs Deps, wires all routes web/templates/layouts/base.html sidebar shell web/templates/pages/*.html one file per page web/static/css/app.css all component styles web/static/js/dashboard.js live stat polling + sparklines web/static/js/tables.js client-side filter/sort/bulk-select ``` ### Request flow ``` HTTP request → middleware chain (BasicAuth → Logger → SecureHeaders → Recovery) → router.go (stdlib mux) → handler struct (receives Deps: Renderer, LAPI, CLI, CLIAvailable, PollInterval) → handler renders to bytes.Buffer first, then writes to ResponseWriter ``` ### Dependency injection `Deps` struct (`internal/handlers/renderer.go:246`) is constructed once in `router.New()` and passed to every handler constructor. Handlers are structs, not functions. ### Template system `Renderer` (`internal/handlers/renderer.go`) parses all page templates at startup: each page in `web/templates/pages/*.html` is combined with `web/templates/layouts/base.html` into a named `*template.Template`. Templates are keyed by filename without extension (e.g. `"dashboard"`, `"decisions"`). All render calls go through `Renderer.Render(w, name, data)` using the buffer-then-write pattern. Template functions available: `inc`, `dec`, `dict`, `decisionBadgeClass`, `originBadgeClass`, `hubStatusClass`, `safeHTML`, `boolIcon`, `truncate`, `join`. ### LAPI authentication On startup, `lapi.Login()` POSTs to `/v1/watchers/login` → JWT stored in memory. All subsequent requests carry `Authorization: Bearer `. On 401, `doJSON()` auto-retries with a fresh login once before failing. ### `cscli` exec security All args passed as a slice (never through shell). Two allow-lists enforced before `exec.CommandContext`: - `allowedSubcommands` (first arg): bouncers, machines, collections, parsers, scenarios, postoverflows, hub, metrics, version - `allowedActions` (second arg): list, add, delete, install, remove, update, upgrade, validate - All other user-supplied values validated against `safeArg` regex `^[a-zA-Z0-9_./:@\-]+$` ### POST pattern All state-changing POST handlers use PRG (Post/Redirect/Get). Flash messages are attached to `PageData` via `WithFlash(type, msg)` and displayed on the redirected GET. All POST handlers must apply `http.MaxBytesReader` before parsing the body. ### Internal JSON API Used by frontend JS — all return JSON, protected by the same BasicAuth middleware. | Method | Path | Description | |---|---|---| | GET | `/api/v1/stats` | Dashboard summary counts | | GET | `/api/v1/health` | LAPI health + cscli availability | ### Adding a new page 1. `internal/handlers/mypage.go` — handler struct with `ServeHTTP` or named methods 2. `web/templates/pages/mypage.html` — starts with `{{template "base" .}}` 3. Add `NavItem` to `SidebarNav` in `renderer.go` 4. `router.go` — `mux.Handle("/mypage", handlers.NewMyPageHandler(deps))` ### UI design Dark industrial aesthetic. Palette: near-black surface `#080b10`, blue-grey panels `#0f1520`, cyan accent `#00d4ff`, threat-red `#ff3b3b`, safe-green `#00e676`. Fonts: `JetBrains Mono` for data, `IBM Plex Sans` for labels. All styles in `web/static/css/app.css`. No external JS frameworks — sparklines drawn with inline SVG ``. Dashboard live stats poll `/api/v1/stats` via `web/static/js/dashboard.js`. Client-side table filter/sort/bulk-select in `web/static/js/tables.js`. ### Environment variables | Variable | Default | Notes | |---|---|---| | `PORT` | `:8080` | Listen address | | `CROWDSEC_API_URL` | `http://localhost:8080` | LAPI base URL | | `CROWDSEC_API_LOGIN` | *(required)* | Machine login | | `CROWDSEC_API_PASSWORD` | *(required)* | Machine password | | `CSCLI_PATH` | `/usr/local/bin/cscli` | Binary path; CLI features disabled if absent | | `UI_USERNAME` | `admin` | Basic Auth username | | `UI_PASSWORD` | `changeme` | Basic Auth password | | `UI_SESSION_SECRET` | *(required, ≥32 chars)* | HMAC signing key | | `POLL_INTERVAL_SEC` | `15` | Dashboard poll interval (seconds) |