CLAUDE.md
CLAUDE.mdCLAUDE.mdroot
Quality
82/100
Scores the file, not the repository.Length
1,450 words
8 headings · 0 code blocksRepository
44k
— · pushed 1 days agoLast changed
2 days ago
First indexed 3 days ago.1# CLAUDE.md23Operational guide for AI agents working in this repo. Long-form human docs:4`CONTRIBUTING.md` (setup, testing philosophy) and `frontend/README.md`.5Read those before large changes. This file is the short, must-follow version.6For a deep navigation map (request lifecycle, cron-job table, symptom → file7index, layering rules), read `docs/architecture.md` on demand — do not guess8file locations when it can answer in one hop.910## Stack11- Backend: Go 1.26 (`module github.com/mhsanaei/3x-ui/v3`), Gin, GORM.12 Runs Xray-core as a managed child process (`internal/xray/process.go`) and13 imports `github.com/xtls/xray-core` for config types + gRPC stats/handler/router14 API. MTProto inbounds run a second managed child — the `mtg-multi` binary15 (a multi-secret mtg fork — NOT a Go dependency; its prebuilt release binary is16 fetched at image/release build time by `DockerInit.sh` + `release.yml`,17 panel-side code in `internal/mtproto/`) — outside Xray, one process per inbound18 serving each19 client's FakeTLS secret via the fork's `[secrets]` section (plus per-client20 ad-tags via `[secret-ad-tags]` and per-client data quota / expiry via21 `[secret-limits]`, mapped from the client's `totalGB`/`expiryTime`). Client,22 ad-tag and quota/expiry edits are hot-applied through the fork's management API23 (`PUT /secrets`, bearer-token guarded) so connections survive; the manager24 falls back to a process restart on older binaries. A client's panel-side25 traffic reset also calls `POST /secrets/{name}/reset-quota` so a renewed client26 is not re-blocked by the sidecar's quota counter.27- Storage: SQLite by default (`/etc/x-ui/x-ui.db` on Linux; the executable dir on28 Windows), PostgreSQL optional (`XUI_DB_TYPE` / `XUI_DB_DSN`). The CGo SQLite29 driver (`mattn/go-sqlite3`) needs a C compiler — `CGO_ENABLED=0` builds fail.30- Frontend: React 19 + Ant Design 6 + Vite 8 + TypeScript in `frontend/`,31 built into `internal/web/dist/` (gitignored) and embedded via `embed.FS`.3233## Repo map34- `main.go` — entry point + `x-ui` CLI (run, migrate, migrate-db, setting, cert).35- `internal/config/` — env parsing (XUI_DEBUG, XUI_LOG_LEVEL, XUI_LOG_FOLDER,36 XUI_BIN_FOLDER, XUI_SKIP_HSTS, XUI_PORT, XUI_DB_*).37- `internal/database/` + `internal/database/model/` — GORM schema (~24 models;38 Inbound, Client, Setting, User are the core), inbound Protocol enum,39 AutoMigrate + hand-written migrations in `db.go`.40- `internal/xray/` — Xray child-process lifecycle, config generation, gRPC API.41- `internal/mtproto/` — MTProto inbounds via the bundled `mtg-multi` binary.42- `internal/sub/` — subscription server (raw / JSON / Clash).43- `internal/eventbus/` — in-process pub/sub (outbound/node health, xray.crash,44 cpu.high, memory.high, login.attempt).45- `internal/logger/`, `internal/util/` (link, crypto, sys, ldap, …),46 `internal/tunnelmonitor/` — shared infrastructure.47- `internal/web/` — Gin server (embeds `dist/` + `translation/`).48 - `controller/` — panel + REST API handlers; OpenAPI at /panel/api/openapi.json.49 - `service/` — business logic (InboundService, SettingService, XrayService,50 node sync); subpackages tgbot/, email/, outbound/, panel/, integration/.51 - `job/` — 17 cron jobs (traffic, fail2ban IP-limit, node heartbeat/sync, LDAP,52 CPU/memory watchdogs, …); full table in `docs/architecture.md` §5.4.53 - `middleware/`, `entity/`, `global/`, `session/` (CSRF), `network/`,54 `runtime/` (master/sub-node over mTLS), `websocket/`.55 - `locale/` + `translation/` — i18n, 13 embedded locale JSON files.56- `frontend/` — React + TS source (see `frontend/CLAUDE.md`).57- `tools/openapigen/` — Go generator that emits frontend types + Zod/JSON schemas58 into `frontend/src/generated/` from Go structs. The OpenAPI doc itself59 (`frontend/public/openapi.json`) is assembled from those + `endpoints.ts` by60 `frontend/scripts/build-openapi.mjs`. (`tools/seedperf/` is a separate seeding61 /load helper.)62- `docs/` — separate Next.js/Fumadocs site (pnpm, own CI in `docs-ci.yml`,63 outside `make verify`). Holds a THIRD independent implementation of64 link/subscription generation in `docs/lib/xray/` — check it whenever65 share-link or install-command output changes.6667## Hard rules (non-negotiable)68- Fix size must match bug size. Find the root cause, then make the SMALLEST69 change that removes it — a one-line guard beats a new subsystem. A small bug70 does not earn new columns, jobs, abstractions, config knobs or helper layers.71 If a fix genuinely needs new architecture, say so and get agreement first;72 never ship it unasked next to the fix.73- Comments in committed Go/TS: 2 lines MAX per comment block. Make the name74 carry the meaning first and rename rather than annotate; spend the 2 lines on75 the *why* a name cannot hold — an invariant, an issue number, a non-obvious76 constraint. Exempt: `//go:build`, `//go:generate`, and other directives.77 HTML `<!-- -->` is fine. (A linter cannot enforce this — you must.)78- New `g.POST`/`g.GET` in `internal/web/controller/` REQUIRES a matching entry79 in `frontend/src/pages/api-docs/endpoints.ts`, then `make gen` (or80 `cd frontend && npm run gen`). Hand-maintained but pinned both ways by81 `TestRouteRegistryContract` (`internal/web/routes_contract_test.go`): a missing82 OR stale entry fails `make test-go`. Scope: `/panel/api/*` + a few session83 routes; sub-server routes are exempt.84- Response examples come from Go struct `example:` tags via `tools/openapigen` —85 never hand-write them. A new struct must be added to openapigen's `StructAllow`86 allowlist (`tools/openapigen/main.go`) or it is silently omitted from87 schemas/examples (and `build-openapi.mjs` then fails on the missing schema).88- A new or renamed endpoint has a FOURTH step nothing checks: copy89 `frontend/public/openapi.json` → `docs/public/openapi.json`, then90 `cd docs && pnpm gen:api` to refresh the MDX under91 `docs/content/docs/en/reference/api/`. `docs-ci.yml` fires only on `docs/**`.92- A new English i18n key goes in EVERY locale JSON in `internal/web/translation/`93 (13 files) AND must be referenced from `frontend/src` or Go in the SAME commit —94 `frontend/src/test/i18n-dead-keys.test.ts` fails both ways. It is a frontend95 test, so run `npm test`, not just `make test-go`. At runtime the frontend falls96 back to en-US; Go (`internal/web/locale/`) returns "" for an unknown key.97- DB / model changes require a migration in `internal/database/db.go`.98- Every state-changing inbound/client op dispatches through `runtime.Runtime`99 (`internal/web/runtime/`) — never straight to `internal/xray/api.go`, never from100 a controller or cron job. A direct call passes every local test and silently101 breaks every multi-node deployment. Other layering rules: `docs/architecture.md` §8.102- Conventional commits: `type(area): short imperative summary`, then a body103 explaining the why. Types in use: `fix`, `feat`, `chore`, `refactor`, `perf`,104 `docs`, `style`.105106## Go conventions107- Stdlib `testing` only (no testify). Table-driven, `t.Run` subtests,108 `t.Helper()` on helpers. Assert the exact value / typed error / emitted109 string, never just `err != nil`. Prefer real deps over mocks: throwaway DB via110 `database.InitDB(filepath.Join(t.TempDir(), "x-ui.db"))` +111 `t.Cleanup(func() { _ = database.CloseDB() })`; `httptest` for HTTP.112 `internal/sub`'s `initSubDB(t)` is the template.113- A test must fail without its fix. Write it, revert the fix, watch it go red,114 restore. A test that passes either way is worse than no test: it certifies115 nothing and then gets cited as proof the fix works.116- Test what can actually break. No test for a getter, a constant, a rename, a117 pure map lookup, or inputs the function can never receive. One real test that118 drives the bug through the actual code path beats five that restate the code.119- Code must pass `golangci-lint run` (gofumpt + goimports formatting): `make lint`.120- Postgres, xray-gRPC-e2e and scale tests `t.Skip` unless `XUI_TEST_PG_DSN`,121 `XUI_DB_TYPE`+`XUI_DB_DSN`, `XRAY_E2E_BINARY` or `XUI_SCALE_TEST` is set — a122 green `go test ./...` does not mean those paths ran.123124## Frontend conventions (summary; full version in frontend/CLAUDE.md)125- Ant Design 6 only — no Tailwind/shadcn. Targeted tweaks, not rewrites.126- TS strict; `@typescript-eslint/no-explicit-any` is an error. Zod schemas in127 `src/schemas/` are the source of truth; infer types with `z.infer`, never128 hand-write. Do not edit `src/generated/`.129- Node 24 (`.nvmrc`) — `make gen` imports `.ts` directly and needs its type130 stripping; Node 22 dies with `ERR_UNKNOWN_FILE_EXTENSION`. `npm test` includes131 a headless-Chromium Storybook project, so run132 `npx playwright install --with-deps chromium` once or `make verify` fails.133- Editing `frontend/src` does NOT change what users see until the Vite build is134 regenerated into `internal/web/dist/`. In `XUI_DEBUG=true`, HTML is served from135 the frozen embedded FS but JS/CSS off disk — after `npm run build` you MUST136 restart `go run .` or you get a blank page with 404s.137- After touching share-link logic (`src/lib/xray/`), run `npm run test` (golden138 fixtures); regenerate snapshots (`npx vitest run -u`) only for intentional139 output changes, never to make a red test green.140141## Build, test, verify142A fresh clone has no `internal/web/dist/`, so a bare `go build ./...` dies with143`pattern all:dist: no matching files found` while ~35 other packages pass — it144reads as a broken repo, not a missing step. Run `make dist-stub` once; every145`make` Go target already depends on it, which is why `make test-go` beats146`go test ./...`. Run `make help` for all targets. The local gate:147148 make verify # gen-check + lint + typecheck + test + build + build-storybook149150That is the *fast* gate, not all of CI. `ci.yml` also runs `make race`,151`make vulncheck`, a live-Postgres job (where a SKIP counts as a failure) and a15230s fuzz smoke on `FuzzParseLink`/`FuzzDecodeCertPin` — run those locally when153you touch DB/dialect or parser code.154155Common targets: `make gen` (regenerate Zod/OpenAPI), `make lint` (Go + frontend),156`make test` (Go `-shuffle=on` + frontend), `make race`, `make build`. See `Makefile`.157158## Definition of done (before opening a PR)1591. `make verify` passes — its `gen-check` already runs `make gen` and fails on a160 dirty `frontend/src/generated` / `frontend/public/openapi.json`.1612. Diff is focused; refactors are separate from feature work.162
Also in MHSanaei/3x-ui
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| MHSanaei/3x-uifrontend/CLAUDE.md · 44k | CLAUDE.md | buildtestapido-not+2 | 83/100 | 3 days ago |
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| bagisto/bagistoCLAUDE.md · 28k | CLAUDE.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| filamentphp/filamentCLAUDE.md · 32k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 3 days ago | |
| nimbalyst/nimbalystpackages/android/CLAUDE.md · 1.4k | CLAUDE.md | setupbuildstylearch+2 | 100/100 | 3 days ago | |
| stacklok/toolhiveCLAUDE.md · 2.0k | CLAUDE.md | buildteststylearch+4 | 100/100 | 3 days ago | |
| dotCMS/corecore-web/CLAUDE.md · 949 | CLAUDE.md | teststylearchtesting-strategy+3 | 100/100 | 3 days ago | |
| microsoft/playwrightCLAUDE.md · 94k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 3 days ago | |
| Adit-Jain-srm/NightmareNetCLAUDE.md · 45 | CLAUDE.md | buildtestlint-formatstyle+6 | 100/100 | 3 days ago | |
| dotCMS/coreCLAUDE.md · 949 | CLAUDE.md | setupbuildteststyle+7 | 99/100 | today |
