كيفية استضافة openGym ذاتياً عبر Docker Compose
انشر openGym على VPS عبر Docker Compose مع وسم Git مثبت، وفعّل 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 الاختياري للقراءة فقط، ويعمل على الجهاز الذي يعمل عليه عميل الذكاء الاصطناعي، وليس داخل الحزمة. وهذا يغيّر ما عليك فعله عندما تكون البيانات موجودة على 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 لهذا الغرض تحديداً. إذا كان Docker Compose جديداً عليك، فابدأ بـ Docker Compose على VPS ثم عُد إلى هنا.
ثبّت الإصدار، لأن هذا المشروع حديث
بما أن مساحة الأسماء في السجل لم تعد موجودة، لم تعد هناك وسم صورة يمكن تثبيته. بدلاً من ذلك، ثبّت نسخة checkout الموجودة على القرص، لأنها تحدد إصدار التطبيق الذي ينتهي به الأمر داخل الحاوية.
cd /opt/opengym
git fetch --tags
git checkout v1.2.7يعرض git status الآن حالة HEAD منفصلة عند ذلك الوسم، وهذا هو المطلوب على الخادم. لن يتغير checkout الحالي ما لم تسجّل checkout مختلفة.
بعد ذلك، اطلب من Compose التوقف تماماً عن الوصول إلى السجل. ضع ما يلي في docker-compose.override.yml، إذ يحمّله Compose تلقائياً ويدمجه فوق الملف المتتبَّع. تُستبدل المفاتيح المفردة بالقيم الموجودة في ملف التجاوز، لذلك لا تحتاج إلى تعديل أي شيء في 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 يعمل.
أغلِق التسجيل بعد إنشاء ملفك الشخصي
يكون التسجيل مفتوحاً افتراضياً، ويكون وضع الضيف مفعّلاً. يعني ذلك أن أي شخص يعثر على عنوان المضيف العام يمكنه إنشاء ملف شخصي على خادمك. أنشئ ملفك الشخصي أولاً، ثم اعثر على معرّف المستخدم الخاص بك: يعرض 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 (بروتوكول سياق النموذج) هو الطريقة التي يتواصل بها عميل مثل 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 هو قناة البروتوكول.
إذا أعاد 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. هذا هو الفرق بالكامل.
شغّل 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 موثّقاً تبني عليه. يشغّل wger مكدساً أكبر: Django تحت gunicorn، وPostgreSQL، وRedis، وعاملاً لـ Celery خلف nginx. اختر openGym إذا كنت تريد حاويتين، وملفات JSON يمكنك قراءتها باستخدام cat، وتسجيل دخول عبر مفتاح مرور من دون إدارة كلمات مرور. في 19 August 2026، كان أول إصدار موسوم من openGym أقدم بشهر واحد فقط، لذا تحقّق من git tag وأنشئ نسخة احتياطية من data/ قبل كل تحديث.