Compare commits
7 Commits
6a86c56e3b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 61a7f57f1f | |||
| 03f07ebee5 | |||
| 972137acfb | |||
| fd23d89e71 | |||
| d9d547ef53 | |||
| 349a0cb821 | |||
| ad4ac6cf8c |
@@ -5,7 +5,7 @@ status: todo
|
|||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-09T15:30:50Z
|
created_at: 2026-02-09T15:30:50Z
|
||||||
updated_at: 2026-02-09T15:31:15Z
|
updated_at: 2026-02-09T16:53:13Z
|
||||||
parent: nuzlocke-tracker-ahza
|
parent: nuzlocke-tracker-ahza
|
||||||
blocking:
|
blocking:
|
||||||
- nuzlocke-tracker-vpn5
|
- nuzlocke-tracker-vpn5
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-ahza
|
# nuzlocke-tracker-ahza
|
||||||
title: Deployment Strategy
|
title: Deployment Strategy
|
||||||
status: todo
|
status: in-progress
|
||||||
type: epic
|
type: epic
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-09T14:03:53Z
|
created_at: 2026-02-09T14:03:53Z
|
||||||
updated_at: 2026-02-09T15:08:29Z
|
updated_at: 2026-02-09T16:53:58Z
|
||||||
---
|
---
|
||||||
|
|
||||||
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.
|
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.
|
||||||
@@ -20,14 +20,14 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p
|
|||||||
|
|
||||||
## Decided Approach
|
## Decided Approach
|
||||||
|
|
||||||
**Docker Compose + Portainer + Local Docker Registry**
|
**Docker Compose + Portainer + Gitea (source hosting, container registry, CI/CD)**
|
||||||
|
|
||||||
1. **A local Docker registry** runs on Unraid as a container, accessible on the LAN (e.g., `unraid:5000` or behind Nginx Proxy Manager).
|
1. **Gitea** runs on Unraid behind Nginx Proxy Manager with SSL (e.g., `gitea.nerdboden.de`). It serves as the self-hosted Git remote, container registry, and (optionally) CI/CD via Gitea Actions.
|
||||||
2. **Images are built on the dev machine** and pushed to the local registry.
|
2. **Images are built on the dev machine** and pushed to Gitea's container registry as **user-level packages** (e.g., `gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest`, `gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest`).
|
||||||
3. **Production runs docker-compose** on Unraid, pulling images from the local registry instead of mounting source.
|
3. **Production runs docker-compose** on Unraid, pulling images from the Gitea container registry instead of mounting source.
|
||||||
4. **Portainer** is installed on Unraid to manage stacks, provide a web UI, and enable webhook-triggered redeployments.
|
4. **Portainer** is installed on Unraid to manage stacks, provide a web UI, and enable webhook-triggered redeployments.
|
||||||
5. **A deploy script** on the dev machine automates the full flow: build images → push to local registry → trigger Portainer webhook to redeploy.
|
5. **A deploy script** on the dev machine automates the full flow: build images → push to Gitea registry → trigger Portainer webhook to redeploy.
|
||||||
6. **Nginx Proxy Manager** handles routing on the LAN (e.g., `nuzlocke.local` → frontend container).
|
6. **Nginx Proxy Manager** handles routing on the LAN (e.g., `nuzlocke.nerdboden.de` → frontend container, `gitea.nerdboden.de` → Gitea).
|
||||||
7. **Database** uses a named Docker volume for persistence; migrations run automatically on API container startup.
|
7. **Database** uses a named Docker volume for persistence; migrations run automatically on API container startup.
|
||||||
|
|
||||||
## Branching Strategy
|
## Branching Strategy
|
||||||
@@ -42,19 +42,19 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p
|
|||||||
1. Create `feature/xyz` from `develop`
|
1. Create `feature/xyz` from `develop`
|
||||||
2. Work on the feature, commit, merge into `develop`
|
2. Work on the feature, commit, merge into `develop`
|
||||||
3. When ready to deploy: merge `develop` → `main`
|
3. When ready to deploy: merge `develop` → `main`
|
||||||
4. Run `./deploy.sh` (builds from `main`, pushes to local registry, triggers Portainer webhook)
|
4. Run `./deploy.sh` (builds from `main`, pushes to Gitea registry, triggers Portainer webhook)
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] **Set up branching structure** — create `develop` branch from `main`, establish the `main`/`develop`/`feature/*` workflow
|
- [ ] **Set up branching structure** — create `develop` branch from `main`, establish the `main`/`develop`/`feature/*` workflow
|
||||||
- [ ] **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`)
|
- [ ] **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`)
|
||||||
- [ ] **Set up local Docker registry on Unraid** — run the `registry:2` container, configure storage volume, optionally put it behind Nginx Proxy Manager with a hostname (e.g., `registry.local`)
|
- [ ] **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
|
||||||
- [ ] **Create production docker-compose file** (`docker-compose.prod.yml`) — uses images from the local registry, production env vars, no source volume mounts, proper restart policies
|
- [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
|
||||||
- [ ] **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 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
|
||||||
- [ ] **Set up Portainer on Unraid** — install Portainer CE as a Docker container, configure the stack from the production compose file
|
- [x] **Set up Portainer on Unraid** — install Portainer CE as a Docker container, configure the stack from the production compose file
|
||||||
- [ ] **Configure Portainer webhook for automated redeployment** — add a webhook trigger in Portainer that pulls latest images and restarts the stack
|
- [x] **Configure Portainer API for automated redeployment** — deploy script uses Portainer CE REST API to pull latest images and restart the stack
|
||||||
- [ ] **Create deploy script** — a script (e.g., `./deploy.sh`) that builds images from `main`, tags them for the local registry, pushes them, and triggers the Portainer webhook to redeploy
|
- [x] **Create deploy script** — `./deploy.sh` builds images, pushes to Gitea registry, triggers Portainer API redeployment
|
||||||
- [ ] **Configure Nginx Proxy Manager** — add proxy host entry pointing to the frontend/API containers on the appropriate ports
|
- [ ] **Configure Nginx Proxy Manager** — add proxy host entries for Gitea and the nuzlocke-tracker frontend/API on the appropriate ports
|
||||||
- [ ] **Environment & secrets management** — create a `.env.prod` template, document required variables, decide on secret handling (`.env` file on Unraid, Portainer env vars, etc.)
|
- [ ] **Environment & secrets management** — create a `.env.prod` template, document required variables, decide on secret handling (`.env` file on Unraid, Portainer env vars, etc.)
|
||||||
- [ ] **Database backup strategy** — set up a simple scheduled backup for the PostgreSQL volume/data (e.g., cron + `pg_dump` script on Unraid)
|
- [ ] **Database backup strategy** — set up a simple scheduled backup for the PostgreSQL volume/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
|
- [ ] **Document the deployment workflow** — README or docs covering how to deploy, redeploy, rollback, and manage the production instance
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-aiw6
|
# nuzlocke-tracker-aiw6
|
||||||
title: Create deploy script
|
title: Create deploy script
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-09T15:30:48Z
|
created_at: 2026-02-09T15:30:48Z
|
||||||
updated_at: 2026-02-09T15:31:15Z
|
updated_at: 2026-02-09T17:28:22Z
|
||||||
parent: nuzlocke-tracker-ahza
|
parent: nuzlocke-tracker-ahza
|
||||||
blocking:
|
blocking:
|
||||||
- nuzlocke-tracker-izf6
|
- nuzlocke-tracker-izf6
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-hwyk
|
# nuzlocke-tracker-hwyk
|
||||||
title: Set up Portainer on Unraid
|
title: Set up Portainer on Unraid
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
|
priority: normal
|
||||||
created_at: 2026-02-09T15:30:44Z
|
created_at: 2026-02-09T15:30:44Z
|
||||||
updated_at: 2026-02-09T15:30:44Z
|
updated_at: 2026-02-09T16:53:41Z
|
||||||
parent: nuzlocke-tracker-ahza
|
parent: nuzlocke-tracker-ahza
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-izf6
|
# nuzlocke-tracker-izf6
|
||||||
title: Set up local Docker registry on Unraid
|
title: Configure Gitea container registry
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
|
priority: normal
|
||||||
created_at: 2026-02-09T15:30:40Z
|
created_at: 2026-02-09T15:30:40Z
|
||||||
updated_at: 2026-02-09T15:30:40Z
|
updated_at: 2026-02-09T16:53:09Z
|
||||||
parent: nuzlocke-tracker-ahza
|
parent: nuzlocke-tracker-ahza
|
||||||
---
|
---
|
||||||
|
|
||||||
Run a `registry:2` container on the Unraid server for storing production Docker images locally.
|
Set up and verify the Gitea container registry for hosting Docker images as user-level packages.
|
||||||
|
|
||||||
- Run `registry:2` as a Docker container on Unraid
|
## Checklist
|
||||||
- Configure a persistent storage volume for the registry data
|
|
||||||
- Optionally put it behind Nginx Proxy Manager with a hostname (e.g., `registry.local`)
|
- [ ] Create a Gitea access token with `read:package` and `write:package` scopes
|
||||||
- Verify pushing/pulling images from the dev machine works
|
- [ ] Verify `docker login gitea.nerdboden.de` works from the dev machine
|
||||||
|
- [ ] Test pushing a Docker image as a user-level package (e.g., `gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest`)
|
||||||
|
- [ ] Verify the image appears under the user's Packages tab in Gitea
|
||||||
|
- [ ] Test pulling the image back (from Unraid or dev machine)
|
||||||
@@ -1,18 +1,28 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-jzqz
|
# nuzlocke-tracker-jzqz
|
||||||
title: Configure Portainer webhook for automated redeployment
|
title: Configure Portainer API for automated redeployment
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-09T15:30:45Z
|
created_at: 2026-02-09T15:30:45Z
|
||||||
updated_at: 2026-02-09T15:31:15Z
|
updated_at: 2026-02-09T17:28:22Z
|
||||||
parent: nuzlocke-tracker-ahza
|
parent: nuzlocke-tracker-ahza
|
||||||
blocking:
|
blocking:
|
||||||
- nuzlocke-tracker-hwyk
|
- nuzlocke-tracker-hwyk
|
||||||
---
|
---
|
||||||
|
|
||||||
Set up a webhook in Portainer that triggers a stack redeployment when called.
|
Use the Portainer CE REST API to trigger stack redeployments from the deploy script.
|
||||||
|
|
||||||
- Create a webhook trigger in Portainer for the nuzlocke-tracker stack
|
Portainer webhooks are a Business-only feature, so we use the API directly instead.
|
||||||
- The webhook should pull the latest images from the local registry and restart the stack
|
|
||||||
- Note the webhook URL for use in the deploy script
|
## Approach
|
||||||
|
|
||||||
|
1. Authenticate with the Portainer API to get a JWT token
|
||||||
|
2. Call the stack update endpoint with `pullImage: true` to pull latest images and recreate containers
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] Identify the stack ID in Portainer (via API or UI)
|
||||||
|
- [ ] Test API authentication (`POST /api/auth`)
|
||||||
|
- [ ] Test triggering a stack redeploy via API
|
||||||
|
- [ ] Integrate into the deploy script
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-re0m
|
# nuzlocke-tracker-re0m
|
||||||
title: Document the deployment workflow
|
title: Document the deployment workflow
|
||||||
status: todo
|
status: in-progress
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-09T15:30:57Z
|
created_at: 2026-02-09T15:30:57Z
|
||||||
updated_at: 2026-02-09T15:31:15Z
|
updated_at: 2026-02-09T16:55:02Z
|
||||||
parent: nuzlocke-tracker-ahza
|
parent: nuzlocke-tracker-ahza
|
||||||
blocking:
|
blocking:
|
||||||
- nuzlocke-tracker-aiw6
|
- nuzlocke-tracker-aiw6
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-vpn5
|
# nuzlocke-tracker-vpn5
|
||||||
title: Create production docker-compose file
|
title: Create production docker-compose file
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
|
priority: normal
|
||||||
created_at: 2026-02-09T15:30:41Z
|
created_at: 2026-02-09T15:30:41Z
|
||||||
updated_at: 2026-02-09T15:30:41Z
|
updated_at: 2026-02-09T16:59:00Z
|
||||||
parent: nuzlocke-tracker-ahza
|
parent: nuzlocke-tracker-ahza
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-xmyh
|
# nuzlocke-tracker-xmyh
|
||||||
title: Create production Dockerfiles
|
title: Create production Dockerfiles
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
|
priority: normal
|
||||||
created_at: 2026-02-09T15:30:42Z
|
created_at: 2026-02-09T15:30:42Z
|
||||||
updated_at: 2026-02-09T15:30:42Z
|
updated_at: 2026-02-09T17:00:32Z
|
||||||
parent: nuzlocke-tracker-ahza
|
parent: nuzlocke-tracker-ahza
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
152
DEPLOYMENT.md
Normal file
152
DEPLOYMENT.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## 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) |
|
||||||
|
| Container Registry | — | Gitea (user-level packages) |
|
||||||
|
| Container Management | — | Portainer CE |
|
||||||
|
| Reverse Proxy | — | Nginx Proxy Manager |
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
- **Nginx Proxy Manager** — reverse proxy with SSL termination for all services on the Unraid server.
|
||||||
|
|
||||||
|
## 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/<user>/<image>:<tag>`:
|
||||||
|
|
||||||
|
```
|
||||||
|
gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Pushing images
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
# Push
|
||||||
|
docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
|
||||||
|
docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Pushed images are visible under the **Packages** tab on your Gitea user profile.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
PostgreSQL 16 with data stored in a named Docker volume.
|
||||||
|
|
||||||
|
- Migrations run automatically on API container startup (Alembic)
|
||||||
|
- **TODO**: Set up scheduled backups (`pg_dump` cron job on Unraid)
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
> **TODO** — rollback procedure to be documented once image tagging strategy is finalized.
|
||||||
|
|
||||||
|
General approach: tag images with version/commit hash in addition to `latest`, so rolling back means redeploying a previous tag.
|
||||||
19
backend/Dockerfile.prod
Normal file
19
backend/Dockerfile.prod
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Production Dockerfile for the backend API
|
||||||
|
FROM python:3.14-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
COPY pyproject.toml README.md alembic.ini ./
|
||||||
|
COPY src/ ./src/
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir .
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "src"]
|
||||||
81
deploy.sh
Executable file
81
deploy.sh
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Configuration ──────────────────────────────────────────────
|
||||||
|
REGISTRY="gitea.nerdboden.de"
|
||||||
|
OWNER="thefurya"
|
||||||
|
IMAGES=("nuzlocke-tracker-api" "nuzlocke-tracker-frontend")
|
||||||
|
DOCKERFILES=("backend/Dockerfile.prod" "frontend/Dockerfile.prod")
|
||||||
|
CONTEXTS=("./backend" "./frontend")
|
||||||
|
|
||||||
|
PORTAINER_URL="${PORTAINER_URL:-https://portainer.nerdboden.de}"
|
||||||
|
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"
|
||||||
|
PORTAINER_STACK_ID="${PORTAINER_STACK_ID:-}"
|
||||||
|
PORTAINER_ENDPOINT_ID="${PORTAINER_ENDPOINT_ID:-1}"
|
||||||
|
|
||||||
|
# ── Helpers ────────────────────────────────────────────────────
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||||
|
error() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
|
||||||
|
|
||||||
|
# ── Preflight checks ──────────────────────────────────────────
|
||||||
|
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
if [[ "$BRANCH" != "main" ]]; then
|
||||||
|
warn "You are on branch '$BRANCH', not 'main'."
|
||||||
|
read -rp "Continue anyway? [y/N] " confirm
|
||||||
|
[[ "$confirm" =~ ^[Yy]$ ]] || exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||||
|
warn "You have uncommitted changes."
|
||||||
|
read -rp "Continue anyway? [y/N] " confirm
|
||||||
|
[[ "$confirm" =~ ^[Yy]$ ]] || exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Build and push images ─────────────────────────────────────
|
||||||
|
for i in "${!IMAGES[@]}"; do
|
||||||
|
IMAGE="${REGISTRY}/${OWNER}/${IMAGES[$i]}:latest"
|
||||||
|
info "Building ${IMAGES[$i]}..."
|
||||||
|
docker build -t "$IMAGE" -f "${DOCKERFILES[$i]}" "${CONTEXTS[$i]}"
|
||||||
|
info "Pushing ${IMAGES[$i]}..."
|
||||||
|
docker push "$IMAGE"
|
||||||
|
done
|
||||||
|
|
||||||
|
info "All images built and pushed."
|
||||||
|
|
||||||
|
# ── Trigger Portainer redeployment ─────────────────────────────
|
||||||
|
if [[ -z "$PORTAINER_API_KEY" ]]; then
|
||||||
|
warn "PORTAINER_API_KEY not set — skipping Portainer redeployment."
|
||||||
|
warn "Set it in your environment or .env.deploy file to enable auto-redeploy."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$PORTAINER_STACK_ID" ]]; then
|
||||||
|
warn "PORTAINER_STACK_ID not set — skipping Portainer redeployment."
|
||||||
|
warn "Find your stack ID in Portainer and set it in your environment."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Fetching stack file from Portainer..."
|
||||||
|
STACK_FILE=$(curl -sf \
|
||||||
|
-H "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||||
|
"${PORTAINER_URL}/api/stacks/${PORTAINER_STACK_ID}/file") \
|
||||||
|
|| error "Failed to fetch stack file from Portainer."
|
||||||
|
|
||||||
|
STACK_CONTENT=$(echo "$STACK_FILE" | jq -r '.StackFileContent')
|
||||||
|
|
||||||
|
info "Triggering stack redeployment..."
|
||||||
|
curl -sf -X PUT \
|
||||||
|
-H "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$(jq -n --arg content "$STACK_CONTENT" '{"pullImage": true, "stackFileContent": $content}')" \
|
||||||
|
"${PORTAINER_URL}/api/stacks/${PORTAINER_STACK_ID}?endpointId=${PORTAINER_ENDPOINT_ID}" \
|
||||||
|
> /dev/null \
|
||||||
|
|| error "Failed to trigger Portainer redeployment."
|
||||||
|
|
||||||
|
info "Stack redeployment triggered successfully!"
|
||||||
38
docker-compose.prod.yml
Normal file
38
docker-compose.prod.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
services:
|
||||||
|
api:
|
||||||
|
image: gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
|
||||||
|
command: >
|
||||||
|
sh -c "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000 --app-dir src"
|
||||||
|
environment:
|
||||||
|
- DEBUG=false
|
||||||
|
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/nuzlocke
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest
|
||||||
|
ports:
|
||||||
|
- "9080:80"
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=postgres
|
||||||
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
- POSTGRES_DB=nuzlocke
|
||||||
|
volumes:
|
||||||
|
- prod_postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
prod_postgres_data:
|
||||||
21
frontend/Dockerfile.prod
Normal file
21
frontend/Dockerfile.prod
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Production Dockerfile for the frontend
|
||||||
|
# Stage 1: Build
|
||||||
|
FROM node:24-slim AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 2: Serve
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
20
frontend/nginx.conf
Normal file
20
frontend/nginx.conf
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Proxy API requests to the backend service
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://api:8000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Serve static files, fall back to index.html for SPA routing
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,7 +107,7 @@ export function BossBattleFormModal({
|
|||||||
<label className="block text-sm font-medium mb-1">Type</label>
|
<label className="block text-sm font-medium mb-1">Type</label>
|
||||||
<select
|
<select
|
||||||
value={bossType}
|
value={bossType}
|
||||||
onChange={(e) => setBossType(e.target.value)}
|
onChange={(e) => setBossType(e.target.value as typeof bossType)}
|
||||||
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||||
>
|
>
|
||||||
{BOSS_TYPES.map((t) => (
|
{BOSS_TYPES.map((t) => (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { advanceLeg, createGenlocke, getGamesByRegion, getGenlockes, getGenlocke, getGenlockeGraveyard, getGenlockeLineages, getLegSurvivors } from '../api/genlockes'
|
import { advanceLeg, createGenlocke, getGamesByRegion, getGenlockes, getGenlocke, getGenlockeGraveyard, getGenlockeLineages, getLegSurvivors } from '../api/genlockes'
|
||||||
import type { AdvanceLegInput, CreateGenlockeInput } from '../types/game'
|
import type { CreateGenlockeInput } from '../types/game'
|
||||||
|
|
||||||
export function useGenlockes() {
|
export function useGenlockes() {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
Reference in New Issue
Block a user