SPLITTER is a Go-based tool that creates and manages multiple Tor network instances load-balanced via HAProxy, with geolocation-based anti-correlation rules for Tor entry/exit node selection.
Two proxy modes are supported:
- Native (default): Uses Tor's built-in
HTTPTunnelPort— no Privoxy dependency. TCP path: User -> HAProxy -> Tor (per-instance) -> Tor Network -> Destination. - Legacy: Uses Privoxy as the HTTP-to-SOCKS bridge. TCP path: User -> HAProxy -> Privoxy (per-instance) -> Tor (per-instance) -> Tor Network -> Destination.
Modern Tor features are auto-detected and enabled when available: Conflux (multi-leg circuits), congestion control, CGO relay cryptography, post-quantum key exchange, and bridge/pluggable transport support (Snowflake, WebTunnel, obfs4).
go build -o splitter .# Run with defaults
./splitter run
# Run with flags
./splitter run --instances 3 --countries 8 --relay-enforce exit
# Short flags (legacy aliases): -i, -c, -re
./splitter run -i 3 -c 8 -re exit
# Run with a profile
./splitter run --profile stealth
# Other subcommands
./splitter status
./splitter test dns
./splitter version
# Relay enforce modes: entry (default, best security), exit (GeoIP bypass), speed (fastest)
# Proxy modes: native (default), legacy (Privoxy)# Run all tests
go test ./...
# Run tests for a specific package
go test ./internal/config/...
# Run with verbose output
go test -v ./...
# Run integration tests (requires tor, haproxy installed)
go test -tags=integration ./...# Go vet
go vet ./...
# golangci-lint (if installed)
golangci-lint rundocker build -t splitter .
docker run -d --name splitter -p 63536:63536 -p 63537:63537 splitter
# Or with docker-compose
docker compose up -dmain.go # Entry point
go.mod / go.sum # Go module definition
cmd/
root.go # Root Cobra command
run.go # `splitter run` subcommand
status.go # `splitter status` - live dashboard
test.go # `splitter test dns` / `splitter test exit-reputation`
version.go # `splitter version` - detected Tor features
reload.go # SIGHUP config reload handler
internal/
cli/ # Cobra setup, flag bindings, input validation
config/ # Config loading: YAML + env vars (SPLITTER_*) + CLI flags
tor/ # Tor instance lifecycle: spawn, config gen, signal, restart
haproxy/ # HAProxy config generation, process management
proxy/ # Proxy abstraction: HTTPTunnelPort (native) or Privoxy (legacy)
country/ # Country selection, rotation daemon, Tor Metrics API client
circuit/ # Circuit renewal, NEWNYM via Tor control protocol (cookie auth)
process/ # Process group lifecycle: spawn, graceful shutdown, SIGTERM->SIGKILL
metrics/ # Prometheus metrics endpoint
health/ # Health checks, DNS leak tests, exit node reputation
network/ # Port allocation via net.Listen (no netstat)
profile/ # Predefined profiles: stealth, balanced, streaming, pentest
template/ # Go template helpers for config generation
templates/
torrc.gotmpl # Tor config template
haproxy.cfg.gotmpl # HAProxy config template
privoxy.cfg.gotmpl # Privoxy config template (legacy mode only)
configs/
default.yaml # Default configuration (replaces settings.cfg)
bridges.yaml # Bridge configuration (Snowflake, obfs4, WebTunnel)
profiles.yaml # Profile definitions (stealth, balanced, streaming, pentest)
useragents.yaml # Bundled Tor Browser User-Agent list
legacy/ # Original Bash version preserved for reference
splitter.sh
func/
settings.cfg
Dockerfile # Multi-stage: golang build -> alpine runtime
docker-compose.yml # Service definition with healthcheck
- Go 1.23+ (module:
github.com/user/splitter). - Use Go modules exclusively; no
GOPATHmode. - Dependencies:
cobra(CLI),gopkg.in/yaml.v3(config). Avoid adding new dependencies without justification.
- Package names: lowercase, single word, no underscores (e.g.,
tor,haproxy,network). - Package names match their directory name.
- No
utilorhelperspackages — put functionality in domain-specific packages.
- Wrap errors with context:
fmt.Errorf("functionName: %w", err). - Always check errors; never silently discard them with
_. - Return errors up the call stack; handle at the appropriate level.
- Use
errors.Is()anderrors.As()for error inspection.
context.Contextis the first parameter in all I/O and long-running functions.- Use
signal.NotifyContextfor graceful shutdown. - Goroutines must accept a context or done channel for cancellation.
- Use
log/slogstructured logging. - Logging is OFF by default (philosophy: no logs, no crime).
- Enable via
--logflag orSPLITTER_LOG=1env var. - JSON format for Docker (detected via
TERM=dumborNO_COLOR), text for terminal. --log-levelcontrols verbosity (default INFO when logs are on).- Never log sensitive data (IPs, circuit paths, authentication tokens).
- Exported functions/types: PascalCase:
NewManager,TorInstance. - Unexported functions/types: camelCase:
findAvailablePort,writeConfig. - Constants: PascalCase (exported) or camelCase (unexported), not UPPER_SNAKE_CASE.
- Acronyms:
HTTP,URL,ID,TLS— e.g.,HTTPTunnelPort,TLSEnabled. - Interfaces: named by behavior (
Runner,ConfigProvider), notImplorInterface.
- No
init()functions. No global mutable state. - Prefer small, focused files. One primary type per file where practical.
- Interfaces are defined where they are consumed, not where they are implemented.
- Group related types and functions together within a package.
- CLI flags (highest priority)
- Environment variables (
SPLITTER_*prefix) - YAML config file (
configs/default.yaml) - Compiled-in defaults (lowest priority)
- Use goroutines for Tor instances, circuit renewal, country rotation.
- Every goroutine must have a cancellation mechanism (context or done channel).
- Use
sync.Mutexor channels for shared state; no data races. - Process management uses
os/execwith context cancellation.
- Table-driven tests using
t.Run(name, func(t *testing.T)). - Test files live in the same package (
*_test.go). - Integration tests behind
//go:build integrationbuild tag. - Use
t.TempDir()for temporary files and directories in tests. - Use
t.Setenv()for environment variable tests.
func TestFunctionName(t *testing.T) {
tests := []struct {
name string
input InputType
want OutputType
wantErr bool
}{
{name: "valid input", input: InputType{...}, want: OutputType{...}},
{name: "invalid input", input: InputType{...}, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FunctionName(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("FunctionName() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("FunctionName() = %v, want %v", got, tt.want)
}
})
}
}- Run
go test ./...before committing. - Run
go vet ./...before committing. - Mock external dependencies (Tor binary, HAProxy, network) in unit tests.
- Integration tests require real tor/haproxy binaries and are skipped in CI.
- Never hardcode ports or paths — use values from
configs/default.yaml. - Use
text/templatefor config generation — torrc, haproxy.cfg, privoxy.cfg all use Go templates intemplates/. - Use
net.Listenfor port availability checks — nonetstat,ss, or shell calls. - Graceful shutdown —
SIGTERM-> wait 5s ->SIGKILLviasignal.NotifyContext. Kill all child processes on exit. - No logs by default — enable via
--logorSPLITTER_LOG=1. Never log sensitive data. - Cookie auth for Tor control port —
CookieAuthentication 1in torrc, readcontrol_auth_cookiefile. Noexpectscripts. - Kill previous instances before starting new ones — clean up stale tor/haproxy/privoxy processes at startup.
- Randomize order — use
math/randto shuffle backend lists and country selection for anti-correlation. - Auto-detect Tor features — parse
tor --versionat startup and conditionally enable Conflux, CGO, congestion control, HTTPTunnelPort. - Config reload via SIGHUP — reload
configs/default.yamlwithout full restart. Restart Tor instances only if torrc parameters changed.
This project uses the Ralph Loop methodology for autonomous AI-driven development. Each iteration starts with fresh context, reads progress from files, implements one task, tests, commits, and updates status.
| File | Purpose |
|---|---|
RALPH_STATUS.md |
Tracks current progress across ROADMAP phases (DO NOT DELETE) |
ROADMAP.md |
Full development plan with ordered tasks |
.opencode/commands/ralph.md |
The /ralph command template |
.opencode/agents/ralph.md |
The Ralph worker agent definition |
- Run
/ralphin OpenCode - The agent picks the next ROADMAP task, implements it, runs tests, commits
- After each iteration, run
/ralphagain to continue - Each iteration starts with fresh context -- no context pollution
- ONE task per
/ralphiteration - Always run
go testbefore committing - Follow ROADMAP task numbering strictly
- Update
RALPH_STATUS.mdafter each iteration - Use
@reviewersubagent before committing complex changes - Use
@securitysubagent before committing Tor/network code
This project has 6 configured agents in opencode.json:
| Agent | Role | Model | Type |
|---|---|---|---|
| build | Development, writes code | GitHub Copilot GPT-5 Mini | primary |
| plan | Architecture, analysis, planning | deepseek/deepseek-chat | primary |
| ralph | Autonomous loop worker | deepseek/deepseek-chat | primary |
| reviewer | Code review (read-only) | GitHub Copilot GPT-5 Mini | subagent |
| explorer | Codebase search (read-only) | GitHub Copilot GPT-5 Mini | subagent |
| security | Tor/network security audit (read-only) | deepseek/deepseek-chat | subagent |
- Use Tab key to cycle between primary agents (build, plan, ralph)
- Use
@agent-nameto invoke subagents (e.g.,@reviewer check this code)
/ralph- Start an autonomous Ralph Loop iteration