آموزش نصب و میزبانی شخصی openGym با Docker Compose
راهنمای کامل استقرار openGym روی VPS با استفاده از Docker Compose. نحوه تنظیم TLS پیش از اولین ورود با Passkey، مدیریت فایلهای JSON و نکات مهم برای اجرای پایدار سرویس.
آنچه با میزبانی شخصی openGym به دست میآورید
شما openGym را با کلون کردن مخزن، ویرایش دو خط در .env و اجرای docker compose up -d --build پشت یک reverse proxy که TLS (امنیت لایه انتقال) را خاتمه میدهد، بهصورت شخصی میزبانی میکنید. openGym یک ردیاب تمرینات ورزشی و وزن بدن است: برنامههای هفتگی، تمرینات هدایتشده، ثبت هر ست و نمودار تغییرات وزن در طول زمان. این نرمافزار تحت مجوز AGPL-3.0 منتشر شده و تمام دادهها را در فایلهای JSON ساده روی دیسک شما ذخیره میکند، بنابراین نیازی به اجرای سرور پایگاه داده نیست.
پشته (stack) شامل دو کانتینر در حال اجرا است: یک کانتینر nginx که نسخه build شده React را ارائه میدهد و یک کانتینر Node که API را در خود جای داده است؛ به علاوه یک job یکبارمصرف که در اولین اجرا حدود 140 مگابایت تصاویر و فایلهای GIF تمرینات را دانلود میکند.
دو نکته وجود دارد که فایل README پروژه به آنها اشاره کرده اما برای کسی که روی یک سرور عمومی مستقر میکند، بهصراحت توضیح نداده است. ورود با Passkey به یک نام میزبان (hostname) وابسته است، بنابراین دامنه و گواهی آن باید پیش از اولین ورود وجود داشته باشند، نه پس از آن. همچنین، سرور اختیاری MCP فقط خواندنی است و روی دستگاهی اجرا میشود که کلاینت هوش مصنوعی شما روی آن قرار دارد، نه داخل پشته؛ این موضوع باعث میشود وقتی دادهها روی یک VPS قرار دارند، اقدامات لازم تغییر کند.
openGym پروژه نوپایی است. اولین نسخه برچسبگذاری شده، v1.0.0، در تاریخ 20 ژوئیه 2026 منتشر شد و v1.2.7 در 18 اوت 2026 عرضه گردید. سیزده برچسب در حدود یک ماه نشان میدهد که برنامه همچنان در حال تغییر است، بنابراین بهجای build کردن آنچه در شاخه پیشفرض قرار دارد، یک برچسب نسخه (release tag) را بررسی و استفاده کنید.
پیش از نخستین ورود، دامنه را برنامهریزی کنید
Passkeyها روش ورود شما به openGym هستند. هر Passkey به یک Relying Party ID (یا همان RP ID) متصل است که در واقع همان دامنهای است که اعتبارنامه روی آن ایجاد شده است؛ مرورگرها نیز Passkeyها را فقط روی HTTPS ایجاد میکنند. تنها استثنا در این مورد localhost است.
این موضوع پیامدی دارد که کاربران معمولاً در گوشیهای خود با آن مواجه میشوند. اگر http://203.0.113.10:8080 را از دستگاه دیگری باز کنید، هیچ اعلان Passkey نمایش داده نمیشود، زیرا مرورگر از ایجاد اعتبارنامه روی مبدأ HTTP ساده یا آدرس IP خام خودداری میکند. یادداشتهای عیبیابی خود پروژه نیز همین نکته را تایید میکنند: عدم نمایش اعلان به این معنی است که شما روی http:// یا یک IP هستید.
بدتر اینکه RP ID در تمامی اعتبارنامههایی که کاربران شما قبلاً ثبت کردهاند، نهادینه شده است. اگر RP_ID را بعداً تغییر دهید، Passkeyهای ذخیرهشده روی دستگاههای کاربران دیگر با دامنه جدید مطابقت نخواهند داشت و در نتیجه هیچکس نمیتواند وارد سیستم شود. ابتدا نام میزبان (hostname) را تعیین کنید، DNS را به سمت VPS هدایت کنید و پیش از آنکه کسی روی Create profile ضربه بزند، گواهی (certificate) را فعال و آماده کنید.
استقرار openGym با Docker Compose
فایل compose، مسیرهای ./data و ./media را بهصورت bind-mount نسبت به موقعیت خود متصل میکند؛ بنابراین دایرکتوری که پروژه را در آن clone میکنید، همان دیتابیس شماست. آن را در محلی با ماندگاری بالا قرار دهید.
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 همچنان یک آدرس clone مربوط به github.com را نمایش میدهد. آن آدرس دیگر در دسترس نیست و مخزن Gitea که در بالا ذکر شد، میزبان اصلی و فعال پروژه است.
فایل .env را ویرایش کنید. سه خط در یک VPS اهمیت دارند.
RP_ID=gym.example.com
ORIGIN=https://gym.example.com
WEB_PORT=127.0.0.1:8080مقدار RP_ID نام دامنه اصلی (hostname) و ORIGIN آدرس کامل شامل طرح (scheme) است. این مقادیر باید دقیقاً با آنچه در نوار آدرس مرورگر وارد میشود مطابقت داشته باشند، در غیر این صورت ورود به سیستم با خطای verification failed مواجه میشود. مقدار WEB_PORT در بخش مربوط به خصوصیسازی پورت 8080 توضیح داده شده است.
docker compose up -d --build
docker compose ps
docker compose logs mediaدستور docker compose ps باید وضعیت web و api را در حالت running و media را در حالت exited با کد 0 نشان دهد. این خروج صحیح است: وظیفه مربوط به رسانه (media job) دارای وضعیت restart: "no" است، زیرا کار آن یک دانلود یکباره است. لاگ آن با خطی که با ✓ Exercise media ready شروع میشود پایان مییابد و ls media/img | wc -l باید عددی چند صدتایی را چاپ کند، نه 0. دایرکتوری خالی به این معنی است که دانلود با شکست مواجه شده و برنامه در نتیجه، کارتهای تمرین را با تصاویر خالی نمایش میدهد.
فلگ --build در اینجا اختیاری نیست. فایل compose به ایمیجهای از پیش ساختهشده در ghcr.io ارجاع میدهد که دیگر منتشر نمیشوند؛ بنابراین docker compose pull با خطای denied یا manifest unknown شکست میخورد و هر دو سرویس از روی سورسکدی که همین الان clone کردهاید ساخته میشوند. هر دوی آنها دارای یک بخش build برای همین منظور هستند. اگر با خود Compose آشنا نیستید، ابتدا Docker Compose روی VPS را مطالعه کنید و سپس به اینجا بازگردید.
نسخه را ثابت کنید، زیرا این پروژه نوپا است
از آنجا که آن namespace در registry حذف شده است، دیگر هیچ image tag برای ثابت کردن (pin) باقی نمانده است. آنچه باید به جای آن ثابت کنید، checkout روی دیسک است، زیرا این مورد تعیین میکند که کدام نسخه از برنامه در نهایت وارد container شود.
cd /opt/opengym
git fetch --tags
git checkout v1.2.7git status اکنون یک detached HEAD را در آن tag گزارش میدهد، که دقیقاً همان چیزی است که روی سرور به آن نیاز دارید. تا زمانی که tag دیگری را checkout نکنید، هیچ تغییری زیر پای شما رخ نمیدهد.
سپس به Compose بگویید که دیگر اصلاً به سراغ registry نرود. این تنظیمات را در docker-compose.override.yml قرار دهید؛ فایلی که Compose بهطور خودکار بارگذاری کرده و روی فایل اصلی (tracked file) ادغام میکند. کلیدهای Scalar توسط این override جایگزین میشوند، بنابراین نیازی به ویرایش هیچ فایلی در git نیست و git pull تمیز باقی میماند. برای مشاهده قوانین کامل ادغام، به نحوه ادغام فایل override توسط Compose مراجعه کنید.
services:
api:
pull_policy: build
web:
pull_policy: buildبا اعمال این تغییر، دستور docker compose up -d در آینده به جای شکست در مرحله pull، از سورسکدی که در اختیار دارید build میگیرد. بررسی کنید که ادغام بهدرستی اعمال شده باشد، سپس build را روی همان tag مجدداً انجام دهید.
docker compose config | grep pull_policy
docker compose up -d --buildپایاندهی TLS با یک reverse proxy
کانتینرها از پروتکل HTTP ساده استفاده میکنند. باید ابزاری در لایهٔ جلویی وجود داشته باشد که گواهی TLS را مدیریت کند. 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 بزنید. اگر اعلان passkey ظاهر شد و سپس ورود به سیستم گزارش verification failed داد، RP_ID یا ORIGIN با URL موجود در نوار آدرس مطابقت ندارد. مقدار .env را اصلاح کنید و دوباره docker compose up -d را اجرا کنید؛ این کار کانتینرها را بازسازی میکند تا مقادیر جدید را بخوانند. دستور docker compose restart باعث بارگذاری مجدد .env نمیشود.
محدود کردن دسترسی به پورت 8080 از اینترنت عمومی
بهصورت پیشفرض، سرویس وب 8080 را روی تمام رابطهای شبکه منتشر میکند؛ بنابراین برنامه از طریق IP عمومی شما و با پروتکل HTTP ساده در دسترس است، در حالی که پروکسی در همان سرور از 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 باید با خطای رد شدن (refused) یا وقفه (timeout) مواجه شود، در حالی که نام دامنه HTTPS همچنان به کار خود ادامه میدهد.
بستن ثبتنام پس از ایجاد پروفایل خود
ثبتنام بهصورت پیشفرض باز است و حالت مهمان (guest mode) فعال میباشد. در یک نام میزبان عمومی، این یعنی هر کسی که URL را پیدا کند میتواند روی سرور شما پروفایل بسازد. ابتدا پروفایل خود را ثبت کنید، سپس شناسه کاربری (user ID) خود را پیدا کنید: ls data/ فایلی به نام state-<uid>.json را برای هر کاربر فهرست میکند و آن <uid> همان مقداری است که به آن نیاز دارید.
ADMIN_UIDS=<your-uid>
INVITE_ONLY=1
ALLOW_GUEST=0دستور docker compose up -d را دوباره اجرا کنید. بخش تنظیمات (Settings) اکنون یک داشبورد مدیریت (Admin dashboard) را نشان میدهد که در آن میتوانید کدهای دعوت تولید یا ابطال کنید؛ بنابراین افرادی که با آنها تمرین میکنید میتوانند ثبتنام کنند و هیچکس دیگری قادر به این کار نخواهد بود. openGym هیچ شناختی از ارائهدهندگان هویت خارجی ندارد، بنابراین آن کدهای دعوت فقط همین یک برنامه را کنترل میکنند و هیچ تأثیری بر سایر بخشهای سرور ندارند؛ اگر ترجیح میدهید برای هر نفر یک حساب کاربری واحد برای تمام سرویسهای خود داشته باشید، قرار دادن Authentik به عنوان یک forward auth proxy پیش از بارگذاری صفحه ورود passkey در openGym، دسترسی به نام میزبان را محدود میکند.
محل ذخیرهسازی دادهها و پشتیبانگیری برای محافظت از آنها
همه چیز در دایرکتوری ./data قرار دارد که در کانتینر API در مسیر /data مونت شده است. چهار نوع فایل وجود دارد: db.json شامل پروفایلها و اعتبارنامههای عمومی passkey است، state-<uid>.json روتینها، تمرینات و وزن بدن یک کاربر را نگه میدارد، secret کلید کوکی نشست (session cookie) است و vapid.json کلیدهای اعلانهای فشاری (push notification) را که در اولین اجرا تولید شدهاند، در خود جای میدهد.
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 مگابایت تصاویر تمرینی است که job مربوط به رسانه میتواند دوباره آنها را بهصورت رایگان دانلود کند.
بازیابی به معنای استخراج فایل tar در همان مسیری است که میزبان، همان دامنه را سرویسدهی میکند. یک passkey که روی گوشی شما ذخیره شده، محدود به RP ID است که در آن ایجاد شده است؛ بنابراین بازیابی روی یک نام میزبان (hostname) جدید، پایگاه دادهای به شما میدهد که هیچکس نمیتواند به آن وارد شود. دامنه را حفظ کنید یا برای ثبت مجدد تمام passkeyها برنامهریزی کنید. همین نظم در مورد هر چیز دیگری که اجرا میکنید نیز صدق میکند و پشتیبانگیری و ارتقای یک استک Docker Compose روال کلی این کار را پوشش میدهد.
سرور MCP فقطخواندنی است و روی ماشین شما اجرا میشود
پروتکل MCP (مخفف model context protocol) روشی است که کلاینتهایی مانند Claude Desktop یا Cursor با یک سرور ابزار محلی ارتباط برقرار میکنند. openGym یک نمونه از آن را در mcp/ ارائه میدهد. این سرور بخشی از فایل compose نیست، یک کانتینر محسوب نمیشود و روی هیچ پورتی گوش نمیدهد. کلاینت آن را به عنوان یک child process اجرا کرده و از طریق 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 و یک worker از نوع Celery است که همگی پشت nginx قرار دارند. در مقابل، شما قابلیت ردیابی تغذیه و مواد تشکیلدهنده، یک REST API مستند، یک پایگاهداده بزرگ از تمرینات ورزشی و امکاناتی برای مربیانی که برنامههای دیگران را مدیریت میکنند، دریافت میکنید.
openGym شامل دو کانتینر و یک پوشه از فایلهای JSON است و هیچ حساب کاربری برای مدیریت، فراتر از passkeyها ندارد. تفاوت دقیقاً همین است.
اگر میخواهید غذا را در کنار تمرینات ردیابی کنید یا به یک API برای توسعه نیاز دارید، wger را اجرا کنید. اگر پشتهای میخواهید که آنقدر کوچک باشد که بتوانید در یک بعدازظهر کل آن را مطالعه کنید و ورود به سیستم بدون رمز عبور (که نشت کند) باشد، openGym را انتخاب کنید. هزینهٔ این انتخاب، بلوغ نرمافزار است: تا تاریخ 19 August 2026، اولین نسخهٔ openGym تنها یک ماه عمر دارد، در حالی که wger سالها سابقهٔ انتشار نسخه دارد. نسخهٔ خود را Pin کنید، از بکآپها نگهداری کنید و پیش از هر بهروزرسانی، یادداشتهای انتشار (release notes) را مطالعه کنید.
اگر هنوز در حال تصمیمگیری هستید که چه چیزی ارزش فضای سرور شما را دارد، چه چیزی در 2026 ارزش self-hosting دارد به بررسی این بدهبستانها میپردازد و این برنامه در کنار 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 هرگز در گوشی من درخواست passkey را نمایش نمیدهد؟
مرورگر به دلیل اینکه شما روی http:// یا یک آدرس IP خام مانند http://192.168.1.20:8080 هستید، از ایجاد اعتبارنامه خودداری میکند. مرورگرها فقط روی مبدأهای HTTPS اجازه استفاده از passkey را میدهند و 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 که به عنوان /data در کانتینر API مونت شده است. این دایرکتوری شامل db.json برای پروفایلها و اعتبارنامههای عمومی passkey، یک فایل state-<uid>.json به ازای هر کاربر برای تمرینات و وزن بدن، secret برای کلید کوکی نشست (session cookie) و vapid.json برای کلیدهای اعلانهای فشاری (push notification) است. با استفاده از docker compose stop api، سپس tar czf ~/opengym-$(date +%F).tar.gz data/ و در نهایت docker compose start api از آن نسخه پشتیبان تهیه کنید و آرشیو را از سرور کپی کنید. از media/ صرفنظر کنید، چرا که شامل 140 مگابایت تصاویر تمرینی است که job مربوط به رسانه، خودش دوباره آنها را دانلود میکند.
آیا Claude میتواند تاریخچه تمرینات openGym مرا بخواند؟
بله، از طریق سرور اختیاری MCP در دایرکتوری mcp/ و فقط برای خواندن. این سرور هشت ابزار را ارائه میدهد که شامل روتینها، برنامههای هفتگی، تمرینات ثبتشده، وزن بدن، تخمین حداکثر یک تکرار (1RM) و تعادل عضلانی است و هیچکدام قابلیت نوشتن ندارند. این یک کانتینر نیست و هیچ پورتی را باز نمیکند: کلاینت شما آن را از طریق stdio اجرا میکند و مستقیماً فایلهای JSON را در OPENGYM_DATA میخواند. از آنجا که این یک مسیر در سیستم فایل است، اجرای openGym روی یک VPS به این معنی است که یا باید یک کپی از data/ را روی دستگاهی که کلاینت را اجرا میکند همگامسازی کنید، یا سرور را از طریق ssh در پیکربندی کلاینت فراخوانی کنید.
آیا باید openGym را خودمیزبانی کنم یا wger را؟
اگر در کنار گزارش تمرینات، به دنبال ردیابی غذا و تغذیه هستید یا به یک REST API مستند برای توسعه نیاز دارید، wger را انتخاب کنید. این سرویس پشته بزرگتری را اجرا میکند: Django تحت gunicorn، PostgreSQL، Redis و یک worker از نوع Celery پشت nginx. اگر به دنبال دو کانتینر، فایلهای JSON که میتوانید با cat بخوانید و ورود با passkey بدون نیاز به مدیریت رمز عبور هستید، openGym را انتخاب کنید. از تاریخ 19 August 2026، اولین نسخه رسمی (tagged release) openGym یک ماهه است، بنابراین قبل از هر بهروزرسانی، یک git tag را بررسی کرده و از data/ نسخه پشتیبان تهیه کنید.