Docs développeurs
Français

Exploitation du serveur

Installation du serveur

Comment installer et exploiter un serveur Pabal avec Docker Compose sur une seule machine accessible depuis Internet.

Voici comment installer le serveur Pabal sur une seule machine accessible depuis Internet. Avec Docker Compose, vous lancez trois conteneurs — serveur Pabal · PostgreSQL · Caddy (HTTPS) — et toutes les données à ne pas perdre sont placées dans des répertoires de l'hôte montés en bind mount. Suivez les étapes dans l'ordre, de haut en bas.

Valeurs d'exemple de cette page

Domaine pabal.me, IP publique du serveur 203.0.113.10 (adresse réservée aux exemples — lisez à sa place l'IP réelle de votre serveur), sources dans /opt/pabal, données dans /srv/pabal, système Ubuntu 24.04 LTS. Les fichiers nécessaires se trouvent dans le dossier deploy/ du dépôt.

Vue d'ensemble

Internet docker compose · ssemiya-net /srv/pabal (bind mount) Application PabalPabal.app Navigateur · botsSite · Bot API OpérateurSSH pabal-server MTProto :8443 Site · bots · admin :8080 uid 1000 · JRE 21 Webhooks → bots pabal-caddy:80 · :443 TLS auto pabal-postgres 16Interne seul · sans port pabal_server/keys · clé RSA pabal_server/data · photos pabal_server/logs postgres_data/ · conversations caddy/ · certificats backups/ · backup.sh MTProto :8443 (déjà chiffré) HTTPS :443 Tunnel SSH → 127.0.0.1:8080/admin/
Un serveur, trois conteneurs, et tout ce qui doit durer dans /srv/pabal

Explication du diagramme

  • Trois zones : les utilisateurs sur Internet → les trois conteneurs du serveur de production → les répertoires montés sur le disque de l'hôte. Les conteneurs peuvent être supprimés et recréés à tout moment : tout ce qui doit durer se trouve dans /srv/pabal.
  • Il n'y a que deux entrées : l'application se connecte directement au serveur par le port MTProto (déjà chiffré, donc pas besoin de HTTPS), tandis que le site web, la documentation et la Bot API passent par le port 443 de Caddy. Caddy bloque les chemins d'administration (/admin, /health …) et ne transmet que le reste.
  • Les pointillés rouges sont le chemin réservé à l'opérateur. La page d'administration n'est ouverte que sur 127.0.0.1:8080 du serveur : on n'y accède donc que par un tunnel SSH. PostgreSQL n'a aucun port ouvert vers l'extérieur.
  • Les deux cases rouges sont les données les plus importantes. La clé RSA de pabal_server/keys est intégrée à l'application lors de sa compilation : si vous la perdez, il faut redéployer toutes les applications. postgres_data/ contient tous les comptes et toutes les conversations. Ce sont les priorités absolues de vos sauvegardes.
  • Les flèches grises représentent l'écriture : le serveur écrit clés, photos, paramètres et journaux, PostgreSQL les événements, Caddy les certificats, chacun dans son propre répertoire.

Prérequis

ÉlémentDétails
ServeurUbuntu 24.04 LTS, 2 vCPU · 4 Go de RAM · 40 Go de SSD pour commencer. Quand le nombre d'utilisateurs et de messages augmente, ajoutez d'abord de la mémoire
IPv4 publiqueIP fixe. L'application est compilée avec l'adresse IP du serveur, et le serveur communique lui aussi son IP à l'application
DomaineUn domaine dont vous pouvez modifier le DNS (enregistrement A)
Moyen d'envoi des codes d'inscriptionUn compte SMS (Twilio · Solapi · webhook) ou e-mail (SMTP)
MacUn Mac pour compiler les applications pour ce serveur (plus un PC Windows si vous voulez aussi produire l'application Windows)
Projet Firebase (facultatif)Pour envoyer des notifications push à l'application Android. Se crée gratuitement avec un compte Google (étape 10)

Ports

PortQuiOuvertureDescription
22/tcpOpérateurIP de l'opérateur uniquement (conseillé)SSH
80/tcpCaddyTout le mondeÉmission des certificats · redirection vers HTTPS
443/tcp, 443/udpCaddyTout le mondeSite web · documentation · Bot API (udp pour HTTP/3)
8443/tcpServeurTout le mondeConnexion de l'application (MTProto)
8080/tcpServeurNe pas ouvrirPage d'administration · contrôle de santé — uniquement sur 127.0.0.1 du serveur
5432/tcpPostgreSQLNe pas ouvrirUniquement sur le réseau interne des conteneurs
Pourquoi le port de l'application n'est-il pas 443 ?

Le port 443 est utilisé par le site web (HTTPS). MTProto n'étant ni du HTTP ni du TLS, les deux ne peuvent pas cohabiter sur un même port. Si vous voulez que l'application passe elle aussi par le 443 à cause des réseaux d'entreprise ou d'établissements scolaires qui bloquent les ports inhabituels, obtenez une deuxième adresse IP et attribuez le port 443 de cette IP au serveur (dans .env, PUBLIC_IP = la deuxième IP et MTPROTO_PORT=443 ; dans le fichier compose, séparez les ports en précisant l'IP). Le serveur indique à l'application « connecte-toi à ce port de cette IP » : gardez donc le même port applicatif à l'intérieur et à l'extérieur du conteneur.

1. Préparer le serveur

sudo apt update && sudo apt -y upgrade
timedatectl                      # vérifier "System clock synchronized: yes"

La synchronisation de l'heure est indispensable. Les numéros de message MTProto sont dérivés de l'heure : si l'horloge du serveur dérive, l'application se reconnecte en boucle. Si la valeur est no : sudo timedatectl set-ntp true.

# Pare-feu
sudo ufw allow OpenSSH           # si possible : sudo ufw allow from <IP de l'opérateur> 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 d'installation officiel)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER     # après reconnexion, docker fonctionne sans sudo
docker compose version            # v2 ou plus récent
Docker et ufw

Les ports que Docker ouvre (ports:) le sont indépendamment des règles ufw. C'est pourquoi le fichier compose n'ouvre que les ports publics (80, 443, 8443), n'ouvre le port d'administration que sur 127.0.0.1:8080 et n'ouvre pas PostgreSQL. Gardez-le en tête si vous modifiez ports:.

2. Récupérer les sources

sudo mkdir -p /opt/pabal && sudo chown $USER: /opt/pabal
git clone <adresse du dépôt> /opt/pabal

Si vous copiez depuis un ordinateur de développement, n'envoyez ni les clés, ni les données, ni les journaux (rsync --exclude 'keys/' --exclude 'data/' --exclude 'logs/' --exclude '**/target/'). La clé RSA du serveur de production est générée par celui-ci lors de son premier démarrage : utiliser en production une clé de développement ferait de cet ordinateur de développement un endroit capable de déchiffrer le trafic de production.

3. Répertoires de données (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        # le conteneur serveur tourne avec l'uid 1000
sudo chown -R $USER: /srv/pabal/backups
sudo chmod 700 /srv/pabal/pabal_server/keys /srv/pabal/backups
RépertoireDans le conteneurContenuEn cas de perte
pabal_server/keys/app/keysprivate.pem (clé privée RSA du serveur), private.pem.pub, (facultatif) firebase-service-account.json (clé secrète Firebase pour les notifications push Android)Recompiler et redéployer toutes les applications (pour la clé Firebase, il suffit d'en télécharger une nouvelle dans la console)
pabal_server/data/app/dataPhotos (media/), admin-token, operations.json (réglages des codes d'inscription, SMS et SMTP, secrets compris)Photos et réglages d'exploitation
pabal_server/logs/app/logsJournaux du serveur (compressés chaque jour)Seulement l'historique
postgres_data/var/lib/postgresql/dataComptes, conversations, messages, connexions, bots, configuration des webhooks : toutTout le service
caddy/data, caddy/config/data, /configCertificats HTTPSÉmis de nouveau automatiquement
backups(hôte uniquement)Résultats de backup.sh—

4. Fichier de configuration .env

cd /opt/pabal/deploy
cp .env.example .env
chmod 600 .env
openssl rand -base64 30 | tr -d '/+=' | cut -c1-32     # valeur à mettre dans POSTGRES_PASSWORD
nano .env
DOMAIN=pabal.me
PUBLIC_IP=203.0.113.10
POSTGRES_PASSWORD=(valeur générée ci-dessus)
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
ValeurÀ indiquerRemarques
DOMAINDomaine du site webLes liens créés par l'application (pabal.me/nom_utilisateur), les adresses de la documentation et les aperçus de liens utilisent aussi cette adresse
PUBLIC_IPIPv4 publique du serveurSur un cloud, l'IP publique affichée dans la console (et non l'IP privée vue depuis le serveur)
POSTGRES_PASSWORDValeur aléatoireDéfinissez-la avant le premier démarrage. PostgreSQL n'utilise cette valeur qu'à la création initiale du répertoire de données
PABAL_HOME/srv/pabalLe répertoire de l'étape 3
PABAL_SOURCE..Le dépôt à partir duquel l'image du serveur est construite : .. si le fichier compose reste dans deploy/ du dépôt, sinon le chemin des sources
PABAL_NETWORKssemiya-netLe réseau Docker que rejoignent les trois conteneurs. À créer au préalable (étape 6)
MTPROTO_PORT8443Si vous le changez, changez aussi le pare-feu et la compilation de l'application
ADMIN_TOKENLaisser videVide, il est créé au premier démarrage dans pabal_server/data/admin-token. Pour le choisir vous-même : 16 caractères minimum
JAVA_OPTSValeur par défautSi la mémoire manque, augmentez -Xmx (jusqu'à environ la moitié de la mémoire du serveur)
FCM_CREDENTIALSLaisser videNotifications push Android. Pour les activer : /app/keys/firebase-service-account.json (étape 10). Vide : le serveur fonctionne sans notifications push
FCM_PROJECT_IDLaisser videSeulement pour envoyer vers un projet Firebase autre que le project_id du fichier de clé
PUSH_PREVIEWStrueÀ false, les notifications n'affichent que l'expéditeur, sans le contenu du message

Le fichier compose de production démarre le serveur avec les numéros de test désactivés (TELEGRAM_TEST_NUMBERS=false). La liste complète des valeurs lues par le serveur se trouve dans Administration et paramètres — variables d'environnement.

5. DNS

NomTypeValeur
pabal.meA203.0.113.10
www.pabal.meA203.0.113.10
dig +short pabal.me        # doit afficher 203.0.113.10 (propagation : de quelques minutes à quelques heures)

Vous pouvez démarrer avant que le DNS ne pointe vers le serveur : Caddy réessaie de lui-même jusqu'à obtenir le certificat.

6. Compiler et démarrer

cd /opt/pabal/deploy
docker network inspect ssemiya-net >/dev/null 2>&1 || docker network create ssemiya-net   # une seule fois
docker compose up -d --build       # quelques minutes la première fois (Maven télécharge les bibliothèques)
docker compose ps                  # pabal-server, pabal-postgres et pabal-caddy en running (healthy)
docker compose logs -f pabal-server      # Ctrl+C pour quitter

Au premier démarrage, des lignes comme celles-ci apparaissent dans les journaux.

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 première ligne est un WARN, mais c'est normal (un simple avertissement : la clé n'est créée qu'une seule fois). Les fois suivantes, vous verrez Loaded RSA key … (fingerprint …). L'empreinte diffère d'un serveur à l'autre.
  • Le serveur crée lui-même les tables au démarrage.

7. Vérifier

# Depuis l'extérieur (votre ordinateur)
curl -sI https://pabal.me/ | head -1                              # HTTP/2 200
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/docs/    # 200 — cette documentation
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/admin/   # 404 — la page d'administration n'est pas publique
curl -s https://pabal.me/docs/server-key.pem | head -1             # -----BEGIN RSA PUBLIC KEY-----
nc -vz 203.0.113.10 8443                                          # succeeded — port de l'application

# Sur le serveur
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. Page d'administration et premiers réglages

# Sur votre ordinateur (laissez la commande tourner)
ssh -N -L 8080:127.0.0.1:8080 <utilisateur>@203.0.113.10

# Le jeton, sur le serveur
sudo cat /srv/pabal/pabal_server/data/admin-token

Ouvrez http://localhost:8080/admin/ dans un navigateur et saisissez le jeton. Réglages à faire avant la mise en service (détails dans Administration et paramètres) :

  1. Réglez le mode de remise des codes (코드 전달 방식) sur SMS ou e-mail. Avec le mode « page d'administration » (관리 화면), l'opérateur doit communiquer chaque code lui-même.
  2. Saisissez les valeurs SMS ou e-mail (SMTP), enregistrez, puis vérifiez avec Envoi de test (테스트 발송) que les codes arrivent vraiment.
  3. Vérifiez que les numéros de test (테스트 번호) sont désactivés. S'ils sont activés, n'importe qui peut se connecter avec un numéro +99966….
  4. Si besoin, démarrez avec Autoriser les nouvelles inscriptions (새 가입 허용) désactivé, pour n'accueillir d'abord que les personnes invitées.

9. Connecter l'application à ce serveur

L'application est compilée avec l'IP, le port et la clé publique du serveur intégrés. Il faut la recompiler avec la clé publique de ce serveur pour qu'elle puisse s'y connecter. Sur le Mac :

# 1. La clé publique de ce serveur (la clé publique, pas la clé privée — vous pouvez aussi la prendre sur le site de documentation)
curl -s -o keys/production.pem.pub https://pabal.me/docs/server-key.pem

# 2. Faire pointer les sources de l'application vers ce serveur + appliquer la marque
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

Suivez ensuite les étapes 5 (configure) et 6 (compilation) de docs/tdesktop-build-guide.md dans le dépôt. Copiez le résultat, out/Debug/Pabal.app, sous un autre nom : si vous recompilez les mêmes sources pour un autre serveur, il sera écrasé.

Avant de distribuer l'application

La compilation actuelle est une version de débogage, sans signature ni notarisation : à la première ouverture, les destinataires doivent faire clic droit → Ouvrir. Une version de débogage stocke ses données dans un dossier à côté de l'application (tdata/) et, si elle ne peut pas y écrire, utilise le même dossier de données que le vrai Telegram Desktop. Demandez aux utilisateurs de placer l'application dans un dossier personnel (par exemple ~/Applications/Pabal/). Une version de publication destinée à une diffusion générale (dossier de données séparé, signature et notarisation Apple) est un chantier à part.

Les applications Android, iPhone et Windows suivent la même méthode (un script qui intègre l'adresse et la clé publique du serveur → compilation), décrite dans docs/mobile-apps-guide.md dans le dépôt. Pour intégrer les notifications push à l'application Android, la configuration d'application Firebase de l'étape 10 doit être prête avant la compilation.

10. Notifications push Android (facultatif)

Android met rapidement en veille une application qui n'est plus à l'écran. Sans notifications push, les messages s'affichent et les appels sonnent uniquement tant que l'application est ouverte. Les notifications push passent par Firebase Cloud Messaging (FCM) de Google (gratuit), et leur contenu (nom de l'expéditeur, aperçu du message) est chiffré par le serveur avec une clé que seul ce téléphone possède : Google ne peut donc pas le lire.

Projet Firebase (de l'opérateur) Compilation · installation Téléphone · Google google-services.json Configuration de l'app Dans l'APK · pas un secret Clé de compte de service firebase-service-account.json Secret · serveur uniquement Compiler l'app Android apply_branding.py → APK pabal-server keys/ · chmod 600 FCM_CREDENTIALS Chiffre avec la clé du téléphone Pabal sur le téléphone Enregistre ce téléphone à l'ouverture Google FCM Relaie du chiffré, sans le lire ① Enregistrement ② Push chiffré ③ Réveil → notification
Deux fichiers pour deux destinations, et des notifications qui circulent chiffrées

Explication du diagramme

  • Trois zones : le projet Firebase que crée l'opérateur → l'endroit où l'on compile l'application et fait tourner le serveur → le téléphone et Google. Un seul projet Firebase fournit les deux fichiers.
  • Les flèches grises sont une préparation à faire une seule fois. Le cadre vert, google-services.json, est intégré à l'application et embarqué dans chaque APK : ce n'est donc pas un secret. Le cadre rouge, la clé de compte de service, est un secret qui ne se trouve que dans keys/ sur le serveur. Les deux fichiers doivent provenir du même projet.
  • Les flèches bleues sont le chemin d'une notification : ① à la première ouverture de l'application, le téléphone enregistre auprès du serveur son jeton FCM et une clé que lui seul possède ; ② quand arrive un nouveau message ou un appel, le serveur chiffre la notification avec cette clé et la remet à FCM ; ③ FCM réveille le téléphone, l'application déchiffre et affiche la notification, puis se reconnecte au serveur pour récupérer ce qu'elle a manqué.
  • Ce que voit Google se limite aux informations d'acheminement : jeton de l'appareil, heure d'envoi, priorité. Le nom de l'expéditeur et le message sont chiffrés : Google ne peut pas les lire.
  • Aucun push n'est envoyé à un téléphone qui affiche l'application — il reçoit déjà tout par sa connexion. Les push des conversations dont les notifications sont désactivées partent avec une priorité non urgente, et l'application les filtre elle-même.
Le projet Firebase et l'application vont de pair

La clé du serveur ne permet d'envoyer des push qu'aux applications compilées avec le même projet Firebase. Une application compilée par quelqu'un d'autre (par exemple celle téléchargée sur Pabal.me) est liée au projet Firebase de cette personne, et a de toute façon été compilée avec l'adresse et la clé publique de son serveur. Compilez vous-même les applications de votre serveur, et créez vous-même le projet Firebase.

Créer un projet Firebase

  1. Dans la console Firebase, choisissez Ajouter un projet. Le nom n'a pas d'importance, et Google Analytics n'est pas nécessaire.
  2. Paramètres du projet → Général → Vos applications → Ajouter une application → Android. Comme nom de package, saisissez me.pabal.android (le nom que le script de compilation donne à l'application). L'empreinte du certificat (SHA-1) n'est pas nécessaire pour les push.
  3. Téléchargez le fichier google-services.json de cette application. C'est une configuration intégrée à l'application, pas un secret.
  4. Dans Paramètres du projet → Comptes de service → Générer une nouvelle clé privée, téléchargez le fichier JSON et renommez-le firebase-service-account.json. Ce fichier est secret — n'en mettez le contenu ni dans un dépôt, ni dans une discussion, ni dans .env.
  5. Dans Paramètres du projet → Cloud Messaging, vérifiez que Firebase Cloud Messaging API (V1) est activée (c'est généralement le cas pour un nouveau projet).

Intégrer la configuration à l'application

Avant de compiler l'application Android, placez google-services.json là où le script de compilation le cherche. Le script vérifie le fichier (que me.pabal.android est bien enregistré dans le projet, et que vous ne lui avez pas donné la clé secrète par erreur), puis l'intègre aux sources de l'application.

mkdir -p ~/.pabal/android
cp ~/Downloads/google-services.json ~/.pabal/android/     # pour le placer ailleurs, passez --google-services <chemin> à apply_branding.py
python3 scripts/android/point_to_server.py <sources Android> \
    --host 203.0.113.10 --port 8443 --key keys/production.pem.pub
python3 scripts/android/apply_branding.py <sources Android>
# À la fin doit s'afficher "Firebase: project <ID du projet>". Si vous voyez pabal-placeholder, l'application n'aura pas de push

Compilez ensuite en suivant la section consacrée à Android de docs/mobile-apps-guide.md dans le dépôt. Les téléphones qui utilisaient l'application d'avant l'ajout des push doivent eux aussi être mis à jour avec l'application nouvellement compilée pour recevoir des notifications.

Placer la clé secrète sur le serveur

sudo cp firebase-service-account.json /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/firebase-service-account.json    # l'utilisateur du conteneur serveur
sudo chmod 600 /srv/pabal/pabal_server/keys/firebase-service-account.json

Ajoutez une ligne à .env. Dans le conteneur, pabal_server/keys correspond à /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 du projet>, 0 device registrations, message text included (encrypted per device)
  • Si cette ligne apparaît, les push sont activés. Si le fichier est illisible ou mal formé, Push notifications are off: … en donne la raison (le contenu du fichier n'apparaît jamais dans les journaux).
  • Avec PUSH_PREVIEWS=false, la ligne se termine par message text left out, et les notifications n'affichent que l'expéditeur, sans le contenu du message.
  • Le conteneur doit pouvoir ouvrir des connexions sortantes vers oauth2.googleapis.com et fcm.googleapis.com (443).

Vérification

  1. Installez (ou mettez à jour) l'application nouvellement compilée sur un téléphone et ouvrez-la une fois. Le journal du serveur affiche Push device registered: user … type 2 token … (push key 256 bytes). Le jeton n'y figure que sous forme d'empreinte.
  2. Balayez l'application dans la liste des applications récentes pour la fermer. N'utilisez pas « Forcer l'arrêt » dans les paramètres — Android ne lui transmet plus de push tant que vous ne l'avez pas rouverte vous-même.
  3. Envoyez-lui un message depuis un autre compte : le journal affiche Push MESSAGE_TEXT to user …: sent et une notification apparaît sur le téléphone.
  • Politique de confidentialité : dans la page d'administration (관리 화면) → Paramètres (설정) → Informations sur l'opérateur (운영자 정보), indiquez Google LLC (Firebase Cloud Messaging, États-Unis) sous Sous-traitance (처리 위탁) et Transferts à l'étranger (국외 이전). Le texte de la politique mentionne déjà le passage par FCM. Reportez la même information dans le formulaire « Sécurité des données » de Google Play.
  • Si la clé a fuité, supprimez-la dans la console Firebase, téléchargez-en une nouvelle, remplacez le fichier et redémarrez le serveur. Inutile de recompiler l'application.
iPhone et ordinateur

Sur iPhone, les notifications push s'activent séparément, non pas avec Firebase mais avec une clé APNs (.p8) issue d'un compte développeur Apple — voir l'étape 11 juste en dessous. Les applications macOS et Windows n'ont pas de notifications push : elles reçoivent les messages tant qu'elles sont lancées.

11. Notifications push iPhone (facultatif)

L'iPhone, lui aussi, suspend rapidement une application qui n'est plus à l'écran. Sans notifications push, les messages et les appels n'arrivent que tant que l'application est ouverte. Les notifications push passent par le service de notifications push d'Apple (APNs) et, comme sur Android, leur contenu est chiffré avec une clé que seul cet iPhone possède. Le seul texte de notification visible par Apple est le nom de l'application et une phrase sans contenu comme « You have a new notification » ; l'extension de notification de l'application iPhone (Notification Service Extension) déchiffre le texte chiffré et le remplace par l'expéditeur et le message.

  • Enregistrement : à chaque démarrage, l'application enregistre deux choses pour chaque compte — l'une pour les notifications (type 1), l'autre pour les appels (VoIP, type 9). Lors de cet enregistrement, le serveur retient la langue de l'application (celle du texte visible de la notification) et son numéro de build.
  • Les messages partent en notifications. Rien n'est envoyé pour les conversations en sourdine. Ce qui a été lu ou supprimé sur un autre appareil est signalé par un push silencieux en arrière-plan, et l'application retire les notifications affichées.
  • Les appels envoyés en VoIP affichent l'écran d'appel CallKit même sur un téléphone verrouillé. Mais une application qui reçoit un push VoIP doit aussitôt signaler l'appel à CallKit ; sinon, iOS tue l'application et, si cela se répète, cesse de lui transmettre les push VoIP. C'est pourquoi la VoIP ne s'active avec APNS_VOIP_MIN_BUILD qu'à partir des builds conçus pour le faire ; avant, les appels partent eux aussi en notifications.
La clé APNs appartient à l'équipe qui a signé l'application

Contrairement à Firebase, il n'y a aucun fichier de configuration à mettre dans l'application — elle est déjà compilée pour utiliser APNs (autorisation des notifications, App Group, extension de notification) : il suffit de placer la clé sur le serveur. Mais la clé du serveur ne permet d'envoyer des push qu'aux applications signées par l'équipe qui a créé la clé et dont le bundle ID est identique à APNS_TOPIC. Si vous avez signé l'application iPhone de votre serveur avec votre propre compte développeur Apple, créez la clé APNs dans ce même compte.

Créer une clé APNs

  1. Apple Developer → Certificates, IDs & Profiles → Keys → +. Il doit s'agir du compte de l'équipe qui a signé l'application.
  2. Saisissez un nom et cochez Apple Push Notifications service (APNs). Choisissez l'environnement Sandbox & Production et une portée couvrant toute l'équipe (Team Scoped) — le serveur envoie vers deux destinations, les notifications et la VoIP.
  3. Register → Download. AuthKey_<ID de clé>.p8 ne peut être téléchargé qu'une seule fois. Ce fichier est secret — n'en mettez le contenu ni dans un dépôt, ni dans une discussion, ni dans .env.

Placer la clé sur le serveur

sudo cp AuthKey_<ID de clé>.p8 /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/AuthKey_<ID de clé>.p8    # l'utilisateur du conteneur serveur
sudo chmod 600 /srv/pabal/pabal_server/keys/AuthKey_<ID de clé>.p8

Ajoutez ces lignes à .env. Dans le conteneur, pabal_server/keys correspond à /app/keys.

APNS_KEY=/app/keys/AuthKey_<ID de clé>.p8
APNS_TEAM_ID=4Z8R468M2D
APNS_TOPIC=me.pabal.ios
APNS_MIN_BUILD=4
APNS_VOIP_MIN_BUILD=
ValeurÀ indiquerRemarques
APNS_KEYChemin du fichier de cléL'ID de clé est lu dans le nom du fichier. Si vous avez donné un autre nom au fichier, indiquez l'ID de clé dans APNS_KEY_ID
APNS_TEAM_IDID de l'équipe qui a signé l'applicationPour l'application Pabal : 4Z8R468M2D. Pour une application que vous avez compilée et signée vous-même, l'ID de votre équipe
APNS_TOPICBundle ID de l'applicationPour l'application Pabal : me.pabal.ios. Les appels (VoIP) partent vers cette valeur suivie de .voip
APNS_MIN_BUILD4Rien n'est envoyé aux builds inférieurs (0 pour tous). 4 exclut Pabal 1.0 (3), produit avant les essais des push
APNS_VOIP_MIN_BUILDLaisser videVide, les appels partent eux aussi en notifications. Indiquez le numéro du premier build qui signale aussitôt les appels à CallKit : à partir de ce build, l'écran d'appel CallKit s'affiche
docker compose up -d pabal-server
docker compose logs pabal-server | grep -i push
INFO  TelegramServer - iPhone push notifications on: APNs key <ID de clé> of team 4Z8R468M2D, topic me.pabal.ios, builds from 4, calls as alerts (TELEGRAM_APNS_VOIP_MIN_BUILD not set)
  • Si cette ligne apparaît, c'est activé. Si le fichier de clé est illisible ou mal formé, iPhone push notifications are off: … en donne la raison (le contenu du fichier n'apparaît jamais dans les journaux). Cela s'active et se désactive indépendamment d'Android (FCM) — vous pouvez n'activer que l'un des deux.
  • Le conteneur doit pouvoir ouvrir des connexions sortantes vers api.push.apple.com (443, HTTP/2). Les builds de débogage installés directement sur un appareil depuis Xcode passent par api.sandbox.push.apple.com (l'application l'indique lors de son enregistrement).

Vérification

  1. Sur un vrai iPhone, ouvrez l'application une fois et autorisez les notifications. Le journal du serveur affiche Push device registered: user … type 1 token … (push key 256 bytes, build …) ainsi qu'une ligne type 9. Impossible de vérifier avec le simulateur — il n'a ni jeton APNs ni CallKit.
  2. Fermez l'application en la balayant vers le haut.
  3. Envoyez-lui un message depuis un autre compte : le journal affiche Push MESSAGE_TEXT to user …: sent, et l'expéditeur ainsi que le contenu apparaissent sur l'écran verrouillé.
  • Politique de confidentialité : dans la page d'administration (관리 화면) → Paramètres (설정) → Informations sur l'opérateur (운영자 정보), indiquez Apple Inc. (service de notifications push d'Apple, États-Unis) sous Sous-traitance (처리 위탁) et Transferts à l'étranger (국외 이전). Le texte de la politique mentionne déjà le passage par APNs. Dans le questionnaire sur la confidentialité de l'app, sur App Store Connect, vérifiez aussi l'élément « identifiant de l'appareil » (jeton push).
  • Si la clé a fuité, révoquez-la (Revoke) dans le compte développeur, créez-en une nouvelle, remplacez le fichier et redémarrez le serveur. Inutile de recompiler l'application.

12. Bots vocaux (facultatif)

Avec les bots vocaux, quand quelqu'un appelle un bot depuis l'application, c'est un modèle vocal d'IA qui répond (les explications pour les propriétaires de bot sont dans Bots vocaux). Il faut ajouter deux composants au serveur, et les documents juridiques ainsi que les versions de l'application doivent être prêts pour que la fonction s'active. Elle est désactivée par défaut.

La voix s'active avec un fichier compose complémentaire

Les composants vocaux ne sont pas dans deploy/docker-compose.yml mais dans le fichier complémentaire deploy/docker-compose.voice.yml : les réglages vocaux du serveur et le réseau réservé à la liaison, la passerelle pabal-voice (profil voice) et le point d'entrée audio de la montre pabal-voice-edge (profil voice-watch). Si .env ne nomme pas ce fichier, il n'y a aucun réglage vocal. Cette configuration a tourné avec le moteur écho, mais la voix n'a encore jamais été activée sur un serveur de production, et aucun appel à un bot vocal n'a encore été mené jusqu'au bout à travers le relais d'appel (TURN) de production — le test de bout en bout a été fait avec un relais de test à l'intérieur d'un réseau Docker (scripts/voice-e2e/run.sh). Avant d'activer, deploy/voice_readiness.py dans le dépôt vérifie ce qui manque. Les conditions avant l'ouverture au public (examen juridique, réponse écrite du fournisseur, publication des applications) relèvent du jugement de l'opérateur, et le code n'en bloque qu'une partie.

ComposantRôleSans lui
pabal-voice (passerelle, image 0.11.0)Tient le bout de l'appel à la place du bot, fait l'échange de clés, et échange l'audio avec le modèle d'IA. Python 3.12 + ntgcalls. Parle au serveur par une liaison (WebSocket, authentifiée mais non chiffrée)Tous les appels aux bots sonnent occupé (no_gateway)
coturn (relais d'appel)Les appels aux bots utilisent uniquement le relais : l'application comme la passerelle désactivent la connexion directe et n'envoient l'audio que par le relais. C'est le même relais que pour les appels entre personnes (TURN_SECRET)Tous les appels aux bots sonnent occupé (no_relay), et la passerelle refuse aussi la connexion (4008)
Clé Vertex AIClé JSON d'un compte de service Google Cloud. Seul le processus moteur de la passerelle l'ouvrePas de modèle, seulement un écho (VOICE_ENGINE=echo, l'audio ne quitte pas la machine)
(Facultatif) clé maîtresseChiffre les clés propres des propriétaires de bot et les secrets du mode fluxClé propre et mode flux désactivés
(Facultatif) composants MCPpabal-mcp · signataire · proxy de sortie (ci-dessous)Serveurs MCP désactivés

Conditions d'activation

Ce que vérifie le serveurSans cela
Fichier du secret de liaison (TELEGRAM_VOICE_LINK_SECRET_FILE, 32 octets)Voix désactivée — le port de liaison n'est même pas ouvert
Relais d'appel (TELEGRAM_TURN_HOST·TELEGRAM_TURN_SECRET)Tous les appels aux bots sonnent occupé
La section id="bot-calls" de la politique de confidentialité du site (en ko et en en) — l'écran de confirmation de l'application renvoie à cet endroitAucun bot ne prend d'appel (journal ERROR). Un brouillon se trouve dans docs/voice-bots/p0/privacy-*.draft.html
La section id="voice-bot-owners" des conditions d'utilisationPersonne ne peut accepter les conditions du propriétaire de bot → transcription, clé propre, mode flux et MCP tous inutilisables
Les sections id="bot-calls-stream" · id="bot-calls-mcp" de la politiqueMode flux · MCP désactivés
L'autorisation des bots dans la page d'administration (par défaut, seulement les bots autorisés) et l'ouverture au public (par défaut, testeurs seuls ; pour ouvrir, il faut taper une phrase de confirmation)Un bot non autorisé ne prend pas d'appels, et avant l'ouverture seuls les testeurs peuvent appeler
La version de l'application de l'appelant : bureau pabal-bc2 (et pabal-bc1 tant que l'écran de confirmation n'existe qu'en coréen et en anglais) · Android 62151 · iPhone 5 ou plus · Apple Watch 1 ou plus (TELEGRAM_VOICE_MIN_BUILD_*)Cette personne n'a pas de bouton d'appel, et si elle appelle quand même, c'est occupé
(Facultatif) Apple Watch : l'API montre du serveur (TELEGRAM_WATCH_API_PORT) et l'adresse audio (TELEGRAM_WATCH_AUDIO_URL), le point d'entrée audio pabal-voice-edge, une passerelle qui annonce direct (ci-dessous)Impossible d'appeler depuis la montre (les applications du téléphone et de l'ordinateur ne changent pas)
(Facultatif) Garder la conversation : la section id="bot-calls-keep-text" de la politique, une passerelle qui annonce keep_text (VOICE_KEEP_TEXT, ci-dessous)Même si la case est cochée, la conversation n'est pas gardée
(Facultatif) Appels des bots : la section id="bot-calls-outbound" de la politique et la section id="voice-bot-owners-outbound" des conditions, le dossier de la clé de preuve, les versions minimales pour les appels sortants, une passerelle qui annonce outbound, l'interrupteur général de la page d'administration (ci-dessous)Aucun bot ne peut appeler personne

Seul un serveur de test (TELEGRAM_TEST_NUMBERS=true) peut sauter la vérification des sections juridiques avec TELEGRAM_VOICE_NOTICE_CHECK=off et laisser tout le monde essayer avec TELEGRAM_VOICE_BOTS (* pour tous les bots) et TELEGRAM_VOICE_TESTERS=*. Ne les utilisez pas en production — un serveur de production ignore TELEGRAM_VOICE_BOTS en affichant un avertissement.

Relais d'appel (coturn)

  • Sur un serveur Linux, utilisez le pabal-turn du fichier compose (COMPOSE_PROFILES=turn). Définissez TURN_SECRET dans .env (openssl rand -hex 32), et ouvrez sur le routeur et le pare-feu TURN_PORT (3478, udp et tcp) et de TURN_MIN_PORT à TURN_MAX_PORT (49160 à 49200, udp). Si vous élargissez la plage, modifiez en même temps le routeur et .env.
  • Sous macOS avec Docker Desktop, ne faites pas tourner coturn dans un conteneur. Tous les appelants y apparaissent comme 192.168.65.1, les autorisations du relais ne correspondent plus, et tout l'audio est jeté alors que tout semble fonctionner. Sur un serveur Mac, installez coturn directement (deploy/turnserver.conf.example, docs/turn-relay-guide.md).
  • L'adresse de relais à donner à la passerelle est l'adresse LAN du serveur : mettez la même valeur dans TELEGRAM_VOICE_TURN_HOST côté serveur et dans VOICE_TURN_HOST côté passerelle. host.docker.internal n'a pas fonctionné, et avec 127.0.0.1 l'allocation n'aboutissait pas. Les applications, elles, reçoivent toujours l'adresse publique (TURN_HOST).
  • Depuis un réseau extérieur, vérifiez avec deploy/check_relay.py --data que l'audio passe réellement. Il arrive que STUN réponde et que l'allocation réussisse, sans que l'audio passe.

Clé Vertex AI

  1. Dans un projet Google Cloud, activez l'Agent Platform API (ancien nom : Vertex AI API, aiplatform.googleapis.com). Un compte de facturation doit être associé. L'API Gemini Developer (clés AI Studio) n'est pas utilisée.
  2. Créez un compte de service et donnez-lui un seul rôle, roles/aiplatform.user (pour restreindre davantage, un rôle personnalisé avec la seule permission aiplatform.endpoints.predict). Créez une clé de production distincte de celle de développement.
  3. Téléchargez la clé JSON et placez-la hors du dépôt et de .env, par exemple dans /srv/pabal/pabal_voice/secrets/vertex-sa.json, lisible seulement par l'utilisateur de la passerelle (Linux : chown 10001:10001, chmod 400). Si tout le monde peut la lire, la passerelle refuse de démarrer.
  4. Le modèle temps réel n'existe que dans us-central1 (Iowa, États-Unis) (vérifié le 2026-09-26). La politique de confidentialité et l'écran de confirmation doivent dire que l'audio des appels part aux États-Unis, et le serveur ne confie un appel à une passerelle que si la région écrite dans l'écran de confirmation de l'application est la même que celle de la passerelle (VOICE_VERTEX_REGION).
  5. À faire : des alertes de budget, un quota de sessions simultanées (le double de la capacité de la passerelle), et le cache de données de Vertex (s'il est activé, il garde les entrées et sorties en mémoire jusqu'à 24 heures) — l'écran de confirmation dit que « Google ne conserve ni l'audio ni le texte après avoir produit la réponse » : tant que le cache n'est pas désactivé, ce n'est pas exact. Recommandé : avant l'ouverture au public, désactivez le cache de données (section 9 de docs/voice-bots/vertex-setup.md dans le dépôt) et réduisez le rôle de la clé à la seule autorisation de prédiction (section 10 du même document).

Le serveur et la passerelle lisent le même secret de liaison dans un fichier. La liaison n'étant pas chiffrée, placez-la uniquement sur un réseau dédié où seuls ces deux conteneurs sont branchés — pas sur ssemiya-net, partagé avec d'autres piles, ni sur le LAN. Ne publiez pas son port.

(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   # celui du serveur (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 # celui de la passerelle (uid 10001)
shred -u /tmp/voice-link.secret

Ces valeurs vont dans .env. Avec elles, le fichier complémentaire ajoute à pabal-server ses réglages vocaux (TELEGRAM_VOICE_*) et le réseau de liaison.

COMPOSE_FILE=docker-compose.yml:docker-compose.voice.yml
COMPOSE_PROFILES=voice                              # avec les autres profils (turn, …), séparés par des virgules
VOICE_LINK_SECRET_FILE=/app/keys/voice-link.secret  # chemin dans le conteneur ; vide : voix désactivée
VOICE_TURN_HOST=192.168.1.64                        # adresse LAN du serveur (où écoute coturn)
VOICE_TESTERS=100001                                # id des personnes qui peuvent appeler avant l'ouverture (la page d'administration en ajoute)
  • Le fichier complémentaire place le serveur sur le réseau purement interne pabal-voice-link (10.231.53.0/29 — serveur .2, passerelle .3), et le serveur n'écoute que sur sa propre adresse dans ce réseau (avec 0.0.0.0, il écouterait aussi sur le réseau partagé auquel il est relié). Si cette plage chevauche votre LAN, votre VPN ou un autre réseau Docker, changez ensemble VOICE_LINK_SUBNET, VOICE_LINK_SERVER_IP et VOICE_LINK_GATEWAY_IP.
  • Pour écouter sur une adresse autre que la boucle locale, TELEGRAM_VOICE_LINK_ANY_ADDRESS=true est nécessaire, et la liaison ne se lie jamais à une adresse publique. Le journal du serveur indique le réseau que voit la liaison.
  • Avant authentification, une connexion a 3 secondes pour l'upgrade et 3 secondes pour le hello ; au plus 2 connexions par adresse et 16 au total.
  • Tous les réglages serveur liés à la voix sont dans Administration et paramètres — Variables d'environnement des bots vocaux.

Passerelle (pabal-voice)

cd /opt/pabal
docker build -t pabal-voice:latest voice/                                       # pour le processeur de cette machine
docker buildx build --platform linux/amd64 -t pabal-voice:latest --load voice/  # pour un serveur Intel
cd deploy && docker compose up -d                                         # selon COMPOSE_FILE et COMPOSE_PROFILES de .env

L'image repose sur un Python 3.12 figé par empreinte, sans compilateur ni pip, et s'exécute avec l'uid 10001. ntgcalls n'existe que pour Linux x86_64/aarch64 et macOS arm64 : sur un Mac Intel, on ne la fait tourner que dans Docker. Le service pabal-voice du fichier complémentaire utilise cette image sans la construire (image: pabal-voice:latest). Pour la construire pour le processeur du serveur sur une autre machine et l'y envoyer, utilisez deploy/ship.sh <user@host> <dossier> --voice (l'image remplacée reste en pabal-voice:previous). Voici le service de passerelle de ce fichier, abrégé.

  pabal-voice:                             # extrait de deploy/docker-compose.voice.yml (abrégé)
    image: pabal-voice:latest
    profiles: [voice]
    init: true
    stop_grace_period: 40s                 # plus long 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:-}  # la même valeur que TELEGRAM_VOICE_TURN_HOST du serveur
      VOICE_CAPACITY: ${VOICE_CAPACITY:-2}
      VOICE_DAILY_MODEL_MINUTES: ${VOICE_DAILY_MODEL_MINUTES:-60}
      VOICE_SAFETY_THRESHOLD: ${VOICE_SAFETY_THRESHOLD:-MEDIUM}   # appels des testeurs : hypothèse à confirmer par l'opérateur (ci-dessous)
      VOICE_BYOK: "false"
      VOICE_STREAM: "false"
    volumes:
      - ${PABAL_HOME:-/srv/pabal}/pabal_voice/secrets:/run/secrets:ro   # secret de liaison et clé Vertex — absents de backup.sh
      - pabal_voice_state:/var/lib/pabal-voice
    networks:
      pabal-voice-link:                    # réseau de liaison partagé avec le seul serveur (internal)
        ipv4_address: ${VOICE_LINK_GATEWAY_IP:-10.231.53.3}
      pabal-voice-out: {}                  # son propre réseau de sortie (Google, relais)
  • En plus de la liaison, la passerelle a besoin d'un chemin vers l'extérieur : Google (us-central1-aiplatform.googleapis.com:443) et le relais d'appel (UDP). Comme le réseau de liaison est internal: true et ne peut pas sortir, on ajoute un second réseau, pabal-voice-out, comme ci-dessus.
  • Landlock : une passerelle qui utilise un modèle ou le mode flux refuse de démarrer (code de sortie 2) sans Landlock dans le noyau Linux (5.13 ou plus, disponible avec le seccomp par défaut de Docker). Le processus média, qui manipule en code natif ce qu'envoie l'appelant, se verrouille lui-même pour ne pas pouvoir lire les fichiers de clés. Dans le conteneur, ne montez pas les fichiers secrets sous /app, /opt, /usr, /lib ou /etc (/run/secrets convient — comme dans l'exemple ci-dessus). VOICE_ALLOW_NO_LANDLOCK=true est réservé aux Mac de développement. Le fonctionnement de Landlock sous macOS avec Docker Desktop n'a pas été vérifié.
  • Mémoire : par appel, 60 à 100 Mo pour le média + 30 à 40 Mo pour le moteur. Avec une capacité de 8 (VOICE_CAPACITY) et une paire en réserve, environ 1,3 Go. Par défaut, le fichier complémentaire prévoit une capacité de 2 et 1 Go de mémoire (VOICE_MEMORY).
  • Filtre de sécurité : VOICE_SAFETY_THRESHOLD vaut LOW (le plus strict) par défaut dans la passerelle, mais lors des mesures il a transformé en refus en anglais 13 phrases coréennes anodines sur 172 (MEDIUM : 0 sur 69). Pour les appels des testeurs, le dépôt part de l'hypothèse que MEDIUM est indiqué explicitement ; changer la valeur par défaut exige d'abord des tests adverses (red team).
  • Limites : par défaut, la passerelle autorise par appel 600 secondes, 400 000 jetons et 30 tours par minute, et par jour (UTC) 300 minutes de modèle et 8 millions de jetons ; le fichier complémentaire fixe la journée à 60 minutes et 1,6 million de jetons (VOICE_DAILY_MODEL_* ; au-delà, les nouveaux appels sonnent occupé). Le moteur OpenAI n'a été testé qu'avec de faux serveurs et reste désactivé, et il n'y a pas de bascule automatique entre moteurs.
  • Langues : en se connectant au serveur, la passerelle annonce les langues de bot qu'elle prend en charge (depuis la version 0.7.0, elle prend en charge les 9), et le serveur ne confie les appels d'un bot qu'à une passerelle qui prend en charge sa langue — une ancienne passerelle qui n'annonce aucune langue (0.6.0) ne reçoit que les bots en coréen et en anglais. Les enregistrements des annonces se trouvent dans le dossier assets/ de l'image, et c'est assets/assets.json qui indique lesquels ont été validés. Sans ce fichier, la passerelle ne peut pas charger l'annonce sur l'IA et ne prend pas d'appels (ERROR dans le journal de démarrage). Les enregistrements non validés (pour l'instant, toutes les langues sauf le coréen et l'anglais) ne sont pas joués, et les appelants de ces langues entendent les annonces en anglais. VOICE_DRAFT_NOTICES=true est un interrupteur réservé aux serveurs de test qui joue aussi les enregistrements à l'état de brouillon ; si VOICE_ALLOW_TEST_URLS=true n'est pas défini aussi, la passerelle ne démarre pas. VOICE_NOTICE_LANG (langue des annonces quand le serveur n'indique pas celle de l'appelant, ko par défaut) doit être l'une des 10 langues des enregistrements (ko en ja zh fr es id vi km ar), et VOICE_LANGUAGE (langue du bot pour un appel sans réglages du propriétaire, ko-KR par défaut) l'un des identifiants de la liste des langues de bot (Bots vocaux — Voix et langues).
  • Les journaux ne contiennent ni le secret de liaison, ni les clés, ni l'audio, ni le texte des conversations, ni les instructions. Sur SIGTERM, la passerelle ne prend plus de nouveaux appels et laisse VOICE_DRAIN_SECONDS (20 secondes par défaut) aux appels en cours avant de les terminer.

Vérifier

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 (réseau)
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: … apparaît quand le secret de liaison est absent, illisible ou ne fait pas 32 octets ; la fin de ligne — but without a TURN relay every bot call is busy, quand il n'y a pas de relais.
  • Si vous voyez l'ERROR Voice bots are on, but no bot is callable: the privacy policy … has no id="bot-calls" section, publiez d'abord la section sur les appels aux bots dans la politique du site.
  • Quand une passerelle se connecte, les features du journal du serveur contiennent bot_settings·end_call·context·tools, plus byok pour une passerelle qui prend en charge les clés des propriétaires et transcripts si elle dispose de l'annonce vocale de transcription (l'ordre varie). audio to doit être identique au destinataire annoncé par l'écran de confirmation de l'application.
  • Côté passerelle, on doit voir en première ligne starting as pabal-voice-1 (capacity 2, … engine gemini, audio to google.vertex.gemini-live@us-central1), dans la ligne du modèle session resumption off; no failover, et aussi media workers sandbox themselves with Landlock (ABI N). recordings for the callers' notices in: Korean, English indique les langues dans lesquelles la passerelle peut jouer les annonces aux appelants (sur un serveur de test qui joue aussi les brouillons, (drafts included: …) s'y ajoute).
  • Ensuite, dans l'onglet Voix (음성) de la page d'administration (Administration et paramètres — Onglet Voix), vérifiez que la passerelle est connectée, ajoutez des testeurs et autorisez les bots.

Clé maîtresse (clé propre · mode flux)

Les clés de modèle des propriétaires de bot et les secrets du mode flux sont conservés chiffrés avec cette clé. Elle doit se trouver à un endroit qui n'est pas copié avec les sauvegardes.

sudo install -d -m 700 -o 1000 -g 1000 /etc/pabal/voice                 # hors 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 elle est dans le dossier de données ou celui des clés RSA, ou à côté, ou si son dossier est un tant soit peu ouvert à d'autres utilisateurs (pas en 700), le serveur ne l'utilise pas (Voice keys off: …). Si le fichier n'existe pas, le serveur le crée à la réception de la première clé (avec un montage en lecture seule, créez-le vous-même comme ci-dessus).
  • Ne la mettez pas dans les sauvegardes, et gardez-en une copie hors ligne à part. Si elle est perdue, tous les propriétaires de bot devront reconfigurer leur clé. Pour la remplacer, donnez l'ancienne clé dans TELEGRAM_VOICE_KEYS_PREVIOUS_MASTER_FILE, démarrez, vérifiez les journaux, puis supprimez l'ancien fichier.
  • Même avec une clé maîtresse, les clés propres ne sont acceptées qu'une fois que les applications affichent l'écran de confirmation (version 2) des bots à clé propre et que vous avez mis TELEGRAM_VOICE_OWN_KEYS_APPS_READY=true. Pour le mode flux, on ne peut appeler qu'une fois les versions d'application contenant l'écran de confirmation du mode flux indiquées dans TELEGRAM_VOICE_STREAM_MIN_BUILD_DESKTOP·_ANDROID·_IOS (par défaut 0 = personne ne peut appeler), et la passerelle doit avoir VOICE_STREAM=true et les IP publiques de ce déploiement (VOICE_STREAM_DENY_ADDRESSES).

Composants MCP (facultatif)

Ces composants appellent pendant les appels les serveurs MCP enregistrés par les propriétaires de bot. Ils sont désactivés par défaut, et deploy/docker-compose.mcp.yml (profil mcp) ajoute trois conteneurs.

ConteneurRôle
pabal-mcp (uid 1101)Le client MCP. Relié au serveur uniquement par un fichier socket ; il n'a qu'un réseau interne et ne sort que par le proxy de sortie. En lecture seule, sans privilèges, 512 Mo
pabal-mcp-signer (uid 1102)Le signataire. Aucun réseau : il ne répond que par socket, et écrit le document public de l'émetteur
pabal-mcp-egress (uid 1103)Le proxy de sortie. Uniquement vers le port 443, et jamais vers les adresses de Pabal lui-même
docker compose -f docker-compose.yml -f docker-compose.mcp.yml --profile mcp up -d
  • Pour les activer, il faut TELEGRAM_MCP_EGRESS_OWN_ADDRESSES (les adresses publiques de Pabal, obligatoire) et les fichiers de clés (secret de liaison, K_grant, clé d'installation — tous dans un dossier en 700 hors de /srv/pabal), ainsi que la section bot-calls-mcp de la politique et la section des propriétaires de bot des conditions sur le site. Les réglages à faire une fois sont listés dans l'ordre dans le commentaire en tête de docker-compose.mcp.yml. Le coffre et les clés de signature ne vont pas dans les sauvegardes.
  • Une fois que les applications affichent dans l'écran de confirmation le destinataire MCP du bot, mettez TELEGRAM_MCP_APPS_READY=true. Avant cela, les serveurs peuvent être enregistrés mais aucun appel n'emporte d'outil MCP.
  • Prévoir une adresse de sortie dédiée aux requêtes MCP n'est pas une condition du code mais une décision d'exploitation (c'est par cette sortie que les paroles de l'appelant partent vers un serveur tiers).
  • Une fois activés, le journal affiche MCP tools on: pabal-mcp dials …; issuer …; ports …; own addresses N; clock by NTP …. deploy/ship.sh <serveur> <dossier> --mcp se contente de construire et d'envoyer les trois images et de copier le fichier override, sans rien activer (jamais essayé sous Docker Linux).

Point d'entrée Apple Watch (facultatif, en préparation)

L'app pour Apple Watch se connecte et passe ses appels par l'API montre du serveur (JSON sur HTTPS, /api/watch/v1/) au lieu de MTProto, et l'audio des appels atteint la passerelle par le point d'entrée audio (pabal-voice-edge, WSS). Le relais d'appel (coturn) n'est pas utilisé. C'est désactivé par défaut.

Le point d'entrée audio de la montre n'est pas encore joignable de l'extérieur

deploy/docker-compose.voice.yml contient le point d'entrée audio pabal-voice-edge (profil voice-watch, sans secret, sur un réseau purement interne), mais pas encore les réglages de l'API montre du serveur (TELEGRAM_WATCH_*) ni les chemins de la montre dans le proxy inverse : une montre à l'extérieur ne peut donc pas l'atteindre. Ce qui suit décrit les valeurs de réglage ; cela n'a jamais été essayé en configuration de production (testé seulement sur le serveur de développement).

RéglagePar défautSignification
TELEGRAM_WATCH_API_PORT—Port de l'API montre. Sans lui, l'API montre est désactivée
TELEGRAM_WATCH_API_HOST, TELEGRAM_WATCH_API_ANY_ADDRESS127.0.0.1, —Adresse d'écoute. Hors boucle locale, il faut ANY_ADDRESS=true, et un proxy TLS devant
TELEGRAM_WATCH_AUDIO_URL—Adresse du point d'entrée audio communiquée à la montre. Le chemin doit être exactement wss://<hôte>/api/watch/v1/audio. Absente ou incorrecte, les appels depuis la montre sont désactivés
TELEGRAM_WATCH_ALLOW_PLAIN—Autorise une adresse ws://. Développement uniquement (tests à la maison)
TELEGRAM_WATCH_SESSION_DAYS7Nombre de jours pendant lesquels la connexion de la montre reste valable (1 à 365)
TELEGRAM_WATCH_AUTH_PER_MINUTE30Limite, pour tout le serveur, des demandes de code de connexion (par minute). Une limite par adresse existe en plus
TELEGRAM_VOICE_MIN_BUILD_WATCHOS1Première version de l'app pour Apple Watch qui affiche l'écran de confirmation en entier
  • Passerelle : VOICE_DIRECT=true et VOICE_EDGE_DIR (dossier de sockets partagé avec le point d'entrée, par défaut /run/pabal-voice-edge). Les appels depuis la montre ne fonctionnent qu'avec un moteur à 16 kHz (Gemini) ou l'écho.
  • Point d'entrée audio : lancez python -m pabal_voice.edge comme second service de la même image (VOICE_EDGE_HOST·VOICE_EDGE_PORT, par défaut 127.0.0.1·8096, VOICE_EDGE_DIR). Le point d'entrée est le premier à décoder ce qui arrive d'Internet : ne lui donnez aucun secret, et placez-le dans un réseau interne que seuls le proxy et la passerelle peuvent atteindre.
  • Le proxy inverse ne transmet que les deux chemins de la montre — /api/watch/v1/audio (WebSocket, vers le point d'entrée) et /api/watch/v1/ (vers l'API montre). Côté API montre, laissez-le ajouter l'adresse du client à la fin de X-Forwarded-For (le serveur s'en sert pour compter la limite de connexion par adresse). Côté point d'entrée, supprimez X-Forwarded-For et X-Real-IP.
  • On ne peut pas s'inscrire depuis la montre, ni appeler un bot qui utilise des outils, MCP, une clé propre, le mode flux ou la transcription du propriétaire du bot (WATCH_NOT_YET).

Garder la conversation (facultatif, en préparation)

Si la personne qui appelle le choisit dans l'écran de confirmation, la conversation de l'appel est gardée en texte uniquement dans sa propre conversation (Utiliser l'application — bots vocaux). Il n'y a pas d'interrupteur d'autorisation de l'opérateur : la fonction s'active quand tout ce qui suit est en place.

  • La section id="bot-calls-keep-text" dans la politique de confidentialité (brouillon : docs/voice-bots/p0/privacy-*.draft.html dans le dépôt). Cette section sert elle-même d'interrupteur. L'application sait si elle doit afficher la case à cocher grâce à pabal_voice_keep_text dans help.getAppConfig.
  • VOICE_KEEP_TEXT de la passerelle 0.9.0 : true par défaut avec un moteur de modèle. Avec le moteur écho (y compris une passerelle réservée au mode flux), true est une erreur de configuration et la passerelle ne démarre pas.
  • Une version de l'application qui contient la case à cocher. Le texte de la case n'entre dans les versions publiées qu'après l'examen juridique. La montre n'affiche pas le texte non examiné ; TELEGRAM_VOICE_KEEP_TEXT_UNREVIEWED=true est un interrupteur réservé aux serveurs de test internes qui affiche le texte non examiné.
  • Sur un serveur où la fonction est activée, le voice_call ended de tous les appels aux bots hors mode flux arrive jusqu'à 10 secondes plus tard, et 30 numéros de message sont réservés après le message d'appel (pour que le propriétaire du bot ne puisse pas connaître le choix de l'appelant).
  • Les textes de conversation qui attendent d'être écrits après l'appel ne sont qu'en mémoire : si le serveur redémarre entre-temps, le texte de cet appel n'est pas gardé.

Appels des bots (facultatif, en attente de l'examen juridique)

Un bot vocal qui appelle de lui-même une personne l'ayant autorisé (Bots vocaux — Appels des bots). Les conditions sont plus nombreuses que pour les appels aux bots, et il ne faut pas l'activer avant la fin de l'examen juridique. Dans l'ordre :

  1. Le serveur : créez le dossier de la clé de preuve (TELEGRAM_VOICE_EVIDENCE_KEY_DIR — 700, hors des dossiers sauvegardés comme ceux des données et des clés) avec new de scripts/voice-evidence-key.py (rotate pour remplacer la clé, check pour la vérifier), et indiquez la première version de l'application avec l'écran d'autorisation comme version minimale pour les appels sortants, par exemple TELEGRAM_VOICE_OUTBOUND_MIN_BUILD_ANDROID (variables d'environnement). Une variable TELEGRAM_VOICE_EVIDENCE_KEY_DIR placée seulement dans .env n'atteint pas le serveur — ajoutez-la au pabal-server de deploy/docker-compose.voice.yml, dans environment (le chemin dans le conteneur) et volumes (le dossier sur l'hôte).
  2. Les applications : publiez des versions de l'application avec l'écran d'autorisation. Leurs règles de liens d'autorisation ne connaissent que pabal.me : pour un autre serveur, son hôte doit être ajouté aux règles de liens des versions de l'application avant qu'un lien envoyé par un bot ouvre l'écran (le menu et la liste des réglages fonctionnent tels quels).
  3. Le juridique : publiez la section id="bot-calls-outbound" de la politique de confidentialité et la section id="voice-bot-owners-outbound" des conditions d'utilisation (ainsi que la section voice-bot-owners, pour que les propriétaires acceptent les conditions). Les brouillons sont dans docs/voice-bots/p0/ du dépôt. Les textes de l'écran d'autorisation et les enregistrements des annonces d'appel sortant (notice-out·apology-rule) sont examinés et approuvés à ce moment aussi.
  4. L'interrupteur : dans Appels des bots de l'onglet Voix de la page d'administration, réglez les pays et les limites et activez avec la phrase de confirmation (Page d'administration — onglet Voix). Chaque bot passe en plus l'examen des appels sortants par l'opérateur.
  • La passerelle : seule une passerelle avec VOICE_OUTBOUND=true passe les appels des bots. Avec un moteur de modèle, elle ne démarre que si les quatre enregistrements des annonces d'appel sortant sont approuvés ; les enregistrements en brouillon (VOICE_OUTBOUND_DRAFT_NOTICES) sont réservés aux serveurs de test.
  • Ne mettez pas dans le .env de production les noms réservés au test et au développement comme TELEGRAM_VOICE_CALLME_UNREVIEWED et TELEGRAM_VOICE_TEST_DIAL.
  • Les appels des bots ne sont pas non plus chiffrés de bout en bout — le son passe par la passerelle de ce serveur et par le fournisseur d'IA.

13. Images de carte dans les messages de position (facultatif)

Quand une position est envoyée, l'application Android et les applications de bureau (macOS, Windows) ne dessinent pas elles-mêmes la carte de la bulle : elles demandent au serveur une image de la carte à cet endroit (upload.getWebFile). Le serveur l'obtient de la Maps Static API de Google et la transmet. Sans elle, Android affiche une image de remplacement et le bureau un cadre vide avec une épingle. L'iPhone dessine la carte sur le téléphone, ce réglage ne le concerne donc pas ; l'envoi d'une position et la grande carte qui s'ouvre quand on la touche fonctionnent dans tous les cas.

  1. Dans la console Google Cloud, choisissez un projet lié à un compte de facturation et activez la Maps Static API.
  2. Créez une clé API ; limitez l'application aux adresses IP (l'IP publique par laquelle le serveur sort sur Internet) et l'API à la seule Maps Static API. La clé reste sur le serveur et n'entre jamais dans les applications.
  3. Mettez-la dans .env et redémarrez le serveur avec docker compose up -d pabal-server.
  4. Vérifiez dans le journal. Quand les cartes sont désactivées, Maps in location messages are off: … en donne la raison (la clé n'apparaît jamais dans le journal).
STATIC_MAPS_KEY=AIza…               # ne laissez la clé ni dans une discussion, ni dans un journal, ni dans le dépôt
STATIC_MAPS_PER_DAY=300             # nouvelles images obtenues par jour (0 = sans limite)
STATIC_MAPS_PER_USER_HOUR=120       # nouvelles images qu'une même personne peut provoquer par heure
INFO  TelegramServer - Maps in location messages from Google Static Maps (120 new per person an hour, 300 a day)
  • Coût : la Maps Static API est gratuite jusqu'à 10 000 requêtes par mois. Le serveur garde chaque image une journée et la redonne à tout appareil qui demande la même carte ; une image redonnée ne compte pas dans les limites. La limite par défaut (300 par jour) fait environ 9 000 par mois, dans la tranche gratuite. Une requête au-delà d'une limite est refusée avec FLOOD_WAIT, et l'application affiche l'image de remplacement en attendant.
  • Politique de confidentialité : les coordonnées (latitude et longitude) de la position partagée vont chez Google — pas l'identité de l'expéditeur. Dans la page d'administration → Paramètres → informations de l'opérateur, ajoutez Google LLC (Maps Static API, États-Unis) aux sous-traitants et aux transferts hors du pays.
  • Si le journal affiche Map picture not made: Google Static Maps answered 403: …, vérifiez les restrictions de la clé (IP, API) ou le compte de facturation.

Exploitation

État et journaux

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

Mise à jour

cd /opt/pabal && git pull
cd deploy
./backup.sh                                     # sauvegarder d'abord
docker compose up -d --build pabal-server             # remplacer uniquement le serveur par la nouvelle image
docker compose logs --since 5m pabal-server | grep -E "Website|ERROR"

Pendant les quelques dizaines de secondes que dure le redémarrage du serveur, l'application est déconnectée, puis se reconnecte d'elle-même sans que l'utilisateur ait à se reconnecter à son compte. Les modifications de tables sont appliquées automatiquement au démarrage du serveur. Les répertoires montés sont indépendants de l'image : ni docker compose down ni la suppression d'images n'effacent les données — veillez seulement à ne jamais supprimer /srv/pabal.

Revenir en arrière : revenir à l'image précédente laisse en place ce que la nouvelle version a déjà écrit dans la base de données. L'ancienne version peut ne pas savoir le lire (une version qui contient le code des bots vocaux, par exemple, écrit de nouveaux événements comme les journaux d'appels dans les conversations, même voix désactivée) : pour revenir en arrière, restaurez donc aussi la base de données depuis la sauvegarde faite juste avant la mise à jour (Restauration).

Sauvegarde

./backup.sh            # → /srv/pabal/backups/<date-heure>/{pabal.dump, server-keys-data.tar.gz}
crontab -e             # tous les jours à 03:00 :
# 0 3 * * * /opt/pabal/deploy/backup.sh >> /srv/pabal/backups/backup.log 2>&1
  • Les sauvegardes de plus de 14 jours sont supprimées automatiquement (modifiable, par exemple KEEP_DAYS=30 ./backup.sh).
  • Copiez-les aussi ailleurs. Une sauvegarde sur le même disque ne survit pas à une panne de ce disque.
  • Les sauvegardes contiennent la clé privée RSA et les secrets SMS et SMTP. Protégez leur lieu de stockage autant que le serveur lui-même.

Restauration

cd /opt/pabal/deploy
B=/srv/pabal/backups/20260920-030000            # sauvegarde à restaurer
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"   # le chemin par défaut de la base : la restauration ne le ramène pas
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

La procédure est la même pour migrer vers un nouveau serveur : effectuez les étapes 1 à 5, puis, au lieu de l'étape 6, faites la restauration ci-dessus et démarrez. Si vous restaurez la même clé, inutile de recompiler l'application (sauf si l'IP du serveur a changé).

Démarrer et arrêter

docker compose restart pabal-server       # redémarrer uniquement le serveur
docker compose stop                 # tout arrêter (les données restent intactes)
docker compose up -d                # redémarrer

Même après un redémarrage de la machine, les conteneurs repartent d'eux-mêmes grâce à restart: unless-stopped.

Liste de contrôle de sécurité

  • .env en permissions 600, POSTGRES_PASSWORD aléatoire
  • /srv/pabal/pabal_server/keys en permissions 700, clé privée présente nulle part ailleurs que dans les sauvegardes
  • (Si les push sont activés) firebase-service-account.json en permissions 600, contenu absent du dépôt comme de .env
  • (Si les push iPhone sont activés) AuthKey_<ID de clé>.p8 en permissions 600, contenu absent du dépôt comme de .env
  • Pare-feu : uniquement 22 (si possible limité à l'IP de l'opérateur), 80, 443 et 8443
  • Depuis l'extérieur, https://domaine/admin/ renvoie 404
  • Page d'administration : numéros de test (테스트 번호) désactivés, mode de remise des codes (코드 전달 방식) sur SMS ou e-mail, envoi de test (테스트 발송) réussi
  • TELEGRAM_WEBHOOK_ALLOW_LOCAL non activé (pour que les webhooks des bots ne puissent pas atteindre le réseau interne)
  • (Si vous avez activé les bots vocaux) Le secret de liaison, la clé Vertex et la clé maîtresse ne sont lisibles que par l'utilisateur qui s'en sert (400/600), et la clé maîtresse est hors de /srv/pabal, donc hors des sauvegardes. Le port de liaison n'est pas publié et le réseau de liaison ne sert qu'au serveur et à la passerelle. Le serveur de production n'a ni TELEGRAM_VOICE_NOTICE_CHECK ni TELEGRAM_VOICE_TESTERS=*
  • SSH : connexion par mot de passe désactivée, clés uniquement
  • Cron de sauvegarde et copie externe vérifiés, restauration testée au moins une fois
  • Mises à jour de sécurité automatiques du système du serveur (unattended-upgrades)

Dépannage

SymptômeCause → solution
L'application reste bloquée sur « Connexion… »① Le port 8443 est bloqué → nc -vz IP 8443, vérifiez ufw et le groupe de sécurité du cloud ② l'application a été compilée avec une autre clé publique → refaites l'étape 9 avec la clé de ce serveur ③ PUBLIC_IP est incorrect → corrigez .env, puis docker compose up -d pabal-server
La connexion s'établit, puis coupe rapidementL'application bascule vers l'adresse annoncée par le serveur (PUBLIC_IP:MTPROTO_PORT), mais cette adresse est fausse → vérifiez PUBLIC_IP
Erreur de certificat HTTPSLe DNS ne pointe pas encore vers ce serveur ou le port 80 est bloqué → dig +short domaine, docker compose logs pabal-caddy
Le serveur redémarre en boucle, password authentication failedPOSTGRES_PASSWORD a été modifié après la création des données → revenez à la valeur d'origine, ou exécutez dans la base ALTER USER pabal PASSWORD '…'
AccessDeniedException: /app/keys/…Propriétaire des répertoires montés → sudo chown -R 1000:1000 /srv/pabal/pabal_server
OutOfMemoryError, lenteursAugmentez -Xmx dans JAVA_OPTS, puis docker compose up -d pabal-server
Les codes d'inscription n'arrivent pasOnglet Codes d'inscription (가입 코드) de la page d'administration : état de l'envoi et raison de l'échec → valeurs SMS et SMTP dans les paramètres, envoi de test
Aucune notification sur Android① Le journal indique Push notifications are off → chemin de FCM_CREDENTIALS et permissions du fichier ② Pas de Push device registered → l'application a été compilée sans configuration Firebase (pabal-placeholder), ou la nouvelle application n'a pas encore été ouverte ③ sent mais rien ne s'affiche → autorisation des notifications et réglages de batterie du téléphone, application « arrêtée de force » ? ④ SENDER_ID_MISMATCH … Registrations are kept → la clé du serveur et le google-services.json de l'application viennent de projets différents ⑤ answered 404 without naming the token → FCM_PROJECT_ID est erroné ⑥ refused an access token → la clé a été révoquée ou appartient à un autre projet (pas de nouvelle demande pendant 5 minutes)
Aucune notification sur iPhone① Le journal indique iPhone push notifications are off → chemin de APNS_KEY, permissions et nom du fichier (AuthKey_<ID de clé>.p8) ② Pas d'enregistrement type 1 → les notifications n'ont pas été autorisées dans l'application, ou c'est le simulateur ③ Enregistrement présent mais rien ne part → ce build est inférieur à APNS_MIN_BUILD (le build de la ligne d'enregistrement), ou la conversation est en sourdine ④ APNs refused the provider token → la clé a été révoquée, ou APNS_TEAM_ID n'est pas l'équipe de la clé (pas de nouvel envoi pendant 5 minutes) ⑤ DeviceTokenNotForTopic … Registrations are kept → APNS_TOPIC diffère du bundle ID de l'application ⑥ sent mais seul « You have a new notification » s'affiche → l'extension de notification n'a pas pu déchiffrer (si vous avez réinstallé l'application, ouvrez-la une fois pour qu'elle s'enregistre à nouveau) ⑦ Les appels n'arrivent qu'en notifications → APNS_VOIP_MIN_BUILD est vide, ou le build lui est inférieur (comportement voulu)
Impossible d'accéder à la page d'administrationLe tunnel SSH est-il actif ? Un autre programme utilise-t-il le port 8080 de votre ordinateur ? (passez à -L 18080:127.0.0.1:8080 et ouvrez localhost:18080)
network ssemiya-net declared as external, but could not be foundLe réseau n'existe pas encore → docker network create ssemiya-net (ou un autre nom via PABAL_NETWORK dans .env)

Limites connues

  • Configuration à serveur unique. Au démarrage, tous les comptes, conversations et messages sont chargés en mémoire. La mise à l'échelle horizontale sur plusieurs machines n'est pas encore possible : quand les données augmentent, il faut ajouter de la mémoire.
  • Les codes d'inscription en attente, les mises à jour non récupérées des bots et les textes de « Garder la conversation » en attente d'écriture sont en mémoire et disparaissent au redémarrage du serveur.
  • Les demandes de code d'inscription sont soumises à un intervalle minimal et à une limite quotidienne, mais il n'existe pas encore de limitation de débit pour les autres requêtes. Juste après l'ouverture au public, surveillez souvent les journaux et le tableau de bord.
  • Les notifications push ne vont qu'aux applications pour téléphone — Android (étape 10) et iPhone (étape 11). Sur iPhone, elles ne vont qu'aux builds égaux ou supérieurs à APNS_MIN_BUILD (Pabal 1.0 (3) en est exclu), et l'écran CallKit des appels ne s'active qu'une fois APNS_VOIP_MIN_BUILD défini. Les applications macOS et Windows reçoivent les messages tant qu'elles sont lancées.
  • La désactivation des notifications d'une conversation n'est gardée qu'en mémoire sur le serveur. Les push des conversations en sourdine partent vers Android en priorité non urgente (le téléphone filtre lui-même ces notifications) et ne partent pas du tout vers l'iPhone ; mais après un redémarrage du serveur, les deux repartent normalement (l'application ne renvoie ce réglage au serveur que lorsque vous le modifiez). Si une application en veille met plus de 20 secondes à se réveiller et à joindre le serveur, l'appel peut se terminer côté appelant comme un appel sans réponse.
  • Les messages acceptent les photos et les fichiers en pièce jointe ; les vidéos et messages vocaux qui se lisent dans l'application ne sont pas encore disponibles. Canaux, supergroupes, appels de groupe et validation en deux étapes ne le sont pas non plus.
  • Les clés d'autorisation sont stockées en clair dans la base de données (le serveur en a besoin pour déchiffrer). Protégez la base et les sauvegardes aussi soigneusement que la clé RSA.

Fichiers

FichierRôle
deploy/docker-compose.ymlConfiguration de production (pabal-server · pabal-postgres · pabal-caddy, bind mounts)
deploy/CaddyfileHTTPS, blocage des chemins d'administration, transmission du site, de la documentation et de la Bot API
deploy/.env.exampleModèle de configuration → deploy/.env
deploy/backup.shDump de la base + archive des clés et des données, nettoyage des anciennes sauvegardes
DockerfileImage du serveur (compilation → JRE 21, uid 1000)
voice/Passerelle vocale pabal-voice (sources, Dockerfile, README.md, spécification de la liaison PROTOCOL.md)
deploy/docker-compose.mcp.ymlLes trois conteneurs MCP des bots vocaux (profil mcp)
deploy/docker-compose.voice.ymlAjoute les composants des bots vocaux — les réglages vocaux du serveur et le réseau de liaison, la passerelle pabal-voice (profil voice) et le point d'entrée audio de la montre pabal-voice-edge (profil voice-watch)
deploy/ship.shConstruit sur une autre machine l'image du serveur (avec --voice, celle de la passerelle aussi) pour le processeur du serveur, la charge par SSH et copie les fichiers de déploiement
deploy/voice_readiness.pyVérifications avant d'activer la voix (clé Vertex, sections juridiques, versions des applications, relais, …) — n'affiche aucun secret
deploy/turnserver.conf.example, deploy/check_relay.pyModèle de configuration de coturn installé directement sur un serveur Mac, outil de vérification du relais