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


v1.2.9
2026-09-11If you run openGym for other people, you have had no way to answer “who signed in, and when?” — the server kept no record of anything. It does now: an activity log in the admin dashboard, covering sign-ins, sign-outs, the attempts that failed, and every admin action. The other half of this release is that the live demo is back, self-hosted this time, after two months offline. And the project has a working home again: openGym now lives on GitLab, where CI builds the container images and the signed Android APK for every release — the thing that has been missing since the GitHub account went.
Activity log
- 🧾 The admin dashboard has an activity log. Successful and failed sign-ins, profile creations and refused signups, sign-outs (including “sign out everywhere”), and every admin action: disabling or re-enabling an account, creating or revoking an invite code, and clearing the log itself. Filter it by sign-ins, admin actions or failures, and page back through it.
- 📄 It is a plain file.
./data/audit.log, one JSON object per line —tail -fandjqread it directly, which also means it is its own export format. Deliberately not part ofdb.json: that file is rewritten in full on every save, and the sign-in handshake is unauthenticated, so a log living in there would have turned one junk request into a full rewrite. Retention is a cap rather than an archive — the lastAUDIT_MAXevents (5,000) orAUDIT_DAYSdays (90), whichever runs out first. - 🔒 It records less than you might expect, on purpose. No IP addresses unless you turn them
on (
AUDIT_IP=netkeeps only the network,fullkeeps the address); never the browser’s user-agent; and never the passkey id behind a failed sign-in, because that id is a stable handle for one device and storing it would let an admin follow an unknown device from one attempt to the next. A rejected invite code is not stored either — a near-miss guess sitting in a log file helps nobody. - 🧹 Clearing it is itself logged, and the event ids keep counting, so an erased stretch always leaves a visible gap.
- ⚙️ On by default when you update, and one variable turns it off. It records strictly less
than your instance already holds — every profile is in
db.json, every workout is instate-<uid>.json, and any admin can already read both — and a log that ships switched off tells you nothing on the day you need it.AUDIT_LOG=0disables it completely; no file is written. Nothing leaves your server either way: this is a local file, not telemetry. - Guests still never appear anywhere — guest mode does not talk to the server at all.
The live demo is back
- ▶️ https://opengym.duarte-santos.ch/demo/ — the in-browser demo, running on the project’s own site instead of GitHub Pages, which went down in August with the suspended account. Same build as before: no backend, no account, seeded example history, and a reset button in its settings. The embedded demo on the landing page works again too.
openGym moved to GitLab
- 🏠 https://gitlab.com/DuarteSantos8/opengym is the home of the project. Same history, same tags, same AGPL. gitea.com was the stopgap after the GitHub suspension and stays as a mirror; it never had a CI runner, which is why releases there had no images.
- 🐳 Prebuilt images are back.
docker compose pullnow fetchesregistry.gitlab.com/duartesantos8/opengym/apiand/web, built for amd64 and arm64 on every release. Pulling is anonymous — the project is public, no login, no token. - 🤖 The APK is built by CI now, not by hand. Every
vX.Y.Ztag produces azipaligned, signed APK, attached to the GitLab release and mirrored onto the download page. The signing key sits in protected CI variables, so it exists only onmainand on version tags — a merge request from a fork builds an unsigned APK and never touches the key. - ✅ Every merge request is tested again. The frontend suite (346 tests), the locale checks,
the fatigue probe and the MCP suite all run on GitLab CI. The GitHub Actions workflows stay
in
.github/for the day that account comes back. - 🌐 The in-browser demo also builds to GitLab Pages (https://opengym-bc111a.gitlab.io/, which https://duartesantos8.gitlab.io/opengym/ redirects to); https://opengym.duarte-santos.ch/demo/ remains the copy the landing page embeds.
- 📄 Security reports have a private channel again: a confidential issue on GitLab. See
SECURITY.md. - 🔁 Dependency updates continue. GitLab has no Dependabot, so Renovate runs from a monthly
scheduled pipeline with the same deliberately quiet policy the Dependabot config had:
grouped per ecosystem, majors on their own, odd-numbered Node images skipped, and the
generated
android//ios/projects left to follow their@capacitor/*packages. Security advisories ignore the schedule and land on their own.
Housekeeping
- The self-hosting docs,
SECURITY.mdand.env.examplecover the activity log, and theapi/server.jsline references inSECURITY.mdare accurate again. - Every repository link in the README, the docs, the app and the website points at GitLab, and the website’s live star/release numbers come from GitLab’s API.
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.9 and ghcr.io/duartesantos8/opengym-web:1.2.9.