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 (Pabal.app) cho máy chủ này |
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 | Phải build lại và phát hành lại toàn bộ ứng dụng |
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"
| 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ủ) |
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 (để nguyên cửa sổ nà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ậ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.
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
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 - 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ộ) - 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ử |
| 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ờ và các update bot chưa lấy 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 request 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.
- Chưa có ứng dụng điện thoại và thông báo đẩy. Tệp đính kèm tin nhắn chỉ hỗ trợ ảnh. Kênh, siêu nhóm và xác minh hai bước cũng chưa có.
- Khóa xác thực được lưu dạng rõ 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) |