Skip to content

deep dive

What's inside Marauder

A tour of every system that makes Marauder feel boring to operate: the auth model, the plugin architecture, the scheduler, the observability stack, the frontend, the deployment story, and the CI pipeline.

Authentication you can trust

Security is opt-out, not opt-in. Every secret on disk is encrypted, every JWT is signed with an asymmetric key, every login is audited.

  • Local accounts with Argon2id password hashing (time=3, memory=64 MiB, parallelism=4)
  • ES256 JWT access tokens, opaque refresh tokens stored as SHA-256 hashes server-side
  • Refresh-token rotation with reuse-detection — a replayed token revokes the entire token family
  • AES-256-GCM encryption at rest for tracker credentials, client configs, and JWT signing keys
  • Master key loaded from MARAUDER_MASTER_KEY env var, 32 bytes base64-encoded, never persisted in plain
  • OpenID Connect sign-in via coreos/go-oidc — pre-built Keycloak realm in the SSO compose profile
  • Async audit log for every login success / failure / logout, visible to admins in the UI
  • Multi-user with strict per-user data isolation at the database WHERE-clause level

Plugin architecture

A new tracker is one Go file implementing one interface. The init() function self-registers with a global registry; main.go just blank-imports the package.

  • Tracker interface: CanParse, Parse, Check, Download — plus optional WithCredentials, WithQuality, WithCloudflare capabilities
  • Client interface: Test, Add, ConfigSchema — supports magnet URIs, raw .torrent bytes, and watch-folder writes
  • Notifier interface: Test, Send — Telegram, Email, Webhook, Pushover bundled
  • 16 trackers, 5 clients, 4 notifiers shipped in v1.0
  • Per-plugin E2E test harness with a fake qBittorrent — every tracker plugin runs through Parse → Login → Verify → Check → Download → submit-to-qbit on every CI run
  • Forum-tracker plugins share a common SessionStore that holds a cookie jar per (tracker, user_id) pair so concurrent topic checks reuse the same logged-in client

Cloudflare bypass — without bundling chromium in the main binary

NNM-Club and other Cloudflare-protected trackers go through a separate sidecar service. The main backend binary stays small.

  • Standalone cfsolver Go service using chromedp + Debian-slim chromium
  • Exposes POST /solve {url} → { user_agent, cookies[] }
  • Built as a separate Docker image, started via the cfsolver compose profile only when needed
  • Tracker plugins opt in via the WithCloudflare capability — no global flag, no surprises
  • Trivial to swap for FlareSolverr later if chromedp falls behind

Scheduler with backpressure and observability

The scheduler is a single dispatch goroutine plus a bounded worker pool. Failures back off exponentially; a busy run never overflows the queue.

  • Configurable tick interval (default 60 s) and worker pool size (default 8)
  • Per-topic check pipeline: load → call tracker Check → compare hash → call Download if changed → decrypt client config → call client Add
  • Exponential backoff on errors, capped at 6 hours; success resets to the topic's configured interval
  • Falls back to the user's default client if a topic has no explicit client_id
  • In-memory ring buffer of the last 50 run summaries with checked / updated / errors counters, exposed at /api/v1/system/status and visualised on the System page
  • Per-tracker check duration histograms in Prometheus

Observability from day one

Structured logs, metrics, health checks, audit log — all of it standard, all of it opt-out, all of it documented.

  • Prometheus /metrics endpoint, gated by a static bearer token (MARAUDER_METRICS_TOKEN)
  • Metrics: HTTP request count + duration histograms (with chi route patterns to keep cardinality bounded), scheduler runs, per-tracker checks + duration histogram, per-tracker updates, per-client submit results
  • Structured JSON logs via zerolog, every request gets a request_id, secrets are scrubbed
  • /health and /ready endpoints with the standard semantics
  • Live System status page in the frontend (auto-refreshes every 5 seconds)
  • Admin-only Audit log page

Frontend that doesn't look like an admin template

React 19 + Vite 8 + Tailwind 4 + shadcn/ui. Dark-first, glass cards, deep violet primary, electric cyan accent, framer-motion entry animations.

  • Zustand auth + prefs stores, TanStack Query for server state
  • Animated login, dashboard with live status tiles, topics list with bulk-edit + density toggle
  • Clients CRUD page with per-plugin field hints and Test-connection buttons
  • Notifiers CRUD with Send-test buttons
  • System status page (any user) and Audit log page (admin)
  • English + Russian UI with a tiny zustand-backed i18n module — the locale switcher lives in the header
  • RFC 7807 problem-details error rendering throughout

Deployment that doesn't surprise you

Docker compose, three Docker images, three compose overlays, one master key. No hidden state, no host dependencies, no manual cert ops.

  • Multi-stage Dockerfiles for backend, frontend, and cfsolver — non-root users, healthchecks, alpine/debian-slim bases
  • deploy/docker-compose.yml: production stack (db + backend + frontend + nginx gateway)
  • deploy/docker-compose.dev.yml: overlay with port publishing + real qBittorrent + Transmission for E2E testing
  • deploy/docker-compose.sso.yml: overlay with Keycloak 26 + a pre-imported realm and a test user
  • All host-exposed ports are non-standard (gateway 6688) to avoid colliding with other services

CI / CD that takes itself seriously

Five GitHub Actions workflows, golangci-lint with 12 rules, Trivy scans, cosign signing, CycloneDX SBOMs, Dependabot.

  • ci.yml: backend race-tests + golangci-lint + govulncheck, frontend tsc + build, cfsolver build/vet — under 3 minutes per PR
  • docker.yml: builds all 3 images on push to main, Trivy scan with HIGH/CRITICAL fail
  • e2e.yml: nightly + on-tag full compose-stack walkthrough (magnet → qBittorrent end-to-end)
  • release.yml: tag-pushed multi-arch (amd64 + arm64) build, cosign keyless signing via OIDC, CycloneDX SBOM per image, GitHub Release with auto-extracted CHANGELOG
  • codeql.yml: GitHub CodeQL SAST for Go and TypeScript with the security-extended query pack
  • dependabot.yml: weekly grouped updates for gomod, npm, github-actions, docker base images