# RackPeek — Agent Guide This document is the entry point for AI agents (Claude Code, OpenCode, etc.) working in this repo. It captures everything needed to understand the codebase, make a focused change, validate it, and open a PR without re-reading the whole tree. --- ## 1. What RackPeek is RackPeek is a **CLI + Web UI for documenting and managing home-lab / small-scale IT infrastructure** (servers, switches, routers, firewalls, access points, UPS units, desktops, laptops, systems, and services). - All state is persisted to a **single YAML file** (`config/config.yaml`) — no database. - Same domain code powers the CLI binary (`rpk`) and the Blazor Server Web UI. - Distributed as a Docker image (`aptacode/rackpeek`) and a self-contained CLI binary. - Live demo: · Docs: ### Core values (these shape design decisions) - **Simplicity** — narrow scope, no enterprise CMDB features. - **Openness** — open YAML format, user owns their data. - **Privacy** — no telemetry, no tracking. - **Dogfooding** — features must be useful to real home-labs. - **Opinionated** — built for home labs, not corporate documentation. If a proposed change conflicts with these values, push back before implementing. --- ## 2. Tech stack | Layer | Tech | |---|---| | Language | C# (.NET **10.0**, `net10.0` TFM) | | CLI | [Spectre.Console.Cli](https://spectreconsole.net/) | | Web UI | Blazor Server (`Microsoft.NET.Sdk.Web`) + a WASM viewer (`RackPeek.Web.Viewer`) for the live demo | | Persistence | YAML (`YamlDotNet`, `DocMigrator.Yaml`) — single file | | Git integration | `LibGit2Sharp` (optional, used when `GIT_TOKEN` is set) | | CLI tests | xUnit + `Spectre.Console.Testing` + `JsonSchema.Net` | | E2E tests | xUnit + `Microsoft.Playwright` + `Testcontainers` (spins up the real Docker image) | | Build runner | [`just`](https://github.com/casey/just) | | Container | `mcr.microsoft.com/dotnet/aspnet:10.0` — exposes port 8080 | | Code style | `dotnet format` (CI gate) + `.editorconfig` | | Analysis | `TreatWarningsAsErrors=true`, `EnforceCodeStyleInBuild=true`, latest analyzers (see `Directory.Build.props`) | **.NET 10 is required.** If `dotnet --version` shows < 10, see `docs/development/dev-setup.md`. A devcontainer is included (`.devcontainer/`). --- ## 3. Solution layout ``` RackPeek.sln ├── RackPeek/ CLI entry point (Spectre.Console.Cli) → produces `rpk` ├── RackPeek.Domain/ Core domain: resources, use-cases, persistence, git │ ├── Resources/ Resource models (Server, Switch, System, Service, …) │ ├── UseCases/ Generic use-cases (Add, Delete, Rename, Cpus, Drives, Gpus, Ports, Labels, Tags, Ansible, SSH, Hosts) │ ├── Persistence/ IResourceCollection, Yaml repositories, migrations │ │ └── Yaml/ YamlResourceCollection, RackPeekConfigMigrationDeserializer, ResourceYamlMigrationService │ ├── Git/ Optional LibGit2Sharp integration (NullGitRepository when disabled) │ ├── Api/ InventoryRequest/Response + UpsertInventoryUseCase (used by Web API) │ └── ServiceCollectionExtensions.cs DI: AddUseCases / AddYamlRepos / AddGitServices ├── Shared.Rcl/ Razor Class Library: Blazor components AND CLI command wiring shared between Web + CLI │ ├── Commands/ Spectre.Console.Cli command classes (one folder per resource kind) │ ├── Components/ Shared Razor components │ ├── Modals/, Layout/, Services/, Console/ │ ├── CliBootstrap.cs Registers all CLI commands + DI internals (single source of truth for the `rpk` command tree) │ └── ConsoleRunner.cs Lets the Web UI execute CLI commands in-process ├── RackPeek.Web/ Blazor Server host (Dockerfile lives here) ├── RackPeek.Web.Viewer/ Blazor WebAssembly viewer (powers the github-pages demo) ├── Tests/ CLI integration tests (xUnit) — fast, no Docker │ ├── EndToEnd/ Per-resource workflow tests using the real CLI │ ├── Api/ Web API endpoint tests (Microsoft.AspNetCore.Mvc.Testing) │ ├── TestConfigs/v1,v2,v3/ Fixture YAML files for migration tests │ └── schemas/ JSON schemas validated against output └── Tests.E2e/ Playwright + Testcontainers — spins up the Docker image and drives the Web UI ├── PageObjectModels/ One POM per page/component (required pattern) └── Infra/PlaywrightFixture.cs Container + browser lifecycle ``` ### Where to put new code | You're adding… | Goes in… | |---|---| | A new CLI subcommand | `Shared.Rcl/Commands//…` + register it in `Shared.Rcl/CliBootstrap.cs` | | A new resource kind | `RackPeek.Domain/Resources//` model, register in `Resource.cs` maps, add YAML migration, wire repos in `ServiceCollectionExtensions.cs`, add Razor pages under `Shared.Rcl//`, add Web routing | | A new use-case | `RackPeek.Domain/UseCases/` — implement `IUseCase` (auto-registered by reflection in `AddUseCases`) or the generic `IResourceUseCase` | | A new Razor component used by CLI+Web | `Shared.Rcl/Components/` | | A new Web page only | `RackPeek.Web/Components/` | | A YAML schema change | Bump schema version under `schemas/vN/` + add migration in `RackPeek.Domain/Persistence/Yaml/` + add migration test in `Tests/TestConfigs/vN/` | --- ## 4. Build, test, run All workflow commands go through `justfile`. Prefer `just ` over running `dotnet` directly so behaviour stays consistent with CI. ### Build ```bash just build # dotnet build RackPeek.sln (Debug) just build-release # Release just build-cli # publish self-contained single-file binary (default linux-x64) just build-cli linux-arm64 # cross-target just build-web # docker build -t rackpeek:ci -f RackPeek.Web/Dockerfile . ``` ### Test ```bash just test-cli # fast CLI tests, no Docker required just e2e-setup # ONCE: installs Playwright CLI + browsers just test-e2e # implies build-web; runs Playwright suite just test-all # = build-web + e2e-setup + test-cli + test-e2e just ci # alias for test-all — matches the pre-PR checklist ``` CI order (`.github/workflows/test.yml`): 1. **`format`** → `dotnet format --verify-no-changes` (runs on `ubuntu-latest`) 2. **`cli-tests`** → `dotnet test Tests` (runs on `ubuntu-latest`, depends on format) 3. **`webui-tests`** → builds the docker image then runs `dotnet test Tests.E2e` (runs on `ubuntu-24.04`, depends on cli-tests) Always run `dotnet format` before commit — formatting breaks CI first. ### Run ```bash just run-docker # build + run container on http://localhost:8080 just rpk [args] # run CLI directly from Debug build just clean # dotnet clean ``` ### Release ```bash just docker-push 1.3.2 # multi-arch (linux/amd64, linux/arm64) push to aptacode/rackpeek ``` CLI binary version is bumped in `RackPeek/RackPeek.csproj` (``). ### Demos (rarely needed by agents) ```bash just build-cli-demo # VHS recording — needs vhs, imagemagick, chrome just build-web-demo # GIF capture — needs Chrome, ImageMagick ``` --- ## 5. Code style Enforced by CI via `dotnet format --verify-no-changes`. From `.editorconfig` + `Directory.Build.props`: - 4-space indent, LF line endings, final newline, UTF-8. - `var` for built-in types and when the type is apparent; explicit type otherwise. - Expression-bodied members only when on a single line. - Private fields are `_camelCase` (underscore prefix, error severity). - Open braces on a new line (Allman) — `csharp_new_line_before_open_brace = all:error`. - **Warnings are errors** repo-wide. Don't introduce nullable warnings or analyzer warnings. - Nullable reference types enabled in every project (`enable`). Default to writing no comments. The project favours readable names + tests-as-documentation. --- ## 6. Persistence model (important) There is **one YAML file**: `config/config.yaml` (or the path given by `RPK_YAML_DIR` env var; the Docker image sets it to `/app/config`). Top-level shape: ```yaml resources: - kind: Server | Switch | Firewall | Router | Accesspoint | Desktop | Laptop | Ups | System | Service name: tags: [...] labels: { key: value } notes: | free-form markdown runsOn: [, ...] # only meaningful for System / Service # kind-specific fields follow (e.g. ports[], cpus[], drives[], gpus[], nics[], network, ram, …) ``` Key invariants (see `RackPeek.Domain/Resources/Resource.cs`): - `name` is the identity within a `kind`. Don't introduce numeric IDs. - `runsOn` relationships are validated by `Resource.CanRunOn`: - `Service` may run on a `System`. - `System` may run on hardware (`Server`, `Switch`, `Firewall`, `Router`, `Accesspoint`, `Desktop`, `Laptop`, `Ups`) or on another `System`. - "Hardware" is the umbrella term for the eight physical kinds above (`Resource.IsHardware`). - Anything that mutates the YAML must go through an `IResourceUseCase` → `IResourceCollection` → repository, never direct file writes. ### YAML migrations Schemas are versioned under `schemas/v1`, `schemas/v2`, `schemas/v3`. Migration code lives in `RackPeek.Domain/Persistence/Yaml/`: - `RackPeekConfigMigrationDeserializer.cs` — deserialisation entry point - `ResourceYamlMigrationService.cs` — applies the version chain When you change persisted YAML shape, the PR **must** include: 1. A new `schemas/vN+1/schema.vN+1.json`. 2. A forward migration that reads vN and emits vN+1. 3. Test fixtures under `Tests/TestConfigs/vN+1/` (note the explicit `` entries in `Tests/Tests.csproj` if you add new files). 4. Backwards compatibility for at least vN, OR a clearly documented breaking change. --- ## 7. CLI surface The full command tree is documented in `docs/Commands.md` and `docs/CommandIndex.md` (auto-generated by `generate-docs.sh`). At a glance: ``` rpk [name] [flags] kinds: summary, servers, switches, routers, firewalls, systems, accesspoints, ups, desktops, laptops, services verbs: summary, add, list, get, describe, set, del, tree sub: cpu, drive, gpu, nic, port, subnets, labels, tags, rename, … ``` When adding/altering commands, regenerate the docs (`./generate-docs.sh`) so the published reference stays in sync. --- ## 8. Environment variables | Var | Default | Purpose | |---|---|---| | `RPK_YAML_DIR` | `config` (CLI) / `/app/config` (Docker) | Directory containing `config.yaml` | | `GIT_TOKEN` | unset | If set, enables `LibGit2GitRepository` for the config dir | | `GIT_USERNAME` | `git` | Username paired with `GIT_TOKEN` | | `ASPNETCORE_URLS` | `http://+:8080` (Docker) | Web UI bind | --- ## 9. Testing principles Read `docs/development/testing-guidelines.md` in full before touching tests. Highlights: - **Test at the edges.** Black-box integration tests over micro-mocked unit tests. If a refactor breaks a test without changing observable behaviour, the test was too coupled. - **CLI tests** (`Tests/`) drive the real `CommandApp`, assert exact stdout, and inspect the YAML written to disk. Use the `ExecuteAsync(...)` helper pattern. - **E2E tests** (`Tests.E2e/`) use Testcontainers to run the real Docker image then drive the Web UI via Playwright. Every page has a Page Object Model (POM) in `Tests.E2e/PageObjectModels/`. Tests should read like workflows, not browser scripts. - E2E tests must be **independent, idempotent, and self-cleaning** — generate unique names with `Guid.NewGuid()` and delete what you create. - Treat every bug as a missing test: reproduce with a failing test, then fix. - Fix flakiness immediately; don't retry. ### Adding a feature checklist - [ ] CLI test covering happy + at least one unhappy path (output + YAML side-effect) - [ ] E2E test for the corresponding Web UI flow (if there is one) - [ ] YAML migration + migration test (if persisted shape changed) - [ ] `dotnet format` clean - [ ] `just ci` green locally --- ## 10. Pull-request workflow From `docs/development/contribution-guidelines.md`: 1. **Find / open a GitHub issue first.** Validate approach with maintainers before coding (issues > Discord for design discussion). 2. Keep PRs **small and focused** — one concern per PR. 3. Open as **Draft**; move to Ready only when: - All tests pass locally (`just ci`) - Scope is complete - No debug code left in (especially `Headless = false` in `PlaywrightFixture.cs`) 4. Pre-PR checklist (mirror in PR body): - [ ] Linked GitHub issue - [ ] Approach validated - [ ] Small, focused PR - [ ] CLI tests passing locally - [ ] E2E tests passing locally - [ ] Behaviour covered by tests - [ ] YAML migration defined (if persisted shape changed) Default branches: feature work targets `staging`; releases flow `staging → main`. --- ## 11. Gotchas - **E2E tests require the Docker image.** `just test-e2e` rebuilds it via `just build-web`. If you change anything in `RackPeek.Web`, `RackPeek.Domain`, or `Shared.Rcl`, the image must be rebuilt before E2E runs. - **Playwright browsers** are installed once via `just e2e-setup`. In CI they're cached under `~/.cache/ms-playwright`. - **Bumping the `Microsoft.Playwright` package invalidates the browser cache.** Each Playwright version pins a specific Chromium build (e.g. 1.58 → `chromium_headless_shell-1208`, 1.59 → `-1217`). After bumping, every E2E test fails fast with `PlaywrightException : Executable doesn't exist at .../chromium_headless_shell-NNNN`. Re-run `just e2e-setup` (or `~/.dotnet/tools/playwright install chromium`) to download the matching build before running the suite. - **Docker image tag** is `rackpeek:ci` locally (referenced by `Tests.E2e/Infra/PlaywrightFixture.cs:9`); the registry tag is `aptacode/rackpeek`. - **Debugging E2E**: temporarily set `Headless = false, SlowMo = 1500` in `Tests.E2e/Infra/PlaywrightFixture.cs`. **Always revert before commit** — CI requires headless. - **TreatWarningsAsErrors** — a stray `unused-variable` warning fails the whole build. Don't add `#pragma warning disable` to push through; fix the warning. - **Git integration** is optional and silently no-ops when `GIT_TOKEN` is absent (`NullGitRepository`). Don't assume git is wired up. - **Single YAML file**: concurrent writes from CLI + Web are not coordinated beyond file replacement. Treat the Web UI as the source of truth while it's running. - The `RackPeek.Web/config copy/` directory looks like cruft but is checked-in — leave it alone unless cleaning up is the explicit goal. - The Web Docker image bundles **both** the Web app and the CLI binary (`rpk` is placed in `/usr/local/bin`). You can `docker exec rackpeek rpk ...` against a running container. --- ## 12. Reference | Path | What | |---|---| | `justfile` | Single source of truth for developer commands | | `RackPeek.sln` | Solution root | | `Directory.Build.props` | Repo-wide MSBuild props (analyzers, warnings-as-errors) | | `.editorconfig` | Formatting + naming rules | | `.github/workflows/test.yml` | CI pipeline (format → cli-tests → webui-tests) | | `.github/workflows/publish-*.yml` | Release pipelines | | `RackPeek.Web/Dockerfile` | Multi-stage build for the runtime image | | `RackPeek/Program.cs` | CLI entry point | | `RackPeek.Web/Program.cs` | Web entry point + DI wiring | | `Shared.Rcl/CliBootstrap.cs` | Master CLI command registration | | `RackPeek.Domain/ServiceCollectionExtensions.cs` | Domain DI registration | | `RackPeek.Domain/Resources/Resource.cs` | Resource base + kind/relationship rules | | `docs/development/contribution-guidelines.md` | PR process | | `docs/development/dev-cheat-sheet.md` | Build / release / Docker / Playwright details | | `docs/development/dev-setup.md` | First-time environment setup | | `docs/development/testing-guidelines.md` | Testing philosophy + examples | | `docs/Commands.md` / `docs/CommandIndex.md` | Auto-generated CLI reference | | `schemas/v1,v2,v3/` | Versioned YAML schemas | | `README.md` | User-facing overview, Docker install, links | | `LICENSE` | License terms |