mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-20 18:11:00 +00:00
refactor(ci): move the bot's repository briefing into versioned files a test pins
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# Repository context for the Claude bot
|
||||
|
||||
Shared briefing for every job in `.github/workflows/claude-bot.yml`. It exists so
|
||||
these facts live in ONE place next to the code instead of being restated in five
|
||||
prompts, where they went stale silently.
|
||||
|
||||
**Read this from the workspace checkout, which is the base revision and is
|
||||
trusted. NEVER read it from `/tmp/head`** — a pull request controls that tree,
|
||||
and a fork that could supply this file could rewrite the rules it carries.
|
||||
|
||||
`CLAUDE.md`, `frontend/CLAUDE.md` and `docs/architecture.md` outrank this file.
|
||||
Where they disagree with it, they win and this file is the thing to fix.
|
||||
`docs/architecture.md` carries a "Symptom -> File" index and the cron-job table,
|
||||
which answer "which file owns X" in one hop; grepping blind wastes turns on a
|
||||
question it already answers.
|
||||
|
||||
## Stack
|
||||
|
||||
3x-ui is an open-source web control panel for managing Xray-core servers.
|
||||
|
||||
- Backend: Go 1.26, module `github.com/mhsanaei/3x-ui/v3`, Gin and GORM.
|
||||
- It runs Xray-core as a managed child process (`internal/xray/process.go`) and
|
||||
imports `github.com/xtls/xray-core` for config types and the gRPC
|
||||
stats/handler/router API. The release the panel BUNDLES is pinned in
|
||||
`DockerInit.sh`; the version it COMPILES against is pinned in `go.mod`, and
|
||||
the two are not always the same.
|
||||
- MTProto inbounds run a SECOND managed child, the `mtg-multi` binary (a
|
||||
multi-secret mtg fork, panel-side code in `internal/mtproto/`), one process
|
||||
per inbound. Client, ad-tag and quota/expiry edits are hot-applied through the
|
||||
fork's management API (`PUT /secrets`) so connections survive, with a process
|
||||
restart as the fallback on older binaries.
|
||||
- Storage: SQLite by default (`/etc/x-ui/x-ui.db` on Linux, the executable
|
||||
directory on Windows) or PostgreSQL (`XUI_DB_TYPE` / `XUI_DB_DSN`). The SQLite
|
||||
driver is CGo, so `CGO_ENABLED=0` builds fail.
|
||||
- Frontend: React 19 + Ant Design 6 + Vite 8 + TypeScript in `frontend/`, built
|
||||
into `internal/web/dist/` (gitignored) and embedded with `embed.FS`.
|
||||
|
||||
## Where things live
|
||||
|
||||
| area | path |
|
||||
| --- | --- |
|
||||
| entry point + `x-ui` CLI | `main.go` |
|
||||
| env parsing | `internal/config/` |
|
||||
| schema, migrations | `internal/database/`, `internal/database/model/` |
|
||||
| Xray child process + config | `internal/xray/` |
|
||||
| MTProto inbounds | `internal/mtproto/` |
|
||||
| subscription server | `internal/sub/` |
|
||||
| HTTP handlers | `internal/web/controller/` |
|
||||
| business logic | `internal/web/service/` |
|
||||
| cron jobs (schedules in `web.go startTask()`) | `internal/web/job/` |
|
||||
| master/sub-node over mTLS | `internal/web/runtime/` |
|
||||
| i18n | `internal/web/locale/`, `internal/web/translation/` |
|
||||
| UI source | `frontend/src/` |
|
||||
| install / upgrade | `install.sh`, `x-ui.sh`, `DockerInit.sh` |
|
||||
|
||||
## Hard rules a change must respect
|
||||
|
||||
- **Dispatch through `runtime.Runtime`.** Every state-changing inbound or client
|
||||
operation goes through the interface in `internal/web/runtime/`, never
|
||||
straight to `internal/xray/api.go`. A direct call passes every local test and
|
||||
silently breaks every multi-node deployment; it is invisible in a single-box
|
||||
reading of a diff.
|
||||
- **Layering.** Controllers are thin — bind, validate, respond — with no GORM
|
||||
queries, no Xray calls and no business rules. `internal/util/*` is leaf-only
|
||||
and must not import service, controller or database. `internal/web/dist/` and
|
||||
`frontend/src/generated/` are generated; a hand-edit is a violation.
|
||||
- **Comments in committed Go/TS/TSX: 2 lines MAX per block**, spent on the *why*
|
||||
a name cannot hold — an invariant, an issue number, a non-obvious constraint.
|
||||
Exempt, never flag: `//go:build`, `//go:generate`, `//nolint:`,
|
||||
`// Code generated ... DO NOT EDIT.`. HTML `<!-- -->` is fine.
|
||||
- **The route contract chain**, which breaks in four distinct places:
|
||||
1. a new `g.POST`/`g.GET` in `internal/web/controller/` needs a matching entry
|
||||
in `frontend/src/pages/api-docs/endpoints.ts` — pinned BOTH ways by
|
||||
`TestRouteRegistryContract` in `internal/web/routes_contract_test.go`, so a
|
||||
renamed or removed route that leaves a stale entry fails too;
|
||||
2. generated artefacts must be regenerated with `make gen`, or CI's `codegen`
|
||||
job fails on a dirty `frontend/src/generated` or
|
||||
`frontend/public/openapi.json`;
|
||||
3. a NEW struct crossing the API boundary must be added to the `StructAllow`
|
||||
allowlist in `tools/openapigen/main.go`, or it is SILENTLY dropped from the
|
||||
schemas and `frontend/scripts/build-openapi.mjs` then fails — a guaranteed
|
||||
CI break, not a style nit;
|
||||
4. the step NOTHING checks — `frontend/public/openapi.json` must be copied to
|
||||
`docs/public/openapi.json` and the MDX regenerated with
|
||||
`cd docs && pnpm gen:api`, because `docs-ci.yml` fires only on `docs/**`.
|
||||
Step 4 is the one that reaches production wrong.
|
||||
- **i18n.** A new English key goes in EVERY locale JSON in
|
||||
`internal/web/translation/` (13 files) AND must be referenced from
|
||||
`frontend/src` or Go in the SAME change.
|
||||
`frontend/src/test/i18n-dead-keys.test.ts` fails on a missing locale file and
|
||||
on an orphan key alike.
|
||||
- **Migrations.** Schema changes are GORM `AutoMigrate` PLUS hand-written
|
||||
migrations in `internal/database/db.go`. There are no migration files and no
|
||||
down-migrations, and everything has to work on SQLite AND PostgreSQL.
|
||||
- **Tests.** Stdlib `testing` only (no testify), table-driven with `t.Run`
|
||||
subtests and `t.Helper()` on helpers. An assertion must pin the exact value,
|
||||
typed error or emitted string — `err != nil` and `len(x) > 0` are findings,
|
||||
not nits. Prefer real dependencies: a throwaway DB via
|
||||
`database.InitDB(filepath.Join(t.TempDir(), "x-ui.db"))` with `t.Cleanup`, and
|
||||
`httptest` for HTTP. `internal/sub`'s `initSubDB(t)` is the template.
|
||||
A test must FAIL without its fix; one that passes either way certifies
|
||||
nothing and then gets cited as proof the fix works.
|
||||
|
||||
## The three link implementations
|
||||
|
||||
Link and subscription generation is implemented three times, independently:
|
||||
|
||||
| language | path | what it feeds |
|
||||
| --- | --- | --- |
|
||||
| Go | `internal/util/link/`, `internal/sub/` | what the panel serves |
|
||||
| TS | `frontend/src/lib/xray/` | what the panel UI shows |
|
||||
| TS | `docs/lib/xray/` | what the docs site shows |
|
||||
|
||||
A change to share-link or subscription output that touches one and not the
|
||||
others is how they drift apart.
|
||||
|
||||
## Downstream programs that must accept what the panel emits
|
||||
|
||||
- **XTLS/Xray-core** — the Xray config the panel generates, and the VLESS/VMess
|
||||
transport and security fields.
|
||||
- **MetaCubeX/mihomo** — consumes the Clash YAML from `internal/sub/`.
|
||||
- **SagerNet/sing-box** — parses the share links the panel emits.
|
||||
- **mhsanaei/mtg-multi** — the MTProto sidecar whose TOML (`[secrets]`,
|
||||
`[secret-ad-tags]`, `[secret-limits]`) and management API
|
||||
(`PUT /secrets`, `POST /secrets/{name}/reset-quota`) `internal/mtproto/`
|
||||
writes and calls.
|
||||
|
||||
## What CI runs
|
||||
|
||||
`.github/workflows/ci.yml`, on every pull request touching Go or frontend code.
|
||||
It is paths-filtered, so a docs-only or workflow-only change produces no run.
|
||||
|
||||
| job | what it proves |
|
||||
| --- | --- |
|
||||
| `go-test` | `go test -shuffle=on -count=1` over every package except `frontend/node_modules` |
|
||||
| `race` | the same set under `-race -shuffle=on` |
|
||||
| `postgres-durable-first` | live PostgreSQL 16: the `PostgresCommitFailure` tests plus `TestHostAutoMigrateCreatesColumns_Postgres` and `TestMigrate_Postgres`. Both steps COUNT passes rather than assert on SKIP, so a renamed or deleted test fails the job |
|
||||
| `govulncheck` | known vulnerabilities |
|
||||
| `golangci` | `golangci-lint` |
|
||||
| `fuzz-smoke` | 30s each on `FuzzParseLink` and `FuzzDecodeCertPin` |
|
||||
| `codegen` | `npm run gen` then `git diff --exit-code` on the generated files |
|
||||
| `frontend` | MSW worker drift, lint, format:check, typecheck, `npm test` (Vitest + headless-Chromium Storybook), build, build-storybook, `npm audit` |
|
||||
|
||||
**What CI does NOT prove.** These test families `t.Skip` unless an environment
|
||||
variable is set, and CI sets only the PostgreSQL ones above:
|
||||
|
||||
| gate | covers |
|
||||
| --- | --- |
|
||||
| `XUI_TEST_PG_DSN` | PostgreSQL-specific paths |
|
||||
| `XUI_DB_TYPE` + `XUI_DB_DSN` | dialect-dependent behaviour |
|
||||
| `XRAY_E2E_BINARY` | the Xray gRPC end-to-end tests in `internal/xray/` |
|
||||
| `XUI_SCALE_TEST` | scale tests in `internal/sub/`, `internal/web/job/`, `internal/web/service/` |
|
||||
|
||||
Mutation testing (`mutation.yml`) runs nightly and never on a pull request, so a
|
||||
test that cannot fail is invisible to CI. `make verify` is the local gate.
|
||||
|
||||
## Support facts reporters get wrong
|
||||
|
||||
- Linux install: `bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)`
|
||||
- Install generates a RANDOM username, password and web base path — never
|
||||
admin/admin. The `x-ui` menu on the server shows or resets them.
|
||||
- The installer service environment file is DISTRO-DEPENDENT:
|
||||
`/etc/default/x-ui` (Debian/Ubuntu), `/etc/conf.d/x-ui` (Arch),
|
||||
`/etc/sysconfig/x-ui` (RHEL/Fedora). Naming the wrong one means the reporter's
|
||||
edit is silently never read by systemd — a common cause of "I set the variable
|
||||
and nothing happened".
|
||||
- Windows is supported. There the database sits next to the executable, not in
|
||||
`/etc` — never quote the Linux path to a Windows user.
|
||||
- SQLite to PostgreSQL: `x-ui migrate-db --dsn "postgres://..."`, then set
|
||||
`XUI_DB_TYPE`/`XUI_DB_DSN` in that file and `systemctl restart x-ui`. The
|
||||
source SQLite file is left in place.
|
||||
- Docker image `ghcr.io/mhsanaei/3x-ui`; PostgreSQL profile
|
||||
`docker compose --profile postgres up -d`. Fail2ban IP-limit enforcement needs
|
||||
`NET_ADMIN` + `NET_RAW` (compose grants them; a bare `docker run` must add
|
||||
`--cap-add=NET_ADMIN --cap-add=NET_RAW`).
|
||||
- Never state that a `XUI_*` variable does not exist without grepping
|
||||
`internal/config/` and `internal/tunnelmonitor/` first. The
|
||||
`XUI_TUNNEL_HEALTH_*` family is the usual answer to "the panel restarts Xray
|
||||
every few minutes".
|
||||
- Security per inbound is none / tls / reality. XTLS is a VLESS *flow*
|
||||
(`xtls-rprx-vision`), not a security setting — never tell anyone to pick XTLS
|
||||
in the security dropdown.
|
||||
- Never hardcode a version. For "is this already fixed" use
|
||||
`gh release list -L 10`, `gh search commits`, and `git log -S`.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Review rubric and lane map
|
||||
|
||||
Shared by the four pull-request review lanes in
|
||||
`.github/workflows/claude-bot.yml`. The lane map below is here so it exists
|
||||
ONCE: when each lane carried its own copy of "mine / not mine", the four copies
|
||||
could quietly contradict each other and the same defect got reported twice or
|
||||
not at all.
|
||||
|
||||
**Read this from the workspace checkout, which is the base revision and is
|
||||
trusted. NEVER read it from `/tmp/head`** — a pull request controls that tree,
|
||||
and a fork that could supply this file could rewrite the rubric it is judged by.
|
||||
|
||||
## Lane map — who owns what
|
||||
|
||||
Ownership is decided by WHAT YOU WOULD HAVE TO BE RIGHT ABOUT for the finding to
|
||||
be true, not by how bad the consequence would be.
|
||||
|
||||
| lane | owns |
|
||||
| --- | --- |
|
||||
| **Senior Developer** | Correctness, edge cases, nil and empty handling, regressions. Layering and the `runtime.Runtime` dispatch rule. Security in code: authn/authz, input validation, injection, XSS, CSRF, SSRF, path traversal, secrets, unsafe defaults — weighted at `internal/web/controller/`, session and middleware, the PUBLIC `internal/sub/` surface, and Xray config generation. Concurrency: races, deadlocks, goroutine and task leaks around the Xray and mtg-multi children, the cron jobs, the eventbus, the websockets. Performance. Maintainability and the 2-line comment cap. Frontend code quality. **Every client-facing field name, encoding and hash choice** the change emits. |
|
||||
| **Senior QA** | `internal/database/**`, `internal/database/model/**`, `internal/config/`, `internal/web/translation/**`, `tools/openapigen/`, `frontend/src/pages/api-docs/endpoints.ts`, `.github/workflows/**`, `Dockerfile*`, `docker-compose.yml`, `install.sh`, `x-ui.sh`, `DockerInit.sh`, `Makefile`, `CLAUDE.md`, `frontend/CLAUDE.md`, `docs/**`, `README*`, `SECURITY.md`. Plus intent, upgrade safety, blast radius, backward compatibility of those contracts, operational impact, and labels. |
|
||||
| **Senior Tester** | Test quality and coverage, what CI proved and what it did not, weak assertions, vacuous tests, snapshot and golden-fixture abuse. |
|
||||
| **Arbiter** | Reconciliation, upstream wire-format resolution, and divergence BETWEEN the three link implementations. |
|
||||
|
||||
### Boundaries that are easy to get wrong
|
||||
|
||||
- **Field names are the Developer's, never QA's** — a config key, JSON tag, URI
|
||||
parameter, YAML key, TOML key, value encoding, hash choice, or which of two
|
||||
variables a field is populated from. However large the blast radius. If your
|
||||
finding is only true when one of those is wrong, it is the Developer's.
|
||||
- **QA outside its own files** may report exactly ONE thing: *a configuration
|
||||
that works on the base branch today behaves differently after this ships, with
|
||||
no operator action* — and only when it can state (a) the concrete existing
|
||||
configuration, (b) what it does today, (c) what it does after. Otherwise drop
|
||||
it; the Developer has it.
|
||||
- **Destroying data IS QA's**, even outside its files: regenerating a live key or
|
||||
UUID, overwriting a stored secret, resetting a traffic counter or expiry. That
|
||||
is blast radius, not correctness.
|
||||
- **`docs/lib/xray/`**: QA reports the process omission ("it was not updated").
|
||||
The Arbiter reports semantic divergence between the three implementations. The
|
||||
Developer reports whether the one in front of it emits the right thing.
|
||||
- **The Tester never** opines on architecture, naming or what the code emits,
|
||||
and never restates a green CI job as a finding.
|
||||
|
||||
## Severity — exactly one per finding, plain text, no emoji
|
||||
|
||||
| level | means |
|
||||
| --- | --- |
|
||||
| Critical | security hole, data corruption or loss, crash, privilege escalation, authentication bypass, unrecoverable migration, or a fleet-wide outage path |
|
||||
| High | likely production bug, incorrect behaviour on a common path, a breaking API or subscription-format change, a missing migration, a guaranteed CI break, or a significant performance problem |
|
||||
| Medium | missing validation, an unhandled edge case, an undeclared behaviour change, documentation or OpenAPI drift, a maintainability problem, or an untested new code path |
|
||||
| Low | minor readability, consistency, operational or documentation improvement |
|
||||
| Suggestion | optional improvement with no correctness or release impact |
|
||||
|
||||
## Confidence — exactly one per finding
|
||||
|
||||
High, Medium, or Low. Reserve **High** for something CONFIRMED in the source and
|
||||
citable as `file:line`, or observed in real command output. Anything inferred,
|
||||
or resting on a detail you could not check, is Medium or Low.
|
||||
|
||||
## Verdict — exactly one
|
||||
|
||||
`Approve`, `Comment`, or `Request changes`.
|
||||
|
||||
## Finding block
|
||||
|
||||
Fields on their own lines:
|
||||
|
||||
```
|
||||
Severity / Confidence / Category
|
||||
Location: file:line as plain text, not a Markdown link
|
||||
Problem: what is wrong
|
||||
Why it matters: the practical runtime, security, operational or upgrade impact
|
||||
Recommendation: the preferred fix
|
||||
```
|
||||
|
||||
The Tester replaces `Why it matters` with `Evidence`: the command or CI job and
|
||||
the real output it read. A code example is optional and, if included, must be a
|
||||
plain fenced code block — never a ```suggestion``` block, since the Arbiter
|
||||
republishes the text.
|
||||
|
||||
## Reporting discipline
|
||||
|
||||
- Report every problem, including Low and Suggestion. Never drop a finding
|
||||
because you are unsure: report it at `Confidence: Low` and say what would
|
||||
confirm it. Severity and confidence ARE the filter.
|
||||
- Dropping a finding because it is not YOURS is different, and is exactly what
|
||||
the lane map asks for. A duplicate only costs the Arbiter a merge.
|
||||
- Do not report the same issue twice, do not bikeshed style, and ignore
|
||||
pure-formatting changes unless they reduce readability. Ignore lock files and
|
||||
true vendor code; do NOT ignore test fixtures or generated files.
|
||||
- If the diff is too large to cover completely, say so and name the files you
|
||||
did NOT review. A truncated review that does not admit it is worse than none.
|
||||
+1946
-788
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user