A high-performance Golang backend that downloads raw ad-blocking filter lists, compiles them into optimized binary formats (.trie, .bloom, .css), packages everything into a .zip, uploads to Cloudflare R2, and tracks metadata in PostgreSQL.
Available mode:
| Mode | Entry Point | Use Case |
|---|---|---|
| API Server | cmd/server/main.go |
Production backend with REST API, R2 upload, DB, and daily cron |
POST /api/build { url }
│
▼
┌──────────────────────────────────────────────┐
│ 1. Validate URL (HEAD request) │
│ 2. Stream download (bufio.Scanner) │
│ 3. Parse domains + cosmetic CSS rules │
│ 4. Build Trie (reversed-label, BFS binary) │
│ 5. Build Bloom Filter (FNV double-hashing) │
│ 6. Format CSS (display:none !important) │
│ 7. Package .trie + .bloom + .css + info.json│
│ into [name].zip (in-memory) │
│ 8. Upload .zip → Cloudflare R2 │
│ 9. Upsert record → PostgreSQL │
└──────────────────────────────────────────────┘
│
▼
{ "status": "success", "downloadUrl": "https://pub-xyz.r2.dev/MyFilter.zip" }
A daily cron (@midnight UTC) automatically:
- Queries all saved filter URLs from PostgreSQL
- Re-downloads, re-compiles, re-zips concurrently (bounded goroutines)
- Re-uploads to R2 and updates
last_updatedtimestamps
blockads_filter_bin/
├── cmd/server/main.go # API server entry point (Gin + graceful shutdown)
├── internal/
│ ├── compiler/compiler.go # Core: download → parse → trie → bloom → css → zip
│ ├── config/config.go # Environment-based configuration
│ ├── cron/scheduler.go # Daily auto-update cron job (robfig/cron)
│ ├── handler/handler.go # HTTP handlers (POST /api/build, GET/DELETE filters)
│ ├── model/model.go # DB models + API request/response payloads
│ ├── storage/r2.go # Cloudflare R2 client (aws-sdk-go-v2, S3-compat)
│ └── store/postgres.go # PostgreSQL queries + auto-migration (pgx)
├── Dockerfile # Multi-stage production build (~15MB)
├── docker-compose.yml # PostgreSQL + API server
├── Makefile # Common dev commands
└── bloom_trie_build_doc.md # Binary format specification
- Go 1.22+
- PostgreSQL 14+ (or use Docker Compose)
- A Cloudflare R2 bucket with API credentials
git clone https://github.com/pass-with-high-score/blockads_filter_bin.git
cd blockads_filter_bin
go mod tidycp .env.example .env
# Edit .env with your actual credentials| Variable | Description | Example |
|---|---|---|
PORT |
Server port | 8080 |
ENVIRONMENT |
Run environment | development |
ADMIN_TOKEN |
Auth token for destructive actions | your_secret_admin_token |
DATABASE_URL |
PostgreSQL connection string | postgres://user:pass@localhost:5432/blockads?sslmode=disable |
R2_ACCOUNT_ID |
Cloudflare Account ID | abc123def456 |
R2_ACCESS_KEY_ID |
R2 API Token Key ID | your_access_key |
R2_SECRET_ACCESS_KEY |
R2 API Token Secret | your_secret_key |
R2_BUCKET_NAME |
R2 Bucket Name | blockads-filters |
R2_PUBLIC_URL |
R2 Public Access URL | https://pub-xyz.r2.dev |
docker compose up --build -dThis starts the API server using the external PostgreSQL database defined in your .env file (DATABASE_URL). The database schema is auto-migrated on startup.
# Ensure your external PostgreSQL database is accessible
make run
# or
go run ./cmd/server(Optional: Append ?force=true to force a recompile if the URL already exists in the database.)
Request:
{
"url": "https://example.com/filter.txt"
}Note: The filter name is automatically derived securely and uniquely from the provided URL (e.g., example_filter_a1b2c3d4).
Response (200 OK):
{
"status": "success",
"downloadUrl": "https://pub-xyz.r2.dev/MyFilter.zip",
"ruleCount": 48231,
"fileSize": 1245678
}Error (400 Bad Request):
{
"status": "error",
"message": "URL validation failed: URL is not reachable: ..."
}Response:
{
"status": "success",
"count": 2,
"filters": [
{
"id": 1,
"name": "MyFilter",
"url": "https://example.com/filter.txt",
"r2DownloadLink": "https://pub-xyz.r2.dev/MyFilter.zip",
"ruleCount": 48231,
"fileSize": 1245678,
"lastUpdated": "2026-03-18T10:00:00Z",
"createdAt": "2026-03-17T15:30:00Z"
}
]
}Requires Header: Authorization: Bearer <ADMIN_TOKEN>
Removes the zip from R2 and the record from PostgreSQL. Requires the url as a query parameter.
Example Request:
curl -X DELETE "http://localhost:8080/api/filters?url=https://example.com/filter.txt" \
-H "Authorization: Bearer your_secret_admin_token"{ "status": "ok", "time": "2026-03-18T10:30:00Z" }Each compiled .zip contains:
| File | Description |
|---|---|
[name].trie |
Binary Trie tree (magic 0x54524945, big-endian, BFS-ordered) |
[name].bloom |
Bloom Filter (magic 0x424C4F4D, FNV-1a/FNV-1 double-hashing, FPR 0.1%) |
[name].css |
Cosmetic filter rules (e.g. .banner { display: none !important; }) |
info.json |
Metadata: { name, url, ruleCount, updatedAt } |
Both .trie and .bloom files are designed for zero-copy mmap consumption by the BlockAds Android/iOS Go engine.
make deps # Download Go module dependencies
make build # Build the API server binary → bin/server
make run # Run the API server locally
make test # Run all tests
make clean # Remove build artifactsAuto-migrated on server startup:
CREATE TABLE filter_lists (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
url TEXT NOT NULL UNIQUE,
r2_download_link TEXT NOT NULL DEFAULT '',
rule_count INTEGER NOT NULL DEFAULT 0,
file_size BIGINT NOT NULL DEFAULT 0,
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_filter_lists_url ON filter_lists (url);- Memory Efficiency: All filter list downloads are processed line-by-line via
bufio.Scanner. The raw text is never loaded entirely into memory, preventing OOM on lists with millions of rules. - In-Memory Zip: The
.trie,.bloom,.css, andinfo.jsonare built as byte slices and zipped in-memory — no temp files on disk. - Smart Caching & Upserts:
POST /api/buildensures we don't duplicate work. Existing URLs are immediately returned from DB unless overridden with?force=true. The database uses theurlas the unique identity for conflict resolution during upserts. - Bounded Concurrency: The cron job uses a semaphore pattern (
chan struct{}) to cap goroutines and prevent CPU/network spikes. - Binary Compatibility: The
.trieand.bloomformats are byte-identical to those produced by the KotlinDomainTrie/BloomFilterBuilderon Android, ensuring cross-platform interoperability.
MIT