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")