Compare commits

...

2 Commits

Author SHA1 Message Date
54a885120d feat(sparkyfitness): add sparkyfitness stack to heimdall
- Compose file with pinned images (v0.16.6.1), healthchecks, Traefik routing
- .env.example with all required variables and generation commands
- README covering access, appdata, backup scope, upgrade procedure
- KBA-002: deployment reference with verification and rollback steps
- repo-deploy.prompt.md: reusable end-to-end deployment workflow prompt
- Session snapshot 2026-05-10

Source: https://github.com/CodeWithCJ/SparkyFitness
2026-05-10 21:58:38 -04:00
d1b25c21a3 fix: update OLLAMA_BASE_URL to use environment variable 2026-05-10 15:27:01 -04:00
6 changed files with 927 additions and 1 deletions

294
.github/prompts/repo-deploy.prompt.md vendored Normal file
View File

@ -0,0 +1,294 @@
---
description: "Gated, end-to-end workflow for evaluating a public or private repo and deploying it into the homelab. Covers repo analysis, node placement, Compose authoring, documentation generation (README + SOP/KBA), smoke testing, and GitOps commit. One repo at a time."
---
# [ROLE]
You are a **Senior Homelab DevOps Engineer** acting as a **mentor and deployment guide**. You help a homelab operator safely evaluate any repo and shepherd it through the full deployment lifecycle: analysis → planning → documentation → testing → production.
You know this lab's conventions intimately:
- **Nodes**: `heimdall` (general apps, media, tools), `waldorf` (GPU/media workloads, Immich, Plex), `watchtower` (Docker auto-updates)
- **Orchestration**: Komodo (GitOps via Gitea), stacks live at `nodes/<node>/<service>/compose.yaml`
- **Reverse proxy**: Traefik with labels for routing
- **Secrets**: `.env` files managed with `git-crypt`; secrets never committed in plaintext
- **Appdata**: NFS at `10.0.0.250:/Volume1/appdata/<service>` or local `/opt/appdata/<service>`
- **User conventions**: `PUID=1000 PGID=1000` unless otherwise required
- **Documentation**: SOPs in `documentation/SOPs/`, KBAs in `documentation/KBAs/`
# [NON-NEGOTIABLES]
- **One repo at a time.** Do not attempt to deploy multiple services in a single run.
- **Explicit confirmation gates.** Do **not** advance past any gate without the exact confirmation phrase.
- **Secrets are sacred.** Never write secrets into tracked files. Always use `.env` references.
- **Minimal blast radius.** Changes must not affect unrelated stacks or infrastructure.
- **Documentation is not optional.** Every deployment produces a README and at minimum a KBA or SOP.
- **Testing is not optional.** Every deployment passes a smoke test checklist before being marked complete.
# [INPUTS]
The primary input variable:
- Repo URL: `${input:repoUrl}`
Additional inputs are collected progressively through the workflow gates.
---
# [WORKFLOW]
## Gate 0 — Repo Intake
**Prompt the user:**
> I have the repo URL. Before we dive in, confirm you are ready to start the deployment lifecycle for this repo. Type exactly:
> `DEPLOY: <short-service-name>`
Do not proceed until the exact phrase is received.
Once confirmed, **fetch and analyze the repo** at `${input:repoUrl}`. Produce a structured **Repo Snapshot** with these fields:
| Field | Value |
|---|---|
| **Repo Name** | |
| **Maintainer / Org** | |
| **Description** | |
| **Primary Language** | |
| **License** | |
| **Last Commit** | |
| **Stars / Activity Signal** | |
| **Deployment Type** | `Docker`, `Docker Compose`, `Binary`, `Script`, `Ansible Role`, `Other` |
| **Official Docs URL** | |
| **Notable Tags / Releases** | |
| **Docker Hub / GHCR image** | (if applicable) |
If the repo is inaccessible, ask the user to paste the relevant README or `docker-compose.yml` sections directly.
---
## Step 1 — Classify & Risk-Assess
Based on the Repo Snapshot, determine:
1. **Deployment Category**
- `A` — Docker Compose app (has image, can integrate into node stacks)
- `B` — Script or CLI tool (runs on host or in CI)
- `C` — Ansible role or playbook
- `D` — Other / requires custom approach
2. **Risk Level**: `Low` / `Medium` / `High`
- High: requires privileged mode, Docker socket, host network, root
- Medium: exposes ports, needs persistent storage, touches shared volumes
- Low: stateless, no special privileges
3. **Recommended Node**: Suggest `heimdall`, `waldorf`, or `watchtower` with reasoning.
4. **Dependency Flags**: Call out any of the following if present:
- External databases (Postgres, Redis, MySQL)
- GPU requirements
- Specific kernel modules or host dependencies
- OAuth / SSO integration (Authentik)
Present this as a brief **Classification Report** and ask for feedback before continuing.
Required confirmation phrase:
> `CLASSIFY CONFIRMED: <service-name>`
---
## Step 2 — Deployment Planning
Collect the following from the user (prompt for each):
- **Target node**: `${input:targetNode}` (heimdall / waldorf / watchtower)
- **Compose folder path**: `nodes/${input:targetNode}/${input:serviceName}/compose.yaml`
- **Appdata path**: `${input:appdataPath}` (NFS or local)
- **Desired subdomain / URL**: `${input:subdomain}.castaldifamily.com` (or none)
- **Authentik SSO**: Required? `${input:ssoRequired}` (yes / no / later)
- **Komodo stack name**: `${input:stackName}`
Then produce a **Deployment Plan** covering:
1. **Compose file location** — path in Git repo
2. **Appdata layout** — directories to pre-create
3. **Network plan** — which Docker networks, port allocations; flag conflicts with existing services
4. **Secrets plan** — list of env vars that must go in `.env`, never in `compose.yaml`
5. **Traefik routing** — labels needed for HTTP/HTTPS routing (if applicable)
6. **SSO integration** — Authentik middleware labels (if applicable)
7. **Komodo configuration** — stack name, repo path, branch, deploy triggers
8. **Backup scope** — which appdata paths need backup coverage
Required confirmation phrase:
> `PLAN CONFIRMED: <service-name>`
---
## Step 3 — Author Compose File
Produce a **production-ready `compose.yaml`** for `nodes/<node>/<service>/compose.yaml` following lab conventions:
```yaml
# nodes/<node>/<service>/compose.yaml
# Deployed via Komodo | Managed by GitOps
# Service: <service-name>
# Source: <repoUrl>
```
Requirements:
- Use a **pinned image tag** (not `latest`)
- Set `restart: unless-stopped`
- Define a `healthcheck` if the upstream image supports it
- Mount appdata via bind mounts (no anonymous volumes)
- Reference all secrets as `${VAR_NAME}` — never hardcoded
- Set `PUID` / `PGID` if the image respects them
- Include Traefik labels if subdomain was provided
- Include Authentik middleware labels if SSO was requested
- Add `deploy.resources.limits` (cpu and memory) with conservative defaults
Also produce a **`.env.example`** listing every variable with a description comment and placeholder value. This file IS committed to Git. The actual `.env` is not.
Required confirmation phrase:
> `COMPOSE APPROVED: <service-name>`
---
## Step 4 — Generate Documentation
Produce **two documents**:
### 4a — Service README
File: `nodes/<node>/<service>/README.md`
Sections:
- **Overview**: What the service does, link to upstream repo and docs
- **Node**: Which node runs it and why
- **Access**: URL, auth method
- **Appdata**: Paths and what lives there
- **Environment Variables**: Table of all vars (name, purpose, example value)
- **Networking**: Ports, Docker networks, Traefik routing
- **Dependencies**: External services or databases required
- **Backup & Recovery**: What to back up, restore steps
- **Known Issues / Notes**: Anything quirky about this deployment
### 4b — Knowledge Base Article (KBA) or SOP
**Choose based on complexity:**
- **KBA** — if this is a standalone service with no multi-step operational process
- **SOP** — if deployment involves multi-node coordination, migrations, or recurring procedures
File: `documentation/KBAs/KBA-XXX-<Service-Name>-Deployment.md` OR `documentation/SOPs/SOP-XXX-Deploy-<Service-Name>.md`
Sections for KBA:
- **Symptom / Use Case**
- **Resolution / Deployment Steps**
- **Verification**
- **Related Resources**
Sections for SOP:
- **Purpose**
- **Prerequisites** (access, infrastructure checklist)
- **Procedures** (numbered steps)
- **Rollback**
- **Verification**
- **Revision History**
Required confirmation phrase:
> `DOCS APPROVED: <service-name>`
---
## Step 5 — Testing & Validation Checklist
Before marking the deployment ready, run through this checklist and mark each item as `PASS`, `FAIL`, or `SKIP` with a note:
### Pre-Deploy Checks
- [ ] Image tag is pinned (not `latest`)
- [ ] All required env vars are documented in `.env.example`
- [ ] No secrets present in `compose.yaml` or README
- [ ] Port conflicts checked against existing node services
- [ ] Appdata directories noted for pre-creation
- [ ] `git-crypt` `.env` file excluded from plaintext tracking (`.gitattributes`)
### Deploy Checks
- [ ] Komodo stack pulls from correct repo path and branch
- [ ] Container starts without error (`docker compose up -d` clean exit)
- [ ] Healthcheck reaches `healthy` state within expected time
- [ ] Service is reachable at the expected URL or port
- [ ] Traefik routes correctly (HTTPS, no certificate errors)
- [ ] SSO / Authentik login works (if configured)
### Post-Deploy Checks
- [ ] Logs show no persistent errors (`docker compose logs --tail=50`)
- [ ] Appdata directories created with correct ownership
- [ ] Service survives a container restart (`docker compose restart`)
- [ ] Komodo webhook triggers a re-deploy successfully (GitOps round-trip)
Present the completed checklist to the user. If any items are `FAIL`, do not proceed — diagnose and resolve first.
Required confirmation phrase:
> `TESTS PASSED: <service-name>`
---
## Gate 5 — GitOps Commit
Present a **commit plan** summarizing all files that will be added or modified:
| Action | File |
|---|---|
| `ADD` | `nodes/<node>/<service>/compose.yaml` |
| `ADD` | `nodes/<node>/<service>/README.md` |
| `ADD` | `nodes/<node>/<service>/.env.example` |
| `ADD` | `documentation/KBAs/KBA-XXX-...md` OR `documentation/SOPs/SOP-XXX-...md` |
Provide the suggested commit message:
```
feat(<service-name>): add <service-name> stack to <node>
- Compose file with pinned image, healthcheck, Traefik routing
- .env.example with all required variables documented
- README covering access, appdata, backup scope
- KBA/SOP for deployment reference
Source: <repoUrl>
```
Required confirmation phrase:
> `COMMIT READY: <service-name>`
Only after this confirmation: provide final file contents ready to copy/paste or apply.
---
## Gate 6 — Deployment Complete
Once files are committed and the Komodo stack is live, prompt the user to confirm:
> "Deployment is live. Run through the Post-Deploy Checks one final time and confirm everything is green."
Required confirmation phrase:
> `DEPLOYED: <service-name>`
Output a brief **Deployment Summary**:
- Service name and URL
- Node
- Image and tag
- Appdata path
- Documentation files created
- Date deployed
- Any open follow-up items (backup integration, SSO, monitoring)
---
# [FORMAT]
- All output is Markdown
- Use tables for structured data (env vars, checklist, file lists)
- Use fenced code blocks with syntax highlighting for all YAML, shell, and config output
- Gate confirmations must be quoted exactly — highlight them in a `> blockquote`
- Never output partial steps; always complete a step fully before presenting the gate
# [TONE]
Mentor-first. Explain *why* each decision matters. Flag risks clearly without being alarmist. Keep momentum — the goal is a clean, documented, tested deployment in the homelab.

View File

@ -0,0 +1,220 @@
# KBA-002: SparkyFitness — Initial Deployment on Heimdall
**Status:** Active
**Created:** May 10, 2026
**Last Updated:** May 10, 2026
**Affected Systems:** heimdall, Komodo, Traefik
**Severity:** Informational
---
## Summary
Reference article for deploying SparkyFitness (self-hosted fitness tracker) on the heimdall node. Covers pre-deployment checklist, secret generation, first-boot sequence, and verification steps.
---
## Use Case
You want to deploy SparkyFitness for the first time, or you are recovering from a failed deployment or data loss event and need to redeploy from scratch.
---
## Prerequisites
### Access Required
- [ ] SSH access to `heimdall`
- [ ] Gitea access: `git.castaldifamily.com`
- [ ] Komodo UI access: `komodo.castaldifamily.com`
- [ ] NFS share mounted and writable at `/mnt/appdata/` on heimdall
### Files in Repo
Confirm these files exist in `nodes/heimdall/sparkyfitness/` on `main`:
- [ ] `compose.yaml`
- [ ] `.env.example`
- [ ] `README.md`
---
## Resolution / Deployment Steps
### Step 1 — Generate Secrets
On any machine with `openssl` available, generate the four required secrets:
```bash
# API encryption key — SAVE THIS. Do not rotate after data is stored.
openssl rand -hex 32
# Better Auth secret — SAVE THIS. Do not rotate after users enable 2FA.
openssl rand -hex 32
# Database superuser password
openssl rand -base64 24
# Application user password
openssl rand -base64 24
```
Store these immediately in the git-crypt-encrypted `.env` file at:
```
nodes/heimdall/sparkyfitness/.env
```
Using `.env.example` as the template:
```bash
cp nodes/heimdall/sparkyfitness/.env.example nodes/heimdall/sparkyfitness/.env
# Edit .env and fill in all four required secret values
```
> ⚠️ Confirm `.gitattributes` marks `nodes/heimdall/sparkyfitness/.env` as a git-crypt file before committing.
### Step 2 — Create Appdata Directories
SSH into heimdall and pre-create the required directories:
```bash
ssh heimdall
mkdir -p /mnt/appdata/sparkyfitness/data/postgresql
mkdir -p /mnt/appdata/sparkyfitness/data/backup
mkdir -p /mnt/appdata/sparkyfitness/data/uploads
```
> PostgreSQL will fail to initialize if `postgresql/` does not exist or has incorrect ownership. Docker will create the directory automatically, but pre-creating it ensures the NFS mount is active and writable before the container starts.
### Step 3 — Commit and Push
```bash
git add nodes/heimdall/sparkyfitness/
git commit -m "feat(sparkyfitness): add sparkyfitness stack to heimdall
- Compose file with pinned images (v0.16.6.1), healthchecks, Traefik routing
- .env.example with all required variables documented
- README covering access, appdata, backup scope
Source: https://github.com/CodeWithCJ/SparkyFitness"
git push origin main
```
### Step 4 — Create Komodo Stack
1. Open Komodo UI → **Stacks** → **New Stack**
2. Configure:
| Field | Value |
|---|---|
| **Name** | `sparkyfitness` |
| **Server** | `heimdall` |
| **Source** | Git Repo |
| **Repo** | `homelab` |
| **Branch** | `main` |
| **Run Directory** | `nodes/heimdall/sparkyfitness` |
| **File Paths** | *(leave blank — uses `compose.yaml` by default)* |
| **Webhook** | Enable — trigger on push to `main` |
| **Auto Update** | ❌ Disabled |
3. Click **Deploy**
### Step 5 — First Boot Sequence
The startup order is enforced by `depends_on` + `healthcheck` conditions:
```
sparkyfitness-db (healthy) → sparkyfitness-server (healthy) → sparkyfitness-frontend
```
Expected timeline:
- `sparkyfitness-db`: healthy within ~30 seconds
- `sparkyfitness-server`: ~6090 seconds (runs DB migrations on first boot)
- `sparkyfitness-frontend`: starts once server is healthy
Monitor startup:
```bash
ssh heimdall
docker compose -f /etc/komodo/stacks/sparkyfitness/compose.yaml logs -f
```
### Step 6 — Set Admin Account
If `SPARKY_FITNESS_ADMIN_EMAIL` is set in `.env`, the first user with that email will be automatically granted admin on server startup.
Alternatively, register via the UI at https://fitness.castaldifamily.com, then promote from the Admin panel.
> After creating your account, set `SPARKY_FITNESS_DISABLE_SIGNUP=true` in `.env` to close open registration. Redeploy to apply.
---
## Verification
Run through the following checks after deployment:
### Container Health
```bash
docker ps --filter "name=sparkyfitness"
# All three containers should show: Up X minutes (healthy)
```
### Log Check
```bash
docker compose logs --tail=50 sparkyfitness-server
# Should show: server started, DB migrations complete, no ERRORs
```
### UI Access
- Open https://fitness.castaldifamily.com in a browser
- Confirm: HTTPS with valid certificate, no browser errors
- Confirm: Login page loads
### Traefik Routing
- Open Traefik dashboard → confirm `sparkyfitness` router is active and green
- Confirm TLS cert is issued via Cloudflare resolver
### Restart Resilience
```bash
docker restart sparkyfitness-frontend
# Wait 30s, then confirm the UI is still accessible
```
---
## Rollback
If deployment fails and you need to revert:
1. Stop the stack in Komodo: **Stack → Stop**
2. Remove containers: `docker compose down` on heimdall
3. The `postgresql/` data directory is untouched by `docker compose down` (bind mount, not a volume)
4. Revert the compose file changes in Git and push — Komodo will redeploy the previous version
> ⚠️ `docker compose down -v` would only affect named volumes. Since we use bind mounts, data in `/mnt/appdata/sparkyfitness/data/` is always preserved unless you manually delete it.
---
## Related Resources
- [SparkyFitness README](../../nodes/heimdall/sparkyfitness/README.md)
- [Upstream GitHub](https://github.com/CodeWithCJ/SparkyFitness)
- [Official Docs](https://codewithcj.github.io/SparkyFitness/)
- [Docker Compose Install Guide](https://codewithcj.github.io/SparkyFitness/install/docker-compose)
- [Environment Variables Reference](https://codewithcj.github.io/SparkyFitness/install/environment-variables)
- [Postgres Upgrade Guide](https://codewithcj.github.io/SparkyFitness/install/postgres-upgrade)
- [KBA-001: Komodo GitOps Stack Deployment Failures](KBA-001-Komodo-GitOps-Stack-Deployment-Failures.md)
---
## Revision History
| Date | Author | Change |
|---|---|---|
| 2026-05-10 | Nathan Castaldi | Initial deployment — SparkyFitness v0.16.6.1 on heimdall |

View File

@ -0,0 +1,82 @@
# Session Snapshot - 2026-05-10
## Summary
Built a reusable end-to-end repo deployment prompt (`repo-deploy.prompt.md`) and ran it live against [SparkyFitness](https://github.com/CodeWithCJ/SparkyFitness) — a self-hosted fitness tracker. Completed all pre-deploy phases: repo analysis, classification, planning, compose authoring, and documentation. Stack is ready to deploy on heimdall pending live smoke tests on the node.
---
## Work Completed
### New Prompt
- Created `.github/prompts/repo-deploy.prompt.md` — a gated, six-step workflow prompt that takes a repo URL and shepherds it through the full deployment lifecycle: analysis → classification → planning → compose authoring → documentation → testing → GitOps commit. Designed to be reusable for any future service.
### SparkyFitness Deployment (heimdall)
- Fetched and analyzed upstream repo (`v0.16.6.1`, 3.4k stars, actively maintained)
- Classified as Category A (Docker Compose), Risk: Medium, Node: heimdall
- Confirmed no port conflicts with existing heimdall services
- Produced deployment plan: `proxy-net` + isolated `sparkyfitness-network`, no host ports exposed, Traefik routing to `fitness.castaldifamily.com`
- Authored `nodes/heimdall/sparkyfitness/compose.yaml` with pinned images, healthchecks on all three services, resource limits, and Traefik labels
- Authored `nodes/heimdall/sparkyfitness/.env.example` with all four required secrets, generation commands, and optional OIDC/SMTP/Garmin blocks
- Created `nodes/heimdall/sparkyfitness/README.md` — full service reference (access, appdata, env vars, networking, backup/recovery, upgrade procedure, known issues)
- Created `documentation/KBAs/KBA-002-SparkyFitness-Deployment.md` — step-by-step deployment KBA covering secret generation, appdata setup, Komodo stack creation, first-boot sequence, verification, and rollback
---
## Files Changed
| Action | File |
|---|---|
| `ADD` | `.github/prompts/repo-deploy.prompt.md` |
| `ADD` | `nodes/heimdall/sparkyfitness/compose.yaml` |
| `ADD` | `nodes/heimdall/sparkyfitness/.env.example` |
| `ADD` | `nodes/heimdall/sparkyfitness/README.md` |
| `ADD` | `documentation/KBAs/KBA-002-SparkyFitness-Deployment.md` |
---
## Validation and Test Results
### Pre-Deploy Checks (static — all PASS)
- ✅ All image tags pinned (`postgres:18.3-alpine`, `sparkyfitness_server:v0.16.6.1`, `sparkyfitness:v0.16.6.1`)
- ✅ No secrets in tracked files — all four secrets use `${}` interpolation; `.env` covered by `.gitignore`
- ✅ No port conflicts on heimdall
- ✅ Appdata pre-creation steps documented in KBA-002
- ✅ `.env.example` documents all required variables with generation commands
### Deploy / Post-Deploy Checks
- ⬜ **PENDING** — require live execution on heimdall (Komodo stack creation, container health, Traefik routing, log check, restart resilience, webhook round-trip)
---
## New Technical Debt
- None. No `@TODO` or `FIXME` comments introduced this session.
---
## Open Issues
- SparkyFitness deploy/post-deploy smoke tests not yet run — stack has not been pushed to Komodo and brought live. Must complete before marking `TESTS PASSED: sparkyfitness`.
- No git-crypt configured in this repo — `.env` files are excluded via `.gitignore` rather than encrypted and tracked. Secrets are not version-controlled. Recommend storing secrets in Vaultwarden and/or completing the git-crypt migration (see `documentation/plans/plan-gitcryptMigration.md`).
- Garmin microservice (`sparkyfitness-garmin`) is commented out — upstream marks it as still in development. Revisit when maintainer marks stable.
- Upcoming mandatory Postgres upgrade flagged by upstream in release notes — monitor and apply before it becomes breaking.
---
## Next Steps
1. SSH into heimdall, pre-create appdata directories (`mkdir -p /mnt/appdata/sparkyfitness/data/{postgresql,backup,uploads}`)
2. Generate secrets (`openssl rand -hex 32` ×2, `openssl rand -base64 24` ×2) and populate `nodes/heimdall/sparkyfitness/.env`
3. Commit and push all new files to `main`
4. Create Komodo stack `sparkyfitness` per KBA-002 Step 4
5. Run live smoke test checklist from Step 5 and confirm `TESTS PASSED: sparkyfitness`
6. Complete Gates 5 & 6 of `repo-deploy.prompt.md` (commit plan → deployment summary)
7. After first use is stable, consider registering initial admin account and disabling open signup (`SPARKY_FITNESS_DISABLE_SIGNUP=true`)
---
## Session Notes
- `repo-deploy.prompt.md` was validated end-to-end in this session and is ready for reuse on future services. The gated confirmation model (exact phrases per step) worked well for keeping the workflow deliberate.
- SparkyFitness chosen as the first service run through the new prompt. All static gates passed; live deploy is the remaining work.

View File

@ -0,0 +1,181 @@
# SparkyFitness
**Node:** heimdall
**Stack:** `sparkyfitness`
**URL:** https://fitness.castaldifamily.com
**Version:** v0.16.6.1
**Source:** https://github.com/CodeWithCJ/SparkyFitness
**Docs:** https://codewithcj.github.io/SparkyFitness/
---
## Overview
SparkyFitness is a self-hosted, privacy-first fitness and health tracking platform — a full alternative to MyFitnessPal. It stores all data on infrastructure you control with no reliance on third-party services.
**Core capabilities:**
- Nutrition, exercise, hydration, sleep, fasting, mood, and body measurement tracking
- Goal setting and daily check-ins
- Interactive charts and long-term reports
- Multiple user profiles and family access
- Native OIDC, TOTP, Passkey, and MFA support
- Health platform integrations: Apple Health, Google Health Connect, Fitbit, Withings, Garmin, Polar, Strava
- Food database integrations: OpenFoodFacts, USDA, FatSecret, Nutritionix
- Recipe app integrations: Mealie, Tandoor
- Optional AI chatbot for conversational food/exercise logging (beta)
**Stack architecture:**
| Container | Image | Role |
|---|---|---|
| `sparkyfitness-db` | `postgres:18.3-alpine` | PostgreSQL database |
| `sparkyfitness-server` | `codewithcj/sparkyfitness_server:v0.16.6.1` | Node.js backend API |
| `sparkyfitness-frontend` | `codewithcj/sparkyfitness:v0.16.6.1` | React frontend (Nginx) |
---
## Node
Runs on **heimdall** — the general-purpose applications node. No GPU is required. Workload is typical of a personal web application.
---
## Access
| Method | Detail |
|---|---|
| **URL** | https://fitness.castaldifamily.com |
| **Auth** | SparkyFitness native auth (TOTP, passkeys, MFA supported) |
| **SSO** | Not configured — OIDC with Authentik can be added via env vars (see `.env.example`) |
| **Admin panel** | Set `SPARKY_FITNESS_ADMIN_EMAIL` in `.env` to auto-grant admin on first start |
> ⚠️ Set `SPARKY_FITNESS_DISABLE_SIGNUP=true` in `.env` after creating your initial account to prevent open registration.
---
## Appdata
All persistent data lives under `/mnt/appdata/sparkyfitness/data/` on the NFS share.
| Path | Contents | Backup Priority |
|---|---|---|
| `postgresql/` | All user data — nutrition logs, workouts, health metrics, accounts | 🔴 Critical |
| `uploads/` | Profile pictures and exercise images | 🟡 Important |
| `backup/` | App-generated PostgreSQL exports | 🟢 Nice-to-have |
Pre-create these directories before first deploy:
```bash
mkdir -p /mnt/appdata/sparkyfitness/data/postgresql
mkdir -p /mnt/appdata/sparkyfitness/data/backup
mkdir -p /mnt/appdata/sparkyfitness/data/uploads
```
---
## Environment Variables
Managed via `.env` (git-crypt encrypted). See `.env.example` for the full reference.
### Required Secrets
| Variable | Purpose | How to Generate |
|---|---|---|
| `SPARKY_FITNESS_DB_PASSWORD` | PostgreSQL superuser password | `openssl rand -base64 24` |
| `SPARKY_FITNESS_APP_DB_PASSWORD` | App-role DB password (limited privileges) | `openssl rand -base64 24` |
| `SPARKY_FITNESS_API_ENCRYPTION_KEY` | 64-char hex key for external integration encryption | `openssl rand -hex 32` |
| `BETTER_AUTH_SECRET` | Signs sessions and encrypts TOTP/2FA data | `openssl rand -hex 32` |
> ⚠️ `SPARKY_FITNESS_API_ENCRYPTION_KEY` — changing this after data is stored invalidates all encrypted external integration credentials.
> ⚠️ `BETTER_AUTH_SECRET` — changing this after users have enabled TOTP/2FA locks them out. Do not rotate without disabling 2FA first.
### Non-Secret Configuration (hardcoded in compose.yaml)
| Variable | Value | Notes |
|---|---|---|
| `SPARKY_FITNESS_FRONTEND_URL` | `https://fitness.castaldifamily.com` | Used for CORS and session binding |
| `SPARKY_FITNESS_DB_HOST` | `sparkyfitness-db` | Internal Docker service name |
| `SPARKY_FITNESS_DB_NAME` | `sparkyfitness_db` | Database name |
| `SPARKY_FITNESS_DB_USER` | `sparky` | Superuser for migrations |
| `SPARKY_FITNESS_APP_DB_USER` | `sparky_app` | Runtime app user (limited privileges) |
| `SPARKY_FITNESS_LOG_LEVEL` | `ERROR` | Use `DEBUG` temporarily for troubleshooting |
| `NODE_ENV` | `production` | |
| `TZ` | `Etc/UTC` | Adjust if local time is needed for date handling |
| `SPARKY_FITNESS_FORCE_EMAIL_LOGIN` | `true` | Failsafe — prevents OIDC misconfiguration lockout |
---
## Networking
| Network | Type | Used By |
|---|---|---|
| `proxy-net` | External (Traefik) | `sparkyfitness-frontend` only |
| `sparkyfitness-network` | Internal bridge | All three containers |
No host ports are exposed. All external traffic enters via Traefik.
### Traefik Routing
| Field | Value |
|---|---|
| **Router** | `sparkyfitness` |
| **Rule** | `Host(\`fitness.castaldifamily.com\`)` |
| **Entrypoint** | `websecure` (HTTPS) |
| **TLS** | Cloudflare cert resolver |
| **Middleware** | `security-headers@file` |
| **Backend port** | `80` (Nginx inside `sparkyfitness-frontend`) |
The frontend Nginx container internally proxies API requests to `sparkyfitness-server:3010` over `sparkyfitness-network`. No separate Traefik route is needed for the API.
---
## Dependencies
| Dependency | Type | Notes |
|---|---|---|
| PostgreSQL 18.3-alpine | Bundled | Self-contained — no shared DB instance |
| Traefik | External (core stack) | Must be running on `proxy-net` |
| Cloudflare DNS | External | Required for TLS cert resolver |
No Redis, GPU, or kernel module dependencies.
---
## Backup & Recovery
### What to Back Up
The `postgresql/` directory is the **single source of truth** for all user data. This must be in the backup rotation before going live.
```
/mnt/appdata/sparkyfitness/data/postgresql/ ← Critical
/mnt/appdata/sparkyfitness/data/uploads/ ← Important
```
### Restore Steps
1. Stop the stack: `docker compose down`
2. Restore the PostgreSQL data directory to `/mnt/appdata/sparkyfitness/data/postgresql/`
3. Restore uploads to `/mnt/appdata/sparkyfitness/data/uploads/`
4. Redeploy: `docker compose up -d`
5. Verify the DB is healthy: `docker compose logs sparkyfitness-db`
### Upgrading
> ⚠️ Auto-updating containers is **not recommended** by the maintainer. Watchtower should be excluded from this stack.
1. Review release notes at https://github.com/CodeWithCJ/SparkyFitness/releases
2. Take a backup of `postgresql/`
3. Update image tags in `compose.yaml`
4. Commit and push — Komodo will redeploy via webhook
5. Check logs: `docker compose logs --tail=50`
---
## Known Issues / Notes
- **Garmin microservice** (`sparkyfitness-garmin`) is still in development upstream. It is commented out of the compose file intentionally. Do not enable until the maintainer marks it stable.
- **Auto-update warning**: The maintainer explicitly warns against automated container updates between releases. Ensure Watchtower is not targeting this stack.
- **Postgres upgrade notice**: The upstream release notes flag a forthcoming mandatory Postgres upgrade. Monitor release notes and follow the [official Postgres upgrade guide](https://codewithcj.github.io/SparkyFitness/install/postgres-upgrade) when the time comes.
- **AI chatbot** is in beta — expect potential bugs if enabled.
- **Family & Friends access** is in beta — expect potential bugs if enabled.

View File

@ -0,0 +1,149 @@
x-info:
repo: https://github.com/CodeWithCJ/SparkyFitness
releases: https://github.com/CodeWithCJ/SparkyFitness/releases
documentation: https://codewithcj.github.io/SparkyFitness/
# nodes/heimdall/sparkyfitness/compose.yaml
# Deployed via Komodo | Managed by GitOps
# Service: sparkyfitness
# Source: https://github.com/CodeWithCJ/SparkyFitness
networks:
proxy-net:
external: true
sparkyfitness-network:
driver: bridge
services:
# ── Database ────────────────────────────────────────────────────────────────
sparkyfitness-db:
image: postgres:18.3-alpine
container_name: sparkyfitness-db
restart: unless-stopped
networks:
- sparkyfitness-network
environment:
POSTGRES_DB: sparkyfitness_db
POSTGRES_USER: sparky
POSTGRES_PASSWORD: "${SPARKY_FITNESS_DB_PASSWORD}"
PUID: 1000
GUID: 1000
volumes:
- /mnt/appdata/sparkyfitness/data/postgresql:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U sparky -d sparkyfitness_db"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ── Backend Server ───────────────────────────────────────────────────────────
sparkyfitness-server:
image: codewithcj/sparkyfitness_server:v0.16.6.1
container_name: sparkyfitness-server
restart: unless-stopped
networks:
- sparkyfitness-network
environment:
# Database connection
SPARKY_FITNESS_DB_HOST: sparkyfitness-db
SPARKY_FITNESS_DB_PORT: 5432
SPARKY_FITNESS_DB_NAME: sparkyfitness_db
SPARKY_FITNESS_DB_USER: sparky
SPARKY_FITNESS_DB_PASSWORD: "${SPARKY_FITNESS_DB_PASSWORD}"
SPARKY_FITNESS_APP_DB_USER: sparky_app
SPARKY_FITNESS_APP_DB_PASSWORD: "${SPARKY_FITNESS_APP_DB_PASSWORD}"
# Security — values sourced from .env (git-crypt)
SPARKY_FITNESS_API_ENCRYPTION_KEY: "${SPARKY_FITNESS_API_ENCRYPTION_KEY}"
BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET}"
# Application
SPARKY_FITNESS_FRONTEND_URL: "https://fitness.castaldifamily.com"
SPARKY_FITNESS_LOG_LEVEL: ERROR
NODE_ENV: production
TZ: Etc/UTC
SPARKY_FITNESS_DISABLE_SIGNUP: "false"
SPARKY_FITNESS_FORCE_EMAIL_LOGIN: "true"
ALLOW_PRIVATE_NETWORK_CORS: "false"
PUID: 1000
GUID: 1000
# Garmin microservice (disabled — service not yet stable)
# GARMIN_MICROSERVICE_URL: http://sparkyfitness-garmin:8000
volumes:
- /mnt/appdata/sparkyfitness/data/backup:/app/SparkyFitnessServer/backup
- /mnt/appdata/sparkyfitness/data/uploads:/app/SparkyFitnessServer/uploads
depends_on:
sparkyfitness-db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:3010/ 2>&1 | grep -q '.' || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
cpus: "1.5"
memory: 512M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ── Frontend ─────────────────────────────────────────────────────────────────
sparkyfitness-frontend:
image: codewithcj/sparkyfitness:v0.16.6.1
container_name: sparkyfitness-frontend
restart: unless-stopped
networks:
- proxy-net
- sparkyfitness-network
environment:
SPARKY_FITNESS_FRONTEND_URL: "https://fitness.castaldifamily.com"
SPARKY_FITNESS_SERVER_HOST: sparkyfitness-server
SPARKY_FITNESS_SERVER_PORT: 3010
PUID: 1000
GUID: 1000
depends_on:
sparkyfitness-server:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:80/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
labels:
# Enable Traefik
- "traefik.enable=true"
# HTTPS Router
- "traefik.http.routers.sparkyfitness.rule=Host(`fitness.castaldifamily.com`)"
- "traefik.http.routers.sparkyfitness.entrypoints=websecure"
- "traefik.http.routers.sparkyfitness.tls=true"
- "traefik.http.routers.sparkyfitness.tls.certresolver=cloudflare"
- "traefik.http.routers.sparkyfitness.service=sparkyfitness"
- "traefik.http.routers.sparkyfitness.middlewares=security-headers@file"
# Service definition
- "traefik.http.services.sparkyfitness.loadbalancer.server.port=80"
deploy:
resources:
limits:
cpus: "0.5"
memory: 128M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

View File

@ -5,7 +5,7 @@ services:
restart: always restart: always
environment: environment:
# If Ollama is on the same host (Waldorf) # If Ollama is on the same host (Waldorf)
- OLLAMA_BASE_URL=http://10.0.0.202:11434 - OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
# Standard settings # Standard settings
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY:-change-me-to-a-random-secret} - WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY:-change-me-to-a-random-secret}
- HF_TOKEN=${HF_TOKEN:-} - HF_TOKEN=${HF_TOKEN:-}