開発者ドキュメント
日本語

サーバー運用

サーバーのインストール

Pabal サーバーを、インターネットに公開された 1 台のサーバーに Docker Compose でインストールして運用する方法。

Pabal サーバーを、インターネットに公開された 1 台のサーバーにインストールする方法です。Docker Compose で Pabal サーバー · PostgreSQL · Caddy(HTTPS)の 3 つのコンテナを起動し、消えてはならないデータはすべてホストのディレクトリにバインドします。上から順に進めてください。

このドキュメントの例の値

ドメイン pabal.me、サーバーのグローバル IP 203.0.113.10(説明用のアドレス — 実際の IP に読み替えてください)、ソースの場所 /opt/pabal、データの場所 /srv/pabal、OS は Ubuntu 24.04 LTS。必要なファイルは、リポジトリの deploy/ フォルダーにあります。

全体像

インターネット docker compose · ssemiya-net /srv/pabal (バインドマウント) Pabal アプリPabal.app ブラウザー · ボットサイト · Bot API 運営者SSH pabal-server MTProto :8443 Web · Bot API · 管理 :8080 uid 1000 · JRE 21 Webhook → ボット (HTTPS) pabal-caddy:80 · :443 証明書自動 pabal-postgres 16内部網専用 · ポートなし pabal_server/keys · RSA 鍵 pabal_server/data · 写真·設定 pabal_server/logs postgres_data/ · すべての会話 caddy/ · 証明書 backups/ · backup.sh MTProto :8443 (独自暗号化) HTTPS :443 SSH トンネル → 127.0.0.1:8080/admin/
サーバー 1 台、コンテナ 3 つ、残すべきものはすべて /srv/pabal に

図の説明

  • 3 つの領域:インターネット上のユーザー → 本番サーバーの 3 つのコンテナ → ホストのディスク上のバインドディレクトリ。コンテナはいつでも削除して作り直せ、残すべきものはすべて /srv/pabalあります。
  • 入口は 2 つだけです:アプリは MTProto ポートでサーバーに直接接続し(独自に暗号化するため HTTPS は不要)、Web サイト・ドキュメント・Bot API は Caddy の 443 を経由します。Caddy は管理用のパス(/admin/health …)をブロックし、それ以外だけを転送します。
  • 赤い点線は運営者だけの経路です。管理画面はサーバーの 127.0.0.1:8080 でしか開いていないため、SSH トンネル経由でのみ入れます。PostgreSQL には、外部に公開するポートがまったくありません。
  • 赤い 2 つの枠が最も重要なデータです。pabal_server/keys の RSA 鍵はアプリに組み込まれてビルドされるため、失うとすべてのアプリを配布し直す必要があり、postgres_data/ にはすべてのアカウントと会話があります。バックアップの最優先対象です。
  • 灰色の矢印は保存を表します:サーバーは鍵・写真・設定・ログを、PostgreSQL はイベントを、Caddy は証明書を、それぞれのディレクトリに書き込みます。

用意するもの

項目内容
サーバーUbuntu 24.04 LTS、開始時の目安は 2 vCPU · 4 GB RAM · 40 GB SSD。ユーザーやメッセージが増えたら、まずメモリを増やしてください
グローバル IPv4固定 IP。アプリはサーバーのアドレスを IP として組み込んでビルドされ、サーバーもアプリに IP で通知します
ドメインDNS を変更できるドメイン 1 つ(A レコード)
登録コードの配信手段SMS(Twilio · Solapi · Webhook)またはメール(SMTP)のアカウント 1 つ
Macアプリ(Pabal.app)をこのサーバー用にビルドするための Mac

ポート

ポート利用者開放説明
22/tcp運営者運営者の IP のみを推奨SSH
80/tcpCaddyすべて証明書の発行 · HTTPS へのリダイレクト
443/tcp, 443/udpCaddyすべてWeb サイト · ドキュメント · Bot API(udp は HTTP/3)
8443/tcpサーバーすべてアプリの接続(MTProto)
8080/tcpサーバー開放しない管理画面 · ヘルスチェック — サーバーの 127.0.0.1 のみ
5432/tcpPostgreSQL開放しないコンテナの内部ネットワークのみ
アプリのポートが 443 ではない理由

443 は Web サイト(HTTPS)が使います。MTProto は HTTP でも TLS でもないため、1 つのポートで振り分けて受けることはできません。珍しいポートをブロックする会社や学校のネットワークのために、アプリも 443 で受けたい場合は、IP をもう 1 つ取得し、その IP の 443 をサーバーに割り当ててください(.envPUBLIC_IP を 2 つ目の IP、MTPROTO_PORT=443 にし、Compose ファイルのポートに IP を付けて分ける)。サーバーはアプリに「この IP のこのポートに接続せよ」と通知するため、コンテナの内外でアプリのポートを同じにしておきます。

1. サーバーの準備

sudo apt update && sudo apt -y upgrade
timedatectl                      # "System clock synchronized: yes" になっているか確認

時刻同期は必須です。MTProto のメッセージ番号は時刻から作られるため、サーバーの時計がずれると、アプリが接続を繰り返します。no なら sudo timedatectl set-ntp true を実行します。

# ファイアウォール
sudo ufw allow OpenSSH           # 可能なら: sudo ufw allow from <運営者の IP> 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(公式インストールスクリプト)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER     # 再ログインすると sudo なしで docker を使える
docker compose version            # v2 以上
Docker と ufw

Docker が開く(ports:)ポートは、ufw のルールに関係なく開きます。そのため Compose ファイルでは、公開するポート(80・443・8443)だけを開き、管理ポートは 127.0.0.1:8080 でのみ開き、PostgreSQL は開きません。ports: を変更するときは、このことを覚えておいてください。

2. ソースの取得

sudo mkdir -p /opt/pabal && sudo chown $USER: /opt/pabal
git clone <リポジトリの URL> /opt/pabal

開発用のコンピューターからコピーする場合は、鍵・データ・ログは送らないでくださいrsync --exclude 'keys/' --exclude 'data/' --exclude 'logs/' --exclude '**/target/')。本番サーバーの RSA 鍵は、本番サーバーの初回起動時に新しく作られます — 開発用の鍵を本番で使うと、その開発用コンピューターが本番のトラフィックを復号できる場所になってしまいます。

3. データディレクトリ(バインドマウント)

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        # サーバーのコンテナは uid 1000 で動作します
sudo chown -R $USER: /srv/pabal/backups
sudo chmod 700 /srv/pabal/pabal_server/keys /srv/pabal/backups
ディレクトリコンテナ内中身失った場合
pabal_server/keys/app/keysprivate.pem(サーバーの RSA 秘密鍵)、private.pem.pubすべてのアプリを再ビルド・再配布
pabal_server/data/app/data写真(media/)、admin-tokenoperations.json(登録コード・SMS・SMTP の設定、シークレットを含む)写真・運用設定
pabal_server/logs/app/logsサーバーログ(日ごとに圧縮)記録のみ
postgres_data/var/lib/postgresql/dataアカウント・会話・メッセージ・ログイン・ボット・Webhook 設定のすべてサービス全体
caddy/data, caddy/config/data, /configHTTPS 証明書再発行される
backups(ホスト専用)backup.sh の出力

4. 設定ファイル .env

cd /opt/pabal/deploy
cp .env.example .env
chmod 600 .env
openssl rand -base64 30 | tr -d '/+=' | cut -c1-32     # 出力された値を POSTGRES_PASSWORD に
nano .env
DOMAIN=pabal.me
PUBLIC_IP=203.0.113.10
POSTGRES_PASSWORD=(上で作った値)
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"
設定内容備考
DOMAINWeb サイトのドメインアプリが作るリンク(pabal.me/ユーザー名)、ドキュメントのアドレス、リンクプレビューもこのアドレス
PUBLIC_IPサーバーのグローバル IPv4クラウドなら、コンソールに表示されるグローバル IP(サーバー内のプライベート IP ではない)
POSTGRES_PASSWORDランダムな値初回起動の前に決めてください。PostgreSQL は、データディレクトリを最初に作るときだけこの値を使います
PABAL_HOME/srv/pabal手順 3 のディレクトリ
PABAL_SOURCE..サーバーイメージをビルドするリポジトリ。compose ファイルがリポジトリの deploy/ にあれば ..、外に置くならソースのパス
PABAL_NETWORKssemiya-net3 つのコンテナが参加する Docker ネットワーク。事前に作成しておきます(手順 6)
MTPROTO_PORT8443変更する場合は、ファイアウォールとアプリのビルドも合わせて変更
ADMIN_TOKEN空のまま空にすると、初回起動時に pabal_server/data/admin-token に作られる。自分で決める場合は 16 文字以上
JAVA_OPTS既定値メモリが足りなければ -Xmx を上げてください(サーバーのメモリの半分程度まで)

本番用の Compose ファイルは、テスト番号を無効にした状態でTELEGRAM_TEST_NUMBERS=false)サーバーを起動します。サーバーが読み込む設定値の全体は、管理と設定 — 環境変数にあります。

5. DNS

名前種類
pabal.meA203.0.113.10
www.pabal.meA203.0.113.10
dig +short pabal.me        # 203.0.113.10 と表示されれば OK(反映には数分〜数時間)

DNS がサーバーを指す前に起動してもかまいません。Caddy が、証明書を取得できるまで自動で再試行します。

6. ビルドと起動

cd /opt/pabal/deploy
docker network inspect ssemiya-net >/dev/null 2>&1 || docker network create ssemiya-net   # 最初に一度だけ
docker compose up -d --build       # 初回は数分かかる(Maven がライブラリをダウンロードする)
docker compose ps                  # pabal-server、pabal-postgres、pabal-caddy が running (healthy)
docker compose logs -f pabal-server      # Ctrl+C で抜ける

初回起動時、ログに次のような行が出力されます。

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/)
  • 1 行目は WARN ですが、正常です(初回に一度だけ鍵を作るという通知)。次回からは Loaded RSA key … (fingerprint …) が出力されます。fingerprint はサーバーごとに異なります。
  • テーブルは、サーバーの起動時に自動で作られます。

7. 動作確認

# 外部(自分のコンピューター)から
curl -sI https://pabal.me/ | head -1                              # HTTP/2 200
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/docs/    # 200 — このドキュメント
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/admin/   # 404 — 管理画面は公開されない
curl -s https://pabal.me/docs/server-key.pem | head -1             # -----BEGIN RSA PUBLIC KEY-----
nc -vz 203.0.113.10 8443                                          # succeeded — アプリのポート

# サーバー上で
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. 管理画面と初期設定

# 自分のコンピューターで(起動したままにしておく)
ssh -N -L 8080:127.0.0.1:8080 <ユーザー>@203.0.113.10

# トークンはサーバー上で
sudo cat /srv/pabal/pabal_server/data/admin-token

ブラウザーで http://localhost:8080/admin/ を開き、トークンを入力します。管理画面の表示は韓国語なので、以下では項目名の後ろに韓国語の表記を添えています。運用前に必ず行う設定(詳しくは管理と設定):

  1. コードの配信方式코드 전달 방식)を SMS またはメールにする。「管理画面」(관리 화면)方式では、運営者がコードを 1 つずつ伝える必要があります。
  2. SMS またはメール(SMTP)이메일 (SMTP))の値を入力して保存 → テスト送信테스트 발송)で、実際に届くか確認する。
  3. テスト番号테스트 번호)が無効になっているか確認する。有効にすると、誰でも +99966… の番号でログインできてしまいます。
  4. 必要なら新規登録の許可새 가입 허용)をオフにして始め、招待する人だけを先に受け入れます。

9. アプリをこのサーバーに接続する

アプリはサーバーの IP · ポート · 公開鍵を組み込んでビルドされます。このサーバーの公開鍵で再ビルドしないと、このサーバーには接続できません。Mac で次を実行します:

# 1. このサーバーの公開鍵(秘密鍵ではなく公開鍵 — ドキュメントサイトからダウンロードしてもかまいません)
curl -s -o keys/production.pem.pub https://pabal.me/docs/server-key.pem

# 2. アプリのソースをこのサーバーに向ける + ブランドを適用
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

続いて、リポジトリの docs/tdesktop-build-guide.md の手順 5(configure)と手順 6(ビルド)を行います。できあがった out/Debug/Pabal.app は、別の名前でコピーしておいてください — 同じソースを別のサーバー用に再ビルドすると上書きされます。

アプリをほかの人に配布する前に

現在のビルドは署名・公証のないデバッグビルドなので、受け取った人ははじめて開くときに、右クリック → 開く を行う必要があります。デバッグビルドはアプリの隣のフォルダー(tdata/)にデータを置き、そこに書き込めない場合は、本物の Telegram Desktop と同じデータフォルダーを使います。アプリはユーザーのフォルダー(例:~/Applications/Pabal/)に置くよう案内してください。一般配布用のリリースビルド(データフォルダーの分離、Apple の署名・公証)は別の作業です。

運用

状態・ログ

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

アップデート

cd /opt/pabal && git pull
cd deploy
./backup.sh                                     # 先にバックアップ
docker compose up -d --build pabal-server             # 新しいイメージでサーバーだけを入れ替える
docker compose logs --since 5m pabal-server | grep -E "Website|ERROR"

サーバーが再起動する数十秒の間、アプリはいったん切断されますが、再ログインせずに自動で再接続します。テーブルの変更は、サーバーの起動時に自動で適用されます。バインドディレクトリはイメージとは無関係なので、docker compose down やイメージの削除でデータが消えることはありません — /srv/pabal さえ削除しなければ大丈夫です。

バックアップ

./backup.sh            # → /srv/pabal/backups/<日付-時刻>/{pabal.dump, server-keys-data.tar.gz}
crontab -e             # 毎日 03:00:
# 0 3 * * * /opt/pabal/deploy/backup.sh >> /srv/pabal/backups/backup.log 2>&1
  • 14 日を過ぎたバックアップは、自動で削除されます(KEEP_DAYS=30 ./backup.sh のように変更できます)。
  • 別の場所にもコピーしてください。同じディスク上のバックアップは、ディスクの故障には耐えられません。
  • バックアップには RSA 秘密鍵と SMS・SMTP のシークレットが含まれています。保管場所も、サーバーと同じくらい厳重に守ってください。

復元

cd /opt/pabal/deploy
B=/srv/pabal/backups/20260920-030000            # 復元するバックアップ
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

新しいサーバーに移行するときも同じです:手順 1〜5 を行ったあと、手順 6 の代わりに上の復元を行ってから起動します。同じ鍵を復元すれば、アプリを再ビルドする必要はありません(サーバーの IP が変わった場合は再ビルド)。

起動・停止

docker compose restart pabal-server       # サーバーだけを再起動
docker compose stop                 # すべて停止(データはそのまま)
docker compose up -d                # 再び起動

サーバーを再起動しても、コンテナは restart: unless-stopped なので自動で起動します。

セキュリティチェックリスト

  • .env の権限は 600、POSTGRES_PASSWORD はランダムな値
  • /srv/pabal/pabal_server/keys の権限は 700、秘密鍵がバックアップ以外の場所にない
  • ファイアウォール:22(可能なら運営者の IP のみ)・80・443・8443 のみ
  • 外部から https://ドメイン/admin/ が 404
  • 管理画面:テスト番号(테스트 번호)が無効、コードの配信方式(코드 전달 방식)が SMS/メール、テスト送信(테스트 발송)が成功
  • TELEGRAM_WEBHOOK_ALLOW_LOCAL を有効にしていない(ボットの Webhook が内部ネットワークへ送られないように)
  • SSH:パスワードログインを無効にし、鍵認証のみ
  • バックアップの cron と外部へのコピーを確認し、復元を一度練習しておく
  • サーバー OS のセキュリティアップデートの自動適用(unattended-upgrades

トラブルシューティング

症状原因 → 対処
アプリが「接続中…」から進まない① 8443 がブロックされている → nc -vz IP 8443、ufw・クラウドのセキュリティグループを確認 ② アプリを別の公開鍵でビルドした → 手順 9 をこのサーバーの鍵でやり直す ③ PUBLIC_IP が間違っている → .env を修正して docker compose up -d pabal-server
最初は接続できるが、すぐに切れるアプリはサーバーから通知されたアドレス(PUBLIC_IP:MTPROTO_PORT)に移るが、そのアドレスが間違っている → PUBLIC_IP を確認
HTTPS 証明書のエラーDNS がまだこのサーバーを指していないか、80 番ポートがブロックされている → dig +short ドメインdocker compose logs pabal-caddy
server が再起動を繰り返す、password authentication failedデータを作ったあとで POSTGRES_PASSWORD を変更した → 元の値に戻すか、DB 内で ALTER USER pabal PASSWORD '…'
AccessDeniedException: /app/keys/…バインドディレクトリの所有者 → sudo chown -R 1000:1000 /srv/pabal/pabal_server
OutOfMemoryError、動作が遅いJAVA_OPTS-Xmx を上げて docker compose up -d pabal-server
登録コードが届かない管理画面の登録コード가입 코드)タブで、送信状態・失敗の理由を確認 → 設定(설정)の SMS・SMTP の値、テスト送信(테스트 발송
管理画面に入れないSSH トンネルが起動しているか、自分のコンピューターの 8080 をほかのプログラムが使っていないか(-L 18080:127.0.0.1:8080 に変えて localhost:18080 で開く)
network ssemiya-net declared as external, but could not be foundネットワークがまだありません → docker network create ssemiya-net(別の名前なら .envPABAL_NETWORK

既知の制限

  • サーバー 1 台の構成です。起動時に、すべてのアカウント・会話・メッセージをメモリに読み込みます。複数台に分散する水平スケーリングはまだできず、データが増えたらメモリを増やす必要があります。
  • 待機中の登録コードや、ボットが未取得のアップデートはメモリ上にあるため、サーバーを再起動すると消えます。
  • 登録コードの要求には間隔と 1 日の上限がありますが、それ以外のリクエスト全体に対するレート制限はまだありません。公開直後は、ログとダッシュボード(대시보드)を頻繁に確認してください。
  • スマートフォンアプリとプッシュ通知はありません。メッセージの添付は写真のみです。チャンネル・スーパーグループ、2 段階認証はまだです。
  • 認証キーは DB に平文で保存されています(サーバーが復号するために必要)。DB とバックアップを、RSA 鍵と同じくらい厳重に守ってください。

ファイル

ファイル役割
deploy/docker-compose.yml本番構成(pabal-server · pabal-postgres · pabal-caddy、バインドマウント)
deploy/CaddyfileHTTPS、管理用パスのブロック、サイト・ドキュメント・Bot API の転送
deploy/.env.example設定のひな形 → deploy/.env
deploy/backup.shDB のダンプ + 鍵・データの保存、古いバックアップの整理
Dockerfileサーバーのイメージ(ビルド → JRE 21、uid 1000)