> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# دليل Asterisk العملي: من التحويلة Extension إلى الرد الآلي IVR
- URL: https://arabroot.io/articles/دليل-asterisk-العملي-من-التحويلة-الى-الرد-الالي/
- Published: 2026-10-19T06:30:00.000Z
- Updated: 2026-10-20T20:46:30.000Z
- Description: نبني مقسم Asterisk من الكود المصدري في Docker، ونكتب خطة اتصال فيها بريد صوتي ورد آلي وتسجيل مكالمات، ثم نتحكم به عبر AMI وARI.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الاستضافة الذاتية والتطبيقات, تطبيقات لفريقك, الشبكات والوصول, الاستضافة الذاتية, Asterisk

في [مقال تتبع المكالمة](https://arabroot.io/articles/%D8%AA%D8%AA%D8%A8%D8%B9-%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A9-sip-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D9%88%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%81%D9%8A-%D8%A7%D8%AA%D8%AC%D8%A7%D9%87-%D9%88%D8%A7%D8%AD%D8%AF/) استخدمنا Asterisk كأداة فقط: تحويلتان وسطران في خطة الاتصال، ثم ركزنا على ما يحدث للحزم في الشبكة. ولكن المقسم في أي مكتب حقيقي يطلب منه أكثر من ذلك بكثير، فالموظف الذي لا يرد يجب أن يترك له المتصل رسالة، والعميل الذي يتصل بالرقم الرئيسي يسمع قائمة «للمبيعات اضغط 1»، والإدارة تريد تسجيل المكالمات، ونظام CRM يريد أن يعرف من يتصل الآن وأن يبدأ مكالمة بضغطة زر. وكل هذا يكتب في Asterisk في ملفات نصية، وأغلب من يفتحها أول مرة يغلقها بسرعة، والسبب أن الوثائق ضخمة ولا تقول لك من أين تبدأ.

المواضيع في السلسلة:

- [مصطلحات الاتصالات الهاتفية VoIP للمبتدئين](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-voip-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)
- [تتبع مكالمة SIP عبر الشبكة: لماذا يصل الصوت في اتجاه واحد؟](https://arabroot.io/articles/%D8%AA%D8%AA%D8%A8%D8%B9-%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A9-sip-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D9%88%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%81%D9%8A-%D8%A7%D8%AA%D8%AC%D8%A7%D9%87-%D9%88%D8%A7%D8%AD%D8%AF/)
- [لماذا يتقطع الصوت في مكالمات VoIP؟ قياس Jitter وPacket Loss](https://arabroot.io/articles/%D9%84%D9%85%D8%A7%D8%B0%D8%A7-%D9%8A%D8%AA%D9%82%D8%B7%D8%B9-%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%81%D9%8A-%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A7%D8%AA-voip/)
- [ربط المقسم PBX بشبكة الهاتف عبر SIP Trunk والأرقام DID](https://arabroot.io/articles/%D8%B1%D8%A8%D8%B7-%D8%A7%D9%84%D9%85%D9%82%D8%B3%D9%85-%D8%A8%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D9%87%D8%A7%D8%AA%D9%81-%D8%B9%D8%A8%D8%B1-sip-trunk/)
- دليل Asterisk العملي: من التحويلة Extension إلى الرد الآلي IVR (الموضوع الحالي)
- [FreePBX: مقسم هاتف PBX كامل تديره من المتصفح](https://arabroot.io/articles/freepbx-%D9%85%D9%82%D8%B3%D9%85-%D9%87%D8%A7%D8%AA%D9%81-%D9%83%D8%A7%D9%85%D9%84-%D8%AA%D8%AF%D9%8A%D8%B1%D9%87-%D9%85%D9%86-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD/)
- [FreeSWITCH للمنصات الكبيرة ومراكز الاتصال Contact Center](https://arabroot.io/articles/freeswitch-%D9%84%D9%84%D9%85%D9%86%D8%B5%D8%A7%D8%AA-%D8%A7%D9%84%D9%83%D8%A8%D9%8A%D8%B1%D8%A9-%D9%88%D9%85%D8%B1%D8%A7%D9%83%D8%B2-%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84/)
- [3CX أم FreePBX أم FreeSWITCH؟ كيف تختار المقسم PBX](https://arabroot.io/articles/3cx-%D8%A7%D9%85-freepbx-%D8%A7%D9%85-freeswitch/)
- [هواتف SIP المكتبية والتطبيقات: لماذا لا يرن الجوال النائم؟](https://arabroot.io/articles/%D9%87%D9%88%D8%A7%D8%AA%D9%81-sip-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%AC%D9%88%D8%A7%D9%84/)
- [مركز اتصال Call Center على Asterisk: الطابور وتقرير SLA](https://arabroot.io/articles/%D9%85%D8%B1%D9%83%D8%B2-%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-call-center-%D8%B9%D9%84%D9%89-asterisk/)
- [تأمين المقسم من الاحتيال على المكالمات Toll Fraud](https://arabroot.io/articles/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%A7%D9%84%D9%85%D9%82%D8%B3%D9%85-%D9%85%D9%86-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%8A%D8%A7%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A7%D8%AA/)
- [موازنة الحمل Load Balancing أمام خوادم Asterisk مع Kamailio](https://arabroot.io/articles/%D9%85%D9%88%D8%A7%D8%B2%D9%86%D8%A9-%D8%A7%D9%84%D8%AD%D9%85%D9%84-%D8%A3%D9%85%D8%A7%D9%85-%D8%AE%D9%88%D8%A7%D8%AF%D9%85-asterisk-%D9%85%D8%B9-kamailio/)

لذلك سوف نبني في هذا المقال مقسماً كاملاً خطوة بخطوة، وكل خطوة تبنى على التي قبلها، وكل مخرج في المقال جاء من تشغيل حقيقي. وسوف نتناول ما يلي:

- ما هو Asterisk، ومتى تختاره ومتى تختار غيره، وأي إصدار تثبت بحسب سياسة الدعم الطويل LTS.
- بناء Asterisk 22 من الكود المصدري في Docker Image، وتشغيله مع Docker Compose.
- التحويلات Extensions في PJSIP بالقوالب Templates.
- أساسيات خطة الاتصال Dialplan، ثم تسجيل المكالمات Call Recording والبريد الصوتي Voicemail.
- رد آلي IVR بقائمة ووقت دوام ومنطقة زمنية.
- التحكم بالمقسم من برامجك عبر AMI وARI، مع تجربة لكل منهما.
- التشخيص من سطر الأوامر CLI، والنسخ الاحتياطي والتحديث، وما يجب أن تغيره قبل الإنتاج.

## ما هو Asterisk، ومتى تختاره؟

[Asterisk](https://www.asterisk.org/?ref=arabroot.io) محرك اتصالات مفتوح المصدر يعمل على Linux، يستقبل المكالمات من الهواتف والخطوط الخارجية، ثم ينفذ عليها ما تكتبه له في خطة الاتصال، سواءً كان ذلك تحويل المكالمة إلى هاتف آخر أو تشغيل رسالة أو تسجيل بريد صوتي أو تسليمها لبرنامج تكتبه أنت. وهو مرخص بترخيص GPL الإصدار الثاني كما في [ملف الترخيص](https://github.com/asterisk/asterisk/blob/master/LICENSE?ref=arabroot.io)، وتملك حقوقه شركة Sangoma التي تبيع أيضاً ترخيصاً تجارياً لمن لا يناسبه GPL.

وقد يتساءل البعض: لماذا نتعب أنفسنا بملفات نصية ما دام FreePBX يقدم واجهة ويب فوق Asterisk نفسه؟ والإجابة على ذلك أن FreePBX يكتب هذه الملفات نيابة عنك، فإذا لم تفهم ما يكتبه فلن تستطيع تشخيص أي عطل فيه، ولن تستطيع إضافة ما لا توفره الواجهة. لذلك فهذا المقال هو الأساس، والمقال التالي في السلسلة عن FreePBX يبني عليه. وبشكل عام:

- اختر Asterisk مباشرة إذا كان المقسم جزءاً من منتج تبنيه، أو تريد التحكم الكامل بخطة الاتصال، أو ستربطه ببرامجك عبر ARI، أو تريد أن تحفظ الإعداد كله في Git.
- اختر FreePBX إذا كان من سيدير المقسم موظف دعم فني يضيف التحويلات ويغير المسارات من المتصفح.
- انظر إلى FreeSWITCH أو Kamailio أمام عدة سيرفرات Asterisk إذا كنت تبني منصة لآلاف المكالمات في الوقت نفسه، وسوف نتناولهما في مقالات هذه السلسلة.

## أي إصدار تثبت؟ سياسة الدعم الطويل LTS

يصدر مشروع Asterisk فرعاً رئيسياً جديداً مرة في السنة تقريباً، ويتناوب بين فرع عادي Standard وفرع بدعم طويل LTS. وبحسب [جدول إصدارات Asterisk](https://docs.asterisk.org/About-the-Project/Asterisk-Versions/?ref=arabroot.io) يحصل فرع LTS على أربع سنوات من الدعم الكامل ثم سنة من إصلاحات الأمان فقط، أما الفرع العادي فيحصل على سنة دعم كامل على الأقل ثم سنة إصلاحات أمان. والجدول التالي يبين الفروع المهمة كما في الصفحة الرسمية في 20 أكتوبر 2026:

| الفرع | النوع    | الإصدار الأول     | إصلاحات الأمان فقط | نهاية الدعم EOL | الحالة                   |
| ----- | -------- | ----------------- | ------------------ | --------------- | ------------------------ |
| 20    | LTS      | 2022-10-19        | 2026-10-19         | 2027-10-19      | دخل مرحلة إصلاحات الأمان |
| 21    | Standard | 2023-10-18        | 2025-10-18         | 2026-10-18      | انتهى دعمه               |
| 22    | LTS      | 2024-10-16        | 2028-10-16         | 2029-10-16      | دعم كامل                 |
| 23    | Standard | 2025-10-15        | 2026-10-15         | 2027-10-15      | دعم كامل بحسب الجدول     |
| 24    | LTS      | 2026-10-15 (مخطط) | 2030-10-15         | 2031-10-15      | إصدار تجريبي RC          |

ولاحظ أن الفرع 24 موعده في الجدول 15 أكتوبر 2026، ولكن آخر ما نشر منه في [صفحة التنزيل](https://downloads.asterisk.org/pub/telephony/asterisk/releases/?ref=arabroot.io) عند كتابة المقال هو 24.0.0-rc2 بتاريخ 8 أكتوبر، أي أنه لم يصدر بشكل نهائي بعد. لذلك اخترنا في هذا الدليل الإصدار 22.11.0، وهو آخر إصدار نهائي من فرع 22 LTS، صدر في 27 أغسطس 2026 كما في [سجل التغييرات](https://downloads.asterisk.org/pub/telephony/asterisk/releases/ChangeLog-22.11.0.md?ref=arabroot.io)، ودعمه الكامل مستمر حتى أكتوبر 2028\. والقاعدة التي ننصح بها هي ألا تقوم بتثبيت فرع LTS جديد في الإنتاج في أول أسابيعه، والسبب أن الإصدارات الأولى من كل فرع تحمل أغلب الأخطاء، فابدأ اليوم بفرع 22، وخطط للانتقال إلى 24 بعد عدة إصدارات تصحيحية منه.

## كيف يعمل Asterisk من الداخل

قبل أن نكتب أي ملف، لنفهم الأجزاء التي سوف نتعامل معها، والصورة التالية تبين ما يحدث داخل Asterisk عندما تصل المكالمة:

![هاتف مكتبي وهاتف برمجي وخط خارجي ترسل مكالماتها إلى برامج تشغيل القنوات في Asterisk مثل chan_pjsip، فتتحول كل مكالمة إلى قناة Channel، ثم تمر إلى النواة Core التي فيها خطة الاتصال والجسور Bridges والترميز، والنواة تستدعي التطبيقات مثل Dial وVoiceMail وMixMonitor وBackGround وQueue وStasis، وعلى اليمين ثلاث طرق للتحكم: سطر الأوامر CLI وAMI على المنفذ 5038 وARI عبر HTTP وWebSocket على 8088](https://arabroot.io/content/images/2026/10/asterisk-01-architecture.webp)

القناة هي المكالمة من جهة واحدة، وخطة الاتصال تقرر ما يحدث لها

- القناة Channel: كل طرف في المكالمة قناة مستقلة، فالمكالمة من Alice إلى Bob قناتان، الأولى `PJSIP/1001-...` من هاتف Alice إلى المقسم، والثانية `PJSIP/1002-...` من المقسم إلى هاتف Bob، وهذا هو دور B2BUA الذي شرحناه في [مقال المصطلحات](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-voip-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#b2bua).
- برنامج تشغيل القنوات Channel Driver: الوحدة التي تتكلم البروتوكول، والمستخدمة اليوم لـ SIP هي `chan_pjsip`، أما `chan_sip` القديمة فقد حذفت من Asterisk في الفرع 21 كما في [سجل تغييرات 21.0.0](https://downloads.asterisk.org/pub/telephony/asterisk/releases/ChangeLog-21.0.0.md?ref=arabroot.io).
- خطة الاتصال Dialplan: التعليمات التي تقرر ماذا يحدث للقناة، في الملف `extensions.conf`.
- التطبيقات Applications: الأفعال التي تستدعيها خطة الاتصال، مثل `Dial` للرنين و`VoiceMail` للبريد الصوتي.
- الجسر Bridge: عندما يرد Bob يضع Asterisk القناتين في جسر واحد فيمر الصوت بينهما.
- طرق التحكم: سطر الأوامر CLI لك أنت، وAMI وARI لبرامجك، وسوف نجرب الثلاثة.

## المتطلبات

- سيرفر Linux عليه Docker وDocker Compose، ويكفي للتجربة ولمكتب صغير معالجان وذاكرة 2 GB، والبناء من الكود المصدري يحتاج وقتاً أطول على الأجهزة الضعيفة.
- مساحة قرص للتسجيلات والبريد الصوتي، وكل دقيقة تسجيل بصيغة WAV تأخذ نحو 1 MB كما سنرى.
- للإنتاج: منفذ SIP 5060/UDP ونطاق منافذ RTP مفتوحان للهواتف فقط، أما التجربة في هذا المقال فتعمل كلها داخل شبكة Docker داخلية دون أي منفذ مفتوح على جهازك.

## بناء Docker Image من الكود المصدري

لا توجد Image رسمية من مشروع Asterisk على Docker Hub، وحزم التوزيعات إما قديمة أو غير موجودة، ففي مقال تتبع المكالمة كانت حزمة Ubuntu 24.04 على الإصدار 20.6.0، أما Debian 12 فلا يوجد فيه حزمة `asterisk` في مستودعاته الرئيسية أصلاً. والطريقة التي توصي بها [وثائق التثبيت](https://docs.asterisk.org/Getting-Started/Installing-Asterisk/Installing-Asterisk-From-Source/?ref=arabroot.io) هي البناء من الكود المصدري، لذلك سوف نبني Image من مرحلتين Multi-stage: الأولى فيها أدوات البناء، والثانية فيها البرنامج الناتج فقط. قم بإنشاء مجلد للمشروع فيه المجلد `build`، وضع فيه الملف `build/Dockerfile`:

```dockerfile
FROM debian:12.15 AS build
ARG AST=22.11.0
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      build-essential wget ca-certificates bzip2 patch pkg-config \
      libedit-dev uuid-dev libjansson-dev libxml2-dev libsqlite3-dev libssl-dev libsrtp2-dev libcurl4-openssl-dev
WORKDIR /usr/src
RUN wget -q https://downloads.asterisk.org/pub/telephony/asterisk/releases/asterisk-${AST}.tar.gz \
 && wget -q https://downloads.asterisk.org/pub/telephony/asterisk/releases/asterisk-${AST}.sha256 \
 && sha256sum -c asterisk-${AST}.sha256 \
 && tar xzf asterisk-${AST}.tar.gz
WORKDIR /usr/src/asterisk-${AST}
RUN ./configure --with-pjproject-bundled --with-jansson-bundled=no > /dev/null \
 && make menuselect.makeopts \
 && menuselect/menuselect --disable BUILD_NATIVE \
      --enable CORE-SOUNDS-EN-ULAW --enable EXTRA-SOUNDS-EN-ULAW menuselect.makeopts
RUN make third-party > /tmp/tp.log 2>&1 || (tail -40 /tmp/tp.log; exit 1)
RUN make -j"$(nproc)" > /tmp/make.log 2>&1 || (tail -80 /tmp/make.log; exit 1)
RUN make install DESTDIR=/opt/ast > /dev/null \
 && make samples DESTDIR=/opt/ast > /dev/null \
 && rm -rf /opt/ast/var/run

FROM debian:12.15
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      libedit2 libuuid1 libjansson4 libxml2 libsqlite3-0 libssl3 libsrtp2-1 libcurl4 ca-certificates \
 && rm -rf /var/lib/apt/lists/* \
 && useradd -r -d /var/lib/asterisk -s /usr/sbin/nologin asterisk
COPY --from=build /opt/ast/ /
RUN ldconfig \
 && mkdir -p /var/spool/asterisk/monitor /var/run/asterisk /var/log/asterisk /var/lib/asterisk \
 && chown -R asterisk:asterisk /var/lib/asterisk /var/spool/asterisk /var/log/asterisk /var/run/asterisk /etc/asterisk
USER asterisk
CMD ["asterisk", "-f", "-vvv"]
```

في الإعداد أعلاه لاحظ التالي:

- نتحقق من الملف المنزل بالأمر `sha256sum -c` مقابل الملف الذي ينشره المشروع بجانبه، فإذا تغير الملف في الطريق يتوقف البناء.
- `--with-pjproject-bundled` يجعل Asterisk يبني نسخته المختبرة من مكتبة pjproject التي تقوم عليها وحدة PJSIP، بدل نسخة التوزيعة، وهي الطريقة التي توصي بها الوثائق.
- الأداة `menuselect` تختار ما يبنى من الوحدات، كما في [وثائق menuselect](https://docs.asterisk.org/Getting-Started/Installing-Asterisk/Installing-Asterisk-From-Source/Using-Menuselect-to-Select-Asterisk-Options/?ref=arabroot.io). وقد أطفأنا `BUILD_NATIVE` حتى لا يرتبط البرنامج بمعالج الجهاز الذي بني عليه، وإلا قد لا يعمل على سيرفر بمعالج أقدم، وأضفنا ملفات الصوت الإنجليزية بصيغة ulaw التي سنستخدمها في الرد الآلي.
- بنينا `third-party` أولاً دون تشغيل متواز، والسبب أن بناء pjproject مع `make -j` فشل عندنا مرتين بالخطأ `Makefile:212: source/pjsip/lib/libpjsua-x86_64-pc-linux-gnu.a] Error 2`، ونجح عندما بنيناه وحده، ثم بنينا بقية Asterisk بالتوازي.
- `make samples` يضع ملفات الإعداد النموذجية في `/etc/asterisk`، وسوف نستبدل منها ما نحتاجه فقط.
- Asterisk يعمل بالمستخدم `asterisk` وليس `root`، فإذا وجد مخترق ثغرة فيه لا يحصل على صلاحيات ال Container كاملة.

نبني ال Image ونتأكد من الإصدار:

```bash
docker build -t arb-asterisk:22.11.0 build
docker run --rm arb-asterisk:22.11.0 asterisk -V
docker image ls arb-asterisk:22.11.0 --format '{{.Size}}'
```

والمخرج سوف يكون كما يلي:

```text
Asterisk 22.11.0
692MB
```

والحجم كبير نسبياً لأن ال Image تحمل ملفات الصوت وملفات الإعداد النموذجية والوثائق المدمجة، وهذا مقبول لسيرفر واحد.

## التشغيل مع Docker Compose

الآن نكتب ملفات الإعداد في المجلد `conf`، ونربط كل ملف منها داخل ال Container بدل نسخ المجلد كاملاً، حتى تبقى بقية الملفات النموذجية كما هي. وهذا هو الملف `docker-compose.yml`:

```yaml
name: arb-ast
services:
  asterisk:
    build: ./build
    image: arb-asterisk:22.11.0
    container_name: arb-ast-pbx
    restart: unless-stopped
    networks:
      voip:
        ipv4_address: 10.88.0.10
    volumes:
      - ./conf/asterisk.conf:/etc/asterisk/asterisk.conf:ro
      - ./conf/pjsip.conf:/etc/asterisk/pjsip.conf:ro
      - ./conf/extensions.conf:/etc/asterisk/extensions.conf:ro
      - ./conf/voicemail.conf:/etc/asterisk/voicemail.conf:ro
      - ./conf/rtp.conf:/etc/asterisk/rtp.conf:ro
      - ./conf/logger.conf:/etc/asterisk/logger.conf:ro
      - ./conf/manager.conf:/etc/asterisk/manager.conf:ro
      - ./conf/http.conf:/etc/asterisk/http.conf:ro
      - ./conf/ari.conf:/etc/asterisk/ari.conf:ro
      - ./prompts:/var/lib/asterisk/sounds/custom:ro
      - spool:/var/spool/asterisk
      - logs:/var/log/asterisk
    healthcheck:
      test: ["CMD", "asterisk", "-rx", "core show uptime"]
      interval: 30s
      timeout: 5s
      retries: 3

networks:
  voip:
    name: arb-ast-net
    internal: true
    ipam:
      config:
        - subnet: 10.88.0.0/24

volumes:
  spool:
  logs:
```

في الإعداد أعلاه لاحظ التالي:

- ال Volume المسمى `spool` يحفظ البريد الصوتي والتسجيلات وقاعدة AstDB، وهي أهم بيانات المقسم بعد ملفات الإعداد، وسوف نشرح سبب وضع AstDB فيه بعد قليل.
- الشبكة `internal: true` لا طريق لها إلى خارج الجهاز، والهواتف في التجربة Containers على الشبكة نفسها، وهذا مناسب للتجربة فقط. أما في الإنتاج فتحتاج الهواتف أن تصل إلى المنفذ 5060 ونطاق RTP كاملاً، ونشر آلاف منافذ UDP بخيار `ports` في Docker بطيء ويستهلك الذاكرة، لذلك الشائع على سيرفر Linux هو `network_mode: host`، وراجع [شبكات Docker وأفضل الممارسات](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/) قبل أن تقرر.
- فحص الصحة Health Check يسأل Asterisk نفسه عن مدة تشغيله، فإذا توقف عن الرد يظهر ال Container بحالة `unhealthy`.

والملف `conf/asterisk.conf` يحدد المجلدات، والسطر المهم فيه هو `astdbdir`:

```ini
[directories]
astcachedir => /var/cache/asterisk
astetcdir => /etc/asterisk
astmoddir => /usr/lib/asterisk/modules
astvarlibdir => /var/lib/asterisk
; AstDB (registrations, call forward, DND...) lives on the spool volume so it survives a new container
astdbdir => /var/spool/asterisk
astkeydir => /var/lib/asterisk
astdatadir => /var/lib/asterisk
astagidir => /var/lib/asterisk/agi-bin
astspooldir => /var/spool/asterisk
astrundir => /var/run/asterisk
astlogdir => /var/log/asterisk
astsbindir => /usr/sbin

[options]
documentation_language = en_US
```

AstDB قاعدة SQLite صغيرة يحفظ فيها Asterisk ما يتغير أثناء التشغيل، ومنها عناوين الهواتف المسجلة. وفي البداية تركناها في مكانها الافتراضي `/var/lib/asterisk` داخل ال Container، ثم أعدنا إنشاء ال Container بالأمر `docker compose up -d` بعد تعديل بسيط، فاتصلت Alice ب Bob وكانت النتيجة كما يلي:

```text
[2026-10-20 08:38:05] ERROR[36]: res_pjsip.c:991 ast_sip_create_dialog_uac: Endpoint '1002': Could not create dialog to invalid URI '1002'.  Is endpoint registered and reachable?
[2026-10-20 08:38:05] NOTICE[125][C-00000002]: app_dial.c:2739 dial_exec_full: Unable to create channel of type 'PJSIP' (cause 3 - No route to destination)
    -- Executing [1002@office:4] NoOp("PJSIP/1001-00000002", "DIALSTATUS=CHANUNAVAIL") in new stack
```

والسبب أن ال Container الجديد بدأ بقاعدة فارغة فنسي أن Bob مسجل، وهاتف Bob لن يسجل من جديد قبل انتهاء مدة تسجيله، وهي نحو ساعة، فكل مكالمة إليه خلال هذه الساعة تفشل. وعندما نقلنا القاعدة إلى ال Volume بقي تسجيل Bob بعد إعادة إنشاء ال Container. ولاحظ أن الملف النموذجي يكتب القسم `[directories](!)`، وقد حذفنا `(!)` لأنها تجعل القسم قالباً Template، وفي تجربتنا تجاهل Asterisk القيمة الجديدة لـ `astdbdir` وبقي على المسار القديم حتى حذفناها، ويمكنك أن تتأكد من المسار بالأمر:

```bash
docker compose up -d
docker exec arb-ast-pbx asterisk -rx 'core show settings' | grep ASTDB
docker compose ps --format '{{.Name}} {{.Status}}'
```

والمخرج سوف يكون كما يلي:

```text
  ASTDB:                       /var/spool/asterisk/astdb
arb-ast-pbx Up 41 seconds (healthy)
```

## التحويلات في PJSIP بالقوالب

شرحنا في [مقال تتبع المكالمة](https://arabroot.io/articles/%D8%AA%D8%AA%D8%A8%D8%B9-%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A9-sip-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D9%88%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%81%D9%8A-%D8%A7%D8%AA%D8%AC%D8%A7%D9%87-%D9%88%D8%A7%D8%AD%D8%AF/#lab-asterisk) أن كل تحويلة في PJSIP ثلاثة أجزاء بالاسم نفسه: `endpoint` و`auth` و`aor`، والعلاقة بينها مشروحة في [وثائق PJSIP](https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res%5Fpjsip/PJSIP-Configuration-Sections-and-Relationships/?ref=arabroot.io). والمشكلة أن كل تحويلة جديدة تعني ثلاثة أقسام، ومع عشرين موظفاً يصبح الملف طويلاً وسهل الخطأ، لذلك سوف نضع لكل جزء قالباً Template ترث منه كل التحويلات. وهذا هو الملف `conf/pjsip.conf`:

```ini
[global]
type=global
user_agent=ArabRoot-PBX

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060

; Templates: every extension inherits from these three
[endpoint-office](!)
type=endpoint
context=office
disallow=all
allow=ulaw,alaw
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
dtmf_mode=rfc4733

[auth-office](!)
type=auth
auth_type=userpass

[aor-office](!)
type=aor
max_contacts=1
remove_existing=yes
qualify_frequency=30

; Extension 1001
[1001](endpoint-office)
auth=1001
aors=1001
callerid="Alice" <1001>
mailboxes=1001@office

[1001](auth-office)
username=1001
password=Vq7-alice-9f3K2m

[1001](aor-office)

; Extension 1002
[1002](endpoint-office)
auth=1002
aors=1002
callerid="Bob" <1002>
mailboxes=1002@office

[1002](auth-office)
username=1002
password=Zr4-bob-6pW8xd

[1002](aor-office)
```

في الإعداد أعلاه لاحظ التالي:

- القوالب الثلاثة تنتهي ب `(!)`، والتحويلة ترث منها بكتابة اسم القالب بين قوسين بعد اسمها، فإضافة موظف جديد تعني ثلاثة أقسام قصيرة فيها الاسم وكلمة المرور فقط.
- `context=office` هو أهم سطر في الملف، فهو يحدد في أي قسم من خطة الاتصال تبدأ مكالمات هذه التحويلة، وبالتالي ما الأرقام التي يحق لها الاتصال بها.
- خيارات NAT الثلاثة `rtp_symmetric` و`force_rport` و`rewrite_contact` مع `qualify_frequency` هي نتيجة [مقال تتبع المكالمة](https://arabroot.io/articles/%D8%AA%D8%AA%D8%A8%D8%B9-%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A9-sip-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D9%88%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%81%D9%8A-%D8%A7%D8%AA%D8%AC%D8%A7%D9%87-%D9%88%D8%A7%D8%AD%D8%AF/#final-config)، فلن نعيد شرحها.
- `dtmf_mode=rfc4733` يرسل أرقام لوحة المفاتيح DTMF كحزم RTP خاصة، وهي الطريقة التي يفهمها أغلب الهواتف، وسوف نحتاجها في الرد الآلي.
- `mailboxes` يربط التحويلة بصندوق بريدها الصوتي، فيرسل المقسم للهاتف إشعاراً عندما تصل رسالة جديدة، وهو ما يضيء لمبة الرسائل في الهاتف المكتبي.

وإذا وجدت القوالب ما زالت طويلة، فهناك طريقة أقصر اسمها [PJSIP Configuration Wizard](https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res%5Fpjsip/PJSIP-Configuration-Wizard/?ref=arabroot.io) تكتب التحويلة في قسم واحد، ولكننا اخترنا القوالب لأن ما يظهر في أوامر التشخيص يطابق ما في الملف تماماً.

بعد تشغيل المقسم نسأله عن التحويلات:

```bash
docker exec arb-ast-pbx asterisk -rx 'pjsip show endpoints'
```

والمخرج سوف يكون كما يلي، بعد حذف ترويسة الجدول:

```text
 Endpoint:  1001/1001                                            Unavailable   0 of inf
     InAuth:  1001/1001
        Aor:  1001                                               1

 Endpoint:  1002/1002                                            Unavailable   0 of inf
     InAuth:  1002/1002
        Aor:  1002                                               1

Objects found: 2
```

التحويلتان `Unavailable` لأن أي هاتف لم يسجل بعد. الآن نشغل هاتف Bob، وهو baresip في Container كما في مقال تتبع المكالمة، مع ملف `accounts` فيه اسم المستخدم وكلمة المرور:

```bash
echo "<sip:1002@10.88.0.10>;auth_pass=Zr4-bob-6pW8xd;answermode=auto" > bob/accounts
docker run -d --name arb-ast-bob --network arb-ast-net --ip 10.88.0.22 \
  -v "$PWD/bob:/conf" -v "$PWD/sounds:/sounds:ro" -v "$PWD/rec-bob:/rec" \
  arb-ast-phone:1 baresip -f /conf
docker exec arb-ast-pbx asterisk -rx 'pjsip show contacts'
```

و `arb-ast-phone:1` Image صغيرة بنيناها من `debian:12.15` فيها baresip وsox وcurl وnc وPython لتجربة الهواتف والبرامج، وملف `bob/config` هو نفسه في مقال تتبع المكالمة. والمخرج سوف يكون كما يلي:

```text
  Contact:  1002/sip:1002-0x5626c87af4b0@10.88.0.22:5060   98d49d3a4b Avail         3.075

Objects found: 1
```

التحويلة 1002 مسجلة وحالتها `Avail` مع زمن الرد بالملي ثانية. أما هاتف Alice فنشغله عند كل مكالمة بسكربت صغير يرسل له أمر `/dial` ثم الأرقام ثم `/hangup`، ولهذا تظهر 1001 غير مسجلة بين المكالمات.

## أساسيات خطة الاتصال Dialplan

خطة الاتصال في `extensions.conf` مقسمة إلى سياقات Contexts، وكل سياق فيه امتدادات Extensions، وكل امتداد فيه خطوات مرقمة اسمها الأولويات Priorities، والقاعدة كما في [وثائق خطة الاتصال](https://docs.asterisk.org/Configuration/Dialplan/Contexts-Extensions-and-Priorities/?ref=arabroot.io) أن Asterisk يبحث في سياق القناة عن امتداد يطابق الرقم المطلوب، ثم ينفذ أولوياته بالترتيب من 1\. ولنأخذ أبسط سطر:

```ini
[office]
exten => _10XX,1,NoOp(Call from ${CALLERID(num)} to ${EXTEN})
 same => n,Dial(PJSIP/${EXTEN},15)
```

- `[office]` هو السياق الذي حددناه في القالب `endpoint-office`.
- `_10XX` نمط Pattern لأنه يبدأ ب `_`، و`X` تعني أي رقم من 0 إلى 9، فالنمط يطابق من 1000 إلى 1099، والأحرف الأخرى مشروحة في [وثائق الأنماط](https://docs.asterisk.org/Configuration/Dialplan/Pattern-Matching/?ref=arabroot.io).
- `same => n` تعني الامتداد نفسه والأولوية التالية، فلا تحتاج أن ترقم الأسطر بنفسك.
- `${EXTEN}` متغير فيه الرقم المطلوب، و`${CALLERID(num)}` دالة تعيد رقم المتصل.
- `Dial(PJSIP/${EXTEN},15)` يرن على التحويلة 15 ثانية، وعندما ينتهي يضع النتيجة في المتغير `DIALSTATUS` كما في [وثائق Dial](https://docs.asterisk.org/Latest%5FAPI/API%5FDocumentation/Dialplan%5FApplications/Dial/?ref=arabroot.io)، مثل `ANSWER` و`NOANSWER` و`BUSY` و`CHANUNAVAIL`.

والصورة التالية تبين ما سوف نبنيه في هذا القسم: تسجيل المكالمة، ثم الرنين، ثم البريد الصوتي بحسب سبب عدم الرد:

![Alice تتصل بالرقم 1002، فيدخل الاتصال السياق office بالنمط _10XX، الأولوية 1 و2 للمتغيرات، ثم MixMonitor يبدأ التسجيل، ثم Dial يرن 15 ثانية، ثم DIALSTATUS يقرر: ANSWER يربط المكالمة، وNOANSWER أو CHANUNAVAIL يذهب إلى VoiceMail برسالة غير متاح، وBUSY إلى VoiceMail برسالة مشغول، وتحتها مسارات ملفات التسجيل والبريد الصوتي على القرص](https://arabroot.io/content/images/2026/10/asterisk-02-dialplan-flow.webp)

كل سطر أولوية تنفذ بالترتيب، وDIALSTATUS يحدد الطريق بعد الرنين

### تسجيل المكالمات Call Recording

التطبيق المسؤول عن التسجيل هو [MixMonitor](https://docs.asterisk.org/Latest%5FAPI/API%5FDocumentation/Dialplan%5FApplications/MixMonitor/?ref=arabroot.io)، وهو يسجل صوت الطرفين في ملف واحد في `/var/spool/asterisk/monitor`. والحل البسيط الذي يكتبه أغلب الناس أول مرة هو تشغيله قبل `Dial` مباشرة:

```ini
exten => _10XX,1,NoOp(Call from ${CALLERID(num)} to ${EXTEN})
 same => n,Set(REC=${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${CALLERID(num)}-${EXTEN})
 same => n,MixMonitor(${REC}.wav)
 same => n,Dial(PJSIP/${EXTEN},15)
```

اتصلت Alice ب Bob، وهذا ما ظهر في سجل المقسم:

```text
    -- Executing [1002@office:1] NoOp("PJSIP/1001-00000000", "Call from 1001 to 1002") in new stack
    -- Executing [1002@office:2] Set("PJSIP/1001-00000000", "REC=20261020-082152-1001-1002") in new stack
    -- Executing [1002@office:3] MixMonitor("PJSIP/1001-00000000", "20261020-082152-1001-1002.wav") in new stack
    -- Executing [1002@office:4] Dial("PJSIP/1001-00000000", "PJSIP/1002,15") in new stack
  == Begin MixMonitor Recording PJSIP/1001-00000000
    -- Called PJSIP/1002
    -- PJSIP/1002-00000001 is ringing
    -- PJSIP/1002-00000001 answered PJSIP/1001-00000000
    -- Channel PJSIP/1002-00000001 joined 'simple_bridge' basic-bridge <0801aaaf-9af8-44df-b7ce-c0b6ef682067>
    -- Channel PJSIP/1001-00000000 joined 'simple_bridge' basic-bridge <0801aaaf-9af8-44df-b7ce-c0b6ef682067>
    -- Channel PJSIP/1001-00000000 left 'simple_bridge' basic-bridge <0801aaaf-9af8-44df-b7ce-c0b6ef682067>
    -- Channel PJSIP/1002-00000001 left 'simple_bridge' basic-bridge <0801aaaf-9af8-44df-b7ce-c0b6ef682067>
  == Spawn extension (office, 1002, 4) exited non-zero on 'PJSIP/1001-00000000'
  == MixMonitor close filestream (mixed)
  == End MixMonitor Recording PJSIP/1001-00000000
```

ولاحظ هنا كيف يظهر ما رسمناه: قناتان، وجسر يدخلانه ويخرجان منه. وعند فحص الملف الناتج بالأداة `soxi` كان المخرج كما يلي:

```text
Channels       : 1
Sample Rate    : 8000
Precision      : 16-bit
Duration       : 00:00:08.08 = 64640 samples ~ 606 CDDA sectors
File Size      : 129k
```

أي 16 KB لكل ثانية، و57.6 MB لكل ساعة تسجيل، فإذا كان في مكتبك 20 موظفاً يتكلم كل منهم ساعتين في اليوم فهذا نحو 2.3 GB يومياً، لذلك خطط للمساحة وللمدة التي تحتفظ فيها بالتسجيلات قبل أن تشغل التسجيل لكل المكالمات.

ولكن هذا الحل له مشكلتان ظهرتا في التجربة نفسها: الأولى أن التسجيل يبدأ قبل الرد، فإذا لم يرد Bob سجل الملف الرنين ورسالة البريد الصوتي كاملة، والثانية أنه يسجل حتى المكالمات الفاشلة، فعندما اتصلت Alice بالرقم 1003، وهو يطابق النمط ولا توجد تحويلة بهذا الرقم، نفذ المقسم `MixMonitor` وأنشأ ملفاً لمكالمة لم تحدث. والحل الأنسب هو أن يبدأ التسجيل لحظة الرد فقط، وهذا ما يفعله الخيار `U` في `Dial`، فهو يشغل روتيناً فرعياً Subroutine على قناة الطرف الذي رد، بعد الرد وقبل ربط الطرفين:

```ini
exten => _10XX,1,NoOp(Call from ${CALLERID(num)} to ${EXTEN})
 same => n,Set(REC=${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${CALLERID(num)}-${EXTEN})
 same => n,Dial(PJSIP/${EXTEN},15,U(record-on-answer^${REC}))

; Runs on the answering channel right after answer: record only the conversation
[record-on-answer]
exten => s,1,MixMonitor(${ARG1}.wav)
 same => n,Return()
```

ثم حذفنا التسجيلات القديمة، واتصلت Alice ب Bob فرد، ثم اتصلت بالرقم 1003:

```bash
docker exec arb-ast-pbx asterisk -rx 'dialplan reload'
docker exec arb-ast-pbx ls -l /var/spool/asterisk/monitor/
```

والمخرج سوف يكون كما يلي:

```text
Dialplan reloaded.
total 128
-rw-r--r-- 1 asterisk asterisk 127084 Oct 20 08:32 20261020-083243-1001-1002.wav
```

ملف واحد للمكالمة التي تمت فقط. ويجب أن تعرف أن تسجيل المكالمات له جانب قانوني قبل الجانب التقني، فراجع الأنظمة التي تنطبق على مؤسستك، والممارسة الشائعة أن يسمع المتصل رسالة تخبره أن المكالمة قد تسجل، ويمكنك إضافتها بالملف `for-quality-purposes` الموجود في ملفات الصوت.

### البريد الصوتي Voicemail

البريد الصوتي يعرف في `conf/voicemail.conf`، وكل سطر صندوق بريد فيه رقم سري يدخله الموظف من هاتفه، والاسم، والبريد الإلكتروني الذي تصله الرسالة عليه:

```ini
[general]
format=wav
maxmsg=100
maxsecs=120
minsecs=2
maxlogins=3
attach=yes
serveremail=pbx@example.com

[office]
1001 => 4826,Alice,alice@example.com
1002 => 7391,Bob,bob@example.com
```

ثم نكمل الامتداد بعد `Dial`، فإذا كان الخط مشغولاً يسمع المتصل رسالة «مشغول»، وفي غير ذلك رسالة «غير متاح»، ونضيف الرقم `*97` ليستمع الموظف إلى بريده:

```ini
 same => n,NoOp(DIALSTATUS=${DIALSTATUS})
 same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail)
 same => n(unavail),VoiceMail(${EXTEN}@office,u)
 same => n,Hangup()
 same => n(busy),VoiceMail(${EXTEN}@office,b)
 same => n,Hangup()

; Listen to your own voicemail
exten => *97,1,VoiceMailMain(${CALLERID(num)}@office)
 same => n,Hangup()
```

في الإعداد أعلاه لاحظ التالي:

- `GotoIf` يقفز إلى العنوان `busy` أو `unavail` بحسب الشرط، والعناوين Labels هي الأسماء بين القوسين بعد `n`.
- الخياران `u` و`b` في [VoiceMail](https://docs.asterisk.org/Latest%5FAPI/API%5FDocumentation/Dialplan%5FApplications/VoiceMail/?ref=arabroot.io) يختاران رسالة الترحيب، و[VoiceMailMain](https://docs.asterisk.org/Latest%5FAPI/API%5FDocumentation/Dialplan%5FApplications/VoiceMailMain/?ref=arabroot.io) هو قائمة الاستماع إلى الرسائل.

جعلنا هاتف Bob لا يرد تلقائياً، واتصلت Alice وأغلقت بعد 30 ثانية، فكان المخرج كما يلي:

```text
    -- Executing [1002@office:5] NoOp("PJSIP/1001-00000002", "DIALSTATUS=NOANSWER") in new stack
    -- Executing [1002@office:6] GotoIf("PJSIP/1001-00000002", "0?busy:unavail") in new stack
    -- Executing [1002@office:7] VoiceMail("PJSIP/1001-00000002", "1002@office,u") in new stack
    -- Recording the message
    -- Recording was 1 seconds long but needs to be at least 2 - abandoning
```

لم تحفظ أي رسالة. والسبب أن المتصل بعد 15 ثانية من الرنين يسمع رسالة الترحيب كاملة قبل الصافرة، وقد أخذت هنا نحو 13 ثانية، فلم يبق للكلام إلا ثانية واحدة، وهي أقل من `minsecs=2`، فرفضها Asterisk. وهذا مهم عندما تختار مدة الرنين، فالمتصل الذي ينتظر 30 ثانية ثم يسمع رسالة طويلة يغلق غالباً قبل الصافرة. أعدنا المكالمة لمدة 40 ثانية، ثم نظرنا في صندوق Bob:

```bash
docker exec arb-ast-pbx ls -l /var/spool/asterisk/voicemail/office/1002/INBOX/
docker exec arb-ast-pbx asterisk -rx 'voicemail show users'
```

والمخرج سوف يكون كما يلي:

```text
-rw-rw-rw- 1 asterisk asterisk    280 Oct 20 08:24 msg0000.txt
-rw-r--r-- 1 asterisk asterisk 179884 Oct 20 08:24 msg0000.wav
Context    Mbox  User                      Zone       NewMsg
office     1001  Alice                                     0
office     1002  Bob                                       1
2 voicemail users configured.
```

والملف `msg0000.txt` يحفظ معلومات الرسالة، ومنها `callerid="Alice" <1001>` و`duration=11`. أما إرسال الرسالة بالبريد الإلكتروني فقد فشل، وهذا السطر من سجل المقسم يبين السبب:

```text
/bin/sh: 1: /usr/sbin/sendmail: not found
```

Asterisk لا يرسل البريد بنفسه، وإنما يستدعي البرنامج `/usr/sbin/sendmail`، فإذا أردت إرسال الرسائل فأضف إلى ال Image برنامجاً مثل msmtp يرسل عبر سيرفر بريد موجود، وقد شرحنا الفرق بين الإرسال المباشر والترحيل في [الفرق بين SMTP والترحيل Relay](https://arabroot.io/articles/%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-smtp-%D9%88%D8%A7%D9%84%D8%AA%D8%B1%D8%AD%D9%8A%D9%84-relay/).

بعد ذلك اتصلت Alice ب `*97` وأدخلت رقمها السري `4826#`:

```text
    -- Executing [*97@office:1] VoiceMailMain("PJSIP/1001-0000000d", "1001@office") in new stack
    -- <PJSIP/1001-0000000d> Playing 'vm-password.ulaw' (language 'en')
    -- <PJSIP/1001-0000000d> Playing 'vm-youhave.ulaw' (language 'en')
    -- <PJSIP/1001-0000000d> Playing 'digits/1.ulaw' (language 'en')
    -- <PJSIP/1001-0000000d> Playing 'vm-INBOX.ulaw' (language 'en')
    -- <PJSIP/1001-0000000d> Playing 'vm-message.ulaw' (language 'en')
```

ومع رقم سري خاطئ يسمع المتصل `vm-incorrect` ثم يطلب منه الرقم مرة أخرى، وبعد `maxlogins=3` محاولات يغلق المقسم الخط. ولاحظ أن الرقم السري المكون من أربعة أرقام ضعيف، وصندوق البريد الذي يمكن الوصول إليه من خارج المكتب كان باباً معروفاً للاحتيال، لذلك لا تقم بالسماح ل `VoiceMailMain` من الخطوط الخارجية إلا بحاجة واضحة.

## الرد الآلي IVR

الرد الآلي Interactive Voice Response، واختصاراً IVR، هو القائمة التي يسمعها المتصل بالرقم الرئيسي: «للمبيعات اضغط 1، وللدعم الفني اضغط 2». والصورة التالية تبين القائمة التي سوف نبنيها:

![متصل يطلب الرقم 500، فيتحقق GotoIfTime من وقت الدوام من الأحد إلى الخميس، فإذا كان خارج الدوام يسمع رسالة الإغلاق ثم يذهب إلى البريد الصوتي 1001، وإذا كان داخل الدوام يسمع القائمة عبر BackGround ثم ينتظر WaitExten خمس ثوان، والرقم 1 يذهب إلى 1001 للمبيعات و2 إلى 1002 للدعم و0 إلى 1001 للموظف، والامتداد i عند الاختيار غير الموجود وt عند انتهاء الوقت يعيدان القائمة ما دام عدد المحاولات أقل من ثلاث](https://arabroot.io/content/images/2026/10/asterisk-03-ivr.webp)

وقت الدوام أولاً، ثم القائمة، ثم الاختيار أو إعادة المحاولة

وهذا هو الإعداد كاملاً، أضفناه إلى `extensions.conf`:

```ini
[globals]
OFFICE_HOURS=08:00-16:59
OFFICE_DAYS=sun-thu
OFFICE_TZ=Asia/Riyadh

[office]
; The main menu (IVR)
exten => 500,1,Goto(ivr-main,s,1)

[ivr-main]
exten => s,1,Answer()
 same => n,Set(TRIES=0)
 same => n,GotoIfTime(${OFFICE_HOURS},${OFFICE_DAYS},*,*,${OFFICE_TZ}?menu)
 same => n,Playback(closed)
 same => n,VoiceMail(1001@office,u)
 same => n,Hangup()
 same => n(menu),Set(TRIES=$[${TRIES} + 1])
 same => n,Background(custom/welcome-ar&for-sales&press-1&for-tech-support&press-2&to-reach-operator&press-0)
 same => n,WaitExten(5)

exten => 1,1,NoOp(IVR choice 1: sales)
 same => n,Goto(office,1001,1)
exten => 2,1,NoOp(IVR choice 2: support)
 same => n,Goto(office,1002,1)
exten => 0,1,Goto(office,1001,1)

exten => i,1,Playback(option-is-invalid)
 same => n,GotoIf($[${TRIES} >= 3]?office,1001,1:s,menu)
exten => t,1,GotoIf($[${TRIES} >= 3]?office,1001,1:s,menu)
```

في الإعداد أعلاه لاحظ التالي:

- [BackGround](https://docs.asterisk.org/Latest%5FAPI/API%5FDocumentation/Dialplan%5FApplications/BackGround/?ref=arabroot.io) يشغل الرسالة ويستمع للأرقام في الوقت نفسه، فالمتصل الذي يعرف الرقم يضغط دون أن ينتظر نهاية القائمة، أما `Playback` فيجبره على الانتظار، لذلك استخدم `Playback` للرسائل التي يجب أن تسمع كاملة فقط.
- [WaitExten](https://docs.asterisk.org/Latest%5FAPI/API%5FDocumentation/Dialplan%5FApplications/WaitExten/?ref=arabroot.io) ينتظر خمس ثوان بعد انتهاء القائمة، والامتدادان `i` و`t` اسمان خاصان يقفز إليهما Asterisk عند ضغط رقم لا يوجد له امتداد، أو عند انتهاء الوقت دون ضغط شيء.
- العداد `TRIES` يمنع المتصل من الدوران في القائمة إلى الأبد، فبعد المحاولة الثالثة يحول إلى الموظف، والمتصل الذي لا يجيد استخدام القائمة يجد إنساناً يرد عليه.
- [GotoIfTime](https://docs.asterisk.org/Latest%5FAPI/API%5FDocumentation/Dialplan%5FApplications/GotoIfTime/?ref=arabroot.io) يأخذ الوقت ثم الأيام ثم أيام الشهر ثم الأشهر ثم المنطقة الزمنية، ووضعنا القيم في `[globals]` حتى تغيرها في مكان واحد.

والمنطقة الزمنية في الإعداد ليست زينة، فال Container يعمل بتوقيت UTC، كما يظهر من الأمر `docker exec arb-ast-pbx date` الذي أعاد `Tue Oct 20 08:26:12 UTC 2026`، وبدون `Asia/Riyadh` سوف يبدأ دوامك في الرد الآلي عند الحادية عشرة صباحاً بتوقيت الرياض وينتهي في الثامنة مساءً. اتصلت Alice بالرقم 500 وضغطت 2 أثناء القائمة:

```text
    -- Executing [s@ivr-main:3] GotoIfTime("PJSIP/1001-00000006", "08:00-16:59,sun-thu,*,*?menu") in new stack
    -- Executing [s@ivr-main:7] Set("PJSIP/1001-00000006", "TRIES=1") in new stack
    -- <PJSIP/1001-00000006> Playing 'for-sales.ulaw' (language 'en')
    -- <PJSIP/1001-00000006> Playing 'press-1.ulaw' (language 'en')
    -- <PJSIP/1001-00000006> Playing 'for-tech-support.ulaw' (language 'en')
    -- <PJSIP/1001-00000006> Playing 'press-2.ulaw' (language 'en')
    -- <PJSIP/1001-00000006> Playing 'to-reach-operator.ulaw' (language 'en')
    -- Executing [2@ivr-main:1] NoOp("PJSIP/1001-00000006", "IVR choice 2: support") in new stack
    -- Executing [2@ivr-main:2] Goto("PJSIP/1001-00000006", "office,1002,1") in new stack
    -- Executing [1002@office:4] Dial("PJSIP/1001-00000006", "PJSIP/1002,15") in new stack
    -- PJSIP/1002-00000007 answered PJSIP/1001-00000006
```

ولاحظ أن الرقم 2 قطع الرسالة `to-reach-operator` قبل نهايتها، وهذا هو الفرق بين `BackGround` و`Playback`. وهذه المحاولة كانت قبل أن نضيف المنطقة الزمنية، ثم كررنا المكالمة وضغطت Alice الرقم 7 ثم لم تضغط شيئاً:

```text
    -- Executing [s@ivr-main:7] Set("PJSIP/1001-00000008", "TRIES=1") in new stack
    -- Executing [i@ivr-main:1] Playback("PJSIP/1001-00000008", "option-is-invalid") in new stack
    -- Executing [i@ivr-main:2] GotoIf("PJSIP/1001-00000008", "0?office,1001,1:s,menu") in new stack
    -- Executing [s@ivr-main:7] Set("PJSIP/1001-00000008", "TRIES=2") in new stack
    -- Executing [s@ivr-main:9] WaitExten("PJSIP/1001-00000008", "5") in new stack
    -- Executing [t@ivr-main:1] GotoIf("PJSIP/1001-00000008", "0?office,1001,1:s,menu") in new stack
    -- Executing [s@ivr-main:7] Set("PJSIP/1001-00000008", "TRIES=3") in new stack
    -- Executing [s@ivr-main:9] WaitExten("PJSIP/1001-00000008", "5") in new stack
    -- Executing [t@ivr-main:1] GotoIf("PJSIP/1001-00000008", "1?office,1001,1:s,menu") in new stack
    -- Executing [1001@office:1] NoOp("PJSIP/1001-00000008", "Call from 1001 to 1001") in new stack
```

الاختيار الخاطئ ذهب إلى `i`، ثم انتهى الوقت مرتين فذهب إلى `t`، وفي المحاولة الثالثة تحقق الشرط فتحولت المكالمة إلى 1001.

وقد يتساءل البعض: كيف أختبر فرع «خارج الدوام» دون أن أنتظر إلى المساء؟ والإجابة أن المتغيرات في `[globals]` يمكن تغييرها من سطر الأوامر دون تعديل الملف، ثم تعود إلى قيمتها عند إعادة التحميل:

```bash
docker exec arb-ast-pbx asterisk -rx 'dialplan set global OFFICE_HOURS 00:00-00:01'
docker exec arb-ast-pbx asterisk -rx 'dialplan reload'
```

وبين الأمرين اتصلت Alice بالرقم 500، فكان المخرج كما يلي:

```text
    -- Global variable 'OFFICE_HOURS' set to '00:00-00:01'
    -- Executing [s@ivr-main:3] GotoIfTime("PJSIP/1001-0000000a", "00:00-00:01,sun-thu,*,*,Asia/Riyadh?menu") in new stack
    -- Executing [s@ivr-main:4] Playback("PJSIP/1001-0000000a", "closed") in new stack
    -- <PJSIP/1001-0000000a> Playing 'closed.ulaw' (language 'en')
    -- Executing [s@ivr-main:5] VoiceMail("PJSIP/1001-0000000a", "1001@office,u") in new stack
```

### رسائلك الصوتية بالعربية

ملفات الصوت التي تأتي مع Asterisk بالإنجليزية، ولذلك في الواقع سوف تسجل رسائلك بنفسك، والشرط الوحيد أن يكون الملف بصيغة يقرؤها Asterisk دون تحويل في كل مكالمة، والأبسط WAV بتردد 8000 Hz وقناة واحدة. سجل الرسالة بأي برنامج، ثم حولها بالأداة sox:

```bash
mkdir -p prompts
sox raw-recording.wav -r 8000 -c 1 -b 16 prompts/welcome-ar.wav
soxi prompts/welcome-ar.wav | grep -E "Channels|Sample Rate|Duration"
```

والمخرج سوف يكون كما يلي:

```text
Channels       : 1
Sample Rate    : 8000
Duration       : 00:00:03.00 = 24000 samples ~ 225 CDDA sectors
```

والمجلد `prompts` مربوط في ملف Compose على `/var/lib/asterisk/sounds/custom`، لذلك تكتب في خطة الاتصال `custom/welcome-ar` دون الامتداد، كما في القائمة أعلاه، وعند الاتصال يظهر في السجل:

```text
    -- <PJSIP/1001-00000000> Playing 'custom/welcome-ar.slin' (language 'en')
```

## AMI: برامجك تراقب المكالمات وتبدأها

واجهة [Asterisk Manager Interface](https://docs.asterisk.org/Configuration/Interfaces/Asterisk-Manager-Interface-AMI/?ref=arabroot.io)، واختصاراً AMI، اتصال TCP على المنفذ 5038، يرسل منه Asterisk الأحداث Events لحظة حدوثها، مثل قناة جديدة أو رنين أو رد، ويستقبل منه الأوامر Actions مثل بدء مكالمة. وهذه هي الطريقة التقليدية التي تربط بها لوحات المراقبة وأنظمة CRM بالمقسم، كأن تظهر بطاقة العميل لموظف الدعم عندما يرن هاتفه. والإعداد في `conf/manager.conf`:

```ini
[general]
enabled=yes
port=5038
bindaddr=0.0.0.0

[crm]
secret=Hm2-ami-8tR5qL
deny=0.0.0.0/0.0.0.0
permit=10.88.0.0/255.255.255.0
read=call,reporting
write=originate
```

في الإعداد أعلاه لاحظ التالي:

- `deny` ثم `permit` يسمحان بالدخول من شبكة التجربة فقط، فحتى لو تسرب ال secret لا يقبله المقسم من عنوان آخر.
- `read` و`write` صلاحيات بالفئات Classes، وقد أعطينا المستخدم `crm` قراءة أحداث المكالمات وبدء المكالمات فقط، وليس صلاحية `system` التي تسمح بإيقاف المقسم أو `command` التي تنفذ أي أمر CLI.
- AMI لا يشفر الاتصال بشكل افتراضي، وال secret يمر نصاً واضحاً، لذلك لا تقم بفتح المنفذ 5038 على الإنترنت أبداً، واجعل البرنامج الذي يستخدمه على السيرفر نفسه أو في شبكة خاصة.

البروتوكول نصي بسيط كما في [وثائق بروتوكول AMI](https://docs.asterisk.org/Configuration/Interfaces/Asterisk-Manager-Interface-AMI/The-Asterisk-Manager-TCP-IP-API/?ref=arabroot.io): أسطر `Key: Value` تنتهي بسطر فارغ، لذلك يكفي `nc` لتجربته. السكربت `ami-test.sh` التالي يسجل الدخول، ثم يطلب قائمة التحويلات، ثم يبدأ مكالمة:

```bash
cat > ami-test.sh <<'EOF'
#!/bin/sh
# Logs in to AMI, asks for the endpoint list, starts a call, and prints what comes back for 12 seconds
{
  printf 'Action: Login\r\nUsername: crm\r\nSecret: Hm2-ami-8tR5qL\r\n\r\n'
  sleep 1
  printf 'Action: PJSIPShowEndpoints\r\nActionID: list-1\r\n\r\n'
  sleep 1
  printf 'Action: Originate\r\nActionID: call-1\r\nChannel: PJSIP/1002\r\nContext: office\r\nExten: 500\r\nPriority: 1\r\nCallerID: "CRM" <1002>\r\nAsync: true\r\n\r\n'
  sleep 12
  printf 'Action: Logoff\r\n\r\n'
} | nc 10.88.0.10 5038
EOF
docker run --rm --network arb-ast-net -v "$PWD/ami-test.sh:/ami-test.sh:ro" arb-ast-phone:1 sh /ami-test.sh | tr -d '\r'
```

والمخرج سوف يكون كما يلي، بعد أن اختصرناه إلى البداية وبعض الأحداث:

```text
Asterisk Call Manager/11.0.0
Response: Success
Message: Authentication accepted

Response: Error
ActionID: list-1
Message: Permission denied

Response: Success
ActionID: call-1
Message: Originate successfully queued

Event: DialEnd
Privilege: call,all
DestChannel: PJSIP/1002-0000000f
DestChannelState: 6
DestChannelStateDesc: Up
DestCallerIDNum: 1002
DestCallerIDName: CRM
DestContext: office
DestExten: s
DestUniqueid: 1792485002.15
DialStatus: ANSWER

Event: OriginateResponse
Privilege: call,all
ActionID: call-1
Response: Success
Channel: PJSIP/1002-0000000f
Context: office
Exten: 500
Reason: 4
Uniqueid: 1792485002.15
```

في المخرج أعلاه لاحظ التالي:

- طلب قائمة التحويلات رفض ب `Permission denied`، لأنه يحتاج صلاحية لم نعطها لهذا المستخدم، وهذا بالضبط ما نريده: كل برنامج يأخذ ما يحتاجه فقط، وإذا احتجت القائمة فأضف الفئة المطلوبة لهذا المستخدم وحده.
- `Originate` رن هاتف Bob أولاً، وعندما رد نقل المكالمة إلى الامتداد 500 في السياق `office`. وهذه هي فكرة «الاتصال بضغطة زر» Click to Call في أنظمة CRM: يرن هاتف الموظف، ثم يتصل المقسم بالرقم المطلوب.
- كل حدث يحمل `Uniqueid`، وهو ما يربط به برنامجك الأحداث الكثيرة للمكالمة نفسها، وقد وصلت خلال 12 ثانية أحداث كثيرة منها تقارير RTCP، لذلك اختر الفئات التي تحتاجها، وإلا امتلأ برنامجك بأحداث لا يستخدمها.

## ARI: برنامجك يتحكم بالمكالمة نفسها

AMI يراقب ويبدأ المكالمات، ولكن خطة الاتصال تبقى هي التي تقرر ما يحدث داخل المكالمة. أما [Asterisk REST Interface](https://docs.asterisk.org/Configuration/Interfaces/Asterisk-REST-Interface-ARI/?ref=arabroot.io)، واختصاراً ARI، فيسلم المكالمة كاملة لبرنامجك: خطة الاتصال ترسلها إلى التطبيق [Stasis](https://docs.asterisk.org/Latest%5FAPI/API%5FDocumentation/Dialplan%5FApplications/Stasis/?ref=arabroot.io)، فيصل الحدث إلى برنامجك عبر WebSocket، ثم يرد برنامجك ويشغل الرسائل ويربط القنوات بطلبات HTTP عادية. وهذه هي الطريقة الحديثة لبناء أنظمة مثل الرد الآلي المرتبط بقاعدة بيانات أو المساعد الصوتي. ونحتاج تشغيل سيرفر HTTP المدمج وتعريف مستخدم:

```ini
; conf/http.conf
[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088

; conf/ari.conf
[general]
enabled=yes

[app]
type=user
read_only=no
password=Kc9-ari-3vN7pD
```

ثم نضيف إلى السياق `office` امتداداً يسلم المكالمة للتطبيق `hello-ari`:

```ini
; An ARI application takes the call
exten => 600,1,Stasis(hello-ari)
 same => n,Hangup()
```

وقبل كتابة أي برنامج، نجرب ARI ب curl من Container على الشبكة نفسها:

```bash
docker run --rm --network arb-ast-net arb-ast-phone:1 sh -c '
  curl -s -u app:Kc9-ari-3vN7pD http://10.88.0.10:8088/ari/endpoints; echo
  curl -s -o /dev/null -w "%{http_code}\n" -u app:wrong http://10.88.0.10:8088/ari/endpoints'
```

والمخرج سوف يكون كما يلي:

```text
[{"technology":"PJSIP","resource":"1001","state":"offline","channel_ids":[]},{"technology":"PJSIP","resource":"1002","state":"online","channel_ids":[]}]
401
```

والآن البرنامج نفسه، وهو بلغة Python ولا يحتاج إلا المكتبة `websockets`: يتصل بالأحداث، وعندما تدخل مكالمة يرد عليها ويشغل رسالة، وعندما تنتهي الرسالة يغلق الخط:

```python
# A minimal ARI application: answers every call that enters Stasis(hello-ari),
# plays one prompt, then hangs up. Uses only the Python standard library + websockets.
import asyncio, base64, json, urllib.request
import websockets

ARI = "http://10.88.0.10:8088/ari"
USER, PASSWORD, APP = "app", "Kc9-ari-3vN7pD", "hello-ari"
AUTH = "Basic " + base64.b64encode(f"{USER}:{PASSWORD}".encode()).decode()

def rest(method, path):
    req = urllib.request.Request(ARI + path, method=method, headers={"Authorization": AUTH})
    with urllib.request.urlopen(req) as r:
        body = r.read()
        print(f"  {method} {path} -> {r.status}")
        return json.loads(body) if body else None

async def main():
    url = f"ws://10.88.0.10:8088/ari/events?app={APP}&api_key={USER}:{PASSWORD}"
    async with websockets.connect(url) as ws:
        print("connected, waiting for calls")
        async for msg in ws:
            ev = json.loads(msg)
            kind = ev["type"]
            if kind == "StasisStart":
                ch = ev["channel"]
                print(f"{kind}: {ch['name']} from {ch['caller']['number']} to {ch['dialplan']['exten']}")
                rest("POST", f"/channels/{ch['id']}/answer")
                rest("POST", f"/channels/{ch['id']}/play?media=sound:tt-monkeys")
            elif kind == "PlaybackFinished":
                chan = ev["playback"]["target_uri"].split(":", 1)[1]
                print(f"{kind}: {ev['playback']['media_uri']}")
                rest("DELETE", f"/channels/{chan}")
            elif kind == "StasisEnd":
                print(f"{kind}: {ev['channel']['name']}")
                return

asyncio.run(main())
```

شغلنا البرنامج في Container على الشبكة نفسها، ثم اتصلت Alice بالرقم 600:

```bash
docker run -d --name arb-ast-ari --network arb-ast-net \
  -v "$PWD/ari-hello.py:/ari-hello.py:ro" arb-ast-phone:1 python3 -u /ari-hello.py
docker logs arb-ast-ari
```

والمخرج سوف يكون كما يلي:

```text
connected, waiting for calls
StasisStart: PJSIP/1001-00000010 from 1001 to 600
  POST /channels/1792485046.16/answer -> 204
  POST /channels/1792485046.16/play?media=sound:tt-monkeys -> 201
PlaybackFinished: sound:tt-monkeys
  DELETE /channels/1792485046.16 -> 204
StasisEnd: PJSIP/1001-00000010
```

ولاحظ أن خطة الاتصال لم تفعل شيئاً إلا تسليم المكالمة، وكل ما بعد ذلك قرره البرنامج. ثم أوقفنا البرنامج واتصلنا بالرقم 600 مرة أخرى، وهذا ما يحدث عندما يتوقف برنامجك:

```text
    -- Executing [600@office:1] Stasis("PJSIP/1001-00000011", "hello-ari") in new stack
[2026-10-20 08:31:26] WARNING[492][C-0000000d]: app_stasis.c:129 app_exec: PJSIP/1001-00000011: Stasis app 'hello-ari' doesn't exist
    -- Executing [600@office:2] Hangup("PJSIP/1001-00000011", "") in new stack
```

التطبيق غير موجود فأغلق المقسم الخط مباشرة، لذلك إذا بنيت شيئاً مهماً على ARI فاجعل في خطة الاتصال بعد `Stasis` مساراً احتياطياً، كتحويل المكالمة إلى موظف، بدل `Hangup`. وانتبه أيضاً إلى أن `http.conf` في تجربتنا يستمع على كل العناوين، والأمر `http show status` يبين أن السيرفر نفسه يقدم أيضاً `/metrics` و`/phoneprov` و`/ws`، لذلك اجعل `bindaddr=127.0.0.1` في الإنتاج إذا كان برنامجك على السيرفر نفسه، أو ضعه خلف Reverse Proxy بشهادة TLS.

## التشخيص من سطر الأوامر CLI

كل ما سبق شخصناه بسطر الأوامر، وهو أول مكان تنظر فيه عند أي مشكلة، وتفاصيله في [وثائق CLI](https://docs.asterisk.org/Operation/Asterisk-Command-Line-Interface/?ref=arabroot.io). ويمكنك الدخول إليه تفاعلياً بالأمر `docker exec -it arb-ast-pbx asterisk -rvvv`، أو تنفيذ أمر واحد ب `asterisk -rx`. وهذه أهم الأوامر:

| السؤال                            | الأمر                                                        |
| --------------------------------- | ------------------------------------------------------------ |
| هل التحويلة مسجلة؟ ومن أي عنوان؟  | pjsip show contacts وpjsip show endpoint 1002                |
| ما المكالمات القائمة الآن؟        | core show channels                                           |
| هل يصل الصوت؟ وكم حزمة ضاعت؟      | pjsip show channelstats                                      |
| ماذا فهم Asterisk من خطة الاتصال؟ | dialplan show 500@office                                     |
| ما رسائل SIP الفعلية؟             | pjsip set logger on ثم off                                   |
| هل طبق التعديل؟                   | dialplan reload أو module reload res\_pjsip.so ثم اقرأ السجل |
| ما الإصدار والمسارات؟             | core show version وcore show settings                        |

والمثال التالي أثناء مكالمة بين Alice وBob:

```bash
docker exec arb-ast-pbx asterisk -rx 'core show channels'
docker exec arb-ast-pbx asterisk -rx 'pjsip show channelstats'
```

والمخرج سوف يكون كما يلي، بعد حذف بعض أسطر الترويسة:

```text
Channel                                                          Location                         State   Application(Data)
PJSIP/1002-00000016                                              (None)                           Up      AppDial((Outgoing Line))
PJSIP/1001-00000015                                              1002@office:3                    Up      Dial(PJSIP/1002,15,U(record-on
2 active channels
1 active call
16 calls processed

 BridgeId ChannelId ........ UpTime.. Codec.   Count    Lost Pct  Jitter   Count    Lost Pct  Jitter RTT....
 4fae1145 1001-00000015      00:00:07 ulaw      316       0    0   0.002    319       0    0   0.004   0.000
 4fae1145 1002-00000016      00:00:07 ulaw      319       0    0   0.002    316       0    0   0.004   0.000
```

### خطأ شائع: reload ينجح والتعديل لم يطبق

لنكسر الإعداد عمداً ونرى ما يحدث، فنكتب في القالب `rtp_symetric=yes` بحرف m واحد، ثم نعيد تحميل PJSIP:

```bash
docker exec arb-ast-pbx asterisk -rx 'module reload res_pjsip.so'
docker logs arb-ast-pbx --since 5s
docker exec arb-ast-pbx asterisk -rx 'pjsip show endpoints'
```

والمخرج سوف يكون كما يلي، بعد الاختصار:

```text
Module 'res_pjsip.so' reloaded successfully.
[2026-10-20 08:34:03] ERROR[35]: config_options.c:780 aco_process_var: Could not find option suitable for category '1001' named 'rtp_symetric' at line 17 of
[2026-10-20 08:34:03] ERROR[35]: res_sorcery_config.c:422 sorcery_config_internal_load: Could not create an object of type 'endpoint' with id '1001' from configuration file 'pjsip.conf'
[2026-10-20 08:34:03] ERROR[35]: config_options.c:780 aco_process_var: Could not find option suitable for category '1002' named 'rtp_symetric' at line 17 of
[2026-10-20 08:34:03] ERROR[35]: res_sorcery_config.c:422 sorcery_config_internal_load: Could not create an object of type 'endpoint' with id '1002' from configuration file 'pjsip.conf'

Objects found: 2
```

الأمر يقول «reloaded successfully»، والتحويلتان ما زالتا موجودتين لأن Asterisk أبقى النسخة القديمة منهما عندما فشل في إنشاء الجديدة، فإذا لم تقرأ السجل فسوف تظن أن تعديلك طبق. والأخطر ما يحدث عند إعادة تشغيل ال Container والخطأ ما زال في الملف:

```bash
docker restart arb-ast-pbx
docker exec arb-ast-pbx asterisk -rx 'pjsip show endpoints'
```

```text
No objects found.
```

كل التحويلات اختفت، لأن الخطأ في القالب المشترك، وهذا عطل كامل للمكتب بسبب حرف واحد، ولن تراه إلا بعد أول إعادة تشغيل، ربما بعد أسابيع من التعديل. لذلك اقرأ السجل بعد كل reload، واحفظ ملفات الإعداد في Git حتى تعرف ماذا تغير ومتى.

### رسائل تراها كثيراً

- رقم لا يطابق أي امتداد: `Call (UDP:10.88.0.21:5060) to extension '2000' rejected because extension not found in context 'office'`، والسبب إما خطأ في الرقم أو أن التحويلة في سياق آخر.
- رقم يطابق النمط ولا توجد تحويلة به: `Unable to create PJSIP channel - endpoint '1003' was not found` ثم `No entry in voicemail config file for '1003'`، فالنمط `_10XX` أوسع من تحويلاتك، وفي الإنتاج اجعل الأنماط على قدر الأرقام الموجودة.
- كلمة مرور خاطئة من هاتف: `Request 'REGISTER' from '<sip:1001@10.88.0.10>' failed for '10.88.0.23:5060' (callid: 52bb871e25547b5b) - Failed to authenticate`، وهذا السطر في الملف `messages.log` هو ما تقرؤه أدوات مثل fail2ban لحظر من يجرب كلمات المرور، وسوف نبنيه في مقال تأمين المقسم.
- عند بدء التشغيل ترى أخطاء مثل `cdr_custom declined to load`، وهي من وحدات لا يوجد لها إعداد في الملفات النموذجية، ولا تؤثر على ما بنيناه.

## التحقق من النجاح

- `docker compose ps` يظهر ال Container بحالة `healthy`.
- `pjsip show contacts` يظهر كل هاتف بحالة `Avail`.
- مكالمة بين تحويلتين يسمع فيها الطرفان، و`pjsip show channelstats` بلا فقد يذكر.
- مكالمة دون رد تنتهي برسالة في `voicemail show users`، ومكالمة مع رد تنتج ملفاً واحداً في `/var/spool/asterisk/monitor`.
- الرقم 500 يشغل القائمة، والأرقام 1 و2 و0 تحول إلى التحويلات الصحيحة.
- بعد `docker compose up -d --force-recreate` تبقى الهواتف مسجلة والرسائل موجودة.

## النسخ الاحتياطي والاستعادة

في هذا الإعداد هناك ثلاثة أشياء تحتاج نسخاً احتياطياً:

- ملفات الإعداد في `conf` والرسائل في `prompts` وملف Compose وال Dockerfile: هذه نصوص، ومكانها الطبيعي مستودع Git خاص، مع إخراج كلمات المرور منها إلى ملف لا يرفع.
- ال Volume المسمى `spool`: فيه البريد الصوتي والتسجيلات وقاعدة AstDB.
- ال Volume المسمى `logs`: مفيد للتحقيق بعد حادثة، وأقل أهمية من غيره.

والأمر التالي ينسخ ال Volume إلى ملف مضغوط دون إيقاف المقسم، ثم يعرض بعض ما فيه:

```bash
mkdir -p backup
docker run --rm --volumes-from arb-ast-pbx -v "$PWD/backup:/backup" debian:12.15 \
  tar czf /backup/asterisk-spool-$(date +%F).tar.gz -C /var/spool/asterisk .
tar tzf backup/asterisk-spool-$(date +%F).tar.gz | grep -E "astdb|INBOX/msg0000.wav|monitor/.*wav" | head -5
```

والمخرج سوف يكون كما يلي:

```text
./voicemail/office/1001/INBOX/msg0000.wav
./voicemail/office/1002/INBOX/msg0000.wav
./astdb.sqlite3
./monitor/20261020-084030-1001-1002.wav
./monitor/20261020-083335-1001-1002.wav
```

ولكي نتأكد من الاستعادة حذفنا كل البريد الصوتي في السياق `office`، ثم استعدناه من نسخة أخذناها قبل ذلك للمجلدين `voicemail` و`monitor` فقط:

```bash
docker exec arb-ast-pbx sh -c 'rm -rf /var/spool/asterisk/voicemail/office'
docker exec arb-ast-pbx asterisk -rx 'voicemail show users' | grep office
docker run --rm --volumes-from arb-ast-pbx -v "$PWD/backup:/backup:ro" debian:12.15 \
  sh -c 'tar xzf /backup/asterisk-spool-test.tar.gz -C /var/spool/asterisk && chown -R 999:999 /var/spool/asterisk/voicemail'
docker exec arb-ast-pbx asterisk -rx 'voicemail show users' | grep office
```

والمخرج سوف يكون كما يلي:

```text
office     1001  Alice                                     0
office     1002  Bob                                       0
office     1001  Alice                                     2
office     1002  Bob                                       1
```

والرقم 999 هو رقم المستخدم `asterisk` داخل ال Image كما يظهر من الأمر `docker exec arb-ast-pbx id`. وطبق على النسخ الاحتياطية قاعدة 3-2-1 كما في [النسخ الاحتياطي بقاعدة 3-2-1 واختبار الاستعادة](https://arabroot.io/articles/%D8%A7%D9%84%D9%86%D8%B3%D8%AE-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%8A%D8%A7%D8%B7%D9%8A-%D8%A8%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-3-2-1-%D9%88%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D8%A7%D8%AF%D8%A9/)، ولاحظ أن التسجيلات بيانات حساسة، فشفر النسخة قبل أن تخرج من السيرفر.

## التحديث إلى إصدار أحدث

داخل الفرع نفسه يكون التحديث بتغيير رقم الإصدار وإعادة البناء:

1. اقرأ سجل التغييرات للإصدار الجديد في [صفحة التنزيل](https://downloads.asterisk.org/pub/telephony/asterisk/releases/?ref=arabroot.io)، وتابع [النشرات الأمنية](https://github.com/asterisk/asterisk/security/advisories?ref=arabroot.io) في مستودع المشروع، فهي التي تحدد متى يجب أن تحدث بسرعة.
2. خذ نسخة احتياطية من ال Volume كما في القسم السابق.
3. غير `ARG AST=22.11.0` في ال Dockerfile ووسم ال Image في ملف Compose إلى الإصدار الجديد، ثم `docker compose build` و`docker compose up -d`.
4. تحقق ب `core show version` وبمكالمة تجريبية وبقراءة السجل بحثاً عن `ERROR`.
5. للرجوع، أعد الوسم القديم وشغل `docker compose up -d`، فال Image القديمة ما زالت على الجهاز.

أما الانتقال من فرع إلى فرع، مثل 22 إلى 24 عندما يصدر، فهو أكبر من ذلك، لأن الخيارات القديمة تحذف بين الفروع، كما حذفت `chan_sip` سابقاً. لذلك اقرأ ملف `UPGRADE.txt` في الكود المصدري للفرع الجديد، وجرب ملفات إعدادك على Container منفصل قبل الإنتاج، وابحث في سجله عن كل سطر `ERROR` و`WARNING`.

## قبل أن تنقل هذا إلى الإنتاج

- غير كل كلمات المرور في هذا المقال إلى قيم عشوائية طويلة، مثل الناتج من `openssl rand -base64 18`، والسبب أن التحويلة التي تخمن كلمة مرورها تعني مكالمات دولية على حسابك.
- لا تقم بفتح 5038 أو 8088 على الإنترنت، واجعل `bindaddr` في `manager.conf` و`http.conf` على `127.0.0.1` أو عنوان الشبكة الخاصة.
- افتح منفذ SIP ونطاق RTP في جدار الحماية للعناوين التي تحتاجها فقط، والأفضل أن تصل الهواتف البعيدة عبر [WireGuard](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-%D9%85%D8%B9-wireguard-%D9%88-wg-easy/)، وقد شرحنا جدار الحماية على السيرفر في [تأمين خادم VPS من أول دخول](https://arabroot.io/articles/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-vps-%D9%85%D9%86-%D8%A3%D9%88%D9%84-%D8%AF%D8%AE%D9%88%D9%84/).
- اجعل كل سياق لا يصل إلا إلى ما يحتاجه، فالسياق الذي تدخل منه مكالمات الخط الخارجي لا يجب أن يحتوي أي مسار يخرج إلى الخط الخارجي مرة أخرى.
- راجع [اعتبارات الأمان في وثائق Asterisk](https://docs.asterisk.org/Deployment/Important-Security-Considerations/?ref=arabroot.io)، وسوف نبني الحماية من الاحتيال وfail2ban وTLS وSRTP في مقال تأمين المقسم ضمن هذه السلسلة.
- إذا كان المقسم خلف NAT في السحابة فأضف `external_media_address` و`external_signaling_address` كما في [مقال تتبع المكالمة](https://arabroot.io/articles/%D8%AA%D8%AA%D8%A8%D8%B9-%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A9-sip-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D9%88%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%81%D9%8A-%D8%A7%D8%AA%D8%AC%D8%A7%D9%87-%D9%88%D8%A7%D8%AD%D8%AF/#production).

## مشكلات شائعة وحلولها

| العرض                                                                  | السبب                                   | الحل                                                         |
| ---------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------ |
| بناء pjproject يفشل ب Error 2 مع make -j                               | البناء المتوازي للمكتبة المدمجة         | ابن make third-party وحده أولاً                              |
| المكالمة إلى هاتف مسجل تفشل ب CHANUNAVAIL بعد إعادة إنشاء ال Container | AstDB داخل ال Container فضاعت التسجيلات | astdbdir على Volume                                          |
| كل التحويلات اختفت بعد إعادة التشغيل                                   | خيار خاطئ في قالب مشترك                 | اقرأ السجل بعد كل reload وابحث عن Could not create an object |
| البريد الصوتي لا يحفظ الرسائل القصيرة                                  | minsecs ورسالة ترحيب طويلة              | قصر مدة الرنين أو الرسالة، أو خفض minsecs                    |
| رسائل البريد الصوتي لا تصل بالبريد الإلكتروني                          | /usr/sbin/sendmail: not found           | أضف msmtp أو Postfix إلى ال Image                            |
| ملفات تسجيل لمكالمات لم يرد عليها                                      | MixMonitor قبل Dial                     | الخيار U() في Dial                                           |
| الرد الآلي يعمل بساعات خاطئة                                           | ال Container بتوقيت UTC                 | المنطقة الزمنية في GotoIfTime                                |
| الأرقام في القائمة لا تعمل                                             | طريقة DTMF مختلفة بين الهاتف والمقسم    | dtmf\_mode=rfc4733 وتأكد من إعداد الهاتف                     |
| Permission denied في AMI                                               | المستخدم لا يملك فئة الأمر              | أضف الفئة المطلوبة لهذا المستخدم وحده                        |
| Stasis app ... doesn't exist                                           | برنامج ARI غير متصل                     | شغل البرنامج، واجعل بعد Stasis مساراً احتياطياً              |

## إزالة التجربة

نزيل ما أنشأناه بالاسم فقط:

```bash
docker rm -f arb-ast-bob arb-ast-ari
docker compose down -v
docker rmi arb-asterisk:22.11.0 arb-ast-phone:1
```

## تدريب عملي

- أضف التحويلة 1003 بثلاثة أقسام قصيرة ترث من القوالب، ثم اجعل الرقم 3 في الرد الآلي يحول إليها، وتأكد ب `dialplan show ivr-main`.
- عدل `ari-hello.py` بحيث يقرأ الرقم الذي يضغطه المتصل من الحدث `ChannelDtmfReceived` ويطبعه، ثم يحول المكالمة إلى خطة الاتصال بطلب `continue`.

## الخلاصة

- اختر فرع LTS مكتملاً، واليوم هو 22.11.0، وابنه من الكود المصدري لأن التوزيعات لا تقدم إصداراً حديثاً.
- القوالب في PJSIP تجعل إضافة موظف ثلاثة أقسام قصيرة، وسطر `context` هو ما يحدد صلاحيات التحويلة.
- خطة الاتصال سياقات وامتدادات وأولويات، و`DIALSTATUS` يحدد ما يحدث بعد الرنين.
- سجل المكالمات بعد الرد فقط بالخيار `U()`، وخطط للمساحة بنحو 57.6 MB لكل ساعة WAV.
- في الرد الآلي استخدم `BackGround` وعداد محاولات ومنطقة زمنية صريحة.
- AMI لمراقبة المكالمات وبدئها، وARI لتسليم المكالمة كاملة لبرنامجك، وكلاهما لا يفتح على الإنترنت.
- بعد كل reload اقرأ السجل، وضع AstDB والبريد والتسجيلات على Volume، واختبر الاستعادة فعلاً.

وفي المقال القادم بإذن الله سوف نثبت FreePBX، ونرى كيف يكتب هذه الملفات نفسها من واجهة الويب.

[![](https://arabroot.io/content/images/2026/10/voip-terms-cover-cover.webp)دليلمصطلحات الاتصالات الهاتفية VoIP للمبتدئينشرح مبسط لمصطلحات الهاتف عبر الإنترنت، وما يمر في الشبكة بين ضغط زر الاتصال ووصول الصوت إلى الطرف الآخر.](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-voip-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/voip-call-path-cover-cover.webp)دليلتتبع مكالمة SIP عبر الشبكة: لماذا يصل الصوت في اتجاه واحد؟نبني مقسماً وهاتفين خلف NAT في تجربة عملية، ونتتبع المكالمة حزمة بحزمة حتى نعرف لماذا يضيع الصوت وكيف نصلحه.](https://arabroot.io/articles/%D8%AA%D8%AA%D8%A8%D8%B9-%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A9-sip-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D9%88%D8%A7%D9%84%D8%B5%D9%88%D8%AA-%D9%81%D9%8A-%D8%A7%D8%AA%D8%AC%D8%A7%D9%87-%D9%88%D8%A7%D8%AD%D8%AF/)[![](https://arabroot.io/content/images/2026/10/docker-networking-cover-cover.webp)دليلشبكات Docker وأفضل الممارسات مع Docker Composeلماذا يبقى المنفذ الذي تنشره في Compose مفتوحاً للإنترنت رغم ufw، وكيف تصل التطبيقات إلى بعضها عبر شبكات Docker دون نشر منافذ.](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/)

## سجل التحديثات

- أكتوبر 2026: كتابة الدليل واختباره على Asterisk 22.11.0 مبنياً من الكود المصدري على Debian 12 في Docker، مع هواتف baresip 1.0.0.