Chat interno para equipos y organizaciones, pensado para instalarse en infraestructura propia.
Funcionalidades · Instalación · Desarrollo · Configuración · Autenticación · Contribuir
Captura con datos de demostración.
Monorepo con un backend en Express + Socket.IO + Prisma (PostgreSQL) y un frontend en Next.js.
Autenticación: LINK funciona con cuentas propias (modo local, sin dependencias externas) o delegando el login en un sistema de identidad externo, con un proveedor de autenticación que se instala como plugin. Se elige en el
.env; ver Autenticación.
- Conversaciones privadas, grupos y "mensajes guardados" (chat con uno mismo), en tiempo real.
- Reacciones, menciones con
@, búsqueda dentro del chat, borradores, reenvío y selección múltiple de mensajes y conversaciones. - Adjuntos (archivos, imágenes, notas de voz, fotos desde la cámara) con almacenamiento en disco o en un servicio compatible con S3, incluida la subida por partes de archivos grandes.
- Encuestas en grupos, compartir contactos y GIFs/stickers vía GIPHY (deshabilitado por defecto, ver Requisitos de GIPHY).
- Llamadas y videollamadas 1 a 1 (WebRTC).
- Notificaciones push (Web Push) y PWA instalable.
- Panel de administración: configuración global, almacenamiento, usuarios (en modo local, alta de cuentas, contraseñas y política de sesión) y registro de auditoría.
- Interfaz en español e inglés, con tema claro y oscuro.
Depende de cómo se instale (ver Inicio rápido con Docker y Desarrollo local):
| Con Docker | Sin Docker (desarrollo) | |
|---|---|---|
| Git | Sí | Sí |
| Docker con Docker Compose | Sí | No (opcional, para tener un PostgreSQL de prueba) |
Node.js 24 (recomendado) o 22.12 o superior, con npm (la versión está fijada en .nvmrc) |
Solo para npm run setup, que también corre dentro de un contenedor |
Sí |
| PostgreSQL (probado con la 17) | Lo incluye docker-compose.yml |
Sí, propio o en un contenedor |
Opcional en los dos casos: un servicio compatible con S3 (SeaweedFS, MinIO, AWS S3…) para el almacenamiento de archivos.
La forma más simple de probar LINK o de instalarlo en un servidor: un solo docker compose
levanta PostgreSQL, el backend y el frontend. Las migraciones de base de datos se aplican solas al
arrancar el backend.
-
Clonar el repositorio.
git clone https://github.com/anderlfrias/link.git cd link -
Generar la configuración. Crea el
.envcon la contraseña de PostgreSQL, las claves de las notificaciones push y los secretos ya generados: no hay que copiar ni editar nada a mano. (Qué hace en detalle: Qué hacenpm run setup. Sin Node.js instalado: Sin Node.js instalado.)npm run setup
-
Levantar LINK. La primera vez construye las imágenes y tarda unos minutos.
docker compose up -d --build
-
Crear el primer admin, con el correo propio en lugar del de ejemplo. Es para el modo por defecto, con cuentas propias; con un proveedor de autenticación externo no aplica (ver Con un proveedor de autenticación externo).
docker compose exec backend npm run auth:admin -- create-admin --email tu-correo@example.comEl comando muestra una contraseña temporal una sola vez: copiarla antes de seguir.
-
Entrar. Abrir http://localhost:3000, iniciar sesión con ese correo y la contraseña temporal, y elegir una contraseña nueva (de 12 caracteres o más).
Listo: el frontend queda en http://localhost:3000 y la API en http://localhost:4000 (Socket.IO en la misma URL). Desde el panel de administración, el admin crea las cuentas del resto del equipo y ajusta la configuración funcional. Si algo no anda, ver Si algo falla.
Crea .env a partir de .env.example y genera lo que hace falta para arrancar:
| Variable | Qué es |
|---|---|
POSTGRES_PASSWORD |
Contraseña de PostgreSQL. |
SESSION_JWT_SECRET |
Firma las sesiones de LINK, con cualquier proveedor de autenticación. |
FILE_URL_SIGNING_SECRET |
Firma las URLs de descarga de archivos. |
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY |
Par de claves de las notificaciones push (Web Push). |
No necesita npm ci, nunca pisa un valor que ya esté en el archivo (solo completa lo que está
vacío, así que se puede volver a correr sin riesgo) y no imprime ningún secreto, solo los nombres
de las variables que completó. Opciones, que se pasan después de -- (por ejemplo,
npm run setup -- --dev):
| Opción | Efecto |
|---|---|
--vapid-subject mailto:equipo@example.org |
Reemplaza el contacto de ejemplo de las notificaciones push (un mailto: o una URL https://). |
--dev |
Prepara los archivos para correr sin Docker (ver Desarrollo local). |
--help |
Muestra la ayuda. |
El mismo script corre dentro de un contenedor:
docker run --rm -v "$PWD":/work -w /work node:24-bookworm-slim node scripts/setup.js(En PowerShell, ${PWD} en lugar de "$PWD". En Linux, agregar --user "$(id -u):$(id -g)": sin
eso, el .env queda a nombre de root y docker compose no puede leerlo.)
Si la instalación delega el login en un sistema de identidad externo en lugar de usar cuentas
propias (ver Autenticación), definir AUTH_PROVIDER_MODULE y las variables del
proveedor en el .env antes de levantar (paso 3). El paso 4 no aplica: las cuentas y sus roles
(por ejemplo admin) vienen del proveedor. SESSION_JWT_SECRET, que el script genera, se usa con
cualquier proveedor.
| Síntoma | Qué hacer |
|---|---|
docker compose no se conecta con Docker (failed to connect to the docker API, Cannot connect to the Docker daemon) |
Docker no está iniciado: abrir Docker Desktop (o iniciar el servicio docker en Linux) y repetir. |
docker compose dice Falta POSTGRES_PASSWORD… o Falta VAPID_… |
No hay .env o está incompleto. Correr npm run setup en la raíz del repo. |
npm run setup dice que falta .env.example |
El checkout está incompleto (por ejemplo, se copió la carpeta sin los archivos ocultos). Volver a clonar el repositorio. |
npm run setup avisa que POSTGRES_PASSWORD sigue en change-me |
El .env ya existía (por ejemplo, copiado a mano) y el script no cambia solo una contraseña existente. Si todavía no se levantó LINK, borrar ese valor del .env y volver a correr el script. Si ya se levantó, no cambiarla: el volumen de PostgreSQL conserva la original. |
| El backend no arranca o se reinicia una y otra vez | Mirar docker compose logs backend. Causas comunes: un .env incompleto o con claves VAPID inválidas, o una POSTGRES_PASSWORD cambiada después del primer arranque (el volumen postgres-data conserva la contraseña original). Volver al valor original o, si no hay datos que conservar, docker compose down -v y levantar de nuevo (borra los datos). |
| El puerto 3000 o el 4000 ya está en uso | Cambiar FRONTEND_PORT o BACKEND_PORT en .env, actualizar PUBLIC_API_URL, PUBLIC_SOCKET_URL y CORS_ORIGIN con los puertos nuevos y volver a correr docker compose up -d --build (las URLs se embeben en el build del frontend). |
| La interfaz carga pero no deja iniciar sesión (en la consola del navegador aparece un error de CORS) | CORS_ORIGIN tiene que ser el origen exacto con el que se abre el frontend: http://localhost:3000 no equivale a http://127.0.0.1:3000 ni a la IP de la máquina. Además, PUBLIC_API_URL y PUBLIC_SOCKET_URL tienen que ser alcanzables desde el navegador. Corregirlos en .env y reconstruir con docker compose up -d --build. |
| Se perdió la contraseña temporal del admin | docker compose exec backend npm run auth:admin -- reset-password --email tu-correo@example.com genera otra (también sirve si el único admin olvidó la suya). |
- Las URLs con las que el navegador llega a la API (
PUBLIC_API_URL,PUBLIC_SOCKET_URL) se embeben en el build del frontend. Si se cambian los puertos o se publica detrás de un dominio, hay que actualizarlas en.envy reconstruir condocker compose build frontend. CORS_ORIGINtiene que ser el origen con el que se abre el frontend (por defectohttp://localhost:3000). Detrás de un proxy inverso, ajustar tambiénTRUST_PROXY(ver SECURITY.md).- Imágenes publicadas. Cada versión (tag
vX.Y.Z) publica en GHCRghcr.io/<dueño>/link-backend:X.Y.Zylink-frontend:X.Y.Z, así que con Docker se puede actualizar cambiando el número de versión, sin reconstruir el backend. La imagen del backend es la base para agregar un proveedor de autenticación (guía). La del frontend sale con las URLs delocalhost(se embeben en el build): detrás de un dominio hay que reconstruirla con las propias, como se explica arriba. - Los archivos subidos quedan en el volumen
uploads, y la base de datos enpostgres-data. - Ver logs:
docker compose logs -f backend. Bajar todo:docker compose down. Bajar y borrar los datos:docker compose down -v. - Actualizar.
git pullydocker compose up -d --build: el backend aplica las migraciones al arrancar. Antes de actualizar una instalación existente, hacer un backup de la base (ver Base de datos y migraciones). El procedimiento completo está en VERSIONING.md.
Para modificar el código. Hace falta Node.js y un PostgreSQL (ver Requisitos).
-
Clonar el repositorio e instalar las dependencias. El
postinstalldel backend genera el cliente de Prisma:git clone https://github.com/anderlfrias/link.git cd link npm ci -
Tener un PostgreSQL disponible. Por ejemplo, con Docker:
docker run -d --name link-postgres -e POSTGRES_USER=link -e POSTGRES_PASSWORD=link -e POSTGRES_DB=link -p 5432:5432 postgres:17-alpine
-
Generar la configuración:
npm run setup -- --dev
Crea
backend/.envyfrontend/.env.locala partir de sus.env.example, y genera en el del backend las claves VAPID y los secretos. ElDATABASE_URLde ejemplo ya apunta al contenedor del paso 2: con otro servidor, ajustarlo enbackend/.env. -
Aplicar las migraciones:
npm run db:migrate
-
Crear el primer admin (modo local), con el correo propio:
npm run auth:admin:dev --workspace=backend -- create-admin --email tu-correo@example.com
Muestra una contraseña temporal una sola vez: copiarla.
-
Levantar backend (puerto 4000) y frontend (puerto 3000) juntos:
npm run dev
Abrir http://localhost:3000 e iniciar sesión con ese correo y la contraseña temporal.
npm test # tests de backend y frontend (Vitest)
npm run test:coverage --workspace=backend # con cobertura
npm run build # build de backend (tsc) y frontend (next build)Cada archivo .env.example documenta sus variables en detalle. npm run setup crea el archivo real
a partir de cada uno (ver Inicio rápido con Docker) y genera los secretos:
| Archivo | Para qué |
|---|---|
.env.example |
docker compose |
backend/.env.example |
Backend sin Docker (backend/.env) |
frontend/.env.example |
Frontend sin Docker (frontend/.env.local) |
Las más importantes del backend:
| Variable | Obligatoria | Descripción |
|---|---|---|
DATABASE_URL |
sí | Conexión a PostgreSQL. |
AUTH_PROVIDER_MODULE |
no | Ruta absoluta al módulo de un proveedor de autenticación externo (guía). Sin definir, cuentas locales. |
SESSION_JWT_SECRET |
sí | Firma las sesiones de LINK, en todos los modos. 32 caracteres o más; cambiarla cierra todas las sesiones. (LOCAL_AUTH_JWT_SECRET, su nombre anterior, se sigue aceptando con un aviso.) |
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT |
sí | Notificaciones push. Con claves inválidas el backend no arranca. |
CORS_ORIGIN |
en producción | Origen(es) del frontend, separados por coma. Con NODE_ENV=production es obligatoria (sin ella el backend no arranca); * acepta cualquier origen a propósito. Fuera de producción, sin definir acepta cualquier origen. |
TRUST_PROXY |
no | Proxies delante del backend: 1 (por defecto), 2, o false si el backend está expuesto directo. Ver SECURITY.md. |
TRUST_CF_CONNECTING_IP |
no | true solo si todo el tráfico entra por Cloudflare. Por defecto false. |
FILE_URL_SIGNING_SECRET |
recomendada | Firma de las URLs de archivos. Sin definir, usa SESSION_JWT_SECRET. |
GIPHY_API_KEY |
no | GIFs/stickers vía GIPHY. Sin definir quedan deshabilitados. Antes de activarlos, ver Requisitos de GIPHY. |
STORAGE_WRITE_PROVIDER, S3_* |
no | Almacenamiento en disco (LOCAL, por defecto) o S3. |
LOG_LEVEL, LOG_PRETTY |
no | Nivel y formato del log (JSON por defecto). |
El frontend solo necesita NEXT_PUBLIC_API_URL y NEXT_PUBLIC_SOCKET_URL, que se embeben al
hacer el build.
La configuración funcional (límites de subida, permisos de grupos, retención de mensajes,
auditoría, etc.) no va en el .env: se edita desde el panel de administración y se guarda en
la base.
El modo se deduce de las variables de entorno del backend:
- Proveedor externo (
AUTH_PROVIDER_MODULEdefinida): el login se delega en un sistema de identidad externo, a través de un proveedor de autenticación. LINK le pide que valide las credenciales solo en el login, crea o actualiza el perfil local del usuario, guarda sus roles (por ejemploadmin) y emite su propia sesión: de ahí en más el token del sistema externo no se acepta en ningún lado. Cómo escribir, instalar y probar un proveedor:docs/auth-providers.md. - Local (sin
AUTH_PROVIDER_MODULE): las cuentas y sus contraseñas viven en la base de LINK. Se inicia sesión con el correo o el nombre de usuario.- El primer admin se crea por terminal con
npm run auth:admin -- create-admin --email <correo>(con Docker,docker compose exec backend npm run auth:admin -- …; sin compilar,auth:admin:dev). La contraseña temporal se muestra una sola vez y hay que cambiarla al entrar.reset-password --email <correo>restablece la de cualquier cuenta, por ejemplo si el único admin olvidó la suya. - Desde el panel de administración, un admin crea y edita cuentas, restablece contraseñas, desbloquea y desactiva cuentas, y define la duración de la sesión y la política de contraseñas (largo mínimo, composición, vencimiento, historial y bloqueo por intentos fallidos).
- Detalles en
backend/src/modules/auth/README.mdy endocs/design/LOCAL_AUTH_PLAN.md.
- El primer admin se crea por terminal con
En los dos modos un admin puede desactivar el acceso de una cuenta al chat. Si el módulo del proveedor no se encuentra o su configuración es inválida, el backend no arranca (en lugar de caer por error en las cuentas locales). Para pasar una instalación existente de un modo al otro, ver LOCAL_AUTH_PLAN.md §10.
El esquema vive en backend/prisma/schema.prisma y se aplica con
migraciones de Prisma (backend/prisma/migrations/):
| Comando | Uso |
|---|---|
npm run db:migrate |
Aplica las migraciones pendientes (instalaciones nuevas, despliegues). |
npm run db:migrate:dev |
Desarrollo: crea una migración nueva a partir de cambios en schema.prisma. |
npm run db:baseline |
Solo una vez, en instalaciones creadas antes de las migraciones (ver abajo). |
No hace falta ningún seed: la fila de configuración global se crea sola en el primer arranque. Con un proveedor externo, los usuarios se crean al iniciar sesión.
Instalaciones existentes creadas con prisma db push. Antes de usar migraciones hay que
marcar la migración inicial como ya aplicada, sin ejecutarla ni tocar datos:
cd backend
npx prisma migrate diff --from-config-datasource --to-schema prisma/schema.prisma # debe decir "No difference detected"
npm run db:baseline
npm run db:migrate # "No pending migrations to apply"Si migrate diff muestra diferencias, la base no coincide con el esquema actual: revisarlas
antes de seguir. Hacer un backup antes de cualquiera de estos pasos.
Backup antes de actualizar. Algunas migraciones cambian datos y no se pueden deshacer. La
20261008120000_purge_deleted_message_content borra de forma permanente el texto, las encuestas y
la relación con los archivos de los mensajes que ya estaban eliminados (los archivos en sí no se
borran: los libera la limpieza de archivos huérfanos, si está activada). Antes de actualizar una
instalación existente, hacer un backup de la base:
# Con Docker (usuario y base por defecto del docker-compose.yml)
docker compose exec -T postgres pg_dump -U link -Fc link > link-backup.dump
# Sin Docker
pg_dump --format=custom --file=link-backup.dump "$DATABASE_URL"npm ci
NEXT_PUBLIC_API_URL=https://api.example.com/api NEXT_PUBLIC_SOCKET_URL=https://api.example.com npm run build
npm run db:migrate
pm2 start ecosystem.config.js # o: npm start- El backend tiene que correr como una sola instancia: Socket.IO no tiene un adapter compartido (Redis), así que con varias instancias los eventos no llegarían a todos los clientes.
ecosystem.config.jsarranca el backend conNODE_ENV=production, así queCORS_ORIGINes obligatoria enbackend/.env.- Si hay un proxy inverso delante, definir
TRUST_PROXY(yTRUST_CF_CONNECTING_IPsi el tráfico entra por Cloudflare) según SECURITY.md.
Por defecto los archivos se guardan en disco (backend/uploads, o el volumen uploads con
Docker). Con ese almacenamiento, el tamaño máximo de un archivo es 32 MB, sin importar lo que
se configure en el panel de administración: la subida por partes de archivos grandes (hasta 2 GB
por defecto) necesita S3. Para usar un servicio compatible con S3:
- Definir
STORAGE_WRITE_PROVIDER=S3y las variablesS3_*. - El navegador sube y descarga directo contra
S3_ENDPOINTcon URLs firmadas, así que esa URL tiene que ser alcanzable igual desde el backend y desde el navegador. Por ejemplo, un dominio propio, no un nombre interno de Docker. - El bucket necesita CORS que permita
GET,PUTyHEADdesde el origen del frontend y que exponga el headerETag.
Los archivos que ya están en disco se pueden migrar a S3 desde el panel de administración. El
diseño completo está en docs/design/LARGE_FILES_PLAN.md.
.
├── backend/ # API Express + Socket.IO + Prisma
│ ├── prisma/ # schema.prisma y migraciones
│ ├── src/modules/ # un módulo por dominio, cada uno con su README
│ ├── src/socket/ # gateway y registro de eventos en tiempo real
│ ├── src/workers/ # tareas periódicas (retención, limpieza, migración de archivos)
│ └── API.md # referencia de la API para el frontend
├── frontend/ # Next.js (App Router)
│ └── src/features/ # una carpeta por funcionalidad (api, components, hooks, types)
├── docs/design/ # documentos de diseño de cada iniciativa
├── scripts/ # npm run setup, sincronizar la versión y generar el sonido de notificación
├── docker-compose.yml
└── ecosystem.config.js # PM2 (producción sin Docker)
Documentación técnica:
docs/ARCHITECTURE.md: arquitectura global del sistema, flujos de datos, seguridad y limitaciones operativas.backend/README.md: arquitectura del backend y modelo de datos relacional.backend/API.md: referencia de la API RESTful y de los eventos de Socket.IO.- Los
README.mdde cada módulo enbackend/src/modules/. docs/auth-providers.md: guía para crear e integrar proveedores de autenticación externos como plugins.docs/design/: documentos de diseño técnico (logging y auditoría, archivos grandes, testing, autenticación local).
Líneas de trabajo planificadas:
- Escalabilidad horizontal: adaptador distribuido para Socket.IO (Redis Streams o Pub/Sub) y store compartido de rate limiting para permitir múltiples instancias del backend en clúster.
- Subida por partes en disco: soporte para subida chunked multipart directamente sobre disco local sin depender de S3 para archivos superiores a 32 MB.
- Herramientas de calidad de código: configuración e integración de linter y formateador homogéneos (ESLint/Prettier o Biome) en el monorepo y la CI.
- Proveedores de identidad estándar: soporte directo para OpenID Connect (OIDC) y SAML 2.0 como plugins de autenticación.
- Llamadas grupales: servidor de medios centralizado (SFU) para audio y video en conferencias multipersona.
Limitaciones operativas y de seguridad conocidas (el detalle está en SECURITY.md):
- El backend debe ejecutarse como una sola instancia (los límites de frecuencia y Socket.IO residen en memoria).
- Los archivos mayores a 32 MB requieren almacenamiento S3 configurado.
- El directorio de usuarios muestra el correo de todas las cuentas activas, y los avatares se descargan sin autenticación previa.
- Los mensajes no cuentan con cifrado de extremo a extremo (E2EE): viajan cifrados en tránsito y se almacenan en claro en la base de datos para auditoría y moderación.
- Las notificaciones push llevan el texto del mensaje y permanecen activas hasta el cierre de sesión o revocación.
- Las URLs firmadas de descarga de archivos tienen una validez de 1 hora y son portadoras.
- La validación por contenido binario real cubre los formatos más comunes; los formatos de texto se validan por el tipo declarado.
- El token de sesión se almacena en
localStoragedel navegador.
- CONTRIBUTING.md: cómo preparar el entorno, convenciones y reglas de tests.
- SECURITY.md: cómo reportar vulnerabilidades y recomendaciones de despliegue.
- CHANGELOG.md y VERSIONING.md: historial y proceso de versiones.
Copyright © 2026 Anderson Frias.
LINK es software libre bajo la GNU Affero General Public License v3.0, solo esa versión
(AGPL-3.0-only). En la práctica, se puede usar, estudiar, modificar y redistribuir. Quien
distribuya una versión modificada, o la ofrezca a otras personas a través de una red (por ejemplo,
una instalación de LINK con cambios propios que usa un equipo), tiene que ofrecerles el código
fuente de esa versión bajo la misma licencia. El texto de LICENSE es el que vale: este
resumen no lo reemplaza.
El contenido de terceros (diseños de avatares, tipografías, íconos y dependencias con licencias a tener en cuenta) está en THIRD_PARTY_NOTICES.md.
El nombre LINK, el logo y los íconos son assets de marca y se tratan aparte del código: no quedan cubiertos por la AGPL. La lista de archivos está en THIRD_PARTY_NOTICES.md.