|
| 1 | +# GitHub Copilot Instructions for usulnet |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +**usulnet** is a self-hosted Docker Management Platform written in **Go** — a single-binary alternative to Portainer. It provides container lifecycle management, security scanning, backup management, reverse proxy configuration, monitoring, multi-node orchestration, and developer tooling through a unified web UI. |
| 6 | + |
| 7 | +- **Module path:** `github.com/fr4nsys/usulnet` |
| 8 | +- **Go version:** 1.25.7 |
| 9 | +- **License:** AGPL-3.0-or-later |
| 10 | +- **Current release:** v26.2.0 Beta |
| 11 | + |
| 12 | +## Architecture |
| 13 | + |
| 14 | +### Operation Modes |
| 15 | + |
| 16 | +The platform supports three operation modes: |
| 17 | + |
| 18 | +1. **standalone** — Single Docker host. All services local. No NATS required. |
| 19 | +2. **master** — Standalone + NATS gateway server for remote agent management. |
| 20 | +3. **agent** — Connects to master via NATS. No web UI. Executes Docker operations on its local host. |
| 21 | + |
| 22 | +### Entry Points |
| 23 | + |
| 24 | +| Binary | Location | Framework | Purpose | |
| 25 | +|--------|----------|-----------|---------| |
| 26 | +| `usulnet` | `cmd/usulnet/` | Cobra CLI | Main server (`serve`, `migrate`, `config`, `admin`, `version`) | |
| 27 | +| `usulnet-agent` | `cmd/usulnet-agent/` | `flag` package | Remote node agent | |
| 28 | + |
| 29 | +### Key Directories |
| 30 | + |
| 31 | +- `cmd/` — Binary entry points (usulnet and usulnet-agent) |
| 32 | +- `internal/api/` — REST API layer with Chi router and middleware |
| 33 | +- `internal/services/` — Business logic layer (~39 service packages) |
| 34 | +- `internal/repository/postgres/` — PostgreSQL repositories with migrations |
| 35 | +- `internal/web/` — Web UI layer (server-side rendered with Templ) |
| 36 | +- `web/static/` — Static assets (CSS, JS, fonts) |
| 37 | +- `tests/` — Tests (unit, integration, e2e, benchmarks, load) |
| 38 | + |
| 39 | +## Technology Stack |
| 40 | + |
| 41 | +### Backend |
| 42 | + |
| 43 | +- **Language:** Go 1.25.7 |
| 44 | +- **HTTP Router:** go-chi/chi/v5 |
| 45 | +- **Database:** PostgreSQL via pgx/v5 + sqlx |
| 46 | +- **Cache:** Redis (go-redis/v9) |
| 47 | +- **Messaging:** NATS with JetStream |
| 48 | +- **Docker:** Docker SDK v28.5.1 |
| 49 | +- **Auth:** JWT (golang-jwt/jwt/v5), OIDC, LDAP |
| 50 | +- **Logging:** uber-go/zap with structured logging |
| 51 | +- **Config:** spf13/viper + spf13/cobra |
| 52 | +- **Validation:** go-playground/validator/v10 |
| 53 | + |
| 54 | +### Frontend |
| 55 | + |
| 56 | +- **Templates:** Templ v0.3.977 (type-safe Go HTML templates) |
| 57 | +- **CSS:** Tailwind CSS v3.4.17 (standalone CLI, no Node.js) |
| 58 | +- **Reactivity:** Alpine.js 3.14.8 |
| 59 | +- **Interactivity:** HTMX 2.0.4 |
| 60 | +- **Charts:** Chart.js |
| 61 | +- **Terminal:** xterm.js (WebSocket-based) |
| 62 | +- **Code Editor:** Monaco Editor |
| 63 | +- **RDP:** Apache Guacamole |
| 64 | +- **Fonts:** IBM Plex Sans/Mono, Space Grotesk (self-hosted) |
| 65 | + |
| 66 | +## Code Conventions |
| 67 | + |
| 68 | +### File Naming |
| 69 | + |
| 70 | +- `handler_<feature>.go` — Web page handlers |
| 71 | +- `adapter_<feature>.go` — Type adapters (service → web layer) |
| 72 | +- `routes_<area>.go` — Route registration |
| 73 | +- `*_templ.go` — Auto-generated from `.templ` files (DO NOT EDIT) |
| 74 | +- `*_test.go` — Test files |
| 75 | + |
| 76 | +### Required Copyright Header |
| 77 | + |
| 78 | +Every Go file must include this header: |
| 79 | + |
| 80 | +```go |
| 81 | +// SPDX-License-Identifier: AGPL-3.0-or-later |
| 82 | +// Copyright (c) 2024-2026 usulnet contributors |
| 83 | +// https://github.com/fr4nsys/usulnet |
| 84 | +``` |
| 85 | + |
| 86 | +### Import Organization |
| 87 | + |
| 88 | +Group imports in this order: |
| 89 | +1. Standard library |
| 90 | +2. Third-party packages |
| 91 | +3. Local packages (`github.com/fr4nsys/usulnet/...`) |
| 92 | + |
| 93 | +Local imports use the module path prefix configured in `.golangci.yml` via goimports. |
| 94 | + |
| 95 | +### Error Handling |
| 96 | + |
| 97 | +- Always wrap errors with context: `fmt.Errorf("descriptive context: %w", err)` |
| 98 | +- Services return errors; handlers log and respond with structured JSON |
| 99 | +- Non-fatal failures are logged but don't block startup |
| 100 | + |
| 101 | +### Logging |
| 102 | + |
| 103 | +- Use **zap** via wrapper at `internal/pkg/logger` |
| 104 | +- Structured key-value pairs: `log.Info("message", "key", value, "key2", value2)` |
| 105 | +- Levels: debug, info, warn, error, fatal |
| 106 | +- JSON in production, console in development |
| 107 | + |
| 108 | +### Service Architecture |
| 109 | + |
| 110 | +- **Constructor injection:** `NewService(repo, deps..., config, logger)` |
| 111 | +- All services accept a `*logger.Logger` |
| 112 | +- Lifecycle methods: `Start(ctx)` / `Stop()` where applicable |
| 113 | +- **Nil-safe design:** handlers check if services are nil |
| 114 | +- **Repository pattern:** each domain has a repository in `internal/repository/postgres/` |
| 115 | + |
| 116 | +### RBAC |
| 117 | + |
| 118 | +Three role tiers enforced via Chi middleware: |
| 119 | +- `admin` — Full access (`RequireAdmin` middleware) |
| 120 | +- `operator` — Operational access (`RequireOperator` middleware) |
| 121 | +- `viewer` — Read-only (`RequireViewer` middleware) |
| 122 | + |
| 123 | +## Build & Development |
| 124 | + |
| 125 | +### Quick Commands |
| 126 | + |
| 127 | +```bash |
| 128 | +# Build |
| 129 | +make build # Full build: templ + CSS + go build |
| 130 | +make build-agent # Build agent binary only |
| 131 | +make frontend # Generate templates + compile CSS |
| 132 | + |
| 133 | +# Run |
| 134 | +make run # go run ./cmd/usulnet |
| 135 | +make dev-up # Start dev services (PostgreSQL, Redis, NATS, MinIO) |
| 136 | +make dev-down # Stop dev services |
| 137 | + |
| 138 | +# Test |
| 139 | +make test # go test -v -race -cover ./... |
| 140 | +make test-coverage # Coverage with HTML report |
| 141 | +make test-e2e # E2E tests (requires services, build tag: e2e) |
| 142 | + |
| 143 | +# Quality |
| 144 | +make lint # golangci-lint run ./... |
| 145 | +make lint-fix # Auto-fix linting issues |
| 146 | +make fmt # gofmt -s -w . |
| 147 | +make vet # go vet ./... |
| 148 | +make quality # Full quality gate (lint + vet + coverage check) |
| 149 | + |
| 150 | +# Database |
| 151 | +make migrate # Run migrations up |
| 152 | +make migrate-down # Roll back migrations |
| 153 | +make migrate-status # Show migration status |
| 154 | + |
| 155 | +# Hooks |
| 156 | +make install-hooks # Install pre-commit hook |
| 157 | +``` |
| 158 | + |
| 159 | +### Development Workflow |
| 160 | + |
| 161 | +1. Start infrastructure: `make dev-up` |
| 162 | +2. Run server: `make run` |
| 163 | +3. Edit `.templ` files → `make templ` (or `make templ-watch`) |
| 164 | +4. Edit CSS → `make css` (or `make css-watch`) |
| 165 | +5. Run tests: `make test` |
| 166 | +6. Run linter: `make lint` |
| 167 | +7. Full check: `make quality` |
| 168 | +8. Build: `make build` |
| 169 | + |
| 170 | +### Adding New Features |
| 171 | + |
| 172 | +When adding a feature, follow this pattern: |
| 173 | +1. Add models in `internal/models/` |
| 174 | +2. Add repository in `internal/repository/postgres/` with migration |
| 175 | +3. Add service in `internal/services/<feature>/` |
| 176 | +4. Add API handlers in `internal/api/handlers/` |
| 177 | +5. Add web handlers in `internal/web/handler_<feature>.go` |
| 178 | +6. Register routes in `internal/web/routes_*.go` or `internal/api/router.go` |
| 179 | +7. Wire service in `internal/app/app.go` |
| 180 | + |
| 181 | +## Database |
| 182 | + |
| 183 | +### PostgreSQL |
| 184 | + |
| 185 | +- Driver: `pgx/v5` + `sqlx` |
| 186 | +- 36 numbered migrations in `internal/repository/postgres/migrations/` |
| 187 | +- Naming: `NNN_description.{up,down}.sql` |
| 188 | +- Run: `make migrate` or `go run ./cmd/usulnet migrate up` |
| 189 | +- Status: `make migrate-status` |
| 190 | + |
| 191 | +### Redis |
| 192 | + |
| 193 | +- Session storage and JWT blacklisting |
| 194 | +- Client at `internal/repository/redis/` |
| 195 | + |
| 196 | +## Configuration |
| 197 | + |
| 198 | +- Loaded via **Viper** with `mapstructure` tags |
| 199 | +- Override with env vars: `USULNET_<KEY>_<NESTED_KEY>` |
| 200 | +- Main config: `config.yaml` |
| 201 | +- Agent config: `config.agent.yaml` |
| 202 | + |
| 203 | +Key sections: `server`, `database`, `redis`, `nats`, `security`, `storage`, `trivy`, `docker`, `logging`, `metrics`, `observability` |
| 204 | + |
| 205 | +## Testing |
| 206 | + |
| 207 | +| Type | Command | Coverage | |
| 208 | +|------|---------|----------| |
| 209 | +| Unit/Integration | `make test` | 40% minimum | |
| 210 | +| Coverage report | `make test-coverage` | Outputs `coverage.html` | |
| 211 | +| Benchmarks | `make test-benchmark` | Router, JWT validation | |
| 212 | +| E2E | `make test-e2e` | Requires services | |
| 213 | +| Load | `tests/load/` | k6 script | |
| 214 | + |
| 215 | +Test environment uses `docker-compose.test.yml` with offset ports. |
| 216 | + |
| 217 | +## Linting |
| 218 | + |
| 219 | +Uses **golangci-lint** with 15 linters (see `.golangci.yml`): |
| 220 | + |
| 221 | +gosec, staticcheck, govet, errcheck, ineffassign, unused, gocritic, revive, gofmt, goimports, prealloc, nilerr, errorlint, misspell, unconvert, whitespace |
| 222 | + |
| 223 | +**Exclusions:** |
| 224 | +- `*_templ.go` files fully excluded |
| 225 | +- Test files excluded from gosec, errcheck, gocritic, prealloc |
| 226 | +- `G101` (hardcoded credentials) excluded from gosec |
| 227 | +- `fieldalignment` and `shadow` disabled in govet |
| 228 | + |
| 229 | +## Pre-commit Hook |
| 230 | + |
| 231 | +Install: `make install-hooks` |
| 232 | + |
| 233 | +Runs: |
| 234 | +1. `gofmt` check (excludes `_templ.go`) |
| 235 | +2. `go vet ./...` |
| 236 | +3. `go test -short -count=1 ./...` |
| 237 | +4. `golangci-lint run --fast ./...` (if installed) |
| 238 | + |
| 239 | +## Docker |
| 240 | + |
| 241 | +### Development |
| 242 | + |
| 243 | +```bash |
| 244 | +make dev-up # Start PostgreSQL, Redis, NATS, MinIO |
| 245 | +make run # Run server locally |
| 246 | +make dev-down # Stop services |
| 247 | +``` |
| 248 | + |
| 249 | +### Production Build |
| 250 | + |
| 251 | +3-stage multi-stage Dockerfile: |
| 252 | +1. golang:1.25.7-alpine — Templ CLI, generate templates, build binary |
| 253 | +2. alpine:3.21 — Download Tailwind CLI, compile CSS |
| 254 | +3. alpine:3.21 — Runtime (docker-cli, trivy, neovim, ripgrep, non-root user) |
| 255 | + |
| 256 | +Ports: **8080** (HTTP), **7443** (HTTPS with self-signed TLS) |
| 257 | + |
| 258 | +## Best Practices |
| 259 | + |
| 260 | +### Code Quality |
| 261 | + |
| 262 | +- **Fix bugs when found** — Don't defer unless it requires multi-day work |
| 263 | +- **Choose correct fix over quick fix** — Avoid technical debt |
| 264 | +- **Never assume, always verify** — Read code, check behavior |
| 265 | +- **Document with file:line references** — Context for future sessions |
| 266 | +- **Use existing libraries** — Only add new deps if absolutely necessary |
| 267 | + |
| 268 | +### Testing |
| 269 | + |
| 270 | +- Write tests that match existing patterns |
| 271 | +- Use table-driven tests for multiple scenarios |
| 272 | +- Mock external dependencies (Docker, NATS, etc.) |
| 273 | +- Test error paths, not just happy paths |
| 274 | +- E2E tests require build tag: `e2e` |
| 275 | + |
| 276 | +### Comments |
| 277 | + |
| 278 | +- Don't add comments unless they match file style or explain complex logic |
| 279 | +- Code should be self-documenting where possible |
| 280 | +- Use structured logging for runtime information |
| 281 | + |
| 282 | +### Tools |
| 283 | + |
| 284 | +- Prefer Makefile targets over direct tool calls |
| 285 | +- Use ecosystem tools (npm init, pip install) to automate |
| 286 | +- Use refactoring tools for automated changes |
| 287 | +- Run linters to fix style issues |
| 288 | + |
| 289 | +## Security |
| 290 | + |
| 291 | +- No hardcoded secrets in source code |
| 292 | +- Use AES-256-GCM for encryption (`internal/pkg/crypto`) |
| 293 | +- Password hashing via bcrypt |
| 294 | +- JWT with blacklisting support |
| 295 | +- TOTP 2FA support (`internal/pkg/totp`) |
| 296 | +- Input validation via `go-playground/validator` |
| 297 | + |
| 298 | +## Key Dependencies |
| 299 | + |
| 300 | +| Category | Package | |
| 301 | +|----------|---------| |
| 302 | +| HTTP | go-chi/chi/v5 | |
| 303 | +| Templates | a-h/templ | |
| 304 | +| Database | jackc/pgx/v5, jmoiron/sqlx | |
| 305 | +| Redis | redis/go-redis/v9 | |
| 306 | +| Messaging | nats-io/nats.go | |
| 307 | +| Docker | docker/docker v28.5.1 | |
| 308 | +| Auth | golang-jwt/jwt/v5, coreos/go-oidc/v3, go-ldap/ldap/v3 | |
| 309 | +| Logging | uber-go/zap | |
| 310 | +| Config | spf13/viper, spf13/cobra | |
| 311 | +| Observability | OpenTelemetry | |
| 312 | + |
| 313 | +## Additional Resources |
| 314 | + |
| 315 | +- Full guide: `CLAUDE.md` (comprehensive AI assistant instructions) |
| 316 | +- Architecture: `docs/architecture.md` |
| 317 | +- Development: `docs/development.md` |
| 318 | +- API docs: `docs/api.md` |
| 319 | +- Agent docs: `AGENT_DEVELOPMENT.md` |
0 commit comments