CodeVetter · Repo Unpack · Contributor handoff
fatedier/frp
Start with AGENTS.md for the command surface: `make build|frps|frpc|test|e2e|fmt|fmt-more|gci|vet`, `golangcici-lint run`, `make web`. Read cmd/frps/root.go + cmd/frpc/sub/root.go, then server/service.go and client/service.go for wiring, and server/control.go for the lifecycle/locking protocol before touching session code. Config changes must go through pkg/config/v1 types + validation and be tested for TOML/YAML/JSON and strict mode. Protocol changes must update pkg/msg on both sides and consider the v2 wire negotiation in pkg/proto/wire. E2E tests need built binaries and Ginkgo; unit tests run with the noweb tag when dashboards aren't built.
- AGENTS.md documents the canonical commands: make build/frps/frpc/test/e2e/e2e-trace/alltest/fmt/fmt-more/gci/vet, make web for dashboards, and points to doc/agents/ runbooks (release.md).
- First files to read: cmd/frps/root.go and cmd/frpc/sub/root.go (entrypoints), server/service.go and client/service.go (composition roots), server/control.go (session lifecycle/locking), pkg/config/v1 (config schema), pkg/msg/msg.go (protocol).
- Build tags matter: 'frps'/'frpc' select per-binary code and 'noweb' is auto-added when web/*/dist is missing — run go test/vet with -tags noweb or build dashboards first.
- When editing server/control.go, respect the documented global lock order (runMu, cm.mu, ctl.lifecycleMu, then registry locks) and the pending/activated/running state machine — generation handoff uses barriers between replaced controls.
- Config edits require updating v1 types, Complete() defaults, validation.Validate* and ideally e2e coverage; strict_config=true by default rejects unknown fields, so new fields break older parsers cross-version.
- Client config now flows through source.Aggregator (ConfigSource + StoreSource); bypassing it breaks /api/reload and store CRUD. Keep Service.reloadMu semantics when touching UpdateConfigSource.
- Run `go test -tags noweb ./pkg/... ./client/... ./server/...` for unit tests; `make e2e` requires bin/frps+bin/frpc built first and ginkgo installed (script auto-installs).
- Don't add proxy types piecemeal: a type needs a configurer in proxyConfigTypeMap, marshal/unmarshal to msg, server factory init, client factory init, validation, and usually e2e — the CLI subcommand is then auto-generated.
- Auth token bytes double as the work-conn encryption key (ClientAuth/ServerAuth key = []byte(resolved.Token)) — changing token handling affects transport encryption.
- Version pinning/compat is enforced by tests: FRP_COMPAT_FLOOR_VERSION=0.61.0 and baseline-count e2e runs mean wire/protocol changes must remain compatible across ~8 released versions.
- Logging uses pkg/util/log and xlog (context-scoped, prefixed by runID); follow existing xl.Debugf/Tracef patterns rather than fmt.Println in service code (entrypoints print user-facing errors via fmt).
- Panic-on-duplicate registries (plugin.Register, proxy factories) run in init() — import cycles or double registration crash at startup, so new plugins/types must use unique names/types.