The story of Capivara — from messy hub to central dashboard with 396 tests
Capivara·

The story of Capivara — from messy hub to central dashboard with 396 tests

The hub that started as financial mess

The original need had zero glamour: I needed to understand where my money was going. Capivara was born as a personal hub to organize my finances — but anyone following this blog knows it became much more than that.

Today, Capivara is the central dashboard of my ecosystem: it monitors Arachne, TatuEngine and services, shows Dogwalk revenue, answers questions about my projects with local RAG, and keeps offsite backups in two different clouds.

And the most impressive part: 396 tests (131 backend + 248 frontend + 17 browser/E2E) making sure none of it breaks.

Context — a dashboard that became infrastructure

The initial commit came with an honest name:

a4eef5d feat: capivara hub — FastAPI + React + Cloudflare Tunnel

FastAPI on the backend, React on the frontend, SQLite as database, and a Cloudflare Tunnel to expose everything over HTTPS without opening ports. The first version had a simple project status dashboard — zero mock data, full cleanup (commit 35f411a).

Capivara grew in waves. First the temporary invites (1h-7d with permissions), then the Umami analytics dashboard, the proxies to other applications (Portfolio staging, Umami, Dogwalk), and hardened authentication with JWT + refresh token rotation + blacklist.

The struggle — security, local RAG, and the backup that didn’t exist

Authentication that had to grow

In the beginning it was a simple login. But a dashboard that controls infrastructure can’t have a weak password. So came:

# backend/auth.py — JWT + bcrypt + 2FA TOTP
def create_access_token(user_id: int) -> str:
  expire = datetime.utcnow() + timedelta(hours=24)
  return jwt.encode(
  {"sub": str(user_id), "exp": expire},
  settings.SECRET,
  algorithm="HS256",
  )

And 2FA TOTP — the same algorithm as Google Authenticator. Every login requires the 6-digit code generated in the app. There was a day I tried to log in without my phone nearby and got locked at the door — a sign the security was working.

The local Second Brain

Then came the fun part: a local RAG chat in the admin. ChromaDB as vector store + Ollama with qwen3.5:4b as the LLM. Memories are ingested automatically from the 6 projects — today there are 430 memories in the vector database.

# backend/routers/chat.py — ChromaDB search
def search_memories(query: str, top_k: int = 5):
  results = collection.query(
  query_texts=[query],
  n_results=top_k,
  include=["documents", "metadatas", "distances"],
  )
  return results

The differentiator: a Telegram bot connected to the same brain. I send a question in chat, the webhook queries the RAG and replies with sources. My projects answer questions about themselves 24/7.

The backup that almost caught me

The most tense moment: I discovered the offsite backup didn’t actually exist. From 14/07 to 31/07, R2 was disabled on the account — the script ran, said it was backing up, but uploaded nothing. If WSL had died in that window, the entire database would be gone.

The fix had two fronts:

  1. Cloudflare R2capivara-backups bucket, validated snapshot every 6h via no_agent cron
  2. Cloudflare D1 disaster recovery — SQLite copy at the Cloudflare edge, synced every 6h
# scripts/backup_to_r2.sh — what saved the database
rclone copy backend/capivara.db r2:capivara-backups/$(date +%F)/ --progress

Tokens rotated, credentials in Bitwarden SM, and the dump manually validated before sleeping peacefully again.

Resolution — the dashboard that became operations

Capivara stopped being “my dashboard” and became part of operations. Today it:

  • Exposes Portfolio staging and Umami proxy (port 3001→3100 to avoid conflicts)
  • Shows the Dogwalk Stripe dashboard (revenue, subscriptions, payouts, 12-month chart)
  • Monitors WSL in real time (CPU, RAM, GPU, disks, Docker, services, processes)
  • Sends push notifications with full offline PWA (VAPID keys)

The frontend refactor was brutal: Dashboard from 994→262 lines (-74%), AdminPage from 1091→97 lines (-91%), initial bundle from 668→238 kB (-64%) with code splitting.

Metrics

Metric Birth Today
Commits 1 107
Backend tests (pytest) 0 131
Frontend tests (Vitest) 0 248+
Browser + E2E tests 0 17
RAG memories (ChromaDB) 0 430
Offsite backups 0 2 (R2 + D1)
Rate limit missing IP + API key dual window

Lessons

Capivara taught me that a personal dashboard is also production. The day the “silent” backup failed without me knowing was a wake-up call: if an automation is critical, it needs validation, not just execution. That’s what led to the validated R2 snapshot and the no_agent sync — systems that fix themselves.

And the second lesson: local RAG with personal data works really well. Having my Second Brain answering about my own projects, with sources, changed how I work — and became a pattern Arachne later took to the extreme.

~/lifelog — bash
$cat about.txt
╔══════════════════════════════════════╗
║  Samuel Medeiros                    ║
║  Senior Software Engineer           ║
║  Stack: Python · TypeScript · Rust  ║
║  Projetos: Arachne, Dogwalk,        ║
║            Capivara, TatuEngine      ║
╚══════════════════════════════════════╝
      
$