وثائق المطوّرين
العربية

تشغيل الخادم

تثبيت الخادم

كيف تثبّت خادم Pabal وتشغّله باستخدام Docker Compose على خادم واحد متاح على الإنترنت.

هذه طريقة تثبيت خادم Pabal على خادم واحد متاح على الإنترنت. تشغّل باستخدام Docker Compose ثلاث حاويات هي خادم Pabal · PostgreSQL · Caddy (HTTPS)، وتربط كل البيانات التي يجب ألا تضيع بمجلدات على المضيف (bind mount). اتبع الخطوات بالترتيب من الأعلى إلى الأسفل.

القيم المستخدمة أمثلةً في هذا المستند

النطاق pabal.me، وعنوان IP العام للخادم 203.0.113.10 (عنوان للتوضيح — ضع مكانه عنوانك الحقيقي عند القراءة)، ومكان الشيفرة المصدرية /opt/pabal، ومكان البيانات /srv/pabal، ونظام التشغيل Ubuntu 24.04 LTS. والملفات اللازمة موجودة في المجلد deploy/ من المستودع.

نظرة سريعة

الإنترنت docker compose · ssemiya-net /srv/pabal (ربط المجلدات) ⁧تطبيق Pabal⁩Pabal.app المتصفح · البوتات⁧الموقع · Bot API⁩ المشغّلSSH pabal-server MTProto :8443 ويب · Bot API · إدارة ‎:8080 uid 1000 · JRE 21 webhook → البوت (HTTPS) pabal-caddy:80 · :443 شهادة آلية pabal-postgres 16داخلي فقط · بلا منفذ pabal_server/keys · ⁧مفتاح RSA⁩ pabal_server/data · صور pabal_server/logs postgres_data/ · كل المحادثات caddy/ · الشهادات backups/ · backup.sh MTProto :8443 (تشفير ذاتي) HTTPS :443 ⁧نفق SSH⁩ → 127.0.0.1:8080/admin/
خادم واحد، وثلاث حاويات، وكل ما يجب أن يبقى موجود في /srv/pabal

شرح المخطط

  • ثلاث مناطق: المستخدمون على الإنترنت ← الحاويات الثلاث على خادم الإنتاج ← المجلدات المربوطة على قرص المضيف. يمكن حذف الحاويات وإعادة إنشائها في أي وقت، أما كل ما يجب أن يبقى ففي /srv/pabal.
  • لا يوجد إلا مدخلان: يتصل التطبيق بالخادم مباشرة عبر منفذ MTProto (لا حاجة إلى HTTPS لأنه يشفّر بنفسه)، أما الموقع والوثائق وBot API فتمر عبر المنفذ 443 في Caddy. ويحجب Caddy مسارات الإدارة (/admin، /health …) ولا يمرّر إلا الباقي.
  • الخط الأحمر المتقطع طريق خاص بالمشغّل. فصفحة الإدارة مفتوحة فقط على 127.0.0.1:8080 في الخادم، ولا يُدخل إليها إلا عبر نفق SSH. أما PostgreSQL فليس له منفذ مفتوح إلى الخارج أصلاً.
  • المربعان الأحمران هما أهم البيانات. مفتاح RSA في pabal_server/keys يُضمَّن في التطبيق عند بنائه، فإذا فقدته فعليك إعادة توزيع كل التطبيقات، وpostgres_data/ يحتوي على كل الحسابات والمحادثات. فهما في رأس أولويات النسخ الاحتياطي.
  • الأسهم الرمادية تعني التخزين: يكتب الخادم المفاتيح والصور والإعدادات والسجلات، ويكتب PostgreSQL الأحداث، ويكتب Caddy الشهادات، كلٌّ في مجلده.

المتطلبات

البندالتفاصيل
الخادمUbuntu 24.04 LTS، وللبداية 2 vCPU · 4 GB RAM · 40 GB SSD. ومع ازدياد المستخدمين والرسائل زِد الذاكرة أولاً
عنوان IPv4 عامعنوان IP ثابت. فالتطبيق يُبنى وعنوان الخادم فيه بصيغة IP، والخادم كذلك يُبلغ التطبيق بعنوانه بصيغة IP
النطاقنطاق واحد يمكنك تعديل DNS الخاص به (سجل A)
وسيلة إيصال رموز التسجيلحساب واحد لـ SMS ‏(Twilio · Solapi · webhook) أو للبريد الإلكتروني (SMTP)
جهاز Macجهاز Mac لبناء التطبيق لهذا الخادم (ولبناء تطبيق Windows أيضاً يلزم جهاز Windows واحد)
مشروع Firebase (اختياري)لإرسال الإشعارات الفورية إلى تطبيق Android. يُنشأ مجاناً بحساب Google (الخطوة 10)

المنافذ

المنفذمن يستخدمهالفتحالوصف
22/tcpالمشغّليُنصح بعنوان IP المشغّل فقطSSH
80/tcpCaddyللجميعإصدار الشهادات · التحويل إلى HTTPS
443/tcp, 443/udpCaddyللجميعالموقع · الوثائق · Bot API (وudp لـ HTTP/3)
8443/tcpالخادمللجميعاتصال التطبيق (MTProto)
8080/tcpالخادملا يُفتحصفحة الإدارة · فحص الصحة — على 127.0.0.1 في الخادم فقط
5432/tcpPostgreSQLلا يُفتحعلى الشبكة الداخلية للحاويات فقط
لماذا ليس منفذ التطبيق 443

المنفذ 443 يستخدمه الموقع (HTTPS). وMTProto ليس HTTP ولا TLS، فلا يمكن تقاسم منفذ واحد بينهما. وإذا أردت أن يستقبل التطبيق أيضاً على 443، بسبب شبكات الشركات والمدارس التي تحجب المنافذ غير المألوفة، فاحصل على عنوان IP إضافي وخصّص المنفذ 443 على ذلك العنوان للخادم (في .env اجعل PUBLIC_IP هو العنوان الثاني، وMTPROTO_PORT=443، وافصل بينهما بإضافة عنوان IP إلى المنافذ في ملف Compose). ولأن الخادم يُبلغ التطبيق «اتصل بهذا المنفذ على هذا العنوان»، اجعل منفذ التطبيق داخل الحاوية وخارجها واحداً.

1. تجهيز الخادم

sudo apt update && sudo apt -y upgrade
timedatectl                      # تحقق من ظهور "System clock synchronized: yes"

مزامنة الوقت ضرورية. فأرقام رسائل MTProto مشتقة من الوقت، وإذا انحرفت ساعة الخادم يكرّر التطبيق محاولات الاتصال. وإذا ظهرت no فنفّذ sudo timedatectl set-ntp true.

# جدار الحماية
sudo ufw allow OpenSSH           # إن أمكن: sudo ufw allow from <IP المشغّل> to any port 22
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw allow 8443/tcp
sudo ufw enable

# Docker (سكربت التثبيت الرسمي)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER     # بعد إعادة تسجيل الدخول يعمل docker دون sudo
docker compose version            # الإصدار v2 أو أحدث
Docker وufw

المنافذ التي يفتحها Docker (ports:) تُفتح بغض النظر عن قواعد ufw. لذلك لا يفتح ملف Compose إلا المنافذ العامة (80 و443 و8443)، ويفتح منفذ الإدارة على 127.0.0.1:8080 فقط، ولا يفتح PostgreSQL أصلاً. تذكّر ذلك عندما تعدّل ports:.

2. جلب الشيفرة المصدرية

sudo mkdir -p /opt/pabal && sudo chown $USER: /opt/pabal
git clone <عنوان المستودع> /opt/pabal

إذا كنت تنسخ من حاسوب التطوير فلا ترسل المفاتيح والبيانات والسجلات (rsync --exclude 'keys/' --exclude 'data/' --exclude 'logs/' --exclude '**/target/'). فمفتاح RSA لخادم الإنتاج يُنشئه خادم الإنتاج بنفسه عند أول تشغيل — وإذا استخدمت مفتاح التطوير في الإنتاج يصبح حاسوب التطوير ذاك مكاناً قادراً على فك تشفير حركة الإنتاج.

3. مجلدات البيانات (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        # تعمل حاوية الخادم بالمعرّف uid 1000
sudo chown -R $USER: /srv/pabal/backups
sudo chmod 700 /srv/pabal/pabal_server/keys /srv/pabal/backups
المجلدداخل الحاويةالمحتوىإذا فُقد
pabal_server/keys/app/keysprivate.pem (مفتاح RSA الخاص بالخادم)، private.pem.pub، و(اختياري) firebase-service-account.json (المفتاح السري لـ Firebase للإشعارات الفورية على Android)إعادة بناء كل التطبيقات وتوزيعها (أما مفتاح Firebase فيكفي إنشاء مفتاح جديد من وحدة التحكم)
pabal_server/data/app/dataالصور (media/)، وadmin-token، وoperations.json (إعدادات رموز التسجيل وSMS وSMTP، بما فيها القيم السرية)الصور وإعدادات التشغيل
pabal_server/logs/app/logsسجلات الخادم (تُضغط يومياً)السجلات فقط
postgres_data/var/lib/postgresql/dataكل الحسابات والمحادثات والرسائل وتسجيلات الدخول والبوتات وإعدادات webhookالخدمة بأكملها
caddy/data، caddy/config/data، /configشهادات HTTPSتُصدر من جديد
backups(على المضيف فقط)نتائج backup.sh—

4. ملف الإعدادات ‎.env

cd /opt/pabal/deploy
cp .env.example .env
chmod 600 .env
openssl rand -base64 30 | tr -d '/+=' | cut -c1-32     # ضع القيمة الناتجة في POSTGRES_PASSWORD
nano .env
DOMAIN=pabal.me
PUBLIC_IP=203.0.113.10
POSTGRES_PASSWORD=(القيمة التي أنشأتها أعلاه)
PABAL_HOME=/srv/pabal
PABAL_SOURCE=..
PABAL_NETWORK=ssemiya-net
MTPROTO_PORT=8443
HTTP_PORT=80
HTTPS_PORT=443
ADMIN_PORT=8080
ADMIN_TOKEN=
JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=100"
FCM_CREDENTIALS=
FCM_PROJECT_ID=
PUSH_PREVIEWS=true
القيمةما تضعهملاحظات
DOMAINنطاق الموقعالروابط التي ينشئها التطبيق (pabal.me/اسم_المستخدم)، وعناوين الوثائق، ومعاينات الروابط، كلها بهذا العنوان
PUBLIC_IPعنوان IPv4 العام للخادمفي الخدمات السحابية هو العنوان العام الظاهر في لوحة التحكم (وليس العنوان الخاص داخل الخادم)
POSTGRES_PASSWORDقيمة عشوائيةحدّدها قبل التشغيل الأول. فـ PostgreSQL لا يستخدم هذه القيمة إلا عند إنشاء مجلد البيانات أول مرة
PABAL_HOME/srv/pabalمجلد الخطوة 3
PABAL_SOURCE..المستودع الذي تُبنى منه صورة الخادم: .. إذا بقي ملف compose داخل deploy/ في المستودع، وإلا فمسار الشيفرة المصدرية
PABAL_NETWORKssemiya-netشبكة Docker التي تنضم إليها الحاويات الثلاث. أنشئها مسبقاً (الخطوة 6)
MTPROTO_PORT8443إذا غيّرته فغيّر معه جدار الحماية وبناء التطبيق
ADMIN_TOKENاتركه فارغاًإذا تُرك فارغاً يُنشأ عند التشغيل الأول في pabal_server/data/admin-token. وإذا أردت تحديده بنفسك فليكن 16 حرفاً على الأقل
JAVA_OPTSالقيمة الافتراضيةإذا لم تكفِ الذاكرة فارفع -Xmx (حتى نحو نصف ذاكرة الخادم)
FCM_CREDENTIALSاتركه فارغاًالإشعارات الفورية على Android. لتفعيلها ضع /app/keys/firebase-service-account.json (الخطوة 10). وإذا تُرك فارغاً يعمل الخادم بلا إشعارات فورية
FCM_PROJECT_IDاتركه فارغاًفقط عند الإرسال إلى مشروع Firebase غير project_id المذكور في ملف المفتاح
PUSH_PREVIEWStrueإذا كانت false تعرض الإشعارات المرسل فقط دون محتوى الرسالة

يشغّل ملف Compose الخاص بالإنتاج الخادمَ وأرقام الاختبار معطّلة (TELEGRAM_TEST_NUMBERS=false). والقائمة الكاملة لقيم الإعدادات التي يقرؤها الخادم موجودة في الإدارة والإعدادات — متغيرات البيئة.

5. DNS

الاسمالنوعالقيمة
pabal.meA203.0.113.10
www.pabal.meA203.0.113.10
dig +short pabal.me        # يجب أن يظهر 203.0.113.10 (قد يستغرق سريان التغيير من دقائق إلى ساعات)

يمكنك التشغيل قبل أن يشير DNS إلى الخادم، فسيعيد Caddy المحاولة تلقائياً حتى يحصل على الشهادة.

6. البناء والتشغيل

cd /opt/pabal/deploy
docker network inspect ssemiya-net >/dev/null 2>&1 || docker network create ssemiya-net   # مرة واحدة فقط
docker compose up -d --build       # في المرة الأولى بضع دقائق (ينزّل Maven المكتبات)
docker compose ps                  # تظهر pabal-server وpabal-postgres وpabal-caddy بحالة running (healthy)
docker compose logs -f pabal-server      # للخروج اضغط Ctrl+C

عند التشغيل الأول تظهر في السجل أسطر مثل هذه.

WARN  ServerKeys - Generated a new RSA key at /app/keys/private.pem (fingerprint -3898654385185406269). Clients must embed /app/keys/private.pem.pub
INFO  TelegramServer - JDBC persistence active (jdbc:postgresql://pabal-postgres:5432/pabal)
INFO  BotFather - BotFather is user 100000
INFO  AdminToken - Created the admin token in /app/data/admin-token
INFO  TelegramServer - Website: http://0.0.0.0:8080/ (published at https://pabal.me/)
  • السطر الأول بمستوى WARN لكنه طبيعي (تنبيه بأن المفتاح يُنشأ مرة واحدة فقط). وفي المرات التالية يظهر Loaded RSA key … (fingerprint …). والبصمة تختلف من خادم إلى آخر.
  • ينشئ الخادم الجداول بنفسه عند التشغيل.

7. التحقق

# من الخارج (من حاسوبك)
curl -sI https://pabal.me/ | head -1                              # HTTP/2 200
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/docs/    # 200 — هذه الوثائق
curl -s -o /dev/null -w '%{http_code}\n' https://pabal.me/admin/   # 404 — صفحة الإدارة غير منشورة
curl -s https://pabal.me/docs/server-key.pem | head -1             # -----BEGIN RSA PUBLIC KEY-----
nc -vz 203.0.113.10 8443                                          # succeeded — منفذ التطبيق

# على الخادم
curl -s http://127.0.0.1:8080/health          # {"status":"UP",…}
ls -l /srv/pabal/pabal_server/keys                  # private.pem(600), private.pem.pub

8. صفحة الإدارة والإعدادات الأولى

# على حاسوبك (اتركه يعمل)
ssh -N -L 8080:127.0.0.1:8080 <user>@203.0.113.10

# الرمز المميّز للإدارة تجده على الخادم
sudo cat /srv/pabal/pabal_server/data/admin-token

افتح http://localhost:8080/admin/ في المتصفح وأدخل الرمز المميّز. الإعدادات التي يجب ضبطها قبل التشغيل الفعلي (التفاصيل في الإدارة والإعدادات):

  1. اجعل طريقة إيصال الرمز (코드 전달 방식) هي SMS أو البريد الإلكتروني. ففي طريقة «صفحة الإدارة» (관리 화면) على المشغّل أن يبلّغ الرموز واحداً واحداً.
  2. أدخل قيم SMS أو البريد الإلكتروني (SMTP) (이메일 (SMTP)) واحفظها ← ثم تحقق بـالإرسال التجريبي (테스트 발송) من أنها تصل فعلاً.
  3. تأكد من أن أرقام الاختبار (테스트 번호) معطّلة. فإذا فُعّلت يستطيع أي شخص تسجيل الدخول بأرقام +99966….
  4. وإن لزم الأمر، عطّل السماح بالتسجيلات الجديدة (새 가입 허용) عند البدء، واستقبل أولاً من تدعوهم فقط.

9. توصيل التطبيق بهذا الخادم

يُبنى التطبيق وعنوان IP الخادم ومنفذه ومفتاحه العام مضمّنة فيه. لذا يجب إعادة بنائه بالمفتاح العام لهذا الخادم كي يتصل به. على جهاز Mac:

# 1. المفتاح العام لهذا الخادم (العام لا الخاص — ويمكن تنزيله من موقع الوثائق)
curl -s -o keys/production.pem.pub https://pabal.me/docs/server-key.pem

# 2. توجيه شيفرة التطبيق إلى هذا الخادم + تطبيق العلامة التجارية
python3 scripts/tdesktop/point_to_server.py ~/Developer/tdesktop \
    --host 203.0.113.10 --port 8443 --key keys/production.pem.pub
python3 scripts/tdesktop/apply_branding.py ~/Developer/tdesktop

بعد ذلك نفّذ الخطوتين 5 (configure) و6 (البناء) من docs/tdesktop-build-guide.md في المستودع. وانسخ الناتج out/Debug/Pabal.app باسم آخر — لأنك إذا أعدت بناء الشيفرة نفسها لخادم آخر فسيُستبدل.

قبل أن توزّع التطبيق على الآخرين

البناء الحالي بناء تصحيح (debug) غير موقّع ولا موثَّق لدى Apple (notarization)، لذا على من يستلمه أن يفتحه أول مرة بالنقر بالزر الأيمن ← فتح. ويحفظ بناء التصحيح بياناته في مجلد بجانب التطبيق (tdata/)، وإذا تعذّرت الكتابة هناك يستخدم مجلد البيانات نفسه الذي يستخدمه تطبيق Telegram Desktop الحقيقي. لذا أرشد المستخدمين إلى وضع التطبيق في مجلد المستخدم (مثل ~/Applications/Pabal/). أما بناء الإصدار للتوزيع العام (مجلد بيانات منفصل، وتوقيع Apple وتوثيقها) فعمل منفصل.

أما تطبيقات Android وiPhone وWindows فالطريقة نفسها (سكربت يضع عنوان الخادم ومفتاحه العام ← البناء) موجودة في docs/mobile-apps-guide.md في المستودع. ولكي يحصل تطبيق Android على الإشعارات الفورية، يجب أن يكون إعداد تطبيق Firebase من الخطوة 10 جاهزاً قبل البناء.

10. الإشعارات الفورية على Android (اختياري)

يُنيم Android سريعاً التطبيقات التي تغادر الشاشة. فإذا لم تفعّل الإشعارات الفورية، فلن تظهر الرسائل ولن ترنّ المكالمات إلا ما دام التطبيق مفتوحاً. وتُرسَل الإشعارات الفورية عبر Firebase Cloud Messaging (FCM) من Google (مجاناً)، ويشفّر الخادم محتوى الإشعار (اسم المرسل ومعاينة الرسالة) بمفتاح لا يملكه إلا ذلك الهاتف، فلا تستطيع Google قراءته.

⁧مشروع Firebase (للمشغّل)⁩ البناء · التثبيت ⁧الهاتف · Google⁩ google-services.json إعداد يوضع في التطبيق ⁧داخل ملف APK · ليس سرّياً⁩ مفتاح حساب الخدمة firebase-service-account.json سرّي · على الخادم فقط ⁧بناء تطبيق Android⁩ apply_branding.py → APK pabal-server keys/ · chmod 600 FCM_CREDENTIALS يشفّر الإشعار بمفتاح الهاتف ⁧تطبيق Pabal على الهاتف⁩ يسجّل الهاتف عند فتحه أول مرة Google FCM ينقل المشفَّر فقط · لا يقرأ المحتوى ⁧① تسجيل الهاتف⁩ ⁧② إشعار مشفَّر⁩ ⁧③ إيقاظ ← إشعار⁩
ملفان إلى مكانين مختلفين، والإشعارات مشفّرة

شرح المخطط

  • ثلاث مناطق: مشروع Firebase الذي ينشئه المشغّل ← المكان الذي يُبنى فيه التطبيق ويُشغَّل الخادم ← الهاتف وGoogle. ومن مشروع Firebase واحد تحصل على ملفين.
  • الأسهم الرمادية تحضير يُجرى مرة واحدة فقط. فالمربع الأخضر google-services.json يدخل في التطبيق ويُحمَل في كل ملف APK، فهو ليس سراً، أما المربع الأحمر، مفتاح حساب الخدمة، فسرّ لا يوضع إلا في keys/ على الخادم. ويجب أن يؤخذ الملفان من المشروع نفسه.
  • الأسهم الزرقاء هي طريق الإشعار: ① عند فتح التطبيق أول مرة يسجّل الهاتف لدى الخادم رمز FCM ومفتاحاً لا يملكه إلا هو، ② وعند وصول رسالة أو مكالمة جديدة يشفّر الخادم الإشعار بذلك المفتاح ويسلّمه إلى FCM، ③ وحين يوقظ FCM الهاتف يفكّ التطبيق التشفير ويعرض الإشعار، ثم يعاود الاتصال بالخادم ليستلم ما فاته.
  • ما تراه Google ليس إلا معلومات التسليم مثل الرمز المميّز للجهاز ووقت الإرسال والأولوية. أما اسم المرسل والرسالة فمشفّران فلا تستطيع قراءتهما.
  • لا تُرسَل إشعارات فورية إلى هاتف يعرض التطبيق — فهو يستلم كل شيء عبر اتصاله أصلاً. والإشعارات الفورية للمحادثات التي كُتمت إشعاراتها تُرسَل بأولوية غير عاجلة، ويتولى التطبيق تصفيتها بنفسه.
مشروع Firebase مقترن بالتطبيق

لا يمكن بمفتاح الخادم إرسال الإشعارات الفورية إلا إلى التطبيقات المبنية بمشروع Firebase نفسه. فالتطبيق الذي بناه شخص آخر (مثل التطبيق الذي يُنزَّل من Pabal.me) مرتبط بمشروع Firebase الخاص بذلك الشخص، وهو أصلاً مبني بعنوان خادمه ومفتاحه العام. فتطبيقات خادمك تبنيها أنت، ومشروع Firebase تنشئه أنت أيضاً.

إنشاء مشروع Firebase

  1. في وحدة تحكم Firebase اختر Add project (إضافة مشروع). الاسم أياً كان، ولا حاجة إلى Google Analytics.
  2. Project settings → General → Your apps → Add app (Android) (إعدادات المشروع ← عام ← تطبيقاتك ← إضافة تطبيق). أدخل في اسم الحزمة me.pabal.android (الاسم الذي يمنحه سكربت البناء للتطبيق). ولا حاجة إلى بصمة الشهادة (SHA-1) للإشعارات الفورية.
  3. نزّل ملف google-services.json الخاص بذلك التطبيق. إنه إعداد يوضع في التطبيق، وليس سراً.
  4. من Project settings → Service accounts → Generate new private key (إعدادات المشروع ← حسابات الخدمة ← إنشاء مفتاح خاص جديد) نزّل ملف JSON وغيّر اسمه إلى firebase-service-account.json. هذا الملف سرّي — لا تضع محتواه في المستودع ولا في المحادثات ولا في .env.
  5. في Project settings → Cloud Messaging تحقّق من أن Firebase Cloud Messaging API (V1) مفعّلة (وهي مفعّلة عادةً في المشاريع الجديدة).

وضع الإعداد في التطبيق

قبل بناء تطبيق Android ضع google-services.json في المكان الذي يبحث فيه سكربت البناء. يفحص السكربت الملف (هل سجّل المشروعُ me.pabal.android، وهل أعطيته المفتاح السري خطأً) ثم يضعه في الشيفرة المصدرية للتطبيق.

mkdir -p ~/.pabal/android
cp ~/Downloads/google-services.json ~/.pabal/android/     # لوضعه في مكان آخر: --google-services <المسار> في apply_branding.py
python3 scripts/android/point_to_server.py <مصدر Android> \
    --host 203.0.113.10 --port 8443 --key keys/production.pem.pub
python3 scripts/android/apply_branding.py <مصدر Android>
# يجب أن يظهر في النهاية "Firebase: project <معرّف المشروع>". وإذا ظهر pabal-placeholder فسيكون التطبيق بلا إشعارات فورية

ثم يتبع البناء قسم Android (Android 앱) في docs/mobile-apps-guide.md في المستودع. والهواتف التي تستخدم نسخة سابقة لإضافة الإشعارات الفورية يجب أيضاً تحديثها إلى التطبيق المبني حديثاً حتى تصلها الإشعارات.

وضع المفتاح السري على الخادم

sudo cp firebase-service-account.json /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/firebase-service-account.json    # مستخدم حاوية الخادم
sudo chmod 600 /srv/pabal/pabal_server/keys/firebase-service-account.json

أضف سطراً واحداً إلى .env. داخل الحاوية يكون pabal_server/keys هو /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 <معرّف المشروع>, 0 device registrations, message text included (encrypted per device)
  • إذا ظهر هذا السطر فقد فُعّلت. وإذا تعذّرت قراءة الملف أو كان تنسيقه خاطئاً، يظهر السبب في Push notifications are off: … (ولا يظهر محتوى الملف في السجل).
  • إذا كانت PUSH_PREVIEWS=false ينتهي السطر بـ message text left out، وتعرض الإشعارات المرسل فقط دون محتوى الرسالة.
  • يجب أن تكون الاتصالات الصادرة من الحاوية إلى oauth2.googleapis.com وfcm.googleapis.com (443) مسموحة.

التحقق

  1. ثبّت التطبيق المبني حديثاً على الهاتف (أو حدّثه) وافتحه مرة واحدة. سيظهر في سجل الخادم Push device registered: user … type 2 token … (push key 256 bytes). ولا يُسجَّل الرمز المميّز إلا على شكل بصمة.
  2. أغلق التطبيق بسحبه من قائمة التطبيقات الأخيرة. ولا تستخدم «فرض الإيقاف» من الإعدادات — فلا يسلّم Android الإشعارات الفورية إلى التطبيق حتى تفتحه بنفسك من جديد.
  3. أرسل رسالة من حساب آخر، فيظهر في السجل Push MESSAGE_TEXT to user …: sent ويظهر الإشعار على الهاتف.
  • سياسة الخصوصية: في صفحة الإدارة ← الإعدادات (설정) ← معلومات المشغّل (운영자 정보)، اكتب Google LLC (Firebase Cloud Messaging، الولايات المتحدة) في تفويض المعالجة (처리 위탁) والنقل إلى الخارج (국외 이전). ونص السياسة يتضمن أصلاً جملة تفيد بأن الإشعارات تمرّ عبر FCM. واكتب الشيء نفسه أيضاً في نموذج «أمان البيانات» في Google Play.
  • إذا تسرّب المفتاح فاحذفه من وحدة تحكم Firebase، وأنشئ مفتاحاً جديداً ونزّله، واستبدل الملف، ثم أعد تشغيل الخادم. ولا حاجة إلى إعادة بناء التطبيق.
iPhone وسطح المكتب

تُفعَّل الإشعارات الفورية على iPhone على حدة، لا عبر Firebase بل بـمفتاح APNs (.p8) من حساب مطوّر Apple — انظر الخطوة 11 أدناه مباشرة. أما تطبيقا macOS وWindows فلا يستخدمان الإشعارات الفورية، ويستلمان الرسائل ما داما مفتوحين.

11. الإشعارات الفورية على iPhone (اختياري)

يوقف iPhone هو الآخر سريعاً التطبيقات التي تغادر الشاشة. فإذا لم تفعّل الإشعارات الفورية، فلن تصل الرسائل والمكالمات إلا ما دام التطبيق مفتوحاً. وتُرسَل الإشعارات الفورية عبر خدمة الإشعارات الفورية من Apple (APNs)، وكما في Android يُشفَّر محتوى الإشعار بمفتاح لا يملكه إلا جهاز iPhone ذاك. ونص الإشعار الذي تستطيع Apple رؤيته ليس إلا اسم التطبيق وعبارة خالية من المحتوى مثل «لديك إشعار جديد»، ثم يفكّ امتداد الإشعارات (Notification Service Extension) داخل تطبيق iPhone التشفير ويعرض اسم المرسل والرسالة.

  • التسجيل: في كل مرة يُفتح فيها التطبيق يسجّل لكل حساب شيئين — واحداً للإشعارات (type 1) وآخر للمكالمات (VoIP، type 9). ومع التسجيل يحفظ الخادم لغة التطبيق (اللغة التي يُكتب بها النص الظاهر) ورقم البناء.
  • تُرسَل الرسائل إشعاراتٍ. ولا يُرسَل شيء إلى المحادثات المكتومة. وما قُرئ أو حُذف على جهاز آخر يُبلَّغ به عبر إشعار صامت في الخلفية، فيزيل التطبيق الإشعارات المعروضة.
  • أما المكالمات فإذا أُرسلت عبر VoIP ظهرت شاشة الاتصال في CallKit حتى والشاشة مقفلة. لكن التطبيق الذي يستلم VoIP يجب أن يُبلغ CallKit بالمكالمة فوراً، وإن لم يفعل أنهى iOS التطبيق، وإن تكرّر ذلك توقّف عن إرسال VoIP إليه. لذلك لا يُفعَّل VoIP عبر APNS_VOIP_MIN_BUILD إلا بدءاً من البناء المصنوع على هذا النحو، وقبله تصل المكالمات أيضاً إشعاراتٍ.
مفتاح APNs يعود إلى الفريق الذي وقّع التطبيق

على خلاف Firebase، لا يوجد ملف إعداد يوضع في التطبيق — فالتطبيق مبنيّ أصلاً ليستطيع استخدام APNs (إذن الإشعارات وApp Group وامتداد الإشعارات)، ويكفي أن تضع المفتاح على الخادم. غير أن مفتاح الخادم لا يستطيع إرسال الإشعارات الفورية إلا إلى التطبيقات التي وقّعها الفريق الذي أنشأ ذلك المفتاح ويطابق معرّف حزمتها APNS_TOPIC. فإذا كنت قد وقّعت تطبيق iPhone الخاص بخادمك بحساب مطوّر Apple الخاص بك، فأنشئ مفتاح APNs أيضاً في ذلك الحساب.

إنشاء مفتاح APNs

  1. Apple Developer → Certificates, IDs & Profiles → Keys → +. يجب أن يكون الحساب حساب الفريق الذي وقّع التطبيق.
  2. اكتب اسماً وحدّد Apple Push Notifications service (APNs). واترك البيئة على Sandbox & Production والنطاق على الفريق كله (Team Scoped) — لأن الإرسال يتم إلى وجهتين: الإشعارات وVoIP.
  3. Register → Download. لا يمكن تنزيل AuthKey_<معرّف المفتاح>.p8 إلا مرة واحدة. هذا الملف سرّي — لا تضع محتواه في المستودع ولا في المحادثات ولا في .env.

وضع المفتاح على الخادم

sudo cp AuthKey_<معرّف المفتاح>.p8 /srv/pabal/pabal_server/keys/
sudo chown 1000:1000 /srv/pabal/pabal_server/keys/AuthKey_<معرّف المفتاح>.p8    # مستخدم حاوية الخادم
sudo chmod 600 /srv/pabal/pabal_server/keys/AuthKey_<معرّف المفتاح>.p8

أضِف ما يلي إلى .env. داخل الحاوية يكون pabal_server/keys هو /app/keys.

APNS_KEY=/app/keys/AuthKey_<معرّف المفتاح>.p8
APNS_TEAM_ID=4Z8R468M2D
APNS_TOPIC=me.pabal.ios
APNS_MIN_BUILD=4
APNS_VOIP_MIN_BUILD=
القيمةما تضعهملاحظات
APNS_KEYمسار ملف المفتاحيُقرأ معرّف المفتاح من اسم الملف. وإن حفظته باسم آخر فضع معرّف المفتاح في APNS_KEY_ID
APNS_TEAM_IDمعرّف الفريق الذي وقّع التطبيقلتطبيقات Pabal هو 4Z8R468M2D. وإن بنيت التطبيق ووقّعته بنفسك فهو معرّف فريقك
APNS_TOPICمعرّف حزمة التطبيق (bundle ID)لتطبيقات Pabal هو me.pabal.ios. وتذهب المكالمات (VoIP) إلى هذه القيمة مضافاً إليها .voip
APNS_MIN_BUILD4لا يُرسَل شيء إلى أي بناء رقمه أقل من هذا (و0 يعني الجميع). والقيمة 4 تستبعد Pabal 1.0 (3) الذي بُني قبل اختبار الإشعارات الفورية
APNS_VOIP_MIN_BUILDاتركه فارغاًإن تُرك فارغاً تصل المكالمات أيضاً إشعاراتٍ. وإن كتبت رقم أول بناء يُبلغ CallKit بالمكالمة فوراً، ظهرت شاشة الاتصال في CallKit بدءاً من ذلك البناء
docker compose up -d pabal-server
docker compose logs pabal-server | grep -i push
INFO  TelegramServer - iPhone push notifications on: APNs key <معرّف المفتاح> of team 4Z8R468M2D, topic me.pabal.ios, builds from 4, calls as alerts (TELEGRAM_APNS_VOIP_MIN_BUILD not set)
  • إذا ظهر هذا السطر فقد فُعّلت. وإذا تعذّرت قراءة ملف المفتاح أو كان تنسيقه خاطئاً، يظهر السبب في iPhone push notifications are off: … (ولا يظهر محتوى الملف في السجل). وهي تُفعَّل وتُعطَّل منفصلةً عن Android (FCM) — ويكفي تفعيل أحدهما فقط.
  • يجب أن تكون الاتصالات الصادرة من الحاوية إلى api.push.apple.com (443، HTTP/2) مسموحة. أما بناءات التصحيح المثبّتة مباشرة على الجهاز عبر Xcode فتذهب إلى api.sandbox.push.apple.com (ويُعلم التطبيق الخادم بذلك عند التسجيل).

التحقق

  1. على جهاز iPhone حقيقي افتح التطبيق مرة واحدة واسمح بالإشعارات. سيظهر في سجل الخادم Push device registered: user … type 1 token … (push key 256 bytes, build …) وسطر type 9. لا يمكن التحقق بالمحاكي — فليس فيه رمز APNs ولا CallKit.
  2. أغلق التطبيق بسحبه إلى الأعلى.
  3. أرسل رسالة من حساب آخر، فيظهر في السجل Push MESSAGE_TEXT to user …: sent ويظهر المرسل والمحتوى على شاشة القفل.
  • سياسة الخصوصية: في صفحة الإدارة ← الإعدادات (설정) ← معلومات المشغّل (운영자 정보)، اكتب Apple Inc. (خدمة الإشعارات الفورية من Apple، الولايات المتحدة) في تفويض المعالجة (처리 위탁) والنقل إلى الخارج (국외 이전). ونص السياسة يتضمن أصلاً جملة تفيد بأن الإشعارات تمرّ عبر APNs. وتحقّق أيضاً من بند معرّفات الجهاز (رمز الإشعارات الفورية) في استبيان خصوصية التطبيق في App Store Connect.
  • إذا تسرّب المفتاح فألغِه بـ Revoke في حساب المطوّر، وأنشئ مفتاحاً جديداً، واستبدل الملف، ثم أعد تشغيل الخادم. ولا حاجة إلى إعادة بناء التطبيق.

12. البوتات الصوتية (اختياري)

البوتات الصوتية ميزة يرد فيها نموذج صوتي بالذكاء الاصطناعي عندما يتصل شخص ببوت من التطبيق (والشرح الموجّه لمالكي البوتات في البوتات الصوتية). يحتاج الخادم إلى مكوّنين إضافيين، ولا تعمل الميزة إلا بعد اكتمال الوثائق القانونية وإصدارات التطبيق. وهي معطّلة افتراضياً.

يُفعَّل الصوت بملف Compose إضافي

مكوّنات الصوت ليست في deploy/docker-compose.yml بل في الملف الإضافي deploy/docker-compose.voice.yml: إعدادات الصوت في الخادم والشبكة المخصّصة للرابط، والبوابة pabal-voice (ملف التعريف voice)، ونقطة دخول الصوت للساعة pabal-voice-edge (ملف التعريف voice-watch). وما لم يُشر .env إلى هذا الملف فلا توجد أي إعدادات للصوت. شُغِّل هذا الإعداد بمحرّك الصدى (echo)، لكن الصوت لم يُفعَّل بعد على خادم إنتاج، ولم تجرِ حتى الآن أي مكالمة بوت صوتي من أولها إلى آخرها عبر خادم الترحيل (TURN) الخاص بالإنتاج — فالاختبار الشامل أُجري بخادم ترحيل تجريبي داخل شبكة Docker (scripts/voice-e2e/run.sh). وقبل التفعيل يفحص deploy/voice_readiness.py في المستودع ما ينقص. أما شروط ما قبل الإطلاق العام (المراجعة القانونية، والرد المكتوب من المزوّد، وتوزيع التطبيق) فأمر يقدّره المشغّل، والشيفرة لا تمنع إلا بعضها.

المكوّنما يفعلهإن لم يوجد
pabal-voice (البوابة، الصورة 0.11.0)يصبح أحد طرفي المكالمة نيابةً عن البوت، فيُجري تبادل المفاتيح وينقل الصوت ذهاباً وإياباً مع نموذج الذكاء الاصطناعي. Python 3.12 + ntgcalls. ويتحدث مع الخادم عبر الرابط (WebSocket، مع مصادقة لكن بلا تشفير)كل مكالمات البوتات مشغولة (no_gateway)
coturn (ترحيل المكالمات)مكالمات البوتات تستخدم الترحيل فقط: يعطّل التطبيق والبوابة كلاهما الاتصال المباشر ولا يرسلان الصوت إلا عبر الترحيل. وهو خادم الترحيل نفسه المستخدم في المكالمات بين الأشخاص (TURN_SECRET)كل مكالمات البوتات مشغولة (no_relay)، وترفض البوابة الاتصال أيضاً (4008)
مفتاح Vertex AIمفتاح JSON لحساب خدمة في Google Cloud. لا تفتحه إلا عملية المحرّك في البوابةصدى فقط بلا نموذج (VOICE_ENGINE=echo، ولا يخرج الصوت من الجهاز)
(اختياري) المفتاح الرئيسيالمفتاح الذي يغلّف مفاتيح مالكي البوتات وأسرار وضع الصوت الخاممفتاح المالك ووضع الصوت الخام معطّلان
(اختياري) مكوّنات MCPpabal-mcp · الموقِّع · وكيل الخروج (أدناه)خوادم MCP معطّلة

شروط التفعيل

ما يتحقق منه الخادمإن لم يوجد
ملف سرّ الرابط (TELEGRAM_VOICE_LINK_SECRET_FILE، 32 بايت)الصوت معطّل — ولا يُفتح منفذ الرابط أيضاً
ترحيل المكالمات (TELEGRAM_TURN_HOST·TELEGRAM_TURN_SECRET)كل مكالمات البوتات مشغولة
القسم id="bot-calls" في سياسة الخصوصية على الموقع (بالكورية والإنجليزية كلتيهما) — تشير إليه نافذة التأكيد في التطبيقلا يستقبل أي بوت (سجل ERROR). والمسودة في docs/voice-bots/p0/privacy-*.draft.html
القسم id="voice-bot-owners" في شروط الاستخداملا يستطيع أحد الموافقة على شروط مالكي البوتات ← فلا يمكن استخدام نص المحادثة ولا مفتاح المالك ولا وضع الصوت الخام ولا MCP
القسمان id="bot-calls-stream" · id="bot-calls-mcp" في السياسةوضع الصوت الخام · MCP معطّلان
السماح للبوت في صفحة الإدارة (الافتراضي البوتات المسموح لها فقط) والإطلاق العام (الافتراضي المُختبِرون فقط، ويلزم للإطلاق كتابة عبارة تأكيد)البوت غير المسموح له لا يستقبل، وقبل الإطلاق العام لا يستطيع الاتصال إلا المُختبِرون
بناء تطبيق المتصل: سطح المكتب pabal-bc2 (وpabal-bc1 أيضاً ما دامت نافذة التأكيد بالكورية والإنجليزية فقط) · Android رقم البناء 62151 · iPhone رقم البناء 5 فأعلى · Apple Watch رقم البناء 1 فأعلى (TELEGRAM_VOICE_MIN_BUILD_*)لا يظهر زر الاتصال لذلك الشخص، وإن اتصل فالخط مشغول
(اختياري) Apple Watch: واجهة الساعة البرمجية في الخادم (TELEGRAM_WATCH_API_PORT) وعنوان الصوت (TELEGRAM_WATCH_AUDIO_URL)، ونقطة دخول الصوت pabal-voice-edge، وبوابة تعلن direct (أدناه)لا يمكن الاتصال من الساعة (وتبقى تطبيقات الهاتف والكمبيوتر كما هي)
(اختياري) الاحتفاظ بالمحادثة: قسم id="bot-calls-keep-text" في السياسة، وبوابة تعلن keep_text (VOICE_KEEP_TEXT، أدناه)حتى لو اختيرت الخانة لا تُحفظ المحادثة
(اختياري) المكالمات من البوتات: قسم id="bot-calls-outbound" في السياسة وقسم id="voice-bot-owners-outbound" في الشروط، ومجلد مفتاح الأدلة، والحد الأدنى من الإصدارات للمكالمات الصادرة، وبوابة تعلن outbound، والمفتاح العام في صفحة الإدارة (أدناه)لا يستطيع أي بوت الاتصال بأحد

على خوادم الاختبار فقط (TELEGRAM_TEST_NUMBERS=true) يمكن تخطي التحقق من الأقسام القانونية بـTELEGRAM_VOICE_NOTICE_CHECK=off، والسماح لأي أحد بالتجربة بـTELEGRAM_VOICE_BOTS (لكل البوتات *) · TELEGRAM_VOICE_TESTERS=*. لا تستخدمها في الإنتاج — فخادم الإنتاج يتجاهل TELEGRAM_VOICE_BOTS مع تحذير.

ترحيل المكالمات (coturn)

  • إن كان الخادم بنظام Linux فاستخدم pabal-turn من ملف Compose (COMPOSE_PROFILES=turn). حدّد TURN_SECRET في .env (openssl rand -hex 32)، وافتح في الموجّه وجدار الحماية TURN_PORT (3478، udp·tcp) والنطاق من TURN_MIN_PORT إلى TURN_MAX_PORT (49160 إلى 49200، udp). وإذا وسّعت النطاق فغيّر الموجّه و.env معاً.
  • لا تشغّل coturn داخل حاوية على Docker Desktop في macOS. فكل المتصلين يظهرون بالعنوان 192.168.65.1 فلا تتطابق أذونات الترحيل، ويبدو كل شيء سليماً في الظاهر بينما يُرمى الصوت كله. وعلى خادم Mac ثبّت coturn مباشرة (deploy/turnserver.conf.example، docs/turn-relay-guide.md).
  • عنوان الترحيل الذي يُعطى للبوابة هو عنوان الخادم في الشبكة المحلية: ضع القيمة نفسها في TELEGRAM_VOICE_TURN_HOST للخادم وVOICE_TURN_HOST للبوابة. فبـhost.docker.internal لم يصل، وبـ127.0.0.1 لم يكتمل التخصيص. أما التطبيق فيتلقى العنوان العام (TURN_HOST) كما هو.
  • من شبكة خارجية تحقّق بـdeploy/check_relay.py --data من أن الصوت يعبر فعلاً. فقد يرد STUN وينجح التخصيص ومع ذلك لا يمر الصوت.

مفتاح Vertex AI

  1. في مشروع Google Cloud فعّل Agent Platform API (اسمها القديم Vertex AI API، aiplatform.googleapis.com). يجب أن يكون المشروع مرتبطاً بحساب فوترة. لا تُستخدم Gemini Developer API (مفاتيح AI Studio).
  2. أنشئ حساب خدمة وامنحه دوراً واحداً فقط هو roles/aiplatform.user (ولتضييقه أكثر استخدم دوراً مخصصاً فيه صلاحية aiplatform.endpoints.predict وحدها). وأنشئ مفتاح الإنتاج منفصلاً عن مفتاح التطوير.
  3. نزّل مفتاح JSON وضعه خارج المستودع وخارج .env، مثلاً في /srv/pabal/pabal_voice/secrets/vertex-sa.json، بحيث لا يقرؤه إلا مستخدم البوابة (في Linux: chown 10001:10001، chmod 400). وإذا كان مقروءاً للجميع ترفض البوابة البدء.
  4. نماذج الوقت الحقيقي (realtime) متوفرة في us-central1 (آيوا، الولايات المتحدة) فقط (تم التحقق في 2026-09-26). يجب أن تذكر سياسة الخصوصية ونافذة التأكيد أن صوت المكالمة يذهب إلى الولايات المتحدة، ويجب أن تطابق المنطقة المكتوبة في نافذة تأكيد التطبيق منطقةَ البوابة (VOICE_VERTEX_REGION) كي يسلّم الخادم المكالمات إلى تلك البوابة.
  5. مهام متبقية: تنبيهات الميزانية، وحصة الجلسات المتزامنة (ضعف سعة البوابة)، وذاكرة التخزين المؤقت للبيانات في Vertex (إذا كانت مفعّلة تُبقي المدخلات والمخرجات في الذاكرة حتى 24 ساعة) — فنافذة التأكيد تقول «لا تحتفظ Google بالصوت والنص بعد توليد الرد»، وهذا لا يصح قبل تعطيل ذاكرة التخزين المؤقت. ننصح بتعطيل ذاكرة التخزين المؤقت للبيانات قبل الإطلاق العام (القسم 9 من docs/voice-bots/vertex-setup.md داخل المستودع)، وبتضييق دور المفتاح إلى صلاحية تنبؤ واحدة (القسم 10 من المستند نفسه).

يقرأ الخادم والبوابة سرّ الرابط نفسه من ملف. ولأن الرابط غير مشفّر، ضعه فقط في شبكة مخصّصة لا تتصل بها إلا هاتان الحاويتان — لا تضعه على ssemiya-net المشتركة مع حزم أخرى ولا على الشبكة المحلية. ولا تنشر المنفذ.

(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   # نسخة الخادم (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 # نسخة البوابة (uid 10001)
shred -u /tmp/voice-link.secret

هذه القيم تُوضع في .env. وبها يضيف الملف الإضافي إلى pabal-server إعدادات الصوت (TELEGRAM_VOICE_*) وشبكة الرابط.

COMPOSE_FILE=docker-compose.yml:docker-compose.voice.yml
COMPOSE_PROFILES=voice                              # مع ملفات التعريف الأخرى (turn …) مفصولة بفواصل
VOICE_LINK_SECRET_FILE=/app/keys/voice-link.secret  # المسار داخل الحاوية؛ فارغ: الصوت معطّل
VOICE_TURN_HOST=192.168.1.64                        # عنوان الخادم في الشبكة المحلية (حيث يستمع coturn)
VOICE_TESTERS=100001                                # معرّفات من يمكنهم الاتصال قبل الإطلاق العام (وتضيف صفحة الإدارة غيرهم)
  • يضع الملف الإضافي الخادم على الشبكة الداخلية الخالصة pabal-voice-link (10.231.53.0/29 — الخادم .2، والبوابة .3)، ولا يستمع الخادم إلا على عنوانه في تلك الشبكة (مع 0.0.0.0 سيُفتح أيضاً على الشبكة المشتركة المتصل بها). وإذا تداخل هذا النطاق مع شبكتك المحلية أو VPN أو شبكة Docker أخرى فغيّر VOICE_LINK_SUBNET وVOICE_LINK_SERVER_IP وVOICE_LINK_GATEWAY_IP معاً.
  • للربط بعنوان غير loopback يلزم TELEGRAM_VOICE_LINK_ANY_ADDRESS=true، ولا يُربط الرابط أبداً بعنوان عام. ويخبرك سجل الخادم بالشبكة التي يراها الرابط.
  • للاتصالات غير المصادَق عليها ينتظر الخادم الترقية 3 ثوانٍ · وhello 3 ثوانٍ، ولا يقبل إلا اتصالين من العنوان الواحد و16 اتصالاً في المجموع.
  • كل إعدادات الخادم المتعلقة بالصوت في الإدارة والإعدادات — متغيرات بيئة البوتات الصوتية.

البوابة (pabal-voice)

cd /opt/pabal
docker build -t pabal-voice:latest voice/                                       # لمعالج هذا الحاسوب
docker buildx build --platform linux/amd64 -t pabal-voice:latest --load voice/  # لخادم Intel
cd deploy && docker compose up -d                                         # وفق COMPOSE_FILE وCOMPOSE_PROFILES في .env

تعمل الصورة بـ Python 3.12 المثبّت بالتجزئة (hash)، بلا مترجم ولا pip، وبالمعرّف uid 10001. ولا تتوفر ntgcalls إلا لـ Linux x86_64/aarch64 وmacOS arm64، لذا لا تعمل على أجهزة Mac بمعالج Intel إلا عبر Docker. وتستخدم خدمة pabal-voice في الملف الإضافي هذه الصورة دون أن تبنيها (image: pabal-voice:latest). ولبنائها لمعالج الخادم على حاسوب آخر وإرسالها إليه استخدم deploy/ship.sh <user@host> <المجلد> --voice (وتبقى الصورة المستبدَلة باسم pabal-voice:previous). وفيما يلي خدمة البوابة من ذلك الملف مختصرة.

  pabal-voice:                             # من deploy/docker-compose.voice.yml (مختصر)
    image: pabal-voice:latest
    profiles: [voice]
    init: true
    stop_grace_period: 40s                 # أطول من 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:-}  # القيمة نفسها التي في TELEGRAM_VOICE_TURN_HOST للخادم
      VOICE_CAPACITY: ${VOICE_CAPACITY:-2}
      VOICE_DAILY_MODEL_MINUTES: ${VOICE_DAILY_MODEL_MINUTES:-60}
      VOICE_SAFETY_THRESHOLD: ${VOICE_SAFETY_THRESHOLD:-MEDIUM}   # مكالمات المُختبِرين: افتراض يتحقق منه المشغّل (أدناه)
      VOICE_BYOK: "false"
      VOICE_STREAM: "false"
    volumes:
      - ${PABAL_HOME:-/srv/pabal}/pabal_voice/secrets:/run/secrets:ro   # سرّ الرابط ومفتاح Vertex — لا يدخلان في backup.sh
      - pabal_voice_state:/var/lib/pabal-voice
    networks:
      pabal-voice-link:                    # شبكة الرابط مع الخادم وحده (internal)
        ipv4_address: ${VOICE_LINK_GATEWAY_IP:-10.231.53.3}
      pabal-voice-out: {}                  # شبكتها الخاصة للخروج (Google · الترحيل)
  • تحتاج البوابة إلى جانب الرابط إلى طريق للخروج: إلى Google (us-central1-aiplatform.googleapis.com:443) وإلى ترحيل المكالمات (UDP). ولأن شبكة الرابط internal: true لا يمكنها الخروج، فأضف شبكة pabal-voice-out كما في المثال.
  • Landlock: البوابة التي تستخدم نموذجاً أو وضع الصوت الخام ترفض البدء (رمز الخروج 2) إذا لم تتوفر Landlock في نواة Linux (الإصدار 5.13 فأعلى، وتعمل مع seccomp الافتراضي في Docker). والسبب أن عملية الوسائط — حيث تعالج شيفرة أصلية ما يرسله المتصل — تقفل نفسها كي لا تستطيع قراءة ملفات المفاتيح. وداخل الحاوية لا تركّب ملفات الأسرار تحت /app·/opt·/usr·/lib·/etc (أما /run/secrets فلا بأس — كما في المثال). وVOICE_ALLOW_NO_LANDLOCK=true لأجهزة Mac الخاصة بالتطوير فقط. ولم يُتحقق من عمل Landlock على Docker Desktop في macOS.
  • الذاكرة: تحتاج المكالمة الواحدة 60 إلى 100 MB للوسائط + 30 إلى 40 MB للمحرّك. ومع سعة 8 (VOICE_CAPACITY) وزوج احتياطي واحد نحو 1.3 GB. وافتراضيّ الملف الإضافي سعة 2 وذاكرة 1 GB (VOICE_MEMORY).
  • القيمة الافتراضية في البوابة لـمرشّح الأمان VOICE_SAFETY_THRESHOLD هي LOW (الأشد صرامة)، لكنها في القياس حوّلت 13 من 172 عبارة كورية سليمة إلى رفض بالإنجليزية (بينما MEDIUM صفر من 69). ويفترض المستودع أن مكالمات المُختبِرين تحدد MEDIUM صراحة، ويلزم اختبار فريق أحمر (red team) قبل تغيير القيمة الافتراضية.
  • الحدود: افتراضيّ البوابة للمكالمة الواحدة 600 ثانية · 400,000 رمز (token) · 30 دوراً في الدقيقة، وفي اليوم (UTC) ‏300 دقيقة للنموذج · 8 ملايين رمز، ويضبط الملف الإضافي اليوم على 60 دقيقة · 1.6 مليون رمز (VOICE_DAILY_MODEL_*، وإذا تجاوزتها فالمكالمات الجديدة مشغولة). ولم يُختبر محرّك OpenAI إلا مع خوادم وهمية وهو معطّل، ولا يوجد تبديل تلقائي بين المحرّكات.
  • اللغات: تُعلن البوابة للخادم عند اتصالها به لغات البوت التي تستطيع إجراء المكالمات بها (منذ الإصدار 0.7.0 يُعلن التسع كلها)، ولا يعطي الخادم مكالمات البوت إلا لبوابة تُجري المكالمات بلغته — أما البوابة القديمة التي لا تُعلن لغاتها (0.6.0) فلا تصلها إلا البوتات الكورية والإنجليزية. وتسجيلات الإعلانات في assets/ داخل الصورة، والملف assets/assets.json هو الذي يحدد أيّ التسجيلات اكتملت مراجعتها. وإذا غاب هذا الملف تعذّر تحميل إعلان الذكاء الاصطناعي فلا تُستقبل المكالمات (ويظهر ERROR في سجل البدء). والتسجيلات التي لم تكتمل مراجعتها (حالياً كل اللغات غير الكورية والإنجليزية) لا تُشغَّل، ويسمع المتصل بتلك اللغة الإعلانات بالإنجليزية. وVOICE_DRAFT_NOTICES=true مفتاح لخوادم الاختبار فقط يشغّل مسودات التسجيلات أيضاً، ولا تبدأ البوابة إذا لم يُضبط معه VOICE_ALLOW_TEST_URLS=true. ويجب أن تكون قيمة VOICE_NOTICE_LANG (لغة الإعلانات حين لا يُخبر الخادمُ البوابةَ بلغة المتصل، الافتراضي ko) إحدى لغات التسجيلات العشر (ko en ja zh fr es id vi km ar)، وقيمة VOICE_LANGUAGE (لغة البوت في المكالمة التي لا تحمل إعدادات من مالك البوت، الافتراضي ko-KR) أحدَ معرّفات قائمة لغات البوت (البوتات الصوتية — الأصوات واللغات).
  • لا تُسجَّل في السجلات أسرار الرابط ولا المفاتيح ولا الصوت ولا نصوص المحادثة ولا التعليمات. وعند SIGTERM لا تقبل البوابة مكالمات جديدة، وتمنح المكالمات الجارية VOICE_DRAIN_SECONDS (افتراضياً 20 ثانية) ثم تنتهي.

التحقق

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 (الشبكة)
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: … إذا غاب سرّ الرابط أو تعذّرت قراءته أو لم يكن 32 بايت، ويظهر في آخر السطر — but without a TURN relay every bot call is busy إذا غاب خادم الترحيل.
  • إذا رأيت ERROR Voice bots are on, but no bot is callable: the privacy policy … has no id="bot-calls" section فعليك أولاً نشر قسم مكالمات البوتات في سياسة الموقع.
  • عندما تتصل البوابة يظهر في features في سجل الخادم bot_settings·end_call·context·tools، وbyok إن كانت البوابة تشغّل مفاتيح المالكين، وtranscripts إن كان لديها صوت إعلان نص المحادثة (والترتيب يختلف كل مرة). ويجب أن يطابق audio to جهة الاستلام التي تذكرها نافذة التأكيد في التطبيق.
  • في جهة البوابة يجب أن يكون السطر الأول starting as pabal-voice-1 (capacity 2, … engine gemini, audio to google.vertex.gemini-live@us-central1)، وأن يتضمن سطر النموذج session resumption off; no failover، وأن يظهر media workers sandbox themselves with Landlock (ABI N). ويذكر السطر recordings for the callers' notices in: Korean, English اللغات التي تستطيع البوابة تشغيل إعلانات المتصلين بها (وعلى خادم اختبار يشغّل المسودات أيضاً يُضاف (drafts included: …)).
  • ثم في تبويب الصوت (음성) في صفحة الإدارة (الإدارة والإعدادات — تبويب الصوت) تحقّق من اتصال البوابة، وأضف المُختبِرين، واسمح للبوت.

المفتاح الرئيسي (مفتاح المالك · وضع الصوت الخام)

تُغلَّف مفاتيح النماذج الخاصة بمالكي البوتات وأسرار وضع الصوت الخام بهذا المفتاح قبل حفظها. ويجب وضعه في مكان لا يُنسخ مع النسخ الاحتياطية.

sudo install -d -m 700 -o 1000 -g 1000 /etc/pabal/voice                 # خارج /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
  • إذا كان داخل مجلد البيانات أو مجلد مفاتيح RSA أو بجانبهما، أو كان المجلد مفتوحاً للمستخدمين الآخرين ولو قليلاً (أي ليس 700)، فلن يستخدمه الخادم (Voice keys off: …). وإذا لم يوجد الملف أنشأه الخادم عند استلام أول مفتاح (أما إن كان التركيب للقراءة فقط فأنشئه بنفسك كما في المثال).
  • لا تضعه في النسخ الاحتياطية، واحتفظ بنسخة منفصلة غير متصلة بالشبكة. وإذا ضاع فعلى كل مالكي البوتات ضبط مفاتيحهم من جديد. وعند الاستبدال مرّر المفتاح القديم في TELEGRAM_VOICE_KEYS_PREVIOUS_MASTER_FILE، وشغّل الخادم، وتحقق من السجل، ثم احذف الملف القديم.
  • حتى مع وجود المفتاح الرئيسي لا يقبل الخادم مفاتيح المالكين إلا بعد أن يعرض التطبيق نافذة التأكيد (النسخة 2) للبوتات ذات مفتاح المالك وتضع TELEGRAM_VOICE_OWN_KEYS_APPS_READY=true. أما وضع الصوت الخام فلا يمكن الاتصال به إلا بعد إعلان إصدارات التطبيق التي تتضمن نافذة تأكيد الصوت الخام عبر TELEGRAM_VOICE_STREAM_MIN_BUILD_DESKTOP·_ANDROID·_IOS (والافتراضي 0 = لا يستطيع أحد الاتصال)، ويجب أن يكون في البوابة VOICE_STREAM=true وعنوان IP العام لهذا النشر (VOICE_STREAM_DENY_ADDRESSES).

مكوّنات MCP (اختياري)

هذه مكوّنات تستدعي أثناء المكالمة خوادم MCP التي سجّلها مالك البوت. وهي معطّلة افتراضياً، ويضيف deploy/docker-compose.mcp.yml (ملف التعريف mcp في Compose) ثلاث حاويات.

الحاويةما تفعله
pabal-mcp (uid 1101)عميل MCP. لا يتصل بالخادم إلا عبر ملف مقبس (socket)، ولأنه لا يملك إلا شبكة داخلية واحدة فلا يخرج إلا عبر وكيل الخروج. للقراءة فقط، بلا صلاحيات، 512 MB
pabal-mcp-signer (uid 1102)الموقِّع. بلا أي شبكة، ولا يجيب إلا عبر المقبس، ويكتب وثيقة الجهة المُصدِرة العامة
pabal-mcp-egress (uid 1103)وكيل الخروج. لا يخرج إلا إلى المنفذ 443، ولا يتجه إلى عناوين Pabal نفسه
docker compose -f docker-compose.yml -f docker-compose.mcp.yml --profile mcp up -d
  • للتفعيل يلزم TELEGRAM_MCP_EGRESS_OWN_ADDRESSES (عناوين Pabal العامة، إلزامي) وملفات المفاتيح (سرّ الرابط، وK_grant، ومفتاح التثبيت — وكلها في مجلد بصلاحيات 700 خارج /srv/pabal)، ويجب أن يتضمن الموقع قسم bot-calls-mcp في السياسة وقسم مالكي البوتات في الشروط. وإعدادات المرة الواحدة مرتبة بالتسلسل في التعليقات أعلى docker-compose.mcp.yml. ولا تضع الخزنة ومفتاح التوقيع في النسخ الاحتياطية.
  • ضع TELEGRAM_MCP_APPS_READY=true فقط بعد أن يعرض التطبيق في نافذة التأكيد جهة استلام MCP الخاصة بالبوت. وقبل ذلك يمكن التسجيل فقط، ولا تحمل أي مكالمة أدوات MCP.
  • تخصيص عنوان خروج خاص لطلبات MCP ليس شرطاً في الشيفرة بل قرار تشغيلي (لأنه المخرج الذي يذهب منه كلام المتصل إلى خوادم أطراف ثالثة).
  • عند التفعيل يظهر في السجل MCP tools on: pabal-mcp dials …; issuer …; ports …; own addresses N; clock by NTP …. وdeploy/ship.sh <الخادم> <المجلد> --mcp لا يفعل سوى بناء الصور الثلاث وتحميلها ونسخ ملف override، ولا يفعّل شيئاً (ولم يُشغَّل قط على Docker في Linux).

نقطة دخول Apple Watch (اختياري، قيد الإعداد)

بدلاً من MTProto يسجّل تطبيق Apple Watch الدخول ويتصل عبر واجهة الساعة البرمجية (HTTPS JSON، /api/watch/v1/) في الخادم، ويصل صوت المكالمة إلى البوابة عبر نقطة دخول الصوت (pabal-voice-edge، WSS). ولا يُستخدم ترحيل المكالمات (coturn). وهي معطّلة افتراضياً.

لا يمكن بلوغ نقطة دخول الصوت للساعة من الخارج بعد

يحتوي deploy/docker-compose.voice.yml على نقطة دخول الصوت pabal-voice-edge (ملف التعريف voice-watch، بلا أسرار، على شبكة داخلية خالصة)، لكنه لا يحتوي بعد على إعدادات واجهة الساعة في الخادم (TELEGRAM_WATCH_*) ولا على مسارات الساعة في الوكيل العكسي، لذا لا تستطيع ساعة من الخارج بلوغها. وما يلي شرح لقيم الإعدادات، ولم يُشغَّل قط بتكوين الإنتاج (اختُبر على خادم التطوير فقط).

الإعدادالافتراضيالمعنى
TELEGRAM_WATCH_API_PORT—منفذ واجهة الساعة. وإن لم يُحدَّد فواجهة الساعة معطّلة
TELEGRAM_WATCH_API_HOST، TELEGRAM_WATCH_API_ANY_ADDRESS127.0.0.1، —عنوان الاستماع. وإن لم يكن loopback فيلزم ANY_ADDRESS=true، وضع أمامه وكيل TLS
TELEGRAM_WATCH_AUDIO_URL—عنوان نقطة دخول الصوت الذي يُبلَّغ للساعة. ويجب أن يكون المسار wss://<المضيف>/api/watch/v1/audio بالضبط. وإن غاب أو كان خاطئاً فمكالمات الساعة معطّلة
TELEGRAM_WATCH_ALLOW_PLAIN—السماح بعنوان ws://. للتطوير فقط (الاختبار داخل المنزل)
TELEGRAM_WATCH_SESSION_DAYS7عدد أيام بقاء تسجيل الدخول في الساعة (1–365)
TELEGRAM_WATCH_AUTH_PER_MINUTE30حد طلبات رمز تسجيل الدخول على مستوى الخادم كله (في الدقيقة). وحد كل عنوان منفصل
TELEGRAM_VOICE_MIN_BUILD_WATCHOS1أول بناء من تطبيق الساعة يعرض نافذة التأكيد كاملة
  • البوابة: VOICE_DIRECT=true وVOICE_EDGE_DIR (مجلد المقابس المشترك مع نقطة الدخول، والافتراضي /run/pabal-voice-edge). ولا تعمل مكالمات الساعة إلا مع محرّك 16 kHz (Gemini) أو الصدى (echo).
  • نقطة دخول الصوت: شغّل python -m pabal_voice.edge خدمةً ثانية من الصورة نفسها (VOICE_EDGE_HOST · VOICE_EDGE_PORT، والافتراضي 127.0.0.1 · 8096، وVOICE_EDGE_DIR). ونقطة الدخول أول ما يفك ما يأتي من الإنترنت، لذا لا تُرفق بها أي سر، وضعها في شبكة داخلية لا يبلغها إلا الوكيل والبوابة.
  • الوكيل العكسي يمرّر مساري الساعة فقط — /api/watch/v1/audio (WebSocket، إلى نقطة الدخول) و/api/watch/v1/ (إلى واجهة الساعة). وفي جهة واجهة الساعة اتركه يضيف عنوان المتصل في آخر X-Forwarded-For (فالخادم يحسب بتلك القيمة حد تسجيل الدخول لكل عنوان). أما في جهة نقطة الدخول فاحذف X-Forwarded-For · X-Real-IP.
  • لا يمكن التسجيل من الساعة، ولا الاتصال بالبوتات التي تستخدم الأدوات · MCP · مفتاح المالك · وضع الصوت الخام · نص المحادثة لمالك البوت (WATCH_NOT_YET).

الاحتفاظ بالمحادثة (اختياري، قيد الإعداد)

ميزة تحفظ محادثة المكالمة نصاً في محادثة المتصل وحدها إذا اختار ذلك في نافذة التأكيد (استخدام التطبيق — البوتات الصوتية). لا يوجد مفتاح سماح للمشغّل، وتُفعَّل إذا توفّر كل ما يلي.

  • قسم id="bot-calls-keep-text" في سياسة الخصوصية (المسودة: docs/voice-bots/p0/privacy-*.draft.html في المستودع). وهذا القسم نفسه هو مفتاح التفعيل. ويعرف التطبيق هل يعرض خانة الاختيار من pabal_voice_keep_text في help.getAppConfig.
  • VOICE_KEEP_TEXT في البوابة 0.9.0: قيمته الافتراضية true مع محرّك النموذج. أما مع محرّك الصدى (بما فيه البوابة المخصّصة للصوت الخام) فإن كان true عُدّ خطأ إعداد ولم تبدأ البوابة.
  • بناء تطبيق فيه خانة الاختيار. ولا يدخل نص الخانة في بناء الإصدار إلا بعد المراجعة القانونية. ولا تعرض الساعة نصاً لم يُراجَع، أما TELEGRAM_VOICE_KEEP_TEXT_UNREVIEWED=true فمفتاح لخادم الاختبار الداخلي فقط يعرض النص قبل مراجعته.
  • على الخادم المفعّل يتأخر voice_call ended حتى 10 ثوانٍ لكل مكالمات البوتات غير الصوت الخام، وتُحجز مسبقاً 30 رقماً من أرقام الرسائل بعد سجل المكالمة (حتى لا يعرف مالك البوت اختيار المتصل).
  • نص المحادثة الذي ينتظر الكتابة بعد انتهاء المكالمة موجود في الذاكرة فقط، فإن أُعيد تشغيل الخادم في تلك الأثناء لم يُحفظ نص تلك المكالمة.

المكالمات من البوتات (اختياري، بانتظار المراجعة القانونية)

بوت صوتي يتصل أولاً بشخص أذِن لذلك البوت بالاتصال به (البوتات الصوتية — المكالمات من البوتات). شروطها أكثر من شروط المكالمات إلى البوتات، ولا تفعّلها قبل انتهاء المراجعة القانونية. الترتيب:

  1. الخادم: أنشئ مجلد مفتاح الأدلة (TELEGRAM_VOICE_EVIDENCE_KEY_DIR — 700، خارج المجلدات التي يُحتفظ لها بنسخ احتياطية مثل مجلدي البيانات والمفاتيح) بالأمر new من scripts/voice-evidence-key.py (وrotate لاستبدال المفتاح، وcheck لفحصه)، واضبط أول إصدار من التطبيق فيه نافذة الإذن كحد أدنى للمكالمات الصادرة، مثل TELEGRAM_VOICE_OUTBOUND_MIN_BUILD_ANDROID (متغيرات البيئة). لا يصل TELEGRAM_VOICE_EVIDENCE_KEY_DIR إلى الخادم إذا كُتب في .env وحده — أضفه إلى pabal-server في deploy/docker-compose.voice.yml، ضمن environment (المسار داخل الحاوية) وvolumes (المجلد على المضيف).
  2. التطبيقات: أصدِر إصدارات من التطبيق فيها نافذة الإذن. قواعد روابط الإذن في التطبيقات لا تعرف إلا pabal.me، لذا يجب لخادم آخر إضافة مضيفه إلى قواعد الروابط في إصدارات التطبيق حتى يفتح الرابط الذي يرسله البوت النافذة (أما القائمة وقائمة الإعدادات فتعملان كما هما).
  3. الجانب القانوني: انشر قسم id="bot-calls-outbound" في سياسة الخصوصية وقسم id="voice-bot-owners-outbound" في شروط الاستخدام (وقسم voice-bot-owners أيضاً، ليوافق المالكون على الشروط). والمسودات في docs/voice-bots/p0/ في المستودع. وتُراجَع نصوص نافذة الإذن وتسجيلات إعلانات المكالمات الصادرة (notice-out·apology-rule) وتُعتمد في هذه المرحلة أيضاً.
  4. المفتاح: في المكالمات من البوتات ضمن تبويب الصوت في صفحة الإدارة، اضبط البلدان والحدود وفعّل بعبارة التأكيد (صفحة الإدارة — تبويب الصوت). ولكل بوت مراجعة مستقلة من المشغّل للمكالمات الصادرة.
  • البوابة: لا تُجري مكالمات البوتات إلا بوابة فيها VOICE_OUTBOUND=true. ومع محرك نموذج لا تبدأ إلا إذا كانت تسجيلات إعلانات المكالمات الصادرة الأربعة معتمدة، أما التسجيلات المسودة (VOICE_OUTBOUND_DRAFT_NOTICES) فلخوادم الاختبار فقط.
  • لا تضع في .env الإنتاج أسماءً خاصة بالاختبار والتطوير مثل TELEGRAM_VOICE_CALLME_UNREVIEWED وTELEGRAM_VOICE_TEST_DIAL.
  • والمكالمات من البوتات ليست مشفّرة من طرف إلى طرف كذلك — يمر الصوت عبر بوابة هذا الخادم ومزوّد الذكاء الاصطناعي.

13. صور الخرائط في رسائل الموقع (اختياري)

عند إرسال موقع لا يرسم تطبيق Android وتطبيقا سطح المكتب (macOS وWindows) الخريطة داخل الفقاعة بأنفسهم، بل يطلبون من الخادم صورة خريطة لتلك النقطة (upload.getWebFile). ويجلب الخادم الصورة من Maps Static API من Google ثم يمرّرها. وبدون ذلك يعرض Android صورة بديلة ويعرض سطح المكتب إطاراً فارغاً فيه دبوس. أما iPhone فيرسم الخريطة على الهاتف نفسه فلا يعنيه هذا الإعداد، وإرسال الموقع والخريطة الكبيرة التي تُفتح عند لمسه يعملان في كل الأحوال.

  1. في وحدة تحكم Google Cloud اختر مشروعاً مرتبطاً بحساب فوترة، وفعّل Maps Static API.
  2. أنشئ مفتاح API؛ وقيّد التطبيق بـعناوين IP (عنوان IP العام الذي يخرج منه الخادم إلى الإنترنت) وقيّد الواجهة بـ Maps Static API وحدها. يبقى المفتاح على الخادم ولا يدخل التطبيقات أبداً.
  3. ضعه في .env وأعد تشغيل الخادم بـ docker compose up -d pabal-server.
  4. تحقّق في السجل. إذا كانت الخرائط معطّلة فإن Maps in location messages are off: … يذكر السبب (ولا تظهر قيمة المفتاح في السجل أبداً).
STATIC_MAPS_KEY=AIza…               # لا تترك قيمة المفتاح في المحادثات أو السجلات أو المستودع
STATIC_MAPS_PER_DAY=300             # عدد الصور الجديدة التي تُجلب في اليوم (0 = بلا حد)
STATIC_MAPS_PER_USER_HOUR=120       # عدد الصور الجديدة التي يستطيع شخص واحد التسبب في جلبها في الساعة
INFO  TelegramServer - Maps in location messages from Google Static Maps (120 new per person an hour, 300 a day)
  • التكلفة: Maps Static API مجانية حتى 10,000 طلب في الشهر. ويحتفظ الخادم بكل صورة يوماً ويعطيها مرة أخرى لأي جهاز يطلب الخريطة نفسها، ولا تُحسب الصورة المعادة ضمن الحدود. والحد الافتراضي (300 في اليوم) يعني نحو 9,000 في الشهر، أي ضمن الشريحة المجانية. والطلب الذي يتجاوز حداً يُرفض بـ FLOOD_WAIT، ويعرض التطبيق الصورة البديلة في أثناء ذلك.
  • سياسة الخصوصية: تذهب إحداثيات الموقع المشارَك (خط العرض وخط الطول) إلى Google — دون هوية المرسل. ومن صفحة الإدارة ← الإعدادات ← معلومات المشغّل أضف Google LLC ‏(Maps Static API، الولايات المتحدة) إلى جهات المعالجة المفوَّضة وإلى النقل إلى الخارج.
  • إذا ظهر في السجل Map picture not made: Google Static Maps answered 403: … فتحقّق من قيود المفتاح (IP وAPI) أو من حساب الفوترة.

التشغيل

الحالة والسجلات

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

التحديث

cd /opt/pabal && git pull
cd deploy
./backup.sh                                     # نسخة احتياطية أولاً
docker compose up -d --build pabal-server             # استبدال الخادم وحده بالصورة الجديدة
docker compose logs --since 5m pabal-server | grep -E "Website|ERROR"

خلال عشرات الثواني التي يُعاد فيها تشغيل الخادم ينقطع التطبيق، ثم يعيد الاتصال بنفسه دون تسجيل دخول جديد. ويطبّق الخادم تغييرات الجداول تلقائياً عند التشغيل. والمجلدات المربوطة مستقلة عن الصورة، فلا تُحذف البيانات بـ docker compose down أو بحذف الصورة — فقط لا تحذف /srv/pabal.

الرجوع إلى الإصدار السابق: الرجوع إلى الصورة السابقة يُبقي ما كتبه الإصدار الجديد في قاعدة البيانات، وقد لا يستطيع الإصدار القديم قراءته (فالإصدار الذي يحوي شيفرة البوتات الصوتية مثلاً يكتب أحداثاً جديدة كسجلات المكالمات في المحادثات حتى والصوت معطّل). لذا عند الرجوع استعد قاعدة البيانات أيضاً من النسخة الاحتياطية المأخوذة قبيل التحديث (الاستعادة).

النسخ الاحتياطي

./backup.sh            # → /srv/pabal/backups/<التاريخ-الوقت>/{pabal.dump, server-keys-data.tar.gz}
crontab -e             # يومياً في الساعة 03:00:
# 0 3 * * * /opt/pabal/deploy/backup.sh >> /srv/pabal/backups/backup.log 2>&1
  • تُحذف تلقائياً النسخ الاحتياطية التي مضى عليها 14 يوماً (ويمكن تغيير ذلك، مثل KEEP_DAYS=30 ./backup.sh).
  • انسخها إلى مكان آخر أيضاً. فالنسخة الاحتياطية على القرص نفسه لا تصمد أمام تعطّل القرص.
  • تحتوي النسخ الاحتياطية على مفتاح RSA الخاص والقيم السرية لـ SMS وSMTP. فاحمِ مكان حفظها بقدر ما تحمي الخادم.

الاستعادة

cd /opt/pabal/deploy
B=/srv/pabal/backups/20260920-030000            # النسخة الاحتياطية المراد استعادتها
docker compose stop pabal-server pabal-caddy
docker compose exec -T pabal-postgres dropdb -U pabal pabal
docker compose exec -T pabal-postgres createdb -U pabal pabal
docker compose exec -T pabal-postgres pg_restore -U pabal -d pabal --no-owner < $B/pabal.dump
docker compose exec -T pabal-postgres psql -U pabal -d pabal -c "ALTER DATABASE pabal SET search_path = public"   # المسار الافتراضي لقاعدة البيانات — الاستعادة لا تعيده، فاضبطه من جديد
sudo tar -C /srv/pabal/pabal_server -xzf $B/server-keys-data.tar.gz
sudo chown -R 1000:1000 /srv/pabal/pabal_server
docker compose up -d

والأمر نفسه عند الانتقال إلى خادم جديد: نفّذ الخطوات من 1 إلى 5، ثم نفّذ الاستعادة أعلاه بدلاً من الخطوة 6، وشغّل. إذا استعدت المفتاح نفسه فلا حاجة إلى إعادة بناء التطبيق (إلا إذا تغيّر عنوان IP الخادم، فعندها أعد البناء).

التشغيل والإيقاف

docker compose restart pabal-server       # إعادة تشغيل الخادم وحده
docker compose stop                 # إيقاف الكل (تبقى البيانات كما هي)
docker compose up -d                # التشغيل من جديد

حتى لو أعدت تشغيل جهاز الخادم، تعمل الحاويات من جديد تلقائياً لأنها مضبوطة على restart: unless-stopped.

قائمة التحقق الأمني

  • صلاحيات .env هي 600، وPOSTGRES_PASSWORD قيمة عشوائية
  • صلاحيات /srv/pabal/pabal_server/keys هي 700، ولا يوجد مفتاح RSA الخاص في أي مكان آخر غير النسخ الاحتياطية
  • (إذا فعّلت الإشعارات الفورية) صلاحيات firebase-service-account.json هي 600، ومحتواه غير موجود في المستودع ولا في .env
  • (إذا فعّلت الإشعارات الفورية على iPhone) صلاحيات AuthKey_<معرّف المفتاح>.p8 هي 600، ومحتواه غير موجود في المستودع ولا في .env
  • جدار الحماية: 22 (لعنوان IP المشغّل فقط إن أمكن) و80 و443 و8443 فقط
  • من الخارج يعيد https://النطاق/admin/ الرمز 404
  • صفحة الإدارة: أرقام الاختبار (테스트 번호) معطّلة، وطريقة إيصال الرمز (코드 전달 방식) هي SMS أو البريد الإلكتروني، والإرسال التجريبي (테스트 발송) ناجح
  • لم تفعّل TELEGRAM_WEBHOOK_ALLOW_LOCAL (كي لا تصل طلبات webhook الخاصة بالبوتات إلى الشبكة الداخلية)
  • (إذا فعّلت البوتات الصوتية) لا يقرأ سرَّ الرابط ومفتاحَ Vertex والمفتاحَ الرئيسي إلا المستخدم الذي يحتاج كل ملف منها (400/600)، والمفتاح الرئيسي خارج /srv/pabal فلا يدخل في النسخ الاحتياطية. ولم يُنشر منفذ الرابط، ولا يستخدم شبكة الرابط إلا الخادم والبوابة. ولا يوجد في خادم الإنتاج TELEGRAM_VOICE_NOTICE_CHECK ولا TELEGRAM_VOICE_TESTERS=*
  • SSH: عطّل تسجيل الدخول بكلمة المرور، واسمح بالمفاتيح فقط
  • تحقق من مهمة cron للنسخ الاحتياطي ومن النسخ إلى مكان خارجي، وتدرّب على الاستعادة مرة واحدة
  • التحديثات الأمنية التلقائية لنظام تشغيل الخادم (unattended-upgrades)

حل المشكلات

العَرَضالسبب ← الحل
التطبيق لا يتجاوز «جارٍ الاتصال…»① المنفذ 8443 محجوب ← nc -vz IP 8443، وتحقق من ufw ومن مجموعة الأمان السحابية ② بُني التطبيق بمفتاح عام آخر ← أعد الخطوة 9 بمفتاح هذا الخادم ③ قيمة PUBLIC_IP خاطئة ← صحّح .env ثم نفّذ docker compose up -d pabal-server
يتصل في البداية ثم ينقطع بعد قليلينتقل التطبيق إلى العنوان الذي أبلغه به الخادم (PUBLIC_IP:MTPROTO_PORT) لكن ذلك العنوان خاطئ ← تحقق من PUBLIC_IP
خطأ في شهادة HTTPSDNS لا يشير بعد إلى هذا الخادم، أو المنفذ 80 محجوب ← dig +short النطاق، docker compose logs pabal-caddy
server يعيد التشغيل باستمرار، مع password authentication failedغيّرت POSTGRES_PASSWORD بعد إنشاء البيانات ← أعده إلى قيمته الأصلية، أو نفّذ داخل قاعدة البيانات ALTER USER pabal PASSWORD '…'
AccessDeniedException: /app/keys/…مالك المجلد المربوط ← sudo chown -R 1000:1000 /srv/pabal/pabal_server
OutOfMemoryError، أو بطءارفع -Xmx في JAVA_OPTS ثم نفّذ docker compose up -d pabal-server
لا تصل رموز التسجيلراجع حالة الإرسال وسبب الفشل في تبويب رموز التسجيل (가입 코드) في صفحة الإدارة ← ثم قيم SMS وSMTP في الإعدادات (설정)، والإرسال التجريبي (테스트 발송)
لا تصل الإشعارات إلى Android① في السجل Push notifications are off ← مسار FCM_CREDENTIALS وصلاحيات الملف ② لا يوجد Push device registered ← بُني التطبيق دون إعداد Firebase (pabal-placeholder) أو لم يُفتح التطبيق الجديد بعد ③ يظهر sent لكن لا يظهر شيء ← إذن الإشعارات وإعدادات البطارية في الهاتف، وهل استُخدم «فرض الإيقاف» مع التطبيق ④ SENDER_ID_MISMATCH … Registrations are kept ← مفتاح الخادم وgoogle-services.json في التطبيق من مشروعين مختلفين ⑤ answered 404 without naming the token ← FCM_PROJECT_ID خاطئ ⑥ refused an access token ← المفتاح أُلغي أو يعود إلى مشروع آخر (ولا يُعاد الطلب طوال 5 دقائق)
لا تصل الإشعارات إلى iPhone① في السجل iPhone push notifications are off ← مسار APNS_KEY وصلاحيات الملف واسمه (AuthKey_<معرّف المفتاح>.p8) ② لا يوجد تسجيل type 1 ← لم يُسمح بالإشعارات في التطبيق، أو هو المحاكي ③ التسجيل موجود لكن لا يُرسَل شيء ← ذلك البناء أقل من APNS_MIN_BUILD (قيمة build في سطر التسجيل)، أو المحادثة مكتومة ④ APNs refused the provider token ← المفتاح أُلغي أو APNS_TEAM_ID لا يطابق فريق المفتاح (ولا يُعاد الإرسال طوال 5 دقائق) ⑤ DeviceTokenNotForTopic … Registrations are kept ← APNS_TOPIC لا يطابق معرّف حزمة التطبيق ⑥ يظهر sent لكن لا يُرى إلا «لديك إشعار جديد» ← لم يستطع امتداد الإشعارات فكّ التشفير (إن أعدت تثبيت التطبيق فافتحه مرة ليُسجَّل من جديد) ⑦ لا تصل المكالمات إلا إشعاراتٍ ← APNS_VOIP_MIN_BUILD فارغ أو أعلى من ذلك البناء (سلوك مقصود)
لا أستطيع الدخول إلى صفحة الإدارةتحقق من أن نفق SSH يعمل، ومن أن برنامجاً آخر لا يستخدم المنفذ 8080 على حاسوبك (غيّره إلى -L 18080:127.0.0.1:8080 وافتح localhost:18080)
network ssemiya-net declared as external, but could not be foundلم تُنشأ الشبكة بعد ← docker network create ssemiya-net (وإن كان اسمها مختلفاً فضعه في PABAL_NETWORK في .env)

القيود المعروفة

  • تكوين بخادم واحد. عند التشغيل تُحمَّل كل الحسابات والمحادثات والرسائل في الذاكرة. والتوسع الأفقي بتوزيعها على عدة خوادم غير ممكن بعد، فمع ازدياد البيانات يجب زيادة الذاكرة.
  • رموز التسجيل المنتظرة، وتحديثات البوتات التي لم تُجلب، ونصوص الاحتفاظ بالمحادثة التي تنتظر الكتابة، موجودة في الذاكرة، فتضيع عند إعادة تشغيل الخادم.
  • لطلبات رموز التسجيل فاصل زمني وحد يومي، لكن لا يوجد بعد حد لمعدل سائر الطلبات. فراقب السجلات ولوحة المعلومات (대시보드) كثيراً بعد الإطلاق مباشرة.
  • لا تصل الإشعارات الفورية إلا إلى تطبيقات الهاتف — Android (الخطوة 10) وiPhone (الخطوة 11). وعلى iPhone لا تصل إلا إلى البناءات التي رقمها APNS_MIN_BUILD فما فوق (ويُستبعد Pabal 1.0 (3))، ولا تعمل شاشة الاتصال في CallKit إلا بعد تحديد APNS_VOIP_MIN_BUILD. أما تطبيقا macOS وWindows فيستلمان ما داما مفتوحين.
  • «كتم الإشعارات» لكل محادثة محفوظ في ذاكرة الخادم فقط. فالإشعارات الفورية للمحادثات المكتومة تُرسَل إلى Android بأولوية غير عاجلة (والهاتف يصفّيها بنفسه) ولا تُرسَل إلى iPhone، لكن بعد إعادة تشغيل الخادم تُرسَل إلى كليهما كالمعتاد (فالتطبيق لا يُبلغ الخادم من جديد إلا عند تغيير الإعداد). وإذا استغرق التطبيق المغلق أكثر من 20 ثانية ليستيقظ ويتصل بالخادم، فقد تنتهي المكالمة عند المتصل بوصفها مكالمة لم يُرَدّ عليها.
  • مرفقات الرسائل تشمل الصور والملفات، أما الفيديو والرسائل الصوتية التي تُشغَّل داخل التطبيق فغير متاحة بعد. والقنوات والمجموعات الخارقة والمكالمات الجماعية والتحقق بخطوتين غير متاحة بعد أيضاً.
  • مفاتيح المصادقة محفوظة في قاعدة البيانات نصاً صريحاً (يحتاج إليها الخادم لفك التشفير). فاحمِ قاعدة البيانات والنسخ الاحتياطية بقدر ما تحمي مفتاح RSA.

الملفات

الملفالدور
deploy/docker-compose.ymlتكوين الإنتاج (pabal-server · pabal-postgres · pabal-caddy، مع ربط المجلدات)
deploy/CaddyfileHTTPS، وحجب مسارات الإدارة، وتمرير الموقع والوثائق وBot API
deploy/.env.exampleنموذج الإعدادات ← deploy/.env
deploy/backup.shتفريغ قاعدة البيانات + أرشفة المفاتيح والبيانات، وتنظيف النسخ القديمة
Dockerfileصورة الخادم (البناء ← JRE 21، uid 1000)
voice/بوابة الصوت pabal-voice (المصدر، وDockerfile، وREADME.md، ومواصفة الرابط PROTOCOL.md)
deploy/docker-compose.mcp.ymlالحاويات الثلاث لمكوّنات MCP الخاصة بالبوتات الصوتية (ملف التعريف mcp في Compose)
deploy/docker-compose.voice.ymlيضيف مكوّنات البوتات الصوتية — إعدادات الصوت في الخادم وشبكة الرابط، والبوابة pabal-voice (ملف التعريف voice)، ونقطة دخول الصوت للساعة pabal-voice-edge (ملف التعريف voice-watch)
deploy/ship.shيبني على حاسوب آخر صورة الخادم (ومع --voice صورة البوابة أيضاً) لمعالج الخادم، ويحمّلها عبر SSH وينسخ ملفات النشر
deploy/voice_readiness.pyفحوص قبل تفعيل الصوت (مفتاح Vertex، والأقسام القانونية، وإصدارات التطبيقات، والترحيل …) — لا يطبع أي سرّ
deploy/turnserver.conf.example، deploy/check_relay.pyنموذج إعدادات coturn يُثبَّت مباشرة على خادم Mac، وأداة التحقق من الترحيل