الأدلة التقنية

Nextcloud: ملفات فريقك وتقويمه وجهات اتصاله على سيرفرك

كيف تثبت Nextcloud على سيرفرك وتربطه بأجهزة فريقك وبالدخول الموحد، ثم تحميه وتحدثه دون أن تفقد بياناتك.

Nextcloud: ملفات فريقك وتقويمه وجهات اتصاله على سيرفرك

في دليل Seafile ذكرنا أن أشهر خيارين لمزامنة ملفات الفريق على سيرفرك هما Seafile وNextcloud، واخترنا Seafile لأنه يركز على الملفات وحدها، ولكن ماذا لو كان فريقك يحتاج أكثر من ذلك: ملفات تتزامن على الأجهزة، وتقويماً مشتركاً للاجتماعات، ودفتر جهات اتصال يصل إلى الهاتف، ودخولاً بحساب الشركة الموحد، وكل ذلك في واجهة واحدة؟ هنا يأتي دور Nextcloud، وهو منصة مفتوحة المصدر برخصة AGPL-3.0 تجمع هذه الأجزاء في سيرفر واحد، وهذا المقال هو الثاني في مرحلة الملفات والتخزين السحابي من خريطة الطريق.

وقد يبدو التثبيت من الوهلة الأولى سهلاً، فأمر واحد يشغل Nextcloud مع قاعدة بيانات SQLite وتفتح الواجهة بعد دقيقة، ولكن في الواقع فهذا الإعداد مناسب للتجربة فقط كما يقول توثيق متطلبات النظام، والسبب أن Nextcloud تطبيق PHP ينفذ كل طلب من جديد، فإذا لم تضع معه قاعدة بيانات حقيقية وذاكرة مؤقتة Cache وقفلاً للملفات File Locking ومهام خلفية Background Jobs منتظمة، فسوف تجده بطيئاً مع خمسة مستخدمين، وتظهر لك تحذيرات في لوحة الإدارة لا تعرف من أين جاءت. ولذلك سوف نبني التثبيت كاملاً من البداية في مختبر حقيقي، ونسجل كل مخرج كما ظهر لنا.

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

  • مكونات التثبيت: Nextcloud مع PostgreSQL وRedis وContainer للمهام الخلفية خلف Reverse Proxy.
  • الفحص الأول بالأمر occ setupchecks، والمهام الخلفية، وضبط الأداء بـ APCu وRedis وOPcache.
  • المستخدمون والمجموعات والمشاركة وصلاحياتها، مع تجربة القراءة والكتابة والرابط العام.
  • كيف تتصل تطبيقات الحاسوب والهاتف، وما هي كلمة مرور الجهاز App Password.
  • التقويم وجهات الاتصال، ثم تجربة صادقة للغة العربية والاتجاه من اليمين لليسار RTL.
  • الدخول الموحد SSO مع authentik عبر OIDC، والأخطاء الثلاثة التي ظهرت لنا.
  • التشفير من جهة السيرفر SSE والتشفير من طرف إلى طرف E2EE، ومتى تختار كل منهما.
  • فحص الأمان، والنسخ الاحتياطي واختبار الاستعادة، والتحديث إصداراً رئيسياً واحداً في كل مرة.

متى تختار Nextcloud بدل Seafile؟

إذا كانت حاجتك هي مزامنة الملفات فقط فإن Seafile أخف وأسرع مع الملفات الكثيرة، وهذا ما شرحناه في دليل Seafile، أما إذا كان الفريق يحتاج التقويم وجهات الاتصال والمحرر المكتبي في المتصفح والدخول الموحد في مكان واحد، فإن Nextcloud هو الأنسب، والسبب أن هذه الأجزاء تطبيقات Apps تعمل داخله وتشترك في نفس المستخدمين والمجموعات والصلاحيات. والثمن أن Nextcloud أثقل على السيرفر ويحتاج عناية أكثر في الأداء والتحديث كما سيأتي. والمقارنة الكاملة بين Nextcloud وSeafile وOpenCloud من المقالات المخطط لها في نفس المرحلة.

دليلSeafile: مزامنة ملفات فريقك ومشاركتها على سيرفرككيف تثبت Seafile على سيرفرك لتزامن ملفات فريقك وتشاركها بدل Dropbox وOneDrive، مع نسخة احتياطية تستطيع استعادتها.دليلالتقويم وجهات الاتصال على سيرفرك مع CalDAV وCardDAVكيف تبقى مواعيدك وجهات اتصالك متطابقة بين الهاتف والحاسوب على سيرفرك، وما الذي يعمل مع Outlook وما الذي لا يعمل.دليلCollabora Online أم ONLYOFFICE؟ حزمة مكتبية في المتصفحأي المحررين يتعامل مع النص العربي وملفات Word وExcel بشكل أفضل داخل Nextcloud، وماذا تسمح لك به رخصة كل منهما.

مما يتكون التثبيت؟

الصورة التالية تبين المكونات التي سوف نشغلها، وكيف يمر الطلب من جهاز المستخدم حتى يصل إلى البيانات:

مخطط تثبيت Nextcloud: المتصفح وتطبيق الحاسوب وتطبيقات الهاتف تتصل عبر HTTPS بالمنفذ 443 إلى Caddy، ثم إلى Container تطبيق Nextcloud الذي يعمل بـ Apache وPHP مع APCu وOPcache، ويتصل بقاعدة PostgreSQL للمستخدمين والمشاركات وفهرس الملفات، وبـ Redis للذاكرة المؤقتة وأقفال الملفات، وبالـ Volume الذي يحمل الكود والإعدادات والبيانات، وContainer المهام الخلفية يشغل cron.php كل خمس دقائق على نفس الـ Volume، وauthentik يقدم الدخول عبر OIDC
لا يصل الإنترنت إلا إلى Caddy، وبقية المكونات على شبكة Docker الداخلية
  • الـ Reverse Proxy: نستخدم Caddy لأنه يصدر شهادة TLS بنفسه، ويمكنك استخدام أي Reverse Proxy آخر كما شرحنا في دليل التطبيق خلف Reverse Proxy.
  • تطبيق Nextcloud: الـ Docker Image الرسمية nextcloud بنسخة apache، وفيها Apache وPHP والإضافات المطلوبة جاهزة.
  • قاعدة البيانات PostgreSQL: تحفظ المستخدمين والمشاركات وفهرس الملفات File Cache، أما محتوى الملفات نفسه فيبقى على القرص. ويوصي التوثيق بالإصدار 18 من PostgreSQL، ويدعم من 14 إلى 18.
  • Redis: ذاكرة مؤقتة مشتركة، والأهم أنه يحمل أقفال الملفات حتى لا يكتب طلبان على نفس الملف في نفس اللحظة.
  • Container المهام الخلفية Cron: نفس الـ Docker Image ولكنه يشغل cron.php كل خمس دقائق، وسوف نرى لماذا لا تكفي الطريقة الافتراضية.

التثبيت خطوة بخطوة

قمنا بتشغيل المختبر على Docker Engine 29.8.0، ونشرنا المنفذ على 127.0.0.1:28480 فقط وعبر HTTP، لأنه مختبر محلي لا يصل إليه أحد من الشبكة، وفي نهاية القسم سوف نذكر ما تغيره على سيرفرك الحقيقي حتى يعمل بنطاق Domain وشهادة TLS. والإصدارات كلها مثبتة Pinned، فلا تستخدم latest أبداً مع Nextcloud، والسبب أن الـ Docker Image تقوم بالترقية تلقائياً عند التشغيل، فإذا قفز الوسم latest إصدارين رئيسيين فسوف يرفض الـ Container أن يعمل كما سنرى في قسم التحديث.

ملف .env والأسرار

نبدأ بمجلد التثبيت ثم نولد كلمات المرور عشوائياً بدل أن نكتبها بأيدينا:

sudo mkdir -p /opt/nextcloud && cd /opt/nextcloud
cat > .env <<EOF
NC_TAG=35.0.1-apache
POSTGRES_PASSWORD=$(openssl rand -hex 24)
NEXTCLOUD_ADMIN_PASSWORD=$(openssl rand -hex 12)
REDIS_PASSWORD=$(openssl rand -hex 24)
EOF
chmod 600 .env

لاحظ أن NEXTCLOUD_ADMIN_PASSWORD تستخدم مرة واحدة فقط في التثبيت الأول لإنشاء حساب المدير admin، فاحفظها في مدير كلمات المرور ثم غيرها من الواجهة.

ملف compose.yaml

والملف التالي هو نفس الملف الذي شغلناه في المختبر:

name: nextcloud
services:
  db:
    image: postgres:18.6
    restart: unless-stopped
    environment:
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - db:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d nextcloud -U nextcloud"]
      interval: 10s
      retries: 10
    networks: [nc]

  redis:
    image: redis:8.10.2-alpine
    restart: unless-stopped
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
    networks: [nc]

  app:
    image: nextcloud:${NC_TAG}
    restart: unless-stopped
    depends_on:
      db: { condition: service_healthy }
      redis: { condition: service_started }
    environment:
      POSTGRES_HOST: db
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      REDIS_HOST: redis
      REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
      NEXTCLOUD_ADMIN_USER: admin
      NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
      NEXTCLOUD_TRUSTED_DOMAINS: 127.0.0.1
      TRUSTED_PROXIES: 10.89.77.0/24
      OVERWRITECLIURL: http://127.0.0.1:28480
      OVERWRITEPROTOCOL: http
      PHP_MEMORY_LIMIT: 1G
      PHP_UPLOAD_LIMIT: 10G
    volumes:
      - html:/var/www/html
    networks: [nc]

  cron:
    image: nextcloud:${NC_TAG}
    restart: unless-stopped
    entrypoint: /cron.sh
    depends_on: [app]
    volumes:
      - html:/var/www/html
    networks: [nc]

  proxy:
    image: caddy:2.11.7-alpine
    restart: unless-stopped
    ports:
      - "127.0.0.1:28480:80"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
    networks: [nc]

volumes:
  db:
  html:

networks:
  nc:
    ipam:
      config:
        - subnet: 10.89.77.0/24

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

  • الـ Volume الخاص بـ PostgreSQL مربوط على /var/lib/postgresql وليس على /var/lib/postgresql/data كما في الأدلة القديمة، والسبب أن الـ Docker Image الرسمية لـ postgres غيرت مكان البيانات ابتداء من الإصدار 18 إلى مجلد باسم الإصدار داخل /var/lib/postgresql، فإذا ربطت المسار القديم فقد تكتب البيانات داخل الـ Container وتضيع عند حذفه.
  • المتغيرات REDIS_HOST وREDIS_HOST_PASSWORD تجعل الـ Docker Image تكتب إعداد Redis بنفسها في ملف config/redis.config.php، فلا تحتاج أن تعدل config.php يدوياً، وسوف نتحقق من ذلك في قسم الأداء.
  • الـ Container المسمى cron يستخدم نفس الـ Docker Image ونفس الـ Volume، ولكن نقطة الدخول Entrypoint فيه هي /cron.sh الذي يشغل cron.php كل خمس دقائق.
  • TRUSTED_PROXIES هو نطاق شبكة Docker التي ثبتناها بالقيمة 10.89.77.0/24، حتى يثق Nextcloud بعنوان المستخدم الحقيقي الذي يرسله Caddy في الترويسة X-Forwarded-For، ولولا ذلك لظهر كل المستخدمين بعنوان Caddy نفسه، ولقامت حماية تخمين كلمات المرور Brute-force Protection بإبطاء الجميع معاً.
  • PHP_UPLOAD_LIMIT يرفع حد الرفع في PHP، وعليه أن يتوافق مع حد الـ Reverse Proxy كما شرحنا في دليل حدود رفع الملفات، وقد رفعنا في المختبر ملفاً حجمه 400 ميجابايت عبر Caddy دون مشكلة.

ملف Caddyfile

:80 {
	request_body {
		max_size 10GB
	}
	redir /.well-known/carddav /remote.php/dav/ 301
	redir /.well-known/caldav /remote.php/dav/ 301
	header Strict-Transport-Security "max-age=31536000;"
	reverse_proxy app:80
}

والسطران redir هما ما يذكره توثيق الـ Reverse Proxy لـ Caddy، والسبب أن الهواتف وبرامج التقويم تبحث عن خدمة CalDAV وCardDAV في المسار /.well-known/caldav قبل أي شيء آخر، كما شرحنا في مقال التقويم وجهات الاتصال.

التشغيل والتأكد من الحالة

docker compose up -d
docker compose exec -u www-data app php occ status

والأداة occ هي واجهة الأوامر التي تدير بها كل شيء في Nextcloud كما في توثيق occ، ويجب أن تشغلها دائماً بالمستخدم www-data صاحب الملفات، والسبب أنك لو شغلتها بالمستخدم root فقد تنشئ ملفات لا يستطيع Apache قراءتها بعد ذلك. وبعد أن ينتهي التثبيت الأول، وقد استغرق في المختبر قرابة دقيقة، سوف يكون المخرج كما يلي:

  - installed: true
  - version: 35.0.1.1
  - versionstring: 35.0.1
  - edition:
  - maintenance: false
  - needsDbUpgrade: false
  - productname: Nextcloud
  - extendedSupport: false

ما الذي تغيره على سيرفرك الحقيقي؟

على السيرفر الحقيقي اجعل Caddy يستمع على المنفذين 80 و443 بنطاق حقيقي حتى يصدر الشهادة بنفسه، وغير ثلاثة متغيرات في خدمة app:

cloud.example.com {
	request_body {
		max_size 10GB
	}
	redir /.well-known/carddav /remote.php/dav/ 301
	redir /.well-known/caldav /remote.php/dav/ 301
	header Strict-Transport-Security "max-age=31536000;"
	reverse_proxy app:80
}
      NEXTCLOUD_TRUSTED_DOMAINS: cloud.example.com
      OVERWRITECLIURL: https://cloud.example.com
      OVERWRITEPROTOCOL: https

والمتغير OVERWRITEPROTOCOL مهم جداً خلف الـ Reverse Proxy، والسبب أن Nextcloud يستقبل الطلب من Caddy عبر HTTP داخل شبكة Docker، فإذا لم تخبره أن المستخدم يراه عبر HTTPS فسوف يولد روابط تبدأ بـ http://، وهذا يكسر تسجيل الدخول في التطبيقات ويظهر تحذيراً في لوحة الإدارة كما سنرى بعد قليل.

الفحص الأول: ماذا يقول Nextcloud عن نفسه؟

في Nextcloud أداة فحص مدمجة تراجع الإعداد كله، وتجدها في الواجهة في صفحة Administration settings ثم Overview، وتشغلها من سطر الأوامر هكذا:

docker compose exec -u www-data app php occ setupchecks

والمخرج سوف يكون طويلاً، وهذه الأسطر المهمة منه كما ظهرت في المختبر:

	system:
		✓ Cron last run: Last background job execution ran seconds ago.
		✓ Transactional File Locking
		✓ Memcache: Configured
		⚠ Mimetype migrations available: One or more mimetype migrations are available. ... Use the command `occ maintenance:repair --include-expensive` to perform the migrations.
	network:
		✓ WebDAV endpoint: Your web server is properly set up to allow file synchronization over WebDAV.
		✓ Data directory protected
		✓ .well-known URLs: Your server is correctly configured to serve `.well-known` URLs.
	database:
		✓ Database missing indices: None
		✓ Database version: 18.6 (Debian 18.6-1.pgdg13+2)
		✓ Database transaction isolation level: Read committed
	security:
		ℹ HTTPS access and URLs: Your instance is generating insecure URLs. If you access your instance over HTTPS, this likely means that your instance is behind a reverse proxy and the Nextcloud `overwrite*` config values are not set correctly.
		⚠ HTTP headers: Some headers are not set correctly on your instance
- The `Strict-Transport-Security` HTTP header is not set (should be at least `15552000` seconds). For enhanced security, it is recommended to enable HSTS.
	config:
		✓ Default phone region: SA
	php:
		✓ PHP APCu configuration
		✓ PHP memory limit: 1 GB
		✓ PHP opcache: Checking from CLI, OPcache checks have been skipped.

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

  • تحذيرا HTTPS وHSTS سببهما أن المختبر يعمل عبر HTTP، فالمتصفح يتجاهل الترويسة Strict-Transport-Security على اتصال HTTP حتى لو أرسلها Caddy، وعلى السيرفر الحقيقي مع الإعداد السابق يختفي التحذيران.
  • تحذير Mimetype migrations ظهر بعد التحديث من إصدار سابق، وحله هو الأمر الذي يذكره المخرج نفسه: occ maintenance:repair --include-expensive.
  • السطر Default phone region أصبح سليماً بعد أن ضبطناه بالأمر occ config:system:set default_phone_region --value=SA، والسبب أن Nextcloud يحتاج رمز الدولة ليفهم أرقام الهاتف المكتوبة دون مفتاح دولي.

والصورة التالية تبين نفس الفحص في الواجهة، مع تحذير HTTPS باللون الأحمر لأننا في المختبر:

صفحة Security and setup warnings في لوحة إدارة Nextcloud تعرض خطأ HTTPS access and URLs لأن الموقع يعمل عبر HTTP، وتحذيرات Errors in the log وHTTP headers، وملاحظات عن AppAPI والمصادقة الثنائية وإعداد البريد
صفحة الفحص في لوحة الإدارة، وتحذيراتها في المختبر سببها العمل عبر HTTP

المهام الخلفية Background Jobs

يحتاج Nextcloud أن ينفذ مهاماً دورية لا يطلبها المستخدم، مثل تنظيف الملفات المؤقتة وإرسال الإشعارات وحذف الإصدارات القديمة ومزامنة دفتر العناوين، والتوثيق يذكر ثلاث طرق لتشغيلها:

  • AJAX: وهي الافتراضية، وفيها ينفذ مهمة واحدة مع كل صفحة يفتحها مستخدم، فإذا لم يفتح أحد الواجهة لا تنفذ أي مهمة، ولذلك يصفها التوثيق بأنها الأقل موثوقية.
  • Webcron: خدمة خارجية تطلب cron.php كل خمس دقائق، وتنفذ مهمة واحدة في كل طلب، أي 288 مهمة في اليوم كحد أقصى، وهذا يكفي فقط للنسخ الصغيرة جداً.
  • Cron: وهي الطريقة الموصى بها، حيث يشغل النظام cron.php كل خمس دقائق دون حدود Apache، وهذا ما يفعله الـ Container المسمى cron.

ووجود الـ Container لا يكفي وحده، فعليك أن تخبر Nextcloud أن يعتمد عليه، ثم تحدد نافذة الصيانة Maintenance Window التي تنفذ فيها المهام الثقيلة:

docker compose exec -u www-data app php occ background:cron
docker compose exec -u www-data app php occ config:system:set maintenance_window_start --type=integer --value=1
Set mode for background jobs to 'cron'
System config value maintenance_window_start set to integer 1

والقيمة 1 تعني أن المهام غير العاجلة تنفذ بين الساعة 1:00 و5:00 بتوقيت UTC، أي بين 4:00 و8:00 صباحاً بتوقيت السعودية، والسبب أن بعض المهام اليومية ثقيلة ولا تريدها في ساعات العمل. وللتأكد أن المهام تعمل فعلاً اطلب قائمتها مع آخر وقت نفذت فيه:

docker compose exec -u www-data app php occ background-job:list --limit=5
+--------------------+-------------------------------------------------------------------+---------------------------+----------+
| id                 | class                                                             | last_run                  | argument |
+--------------------+-------------------------------------------------------------------+---------------------------+----------+
| 138403826814185472 | OCA\UpdateNotification\BackgroundJob\UpdateAvailableNotifications | 2026-10-08T23:30:09+00:00 | null     |
| 138403826361200640 | OCA\Activity\BackgroundJob\RemoveFormerActivitySettings           | 2026-10-08T23:30:00+00:00 | null     |
| 138403815846080512 | OCA\Text\Cron\Cleanup                                             | 2026-10-09T00:20:03+00:00 | null     |
| 138403826247954432 | OCA\Activity\BackgroundJob\EmailNotification                      | 2026-10-09T00:20:03+00:00 | null     |
| 138403786632753152 | OCA\Files\BackgroundJob\CleanupDirectEditingTokens                | 2026-10-09T00:05:07+00:00 | null     |
+--------------------+-------------------------------------------------------------------+---------------------------+----------+

وفي الواجهة تجد نفس المعلومة في Basic settings، حيث يظهر وقت آخر تنفيذ باللون الأخضر:

قسم Background jobs في الإعدادات الأساسية لـ Nextcloud يعرض رسالة خضراء Last job ran 4 minutes ago، والخيار Cron (Recommended) محدداً بدل AJAX وWebcron
الطريقة Cron محددة، وآخر مهمة نفذت قبل أربع دقائق

ضبط الأداء: APCu وRedis وOPcache

كل طلب إلى Nextcloud يعني أن PHP يقرأ مئات الملفات ويسأل قاعدة البيانات عشرات المرات، ولذلك يعتمد الأداء على ثلاث طبقات من الذاكرة المؤقتة يشرحها توثيق الذاكرة المؤقتة ودليل ضبط السيرفر:

  • OPcache: يحفظ كود PHP بعد ترجمته Compiled Bytecode حتى لا يترجمه مع كل طلب، وهو مدمج في PHP.
  • APCu: ذاكرة مؤقتة محلية Local Cache داخل عملية PHP نفسها، وهي الأسرع، ولكنها لا تشارك بين أكثر من سيرفر.
  • Redis: ذاكرة مؤقتة موزعة Distributed Cache يراها كل PHP Worker، وتحمل أقفال الملفات Transactional File Locking.

والتوصية في التوثيق للمؤسسة التي تعمل على سيرفر واحد هي APCu للذاكرة المحلية وRedis لما سواها. وقد يتساءل البعض: لماذا لا نستخدم Redis لكل شيء ونرتاح؟ والإجابة أن التوثيق يذكر أن APCu أسرع من Redis في الذاكرة المحلية لأنه لا يمر عبر الشبكة، فإذا كانت الذاكرة كافية فاجعل APCu للذاكرة المحلية وRedis للأقفال. والآن نتحقق مما كتبته الـ Docker Image بنفسها:

for k in memcache.local memcache.distributed memcache.locking; do
  echo "$k = $(docker compose exec -u www-data app php occ config:system:get $k)"
done
memcache.local = \OC\Memcache\APCu
memcache.distributed = \OC\Memcache\Redis
memcache.locking = \OC\Memcache\Redis

والإعداد كما يوصي التوثيق تماماً. ولكي ترى أقفال الملفات بعينك قمنا برفع ملف حجمه 400 ميجابايت، وأثناء الرفع طلبنا من Redis المفاتيح التي فيها كلمة lock:

docker compose exec redis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning --scan --pattern '*lock*'
b149be05601915d593db82b2d5b5d2de/lockfiles/5c06021cf7b5333273c282481050cb26
b149be05601915d593db82b2d5b5d2de/lockfiles/daa41323d18bae5cf90b8e67be248256

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

أما OPcache فإعداداته تأتي من الملف opcache-recommended.ini داخل الـ Docker Image:

docker compose exec app sh -c 'cat /usr/local/etc/php/conf.d/opcache-recommended.ini; php -r "echo \"apc.shm_size=\".ini_get(\"apc.shm_size\").\"\n\";"'
opcache.enable=1
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=10000
opcache.memory_consumption=${PHP_OPCACHE_MEMORY_CONSUMPTION}
opcache.save_comments=1
opcache.revalidate_freq=60
opcache.jit=1255
opcache.jit_buffer_size=8M
apc.shm_size=32M

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

  • opcache.save_comments=1 شرط إلزامي، والسبب أن Nextcloud يقرأ تعليقات الكود أثناء التشغيل كما يذكر دليل ضبط السيرفر.
  • opcache.revalidate_freq=60 يعني أن PHP يتحقق من تغير الملفات كل 60 ثانية بدل ثانيتين، وهذا أسرع، ولكنه يعني أن تعديلك على config.php قد يتأخر دقيقة حتى يظهر، فلا تستغرب ذلك.
  • الـ JIT مفعل بالقيمتين 1255 و8M كما في التوثيق، وقيمة opcache.memory_consumption تأتي من المتغير PHP_OPCACHE_MEMORY_CONSUMPTION وقيمته الافتراضية في الـ Docker Image هي 128.
  • apc.shm_size=32M هو الحجم الافتراضي لـ APCu، ويقول التوثيق إن 32M منخفض عادة لـ Nextcloud وإن 128M نقطة بداية جيدة، فإذا كان لديك عدد كبير من المستخدمين أو التطبيقات فارفعه بملف ini إضافي تربطه داخل /usr/local/etc/php/conf.d/.

ونصيحة أخيرة في الأداء جاءت من المختبر نفسه: كل مستخدم جديد حصل على ملفات أمثلة Skeleton Files حجمها قرابة 60 ميجابايت من صور وفيديو وملفات PDF، فإذا كان لديك مئات المستخدمين فهذه مساحة ضائعة في القرص وفي النسخ الاحتياطي، ويمكنك إيقافها بجعل الإعداد skeletondirectory فارغاً كما في قائمة إعدادات config.php:

docker compose exec -u www-data app php occ config:system:set skeletondirectory --value=''

المستخدمون والمجموعات والمشاركة

لنفرض أن لديك فريقاً مالياً يحتاج مجلد سياسات يقرؤه ولا يعدله، وموظفاً من خارج الفريق لا يجب أن يراه أصلاً. نبدأ بإنشاء المجموعة والمستخدمين من سطر الأوامر، وكلمة المرور تمر عبر متغير بيئة Environment Variable حتى لا تظهر في سجل الأوامر:

docker compose exec -u www-data app php occ group:add finance
docker compose exec -u www-data -e OC_PASS="$ALICE_PASS" app php occ user:add --password-from-env --display-name="Alice" --group=finance alice
docker compose exec -u www-data -e OC_PASS="$BOB_PASS" app php occ user:add --password-from-env --display-name="Bob" bob
Created group "finance"
The account "alice" was created successfully
Display name set to "Alice"
Account "alice" added to group "finance"
The account "bob" was created successfully
Display name set to "Bob"

ثم ينشئ المدير المجلد Policies ويشاركه مع المجموعة finance بصلاحية القراءة فقط عبر واجهة المشاركة OCS Share API، وهي نفس الواجهة التي تستخدمها الواجهة الرسومية:

curl -s -u admin:"$ADMIN_PASS" -H "OCS-APIRequest: true" -H "Accept: application/json" \
  -X POST https://cloud.example.com/ocs/v2.php/apps/files_sharing/api/v1/shares \
  -d path=/Policies -d shareType=1 -d shareWith=finance -d permissions=1
200 {'id': '1', 'share_type': 1, 'share_with': 'finance', 'permissions': 1, 'path': '/Policies'}

والقيمة shareType=1 تعني المشاركة مع مجموعة، وpermissions رقم يجمع الصلاحيات كما في الجدول التالي:

الصلاحيةالقيمة
القراءة Read1
التعديل Update2
الإنشاء Create4
الحذف Delete8
إعادة المشاركة Share16
كل الصلاحيات31

والآن نختبر ما يحدث فعلاً: Alice من المجموعة تقرأ الملف، ثم تحاول أن تكتب عليه، ثم يحاول Bob الذي ليس في المجموعة أن يقرأه:

curl -s -u alice:"$ALICE_PASS" https://cloud.example.com/remote.php/dav/files/alice/Policies/leave-policy.txt
curl -s -o /dev/null -w "alice write: %{http_code}\n" -u alice:"$ALICE_PASS" -T edit.txt \
  https://cloud.example.com/remote.php/dav/files/alice/Policies/leave-policy.txt
curl -s -o /dev/null -w "bob read: %{http_code}\n" -u bob:"$BOB_PASS" \
  https://cloud.example.com/remote.php/dav/files/bob/Policies/leave-policy.txt
Leave policy v1
alice write: 403
bob read: 404

لاحظ أن Bob حصل على 404 وليس 403، أي أن Nextcloud لا يخبره حتى بوجود المجلد، وهذا هو السلوك الصحيح، والسبب أن الرد 403 كان سيكشف له أن هناك ملفاً بهذا الاسم لا يملك صلاحيته.

رابط عام بكلمة مرور وتاريخ انتهاء

وتحتاج Alice أن ترسل التقرير السنوي لمراجع حسابات من خارج الشركة، والطريقة البسيطة هي رابط عام Public Link بلا حماية، وهذه الطريقة تعني أن أي شخص يصل إليه الرابط في بريد معاد توجيهه أو في سجل محادثة يستطيع فتح الملف إلى الأبد، ولذلك فالطريقة الأنسب هي رابط بكلمة مرور وتاريخ انتهاء. وshareType=3 تعني رابطاً عاماً، ومسار الملف العربي نرسله بترميز URL Encoding:

enc() { python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1"; }
curl -s -u alice:"$ALICE_PASS" -H "OCS-APIRequest: true" -H "Accept: application/json" \
  -X POST https://cloud.example.com/ocs/v2.php/apps/files_sharing/api/v1/shares \
  -d "path=$(enc '/المالية/التقرير-السنوي-2026.txt')" -d shareType=3 -d permissions=1 \
  -d password="$LINK_PASS" -d expireDate=2026-10-16

والمخرج JSON طويل، وهذه أهم حقوله:

"id":"3","share_type":3,"permissions":17,"expiration":"2026-10-16 23:59:59","token":"yswSx2pf7wCnHoD",
"password":"redacted","url":"http:\/\/127.0.0.1:28480\/s\/yswSx2pf7wCnHoD"

لاحظ أن الصلاحية أصبحت 17 وليست 1، أي القراءة مع إعادة المشاركة، وأن كلمة المرور لا تعود في الرد وإنما تظهر كلمة redacted. ثم نجرب الرابط من خارج الحساب بلا كلمة مرور، وبكلمة المرور الصحيحة، وبكلمة خاطئة:

no password: 401
الإيرادات: 1,250,000 ريال
with password: 200
wrong password: 401

والصورتان التاليتان تبينان المشاركة في الواجهة: لوحة المشاركة الجانبية للملف، ثم الصفحة التي يراها من يفتح الرابط:

واجهة الملفات في Nextcloud باللغة العربية، مجلد المالية فيه ملفان، واللوحة الجانبية للملف التقرير السنوي مفتوحة على تبويب المشاركة وفيها مشاركات داخلية ورابط داخلي ومشاركات خارجية ورابط مشاركة للعرض فقط
لوحة المشاركة للملف، والرابط العام فيها للعرض فقط
صفحة الرابط العام في Nextcloud بعنوان هذه المشاركة محمية بكلمة مرور، وفيها حقل كلمة المرور وزر إرسال
ما يراه المراجع الخارجي عند فتح الرابط

تطبيقات الحاسوب والهاتف: كيف تتصل؟

لتطبيق Nextcloud على الحاسوب Desktop Client لأنظمة Windows وmacOS وLinux، وآخر إصدار منه وقت كتابة المقال هو v34.0.5، ويزامن مجلداً على جهازك مع حسابك عبر WebDAV، ويدعم الملفات الافتراضية Virtual Files كما في دليل الاستخدام، أي أن الملفات تظهر في المستكشف بأسمائها دون أن تنزل فعلاً حتى تفتحها، وهذا مهم إذا كان حجم ملفات الفريق أكبر من قرص الحاسوب. وعلى الهاتف تطبيق للملفات، وأما التقويم وجهات الاتصال فيمر عبر CalDAV وCardDAV كما سيأتي.

والسؤال المهم هنا أمني: هل يحفظ التطبيق كلمة مرور حسابك؟ الإجابة لا، والسبب أن التطبيقات تستخدم Login Flow v2 الذي يعطي كل جهاز كلمة مرور خاصة به App Password، والصورة التالية تبين خطواته:

مخطط Login Flow v2 في خمس خطوات: تطبيق الحاسوب يطلب POST إلى index.php/login/v2 ويحصل على رابط دخول ورمز استطلاع صالح عشرين دقيقة، ثم يفتح الرابط في المتصفح، ويطلب POST إلى login/v2/poll مرة بعد مرة، ويسجل المستخدم دخوله في المتصفح ويضغط Grant access، فيعيد السيرفر اسم الدخول وكلمة مرور خاصة بالجهاز، ثم يزامن التطبيق عبر WebDAV بها
كل جهاز يحصل على كلمة مرور خاصة به، وكلمة مرور الحساب لا تخرج من المتصفح

وقد نفذنا الخطوات بأنفسنا بالأمر curl كما يفعل التطبيق تماماً، فالطلب الأول يعطينا رابط الدخول ورمز الاستطلاع Poll Token:

curl -s -X POST -A "Nextcloud desktop (lab)" https://cloud.example.com/index.php/login/v2
{"poll":{"token":"YaSbmBGshWlrO862Hz3FlN6AO2PG53Lue7LRvy73hIWjU9QyG5fODegjegZPoJ9e9ONo51DSBe4eyEOgG87FZVFTQq38KOwIEsjNZfK6IaKHm4RJ3Bon5JAaAw0B50Km","endpoint":"http:\/\/127.0.0.1:28480\/login\/v2\/poll"},"login":"http:\/\/127.0.0.1:28480\/login\/v2\/flow\/1bCoE2moa9N9bKZCC4nTHnZFwnOaubTqGDl8EXQGQ0Vcl1kvoMnva2xCUNBgB3gUqLiVKvWsMUj1ztx1AJuWOPLa21z3udJNS5skq7jDmSvi9KqSgKfbyR13irbrUCUy"}

ثم فتحنا رابط login في المتصفح، ودخل Bob بكلمة مروره، فظهرت له صفحة تطلب الموافقة، ولاحظ أنها تذكر اسم التطبيق كما أرسله في الترويسة User-Agent:

صفحة Account access في Nextcloud تقول Currently logged in as Bob وأنك على وشك منح Nextcloud desktop (lab) الوصول إلى حسابك، مع زر Grant access
المستخدم يرى اسم التطبيق الذي يطلب الوصول قبل أن يوافق

وبعد الضغط على Grant access أعاد طلب الاستطلاع كلمة المرور الخاصة بالجهاز، وقد أخفينا أولها هنا:

curl -s -X POST https://cloud.example.com/login/v2/poll -d "token=$POLL_TOKEN"
{"server":"http:\/\/127.0.0.1:28480","loginName":"bob","appPassword":"XXXXXXLTBG7Fnb6W05lIZ3e9G1lzAeUvE6NtuqBTZZF2qplvOthz7RQ365nfdFpk7pWFMIFZ"}

والفائدة الحقيقية تظهر عندما يضيع حاسوب أحد الموظفين، فأنت لا تغير كلمة مروره ولا تخرجه من بقية أجهزته، وإنما تلغي كلمة مرور ذلك الجهاز وحده. وقد نفذنا ذلك في المختبر بكلمة مرور جهاز أنشأناها لحساب Bob باسم bob-laptop (lab):

docker compose exec -u www-data app php occ user:auth-tokens:list bob
docker compose exec -u www-data app php occ user:auth-tokens:delete bob 12
| 12 | bob-laptop (lab) | 2026-10-09T00:38:55+00:00 | permanent | filesystem |
PROPFIND with app password: 207
PROPFIND after revoke: 401

أي أن نفس الطلب بنفس كلمة مرور الجهاز نجح قبل الإلغاء بالرمز 207، ورفض بعده بالرمز 401.

التقويم وجهات الاتصال

شرحنا البروتوكولين CalDAV وCardDAV وكيف تتزامن الأجهزة بهما في مقال التقويم وجهات الاتصال، فلن نكرر ذلك هنا، والذي يخص Nextcloud أن الخادم Server لهذين البروتوكولين مدمج فيه، وأما الواجهة فتأتي من تطبيقين تثبتهما من متجر التطبيقات App Store:

docker compose exec -u www-data app php occ app:install calendar
docker compose exec -u www-data app php occ app:install contacts
calendar 6.6.2 installed
calendar enabled
contacts 8.9.1 installed
contacts enabled

ثم أنشأنا تقويماً لـ Alice باسم finance، ورفعنا إليه موعداً عربياً بملف iCalendar عبر الطلب PUT كما يفعل أي هاتف:

docker compose exec -u www-data app php occ dav:create-calendar alice finance
curl -s -o /dev/null -w "PUT event %{http_code}\n" -u alice:"$ALICE_PASS" -T ev.ics \
  -H "Content-Type: text/calendar; charset=utf-8" \
  https://cloud.example.com/remote.php/dav/calendars/alice/finance/budget-review.ics
curl -s -o /dev/null -w "well-known caldav: %{http_code} -> %{redirect_url}\n" https://cloud.example.com/.well-known/caldav
PUT event 201
well-known caldav: 301 -> http://127.0.0.1:28480/remote.php/dav/

والملف ev.ics فيه موعد يوم 12 أكتوبر 2026 الساعة 10:00 بتوقيت Asia/Riyadh بعنوان «مراجعة ميزانية الربع الرابع»، وبنفس الطريقة أضفنا جهة اتصال عربية بملف vCard إلى المسار addressbooks/users/alice/contacts/. والصورتان التاليتان تبينان النتيجة في الواجهة العربية:

تطبيق التقويم في Nextcloud باللغة العربية بعرض الأسبوع 42 من 2026، والموعد مراجعة ميزانية الربع يوم الإثنين 12 أكتوبر من 10:00 إلى 11:00، والأرقام بالأرقام الهندية، وفي القائمة الجانبية تقاويم خاص وأعياد ميلاد جهات الاتصال وfinance مع عبارات إنجليزية لم تترجم مثل My calendars وMeeting proposals
الموعد الذي رفعناه عبر CalDAV يظهر في التقويم، مع عبارات لم تترجم بعد
تطبيق جهات الاتصال في Nextcloud باللغة العربية يعرض جهة الاتصال سارة المحاسبة من الإدارة المالية برقم هاتف وبريد عمل، وفي القائمة دفاتر العناوين وجهات الاتصال والحسابات
جهة الاتصال التي رفعناها عبر CardDAV كما تظهر في تطبيق جهات الاتصال

وإذا كان الفريق يحتاج تحرير ملفات Word وExcel داخل المتصفح فهذا يحتاج محرراً منفصلاً تربطه بـ Nextcloud، وقد قارنا الخيارين في مقال Collabora Online أم ONLYOFFICE.

العربية والاتجاه من اليمين لليسار RTL: تجربة صادقة

ضبطنا لغة Alice بالأمر occ user:setting alice core lang ar ومنطقتها بالأمر occ user:setting alice core locale ar_SA، ثم فتحنا الواجهة ودققنا في كل صفحة، والصورة التالية تبين مجلد الملفات:

واجهة الملفات في Nextcloud باللغة العربية من اليمين لليسار، مجلد المالية فيه ملفا إقرار ضريبي والتقرير السنوي 2026، والقائمة الجانبية بالعربية مع عبارات إنجليزية مثل Folder tree وSearch here في مربع البحث
الواجهة من اليمين لليسار، مع عبارات قليلة لم تترجم

وهذه النتيجة كما وجدناها، ما يعمل وما لا يعمل:

  • الاتجاه سليم: القوائم والأزرار والجداول تنقلب من اليمين لليسار بشكل صحيح، والأسماء العربية للمجلدات والملفات تحفظ وتعرض دون مشاكل، وحتى مجلد القوالب Templates أنشأه Nextcloud لـ Alice باسم «القوالب» لأن لغتها العربية.
  • الترجمة شبه كاملة: أغلب الواجهة بالعربية، ولكن بقيت عبارات إنجليزية مثل My calendars وMeeting proposals في التقويم وFolder tree وSearch here في الملفات وApps, files, messages, and more في البحث العام، والسبب أنها عبارات جديدة لم يترجمها المتطوعون بعد.
  • الأرقام: مع المنطقة ar_SA تظهر التواريخ والساعات في التقويم بالأرقام الهندية (١٢/١٠/٢٠٢٦)، وإذا كان فريقك يفضل الأرقام العربية الغربية فاختر منطقة أخرى من إعدادات المستخدم.
  • البحث في أسماء الملفات لا يوحد الهمزات: بحثنا عبر واجهة البحث عن «تقرير» فوجدنا الملف، وعن «إقرار» فوجدنا الملف، ولكن البحث عن «اقرار» دون همزة أعاد صفر نتائج، وهذا مهم لأن كثيراً من الناس يكتبون دون همزات.
  • معاينة الملف النصي تفصل الحروف: الصورة المصغرة Preview التي يولدها Nextcloud لملف نصي عربي تعرض الحروف منفصلة غير متصلة، كما تراها في أعلى لوحة المشاركة في الصورة السابقة، وأما فتح الملف نفسه فيعرض النص سليماً.
  • زر الدخول الموحد: النص «تسجيل الدخول بـ authentik» يظهر بترتيب مقلوب لأن اسم المزود إنجليزي في نهاية جملة عربية، كما سترى في صورة قسم الدخول الموحد، وهذه مشكلة اتجاه نص Bidi وليست مشكلة ترجمة.
== تقرير
1 ['التقرير-السنوي-2026.txt']
== اقرار
0 []
== إقرار
1 ['إقرار-ضريبي.txt']

والمخرج أعلاه من واجهة البحث /ocs/v2.php/search/providers/files/search?term=، والخلاصة أن العربية في Nextcloud صالحة للعمل اليومي، ولكن علم فريقك أن يبحث بالهمزة كما كتبت في اسم الملف، أو اتفق معهم على تسمية الملفات دون همزات.

الدخول الموحد SSO مع authentik

إذا كان لديك authentik يدير حسابات الشركة، فالأنسب أن يدخل الموظفون إلى Nextcloud بنفس الحساب، فإذا غادر أحدهم الشركة توقف حسابه في مكان واحد. والربط يتم عبر OpenID Connect الذي شرحناه في مقال OAuth وOpenID Connect، وفي Nextcloud يقوم بذلك التطبيق الرسمي user_oidc.

الإعداد في authentik

أنشئ في authentik تطبيقاً Application ومزوداً Provider من نوع OAuth2/OpenID بنفس الخطوات التي شرحناها في دليل ربط Gitea مع authentik، مع هذه القيم:

  • نوع العميل Client Type هو Confidential، والمعرف Client ID هو nextcloud.
  • رابط العودة Redirect URI هو https://cloud.example.com/apps/user_oidc/code بمطابقة تامة Strict.
  • النطاقات Scopes هي openid وemail وprofile، ونوع الدخول Grant Types يشمل authorization_code.
  • معرف المستخدم Subject مبني على اسم المستخدم، حتى يكون اسم الحساب في Nextcloud مفهوماً وليس رقماً طويلاً.

الإعداد في Nextcloud

docker compose exec -u www-data app php occ app:install user_oidc
docker compose exec -u www-data -e OIDC_SECRET="$OIDC_SECRET" app php occ user_oidc:provider authentik \
  --clientid=nextcloud --clientsecret-env=OIDC_SECRET \
  --discoveryuri=https://auth.example.com/application/o/nextcloud/.well-known/openid-configuration \
  --unique-uid=0 --mapping-uid=preferred_username
docker compose exec -u www-data app php occ user_oidc:provider
Client secret loaded from environment variable "OIDC_SECRET"
+----+------------+---------------------------------------------------------------------------------+----------------------+-----------+
| ID | Identifier | Discovery endpoint                                                              | End session endpoint | Client ID |
+----+------------+---------------------------------------------------------------------------------+----------------------+-----------+
| 1  | authentik  | http://127.0.0.1:28490/application/o/nextcloud/.well-known/openid-configuration |                      | nextcloud |
+----+------------+---------------------------------------------------------------------------------+----------------------+-----------+

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

  • --clientsecret-env يقرأ السر من متغير بيئة بدل أن تكتبه في سطر الأوامر فيبقى في سجل الـ Shell.
  • --unique-uid=0 مع --mapping-uid=preferred_username يجعل اسم الحساب في Nextcloud هو اسم المستخدم في authentik نفسه، والقيمة الافتراضية 1 تجعل الاسم هاشاً لا يقرؤه أحد.
  • يبقى الدخول بكلمة المرور المحلية متاحاً بجانب زر authentik، وهذا مفيد لحساب المدير إذا تعطل authentik، فإذا أردت أن يدخل الجميع عبر authentik وحده فاجعل الإعداد allow_multiple_user_backends في التطبيق user_oidc بالقيمة 0.

ثلاثة أخطاء ظهرت لنا قبل أن ينجح الدخول

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

  1. «You must access Nextcloud with HTTPS to use OpenID Connect.»: التطبيق user_oidc يرفض العمل عبر HTTP، وهذا صحيح في الإنتاج، وعلى سيرفرك مع HTTPS لن ترى الرسالة. وفي المختبر المحلي فقط سمحنا بـ HTTP بالإعداد occ config:app:set user_oidc allow_insecure_http --value=1 --type=boolean --lazy، فلا تقم بذلك على سيرفر حقيقي.
  2. «Could not reach the OpenID Connect provider»: وفي سجل Nextcloud كان السبب الحقيقي Host "127.0.0.1" violates local access rules، والسبب أن Nextcloud يمنع افتراضياً الاتصال بالسيرفرات ذات العناوين المحلية كما في قائمة إعدادات config.php، حتى لا يستخدمه أحد للوصول إلى خدمات داخلية في شبكتك، فإذا كان authentik على نفس السيرفر أو في شبكة داخلية فسوف تحتاج occ config:system:set allow_local_remote_servers --value=true --type=boolean، وهذا يفتح الاتصال بكل العناوين الداخلية فقرره وأنت تعرف ذلك.
  3. «The request is otherwise malformed»: جاءت من authentik، وفي سجله السبب Invalid grant_type for provider، فقد أنشأنا المزود عبر واجهة API في authentik فبقي الحقل grant_types فارغاً، وبعد إضافة authorization_code وrefresh_token نجح الدخول.

والصور التالية تبين الدخول بعد الإصلاح: زر authentik في صفحة الدخول، ثم صفحة authentik التي تقول إنك تدخل إلى Nextcloud، ثم الصفحة الرئيسية لحساب Sara الذي أنشأه Nextcloud تلقائياً عند أول دخول:

صفحة تسجيل الدخول إلى Nextcloud بالعربية فيها حقلا اسم الحساب وكلمة المرور وزر تسجيل الدخول، وفي الأسفل زر authentik تسجيل الدخول بـ يظهر بترتيب مقلوب
زر الدخول عبر authentik أسفل الدخول المحلي، ونصه مقلوب الترتيب
صفحة دخول authentik تقول Welcome to authentik وLog in to continue to Nextcloud مع حقل البريد الإلكتروني أو اسم المستخدم بالعربية وزر تسجيل الدخول
authentik يطلب اسم المستخدم ويذكر التطبيق الذي ستدخل إليه
الصفحة الرئيسية في Nextcloud بالعربية تقول أهلاً Sara مع لوحتي الأحداث القادمة والملفات الموصى بها
حساب Sara أنشئ تلقائياً عند أول دخول، بلغة المتصفح العربية

وللتأكد من جهة السيرفر:

docker compose exec -u www-data app php occ user:info sara
  - user_id: sara
  - display_name: Sara
  - email: [email protected]
  - enabled: true
  - first_seen: 2026-10-09T00:12:24+00:00
  - backend: user_oidc

لاحظ أن backend هو user_oidc، أي أن هذا الحساب لا يملك كلمة مرور في Nextcloud أصلاً. وقد يتساءل البعض: كيف يدخل تطبيق الحاسوب إذن؟ والإجابة أن Login Flow v2 الذي رأيناه يفتح المتصفح، والمتصفح يمر على authentik كأي دخول عادي، ثم يحصل التطبيق على كلمة مرور الجهاز كما سبق.

التشفير: SSE أم E2EE؟

في Nextcloud نوعان من التشفير يخلط الناس بينهما كثيراً، ويضع توثيق التشفير من جهة السيرفر جدولاً واضحاً للفرق، والصورة التالية تلخصه مع ما رأيناه في المختبر:

مخطط من جزأين: على اليمين التشفير من جهة السيرفر SSE حيث يصل الملف مقروءاً عبر TLS ويشفره السيرفر بمفاتيح يحتفظ بها، وعلى القرص يظهر الملف salaries-october.txt بترويسة HBEGIN وAES-256-CTR وحجمه 8320 بايت بدل 32، ومزاياه حماية التخزين الخارجي وبقاء كل الميزات، وعيوبه أن المدير والمخترق على السيرفر يقرآن الملفات وأن الأسماء تبقى مكشوفة، وعلى اليسار التشفير من طرف إلى طرف E2EE حيث يشفر الجهاز قبل الرفع ولا يخزن السيرفر إلا ما لا يستطيع قراءته، والمجلد يحدد مشفراً من تطبيق الحاسوب أو الهاتف ويجب أن يكون فارغاً، وعيوبه أن الويب للعرض والتنزيل فقط وأن التقويم وجهات الاتصال غير مشمولة
SSE يحمي البيانات من مزود التخزين، وE2EE يحميها من السيرفر نفسه

التشفير من جهة السيرفر SSE كما رأيناه على القرص

فعلنا التشفير بالمفتاح الرئيسي Master Key، وهو الوضع الافتراضي الذي يوصي به التوثيق لأغلب الحالات:

docker compose exec -u www-data app php occ app:enable encryption
docker compose exec -u www-data app php occ encryption:enable
docker compose exec -u www-data app php occ encryption:status
encryption 3.0.0-dev.0 enabled
Encryption enabled

Default module: OC_DEFAULT_MODULE
  - enabled: true
  - defaultModule: OC_DEFAULT_MODULE

ثم رفع Bob ملفاً نصياً حجمه 32 بايت باسم salaries-october.txt، ونظرنا إليه على القرص مباشرة:

docker compose exec app sh -c 'ls -la /var/www/html/data/bob/files/ | grep salaries; head -c 120 /var/www/html/data/bob/files/salaries-october.txt'
-rw-r--r-- 1 www-data www-data     8320 Oct  9 00:32 salaries-october.txt
HBEGIN:oc_encryption_module:OC_DEFAULT_MODULE:cipher:AES-256-CTR:signed:true:useLegacyFileKey:false:encoding:binary:HEND

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

  • المحتوى مشفر بـ AES-256-CTR مع توقيع، فمن يسرق نسخة من القرص لا يقرأ الرواتب.
  • اسم الملف salaries-october.txt مكشوف تماماً، وهذا ما يقوله التوثيق: SSE يشفر المحتوى فقط ولا يشفر أسماء الملفات والمجلدات.
  • الملف الذي حجمه 32 بايت أصبح 8320 بايت على القرص، فللملفات الصغيرة حجم إضافي ثابت، وأما الملفات الكبيرة فالزيادة فيها صغيرة نسبياً.
  • الملفات التي كانت موجودة قبل التفعيل بقيت كما هي غير مشفرة، فقد قرأنا ملف leave-policy.txt القديم من القرص كنص عادي، ولتشفيرها تحتاج الأمر occ encryption:encrypt-all.
  • عندما طلب Bob الملف عبر WebDAV حصل عليه مقروءاً، والسبب أن السيرفر يفك التشفير بنفسه، وهذا يعني أن مدير السيرفر، ومن يخترق السيرفر، يستطيع قراءة الملفات أيضاً.

ولذلك يقول التوثيق إن الهدف الأساسي من SSE هو حماية الملفات على تخزين خارجي External Storage مثل S3 أو SMB لا تثق بمزوده، وإن التخزين المحلي تحميه أدوات تشفير القرص مثل LUKS بشكل أفضل، لأنها تحمي من سرقة القرص دون أن تكلفك أي ميزة. وانتبه لأمرين قبل التفعيل: الأول أن ضياع المفاتيح في data/files_encryption أو قيمة secret في config.php يعني ضياع الملفات نهائياً، فهي جزء من النسخة الاحتياطية، والثاني أن وضع مفاتيح المستخدمين User Keys لا يعمل مع الدخول الموحد ولا مع كلمات مرور الأجهزة كما يذكر التوثيق، فإذا كنت تستخدم authentik فابق على المفتاح الرئيسي.

⚠️
لا يمكن إيقاف SSE من الواجهة بعد تفعيله، والطريقة الوحيدة هي الأمر occ encryption:decrypt-all الذي يفك تشفير كل الملفات، فلا تقم بتفعيله على سيرفر فيه بيانات حقيقية قبل أن تجربه على نسخة، وقبل أن تنسخ المفاتيح احتياطياً.

التشفير من طرف إلى طرف E2EE

إذا كان المطلوب ألا يقرأ مدير السيرفر نفسه ملفات قسم معين، مثل ملفات الموارد البشرية، فهذا هو E2EE من التطبيق end_to_end_encryption، وفيه يشفر تطبيق الحاسوب أو الهاتف الملف قبل رفعه، فلا يصل إلى السيرفر إلا ما لا يستطيع قراءته. ولكن له ثمن واضح يذكره التوثيق: تحدد المجلد مشفراً من تطبيق الحاسوب أو الهاتف ويجب أن يكون فارغاً، وفي واجهة الويب تستطيع فقط عرض الملفات المشفرة وتنزيلها دون تحريرها، والتشفير يشمل الملفات فقط وليس التقويم وجهات الاتصال، وكل أجهزة المستخدم يجب أن تستخدم نفس العبارة السرية Mnemonic. ولذلك لا تجعله افتراضياً للجميع، وإنما للمجلدات الحساسة فقط.

فحص الأمان وتقوية السيرفر

بعد أن يصبح السيرفر متاحاً على الإنترنت بنطاقه، افتح Nextcloud Security Scan واكتب النطاق، فيفحص من الخارج إصدار Nextcloud والترويسات الأمنية وبعض الإعدادات المعروفة ويعطيك تقييماً، ولم نستطع تشغيله على المختبر لأنه على 127.0.0.1 ولا يصل إليه الفحص الخارجي، ولذلك اعتمدنا على occ setupchecks الذي يغطي الجزء الداخلي. ومع الفحصين راجع دليل تقوية السيرفر، وأهم ما فيه:

  • اجعل المصادقة الثنائية Two-factor Authentication إلزامية للمدير على الأقل، وقد ذكرها الفحص في المختبر بأنها متاحة وغير مفروضة.
  • تأكد أن مجلد البيانات غير متاح من الويب، وهذا ما يفحصه السطر Data directory protected.
  • أبق حماية تخمين كلمات المرور Brute-force Protection مفعلة، ولذلك تحتاج TRUSTED_PROXIES صحيحاً كما ذكرنا، حتى يرى Nextcloud عنوان المهاجم وليس عنوان Caddy.
  • حدث Nextcloud وتطبيقاته بانتظام، فالفحص الخارجي يعطي تقييماً منخفضاً لأي إصدار فيه ثغرات معروفة.

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

تطبيق المزامنة ليس نسخة احتياطية، فهو ينقل الحذف الخاطئ إلى كل الأجهزة كما ينقل التعديل الصحيح، وقد شرحنا القاعدة العامة في دليل النسخ الاحتياطي بقاعدة 3-2-1. وفي Nextcloud يذكر توثيق النسخ الاحتياطي أربعة أشياء تنسخها معاً: مجلد الإعدادات config، ومجلد البيانات data، ومجلد السمات themes، وقاعدة البيانات، مع تفعيل وضع الصيانة Maintenance Mode أثناء النسخ، والسبب أن نسخ قاعدة البيانات والملفات في لحظتين مختلفتين بينما المستخدمون يرفعون الملفات قد يعطيك قاعدة تشير إلى ملفات غير موجودة في النسخة:

mkdir -p backup
docker compose exec -u www-data app php occ maintenance:mode --on
docker compose exec -T db pg_dump -U nextcloud -Fc nextcloud > backup/nextcloud-db.dump
docker compose exec -T app tar czf - -C /var/www/html config data themes > backup/nextcloud-files.tar.gz
docker compose exec -u www-data app php occ maintenance:mode --off
ls -la backup
Maintenance mode enabled
Maintenance mode disabled
1530207 nextcloud-db.dump
313202393 nextcloud-files.tar.gz

واستغرق النسخ كله في المختبر دقيقتين ونصفاً، وهي المدة التي لا يستطيع فيها المستخدمون الوصول، ولذلك ضعه في المهام المجدولة في وقت متأخر من الليل، وإذا كبرت البيانات فانسخ مجلد data نسخاً تزايدياً بأداة مثل restic أو borg كما في مقارنة أدوات النسخ الاحتياطي. والنسخة التي لم تختبر استعادتها ليست نسخة، ولذلك استعدنا ملف قاعدة البيانات في قاعدة مؤقتة وقارنا عدد الملفات والمشاركات والمواعيد مع الأصل:

docker compose exec db createdb -U nextcloud restoretest
docker compose exec -T db pg_restore -U nextcloud -d restoretest --no-owner < backup/nextcloud-db.dump
for d in nextcloud restoretest; do
  echo "$d: $(docker compose exec db psql -U nextcloud -d $d -tAc "select (select count(*) from oc_filecache)||' files, '||(select count(*) from oc_share)||' shares, '||(select count(*) from oc_calendarobjects)||' events'")"
done
docker compose exec db dropdb -U nextcloud restoretest
nextcloud: 337 files, 3 shares, 9 events
restoretest: 337 files, 3 shares, 9 events

والأرقام متطابقة. وخطوات الاستعادة الكاملة على سيرفر جديد في توثيق الاستعادة: تعيد المجلدات الثلاثة، ثم قاعدة البيانات، ثم تشغل occ maintenance:data-fingerprint حتى تعرف تطبيقات المزامنة أن البيانات استعيدت فلا تحذف ملفات جديدة من الأجهزة ظناً منها أنها حذفت من السيرفر.

التحديث: إصدار رئيسي واحد في كل مرة

يقول توثيق التحديث بوضوح إنك لا تستطيع تخطي الإصدارات الرئيسية Major Releases، وإنك تحدث إلى آخر إصدار فرعي في إصدارك الحالي ثم إلى الإصدار الرئيسي التالي، وإن الرجوع إلى إصدار أقدم Downgrade غير مدعوم وقد يتلف البيانات. ولكي نرى ذلك بأعيننا بدأنا المختبر نفسه بالإصدار 33.0.9، ثم غيرنا NC_TAG مباشرة إلى 35.0.1، والطريقة البسيطة هي أن تفعل هذا على سيرفرك وتنتظر، وهذا ما حدث:

sed -i 's/^NC_TAG=.*/NC_TAG=35.0.1-apache/' .env
docker compose up -d app cron
docker compose logs app
Initializing nextcloud 35.0.1.1 ...
Can't start Nextcloud because upgrading from 33.0.9.1 to 35.0.1.1 is not supported.
It is only possible to upgrade one major version at a time. For example, if you want to upgrade from version 14 to 16, you will have to upgrade from version 14 to 15, then from 15 to 16.

ورفض الـ Container أن يعمل ودخل في حلقة إعادة التشغيل Restarting، وهذا من حسن الحظ لأن الفحص موجود في نقطة دخول الـ Docker Image قبل أن يلمس أي بيانات. والطريقة الصحيحة هي أن تأخذ نسخة احتياطية أولاً، ثم تمر على 34 قبل 35:

sed -i 's/^NC_TAG=.*/NC_TAG=34.0.4-apache/' .env
docker compose up -d app cron
docker compose logs -f app
Initializing nextcloud 34.0.4.1 ...
Upgrading nextcloud from 33.0.9.1 ...
Setting log level to debug
Turned on maintenance mode
Updating database schema
Updated database
Updating <federation> ...
Updated <federation> to 1.24.0
...
Starting code integrity check...
Finished code integrity check
Update successful
Turned off maintenance mode
Resetting log level

وقبل الانتقال إلى الإصدار الرئيسي التالي يطلب التوثيق أن تنتظر انتهاء الترحيلات الخلفية Background Migrations بتشغيل cron.php مرتين أو ثلاثاً، والسبب أن بعض تغييرات قاعدة البيانات تجدول كمهام خلفية بعد التحديث، فإذا قفزت قبل أن تنتهي فقد تبدأ الترقية التالية على قاعدة لم تكتمل:

for i in 1 2 3; do docker compose exec -u www-data app php -f /var/www/html/cron.php; done
sed -i 's/^NC_TAG=.*/NC_TAG=35.0.1-apache/' .env
docker compose up -d app cron
Initializing nextcloud 35.0.1.1 ...
Upgrading nextcloud from 34.0.4.1 ...
...
Update successful
Turned off maintenance mode
Resetting log level
The database schema does not match what is expected for the installed version:
  - missing table 'oc_federated_invites'
  - oc_taskprocessing_tasks: missing index 'taskp_status_type_upd'
Run "occ db:schema:check -v" for details.

واستغرقت الترقية من 34 إلى 35 قرابة دقيقة واحدة. ولاحظ الرسالة الأخيرة، فالفهرس الناقص يضيفه الأمر الذي يذكره التوثيق في خطوات ما بعد التحديث:

docker compose exec -u www-data app php occ db:add-missing-indices
docker compose exec -u www-data app php occ db:add-missing-columns
docker compose exec -u www-data app php occ db:add-missing-primary-keys
Adding additional taskp_status_type_upd index to the oc_taskprocessing_tasks table, this can take some time...
oc_taskprocessing_tasks table updated successfully.
Done.
Done.

وأما الجدول oc_federated_invites فبقي في نتيجة occ db:schema:check، وقد بحثنا في كود التطبيق cloud_federation_api فوجدنا خطوة إصلاح باسم DropFederatedInvitesTable تحذف هذا الجدول عمداً إذا كان فارغاً، أي أن غيابه مقصود في هذا الإصدار، ولم يظهر في occ setupchecks أي تحذير عن قاعدة البيانات بعد إضافة الفهرس. وبعد كل تحديث شغل occ setupchecks مرة أخرى، ولا تحدث يوم الخميس قبل إجازتك.

💡
لاحظ أن الـ Docker Image للإصدار 33.0.9 جاءت بـ PHP 8.4.26، وأن 34.0.4 و35.0.1 جاءتا بـ PHP 8.5.11، فالتحديث الرئيسي يغير إصدار PHP أيضاً، وهذا سبب إضافي لتجربته على نسخة قبل الإنتاج إذا كان لديك تطبيقات من طرف ثالث.

تدريب عملي

  1. أنشئ مجلداً مشتركاً مع مجموعة بالصلاحية 5 (القراءة مع الإنشاء)، ثم جرب أن يرفع عضو من المجموعة ملفاً جديداً وأن يعدل ملفاً موجوداً، وفسر لماذا نجح أحدهما وفشل الآخر.
  2. شغل Login Flow v2 بالأمر curl كما فعلنا، ثم الغ كلمة مرور الجهاز بالأمر occ user:auth-tokens:delete، وتأكد أن الطلب التالي يرد بالرمز 401.

الخلاصة

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

  • Nextcloud منصة برخصة AGPL-3.0 تجمع الملفات والتقويم وجهات الاتصال والمشاركة في سيرفر واحد، وهو الأنسب عندما يحتاج الفريق أكثر من مزامنة الملفات.
  • التثبيت الجاد يحتاج PostgreSQL وRedis وContainer للمهام الخلفية بالطريقة Cron خلف Reverse Proxy، مع TRUSTED_PROXIES وOVERWRITEPROTOCOL صحيحين.
  • APCu للذاكرة المحلية وRedis للذاكرة الموزعة وأقفال الملفات، وOPcache مع save_comments=1، وocc setupchecks يخبرك بما ينقص.
  • الصلاحيات أرقام تجمعها، والروابط العامة تحتاج كلمة مرور وتاريخ انتهاء، وكل جهاز يحصل على كلمة مرور خاصة به تلغيها وحدها عند ضياعه.
  • العربية صالحة للعمل اليومي مع عبارات لم تترجم، وبحث لا يوحد الهمزات، ومعاينات نصية تفصل الحروف.
  • الدخول الموحد مع authentik يحتاج HTTPS، وقد يحتاج allow_local_remote_servers إذا كان authentik في شبكة داخلية.
  • SSE يحمي التخزين الخارجي ولا يحميك من السيرفر نفسه، وE2EE يحميك من السيرفر بثمن في الميزات، وتشفير القرص يكفي للتخزين المحلي.
  • انسخ الإعدادات والبيانات وقاعدة البيانات في وضع الصيانة، واختبر الاستعادة، وحدث إصداراً رئيسياً واحداً في كل مرة.
دليلSeafile: مزامنة ملفات فريقك ومشاركتها على سيرفرككيف تثبت Seafile على سيرفرك لتزامن ملفات فريقك وتشاركها بدل Dropbox وOneDrive، مع نسخة احتياطية تستطيع استعادتها.دليلتثبيت authentik للدخول الموحد SSO إلى تطبيقاتككيف تثبت authentik حتى يدخل كل موظف إلى تطبيقات الفريق بحساب واحد، مع فرض المصادقة الثنائية على الجميع.دليلالتقويم وجهات الاتصال على سيرفرك مع CalDAV وCardDAVكيف تبقى مواعيدك وجهات اتصالك متطابقة بين الهاتف والحاسوب على سيرفرك، وما الذي يعمل مع Outlook وما الذي لا يعمل.دليلالنسخ الاحتياطي بقاعدة 3-2-1 واختبار الاستعادةكيف تحدد كم بيانات تقبل أن تخسر، وتبني نسخاً لا يستطيع المخترق حذفها، وتتأكد أنك تستطيع استعادتها فعلاً.

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

  • أكتوبر 2026: كتابة المقال، واختبار كل الخطوات على Docker Engine 29.8.0 مع Nextcloud 35.0.1 (nextcloud:35.0.1-apache) بعد الترقية من 33.0.9 عبر 34.0.4، وPostgreSQL 18.6 وRedis 8.10.2 وCaddy 2.11.7، والتطبيقات calendar 6.6.2 وcontacts 8.9.1 وuser_oidc 8.11.0، وauthentik 2026.8.3.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.