openGym ships in two flavors from the same codebase:
| Self-hosted (this repo’s default) | Mobile app (VITE_MOBILE=1) |
|
|---|---|---|
| Runs | in any browser, against your own server | natively on iPhone / Android (Capacitor shell) |
| Accounts | passkey sign-in, one profile per person | none — the phone is the account |
| Data | synced to your server, readable on desktop | stays on the device (file in the app’s private storage) |
| Reminders | Web Push from your server | native local notifications, no server involved |
| Exercise media | served by your server (img/, gif/) |
loaded from the jsDelivr CDN |
The mobile flavor never talks to a backend: no sign-in screen, no sync, no telemetry.
State is mirrored from localStorage into opengym-state.json in the app’s private data
directory on every change (iOS is allowed to evict WebView storage under pressure — the
file mirror is the durable copy and is restored on launch). Backups go out through the
OS share sheet instead of a browser download.
brew install cocoapods). A free Apple ID
is enough to run the app on your own iPhone (see below); paid membership is only needed
for App Store distribution, which openGym doesn’t do.cd frontend
npm install
npm run build:mobile # VITE_MOBILE build + `cap sync` into android/ and ios/
npx cap open android # opens Android Studio → run on emulator or device
npx cap open ios # opens Xcode (Mac only) → set your signing team, then run
npm run build:mobile bakes the CDN media base into the bundle and copies the web build
into both native projects — re-run it after every web-code change before building natively.
Heads-up: after
build:mobile,frontend/distcontains the mobile bundle. Run a plainnpm run buildagain before deployingdistto a server.
frontend/resources/icon.svg is the 1024×1024 source (the app’s dumbbell glyph on the
app background). Generate all platform assets from it on a machine with the tooling:
cd frontend
npx @capacitor/assets generate --iconBackgroundColor '#0c0e12' --splashBackgroundColor '#0c0e12'
(If the generator won’t take the SVG directly, export it to resources/icon.png at
1024×1024 first — any image tool can do it.)
openGym’s mobile app is not on the Play Store or App Store, and that’s a choice: no store accounts, no store rules, no yearly fees between you and an open-source app.
The official signed APK is at opengym.duarte-santos.ch. Android asks you to allow installs from the browser the first time — that’s standard for any app outside the Play Store.
To build and sign your own:
cd frontend && npm run build:mobile
cd android && ./gradlew assembleRelease # → app/build/outputs/apk/release/app-release-unsigned.apk
# one-time: create a keystore. KEEP IT — updates must be signed with the same key,
# or Android refuses to install the new version over the old one.
keytool -genkeypair -keystore my.keystore -alias opengym -keyalg RSA -validity 10950
# align + sign (zipalign/apksigner ship with the Android SDK build-tools)
zipalign -f -p 4 app-release-unsigned.apk aligned.apk
apksigner sign --ks my.keystore --ks-key-alias opengym --out openGym.apk aligned.apk
Apple does not allow installing apps outside the App Store, so there is no .ipa download
that would simply install. Your free options:
ios/ in Xcode with a free Apple ID as the team and run it
onto your own iPhone. Apple expires the signature after 7 days; re-run from Xcode to renew.versionName/versionCode in android/app/build.gradle per release; keep them in
step with frontend/package.json. versionCode must strictly increase or updates won’t
install over an existing APK.NOTICE.md carries an app-store exception (an additional permission under
AGPL §7) granted by the copyright holder — relevant only if store distribution ever happens.SCHEDULE_EXACT_ALARM so the reminder fires to the minute
where the user allows it.