Vận hành máy chủ
Cài đặt máy chủ
Cách cài đặt và vận hành máy chủ Pabal bằng Docker Compose trên một máy chủ công khai trên Internet.
Đây là cách cài đặt máy chủ Pabal trên một máy chủ công khai trên Internet. Dùng Docker Compose để chạy ba container máy chủ Pabal · PostgreSQL · Caddy (HTTPS), và mọi dữ liệu không được phép mất đều được bind vào thư mục trên máy chủ (host). Chỉ cần làm theo lần lượt từ trên xuống.
Tên miền pabal.me, IP công khai của máy chủ 203.0.113.10 (địa chỉ dùng để minh họa — hãy thay bằng IP thật khi đọc), vị trí mã nguồn /opt/pabal, vị trí dữ liệu /srv/pabal, hệ điều hành Ubuntu 24.04 LTS. Các tệp cần thiết nằm trong thư mục deploy/ của kho mã.
Tổng quan
Giải thích sơ đồ
- Ba vùng: người dùng trên Internet → ba container trên máy chủ vận hành → các thư mục bind trên ổ đĩa của host. Container có thể xóa và tạo lại bất cứ lúc nào, còn mọi thứ cần giữ lại đều nằm trong
/srv/pabal. - Chỉ có hai cổng vào: ứng dụng kết nối thẳng đến máy chủ qua cổng MTProto (tự mã hóa nên không cần HTTPS), còn trang web, tài liệu và Bot API đi qua cổng 443 của Caddy. Caddy chặn các đường dẫn quản trị (
/admin,/health…) và chỉ chuyển tiếp phần còn lại. - Nét đứt màu đỏ là lối đi riêng của người vận hành. Trang quản trị chỉ mở trên
127.0.0.1:8080của máy chủ nên chỉ vào được qua tunnel SSH. PostgreSQL hoàn toàn không mở cổng nào ra bên ngoài. - Hai ô màu đỏ là dữ liệu quan trọng nhất. Khóa RSA trong
pabal_server/keysđược nhúng vào ứng dụng khi build, nên nếu mất thì phải phát hành lại toàn bộ ứng dụng; cònpostgres_data/chứa mọi tài khoản và cuộc trò chuyện. Đây là ưu tiên sao lưu số một. - Mũi tên xám là việc lưu trữ: máy chủ ghi khóa, ảnh, cài đặt và log; PostgreSQL ghi sự kiện; Caddy ghi chứng chỉ — mỗi thứ vào thư mục riêng của mình.
Cần chuẩn bị
| Mục | Nội dung |
|---|---|
| Máy chủ | Ubuntu 24.04 LTS, mức khởi điểm 2 vCPU · 4 GB RAM · 40 GB SSD. Khi số người dùng và tin nhắn tăng, hãy tăng bộ nhớ trước |
| IPv4 công khai | IP tĩnh. Ứng dụng được build với địa chỉ máy chủ dạng IP, và máy chủ cũng thông báo IP cho ứng dụng |
| Tên miền | Một tên miền mà bạn sửa được DNS (bản ghi A) |
| Phương tiện gửi mã đăng ký | Một tài khoản SMS (Twilio · Solapi · webhook) hoặc email (SMTP) |
| Máy Mac | Máy Mac để build ứng dụng cho máy chủ này (nếu muốn làm cả ứng dụng Windows thì cần thêm một máy tính Windows) |
| Dự án Firebase (tùy chọn) | Khi muốn gửi thông báo đẩy đến ứng dụng Android. Tạo miễn phí bằng tài khoản Google (bước 10) |
Cổng
| Cổng | Ai dùng | Mở | Mô tả |
|---|---|---|---|
| 22/tcp | Người vận hành | Nên chỉ cho IP người vận hành | SSH |
| 80/tcp | Caddy | Mọi người | Cấp chứng chỉ · chuyển hướng sang HTTPS |
| 443/tcp, 443/udp | Caddy | Mọi người | Trang web · tài liệu · Bot API (udp dành cho HTTP/3) |
| 8443/tcp | Máy chủ | Mọi người | Ứng dụng kết nối (MTProto) |
| 8080/tcp | Máy chủ | Không mở | Trang quản trị · health check — chỉ trên 127.0.0.1 của máy chủ |
| 5432/tcp | PostgreSQL | Không mở | Chỉ trong mạng nội bộ của container |
Cổng 443 đã được trang web (HTTPS) dùng. MTProto không phải HTTP cũng không phải TLS, nên không thể tách ra để nhận chung trên một cổng. Nếu vì mạng công ty hay trường học chặn các cổng ít gặp mà bạn muốn ứng dụng cũng nhận trên 443, hãy xin thêm một IP nữa và giao cổng 443 của IP đó cho máy chủ (trong .env đặt PUBLIC_IP là IP thứ hai, MTPROTO_PORT=443, và tách cổng trong tệp compose bằng cách gắn IP vào). Máy chủ báo cho ứng dụng "hãy kết nối đến cổng này của IP này", nên cổng ứng dụng bên trong và bên ngoài container phải giống nhau.
1. Chuẩn bị máy chủ
sudo apt update && sudo apt -y upgrade
timedatectl # kiểm tra có "System clock synchronized: yes" không
Đồng bộ thời gian là bắt buộc. Số hiệu tin nhắn MTProto được tạo từ thời gian, nên nếu đồng hồ máy chủ bị lệch, ứng dụng sẽ kết nối đi kết nối lại liên tục. Nếu là no thì chạy sudo timedatectl set-ntp true.
# Tường lửa
sudo ufw allow OpenSSH # nếu được: sudo ufw allow from <IP người vận hành> to any port 22
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw allow 8443/tcp
sudo ufw enable
# Docker (script cài đặt chính thức)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER # đăng nhập lại để dùng docker không cần sudo
docker compose version # v2 trở lên
Các cổng mà Docker mở (ports:) sẽ được mở bất kể quy tắc ufw. Vì vậy tệp compose chỉ mở các cổng cần công khai (80 · 443 · 8443), cổng quản trị chỉ mở ở 127.0.0.1:8080, và PostgreSQL thì không mở. Hãy nhớ điều này khi sửa ports:.
2. Lấy mã nguồn
sudo mkdir -p /opt/pabal && sudo chown $USER: /opt/pabal
git clone <địa-chỉ-kho-mã> /opt/pabal
Nếu sao chép từ máy tính phát triển, đừng gửi khóa, dữ liệu và log (rsync --exclude 'keys/' --exclude 'data/' --exclude 'logs/' --exclude '**/target/'). Khóa RSA của máy chủ vận hành sẽ được tạo mới khi máy chủ vận hành chạy lần đầu — nếu dùng khóa phát triển cho môi trường vận hành, chiếc máy tính phát triển đó sẽ trở thành nơi có thể giải mã lưu lượng vận hành.
3. Thư mục dữ liệu (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 # container máy chủ chạy với uid 1000
sudo chown -R $USER: /srv/pabal/backups
sudo chmod 700 /srv/pabal/pabal_server/keys /srv/pabal/backups
| Thư mục | Trong container | Chứa gì | Nếu mất |
|---|---|---|---|
pabal_server/keys | /app/keys | private.pem (khóa bí mật RSA của máy chủ), private.pem.pub, (tùy chọn) firebase-service-account.json (khóa bí mật Firebase dùng cho thông báo đẩy Android) | Phải build lại và phát hành lại toàn bộ ứng dụng (khóa Firebase thì chỉ cần tải lại từ console) |
pabal_server/data | /app/data | Ảnh (media/), admin-token, operations.json (cấu hình mã đăng ký, SMS, SMTP, gồm cả các giá trị bí mật) | Ảnh và cấu hình vận hành |
pabal_server/logs | /app/logs | Log máy chủ (nén mỗi ngày) | Chỉ mất lịch sử ghi |
postgres_data | /var/lib/postgresql/data | Toàn bộ tài khoản, cuộc trò chuyện, tin nhắn, đăng nhập, bot, cấu hình webhook | Toàn bộ dịch vụ |
caddy/data, caddy/config | /data, /config | Chứng chỉ HTTPS | Sẽ được cấp lại |
backups | (chỉ trên host) | Kết quả của backup.sh | — |
4. Tệp cấu hình .env
cd /opt/pabal/deploy
cp .env.example .env
chmod 600 .env
openssl rand -base64 30 | tr -d '/+=' | cut -c1-32 # dùng giá trị in ra cho POSTGRES_PASSWORD
nano .env
DOMAIN=pabal.me
PUBLIC_IP=203.0.113.10
POSTGRES_PASSWORD=(giá trị vừa tạo ở trên)
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
| Biến | Giá trị cần điền | Ghi chú |
|---|---|---|
DOMAIN | Tên miền của trang web | Liên kết do ứng dụng tạo (pabal.me/tên-người-dùng), địa chỉ trong tài liệu và bản xem trước liên kết đều dùng địa chỉ này |
PUBLIC_IP | IPv4 công khai của máy chủ | Nếu dùng đám mây, đó là IP công khai hiển thị trên bảng điều khiển (không phải IP riêng bên trong máy chủ) |
POSTGRES_PASSWORD | Giá trị ngẫu nhiên | Hãy đặt trước khi chạy lần đầu. PostgreSQL chỉ dùng giá trị này khi tạo thư mục dữ liệu lần đầu |
PABAL_HOME | /srv/pabal | Thư mục ở bước 3 |
PABAL_SOURCE | .. | Kho mã nguồn dùng để build image máy chủ: .. nếu tệp compose nằm trong deploy/ của kho, nếu không thì là đường dẫn tới mã nguồn |
PABAL_NETWORK | ssemiya-net | Mạng Docker mà ba container cùng tham gia. Tạo trước (bước 6) |
MTPROTO_PORT | 8443 | Nếu đổi thì phải đổi cả tường lửa và bản build ứng dụng |
ADMIN_TOKEN | Để trống | Nếu để trống, token sẽ được tạo trong pabal_server/data/admin-token khi chạy lần đầu. Muốn tự đặt thì dùng từ 16 ký tự trở lên |
JAVA_OPTS | Giá trị mặc định | Nếu thiếu bộ nhớ, hãy tăng -Xmx (đến khoảng một nửa bộ nhớ máy chủ) |
FCM_CREDENTIALS | Để trống | Thông báo đẩy Android. Khi bật thì đặt /app/keys/firebase-service-account.json (bước 10). Để trống thì máy chủ chạy không có thông báo đẩy |
FCM_PROJECT_ID | Để trống | Chỉ dùng khi gửi đến một dự án Firebase khác với project_id trong tệp khóa |
PUSH_PREVIEWS | true | Nếu là false, thông báo chỉ hiện người gửi, không có nội dung tin nhắn |
Tệp compose vận hành khởi động máy chủ với số thử nghiệm đã tắt (TELEGRAM_TEST_NUMBERS=false). Toàn bộ giá trị cấu hình mà máy chủ đọc có trong Quản trị và cấu hình — biến môi trường.
5. DNS
| Tên | Loại | Giá trị |
|---|---|---|
pabal.me | A | 203.0.113.10 |
www.pabal.me | A | 203.0.113.10 |
dig +short pabal.me # phải hiện 203.0.113.10 (mất vài phút đến vài giờ để có hiệu lực)
Có thể khởi động trước khi DNS trỏ về máy chủ. Caddy sẽ tự thử lại cho đến khi lấy được chứng chỉ.
6. Build và khởi động
cd /opt/pabal/deploy
docker network inspect ssemiya-net >/dev/null 2>&1 || docker network create ssemiya-net # chỉ một lần
docker compose up -d --build # lần đầu mất vài phút (Maven tải thư viện)
docker compose ps # pabal-server, pabal-postgres, pabal-caddy ở trạng thái running (healthy)
docker compose logs -f pabal-server # nhấn Ctrl+C để thoát
Khi khởi động lần đầu, log sẽ có những dòng như sau.
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/)
- Dòng đầu tiên là WARN nhưng vẫn bình thường (thông báo khóa chỉ được tạo một lần duy nhất). Từ lần sau sẽ hiện
Loaded RSA key … (fingerprint …). Fingerprint khác nhau ở mỗi máy chủ. - Các bảng được máy chủ tự tạo khi khởi động.
7. Kiểm tra
# Từ bên ngoài (máy tính của bạn)
curl -sI https://pabal.me/ | head -1 # HTTP/2 200
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/docs/ # 200 — tài liệu này
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/admin/ # 404 — trang quản trị không công khai
curl -s https://pabal.me/docs/server-key.pem | head -1 # -----BEGIN RSA PUBLIC KEY-----
nc -vz 203.0.113.10 8443 # succeeded — cổng của ứng dụng
# Trên máy chủ
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. Trang quản trị và cấu hình ban đầu
# Trên máy tính của bạn (để lệnh này tiếp tục chạy)
ssh -N -L 8080:127.0.0.1:8080 <người-dùng>@203.0.113.10
# Lấy token trên máy chủ
sudo cat /srv/pabal/pabal_server/data/admin-token
Mở http://localhost:8080/admin/ bằng trình duyệt và nhập token. Những thiết lập bắt buộc trước khi vận hành (chi tiết xem Quản trị và cấu hình):
- Chuyển Cách gửi mã (코드 전달 방식) sang SMS hoặc email. Với cách "Trang quản trị" (관리 화면), người vận hành phải tự báo mã cho từng người.
- Điền các giá trị SMS hoặc email (SMTP) rồi lưu → bấm Gửi thử (테스트 발송) để kiểm tra mã có đến thật không.
- Kiểm tra Số thử nghiệm (테스트 번호) đã tắt. Nếu bật, bất kỳ ai cũng đăng nhập được bằng số
+99966…. - Nếu cần, hãy tắt Cho phép đăng ký mới (새 가입 허용) khi bắt đầu để chỉ nhận trước những người được mời.
9. Kết nối ứng dụng với máy chủ này
Ứng dụng được build với IP · cổng · khóa công khai của máy chủ nhúng sẵn bên trong. Phải build lại bằng khóa công khai của máy chủ này thì ứng dụng mới kết nối được. Trên Mac:
# 1. Khóa công khai của máy chủ này (khóa công khai chứ không phải khóa bí mật — tải từ trang tài liệu cũng được)
curl -s -o keys/production.pem.pub https://pabal.me/docs/server-key.pem
# 2. Trỏ mã nguồn ứng dụng đến máy chủ này + áp dụng thương hiệu
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
Sau đó làm bước 5 (configure) và bước 6 (build) trong docs/tdesktop-build-guide.md của kho mã. Hãy sao chép kết quả out/Debug/Pabal.app sang một tên khác — nếu build lại cùng mã nguồn cho máy chủ khác, nó sẽ bị ghi đè.
Bản build hiện tại là bản debug chưa được ký và chưa được Apple chứng thực (notarize), nên người nhận phải nhấp chuột phải → Mở khi mở lần đầu. Bản debug lưu dữ liệu trong thư mục cạnh ứng dụng (tdata/), và nếu không ghi được vào đó thì sẽ dùng chung thư mục dữ liệu với Telegram Desktop thật. Hãy hướng dẫn người dùng đặt ứng dụng trong thư mục người dùng (ví dụ ~/Applications/Pabal/). Bản release để phân phối rộng rãi (tách thư mục dữ liệu, ký và chứng thực của Apple) là một công việc riêng.
Với ứng dụng Android, iPhone và Windows, cách làm tương tự (chạy script đưa địa chỉ và khóa công khai của máy chủ vào → build) có trong docs/mobile-apps-guide.md của kho mã. Muốn ứng dụng Android có thông báo đẩy thì phải có thiết lập ứng dụng Firebase ở bước 10 trước khi build.
10. Thông báo đẩy Android (tùy chọn)
Android sẽ sớm cho ngủ những ứng dụng đã rời khỏi màn hình. Nếu không bật thông báo đẩy, tin nhắn chỉ hiện ra và cuộc gọi chỉ đổ chuông trong lúc ứng dụng đang mở. Thông báo đẩy được gửi qua Firebase Cloud Messaging (FCM) của Google (miễn phí), và nội dung thông báo (tên người gửi, bản xem trước tin nhắn) được máy chủ mã hóa bằng khóa mà chỉ chiếc điện thoại đó có, nên Google không đọc được.
Giải thích sơ đồ
- Ba vùng: dự án Firebase do người vận hành tạo → nơi build ứng dụng và bật máy chủ → điện thoại và Google. Hai tệp được lấy từ cùng một dự án Firebase.
- Mũi tên xám là phần chuẩn bị chỉ làm một lần. Ô xanh lá
google-services.jsonnằm trong ứng dụng và có mặt trong mọi APK nên không phải bí mật, còn ô đỏ, khóa tài khoản dịch vụ, là bí mật chỉ để trongkeys/của máy chủ. Hai tệp phải lấy từ cùng một dự án. - Mũi tên xanh dương là đường đi của thông báo: ① khi mở ứng dụng lần đầu, điện thoại đăng ký token FCM và khóa mà chỉ nó có với máy chủ; ② khi có tin nhắn hay cuộc gọi mới, máy chủ mã hóa thông báo bằng khóa đó rồi chuyển cho FCM; ③ FCM đánh thức điện thoại, ứng dụng giải mã, hiện thông báo rồi kết nối lại máy chủ để nhận những gì đã lỡ.
- Thứ Google thấy chỉ là thông tin chuyển phát như token thiết bị, thời điểm gửi và mức ưu tiên. Tên người gửi và tin nhắn là bản mã nên Google không đọc được.
- Điện thoại đang mở ứng dụng thì không được gửi thông báo đẩy — vì nó đã nhận qua kết nối. Thông báo đẩy của các cuộc trò chuyện đã tắt thông báo được gửi với mức ưu tiên không khẩn, và ứng dụng tự lọc bỏ.
Khóa của máy chủ chỉ gửi được thông báo đẩy đến ứng dụng build bằng cùng một dự án Firebase. Ứng dụng do người khác build (ví dụ ứng dụng tải từ Pabal.me) gắn với dự án Firebase của người đó, và ngay từ đầu đã được build bằng địa chỉ và khóa công khai của máy chủ người đó. Ứng dụng cho máy chủ của bạn thì bạn tự build, và dự án Firebase cũng do bạn tự tạo.
Tạo dự án Firebase
- Trong Firebase console, chọn Thêm dự án. Đặt tên gì cũng được, và không cần Google Analytics.
- Cài đặt dự án → Chung → Ứng dụng của bạn → Thêm ứng dụng (Android). Nhập
me.pabal.androidvào tên gói (tên mà script build gắn cho ứng dụng). Vân tay chứng chỉ (SHA-1) không cần cho thông báo đẩy. - Tải về
google-services.jsoncủa ứng dụng đó. Đây là thiết lập đưa vào ứng dụng, không phải bí mật. - Vào Cài đặt dự án → Tài khoản dịch vụ → Tạo khóa riêng tư mới để tải về tệp JSON, rồi đổi tên thành
firebase-service-account.json. Tệp này là bí mật — đừng đưa nội dung của nó vào kho mã, cuộc trò chuyện hay.env. - Trong Cài đặt dự án → Cloud Messaging, kiểm tra xem Firebase Cloud Messaging API (V1) đã được bật chưa (dự án mới thường đã bật sẵn).
Đưa thiết lập vào ứng dụng
Trước khi build ứng dụng Android, hãy đặt google-services.json vào chỗ mà script build tìm đến. Script kiểm tra tệp (dự án có đăng ký me.pabal.android chưa, có phải bạn đưa nhầm khóa bí mật không) rồi mới đưa nó vào mã nguồn ứng dụng.
mkdir -p ~/.pabal/android
cp ~/Downloads/google-services.json ~/.pabal/android/ # muốn đặt ở chỗ khác thì thêm --google-services <đường dẫn> cho apply_branding.py
python3 scripts/android/point_to_server.py <mã nguồn Android> \
--host 203.0.113.10 --port 8443 --key keys/production.pem.pub
python3 scripts/android/apply_branding.py <mã nguồn Android>
# ở cuối phải hiện "Firebase: project <ID dự án>". Nếu thấy pabal-placeholder thì ứng dụng sẽ không có thông báo đẩy
Sau đó, build theo mục Android (Android 앱) trong docs/mobile-apps-guide.md của kho mã. Điện thoại đang dùng ứng dụng từ trước khi có thông báo đẩy cũng phải cập nhật lên ứng dụng vừa build thì mới nhận được thông báo.
Đặt khóa bí mật trên máy chủ
sudo cp firebase-service-account.json /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/firebase-service-account.json # người dùng của container máy chủ
sudo chmod 600 /srv/pabal/pabal_server/keys/firebase-service-account.json
Thêm một dòng vào .env. Bên trong container, pabal_server/keys là /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 dự án>, 0 device registrations, message text included (encrypted per device)
- Thấy dòng này là đã bật. Nếu không đọc được tệp hoặc tệp sai định dạng, lý do sẽ hiện ở
Push notifications are off: …(nội dung tệp không xuất hiện trong log). - Nếu
PUSH_PREVIEWS=false, cuối dòng sẽ làmessage text left out, và thông báo chỉ hiện người gửi, không có nội dung tin nhắn. - Container phải mở được kết nối ra ngoài tới
oauth2.googleapis.comvàfcm.googleapis.com(443).
Kiểm tra
- Cài (hoặc cập nhật) ứng dụng vừa build lên điện thoại và mở một lần. Log máy chủ sẽ ghi
Push device registered: user … type 2 token … (push key 256 bytes). Token chỉ được ghi dưới dạng dấu vân tay. - Vuốt để đóng ứng dụng trong danh sách ứng dụng gần đây. Đừng dùng "Buộc dừng" trong Cài đặt — Android sẽ không chuyển thông báo đẩy cho đến khi bạn tự mở lại ứng dụng.
- Gửi tin nhắn từ một tài khoản khác: log ghi
Push MESSAGE_TEXT to user …: sentvà điện thoại hiện thông báo.
- Chính sách quyền riêng tư: trong trang quản trị → Cài đặt (설정) → Thông tin người vận hành (운영자 정보), hãy ghi Google LLC (Firebase Cloud Messaging, Hoa Kỳ) vào Ủy thác xử lý (처리 위탁) và Chuyển ra nước ngoài (국외 이전). Nội dung chính sách đã có câu nói rằng thông báo đi qua FCM. Hãy ghi nội dung tương tự vào biểu mẫu "An toàn dữ liệu" của Google Play.
- Nếu khóa bị lộ, hãy xóa khóa đó trong Firebase console, tải khóa mới, thay tệp rồi khởi động lại máy chủ. Không cần build lại ứng dụng.
iPhone được bật riêng, không phải bằng Firebase mà bằng khóa APNs (.p8) của tài khoản nhà phát triển Apple — xem bước 11 ngay bên dưới. Ứng dụng Mac và Windows không dùng thông báo đẩy mà nhận tin trong lúc đang bật.
11. Thông báo đẩy iPhone (tùy chọn)
iPhone cũng sớm dừng những ứng dụng đã rời khỏi màn hình. Nếu không bật thông báo đẩy, tin nhắn và cuộc gọi chỉ đến trong lúc ứng dụng đang mở. Thông báo đẩy được gửi qua dịch vụ thông báo đẩy của Apple (APNs), và giống như Android, nội dung thông báo được mã hóa bằng khóa mà chỉ chiếc iPhone đó có. Phần chữ thông báo mà Apple thấy được chỉ là tên ứng dụng và một câu không có nội dung như "Bạn có thông báo mới"; phần mở rộng thông báo (Notification Service Extension) bên trong ứng dụng iPhone sẽ giải mã bản mã và hiển thị thành người gửi và tin nhắn.
- Đăng ký: mỗi lần mở, ứng dụng đăng ký hai thứ cho mỗi tài khoản — một cho thông báo (type 1) và một cho cuộc gọi (VoIP, type 9). Cùng với đăng ký, máy chủ ghi nhớ ngôn ngữ của ứng dụng (ngôn ngữ dùng cho phần chữ bên ngoài) và số build.
- Tin nhắn được gửi thành thông báo. Các cuộc trò chuyện đã tắt thông báo thì không được gửi gì. Những gì đã đọc hoặc đã xóa trên thiết bị khác được báo bằng push chạy nền không có âm thanh, để ứng dụng dọn các thông báo đang hiện.
- Cuộc gọi nếu gửi bằng VoIP sẽ hiện màn hình cuộc gọi CallKit ngay cả khi màn hình đang khóa. Tuy nhiên, ứng dụng nhận VoIP phải báo cuộc gọi cho CallKit ngay lập tức; nếu không làm được, iOS sẽ đóng ứng dụng, và nếu lặp lại nhiều lần thì iOS ngừng gửi VoIP. Vì vậy chỉ bật VoIP bằng
APNS_VOIP_MIN_BUILDtừ bản build đã được làm như vậy; trước đó, cuộc gọi cũng được gửi thành thông báo.
Khác với Firebase, không có tệp thiết lập nào phải đưa vào ứng dụng — ứng dụng đã được build sẵn để dùng APNs (quyền thông báo, App Group, phần mở rộng thông báo), chỉ cần đặt khóa trên máy chủ. Tuy nhiên, bằng khóa của máy chủ chỉ có thể gửi thông báo đẩy tới những ứng dụng được ký bởi nhóm đã tạo khóa đó và có bundle ID trùng với APNS_TOPIC. Nếu bạn đã ký ứng dụng iPhone cho máy chủ của mình bằng tài khoản nhà phát triển Apple của bạn, hãy tạo khóa APNs cũng trong tài khoản đó.
Tạo khóa APNs
- Apple Developer → Certificates, IDs & Profiles → Keys → +. Phải là tài khoản của nhóm đã ký ứng dụng.
- Nhập tên và đánh dấu Apple Push Notifications service (APNs). Để môi trường là Sandbox & Production và phạm vi là toàn nhóm (Team Scoped) — vì thông báo được gửi tới hai nơi, thông báo thường và VoIP.
- Register → Download.
AuthKey_<ID khóa>.p8chỉ tải về được một lần. Tệp này là bí mật — đừng đưa nội dung của nó vào kho mã, cuộc trò chuyện hay.env.
Đặt khóa trên máy chủ
sudo cp AuthKey_<ID khóa>.p8 /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/AuthKey_<ID khóa>.p8 # người dùng của container máy chủ
sudo chmod 600 /srv/pabal/pabal_server/keys/AuthKey_<ID khóa>.p8
Thêm vào .env. Bên trong container, pabal_server/keys là /app/keys.
APNS_KEY=/app/keys/AuthKey_<ID khóa>.p8
APNS_TEAM_ID=4Z8R468M2D
APNS_TOPIC=me.pabal.ios
APNS_MIN_BUILD=4
APNS_VOIP_MIN_BUILD=
| Biến | Giá trị cần điền | Ghi chú |
|---|---|---|
APNS_KEY | Đường dẫn tệp khóa | ID khóa được đọc từ tên tệp. Nếu bạn đặt tệp dưới tên khác, hãy điền ID khóa vào APNS_KEY_ID |
APNS_TEAM_ID | ID nhóm đã ký ứng dụng | Với ứng dụng của Pabal là 4Z8R468M2D. Nếu bạn tự build và ký ứng dụng thì là nhóm của bạn |
APNS_TOPIC | Bundle ID của ứng dụng | Với ứng dụng của Pabal là me.pabal.ios. Cuộc gọi (VoIP) được gửi tới giá trị này có thêm .voip |
APNS_MIN_BUILD | 4 | Không gửi gì tới các bản build thấp hơn giá trị này (0 là gửi tới tất cả). 4 là giá trị loại trừ Pabal 1.0 (3), bản được làm trước khi thử thông báo đẩy |
APNS_VOIP_MIN_BUILD | Để trống | Nếu để trống, cuộc gọi cũng được gửi thành thông báo. Nếu điền số build đầu tiên báo cuộc gọi cho CallKit ngay lập tức, màn hình cuộc gọi CallKit sẽ hiện từ bản build đó |
docker compose up -d pabal-server
docker compose logs pabal-server | grep -i push
INFO TelegramServer - iPhone push notifications on: APNs key <ID khóa> of team 4Z8R468M2D, topic me.pabal.ios, builds from 4, calls as alerts (TELEGRAM_APNS_VOIP_MIN_BUILD not set)
- Thấy dòng này là đã bật. Nếu không đọc được tệp khóa hoặc tệp sai định dạng, lý do sẽ hiện ở
iPhone push notifications are off: …(nội dung tệp không xuất hiện trong log). Thông báo đẩy iPhone được bật và tắt riêng với Android (FCM) — chỉ bật một bên cũng được. - Container phải mở được kết nối ra ngoài tới
api.push.apple.com(443, HTTP/2). Bản build gỡ lỗi (debug) cài thẳng lên thiết bị bằng Xcode sẽ đi tớiapi.sandbox.push.apple.com(ứng dụng báo điều này khi đăng ký).
Kiểm tra
- Trên một chiếc iPhone thật, mở ứng dụng một lần và cho phép thông báo. Log máy chủ sẽ ghi
Push device registered: user … type 1 token … (push key 256 bytes, build …)và dòngtype 9. Không thể kiểm tra bằng trình mô phỏng (simulator) — ở đó không có token APNs lẫn CallKit. - Vuốt ứng dụng lên để đóng.
- Gửi tin nhắn từ một tài khoản khác: log ghi
Push MESSAGE_TEXT to user …: sentvà màn hình khóa hiện người gửi cùng nội dung.
- Chính sách quyền riêng tư: trong trang quản trị → Cài đặt (설정) → Thông tin người vận hành (운영자 정보), hãy ghi Apple Inc. (dịch vụ thông báo đẩy của Apple, Hoa Kỳ) vào Ủy thác xử lý (처리 위탁) và Chuyển ra nước ngoài (국외 이전). Nội dung chính sách đã có câu nói rằng thông báo đi qua APNs. Hãy kiểm tra cả mục ID thiết bị (token push) trong bảng khảo sát quyền riêng tư của ứng dụng trên App Store Connect.
- Nếu khóa bị lộ, hãy Revoke khóa đó trong tài khoản nhà phát triển, tạo khóa mới, thay tệp rồi khởi động lại máy chủ. Không cần build lại ứng dụng.
12. Bot thoại (tùy chọn)
Bot thoại là tính năng mà khi có người gọi cho bot từ ứng dụng, một mô hình giọng nói AI sẽ trả lời (phần dành cho chủ bot nằm ở Bot thoại). Máy chủ cần gắn thêm hai thành phần, đồng thời phải có đủ văn bản pháp lý và bản build ứng dụng thì tính năng mới bật. Mặc định là tắt.
Các thành phần thoại không nằm trong deploy/docker-compose.yml mà trong tệp bổ sung deploy/docker-compose.voice.yml — cài đặt thoại của máy chủ và mạng riêng cho liên kết, gateway pabal-voice (profile voice) và điểm vào âm thanh của đồng hồ pabal-voice-edge (profile voice-watch). Nếu .env không gọi tệp này thì hoàn toàn không có cài đặt thoại. Cấu hình này đã chạy thử với engine echo, nhưng chưa từng bật thoại trên máy chủ vận hành, và cũng chưa từng có cuộc gọi bot thoại nào đi trọn vẹn qua bộ chuyển tiếp (TURN) vận hành — thử nghiệm toàn tuyến được làm với bộ chuyển tiếp thử nghiệm bên trong mạng Docker (scripts/voice-e2e/run.sh). Trước khi bật, deploy/voice_readiness.py trong kho mã kiểm tra những gì còn thiếu. Các điều kiện trước khi công khai (rà soát pháp lý, câu trả lời bằng văn bản của nhà cung cấp, phát hành ứng dụng) là việc người vận hành phán đoán, và mã nguồn chỉ chặn một phần trong số đó.
| Thành phần | Việc nó làm | Nếu không có |
|---|---|---|
| pabal-voice (gateway, image 0.11.0) | Thay bot làm một đầu của cuộc gọi, thực hiện trao đổi khóa, và chuyển âm thanh qua lại với mô hình AI. Python 3.12 + ntgcalls. Giao tiếp với máy chủ qua liên kết (WebSocket, có xác thực nhưng không mã hóa) | Mọi cuộc gọi bot đều báo bận (no_gateway) |
| coturn (chuyển tiếp cuộc gọi) | Cuộc gọi bot chỉ dùng chuyển tiếp: cả ứng dụng lẫn gateway đều tắt kết nối trực tiếp và chỉ gửi âm thanh qua bộ chuyển tiếp. Là cùng bộ chuyển tiếp dùng cho cuộc gọi giữa người với người (TURN_SECRET) | Mọi cuộc gọi bot đều báo bận (no_relay), gateway cũng từ chối kết nối (4008) |
| Khóa Vertex AI | Khóa JSON của tài khoản dịch vụ Google Cloud. Chỉ tiến trình engine của gateway mở nó | Chỉ có echo, không có mô hình (VOICE_ENGINE=echo, âm thanh không rời khỏi máy) |
| (Tùy chọn) khóa chủ | Khóa bọc khóa riêng của chủ bot và bí mật của chế độ âm thanh gốc | Khóa riêng và chế độ âm thanh gốc bị tắt |
| (Tùy chọn) thành phần MCP | pabal-mcp · bộ ký · proxy đi ra (bên dưới) | Máy chủ MCP bị tắt |
Điều kiện để bật
| Máy chủ kiểm tra | Nếu không có |
|---|---|
Tệp bí mật liên kết (TELEGRAM_VOICE_LINK_SECRET_FILE, 32 byte) | Tính năng thoại tắt — cổng liên kết cũng không mở |
Chuyển tiếp cuộc gọi (TELEGRAM_TURN_HOST·TELEGRAM_TURN_SECRET) | Mọi cuộc gọi bot đều báo bận |
Mục id="bot-calls" trong chính sách quyền riêng tư của trang web (cả ko và en) — màn hình xác nhận của ứng dụng trỏ tới đây | Không bot nào nhận cuộc gọi (log ERROR). Bản nháp nằm ở docs/voice-bots/p0/privacy-*.draft.html |
Mục id="voice-bot-owners" trong điều khoản sử dụng | Không ai đồng ý được điều khoản chủ bot → không dùng được bản ghi hội thoại, khóa riêng, chế độ âm thanh gốc hay MCP |
Các mục id="bot-calls-stream" · id="bot-calls-mcp" trong chính sách | Chế độ âm thanh gốc · MCP tắt |
| Việc cho phép bot trên trang quản trị (mặc định chỉ bot đã cho phép) và công khai (mặc định chỉ người thử nghiệm; muốn công khai phải nhập câu xác nhận) | Bot chưa được cho phép thì không nhận cuộc gọi, và trước khi công khai chỉ người thử nghiệm mới gọi được |
Bản build ứng dụng của người gọi: máy tính pabal-bc2 (khi màn hình xác nhận chỉ có tiếng Hàn và tiếng Anh thì cả pabal-bc1) · Android 62151 · iPhone 5 trở lên · Apple Watch 1 trở lên (TELEGRAM_VOICE_MIN_BUILD_*) | Người đó không thấy nút gọi, và có gọi thì cũng báo bận |
(Tùy chọn) Apple Watch: API đồng hồ của máy chủ (TELEGRAM_WATCH_API_PORT) và địa chỉ âm thanh (TELEGRAM_WATCH_AUDIO_URL), điểm vào âm thanh pabal-voice-edge, gateway khai báo direct (bên dưới) | Không gọi được từ đồng hồ (ứng dụng điện thoại và máy tính vẫn như cũ) |
(Tùy chọn) Lưu cuộc trò chuyện: mục id="bot-calls-keep-text" trong chính sách, gateway khai báo keep_text (VOICE_KEEP_TEXT, bên dưới) | Dù chọn hộp chọn, nội dung cuộc gọi cũng không được lưu |
(Tùy chọn) Cuộc gọi từ bot: mục id="bot-calls-outbound" trong chính sách và mục id="voice-bot-owners-outbound" trong điều khoản, thư mục khóa bằng chứng, bản tối thiểu cho gọi đi, gateway khai báo outbound, công tắc chung trên trang quản trị (bên dưới) | Không bot nào gọi được cho ai |
Chỉ trên máy chủ thử nghiệm (TELEGRAM_TEST_NUMBERS=true) mới có thể bỏ qua kiểm tra mục pháp lý bằng TELEGRAM_VOICE_NOTICE_CHECK=off, và cho bất kỳ ai thử bằng TELEGRAM_VOICE_BOTS (mọi bot là *) · TELEGRAM_VOICE_TESTERS=*. Đừng dùng những thứ này khi vận hành — máy chủ vận hành sẽ bỏ qua TELEGRAM_VOICE_BOTS kèm một cảnh báo.
Chuyển tiếp cuộc gọi (coturn)
- Nếu là máy chủ Linux, hãy dùng
pabal-turntrong compose (COMPOSE_PROFILES=turn). ĐặtTURN_SECRETtrong.env(openssl rand -hex 32), rồi mở trên router và tường lửaTURN_PORT(3478, udp·tcp) vàTURN_MIN_PORT–TURN_MAX_PORT(49160–49200, udp). Nếu mở rộng dải cổng thì sửa cả router lẫn.env. - Trên Docker Desktop của macOS, đừng chạy coturn trong container. Mọi người gọi đều hiện thành
192.168.65.1nên quyền chuyển tiếp không khớp, và bề ngoài thì mọi thứ trông ổn nhưng toàn bộ âm thanh bị bỏ đi. Trên máy chủ Mac, hãy cài coturn trực tiếp (deploy/turnserver.conf.example,docs/turn-relay-guide.md). - Địa chỉ chuyển tiếp đưa cho gateway là địa chỉ LAN của máy chủ: đặt cùng một giá trị cho
TELEGRAM_VOICE_TURN_HOSTcủa máy chủ vàVOICE_TURN_HOSTcủa gateway. Dùnghost.docker.internalthì không tới được, còn dùng127.0.0.1thì việc cấp phát không hoàn tất. Ứng dụng vẫn nhận địa chỉ công khai (TURN_HOST) như cũ. - Từ mạng bên ngoài, dùng
deploy/check_relay.py --datađể kiểm tra âm thanh có thật sự đi qua hay không. Có trường hợp STUN trả lời và cấp phát thành công nhưng âm thanh vẫn không đi.
Khóa Vertex AI
- Trong dự án Google Cloud, bật Agent Platform API (tên cũ là Vertex AI API,
aiplatform.googleapis.com). Dự án phải được liên kết với một tài khoản thanh toán. Không dùng Gemini Developer API (khóa AI Studio). - Tạo một tài khoản dịch vụ và chỉ cấp đúng một vai trò
roles/aiplatform.user(muốn hẹp hơn nữa thì dùng vai trò tùy chỉnh chỉ có quyềnaiplatform.endpoints.predict). Tạo khóa cho vận hành riêng với khóa cho phát triển. - Tải khóa JSON về và đặt ngoài kho mã và ngoài
.env, ví dụ/srv/pabal/pabal_voice/secrets/vertex-sa.json, sao cho chỉ người dùng của gateway đọc được (Linux:chown 10001:10001,chmod 400). Nếu ai cũng đọc được, gateway sẽ từ chối khởi động. - Mô hình thời gian thực chỉ có ở
us-central1(Iowa, Hoa Kỳ) (xác nhận ngày 2026-09-26). Chính sách quyền riêng tư và màn hình xác nhận phải nói rằng âm thanh cuộc gọi được gửi sang Hoa Kỳ, và vùng ghi trên màn hình xác nhận của ứng dụng phải trùng với vùng của gateway (VOICE_VERTEX_REGION) thì máy chủ mới chuyển cuộc gọi cho gateway đó. - Việc cần làm: cảnh báo ngân sách, hạn mức phiên đồng thời (gấp đôi sức chứa gateway), và bộ nhớ đệm dữ liệu của Vertex (khi bật, dữ liệu vào ra được giữ trong bộ nhớ tối đa 24 giờ) — màn hình xác nhận nói rằng "Google không giữ âm thanh và văn bản sau khi tạo câu trả lời", nên trước khi tắt bộ nhớ đệm thì câu đó chưa đúng. Khuyến nghị: tắt bộ nhớ đệm dữ liệu trước khi công khai (mục 9 của
docs/voice-bots/vertex-setup.mdtrong kho mã), và thu hẹp vai trò của khóa xuống còn một quyền dự đoán (mục 10 của cùng tài liệu).
Liên kết và cài đặt máy chủ
Máy chủ và gateway đọc cùng một bí mật liên kết từ tệp. Vì liên kết không được mã hóa, chỉ đặt nó trong mạng riêng mà chỉ hai container này nối vào — đừng đặt trên ssemiya-net dùng chung với các stack khác hay trên LAN. Không công bố cổng ra ngoài.
(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 # bản của máy chủ (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 # bản của gateway (uid 10001)
shred -u /tmp/voice-link.secret
Các giá trị này đặt trong .env. Với chúng, tệp bổ sung thêm cho pabal-server cài đặt thoại (TELEGRAM_VOICE_*) và mạng liên kết.
COMPOSE_FILE=docker-compose.yml:docker-compose.voice.yml
COMPOSE_PROFILES=voice # cùng các profile khác (turn, …), cách nhau bằng dấu phẩy
VOICE_LINK_SECRET_FILE=/app/keys/voice-link.secret # đường dẫn bên trong container; để trống: tắt thoại
VOICE_TURN_HOST=192.168.1.64 # địa chỉ LAN của máy chủ (nơi coturn lắng nghe)
VOICE_TESTERS=100001 # id những người được gọi trước khi công khai (trang quản trị thêm được)
- Tệp bổ sung gắn máy chủ vào mạng chỉ dùng nội bộ
pabal-voice-link(10.231.53.0/29— máy chủ.2, gateway.3), và máy chủ chỉ lắng nghe trên địa chỉ của chính nó trong mạng đó (với0.0.0.0thì cổng cũng mở trên mạng dùng chung mà nó cùng gắn vào). Nếu dải này trùng với LAN, VPN hoặc một mạng Docker khác, hãy đổi cùng lúcVOICE_LINK_SUBNET,VOICE_LINK_SERVER_IPvàVOICE_LINK_GATEWAY_IP. - Muốn gắn vào địa chỉ không phải loopback thì phải có
TELEGRAM_VOICE_LINK_ANY_ADDRESS=true, và liên kết không bao giờ gắn vào địa chỉ công khai. Log máy chủ cho biết liên kết đang thấy mạng nào. - Với kết nối chưa xác thực, máy chủ chỉ chờ nâng cấp 3 giây · hello 3 giây, nhận tối đa 2 kết nối từ một địa chỉ và tổng cộng 16 kết nối.
- Toàn bộ cài đặt máy chủ liên quan đến thoại nằm ở Quản trị và cấu hình — biến môi trường bot thoại.
Gateway (pabal-voice)
cd /opt/pabal
docker build -t pabal-voice:latest voice/ # cho CPU của máy tính này
docker buildx build --platform linux/amd64 -t pabal-voice:latest --load voice/ # cho máy chủ Intel
cd deploy && docker compose up -d # theo COMPOSE_FILE và COMPOSE_PROFILES trong .env
Image chạy Python 3.12 được cố định bằng hash, không có trình biên dịch hay pip, và chạy với uid 10001. ntgcalls chỉ có cho Linux x86_64/aarch64 và macOS arm64 nên trên Mac Intel chỉ chạy được bằng Docker. Dịch vụ pabal-voice trong tệp bổ sung dùng image này mà không dựng lại (image: pabal-voice:latest). Muốn dựng cho CPU của máy chủ trên một máy tính khác rồi gửi sang, hãy dùng deploy/ship.sh <user@host> <thư mục> --voice (image bị thay vẫn còn dưới tên pabal-voice:previous). Dưới đây là dịch vụ gateway trong tệp đó, đã rút gọn.
pabal-voice: # trích từ deploy/docker-compose.voice.yml (rút gọn)
image: pabal-voice:latest
profiles: [voice]
init: true
stop_grace_period: 40s # dài hơn 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:-} # cùng giá trị với TELEGRAM_VOICE_TURN_HOST của máy chủ
VOICE_CAPACITY: ${VOICE_CAPACITY:-2}
VOICE_DAILY_MODEL_MINUTES: ${VOICE_DAILY_MODEL_MINUTES:-60}
VOICE_SAFETY_THRESHOLD: ${VOICE_SAFETY_THRESHOLD:-MEDIUM} # cuộc gọi của người thử nghiệm: giả định người vận hành cần xác nhận (bên dưới)
VOICE_BYOK: "false"
VOICE_STREAM: "false"
volumes:
- ${PABAL_HOME:-/srv/pabal}/pabal_voice/secrets:/run/secrets:ro # bí mật liên kết và khóa Vertex — không nằm trong backup.sh
- pabal_voice_state:/var/lib/pabal-voice
networks:
pabal-voice-link: # mạng liên kết chỉ dùng với máy chủ (internal)
ipv4_address: ${VOICE_LINK_GATEWAY_IP:-10.231.53.3}
pabal-voice-out: {} # mạng đi ra ngoài của riêng nó (Google · chuyển tiếp)
- Ngoài liên kết, gateway còn cần đường đi ra ngoài: tới Google (
us-central1-aiplatform.googleapis.com:443) và tới bộ chuyển tiếp cuộc gọi (UDP). Vì mạng liên kết làinternal: truenên không ra ngoài được, do đó hãy gắn thêm mạngpabal-voice-outnhư trên. - Landlock: gateway dùng mô hình hoặc chế độ âm thanh gốc sẽ từ chối khởi động (mã thoát 2) nếu nhân Linux không có Landlock (5.13 trở lên, hoạt động với seccomp mặc định của Docker). Lý do là tiến trình xử lý media — nơi mã native xử lý những gì người gọi gửi tới — tự khóa mình lại để không đọc được tệp khóa. Trong container, đừng gắn tệp bí mật vào dưới
/app·/opt·/usr·/lib·/etc(/run/secretsthì được — như ví dụ trên).VOICE_ALLOW_NO_LANDLOCK=truechỉ dành cho máy Mac dùng để phát triển. Chưa kiểm tra Landlock có hoạt động trên Docker Desktop của macOS hay không. - Bộ nhớ: mỗi cuộc gọi cần 60–100 MB cho media + 30–40 MB cho engine. Với sức chứa 8 (
VOICE_CAPACITY) cộng một cặp dự phòng thì khoảng 1,3 GB. Mặc định của tệp bổ sung là sức chứa 2 và bộ nhớ 1 GB (VOICE_MEMORY). - Mặc định của gateway cho bộ lọc an toàn
VOICE_SAFETY_THRESHOLDlàLOW(nghiêm ngặt nhất), nhưng khi đo, nó đã biến 13 trong 172 câu tiếng Hàn bình thường thành lời từ chối bằng tiếng Anh (MEDIUMthì 0 trong 69). Kho mã giả định cuộc gọi của người thử nghiệm ghi rõMEDIUM, và trước khi đổi giá trị mặc định cần có thử nghiệm red team. - Giới hạn: mặc định của gateway là mỗi cuộc gọi 600 giây · 400.000 token · 30 lượt mỗi phút, mỗi ngày (UTC) 300 phút mô hình · 8 triệu token; tệp bổ sung đặt mức mỗi ngày là 60 phút · 1,6 triệu token (
VOICE_DAILY_MODEL_*, vượt quá thì cuộc gọi mới báo bận). Engine OpenAI mới chỉ được thử với máy chủ giả và đang tắt, và không có chuyển đổi tự động giữa các engine. - Ngôn ngữ: khi kết nối vào máy chủ, gateway khai báo những ngôn ngữ bot mà nó gọi được (từ 0.7.0 khai báo cả 9), và máy chủ chỉ giao cuộc gọi của một bot cho gateway gọi được bằng ngôn ngữ của bot đó — gateway cũ không khai báo ngôn ngữ (0.6.0) chỉ nhận bot tiếng Hàn và tiếng Anh. Bản ghi âm thông báo nằm trong
assets/của image, và bản nào đã rà soát xong là doassets/assets.jsonquyết định. Nếu thiếu tệp này, gateway không nạp được thông báo AI nên không nhận được cuộc gọi (log khởi động có ERROR). Bản ghi âm chưa rà soát xong (hiện là mọi ngôn ngữ ngoài tiếng Hàn và tiếng Anh) sẽ không được phát, và người gọi dùng ngôn ngữ đó nghe thông báo tiếng Anh.VOICE_DRAFT_NOTICES=truelà công tắc chỉ dành cho máy chủ thử nghiệm để phát cả bản ghi âm nháp; nếu không có kèmVOICE_ALLOW_TEST_URLS=truethì gateway không khởi động.VOICE_NOTICE_LANG(ngôn ngữ thông báo khi máy chủ không cho biết ngôn ngữ của người gọi, mặc địnhko) phải là một trong 10 ngôn ngữ ghi âm (koenjazhfresidvikmar), cònVOICE_LANGUAGE(ngôn ngữ bot cho cuộc gọi không có cài đặt của chủ bot, mặc địnhko-KR) phải là một ID trong danh sách ngôn ngữ bot (Bot thoại — Giọng nói và ngôn ngữ). - Log không ghi bí mật liên kết, khóa, âm thanh, văn bản hội thoại hay chỉ dẫn. Khi nhận SIGTERM, gateway không nhận cuộc gọi mới, cho các cuộc gọi đang diễn ra thêm
VOICE_DRAIN_SECONDS(mặc định 20 giây) rồi kết thúc.
Kiểm tra
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 (mạng)
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: …xuất hiện khi không có bí mật liên kết, không đọc được hoặc nó không phải 32 byte; còn phần cuối dòng— but without a TURN relay every bot call is busyxuất hiện khi không có bộ chuyển tiếp.- Nếu thấy ERROR
Voice bots are on, but no bot is callable: the privacy policy … has no id="bot-calls" sectionthì trước hết phải đăng mục cuộc gọi bot lên chính sách của trang web. - Khi gateway kết nối,
featurestrong log máy chủ cóbot_settings·end_call·context·tools, thêmbyoknếu gateway chạy khóa của chủ bot, vàtranscriptsnếu có giọng thông báo bản ghi hội thoại (thứ tự mỗi lần mỗi khác).audio tophải trùng với nơi nhận mà màn hình xác nhận của ứng dụng nêu ra. - Ở phía gateway, dòng đầu tiên phải là
starting as pabal-voice-1 (capacity 2, … engine gemini, audio to google.vertex.gemini-live@us-central1), dòng về mô hình phải cósession resumption off; no failover, và phải cómedia workers sandbox themselves with Landlock (ABI N).recordings for the callers' notices in: Korean, Englishlà những ngôn ngữ có thể phát thông báo cho người gọi (nếu là máy chủ thử nghiệm phát cả bản nháp thì dòng này có thêm(drafts included: …)). - Sau đó, ở tab Thoại (음성) của trang quản trị (Quản trị và cấu hình — tab Thoại), hãy xem gateway đã kết nối chưa, thêm người thử nghiệm và cho phép bot.
Khóa chủ (khóa riêng · chế độ âm thanh gốc)
Khóa mô hình của chủ bot và bí mật của chế độ âm thanh gốc được bọc bằng khóa này rồi mới lưu. Phải đặt nó ở nơi không bị sao chép cùng với bản sao lưu.
sudo install -d -m 700 -o 1000 -g 1000 /etc/pabal/voice # ngoài /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
- Nếu nằm trong hoặc cạnh thư mục dữ liệu hay thư mục khóa RSA, hoặc thư mục mở cho người dùng khác dù chỉ một chút (không phải 700), máy chủ sẽ không dùng nó (
Voice keys off: …). Nếu tệp chưa có, máy chủ sẽ tạo khi nhận khóa đầu tiên (nếu gắn chỉ đọc thì hãy tự tạo như trên). - Đừng đưa vào bản sao lưu, hãy giữ riêng một bản sao ngoại tuyến. Nếu mất, mọi chủ bot sẽ phải đặt lại khóa. Khi thay khóa, hãy đưa khóa cũ vào
TELEGRAM_VOICE_KEYS_PREVIOUS_MASTER_FILE, khởi động, kiểm tra log, rồi xóa tệp cũ. - Dù có khóa chủ, máy chủ vẫn không nhận khóa riêng cho đến khi ứng dụng hiển thị màn hình xác nhận (phiên bản 2) cho bot dùng khóa riêng và bạn đặt
TELEGRAM_VOICE_OWN_KEYS_APPS_READY=true. Với chế độ âm thanh gốc, phải khai báo bản build ứng dụng có màn hình xác nhận âm thanh gốc quaTELEGRAM_VOICE_STREAM_MIN_BUILD_DESKTOP·_ANDROID·_IOSthì mới gọi được (mặc định 0 = không ai gọi được), và gateway phải cóVOICE_STREAM=truecùng IP công khai của bản triển khai này (VOICE_STREAM_DENY_ADDRESSES).
Thành phần MCP (tùy chọn)
Đây là thành phần gọi máy chủ MCP mà chủ bot đã đăng ký, ngay trong cuộc gọi. Mặc định là tắt; deploy/docker-compose.mcp.yml (profile mcp) thêm ba container.
| Container | Việc nó làm |
|---|---|
pabal-mcp (uid 1101) | Client MCP. Chỉ nối với máy chủ qua tệp socket, và vì chỉ có một mạng nội bộ nên chỉ ra ngoài được qua proxy đi ra. Chỉ đọc, không có quyền đặc biệt, 512 MB |
pabal-mcp-signer (uid 1102) | Bộ ký. Hoàn toàn không có mạng, chỉ trả lời qua socket, và ghi tài liệu công khai của bên phát hành |
pabal-mcp-egress (uid 1103) | Proxy đi ra. Chỉ ra cổng 443, không đi tới chính địa chỉ của Pabal |
docker compose -f docker-compose.yml -f docker-compose.mcp.yml --profile mcp up -d
- Để bật, cần có
TELEGRAM_MCP_EGRESS_OWN_ADDRESSES(các địa chỉ công khai của Pabal, bắt buộc) và các tệp khóa (bí mật liên kết,K_grant, khóa cài đặt — tất cả trong thư mục 700 ngoài/srv/pabal), đồng thời trang web phải có mụcbot-calls-mcpcủa chính sách và mục chủ bot của điều khoản. Các bước thiết lập một lần được ghi theo thứ tự trong chú thích ở đầudocker-compose.mcp.yml. Không đưa kho bí mật và khóa ký vào bản sao lưu. - Chỉ đặt
TELEGRAM_MCP_APPS_READY=truesau khi ứng dụng đã hiển thị nơi nhận MCP của bot trên màn hình xác nhận. Trước đó chỉ đăng ký được, và không cuộc gọi nào mang theo công cụ MCP. - Việc dành riêng một địa chỉ đi ra cho yêu cầu MCP không phải là điều kiện trong mã mà là quyết định vận hành (vì đó là lối ra để lời người gọi đi tới máy chủ của bên thứ ba).
- Khi bật, log có dòng
MCP tools on: pabal-mcp dials …; issuer …; ports …; own addresses N; clock by NTP ….deploy/ship.sh <máy-chủ> <thư-mục> --mcpchỉ dựng và nạp ba image rồi sao chép tệp override, không bật thứ gì (chưa từng được chạy trên Docker Linux).
Điểm vào của Apple Watch (tùy chọn, đang chuẩn bị)
Thay vì MTProto, ứng dụng Apple Watch đăng nhập và gọi điện qua API đồng hồ (HTTPS JSON, /api/watch/v1/) của máy chủ, còn âm thanh cuộc gọi đi qua điểm vào âm thanh (pabal-voice-edge, WSS) để tới gateway. Máy chủ chuyển tiếp cuộc gọi (coturn) không được dùng. Mặc định tắt.
deploy/docker-compose.voice.yml đã có điểm vào âm thanh pabal-voice-edge (profile voice-watch, không có bí mật, trên mạng chỉ dùng nội bộ), nhưng chưa có cài đặt API đồng hồ của máy chủ (TELEGRAM_WATCH_*) lẫn đường dẫn đồng hồ trên reverse proxy, nên đồng hồ từ bên ngoài chưa vào được. Phần dưới đây là hướng dẫn giá trị cài đặt, chưa từng chạy thử ở cấu hình vận hành (chỉ thử trên máy chủ phát triển).
| Cài đặt | Mặc định | Ý nghĩa |
|---|---|---|
TELEGRAM_WATCH_API_PORT | — | Cổng của API đồng hồ. Nếu không có thì API đồng hồ tắt |
TELEGRAM_WATCH_API_HOST, TELEGRAM_WATCH_API_ANY_ADDRESS | 127.0.0.1, — | Địa chỉ lắng nghe. Nếu không phải loopback thì cần ANY_ADDRESS=true, và đặt một proxy TLS phía trước |
TELEGRAM_WATCH_AUDIO_URL | — | Địa chỉ điểm vào âm thanh báo cho đồng hồ. Đường dẫn phải đúng là wss://<host>/api/watch/v1/audio. Nếu không có hoặc sai thì cuộc gọi từ đồng hồ tắt |
TELEGRAM_WATCH_ALLOW_PLAIN | — | Cho phép địa chỉ ws://. Chỉ để phát triển (thử trong mạng nhà) |
TELEGRAM_WATCH_SESSION_DAYS | 7 | Số ngày duy trì đăng nhập trên đồng hồ (1–365) |
TELEGRAM_WATCH_AUTH_PER_MINUTE | 30 | Giới hạn yêu cầu mã đăng nhập của toàn máy chủ (mỗi phút). Giới hạn theo từng địa chỉ được tính riêng |
TELEGRAM_VOICE_MIN_BUILD_WATCHOS | 1 | Bản build ứng dụng đồng hồ đầu tiên hiển thị toàn bộ màn hình xác nhận |
- Gateway:
VOICE_DIRECT=truevàVOICE_EDGE_DIR(thư mục socket dùng chung với điểm vào, mặc định/run/pabal-voice-edge). Cuộc gọi từ đồng hồ chỉ chạy với engine 16 kHz (Gemini) hoặc echo. - Điểm vào âm thanh: chạy
python -m pabal_voice.edgenhư dịch vụ thứ hai của cùng image (VOICE_EDGE_HOST·VOICE_EDGE_PORT, mặc định127.0.0.1· 8096,VOICE_EDGE_DIR). Điểm vào là nơi đầu tiên giải mã dữ liệu đến từ Internet, nên đừng gắn bất kỳ bí mật nào, và đặt nó trong mạng nội bộ mà ngoài proxy và gateway thì không gì chạm tới được. - Reverse proxy chỉ chuyển hai đường dẫn của đồng hồ —
/api/watch/v1/audio(WebSocket, tới điểm vào) và/api/watch/v1/(tới API đồng hồ). Với phía API đồng hồ, hãy để proxy nối địa chỉ người kết nối vào cuốiX-Forwarded-For(máy chủ dùng giá trị đó để đếm giới hạn đăng nhập theo từng địa chỉ). Với phía điểm vào, hãy xóaX-Forwarded-For·X-Real-IP. - Không đăng ký được trên đồng hồ, và không gọi được bot dùng công cụ · MCP · khóa riêng · chế độ âm thanh gốc · bản ghi hội thoại của chủ bot (
WATCH_NOT_YET).
Lưu cuộc trò chuyện (tùy chọn, đang chuẩn bị)
Đây là tính năng lưu nội dung cuộc gọi thành văn bản chỉ trong cuộc trò chuyện của người gọi, nếu người đó chọn trên màn hình xác nhận (Sử dụng ứng dụng — Bot thoại). Không có công tắc cho phép của người vận hành; tính năng bật khi có đủ tất cả các điều kiện dưới đây.
- Mục
id="bot-calls-keep-text"trong chính sách quyền riêng tư (bản nháp:docs/voice-bots/p0/privacy-*.draft.htmltrong kho mã). Chính mục này là công tắc bật. Ứng dụng biết có hiện hộp chọn hay không quapabal_voice_keep_textcủahelp.getAppConfig. VOICE_KEEP_TEXTcủa gateway 0.9.0: với engine mô hình thì mặc định làtrue. Với engine echo (kể cả gateway chỉ dành cho âm thanh gốc), nếu làtruethì bị coi là lỗi cài đặt và gateway không khởi động.- Bản build ứng dụng có hộp chọn. Câu chữ của hộp chọn phải qua rà soát pháp lý mới được đưa vào bản build phát hành. Đồng hồ không hiện câu chữ chưa rà soát, còn
TELEGRAM_VOICE_KEEP_TEXT_UNREVIEWED=truelà công tắc chỉ dành cho máy chủ thử nghiệm nội bộ để hiện câu chữ chưa rà soát. - Trên máy chủ đã bật,
voice_call endedcủa mọi cuộc gọi bot không ở chế độ âm thanh gốc đều muộn tối đa 10 giây, và 30 số tin nhắn sau nhật ký cuộc gọi được giữ trước (để chủ bot không biết lựa chọn của người gọi). - Văn bản hội thoại chờ ghi sau khi cuộc gọi kết thúc chỉ nằm trong bộ nhớ, nên nếu khởi động lại máy chủ trong lúc đó thì văn bản của cuộc gọi đó không được lưu.
Cuộc gọi từ bot (tùy chọn, chờ rà soát pháp lý)
Bot thoại chủ động gọi cho người đã cho phép bot đó gọi cho mình (Bot thoại — Cuộc gọi từ bot). Tính năng này nhiều điều kiện hơn cuộc gọi đến bot, và đừng bật trước khi rà soát pháp lý xong. Thứ tự:
- Máy chủ: tạo thư mục khóa bằng chứng (
TELEGRAM_VOICE_EVIDENCE_KEY_DIR— 700, nằm ngoài các thư mục được sao lưu như thư mục dữ liệu và khóa) bằng lệnhnewcủascripts/voice-evidence-key.py(rotateđể thay khóa,checkđể kiểm tra), và đặt bản ứng dụng đầu tiên có màn hình cho phép làm bản tối thiểu cho gọi đi, chẳng hạnTELEGRAM_VOICE_OUTBOUND_MIN_BUILD_ANDROID(biến môi trường).TELEGRAM_VOICE_EVIDENCE_KEY_DIRchỉ ghi trong.envsẽ không tới được máy chủ — hãy thêm nó vàopabal-servertrongdeploy/docker-compose.voice.yml, ởenvironment(đường dẫn trong container) vàvolumes(thư mục đó trên máy host). - Ứng dụng: phát hành bản ứng dụng có màn hình cho phép. Quy tắc liên kết cho phép của ứng dụng chỉ biết
pabal.me, nên với máy chủ khác phải thêm host của máy chủ đó vào quy tắc liên kết của bản ứng dụng thì liên kết bot gửi mới mở được màn hình (menu và danh sách cài đặt vẫn dùng như cũ). - Pháp lý: đăng mục
id="bot-calls-outbound"trong chính sách quyền riêng tư và mụcid="voice-bot-owners-outbound"trong điều khoản sử dụng (cùng mụcvoice-bot-owners, để chủ bot đồng ý điều khoản). Bản nháp nằm ởdocs/voice-bots/p0/trong kho mã. Câu chữ của màn hình cho phép và bản ghi âm thông báo gọi đi (notice-out·apology-rule) cũng được rà soát và duyệt ở bước này. - Công tắc: ở Cuộc gọi từ bot trong thẻ Thoại của trang quản trị, đặt quốc gia và giới hạn rồi bật bằng câu xác nhận (Trang quản trị — thẻ Thoại). Mỗi bot còn phải qua người vận hành xét duyệt gọi đi riêng.
- Gateway: chỉ gateway có
VOICE_OUTBOUND=truemới thực hiện cuộc gọi của bot. Với engine mô hình, nó chỉ khởi động khi bốn bản ghi âm thông báo gọi đi đã được duyệt; bản ghi âm nháp (VOICE_OUTBOUND_DRAFT_NOTICES) chỉ dành cho máy chủ thử nghiệm. - Đừng đặt các tên chỉ dành cho thử nghiệm và phát triển như
TELEGRAM_VOICE_CALLME_UNREVIEWEDvàTELEGRAM_VOICE_TEST_DIALtrong.envcủa máy chủ vận hành. - Cuộc gọi từ bot cũng không được mã hóa đầu cuối — âm thanh đi qua gateway của máy chủ này và nhà cung cấp AI.
13. Ảnh bản đồ trong tin nhắn vị trí (tùy chọn)
Khi gửi vị trí, ứng dụng Android và ứng dụng máy tính (Mac, Windows) không tự vẽ bản đồ trong bong bóng tin nhắn mà xin máy chủ ảnh bản đồ của điểm đó (upload.getWebFile). Máy chủ lấy ảnh từ Maps Static API của Google rồi chuyển lại. Nếu không bật, Android hiện ảnh giữ chỗ còn máy tính chỉ hiện khung trống với một chiếc ghim. iPhone tự vẽ bản đồ trên điện thoại nên không liên quan đến cài đặt này; việc gửi vị trí và bản đồ lớn mở ra khi chạm vào vẫn hoạt động bình thường.
- Trong bảng điều khiển Google Cloud, chọn một dự án đã gắn tài khoản thanh toán rồi bật Maps Static API.
- Tạo khóa API; giới hạn ứng dụng theo địa chỉ IP (IP công khai mà máy chủ dùng để ra internet) và giới hạn API chỉ ở Maps Static API. Khóa này chỉ nằm trên máy chủ và không bao giờ được đưa vào ứng dụng.
- Ghi vào
.envrồi khởi động lại máy chủ bằngdocker compose up -d pabal-server. - Kiểm tra nhật ký. Nếu bản đồ đang tắt,
Maps in location messages are off: …cho biết lý do (giá trị khóa không bao giờ xuất hiện trong nhật ký).
STATIC_MAPS_KEY=AIza… # đừng để lại giá trị khóa trong tin nhắn, nhật ký hay kho mã
STATIC_MAPS_PER_DAY=300 # số ảnh mới lấy mỗi ngày (0 = không giới hạn)
STATIC_MAPS_PER_USER_HOUR=120 # số ảnh mới một người được gây ra mỗi giờ
INFO TelegramServer - Maps in location messages from Google Static Maps (120 new per person an hour, 300 a day)
- Chi phí: Maps Static API miễn phí đến 10.000 yêu cầu mỗi tháng. Máy chủ giữ mỗi ảnh trong một ngày và đưa lại cho thiết bị nào xin cùng bản đồ đó; ảnh đưa lại không bị tính vào giới hạn. Giới hạn mặc định (300 mỗi ngày) tương đương khoảng 9.000 mỗi tháng, vẫn trong mức miễn phí. Yêu cầu vượt giới hạn bị từ chối bằng
FLOOD_WAIT, và trong lúc đó ứng dụng hiện ảnh giữ chỗ. - Chính sách quyền riêng tư: tọa độ (vĩ độ, kinh độ) của vị trí được chia sẻ được gửi đến Google — không gửi ai là người gửi. Trong trang quản trị → Cài đặt → thông tin người vận hành, hãy thêm Google LLC (Maps Static API, Hoa Kỳ) vào mục bên xử lý được ủy thác và chuyển dữ liệu ra nước ngoài.
- Nếu nhật ký có
Map picture not made: Google Static Maps answered 403: …, hãy kiểm tra giới hạn của khóa (IP, API) hoặc tài khoản thanh toán.
Vận hành
Trạng thái, 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
Cập nhật
cd /opt/pabal && git pull
cd deploy
./backup.sh # sao lưu trước
docker compose up -d --build pabal-server # chỉ thay máy chủ bằng image mới
docker compose logs --since 5m pabal-server | grep -E "Website|ERROR"
Trong vài chục giây máy chủ khởi động lại, ứng dụng bị ngắt kết nối rồi tự kết nối lại mà không cần đăng nhập lại. Thay đổi bảng được máy chủ tự áp dụng khi khởi động. Các thư mục bind không phụ thuộc vào image, nên docker compose down hay xóa image cũng không làm mất dữ liệu — chỉ cần đừng xóa /srv/pabal.
Quay lại bản trước: quay về image trước đó không xóa những gì phiên bản mới đã ghi vào cơ sở dữ liệu, và phiên bản cũ có thể không đọc được chúng (ví dụ, phiên bản có mã bot thoại ghi các sự kiện mới như nhật ký cuộc gọi trong cuộc trò chuyện, kể cả khi thoại đang tắt). Vì vậy khi quay lại, hãy khôi phục cả cơ sở dữ liệu từ bản sao lưu tạo ngay trước khi cập nhật (Khôi phục).
Sao lưu
./backup.sh # → /srv/pabal/backups/<ngày-giờ>/{pabal.dump, server-keys-data.tar.gz}
crontab -e # mỗi ngày lúc 03:00:
# 0 3 * * * /opt/pabal/deploy/backup.sh >> /srv/pabal/backups/backup.log 2>&1
- Bản sao lưu quá 14 ngày sẽ tự bị xóa (có thể đổi, ví dụ
KEEP_DAYS=30 ./backup.sh). - Hãy sao chép sang nơi khác nữa. Bản sao lưu trên cùng ổ đĩa không chống được hỏng ổ đĩa.
- Bản sao lưu chứa khóa bí mật RSA và các giá trị bí mật SMS, SMTP. Nơi lưu trữ cũng cần được bảo vệ kỹ như máy chủ.
Khôi phục
cd /opt/pabal/deploy
B=/srv/pabal/backups/20260920-030000 # bản sao lưu cần khôi phục
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" # đường dẫn mặc định của cơ sở dữ liệu — khôi phục không mang nó trở lại
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
Khi chuyển sang máy chủ mới cũng làm tương tự: làm các bước 1–5, rồi thay cho bước 6 thì thực hiện khôi phục như trên và khởi động. Nếu khôi phục đúng khóa cũ thì không cần build lại ứng dụng (nếu IP máy chủ thay đổi thì phải build lại).
Bật, tắt
docker compose restart pabal-server # chỉ khởi động lại máy chủ
docker compose stop # dừng tất cả (dữ liệu giữ nguyên)
docker compose up -d # bật lại
Kể cả khi khởi động lại máy chủ, các container vẫn tự bật lại vì có restart: unless-stopped.
Danh sách kiểm tra bảo mật
- Quyền của
.envlà 600,POSTGRES_PASSWORDlà giá trị ngẫu nhiên - Quyền của
/srv/pabal/pabal_server/keyslà 700, khóa bí mật không nằm ở đâu khác ngoài bản sao lưu - (Nếu đã bật thông báo đẩy) quyền của
firebase-service-account.jsonlà 600, nội dung không nằm trong kho mã hay.env - (Nếu đã bật thông báo đẩy iPhone) quyền của
AuthKey_<ID khóa>.p8là 600, nội dung không nằm trong kho mã hay.env - Tường lửa: chỉ 22 (nếu được thì chỉ cho IP người vận hành), 80, 443, 8443
- Từ bên ngoài,
https://tên-miền/admin/trả về 404 - Trang quản trị: số thử nghiệm đã tắt, cách gửi mã là SMS/email, gửi thử thành công
- Không bật
TELEGRAM_WEBHOOK_ALLOW_LOCAL(để webhook của bot không đi vào mạng nội bộ) - (Nếu đã bật bot thoại) bí mật liên kết, khóa Vertex và khóa chủ chỉ đọc được bởi đúng người dùng cần đọc từng tệp (400/600), và khóa chủ nằm ngoài
/srv/pabalnên không lọt vào bản sao lưu. Không công bố cổng liên kết ra ngoài, và mạng liên kết chỉ máy chủ và gateway dùng. Máy chủ vận hành không cóTELEGRAM_VOICE_NOTICE_CHECKhayTELEGRAM_VOICE_TESTERS=* - SSH: tắt đăng nhập bằng mật khẩu, chỉ dùng khóa
- Kiểm tra cron sao lưu và bản sao bên ngoài, tập khôi phục ít nhất một lần
- Tự động cập nhật bảo mật cho hệ điều hành máy chủ (
unattended-upgrades)
Xử lý sự cố
| Triệu chứng | Nguyên nhân → cách xử lý |
|---|---|
| Ứng dụng kẹt ở "Đang kết nối…" | ① Cổng 8443 bị chặn → nc -vz IP 8443, kiểm tra ufw và security group của đám mây ② Ứng dụng được build bằng khóa công khai khác → làm lại bước 9 với khóa của máy chủ này ③ PUBLIC_IP sai → sửa .env rồi chạy docker compose up -d pabal-server |
| Lúc đầu kết nối được nhưng bị ngắt ngay sau đó | Ứng dụng chuyển sang địa chỉ mà máy chủ báo (PUBLIC_IP:MTPROTO_PORT) nhưng địa chỉ đó sai → kiểm tra PUBLIC_IP |
| Lỗi chứng chỉ HTTPS | DNS chưa trỏ về máy chủ này hoặc cổng 80 bị chặn → dig +short tên-miền, docker compose logs pabal-caddy |
server khởi động lại liên tục, password authentication failed | Đã đổi POSTGRES_PASSWORD sau khi dữ liệu được tạo → trả về giá trị cũ, hoặc chạy ALTER USER pabal PASSWORD '…' bên trong DB |
AccessDeniedException: /app/keys/… | Chủ sở hữu thư mục bind → sudo chown -R 1000:1000 /srv/pabal/pabal_server |
OutOfMemoryError, chạy chậm | Tăng -Xmx trong JAVA_OPTS rồi chạy docker compose up -d pabal-server |
| Không nhận được mã đăng ký | Xem trạng thái gửi và lý do thất bại ở tab Mã đăng ký (가입 코드) của trang quản trị → kiểm tra giá trị SMS, SMTP trong phần cài đặt, gửi thử |
| Android không nhận được thông báo | ① Log có Push notifications are off → đường dẫn FCM_CREDENTIALS và quyền của tệp ② Không có Push device registered → ứng dụng được build mà không có thiết lập Firebase (pabal-placeholder) hoặc chưa mở ứng dụng mới ③ Có sent mà không hiện → quyền thông báo và thiết lập pin của điện thoại, ứng dụng có bị "Buộc dừng" không ④ SENDER_ID_MISMATCH … Registrations are kept → khóa của máy chủ và google-services.json của ứng dụng thuộc hai dự án khác nhau ⑤ answered 404 without naming the token → FCM_PROJECT_ID sai ⑥ refused an access token → khóa đã bị thu hồi hoặc là khóa của dự án khác (trong 5 phút sẽ không hỏi lại) |
| iPhone không nhận được thông báo | ① Log có iPhone push notifications are off → đường dẫn APNS_KEY, quyền và tên tệp (AuthKey_<ID khóa>.p8) ② Không có đăng ký type 1 → chưa cho phép thông báo trong ứng dụng, hoặc đang dùng trình mô phỏng ③ Có đăng ký nhưng không gửi gì → bản build đó thấp hơn APNS_MIN_BUILD (xem build trong dòng đăng ký), hoặc cuộc trò chuyện đã tắt thông báo ④ APNs refused the provider token → khóa đã bị thu hồi hoặc APNS_TEAM_ID khác nhóm của khóa (trong 5 phút sẽ không gửi lại) ⑤ DeviceTokenNotForTopic … Registrations are kept → APNS_TOPIC khác bundle ID của ứng dụng ⑥ Có sent nhưng chỉ thấy "Bạn có thông báo mới" → phần mở rộng thông báo không giải mã được (nếu đã cài lại ứng dụng, hãy mở một lần để đăng ký lại) ⑦ Cuộc gọi chỉ đến dưới dạng thông báo → APNS_VOIP_MIN_BUILD đang để trống hoặc cao hơn bản build đó (đúng như thiết kế) |
| Không vào được trang quản trị | Tunnel SSH có đang chạy không, cổng 8080 trên máy của bạn có đang bị chương trình khác dùng không (đổi thành -L 18080:127.0.0.1:8080 rồi mở localhost:18080) |
network ssemiya-net declared as external, but could not be found | Mạng chưa được tạo → docker network create ssemiya-net (hoặc tên khác qua PABAL_NETWORK trong .env) |
Các giới hạn đã biết
- Đây là cấu hình một máy chủ. Khi khởi động, máy chủ nạp mọi tài khoản, cuộc trò chuyện và tin nhắn vào bộ nhớ. Chưa hỗ trợ mở rộng ngang ra nhiều máy, và khi dữ liệu tăng thì phải tăng bộ nhớ.
- Mã đăng ký đang chờ, các update bot chưa lấy và văn bản lưu cuộc trò chuyện đang chờ ghi nằm trong bộ nhớ, nên sẽ mất khi máy chủ khởi động lại.
- Yêu cầu mã đăng ký có giới hạn khoảng cách và số lần mỗi ngày, nhưng chưa có giới hạn tốc độ cho các yêu cầu khác nói chung. Ngay sau khi công khai, hãy thường xuyên theo dõi log và bảng điều khiển.
- Thông báo đẩy chỉ đến ứng dụng điện thoại — Android (bước 10) và iPhone (bước 11). Với iPhone, thông báo chỉ đến các bản build từ
APNS_MIN_BUILDtrở lên (không gồm Pabal 1.0 (3)), và màn hình cuộc gọi CallKit chỉ bật sau khi đặtAPNS_VOIP_MIN_BUILD. Ứng dụng Mac và Windows nhận tin trong lúc đang bật. - "Tắt thông báo" của từng cuộc trò chuyện chỉ nằm trong bộ nhớ máy chủ. Thông báo đẩy của các cuộc trò chuyện đã tắt thông báo được gửi tới Android với mức ưu tiên không khẩn (điện thoại tự lọc) và không được gửi tới iPhone, nhưng sau khi khởi động lại máy chủ thì cả hai đều được gửi như bình thường (ứng dụng chỉ báo lại cho máy chủ khi bạn đổi thiết lập). Nếu ứng dụng đang tắt cần hơn 20 giây để thức dậy và kết nối máy chủ, cuộc gọi có thể kết thúc thành cuộc gọi nhỡ ở phía người gọi.
- Tin nhắn đính kèm được ảnh và tệp, còn tin nhắn video và tin nhắn thoại phát ngay trong ứng dụng thì chưa có. Kênh, siêu nhóm, gọi nhóm và xác minh hai bước cũng chưa có.
- Khóa xác thực được lưu dưới dạng văn bản rõ (plaintext) trong DB (máy chủ cần nó để giải mã). Hãy bảo vệ DB và bản sao lưu kỹ như khóa RSA.
Tệp
| Tệp | Vai trò |
|---|---|
deploy/docker-compose.yml | Cấu hình vận hành (pabal-server · pabal-postgres · pabal-caddy, bind mount) |
deploy/Caddyfile | HTTPS, chặn đường dẫn quản trị, chuyển tiếp trang web, tài liệu và Bot API |
deploy/.env.example | Mẫu cấu hình → deploy/.env |
deploy/backup.sh | Dump DB + lưu khóa và dữ liệu, dọn các bản sao lưu cũ |
Dockerfile | Image máy chủ (build → JRE 21, uid 1000) |
voice/ | Gateway thoại pabal-voice (mã nguồn, Dockerfile, README.md, đặc tả liên kết PROTOCOL.md) |
deploy/docker-compose.mcp.yml | Ba container của thành phần MCP cho bot thoại (profile mcp) |
deploy/docker-compose.voice.yml | Thêm các thành phần bot thoại — cài đặt thoại của máy chủ và mạng liên kết, gateway pabal-voice (profile voice) và điểm vào âm thanh của đồng hồ pabal-voice-edge (profile voice-watch) |
deploy/ship.sh | Dựng image máy chủ (với --voice thì cả image gateway) cho CPU của máy chủ trên một máy tính khác, nạp qua SSH và sao chép các tệp triển khai |
deploy/voice_readiness.py | Kiểm tra trước khi bật thoại (khóa Vertex, các mục pháp lý, bản build ứng dụng, chuyển tiếp, …) — không in ra bí mật nào |
deploy/turnserver.conf.example, deploy/check_relay.py | Mẫu cấu hình coturn cài trực tiếp trên máy chủ Mac, công cụ kiểm tra chuyển tiếp |