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.
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
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:8080du 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/keysest 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ément | Détails |
|---|---|
| Serveur | Ubuntu 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 publique | IP fixe. L'application est compilée avec l'adresse IP du serveur, et le serveur communique lui aussi son IP à l'application |
| Domaine | Un domaine dont vous pouvez modifier le DNS (enregistrement A) |
| Moyen d'envoi des codes d'inscription | Un compte SMS (Twilio · Solapi · webhook) ou e-mail (SMTP) |
| Mac | Un Mac pour compiler l'application (Pabal.app) pour ce serveur |
Ports
| Port | Qui | Ouverture | Description |
|---|---|---|---|
| 22/tcp | Opérateur | IP de l'opérateur uniquement (conseillé) | SSH |
| 80/tcp | Caddy | Tout le monde | Émission des certificats · redirection vers HTTPS |
| 443/tcp, 443/udp | Caddy | Tout le monde | Site web · documentation · Bot API (udp pour HTTP/3) |
| 8443/tcp | Serveur | Tout le monde | Connexion de l'application (MTProto) |
| 8080/tcp | Serveur | Ne pas ouvrir | Page d'administration · contrôle de santé — uniquement sur 127.0.0.1 du serveur |
| 5432/tcp | PostgreSQL | Ne pas ouvrir | Uniquement sur le réseau interne des conteneurs |
Le port 443 est utilisé par le site web (HTTPS). MTProto n'étant ni du HTTP ni du TLS, on ne peut pas le distinguer 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
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épertoire | Dans le conteneur | Contenu | En cas de perte |
|---|---|---|---|
pabal_server/keys | /app/keys | private.pem (clé privée RSA du serveur), private.pem.pub | Recompiler et redéployer toutes les applications |
pabal_server/data | /app/data | Photos (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/logs | Journaux du serveur (compressés chaque jour) | Seulement l'historique |
postgres_data | /var/lib/postgresql/data | Comptes, conversations, messages, connexions, bots, configuration des webhooks : tout | Tout le service |
caddy/data, caddy/config | /data, /config | Certificats 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"
| Valeur | À indiquer | Remarques |
|---|---|---|
DOMAIN | Domaine du site web | Les 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_IP | IPv4 publique du serveur | Sur un cloud, l'IP publique affichée dans la console (et non l'IP privée vue depuis le serveur) |
POSTGRES_PASSWORD | Valeur aléatoire | Dé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/pabal | Le 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_NETWORK | ssemiya-net | Le réseau Docker que rejoignent les trois conteneurs. À créer au préalable (étape 6) |
MTPROTO_PORT | 8443 | Si vous le changez, changez aussi le pare-feu et la compilation de l'application |
ADMIN_TOKEN | Laisser vide | Vide, il est créé au premier démarrage dans pabal_server/data/admin-token. Pour le choisir vous-même : 16 caractères minimum |
JAVA_OPTS | Valeur par défaut | Si la mémoire manque, augmentez -Xmx (jusqu'à environ la moitié de la mémoire du serveur) |
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
| Nom | Type | Valeur |
|---|---|---|
pabal.me | A | 203.0.113.10 |
www.pabal.me | A | 203.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) :
- 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.
- Saisissez les valeurs SMS ou e-mail (SMTP), enregistrez, puis vérifiez avec Envoi de test (테스트 발송) que les codes arrivent vraiment.
- 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…. - 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é.
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.
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.
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
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é
.enven permissions 600,POSTGRES_PASSWORDaléatoire/srv/pabal/pabal_server/keysen permissions 700, clé privée présente nulle part ailleurs que dans les sauvegardes- 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_LOCALnon activé (pour que les webhooks des bots ne puissent pas atteindre le réseau interne)- 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ôme | Cause → 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 rapidement | L'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 HTTPS | Le DNS ne pointe pas encore vers ce serveur ou le port 80 est bloqué → dig +short domaine, docker compose logs pabal-caddy |
server redémarre sans cesse, password authentication failed | POSTGRES_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, lenteurs | Augmentez -Xmx dans JAVA_OPTS, puis docker compose up -d pabal-server |
| Les codes d'inscription n'arrivent pas | Onglet 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 |
| Impossible d'accéder à la page d'administration | Le 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 found | Le 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 et les mises à jour non récupérées des bots 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.
- Pas d'application mobile ni de notifications push. Seules les photos peuvent être jointes aux messages. Canaux, supergroupes et validation en deux étapes ne sont pas encore disponibles.
- 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
| Fichier | Rôle |
|---|---|
deploy/docker-compose.yml | Configuration de production (pabal-server · pabal-postgres · pabal-caddy, bind mounts) |
deploy/Caddyfile | HTTPS, blocage des chemins d'administration, transmission du site, de la documentation et de la Bot API |
deploy/.env.example | Modèle de configuration → deploy/.env |
deploy/backup.sh | Dump de la base + archive des clés et des données, nettoyage des anciennes sauvegardes |
Dockerfile | Image du serveur (compilation → JRE 21, uid 1000) |