Informe técnico de ingeniería: ecosistema gghstats v1.5.1

1. Contexto y filosofía de diseño
Los datos de tráfico de GitHub (vistas y clones) solo se retienen en una ventana rodante de 14 días. Sin captura propia, esa telemetría se pierde. gghstats persiste histórico de tráfico en SQLite, con dashboard, API, CLI y métricas Prometheus, de modo que la organización conserva series más allá del límite de la API.

El diseño prioriza soberanía de datos y minimalismo operativo: un binario Go + SQLite, sin base de datos externa obligatoria, orientado al autoalojamiento frente a SaaS que exige delegar un PAT a terceros.
Separación de responsabilidades
| Repositorio | Rol |
|---|---|
| gghstats (core) | Aplicación: sync GitHub → SQLite, serve, API/UI, CLI, Prometheus, alertas opt-in, imagen GHCR / binarios. Variables de app como GGHSTATS_DB, GGHSTATS_TRUSTED_PROXIES, GGHSTATS_GITHUB_TOKEN, etc. |
| gghstats-selfhosted (despliegue) | IaC operativo: Compose (minimal / Traefik TLS / observability), Helm, layout GGHSTATS_HOST_DATA, pin de imagen, docs de VPS. No es el código del motor. |

Traefik, Helm, stacks Grafana/Prometheus/Loki y la convención GGHSTATS_HOST_DATA pertenecen a selfhosted. El core solo expone el binario/imagen y la semántica de variables de aplicación (p. ej. confiar cabeceras X-Forwarded-* vía GGHSTATS_TRUSTED_PROXIES).
2. Instalación y despliegue
Evaluación rápida (core)
| Método | Comando / herramienta | Uso |
|---|---|---|
| Script | curl -fsSL https://get.gghstats.com/install.sh | sh |
Instalación rápida Linux/macOS (pin: VERSION=v1.5.1 …) |
| Homebrew | brew install hrodrig/gghstats/gghstats |
Workstations |
| Go | go install github.com/hrodrig/gghstats/cmd/gghstats@latest |
Fuente / bleeding-edge |
Modo demo: gghstats serve --demo (o GGHSTATS_DEMO=true) — UI con datos sintéticos, sin PAT ni sync real.
Producción: qué vive dónde
Core (aplicación):
- Persistencia SQLite:
GGHSTATS_DB/--db(en contenedor suele ser/data/gghstats.dbcon volumen en/data). - Detrás de un reverse proxy:
GGHSTATS_TRUSTED_PROXIES(CIDR/IP del peer que habla con gghstats) para que rate-limit / whitelist / access log usen el cliente real víaX-Forwarded-For/X-Real-IP. Lista vacía = ignorar esas cabeceras (seguro por defecto). - Imagen:
ghcr.io/hrodrig/gghstatssobregcr.io/distroless/static-debian13:nonroot.
Selfhosted (manifiestos): versión de repo ~0.1.58, pin de app GGHSTATS_VERSION=v1.5.1.
GGHSTATS_HOST_DATA: directorio host (p. ej./home/gghstats/gghstats-data) con.env, SQLite montado en/data, y opcionalmente.env.observability/ CSS de tema. Compose se invoca con--env-file "${GGHSTATS_HOST_DATA}/.env".- Compose minimal: un servicio, puerto host (p. ej. 8080).
- Compose Traefik: HTTPS (80/443), Let’s Encrypt (
GGHSTATS_HOSTNAME,ACME_EMAIL). Rate-limit en borde + app;/metricsexcluido del router público (scrape interno). Authelia SSO opcional sobre la redgghstats_edge. - Observability (opcional, requiere Traefik /
gghstats_edge): Prometheus, Grafana, Loki; proyecto Compose-p gghstats-obs; overlay Traefik para Grafana en FQDN propio. - Helm: chart
gghstatsdesdehttps://hrodrig.github.io/gghstats-selfhosted(o clone./run/kubernetes/helm/gghstats); token en Secret; PVC en/data. - Upgrade de imagen: cambiar
GGHSTATS_VERSION→pull+up -d(no bastarestart). Helper:./run/scripts/compose-stack.sh.
Alertas (Slack / webhook / Loki / SMTP) son del core (GGHSTATS_ALERTS_*); selfhosted solo cablea esas vars en Compose/Helm y documenta gghstats alert test.
3. Trayectoria: v1.1.0 → v1.5.1
v1.1.0 — Pins + Featured (migración v6)
- CLI
gghstats repo/gghstats featured: pins unen al conjunto de tráfico (FILTER ∪ pins); Featured es vitrina editorial sin tráfico en/. - Tablas
pinsyfeatured(migración v6). - Página
/featured(grid neo-brutalist); enlace de nav solo si el catálogo no está vacío.
v1.2.0 — UX Featured + números
- Formato de conteos (separadores /
GGHSTATS_COMPACT_NUMBERS). /featured: paginación, búsqueda y orden (page,per_page,q,sort,dir) víaFilterFeatured.
v1.3.0 — Índice estadístico + JSONL
- Panel de estadísticas diarias de clones, ranking/
# - %, línea Unique en el chart del índice. - Export JSONL del índice (resumen + historial + referrers/paths/stars por repo filtrado). Nace aquí; el esquema API/SQLite de tráfico no cambia.
v1.4.0 — Uniques UX + Featured JSON + sello UTC
- U1: uniques como cifra primaria; eventos clones/views secundarios.
GET /api/v1/featured+/featureden sitemap (si hay entradas).- JSONL:
Content-Dispositioncon stamp UTCgghstats-export-YYYYMMDD-HHMM.jsonl(#23). Fixes Carlok #22–#25 (locale charts, tooltips, Rank).
v1.5.0 — Cinco slices de reportería
| Slice | Contenido |
|---|---|
| F-fresh | Estado de frescura / cobertura por métrica (views / clones). Statuses: fresh | delayed | missing | failed | never. Charts con huecos null (cero explícito ≠ día omitido). Fetch views/clones independiente. Migración v7 (traffic_metric_state, traffic_metric_coverage). |
| V-vis | github_visibility + report_policy (exclude > include > inherit); GGHSTATS_REPORT_PRIVATE; CLI repo report ls/set. Superficies de reporte fail-closed (404 indistinguible). Colección (filter/pins/INCLUDE_PRIVATE) separada del reporting. Migración v8. |
| V-json | gghstats repo report ls --json (+ filtros --visibility / --policy) para scripts post-upgrade. |
| U-legend | Leyenda i18n en charts de detalle: gap/null = no reportado por GitHub; 0 = cero confirmado. |
| X-chart | Descarga JSON alineado al chart (/{owner}/{repo}/traffic.json); dogfood …/traffic?dense=1 (sparse por defecto sin cambios). |
Operadores: tras 1.5.0, filas existentes → unknown + inherit hasta el siguiente sync (o repo report set … include). El histórico SQLite no se borra; dashboard/APIs/exports/badges pueden quedar vacíos hasta entonces.
v1.5.1 — Fix Featured (#47)
En 1.5.0, Featured se trató erróneamente como superficie de reporte: /featured, API, nav y sitemap exigían una fila repos recolectada y reportVisible. Eso rompe la vitrina editorial (entrada Featured ≠ repo con tráfico).
1.5.1 restaura el catálogo: FilterFeatured / FeaturedCount de nuevo. La visibilidad de reporte sigue aplicando solo a rutas de tráfico/dashboard. No hace falta sync para la vitrina. No es un relajamiento del fail-closed del dashboard; el fallo era el scope incorrecto de Featured.
4. Datos, seguridad y cadena de suministro
Migraciones SQLite relevantes
| Versión | Contenido |
|---|---|
| v6 | pins, featured |
| v7 | Frescura/cobertura de tráfico |
| v8 | repos.github_visibility, repos.report_policy |
Apertura con WAL para lecturas concurrentes (UI/API) mientras el sync escribe.
Defensa en profundidad (core)
- Rate limiting token-bucket por IP; whitelist opcional;
GGHSTATS_API_ONLY. GGHSTATS_TRUSTED_PROXIES(ver §2).- Cosign + SBOM en releases; cobertura de tests ≥ 80 % en release-check.
- Imagen distroless nonroot.
Alertas
Opt-in (GGHSTATS_ALERTS_ENABLED): sinks Slack / webhook / Loki / SMTP; reglas tras sync; validación con gghstats alert test. Los stacks Grafana/Loki de observability Compose son despliegue selfhosted, distintos del sink Loki de la app.
5. Cierre
v1.5.1 consolida la línea 1.5: frescura honesta (cinco estados + gaps null), visibilidad de reporte fail-closed con migraciones v7/v8, y la corrección de que Featured es catálogo editorial, no report scope. El motor permanece en gghstats; Traefik, Helm, GGHSTATS_HOST_DATA y stacks de observabilidad viven en gghstats-selfhosted, con imagen pin v1.5.1.
Referencias: CHANGELOG · SPEC · README core · gghstats-selfhosted · install https://get.gghstats.com/install.sh