في دليل 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، وماذا تسمح لك به رخصة كل منهما.مما يتكون التثبيت؟
الصورة التالية تبين المكونات التي سوف نشغلها، وكيف يمر الطلب من جهاز المستخدم حتى يصل إلى البيانات:

- الـ 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 باللون الأحمر لأننا في المختبر:

المهام الخلفية 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=1Set 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، حيث يظهر وقت آخر تنفيذ باللون الأخضر:

ضبط الأداء: 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)"
donememcache.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" bobCreated 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=1200 {'id': '1', 'share_type': 1, 'share_with': 'finance', 'permissions': 1, 'path': '/Policies'}والقيمة shareType=1 تعني المشاركة مع مجموعة، وpermissions رقم يجمع الصلاحيات كما في الجدول التالي:
| الصلاحية | القيمة |
|---|---|
| القراءة Read | 1 |
| التعديل Update | 2 |
| الإنشاء Create | 4 |
| الحذف Delete | 8 |
| إعادة المشاركة Share | 16 |
| كل الصلاحيات | 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.txtLeave 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 على الحاسوب Desktop Client لأنظمة Windows وmacOS وLinux، وآخر إصدار منه وقت كتابة المقال هو v34.0.5، ويزامن مجلداً على جهازك مع حسابك عبر WebDAV، ويدعم الملفات الافتراضية Virtual Files كما في دليل الاستخدام، أي أن الملفات تظهر في المستكشف بأسمائها دون أن تنزل فعلاً حتى تفتحها، وهذا مهم إذا كان حجم ملفات الفريق أكبر من قرص الحاسوب. وعلى الهاتف تطبيق للملفات، وأما التقويم وجهات الاتصال فيمر عبر CalDAV وCardDAV كما سيأتي.
والسؤال المهم هنا أمني: هل يحفظ التطبيق كلمة مرور حسابك؟ الإجابة لا، والسبب أن التطبيقات تستخدم Login Flow v2 الذي يعطي كل جهاز كلمة مرور خاصة به App Password، والصورة التالية تبين خطواته:

وقد نفذنا الخطوات بأنفسنا بالأمر 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:

وبعد الضغط على 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 contactscalendar 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/caldavPUT 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/. والصورتان التاليتان تبينان النتيجة في الواجهة العربية:


وإذا كان الفريق يحتاج تحرير ملفات Word وExcel داخل المتصفح فهذا يحتاج محرراً منفصلاً تربطه بـ Nextcloud، وقد قارنا الخيارين في مقال Collabora Online أم ONLYOFFICE.
العربية والاتجاه من اليمين لليسار RTL: تجربة صادقة
ضبطنا لغة Alice بالأمر occ user:setting alice core lang ar ومنطقتها بالأمر occ user:setting alice core locale ar_SA، ثم فتحنا الواجهة ودققنا في كل صفحة، والصورة التالية تبين مجلد الملفات:

وهذه النتيجة كما وجدناها، ما يعمل وما لا يعمل:
- الاتجاه سليم: القوائم والأزرار والجداول تنقلب من اليمين لليسار بشكل صحيح، والأسماء العربية للمجلدات والملفات تحفظ وتعرض دون مشاكل، وحتى مجلد القوالب 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:providerClient 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.
ثلاثة أخطاء ظهرت لنا قبل أن ينجح الدخول
لم ينجح الدخول من المرة الأولى، وكل خطأ منها قد يقابلك على سيرفرك:
- «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، فلا تقم بذلك على سيرفر حقيقي. - «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، وهذا يفتح الاتصال بكل العناوين الداخلية فقرره وأنت تعرف ذلك. - «The request is otherwise malformed»: جاءت من authentik، وفي سجله السبب
Invalid grant_type for provider، فقد أنشأنا المزود عبر واجهة API في authentik فبقي الحقلgrant_typesفارغاً، وبعد إضافةauthorization_codeوrefresh_tokenنجح الدخول.
والصور التالية تبين الدخول بعد الإصلاح: زر authentik في صفحة الدخول، ثم صفحة authentik التي تقول إنك تدخل إلى Nextcloud، ثم الصفحة الرئيسية لحساب Sara الذي أنشأه Nextcloud تلقائياً عند أول دخول:



وللتأكد من جهة السيرفر:
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 كما رأيناه على القرص
فعلنا التشفير بالمفتاح الرئيسي 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:statusencryption 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 فابق على المفتاح الرئيسي.
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 backupMaintenance 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 restoretestnextcloud: 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 appInitializing 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 appInitializing 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 cronInitializing 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-keysAdding 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 مرة أخرى، ولا تحدث يوم الخميس قبل إجازتك.
تدريب عملي
- أنشئ مجلداً مشتركاً مع مجموعة بالصلاحية 5 (القراءة مع الإنشاء)، ثم جرب أن يرفع عضو من المجموعة ملفاً جديداً وأن يعدل ملفاً موجوداً، وفسر لماذا نجح أحدهما وفشل الآخر.
- شغل 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.