Skip to content

Commit 148deee

Browse files
rickcrawfordclaude
andcommitted
Initial release: SBproxy v0.1.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0 parents  commit 148deee

1,541 files changed

Lines changed: 345414 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Default: require review from Rick for all changes
2+
* @rickcrawford

.github/dependabot.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: gomod
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
- package-ecosystem: github-actions
8+
directory: /
9+
schedule:
10+
interval: weekly

.github/workflows/ci.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CI
2+
on:
3+
push:
4+
branches: [main]
5+
pull_request:
6+
branches: [main]
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-go@v5
14+
with:
15+
go-version: '1.25.5'
16+
- name: Build with version info
17+
run: |
18+
VERSION=$(cat VERSION)
19+
GIT_HASH=$(git rev-parse --short HEAD)
20+
BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
21+
go build -ldflags "-s -w \
22+
-X github.com/soapbucket/sbproxy/internal/version.Version=${VERSION} \
23+
-X github.com/soapbucket/sbproxy/internal/version.BuildHash=${GIT_HASH} \
24+
-X github.com/soapbucket/sbproxy/internal/version.BuildDate=${BUILD_DATE}" \
25+
./...
26+
- run: go vet ./...
27+
- run: go test ./... -count=1 -timeout 300s -race
28+
- name: Install and run golangci-lint
29+
run: |
30+
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
31+
golangci-lint run ./...
32+
- run: make check

.github/workflows/release.yml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: write
10+
packages: write
11+
12+
jobs:
13+
release:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- uses: actions/setup-go@v5
21+
with:
22+
go-version: '1.25.5'
23+
24+
- name: Login to GHCR
25+
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
26+
27+
- name: Run GoReleaser
28+
uses: goreleaser/goreleaser-action@v7
29+
with:
30+
version: latest
31+
args: release --clean
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
35+
update-homebrew:
36+
needs: release
37+
runs-on: ubuntu-latest
38+
steps:
39+
- uses: actions/checkout@v4
40+
41+
- name: Get version from tag
42+
id: version
43+
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
44+
45+
- name: Download release artifacts and compute hashes
46+
id: hashes
47+
run: |
48+
VERSION=${{ steps.version.outputs.version }}
49+
BASE_URL="https://github.com/soapbucket/sbproxy/releases/download/v${VERSION}"
50+
51+
for platform in darwin_arm64 darwin_amd64 linux_arm64 linux_amd64; do
52+
archive="sbproxy_${platform}.tar.gz"
53+
echo "Downloading ${archive}..."
54+
curl -fsSL "${BASE_URL}/${archive}" -o "/tmp/${archive}" || {
55+
echo "WARN: ${archive} not available"
56+
continue
57+
}
58+
hash=$(sha256sum "/tmp/${archive}" | awk '{print $1}')
59+
echo "${platform}=${hash}" >> "$GITHUB_OUTPUT"
60+
echo " ${platform}: ${hash}"
61+
done
62+
63+
- name: Update Homebrew formula
64+
uses: actions/checkout@v4
65+
with:
66+
repository: soapbucket/homebrew-sbproxy
67+
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
68+
path: homebrew-sbproxy
69+
70+
- name: Write updated formula
71+
run: |
72+
VERSION=${{ steps.version.outputs.version }}
73+
cat > homebrew-sbproxy/sbproxy.rb << 'FORMULA'
74+
class Sbproxy < Formula
75+
desc "High-performance reverse proxy and AI gateway"
76+
homepage "https://sbproxy.dev"
77+
license "Apache-2.0"
78+
version "VERSION_PLACEHOLDER"
79+
80+
on_macos do
81+
if Hardware::CPU.arm?
82+
url "https://github.com/soapbucket/sbproxy/releases/download/v#{version}/sbproxy_darwin_arm64.tar.gz"
83+
sha256 "DARWIN_ARM64_PLACEHOLDER"
84+
else
85+
url "https://github.com/soapbucket/sbproxy/releases/download/v#{version}/sbproxy_darwin_amd64.tar.gz"
86+
sha256 "DARWIN_AMD64_PLACEHOLDER"
87+
end
88+
end
89+
90+
on_linux do
91+
if Hardware::CPU.arm?
92+
url "https://github.com/soapbucket/sbproxy/releases/download/v#{version}/sbproxy_linux_arm64.tar.gz"
93+
sha256 "LINUX_ARM64_PLACEHOLDER"
94+
else
95+
url "https://github.com/soapbucket/sbproxy/releases/download/v#{version}/sbproxy_linux_amd64.tar.gz"
96+
sha256 "LINUX_AMD64_PLACEHOLDER"
97+
end
98+
end
99+
100+
def install
101+
bin.install "sbproxy"
102+
end
103+
104+
test do
105+
assert_match version.to_s, shell_output("#{bin}/sbproxy --version")
106+
end
107+
end
108+
FORMULA
109+
110+
# Replace placeholders
111+
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/" homebrew-sbproxy/sbproxy.rb
112+
sed -i "s/DARWIN_ARM64_PLACEHOLDER/${{ steps.hashes.outputs.darwin_arm64 }}/" homebrew-sbproxy/sbproxy.rb
113+
sed -i "s/DARWIN_AMD64_PLACEHOLDER/${{ steps.hashes.outputs.darwin_amd64 }}/" homebrew-sbproxy/sbproxy.rb
114+
sed -i "s/LINUX_ARM64_PLACEHOLDER/${{ steps.hashes.outputs.linux_arm64 }}/" homebrew-sbproxy/sbproxy.rb
115+
sed -i "s/LINUX_AMD64_PLACEHOLDER/${{ steps.hashes.outputs.linux_amd64 }}/" homebrew-sbproxy/sbproxy.rb
116+
117+
# Fix indentation (heredoc adds leading spaces)
118+
sed -i 's/^ //' homebrew-sbproxy/sbproxy.rb
119+
120+
- name: Commit and push formula
121+
working-directory: homebrew-sbproxy
122+
run: |
123+
git config user.name "github-actions[bot]"
124+
git config user.email "github-actions[bot]@users.noreply.github.com"
125+
git add sbproxy.rb
126+
git commit -m "sbproxy ${{ steps.version.outputs.version }}"
127+
git push

.gitignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Binaries
2+
bin/
3+
/sbproxy
4+
*.exe
5+
*.dll
6+
*.so
7+
*.dylib
8+
9+
# Test
10+
*.test
11+
*.out
12+
coverage.txt
13+
coverage.html
14+
15+
# Benchmarks
16+
new.txt
17+
18+
# IDE
19+
.idea/
20+
.vscode/
21+
*.swp
22+
*.swo
23+
*~
24+
25+
# Generated certs (use make certs to regenerate)
26+
certs/
27+
28+
# OS
29+
.DS_Store
30+
Thumbs.db
31+
32+
# Build
33+
dist/
34+
35+
# Temporary
36+
tmp/
37+
*.tmp
38+
*.log
39+
e2e/servers/echo-server

.golangci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
run:
2+
timeout: 5m
3+
4+
linters:
5+
enable:
6+
- errcheck
7+
- govet
8+
- staticcheck
9+
- unused
10+
- gosimple
11+
- ineffassign
12+
13+
issues:
14+
exclude-rules:
15+
- path: _test\.go
16+
linters:
17+
- errcheck

.goreleaser.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
version: 2
2+
3+
builds:
4+
- main: ./cmd/sbproxy
5+
binary: sbproxy
6+
env:
7+
- CGO_ENABLED=0
8+
goos:
9+
- linux
10+
- darwin
11+
goarch:
12+
- amd64
13+
- arm64
14+
ldflags:
15+
- -s -w
16+
- -X github.com/soapbucket/sbproxy/internal/version.Version={{.Version}}
17+
- -X github.com/soapbucket/sbproxy/internal/version.BuildHash={{.ShortCommit}}
18+
- -X github.com/soapbucket/sbproxy/internal/version.BuildDate={{.Date}}
19+
20+
archives:
21+
- formats:
22+
- tar.gz
23+
name_template: "sbproxy_{{ .Os }}_{{ .Arch }}"
24+
25+
dockers:
26+
- image_templates:
27+
- "ghcr.io/soapbucket/sbproxy:{{ .Tag }}"
28+
- "ghcr.io/soapbucket/sbproxy:latest"
29+
dockerfile: Dockerfile.goreleaser
30+
extra_files: []
31+
build_flag_templates:
32+
- "--pull"
33+
- "--label=org.opencontainers.image.created={{.Date}}"
34+
- "--label=org.opencontainers.image.title=sbproxy"
35+
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
36+
- "--label=org.opencontainers.image.version={{.Version}}"
37+
38+
changelog:
39+
sort: asc
40+
filters:
41+
exclude:
42+
- "^docs:"
43+
- "^test:"
44+
- "^ci:"

AGENTS.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# AGENTS.md - AI Agent Instructions for SBproxy
2+
3+
## Project Overview
4+
5+
SBproxy is a reverse proxy and AI gateway written in Go. Single binary, Caddy-style plugin architecture, 18-layer compiled handler chain.
6+
7+
- **Source:** https://github.com/soapbucket/sbproxy
8+
- **Docs:** https://sbproxy.dev/docs
9+
- **License:** Apache 2.0
10+
11+
## Repository Structure
12+
13+
```
14+
cmd/sbproxy/ Binary entry point
15+
pkg/ Public API (plugin interfaces, config types, events)
16+
internal/ Private implementation
17+
config/ Config loading, validation, compilation (compiler.go is key)
18+
engine/ HTTP pipeline (middleware, handlers, transport)
19+
modules/ Plugin modules (action/, auth/, policy/, transform/)
20+
middleware/ Per-origin middleware (callback, cors, waf, modifier, etc.)
21+
ai/ AI gateway (providers, routing, guardrails)
22+
extension/ Scripting (CEL, Lua, MCP)
23+
observe/ Logging, metrics, telemetry, events
24+
loader/ Config lifecycle, feature flags
25+
platform/ Infrastructure (circuit breaker, DNS, health, messenger)
26+
request/ Request context (reqctx, session, rate limit)
27+
security/ Crypto, cert pinning, PII, signatures
28+
cache/ Response and object caching
29+
service/ Server lifecycle
30+
examples/ 16 working config examples (all use test.sbproxy.dev)
31+
docs/ Architecture, configuration, scripting docs
32+
```
33+
34+
## Build and Test
35+
36+
```bash
37+
go build ./... # build
38+
go test ./... # test all
39+
go test ./internal/config/ -count=1 -timeout 120s # config tests
40+
go test ./internal/modules/... -v # module tests
41+
go vet ./... # lint
42+
go test -race ./... -count=1 -timeout 180s # race detector
43+
```
44+
45+
## Key Architectural Concepts
46+
47+
### Plugin System
48+
Modules register via `init()` into `pkg/plugin` registry. Five interfaces:
49+
- `ActionHandler` - what to do with a request (proxy, redirect, static, AI, etc.)
50+
- `AuthProvider` - who can access (api_key, jwt, basic_auth, etc.)
51+
- `PolicyEnforcer` - rules and limits (rate_limit, waf, cel expression, etc.)
52+
- `TransformHandler` - modify response body (json_projection, html, lua, etc.)
53+
- `RequestEnricher` - enrich request context (GeoIP, UA parsing - enterprise only)
54+
55+
### Compiled Handler Chain
56+
`internal/config/compiler.go: CompileOrigin()` builds an 18-layer handler chain per origin. Built inside-out, executes outside-in. Zero per-request allocation.
57+
58+
### Config Format
59+
YAML config (sb.yml) with `proxy:` (global) and `origins:` (per-hostname) top-level keys. Each origin has sibling fields: `action`, `authentication`, `policies`, `transforms`, `request_modifiers`, `response_modifiers`, `forward_rules`, `response_cache`, `on_request`, `on_response`, `compression`, `cors`, `error_pages`.
60+
61+
**Critical rule:** `authentication`, `policies`, `transforms`, etc. are SIBLINGS of `action`, never nested inside it.
62+
63+
### Header Normalization
64+
Two systems with different rules:
65+
- **CEL expressions:** lowercase, hyphens preserved, bracket notation: `request.headers["x-api-key"]`
66+
- **Mustache templates:** lowercase, hyphens to underscores, dot notation: `{{ request.headers.x_api_key }}`
67+
68+
### Enterprise Boundary
69+
- OSS code must NEVER import enterprise packages
70+
- Enterprise code imports only `sbproxy/pkg/*` (never `sbproxy/internal/*`)
71+
- Enterprise features: canary, shadow, geo-blocking, OAuth, WASM, guardrails, semantic cache
72+
- Enterprise modules register via the same `plugin.Register*()` pattern
73+
74+
## Code Style
75+
76+
- Standard `gofmt`
77+
- Explicit `if err != nil` error handling, no panics in production
78+
- All exported types and functions must have Go doc comments
79+
- Add logical section comments at decision points, not on every line
80+
- Use `// --- Section Name ---` dividers in larger files
81+
- Do NOT use em dashes in any content
82+
- Do NOT expose internal implementation details in marketing content
83+
- All documentation files must have `*Last modified: YYYY-MM-DD*` after the title
84+
85+
## When Making Changes
86+
87+
1. Read the existing code before modifying
88+
2. Follow existing patterns in the package
89+
3. Run `go build ./...` after every change
90+
4. Run `go vet ./...` before committing
91+
5. Run `go test` for affected packages
92+
6. All examples use test.sbproxy.dev as the backend
93+
7. Do NOT include enterprise features in OSS code
94+
95+
## Adding a New Module
96+
97+
1. Create package under `internal/modules/{type}/{name}/`
98+
2. Implement the appropriate `pkg/plugin` interface
99+
3. Register via `plugin.Register*()` in `init()`
100+
4. Add blank import to `internal/modules/imports.go`
101+
5. Write tests
102+
6. Run full build and test suite
103+
104+
## Important Files
105+
106+
| File | Purpose |
107+
|---|---|
108+
| `internal/config/compiler.go` | Origin compilation (18-layer handler chain) |
109+
| `pkg/plugin/registry.go` | Plugin registration |
110+
| `pkg/plugin/services.go` | ServiceProvider interface |
111+
| `pkg/plugin/enricher.go` | RequestEnricher interface |
112+
| `internal/engine/middleware/middleware.go` | Global middleware chain and origin routing |
113+
| `internal/modules/imports.go` | Module activation via blank imports |
114+
| `internal/service/service.go` | Server startup sequence |
115+
| `internal/request/reqctx/types.go` | RequestData struct |
116+
| `internal/template/resolver.go` | Template resolution (9-namespace model) |

0 commit comments

Comments
 (0)