Dokumentasi pengembang
Bahasa Indonesia

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.

Nilai contoh dalam dokumen ini

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

Internet docker compose · ssemiya-net /srv/pabal (bind mount) Aplikasi PabalPabal.app Peramban · botsitus · Bot API OperatorSSH pabal-server MTProto :8443 web · Bot API · admin :8080 uid 1000 · JRE 21 webhook → bot (HTTPS) pabal-caddy:80 · :443 TLS otomatis pabal-postgres 16internal saja · tanpa port pabal_server/keys · kunci RSA pabal_server/data · foto pabal_server/logs postgres_data/ · obrolan caddy/ · sertifikat backups/ · backup.sh MTProto :8443 (enkripsi sendiri) HTTPS :443 tunnel SSH → 127.0.0.1:8080/admin/
Satu server, tiga kontainer, dan semua yang harus bertahan ada di /srv/pabal

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:8080 milik 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/keys ditanam 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

ButirIsi
ServerUbuntu 24.04 LTS, untuk permulaan 2 vCPU · 4 GB RAM · 40 GB SSD. Jika pengguna dan pesan bertambah, tambahkan memori terlebih dahulu
IPv4 publikIP statis. Aplikasi dibangun dengan alamat server berupa IP, dan server juga memberi tahu aplikasi alamatnya dalam bentuk IP
DomainSatu domain yang DNS-nya bisa Anda ubah (record A)
Sarana pengiriman kode pendaftaranSatu akun SMS (Twilio · Solapi · webhook) atau email (SMTP)
MacMac untuk membangun aplikasi bagi server ini (jika ingin membuat aplikasi Windows juga, perlu satu PC Windows)
Project Firebase (opsional)Untuk mengirim notifikasi push ke aplikasi Android. Dibuat gratis dengan akun Google (langkah 10)

Port

PortSiapaDibuka untukDeskripsi
22/tcpOperatorDisarankan hanya IP operatorSSH
80/tcpCaddySemuaPenerbitan sertifikat · pengalihan ke HTTPS
443/tcp, 443/udpCaddySemuaSitus web · dokumentasi · Bot API (udp untuk HTTP/3)
8443/tcpServerSemuaKoneksi aplikasi (MTProto)
8080/tcpServerJangan dibukaHalaman admin · pemeriksaan kesehatan — hanya di 127.0.0.1 milik server
5432/tcpPostgreSQLJangan dibukaHanya di jaringan internal kontainer
Mengapa port aplikasi bukan 443

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
Docker dan ufw

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 mengirim 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 jadi mampu mendekripsi 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
DirektoriDi dalam kontainerIsinyaJika hilang
pabal_server/keys/app/keysprivate.pem (kunci privat RSA server), private.pem.pub, (opsional) firebase-service-account.json (kunci rahasia Firebase untuk push Android)Semua aplikasi harus dibangun dan didistribusikan ulang (kunci Firebase cukup diunduh ulang dari konsol)
pabal_server/data/app/dataFoto (media/), admin-token, operations.json (pengaturan kode pendaftaran, SMS, dan SMTP, termasuk nilai rahasia)Foto dan pengaturan operasional
pabal_server/logs/app/logsLog server (dikompres setiap hari)Hanya catatan
postgres_data/var/lib/postgresql/dataSemua akun, obrolan, pesan, status masuk, bot, dan pengaturan webhookSeluruh layanan
caddy/data, caddy/config/data, /configSertifikat HTTPSDiterbitkan 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"
FCM_CREDENTIALS=
FCM_PROJECT_ID=
PUSH_PREVIEWS=true
NilaiYang diisiCatatan
DOMAINDomain situs webTautan yang dibuat aplikasi (pabal.me/nama_pengguna), alamat di dokumentasi, dan pratinjau tautan juga memakai alamat ini
PUBLIC_IPIPv4 publik serverDi cloud, IP publik yang tampil di konsol (bukan IP privat di dalam server)
POSTGRES_PASSWORDNilai acakTentukan sebelum menyalakan pertama kali. PostgreSQL hanya memakai nilai ini saat pertama kali membuat direktori data
PABAL_HOME/srv/pabalDirektori dari langkah 3
PABAL_SOURCE..Repositori tempat image server dibangun: .. jika berkas compose tetap di deploy/ dalam repositori; jika tidak, path ke kode sumber
PABAL_NETWORKssemiya-netJaringan Docker tempat ketiga kontainer bergabung. Buat terlebih dahulu (langkah 6)
MTPROTO_PORT8443Jika diubah, ubah juga firewall dan build aplikasi
ADMIN_TOKENBiarkan kosongJika kosong, token dibuat di pabal_server/data/admin-token saat pertama kali menyala. Jika ingin menentukannya sendiri, minimal 16 karakter
JAVA_OPTSNilai bawaanJika memori kurang, naikkan -Xmx (hingga sekitar setengah memori server)
FCM_CREDENTIALSBiarkan kosongNotifikasi push Android. Untuk menyalakannya, /app/keys/firebase-service-account.json (langkah 10). Jika kosong, server berjalan tanpa push
FCM_PROJECT_IDBiarkan kosongHanya jika mengirim ke project Firebase selain project_id di berkas kunci
PUSH_PREVIEWStrueJika false, notifikasi hanya menampilkan pengirim tanpa isi pesan

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

NamaJenisNilai
pabal.meA203.0.113.10
www.pabal.meA203.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. Membangun dan menyalakan

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):

  1. Atur Cara pengiriman kode (코드 전달 방식) ke SMS atau email. Dengan cara "Halaman admin" (관리 화면), operator harus memberitahukan kode satu per satu.
  2. Isi nilai SMS atau Email (SMTP) (이메일 (SMTP)) lalu simpan → pastikan kode benar-benar terkirim dengan Kirim uji (테스트 발송).
  3. Pastikan Nomor uji (테스트 번호) dimatikan. Jika dinyalakan, siapa pun bisa masuk dengan nomor +99966….
  4. 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.

Sebelum membagikan aplikasi kepada orang lain

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. Mintalah pengguna menaruh aplikasi di folder pengguna (misalnya ~/Applications/Pabal/). Build rilis untuk distribusi umum (folder data terpisah, tanda tangan dan notarisasi Apple) adalah pekerjaan tersendiri.

Untuk aplikasi Android, iPhone, dan Windows, cara yang sama (skrip yang memasukkan alamat dan kunci publik server → build) ada di docs/mobile-apps-guide.md di repositori. Agar aplikasi Android punya notifikasi push, pengaturan aplikasi Firebase dari langkah 10 harus sudah siap sebelum build.

10. Notifikasi push Android (opsional)

Android segera menidurkan aplikasi yang sudah tidak ada di layar. Tanpa push, pesan hanya muncul dan panggilan hanya berdering selama aplikasi terbuka. Push dikirim lewat Firebase Cloud Messaging (FCM) milik Google (gratis), dan isi notifikasi (nama pengirim, pratinjau pesan) dienkripsi server dengan kunci yang hanya dimiliki ponsel itu, sehingga Google tidak bisa membacanya.

Project Firebase (milik operator) Build · instalasi Ponsel · Google google-services.json Pengaturan untuk aplikasi Ikut masuk ke APK · bukan rahasia Kunci akun layanan firebase-service-account.json Rahasia · hanya di server Build aplikasi Android apply_branding.py → APK pabal-server keys/ · chmod 600 FCM_CREDENTIALS Mengenkripsi dengan kunci ponsel Aplikasi Pabal di ponsel Mendaftarkan ponsel saat pertama dibuka Google FCM Teks terenkripsi saja · tak terbaca ① Daftarkan ponsel ② Terenkripsi ③ Bangunkan → notif
Dua berkas ke dua tempat berbeda, notifikasi dalam bentuk terenkripsi

Penjelasan diagram

  • Tiga area: project Firebase yang dibuat operator → tempat membangun aplikasi dan menyalakan server → ponsel dan Google. Dua berkas diambil dari satu project Firebase.
  • Panah abu-abu adalah persiapan yang cukup dilakukan sekali. Kotak hijau google-services.json masuk ke dalam aplikasi dan ikut di setiap APK, jadi bukan rahasia; kotak merah, kunci akun layanan, adalah rahasia yang hanya disimpan di keys/ server. Kedua berkas harus diambil dari project yang sama.
  • Panah biru adalah jalur notifikasi: ① saat aplikasi pertama kali dibuka, ponsel mendaftarkan token FCM dan kunci yang hanya dimiliki ponsel itu ke server; ② saat ada pesan atau panggilan baru, server mengenkripsi notifikasi dengan kunci itu dan menyerahkannya ke FCM; ③ FCM membangunkan ponsel, aplikasi membuka isinya, menampilkan notifikasi, lalu tersambung kembali ke server untuk menerima yang terlewat.
  • Yang dilihat Google hanyalah informasi pengiriman seperti token perangkat, waktu kirim, dan prioritas. Nama pengirim dan pesan terenkripsi sehingga tidak bisa dibaca.
  • Ponsel yang sedang membuka aplikasi tidak dikirimi push — ia sudah menerimanya lewat sambungan. Push untuk obrolan yang notifikasinya dimatikan dikirim dengan prioritas tidak mendesak, dan aplikasi menyaringnya sendiri.
Project Firebase berpasangan dengan aplikasi

Dengan kunci server, push hanya bisa dikirim ke aplikasi yang dibangun dengan project Firebase yang sama. Aplikasi yang dibangun orang lain (misalnya yang diunduh dari Pabal.me) terikat pada project Firebase orang itu, dan sejak awal dibangun dengan alamat serta kunci publik server orang itu. Aplikasi untuk server Anda Anda bangun sendiri, dan project Firebase-nya juga Anda buat sendiri.

Membuat project Firebase

  1. Di konsol Firebase, pilih Tambahkan project. Namanya bebas, dan Google Analytics tidak diperlukan.
  2. Setelan project → Umum → Aplikasi Anda → Tambahkan aplikasi (Android). Isi nama paket dengan me.pabal.android (nama yang dipasang skrip build pada aplikasi). Sidik jari sertifikat (SHA-1) tidak diperlukan untuk push.
  3. Unduh google-services.json milik aplikasi itu. Ini pengaturan yang dimasukkan ke aplikasi dan bukan rahasia.
  4. Lewat Setelan project → Akun layanan → Buat kunci pribadi baru, unduh berkas JSON lalu ganti namanya menjadi firebase-service-account.json. Berkas ini rahasia — jangan masukkan isinya ke repositori, obrolan, atau .env.
  5. Di Setelan project → Cloud Messaging, pastikan Firebase Cloud Messaging API (V1) sudah diaktifkan (project baru biasanya sudah aktif).

Memasukkan pengaturan ke aplikasi

Sebelum membangun aplikasi Android, taruh google-services.json di tempat yang dicari skrip build. Skrip memeriksa berkas itu (apakah project-nya sudah mendaftarkan me.pabal.android, dan apakah Anda tidak keliru memberikan kunci rahasia) lalu memasukkannya ke kode sumber aplikasi.

mkdir -p ~/.pabal/android
cp ~/Downloads/google-services.json ~/.pabal/android/     # untuk menaruhnya di tempat lain: --google-services <path> pada apply_branding.py
python3 scripts/android/point_to_server.py <kode sumber Android> \
    --host 203.0.113.10 --port 8443 --key keys/production.pem.pub
python3 scripts/android/apply_branding.py <kode sumber Android>
# di akhir harus muncul "Firebase: project <ID project>". Jika yang terlihat pabal-placeholder, aplikasinya jadi tanpa push

Build selanjutnya mengikuti bagian Android (Android 앱) di docs/mobile-apps-guide.md di repositori. Ponsel yang memakai aplikasi versi sebelum ada push juga harus diperbarui ke aplikasi hasil build baru agar notifikasi masuk.

Menaruh kunci rahasia di server

sudo cp firebase-service-account.json /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/firebase-service-account.json    # pengguna kontainer server
sudo chmod 600 /srv/pabal/pabal_server/keys/firebase-service-account.json

Tambahkan satu baris ke .env. Di dalam kontainer, pabal_server/keys adalah /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 project>, 0 device registrations, message text included (encrypted per device)
  • Jika baris ini muncul, push sudah menyala. Jika berkas tidak bisa dibaca atau formatnya salah, alasannya muncul di Push notifications are off: … (isi berkas tidak ditampilkan di log).
  • Jika PUSH_PREVIEWS=false, akhir baris menjadi message text left out, dan notifikasi hanya menampilkan pengirim tanpa isi pesan.
  • Kontainer harus bisa membuka sambungan keluar ke oauth2.googleapis.com dan fcm.googleapis.com (443).

Pemeriksaan

  1. Pasang (atau perbarui) aplikasi hasil build baru di ponsel lalu buka sekali. Di log server muncul Push device registered: user … type 2 token … (push key 256 bytes). Token hanya tercatat sebagai sidik jari.
  2. Tutup aplikasi dengan menggesernya dari daftar aplikasi terbaru. Jangan pakai "Paksa berhenti" di Setelan — Android tidak akan meneruskan push sampai Anda sendiri membuka aplikasi itu lagi.
  3. Kirim pesan dari akun lain: di log muncul Push MESSAGE_TEXT to user …: sent dan notifikasi muncul di ponsel.
  • Kebijakan privasi: di halaman admin → Pengaturan (설정) → Informasi operator (운영자 정보), tuliskan Google LLC (Firebase Cloud Messaging, Amerika Serikat) pada Pemrosesan oleh pihak lain (처리 위탁) dan Transfer ke luar negeri (국외 이전). Teks kebijakannya sudah memuat kalimat bahwa notifikasi melewati FCM. Tuliskan hal yang sama di formulir "Keamanan data" Google Play.
  • Jika kunci bocor, hapus kunci itu di konsol Firebase, unduh yang baru, ganti berkasnya, lalu mulai ulang server. Aplikasi tidak perlu dibangun ulang.
iPhone dan desktop

iPhone dinyalakan terpisah, bukan dengan Firebase melainkan dengan kunci APNs (.p8) dari akun pengembang Apple — lihat langkah 11 tepat di bawah. Aplikasi Mac dan Windows tidak memakai push; keduanya menerima pesan selama terbuka.

11. Notifikasi push iPhone (opsional)

iPhone juga segera menghentikan aplikasi yang sudah tidak ada di layar. Tanpa push, pesan dan panggilan hanya diterima selama aplikasi terbuka. Push dikirim lewat layanan notifikasi push Apple (APNs), dan seperti di Android, isi notifikasi dienkripsi dengan kunci yang hanya dimiliki iPhone itu. Teks notifikasi yang bisa dilihat Apple hanyalah nama aplikasi dan kalimat tanpa isi seperti "Ada notifikasi baru"; ekstensi notifikasi (Notification Service Extension) di dalam aplikasi iPhone membuka teks terenkripsi itu dan menampilkannya sebagai pengirim dan pesan.

  • Pendaftaran: setiap kali dibuka, aplikasi mendaftarkan dua hal untuk setiap akun — untuk notifikasi (type 1) dan untuk panggilan (VoIP, type 9). Bersama pendaftaran itu, server mengingat bahasa aplikasi (bahasa untuk teks luar) dan nomor build-nya.
  • Pesan dikirim sebagai notifikasi. Obrolan yang notifikasinya dimatikan tidak dikirimi apa pun. Apa yang dibaca atau dihapus di perangkat lain diberitahukan lewat push latar belakang tanpa suara, sehingga aplikasi menyingkirkan notifikasi yang masih tampil.
  • Panggilan yang dikirim lewat VoIP memunculkan layar panggilan CallKit bahkan saat layar terkunci. Namun aplikasi yang menerima VoIP harus segera memberi tahu CallKit tentang panggilan itu; jika tidak, iOS mematikan aplikasinya, dan jika terus berulang, iOS berhenti mengirimkan VoIP. Karena itu VoIP hanya dinyalakan dengan APNS_VOIP_MIN_BUILD mulai dari build yang sudah dibuat seperti itu; sebelum itu, panggilan juga dikirim sebagai notifikasi.
Kunci APNs milik tim yang menandatangani aplikasi

Berbeda dengan Firebase, tidak ada berkas pengaturan yang perlu dimasukkan ke aplikasi — aplikasinya sudah dibangun agar bisa memakai APNs (izin notifikasi, App Group, ekstensi notifikasi), jadi cukup taruh kuncinya di server. Namun dengan kunci server, push hanya bisa dikirim ke aplikasi yang ditandatangani oleh tim pembuat kunci itu dan yang bundle ID-nya sama dengan APNS_TOPIC. Jika aplikasi iPhone untuk server Anda ditandatangani dengan akun pengembang Apple Anda, buat juga kunci APNs di akun itu.

Membuat kunci APNs

  1. Apple Developer → Certificates, IDs & Profiles → Keys → +. Harus akun milik tim yang menandatangani aplikasi.
  2. Isi nama lalu centang Apple Push Notifications service (APNs). Biarkan lingkungannya Sandbox & Production dan cakupannya seluruh tim (Team Scoped) — karena push dikirim ke dua tempat, notifikasi dan VoIP.
  3. Register → Download. AuthKey_<ID kunci>.p8 hanya bisa diunduh sekali. Berkas ini rahasia — jangan masukkan isinya ke repositori, obrolan, atau .env.

Menaruh kunci di server

sudo cp AuthKey_<ID kunci>.p8 /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/AuthKey_<ID kunci>.p8    # pengguna kontainer server
sudo chmod 600 /srv/pabal/pabal_server/keys/AuthKey_<ID kunci>.p8

Tambahkan ke .env. Di dalam kontainer, pabal_server/keys adalah /app/keys.

APNS_KEY=/app/keys/AuthKey_<ID kunci>.p8
APNS_TEAM_ID=4Z8R468M2D
APNS_TOPIC=me.pabal.ios
APNS_MIN_BUILD=4
APNS_VOIP_MIN_BUILD=
NilaiYang diisiCatatan
APNS_KEYPath berkas kunciID kunci dibaca dari nama berkas. Jika berkas disimpan dengan nama lain, isi ID kunci di APNS_KEY_ID
APNS_TEAM_IDID tim yang menandatangani aplikasiUntuk aplikasi Pabal: 4Z8R468M2D. Jika Anda sendiri yang membangun dan menandatangani aplikasinya, ID tim Anda
APNS_TOPICBundle ID aplikasiUntuk aplikasi Pabal: me.pabal.ios. Panggilan (VoIP) dikirim ke nilai ini ditambah .voip
APNS_MIN_BUILD4Build yang lebih rendah dari ini tidak dikirimi apa pun (0 berarti semua). Nilai 4 mengecualikan Pabal 1.0 (3), yang dibuat sebelum push diuji
APNS_VOIP_MIN_BUILDBiarkan kosongJika kosong, panggilan juga dikirim sebagai notifikasi. Jika diisi nomor build pertama yang langsung memberi tahu CallKit tentang panggilan, layar panggilan CallKit muncul mulai dari build itu
docker compose up -d pabal-server
docker compose logs pabal-server | grep -i push
INFO  TelegramServer - iPhone push notifications on: APNs key <ID kunci> of team 4Z8R468M2D, topic me.pabal.ios, builds from 4, calls as alerts (TELEGRAM_APNS_VOIP_MIN_BUILD not set)
  • Jika baris ini muncul, push sudah menyala. Jika berkas kunci tidak bisa dibaca atau formatnya salah, alasannya muncul di iPhone push notifications are off: … (isi berkas tidak ditampilkan di log). Push iPhone dinyalakan dan dimatikan terpisah dari Android (FCM) — boleh hanya salah satu yang dinyalakan.
  • Kontainer harus bisa membuka sambungan keluar ke api.push.apple.com (443, HTTP/2). Build debug yang dipasang langsung ke perangkat dengan Xcode memakai api.sandbox.push.apple.com (aplikasi memberitahukannya saat mendaftar).

Pemeriksaan

  1. Di iPhone sungguhan, buka aplikasi sekali dan izinkan notifikasi. Di log server muncul Push device registered: user … type 1 token … (push key 256 bytes, build …) dan baris type 9. Tidak bisa diperiksa dengan simulator — simulator tidak punya token APNs maupun CallKit.
  2. Tutup aplikasi dengan menggesernya ke atas.
  3. Kirim pesan dari akun lain: di log muncul Push MESSAGE_TEXT to user …: sent dan pengirim serta isi pesannya muncul di layar kunci.
  • Kebijakan privasi: di halaman admin → Pengaturan (설정) → Informasi operator (운영자 정보), tuliskan Apple Inc. (layanan notifikasi push Apple, Amerika Serikat) pada Pemrosesan oleh pihak lain (처리 위탁) dan Transfer ke luar negeri (국외 이전). Teks kebijakannya sudah memuat kalimat bahwa notifikasi melewati APNs. Periksa juga butir ID perangkat (token push) di kuesioner privasi aplikasi di App Store Connect.
  • Jika kunci bocor, lakukan Revoke pada kunci itu di akun pengembang, buat yang baru, ganti berkasnya, lalu mulai ulang server. Aplikasi tidak perlu dibangun ulang.

12. Bot suara (opsional)

Bot suara adalah fitur yang membuat model suara AI menjawab ketika seseorang menelepon bot dari aplikasi (penjelasan untuk pemilik bot ada di Bot suara). Server memerlukan dua komponen tambahan, dan dokumen hukum serta build aplikasi harus sudah siap agar fitur ini menyala. Bawaannya mati.

Suara dinyalakan dengan berkas compose tambahan

Komponen suara tidak ada di deploy/docker-compose.yml, tetapi di berkas tambahan deploy/docker-compose.voice.yml — pengaturan suara server dan jaringan khusus link, gateway pabal-voice (profil voice), serta pintu masuk audio jam tangan pabal-voice-edge (profil voice-watch). Jika .env tidak menyebut berkas ini, tidak ada pengaturan suara sama sekali. Konfigurasi ini sudah dijalankan dengan mesin echo, tetapi suara belum pernah dinyalakan di server produksi, dan panggilan bot suara juga belum pernah dicoba sampai tuntas lewat relay panggilan (TURN) produksi — uji ujung ke ujung dilakukan dengan relay uji di dalam jaringan Docker (scripts/voice-e2e/run.sh). Sebelum menyalakannya, deploy/voice_readiness.py di repositori memeriksa apa yang masih kurang. Syarat sebelum peluncuran publik (telaah hukum, jawaban tertulis penyedia, perilisan aplikasi) adalah keputusan operator, dan kode hanya mencegah sebagian di antaranya.

KomponenTugasnyaJika tidak ada
pabal-voice (gateway, image 0.11.0)Menjadi salah satu ujung panggilan menggantikan bot, melakukan pertukaran kunci, dan bertukar audio dengan model AI. Python 3.12 + ntgcalls. Berbicara dengan server lewat link (WebSocket, terautentikasi tetapi tidak terenkripsi)Semua panggilan bot berbunyi sibuk (no_gateway)
coturn (relay panggilan)Panggilan bot hanya memakai relay: aplikasi maupun gateway mematikan koneksi langsung dan hanya mengirim audio lewat relay. Sama dengan relay untuk panggilan antarmanusia (TURN_SECRET)Semua panggilan bot berbunyi sibuk (no_relay), dan gateway juga menolak koneksi (4008)
Kunci Vertex AIKunci JSON akun layanan Google Cloud. Hanya dibuka oleh proses mesin di gatewayTanpa model, hanya gema (VOICE_ENGINE=echo, audio tidak keluar dari mesin)
(Opsional) kunci indukKunci yang membungkus kunci sendiri milik pemilik bot dan rahasia mode streamKunci sendiri dan mode stream mati
(Opsional) komponen MCPpabal-mcp · penanda tangan · proxy keluar (di bawah)Server MCP mati

Syarat agar menyala

Yang diperiksa serverJika tidak ada
Berkas rahasia link (TELEGRAM_VOICE_LINK_SECRET_FILE, 32 byte)Suara mati — port link pun tidak dibuka
Relay panggilan (TELEGRAM_TURN_HOST·TELEGRAM_TURN_SECRET)Semua panggilan bot berbunyi sibuk
Bagian id="bot-calls" di kebijakan privasi situs (ko dan en) — layar konfirmasi aplikasi menunjuk ke siniTidak ada bot yang menerima telepon (log ERROR). Drafnya ada di docs/voice-bots/p0/privacy-*.draft.html
Bagian id="voice-bot-owners" di ketentuan layananTidak ada yang bisa menyetujui ketentuan pemilik bot → transkrip, kunci sendiri, mode stream, dan MCP tidak bisa dipakai
Bagian id="bot-calls-stream" · id="bot-calls-mcp" di kebijakanMode stream · MCP mati
Izin bot di halaman admin (bawaannya hanya bot yang diizinkan) dan peluncuran publik (bawaannya hanya penguji; untuk membukanya, ketik kalimat konfirmasi)Bot yang tidak diizinkan tidak menerima telepon, dan sebelum peluncuran publik hanya penguji yang bisa menelepon
Build aplikasi penelepon: desktop pabal-bc2 (juga pabal-bc1 selama layar konfirmasi hanya ada dalam bahasa Korea dan Inggris) · Android 62151 · iPhone 5 ke atas · Apple Watch 1 ke atas (TELEGRAM_VOICE_MIN_BUILD_*)Orang itu tidak punya tombol panggil, dan jika tetap menelepon, hasilnya sibuk
(Opsional) Apple Watch: API jam tangan di server (TELEGRAM_WATCH_API_PORT) dan alamat audio (TELEGRAM_WATCH_AUDIO_URL), pintu masuk audio pabal-voice-edge, gateway yang mengumumkan direct (di bawah)Tidak bisa menelepon dari jam tangan (aplikasi ponsel dan komputer tetap sama)
(Opsional) Simpan percakapan: bagian id="bot-calls-keep-text" di kebijakan, gateway yang mengumumkan keep_text (VOICE_KEEP_TEXT, di bawah)Walau kotaknya dicentang, percakapan tidak disimpan
(Opsional) Panggilan dari bot: bagian id="bot-calls-outbound" di kebijakan dan bagian id="voice-bot-owners-outbound" di ketentuan, folder kunci bukti, versi minimum untuk panggilan keluar, gateway yang mengumumkan outbound, sakelar umum di halaman admin (di bawah)Tidak ada bot yang bisa menelepon siapa pun

Hanya di server uji (TELEGRAM_TEST_NUMBERS=true), pemeriksaan bagian hukum bisa dilewati dengan TELEGRAM_VOICE_NOTICE_CHECK=off, dan siapa pun bisa mencoba dengan TELEGRAM_VOICE_BOTS (semua bot: *) · TELEGRAM_VOICE_TESTERS=*. Jangan dipakai di produksi — server produksi mengabaikan TELEGRAM_VOICE_BOTS disertai peringatan.

Relay panggilan (coturn)

  • Di server Linux, pakai pabal-turn dari berkas compose (COMPOSE_PROFILES=turn). Tentukan TURN_SECRET di .env (openssl rand -hex 32), lalu buka di router dan firewall TURN_PORT (3478, udp dan tcp) serta TURN_MIN_PORT–TURN_MAX_PORT (49160–49200, udp). Jika rentangnya diperlebar, ubah router dan .env bersamaan.
  • Di Docker Desktop macOS, jangan jalankan coturn sebagai kontainer. Semua penelepon terlihat sebagai 192.168.65.1 sehingga izin relay tidak cocok, dan meski tampak normal, semua audio dibuang. Di server Mac, pasang coturn langsung (deploy/turnserver.conf.example, docs/turn-relay-guide.md).
  • Alamat relay yang diberikan ke gateway adalah alamat LAN server: isi nilai yang sama di TELEGRAM_VOICE_TURN_HOST milik server dan VOICE_TURN_HOST milik gateway. Dengan host.docker.internal relay tidak terjangkau, dan dengan 127.0.0.1 alokasinya tidak selesai. Aplikasi tetap menerima alamat publik (TURN_HOST).
  • Dari jaringan luar, pastikan dengan deploy/check_relay.py --data bahwa audio benar-benar tersalur. Ada kalanya STUN menjawab dan alokasi berhasil, tetapi audionya tidak tersalur.

Kunci Vertex AI

  1. Di project Google Cloud, nyalakan Agent Platform API (nama lamanya Vertex AI API, aiplatform.googleapis.com). Akun penagihan harus sudah terhubung. Gemini Developer API (kunci AI Studio) tidak dipakai.
  2. Buat satu akun layanan dan beri satu peran saja, roles/aiplatform.user (untuk mempersempit lagi, peran kustom yang hanya berisi izin aiplatform.endpoints.predict). Buat kunci produksi terpisah dari kunci pengembangan.
  3. Unduh kunci JSON dan taruh di luar repositori dan .env, misalnya di /srv/pabal/pabal_voice/secrets/vertex-sa.json, agar hanya bisa dibaca pengguna gateway (Linux: chown 10001:10001, chmod 400). Jika semua orang bisa membacanya, gateway menolak untuk mulai.
  4. Model real-time hanya ada di us-central1 (Iowa, Amerika Serikat) (dipastikan 2026-09-26). Kebijakan privasi dan layar konfirmasi harus menyebutkan bahwa audio panggilan dikirim ke Amerika Serikat, dan server hanya memberikan panggilan ke gateway jika wilayah yang tertulis di layar konfirmasi aplikasi sama dengan wilayah gateway (VOICE_VERTEX_REGION).
  5. Yang perlu dikerjakan: peringatan anggaran, kuota sesi bersamaan (dua kali kapasitas gateway), dan cache data Vertex (jika menyala, masukan dan keluaran disimpan di memori hingga 24 jam) — layar konfirmasi menyatakan "Google tidak menyimpan audio maupun teks setelah membuat jawaban", jadi selama cache belum dimatikan, pernyataan itu tidak sesuai. Disarankan: sebelum peluncuran publik, matikan cache data (bagian 9 dari docs/voice-bots/vertex-setup.md di repositori) dan persempit peran kunci menjadi satu izin prediksi saja (bagian 10 dari dokumen yang sama).

Server dan gateway membaca rahasia link yang sama dari berkas. Karena link tidak terenkripsi, taruh hanya di jaringan khusus yang hanya disambungi kedua kontainer itu — jangan di ssemiya-net yang dipakai bersama stack lain, atau di LAN. Port-nya tidak dipublikasikan.

(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   # milik server (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 # milik gateway (uid 10001)
shred -u /tmp/voice-link.secret

Nilai-nilai ini masuk ke .env. Dengan nilai itu, berkas tambahan memberi pabal-server pengaturan suaranya (TELEGRAM_VOICE_*) dan jaringan link.

COMPOSE_FILE=docker-compose.yml:docker-compose.voice.yml
COMPOSE_PROFILES=voice                              # bersama profil lain (turn, …), dipisahkan koma
VOICE_LINK_SECRET_FILE=/app/keys/voice-link.secret  # jalur di dalam container; kosong: suara mati
VOICE_TURN_HOST=192.168.1.64                        # alamat LAN server (tempat coturn mendengarkan)
VOICE_TESTERS=100001                                # id orang yang boleh menelepon sebelum peluncuran (halaman admin bisa menambah)
  • Berkas tambahan memasang server di jaringan khusus internal pabal-voice-link (10.231.53.0/29 — server .2, gateway .3), dan server hanya mendengarkan di alamatnya sendiri di jaringan itu (dengan 0.0.0.0, port itu juga terbuka di jaringan bersama yang ikut tersambung). Jika rentang ini bertabrakan dengan LAN, VPN, atau jaringan Docker lain, ubah VOICE_LINK_SUBNET, VOICE_LINK_SERVER_IP, dan VOICE_LINK_GATEWAY_IP bersama-sama.
  • Untuk mendengarkan di alamat selain loopback, TELEGRAM_VOICE_LINK_ANY_ADDRESS=true diperlukan, dan link tidak pernah terikat ke alamat publik. Log server menunjukkan jaringan yang dilihat link.
  • Untuk koneksi sebelum autentikasi, batasnya 3 detik untuk upgrade dan 3 detik untuk hello, 2 koneksi per alamat, dan total 16 koneksi.
  • Seluruh pengaturan server yang terkait suara ada di Administrasi dan pengaturan — Variabel lingkungan bot suara.

Gateway (pabal-voice)

cd /opt/pabal
docker build -t pabal-voice:latest voice/                                       # untuk CPU komputer ini
docker buildx build --platform linux/amd64 -t pabal-voice:latest --load voice/  # untuk server Intel
cd deploy && docker compose up -d                                         # sesuai COMPOSE_FILE dan COMPOSE_PROFILES di .env

Image ini memakai Python 3.12 yang versinya dikunci dengan hash, tanpa kompiler dan pip, dan berjalan sebagai uid 10001. ntgcalls hanya tersedia untuk Linux x86_64/aarch64 dan macOS arm64, jadi di Mac Intel hanya bisa dijalankan dengan Docker. Layanan pabal-voice di berkas tambahan memakai image ini tanpa membangunnya (image: pabal-voice:latest). Untuk membangunnya bagi CPU server di komputer lain lalu mengirimnya, pakai deploy/ship.sh <user@host> <folder> --voice (image yang digantikan tetap ada sebagai pabal-voice:previous). Berikut layanan gateway dari berkas itu, diringkas.

  pabal-voice:                             # dari deploy/docker-compose.voice.yml (diringkas)
    image: pabal-voice:latest
    profiles: [voice]
    init: true
    stop_grace_period: 40s                 # lebih lama daripada 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:-}  # nilai yang sama dengan TELEGRAM_VOICE_TURN_HOST di server
      VOICE_CAPACITY: ${VOICE_CAPACITY:-2}
      VOICE_DAILY_MODEL_MINUTES: ${VOICE_DAILY_MODEL_MINUTES:-60}
      VOICE_SAFETY_THRESHOLD: ${VOICE_SAFETY_THRESHOLD:-MEDIUM}   # panggilan penguji: asumsi yang perlu dipastikan operator (lihat di bawah)
      VOICE_BYOK: "false"
      VOICE_STREAM: "false"
    volumes:
      - ${PABAL_HOME:-/srv/pabal}/pabal_voice/secrets:/run/secrets:ro   # rahasia link dan kunci Vertex — tidak masuk backup.sh
      - pabal_voice_state:/var/lib/pabal-voice
    networks:
      pabal-voice-link:                    # jaringan link yang hanya dipakai bersama server (internal)
        ipv4_address: ${VOICE_LINK_GATEWAY_IP:-10.231.53.3}
      pabal-voice-out: {}                  # jaringan keluarnya sendiri (Google, relay)
  • Selain link, gateway juga memerlukan jalur keluar: Google (us-central1-aiplatform.googleapis.com:443) dan relay panggilan (UDP). Karena jaringan link bersifat internal: true dan tidak bisa keluar, tambahkan satu jaringan lagi, pabal-voice-out, seperti di atas.
  • Landlock: gateway yang memakai model atau mode stream menolak untuk mulai (kode keluar 2) jika kernel Linux tidak punya Landlock (5.13 ke atas; bisa dengan seccomp bawaan Docker). Proses media, yang menangani kiriman penelepon dengan kode native, mengunci dirinya sendiri agar tidak bisa membaca berkas kunci. Di dalam kontainer, jangan memasang berkas rahasia di bawah /app·/opt·/usr·/lib·/etc (/run/secrets tidak apa-apa — seperti contoh di atas). VOICE_ALLOW_NO_LANDLOCK=true hanya untuk Mac pengembangan. Belum dipastikan apakah Landlock berfungsi di Docker Desktop macOS.
  • Memori: per panggilan, media 60–100 MB + mesin 30–40 MB. Dengan kapasitas 8 (VOICE_CAPACITY) dan sepasang cadangan, sekitar 1,3 GB. Bawaan berkas tambahan adalah kapasitas 2 dan memori 1 GB (VOICE_MEMORY).
  • Filter keamanan VOICE_SAFETY_THRESHOLD bawaannya di gateway adalah LOW (paling ketat), tetapi dalam pengukuran filter ini mengubah 13 dari 172 ucapan bahasa Korea yang wajar menjadi penolakan berbahasa Inggris (MEDIUM: 0 dari 69). Repositori berasumsi bahwa panggilan penguji menyebutkan MEDIUM secara eksplisit, dan uji red team diperlukan sebelum nilai bawaannya diubah.
  • Batas: bawaan gateway adalah 600 detik, 400.000 token, dan 30 giliran per menit per panggilan, serta 300 menit model dan 8 juta token per hari (UTC); berkas tambahan menetapkan batas harian 60 menit dan 1,6 juta token (VOICE_DAILY_MODEL_*, jika terlampaui, panggilan baru berbunyi sibuk). Mesin OpenAI hanya diuji dengan server tiruan dan dalam keadaan mati, dan tidak ada peralihan otomatis antarmesin.
  • Bahasa: saat tersambung ke server, gateway menyatakan bahasa bot yang bisa ia tangani (sejak 0.7.0 mendukung kesembilannya), dan server hanya memberikan panggilan sebuah bot kepada gateway yang bisa menangani panggilan dalam bahasa bot itu — gateway lama yang tidak menyatakan bahasa (0.6.0) hanya mendapat bot berbahasa Korea dan Inggris. Rekaman pengumuman ada di assets/ di dalam image, dan rekaman mana yang sudah lolos telaah ditentukan oleh assets/assets.json. Jika berkas ini tidak ada, gateway tidak bisa memuat pengumuman AI sehingga tidak bisa menerima panggilan (ada ERROR di log saat mulai). Rekaman yang belum lolos telaah (saat ini semua bahasa selain bahasa Korea dan Inggris) tidak diputar, dan penelepon yang memakai bahasa itu mendengar pengumuman berbahasa Inggris. VOICE_DRAFT_NOTICES=true adalah sakelar khusus server uji yang ikut memutar rekaman draf, dan gateway tidak mau mulai jika VOICE_ALLOW_TEST_URLS=true tidak ikut diatur. VOICE_NOTICE_LANG (bahasa pengumuman jika server tidak memberi tahu bahasa penelepon, bawaan ko) harus salah satu dari 10 bahasa rekaman (ko en ja zh fr es id vi km ar), dan VOICE_LANGUAGE (bahasa bot untuk panggilan tanpa pengaturan pemilik bot, bawaan ko-KR) harus salah satu id di daftar bahasa bot (Bot suara — Suara dan bahasa).
  • Log tidak memuat rahasia link, kunci, audio, teks percakapan, maupun instruksi. Saat menerima SIGTERM, gateway berhenti menerima panggilan baru dan memberi panggilan yang berlangsung waktu VOICE_DRAIN_SECONDS (bawaan 20 detik) sebelum mengakhirinya.

Pemeriksaan

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 (jaringan)
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: … muncul jika rahasia link tidak ada, tidak bisa dibaca, atau bukan 32 byte; akhir baris — but without a TURN relay every bot call is busy muncul jika tidak ada relay.
  • Jika muncul ERROR Voice bots are on, but no bot is callable: the privacy policy … has no id="bot-calls" section, muat dulu bagian panggilan bot di kebijakan privasi situs.
  • Saat gateway tersambung, features di log server menampilkan bot_settings·end_call·context·tools, ditambah byok untuk gateway yang menjalankan kunci milik pemilik, dan transcripts jika punya pengumuman suara transkrip (urutannya bisa berbeda-beda). audio to harus sama dengan penerima yang disebutkan di layar konfirmasi aplikasi.
  • Di sisi gateway, baris pertama harus berisi starting as pabal-voice-1 (capacity 2, … engine gemini, audio to google.vertex.gemini-live@us-central1), baris model berisi session resumption off; no failover, dan juga harus muncul media workers sandbox themselves with Landlock (ABI N). Baris recordings for the callers' notices in: Korean, English menunjukkan bahasa yang pengumumannya bisa diputar untuk penelepon (di server uji yang ikut memutar draf, ditambahkan (drafts included: …)).
  • Setelah itu, di tab Suara (음성) halaman admin (Administrasi dan pengaturan — Tab Suara), periksa apakah gateway sudah tersambung, masukkan penguji, lalu izinkan bot.

Kunci induk (kunci sendiri · mode stream)

Kunci model milik pemilik bot dan rahasia mode stream disimpan terbungkus dengan kunci ini. Kunci ini harus diletakkan di tempat yang tidak ikut tersalin bersama cadangan.

sudo install -d -m 700 -o 1000 -g 1000 /etc/pabal/voice                 # di luar /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
  • Jika kunci berada di dalam atau di samping folder data atau folder kunci RSA, atau foldernya sedikit saja terbuka bagi pengguna lain (bukan 700), server tidak memakainya (Voice keys off: …). Jika berkasnya tidak ada, server membuatnya saat menerima kunci pertama (jika dipasang read-only, buat sendiri seperti di atas).
  • Jangan masukkan ke cadangan, dan simpan satu salinan offline secara terpisah. Jika hilang, semua pemilik bot harus mengatur ulang kuncinya. Untuk menggantinya, beri kunci lama di TELEGRAM_VOICE_KEYS_PREVIOUS_MASTER_FILE, nyalakan, periksa log, lalu hapus berkas lama.
  • Meski kunci induk sudah ada, kunci sendiri baru diterima setelah aplikasi menampilkan layar konfirmasi (versi 2) untuk bot berkunci sendiri dan Anda mengisi TELEGRAM_VOICE_OWN_KEYS_APPS_READY=true. Bot mode stream baru bisa ditelepon setelah build aplikasi yang memuat layar konfirmasi mode stream disebutkan di TELEGRAM_VOICE_STREAM_MIN_BUILD_DESKTOP·_ANDROID·_IOS (bawaan 0 = tidak ada yang bisa menelepon), dan gateway harus punya VOICE_STREAM=true serta IP publik deployment ini (VOICE_STREAM_DENY_ADDRESSES).

Komponen MCP (opsional)

Komponen ini memanggil server MCP yang didaftarkan pemilik bot selama panggilan. Bawaannya mati, dan deploy/docker-compose.mcp.yml (profil mcp) menambahkan tiga kontainer.

KontainerTugasnya
pabal-mcp (uid 1101)Klien MCP. Terhubung dengan server hanya lewat berkas socket, hanya punya satu jaringan internal, dan keluar hanya lewat proxy keluar. Read-only, tanpa hak istimewa, 512 MB
pabal-mcp-signer (uid 1102)Penanda tangan. Sama sekali tanpa jaringan, hanya menjawab lewat socket, dan menulis dokumen penerbit publik
pabal-mcp-egress (uid 1103)Proxy keluar. Hanya ke port 443, dan tidak pernah ke alamat Pabal sendiri
docker compose -f docker-compose.yml -f docker-compose.mcp.yml --profile mcp up -d
  • Untuk menyalakannya diperlukan TELEGRAM_MCP_EGRESS_OWN_ADDRESSES (alamat publik Pabal, wajib) dan berkas-berkas kunci (rahasia link, K_grant, kunci instalasi — semuanya di folder 700 di luar /srv/pabal), serta situs harus memuat bagian bot-calls-mcp di kebijakan dan bagian pemilik bot di ketentuan. Pengaturan yang cukup dilakukan sekali tercantum berurutan di komentar paling atas docker-compose.mcp.yml. Vault dan kunci penanda tangan tidak dimasukkan ke cadangan.
  • Isi TELEGRAM_MCP_APPS_READY=true setelah aplikasi menampilkan penerima MCP bot di layar konfirmasi. Sebelum itu, server hanya bisa didaftarkan, dan tidak ada panggilan yang membawa tool MCP.
  • Menyediakan alamat keluar khusus untuk permintaan MCP bukan syarat kode, melainkan keputusan operasional (karena itulah pintu keluar tempat ucapan penelepon dikirim ke server pihak ketiga).
  • Jika menyala, log menampilkan MCP tools on: pabal-mcp dials …; issuer …; ports …; own addresses N; clock by NTP …. deploy/ship.sh <server> <folder> --mcp hanya membangun dan mengirim tiga image serta menyalin berkas override, tanpa menyalakan apa pun (belum pernah dicoba di Docker Linux).

Pintu masuk Apple Watch (opsional, sedang disiapkan)

Aplikasi jam tangan masuk dan menelepon lewat API jam tangan di server (JSON lewat HTTPS, /api/watch/v1/), bukan MTProto, dan audio panggilan sampai ke gateway lewat pintu masuk audio (pabal-voice-edge, WSS). Relay panggilan (coturn) tidak dipakai. Secara bawaan fitur ini mati.

Pintu masuk audio jam tangan belum bisa dicapai dari luar

deploy/docker-compose.voice.yml sudah memuat pintu masuk audio pabal-voice-edge (profil voice-watch, tanpa rahasia, di jaringan khusus internal), tetapi belum memuat pengaturan API jam tangan di server (TELEGRAM_WATCH_*) maupun jalur jam tangan di reverse proxy, sehingga jam tangan dari luar belum bisa mencapainya. Yang berikut adalah panduan nilai pengaturan, dan belum pernah dijalankan dalam konfigurasi produksi (hanya diuji di server pengembangan).

PengaturanBawaanArti
TELEGRAM_WATCH_API_PORT—Port API jam tangan. Jika tidak ada, API jam tangan mati
TELEGRAM_WATCH_API_HOST, TELEGRAM_WATCH_API_ANY_ADDRESS127.0.0.1, —Alamat yang didengarkan. Jika bukan loopback, harus ada ANY_ADDRESS=true, dan pasang proxy TLS di depannya
TELEGRAM_WATCH_AUDIO_URL—Alamat pintu masuk audio yang diberitahukan ke jam tangan. Jalurnya harus tepat wss://<host>/api/watch/v1/audio. Jika tidak ada atau salah, panggilan dari jam tangan mati
TELEGRAM_WATCH_ALLOW_PLAIN—Mengizinkan alamat ws://. Khusus pengembangan (uji di rumah)
TELEGRAM_WATCH_SESSION_DAYS7Jumlah hari login jam tangan tetap berlaku (1–365)
TELEGRAM_WATCH_AUTH_PER_MINUTE30Batas permintaan kode login untuk seluruh server (per menit). Batas per alamat ada tersendiri
TELEGRAM_VOICE_MIN_BUILD_WATCHOS1Build aplikasi jam tangan pertama yang menampilkan layar konfirmasi lengkap
  • Gateway: VOICE_DIRECT=true dan VOICE_EDGE_DIR (folder soket yang dipakai bersama pintu masuk, bawaan /run/pabal-voice-edge). Panggilan dari jam tangan hanya bisa dengan mesin 16 kHz (Gemini) atau echo.
  • Pintu masuk audio: jalankan python -m pabal_voice.edge sebagai layanan kedua dari image yang sama (VOICE_EDGE_HOST·VOICE_EDGE_PORT, bawaan 127.0.0.1·8096, VOICE_EDGE_DIR). Pintu masuk adalah tempat pertama yang membuka data dari internet, jadi jangan pasang rahasia apa pun padanya, dan letakkan di jaringan internal yang hanya bisa dijangkau proxy dan gateway.
  • Reverse proxy hanya meneruskan dua jalur jam tangan — /api/watch/v1/audio (WebSocket, ke pintu masuk) dan /api/watch/v1/ (ke API jam tangan). Untuk sisi API jam tangan, biarkan proxy menambahkan alamat klien di akhir X-Forwarded-For (server memakai nilai itu untuk menghitung batas login per alamat). Untuk sisi pintu masuk, hapus X-Forwarded-For dan X-Real-IP.
  • Di jam tangan tidak bisa mendaftar, dan bot yang memakai tool, MCP, kunci sendiri, mode stream, atau transkrip pemilik bot tidak bisa ditelepon (WATCH_NOT_YET).

Simpan percakapan (opsional, sedang disiapkan)

Jika penelepon memilihnya di layar konfirmasi, percakapan panggilan disimpan sebagai teks hanya di obrolan orang itu (Menggunakan aplikasi — bot suara). Tidak ada sakelar izin operator; fitur ini menyala jika semua hal berikut ada.

  • Bagian id="bot-calls-keep-text" di kebijakan privasi (draf: docs/voice-bots/p0/privacy-*.draft.html di repositori). Bagian ini sendirilah sakelarnya. Aplikasi tahu apakah kotak centang ditampilkan dari pabal_voice_keep_text di help.getAppConfig.
  • VOICE_KEEP_TEXT pada gateway 0.9.0: bawaan true untuk mesin model. Pada mesin echo (termasuk gateway khusus mode stream), true adalah kesalahan konfigurasi dan gateway tidak mau mulai.
  • Build aplikasi yang memuat kotak centang. Teks kotak centang baru dimuat di build rilis setelah tinjauan hukum selesai. Jam tangan tidak menampilkan teks yang belum ditinjau; TELEGRAM_VOICE_KEEP_TEXT_UNREVIEWED=true adalah sakelar khusus server uji internal yang menampilkan teks yang belum ditinjau.
  • Di server yang menyalakannya, voice_call ended untuk semua panggilan bot yang bukan mode stream datang paling lambat 10 detik kemudian, dan 30 nomor pesan setelah catatan panggilan dipesan lebih dulu (agar pemilik bot tidak bisa mengetahui pilihan penelepon).
  • Teks percakapan yang menunggu ditulis setelah panggilan selesai hanya ada di memori, jadi jika server dimulai ulang sebelum itu, teks panggilan tersebut tidak tersimpan.

Panggilan dari bot (opsional, menunggu tinjauan hukum)

Bot suara yang menelepon lebih dulu orang yang mengizinkan bot itu meneleponnya (Bot suara — Panggilan dari bot). Syaratnya lebih banyak daripada panggilan ke bot, dan jangan menyalakannya sebelum tinjauan hukum selesai. Urutannya:

  1. Server: buat folder kunci bukti (TELEGRAM_VOICE_EVIDENCE_KEY_DIR — 700, di luar folder yang dicadangkan seperti folder data dan kunci) dengan new dari scripts/voice-evidence-key.py (rotate untuk mengganti kunci, check untuk memeriksanya), dan tetapkan versi aplikasi pertama dengan layar izin sebagai versi minimum panggilan keluar, misalnya TELEGRAM_VOICE_OUTBOUND_MIN_BUILD_ANDROID (variabel lingkungan). TELEGRAM_VOICE_EVIDENCE_KEY_DIR yang hanya ditulis di .env tidak sampai ke server — tambahkan ke pabal-server di deploy/docker-compose.voice.yml, pada environment (path di dalam kontainer) dan volumes (folder di host).
  2. Aplikasi: rilis versi aplikasi dengan layar izin. Aturan tautan izin aplikasi hanya mengenal pabal.me, jadi untuk server lain host-nya harus ditambahkan ke aturan tautan versi aplikasi agar tautan yang dikirim bot bisa membuka layar (menu dan daftar pengaturan tetap berfungsi).
  3. Hukum: muat bagian id="bot-calls-outbound" dalam kebijakan privasi dan bagian id="voice-bot-owners-outbound" dalam ketentuan layanan (juga bagian voice-bot-owners, agar pemilik bisa menyetujui ketentuan). Drafnya ada di docs/voice-bots/p0/ di repositori. Teks layar izin dan rekaman pengumuman panggilan keluar (notice-out·apology-rule) juga ditinjau dan disetujui pada tahap ini.
  4. Sakelar: di Panggilan dari bot pada tab Suara di halaman admin, tetapkan negara dan batas lalu nyalakan dengan kalimat konfirmasi (Halaman admin — tab Suara). Setiap bot juga melewati tinjauan panggilan keluar oleh operator.
  • Gateway: hanya gateway dengan VOICE_OUTBOUND=true yang melakukan panggilan bot. Dengan mesin model, gateway hanya mau mulai jika keempat rekaman pengumuman panggilan keluar sudah disetujui; rekaman draf (VOICE_OUTBOUND_DRAFT_NOTICES) khusus server uji.
  • Jangan taruh nama khusus uji dan pengembangan seperti TELEGRAM_VOICE_CALLME_UNREVIEWED dan TELEGRAM_VOICE_TEST_DIAL di .env produksi.
  • Panggilan dari bot juga tidak terenkripsi ujung ke ujung — suara melewati gateway server ini dan penyedia AI.

13. Gambar peta di pesan lokasi (opsional)

Saat lokasi dikirim, aplikasi Android dan aplikasi desktop (Mac, Windows) tidak menggambar sendiri peta di dalam gelembung pesan, tetapi meminta gambar peta titik itu ke server (upload.getWebFile). Server mengambil gambarnya dari Maps Static API Google lalu meneruskannya. Tanpa fitur ini, Android menampilkan gambar pengganti dan desktop hanya kotak kosong dengan pin. iPhone menggambar peta di ponsel sendiri, jadi pengaturan ini tidak berpengaruh di sana; mengirim lokasi dan peta besar yang terbuka saat lokasi diketuk tetap berfungsi.

  1. Di konsol Google Cloud, pilih proyek yang terhubung dengan akun penagihan, lalu aktifkan Maps Static API.
  2. Buat kunci API; batasi aplikasinya ke alamat IP (IP publik yang dipakai server untuk keluar ke internet) dan API-nya ke Maps Static API saja. Kunci ini hanya ada di server dan tidak pernah masuk ke aplikasi.
  3. Masukkan ke .env, lalu mulai ulang server dengan docker compose up -d pabal-server.
  4. Periksa log. Jika peta mati, Maps in location messages are off: … menyebutkan alasannya (nilai kunci tidak pernah muncul di log).
STATIC_MAPS_KEY=AIza…               # jangan tinggalkan nilai kunci di obrolan, log, atau repositori
STATIC_MAPS_PER_DAY=300             # jumlah gambar baru yang diambil per hari (0 = tanpa batas)
STATIC_MAPS_PER_USER_HOUR=120       # jumlah gambar baru yang boleh dipicu satu orang per jam
INFO  TelegramServer - Maps in location messages from Google Static Maps (120 new per person an hour, 300 a day)
  • Biaya: Maps Static API gratis sampai 10.000 permintaan per bulan. Server menyimpan setiap gambar selama sehari dan memberikannya lagi ke perangkat yang meminta peta yang sama; gambar yang diberikan lagi tidak dihitung dalam batas. Batas bawaan (300 per hari) berarti sekitar 9.000 per bulan, masih dalam kuota gratis. Permintaan yang melewati batas ditolak dengan FLOOD_WAIT, dan selama itu aplikasi menampilkan gambar pengganti.
  • Kebijakan privasi: koordinat (lintang dan bujur) lokasi yang dibagikan dikirim ke Google — tanpa siapa pengirimnya. Di halaman admin → Pengaturan → informasi operator, tambahkan Google LLC (Maps Static API, Amerika Serikat) ke pihak pemroses dan transfer ke luar negeri.
  • Jika log menampilkan Map picture not made: Google Static Maps answered 403: …, periksa batasan kunci (IP, API) atau akun penagihannya.

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 saat server dimulai 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.

Mengembalikan versi: kembali ke image sebelumnya tidak menghapus apa yang sudah ditulis versi baru ke basis data, dan versi lama mungkin tidak bisa membacanya (misalnya, versi yang memuat kode bot suara menulis peristiwa baru seperti riwayat panggilan di obrolan, walaupun suara mati). Jadi saat mengembalikan versi, pulihkan juga basis data dari cadangan yang dibuat tepat sebelum pembaruan (Pemulihan).

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
docker compose exec -T pabal-postgres psql -U pabal -d pabal -c "ALTER DATABASE pabal SET search_path = public"   # jalur bawaan basis data — pemulihan tidak membawanya kembali
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       # mulai 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 .env 600, POSTGRES_PASSWORD berupa nilai acak
  • Izin /srv/pabal/pabal_server/keys 700, dan kunci privat tidak ada di tempat lain selain cadangan
  • (Jika push dinyalakan) izin firebase-service-account.json 600, dan isinya tidak ada di repositori maupun .env
  • (Jika push iPhone dinyalakan) izin AuthKey_<ID kunci>.p8 600, dan isinya tidak ada di repositori maupun .env
  • 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_LOCAL tidak dinyalakan (agar webhook bot tidak bisa menuju jaringan internal)
  • (Jika bot suara dinyalakan) Rahasia link, kunci Vertex, dan kunci induk hanya bisa dibaca oleh pengguna yang memakainya (400/600), dan kunci induk berada di luar /srv/pabal sehingga tidak masuk cadangan. Port link tidak dipublikasikan dan jaringan link hanya dipakai server dan gateway. Server produksi tidak memakai TELEGRAM_VOICE_NOTICE_CHECK maupun TELEGRAM_VOICE_TESTERS=*
  • 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

GejalaPenyebab → solusi
Aplikasi tertahan di "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 terputusAplikasi berpindah ke alamat yang diberitahukan server (PUBLIC_IP:MTPROTO_PORT), tetapi alamat itu salah → periksa PUBLIC_IP
Error sertifikat HTTPSDNS belum mengarah ke server ini, atau port 80 terblokir → dig +short domain, docker compose logs pabal-caddy
server terus dimulai ulang, password authentication failedPOSTGRES_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 lambatNaikkan -Xmx di JAVA_OPTS lalu docker compose up -d pabal-server
Kode pendaftaran tidak datangLihat status pengiriman dan alasan kegagalan di tab Kode pendaftaran (가입 코드) pada halaman admin → periksa nilai SMS dan SMTP di tab Pengaturan (설정), lalu Kirim uji (테스트 발송)
Notifikasi tidak masuk di Android① Di log ada Push notifications are off → path FCM_CREDENTIALS dan izin berkas ② Tidak ada Push device registered → aplikasi dibangun tanpa pengaturan Firebase (pabal-placeholder) atau aplikasi baru belum dibuka ③ Tertulis sent tetapi tidak muncul → izin notifikasi dan pengaturan baterai ponsel, serta apakah aplikasi di-"Paksa berhenti" ④ SENDER_ID_MISMATCH … Registrations are kept → kunci server dan google-services.json aplikasi berasal dari project yang berbeda ⑤ answered 404 without naming the token → FCM_PROJECT_ID salah ⑥ refused an access token → kunci sudah dicabut atau milik project lain (tidak ditanyakan lagi selama 5 menit)
Notifikasi tidak masuk di iPhone① Di log ada iPhone push notifications are off → path APNS_KEY, izin berkas, dan namanya (AuthKey_<ID kunci>.p8) ② Tidak ada pendaftaran type 1 → notifikasi tidak diizinkan di aplikasi, atau yang dipakai adalah simulator ③ Ada pendaftaran tetapi tidak ada yang terkirim → build itu lebih rendah dari APNS_MIN_BUILD (lihat build di baris pendaftaran), atau notifikasi obrolan itu dimatikan ④ APNs refused the provider token → kunci sudah dicabut atau APNS_TEAM_ID berbeda dengan tim pemilik kunci (tidak dikirim lagi selama 5 menit) ⑤ DeviceTokenNotForTopic … Registrations are kept → APNS_TOPIC berbeda dengan bundle ID aplikasi ⑥ Tertulis sent tetapi yang terlihat hanya "Ada notifikasi baru" → ekstensi notifikasi gagal membukanya (jika aplikasi dipasang ulang, buka sekali agar mendaftar ulang) ⑦ Panggilan hanya datang sebagai notifikasi → APNS_VOIP_MIN_BUILD kosong atau lebih tinggi dari build itu (memang begitu rancangannya)
Tidak bisa masuk ke halaman adminPastikan 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 foundJaringan 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, update bot yang belum diambil, dan teks Simpan percakapan yang menunggu ditulis 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.
  • Notifikasi push hanya sampai ke aplikasi ponsel — Android (langkah 10) dan iPhone (langkah 11). Di iPhone, push hanya sampai ke build APNS_MIN_BUILD ke atas (Pabal 1.0 (3) tidak termasuk), dan layar panggilan CallKit baru menyala setelah APNS_VOIP_MIN_BUILD ditetapkan. Aplikasi Mac dan Windows menerima pesan selama terbuka.
  • "Matikan notifikasi" per obrolan hanya disimpan di memori server. Push untuk obrolan yang notifikasinya dimatikan dikirim ke Android dengan prioritas tidak mendesak (ponsel menyaringnya sendiri) dan tidak dikirim ke iPhone, tetapi setelah server dimulai ulang, keduanya kembali terkirim seperti biasa (aplikasi hanya memberi tahu server lagi saat pengaturannya diubah). Jika aplikasi yang tertutup butuh lebih dari 20 detik untuk bangun dan tersambung ke server, panggilan bisa berakhir sebagai panggilan tak terjawab di pihak penelepon.
  • Lampiran pesan bisa berupa foto dan berkas, tetapi pesan video dan suara yang diputar di dalam aplikasi belum ada. Kanal, supergrup, panggilan grup, dan verifikasi dua langkah juga belum tersedia.
  • Auth key tersimpan di DB sebagai teks biasa (diperlukan agar server bisa mendekripsi). Jaga DB dan cadangannya seketat kunci RSA.

Berkas

BerkasPeran
deploy/docker-compose.ymlSusunan produksi (pabal-server · pabal-postgres · pabal-caddy, bind mount)
deploy/CaddyfileHTTPS, pemblokiran jalur admin, penerusan situs, dokumentasi, dan Bot API
deploy/.env.exampleContoh pengaturan → deploy/.env
deploy/backup.shDump DB + arsip kunci dan data, pembersihan cadangan lama
DockerfileImage server (build → JRE 21, uid 1000)
voice/Gateway suara pabal-voice (kode sumber, Dockerfile, README.md, spesifikasi link PROTOCOL.md)
deploy/docker-compose.mcp.ymlTiga kontainer komponen MCP untuk bot suara (profil mcp)
deploy/docker-compose.voice.ymlMenambahkan komponen bot suara — pengaturan suara server dan jaringan link, gateway pabal-voice (profil voice), dan pintu masuk audio jam tangan pabal-voice-edge (profil voice-watch)
deploy/ship.shMembangun image server (dengan --voice, image gateway juga) untuk CPU server di komputer lain, memuatnya lewat SSH, dan menyalin berkas penerapan
deploy/voice_readiness.pyPemeriksaan sebelum menyalakan suara (kunci Vertex, bagian hukum, build aplikasi, relay, …) — tidak mencetak rahasia apa pun
deploy/turnserver.conf.example, deploy/check_relay.pyContoh pengaturan coturn yang dipasang langsung di server Mac, dan alat pemeriksa relay