A Caddy & ExpressJS based webapp for importing and sharing data from the Lyfta fitness app
1.1K
Self-hosted mirror, share-card generator, shareable workout pages & Telegram auto-poster for the Lyfta workout tracker.
sharp. Gender-aware: male/female body maps with automatic fallback/share/:id links anyone can open, showing your share card, workout stats, exercises with type badges (Weight × Reps, 2 Dumbbells, Bodyweight Assisted, etc.), decoded personal records (Est. 1RM, Max Weight, Max Set Volume, Max Reps), and set-by-set details<b>, <i>, <u>, <s>) and template tokensviewProfileGraph API\uXXXX escape sequences from Lyfta's API including emoji surrogate pairsmkdir -p /opt/docker/openlyfta/data
/opt/docker/openlyfta/Caddyfile::80 {
reverse_proxy localhost:3000
}
docker compose:services:
openlyfta:
image: wolfgirl/openlyfta:latest
container_name: openlyfta
restart: unless-stopped
pull_policy: always
ports:
- "80:80"
volumes:
- /opt/docker/openlyfta/data:/data
- /opt/docker/openlyfta/Caddyfile:/etc/caddy/Caddyfile:ro
Open http://<server-ip> in your browser. On first visit, set your admin password.
Go to Settings, enter your Lyfta email and password, then click Sync now.
mkdir -p /opt/docker/openlyfta/data
docker run -d \
--name openlyfta \
-p 80:80 \
-v /opt/docker/openlyfta/data:/data \
-v /opt/docker/openlyfta/Caddyfile:/etc/caddy/Caddyfile:ro \
--restart unless-stopped \
--pull always \
wolfgirl/openlyfta:latest
npm install
npm start
Server runs on http://localhost:3000.
All settings can be configured via the web UI under Settings, or via environment variables:
| Setting | Env Variable | Description | Default |
|---|---|---|---|
| Lyfta email | LYFTA_EMAIL | Your Lyfta account email | - |
| Lyfta password | LYFTA_PASSWORD | Your Lyfta account password | - |
| Lyfta device ID | LYFTA_DEVICE_ID | Device ID string | OpenLyfta, Server, 14 |
| Admin password | OPENLYFTA_ADMIN_PASSWORD | Password for web UI login | Set on first login |
| Weight unit | - | kg or lbs (toggle in Settings) | kg |
| Public URL | - | Base URL for shareable links (e.g. https://gym.example.com) | - |
| Sync schedule | - | Cron expression for auto-sync | */10 * * * * |
| Telegram bot token | - | From @BotFather | - |
| Telegram chat ID | - | Target chat/channel ID | - |
| Telegram auto-send | - | Send new workouts automatically | Off |
https://api.telegram.org/bot<TOKEN>/getUpdates)Use these tokens in your Telegram caption template. HTML formatting is supported (<b>, <i>, <u>, <s>):
| Token | Description |
|---|---|
<date> | Workout date |
<title> | Workout title (decoded) |
<workoutname> | Same as <title> |
<duration> | Workout duration |
<volume> | Total volume (raw number) |
<volumeformatted> | Total volume formatted (e.g. 5,150kg) |
<totalsets> | Total number of sets |
<exercises> | Number of exercises |
<workoutnumber> | Workout number |
<calories> | Calories burned |
<bodyweight> | Body weight |
<sharelink> | Shareable workout page URL (requires Public URL setting) |
#EB445A)#C87887)N × ExerciseName labels under each image (truncated with ellipsis if too long)Each workout has a public page at /share/<id> that shows:
30kg not 30.000kg)#1, #2, etc.)🏆 Est. 1RM: 84, 🏆 Max Weight: 60)┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Caddy │───▶│ Express (Node) │───▶│ SQLite │
│ (port 80) │ │ supervisord │ │ (better- │
│ reverse │ │ ┌─────────────┐ │ │ sqlite3) │
│ proxy │ │ │ Sync engine │ │ └──────────────┘
└─────────────┘ │ │ (Lyfta API) │ │
│ └─────────────┘ │ ┌──────────────┐
│ ┌─────────────┐ │───▶│ Telegram │
│ │ Card gen │ │ │ Bot API │
│ │ (sharp) │ │ └──────────────┘
│ └─────────────┘ │
│ ┌─────────────┐ │ ┌──────────────┐
│ │ Share pages │ │───▶│ Public web │
│ │ (/share/:id)│ │ │ (no auth) │
│ └─────────────┘ │ └──────────────┘
└──────────────────┘
app/
├── src/
│ ├── index.js # Entrypoint: Express server, routes, cron, auto-sync
│ ├── store.js # SQLite schema + CRUD (workouts, exercises, profiles, logs)
│ ├── auth/session.js # Session-based cookie auth
│ ├── lyfta/
│ │ ├── client.js # Lyfta API client (login, feeds, exercises, profile, catalog)
│ │ └── sync.js # Sync orchestrator (feed walk, backfill, catalog, profile)
│ ├── image/
│ │ ├── card.js # Share-card + collage generator (sharp composite + SVG)
│ │ └── muscles.js # Auto-generated muscle→drawable mapping (from APK)
│ ├── telegram/bot.js # Telegram sendPhoto multipart sender + caption templates
│ ├── share/page.js # Server-rendered public share page HTML generator
│ ├── lib/
│ │ ├── pipeline.js # Orchestrates: sync → card gen → Telegram send
│ │ └── logger.js # Logger wrapper (console + SQLite logs table)
│ └── routes/api.js # REST API routes
├── public/
│ ├── index.html # Dashboard GUI
│ ├── stickfigure.png # OpenLyfta icon
│ ├── transparent.png # OpenLyfta logo (transparent background)
│ └── favicon-32.png # Favicon
├── assets/
│ ├── img/ # Logo PNGs
│ ├── font/ # Google Sans TTF for card text rendering
│ └── bodymaps/ # Male + female muscle overlay webps (from Lyfta APK)
├── Dockerfile # Single-image build (Node + Caddy + supervisord)
├── docker-compose.yml # Docker Compose / Portainer deployment
├── Caddyfile # Caddy reverse proxy config (HTTP port 80)
├── supervisord.conf # Process manager config (Caddy + Node)
└── .gitignore
This project is for personal use. Lyfta is a trademark of its respective owners. Neither the developer Riley Kivimäki nor OpenLyfta is affiliated with nor endorsed by Lyfta.
Content type
Image
Digest
sha256:4a189efe8…
Size
238.8 MB
Last updated
4 months ago
docker pull wolfgirl/openlyfta