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 l'application (Pabal.app) pour ce serveur

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, 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
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.pubRecompiler et redéployer toutes les applications
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"
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)

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.

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é

  • .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
  • 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)
  • 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
server redémarre sans cesse, 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
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 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

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)