{"id":50959047,"url":"https://github.com/bndct-devops/lifty","last_synced_at":"2026-06-18T11:30:43.739Z","repository":{"id":341639251,"uuid":"1169304204","full_name":"bndct-devops/lifty","owner":"bndct-devops","description":"Self-hosted personal workout tracker. PWA, multi-profile, themes, rest timer, analytics.","archived":false,"fork":false,"pushed_at":"2026-03-13T17:58:36.000Z","size":1662,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2026-03-14T05:57:29.540Z","etag":null,"topics":["docker","fastapi","pwa","react","self-hosted","workout-tracker"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bndct-devops.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-28T13:39:20.000Z","updated_at":"2026-03-13T17:58:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bndct-devops/lifty","commit_stats":null,"previous_names":["bndct-devops/lifty"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/bndct-devops/lifty","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bndct-devops%2Flifty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bndct-devops%2Flifty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bndct-devops%2Flifty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bndct-devops%2Flifty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bndct-devops","download_url":"https://codeload.github.com/bndct-devops/lifty/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bndct-devops%2Flifty/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34489019,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","fastapi","pwa","react","self-hosted","workout-tracker"],"created_at":"2026-06-18T11:30:41.818Z","updated_at":"2026-06-18T11:30:43.724Z","avatar_url":"https://github.com/bndct-devops.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lifty\n\n\u003e A self-hosted, privacy-first workout tracker inspired by [Strong](https://www.strong.app). No accounts, no cloud, no subscriptions — just your data on your own server.\n\n**FastAPI · React PWA · SQLite · Docker Compose**\n\n---\n\n## Screenshots\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/home.png\" width=\"180\" alt=\"Home tab\" /\u003e\n  \u003cimg src=\"docs/screenshots/workout.png\" width=\"180\" alt=\"Active workout\" /\u003e\n  \u003cimg src=\"docs/screenshots/history.png\" width=\"180\" alt=\"History\" /\u003e\n  \u003cimg src=\"docs/screenshots/history-detail.png\" width=\"180\" alt=\"Workout detail\" /\u003e\n  \u003cimg src=\"docs/screenshots/progress.png\" width=\"180\" alt=\"Progress\" /\u003e\n  \u003cimg src=\"docs/screenshots/settings.png\" width=\"180\" alt=\"Settings\" /\u003e\n\u003c/p\u003e\n\n---\n\n## Features\n\n### Workouts\n- Start a workout, add exercises on the fly, log sets with weight + reps\n- Previous session's sets shown inline as reference\n- Reorder exercises during a workout\n- Rename workouts, add notes\n- Mark rest days from the home screen\n\n### Rest Timer\n- Configurable duration (60 / 90 / 120 / 180s) saved per profile\n- Web Audio ding + vibration on finish\n- Background-accurate — stays correct after screen lock or tab switch\n- Push notification fires even when the screen is off (Android + iOS PWA)\n\n### Progress \u0026 History\n- Full workout log with monthly calendar view\n- Per-workout detail sheet — stats, muscle group breakdown, sets with estimated 1RM\n- PRs per exercise (Epley 1RM), grouped by body part\n- Weekly / daily volume bar chart (sets or tonnage)\n- Muscle group donut chart\n- 26-week activity heatmap\n\n### Profiles \u0026 Settings\n- Multiple profiles on a single instance\n- Per-profile: unit (kg / lbs), theme, avatar colour, week start day, rest duration, ding toggle\n- Themes: Dark, Light, AMOLED, Tokyo Night, Dracula, Nord, Gruvbox, Rosé Pine, [Catppuccin](https://catppuccin.com) Mocha / Macchiato / Frappé / Latte\n\n### Import \u0026 Export\n- Strong CSV import — bring in your full workout history\n- CSV export per profile\n\n### PWA\n- Installable on iOS and Android\n- Offline support via service worker\n- Flamingo barbell icon, themed status bar\n\n---\n\n## PWA \u0026 Service Worker\n\nThe service worker provides two things:\n\n| Feature | How it works |\n|---|---|\n| **Offline support** | Static assets cached on first load; API falls back to cached responses when offline |\n| **Background notifications** | Rest timer end time posted to SW on start; SW fires `showNotification` at the right time regardless of whether the page is suspended |\n\n**Platform support:**\n\n| Platform | Offline | Background notification |\n|---|---|---|\n| Android | ✅ | ✅ |\n| iOS 16.4+ (PWA) | ✅ | ✅ — add to home screen first |\n| iOS \u003c 16.4 / desktop | ✅ | ❌ — in-page ding still works when visible |\n\n---\n\n## Stack\n\n| Layer | Tech |\n|---|---|\n| Backend | FastAPI + SQLModel + SQLite, Python 3.11 |\n| Frontend | React 18 + Vite + plain CSS |\n| Serving | nginx (frontend), uvicorn (backend) |\n| Containers | Docker + Docker Compose |\n| CI | GitHub Actions → `ghcr.io` (amd64 + arm64) |\n\n---\n\n## Running locally\n\n```bash\n./scripts/dev_up.sh\n```\n\nBuilds both containers and seeds the exercise library.\n\n| Service | URL |\n|---|---|\n| Frontend | http://localhost:5173 |\n| Backend API | http://localhost:8000 |\n| API docs | http://localhost:8000/docs |\n\n**Reset the database:**\n```bash\nrm data/lifty.db \u0026\u0026 docker compose restart backend\n```\n\n---\n\n## Self-hosting\n\nImages are built and pushed to `ghcr.io` on every push to `main` (amd64 + arm64).\n\nCreate a `docker-compose.yml` on your host:\n\n```yaml\nservices:\n  backend:\n    image: ghcr.io/bndct-devops/lifty-backend:latest\n    restart: unless-stopped\n    expose:\n      - \"8000\"\n    volumes:\n      - /mnt/user/appdata/lifty:/data   # adjust path as needed\n    environment:\n      - LIFTY_DB=/data/lifty.db\n      # - LIFTY_PASSWORD=your-password-here\n  frontend:\n    image: ghcr.io/bndct-devops/lifty-frontend:latest\n    restart: unless-stopped\n    ports:\n      - \"3420:80\"\n    depends_on:\n      - backend\n```\n\nThen:\n\n```bash\ndocker compose pull\ndocker compose up -d\n```\n\nUpdate the volume path to wherever you want the SQLite database stored on your host.\n\n---\n\n## Security\n\nBy default the API has **no authentication** — fine for local/VPN use, but **set a password before exposing to the internet**.\n\n### Enable instance auth\n\nUncomment `LIFTY_PASSWORD` in your `docker-compose.yml`:\n\n```yaml\nenvironment:\n  - LIFTY_DB=/data/lifty.db\n  - LIFTY_PASSWORD=your-strong-password\n```\n\nRestart the backend. The app will show a password screen on load. Once unlocked, a 30-day JWT is stored in the browser — you won't be prompted again until it expires.\n\n- **Change/reset password**: Settings → Instance Auth → Change Password (or set `LIFTY_PASSWORD` env var again and restart to override)\n- **Sign out**: Settings → Instance Auth → Sign Out\n\n## Project structure\n\n```\nbackend/\n  main.py              # FastAPI app, all endpoints\n  models.py            # SQLModel table definitions\n  schemas.py           # Pydantic request/response types\n  db.py                # engine + table creation\n  seed_exercises.py    # built-in exercise library (runs on startup)\nfrontend/\n  public/\n    sw.js              # service worker — offline cache + background notifications\n    manifest.json      # PWA manifest\n    favicon.svg        # flamingo barbell icon\n  src/\n    App.jsx            # entire frontend\n    api.js             # fetch wrappers for all backend endpoints\n    styles.css         # CSS custom properties + layout\n  nginx.conf           # proxies /api/* to backend\nscripts/\n  dev_up.sh            # one-command local dev start\ndocker-compose.yml         # local dev (builds from source)\n.github/workflows/\n  build-push.yml       # CI: build multi-arch images, push to ghcr.io\n```\n\n---\n\n## Acknowledgements\n\n- Inspired by [Strong](https://www.strong.app) — the best commercial workout tracker, which lifty aims to self-host-replace\n- Catppuccin theme palette by [Catppuccin](https://catppuccin.com)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbndct-devops%2Flifty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbndct-devops%2Flifty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbndct-devops%2Flifty/lists"}