> ## 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.

# BookStack: توثيق فريقك في كتب وفصول على سيرفرك
- URL: https://arabroot.io/articles/تثبيت-bookstack-لتوثيق-الفريق/
- Published: 2026-10-09T12:30:00.000Z
- Updated: 2026-10-09T13:01:22.000Z
- Description: كيف تشغل BookStack على سيرفرك وتنظم توثيق فريقك في أرفف وكتب وفصول، مع صلاحيات ودخول موحد وبحث بالعربية.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الاستضافة الذاتية والتطبيقات, تطبيقات لفريقك, الاستضافة الذاتية, BookStack

في دليل [Outline: قاعدة معرفة فريقك على سيرفرك](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-outline-%D9%84%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D9%85%D8%B9%D8%B1%D9%81%D8%A9-%D8%A7%D9%84%D9%81%D8%B1%D9%8A%D9%82/) بنينا مكاناً يكتب فيه الفريق إجراءاته وقراراته بحرية، في مجموعات ومستندات متداخلة كما يريد كل كاتب. وهذه الحرية مناسبة لفريق صغير يعرف بعضه، ولكن عندما يكبر التوثيق ويصبح فيه دليل للموظف الجديد، وسياسات للموارد البشرية لا يجب أن يراها الجميع، وإجراءات تقنية يقرؤها المهندس الجديد بالترتيب، يحتاج الفريق إلى شكل ثابت يعرف فيه كل شخص أين يضع الصفحة وأين يجدها. وهذا ما يقدمه [BookStack](https://www.bookstackapp.com/?ref=arabroot.io)، فهو يرتب التوثيق كمكتبة: أرفف عليها كتب، وفي كل كتاب فصول، وفي كل فصل صفحات، ولكل مستوى منها صلاحياته.

وسوف نناقش في هذا المقال ما يلي:

- متى يناسبك BookStack، والمستويات الأربعة التي يبني عليها التوثيق.
- التثبيت مع Docker Compose وMariaDB، ومفتاح التطبيق APP\_KEY الذي أصبح إلزامياً.
- إنشاء المحتوى من الواجهة ومن ال API.
- العربية واتجاه النص RTL بصدق: ما يعمل، وما يحتاج إلى إصلاح، وما لا يعمل.
- البحث بالعربية بأمثلة حقيقية.
- الأدوار والصلاحيات على مستوى الكتاب.
- الدخول الموحد عبر authentik بال OIDC مع ربط المجموعات بالأدوار.
- التصدير، والنسخ الاحتياطي مع اختبار استعادة كامل، والتحديث إلى إصدار أحدث.

## متى يناسبك BookStack؟

BookStack تطبيق ويب مكتوب ب PHP على إطار Laravel، ويخزن بياناته في MySQL أو MariaDB، ورخصته [MIT](https://github.com/BookStackApp/BookStack/blob/v26.09.1/LICENSE?ref=arabroot.io)، وآخر إصدار له وقت كتابة المقال هو [v26.09.1](https://github.com/BookStackApp/BookStack/releases/tag/v26.09.1?ref=arabroot.io) الصادر في 29 سبتمبر 2026، ويطور اليوم على [Codeberg](https://codeberg.org/bookstack/bookstack?ref=arabroot.io) مع نسخة على GitHub. وأهم ما يميزه أنه يفرض على التوثيق شكلاً واحداً بأربعة مستويات، وهذا ما تبينه الصورة التالية بالمحتوى الذي أنشأناه في المختبر:

![رسم يوضح رف دليل الشركة وعليه كتابان هما الموارد البشرية والتقنية، وفي كل كتاب فصل، وتحت الفصول صفحات، وصفحة قائمة الأنظمة داخل كتاب التقنية مباشرة دون فصل، مع قفل على كتاب الموارد البشرية يمنع دور Viewer من رؤيته ومن رؤية كل ما تحته](https://arabroot.io/content/images/2026/10/bookstack-01-structure.webp)

الرف يجمع الكتب، والكتاب فيه فصول وصفحات، والصلاحية على الكتاب تنتقل إلى ما تحته

- **الرف Shelf**: مجموعة كتب، مثل «دليل الشركة»، والكتاب الواحد يمكن أن يكون على أكثر من رف.
- **الكتاب Book**: الوحدة الأساسية، مثل كتاب لكل قسم أو لكل نظام، وهو المستوى الذي تضع عليه الصلاحيات غالباً.
- **الفصل Chapter**: مجموعة صفحات داخل الكتاب، وهو اختياري.
- **الصفحة Page**: المحتوى نفسه، بمحرر مرئي WYSIWYG أو بمحرر Markdown.

لذلك فهو مناسب عندما يكون للتوثيق مالك واضح وشكل متوقع، مثل أدلة الموظفين والإجراءات التشغيلية وتوثيق الأنظمة، وعندما تحتاج إلى إخفاء كتاب كامل عن دور معين. أما إذا كان فريقك يريد مساحة مرنة للكتابة المشتركة في الوقت نفسه وتعليقات كثيرة، فقد يكون Outline أقرب إليه، وسوف نقارن بين أدوات التوثيق في مقال قادم ضمن [مرحلة التوثيق والمعرفة](https://arabroot.io/start/#stage-knowledge).

## التثبيت مع Docker Compose

لا يوجد Docker Image رسمية من مشروع BookStack نفسه، وإنما يذكر [توثيق التثبيت](https://www.bookstackapp.com/docs/admin/installation/?ref=arabroot.io) خيارين من المجتمع، أشهرهما Docker Image من [LinuxServer.io](https://docs.linuxserver.io/images/docker-bookstack/?ref=arabroot.io)، وهي التي استخدمناها. ولاحظ أن هذه ال Docker Image تحفظ كل ملفات التطبيق المتغيرة تحت المسار `/config`، فالمرفقات في `/config/www/files` والصور في `/config/www/images` و`/config/www/uploads` بحسب [صفحة المشروع](https://github.com/linuxserver/docker-bookstack?ref=arabroot.io)، وهذا ما سوف نحتاجه في النسخ الاحتياطي.

وأول خطوة هي مفتاح التطبيق APP\_KEY، وهو مفتاح يشفر به BookStack الجلسات وبيانات المصادقة الثنائية MFA. ومنذ الإصدار v26.09 أصبح إلزامياً، فإذا لم تحدده يظهر خطأ بدلاً من أن يستخدم BookStack قيمة افتراضية كما كان يفعل سابقاً، بحسب [صفحة التحديث](https://www.bookstackapp.com/docs/admin/updates/?ref=arabroot.io). والأمر التالي يولده من ال Docker Image نفسها:

```bash
docker run --rm --entrypoint /bin/bash lscr.io/linuxserver/bookstack:v26.09.1-ls287 appkey
```

والمخرج سوف يكون سطراً بالشكل التالي، والقيمة عندك مختلفة بالطبع:

```text
base64:UZzPr40KNWl+DXEsWcnDbymab6VjP6JHh2NIJXloplY=
```

ضع المفتاح وكلمات مرور قاعدة البيانات في ملف `.env` بجانب ملف Compose، واحفظ نسخة منه في مكان آمن، لأنك إذا فقدت المفتاح فقدت معه بيانات MFA للمستخدمين:

```bash
BOOKSTACK_TAG=v26.09.1-ls287
APP_KEY=base64:...
DB_PASSWORD=...
DB_ROOT_PASSWORD=...
```

وملف `compose.yaml` هو التالي:

```yaml
services:
  db:
    image: mariadb:11.8.9
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_DATABASE: bookstack
      MARIADB_USER: bookstack
      MARIADB_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      retries: 10

  bookstack:
    image: lscr.io/linuxserver/bookstack:${BOOKSTACK_TAG}
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      PUID: "1000"
      PGID: "1000"
      TZ: Asia/Riyadh
      APP_URL: https://wiki.example.com
      APP_KEY: ${APP_KEY}
      APP_LANG: ar
      DB_HOST: db
      DB_PORT: "3306"
      DB_USERNAME: bookstack
      DB_PASSWORD: ${DB_PASSWORD}
      DB_DATABASE: bookstack
    volumes:
      - config:/config
    ports:
      - "127.0.0.1:6875:80"

volumes:
  db:
  config:
```

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

- الوسم `v26.09.1-ls287` ثابت، والرقم `ls287` هو رقم بناء LinuxServer، فلا تستخدم `latest` لأن التحديث يجب أن يكون قراراً منك بعد نسخة احتياطية.
- شرط `service_healthy` يجعل BookStack ينتظر حتى تصبح MariaDB جاهزة فعلاً، وسوف نرى في قسم الاستعادة لماذا يهم هذا الترتيب.
- `APP_LANG: ar` يجعل العربية لغة الواجهة الافتراضية للمستخدمين الجدد والزوار، بحسب [توثيق اللغة](https://www.bookstackapp.com/docs/admin/language-config/?ref=arabroot.io)، وكل مستخدم يستطيع أن يغيرها من إعداداته.
- `APP_URL` يجب أن يكون العنوان الذي يكتبه المستخدم في المتصفح تماماً، لأن BookStack يبني منه كل الروابط، والمنفذ `127.0.0.1:6875` لا يصل إليه إلا ال Reverse Proxy على السيرفر نفسه، كما شرحنا في [تطبيقك خلف Reverse Proxy](https://arabroot.io/articles/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-%D8%AE%D9%84%D9%81-reverse-proxy/).

عند التشغيل الأول بالأمر `docker compose up -d` ينشئ BookStack جداول قاعدة البيانات، وفي المختبر ظهرت في السجل أسطر Migration عددها 104، كل منها ينتهي ب `DONE`، وهذه آخرها:

```text
bookstack-1  |   2026_04_19_141616_add_revision_view_all_permission ............ 24.24ms DONE
bookstack-1  |   2026_07_27_201402_update_users_external_auth_id_collation ..... 50.35ms DONE
bookstack-1  | [custom-init] No custom files found, skipping...
bookstack-1  | [ls.io-init] done.
```

وبعد ذلك يدخل المدير الأول بالبريد `admin@admin.com` وكلمة المرور `password`، وهي قيم معروفة لكل من قرأ التوثيق، لذلك غيرهما فوراً قبل أن تجعل الموقع متاحاً من الإنترنت. وإذا غيرت `APP_URL` بعد ذلك، فالروابط القديمة محفوظة داخل الصفحات في قاعدة البيانات، ويحدثها الأمر `php /app/www/artisan bookstack:update-url` كما سوف نرى في قسم الاستعادة.

## أول رف وكتاب وصفحة

من الواجهة تنشئ الرف من قائمة «الأرفف»، والكتاب من «الكتب»، ثم الفصول والصفحات من داخل الكتاب. وإذا كان لديك توثيق كثير تريد نقله فالأفضل هو ال API، وتوثيقه الكامل موجود داخل كل نسخة على المسار `/api/docs`، ويمكنك أن تراه على [النسخة التجريبية](https://demo.bookstackapp.com/api/docs?ref=arabroot.io). وللوصول إليه تنشئ توكن Token من صفحة حسابك ثم قسم الدخول والأمان `/my-account/auth`، فتحصل على معرف Token ID وسر Token Secret، ويرسلان في الترويسة `Authorization`:

```bash
curl -s -X POST https://wiki.example.com/api/books \
  -H "Authorization: Token $TOKEN_ID:$TOKEN_SECRET" -H 'Content-Type: application/json' \
  --data-binary '{"name": "المالية", "description": "الفواتير والمشتريات"}'
```

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

```text
{"id": 10, "name": "المالية", "slug": "almaly", "created_by": 1}
```

لاحظ الحقل `slug`، فهو الجزء الذي يظهر في الرابط، وقد حوله BookStack من «المالية» إلى حروف لاتينية `almaly`، وكذلك أصبح «دليل الشركة» `dlyl-alshrk` و«إضافة موظف جديد إلى شبكة WireGuard» `adaf-mothf-gdyd-al-shbk-wireguard`. فالروابط تبقى قصيرة وتعمل في كل مكان، ولكنها لا تقرأ بالعربية، وإذا غيرت عنوان الصفحة لاحقاً تغير الرابط معه، ففي المختبر أصبح رابط «قائمة الأنظمة الداخلية» `kaym-alanthm-aldakhly`، لذلك ضع في الصفحات الأخرى روابط تنشئها من المحرر وليس روابط تنسخها باليد. وبهذه الطريقة أنشأنا في المختبر بسكربت Python صغير رفاً وكتابين وفصلين وأربع صفحات، والصورة التالية تبين الرف في الواجهة العربية:

![واجهة BookStack v26.09 بالعربية من اليمين إلى اليسار، وفيها رف دليل الشركة وعليه كتابا الموارد البشرية والتقنية، والقائمة الجانبية فيها إجراءات الرف وتفاصيله ونشاطاته](https://arabroot.io/content/images/2026/10/bookstack-02-shelf.webp)

الواجهة كلها من اليمين إلى اليسار بمجرد اختيار العربية

## العربية واتجاه النص: ما يعمل وما لا يعمل

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

![صفحة إضافة موظف جديد إلى شبكة WireGuard في BookStack بالعربية، وفيها خطوات مرقمة وكتلة كود وجدول عناوين واقتباس، ويظهر فيها المسار etc/wireguard/wg0.conf والشرطة المائلة في آخره بدلاً من أوله](https://arabroot.io/content/images/2026/10/bookstack-03-page-rtl.webp)

النص العربي والجدول سليمان، أما المسار داخل السطر فقد انتقلت شرطته المائلة إلى آخره

النص العربي والعناوين والجدول والقوائم المرقمة كلها سليمة، ولكن لاحظ السطر الثاني من الخطوات، فالمسار `/etc/wireguard/wg0.conf` ظهر بالشكل `etc/wireguard/wg0.conf/`، والسبب أن الكود داخل السطر يرث اتجاه الفقرة العربية، فتنتقل الشرطة المائلة في أوله إلى الجهة الأخرى بحسب قواعد النص ثنائي الاتجاه Bidirectional Text، وهذا خطأ خطير في دليل يتبعه المهندس حرفاً بحرف. وكذلك ظهرت كتلة الكود مصفوفة إلى اليمين ورقم السطر على اليمين. والحل أن نجبر الكود على الاتجاه من اليسار إلى اليمين، وفي BookStack مكان لذلك في الإعدادات، ثم التخصيص، ثم حقل Custom HTML Head Content، فأضفنا فيه:

```html
<style>
.page-content code, .page-content pre, .page-content .cm-editor { direction: ltr; unicode-bidi: isolate; text-align: left; }
</style>
```

والصورة التالية تبين الصفحة نفسها بعد الحفظ:

![الصفحة نفسها بعد إضافة CSS في إعدادات التخصيص، ويظهر المسار /etc/wireguard/wg0.conf صحيحاً، وكتلة الكود من اليسار إلى اليمين ورقم السطر على اليسار](https://arabroot.io/content/images/2026/10/bookstack-04-page-ltr-code.webp)

بعد سطر CSS واحد أصبح المسار صحيحاً وأصبحت كتلة الكود من اليسار إلى اليمين

أما التصدير إلى PDF فهو نقطة الضعف الحقيقية للعربية. ف BookStack يستخدم بشكل افتراضي مكتبة dompdf، ويقول [توثيق PDF](https://www.bookstackapp.com/docs/admin/pdf-rendering/?ref=arabroot.io) إنها محدودة الإمكانيات، وإن الخط الافتراضي DejaVu Sans يغطي اللغات اللاتينية أساساً. وفي المختبر ظهرت الحروف العربية متصلة بشكل صحيح، ولكن الكلمات جاءت بترتيب معكوس من اليسار إلى اليمين، كما في الصورة التالية من ملف PDF الذي صدرناه:

![ملف PDF صدره BookStack بمكتبة dompdf لصفحة WireGuard، والكلمات العربية في العناوين والفقرات مرتبة من اليسار إلى اليمين فلا تقرأ، بينما الأوامر الإنجليزية سليمة](https://arabroot.io/content/images/2026/10/bookstack-05-pdf-dompdf.webp)

ملف PDF من dompdf: الحروف متصلة ولكن الكلمات بترتيب معكوس

وتغيير الخط لا يحل هذه المشكلة، لأن المشكلة في ترتيب الكلمات وليس في شكل الحروف. وأمامك حلان: الأول أن تصدر الصفحة بصيغة Contained Web File، وهي ملف HTML واحد فيه الصور والتنسيق، ثم تطبعه إلى PDF من المتصفح، وقد فعلنا ذلك بمتصفح Chrome فخرجت الصفحة العربية سليمة. والثاني أن تجعل BookStack يستخدم برنامجاً آخر بالخيار `EXPORT_PDF_COMMAND`، ويحذر التوثيق نفسه من أن هذا الخيار يشغل برنامجاً خارجياً على HTML يكتبه المستخدمون، فيجب عزله عن الشبكة والملفات، ولم نجربه في هذا المقال.

## البحث بالعربية

يدعم BookStack صيغة بحث متقدمة يشرحها [توثيق البحث](https://www.bookstackapp.com/docs/user/searching/?ref=arabroot.io): الكلمات العادية، والعبارة الكاملة بين علامتي تنصيص، والوسوم Tags بين أقواس مربعة، والمرشحات Filters بين أقواس معقوفة. ولكن السؤال المهم لنا هو كيف يتعامل مع العربية، حيث تلتصق «ال» و«و» و«ل» بالكلمة. فأرسلنا مجموعة استعلامات إلى المسار `/api/search` على المحتوى الذي أنشأناه، وكانت النتائج كما يلي:

| الاستعلام                   | عدد النتائج | الملاحظة                                   |
| --------------------------- | ----------- | ------------------------------------------ |
| السيرفرات                   | 4           | الفصل والكتاب وصفحتان                      |
| السيرف                      | 4           | جزء من أول الكلمة يكفي                     |
| سيرفرات                     | 0           | الكلمة في النص هي «السيرفرات» و«للسيرفرات» |
| سيرفر                       | 0           | السبب نفسه                                 |
| إجازة                       | 1           | موجودة في النص بهذا الشكل                  |
| اجازة                       | 0           | لا يوحد البحث بين الهمزة والألف            |
| wireguard                   | 1           | البحث لا يفرق بين الحروف الكبيرة والصغيرة  |
| "المفتاح الخاص"             | 1           | العبارة الكاملة                            |
| \[قسم=التقنية\]             | 2           | البحث بالوسم وقيمته                        |
| {type:page} \[قسم=التقنية\] | 2           | الصفحات فقط ذات الوسم                      |
| 10.8.0.12                   | 1           | عنوان داخل جدول                            |

والنتيجة أن البحث يطابق أول الكلمة كما كتبت في النص، فالكلمة «السيرف» تجد «السيرفرات»، ولكن «سيرفرات» لا تجدها لأن «ال» جزء من الكلمة عنده، ولا يوجد تحليل صرفي للعربية ولا توحيد للهمزات. والصورة التالية تبين صفحة النتائج في الواجهة:

![صفحة نتائج البحث في BookStack عن الكلمة السيرف، وفيها أربع نتائج هي فصل السيرفرات والشبكة وصفحة WireGuard وكتاب التقنية وصفحة النسخ الاحتياطي، مع تمييز الجزء المطابق وخيارات البحث المفصل في الجانب](https://arabroot.io/content/images/2026/10/bookstack-06-search.webp)

البحث عن «السيرف» يجد كل ما يبدأ بها، والجزء المطابق مميز

لذلك اتفق مع فريقك على ثلاث عادات: ابحث بأول الكلمة دون آخرها، وجرب الكلمة مع «ال» ودونها، واستخدم الوسوم Tags للتصنيف المهم مثل القسم والنظام، لأن البحث بالوسم دقيق ولا يتأثر بشكل الكلمة.

## الأدوار والصلاحيات

في كل نسخة جديدة أربعة أدوار جاهزة: Admin وEditor وViewer وPublic للزوار غير المسجلين، وكل دور له صلاحيات عامة على النظام، وصلاحيات افتراضية على المحتوى. ثم يمكن أن تغير هذه الصلاحيات على أي رف أو كتاب أو فصل أو صفحة، ويشرح [توثيق الأدوار والصلاحيات](https://www.bookstackapp.com/docs/user/roles-and-permissions/?ref=arabroot.io) القواعد التالية:

- الصلاحية على الكتاب أو الفصل تنتقل إلى كل ما تحته، إلا إذا كانت لعنصر منها صلاحيات خاصة به.
- الصلاحية على الرف لا تنتقل إلى كتبه، لأن الكتاب يمكن أن يكون على أكثر من رف، ولكن يوجد زر ينسخها إلى كل كتب الرف مرة واحدة.
- الصلاحية الخاصة بدور معين أقوى من صلاحية «الآخرين Everyone Else»، وهذه أقوى من صلاحيات الدور العامة.
- دور Admin يحتفظ دائماً بكل الصلاحيات ولا يظهر في شاشة صلاحيات المحتوى.

وفي المختبر أنشأنا المستخدم Khalid بدور Viewer، ثم منعنا هذا الدور من رؤية كتاب الموارد البشرية، وكلاهما عبر ال API:

```bash
curl -s -X POST $B/api/users -H "$AUTH" -H 'Content-Type: application/json' \
  --data '{"name":"Khalid","email":"khalid@example.com","password":"Khalid-Lab-2026!","roles":[3],"language":"ar"}'

curl -s -X PUT $B/api/content-permissions/book/1 -H "$AUTH" -H 'Content-Type: application/json' \
  --data '{"role_permissions":[{"role_id":3,"view":false,"create":false,"update":false,"delete":false}]}'
```

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

```text
{"owner":{"id":1,"name":"Admin","slug":"admin"},"role_permissions":[{"role_id":3,"view":false,"create":false,"update":false,"delete":false,"role":{"id":3,"display_name":"Viewer"}}],"fallback_permissions":{"inheriting":true,"view":null,"create":null,"update":null,"delete":null}}
```

والرقم 3 هو رقم دور Viewer كما أعاده الطلب `GET /api/roles`. و`"inheriting":true` في آخر الرد يعني أن بقية الأدوار تبقى على صلاحياتها الافتراضية. والصورة التالية تبين الصلاحية نفسها في شاشة صلاحيات الكتاب:

![شاشة أذونات كتاب الموارد البشرية في BookStack، وفيها صف لدور Viewer وكل الخانات عرض وإنشاء وتحديث وحذف غير محددة، وصف للآخرين مع خيار وراثة الإعدادات الافتراضية](https://arabroot.io/content/images/2026/10/bookstack-07-permissions.webp)

دور Viewer ممنوع من كل شيء في هذا الكتاب، وبقية الأدوار ترث صلاحياتها

وعندما دخل Khalid رأى الرف وعليه كتاب التقنية وحده، وعندما فتح رابط كتاب الموارد البشرية مباشرة ظهرت له رسالة «لم يتم العثور على الكتاب» مع ملاحظة أنه قد لا يملك صلاحية رؤيته، أي أن BookStack لا يؤكد له وجود الكتاب أصلاً:

![رف دليل الشركة كما يراه المستخدم Khalid بدور Viewer، وعليه كتاب التقنية فقط دون كتاب الموارد البشرية، ودون أزرار التعديل](https://arabroot.io/content/images/2026/10/bookstack-08-viewer-shelf.webp)

الرف نفسه كما يراه دور Viewer: كتاب واحد ولا توجد أزرار تعديل

## الدخول الموحد عبر authentik بال OIDC

إذا كان لديك [authentik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-authentik-%D9%84%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%88%D8%AD%D8%AF/) فلا داعي لكلمات مرور منفصلة في BookStack، والفكرة شرحناها في [شرح OAuth وOpenID Connect](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/). ويذكر [توثيق OIDC في BookStack](https://www.bookstackapp.com/docs/admin/oidc-auth/?ref=arabroot.io) ثلاثة قيود يجب أن تعرفها قبل البدء: التوقيع RS256 فقط، وعنوان المصدر Issuer يجب أن يبدأ ب `https://`، وBookStack يربط المستخدم بالقيمة `sub` وليس بالبريد.

في authentik أنشئ Application ومعه OAuth2/OpenID Provider من نوع Confidential، كما في [صفحة BookStack في توثيق authentik](https://integrations.goauthentik.io/documentation/bookstack/?ref=arabroot.io)، واجعل عنوان العودة Redirect URI هو `https://wiki.example.com/oidc/callback` بالمطابقة الدقيقة Strict، واختر شهادة التوقيع لكي يوقع ب RS256\. وفي المختبر أنشأنا ال Provider عبر API authentik، فظهر خطأ يستحق الذكر: رجع المتصفح إلى BookStack برسالة «An unknown error occurred»، وفي سجل authentik السطر التالي:

```text
"event": "Invalid grant_type for provider", "grant_type": "authorization_code"
```

والسبب أن ال Provider الذي ينشأ عبر ال API تكون قائمة `grant_types` فيه فارغة، فأضفنا `authorization_code` و`refresh_token` فعمل الدخول. وإذا أنشأته من الواجهة فتأكد من هذه القائمة في إعدادات ال Provider إذا رأيت الرسالة نفسها. أما في BookStack فنضيف المتغيرات التالية إلى خدمة `bookstack` في ملف Compose:

```yaml
      AUTH_METHOD: oidc
      AUTH_AUTO_INITIATE: "false"
      OIDC_NAME: authentik
      OIDC_DISPLAY_NAME_CLAIMS: name
      OIDC_CLIENT_ID: bookstack
      OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
      OIDC_ISSUER: https://auth.example.com/application/o/bookstack/
      OIDC_ISSUER_DISCOVER: "true"
      OIDC_END_SESSION_ENDPOINT: "true"
      OIDC_USER_TO_GROUPS: "true"
      OIDC_GROUPS_CLAIM: groups
      OIDC_REMOVE_FROM_GROUPS: "true"
```

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

- عنوان `OIDC_ISSUER` هو عنوان ال Provider في authentik مع الشرطة المائلة في آخره، ومع `OIDC_ISSUER_DISCOVER` يقرأ BookStack منه بقية العناوين والمفاتيح، ويرفض الاستمرار إذا كانت قيمة `issuer` في الرد مختلفة عنه حرفاً بحرف.
- `OIDC_USER_TO_GROUPS` و`OIDC_GROUPS_CLAIM` يربطان مجموعات authentik بأدوار BookStack بالاسم، فالمستخدم في المجموعة `Editor` يحصل على دور Editor، و`OIDC_REMOVE_FROM_GROUPS` يسحب منه الأدوار التي لم تعد تقابل مجموعاته عند كل دخول.
- لم نحتج إلى `OIDC_ADDITIONAL_SCOPES`، لأن authentik يضع المطالبة `groups` ضمن ال Scope المسمى `profile`.

وبعد إعادة إنشاء ال Container تصبح صفحة الدخول زراً واحداً، ثم صفحة authentik:

![صفحة تسجيل الدخول في BookStack بعد تفعيل OIDC، وفيها زر واحد هو تسجيل الدخول باستخدام authentik دون حقلي البريد وكلمة المرور](https://arabroot.io/content/images/2026/10/bookstack-09-login-sso.webp)

مع AUTH\_METHOD=oidc تختفي حقول كلمة المرور ويبقى زر authentik

![صفحة دخول authentik وفيها رسالة Log in to continue to BookStack وحقل البريد الإلكتروني أو اسم المستخدم بالعربية وزر تسجيل الدخول](https://arabroot.io/content/images/2026/10/bookstack-10-authentik-login.webp)

authentik يذكر اسم التطبيق الذي طلب الدخول

دخلت المستخدمة سارة وهي في مجموعة `Editor` في authentik، فأنشأ BookStack حسابها تلقائياً، وأعاد الطلب `GET /api/users/4` ما يلي بعد اختيار بعض الحقول:

```text
سارة الأحمد sara@example.com external_auth_id= 695de2b5fd088fca… roles= ['Editor']
```

والقيمة `external_auth_id` هي `sub` القادمة من authentik، وهي التي يربط بها BookStack الحساب في كل دخول بعد ذلك. والصورة التالية تبين كتاب التقنية كما تراه سارة، ومعها أزرار إنشاء الصفحات والفصول التي يعطيها دور Editor:

![كتاب التقنية في BookStack كما تراه المستخدمة سارة بعد الدخول عبر authentik، وفي القائمة الجانبية أزرار صفحة جديدة وفصل جديد وتعديل ونسخ وحذف](https://arabroot.io/content/images/2026/10/bookstack-11-sso-editor.webp)

الدور جاء من مجموعة authentik، دون أي إعداد للمستخدم داخل BookStack

⚠️

عندما تغير `AUTH_METHOD` إلى `oidc` لا يستطيع أي مستخدم أن يدخل بكلمة المرور، ومنهم المدير الذي أنشأته في البداية، لأن حسابه ليس مربوطاً بأي حساب في authentik. لذلك قبل التغيير اجعل المسؤول عن BookStack في مجموعة اسمها `Admin` في authentik لكي يحصل على دور Admin عند أول دخول، أو أدخل قيمة `sub` الخاصة به في حقل External Authentication ID في حسابه، كما يشرح التوثيق في قسم Switching to OIDC with Existing Users. وتوكنات ال API تبقى تعمل بعد التغيير.

وفي المختبر احتجنا إلى ثلاثة أشياء لا تحتاجها على سيرفر حقيقي، لأن authentik كان على الاسم `auth.localhost` بشهادة من جهة توقيع صنعناها للمختبر: وثقنا بشهادة هذه الجهة داخل Container الخاص ب BookStack بسكربت في المجلد `/custom-cont-init.d` الذي تدعمه صور LinuxServer كما في [توثيق التخصيص](https://docs.linuxserver.io/general/container-customization/?ref=arabroot.io)، ثم أضفنا Container صغيراً بالأداة socat يحول المنفذ المحلي إلى authentik، لأن الأمر curl داخل ال Container حول الاسم `auth.localhost` إلى `127.0.0.1` دون أن يسأل DNS، وBookStack يتصل ب authentik عبر مكتبة curl نفسها في PHP. أما على سيرفرك فيكون authentik على نطاق حقيقي بشهادة Let's Encrypt فلا تحتاج إلى أي منها.

## التصدير

كل صفحة وفصل وكتاب يمكن أن يصدر من قائمة «تصدير» أو من ال API، وهذه الأحجام من المختبر لصفحة WireGuard:

| الصيغة             | المسار في ال API              | الحجم             | الملاحظة                                              |
| ------------------ | ----------------------------- | ----------------- | ----------------------------------------------------- |
| Markdown           | /api/pages/7/export/markdown  | 836 بايت          | المحتوى كما كتب، والعربية سليمة                       |
| نص عادي            | /api/pages/7/export/plaintext | 759 بايت          | للبحث أو النقل إلى نظام آخر                           |
| Contained Web File | /api/pages/7/export/html      | 21,536 بايت       | ملف HTML واحد فيه التنسيق، ويطبع من المتصفح بشكل سليم |
| PDF                | /api/pages/7/export/pdf       | 1,132,793 بايت    | dompdf، والعربية بترتيب معكوس كما رأينا               |
| Portable ZIP       | /api/books/2/export/zip       | 1,849 بايت للكتاب | ملف data.json ومجلد files، ويستورد في BookStack آخر   |

وحجم PDF الكبير سببه أن dompdf يضع داخل الملف خطوط DejaVu الثلاثة التي استخدمها. أما ZIP فهو الصيغة المناسبة لنقل كتاب بين نسختين من BookStack، لأنه يحفظ الشكل والوسوم والمرفقات، ولكنه لا يغني عن النسخة الاحتياطية، لأنه لا يحمل المستخدمين ولا الصلاحيات ولا سجل المراجعات.

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

يقول [توثيق النسخ الاحتياطي](https://www.bookstackapp.com/docs/admin/backup-restore/?ref=arabroot.io) إن هناك نوعين من البيانات: قاعدة البيانات، والملفات، وهي ملف `.env` ومجلدات الصور والمرفقات والقوالب Themes. وفي Docker Image الخاصة ب LinuxServer كلها تحت `/config/www`. ولكن لاحظ أمراً اكتشفناه في المختبر، فالملف `/config/www/.env` فيه قيم مثالية مثل `APP_KEY=SomeRandomString`، لأن القيم الحقيقية تأتي من متغيرات البيئة في ملف Compose، لذلك يجب أن تنسخ احتياطياً ملفي `compose.yaml` و`.env` اللذين بجانبه، وإلا فقدت المفتاح. والأمران التاليان يأخذان النسخة:

```bash
docker compose exec -T db sh -c 'mariadb-dump --single-transaction -u bookstack -p"$MARIADB_PASSWORD" bookstack' \
  > backup/bookstack-2026-10-09.sql
docker run --rm -v bookstack_config:/config:ro -v "$PWD/backup:/backup" alpine:3.22 \
  tar czf /backup/bookstack-files-2026-10-09.tar.gz -C /config/www .env uploads files images themes
ls -l backup
```

```text
-rw-r--r-- 1 wajdy 197609 86066 Oct  9 04:05 bookstack-2026-10-09.sql
-rw-r--r-- 1 wajdy 197609 42777 Oct  9 04:05 bookstack-files-2026-10-09.tar.gz
```

الخيار `--single-transaction` يأخذ نسخة متسقة من جداول InnoDB دون أن يقفل قاعدة البيانات، فلا يتوقف الموقع أثناء النسخ. واسم ال Volume `bookstack_config` هو اسم المجلد ثم اسم ال Volume، فتأكد منه بالأمر `docker volume ls`.

ولكي نعرف أن النسخة تعمل فعلاً استعدناها في نسخة جديدة كاملة من BookStack بمشروع Compose آخر ومنفذ آخر. وفي المحاولة الأولى شغلنا قاعدة البيانات واستوردنا النسخة مباشرة، فظهر الخطأ التالي لأن MariaDB لم تكن جاهزة بعد:

```text
ERROR 2002 (HY000): Can't connect to local server through socket '/run/mysqld/mysqld.sock' (2)
```

ثم شغلنا BookStack فوجد قاعدة بيانات فارغة، فأنشأ جداول جديدة فارغة، وهذا أسوأ ما يمكن أن يحدث في استعادة حقيقية لأنه يبدو كأنه نجح. لذلك يقول التوثيق إن قاعدة البيانات تستعاد قبل تشغيل Container الخاص ب BookStack، والترتيب الصحيح هو:

```bash
docker compose up -d --wait db
docker compose exec -T db sh -c 'mariadb -u bookstack -p"$MARIADB_PASSWORD" bookstack' < backup/bookstack-2026-10-09.sql
docker run --rm -v bookstack_config:/config -v "$PWD/backup:/backup:ro" alpine:3.22 \
  sh -c 'mkdir -p /config/www && tar xzf /backup/bookstack-files-2026-10-09.tar.gz -C /config/www && chown -R 1000:1000 /config'
docker compose up -d bookstack
```

الخيار `--wait` يجعل Compose ينتظر حتى يصبح فحص الصحة Healthcheck الخاص ب MariaDB ناجحاً، و`chown` يعطي الملفات للمستخدم 1000 الذي حددناه في `PUID`. وعند التشغيل كتب BookStack في سجله `Nothing to migrate.`، أي أنه وجد الجداول كاملة. ولأن النسخة الجديدة على عنوان مختلف شغلنا أمر تحديث العنوان الذي يذكره التوثيق:

```bash
docker compose exec bookstack php /app/www/artisan bookstack:update-url http://wiki.localhost:29401 http://wiki.localhost:29402
```

```text
Updated 0 JSON encoded rows in settings->value
URL update procedure complete.
Be sure to run "php artisan cache:clear" to clear any old URLs in the cache.
```

ثم تحققنا عبر ال API في النسخة المستعادة من عدد الصفحات ومن محتوى المرفق الذي رفعناه قبل النسخ:

```text
pages via API: 4
wg-sara.conf Address = 10.8.0.11/32
```

الصفحات الأربع موجودة، والمرفق عاد بمحتواه، والتوكن نفسه عمل لأنه محفوظ في قاعدة البيانات. وبعد ذلك انقل ملفات النسخة إلى مكان آخر كما في [قاعدة 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/).

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

تقول LinuxServer إن التحديث يكون بسحب Docker Image الجديدة وإعادة إنشاء ال Container، وليس بالتحديث داخله، ويطبق BookStack تغييرات قاعدة البيانات Migrations تلقائياً عند التشغيل. وقد بدأنا المختبر بالإصدار v26.09 ثم حدثناه إلى v26.09.1 بالخطوات التالية:

1. اقرأ [قسم Version Specific Instructions في صفحة التحديث](https://www.bookstackapp.com/docs/admin/updates/?ref=arabroot.io) لكل إصدار بين إصدارك والإصدار الجديد، ففيه التغييرات التي تكسر الإعداد مثل إلزام APP\_KEY في v26.09، والثغرات الأمنية التي أصلحها كل إصدار، مثل مجموعة الثغرات التي أصلحها الإصداران v26.05.3 وv26.05.4.
2. خذ نسخة احتياطية كما في القسم السابق.
3. غير الوسم في `.env` من `v26.09-ls286` إلى `v26.09.1-ls287`، ثم شغل `docker compose pull` و`docker compose up -d`.
4. تأكد من السجل ومن رقم الإصدار.

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

```bash
docker compose logs bookstack | grep -i 'migrat'
curl -s -H "$AUTH" $B/api/system
```

```text
bookstack-1  | [migrations] done
bookstack-1  |    INFO  Nothing to migrate.
{"version":"v26.09.1","instance_id":"483ca879-0e4c-44a0-988c-2a6260e0e131","app_name":"BookStack","app_logo":"http:\/\/wiki.localhost:29401\/logo.png","base_url":"http:\/\/wiki.localhost:29401"}
```

لم تكن هناك تغييرات في قاعدة البيانات لأن v26.09.1 إصدار إصلاحات، وفيه بحسب [صفحة الإصدار](https://github.com/BookStackApp/BookStack/releases/tag/v26.09.1?ref=arabroot.io) فحص لحجم ملفات ZIP قبل فكها عند الاستيراد، وإصلاح لصلاحيات الصفحات عند تحويل فصل إلى كتاب. أما عند الانتقال بين إصدارات كبيرة فسوف ترى أسطر `DONE` لكل تغيير كما في التشغيل الأول.

## إعدادات تستحق الانتباه

في ملف [.env.example.complete](https://github.com/BookStackApp/BookStack/blob/v26.09.1/.env.example.complete?ref=arabroot.io) كل الخيارات، وهذه ثلاثة منها نوصي بالنظر فيها:

- `**DISABLE_EXTERNAL_SERVICES=true**`: بشكل افتراضي يجلب BookStack الصور الشخصية من Gravatar ويستخدم diagrams.net للرسوم، وفي المختبر جلب السيرفر صورة لكل مستخدم جديد وحفظها في المجلد `/config/www/uploads/images/user`. وهذا الخيار يوقف الخدمتين، فلا يرسل السيرفر هاش بريد كل موظف إلى طرف خارجي ليجلب صورته.
- **`AVATAR_URL`**: إذا أردت صوراً شخصية من خدمة تختارها أنت مثل Libravatar.
- **`QUEUE_CONNECTION=database`**: يجعل إرسال البريد وال Webhooks في الخلفية، كما تذكر صفحة LinuxServer، فلا ينتظر المستخدم حتى يرسل البريد.

## أدلة تكمل هذا المقال

هذا المقال جزء من [مرحلة التوثيق والمعرفة](https://arabroot.io/start/#stage-knowledge) في خريطة التعلم، وفيها مقالات قادمة عن Wiki.js وDocmost ثم مقارنة بينها وبين Outline وBookStack. وهذه الأدلة التي يعتمد عليها هذا المقال:

[![](https://arabroot.io/content/images/2026/10/outline-cover-cover.webp)دليلOutline: قاعدة معرفة فريقك على سيرفرككيف تشغل Outline على سيرفرك ليكتب فيه فريقك إجراءاته وقراراته، ويدخل إليه الموظفون بحساباتهم في authentik.](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-outline-%D9%84%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D9%85%D8%B9%D8%B1%D9%81%D8%A9-%D8%A7%D9%84%D9%81%D8%B1%D9%8A%D9%82/)[![](https://arabroot.io/content/images/2026/10/authentik-cover-cover-1.webp)دليلتثبيت authentik للدخول الموحد SSO إلى تطبيقاتككيف تثبت authentik حتى يدخل كل موظف إلى تطبيقات الفريق بحساب واحد، مع فرض المصادقة الثنائية على الجميع.](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-authentik-%D9%84%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%88%D8%AD%D8%AF/)[![](https://arabroot.io/content/images/2026/10/oauth-oidc-cover-cover.webp)دليلشرح OAuth وOpenID Connect للمبتدئينلماذا لا يجب أن يعرف أي تطبيق كلمة مرورك، وكيف يحل OAuth ذلك بتوكنات محدودة، وكيف يضيف OpenID Connect الدخول الموحد.](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%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/behind-proxy-cover-cover.webp)دليلكيف يرى تطبيقك عنوان الزائر الحقيقي خلف ال Reverse Proxyإذا كان تطبيقك يرى عنوان ال Proxy بدلاً من الزائر أو يولد روابط http، فهذا هو السبب والإعداد الصحيح لإطار عملك.](https://arabroot.io/articles/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-%D8%AE%D9%84%D9%81-reverse-proxy/)[![](https://arabroot.io/content/images/2026/10/backup-321-cover-cover.webp)دليلالنسخ الاحتياطي بقاعدة 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/)

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- BookStack يرتب التوثيق في أرفف وكتب وفصول وصفحات، ورخصته MIT، ويناسب التوثيق الذي له مالك وشكل ثابت ويحتاج إلى صلاحيات على مستوى الكتاب.
- ثبته بال Docker Image من LinuxServer بوسم ثابت مع MariaDB، وولد APP\_KEY واحفظه مع ملف `.env`، لأنه إلزامي منذ v26.09 ولأن فقده يفقدك بيانات MFA، وغير حساب المدير الافتراضي فوراً.
- الواجهة والمحتوى بالعربية سليمان، ولكن الكود داخل السطر يحتاج إلى سطر CSS في التخصيص، وتصدير PDF الافتراضي لا يصلح للعربية، فاطبع ملف HTML من المتصفح.
- البحث يطابق أول الكلمة كما كتبت، فلا يجد «سيرفرات» إذا كان النص «السيرفرات»، لذلك علم فريقك البحث بأول الكلمة واستخدم الوسوم للتصنيف.
- الصلاحية على الكتاب تنتقل إلى ما تحته، وعلى الرف لا تنتقل، والمستخدم الممنوع لا يرى حتى أن الكتاب موجود.
- مع authentik يصبح الدخول زراً واحداً وتأتي الأدوار من المجموعات، ولكن اربط حساب المدير قبل التغيير، وتأكد من أنواع المنح grant types في ال Provider.
- انسخ قاعدة البيانات و`/config/www` وملفي Compose و`.env`، وعند الاستعادة شغل قاعدة البيانات واستورد النسخة قبل أن تشغل BookStack.

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

- أكتوبر 2026: كتابة الدليل واختباره على BookStack v26.09 ثم v26.09.1 بال Docker Image `lscr.io/linuxserver/bookstack` (الوسمان `v26.09-ls286` و`v26.09.1-ls287`، PHP 8.5.11) مع `mariadb:11.8.9`، والدخول الموحد عبر authentik 2026.8.3، على Docker Engine 29.8.0.