feat: Add CI/CD configuration, pre-commit hooks, and enhance server TCP handling
cognitive-glitch committed
Oct 22, 2025 at 00:42 UTC
58c8aff41b10739e6b66d149be5e3cecb4850c3c
7 files changed
+977
-7
.github/workflows/ci.yml
new
+237
@@ -0,0 +1,237 @@
1
+name: CI
2
+
3
+on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+ workflow_dispatch:
9
+
10
+permissions:
11
+ contents: read
12
+ pull-requests: read
13
+
14
+jobs:
15
+ # Lint job - formatters and linters
16
+ lint:
17
+ name: Lint
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Set up Go
24
+ uses: actions/setup-go@v5
25
+ with:
26
+ go-version: '1.25.0'
27
+ cache: true
28
+
29
+ - name: Verify dependencies
30
+ run: |
31
+ go mod download
32
+ go mod verify
33
+
34
+ - name: Run gofumpt
35
+ run: |
36
+ go install mvdan.cc/gofumpt@latest
37
+ if [ -n "$(gofumpt -l .)" ]; then
38
+ echo "Go code is not formatted with gofumpt:"
39
+ gofumpt -d .
40
+ exit 1
41
+ fi
42
+
43
+ - name: Run goimports
44
+ run: |
45
+ go install golang.org/x/tools/cmd/goimports@latest
46
+ if [ -n "$(goimports -local github.com/gosuda/relaydns -l .)" ]; then
47
+ echo "Go imports are not formatted:"
48
+ goimports -local github.com/gosuda/relaydns -d .
49
+ exit 1
50
+ fi
51
+
52
+ - name: Run go vet
53
+ run: go vet ./...
54
+
55
+ - name: Install golangci-lint
56
+ uses: golangci/golangci-lint-action@v6
57
+ with:
58
+ version: v2.5.0
59
+ args: --timeout=5m --config=.golangci.yml
60
+
61
+ - name: Check go.mod tidiness
62
+ run: |
63
+ go mod tidy
64
+ if ! git diff --exit-code go.mod go.sum; then
65
+ echo "go.mod or go.sum is not tidy"
66
+ exit 1
67
+ fi
68
+
69
+ # Build job - verify binaries compile
70
+ build:
71
+ name: Build
72
+ runs-on: ubuntu-latest
73
+ strategy:
74
+ matrix:
75
+ go-version: ['1.25.0', '1.24.x']
76
+ steps:
77
+ - name: Checkout code
78
+ uses: actions/checkout@v4
79
+
80
+ - name: Set up Go ${{ matrix.go-version }}
81
+ uses: actions/setup-go@v5
82
+ with:
83
+ go-version: ${{ matrix.go-version }}
84
+ cache: true
85
+
86
+ - name: Download dependencies
87
+ run: go mod download
88
+
89
+ - name: Build server binary
90
+ run: go build -v -trimpath -o bin/relaydns-server ./cmd/server
91
+
92
+ - name: Build example HTTP client
93
+ run: go build -v -trimpath -o bin/relaydns-client ./cmd/example_http_client
94
+
95
+ - name: Build example chat client
96
+ run: go build -v -trimpath -o bin/relaydns-chat ./cmd/example_chat
97
+
98
+ - name: Upload binaries
99
+ uses: actions/upload-artifact@v4
100
+ if: matrix.go-version == '1.25.0'
101
+ with:
102
+ name: binaries
103
+ path: bin/*
104
+ retention-days: 7
105
+
106
+ # Test job - unit tests with race detection
107
+ test:
108
+ name: Test
109
+ runs-on: ubuntu-latest
110
+ strategy:
111
+ matrix:
112
+ go-version: ['1.25.0', '1.24.x']
113
+ steps:
114
+ - name: Checkout code
115
+ uses: actions/checkout@v4
116
+
117
+ - name: Set up Go ${{ matrix.go-version }}
118
+ uses: actions/setup-go@v5
119
+ with:
120
+ go-version: ${{ matrix.go-version }}
121
+ cache: true
122
+
123
+ - name: Download dependencies
124
+ run: go mod download
125
+
126
+ - name: Run tests
127
+ run: go test -v -race -timeout=5m -coverprofile=coverage.out -covermode=atomic ./...
128
+
129
+ - name: Generate coverage report
130
+ if: matrix.go-version == '1.25.0'
131
+ run: go tool cover -html=coverage.out -o coverage.html
132
+
133
+ - name: Upload coverage report
134
+ uses: actions/upload-artifact@v4
135
+ if: matrix.go-version == '1.25.0'
136
+ with:
137
+ name: coverage-report
138
+ path: coverage.html
139
+ retention-days: 7
140
+
141
+ - name: Check test coverage
142
+ if: matrix.go-version == '1.25.0'
143
+ run: |
144
+ coverage=$(go tool cover -func=coverage.out | grep total | awk '{print substr($3, 1, length($3)-1)}')
145
+ echo "Total test coverage: ${coverage}%"
146
+ if (( $(echo "$coverage < 30" | bc -l) )); then
147
+ echo "Warning: Test coverage is below 30%"
148
+ fi
149
+
150
+ # Sanitizers job - static analysis and security checks
151
+ sanitizers:
152
+ name: Sanitizers
153
+ runs-on: ubuntu-latest
154
+ steps:
155
+ - name: Checkout code
156
+ uses: actions/checkout@v4
157
+
158
+ - name: Set up Go
159
+ uses: actions/setup-go@v5
160
+ with:
161
+ go-version: '1.25.0'
162
+ cache: true
163
+
164
+ - name: Download dependencies
165
+ run: go mod download
166
+
167
+ - name: Run staticcheck
168
+ run: |
169
+ go install honnef.co/go/tools/cmd/staticcheck@latest
170
+ staticcheck ./...
171
+
172
+ - name: Run gosec (security scanner)
173
+ run: |
174
+ go install github.com/securego/gosec/v2/cmd/gosec@latest
175
+ gosec -fmt=json -out=gosec-report.json ./... || true
176
+ gosec ./...
177
+
178
+ - name: Upload gosec report
179
+ uses: actions/upload-artifact@v4
180
+ with:
181
+ name: gosec-report
182
+ path: gosec-report.json
183
+ retention-days: 7
184
+
185
+ - name: Check for ineffective assignments
186
+ run: |
187
+ go install github.com/gordonklaus/ineffassign@latest
188
+ ineffassign ./...
189
+
190
+ - name: Check for unused code
191
+ run: |
192
+ go install honnef.co/go/tools/cmd/staticcheck@latest
193
+ staticcheck -checks=U1000 ./...
194
+
195
+ # Docker build job
196
+ docker:
197
+ name: Docker Build
198
+ runs-on: ubuntu-latest
199
+ steps:
200
+ - name: Checkout code
201
+ uses: actions/checkout@v4
202
+
203
+ - name: Set up Docker Buildx
204
+ uses: docker/setup-buildx-action@v3
205
+
206
+ - name: Build Docker image
207
+ uses: docker/build-push-action@v6
208
+ with:
209
+ context: .
210
+ file: ./Dockerfile
211
+ push: false
212
+ tags: relaydns-server:ci
213
+ cache-from: type=gha
214
+ cache-to: type=gha,mode=max
215
+
216
+ # Dependency check job
217
+ dependencies:
218
+ name: Check Dependencies
219
+ runs-on: ubuntu-latest
220
+ steps:
221
+ - name: Checkout code
222
+ uses: actions/checkout@v4
223
+
224
+ - name: Set up Go
225
+ uses: actions/setup-go@v5
226
+ with:
227
+ go-version: '1.25.0'
228
+ cache: true
229
+
230
+ - name: Check for known vulnerabilities
231
+ run: |
232
+ go install golang.org/x/vuln/cmd/govulncheck@latest
233
+ govulncheck ./...
234
+
235
+ - name: Check for outdated dependencies
236
+ run: |
237
+ go list -u -m -json all | jq -r 'select(.Update != null) | "\(.Path): \(.Version) -> \(.Update.Version)"' || true
.pre-commit-config.yaml
new
+83
@@ -0,0 +1,83 @@
1
+# Pre-commit hooks for Go project
2
+# Install: pip install pre-commit && pre-commit install
3
+# Run manually: pre-commit run --all-files
4
+# Update hooks: pre-commit autoupdate
5
+
6
+repos:
7
+ # General file checks
8
+ - repo: https://github.com/pre-commit/pre-commit-hooks
9
+ rev: v6.0.0
10
+ hooks:
11
+ - id: trailing-whitespace
12
+ args: [--markdown-linebreak-ext=md]
13
+ - id: end-of-file-fixer
14
+ - id: check-yaml
15
+ args: [--allow-multiple-documents]
16
+ - id: check-added-large-files
17
+ args: [--maxkb=1024]
18
+ - id: check-merge-conflict
19
+ - id: check-case-conflict
20
+ - id: mixed-line-ending
21
+ args: [--fix=lf]
22
+ - id: detect-private-key
23
+
24
+ # YAML linting
25
+ - repo: https://github.com/adrienverge/yamllint
26
+ rev: v1.37.1
27
+ hooks:
28
+ - id: yamllint
29
+ args: [-c=.yamllint.yml]
30
+ exclude: ^(vendor/|\.github/)
31
+
32
+ # Local checks (formatting, go vet, mod tidy, build, test)
33
+ - repo: local
34
+ hooks:
35
+ - id: golangci-lint-run
36
+ name: golangci-lint run
37
+ description: Run golangci-lint to analyze Go code
38
+ entry: golangci-lint run
39
+ language: system
40
+ pass_filenames: false
41
+ types: [go]
42
+
43
+ - id: golangci-lint-fmt
44
+ name: golangci-lint fmt
45
+ description: Format Go code using golangci-lint
46
+ entry: golangci-lint fmt
47
+ language: system
48
+ pass_filenames: false
49
+ types: [go]
50
+
51
+ - id: go-vet
52
+ name: go vet
53
+ description: Run 'go vet' to examine Go source code
54
+ entry: go vet
55
+ language: system
56
+ pass_filenames: false
57
+ types: [go]
58
+ args: [./...]
59
+
60
+ - id: go-mod-tidy
61
+ name: go mod tidy
62
+ description: Run 'go mod tidy' to ensure go.mod matches source
63
+ entry: bash -c 'go mod tidy && git diff --exit-code go.mod go.sum'
64
+ language: system
65
+ pass_filenames: false
66
+ always_run: true
67
+
68
+ - id: go-build
69
+ name: go build
70
+ description: Build all Go binaries to ensure they compile
71
+ entry: bash -c 'go build -trimpath -o /tmp/relaydns-server ./cmd/server && go build -trimpath -o /tmp/relaydns-client ./cmd/example_http_client && go build -trimpath -o /tmp/relaydns-chat ./cmd/example_chat'
72
+ language: system
73
+ pass_filenames: false
74
+ types: [go]
75
+
76
+ - id: go-test
77
+ name: go test
78
+ description: Run unit tests with race detector
79
+ entry: go test
80
+ language: system
81
+ pass_filenames: false
82
+ types: [go]
83
+ args: [-v, -race, -timeout=3m, ./...]
.yamllint.yml
new
+14
@@ -0,0 +1,14 @@
1
+---
2
+extends: default
3
+
4
+rules:
5
+ line-length:
6
+ max: 120
7
+ level: warning
8
+ indentation:
9
+ spaces: 2
10
+ document-start: disable
11
+ truthy:
12
+ allowed-values: ['true', 'false', 'on', 'off']
13
+ comments:
14
+ min-spaces-from-content: 1
CI_CD.md
new
+475
@@ -0,0 +1,475 @@
1
+# CI/CD Setup Guide
2
+
3
+This document describes the CI/CD infrastructure for the RelayDNS project, including pre-commit hooks, linters, formatters, sanitizers, and GitHub Actions workflows.
4
+
5
+## Table of Contents
6
+
7
+- [Overview](#overview)
8
+- [Quick Start](#quick-start)
9
+- [Tools](#tools)
10
+- [Pre-commit Hooks](#pre-commit-hooks)
11
+- [GitHub Actions](#github-actions)
12
+- [Makefile Targets](#makefile-targets)
13
+- [Configuration Files](#configuration-files)
14
+- [Troubleshooting](#troubleshooting)
15
+
16
+## Overview
17
+
18
+The CI/CD setup includes:
19
+
20
+1. **Pre-commit hooks** - Run checks locally before commits
21
+2. **golangci-lint** - Comprehensive Go linting with 30+ linters
22
+3. **Formatters** - gofmt, goimports, gci
23
+4. **Sanitizers** - Race detector, staticcheck, gosec, govulncheck
24
+5. **GitHub Actions** - Automated CI pipeline on push/PR
25
+
26
+## Quick Start
27
+
28
+### 1. Install Required Tools
29
+
30
+```bash
31
+# Install Go tools
32
+make install-tools
33
+
34
+# Install golangci-lint
35
+# See: https://golangci-lint.run/usage/install/
36
+curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0
37
+
38
+# Install pre-commit (Python required)
39
+pip install pre-commit
40
+
41
+# Install pre-commit hooks
42
+make pre-commit-install
43
+```
44
+
45
+### 2. Run Local CI Checks
46
+
47
+```bash
48
+# Run all local CI checks (fmt, tidy, vet, lint, test-race)
49
+make ci-local
50
+
51
+# Or run individual checks
52
+make fmt # Format code
53
+make lint # Run linters
54
+make test-race # Run tests with race detector
55
+```
56
+
57
+### 3. Commit Changes
58
+
59
+After installing pre-commit hooks, every commit will automatically run:
60
+- File checks (trailing whitespace, EOF, YAML validation)
61
+- Go formatting (gofmt, goimports)
62
+- Go vet
63
+- golangci-lint
64
+- Build check (ensure all binaries compile)
65
+- Tests with race detector
66
+
67
+```bash
68
+git add .
69
+git commit -m "Your commit message"
70
+# Pre-commit hooks will run automatically
71
+```
72
+
73
+## Tools
74
+
75
+### golangci-lint
76
+
77
+Configuration: `.golangci.yml`
78
+
79
+Enabled linters:
80
+- **Formatters**: gofmt, goimports, gci
81
+- **Core**: govet, staticcheck, errcheck, gosimple, ineffassign, unused
82
+- **Style**: revive, stylecheck
83
+- **Security**: gosec, bodyclose, noctx, rowserrcheck, sqlclosecheck
84
+- **Complexity**: gocyclo, gocognit, cyclop, nestif
85
+- **Error handling**: errname, errorlint
86
+- **Performance**: prealloc
87
+- **Code quality**: dupl, goconst, unconvert, unparam, nakedret, misspell
88
+
89
+Usage:
90
+```bash
91
+# Run all linters
92
+make lint
93
+
94
+# Run with auto-fix
95
+make lint-fix
96
+
97
+# Run golangci-lint directly
98
+golangci-lint run --timeout=5m --config=.golangci.yml
99
+```
100
+
101
+### Formatters
102
+
103
+**gofmt** - Standard Go formatter
104
+```bash
105
+go fmt ./...
106
+```
107
+
108
+**goimports** - Organizes imports
109
+```bash
110
+goimports -local github.com/gosuda/relaydns -w .
111
+```
112
+
113
+**gci** - Controls import order (integrated in golangci-lint)
114
+- Standard library imports
115
+- Third-party imports
116
+- Local imports (github.com/gosuda/relaydns)
117
+
118
+### Sanitizers
119
+
120
+**Race Detector** - Detects data races
121
+```bash
122
+make test-race
123
+go test -race ./...
124
+```
125
+
126
+**staticcheck** - Advanced static analysis
127
+```bash
128
+make staticcheck
129
+staticcheck ./...
130
+```
131
+
132
+**gosec** - Security scanner
133
+```bash
134
+make gosec
135
+gosec ./...
136
+```
137
+
138
+**govulncheck** - Vulnerability scanner
139
+```bash
140
+make govulncheck
141
+govulncheck ./...
142
+```
143
+
144
+## Pre-commit Hooks
145
+
146
+Configuration: `.pre-commit-config.yaml`
147
+
148
+Hooks run automatically before each commit:
149
+
150
+1. **General file checks**
151
+ - Trailing whitespace
152
+ - End of file fixer
153
+ - YAML validation
154
+ - Large file check (max 1MB)
155
+ - Merge conflict detection
156
+ - Private key detection
157
+
158
+2. **Go checks**
159
+ - `go fmt ./...`
160
+ - `goimports -local github.com/gosuda/relaydns`
161
+ - `go vet ./...`
162
+ - `go mod tidy` (ensures go.mod is clean)
163
+ - `golangci-lint run`
164
+
165
+3. **Build verification**
166
+ - Compile all binaries (server, client, chat)
167
+
168
+4. **Tests**
169
+ - Run all tests with race detector
170
+
171
+5. **Additional linting**
172
+ - YAML linting (yamllint)
173
+ - Dockerfile linting (hadolint)
174
+
175
+### Managing Pre-commit Hooks
176
+
177
+```bash
178
+# Install hooks
179
+make pre-commit-install
180
+
181
+# Run manually on all files
182
+make pre-commit-run
183
+pre-commit run --all-files
184
+
185
+# Run manually on staged files
186
+pre-commit run
187
+
188
+# Update hooks to latest versions
189
+pre-commit autoupdate
190
+
191
+# Skip hooks for a commit (use sparingly)
192
+git commit --no-verify -m "Emergency fix"
193
+```
194
+
195
+## GitHub Actions
196
+
197
+Configuration: `.github/workflows/ci.yml`
198
+
199
+Triggered on:
200
+- Push to `main` or `develop` branches
201
+- Pull requests to `main` or `develop`
202
+- Manual workflow dispatch
203
+
204
+### Jobs
205
+
206
+#### 1. Lint Job
207
+- Runs on: `ubuntu-latest`
208
+- Go version: `1.25.0`
209
+- Steps:
210
+ - Verify dependencies
211
+ - Run gofmt check
212
+ - Run goimports check
213
+ - Run go vet
214
+ - Run golangci-lint
215
+ - Check go.mod tidiness
216
+
217
+#### 2. Build Job
218
+- Runs on: `ubuntu-latest`
219
+- Go versions: `1.25.0`, `1.24.x` (matrix)
220
+- Steps:
221
+ - Build server binary
222
+ - Build example HTTP client
223
+ - Build example chat client
224
+ - Upload binaries (artifacts, 7 days retention)
225
+
226
+#### 3. Test Job
227
+- Runs on: `ubuntu-latest`
228
+- Go versions: `1.25.0`, `1.24.x` (matrix)
229
+- Steps:
230
+ - Run tests with race detector
231
+ - Generate coverage report
232
+ - Upload coverage HTML (7 days retention)
233
+ - Check coverage threshold (warning if < 30%)
234
+
235
+#### 4. Sanitizers Job
236
+- Runs on: `ubuntu-latest`
237
+- Go version: `1.25.0`
238
+- Steps:
239
+ - Run staticcheck
240
+ - Run gosec (security scanner)
241
+ - Upload gosec report (JSON)
242
+ - Check for ineffective assignments
243
+ - Check for unused code
244
+
245
+#### 5. Docker Job
246
+- Runs on: `ubuntu-latest`
247
+- Steps:
248
+ - Build Docker image
249
+ - Use buildx for caching
250
+ - Validate Dockerfile
251
+
252
+#### 6. Dependencies Job
253
+- Runs on: `ubuntu-latest`
254
+- Go version: `1.25.0`
255
+- Steps:
256
+ - Run govulncheck (vulnerability scanner)
257
+ - Check for outdated dependencies
258
+
259
+### Viewing Results
260
+
261
+- Go to GitHub Actions tab in your repository
262
+- Click on a workflow run to see job details
263
+- Download artifacts (binaries, coverage reports) from workflow summary
264
+
265
+## Makefile Targets
266
+
267
+### Development
268
+
269
+```bash
270
+make fmt # Format Go code with gofmt and goimports
271
+make tidy # Tidy go.mod
272
+make vet # Run go vet
273
+```
274
+
275
+### Linting
276
+
277
+```bash
278
+make lint # Run golangci-lint
279
+make lint-fix # Run golangci-lint with auto-fix
280
+```
281
+
282
+### Testing
283
+
284
+```bash
285
+make test # Run unit tests
286
+make test-race # Run tests with race detector
287
+make test-coverage # Run tests with coverage report (generates coverage.html)
288
+```
289
+
290
+### Static Analysis & Security
291
+
292
+```bash
293
+make staticcheck # Run staticcheck
294
+make gosec # Run gosec security scanner
295
+make govulncheck # Check for known vulnerabilities
296
+```
297
+
298
+### Build
299
+
300
+```bash
301
+make build-all # Build all binaries (server, client, chat)
302
+make client-build # Build example HTTP client
303
+make chat-build # Build example chat client
304
+```
305
+
306
+### Pre-commit
307
+
308
+```bash
309
+make pre-commit-install # Install pre-commit hooks
310
+make pre-commit-run # Run pre-commit on all files
311
+```
312
+
313
+### CI/CD
314
+
315
+```bash
316
+make ci-local # Run local CI checks (fmt, tidy, vet, lint, test-race)
317
+make install-tools # Install development tools
318
+make clean # Remove build artifacts and reports
319
+```
320
+
321
+## Configuration Files
322
+
323
+### `.golangci.yml`
324
+
325
+Main linter configuration with 30+ linters enabled.
326
+
327
+Key settings:
328
+- Timeout: 5 minutes
329
+- Go version: 1.25
330
+- Local prefix: `github.com/gosuda/relaydns`
331
+- Excludes test files from certain linters
332
+- Excludes example code from security checks
333
+
334
+### `.pre-commit-config.yaml`
335
+
336
+Pre-commit hooks configuration.
337
+
338
+Repos:
339
+- `pre-commit/pre-commit-hooks`: General file checks
340
+- `dnephin/pre-commit-golang`: Go formatting and checks
341
+- `golangci/golangci-lint`: Comprehensive linting
342
+- `local`: Build and test checks
343
+- `adrienverge/yamllint`: YAML linting
344
+- `hadolint/hadolint`: Dockerfile linting
345
+
346
+### `.yamllint.yml`
347
+
348
+YAML linting configuration.
349
+
350
+Settings:
351
+- Max line length: 120 (warning level)
352
+- Indentation: 2 spaces
353
+- Document start: disabled
354
+- Truthy values: `true`, `false`, `on`, `off`
355
+
356
+### `.github/workflows/ci.yml`
357
+
358
+GitHub Actions workflow definition with 6 jobs:
359
+1. Lint
360
+2. Build
361
+3. Test
362
+4. Sanitizers
363
+5. Docker
364
+6. Dependencies
365
+
366
+## Troubleshooting
367
+
368
+### golangci-lint fails with "deadline exceeded"
369
+
370
+Increase timeout:
371
+```bash
372
+golangci-lint run --timeout=10m
373
+```
374
+
375
+Or edit `.golangci.yml`:
376
+```yaml
377
+run:
378
+ timeout: 10m
379
+```
380
+
381
+### Pre-commit hooks are slow
382
+
383
+Skip build and test checks for faster commits:
384
+```bash
385
+SKIP=go-build,go-test git commit -m "Quick fix"
386
+```
387
+
388
+Or disable specific hooks in `.pre-commit-config.yaml`.
389
+
390
+### GitHub Actions fails on Go 1.24.x
391
+
392
+The project requires Go 1.25.0 features. Consider removing Go 1.24.x from the matrix if incompatible.
393
+
394
+Edit `.github/workflows/ci.yml`:
395
+```yaml
396
+strategy:
397
+ matrix:
398
+ go-version: ['1.25.0'] # Remove 1.24.x
399
+```
400
+
401
+### Pre-commit installation fails
402
+
403
+Ensure Python and pip are installed:
404
+```bash
405
+python3 --version
406
+pip --version
407
+pip install --user pre-commit
408
+```
409
+
410
+### golangci-lint not found
411
+
412
+Install golangci-lint:
413
+```bash
414
+# Linux/macOS
415
+curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0
416
+
417
+# Or using Go
418
+go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.5.0
419
+
420
+# Verify installation
421
+golangci-lint --version
422
+```
423
+
424
+### Tests fail with race detector
425
+
426
+Race detector found a data race. Fix the race condition in your code:
427
+1. Review the race detector output
428
+2. Identify the conflicting goroutines
429
+3. Add proper synchronization (mutex, channel, atomic)
430
+4. Re-run tests
431
+
432
+### Coverage report not generated
433
+
434
+Ensure you have write permissions and run:
435
+```bash
436
+make test-coverage
437
+# Opens coverage.html in browser
438
+open coverage.html # macOS
439
+xdg-open coverage.html # Linux
440
+```
441
+
442
+## Best Practices
443
+
444
+1. **Always run `make ci-local` before pushing**
445
+ - Catches issues early
446
+ - Reduces CI failures
447
+
448
+2. **Fix linter warnings**
449
+ - Don't disable linters without good reason
450
+ - Use `//nolint:lintername` sparingly with explanation
451
+
452
+3. **Maintain test coverage**
453
+ - Aim for > 50% coverage
454
+ - Write tests for critical paths
455
+ - Use table-driven tests
456
+
457
+4. **Keep dependencies updated**
458
+ - Run `make govulncheck` regularly
459
+ - Update vulnerable dependencies promptly
460
+
461
+5. **Use pre-commit hooks**
462
+ - Prevents committing broken code
463
+ - Enforces code quality standards
464
+
465
+6. **Review GitHub Actions failures**
466
+ - Don't merge PRs with failing checks
467
+ - Investigate root causes, don't just re-run
468
+
469
+## Additional Resources
470
+
471
+- [golangci-lint documentation](https://golangci-lint.run/)
472
+- [pre-commit documentation](https://pre-commit.com/)
473
+- [GitHub Actions documentation](https://docs.github.com/en/actions)
474
+- [Go race detector](https://go.dev/doc/articles/race_detector)
475
+- [staticcheck documentation](https://staticcheck.io/)
Makefile
+143
-4
@@ -1,5 +1,7 @@
1
SHELL := /bin/sh
2
-.PHONY: help server-up server-down server-build client-run client-build chat-run chat-build fmt tidy
2
+.PHONY: help server-up server-down server-build client-run client-build chat-run chat-build \
3
+ fmt tidy lint lint-fix test test-race test-coverage build-all clean \
4
+ install-tools pre-commit-install ci-local
5
6
# Detect docker compose command (override with `make DC="docker-compose"` if needed)
7
DC ?= docker compose
@@ -43,21 +45,158 @@ chat-run:
45
chat-build:
46
go build -trimpath -o bin/relaydns-chat ./cmd/example_chat
47
48
+# ---------- Build all binaries ----------
49
+build-all: clean
50
+ @echo "Building all binaries..."
51
+ @mkdir -p bin
52
+ go build -trimpath -o bin/relaydns-server ./cmd/server
53
+ go build -trimpath -o bin/relaydns-client ./cmd/example_http_client
54
+ go build -trimpath -o bin/relaydns-chat ./cmd/example_chat
55
+ @echo "Binaries built successfully in ./bin/"
56
+
57
# ---------- Dev helpers ----------
58
fmt:
48
- go fmt ./...
59
+ @echo "Formatting Go code..."
60
+ @command -v gofumpt >/dev/null 2>&1 || { echo "Installing gofumpt..."; go install mvdan.cc/gofumpt@latest; }
61
+ gofumpt -l -w .
62
+ @command -v goimports >/dev/null 2>&1 || { echo "Installing goimports..."; go install golang.org/x/tools/cmd/goimports@latest; }
63
+ goimports -local github.com/gosuda/relaydns -w .
64
65
tidy:
66
+ @echo "Tidying go.mod..."
67
go mod tidy
68
69
+# ---------- Linting ----------
70
+lint:
71
+ @echo "Running linters..."
72
+ @command -v golangci-lint >/dev/null 2>&1 || { echo "golangci-lint not found. Install: https://golangci-lint.run/usage/install/"; exit 1; }
73
+ golangci-lint run --timeout=5m --config=.golangci.yml
74
+
75
+lint-fix:
76
+ @echo "Running linters with auto-fix..."
77
+ @command -v golangci-lint >/dev/null 2>&1 || { echo "golangci-lint not found. Install: https://golangci-lint.run/usage/install/"; exit 1; }
78
+ golangci-lint run --timeout=5m --config=.golangci.yml --fix
79
+
80
+# ---------- Testing ----------
81
+test:
82
+ @echo "Running tests..."
83
+ go test -v -timeout=5m ./...
84
+
85
+test-race:
86
+ @echo "Running tests with race detector..."
87
+ go test -v -race -timeout=5m ./...
88
+
89
+test-coverage:
90
+ @echo "Running tests with coverage..."
91
+ go test -v -race -timeout=5m -coverprofile=coverage.out -covermode=atomic ./...
92
+ go tool cover -html=coverage.out -o coverage.html
93
+ @echo "Coverage report generated: coverage.html"
94
+ @go tool cover -func=coverage.out | grep total
95
+
96
+# ---------- Static analysis & sanitizers ----------
97
+vet:
98
+ @echo "Running go vet..."
99
+ go vet ./...
100
+
101
+staticcheck:
102
+ @echo "Running staticcheck..."
103
+ @command -v staticcheck >/dev/null 2>&1 || { echo "Installing staticcheck..."; go install honnef.co/go/tools/cmd/staticcheck@latest; }
104
+ staticcheck ./...
105
+
106
+gosec:
107
+ @echo "Running gosec (security scanner)..."
108
+ @command -v gosec >/dev/null 2>&1 || { echo "Installing gosec..."; go install github.com/securego/gosec/v2/cmd/gosec@latest; }
109
+ gosec ./...
110
+
111
+govulncheck:
112
+ @echo "Checking for known vulnerabilities..."
113
+ @command -v govulncheck >/dev/null 2>&1 || { echo "Installing govulncheck..."; go install golang.org/x/vuln/cmd/govulncheck@latest; }
114
+ govulncheck ./...
115
+
116
+# ---------- Pre-commit hooks ----------
117
+pre-commit-install:
118
+ @echo "Installing pre-commit hooks..."
119
+ @command -v pre-commit >/dev/null 2>&1 || { echo "pre-commit not found. Install: pip install pre-commit"; exit 1; }
120
+ pre-commit install
121
+ @echo "Pre-commit hooks installed successfully"
122
+
123
+pre-commit-run:
124
+ @echo "Running pre-commit hooks on all files..."
125
+ @command -v pre-commit >/dev/null 2>&1 || { echo "pre-commit not found. Install: pip install pre-commit"; exit 1; }
126
+ pre-commit run --all-files
127
+
128
+# ---------- Tool installation ----------
129
+install-tools:
130
+ @echo "Installing development tools..."
131
+ go install golang.org/x/tools/cmd/goimports@latest
132
+ go install mvdan.cc/gofumpt@latest
133
+ go install honnef.co/go/tools/cmd/staticcheck@latest
134
+ go install github.com/securego/gosec/v2/cmd/gosec@latest
135
+ go install golang.org/x/vuln/cmd/govulncheck@latest
136
+ go install github.com/gordonklaus/ineffassign@latest
137
+ @echo "Tools installed successfully"
138
+ @echo "Note: Install golangci-lint separately: https://golangci-lint.run/usage/install/"
139
+ @echo "Note: Install pre-commit separately: pip install pre-commit"
140
+
141
+# ---------- CI local simulation ----------
142
+ci-local: fmt tidy vet lint test-race
143
+ @echo "Local CI checks completed successfully"
144
+
145
+# ---------- Cleanup ----------
146
+clean:
147
+ @echo "Cleaning build artifacts..."
148
+ rm -rf bin/
149
+ rm -f coverage.out coverage.html
150
+ rm -f gosec-report.json
151
+ @echo "Cleanup completed"
152
+
153
+# ---------- Help ----------
154
help:
155
+ @echo "RelayDNS Makefile"
156
+ @echo ""
157
@echo "Server:"
158
@echo " make server-up # build and start relayserver (docker compose)"
159
@echo " make server-down # stop and remove containers"
57
- @echo "\nClients (optional):"
160
+ @echo " make server-build # build server image only"
161
+ @echo ""
162
+ @echo "Clients (optional):"
163
@echo " make client-run # run example_http_client locally"
164
@echo " make client-build # build example_http_client to ./bin/relaydns-client"
165
@echo " make chat-run # run example_chat locally (WS UI + advertiser)"
166
@echo " make chat-build # build example_chat to ./bin/relaydns-chat"
62
- @echo "\nFlags (override with make VAR=value):"
167
+ @echo ""
168
+ @echo "Build:"
169
+ @echo " make build-all # build all binaries (server, client, chat)"
170
+ @echo ""
171
+ @echo "Development:"
172
+ @echo " make fmt # format Go code with gofmt and goimports"
173
+ @echo " make tidy # tidy go.mod"
174
+ @echo " make vet # run go vet"
175
+ @echo " make lint # run golangci-lint"
176
+ @echo " make lint-fix # run golangci-lint with auto-fix"
177
+ @echo ""
178
+ @echo "Testing:"
179
+ @echo " make test # run unit tests"
180
+ @echo " make test-race # run tests with race detector"
181
+ @echo " make test-coverage # run tests with coverage report"
182
+ @echo ""
183
+ @echo "Static Analysis & Security:"
184
+ @echo " make staticcheck # run staticcheck"
185
+ @echo " make gosec # run gosec security scanner"
186
+ @echo " make govulncheck # check for known vulnerabilities"
187
+ @echo ""
188
+ @echo "Pre-commit:"
189
+ @echo " make pre-commit-install # install pre-commit hooks"
190
+ @echo " make pre-commit-run # run pre-commit on all files"
191
+ @echo ""
192
+ @echo "Tools:"
193
+ @echo " make install-tools # install development tools"
194
+ @echo ""
195
+ @echo "CI/CD:"
196
+ @echo " make ci-local # run local CI checks (fmt, tidy, vet, lint, test-race)"
197
+ @echo ""
198
+ @echo "Cleanup:"
199
+ @echo " make clean # remove build artifacts and reports"
200
+ @echo ""
201
+ @echo "Flags (override with make VAR=value):"
202
@echo " SERVER_URL BACKEND_PORT CHAT_PORT CHAT_NAME"
README.md
+1
-1
@@ -126,4 +126,4 @@ Chat client flags (see `make chat-run`):
126
- Cloudflare DNS:
127
- Web UI/proxy (8080): can be proxied (orange cloud) if using HTTP/HTTPS
128
- libp2p (4001 tcp/udp): must be DNS only (gray cloud). Cloudflare proxy doesn’t support arbitrary TCP/UDP ports.
129
-- WebSocket: server proxies 101 and tunnels bytes; the chat backend allows any HTTP(S) Origin for demo. Restrict in production.
\ No newline at end of file
129
+- WebSocket: server proxies 101 and tunnels bytes; the chat backend allows any HTTP(S) Origin for demo. Restrict in production.
cmd/server/main.go
+24
-2
@@ -6,6 +6,7 @@ import (
6
"net"
7
"os"
8
"os/signal"
9
+ "sync"
10
"syscall"
11
"time"
12
@@ -43,6 +44,9 @@ func runServer(cmd *cobra.Command, args []string) error {
44
ctx, cancel := context.WithCancel(context.Background())
45
defer cancel()
46
47
+ // Wait group for tracking TCP ingress goroutines
48
+ var tcpWg sync.WaitGroup
49
+
50
h, err := relaydns.MakeHost(ctx, flagP2pPort, true)
51
if err != nil {
52
return err
@@ -57,7 +61,8 @@ func runServer(cmd *cobra.Command, args []string) error {
61
62
// Optional raw TCP ingress (e.g., SSH)
63
if flagTcpPort > 0 {
60
- go serveTCPIngress(ctx, fmt.Sprintf(":%d", flagTcpPort), d)
64
+ tcpWg.Add(1)
65
+ go serveTCPIngress(ctx, fmt.Sprintf(":%d", flagTcpPort), d, &tcpWg)
66
}
67
68
// graceful shutdown
@@ -86,23 +91,35 @@ func runServer(cmd *cobra.Command, args []string) error {
91
log.Warn().Err(err).Msg("[server] libp2p host close error")
92
}
93
94
+ // Wait for all TCP ingress goroutines to complete
95
+ log.Debug().Msg("[server] waiting for TCP ingress goroutines...")
96
+ tcpWg.Wait()
97
+ log.Debug().Msg("[server] all TCP ingress goroutines stopped")
98
+
99
log.Info().Msg("[server] shutdown complete")
100
return nil
101
}
102
103
// serveTCPIngress listens on addr for raw TCP (e.g., SSH) and proxies
104
// incoming connections to a chosen peer over libp2p stream using Director.
95
-func serveTCPIngress(ctx context.Context, addr string, d *relaydns.RelayServer) {
105
+func serveTCPIngress(ctx context.Context, addr string, d *relaydns.RelayServer, wg *sync.WaitGroup) {
106
+ defer wg.Done()
107
+
108
ln, err := net.Listen("tcp", addr)
109
if err != nil {
110
log.Error().Err(err).Msgf("tcp ingress listen failed: %s", addr)
111
return
112
}
113
log.Info().Msgf("[server] tcp ingress: %s", addr)
114
+
115
+ // Goroutine to close listener on context cancellation
116
+ wg.Add(1)
117
go func() {
118
+ defer wg.Done()
119
<-ctx.Done()
120
_ = ln.Close()
121
}()
122
+
123
for {
124
conn, err := ln.Accept()
125
if err != nil {
@@ -113,7 +130,12 @@ func serveTCPIngress(ctx context.Context, addr string, d *relaydns.RelayServer)
130
}
131
continue
132
}
133
+
134
+ // Launch connection handler with wait group tracking
135
+ wg.Add(1)
136
go func(c net.Conn) {
137
+ defer wg.Done()
138
+
139
hosts := d.Hosts()
140
if len(hosts) == 0 {
141
log.Warn().Msg("tcp ingress: no backend peers available")