Skip to content

Commit 4adb02f

Browse files
authored
Merge pull request #233 from umputun/maint/deps-update-nov2025
Maintenance: update dependencies and fix linter warnings
2 parents b56560f + 089e683 commit 4adb02f

612 files changed

Lines changed: 51187 additions & 22753 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/workflows/ci.yml

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@ jobs:
1111
runs-on: ubuntu-latest
1212

1313
steps:
14-
- name: set up go 1.24
15-
uses: actions/setup-go@v5
14+
- name: free disk space
15+
run: |
16+
sudo rm -rf /usr/share/dotnet
17+
sudo rm -rf /opt/ghc
18+
sudo rm -rf /usr/local/share/boost
19+
docker system prune -af
20+
21+
- name: set up go 1.25
22+
uses: actions/setup-go@v6
1623
with:
17-
go-version: "1.24"
24+
go-version: "1.25"
1825
id: go
1926

2027
- name: checkout
21-
uses: actions/checkout@v4
28+
uses: actions/checkout@v6
2229

2330
- name: build and test
2431
run: |
@@ -38,14 +45,14 @@ jobs:
3845
TZ: "America/Chicago"
3946

4047
- name: golangci-lint
41-
uses: golangci/golangci-lint-action@v7
48+
uses: golangci/golangci-lint-action@v9
4249
with:
43-
version: v2.1.1
50+
version: latest
4451

4552
- name: golangci-lint on example directory
46-
uses: golangci/golangci-lint-action@v7
53+
uses: golangci/golangci-lint-action@v9
4754
with:
48-
version: v2.1.1
55+
version: latest
4956
args: --config ../../.golangci.yml
5057
working-directory: examples/plugin
5158

.golangci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ linters:
1717
- unconvert
1818
- unparam
1919
- unused
20+
- nestif
21+
- testifylint
22+
- wrapcheck
23+
- errorlint
2024
settings:
2125
goconst:
2226
min-len: 2

CLAUDE.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Reproxy is a simple edge HTTP(s) reverse proxy supporting multiple providers (docker, static, file, consul catalog). It handles automatic SSL termination with Let's Encrypt, load balancing, health checks, and optional plugin support via RPC.
8+
9+
## Build and Test Commands
10+
11+
```bash
12+
# run all tests
13+
cd app && go test ./...
14+
15+
# run tests with race detection
16+
cd app && go test -race -timeout=60s -count 1 ./...
17+
18+
# run specific test
19+
cd app && go test -run TestName ./...
20+
21+
# run tests with coverage
22+
cd app && go test -cover ./...
23+
24+
# build binary (from repo root)
25+
make build
26+
27+
# build docker image
28+
make docker
29+
30+
# run linter (from repo root)
31+
golangci-lint run
32+
```
33+
34+
## Architecture
35+
36+
### Core Components
37+
38+
- **app/main.go** - CLI entry point with all configuration options (flags, environment variables). Wires together providers, proxy server, and management services.
39+
40+
- **app/discovery/** - Service discovery layer
41+
- `discovery.go` - `Service` aggregates multiple providers, merges URL mappers, handles health checks
42+
- `provider/` - Provider implementations (docker, file, static, consul-catalog)
43+
- `URLMapper` - Core routing rule struct containing server, source regex, destination, health ping URL, match type
44+
45+
- **app/proxy/** - HTTP/HTTPS proxy server
46+
- `proxy.go` - `Http` struct is the main proxy server, handles both http and https modes
47+
- `handlers.go` - Middleware handlers (throttling, auth, logging, headers)
48+
- `ssl.go` - SSL/TLS configuration and ACME (Let's Encrypt) autocert management
49+
- `health.go` - Health check endpoint handlers
50+
- `lb_selector.go` - Load balancer strategies (random, failover, roundrobin)
51+
- `only_from.go` - IP-based access control middleware
52+
53+
- **app/mgmt/** - Management API
54+
- `server.go` - Exposes `/routes` and `/metrics` endpoints
55+
- `metrics.go` - Prometheus metrics middleware
56+
57+
- **app/plugin/** - Plugin system
58+
- `conductor.go` - RPC server for plugin registration and middleware chain
59+
60+
- **lib/** - Plugin development library
61+
- Used by external plugins to implement custom middleware via RPC
62+
63+
### Request Flow
64+
65+
1. Request hits `proxy.Http.Run()` which sets up middleware chain
66+
2. `matchHandler` middleware matches request to `URLMapper` via `discovery.Service.Match()`
67+
3. Match result stored in request context
68+
4. `proxyHandler` routes to either:
69+
- `httputil.ReverseProxy` for proxy matches (MTProxy)
70+
- File server for static matches (MTStatic)
71+
- Assets handler for default static files
72+
73+
### Provider Priority
74+
75+
Providers are processed in order: static → file → docker → consul-catalog. Earlier providers take precedence for conflicting rules.
76+
77+
### SSL Modes
78+
79+
- `SSLNone` - HTTP only
80+
- `SSLStatic` - User-provided certificates
81+
- `SSLAuto` - ACME/Let's Encrypt automatic certificates with DNS-01 or HTTP-01 challenges
82+
83+
## Key Patterns
84+
85+
- Configuration via `github.com/umputun/go-flags` with struct tags for CLI/env options
86+
- Logging via `github.com/go-pkgz/lgr`
87+
- Middleware chain built with `github.com/go-pkgz/rest.Wrap()`
88+
- Mocks generated with moq, stored in same package with `_mock.go` suffix
89+
- Tests use testify assertions
90+
91+
## Environment Detection
92+
93+
Reproxy auto-detects docker environment via `REPROXY_IN_DOCKER` env var and adjusts default ports:
94+
- Docker: `0.0.0.0:8080` (http) / `0.0.0.0:8443` (https)
95+
- Non-docker: `127.0.0.1:80` (http) / `127.0.0.1:443` (https)

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.24-alpine AS backend
1+
FROM golang:1.25-alpine AS backend
22

33
ARG GIT_BRANCH
44
ARG GITHUB_SHA
@@ -19,7 +19,7 @@ RUN \
1919
cd app && go build -o /build/reproxy -ldflags "-X main.revision=${version} -s -w"
2020

2121

22-
FROM ghcr.io/umputun/baseimage/app:v1.15.0 AS base
22+
FROM ghcr.io/umputun/baseimage/app:latest AS base
2323

2424
FROM scratch
2525
LABEL org.opencontainers.image.source="https://github.com/umputun/reproxy"

app/discovery/discovery.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func (s *Service) Run(ctx context.Context) error {
127127
for {
128128
select {
129129
case <-ctx.Done():
130-
return ctx.Err()
130+
return fmt.Errorf("discovery service interrupted: %w", ctx.Err())
131131
case ev := <-ch:
132132
log.Printf("[DEBUG] new update event received, %s", ev)
133133
evRecv = true
@@ -548,7 +548,7 @@ func (m URLMapper) ping() (string, error) {
548548
return errMsg, fmt.Errorf("%s %s: %s, %s", m.Server, m.SrcMatch.String(), m.PingURL, resp.Status)
549549
}
550550

551-
return "", err
551+
return "", nil
552552
}
553553

554554
// Contains checks if the input string (e) in the given slice

app/discovery/discovery_test.go

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,20 @@ func TestService_Run(t *testing.T) {
5959

6060
err := svc.Run(ctx)
6161
require.Error(t, err)
62-
assert.Equal(t, context.DeadlineExceeded, err)
62+
require.ErrorIs(t, err, context.DeadlineExceeded)
6363
mappers := svc.Mappers()
64-
assert.Equal(t, 3, len(mappers))
64+
assert.Len(t, mappers, 3)
6565
assert.Equal(t, PIDocker, mappers[0].ProviderID)
6666
assert.Equal(t, "localhost", mappers[0].Server)
6767
assert.Equal(t, "/api/svc3/xyz", mappers[0].SrcMatch.String())
6868
assert.Equal(t, "http://127.0.0.3:8080/blah3/xyz", mappers[0].Dst)
6969
assert.Equal(t, []string{"127.0.0.1"}, mappers[0].OnlyFromIPs)
7070

71-
assert.Equal(t, 1, len(p1.EventsCalls()))
72-
assert.Equal(t, 1, len(p2.EventsCalls()))
71+
assert.Len(t, p1.EventsCalls(), 1)
72+
assert.Len(t, p2.EventsCalls(), 1)
7373

74-
assert.Equal(t, 1, len(p1.ListCalls()))
75-
assert.Equal(t, 1, len(p2.ListCalls()))
74+
assert.Len(t, p1.ListCalls(), 1)
75+
assert.Len(t, p2.ListCalls(), 1)
7676
}
7777

7878
func TestService_Match(t *testing.T) {
@@ -125,8 +125,8 @@ func TestService_Match(t *testing.T) {
125125

126126
err := svc.Run(ctx)
127127
require.Error(t, err)
128-
assert.Equal(t, context.DeadlineExceeded, err)
129-
assert.Equal(t, 12, len(svc.Mappers()))
128+
require.ErrorIs(t, err, context.DeadlineExceeded)
129+
assert.Len(t, svc.Mappers(), 12)
130130

131131
tbl := []struct {
132132
server, src string
@@ -166,7 +166,7 @@ func TestService_Match(t *testing.T) {
166166
for i, tt := range tbl {
167167
t.Run(strconv.Itoa(i)+"-"+tt.server, func(t *testing.T) {
168168
res := svc.Match(tt.server, tt.src)
169-
require.Equal(t, len(tt.res.Routes), len(res.Routes), res.Routes)
169+
require.Len(t, res.Routes, len(tt.res.Routes), res.Routes)
170170
for i := 0; i < len(res.Routes); i++ {
171171
assert.Equal(t, tt.res.Routes[i].Alive, res.Routes[i].Alive)
172172
assert.Equal(t, tt.res.Routes[i].Destination, res.Routes[i].Destination)
@@ -212,7 +212,7 @@ func TestService_MatchServerRegex(t *testing.T) {
212212

213213
err := svc.Run(ctx)
214214
require.Error(t, err)
215-
assert.Equal(t, context.DeadlineExceeded, err)
215+
require.ErrorIs(t, err, context.DeadlineExceeded)
216216

217217
tbl := []struct {
218218
name string
@@ -266,7 +266,7 @@ func TestService_MatchServerRegex(t *testing.T) {
266266
for i, tt := range tbl {
267267
t.Run(strconv.Itoa(i)+"-"+tt.server, func(t *testing.T) {
268268
res := svc.Match(tt.server, tt.src)
269-
require.Equal(t, len(tt.res.Routes), len(res.Routes), res.Routes)
269+
require.Len(t, res.Routes, len(tt.res.Routes), res.Routes)
270270
for i := 0; i < len(res.Routes); i++ {
271271
assert.Equal(t, tt.res.Routes[i].Alive, res.Routes[i].Alive)
272272
assert.Equal(t, tt.res.Routes[i].Destination, res.Routes[i].Destination)
@@ -296,7 +296,7 @@ func TestService_MatchServerRegexInvalidateCache(t *testing.T) {
296296

297297
go func() {
298298
err := svc.Run(ctx)
299-
require.Error(t, err)
299+
assert.Error(t, err)
300300
}()
301301

302302
res <- PIFile
@@ -314,7 +314,7 @@ func TestService_MatchServerRegexInvalidateCache(t *testing.T) {
314314
time.Sleep(50 * time.Millisecond)
315315

316316
match = svc.Match("test-server", "/")
317-
assert.Len(t, match.Routes, 0)
317+
assert.Empty(t, match.Routes)
318318
}
319319

320320
func TestService_MatchConflictRegex(t *testing.T) {
@@ -339,8 +339,8 @@ func TestService_MatchConflictRegex(t *testing.T) {
339339

340340
err := svc.Run(ctx)
341341
require.Error(t, err)
342-
assert.Equal(t, context.DeadlineExceeded, err)
343-
assert.Equal(t, 3, len(svc.Mappers()))
342+
require.ErrorIs(t, err, context.DeadlineExceeded)
343+
assert.Len(t, svc.Mappers(), 3)
344344

345345
tbl := []struct {
346346
server, src string
@@ -360,7 +360,7 @@ func TestService_MatchConflictRegex(t *testing.T) {
360360
tt := tt
361361
t.Run(strconv.Itoa(i), func(t *testing.T) {
362362
res := svc.Match(tt.server, tt.src)
363-
require.Equal(t, len(tt.res.Routes), len(res.Routes), res.Routes)
363+
require.Len(t, res.Routes, len(tt.res.Routes), res.Routes)
364364
for i := 0; i < len(res.Routes); i++ {
365365
assert.Equal(t, tt.res.Routes[i].Alive, res.Routes[i].Alive)
366366
assert.Equal(t, tt.res.Routes[i].Destination, res.Routes[i].Destination)
@@ -408,8 +408,8 @@ func TestService_Match192(t *testing.T) {
408408

409409
err := svc.Run(ctx)
410410
require.Error(t, err)
411-
assert.Equal(t, context.DeadlineExceeded, err)
412-
assert.Equal(t, 3, len(svc.Mappers()))
411+
require.ErrorIs(t, err, context.DeadlineExceeded)
412+
assert.Len(t, svc.Mappers(), 3)
413413

414414
tbl := []struct {
415415
server, src string
@@ -427,7 +427,7 @@ func TestService_Match192(t *testing.T) {
427427
tt := tt
428428
t.Run(strconv.Itoa(i), func(t *testing.T) {
429429
res := svc.Match(tt.server, tt.src)
430-
require.Equal(t, len(tt.res.Routes), len(res.Routes), res.Routes)
430+
require.Len(t, res.Routes, len(tt.res.Routes), res.Routes)
431431
for i := 0; i < len(res.Routes); i++ {
432432
assert.Equal(t, tt.res.Routes[i].Alive, res.Routes[i].Alive)
433433
assert.Equal(t, tt.res.Routes[i].Destination, res.Routes[i].Destination)
@@ -469,8 +469,8 @@ func TestService_Servers(t *testing.T) {
469469
svc := NewService([]Provider{p1, p2}, time.Millisecond*100)
470470
err := svc.Run(ctx)
471471
require.Error(t, err)
472-
assert.Equal(t, context.DeadlineExceeded, err)
473-
assert.Equal(t, 3, len(svc.mappers))
472+
require.ErrorIs(t, err, context.DeadlineExceeded)
473+
assert.Len(t, svc.mappers, 3)
474474

475475
servers := svc.Servers()
476476
assert.Equal(t, []string{"m.example.com", "xx.reproxy.io"}, servers)
@@ -601,18 +601,18 @@ func TestService_ScheduleHealthCheck(t *testing.T) {
601601

602602
err := svc.Run(ctx)
603603
require.Error(t, err)
604-
assert.Equal(t, context.DeadlineExceeded, err)
604+
require.ErrorIs(t, err, context.DeadlineExceeded)
605605
mappers := svc.Mappers()
606-
assert.Equal(t, 3, len(mappers))
606+
assert.Len(t, mappers, 3)
607607
assert.Equal(t, wantMappers, mappers)
608608

609609
svc.ScheduleHealthCheck(context.Background(), time.Microsecond*2)
610610
time.Sleep(time.Millisecond * 10)
611611

612612
mappers = svc.Mappers()
613-
assert.Equal(t, false, mappers[0].dead)
614-
assert.Equal(t, true, mappers[1].dead)
615-
assert.Equal(t, false, mappers[2].dead)
613+
assert.False(t, mappers[0].dead)
614+
assert.True(t, mappers[1].dead)
615+
assert.False(t, mappers[2].dead)
616616
}
617617

618618
func Test_ping(t *testing.T) {
@@ -709,8 +709,8 @@ func TestCheckHealth(t *testing.T) {
709709
t.Logf("mappers: %v", mappers)
710710

711711
res := svc.CheckHealth()
712-
assert.Equal(t, 3, len(res))
713-
assert.Error(t, res[failPingULR])
712+
assert.Len(t, res, 3)
713+
require.Error(t, res[failPingULR])
714714
assert.NoError(t, res[ts.URL])
715715
assert.NoError(t, res[ts2.URL])
716716
}

0 commit comments

Comments
 (0)