- Create index.html for the web application interface. - Implement deploy.sh script for building and deploying the application to a Docker Swarm manager. - Add docker-compose.yml for defining application and PostgreSQL service configurations.
5.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
OTS Signs Orchestrator — a .NET 9.0 system for provisioning and managing Xibo CMS instances on Docker Swarm. Two projects in one solution:
- OTSSignsOrchestrator — ASP.NET Core API + React web UI + SignalR + Quartz scheduler. PostgreSQL 16. Contains all services, models, configuration, and business logic.
- OTSSignsOrchestrator.Tests — xUnit test project.
External integrations: Xibo CMS API (OAuth2), Authentik (SAML IdP), Bitwarden Secrets, Docker Swarm (SSH), Git (LibGit2Sharp), MySQL 8.4, Stripe, SendGrid, NFS volumes.
Build & Run Commands
# Build entire solution
dotnet build
# Run application
dotnet run --project OTSSignsOrchestrator/OTSSignsOrchestrator.csproj
# Run tests
dotnet test OTSSignsOrchestrator.Tests/OTSSignsOrchestrator.Tests.csproj
# Run a single test
dotnet test OTSSignsOrchestrator.Tests --filter "FullyQualifiedName~TestClassName.TestMethodName"
# Frontend dev server (from ClientApp/)
cd OTSSignsOrchestrator/ClientApp && npm run dev
# Build frontend for production (outputs to wwwroot/)
cd OTSSignsOrchestrator/ClientApp && npm run build
# EF Core migrations
dotnet ef migrations add <Name> --project OTSSignsOrchestrator --startup-project OTSSignsOrchestrator
# Local dev PostgreSQL
docker compose -f docker-compose.dev.yml up -d
Architecture
The application uses a job-queue architecture with a React web UI:
- React Web UI (
ClientApp/) — Vite + React + TypeScript + Tailwind CSS, served fromwwwroot/. Cookie-based JWT auth (httpOnly, Secure, SameSite=Strict). - REST API (
Api/) — Minimal API endpoint groups via.MapGroup()with JWT auth - SignalR Hub (
Hubs/FleetHub.cs) — Real-time updates to web UI clients - ProvisioningWorker (
Workers/) — Background service that pollsJobstable, claims jobs, resolves the correctIProvisioningPipeline, and executes steps - Pipelines — Each job type has a pipeline (Phase1, Phase2, BYOI SAML, Suspend, Reactivate, Decommission, etc.). Steps emit
JobSteprecords broadcast via SignalR - HealthCheckEngine (
Health/) — Background service running 16 health check types - Quartz Jobs (
Jobs/) — Scheduled tasks (cert expiry, daily snapshots, reports) - Stripe Webhooks (
Webhooks/) — Idempotent webhook processing
Data flow: Web UI creates Job → ProvisioningWorker claims it → pipeline runs steps → JobStep records broadcast via SignalR → Web UI updates in real-time.
Project Structure
OTSSignsOrchestrator/
├── Api/ # Minimal API endpoint groups
├── Auth/ # JWT/auth services
├── ClientApp/ # React + Vite frontend
├── Clients/ # External API clients (Xibo, Authentik)
├── Configuration/ # AppConstants, AppOptions
├── Data/ # OrchestratorDbContext
│ └── Entities/ # EF Core entity models
├── Health/ # Health check engine + checks
├── Hubs/ # SignalR hubs
├── Jobs/ # Quartz scheduled jobs
├── Models/DTOs/ # Data transfer objects
├── Reports/ # PDF report generation
├── Services/ # Business logic + integrations
├── Webhooks/ # Stripe webhook handler
├── Workers/ # Provisioning pipelines + worker
└── wwwroot/ # Built frontend assets
Critical Rules
Xibo API — non-negotiable
GET /api/applicationis BLOCKED — only POST and DELETE exist- Group endpoints are
/api/group, never/api/usergroup - Feature assignment is
POST /api/group/{id}/acl, NOT/features - Always pass
length=200and useGetAllPagesAsync()— default pagination is 10 items, causing silent data truncation - OAuth2 client secret returned ONCE on creation — capture immediately
Stripe webhooks — idempotency mandatory
- Check
StripeEventstable forstripe_event_idbefore processing - Insert the
StripeEventrow first, then process
No AI autonomy in infrastructure actions
- All infrastructure actions must flow through the
ProvisioningWorkerjob queue via an operator-initiatedJobrecord
Immutability
AuditLog, Message, and Evidence are append-only. Never generate Update/Delete methods on their repositories.
Credential handling
Never store secrets in the database. Secrets go to Bitwarden only. OauthAppRegistry stores clientId only.
Naming Conventions
- Customer abbreviation: exactly 3 lowercase letters (
^[a-z]{3}$) - Stack name:
{abbrev}-cms-stack, Service:{abbrev}-web, DB:{abbrev}_cms_db - Secret names via
AppConstantshelpers AppConstants.SanitizeName()filters to[a-z0-9_-]
Testing Requirements
- Integration tests require Testcontainers with real PostgreSQL 16 — no SQLite substitutions
- Unit tests required for: evidence hashing, AI context assembly, pattern detection, abbreviation uniqueness, Stripe idempotency
Pitfalls
- SSH host state:
SshDockerCliService.SetHost()must be called before each host operation in loops - Bitwarden cache: Call
FlushCacheAsync()after creating secrets before reading them back - No saga/rollback: Partial failures across Git → MySQL → Docker → Xibo leave orphaned resources; cleanup is manual
- Docker volumes are sticky: Failed deploys leave volumes with old NFS driver options
- Template CIFS→NFS compat: Old
{{CIFS_*}}tokens still render correctly as NFS equivalents
Code Generation Checklist
- After generating a class implementing an interface, verify all members are implemented
- After generating a pipeline, verify all steps produce
JobStepentities with progress broadcast viaIHubContext<FleetHub> - Do not stub steps as TODO — implement fully or flag explicitly