diff --git a/.beans/nuzlocke-tracker-ahza--deployment-strategy.md b/.beans/nuzlocke-tracker-ahza--deployment-strategy.md index 36b5766..0b039d6 100644 --- a/.beans/nuzlocke-tracker-ahza--deployment-strategy.md +++ b/.beans/nuzlocke-tracker-ahza--deployment-strategy.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-ahza title: Deployment Strategy -status: in-progress +status: completed type: epic priority: normal created_at: 2026-02-09T14:03:53Z -updated_at: 2026-02-10T08:16:36Z +updated_at: 2026-02-10T11:36:07Z --- Define and implement a deployment strategy for running the nuzlocke-tracker in production on a local Unraid server while keeping laptop/PC as the development environment. @@ -47,12 +47,12 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p - [x] **Set up branching structure** — create `develop` branch from `main`, establish the `main`/`develop`/`feature/*` workflow - [x] **Update CLAUDE.md with branching rules** — once the branching structure is in place, add instructions to CLAUDE.md that the branching strategy must be adhered to (always work on feature branches, never commit directly to `main`, merge flow is `feature/*` → `develop` → `main`) -- [ ] **Configure Gitea container registry** — create an access token with `read:package` and `write:package` scopes, verify `docker login gitea.nerdboden.de` works, test pushing and pulling an image as a user-level package +- [x] **Configure Gitea container registry** — create an access token with `read:package` and `write:package` scopes, verify `docker login gitea.nerdboden.de` works, test pushing and pulling an image as a user-level package - [x] **Create production docker-compose file** (`docker-compose.prod.yml`) — uses images from the Gitea container registry, production env vars, no source volume mounts, proper restart policies - [x] **Create production Dockerfiles (or multi-stage builds)** — ensure frontend is built and served statically (e.g., via the API or a lightweight nginx container), API runs without debug mode - [x] **Create deploy script** — `./deploy.sh` builds images (podman/docker, linux/amd64), pushes to Gitea registry, SCPs compose file, generates `.env` if needed, pulls and starts containers via SSH - [x] **Configure Nginx Proxy Manager** — add proxy host entries for Gitea and the nuzlocke-tracker frontend/API on the appropriate ports - [x] **Environment & secrets management** — deploy script auto-generates `.env` with `POSTGRES_PASSWORD` on Unraid if missing; file lives at `/mnt/user/appdata/nuzlocke-tracker/.env` -- [ ] **Implement Gitea Actions CI/CD pipeline** — set up Gitea Actions runner on Unraid, create CI workflow (lint/test on `develop`) and deploy workflow (build/push/deploy on `main`); uses GitHub Actions-compatible syntax for portability +- [x] **Implement Gitea Actions CI/CD pipeline** — set up Gitea Actions runner on Unraid, create CI workflow (lint/test on `develop`) and deploy workflow (build/push/deploy on `main`); uses GitHub Actions-compatible syntax for portability - [x] **Database backup strategy** — set up a simple scheduled backup for the PostgreSQL data (e.g., cron + `pg_dump` script on Unraid) -- [ ] **Document the deployment workflow** — README or docs covering how to deploy, redeploy, rollback, and manage the production instance \ No newline at end of file +- [x] **Document the deployment workflow** — README or docs covering how to deploy, redeploy, rollback, and manage the production instance \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-jlzs--implement-gitea-actions-cicd-pipeline.md b/.beans/nuzlocke-tracker-jlzs--implement-gitea-actions-cicd-pipeline.md index af3b02a..89e2b73 100644 --- a/.beans/nuzlocke-tracker-jlzs--implement-gitea-actions-cicd-pipeline.md +++ b/.beans/nuzlocke-tracker-jlzs--implement-gitea-actions-cicd-pipeline.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-jlzs title: Implement Gitea Actions CI/CD pipeline -status: in-progress +status: completed type: task priority: normal created_at: 2026-02-10T09:38:15Z -updated_at: 2026-02-10T11:12:32Z +updated_at: 2026-02-10T11:34:52Z parent: nuzlocke-tracker-ahza --- @@ -24,6 +24,6 @@ Set up Gitea Actions as the CI/CD pipeline for the nuzlocke-tracker. Gitea Actio - [x] **Set up a Gitea Actions runner** — `act_runner` is deployed on Unraid and registered with Gitea - [x] **Create CI workflow** (`.github/workflows/ci.yml`) — on push to `develop` and PRs: run `ruff check` + `ruff format --check` for backend, `eslint` + `tsc` for frontend. Tests can be added later when they exist. - [x] **Create deploy workflow** (`.github/workflows/deploy.yml`) — triggered via `workflow_dispatch` on `main`: build Docker images (linux/amd64), push to the Gitea container registry, deploy to Unraid via SSH (`docker compose pull && docker compose up -d`) -- [ ] **Configure secrets in Gitea** — generate a new SSH keypair, add the public key to Unraid root user's `authorized_keys`, add the private key as a Gitea repo secret (`DEPLOY_SSH_KEY`). Also add any registry credentials or other sensitive values the workflows need. -- [ ] **Test the full pipeline** — push a change through `feature/*` → `develop` (verify CI runs), then merge `develop` → `main` and trigger the deploy workflow via `workflow_dispatch` to verify end-to-end -- [ ] **Update deployment docs** — document the Gitea Actions setup, how to manage the runner, and how CI/CD fits into the deployment workflow \ No newline at end of file +- [x] **Configure secrets in Gitea** — generate a new SSH keypair, add the public key to Unraid root user's `authorized_keys`, add the private key as a Gitea repo secret (`DEPLOY_SSH_KEY`). Also add any registry credentials or other sensitive values the workflows need. +- [x] **Test the full pipeline** — push a change through `feature/*` → `develop` (verify CI runs), then merge `develop` → `main` and trigger the deploy workflow via `workflow_dispatch` to verify end-to-end +- [x] **Update deployment docs** — document the Gitea Actions setup, how to manage the runner, and how CI/CD fits into the deployment workflow \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-re0m--document-the-deployment-workflow.md b/.beans/nuzlocke-tracker-re0m--document-the-deployment-workflow.md index 784999f..72ba486 100644 --- a/.beans/nuzlocke-tracker-re0m--document-the-deployment-workflow.md +++ b/.beans/nuzlocke-tracker-re0m--document-the-deployment-workflow.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-re0m title: Document the deployment workflow -status: in-progress +status: completed type: task priority: normal created_at: 2026-02-09T15:30:57Z -updated_at: 2026-02-10T08:44:29Z +updated_at: 2026-02-10T11:35:41Z parent: nuzlocke-tracker-ahza blocking: - nuzlocke-tracker-aiw6 diff --git a/.beans/nuzlocke-tracker-xvaw--clean-up-frontend-branding-and-metadata.md b/.beans/nuzlocke-tracker-xvaw--clean-up-frontend-branding-and-metadata.md index 81c425c..85815e6 100644 --- a/.beans/nuzlocke-tracker-xvaw--clean-up-frontend-branding-and-metadata.md +++ b/.beans/nuzlocke-tracker-xvaw--clean-up-frontend-branding-and-metadata.md @@ -1,10 +1,11 @@ --- # nuzlocke-tracker-xvaw title: Clean up frontend branding and metadata -status: todo +status: in-progress type: task +priority: normal created_at: 2026-02-10T09:36:24Z -updated_at: 2026-02-10T09:36:24Z +updated_at: 2026-02-10T11:40:29Z --- The frontend currently uses all Vite defaults — generic title, Vite favicon, no manifest, no meta tags. Clean it up so it looks polished and professional as "Nuzlocke Tracker". @@ -21,14 +22,14 @@ The frontend currently uses all Vite defaults — generic title, Vite favicon, n ## Checklist -- [ ] Design or source a proper favicon (Pokeball-themed or similar, in SVG + PNG formats) -- [ ] Add favicon files to `public/` (favicon.ico, favicon.svg, apple-touch-icon.png, favicon-16x16.png, favicon-32x32.png) -- [ ] Update `index.html` title from "frontend" to "Nuzlocke Tracker" -- [ ] Add meta description tag (e.g. "Track your Nuzlocke challenge runs across all Pokemon games") -- [ ] Add theme-color meta tag matching the app's primary color -- [ ] Add Open Graph meta tags (og:title, og:description, og:type) for link previews -- [ ] Create a `site.webmanifest` with app name, short_name, icons, theme_color, background_color -- [ ] Link the manifest in `index.html` -- [ ] Remove unused default assets (`public/vite.svg`, `src/assets/react.svg`) -- [ ] Update `package.json` name from "frontend" to "nuzlocke-tracker" (or "nuzlocke-tracker-frontend") +- [x] Design or source a proper favicon (Pokeball with skull, in SVG + PNG formats) +- [x] Add favicon files to `public/` (favicon.ico, favicon.svg, apple-touch-icon.png, favicon-16x16.png, favicon-32x32.png, icon-192.png, icon-512.png) +- [x] Update `index.html` title from "frontend" to "Nuzlocke Tracker" +- [x] Add meta description tag +- [x] Add theme-color meta tag (#DC2626, Pokeball red) +- [x] Add Open Graph meta tags (og:title, og:description, og:type) +- [x] Create a `site.webmanifest` with app name, short_name, icons, theme_color, background_color +- [x] Link the manifest in `index.html` +- [x] Remove unused default assets (`public/vite.svg`, `src/assets/react.svg`) +- [x] Update `package.json` name to "nuzlocke-tracker-frontend" - [ ] Consider adding dynamic page titles per route (e.g. "Runs | Nuzlocke Tracker") — optional stretch goal \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f59f746..5ce6a35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,10 +5,18 @@ on: branches: [develop] paths-ignore: - ".beans/**" + - "*.md" + - "LICENSE" + - ".gitignore" + - ".github/workflows/deploy.yml" pull_request: branches: [develop] paths-ignore: - ".beans/**" + - "*.md" + - "LICENSE" + - ".gitignore" + - ".github/workflows/deploy.yml" jobs: backend-lint: diff --git a/CLAUDE.md b/CLAUDE.md index c3d2249..28ab3bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,8 @@ - Day-to-day work happens on `develop`. - New work is done on `feature/*` branches off `develop`. - Merge flow: `feature/*` → `develop` → `main`. +- **Squash merge** `feature/*` into `develop` (one clean commit per feature). +- **Merge commit** `develop` into `main` (marks deploy points). # Instructions diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 236d6a6..da81873 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -1,31 +1,95 @@ # Deployment -This document describes the deployment architecture and workflows for the nuzlocke-tracker. It is a living document — sections marked with **TODO** are planned but not yet implemented. +This document describes the deployment architecture and workflows for the nuzlocke-tracker. ## Architecture Overview | Component | Dev (Laptop/PC) | Production (Unraid) | |---|---|---| | API | `docker-compose.yml` (hot reload) | `docker-compose.prod.yml` (built image) | -| Frontend | `docker-compose.yml` (Vite dev server) | `docker-compose.prod.yml` (built image) | -| Database | PostgreSQL 16 (Docker volume) | PostgreSQL 16 (Docker volume) | +| Frontend | `docker-compose.yml` (Vite dev server) | `docker-compose.prod.yml` (nginx, static build) | +| Database | PostgreSQL 16 (Docker volume) | PostgreSQL 16 (bind mount) | | Container Registry | — | Gitea (user-level packages) | -| Container Management | — | Portainer CE | | Reverse Proxy | — | Nginx Proxy Manager | +| CI/CD | — | Gitea Actions | ### Services -- **Gitea** — self-hosted Git server, container registry, and (future) CI/CD. Accessible at `gitea.nerdboden.de` via SSL. -- **Portainer** — Docker management UI. Accessible at `portainer.nerdboden.de` via SSL. Manages the production stack and provides webhook-triggered redeployments. +- **Gitea** — self-hosted Git server, container registry, and CI/CD runner. Accessible at `gitea.nerdboden.de` via SSL. - **Nginx Proxy Manager** — reverse proxy with SSL termination for all services on the Unraid server. +## Branching Strategy + +| Branch | Purpose | Merge strategy | +|---|---|---| +| `main` | Always production-ready. Deploy workflow builds from here. | Merge commit from `develop` | +| `develop` | Integration branch for day-to-day work. CI runs on push. | Squash merge from `feature/*` | +| `feature/*` | Short-lived branches off `develop` for individual features/fixes. | — | + +### Workflow + +1. Create `feature/xyz` from `develop` +2. Work on the feature, commit, squash merge into `develop` +3. CI runs automatically on `develop` (lint checks) +4. When ready to deploy: merge `develop` into `main` (merge commit) +5. Trigger the deploy workflow via `workflow_dispatch` in Gitea Actions + +## CI/CD (Gitea Actions) + +The project uses two Gitea Actions workflows (GitHub Actions-compatible syntax): + +### CI (`.github/workflows/ci.yml`) + +**Triggers:** Push to `develop`, PRs targeting `develop` (skips bean-only changes) + +**Jobs:** +- `backend-lint` — `ruff check` + `ruff format --check` +- `frontend-lint` — `eslint` + `tsc -b` + +### Deploy (`.github/workflows/deploy.yml`) + +**Triggers:** Manual via `workflow_dispatch` (must be on `main`) + +**Steps:** +1. Login to Gitea container registry +2. Build Docker images (linux/amd64) for API and frontend +3. Push images to the Gitea registry +4. SCP compose file and backup script to Unraid +5. SSH into Unraid and run `docker compose pull && docker compose up -d` + +### Secrets (configured in Gitea repo settings) + +| Secret | Purpose | +|---|---| +| `REGISTRY_USERNAME` | Gitea username for pushing images | +| `REGISTRY_PASSWORD` | Gitea access token (`read:package` + `write:package` scopes) | +| `DEPLOY_SSH_KEY` | SSH private key for `root@192.168.1.10` | + +### Runner + +An `act_runner` container runs on Unraid with the Docker socket mounted, registered with the Gitea instance. + +## Manual Deployment + +The `./deploy.sh` script can also be used to deploy from the dev machine: + +```bash +# Ensure you're on main with latest changes +git checkout main +./deploy.sh +``` + +The script: +1. Checks you're on `main` with no uncommitted changes +2. Builds Docker images (podman or docker, linux/amd64) +3. Pushes to the Gitea container registry +4. SCPs `docker-compose.prod.yml` and `backup.sh` to Unraid +5. Generates `.env` with a random `POSTGRES_PASSWORD` if missing +6. Pulls images and restarts containers on Unraid via SSH + ## Container Registry -Docker images are hosted on Gitea's built-in container registry as **user-level packages**. - -### Image naming - -Images use the format `gitea.nerdboden.de//:`: +Images are hosted on Gitea's built-in container registry as user-level packages: ``` gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest @@ -35,118 +99,58 @@ gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest ### Authentication 1. Create a Gitea access token at **Settings > Applications** with `read:package` and `write:package` scopes. -2. Log in from the dev machine: - ```bash - docker login gitea.nerdboden.de - ``` - Use your Gitea username and the access token as password. +2. Log in: `docker login gitea.nerdboden.de` (username + token as password). -### Pushing images +## Production Environment -```bash -# Build and tag -docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest ./backend -docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest ./frontend +### File layout on Unraid -# Push -docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest -docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest +``` +/mnt/user/appdata/nuzlocke-tracker/ +├── docker-compose.yml # production compose (synced from repo) +├── .env # POSTGRES_PASSWORD (auto-generated) +├── backup.sh # database backup script (synced from repo) +├── backups/ # pg_dump backups (daily, 7-day retention) +│ └── cron.log +└── data/ + └── postgres/ # PostgreSQL data (bind mount) ``` -Pushed images are visible under the **Packages** tab on your Gitea user profile. +### Environment variables -## Branching Strategy - -The project uses a `main` / `develop` / `feature/*` branching model. - -| Branch | Purpose | -|---|---| -| `main` | Always production-ready. Deploy script builds from here. | -| `develop` | Integration branch for day-to-day work. | -| `feature/*` | Short-lived branches off `develop` for individual features/fixes. | - -### Workflow - -1. Create `feature/xyz` from `develop` -2. Work on the feature, commit, merge into `develop` -3. When ready to deploy: merge `develop` into `main` -4. Run the deploy script (see below) - -## Deploying - -> **TODO** — deploy script (`./deploy.sh`) not yet created. - -The deploy script will automate: - -1. Build Docker images from `main` -2. Tag and push to the Gitea container registry -3. Trigger the Portainer webhook to pull new images and restart the stack - -### Manual deployment - -Until the deploy script is in place, deploy manually: - -```bash -# 1. Ensure you're on main with latest changes -git checkout main - -# 2. Build and push images -docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest ./backend -docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest ./frontend -docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest -docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest - -# 3. On Unraid (or via Portainer): pull and restart -docker compose -f docker-compose.prod.yml pull -docker compose -f docker-compose.prod.yml up -d -``` - -## Production Compose - -> **TODO** — `docker-compose.prod.yml` not yet created. - -The production compose file will differ from the dev compose in: - -- Uses pre-built images from the Gitea registry (no source volume mounts) -- No hot reload / debug mode -- Production environment variables -- Proper restart policies -- Frontend served as a static build (not Vite dev server) - -## Portainer - -Portainer CE is running on Unraid at `portainer.nerdboden.de`. - -- Manages the production Docker stack -- **TODO**: Configure a webhook for automated redeployment (pull latest images + restart on trigger) - -## Nginx Proxy Manager - -NPM runs on Unraid and handles SSL termination and routing for: - -- `gitea.nerdboden.de` → Gitea -- `portainer.nerdboden.de` → Portainer -- **TODO**: `nuzlocke.nerdboden.de` (or similar) → nuzlocke-tracker frontend/API - -## Environment & Secrets - -> **TODO** — `.env.prod` template not yet created. - -Production environment variables to configure: - -- `DATABASE_URL` — PostgreSQL connection string -- `DEBUG` — must be `false` in production -- Additional secrets TBD +The `.env` file is auto-generated on first deploy with a random `POSTGRES_PASSWORD`. The compose file references it for both the database and API connection string. ## Database -PostgreSQL 16 with data stored in a named Docker volume. +PostgreSQL 16 with data persisted via bind mount at `./data/postgres/`. -- Migrations run automatically on API container startup (Alembic) -- **TODO**: Set up scheduled backups (`pg_dump` cron job on Unraid) +- Migrations run automatically on API container startup (`alembic upgrade head`) +- Daily backups via `pg_dump` scheduled through the Unraid User Scripts plugin (03:00, 7-day retention) + +### Backup + +Backups are created by `backup.sh` and stored in `/mnt/user/appdata/nuzlocke-tracker/backups/`. The script is scheduled via the Unraid User Scripts plugin. + +### Restore + +```bash +cd /mnt/user/appdata/nuzlocke-tracker +gunzip -c backups/nuzlocke-YYYYMMDD-HHMMSS.sql.gz | \ + docker compose exec -T db psql -U postgres nuzlocke +``` ## Rollback -> **TODO** — rollback procedure to be documented once image tagging strategy is finalized. +Currently images are tagged as `latest` only. To roll back: -General approach: tag images with version/commit hash in addition to `latest`, so rolling back means redeploying a previous tag. +1. Revert the merge commit on `main` +2. Trigger the deploy workflow (or run `./deploy.sh`) to rebuild and push + +For more granular rollback, consider adding commit-hash tags to images in the future. + +## Nginx Proxy Manager + +NPM runs on Unraid and handles SSL termination and routing: + +- `nuzlocke.nerdboden.de` → nuzlocke-tracker frontend (port 9080) +- `gitea.nerdboden.de` → Gitea diff --git a/frontend/index.html b/frontend/index.html index 072a57e..02eb0db 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,19 @@ - - frontend + Nuzlocke Tracker + + + + + + + + + + +
diff --git a/frontend/package.json b/frontend/package.json index acfd726..e64fb30 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "frontend", + "name": "nuzlocke-tracker-frontend", "private": true, "version": "0.0.0", "type": "module", diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..c6746d1 Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/favicon-16x16.png b/frontend/public/favicon-16x16.png new file mode 100644 index 0000000..ae41eb5 Binary files /dev/null and b/frontend/public/favicon-16x16.png differ diff --git a/frontend/public/favicon-32x32.png b/frontend/public/favicon-32x32.png new file mode 100644 index 0000000..2ffe58c Binary files /dev/null and b/frontend/public/favicon-32x32.png differ diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..1f04aa6 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..77a1f13 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/icon-192.png b/frontend/public/icon-192.png new file mode 100644 index 0000000..0020910 Binary files /dev/null and b/frontend/public/icon-192.png differ diff --git a/frontend/public/icon-512.png b/frontend/public/icon-512.png new file mode 100644 index 0000000..686487a Binary files /dev/null and b/frontend/public/icon-512.png differ diff --git a/frontend/public/site.webmanifest b/frontend/public/site.webmanifest new file mode 100644 index 0000000..ecb7b2b --- /dev/null +++ b/frontend/public/site.webmanifest @@ -0,0 +1,11 @@ +{ + "name": "Nuzlocke Tracker", + "short_name": "Nuzlocke", + "icons": [ + { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" } + ], + "theme_color": "#DC2626", + "background_color": "#111827", + "display": "standalone" +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file