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
This commit is contained in:
parent
d1b25c21a3
commit
54a885120d
294
.github/prompts/repo-deploy.prompt.md
vendored
Normal file
294
.github/prompts/repo-deploy.prompt.md
vendored
Normal 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.
|
||||
220
documentation/KBAs/KBA-002-SparkyFitness-Deployment.md
Normal file
220
documentation/KBAs/KBA-002-SparkyFitness-Deployment.md
Normal 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`: ~60–90 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 |
|
||||
82
documentation/project-history/SESSION_SNAPSHOT_2026-05-10.md
Normal file
82
documentation/project-history/SESSION_SNAPSHOT_2026-05-10.md
Normal 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.
|
||||
181
nodes/heimdall/sparkyfitness/README.md
Normal file
181
nodes/heimdall/sparkyfitness/README.md
Normal 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.
|
||||
149
nodes/heimdall/sparkyfitness/compose.yaml
Normal file
149
nodes/heimdall/sparkyfitness/compose.yaml
Normal 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"
|
||||
Loading…
x
Reference in New Issue
Block a user