openGym
Self-hosted gym and body-weight tracker with workout planning and muscle recovery tracking
Alternative to: strong, hevy, fitnotes


v1.2.10
2026-09-11The first release since the move to GitLab that is mostly other people’s work. Nine contributors, thirteen merge requests: Brazilian Portuguese in full — interface, all 1,324 exercise names and all 7,710 instruction steps — equipment profiles for people who train in more than one gym, drop-sets and rest-pause, a way to pair the mobile app with your own server, and a round of security hardening on the API and the reverse proxy.
Alongside that, the bugs you reported in #bugs-and-ideas and on the issue tracker, several of which turned out to be worse than they looked from the outside.
Training
- 🔥 “Add warm-up set” gave you a warm-up at your full working weight. It looked for the row to ramp from at the position before the first work set — which for the first warm-up is index −1, so it fell through to the last row instead: your heaviest set. Every warm-up you added had to be corrected by hand. Warm-ups now ramp toward the work weight, each one closing half the remaining gap (50% → 75% → 87.5%), rounded down to what you can actually load.
- 🏋️ Warm-ups can be planned in the routine. Set how many an exercise gets and the session starts with them already in place, ramped, instead of you adding them every time. They stay out of volume, records and progression, and they travel with a shared plan.
- ⏱️ The rest timer skipped the last set of every exercise. It was written as “final sets finish quietly”, but another exercise follows that set and you rest before it too — so a two-set exercise timed one rest instead of two. It now rests after every completed set except the last one of the session. (issue #3, and zkssyth arrived at the same rule independently)
- 📝 Notes you can write during a workout, in three kinds, because they have three different lifetimes: what happened today, on the exercise; what is always true about the movement (seat height, pin position); and how the session went as a whole. The per-session note carries a pin — the app cannot know whether “shoulder twinged” is a diary line or “go narrower” is a message to your next self, but you do, at the moment you write it. Pinned notes come back the next time that exercise comes up.
- 💥 Drop-sets and rest-pause / myo-reps, planned per exercise and extending the set row itself rather than adding new sets.
Equipment, and training in more than one gym
- 🎒 Equipment profiles. Build a “Home” and a “Gym” list of what you actually own, and the library, the exercise picker and your routines filter to it — with a warning on any routine exercise that needs something the active profile does not have. Body-weight exercises are always available.
Brazilian Portuguese
- 🇧🇷 pt-BR is complete: the interface, every built-in exercise name, and every instruction step. It is defined as a layer over pt-PT rather than a copy, so a string added to Portuguese can never silently go missing here — with tests that fingerprint every inherited entry and refuse European vocabulary (ficheiro, telemóvel, ecrã, «guillemets»).
- 🔔 Server-sent notifications — rest-timer alerts, test pushes, workout-day reminders — follow the profile’s language instead of always arriving in English.
Fixes
- ✅ Home kept asking you to do the workout you had just done. The week strip knew the day was finished; the row underneath did not, and went on showing the routine behind a green Start tag. (issue #4)
- 📈 “Exercise progress” was empty for bodyweight exercises. Pull-ups and push-ups carry no weight, and every point without one was dropped, so a full history read as “No data yet”. When nothing in an exercise’s history was ever loaded, the reps are the progress — so that is what is plotted now. Add a weighted set later and it switches back to weight. (issue #5)
- 🫥 The tab bar was see-through. At 72% opacity the page read straight through it and the
labels competed with whatever was scrolling behind them — nobody needs to read the content
under a navigation bar. It is 94% now in both themes, and blurs less, which is also faster on
Android, where a blur behind a fixed element is recomposited on every scroll frame. Where
backdrop-filteris unavailable at all — old Android WebViews, “reduce transparency” — the blurred surfaces now go fully opaque instead of leaving just the alpha, which was the worst of both. (reported by mflova, seconded by seals187) - 📥 No Smith-machine exercise could ever match on import. The
machine → leverrule ran beforesmith machine → smith, so the generic one ate the word first. Hevy’s vocabulary is now mapped as well, and exercises it cannot match take their body part from the name instead of defaulting to “upper legs” — which had been attributing a third of an imported history to the legs. (diagnosed by rubik_97, fixed by koyosan) - 🗓️ Charts show the calendar year when the data spans more than one, so a point from November 2025 is not confused with February 2026. (mflova)
- 🔤 Exercise search matches whole words in any order, ignores accents, and searches the translated names too — “bench barbell” finds the barbell bench press, “elevacao” finds “elevação”. The haystack is now built once per exercise instead of on every keystroke.
- 🔑 Passkeys work in Chrome on iOS, which was excluded by a user-agent test rather than by asking the browser what it supports.
- 📱 The reps field in a superset is no longer squeezed on narrow phones, and secondary muscles are visible while building a plan, not only in the Exercises tab.
Self-hosting
- 🔐 Security hardening on the API and the proxy: SSRF and an unbounded handler in push delivery, CSRF from a sibling subdomain, session-cookie shadowing, and non-forgeable proxy headers. Details in SECURITY.md.
- 🧭 When a passkey fails, the error now says what is misconfigured. “Unexpected RP ID hash”
told you nothing about your own
.env; it now names theRP_IDandORIGINthe server is actually running with.docs/SELF_HOSTING.mdgained a section that works through the usual causes in order — including thatdocker compose restartdoes not re-read.env, and that on a LAN without certificates there is nothing you can configure to make passkeys work. - 📲 “Connect to my server” pairs the standalone mobile app to a self-hosted instance with a one-time code, no passkey ceremony needed inside the app’s WebView.
- 💾 Optional local auto-backup on the mobile app after finishing a workout or editing a routine, and a System theme option that follows the OS.
With thanks to mzspicoli, Josevi, andi242, Space_Hermes, Horus Gonzalez, wagenheimer, koyosan, mflova and Aaron Sachs — and to everyone who reported one of the bugs above.
Published on GitHub on 2026-09-11, mirroring the GitLab release of the same tag, where the artefacts above were built (!NN numbers are GitLab merge requests). The same images are on GHCR, built from this release by GitHub Actions: ghcr.io/duartesantos8/opengym-api:1.2.10 and ghcr.io/duartesantos8/opengym-web:1.2.10.