تشغيل الخادم
تثبيت الخادم
كيف تثبّت خادم 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/ من المستودع.
نظرة سريعة
شرح المخطط
- ثلاث مناطق: المستخدمون على الإنترنت ← الحاويات الثلاث على خادم الإنتاج ← المجلدات المربوطة على قرص المضيف. يمكن حذف الحاويات وإعادة إنشائها في أي وقت، أما كل ما يجب أن يبقى ففي
/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/tcp | Caddy | للجميع | إصدار الشهادات · التحويل إلى HTTPS |
| 443/tcp, 443/udp | Caddy | للجميع | الموقع · الوثائق · Bot API (وudp لـ HTTP/3) |
| 8443/tcp | الخادم | للجميع | اتصال التطبيق (MTProto) |
| 8080/tcp | الخادم | لا يُفتح | صفحة الإدارة · فحص الصحة — على 127.0.0.1 في الخادم فقط |
| 5432/tcp | PostgreSQL | لا يُفتح | على الشبكة الداخلية للحاويات فقط |
المنفذ 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 (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/keys | private.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_NETWORK | ssemiya-net | شبكة Docker التي تنضم إليها الحاويات الثلاث. أنشئها مسبقاً (الخطوة 6) |
MTPROTO_PORT | 8443 | إذا غيّرته فغيّر معه جدار الحماية وبناء التطبيق |
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_PREVIEWS | true | إذا كانت false تعرض الإشعارات المرسل فقط دون محتوى الرسالة |
يشغّل ملف Compose الخاص بالإنتاج الخادمَ وأرقام الاختبار معطّلة (TELEGRAM_TEST_NUMBERS=false). والقائمة الكاملة لقيم الإعدادات التي يقرؤها الخادم موجودة في الإدارة والإعدادات — متغيرات البيئة.
5. DNS
| الاسم | النوع | القيمة |
|---|---|---|
pabal.me | A | 203.0.113.10 |
www.pabal.me | A | 203.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/ في المتصفح وأدخل الرمز المميّز. الإعدادات التي يجب ضبطها قبل التشغيل الفعلي (التفاصيل في الإدارة والإعدادات):
- اجعل طريقة إيصال الرمز (코드 전달 방식) هي SMS أو البريد الإلكتروني. ففي طريقة «صفحة الإدارة» (관리 화면) على المشغّل أن يبلّغ الرموز واحداً واحداً.
- أدخل قيم SMS أو البريد الإلكتروني (SMTP) (이메일 (SMTP)) واحفظها ← ثم تحقق بـالإرسال التجريبي (테스트 발송) من أنها تصل فعلاً.
- تأكد من أن أرقام الاختبار (테스트 번호) معطّلة. فإذا فُعّلت يستطيع أي شخص تسجيل الدخول بأرقام
+99966…. - وإن لزم الأمر، عطّل السماح بالتسجيلات الجديدة (새 가입 허용) عند البدء، واستقبل أولاً من تدعوهم فقط.
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. ومن مشروع Firebase واحد تحصل على ملفين.
- الأسهم الرمادية تحضير يُجرى مرة واحدة فقط. فالمربع الأخضر
google-services.jsonيدخل في التطبيق ويُحمَل في كل ملف APK، فهو ليس سراً، أما المربع الأحمر، مفتاح حساب الخدمة، فسرّ لا يوضع إلا فيkeys/على الخادم. ويجب أن يؤخذ الملفان من المشروع نفسه. - الأسهم الزرقاء هي طريق الإشعار: ① عند فتح التطبيق أول مرة يسجّل الهاتف لدى الخادم رمز FCM ومفتاحاً لا يملكه إلا هو، ② وعند وصول رسالة أو مكالمة جديدة يشفّر الخادم الإشعار بذلك المفتاح ويسلّمه إلى FCM، ③ وحين يوقظ FCM الهاتف يفكّ التطبيق التشفير ويعرض الإشعار، ثم يعاود الاتصال بالخادم ليستلم ما فاته.
- ما تراه Google ليس إلا معلومات التسليم مثل الرمز المميّز للجهاز ووقت الإرسال والأولوية. أما اسم المرسل والرسالة فمشفّران فلا تستطيع قراءتهما.
- لا تُرسَل إشعارات فورية إلى هاتف يعرض التطبيق — فهو يستلم كل شيء عبر اتصاله أصلاً. والإشعارات الفورية للمحادثات التي كُتمت إشعاراتها تُرسَل بأولوية غير عاجلة، ويتولى التطبيق تصفيتها بنفسه.
لا يمكن بمفتاح الخادم إرسال الإشعارات الفورية إلا إلى التطبيقات المبنية بمشروع Firebase نفسه. فالتطبيق الذي بناه شخص آخر (مثل التطبيق الذي يُنزَّل من Pabal.me) مرتبط بمشروع Firebase الخاص بذلك الشخص، وهو أصلاً مبني بعنوان خادمه ومفتاحه العام. فتطبيقات خادمك تبنيها أنت، ومشروع Firebase تنشئه أنت أيضاً.
إنشاء مشروع Firebase
- في وحدة تحكم Firebase اختر Add project (إضافة مشروع). الاسم أياً كان، ولا حاجة إلى Google Analytics.
- Project settings → General → Your apps → Add app (Android) (إعدادات المشروع ← عام ← تطبيقاتك ← إضافة تطبيق). أدخل في اسم الحزمة
me.pabal.android(الاسم الذي يمنحه سكربت البناء للتطبيق). ولا حاجة إلى بصمة الشهادة (SHA-1) للإشعارات الفورية. - نزّل ملف
google-services.jsonالخاص بذلك التطبيق. إنه إعداد يوضع في التطبيق، وليس سراً. - من Project settings → Service accounts → Generate new private key (إعدادات المشروع ← حسابات الخدمة ← إنشاء مفتاح خاص جديد) نزّل ملف JSON وغيّر اسمه إلى
firebase-service-account.json. هذا الملف سرّي — لا تضع محتواه في المستودع ولا في المحادثات ولا في.env. - في 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) مسموحة.
التحقق
- ثبّت التطبيق المبني حديثاً على الهاتف (أو حدّثه) وافتحه مرة واحدة. سيظهر في سجل الخادم
Push device registered: user … type 2 token … (push key 256 bytes). ولا يُسجَّل الرمز المميّز إلا على شكل بصمة. - أغلق التطبيق بسحبه من قائمة التطبيقات الأخيرة. ولا تستخدم «فرض الإيقاف» من الإعدادات — فلا يسلّم Android الإشعارات الفورية إلى التطبيق حتى تفتحه بنفسك من جديد.
- أرسل رسالة من حساب آخر، فيظهر في السجل
Push MESSAGE_TEXT to user …: sentويظهر الإشعار على الهاتف.
- سياسة الخصوصية: في صفحة الإدارة ← الإعدادات (설정) ← معلومات المشغّل (운영자 정보)، اكتب Google LLC (Firebase Cloud Messaging، الولايات المتحدة) في تفويض المعالجة (처리 위탁) والنقل إلى الخارج (국외 이전). ونص السياسة يتضمن أصلاً جملة تفيد بأن الإشعارات تمرّ عبر FCM. واكتب الشيء نفسه أيضاً في نموذج «أمان البيانات» في Google Play.
- إذا تسرّب المفتاح فاحذفه من وحدة تحكم Firebase، وأنشئ مفتاحاً جديداً ونزّله، واستبدل الملف، ثم أعد تشغيل الخادم. ولا حاجة إلى إعادة بناء التطبيق.
تُفعَّل الإشعارات الفورية على 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إلا بدءاً من البناء المصنوع على هذا النحو، وقبله تصل المكالمات أيضاً إشعاراتٍ.
على خلاف Firebase، لا يوجد ملف إعداد يوضع في التطبيق — فالتطبيق مبنيّ أصلاً ليستطيع استخدام APNs (إذن الإشعارات وApp Group وامتداد الإشعارات)، ويكفي أن تضع المفتاح على الخادم. غير أن مفتاح الخادم لا يستطيع إرسال الإشعارات الفورية إلا إلى التطبيقات التي وقّعها الفريق الذي أنشأ ذلك المفتاح ويطابق معرّف حزمتها APNS_TOPIC. فإذا كنت قد وقّعت تطبيق iPhone الخاص بخادمك بحساب مطوّر Apple الخاص بك، فأنشئ مفتاح APNs أيضاً في ذلك الحساب.
إنشاء مفتاح APNs
- Apple Developer → Certificates, IDs & Profiles → Keys → +. يجب أن يكون الحساب حساب الفريق الذي وقّع التطبيق.
- اكتب اسماً وحدّد Apple Push Notifications service (APNs). واترك البيئة على Sandbox & Production والنطاق على الفريق كله (Team Scoped) — لأن الإرسال يتم إلى وجهتين: الإشعارات وVoIP.
- 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_BUILD | 4 | لا يُرسَل شيء إلى أي بناء رقمه أقل من هذا (و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(ويُعلم التطبيق الخادم بذلك عند التسجيل).
التحقق
- على جهاز iPhone حقيقي افتح التطبيق مرة واحدة واسمح بالإشعارات. سيظهر في سجل الخادم
Push device registered: user … type 1 token … (push key 256 bytes, build …)وسطرtype 9. لا يمكن التحقق بالمحاكي — فليس فيه رمز APNs ولا CallKit. - أغلق التطبيق بسحبه إلى الأعلى.
- أرسل رسالة من حساب آخر، فيظهر في السجل
Push MESSAGE_TEXT to user …: sentويظهر المرسل والمحتوى على شاشة القفل.
- سياسة الخصوصية: في صفحة الإدارة ← الإعدادات (설정) ← معلومات المشغّل (운영자 정보)، اكتب Apple Inc. (خدمة الإشعارات الفورية من Apple، الولايات المتحدة) في تفويض المعالجة (처리 위탁) والنقل إلى الخارج (국외 이전). ونص السياسة يتضمن أصلاً جملة تفيد بأن الإشعارات تمرّ عبر APNs. وتحقّق أيضاً من بند معرّفات الجهاز (رمز الإشعارات الفورية) في استبيان خصوصية التطبيق في App Store Connect.
- إذا تسرّب المفتاح فألغِه بـ Revoke في حساب المطوّر، وأنشئ مفتاحاً جديداً، واستبدل الملف، ثم أعد تشغيل الخادم. ولا حاجة إلى إعادة بناء التطبيق.
12. البوتات الصوتية (اختياري)
البوتات الصوتية ميزة يرد فيها نموذج صوتي بالذكاء الاصطناعي عندما يتصل شخص ببوت من التطبيق (والشرح الموجّه لمالكي البوتات في البوتات الصوتية). يحتاج الخادم إلى مكوّنين إضافيين، ولا تعمل الميزة إلا بعد اكتمال الوثائق القانونية وإصدارات التطبيق. وهي معطّلة افتراضياً.
مكوّنات الصوت ليست في 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، ولا يخرج الصوت من الجهاز) |
| (اختياري) المفتاح الرئيسي | المفتاح الذي يغلّف مفاتيح مالكي البوتات وأسرار وضع الصوت الخام | مفتاح المالك ووضع الصوت الخام معطّلان |
| (اختياري) مكوّنات MCP | pabal-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
- في مشروع Google Cloud فعّل Agent Platform API (اسمها القديم Vertex AI API،
aiplatform.googleapis.com). يجب أن يكون المشروع مرتبطاً بحساب فوترة. لا تُستخدم Gemini Developer API (مفاتيح AI Studio). - أنشئ حساب خدمة وامنحه دوراً واحداً فقط هو
roles/aiplatform.user(ولتضييقه أكثر استخدم دوراً مخصصاً فيه صلاحيةaiplatform.endpoints.predictوحدها). وأنشئ مفتاح الإنتاج منفصلاً عن مفتاح التطوير. - نزّل مفتاح JSON وضعه خارج المستودع وخارج
.env، مثلاً في/srv/pabal/pabal_voice/secrets/vertex-sa.json، بحيث لا يقرؤه إلا مستخدم البوابة (في Linux:chown 10001:10001،chmod 400). وإذا كان مقروءاً للجميع ترفض البوابة البدء. - نماذج الوقت الحقيقي (realtime) متوفرة في
us-central1(آيوا، الولايات المتحدة) فقط (تم التحقق في 2026-09-26). يجب أن تذكر سياسة الخصوصية ونافذة التأكيد أن صوت المكالمة يذهب إلى الولايات المتحدة، ويجب أن تطابق المنطقة المكتوبة في نافذة تأكيد التطبيق منطقةَ البوابة (VOICE_VERTEX_REGION) كي يسلّم الخادم المكالمات إلى تلك البوابة. - مهام متبقية: تنبيهات الميزانية، وحصة الجلسات المتزامنة (ضعف سعة البوابة)، وذاكرة التخزين المؤقت للبيانات في 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) إحدى لغات التسجيلات العشر (koenjazhfresidvikmar)، وقيمة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_ADDRESS | 127.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_DAYS | 7 | عدد أيام بقاء تسجيل الدخول في الساعة (1–365) |
TELEGRAM_WATCH_AUTH_PER_MINUTE | 30 | حد طلبات رمز تسجيل الدخول على مستوى الخادم كله (في الدقيقة). وحد كل عنوان منفصل |
TELEGRAM_VOICE_MIN_BUILD_WATCHOS | 1 | أول بناء من تطبيق الساعة يعرض نافذة التأكيد كاملة |
- البوابة:
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 رقماً من أرقام الرسائل بعد سجل المكالمة (حتى لا يعرف مالك البوت اختيار المتصل). - نص المحادثة الذي ينتظر الكتابة بعد انتهاء المكالمة موجود في الذاكرة فقط، فإن أُعيد تشغيل الخادم في تلك الأثناء لم يُحفظ نص تلك المكالمة.
المكالمات من البوتات (اختياري، بانتظار المراجعة القانونية)
بوت صوتي يتصل أولاً بشخص أذِن لذلك البوت بالاتصال به (البوتات الصوتية — المكالمات من البوتات). شروطها أكثر من شروط المكالمات إلى البوتات، ولا تفعّلها قبل انتهاء المراجعة القانونية. الترتيب:
- الخادم: أنشئ مجلد مفتاح الأدلة (
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(المجلد على المضيف). - التطبيقات: أصدِر إصدارات من التطبيق فيها نافذة الإذن. قواعد روابط الإذن في التطبيقات لا تعرف إلا
pabal.me، لذا يجب لخادم آخر إضافة مضيفه إلى قواعد الروابط في إصدارات التطبيق حتى يفتح الرابط الذي يرسله البوت النافذة (أما القائمة وقائمة الإعدادات فتعملان كما هما). - الجانب القانوني: انشر قسم
id="bot-calls-outbound"في سياسة الخصوصية وقسمid="voice-bot-owners-outbound"في شروط الاستخدام (وقسمvoice-bot-ownersأيضاً، ليوافق المالكون على الشروط). والمسودات فيdocs/voice-bots/p0/في المستودع. وتُراجَع نصوص نافذة الإذن وتسجيلات إعلانات المكالمات الصادرة (notice-out·apology-rule) وتُعتمد في هذه المرحلة أيضاً. - المفتاح: في المكالمات من البوتات ضمن تبويب الصوت في صفحة الإدارة، اضبط البلدان والحدود وفعّل بعبارة التأكيد (صفحة الإدارة — تبويب الصوت). ولكل بوت مراجعة مستقلة من المشغّل للمكالمات الصادرة.
- البوابة: لا تُجري مكالمات البوتات إلا بوابة فيها
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 فيرسم الخريطة على الهاتف نفسه فلا يعنيه هذا الإعداد، وإرسال الموقع والخريطة الكبيرة التي تُفتح عند لمسه يعملان في كل الأحوال.
- في وحدة تحكم Google Cloud اختر مشروعاً مرتبطاً بحساب فوترة، وفعّل Maps Static API.
- أنشئ مفتاح API؛ وقيّد التطبيق بـعناوين IP (عنوان IP العام الذي يخرج منه الخادم إلى الإنترنت) وقيّد الواجهة بـ Maps Static API وحدها. يبقى المفتاح على الخادم ولا يدخل التطبيقات أبداً.
- ضعه في
.envوأعد تشغيل الخادم بـdocker compose up -d pabal-server. - تحقّق في السجل. إذا كانت الخرائط معطّلة فإن
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 |
| خطأ في شهادة HTTPS | DNS لا يشير بعد إلى هذا الخادم، أو المنفذ 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/Caddyfile | HTTPS، وحجب مسارات الإدارة، وتمرير الموقع والوثائق و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، وأداة التحقق من الترحيل |