Skip to content

Commit b0c489b

Browse files
authored
feat: support TUI mode (#378)
## Summary Adds TUI (terminal UI) mode with bandwidth monitoring + reworks TUN networking and proxy auto-configuration internals. - TUI: bandwidth meter, exponential moving average smoothing, `--no-tui` headless flag. - TUN device: migrated from `water` to `wireguard-go`; unified state management across darwin/freebsd/linux; refactored network configuration to a job-based pattern (`ConfigurationJob`). - Server: `SetNetworkConfig` renamed to `AutoConfigureNetwork`; cleaner state lifecycle in darwin proxy. - Cross-platform gateway discovery consolidated; misc fixes (HOME resolution under sudo, freebsd loop, Linux IFF_VNET_HDR). ## Notes - Internal API renames (ConfigurationJob.Set/Unset → Up/Down, AutoConfigureNetwork signature, ListenAndServe ready-channel removal). These are internal only — no public consumer impact. - BREAKING CHANGE footers were removed from commit messages so release-please proposes a minor bump (v1.4.0). The renames are internal API changes, not user-facing breakage. ## Test plan - [x] Tests pass locally (`go test ./...`). - [x] golangci-lint clean. - [x] Manual TUI verification on darwin / linux. - [x] Sanity check on freebsd build.
1 parent b9fb0d1 commit b0c489b

68 files changed

Lines changed: 3769 additions & 1843 deletions

Some content is hidden

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

.claude/rules/documentation.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Documentation
2+
3+
User-facing documentation lives under `docs/` and is published as the mkdocs site. **Only update it when a change affects configuration or user-visible behavior.** Pure refactors, internal renames, test changes, and implementation tweaks that leave the public surface unchanged do not require doc updates.
4+
5+
Update `docs/` when a change:
6+
- adds, removes, or renames a config option (CLI flag, TOML key, env var, default value)
7+
- changes the runtime behavior a user can observe (proxy modes, DNS resolution, fake-packet behavior, TUI interactions, exit codes, log output users rely on)
8+
- changes install, build, or run instructions
9+
10+
Place updates in the section that matches the change:
11+
- `docs/user-guide/` — config options and runtime behavior (`app.md`, `connection.md`, `dns.md`, `https.md`, `udp.md`, `policy.md`, `configuration-recipes/`, etc.)
12+
- `docs/getting-started/` — install, quick-start, introduction
13+
- `docs/developer-guide/` — build/test/lint workflow, commit conventions
14+
15+
If unsure whether a change is user-visible, default to **no doc update** and mention it so the user can decide.

.claude/rules/formatting.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Formatting
2+
3+
Code must be formatted with `gofmt`. After any code change:
4+
- Check: `gofmt -l .` from the project root (no output means OK)
5+
- Format: `gofmt -w .` from the project root
6+
7+
Run `golangci-lint run` from the project root for additional linting; configuration lives in `.golangci.yml`.
8+
9+
All formatting and lint checks must pass before considering the task complete.

.claude/rules/git.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Git
2+
3+
Never create commits unless the user explicitly asks for it.
4+
5+
Commit messages follow the Conventional Commits style already used in this repo (`feat:`, `fix:`, `refactor:`, `refactor!:`, etc.), optionally scoped (`refactor(server):`).

.claude/rules/security.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Security
2+
3+
Never hardcode absolute paths containing usernames or system-specific directories.
4+
This applies to all files including source code, configuration, and settings files.
5+
6+
```go
7+
// incorrect
8+
path := "/Users/username/personal/spoofdpi/internal"
9+
10+
// correct
11+
home, _ := os.UserHomeDir()
12+
path := filepath.Join(home, ".spoofdpi")
13+
```
14+
15+
```json
16+
// incorrect
17+
{ "command": "cd /Users/username/personal/spoofdpi && go test ./..." }
18+
19+
// correct
20+
{ "command": "go test ./..." }
21+
```

.claude/rules/testing.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Testing
2+
3+
After any code change, run `go test ./...` from the project root and all tests must pass before considering the task complete.
4+
5+
These conventions follow the patterns established in `internal/config/*_test.go`. Apply them to new tests; only deviate when there is a clear, code-specific reason.
6+
7+
## Naming
8+
9+
- Method tests: `Test<Type>_<Method>` — e.g. `TestAppOptions_UnmarshalTOML`, `TestAppOptions_Clone`, `TestAppOptions_Merge`.
10+
- Free-function tests: `Test<Function>` — e.g. `TestCheckDomainPattern`, `TestMustParseBytes`.
11+
- Subtest names use lowercase phrases describing the scenario: `"valid general options"`, `"nil receiver"`, `"merge values"`, `"invalid type"`.
12+
13+
## Table-driven tests
14+
15+
Default to table-driven. The slice is always named `tcs`, never `tests` / `cases`:
16+
17+
```go
18+
tcs := []struct {
19+
name string
20+
input any
21+
wantErr bool
22+
assert func(t *testing.T, o AppOptions)
23+
}{
24+
// ...
25+
}
26+
27+
for _, tc := range tcs {
28+
t.Run(tc.name, func(t *testing.T) {
29+
var o AppOptions
30+
err := o.UnmarshalTOML(tc.input)
31+
if tc.wantErr {
32+
assert.Error(t, err)
33+
} else {
34+
assert.NoError(t, err)
35+
if tc.assert != nil {
36+
tc.assert(t, o)
37+
}
38+
}
39+
})
40+
}
41+
```
42+
43+
Common struct fields (use the names that fit the test, not all of them):
44+
- `name string` — required
45+
- `input` — input value, typed appropriately
46+
- `expected` — expected value when comparison is a single equality
47+
- `wantErr bool` — expected error flag
48+
- `wantPanic bool` — for `Must*` functions
49+
- `assert func(t *testing.T, ...)` — closure for non-trivial output verification (multi-field structs, pointer identity, etc.)
50+
- For `Merge`: `base`, `override`, `assert`
51+
- For `Clone`: `input`, `assert(t, input, output)` so the closure can verify identity vs. value
52+
53+
For simple validators where each case is just `(name, input, wantErr)`, use compact positional literals:
54+
55+
```go
56+
tcs := []struct {
57+
name string
58+
input string
59+
wantErr bool
60+
}{
61+
{"valid domain", "example.com", false},
62+
{"invalid empty", "", true},
63+
}
64+
```
65+
66+
## When inline `t.Run` is acceptable
67+
68+
When a test needs a handful of distinctly-shaped scenarios that don't share a struct cleanly (e.g. parsing different TOML snippets), inline `t.Run` blocks are fine — see `TestSegmentPlan_UnmarshalTOML`. Don't force a table when each case is structurally different.
69+
70+
## Assertions
71+
72+
- Use `github.com/stretchr/testify/assert` for normal checks.
73+
- Use `github.com/stretchr/testify/require` only when subsequent code in the test cannot meaningfully run on failure (setup invariants, "function never called"). See `internal/config/cli_test.go` for examples.
74+
- Use `assert.NotSame` to verify `Clone` returns a distinct pointer.
75+
- Use `assert.Panics` / `assert.NotPanics` for `Must*` functions.
76+
77+
## Constructing values
78+
79+
- Build optional pointer fields with `lo.ToPtr(...)` from `github.com/samber/lo`. Do not write `func() *T { v := x; return &v }()` helpers.
80+
- Build `*net.TCPAddr` with explicit `&net.TCPAddr{IP: net.ParseIP(...), Port: ...}`.
81+
82+
## Coverage
83+
84+
Each function should have tests covering: happy path, edge cases, and failure cases. For `UnmarshalTOML` always include an `"invalid type"` case; for `Clone` always include a `"nil receiver"` case; for `Merge` always include `"nil receiver"` and `"nil override"` cases.
85+
86+
## Section grouping
87+
88+
When a single `_test.go` file covers multiple related types, separate them with ASCII box headers (matches `types_test.go`):
89+
90+
```go
91+
// ┌─────────────────┐
92+
// │ GENERAL OPTIONS │
93+
// └─────────────────┘
94+
```
95+
96+
## Parallelism
97+
98+
Current config tests do not use `t.Parallel()`. Don't add it unless the rest of the suite is being migrated together — mixing parallel and serial tests in the same package can mask shared-state bugs.

.claude/settings.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"hooks": {
3+
"PreToolUse": [
4+
{
5+
"matcher": "Bash",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"if": "Bash(git commit*)",
10+
"command": "go test ./... && test -z \"$(gofmt -l .)\" && golangci-lint run",
11+
"timeout": 180,
12+
"statusMessage": "Running Go tests, format check, and golangci-lint..."
13+
}
14+
]
15+
}
16+
]
17+
}
18+
}

.gitmessage

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# types: ["feat", "fix", "chore", "refactor", "perf", "ui", "lsp", "plugin"]

.golangci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ linters:
2222
formatters:
2323
enable:
2424
- gci
25-
- gofmt
25+
# - gofmt
2626
- gofumpt
2727
- goimports
2828
- golines

Taskfile.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: '3'
2+
3+
tasks:
4+
run:
5+
desc: Build and run with dynamic arguments
6+
cmds:
7+
- go build ./cmd/...
8+
- sudo -E ./spoofdpi {{.CLI_ARGS}}

cmd/spoofdpi/logo.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
███████╗██████╗ ██████╗ ██████╗ ███████╗██████╗ ██████╗ ██╗
2+
██╔════╝██╔══██╗██╔═══██╗██╔═══██╗██╔════╝██╔══██╗██╔══██╗██║
3+
███████╗██████╔╝██║ ██║██║ ██║█████╗ ██║ ██║██████╔╝██║
4+
╚════██║██╔═══╝ ██║ ██║██║ ██║██╔══╝ ██║ ██║██╔═══╝ ██║
5+
███████║██║ ╚██████╔╝╚██████╔╝██║ ██████╔╝██║ ██║
6+
╚══════╝╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝

0 commit comments

Comments
 (0)