Menjalankan server
Instalasi server
Cara memasang dan menjalankan server Pabal dengan Docker Compose di satu server yang terbuka ke internet.
Dokumen ini menjelaskan cara memasang server Pabal di satu server yang terbuka ke internet. Dengan Docker Compose, Anda menjalankan tiga kontainer — server Pabal · PostgreSQL · Caddy (HTTPS) — dan semua data yang tidak boleh hilang di-bind ke direktori host. Cukup ikuti langkah-langkahnya secara berurutan dari atas.
Domain pabal.me, IP publik server 203.0.113.10 (alamat untuk contoh — ganti dengan IP Anda yang sebenarnya saat membaca), lokasi kode sumber /opt/pabal, lokasi data /srv/pabal, sistem operasi Ubuntu 24.04 LTS. Berkas yang diperlukan ada di folder deploy/ dalam repositori.
Gambaran sekilas
Penjelasan diagram
- Tiga area: pengguna di internet → tiga kontainer di server produksi → direktori bind di disk host. Kontainer bisa dihapus dan dibuat ulang kapan saja; semua yang harus bertahan ada di
/srv/pabal. - Hanya ada dua pintu masuk: aplikasi langsung tersambung ke server lewat port MTProto (terenkripsi sendiri, jadi tidak perlu HTTPS), sedangkan situs web, dokumentasi, dan Bot API melewati port 443 milik Caddy. Caddy memblokir jalur administrasi (
/admin,/health…) dan hanya meneruskan sisanya. - Garis putus-putus merah adalah jalur khusus operator. Halaman admin hanya terbuka di
127.0.0.1:8080milik server, sehingga hanya bisa dimasuki lewat tunnel SSH. PostgreSQL sama sekali tidak punya port yang terbuka ke luar. - Dua kotak merah adalah data yang paling penting. Kunci RSA di
pabal_server/keysditanam ke dalam aplikasi saat build, jadi jika hilang Anda harus mendistribusikan ulang semua aplikasi;postgres_data/berisi semua akun dan obrolan. Keduanya prioritas pertama untuk dicadangkan. - Panah abu-abu adalah penyimpanan: server menulis kunci, foto, pengaturan, dan log; PostgreSQL menulis event; dan Caddy menulis sertifikat, masing-masing ke direktorinya sendiri.
Persiapan
| Butir | Isi |
|---|---|
| Server | Ubuntu 24.04 LTS, untuk permulaan 2 vCPU · 4 GB RAM · 40 GB SSD. Jika pengguna dan pesan bertambah, tambahkan memori terlebih dahulu |
| IPv4 publik | IP statis. Aplikasi dibangun dengan alamat server berupa IP, dan server juga memberi tahu aplikasi alamatnya dalam bentuk IP |
| Domain | Satu domain yang DNS-nya bisa Anda ubah (record A) |
| Sarana pengiriman kode pendaftaran | Satu akun SMS (Twilio · Solapi · webhook) atau email (SMTP) |
| Mac | Mac untuk membangun aplikasi (Pabal.app) bagi server ini |
Port
| Port | Siapa | Dibuka untuk | Deskripsi |
|---|---|---|---|
| 22/tcp | Operator | Disarankan hanya IP operator | SSH |
| 80/tcp | Caddy | Semua | Penerbitan sertifikat · pengalihan ke HTTPS |
| 443/tcp, 443/udp | Caddy | Semua | Situs web · dokumentasi · Bot API (udp untuk HTTP/3) |
| 8443/tcp | Server | Semua | Koneksi aplikasi (MTProto) |
| 8080/tcp | Server | Jangan dibuka | Halaman admin · pemeriksaan kesehatan — hanya di 127.0.0.1 milik server |
| 5432/tcp | PostgreSQL | Jangan dibuka | Hanya di jaringan internal kontainer |
Port 443 dipakai oleh situs web (HTTPS). MTProto bukan HTTP dan bukan TLS, jadi keduanya tidak bisa dibagi dalam satu port. Jika Anda ingin aplikasi juga diterima di 443 karena jaringan kantor atau kampus yang memblokir port tak lazim, ambil satu IP lagi dan berikan port 443 di IP itu kepada server (di .env, isi PUBLIC_IP dengan IP kedua dan MTPROTO_PORT=443, lalu bedakan dengan menambahkan IP pada port di berkas compose). Server memberi tahu aplikasi "sambungkan ke port ini di IP ini", jadi port aplikasi di dalam dan di luar kontainer dibuat sama.
1. Menyiapkan server
sudo apt update && sudo apt -y upgrade
timedatectl # pastikan tertulis "System clock synchronized: yes"
Sinkronisasi waktu wajib. Nomor pesan MTProto diturunkan dari waktu, jadi jika jam server meleset, aplikasi akan terus mengulang koneksi. Jika no, jalankan sudo timedatectl set-ntp true.
# firewall
sudo ufw allow OpenSSH # jika memungkinkan: sudo ufw allow from <IP_operator> 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 (skrip instalasi resmi)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER # setelah login ulang, docker bisa dipakai tanpa sudo
docker compose version # v2 atau lebih baru
Port yang dibuka Docker (ports:) terbuka tanpa memedulikan aturan ufw. Karena itu berkas compose hanya membuka port publik (80, 443, 8443), membuka port admin hanya sebagai 127.0.0.1:8080, dan tidak membuka PostgreSQL. Ingat hal ini saat mengubah ports:.
2. Mengambil kode sumber
sudo mkdir -p /opt/pabal && sudo chown $USER: /opt/pabal
git clone <alamat_repositori> /opt/pabal
Jika Anda menyalinnya dari komputer pengembangan, jangan ikut kirim kunci, data, dan log (rsync --exclude 'keys/' --exclude 'data/' --exclude 'logs/' --exclude '**/target/'). Kunci RSA server produksi dibuat baru oleh server produksi saat pertama kali menyala — jika kunci pengembangan dipakai di produksi, komputer pengembangan itu menjadi tempat yang bisa membuka lalu lintas produksi.
3. Direktori data (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 # kontainer server berjalan sebagai uid 1000
sudo chown -R $USER: /srv/pabal/backups
sudo chmod 700 /srv/pabal/pabal_server/keys /srv/pabal/backups
| Direktori | Di dalam kontainer | Isinya | Jika hilang |
|---|---|---|---|
pabal_server/keys | /app/keys | private.pem (kunci privat RSA server), private.pem.pub | Semua aplikasi harus dibangun dan didistribusikan ulang |
pabal_server/data | /app/data | Foto (media/), admin-token, operations.json (pengaturan kode pendaftaran, SMS, dan SMTP, termasuk nilai rahasia) | Foto dan pengaturan operasional |
pabal_server/logs | /app/logs | Log server (dikompres setiap hari) | Hanya catatan |
postgres_data | /var/lib/postgresql/data | Semua akun, obrolan, pesan, status masuk, bot, dan pengaturan webhook | Seluruh layanan |
caddy/data, caddy/config | /data, /config | Sertifikat HTTPS | Diterbitkan ulang |
backups | (khusus host) | Hasil backup.sh | — |
4. Berkas pengaturan .env
cd /opt/pabal/deploy
cp .env.example .env
chmod 600 .env
openssl rand -base64 30 | tr -d '/+=' | cut -c1-32 # masukkan hasilnya ke POSTGRES_PASSWORD
nano .env
DOMAIN=pabal.me
PUBLIC_IP=203.0.113.10
POSTGRES_PASSWORD=(nilai yang dibuat di atas)
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"
| Nilai | Yang diisi | Catatan |
|---|---|---|
DOMAIN | Domain situs web | Tautan yang dibuat aplikasi (pabal.me/nama_pengguna), alamat di dokumentasi, dan pratinjau tautan juga memakai alamat ini |
PUBLIC_IP | IPv4 publik server | Di cloud, IP publik yang tampil di konsol (bukan IP privat di dalam server) |
POSTGRES_PASSWORD | Nilai acak | Tentukan sebelum menyalakan pertama kali. PostgreSQL hanya memakai nilai ini saat pertama kali membuat direktori data |
PABAL_HOME | /srv/pabal | Direktori dari langkah 3 |
PABAL_SOURCE | .. | Repositori tempat image server dibangun: .. jika file compose tetap di deploy/ dalam repositori; jika tidak, path ke kode sumber |
PABAL_NETWORK | ssemiya-net | Jaringan Docker tempat ketiga kontainer bergabung. Buat terlebih dahulu (langkah 6) |
MTPROTO_PORT | 8443 | Jika diubah, ubah juga firewall dan build aplikasi |
ADMIN_TOKEN | Biarkan kosong | Jika kosong, token dibuat di pabal_server/data/admin-token saat pertama kali menyala. Jika ingin menentukannya sendiri, minimal 16 karakter |
JAVA_OPTS | Nilai bawaan | Jika memori kurang, naikkan -Xmx (hingga sekitar setengah memori server) |
Berkas compose produksi menyalakan server dengan nomor uji dimatikan (TELEGRAM_TEST_NUMBERS=false). Daftar lengkap nilai pengaturan yang dibaca server ada di Administrasi dan pengaturan — variabel lingkungan.
5. DNS
| Nama | Jenis | Nilai |
|---|---|---|
pabal.me | A | 203.0.113.10 |
www.pabal.me | A | 203.0.113.10 |
dig +short pabal.me # harus muncul 203.0.113.10 (penerapannya butuh beberapa menit hingga beberapa jam)
Server boleh dinyalakan sebelum DNS mengarah ke server. Caddy akan terus mencoba ulang sampai berhasil mendapatkan sertifikat.
6. Build dan nyalakan
cd /opt/pabal/deploy
docker network inspect ssemiya-net >/dev/null 2>&1 || docker network create ssemiya-net # cukup sekali
docker compose up -d --build # pertama kali butuh beberapa menit (Maven mengunduh pustaka)
docker compose ps # pabal-server, pabal-postgres, pabal-caddy berstatus running (healthy)
docker compose logs -f pabal-server # keluar dengan Ctrl+C
Saat pertama kali menyala, baris-baris seperti ini muncul di log.
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/)
- Baris pertama berupa WARN, tetapi itu normal (pemberitahuan bahwa kunci dibuat hanya sekali di awal). Selanjutnya yang muncul adalah
Loaded RSA key … (fingerprint …). Fingerprint berbeda untuk setiap server. - Tabel dibuat sendiri oleh server saat menyala.
7. Pemeriksaan
# dari luar (komputer Anda)
curl -sI https://pabal.me/ | head -1 # HTTP/2 200
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/docs/ # 200 — dokumentasi ini
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/admin/ # 404 — halaman admin tidak dibuka untuk publik
curl -s https://pabal.me/docs/server-key.pem | head -1 # -----BEGIN RSA PUBLIC KEY-----
nc -vz 203.0.113.10 8443 # succeeded — port aplikasi
# di server
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. Halaman admin dan pengaturan pertama
# di komputer Anda (biarkan tetap berjalan)
ssh -N -L 8080:127.0.0.1:8080 <pengguna>@203.0.113.10
# token diambil di server
sudo cat /srv/pabal/pabal_server/data/admin-token
Buka http://localhost:8080/admin/ di peramban dan masukkan token. Pengaturan yang wajib dilakukan sebelum beroperasi (rinciannya di Administrasi dan pengaturan):
- Atur Cara pengiriman kode (코드 전달 방식) ke SMS atau email. Dengan cara "Halaman admin" (관리 화면), operator harus memberitahukan kode satu per satu.
- Isi nilai SMS atau Email (SMTP) (이메일 (SMTP)) lalu simpan → pastikan kode benar-benar terkirim dengan Kirim uji (테스트 발송).
- Pastikan Nomor uji (테스트 번호) dimatikan. Jika dinyalakan, siapa pun bisa masuk dengan nomor
+99966…. - Jika perlu, matikan Izinkan pendaftaran baru (새 가입 허용) di awal, lalu terima dulu hanya orang yang Anda undang.
9. Menghubungkan aplikasi ke server ini
Aplikasi dibangun dengan IP, port, dan kunci publik server tertanam di dalamnya. Aplikasi hanya bisa tersambung ke server ini jika dibangun ulang dengan kunci publik server ini. Di Mac:
# 1. kunci publik server ini (kunci publik, bukan kunci privat — boleh juga diunduh dari situs dokumentasi)
curl -s -o keys/production.pem.pub https://pabal.me/docs/server-key.pem
# 2. arahkan kode sumber aplikasi ke server ini + terapkan branding
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
Setelah itu, jalankan langkah 5 (configure) dan langkah 6 (build) di docs/tdesktop-build-guide.md dalam repositori. Salin hasilnya, out/Debug/Pabal.app, dengan nama lain — jika kode sumber yang sama dibangun ulang untuk server lain, hasilnya akan tertimpa.
Build saat ini adalah build debug tanpa tanda tangan dan notarisasi, jadi penerima harus membukanya dengan klik kanan → Buka saat pertama kali. Build debug menyimpan data di folder di samping aplikasi (tdata/), dan jika tidak bisa menulis di sana, ia memakai folder data yang sama dengan Telegram Desktop asli. Arahkan pengguna untuk menaruh aplikasi di folder pengguna (misalnya ~/Applications/Pabal/). Build rilis untuk distribusi umum (folder data terpisah, tanda tangan dan notarisasi Apple) adalah pekerjaan tersendiri.
Operasional
Status dan log
cd /opt/pabal/deploy
docker compose ps
docker compose logs --since 1h pabal-server
tail -f /srv/pabal/pabal_server/logs/telegram-server.log
Pembaruan
cd /opt/pabal && git pull
cd deploy
./backup.sh # cadangkan dulu
docker compose up -d --build pabal-server # ganti server saja dengan image baru
docker compose logs --since 5m pabal-server | grep -E "Website|ERROR"
Selama beberapa puluh detik server menyala ulang, aplikasi terputus lalu tersambung kembali dengan sendirinya tanpa perlu masuk lagi. Perubahan tabel diterapkan otomatis saat server menyala. Direktori bind tidak bergantung pada image, jadi data tidak terhapus oleh docker compose down atau penghapusan image — asalkan Anda tidak menghapus /srv/pabal.
Pencadangan
./backup.sh # → /srv/pabal/backups/<tanggal-waktu>/{pabal.dump, server-keys-data.tar.gz}
crontab -e # setiap hari pukul 03:00:
# 0 3 * * * /opt/pabal/deploy/backup.sh >> /srv/pabal/backups/backup.log 2>&1
- Cadangan yang berumur lebih dari 14 hari dihapus otomatis (bisa diubah, misalnya
KEEP_DAYS=30 ./backup.sh). - Salin juga ke tempat lain. Cadangan di disk yang sama tidak selamat jika disk itu rusak.
- Cadangan berisi kunci privat RSA serta nilai rahasia SMS dan SMTP. Jaga tempat penyimpanannya seketat server itu sendiri.
Pemulihan
cd /opt/pabal/deploy
B=/srv/pabal/backups/20260920-030000 # cadangan yang akan dipulihkan
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
Hal yang sama berlaku saat pindah ke server baru: lakukan langkah 1–5, lalu jalankan pemulihan di atas sebagai ganti langkah 6, dan nyalakan. Jika kunci yang sama dipulihkan, aplikasi tidak perlu dibangun ulang (kecuali IP server berubah).
Menyalakan dan mematikan
docker compose restart pabal-server # nyalakan ulang server saja
docker compose stop # hentikan semuanya (data tetap utuh)
docker compose up -d # nyalakan lagi
Kontainer memakai restart: unless-stopped, jadi menyala sendiri meskipun server di-reboot.
Daftar periksa keamanan
- Izin
.env600,POSTGRES_PASSWORDberupa nilai acak - Izin
/srv/pabal/pabal_server/keys700, dan kunci privat tidak ada di tempat lain selain cadangan - Firewall: hanya 22 (sebisa mungkin hanya IP operator), 80, 443, dan 8443
https://domain/admin/menghasilkan 404 dari luar- Halaman admin: Nomor uji (테스트 번호) mati, Cara pengiriman kode (코드 전달 방식) SMS/email, Kirim uji (테스트 발송) berhasil
TELEGRAM_WEBHOOK_ALLOW_LOCALtidak dinyalakan (agar webhook bot tidak bisa menuju jaringan internal)- SSH: matikan login dengan kata sandi, hanya dengan kunci
- Cron cadangan dan salinan eksternal sudah diperiksa, dan pemulihan sudah dilatih sekali
- Pembaruan keamanan otomatis OS server (
unattended-upgrades)
Pemecahan masalah
| Gejala | Penyebab → solusi |
|---|---|
| Aplikasi tidak pernah lewat dari "Connecting…" | ① 8443 terblokir → nc -vz IP 8443, periksa ufw dan security group cloud ② aplikasi dibangun dengan kunci publik lain → ulangi langkah 9 dengan kunci server ini ③ PUBLIC_IP salah → perbaiki .env lalu docker compose up -d pabal-server |
| Awalnya tersambung, tetapi segera terputus | Aplikasi berpindah ke alamat yang diberitahukan server (PUBLIC_IP:MTPROTO_PORT), tetapi alamat itu salah → periksa PUBLIC_IP |
| Error sertifikat HTTPS | DNS belum mengarah ke server ini, atau port 80 terblokir → dig +short domain, docker compose logs pabal-caddy |
server terus menyala ulang, password authentication failed | POSTGRES_PASSWORD diubah setelah data dibuat → kembalikan ke nilai semula, atau jalankan ALTER USER pabal PASSWORD '…' di dalam DB |
AccessDeniedException: /app/keys/… | Pemilik direktori bind → sudo chown -R 1000:1000 /srv/pabal/pabal_server |
OutOfMemoryError, menjadi lambat | Naikkan -Xmx di JAVA_OPTS lalu docker compose up -d pabal-server |
| Kode pendaftaran tidak datang | Lihat status pengiriman dan alasan kegagalan di tab Kode pendaftaran (가입 코드) pada halaman admin → periksa nilai SMS dan SMTP di tab Pengaturan (설정), lalu Kirim uji (테스트 발송) |
| Tidak bisa masuk ke halaman admin | Pastikan tunnel SSH berjalan, dan periksa apakah port 8080 di komputer Anda dipakai program lain (ganti dengan -L 18080:127.0.0.1:8080 lalu buka localhost:18080) |
network ssemiya-net declared as external, but could not be found | Jaringan belum dibuat → docker network create ssemiya-net (atau nama lain lewat PABAL_NETWORK di .env) |
Keterbatasan yang diketahui
- Ini susunan satu server. Saat menyala, server memuat semua akun, obrolan, dan pesan ke memori. Penskalaan horizontal ke beberapa server belum bisa, dan jika data bertambah, memori harus ditambah.
- Kode pendaftaran yang sedang menunggu dan update bot yang belum diambil ada di memori, jadi hilang saat server dimulai ulang.
- Permintaan kode pendaftaran punya jeda dan batas harian, tetapi belum ada pembatasan laju untuk permintaan lainnya secara keseluruhan. Sesaat setelah dibuka untuk publik, sering-seringlah memeriksa log dan dasbor.
- Belum ada aplikasi ponsel dan notifikasi push. Lampiran pesan hanya foto. Kanal, supergrup, dan verifikasi dua langkah belum tersedia.
- Auth key tersimpan di DB sebagai teks biasa (diperlukan agar server bisa mendekripsi). Jaga DB dan cadangannya seketat kunci RSA.
Berkas
| Berkas | Peran |
|---|---|
deploy/docker-compose.yml | Susunan produksi (pabal-server · pabal-postgres · pabal-caddy, bind mount) |
deploy/Caddyfile | HTTPS, pemblokiran jalur admin, penerusan situs, dokumentasi, dan Bot API |
deploy/.env.example | Contoh pengaturan → deploy/.env |
deploy/backup.sh | Dump DB + arsip kunci dan data, pembersihan cadangan lama |
Dockerfile | Image server (build → JRE 21, uid 1000) |