* chore: add `AGENTS.md` Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * Apply suggestions from code review Co-authored-by: Francis Lavoie <lavofr@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * review feedback Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> --------- Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> Co-authored-by: Francis Lavoie <lavofr@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
6.8 KiB
Caddy Project Guidelines
Mission
Every site on HTTPS. Caddy is a security-first, modular, extensible server platform.
Code Style
Go Idioms
Follow Go Code Review Comments:
- Error flow: Early return, indent error handling—not else blocks
if err != nil { return err } // normal code - Naming: initialisms (
URL,HTTP,ID—notUrl,Http,Id) - Receiver names: 1–2 letters reflecting type (
cforClient,hforHandler) - Error strings: Lowercase, no trailing punctuation (
"something failed"not"Something failed.") - Doc comments: Full sentences starting with the name being documented
// Handler serves HTTP requests for the file server. type Handler struct { ... } - Empty slices:
var t []string(nil slice), nott := []string{}(non-nil zero-length) - Don't panic: Use error returns for normal error handling
Caddy Patterns
Module registration:
func init() {
caddy.RegisterModule(MyModule{})
}
func (MyModule) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "namespace.category.name",
New: func() caddy.Module { return new(MyModule) },
}
}
Module lifecycle: New() → JSON unmarshal → Provision() → Validate() → use → Cleanup()
Interface guards — compile-time verification that modules implement required interfaces:
var (
_ caddy.Provisioner = (*MyModule)(nil)
_ caddy.Validator = (*MyModule)(nil)
_ caddyfile.Unmarshaler = (*MyModule)(nil)
)
Structured logging — use the module-scoped logger from context:
func (m *MyModule) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger()
m.logger.Debug("provisioning", zap.String("field", m.Field))
return nil
}
Caddyfile support — implement UnmarshalCaddyfile(*caddyfile.Dispenser) using the Dispenser API:
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
//
// directive [arg1] [arg2] {
// subdir value
// }
func (m *MyModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // consume directive name
for d.NextArg() {
// handle inline arguments
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "subdir":
if !d.NextArg() {
return d.ArgErr()
}
m.Field = d.Val()
default:
return d.Errf("unrecognized subdirective: %s", d.Val())
}
}
return nil
}
Admin API: Implement caddy.AdminRouter for custom endpoints.
Context: Use caddy.Context for accessing other apps/modules and logging—don't store contexts in structs.
Architecture
Caddy is built around a module system where everything is a module registered via caddy.RegisterModule():
- Apps (
caddy.App): Top-level modules likehttp,tls,pkithat Caddy loads and runs - Modules (
caddy.Module): Extensible components with namespaced IDs (e.g.,http.handlers.file_server) - Configuration: Native JSON with adapters (Caddyfile → JSON via
caddyconfig/httpcaddyfile)
| Directory | Purpose |
|---|---|
modules/ |
All standard modules (HTTP, TLS, PKI, etc.) |
modules/standard/imports.go |
Standard module registry |
caddyconfig/httpcaddyfile/ |
Caddyfile → JSON adapter for HTTP |
caddytest/ |
Test utilities and integration tests |
cmd/caddy/ |
CLI entry point with module imports |
Critical Packages
caddyhttp and caddytls require extra scrutiny in code review—these are security-critical.
Quality Gates
All required before PR is merge-ready:
| Gate | Command | Notes |
|---|---|---|
| Tests pass | go test -race -short ./... |
Race detection enabled |
| Lint clean | golangci-lint run --timeout 10m |
No warnings in changed files |
| Builds | go build ./... |
Must compile |
| Benchmarks | go test -bench=. -benchmem |
Required for optimizations |
CI runs tests on Linux, macOS, and Windows—ensure cross-platform compatibility.
Build & Test
# Build
cd cmd/caddy && go build
# Tests with race detection (matches CI)
go test -race -short ./...
# Integration tests
go test ./caddytest/integration/...
# Lint (matches CI)
golangci-lint run --timeout 10m
Testing Conventions
Table-driven tests (preferred pattern):
func TestFeature(t *testing.T) {
for i, tc := range []struct {
input string
expected string
wantErr bool
}{
{input: "valid", expected: "result", wantErr: false},
{input: "invalid", expected: "", wantErr: true},
} {
actual, err := Function(tc.input)
if tc.wantErr && err == nil {
t.Errorf("Test %d: expected error but got none", i)
}
if !tc.wantErr && err != nil {
t.Errorf("Test %d: unexpected error: %v", i, err)
}
if actual != tc.expected {
t.Errorf("Test %d: expected %q, got %q", i, tc.expected, actual)
}
}
}
Integration tests use caddytest.Tester:
func TestHTTPFeature(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
localhost:9080 {
respond "hello"
}`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/", 200, "hello")
}
Use non-standard ports (9080, 9443, 2999) to avoid conflicts with running servers.
AI Contribution Policy
Per CONTRIBUTING.md, AI-assisted code MUST be:
- Disclosed — Tell reviewers when code was AI-generated or AI-assisted, mentioning which agent/model is used
- Fully comprehended — You must be able to explain every line
- Tested — Automated tests when feasible, thorough manual tests otherwise
- Licensed — Verify AI output doesn't include plagiarized or incompatibly-licensed code
- Contributor License Agreement (CLA) — The CLA must be signed by the human user
Do NOT submit code you cannot fully explain. Contributors are responsible for their submissions.
Dependencies
- Avoid new dependencies — Justify any additions; tiny deps can be inlined
- No exported dependency types — Caddy must not export types defined by external packages
- Use Go modules; check with
go mod tidy
Further Reading
- CONTRIBUTING.md — Full PR process and expectations
- Extending Caddy — Module development guide
- JSON Config — Native configuration reference
- Caddyfile — Caddyfile syntax guide