Docs para desarrolladores
Español

Operar el servidor

Instalar el servidor

Cómo instalar y operar el servidor Pabal con Docker Compose en un único servidor accesible desde internet.

Cómo instalar el servidor Pabal en un único servidor accesible desde internet. Con Docker Compose se levantan tres contenedores, servidor Pabal · PostgreSQL · Caddy (HTTPS), y todos los datos que no deben perderse se montan (bind mount) en directorios del host. Basta con seguir los pasos en orden, de arriba abajo.

Valores de ejemplo de este documento

Dominio pabal.me, IP pública del servidor 203.0.113.10 (una dirección de ejemplo: sustitúyela por tu IP real al leer), código fuente en /opt/pabal, datos en /srv/pabal, sistema operativo Ubuntu 24.04 LTS. Los archivos necesarios están en la carpeta deploy/ del repositorio.

Visión general

Internet docker compose · ssemiya-net /srv/pabal (bind mount) Aplicación PabalPabal.app Navegador · botsSitio · Bot API OperadorSSH pabal-server MTProto :8443 Web·Bot API·admin:8080 uid 1000 · JRE 21 Webhooks → bots pabal-caddy:80 · :443 · TLS auto pabal-postgres 16Red interna · sin puerto pabal_server/keys · clave RSA pabal_server/data · fotos pabal_server/logs postgres_data/ · cuentas·chats caddy/ · certificados backups/ · backup.sh MTProto :8443 (cifrado propio) HTTPS :443 Túnel SSH → 127.0.0.1:8080/admin/
Un servidor, tres contenedores y todo lo que debe perdurar, en /srv/pabal

Explicación del diagrama

  • Tres zonas: usuarios en internet → los tres contenedores del servidor de producción → los directorios montados en el disco del host. Los contenedores se pueden borrar y volver a crear en cualquier momento; todo lo que debe perdurar está en /srv/pabal.
  • Solo hay dos entradas: la aplicación se conecta directamente al servidor por el puerto MTProto (MTProto lleva su propio cifrado, así que no necesita HTTPS), y el sitio web, la documentación y la Bot API pasan por el 443 de Caddy. Caddy bloquea las rutas de administración (/admin, /health …) y deja pasar el resto.
  • La línea discontinua roja es el camino reservado al operador. El panel de administración solo está abierto en 127.0.0.1:8080 del servidor, así que solo se entra por un túnel SSH. PostgreSQL no tiene ningún puerto abierto hacia fuera.
  • Las dos casillas rojas son los datos más importantes. La clave RSA de pabal_server/keys va incorporada en la aplicación al compilarla, así que si la pierdes tendrás que redistribuir todas las aplicaciones; y en postgres_data/ están todas las cuentas y conversaciones. Son la prioridad número uno de las copias de seguridad.
  • Las flechas grises son escrituras: el servidor escribe claves, fotos, ajustes y logs; PostgreSQL, los eventos; y Caddy, los certificados, cada uno en su propio directorio.

Requisitos

ConceptoDetalle
ServidorUbuntu 24.04 LTS; para empezar, 2 vCPU · 4 GB de RAM · 40 GB de SSD. Cuando crezcan los usuarios y los mensajes, amplía primero la memoria
IPv4 públicaIP fija. La aplicación se compila con la dirección IP del servidor, y el servidor también comunica su IP a la aplicación
DominioUn dominio cuyo DNS puedas modificar (registro A)
Medio de entrega de los códigos de registroUna cuenta de SMS (Twilio · Solapi · webhook) o de correo electrónico (SMTP)
MacUn Mac en el que compilar la aplicación para este servidor (y, si también quieres crear la aplicación para Windows, un PC con Windows)
Proyecto de Firebase (opcional)Para enviar notificaciones push a la aplicación de Android. Se crea gratis con una cuenta de Google (paso 10)

Puertos

PuertoQuiénAbrirDescripción
22/tcpOperadorSe recomienda solo la IP del operadorSSH
80/tcpCaddyA todosEmisión de certificados · redirección a HTTPS
443/tcp, 443/udpCaddyA todosSitio web · documentación · Bot API (udp es HTTP/3)
8443/tcpServidorA todosConexión de la aplicación (MTProto)
8080/tcpServidorNo abrirPanel de administración · comprobación de estado: solo en 127.0.0.1 del servidor
5432/tcpPostgreSQLNo abrirSolo en la red interna de los contenedores
Por qué el puerto de la aplicación no es el 443

El 443 lo usa el sitio web (HTTPS). MTProto no es ni HTTP ni TLS, así que no se puede repartir el tráfico de un mismo puerto entre ambos. Si, por las redes de empresas o centros educativos que bloquean los puertos poco habituales, quieres que la aplicación también se conecte por el 443, consigue una IP más y asigna el 443 de esa IP al servidor (en .env, pon en PUBLIC_IP la segunda IP y MTPROTO_PORT=443, y reparte los puertos del archivo compose añadiéndoles la IP). El servidor le dice a la aplicación «conéctate a este puerto de esta IP», así que mantén igual el puerto de la aplicación dentro y fuera del contenedor.

1. Preparar el servidor

sudo apt update && sudo apt -y upgrade
timedatectl                      # comprueba que diga "System clock synchronized: yes"

La sincronización de la hora es imprescindible. Los números de los mensajes MTProto se derivan de la hora, así que si el reloj del servidor se desvía, la aplicación reconecta una y otra vez. Si aparece no, ejecuta sudo timedatectl set-ntp true.

# Cortafuegos
sudo ufw allow OpenSSH           # si es posible: sudo ufw allow from <IP del operador> to any port 22
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw allow 8443/tcp
sudo ufw enable

# Docker (script de instalación oficial)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER     # al volver a iniciar sesión, docker funciona sin sudo
docker compose version            # v2 o posterior
Docker y ufw

Los puertos que abre Docker (ports:) se abren independientemente de las reglas de ufw. Por eso el archivo compose solo abre los puertos públicos (80, 443 y 8443), abre el puerto de administración únicamente como 127.0.0.1:8080 y no abre PostgreSQL. Tenlo presente cuando modifiques ports:.

2. Obtener el código fuente

sudo mkdir -p /opt/pabal && sudo chown $USER: /opt/pabal
git clone <dirección del repositorio> /opt/pabal

Si lo copias desde tu ordenador de desarrollo, no envíes las claves, los datos ni los logs (rsync --exclude 'keys/' --exclude 'data/' --exclude 'logs/' --exclude '**/target/'). La clave RSA del servidor de producción la genera el propio servidor de producción la primera vez que arranca: si usas en producción la clave de desarrollo, ese ordenador de desarrollo se convierte en un lugar desde el que se puede descifrar el tráfico de producción.

3. Directorios de datos (bind mount)

sudo mkdir -p /srv/pabal/{postgres_data,pabal_server/keys,pabal_server/data,pabal_server/logs,caddy/data,caddy/config,backups}
sudo chown -R 1000:1000 /srv/pabal/pabal_server        # el contenedor del servidor se ejecuta con uid 1000
sudo chown -R $USER: /srv/pabal/backups
sudo chmod 700 /srv/pabal/pabal_server/keys /srv/pabal/backups
DirectorioDentro del contenedorContenidoSi se pierde
pabal_server/keys/app/keysprivate.pem (clave privada RSA del servidor), private.pem.pub y, opcionalmente, firebase-service-account.json (clave secreta de Firebase para las notificaciones push de Android)Hay que recompilar y redistribuir todas las aplicaciones (la clave de Firebase basta con volver a descargarla de la consola)
pabal_server/data/app/dataFotos (media/), admin-token, operations.json (ajustes de códigos de registro, SMS y SMTP, secretos incluidos)Fotos y ajustes de operación
pabal_server/logs/app/logsLogs del servidor (comprimidos a diario)Solo el historial
postgres_data/var/lib/postgresql/dataTodas las cuentas, conversaciones, mensajes, inicios de sesión, bots y ajustes de webhooksTodo el servicio
caddy/data, caddy/config/data, /configCertificados HTTPSSe vuelven a emitir
backups(solo en el host)Resultados de backup.sh—

4. Archivo de configuración .env

cd /opt/pabal/deploy
cp .env.example .env
chmod 600 .env
openssl rand -base64 30 | tr -d '/+=' | cut -c1-32     # pon el valor resultante en POSTGRES_PASSWORD
nano .env
DOMAIN=pabal.me
PUBLIC_IP=203.0.113.10
POSTGRES_PASSWORD=(el valor generado arriba)
PABAL_HOME=/srv/pabal
PABAL_SOURCE=..
PABAL_NETWORK=ssemiya-net
MTPROTO_PORT=8443
HTTP_PORT=80
HTTPS_PORT=443
ADMIN_PORT=8080
ADMIN_TOKEN=
JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=100"
FCM_CREDENTIALS=
FCM_PROJECT_ID=
PUSH_PREVIEWS=true
ValorQué ponerNotas
DOMAINEl dominio del sitio webLos enlaces que crea la aplicación (pabal.me/nombre_de_usuario), las direcciones de la documentación y las vistas previas de enlaces también usan esta dirección
PUBLIC_IPLa IPv4 pública del servidorEn la nube, la IP pública que aparece en la consola (no la IP privada interna del servidor)
POSTGRES_PASSWORDUn valor aleatorioFíjalo antes del primer arranque. PostgreSQL solo usa este valor cuando crea el directorio de datos por primera vez
PABAL_HOME/srv/pabalEl directorio del paso 3
PABAL_SOURCE..El repositorio desde el que se construye la imagen del servidor: .. si el archivo compose está en deploy/ del repositorio; si no, la ruta del código fuente
PABAL_NETWORKssemiya-netLa red de Docker a la que se unen los tres contenedores. Créala antes (paso 6)
MTPROTO_PORT8443Si lo cambias, cambia también el cortafuegos y la compilación de la aplicación
ADMIN_TOKENDéjalo vacíoSi se deja vacío, se crea en pabal_server/data/admin-token en el primer arranque. Si quieres fijarlo tú, al menos 16 caracteres
JAVA_OPTSEl valor por defectoSi falta memoria, sube -Xmx (hasta aproximadamente la mitad de la memoria del servidor)
FCM_CREDENTIALSDéjalo vacíoNotificaciones push de Android. Para activarlas, /app/keys/firebase-service-account.json (paso 10). Si está vacío, el servidor funciona sin notificaciones push
FCM_PROJECT_IDDéjalo vacíoSolo si hay que enviar a un proyecto de Firebase distinto del project_id del archivo de clave
PUSH_PREVIEWStrueCon false, las notificaciones muestran solo el remitente, sin el contenido del mensaje

El archivo compose de producción arranca el servidor con los números de prueba desactivados (TELEGRAM_TEST_NUMBERS=false). La lista completa de valores de configuración que lee el servidor está en Administración y ajustes — Variables de entorno.

5. DNS

NombreTipoValor
pabal.meA203.0.113.10
www.pabal.meA203.0.113.10
dig +short pabal.me        # debe devolver 203.0.113.10 (el cambio tarda de unos minutos a unas horas en propagarse)

Puedes arrancar antes de que el DNS apunte al servidor. Caddy lo reintentará por su cuenta hasta obtener el certificado.

6. Compilar y arrancar

cd /opt/pabal/deploy
docker network inspect ssemiya-net >/dev/null 2>&1 || docker network create ssemiya-net   # solo una vez
docker compose up -d --build       # la primera vez tarda unos minutos (Maven descarga las bibliotecas)
docker compose ps                  # pabal-server, pabal-postgres y pabal-caddy en estado running (healthy)
docker compose logs -f pabal-server      # sal con Ctrl+C

En el primer arranque, el log muestra líneas como estas.

WARN  ServerKeys - Generated a new RSA key at /app/keys/private.pem (fingerprint -3898654385185406269). Clients must embed /app/keys/private.pem.pub
INFO  TelegramServer - JDBC persistence active (jdbc:postgresql://pabal-postgres:5432/pabal)
INFO  BotFather - BotFather is user 100000
INFO  AdminToken - Created the admin token in /app/data/admin-token
INFO  TelegramServer - Website: http://0.0.0.0:8080/ (published at https://pabal.me/)
  • La primera línea es un WARN, pero es normal (avisa de que la clave se genera, solo esta primera vez). A partir de entonces aparece Loaded RSA key … (fingerprint …). El fingerprint es distinto en cada servidor.
  • El servidor crea las tablas por sí solo al arrancar.

7. Comprobación

# Desde fuera (tu ordenador)
curl -sI https://pabal.me/ | head -1                              # HTTP/2 200
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/docs/    # 200 — esta documentación
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/admin/   # 404 — el panel de administración no es público
curl -s https://pabal.me/docs/server-key.pem | head -1             # -----BEGIN RSA PUBLIC KEY-----
nc -vz 203.0.113.10 8443                                          # succeeded — puerto de la aplicación

# En el servidor
curl -s http://127.0.0.1:8080/health          # {"status":"UP",…}
ls -l /srv/pabal/pabal_server/keys                  # private.pem(600), private.pem.pub

8. Panel de administración y primeros ajustes

# En tu ordenador (déjalo abierto)
ssh -N -L 8080:127.0.0.1:8080 <usuario>@203.0.113.10

# El token, en el servidor
sudo cat /srv/pabal/pabal_server/data/admin-token

Abre http://localhost:8080/admin/ en el navegador e introduce el token. Ajustes imprescindibles antes de entrar en producción (los detalles están en Administración y ajustes):

  1. Pon el método de entrega del código (코드 전달 방식) en SMS o correo electrónico. Con el método «panel de administración» (관리 화면), el operador tiene que comunicar los códigos uno a uno.
  2. Introduce los datos de SMS o de correo electrónico (SMTP), guarda y comprueba con un envío de prueba (테스트 발송) que llegan de verdad.
  3. Comprueba que los números de prueba (테스트 번호) están desactivados. Si están activados, cualquiera puede iniciar sesión con un número +99966….
  4. Si lo necesitas, desactiva Permitir registros nuevos (새 가입 허용) al empezar y da de alta primero solo a las personas que invites.

9. Conectar la aplicación a este servidor

La aplicación se compila con la IP, el puerto y la clave pública del servidor incorporados. Para que se conecte a este servidor, hay que volver a compilarla con la clave pública de este servidor. En el Mac:

# 1. La clave pública de este servidor (la pública, no la privada: también puedes descargarla del sitio de documentación)
curl -s -o keys/production.pem.pub https://pabal.me/docs/server-key.pem

# 2. Apuntar el código fuente de la aplicación a este servidor + aplicar la marca
python3 scripts/tdesktop/point_to_server.py ~/Developer/tdesktop \
    --host 203.0.113.10 --port 8443 --key keys/production.pem.pub
python3 scripts/tdesktop/apply_branding.py ~/Developer/tdesktop

Después, sigue los pasos 5 (configure) y 6 (compilación) de docs/tdesktop-build-guide.md del repositorio. Copia el resultado, out/Debug/Pabal.app, con otro nombre: si vuelves a compilar el mismo código fuente para otro servidor, se sobrescribe.

Antes de repartir la aplicación a otras personas

La compilación actual es una compilación de depuración sin firma ni notarización, así que quien la reciba tendrá que hacer clic derecho → Abrir la primera vez que la abra. Una compilación de depuración guarda sus datos en una carpeta junto a la aplicación (tdata/) y, si no puede escribir allí, usa la misma carpeta de datos que la auténtica Telegram Desktop. Indica a los usuarios que coloquen la aplicación en una carpeta de su usuario (por ejemplo, ~/Applications/Pabal/). Una compilación release para distribución general (carpeta de datos separada, firma y notarización de Apple) es un trabajo aparte.

Para las aplicaciones de Android, iPhone y Windows, el mismo procedimiento (un script que incorpora la dirección y la clave pública del servidor → compilación) está en docs/mobile-apps-guide.md del repositorio. Para que la aplicación de Android tenga notificaciones push, la configuración de la app de Firebase del paso 10 tiene que estar lista antes de compilar.

10. Notificaciones push de Android (opcional)

Android pone enseguida en reposo las aplicaciones que salen de la pantalla. Si no activas las notificaciones push, los mensajes aparecen y las llamadas suenan solo mientras la aplicación está abierta. Las notificaciones push se envían con Firebase Cloud Messaging (FCM) de Google (gratis), y el servidor cifra con una clave que solo tiene ese teléfono el contenido de la notificación (nombre del remitente, vista previa del mensaje), así que Google no puede leerlo.

Proyecto de Firebase (del operador) Compilar · instalar Teléfono · Google google-services.json Configuración de la app Va dentro del APK · no es secreta Clave de cuenta de servicio firebase-service-account.json Secreta · solo en el servidor Compilar la app Android apply_branding.py → APK pabal-server keys/ · chmod 600 FCM_CREDENTIALS Cifra con la clave del teléfono Pabal en el teléfono Al abrirla, registra el teléfono Google FCM Pasa solo cifrado · no puede leerlo ① Registrar teléfono ② Aviso cifrado ③ Despierta → aviso
Dos archivos, cada uno a su sitio; las notificaciones, cifradas

Explicación del diagrama

  • Tres zonas: el proyecto de Firebase que crea el operador → donde se compila la aplicación y se pone en marcha el servidor → el teléfono y Google. De un mismo proyecto de Firebase se obtienen dos archivos.
  • Las flechas grises son la preparación, que se hace una sola vez. La casilla verde, google-services.json, va dentro de la aplicación y viaja en todos los APK, así que no es secreta; la casilla roja, la clave de la cuenta de servicio, es un secreto que solo se guarda en keys/ del servidor. Los dos archivos tienen que salir del mismo proyecto.
  • Las flechas azules son el camino de una notificación: ① al abrir la aplicación por primera vez, el teléfono registra en el servidor su token de FCM y una clave que solo tiene ese teléfono; ② cuando llega un mensaje o una llamada, el servidor cifra la notificación con esa clave y se la pasa a FCM; ③ FCM despierta el teléfono, y la aplicación la descifra, muestra el aviso y vuelve a conectarse al servidor para recibir lo que se perdió.
  • Lo que ve Google son solo datos de entrega, como el token del dispositivo, la hora de envío y la prioridad. El nombre del remitente y el mensaje van cifrados, así que no puede leerlos.
  • A un teléfono que tiene la aplicación en pantalla no se le envían notificaciones push: ya lo recibe todo por su conexión. Las notificaciones push de los chats silenciados se envían con prioridad no urgente, y la propia aplicación las filtra.
El proyecto de Firebase va emparejado con la aplicación

Con la clave del servidor solo se pueden enviar notificaciones push a aplicaciones compiladas con el mismo proyecto de Firebase. Una aplicación compilada por otra persona (por ejemplo, la que se descarga de Pabal.me) está ligada al proyecto de Firebase de esa persona y, además, se compiló con la dirección y la clave pública del servidor de esa persona. Las aplicaciones de tu servidor las compilas tú, y el proyecto de Firebase también lo creas tú.

Crear el proyecto de Firebase

  1. En la consola de Firebase, Agregar proyecto. El nombre puede ser cualquiera, y Google Analytics no hace falta.
  2. Configuración del proyecto → General → Tus apps → Agregar app (Android). En el nombre del paquete pon me.pabal.android (el nombre que el script de compilación le da a la aplicación). La huella del certificado (SHA-1) no hace falta para las notificaciones push.
  3. Descarga el google-services.json de esa app. Es la configuración que va dentro de la aplicación y no es secreta.
  4. En Configuración del proyecto → Cuentas de servicio → Generar nueva clave privada descarga un archivo JSON y cámbiale el nombre a firebase-service-account.json. Este archivo es secreto: no pongas su contenido en el repositorio, en un chat ni en .env.
  5. En Configuración del proyecto → Cloud Messaging, comprueba que API de Firebase Cloud Messaging (V1) está habilitada (en los proyectos nuevos suele estarlo).

Poner la configuración en la aplicación

Antes de compilar la aplicación de Android, deja google-services.json donde lo busca el script de compilación. El script revisa el archivo (que sea de un proyecto con me.pabal.android registrado y que no le hayas dado por error la clave secreta) y lo incorpora al código fuente de la aplicación.

mkdir -p ~/.pabal/android
cp ~/Downloads/google-services.json ~/.pabal/android/     # para dejarlo en otro sitio: --google-services <ruta> en apply_branding.py
python3 scripts/android/point_to_server.py <código fuente de Android> \
    --host 203.0.113.10 --port 8443 --key keys/production.pem.pub
python3 scripts/android/apply_branding.py <código fuente de Android>
# al final debe salir "Firebase: project <ID del proyecto>". Si aparece pabal-placeholder, la aplicación queda sin notificaciones push

Después, la compilación sigue el apartado de Android (Android 앱) de docs/mobile-apps-guide.md del repositorio. Los teléfonos que usaban la aplicación de antes de las notificaciones push también tienen que actualizarse a la aplicación recién compilada para recibirlas.

Poner la clave secreta en el servidor

sudo cp firebase-service-account.json /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/firebase-service-account.json    # el usuario del contenedor del servidor
sudo chmod 600 /srv/pabal/pabal_server/keys/firebase-service-account.json

Añade una línea a .env. Dentro del contenedor, pabal_server/keys es /app/keys.

FCM_CREDENTIALS=/app/keys/firebase-service-account.json
docker compose up -d pabal-server
docker compose logs pabal-server | grep -i push
INFO  TelegramServer - Push notifications on: Firebase project <ID del proyecto>, 0 device registrations, message text included (encrypted per device)
  • Si ves esta línea, están activadas. Si no se puede leer el archivo o su formato es incorrecto, el motivo aparece en Push notifications are off: … (el contenido del archivo no sale en el log).
  • Con PUSH_PREVIEWS=false, la línea termina en message text left out y las notificaciones muestran solo el remitente, sin el contenido del mensaje.
  • El contenedor tiene que poder abrir conexiones salientes a oauth2.googleapis.com y fcm.googleapis.com (443).

Comprobación

  1. Instala (o actualiza) en el teléfono la aplicación recién compilada y ábrela una vez. En el log del servidor aparece Push device registered: user … type 2 token … (push key 256 bytes). El token solo queda como huella.
  2. Cierra la aplicación deslizándola en la lista de aplicaciones recientes. No uses «Forzar detención» de Ajustes: Android no entrega notificaciones push a la aplicación hasta que vuelvas a abrirla tú.
  3. Envía un mensaje desde otra cuenta: en el log aparece Push MESSAGE_TEXT to user …: sent y la notificación aparece en el teléfono.
  • Política de privacidad: en el panel de administración → Ajustes (설정) → Datos del operador (운영자 정보), añade Google LLC (Firebase Cloud Messaging, EE. UU.) en Encargados del tratamiento (처리 위탁) y en Transferencias internacionales (국외 이전). El texto de la política ya dice que las notificaciones pasan por FCM. Pon lo mismo en el formulario de «Seguridad de los datos» de Google Play.
  • Si la clave se filtra, bórrala en la consola de Firebase, descarga una nueva, sustituye el archivo y reinicia el servidor. No hace falta recompilar la aplicación.
iPhone y escritorio

El iPhone se activa aparte, no con Firebase sino con una clave de APNs (.p8) de la cuenta de desarrollador de Apple: justo debajo, en el paso 11. Las aplicaciones para macOS y Windows no usan push: reciben los mensajes mientras están abiertas.

11. Notificaciones push de iPhone (opcional)

El iPhone también detiene enseguida las aplicaciones que salen de la pantalla. Si no activas las notificaciones push, los mensajes y las llamadas llegan solo mientras la aplicación está abierta. Las notificaciones push se envían por el servicio de notificaciones push de Apple (APNs) y, igual que en Android, el contenido de la notificación se cifra con una clave que solo tiene ese iPhone. El texto de la notificación que Apple puede ver es solo el nombre de la aplicación y una frase sin contenido como «Tienes una notificación nueva»; la extensión de notificaciones (Notification Service Extension) que lleva la aplicación para iPhone descifra el texto cifrado y lo convierte en el remitente y el mensaje.

  • Registro: cada vez que se abre, la aplicación registra dos cosas por cada cuenta: una para notificaciones (type 1) y otra para llamadas (VoIP, type 9). Junto con el registro, el servidor guarda el idioma de la aplicación (en el que escribe el texto visible) y su número de compilación.
  • Los mensajes van como notificaciones. A los chats silenciados no se les envía nada. Lo que se lee o se borra en otro dispositivo se avisa con un push silencioso en segundo plano, y la aplicación retira las notificaciones que estaban a la vista.
  • Las llamadas enviadas por VoIP muestran la pantalla de llamada de CallKit incluso con la pantalla bloqueada. Pero una aplicación que recibe un push VoIP tiene que avisar a CallKit de la llamada de inmediato; si no lo hace, iOS cierra la aplicación y, si se repite, deja de entregarle pushes VoIP. Por eso VoIP solo se activa con APNS_VOIP_MIN_BUILD a partir de la compilación que ya lo hace así; antes, las llamadas también van como notificaciones.
La clave de APNs es del equipo que firmó la aplicación

A diferencia de Firebase, no hay ningún archivo de configuración que poner en la aplicación: la aplicación ya está compilada para poder usar APNs (permiso de notificaciones, App Group, extensión de notificaciones), y basta con dejar la clave en el servidor. Pero con la clave del servidor solo se pueden enviar notificaciones push a aplicaciones firmadas por el equipo que creó esa clave y cuyo ID de paquete coincide con APNS_TOPIC. Si firmaste la aplicación para iPhone de tu servidor con tu cuenta de desarrollador de Apple, crea la clave de APNs también en esa cuenta.

Crear la clave de APNs

  1. Apple Developer → Certificates, IDs & Profiles → Keys → +. Tiene que ser la cuenta del equipo que firmó la aplicación.
  2. Escribe un nombre y marca Apple Push Notifications service (APNs). Deja el entorno en Sandbox & Production y el alcance en todo el equipo (Team Scoped): se envía a dos destinos, notificaciones y VoIP.
  3. Register → Download. AuthKey_<ID de la clave>.p8 solo se puede descargar una vez. Este archivo es secreto: no pongas su contenido en el repositorio, en un chat ni en .env.

Poner la clave en el servidor

sudo cp AuthKey_<ID de la clave>.p8 /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/AuthKey_<ID de la clave>.p8    # el usuario del contenedor del servidor
sudo chmod 600 /srv/pabal/pabal_server/keys/AuthKey_<ID de la clave>.p8

Añade estas líneas a .env. Dentro del contenedor, pabal_server/keys es /app/keys.

APNS_KEY=/app/keys/AuthKey_<ID de la clave>.p8
APNS_TEAM_ID=4Z8R468M2D
APNS_TOPIC=me.pabal.ios
APNS_MIN_BUILD=4
APNS_VOIP_MIN_BUILD=
ValorQué ponerNotas
APNS_KEYRuta del archivo de la claveEl ID de la clave se lee del nombre del archivo. Si lo guardaste con otro nombre, pon el ID de la clave en APNS_KEY_ID
APNS_TEAM_IDID del equipo que firmó la aplicaciónPara las aplicaciones de Pabal, 4Z8R468M2D. Si compilaste y firmaste tú la aplicación, el de tu equipo
APNS_TOPICID de paquete (bundle ID) de la aplicaciónPara las aplicaciones de Pabal, me.pabal.ios. Las llamadas (VoIP) van a este mismo valor con .voip añadido
APNS_MIN_BUILD4A las compilaciones con un número inferior no se les envía nada (con 0, a todas). 4 es el valor que deja fuera Pabal 1.0 (3), compilada antes de probar las notificaciones push
APNS_VOIP_MIN_BUILDDéjalo vacíoSi está vacío, las llamadas también van como notificaciones. Si pones el número de la primera compilación que avisa a CallKit de la llamada de inmediato, a partir de esa compilación aparece la pantalla de llamada de CallKit
docker compose up -d pabal-server
docker compose logs pabal-server | grep -i push
INFO  TelegramServer - iPhone push notifications on: APNs key <ID de la clave> of team 4Z8R468M2D, topic me.pabal.ios, builds from 4, calls as alerts (TELEGRAM_APNS_VOIP_MIN_BUILD not set)
  • Si ves esta línea, están activadas. Si no se puede leer el archivo de la clave o su formato es incorrecto, el motivo aparece en iPhone push notifications are off: … (el contenido del archivo no sale en el log). Se activan y se desactivan independientemente de las de Android (FCM): puedes tener activadas solo las de uno de los dos sistemas.
  • El contenedor tiene que poder abrir conexiones salientes a api.push.apple.com (443, HTTP/2). Las compilaciones de depuración instaladas directamente en el dispositivo con Xcode van a api.sandbox.push.apple.com (la aplicación lo indica al registrarse).

Comprobación

  1. En un iPhone real, abre la aplicación una vez y permite las notificaciones. En el log del servidor aparecen Push device registered: user … type 1 token … (push key 256 bytes, build …) y una línea con type 9. No se puede comprobar con el simulador: no tiene token de APNs ni CallKit.
  2. Cierra la aplicación deslizándola hacia arriba.
  3. Envía un mensaje desde otra cuenta: en el log aparece Push MESSAGE_TEXT to user …: sent y en la pantalla bloqueada aparecen el remitente y el contenido.
  • Política de privacidad: en el panel de administración → Ajustes (설정) → Datos del operador (운영자 정보), añade Apple Inc. (servicio de notificaciones push de Apple, EE. UU.) en Encargados del tratamiento (처리 위탁) y en Transferencias internacionales (국외 이전). El texto de la política ya dice que las notificaciones pasan por APNs. Revisa también el apartado de identificadores del dispositivo (token de push) en el cuestionario de privacidad de la aplicación en App Store Connect.
  • Si la clave se filtra, revócala con Revoke en la cuenta de desarrollador, crea una nueva, sustituye el archivo y reinicia el servidor. No hace falta recompilar la aplicación.

12. Bots de voz (opcional)

Con los bots de voz, cuando alguien llama a un bot desde la aplicación, contesta un modelo de voz de IA (la explicación para propietarios de bots está en Bots de voz). Hay que añadir dos componentes al servidor, y además tienen que estar listos los documentos legales y las compilaciones de la aplicación para que se active. Por defecto está desactivado.

La voz se activa con un archivo compose adicional

Los componentes de voz no están en deploy/docker-compose.yml, sino en el archivo adicional deploy/docker-compose.voice.yml: los ajustes de voz del servidor y la red reservada al enlace, la pasarela pabal-voice (perfil voice) y el punto de entrada de audio del reloj pabal-voice-edge (perfil voice-watch). Si .env no nombra ese archivo, no hay ningún ajuste de voz. Esta configuración se ha probado con el motor de eco, pero la voz todavía no se ha activado en un servidor de producción ni se ha completado nunca una llamada a un bot de voz a través del relé de llamadas (TURN) de producción: la prueba de extremo a extremo se hizo con un relé de pruebas dentro de una red de Docker (scripts/voice-e2e/run.sh). Antes de activarla, deploy/voice_readiness.py del repositorio comprueba lo que falta. Las condiciones antes de la apertura al público (revisión legal, respuesta por escrito del proveedor, publicación de las aplicaciones) son decisión del operador, y el código solo impide una parte de ellas.

ComponenteQué haceSin él
pabal-voice (pasarela, imagen 0.11.0)Hace de extremo de la llamada en lugar del bot, realiza el intercambio de claves e intercambia el audio con el modelo de IA. Python 3.12 + ntgcalls. Habla con el servidor por un enlace (WebSocket, autenticado pero sin cifrar)Todas las llamadas a bots dan ocupado (no_gateway)
coturn (relé de llamadas)Las llamadas a bots usan solo el relé: tanto la aplicación como la pasarela desactivan la conexión directa y envían el audio solo por el relé. Es el mismo relé que el de las llamadas entre personas (TURN_SECRET)Todas las llamadas a bots dan ocupado (no_relay), y la pasarela también rechaza la conexión (4008)
Clave de Vertex AIClave JSON de una cuenta de servicio de Google Cloud. Solo la abre el proceso del motor de la pasarelaSin modelo, solo eco (VOICE_ENGINE=echo; el audio no sale de la máquina)
(Opcional) clave maestraCifra las claves propias de los propietarios de bots y los secretos del modo streamClave propia y modo stream desactivados
(Opcional) componentes MCPpabal-mcp · firmante · proxy de salida (abajo)Servidores MCP desactivados

Condiciones para que se active

Lo que comprueba el servidorSi falta
Archivo del secreto de enlace (TELEGRAM_VOICE_LINK_SECRET_FILE, 32 bytes)Voz desactivada: ni siquiera abre el puerto del enlace
Relé de llamadas (TELEGRAM_TURN_HOST·TELEGRAM_TURN_SECRET)Todas las llamadas a bots dan ocupado
La sección id="bot-calls" de la política de privacidad del sitio (en coreano y en inglés); la pantalla de confirmación de la aplicación enlaza a ese lugarNingún bot atiende llamadas (ERROR en el log). Hay un borrador en docs/voice-bots/p0/privacy-*.draft.html
La sección id="voice-bot-owners" de los términos del servicioNadie puede aceptar las condiciones para propietarios de bots → no se pueden usar la transcripción, la clave propia, el modo stream ni MCP
Las secciones id="bot-calls-stream" · id="bot-calls-mcp" de la políticaModo stream · MCP desactivados
La autorización de bots en el panel de administración (por defecto, solo los bots autorizados) y la apertura al público (por defecto, solo testers; para abrir hay que escribir una frase de confirmación)Un bot no autorizado no atiende llamadas, y antes de la apertura solo pueden llamar los testers
La compilación de la aplicación de quien llama: escritorio pabal-bc2 (y pabal-bc1 mientras la pantalla de confirmación solo exista en coreano e inglés) · Android 62151 · iPhone 5 o superior · Apple Watch 1 o superior (TELEGRAM_VOICE_MIN_BUILD_*)Esa persona no tiene botón de llamada, y si llama igualmente, da ocupado
(Opcional) Apple Watch: la API del reloj del servidor (TELEGRAM_WATCH_API_PORT) y la dirección de audio (TELEGRAM_WATCH_AUDIO_URL), el punto de entrada de audio pabal-voice-edge y una pasarela que declare direct (más abajo)No se puede llamar desde el reloj (las aplicaciones del teléfono y del ordenador siguen igual)
(Opcional) Guardar conversación: la sección id="bot-calls-keep-text" de la política y una pasarela que declare keep_text (VOICE_KEEP_TEXT, más abajo)Aunque se marque la casilla, la conversación no se guarda
(Opcional) Llamadas de bots: la sección id="bot-calls-outbound" de la política y la sección id="voice-bot-owners-outbound" de los términos, la carpeta de la clave de pruebas, las versiones mínimas para llamadas salientes, una pasarela que declare outbound, el interruptor general del panel de administración (más abajo)Ningún bot puede llamar a nadie

Solo en un servidor de pruebas (TELEGRAM_TEST_NUMBERS=true) se puede saltar la comprobación de las secciones legales con TELEGRAM_VOICE_NOTICE_CHECK=off y dejar que cualquiera pruebe con TELEGRAM_VOICE_BOTS (* para todos los bots) y TELEGRAM_VOICE_TESTERS=*. No los uses en producción: un servidor de producción ignora TELEGRAM_VOICE_BOTS y muestra un aviso.

Relé de llamadas (coturn)

  • En un servidor Linux, usa el pabal-turn del archivo compose (COMPOSE_PROFILES=turn). Define TURN_SECRET en .env (openssl rand -hex 32) y abre en el router y el cortafuegos TURN_PORT (3478, udp y tcp) y de TURN_MIN_PORT a TURN_MAX_PORT (de 49160 a 49200, udp). Si amplías el rango, cambia a la vez el router y .env.
  • En macOS con Docker Desktop, no ejecutes coturn en un contenedor. Todas las personas que llaman aparecen como 192.168.65.1, los permisos del relé dejan de coincidir y todo el audio se descarta aunque por fuera todo parezca funcionar. En un servidor Mac, instala coturn directamente (deploy/turnserver.conf.example, docs/turn-relay-guide.md).
  • La dirección de relé que se da a la pasarela es la dirección LAN del servidor: pon el mismo valor en TELEGRAM_VOICE_TURN_HOST del servidor y en VOICE_TURN_HOST de la pasarela. Con host.docker.internal no llegaba, y con 127.0.0.1 la asignación no terminaba. A las aplicaciones les sigue llegando la dirección pública (TURN_HOST).
  • Comprueba desde una red externa con deploy/check_relay.py --data que el audio pasa de verdad. A veces STUN responde y la asignación funciona, pero el audio no pasa.

Clave de Vertex AI

  1. En un proyecto de Google Cloud, activa la Agent Platform API (antes, Vertex AI API; aiplatform.googleapis.com). Tiene que tener una cuenta de facturación vinculada. No se usa la Gemini Developer API (claves de AI Studio).
  2. Crea una cuenta de servicio y dale un único rol, roles/aiplatform.user (para restringirlo más, un rol personalizado con el único permiso aiplatform.endpoints.predict). Crea la clave de producción aparte de la de desarrollo.
  3. Descarga la clave JSON y ponla fuera del repositorio y de .env, por ejemplo en /srv/pabal/pabal_voice/secrets/vertex-sa.json, legible solo por el usuario de la pasarela (Linux: chown 10001:10001, chmod 400). Si cualquiera puede leerla, la pasarela se niega a arrancar.
  4. El modelo en tiempo real solo existe en us-central1 (Iowa, Estados Unidos) (comprobado el 2026-09-26). La política de privacidad y la pantalla de confirmación tienen que decir que el audio de las llamadas va a Estados Unidos, y el servidor solo pasa llamadas a una pasarela cuya región (VOICE_VERTEX_REGION) coincida con la región que indica la pantalla de confirmación de la aplicación.
  5. Pendiente: alertas de presupuesto, una cuota de sesiones simultáneas (el doble de la capacidad de la pasarela) y la caché de datos de Vertex (si está activada, guarda en memoria las entradas y salidas hasta 24 horas). La pantalla de confirmación dice que «Google no conserva el audio ni el texto después de generar la respuesta», así que, mientras la caché siga activada, esa frase no es del todo cierta. Recomendado: antes de abrir al público, desactiva la caché de datos (sección 9 de docs/voice-bots/vertex-setup.md en el repositorio) y limita el rol de la clave al permiso de predicción (sección 10 del mismo documento).

El servidor y la pasarela leen el mismo secreto de enlace de un archivo. Como el enlace no va cifrado, ponlo solo en una red dedicada a la que solo estén conectados esos dos contenedores: no en ssemiya-net, compartida con otras pilas, ni en la LAN. No publiques su puerto.

(umask 077; openssl rand -hex 32 > /tmp/voice-link.secret)
sudo install -m 400 -o 1000 /tmp/voice-link.secret /srv/pabal/pabal_server/keys/voice-link.secret   # el del servidor (uid 1000)
sudo install -d -m 700 -o 10001 /srv/pabal/pabal_voice/secrets
sudo install -m 400 -o 10001 /tmp/voice-link.secret /srv/pabal/pabal_voice/secrets/voice-link.secret # el de la pasarela (uid 10001)
shred -u /tmp/voice-link.secret

Estos valores van en .env. Con ellos, el archivo adicional añade a pabal-server sus ajustes de voz (TELEGRAM_VOICE_*) y la red de enlace.

COMPOSE_FILE=docker-compose.yml:docker-compose.voice.yml
COMPOSE_PROFILES=voice                              # junto a otros perfiles (turn, …), separados por comas
VOICE_LINK_SECRET_FILE=/app/keys/voice-link.secret  # ruta dentro del contenedor; vacío: voz desactivada
VOICE_TURN_HOST=192.168.1.64                        # dirección LAN del servidor (donde escucha coturn)
VOICE_TESTERS=100001                                # id de quienes pueden llamar antes de la apertura (el panel de administración añade más)
  • El archivo adicional conecta el servidor a la red solo interna pabal-voice-link (10.231.53.0/29: servidor .2, pasarela .3), y el servidor solo escucha en su propia dirección de esa red (con 0.0.0.0 también se abriría en la red compartida a la que está conectado). Si ese rango coincide con tu LAN, tu VPN u otra red de Docker, cambia a la vez VOICE_LINK_SUBNET, VOICE_LINK_SERVER_IP y VOICE_LINK_GATEWAY_IP.
  • Para escuchar en una dirección que no sea loopback hace falta TELEGRAM_VOICE_LINK_ANY_ADDRESS=true, y el enlace nunca se asocia a una dirección pública. El log del servidor indica qué red ve el enlace.
  • Antes de autenticarse, una conexión tiene 3 segundos para el upgrade y 3 para el hello; como máximo 2 conexiones por dirección y 16 en total.
  • Todos los ajustes de servidor relacionados con la voz están en Administración y ajustes — Variables de entorno de los bots de voz.

Pasarela (pabal-voice)

cd /opt/pabal
docker build -t pabal-voice:latest voice/                                       # para la CPU de este equipo
docker buildx build --platform linux/amd64 -t pabal-voice:latest --load voice/  # para un servidor Intel
cd deploy && docker compose up -d                                         # según COMPOSE_FILE y COMPOSE_PROFILES de .env

La imagen usa un Python 3.12 fijado por hash, sin compilador ni pip, y se ejecuta con el uid 10001. ntgcalls solo existe para Linux x86_64/aarch64 y macOS arm64, así que en un Mac Intel solo se ejecuta con Docker. El servicio pabal-voice del archivo adicional usa esta imagen sin construirla (image: pabal-voice:latest). Para construirla en otro equipo para la CPU del servidor y enviarla, usa deploy/ship.sh <user@host> <carpeta> --voice (la imagen sustituida queda como pabal-voice:previous). Abajo está, abreviado, el servicio de pasarela de ese archivo.

  pabal-voice:                             # de deploy/docker-compose.voice.yml (abreviado)
    image: pabal-voice:latest
    profiles: [voice]
    init: true
    stop_grace_period: 40s                 # más largo que VOICE_DRAIN_SECONDS (20)
    security_opt: ["no-new-privileges:true"]
    cap_drop: [ALL]
    mem_limit: ${VOICE_MEMORY:-1g}
    ulimits:
      core: 0
    environment:
      VOICE_LINK_URL: ws://${VOICE_LINK_SERVER_IP:-10.231.53.2}:8095/voice/link
      VOICE_LINK_SECRET_FILE: /run/secrets/voice-link.secret
      VOICE_ENGINE: gemini
      VOICE_VERTEX_KEY_FILE: /run/secrets/vertex-sa.json
      VOICE_VERTEX_REGION: us-central1
      VOICE_TURN_HOST: ${VOICE_TURN_HOST:-}  # el mismo valor que TELEGRAM_VOICE_TURN_HOST del servidor
      VOICE_CAPACITY: ${VOICE_CAPACITY:-2}
      VOICE_DAILY_MODEL_MINUTES: ${VOICE_DAILY_MODEL_MINUTES:-60}
      VOICE_SAFETY_THRESHOLD: ${VOICE_SAFETY_THRESHOLD:-MEDIUM}   # llamadas de testers: supuesto que debe confirmar el operador (abajo)
      VOICE_BYOK: "false"
      VOICE_STREAM: "false"
    volumes:
      - ${PABAL_HOME:-/srv/pabal}/pabal_voice/secrets:/run/secrets:ro   # secreto de enlace y clave de Vertex; no entran en backup.sh
      - pabal_voice_state:/var/lib/pabal-voice
    networks:
      pabal-voice-link:                    # red de enlace compartida solo con el servidor (internal)
        ipv4_address: ${VOICE_LINK_GATEWAY_IP:-10.231.53.3}
      pabal-voice-out: {}                  # su propia red de salida (Google, relé)
  • Además del enlace, la pasarela necesita un camino hacia fuera: Google (us-central1-aiplatform.googleapis.com:443) y el relé de llamadas (UDP). Como la red de enlace es internal: true y no puede salir, se añade otra red, pabal-voice-out, como arriba.
  • Landlock: una pasarela que usa un modelo o el modo stream se niega a arrancar (código de salida 2) si el núcleo Linux no tiene Landlock (5.13 o superior; funciona con el seccomp predeterminado de Docker). El proceso de medios, que maneja con código nativo lo que envía quien llama, se bloquea a sí mismo para no poder leer los archivos de claves. Dentro del contenedor no montes los archivos secretos bajo /app, /opt, /usr, /lib ni /etc (/run/secrets sirve, como en el ejemplo de arriba). VOICE_ALLOW_NO_LANDLOCK=true es solo para los Mac de desarrollo. No se ha comprobado si Landlock funciona en macOS con Docker Desktop.
  • Memoria: por llamada, de 60 a 100 MB para los medios + de 30 a 40 MB para el motor. Con una capacidad de 8 (VOICE_CAPACITY) y un par de reserva, alrededor de 1,3 GB. Por defecto, el archivo adicional usa una capacidad de 2 y 1 GB de memoria (VOICE_MEMORY).
  • Filtro de seguridad: el valor predeterminado de VOICE_SAFETY_THRESHOLD en la pasarela es LOW (el más estricto), pero en las mediciones convirtió en rechazos en inglés 13 de 172 frases normales en coreano (MEDIUM: 0 de 69). El repositorio da por supuesto que en las llamadas de testers se indica MEDIUM de forma explícita; antes de cambiar el valor predeterminado hacen falta pruebas de red team.
  • Límites: por defecto, la pasarela permite por llamada 600 segundos, 400.000 tokens y 30 turnos por minuto, y por día (UTC) 300 minutos de modelo y 8 millones de tokens; el archivo adicional fija el día en 60 minutos y 1,6 millones de tokens (VOICE_DAILY_MODEL_*; por encima, las llamadas nuevas dan ocupado). El motor de OpenAI solo se ha probado con servidores falsos y está desactivado, y no hay cambio automático entre motores.
  • Idiomas: al conectarse al servidor, la pasarela indica en qué idiomas de bot puede atender llamadas (desde la 0.7.0, en los 9), y el servidor solo le pasa las llamadas de un bot a una pasarela que atienda llamadas en el idioma de ese bot: a una pasarela antigua que no indica sus idiomas (0.6.0) solo le llegan bots en coreano e inglés. Las grabaciones de los avisos están en assets/ dentro de la imagen, y assets/assets.json determina cuáles han superado la revisión. Sin ese archivo, la pasarela no puede incluir el aviso de IA y no atiende llamadas (con un ERROR en el log de arranque). Las grabaciones que no han superado la revisión (hoy, las de todos los idiomas salvo el coreano y el inglés) no se reproducen, y quien llama en esos idiomas oye los avisos en inglés. VOICE_DRAFT_NOTICES=true es un interruptor solo para servidores de pruebas que reproduce también los borradores, y sin VOICE_ALLOW_TEST_URLS=true la pasarela no arranca. VOICE_NOTICE_LANG (el idioma de los avisos cuando el servidor no indica el idioma de quien llama, ko por defecto) tiene que ser uno de los 10 idiomas de grabación (ko en ja zh fr es id vi km ar), y VOICE_LANGUAGE (el idioma del bot en las llamadas sin ajustes del propietario del bot, ko-KR por defecto), uno de los ID de la lista de idiomas del bot (Bots de voz — Voces e idiomas).
  • En los logs no quedan ni el secreto de enlace, ni las claves, ni el audio, ni el texto de las conversaciones, ni las instrucciones. Con SIGTERM, deja de aceptar llamadas nuevas y da VOICE_DRAIN_SECONDS (20 segundos por defecto) a las llamadas en curso antes de terminarlas.

Comprobar

docker compose logs pabal-server | grep -E "Voice|voice"
docker compose logs pabal-voice | head -20
INFO  TelegramServer - Voice bots on: gateways connect to ws://10.231.53.2:8095/voice/link; bots 0 listed, 2 calls each at most
INFO  VoiceGatewayLink - Voice gateway link listening on ws://10.231.53.2:8095/voice/link (red)
INFO  TelegramServer - Voice bot callers: patched apps only (desktop pabal-bc1+, Android 62151+, iOS 5+), recipients google.vertex.gemini-live@us-central1, testers 0 listed + the admin page's
INFO  VoiceGatewayLink - Voice gateway pabal-voice-1 (boot 823c9600d05e811847ce95a8f1ba9efa) connected: new session, capacity 2, features […], audio to google.vertex.gemini-live@us-central1
  • Voice bots are off: … aparece cuando el secreto de enlace falta, no se puede leer o no tiene 32 bytes; el final de línea — but without a TURN relay every bot call is busy, cuando no hay relé.
  • Si ves el ERROR Voice bots are on, but no bot is callable: the privacy policy … has no id="bot-calls" section, primero hay que publicar en la política del sitio la sección de llamadas a bots.
  • Cuando se conecta una pasarela, en features del log del servidor aparecen bot_settings·end_call·context·tools, además de byok si la pasarela usa las claves de los propietarios y transcripts si tiene el aviso de voz de transcripción (el orden varía). audio to tiene que coincidir con el destinatario que indica la pantalla de confirmación de la aplicación.
  • En la pasarela, la primera línea debe ser starting as pabal-voice-1 (capacity 2, … engine gemini, audio to google.vertex.gemini-live@us-central1), la línea del modelo debe decir session resumption off; no failover, y también debe aparecer media workers sandbox themselves with Landlock (ABI N). recordings for the callers' notices in: Korean, English indica los idiomas en los que se pueden reproducir los avisos para quien llama (en un servidor de pruebas que reproduce también los borradores, se añade (drafts included: …)).
  • Después, en la pestaña Voz (음성) del panel de administración (Administración y ajustes — Pestaña Voz), comprueba que la pasarela está conectada, añade testers y autoriza los bots.

Clave maestra (clave propia · modo stream)

Las claves de modelo de los propietarios de bots y los secretos del modo stream se guardan cifrados con esta clave. Tiene que estar en un sitio que no se copie con las copias de seguridad.

sudo install -d -m 700 -o 1000 -g 1000 /etc/pabal/voice                 # fuera de /srv/pabal, 700
sudo sh -c 'umask 077; openssl rand -hex 32 > /etc/pabal/voice/voice-master.key'
sudo chown 1000:1000 /etc/pabal/voice/voice-master.key
      TELEGRAM_VOICE_KEYS_MASTER_FILE: /app/secrets/voice-master.key
    volumes:
      - /etc/pabal/voice:/app/secrets:ro
  • Si está dentro o al lado de la carpeta de datos o de la de claves RSA, o si su carpeta está mínimamente abierta a otros usuarios (no es 700), el servidor no la usa (Voice keys off: …). Si el archivo no existe, el servidor lo crea al recibir la primera clave (con un montaje de solo lectura, créalo tú como arriba).
  • No la metas en las copias de seguridad, y guarda aparte una copia sin conexión. Si se pierde, todos los propietarios de bots tendrán que volver a configurar su clave. Para cambiarla, indica la clave antigua en TELEGRAM_VOICE_KEYS_PREVIOUS_MASTER_FILE, arranca, revisa los logs y borra el archivo antiguo.
  • Aunque haya clave maestra, las claves propias no se aceptan hasta que las aplicaciones muestren la pantalla de confirmación (versión 2) de los bots con clave propia y pongas TELEGRAM_VOICE_OWN_KEYS_APPS_READY=true. En el modo stream, solo se puede llamar cuando indicas en TELEGRAM_VOICE_STREAM_MIN_BUILD_DESKTOP·_ANDROID·_IOS las compilaciones de la aplicación con la pantalla de confirmación del modo stream (por defecto 0 = nadie puede llamar), y la pasarela necesita VOICE_STREAM=true y las IP públicas de este despliegue (VOICE_STREAM_DENY_ADDRESSES).

Componentes MCP (opcional)

Son los componentes que, durante las llamadas, llaman a los servidores MCP registrados por los propietarios de bots. Por defecto están desactivados, y deploy/docker-compose.mcp.yml (perfil mcp) añade tres contenedores.

ContenedorQué hace
pabal-mcp (uid 1101)El cliente MCP. Se conecta con el servidor solo mediante un archivo de socket, tiene una única red interna y solo sale al exterior por el proxy de salida. Solo lectura, sin privilegios, 512 MB
pabal-mcp-signer (uid 1102)El firmante. No tiene ninguna red, solo responde por socket y escribe el documento público del emisor
pabal-mcp-egress (uid 1103)El proxy de salida. Solo al puerto 443, y nunca a las direcciones del propio Pabal
docker compose -f docker-compose.yml -f docker-compose.mcp.yml --profile mcp up -d
  • Para activarlos hacen falta TELEGRAM_MCP_EGRESS_OWN_ADDRESSES (las direcciones públicas de Pabal, obligatorio) y los archivos de claves (secreto de enlace, K_grant, clave de instalación; todos en una carpeta 700 fuera de /srv/pabal), y el sitio tiene que tener la sección bot-calls-mcp de la política y la sección de propietarios de bots de los términos del servicio. La configuración que se hace una sola vez está, en orden, en el comentario del principio de docker-compose.mcp.yml. El almacén y las claves de firma no van a las copias de seguridad.
  • Cuando las aplicaciones ya muestren en la pantalla de confirmación el destinatario MCP del bot, pon TELEGRAM_MCP_APPS_READY=true. Hasta entonces se pueden registrar servidores, pero ninguna llamada lleva herramientas MCP.
  • Tener una dirección de salida dedicada para las peticiones MCP no es una condición del código, sino una decisión de operación (por esa salida van las palabras de quien llama a un servidor de terceros).
  • Cuando se activan, el log muestra MCP tools on: pabal-mcp dials …; issuer …; ports …; own addresses N; clock by NTP …. deploy/ship.sh <servidor> <carpeta> --mcp solo construye y envía las tres imágenes y copia el archivo override, sin activar nada (nunca se ha probado en Docker de Linux).

Punto de entrada del Apple Watch (opcional, en preparación)

La app del reloj inicia sesión y llama por la API del reloj del servidor (JSON sobre HTTPS, /api/watch/v1/) en lugar de MTProto, y el audio de las llamadas llega a la pasarela por el punto de entrada de audio (pabal-voice-edge, WSS). No se usa el relé de llamadas (coturn). Está desactivado por defecto.

El punto de entrada de audio del reloj todavía no es accesible desde fuera

deploy/docker-compose.voice.yml incluye el punto de entrada de audio pabal-voice-edge (perfil voice-watch, sin secretos, en una red solo interna), pero todavía no los ajustes de la API del reloj del servidor (TELEGRAM_WATCH_*) ni las rutas del reloj en el proxy inverso, así que un reloj desde fuera no llega. Lo que sigue describe los valores de configuración; nunca se ha probado en una configuración de producción (solo en el servidor de desarrollo).

AjustePredeterminadoSignificado
TELEGRAM_WATCH_API_PORT—Puerto de la API del reloj. Sin él, la API del reloj está desactivada
TELEGRAM_WATCH_API_HOST, TELEGRAM_WATCH_API_ANY_ADDRESS127.0.0.1, —Dirección de escucha. Si no es loopback, hace falta ANY_ADDRESS=true, y delante va un proxy TLS
TELEGRAM_WATCH_AUDIO_URL—Dirección del punto de entrada de audio que se comunica al reloj. La ruta tiene que ser exactamente wss://<host>/api/watch/v1/audio. Si falta o es incorrecta, las llamadas desde el reloj se desactivan
TELEGRAM_WATCH_ALLOW_PLAIN—Permite una dirección ws://. Solo para desarrollo (pruebas en casa)
TELEGRAM_WATCH_SESSION_DAYS7Días que dura el inicio de sesión del reloj (1–365)
TELEGRAM_WATCH_AUTH_PER_MINUTE30Límite de solicitudes de código de inicio de sesión para todo el servidor (por minuto). Además hay un límite por dirección
TELEGRAM_VOICE_MIN_BUILD_WATCHOS1Primera compilación de la app del reloj que muestra la pantalla de confirmación completa
  • Pasarela: VOICE_DIRECT=true y VOICE_EDGE_DIR (carpeta de sockets compartida con el punto de entrada, por defecto /run/pabal-voice-edge). Las llamadas desde el reloj solo funcionan con un motor de 16 kHz (Gemini) o con eco.
  • Punto de entrada de audio: ejecuta python -m pabal_voice.edge como segundo servicio de la misma imagen (VOICE_EDGE_HOST·VOICE_EDGE_PORT, por defecto 127.0.0.1·8096, VOICE_EDGE_DIR). El punto de entrada es el primero en decodificar lo que llega de Internet: no le montes ningún secreto y colócalo en una red interna a la que solo lleguen el proxy y la pasarela.
  • El proxy inverso solo reenvía las dos rutas del reloj: /api/watch/v1/audio (WebSocket, al punto de entrada) y /api/watch/v1/ (a la API del reloj). En el lado de la API del reloj, deja que añada la dirección del cliente al final de X-Forwarded-For (el servidor la usa para contar el límite de inicio de sesión por dirección). En el lado del punto de entrada, elimina X-Forwarded-For y X-Real-IP.
  • En el reloj no es posible registrarse, ni llamar a bots que usan herramientas, MCP, clave propia, modo stream o la transcripción del propietario del bot (WATCH_NOT_YET).

Guardar conversación (opcional, en preparación)

Si quien llama lo elige en la pantalla de confirmación, la conversación de la llamada se guarda como texto solo en su propio chat (Usar la aplicación: bots de voz). No hay interruptor de autorización del operador: se activa cuando se cumple todo lo siguiente.

  • La sección id="bot-calls-keep-text" en la política de privacidad (borrador: docs/voice-bots/p0/privacy-*.draft.html en el repositorio). Esta sección es el propio interruptor. La aplicación sabe si debe mostrar la casilla por pabal_voice_keep_text en help.getAppConfig.
  • VOICE_KEEP_TEXT de la pasarela 0.9.0: true por defecto con un motor de modelo. Con el motor de eco (incluida una pasarela solo para modo stream), true es un error de configuración y no arranca.
  • Una compilación de la aplicación con la casilla. El texto de la casilla solo entra en las compilaciones publicadas después de la revisión legal. El reloj no muestra el texto sin revisar; TELEGRAM_VOICE_KEEP_TEXT_UNREVIEWED=true es un interruptor solo para servidores de pruebas internos que muestra el texto sin revisar.
  • En un servidor con la función activada, el voice_call ended de todas las llamadas a bots que no son de modo stream llega hasta 10 segundos más tarde, y se reservan 30 números de mensaje después del registro de la llamada (para que el propietario del bot no pueda saber qué eligió quien llama).
  • Los textos de conversación que esperan a escribirse al terminar la llamada están solo en memoria: si entretanto se reinicia el servidor, el texto de esa llamada no se guarda.

Llamadas de bots (opcional, a la espera de la revisión legal)

Un bot de voz que llama primero a una persona que permitió que ese bot la llamara (Bots de voz — Llamadas de bots). Tiene más condiciones que las llamadas a bots, y no deberías activarlo antes de terminar la revisión legal. En este orden:

  1. El servidor: crea la carpeta de la clave de pruebas (TELEGRAM_VOICE_EVIDENCE_KEY_DIR: 700, fuera de las carpetas de las copias de seguridad como las de datos y claves) con new de scripts/voice-evidence-key.py (rotate para cambiar la clave, check para comprobarla) y pon la primera versión de la aplicación con la pantalla de permiso como versión mínima para llamadas salientes, por ejemplo TELEGRAM_VOICE_OUTBOUND_MIN_BUILD_ANDROID (variables de entorno). Si TELEGRAM_VOICE_EVIDENCE_KEY_DIR está solo en .env, no llega al servidor: añádela al pabal-server de deploy/docker-compose.voice.yml, en environment (la ruta dentro del contenedor) y volumes (la carpeta del host).
  2. Las aplicaciones: publica versiones de la aplicación con la pantalla de permiso. Sus reglas de enlaces de permiso solo conocen pabal.me, así que en otro servidor hay que añadir su host a las reglas de enlaces de las versiones de la aplicación para que un enlace enviado por un bot abra la pantalla (el menú y la lista de ajustes funcionan igual).
  3. Lo legal: publica la sección id="bot-calls-outbound" de la política de privacidad y la sección id="voice-bot-owners-outbound" de los términos del servicio (y la sección voice-bot-owners, para que los propietarios acepten las condiciones). Los borradores están en docs/voice-bots/p0/ del repositorio. Los textos de la pantalla de permiso y las grabaciones de los avisos de llamada saliente (notice-out·apology-rule) se revisan y aprueban también en este paso.
  4. El interruptor: en Llamadas de bots de la pestaña Voz del panel de administración, fija los países y los límites y actívalo con la frase de confirmación (Panel de administración: pestaña Voz). Cada bot pasa además por la revisión de llamadas salientes del operador.
  • La pasarela: solo una pasarela con VOICE_OUTBOUND=true hace las llamadas de los bots. Con un motor de modelo, solo arranca si las cuatro grabaciones de los avisos de llamada saliente están aprobadas; las grabaciones en borrador (VOICE_OUTBOUND_DRAFT_NOTICES) son solo para servidores de pruebas.
  • No pongas en el .env de producción nombres solo para pruebas y desarrollo como TELEGRAM_VOICE_CALLME_UNREVIEWED y TELEGRAM_VOICE_TEST_DIAL.
  • Las llamadas de bots tampoco tienen cifrado de extremo a extremo: el audio pasa por la pasarela de este servidor y por el proveedor de IA.

13. Imágenes de mapa en los mensajes de ubicación (opcional)

Cuando se envía una ubicación, la aplicación de Android y las de escritorio (macOS, Windows) no dibujan ellas mismas el mapa de la burbuja: le piden al servidor una imagen del mapa de ese punto (upload.getWebFile). El servidor la obtiene de la Maps Static API de Google y la entrega. Sin ella, Android muestra una imagen de relleno y el escritorio un recuadro vacío con un alfiler. El iPhone dibuja el mapa en el propio teléfono, así que este ajuste no le afecta, y enviar una ubicación y el mapa grande que se abre al tocarla funcionan en todos los casos.

  1. En la consola de Google Cloud, elige un proyecto con una cuenta de facturación y habilita la Maps Static API.
  2. Crea una clave de API; restringe la aplicación a direcciones IP (la IP pública con la que el servidor sale a internet) y la API a la Maps Static API sola. La clave se queda en el servidor y nunca va dentro de las aplicaciones.
  3. Ponla en .env y reinicia el servidor con docker compose up -d pabal-server.
  4. Compruébalo en el registro. Si los mapas están desactivados, Maps in location messages are off: … dice por qué (el valor de la clave nunca aparece en el registro).
STATIC_MAPS_KEY=AIza…               # no dejes la clave en chats, registros ni en el repositorio
STATIC_MAPS_PER_DAY=300             # imágenes nuevas que se obtienen al día (0 = sin límite)
STATIC_MAPS_PER_USER_HOUR=120       # imágenes nuevas que una persona puede provocar por hora
INFO  TelegramServer - Maps in location messages from Google Static Maps (120 new per person an hour, 300 a day)
  • Coste: la Maps Static API es gratuita hasta 10.000 peticiones al mes. El servidor guarda cada imagen un día y se la vuelve a dar a cualquier dispositivo que pida el mismo mapa; una imagen repetida no cuenta para los límites. El límite por defecto (300 al día) son unas 9.000 al mes, dentro del tramo gratuito. Una petición por encima de un límite se rechaza con FLOOD_WAIT, y mientras tanto la aplicación muestra la imagen de relleno.
  • Política de privacidad: las coordenadas (latitud y longitud) de la ubicación compartida van a Google, pero no quién la envió. En el panel de administración → Ajustes → información del operador, añade Google LLC (Maps Static API, Estados Unidos) a los encargados del tratamiento y a las transferencias internacionales.
  • Si el registro muestra Map picture not made: Google Static Maps answered 403: …, revisa las restricciones de la clave (IP, API) o la cuenta de facturación.

Operación

Estado y logs

cd /opt/pabal/deploy
docker compose ps
docker compose logs --since 1h pabal-server
tail -f /srv/pabal/pabal_server/logs/telegram-server.log

Actualizar

cd /opt/pabal && git pull
cd deploy
./backup.sh                                     # primero, copia de seguridad
docker compose up -d --build pabal-server             # sustituye solo el servidor por la imagen nueva
docker compose logs --since 5m pabal-server | grep -E "Website|ERROR"

Durante las decenas de segundos que tarda el servidor en volver a arrancar, la aplicación se desconecta y después se reconecta sola sin volver a iniciar sesión. El servidor aplica solo los cambios de tablas al arrancar. Los directorios montados no dependen de la imagen, así que docker compose down o borrar la imagen no borra los datos: simplemente, no borres /srv/pabal.

Volver atrás: volver a la imagen anterior deja en la base de datos lo que la versión nueva ya escribió, y puede que la versión antigua no sepa leerlo (por ejemplo, una versión con el código de los bots de voz escribe eventos nuevos, como los registros de llamadas en los chats, aunque la voz esté desactivada). Por eso, al volver atrás, restaura también la base de datos de la copia hecha justo antes de la actualización (Restaurar).

Copias de seguridad

./backup.sh            # → /srv/pabal/backups/<fecha-hora>/{pabal.dump, server-keys-data.tar.gz}
crontab -e             # todos los días a las 03:00:
# 0 3 * * * /opt/pabal/deploy/backup.sh >> /srv/pabal/backups/backup.log 2>&1
  • Las copias de más de 14 días se borran solas (se puede cambiar, por ejemplo con KEEP_DAYS=30 ./backup.sh).
  • Cópialas también a otro sitio. Una copia en el mismo disco no sobrevive a una avería del disco.
  • Las copias contienen la clave privada RSA y los secretos de SMS y SMTP. Protege el lugar donde las guardes tanto como el servidor.

Restaurar

cd /opt/pabal/deploy
B=/srv/pabal/backups/20260920-030000            # copia que se va a restaurar
docker compose stop pabal-server pabal-caddy
docker compose exec -T pabal-postgres dropdb -U pabal pabal
docker compose exec -T pabal-postgres createdb -U pabal pabal
docker compose exec -T pabal-postgres pg_restore -U pabal -d pabal --no-owner < $B/pabal.dump
docker compose exec -T pabal-postgres psql -U pabal -d pabal -c "ALTER DATABASE pabal SET search_path = public"   # la ruta por defecto de la base de datos: la restauración no la trae de vuelta
sudo tar -C /srv/pabal/pabal_server -xzf $B/server-keys-data.tar.gz
sudo chown -R 1000:1000 /srv/pabal/pabal_server
docker compose up -d

Para migrar a un servidor nuevo se hace lo mismo: sigue los pasos 1 a 5 y, en lugar del paso 6, haz la restauración de arriba y arranca. Si restauras la misma clave, no hace falta volver a compilar la aplicación (si cambió la IP del servidor, sí hay que recompilarla).

Arrancar y detener

docker compose restart pabal-server       # reinicia solo el servidor
docker compose stop                 # detiene todo (los datos se conservan)
docker compose up -d                # vuelve a arrancar

Aunque reinicies la máquina, los contenedores vuelven a arrancar solos porque tienen restart: unless-stopped.

Lista de comprobación de seguridad

  • .env con permisos 600 y POSTGRES_PASSWORD con un valor aleatorio
  • /srv/pabal/pabal_server/keys con permisos 700, y la clave privada no está en ningún otro sitio salvo las copias de seguridad
  • (Si has activado las notificaciones push) firebase-service-account.json con permisos 600, y su contenido no está en el repositorio ni en .env
  • (Si has activado las notificaciones push de iPhone) AuthKey_<ID de la clave>.p8 con permisos 600, y su contenido no está en el repositorio ni en .env
  • Cortafuegos: solo 22 (si es posible, solo desde la IP del operador), 80, 443 y 8443
  • Desde fuera, https://dominio/admin/ devuelve 404
  • Panel de administración: números de prueba (테스트 번호) desactivados, método de entrega del código (코드 전달 방식) en SMS o correo electrónico, envío de prueba (테스트 발송) correcto
  • TELEGRAM_WEBHOOK_ALLOW_LOCAL sin activar (para que los webhooks de los bots no puedan ir a la red interna)
  • (Si activaste los bots de voz) El secreto de enlace, la clave de Vertex y la clave maestra solo los puede leer el usuario que los usa (400/600), y la clave maestra está fuera de /srv/pabal, así que no entra en las copias de seguridad. El puerto del enlace no está publicado y la red del enlace solo la usan el servidor y la pasarela. El servidor de producción no tiene TELEGRAM_VOICE_NOTICE_CHECK ni TELEGRAM_VOICE_TESTERS=*
  • SSH: inicio de sesión con contraseña desactivado, solo con clave
  • Cron de copias de seguridad y copia externa comprobados; restauración ensayada al menos una vez
  • Actualizaciones de seguridad automáticas del sistema operativo del servidor (unattended-upgrades)

Solución de problemas

SíntomaCausa → solución
La aplicación no pasa de «Conectando…»① El 8443 está bloqueado → nc -vz IP 8443, revisa ufw y el grupo de seguridad de la nube ② La aplicación se compiló con otra clave pública → repite el paso 9 con la clave de este servidor ③ PUBLIC_IP es incorrecta → corrige .env y ejecuta docker compose up -d pabal-server
Al principio se conecta, pero se corta enseguidaLa aplicación pasa a la dirección que le indica el servidor (PUBLIC_IP:MTPROTO_PORT) y esa dirección es incorrecta → revisa PUBLIC_IP
Error de certificado HTTPSEl DNS todavía no apunta a este servidor o el puerto 80 está bloqueado → dig +short dominio, docker compose logs pabal-caddy
pabal-server se reinicia una y otra vez, password authentication failedSe cambió POSTGRES_PASSWORD después de crear los datos → vuelve al valor original o, dentro de la base de datos, ALTER USER pabal PASSWORD '…'
AccessDeniedException: /app/keys/…Propietario de los directorios montados → sudo chown -R 1000:1000 /srv/pabal/pabal_server
OutOfMemoryError, lentitudSube -Xmx en JAVA_OPTS y ejecuta docker compose up -d pabal-server
No llega el código de registroEstado de envío y motivo del fallo en la pestaña Códigos de registro (가입 코드) del panel de administración → valores de SMS y SMTP en Ajustes (설정), envío de prueba (테스트 발송)
No llegan notificaciones a Android① En el log aparece Push notifications are off → ruta de FCM_CREDENTIALS y permisos del archivo ② No aparece Push device registered → la aplicación se compiló sin la configuración de Firebase (pabal-placeholder) o aún no se ha abierto la aplicación nueva ③ Pone sent pero no aparece nada → permiso de notificaciones y ajustes de batería del teléfono, y si se usó «Forzar detención» con la aplicación ④ SENDER_ID_MISMATCH … Registrations are kept → la clave del servidor y el google-services.json de la aplicación son de proyectos distintos ⑤ answered 404 without naming the token → FCM_PROJECT_ID es incorrecto ⑥ refused an access token → la clave está revocada o es de otro proyecto (no se vuelve a pedir durante 5 minutos)
No llegan notificaciones al iPhone① En el log aparece iPhone push notifications are off → ruta de APNS_KEY, permisos y nombre del archivo (AuthKey_<ID de la clave>.p8) ② No hay registro type 1 → no se permitieron las notificaciones en la aplicación, o es el simulador ③ Hay registro, pero no sale nada → esa compilación es inferior a APNS_MIN_BUILD (el build de la línea de registro), o el chat está silenciado ④ APNs refused the provider token → la clave está revocada o APNS_TEAM_ID no es el equipo de la clave (no se vuelve a enviar durante 5 minutos) ⑤ DeviceTokenNotForTopic … Registrations are kept → APNS_TOPIC no coincide con el ID de paquete de la aplicación ⑥ Pone sent, pero solo se ve «Tienes una notificación nueva» → la extensión de notificaciones no pudo descifrarla (si reinstalaste la aplicación, ábrela una vez para que vuelva a registrarse) ⑦ Las llamadas solo llegan como notificaciones → APNS_VOIP_MIN_BUILD está vacío o es superior a esa compilación (es el comportamiento previsto)
No puedo entrar al panel de administraciónComprueba que el túnel SSH está abierto y si otro programa usa el 8080 de tu ordenador (cámbialo por -L 18080:127.0.0.1:8080 y usa localhost:18080)
network ssemiya-net declared as external, but could not be foundLa red todavía no existe → docker network create ssemiya-net (u otro nombre con PABAL_NETWORK en .env)

Limitaciones conocidas

  • Es una configuración de un solo servidor. Al arrancar, carga en memoria todas las cuentas, conversaciones y mensajes. Todavía no admite escalado horizontal repartido entre varias máquinas, y a medida que crecen los datos hay que ampliar la memoria.
  • Los códigos de registro pendientes, las actualizaciones que los bots no han recogido y los textos de «Guardar conversación» que esperan a escribirse están en memoria, así que se pierden al reiniciar el servidor.
  • Las peticiones de códigos de registro tienen un intervalo mínimo y un límite diario, pero todavía no hay límite de frecuencia para el resto de las peticiones. Justo después de abrir el servicio al público, revisa a menudo los logs y la pestaña Resumen (대시보드) del panel de administración.
  • Las notificaciones push solo llegan a las aplicaciones de teléfono: Android (paso 10) e iPhone (paso 11). En iPhone solo llegan a compilaciones iguales o superiores a APNS_MIN_BUILD (Pabal 1.0 (3) queda fuera), y la pantalla de llamada de CallKit solo se activa después de fijar APNS_VOIP_MIN_BUILD. Las aplicaciones para macOS y Windows reciben mientras están abiertas.
  • El «silenciar notificaciones» de cada chat solo está en la memoria del servidor. Las notificaciones push de los chats silenciados salen con prioridad no urgente en Android (el propio teléfono las filtra) y no salen en iPhone, pero, tras reiniciar el servidor, salen como siempre en los dos (la aplicación solo vuelve a avisar al servidor cuando cambias el ajuste). Si una aplicación cerrada tarda más de 20 segundos en despertar y conectarse al servidor, la llamada puede acabar como no contestada para quien llama.
  • Los mensajes admiten fotos y archivos como adjuntos; los mensajes de vídeo y de voz que se reproducen dentro de la aplicación todavía no. Canales, supergrupos, llamadas de grupo y verificación en dos pasos tampoco están disponibles aún.
  • Las claves de autorización están en texto plano en la base de datos (el servidor las necesita para descifrar). Protege la base de datos y las copias de seguridad tanto como la clave RSA.

Archivos

ArchivoFunción
deploy/docker-compose.ymlConfiguración de producción (pabal-server · pabal-postgres · pabal-caddy, bind mounts)
deploy/CaddyfileHTTPS, bloqueo de las rutas de administración, reenvío del sitio, la documentación y la Bot API
deploy/.env.examplePlantilla de configuración → deploy/.env
deploy/backup.shVolcado de la base de datos + archivo de claves y datos, limpieza de copias antiguas
DockerfileImagen del servidor (compilación → JRE 21, uid 1000)
voice/Pasarela de voz pabal-voice (código fuente, Dockerfile, README.md, especificación del enlace PROTOCOL.md)
deploy/docker-compose.mcp.ymlLos tres contenedores MCP de los bots de voz (perfil mcp)
deploy/docker-compose.voice.ymlAñade los componentes de los bots de voz: los ajustes de voz del servidor y la red de enlace, la pasarela pabal-voice (perfil voice) y el punto de entrada de audio del reloj pabal-voice-edge (perfil voice-watch)
deploy/ship.shConstruye en otro equipo la imagen del servidor (con --voice, también la de la pasarela) para la CPU del servidor, la carga por SSH y copia los archivos de despliegue
deploy/voice_readiness.pyComprobaciones antes de activar la voz (clave de Vertex, secciones legales, compilaciones de las aplicaciones, relé, …); no muestra ningún secreto
deploy/turnserver.conf.example, deploy/check_relay.pyPlantilla de configuración de coturn instalado directamente en un servidor Mac y herramienta para comprobar el relé