# AGENTS.md This file provides guidance to LLM agents when working with code in this repository. ## Project Overview Trackers is a Go application that aggregates torrent tracker links from multiple sources (HTTP, HTTPS, and local files). It: - Polls configured sources at regular intervals - Deduplicates and validates tracker links - Caches results to disk for resilience - Serves aggregated tracker lists via HTTP API - Runs a single HTTP server with minimal dependencies This is an educational project written in Go 1.26. ## Development Commands ### Build ```bash go build -o trackers ./main.go ``` Через [Task](https://taskfile.dev) (`task --list` — полный список): ```bash task build # статический бинарь linux/amd64 (CGO_ENABLED=0) для сервера task image # docker-образ из готового бинаря (Dockerfile.deploy) ``` ### Run ```bash go run main.go -config config.toml ``` ### Test The codebase has no tests currently. Tests can be added with: ```bash go test ./... ``` To run a single test: ```bash go test -run TestName ``` ### Lint ```bash go fmt ./... go vet ./... ``` ### Release Build Uses goreleaser for cross-platform builds (Linux amd64/arm64): ```bash goreleaser build --snapshot --clean ``` ## Deploy paths Two independent ways to ship the service: 1. **Yandex Container Registry (buckland)** — CI (`.gitea/workflows/release.yml`) собирает образ из `Dockerfile` (многоступенчатая сборка из исходников) и пушит в реестр; buckland тянет образ оттуда. 2. **Локальная сборка (umbar)** — `Dockerfile.deploy` упаковывает готовый бинарь (`task build`). Плейбук `umbar/playbook-trackers.yml` собирает бинарь на control-хосте, доставляет его + `Dockerfile.deploy` на сервер и собирает образ там (Go-тулчейн на сервере не нужен). Так же устроен `jellybit`. ## Architecture **Single File Design**: All code is in `main.go` with clear functional separation: 1. **Config System** (`loadConfig`): Parses TOML configuration with defaults - `port`: HTTP server port (default: 8080) - `cache_dir`: Directory for caching tracker lists (default: cache/) - `poll_interval`: How often to refresh sources (default: 60m) - `sources`: Array of URLs/file paths to fetch tracker lists from 2. **Aggregator** (type `Aggregator`): Thread-safe in-memory deduplication - Maintains per-source tracker sets using `sync.RWMutex` - `Update()`: Stores new tracker list for a source - `List()`: Returns combined sorted list across all sources 3. **Polling System** (`pollSource`/`runOnce`): Background goroutine per source - Fetches source on startup and at configured intervals - Updates aggregator and writes to cache on success - Graceful shutdown on SIGINT/SIGTERM 4. **HTTP Handler** (`/list` endpoint): Returns deduplicated tracker list as plain text - Links separated by double newlines - Read timeouts enforce reasonable request handling 5. **Source Fetching** (`fetchSource`): Pluggable source handlers - **HTTP/HTTPS**: Makes requests with context support and timeout (15s) - **File**: Reads local files via `file://` URLs - Response is parsed line-by-line 6. **Link Validation** (`normalizeLinks`/`isValidTrackerLink`): - Strips whitespace and empty lines - Validates URL format and supported schemes: `http`, `https`, `udp`, `ws`, `wss` - Deduplicates via map-based set 7. **Caching** (`writeCache`/`loadCachedLinks`): SHA1-hashed filenames in `cache_dir/` - Enables graceful degradation if source becomes unavailable - Filenames are hex-encoded SHA1(source_url) + ".txt" ## Key Design Decisions - **No external dependencies except go-toml**: Keeps binary small and build simple - **Simple HTTP server**: Uses stdlib `net/http` instead of frameworks - **Per-source goroutines**: Allows independent polling without blocking - **RWMutex for reads**: Readers don't block each other when listing trackers - **Context propagation**: Respects shutdown signals in all async operations - **Line-based parsing**: Flexible input format (handles various tracker list formats) ## Testing Notes The project follows Go conventions but has no test files. Consider adding tests for: - Link validation edge cases - Config parsing with invalid inputs - Concurrent aggregator updates - Cache file I/O