طريقة استضافة openGym ذاتيًا على VPS باستخدام Docker
انشر openGym على VPS باستخدام Docker Compose مع تثبيت الوسم v1.2.7، وإعداد TLS قبل أول Passkey، ومعرفة مكان ملفات JSON وخادم MCP للقراءة فقط.
ما تحصل عليه عند الاستضافة الذاتية لـ openGym
تُشغّل openGym باستضافته ذاتياً عبر استنساخ المستودع، وتعديل سطرين في .env، ثم تشغيل docker compose up -d --build خلف Reverse Proxy ينهي TLS (أمان طبقة النقل). openGym تطبيق لتتبع التمارين ووزن الجسم: خطط أسبوعية، وتمارين موجّهة، وتسجيل كل مجموعة، ومتابعة الوزن بمرور الوقت. التطبيق مرخّص بموجب AGPL-3.0، ويخزّن كل شيء في ملفات JSON نصية على القرص، لذلك لا تحتاج إلى تشغيل خادم قاعدة بيانات.
تتكوّن الحزمة من حاويتين تعملان باستمرار: حاوية nginx تقدّم بنية React، وحاوية Node تستضيف API، إضافة إلى مهمة تُنفّذ مرة واحدة لتنزيل نحو 140 MB من صور التمارين وملفات GIF عند تشغيل التطبيق لأول مرة.
هناك أمران يلمّح إليهما README في المشروع، لكنه لا يوضحهما لمن ينشر التطبيق على خادم عام. يرتبط تسجيل الدخول باستخدام Passkey باسم مضيف، لذلك يجب أن يكون النطاق وشهادته موجودين قبل تسجيل الدخول الأول، وليس بعده. كما أن خادم MCP الاختياري للقراءة فقط، ويعمل على الجهاز الذي يعمل عليه عميل AI، وليس داخل الحزمة. وهذا يغيّر ما يجب عليك فعله عندما تكون البيانات على VPS.
openGym مشروع حديث. يعود أول إصدار موسوم، v1.0.0، إلى 20 July 2026، ووصل الإصدار v1.2.7 في 18 August 2026. يشير وجود 13 وسمًا خلال نحو شهر إلى أن التطبيق لا يزال يتغير. لذلك تحقّق من وسم إصدار محدد بدلاً من بناء الإصدار الموجود، أياً كان، على الفرع الافتراضي.
خطّط للنطاق قبل تسجيل الدخول الأول
تستخدم openGym مفاتيح المرور لتسجيل الدخول. يرتبط مفتاح المرور بمعرّف الطرف المعتمد (RP ID)، وهو النطاق الذي أُنشئت عليه بيانات الاعتماد. ولا تنشئ المتصفحات مفاتيح مرور إلا عبر HTTPS. الاستثناء الوحيد هو localhost.
ويظهر أثر ذلك عند استخدام الهاتف. افتح http://203.0.113.10:8080 من جهاز آخر، ولن يظهر طلب مفتاح المرور إطلاقاً، لأن المتصفح يرفض إنشاء بيانات اعتماد على مصدر HTTP عادي أو على عنوان IP مجرد. وتذكر ملاحظات استكشاف الأخطاء وإصلاحها الخاصة بالمشروع الشيء نفسه: عدم ظهور الطلب يعني أنك تستخدم http:// أو عنوان IP.
والأسوأ أن معرّف الطرف المعتمد مضمّن في كل بيانات اعتماد سجّلها المستخدمون مسبقاً. إذا غيّرت RP_ID لاحقاً، فلن تعود مفاتيح المرور المخزنة على أجهزتهم متطابقة، ولن يتمكن أحد من تسجيل الدخول. حدّد اسم المضيف أولاً، ووجّه DNS إلى VPS، وشغّل الشهادة بنجاح قبل أن يضغط أي مستخدم على Create profile.
نشر openGym باستخدام Docker Compose
يربط ملف Compose المسارين ./data و./media بالنظام المضيف نسبةً إلى موقعه، لذلك فإن الدليل الذي تستنسخ المشروع إليه يصبح قاعدة بياناتك. ضعه في موقع دائم.
sudo install -d -o "$USER" -g "$USER" /opt/opengym
git clone https://gitea.com/DuarteSantos/openGym /opt/opengym
cd /opt/opengym
cp .env.example .envما زال ملف README يعرض عنوان استنساخ github.com. لم يعد هذا العنوان قابلاً للوصول، ومستودع Gitea أعلاه هو الموقع الفعلي للمشروع.
حرّر .env. هناك 3 أسطر مهمة على VPS.
RP_ID=gym.example.com
ORIGIN=https://gym.example.com
WEB_PORT=127.0.0.1:8080يمثل RP_ID اسم المضيف المجرد، بينما يمثل ORIGIN عنوان URL الكامل بما في ذلك المخطط. يجب أن يتطابقا تماماً مع العنوان الظاهر في شريط العناوين، وإلا فسيفشل تسجيل الدخول مع verification failed. وتوضَّح قيمة WEB_PORT في القسم الخاص بإبقاء المنفذ 8080 خاصاً.
docker compose up -d --build
docker compose ps
docker compose logs mediaيجب أن يعرض docker compose ps كلاً من web وapi وهما في حالة التشغيل، وأن يعرض media وقد خرج بالرمز 0. هذا الخروج صحيح: فقد restart: "no" مهمة الوسائط لأن عملها عبارة عن تنزيل يُنفّذ مرة واحدة. وينتهي سجلها بسطر يبدأ بـ ✓ Exercise media ready، ويجب أن يطبع ls media/img | wc -l بضع مئات بدلاً من 0. يعني الدليل الفارغ أن التنزيل فشل، وعندها يعرض التطبيق بطاقات التمارين مع صور فارغة.
العَلَم --build إلزامي هنا. يسمّي ملف Compose صوراً مبنية مسبقاً على ghcr.io، ولم تعد هذه الصور منشورة. لذلك يفشل docker compose pull مع denied أو manifest unknown، وتُبنى الخدمتان بدلاً من ذلك من المصدر الذي استنسخته للتو. ويتضمن كل منهما قسماً build لهذا الغرض تحديداً. إذا كان Compose جديداً عليك، فابدأ بـ Docker Compose على VPS ثم عد إلى هنا.
ثبّت الإصدار، لأن هذا المشروع حديث
بما أنّ مساحة أسماء السجل تلك لم تعد متاحة، لم تعد هناك وسم صورة يمكن تثبيته. بدلاً من ذلك، ثبّت نسخة checkout الموجودة على القرص، لأنها تحدد إصدار التطبيق الذي سينتهي به الأمر داخل الحاوية.
cd /opt/opengym
git fetch --tags
git checkout v1.2.7يعرض git status الآن حالة HEAD منفصلة عند ذلك الوسم، وهذا هو المطلوب على الخادم. لن يتغير الإصدار الموجود أسفلك إلى أن تنفّذ checkout لإصدار آخر.
ثم أخبر Compose بالتوقف عن محاولة الوصول إلى السجل تماماً. ضع هذا في docker-compose.override.yml، إذ يحمّله Compose تلقائياً ويدمجه فوق الملف المتتبَّع. تُستبدل المفاتيح scalar بقيم ملف التجاوز، لذلك لا حاجة إلى تعديل أي شيء في git، ويبقى git pull نظيفاً. راجع كيفية دمج Compose لملف التجاوز للاطلاع على قواعد الدمج الكاملة.
services:
api:
pull_policy: build
web:
pull_policy: buildبعد ذلك، ينشئ docker compose up -d لاحقاً الصورة من المصدر الموجود لديك بدلاً من الفشل عند محاولة السحب. تحقّق من تطبيق الدمج، ثم أعد الإنشاء عند ذلك الوسم.
docker compose config | grep pull_policy
docker compose up -d --buildإنهاء TLS باستخدام reverse proxy
تستخدم الحاويات HTTP عاديًا. يجب أن يتولى مكوّن أمامها الشهادة. يُعد Caddy المسار الأقصر، لأنه يطلب الشهادة من Let's Encrypt ويجددها تلقائيًا.
gym.example.com {
reverse_proxy 127.0.0.1:8080
}يعمل nginx وTraefik وNginx Proxy Manager بالطريقة نفسها. وينطبق ذلك أيضًا على Cloudflare Tunnel، الذي يوثّقه المشروع ولا يحتاج إلى فتح أي منفذ وارد.
curl -sI https://gym.example.com | head -1يجب أن يعيد ذلك HTTP/2 200 من دون تحذير بشأن الشهادة. افتح الموقع الآن في متصفح، وانقر على Create profile. إذا ظهر طلب مفتاح المرور، ثم أبلغ تسجيل الدخول عن verification failed، فهذا يعني أن RP_ID أو ORIGIN لا يطابق عنوان URL في شريط العنوان. أصلح .env، ثم شغّل docker compose up -d مرة أخرى. فهذا يعيد إنشاء الحاويات كي تقرأ القيم الجديدة. لا يعيد docker compose restart تحميل .env.
إبقاء المنفذ 8080 خارج الإنترنت العام
تَنشر خدمة الويب افتراضياً 8080 على كل الواجهات، لذلك يمكن الوصول إلى التطبيق عبر HTTP غير المشفّر باستخدام عنوان IP العام، بينما يوفّر الـproxy خدمة HTTPS على الخادم نفسه. لا تعالج قاعدة جدار ناري هذه المشكلة. ينشر Docker المنفذ باستخدام قاعدة DNAT في جدول nat، ثم تُعالَج حركة المرور هذه في السلسلة FORWARD حيث تقبلها قواعد Docker الخاصة، بينما توجد قواعد ufw في مسار INPUT. لذلك لا يحظر sudo ufw deny 8080/tcp أي شيء.
الحل هو النشر على عنوان loopback فقط. يربط ملف compose "${WEB_PORT:-8080}:${NGINX_PORT:-80}"، لذلك تُستبدل أي قيمة تضبطها في WEB_PORT على الجانب الأيسر من ذلك الربط، كما تقبل الصيغة المختصرة في Docker زوج ip:port في هذا الموضع. لذلك تعمل WEB_PORT=127.0.0.1:8080.
docker compose config
sudo ss -ltnp | grep 8080في الإعداد المدمج، وتحت ports الخاص بخدمة الويب، يجب أن ترى host_ip: 127.0.0.1. يجب أن يعرض ss القيمة 127.0.0.1:8080، لا 0.0.0.0:8080. ومن جهاز آخر، يجب أن يُرفض الاتصال بـcurl http://<your-vps-ip>:8080 الآن أو تنتهي مهلة الاتصال، بينما يستمر اسم مضيف HTTPS في العمل.
أغلق التسجيل بعد إنشاء ملفك الشخصي
يكون التسجيل مفتوحاً افتراضياً، ويكون وضع الضيف مفعّلاً. على اسم مضيف عام، يعني ذلك أن أي شخص يعثر على عنوان URL يمكنه إنشاء ملف شخصي على خادمك. أنشئ ملفك الشخصي أولاً، ثم ابحث عن معرّف المستخدم الخاص بك: يعرض ls data/ ملفاً باسم state-<uid>.json لكل مستخدم، وتكون قيمة <uid> هي القيمة المطلوبة.
ADMIN_UIDS=<your-uid>
INVITE_ONLY=1
ALLOW_GUEST=0شغّل docker compose up -d مرة أخرى. يعرض Settings الآن لوحة Admin يمكنك من خلالها إنشاء رموز الدعوة وإبطالها، بحيث يستطيع الأشخاص الذين تتدرب معهم التسجيل ولا يستطيع أي شخص آخر ذلك. لا يعرف openGym شيئاً عن موفّري الهوية الخارجيين، لذلك تتحكم رموز الدعوة هذه في هذا التطبيق وحده ولا تؤثر في أي شيء آخر على الخادم؛ وإذا كنت تفضّل منح حساب واحد لكل شخص عبر جميع الخدمات التي تشغّلها، فإن وضع Authentik أمامه كوكيل forward auth يقيّد الوصول إلى اسم المضيف قبل تحميل تسجيل الدخول باستخدام passkey الخاص بـ openGym.
مكان البيانات، والنسخة الاحتياطية التي تحميها
يوجد كل شيء في الدليل ./data، وهو موصول بحاوية API في المسار /data. توجد أربعة أنواع من الملفات: يحتوي db.json على الملفات الشخصية وبيانات اعتماد مفاتيح المرور العامة، ويحتوي state-<uid>.json على روتينات المستخدم الواحد وتمارينه ووزن جسمه، ويحتوي secret على مفتاح ملف تعريف ارتباط الجلسة، ويحتوي vapid.json على مفاتيح إشعارات الدفع التي تُنشأ عند التشغيل الأول.
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start apiأوقف API أولاً لأن tar ينسخ الملفات بينما قد يكون API يكتب إلى أحدها، ولأن استعادة ملف JSON نُسخ جزئياً تنتج ملف JSON تالفاً. تستغرق عمليتا الإيقاف والتشغيل نحو ثانيتين. بعد ذلك، انسخ الأرشيف إلى خارج الخادم، لأن أرشيفاً موجوداً على VPS لا ينجو من فقدان VPS. استبعد media/ من النسخة الاحتياطية؛ فهو يحتوي على 140 MB من صور التمارين التي تنزّلها مهمة الوسائط مجدداً مجاناً.
تعني الاستعادة فك الأرشيف في المسار نفسه على مضيف يقدّم النطاق نفسه. يكون مفتاح المرور المخزّن على هاتفك مرتبطاً بمعرّف RP الذي أُنشئ له، ولذلك تمنحك الاستعادة على اسم مضيف جديد قاعدة بيانات تعمل، لكن لا يستطيع أحد تسجيل الدخول باستخدامها. حافظ على النطاق، أو خطط لإعادة تسجيل كل مفتاح مرور. وينطبق المبدأ نفسه على كل ما تشغّله، ويغطي النسخ الاحتياطي وترقية مكدس Docker Compose الإجراء العام.
خادم MCP للقراءة فقط، ويعمل على جهازك
MCP (model context protocol) هو الطريقة التي يتواصل بها عميل مثل Claude Desktop أو Cursor مع خادم أدوات محلي. يوفّر openGym خادماً في mcp/. لا يُعد جزءاً من ملف compose، وليس حاوية، ولا يستمع على أي منفذ. يشغّله العميل كعملية فرعية ويتواصل معه عبر stdio، ولذلك يذكر README أنه لا يغادر جهازك.
ثبّته حيث يعمل العميل، وليس على الخادم:
cd openGym/mcp
npm installثم أضفه إلى claude_desktop_config.json:
{
"mcpServers": {
"opengym": {
"command": "node",
"args": ["/absolute/path/to/openGym/mcp/src/index.js"],
"env": {
"OPENGYM_DATA": "/absolute/path/to/openGym/data",
"OPENGYM_UID": "<your-uid>"
}
}
}
}يُعد OPENGYM_UID اختيارياً في تثبيت لمستخدم واحد، حيث يكتشف الخادم ملف التعريف الوحيد الذي يعثر عليه. يوفّر ثماني أدوات: list_routines وget_routine وget_week_plan وlist_workouts وget_workout وget_bodyweight وestimate_1rm وmuscle_balance. تقرأ كل أداة منها البيانات. ولا تكتب أي منها شيئاً، لذلك يستطيع المساعد الإجابة عن التمرين الذي سجلته الأسبوع الماضي، لكنه لا يستطيع تسجيل مجموعة تمارين، أو تعديل روتين، أو حذف أي شيء. هذه القائمة مثال مختصر على مبدأ يتكرر في تصميم الوكلاء، وهو أن الأدوات التي تكشفها تحدد كل ما يستطيع النموذج فعله. وتُعد تعلُّم كيفية عمل الوكلاء بكتابة الحلقة بنفسك أسرع طريقة لفهم أن مجموعة الأدوات للقراءة فقط هي خيار تصميم، وليست قيداً.
إليك المشكلة التي يجب على مستخدم VPS حلها. OPENGYM_DATA هو مسار في نظام الملفات، بينما توجد بياناتك على VPS ويعمل عميل الذكاء الاصطناعي على حاسوبك المحمول. هناك خياران يوضحان الوضع فعلياً.
- انسخ البيانات إلى جهازك وأشر الخادم إلى النسخة:
rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/، ثم اضبطOPENGYM_DATAعلى~/opengym-data. لا يسبب الخادم إلا عمليات قراءة، لذلك لا تفقد النسخة أي بيانات. أعد تشغيل rsync عندما تريد أرقاماً محدثة. - شغّل الخادم عبر ssh، مع ضبط
commandعلىsshوضبطargsعلى["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"]. يتطلب ذلك تثبيت Node على VPS، واستخدام تسجيل دخول لا يطبع شيئاً إلى stdout، لأن stdout هو قناة البروتوكول.
يفترض كلا الخيارين أن الوكيل نفسه يعمل على حاسوبك المحمول. وإذا كنت تفضّل تشغيله على الخادم نفسه الذي توجد عليه البيانات، فإن OneCLI يوفّر لكل شخص وكيلاً معزولاً داخل بيئة اختبار على الخادم، وبذلك تصبح قفزة stdio إلى data/ محلية مرة أخرى.
إذا أعاد cat data/db.json القيمة Permission denied، فقد كتبت حاوية API هذه الملفات بصلاحية root، ولا يستطيع حساب تسجيل الدخول الخاص بك قراءتها. انسخها باستخدام sudo، أو غيّر الملكية على المضيف. بالنسبة إلى الخوادم التي يُفترض أن تستمع عبر الشبكة بدلاً من stdio، راجع تشغيل خوادم MCP على VPS.
openGym أم wger: أيهما ينبغي أن تشغّل؟
يُعدّ wger الخيار الراسخ في هذا المجال، وهو برنامج أكبر بكثير. تُشغّل حزمة Compose الخاصة به gunicorn لتقديم تطبيق Django، وPostgreSQL، وRedis، وعامل Celery خلف nginx. في المقابل، تحصل على تتبّع التغذية والمكوّنات، وREST API موثّقة، وقاعدة بيانات كبيرة لتمارين المجتمع، وميزات للمدرّبين الذين يديرون خطط أشخاص آخرين.
يتكوّن openGym من حاويتين ومجلد يحتوي على ملفات JSON، ولا توجد فيه حسابات تحتاج إلى إدارة باستثناء passkeys. هذا هو الفرق كله. إذا سبق لك تشغيل تثبيت Chatwoot وصيانته، حيث تعني النسخة الاحتياطية تفريغ قاعدة بيانات Postgres إلى جانب مجلد التحميلات، ويتطلب كل تحديث للإصدار تشغيل عمليات ترحيل قاعدة البيانات، فأنت تعرف بالفعل طبيعة الصيانة التي يفرضها wger.
شغّل wger إذا أردت تتبّع الطعام إلى جانب التدريب، أو إذا كنت تحتاج إلى API تبني عليه. شغّل openGym إذا أردت حزمة صغيرة بما يكفي لقراءة مكوّناتها من البداية إلى النهاية خلال فترة بعد الظهر، وتسجيل دخول بلا كلمة مرور يمكن تسريبها. تكلفة هذا الاختيار هي انخفاض مستوى النضج: في 19 August 2026، كان أول إصدار من openGym قد صدر قبل شهر، بينما يملك wger سنوات من الإصدارات السابقة. ثبّت إصدارك، واحتفظ بالنسخ الاحتياطية، واقرأ ملاحظات الإصدار قبل كل تحديث.
إذا كنت لا تزال تقرر ما الذي يستحق مساحة على الخادم، فراجع ما الذي يستحق الاستضافة الذاتية في 2026 لمعرفة المفاضلات، ويمكن لهذا التطبيق أن يعمل بسهولة إلى جانب Mealie للوصفات أو Actual Budget لإدارة المال على VPS صغير واحد.
التحديث دون فقدان أي بيانات
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api
git fetch --tagsتحقّق من الإصدار المطلوب باستخدام git checkout v<new>، ثم نفّذ docker compose up -d --build لإعادة إنشاء الحاويات من ذلك الوسم. أجرِ النسخ الاحتياطي أولاً في كل مرة، لأن مسار الاستعادة لملفات JSON المخزنة على القرص هو أمر tar واحد ويستغرق ثواني.
FAQ
لماذا لا يعرض openGym أبداً مطالبة بمفتاح مرور على هاتفي؟
يرفض المتصفح إنشاء بيانات اعتماد لأنك تستخدم http:// أو عنوان IP مجرداً مثل http://192.168.1.20:8080. لا تسمح المتصفحات بمفاتيح المرور إلا في مصادر HTTPS، مع localhost باعتباره الاستثناء الوحيد. ضع openGym خلف reverse proxy يحمل شهادة حقيقية لاسم مضيف حقيقي، واضبط RP_ID=gym.example.com وORIGIN=https://gym.example.com في .env، ثم شغّل docker compose up -d لكي تلتقط الحاويات القيم الجديدة. إذا ظهرت المطالبة لكن أبلغ تسجيل الدخول عن verification failed، فهذا يعني أن القيمتين لا تطابقان عنوان URL الظاهر في شريط العنوان تطابقاً تاماً.
أين يخزّن openGym بياناتي، وكيف أنشئ نسخة احتياطية منها؟
في الدليل ./data الموجود بجانب ملف compose، والمربوط داخل حاوية API بوصفه /data. يحتوي هذا الدليل على db.json للملفات الشخصية وبيانات اعتماد مفاتيح المرور العامة، وملف state-<uid>.json واحداً لكل مستخدم لتسجيلات التمارين ووزن الجسم، وsecret لمفتاح ملف تعريف ارتباط الجلسة، وvapid.json لمفاتيح إشعارات الدفع. أنشئ نسخة احتياطية باستخدام docker compose stop api، ثم tar czf ~/opengym-$(date +%F).tar.gz data/، ثم docker compose start api، وانسخ الأرشيف خارج الخادم. استبعد media/، الذي حجمه 140 MB ويحتوي على صور التمارين التي تعيد مهمة الوسائط تنزيلها تلقائياً.
هل يستطيع Claude قراءة سجل تماريني في openGym؟
نعم، من خلال خادم MCP الاختياري في الدليل mcp/، وللقراءة فقط. يوفّر ثماني أدوات تغطي الروتينات، وخطط الأسابيع، والتمارين المسجلة، ووزن الجسم، والحد الأقصى التقديري لتكرار واحد، وتوازن العضلات، ولا تكتب أي منها بيانات إلى النظام. هذا الخادم ليس حاوية ولا يفتح أي منفذ؛ إذ يشغّله العميل عبر stdio، ويقرأ ملفات JSON الموجودة مباشرة في OPENGYM_DATA. وبما أن ذلك مسار لنظام الملفات، فإن تشغيل openGym على VPS يعني إما مزامنة نسخة من data/ إلى الجهاز الذي يشغّل العميل، أو استدعاء الخادم عبر ssh من إعدادات العميل.
هل ينبغي أن أستضيف openGym ذاتياً أم wger؟
اختر wger إذا كنت تريد تتبع الطعام والتغذية إلى جانب سجل التدريب، أو تريد REST API موثقاً تبني عليه. فهو يشغّل مكدساً أكبر يتضمن Django تحت gunicorn، وPostgreSQL، وRedis، وعاملاً لـ Celery خلف nginx. اختر openGym إذا كنت تريد حاويتين، وملفات JSON يمكنك قراءتها باستخدام cat، وتسجيل دخول بمفتاح مرور من دون إدارة كلمات مرور. اعتباراً من 19 August 2026، لا يتجاوز عمر أول إصدار موسوم من openGym شهراً واحداً، لذا تحقّق من git tag وأنشئ نسخة احتياطية من data/ قبل كل تحديث.