Hub de difusión para la red de acampadas (fork de trabajo del Tactical Media Broadcast Hub)
Find a file
hacklab 533a0fb1c0 docs: T-HUB README and validation guide
README: architecture diagrams (single account, shared accounts,
components), status per platform, outreach, hardware and bandwidth
requirements, ports, security practices and what to monitor.
docs/T-HUB-VALIDACION.txt: per-commit test plan for reviewers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:15:04 +02:00
.github/workflows Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00
apps style: acampada26s.net palette, Anton titles and T-HUB logo 2026-10-15 14:15:03 +02:00
docs docs: T-HUB README and validation guide 2026-10-15 14:15:04 +02:00
infra feat: enable oCIS public link sharing (folders can be shared publicly) 2026-10-15 09:56:25 +00:00
packages feat: wire the Instagram connector into connect and publishing 2026-10-15 14:15:03 +02:00
scripts e2e: brand palette guard — fails on blue-family colors in the embedded surfaces 2026-10-13 19:42:06 +00:00
.dockerignore Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00
.editorconfig Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00
.gitignore Track the files scaffold; sync deployed oCIS compose requirements 2026-10-11 07:51:59 +00:00
eslint.config.mjs Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00
LICENSE Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00
package.json Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00
pnpm-lock.yaml feat: wire the Instagram connector into connect and publishing 2026-10-15 14:15:03 +02:00
pnpm-workspace.yaml Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00
README.md docs: T-HUB README and validation guide 2026-10-15 14:15:04 +02:00
tsconfig.base.json Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00
turbo.json Tactical Media Broadcast Hub — initial release 2026-10-10 11:56:42 +00:00

T-HUB

T-HUB

Copia de trabajo del Tactical Media Broadcast Hub para la red de acampadas: acampada26s.net, tomalacalle.net y takethestreet.net. El despliegue en uso es app.occupymedia.cc.

Seguimos la rama main de upstream y mandamos allí los cambios que les sirvan. Lo propio de T-HUB va en este repo. Para revisar y validar los cambios, ver docs/T-HUB-VALIDACION.txt.

Cambios respecto a upstream:

  • Instagram registrado en conexión y publicación; claves de la app de Meta solo en el entorno del servidor.
  • Panel Outreach para escribir por privado a mano, contacto a contacto, con seguimiento.
  • RLS forzado en media_assets (migración 042).
  • Estilo de acampada26s.net en la web: negro, blanco, neón #00e5ff y títulos en Anton.
Remoto URL
origin https://gitea.laenre.net/hacklab/T-HUB.git
upstream https://gitlab.moxexley.com/mediatools/tacticalmediahub.git
git fetch upstream && git merge upstream/main

Arquitectura

1. Una cuenta

Una persona conecta una cuenta del colectivo una sola vez. A partir de ahí redacta, alguien aprueba y el Hub publica. La contraseña de la red social no pasa nunca por el Hub: la red devuelve un token y el Hub lo guarda cifrado.

flowchart LR
    P([Persona]) -->|1. Conectar| H[T-HUB]
    H -->|2. Pantalla de permiso| R[(Red social)]
    R -->|3. Token| H
    P -->|4. Redacta| H
    H -->|5. Aprobación| A([Otra persona del colectivo])
    A -->|6. Visto bueno| H
    H -->|7. Publica a su hora| R

2. Varias cuentas compartidas

Cada colectivo tiene sus cuentas y sus reglas de aprobación. Un borrador puede ir a varias cuentas a la vez: el Hub crea una copia por destino y cada una pasa la regla de su cuenta (automática, una persona, mayoría o consenso). Lo que deba salir en toda la red sube a la cola del colectivo padre.

flowchart TB
    subgraph TTS[TAKE THE STREET]
        Q[Cola común] --> IG1[Instagram internacional]
        Q --> M1[Mastodon @takethestreet]
    end
    subgraph S26[ACAMPADA 26S]
        D[Borrador] --> IG2[Instagram @acampada26s]
        D --> M2[Mastodon @26s]
        D --> T2[Telegram acampada26s]
    end
    subgraph SUB[Acampadas locales]
        B[Barcelona] --> IG3[Instagram propio]
        S[Sevilla] --> IG4[Instagram propio]
    end
    S26 -->|Sube para toda la red| Q
    SUB -->|Sube a 26S| D
    IG2 -.->|Regla: 2 aprobaciones| G{{Aprobación}}
    M2 -.->|Regla: automática| G

3. Parte técnica

La web solo habla con la API. La API guarda el estado en Postgres con aislamiento por colectivo (RLS), los tokens van en un vault cifrado y el envío programado lo hace el scheduler con Temporal. Cada red es un conector; fuera de los conectores no hay código que distinga redes.

flowchart LR
    subgraph Cliente
        W[apps/web<br/>Next.js]
    end
    subgraph Control
        API[apps/api<br/>NestJS]
        SCH[apps/scheduler<br/>Temporal worker]
        REG[ConnectorRegistry<br/>packages/connector-*]
    end
    subgraph Datos
        PG[(PostgreSQL<br/>RLS por colectivo)]
        RD[(Redis<br/>estado OAuth 10 min)]
        V[Vault<br/>AES-256-GCM + KEK]
        S3[(S3 / R2<br/>medios cifrados)]
    end
    subgraph Medios
        MW[apps/media-worker<br/>FFmpeg]
        PT[PeerTube<br/>tube.occupymedia.cc]
    end
    subgraph Redes
        MA[Mastodon API]
        IG[Graph API<br/>Instagram]
        TG[Telegram Bot API]
    end

    W -->|HTTPS + sesión| API
    API --> PG
    API --> RD
    API --> V
    V --> PG
    API -->|connect / identity| REG
    SCH -->|publish| REG
    SCH --> PG
    REG -->|providerFetch<br/>control de salida| MA
    REG --> IG
    REG --> TG
    MW --> S3
    MW -->|RTMP| PT

Conectar una cuenta (OAuth con state de un solo uso y PKCE donde la red lo admite):

  1. POST /connections/authorize: la API guarda en Redis, cifrado, el estado de la petición y devuelve la URL de la red.
  2. La persona acepta en la red y la red vuelve a GET /connections/callback.
  3. La API comprueba state, cookie y permisos, canjea el código y guarda el token en el vault.

Estado por red

Red Conectar Publicar Notas
Mastodon Sí Texto Sin claves de servidor; cada cuenta registra la app en su instancia.
Telegram Sí Texto Token del bot de BotFather.
Instagram Sí, con INSTAGRAM_CLIENT_ID/SECRET No todavía Meta descarga la imagen de una URL pública y los medios están cifrados: falta un endpoint temporal. Ver docs/oauth-connect.md.
PeerTube No Directo sí Falta el puente OAuth en tube.occupymedia.cc.
X No No Sin conector. API de pago por uso.
TikTok No No Fuera de alcance: la API exige auditoría y sin ella solo publica en privado.

Panel Outreach (mensajes privados)

Lista de cuentas públicas (artistas, colectivos, medios) a las que escribir por privado para pedir difusión. Cada contacto tiene su mensaje, se puede rellenar con una plantilla ({name}, {handle}) y se marca enviado, respondido o rechazado. Se importa en CSV:

name,platform,handle,profileUrl,category,language
Climáximo,bluesky,climaximopt.bsky.social,https://bsky.app/profile/climaximopt.bsky.social,clima,pt

El Hub no envía los mensajes. "Copy & open profile" copia el texto y abre el perfil; la persona lo pega y lo envía desde la propia red. Motivos:

Red Abrir un privado por API Problema
Instagram No La API solo deja responder a quien te escribió en las últimas 24 h.
X Sí, de pago Mensajes en masa a desconocidos van contra sus normas de automatización: suspensión.
Mastodon Sí (mención privada) Las instancias silencian o bloquean cuentas que lo hacen en masa; puede caer la instancia entera.
Bluesky Sí Mismo criterio de spam; los chats de desconocidos suelen estar cerrados.

El contador de la pantalla avisa a partir de 20 primeros mensajes al día por colectivo. Repartidlos entre varias personas y días, y personalizad cada uno.

Requisitos

Medido en el despliegue actual (ver docs/ops/2026-10-12-cloud-hosting-requirements.md). Los medios van a R2, así que el disco no crece con los vídeos.

Nivel CPU RAM Disco Para qué
Mínimo 4 vCPU 8 GB 160 GB SSD Todo el stack, transcodificación fuera de la máquina
Recomendado 8 vCPU 16 GB 240 GB SSD Directos, transcodificación en la máquina, Collabora
Nodo de render (opcional) 4+ núcleos o GPU con NVENC 8 GB 50 GB Se une por Tailscale; nunca guarda claves

Uso real hoy: unos 2,6 GB de RAM entre todos los servicios.

Ancho de banda:

Uso Necesario
Panel, publicación, APIs de redes < 1 Mbps sostenido
Un directo saliente (1080p30 hacia PeerTube) 6 Mbps de subida por directo; 3 Mbps a 720p
Espectadores Los sirve Cloudflare desde la caché de HLS; el origen no escala con la audiencia
Subidas de vídeo Picos de lo que dé la línea; límite de 2 GiB por archivo

Coste orientativo: VPS de 30 €/mes + R2 de 1 a 5 $/mes. Cloudflare en plan gratuito.

Red y puertos

Entrada desde internet: ninguna directa. Todo entra por el túnel de Cloudflare; el nginx de borde solo acepta rangos de Cloudflare en 80/443.

Puerto Servicio Exponer a
443/tcp nginx de borde Solo rangos de Cloudflare
80/tcp nginx de borde (redirección) Solo rangos de Cloudflare
3000/tcp API Solo 127.0.0.1
3001/tcp Web Solo 127.0.0.1
9000/tcp PeerTube Solo 127.0.0.1 o Tailscale
1935/tcp RTMP de PeerTube Solo Tailscale (worker de medios)
6167/tcp Matrix (conduwuit) Solo 127.0.0.1
9200, 9980/tcp oCIS, Collabora Solo 127.0.0.1
5432, 6379, 7233 Postgres, Redis, Temporal Nunca fuera de la red de Docker

Salida que hace falta: 443/tcp a las APIs de las redes y a R2, 7844/tcp+udp para cloudflared, 41641/udp para Tailscale.

Docker publica puertos saltándose ufw. Para cerrarlos: publicar en 127.0.0.1:PUERTO o en la IP de Tailscale, o añadir reglas en la cadena DOCKER-USER.

Seguridad y monitorización

Prácticas para cualquier despliegue. Los fallos de seguridad se comunican en privado a quien mantiene el código, no en issues públicas.

  • Registro solo por invitación. Un registro abierto da a cualquiera un colectivo propio y permisos de Owner sobre él.
  • Un secreto por servicio. La web y los workers no necesitan SUPERPOSTER_KEK, S3_KEK ni SESSION_SECRET. Claves distintas para el vault y para los medios.
  • FFmpeg siempre en sandbox (NODE_ENV=production, MEDIA_SANDBOX=on), sin red y sin variables de entorno heredadas.
  • Cabeceras: CSP estricta, HSTS, X-Content-Type-Options, Referrer-Policy en web y API.
  • Límites de peticiones en /auth/*, invitaciones, OIDC y subidas públicas, por CF-Connecting-IP.
  • Metadatos fuera: quitar EXIF/GPS de fotos y vídeos antes de guardarlos o publicarlos.
  • Copias cifradas (age o GPG) y fuera de la máquina; probar la restauración.
  • Auditoría inalterable: quitar UPDATE/DELETE sobre audit_log al rol sp_app y anclar fuera la cabeza de la cadena.

Qué vigilar (Prometheus + Grafana ya están en infra/; Grafana nunca con acceso anónimo):

Señal Alerta si
401/403 en /auth/login por IP > 20 en 5 min
Registros nuevos / invitaciones canjeadas Cualquiera fuera de lo previsto
Eventos audit_log: member.*, connection.removed, group.deleted, outreach.removed Picos o actores raros
connections en estado needs_credentials Cualquiera: token revocado o caducado
Errores 5xx de la API > 1 % durante 5 min
Memoria de API y workers > 80 %
Cola de publicación failed/unknown Cualquiera
Logins al panel de Cloudflare y cambios de DNS Siempre

Desarrollo

corepack enable
pnpm install
pnpm exec turbo run build typecheck --filter=@tactical-media-broadcast-hub/api...
pnpm --filter @tactical-media-broadcast-hub/connector-instagram test

Los tests de integración de apps/api que usan Postgres se saltan si no hay base de datos configurada.


Upstream: Tactical Media Broadcast Hub

Self-hosted multi-tenant social / video / livestream management platform with a generic connector system and a Virtual Channel / Auto Director subsystem.

Built for collectives that need to coordinate media across platforms without concentrating access in one person's hands. Every credential lives in an encrypted vault. Every editorial decision lands in a tamper-evident audit log. Every feature is multi-tenant with row-level security.

Features

  • Publishing pipeline — drafts → group approval → schedule → fenced dispatch → receipts. Per-connection publish governance: auto, one-admin, majority or consensus sign-off per account — boosts included. Multi-platform fan-out (Mastodon, Telegram, Bluesky, PeerTube, Facebook, Instagram, Threads, LinkedIn, Reddit, Twitch, Discord, Slack). DST-proof scheduling.
  • Files SSO — the Hub is the OIDC issuer for oCIS Drive: one sign-in, auto-provisioned personal drives, rotating refresh tokens that die with your Hub session (logout propagates).
  • Column wall — live multi-platform feed columns, TweetDeck-style. Safe rendering (hostile-HTML tested). Per-source cooldown + adaptive backoff.
  • Live Control Room — browser multiview, TAKE/CUT, preview/program, lower-thirds, scrolling chyrons, audio mixing, tally.
  • Auto-Director — priority rules switch sources automatically so the stream never dies mid-action.
  • Chat governance — bridged chats (Telegram/Twitch/Discord) into one overlay. Polls with configurable quorum. Approval gates on every post. Machine-readable reason codes.
  • Distributed transcode workers — laptops and GPUs join as render workers over Tailscale. Per-job sandboxed FFmpeg (UID/seccomp/no-network).
  • Archive — content-addressed encrypted S3, dedup, full-text search, CSV/JSON export. Deleted platform content stays citable.
  • Analytics — month-partitioned snapshots, current + previous month with deltas, per-provider breakdown.

Quickstart

pnpm install
pnpm lint && pnpm typecheck && pnpm build

# infrastructure
docker compose -f infra/docker-compose.yml up -d postgres redis temporal minio otel-collector prometheus grafana

# everything incl. media stub + mock live source + mock RTMP sink
docker compose -f infra/docker-compose.yml --profile dev up -d

# apps
pnpm dev            # api :3000, web :3001(adjust if colliding), scheduler, media-worker :3100
curl localhost:3000/healthz
curl localhost:3000/readyz

Documentation

Section Path
Engineering guide docs/engineering-guide.md
Admin guide docs/admin-guide.md
User manual docs/user-manual-v2.md
Design docs docs/design/
Streaming architecture docs/design/streaming-architecture.md
Deployment (occupymedia.cc) infra/occupymedia/DEPLOY.md

Deterministic live-source testing

pnpm --filter @tactical-media-broadcast-hub/media-worker soak -- --hours 8 --out

Security & trust model

Self-hosted, end to end — no corporate cloud, no accounts needed to run the system itself. Multi-tenant with row-level security; platform credentials in an encrypted vault the database never sees in plaintext; every editorial action in a hash-chained audit log; anonymous-infrastructure friendly (the host carries boxes, not content — storage is ciphertext).