Publicat în

Analytics Auto-Gazduit Fără Boți cu GoatCounter pe NixOS: Confidențialitate și Fiabilitate la Scară Mare

Când Vincent Bernat a eliminat Google Analytics din 2016, a ales GoAccess pentru analiza logurilor serverului — o abordare respectuoasă față de confidențialitate, dar vulnerabilă la inflația artificială a traficului. În ultimii ani, scraper-ele AI au împins „vizitatorii” la ~2.000/zi, făcând statisticile inutilizabile. Soluția? GoatCounter: o platformă open-source, privacy-first, cu un singur binary și SQLite. Dar povestea nu se oprește la instalare. Bernat a înlocuit clientul JavaScript cu unul custom care filtrează boții prin interacțiune utilizator, a adăugat fallback CSS pentru non-JS, și a implementat un proxy local pe fiecare dintre cele 5 servere web (Europa și America de Nord) care bufferizează hit-urile pentru câteva zile. Totul deployat declarativ pe NixOS cu Colmena, containere systemd-nspawn efemere, sekret management și backup Litestream către Hetzner Storage Box. Rezultatul: <200 vizitatori umani reali/zi vs 2.000 în loguri. Următoarele secțiuni detaliază arhitectura, codul și lecțiile învățate.

De Ce GoatCounter: Simplicitate, Confidențialitate, Control Total

GoatCounter nu colectează date personale: nu stochează IP-uri, nu folosește cookies. Creează un identificator de sesiune valid 8 ore bazat pe User-Agent + IP (hash-uire). Setul de funcții este modest dar suficient pentru un blog: pagini vizitate, referrers, browsere, rezoluții. Alternativele privacy-friendly, în ordine crescătoare de complexitate: Umami (Node.js + PostgreSQL/MySQL), Plausible (Elixir + ClickHouse), Rybbit (Go + SQLite). GoatCounter rămâne cea mai ușoară: un singur binary (~15 MB) + SQLite. Autorul (Martin Tournoij) rulează o instanță publică la stats.arp242.net — poți testa înainte de a găzdui propria.

Pentru infrastructuri serioase, servere dedicate oferă izolare completă și performanțe predictibile — esențiale când rulezi analytics propriu pe mașini fizice fără „noisy neighbors”. Dacă ești la început, un VPS Romania cu NVMe și resurse garantate poate fi punctul de plecare ideal pentru a experimenta cu GoatCounter înainte de a scala.

Client JavaScript Custom: Filtrare Agresivă prin „Dovadă de Umanitate”

Clientul original GoatCounter (2.189 bytes min+gz) include funcții inutilizate: contor vizitatori, tracking click-uri, setări configurabile. Bernat l-a înlocuit cu o funcție minimală count() care trimite doar esențialele: path, title, referrer, query string, screen width, flag eveniment, nonce aleatoriu. Cheia: filtrarea boților prin interacțiune obligatorie — idee furată de la Bear Blog.

let sendHit = () => (sendHit = () => {}, count());
["touchmove", "mousemove", "keydown", "pointerdown"].forEach(eventName =>
  document.addEventListener(eventName, sendHit, { once: true, passive: true })
);

Primul eveniment de tip touch/mouse/keyboard declanșează count() o singură dată (once: true). Boții AI (și Googlebot cu headless Chromium) nu interacționează cu pagină — nu declanșează evenimente. Rezultat: GoAccess raporta ~2.000/zi, GoatCounter numără <200 umani. Pentru cei ~2% cu JavaScript dezactivat, fallback CSS folosește :hover pe <body class="nojs"> pentru a încărca un background-image /count?...&r=NoJS — tot o interacțiune, dar la nivel CSS.

<style>
  .nojs body:hover { border-image: url('/count?p=/path&t=Title&r=NoJS&e=false'); }
</style>
<script>if ("noModule" in HTMLScriptElement.prototype) document.documentElement.classList.remove("nojs");</script>

Această abordare „proof of humanity” a rezistat 18+ luni. Testul CSS-only fără interacțiune (februarie 2025) a înregistrat până la 10x mai puține pageviews decât nginx logs, chiar după filtrare UA bot. GoatCounter cu interacțiune: ~3x mai puține — eficiență reală.

Proxy Local pe Fiecare Server: Reziliență la Level de Infrastructură

5 servere (web02 Paris, web03 Helsinki, web04 Nuremberg, web05 Ashburn, web06 Chicago) servesc conținutul, dar GoatCounter rulează pe unul singur. Dacă backend-ul cade, hit-urile se pierd. Soluția: proxy local pe fiecare server ascultând pe /count (127.0.0.3:8087), stocând hit-uri în memorie (buffer suficient pentru câteva zile downtime), trimițând batch-uri către upstream via /api/v0/count autentificat.

Codul proxy a fost propus upstream (PR #909) dar respins de maintainer — „prea mult cod pentru un caz de nicșă”. Bernat îl menține în fork propriu (feature/proxy). Nginx expune endpoint-ul pe același domeniu ca blogul pentru a evita ad-blockers:

location = /count {
  access_log off;
  proxy_pass http://127.0.0.3:8087/count;
  proxy_pass_request_headers off;
  proxy_set_header Accept-Language $http_accept_language;
  proxy_set_header User-Agent $http_user_agent;
  proxy_set_header X-Real-Ip $remote_addr;
}

Expunerea pe același domeniu sună controvertată („nu respect alegerea cititorului”), dar GoatCounter fiind privacy-friendly, Bernat o consideră acceptabilă. Proxy-ul nu are date persistente, dar are nevoie de API key — gestionată prin Colmena keys (vezi secțiunea NixOS).

Deploy Declarativ pe NixOS: Containere Efemere, Secrets, Backup Litestream

Serverele rulează NixOS + Colmena (deployment stateless). Fiecare aplicație într-un container systemd-nspawn efemer, rețea partajată cu host-ul (izolare de rețea = complexitate inutilă). Modul custom container.nix encapsulează opțiuni repetitive: ephemeral=true, privateNetwork=false, servicii non-esențiale dezactivate (console-getty, systemd-logind, systemd-oomd).

Persistența: directoare pe host montate în container via bindMounts. Pentru GoatCounter: /var/db/goatcounter → container la același path. Secrets: Colmena uploadează chei în /var/keys (grup keys, permisiuni 0640), containerul le accesează read-only la /etc/nume-secret. Exemplu: GOATCOUNTER_GEODB pentru descărcare baza GeoIP, GOATCOUNTER_API_KEY pentru proxy.

Backup Litestream: replicare streaming WAL SQLite către Hetzner Storage Box via SFTP. Configurare: snapshot zilnic reținut 15 zile, 3 niveluri compactare (5min, 30min, 3h), auto-recuperare (acceptând riscul mic de pierdere ultimele înregistrări), replică organizată pe hostname. Restore: oprire containere, mutare DB veche, litestream restore din env cu secrete, restart — ~20 min pentru 20 MiB.

# Fragment Litestream config
services.litestream = {
  enable = true;
  environmentFile = "/etc/litestream.env";
  settings = {
    auto-recover = true;
    snapshot = { interval = "24h"; retention = "360h"; };
    levels = [ { interval = "5m"; } { interval = "30m"; } { interval = "3h"; } ];
    dbs = lib.mapAttrsToList (name: path: {
      path = path;
      replica = { type = "sftp"; host = "${SQLITE_BACKUP_HOST}"; user = "${SQLITE_BACKUP_USER}"; password = "${SQLITE_BACKUP_PASSWORD}"; host-key = "${SQLITE_BACKUP_HOSTKEY}"; path = "${config.networking.hostName}/${name}"; };
    }) cfg.databases;
  };
};

Arhitectura completă: 5 proxy locale → 1 GoatCounter central → Litestream → Hetzner Box. Zero third-party, zero cookies, zero IP-uri stocate. Analytics real, control total.

Pași Practici pentru Implementare Proprie

  1. Instalează GoatCounter pe server dedicat (binary + SQLite) sau testează instanța publică stats.arp242.net
  2. Înlocuiește clientul JS cu funcția count() minimală + event listeners once: true, passive: true pentru touch/mouse/keyboard
  3. Adaugă fallback CSS .nojs body:hover cu background-image /count?...&r=NoJS pentru non-JS (~2% trafic)
  4. Deploy-ează proxy local pe fiecare frontend server (buffer memorie, batch API /api/v0/count, rate-limit 10 req/s/IP)
  5. Configurează Nginx să expună /count pe domeniul tău (evită ad-blockers) cu proxy_pass la proxy-ul local
  6. Folosește NixOS + Colmena cu containere systemd-nspawn efemere, bindMounts pentru persistență, keys pentru secrete
  7. Activează Litestream pentru backup SQLite streaming către SFTP (Hetzner Storage Box sau alternativă) cu snapshot zilnic + 3 niveluri compactare
  8. Testează restore periodic: systemctl stop container@..., mv db.sqlite{,.old}, litestream restore, systemctl start

Concluzie

Zeče ani după eliminarea Google Analytics, analytics-ul bazat pe JavaScript revine pe blogul lui Bernat — dar dezechilibrat: fără cookies, fără IP-uri, fără third-party. GoatCounter + client custom + proxy local + NixOS declarativ = stack complet sub control, rezistent la scraperi AI, cu backup automat și restore testat. Pentru administrați Linux, furnizori hosting și ingineri DevOps, acest model demonstrează că confidențialitatea și observabilitatea nu sunt mutu-excluzive — necesită doar arhitectură gândită, nu compromisuri. Dacă îți rulezi propria infrastructură, merită investit în stack-ul propriu de analytics. Datele tale, regulile tale.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *