diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 4207f9f2..37eb0e87 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -38,7 +38,10 @@ Decimal phases appear between their surrounding integers in numeric order. 2. The Terragrunt stack applies cleanly from a fresh checkout with no manual cluster intervention 3. The NFS volume is mounted inside the running pod and a file written to it survives a pod restart 4. Woodpecker CI pipeline exists and triggers on push to the service's directory -**Plans**: TBD +**Plans**: 2 plans +Plans: +- [ ] 01-01-PLAN.md — Create FastAPI backend app, Dockerfile, and build/push Docker image +- [ ] 01-02-PLAN.md — Update Terraform deployment, apply stack, verify NFS, add CI pipeline ### Phase 2: F1 Schedule Subsystem **Goal**: The system automatically fetches the full F1 race calendar and serves it as structured data — users can see all sessions for the current season with correct times. @@ -125,7 +128,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Infrastructure and Deployment | 0/TBD | Not started | - | +| 1. Infrastructure and Deployment | 0/2 | Planning complete | - | | 2. F1 Schedule Subsystem | 0/TBD | Not started | - | | 3. Extractor Framework and First Site | 0/TBD | Not started | - | | 4. Stream Health and Fallback | 0/TBD | Not started | - | diff --git a/.planning/phases/01-infrastructure-and-deployment/01-01-PLAN.md b/.planning/phases/01-infrastructure-and-deployment/01-01-PLAN.md new file mode 100644 index 00000000..a767fc80 --- /dev/null +++ b/.planning/phases/01-infrastructure-and-deployment/01-01-PLAN.md @@ -0,0 +1,173 @@ +--- +phase: 01-infrastructure-and-deployment +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - stacks/f1-stream/files/backend/main.py + - stacks/f1-stream/files/backend/requirements.txt + - stacks/f1-stream/files/Dockerfile + - stacks/f1-stream/files/redeploy.sh +autonomous: true +requirements: + - DEPL-01 + +must_haves: + truths: + - "A Docker image viktorbarzin/f1-stream:v2.0.0 exists and can be pulled" + - "The image starts a FastAPI server on port 8000 that responds to GET /health with 200" + - "The image is based on python:3.13-slim-bookworm and runs without errors" + artifacts: + - path: "stacks/f1-stream/files/backend/main.py" + provides: "FastAPI app with health endpoint" + contains: "/health" + - path: "stacks/f1-stream/files/backend/requirements.txt" + provides: "Python dependencies" + contains: "fastapi" + - path: "stacks/f1-stream/files/Dockerfile" + provides: "Multi-stage Docker build for Python FastAPI" + contains: "python:3.13-slim-bookworm" + - path: "stacks/f1-stream/files/redeploy.sh" + provides: "Build, push, restart script" + contains: "docker build" + key_links: + - from: "stacks/f1-stream/files/Dockerfile" + to: "stacks/f1-stream/files/backend/main.py" + via: "COPY backend/ into image" + pattern: "COPY.*backend" + - from: "stacks/f1-stream/files/Dockerfile" + to: "stacks/f1-stream/files/backend/requirements.txt" + via: "pip install requirements" + pattern: "pip install.*requirements" +--- + + +Create a minimal FastAPI backend application with a health endpoint and build a Docker image for it. This replaces the existing Go-based f1-stream application with the new Python/FastAPI stack. + +Purpose: Provide a deployable container image that the Terraform stack (Plan 02) will reference. The health endpoint proves the service is running correctly. +Output: Docker image `viktorbarzin/f1-stream:v2.0.0` pushed to Docker Hub, containing a working FastAPI server. + + + +@/Users/viktorbarzin/.claude/get-shit-done/workflows/execute-plan.md +@/Users/viktorbarzin/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/research/STACK.md + +# Existing files to replace/modify: +@stacks/f1-stream/files/Dockerfile +@stacks/f1-stream/files/redeploy.sh + + + + + + Task 1: Create FastAPI backend application with health endpoint + stacks/f1-stream/files/backend/main.py, stacks/f1-stream/files/backend/requirements.txt + +Create the directory `stacks/f1-stream/files/backend/`. + +Create `stacks/f1-stream/files/backend/requirements.txt` with pinned versions: +``` +fastapi==0.132.0 +uvicorn[standard] +``` + +Create `stacks/f1-stream/files/backend/main.py` with a minimal FastAPI application: +- Import FastAPI +- Create app instance with title "F1 Streams" +- Add `GET /health` endpoint that returns `{"status": "ok"}` +- Add `GET /` root endpoint that returns `{"service": "f1-streams", "version": "2.0.0"}` +- Add an `if __name__ == "__main__"` block that runs uvicorn on host 0.0.0.0 port 8000 + +This is intentionally minimal -- just enough to prove the deployment works. Later phases will add schedule, extractor, and proxy routes. + +Do NOT add any other dependencies or routes beyond the health check and root. Keep it simple. + + +Run `python3 -c "import ast; ast.parse(open('stacks/f1-stream/files/backend/main.py').read()); print('Syntax OK')"` to verify the Python file is valid. +Verify requirements.txt exists and contains fastapi and uvicorn. + + +`backend/main.py` exists with a valid FastAPI app that has `/health` and `/` endpoints. `requirements.txt` lists fastapi and uvicorn. + + + + + Task 2: Create Dockerfile and build/push the container image + stacks/f1-stream/files/Dockerfile, stacks/f1-stream/files/redeploy.sh + +Replace the existing Go Dockerfile at `stacks/f1-stream/files/Dockerfile` with a Python-based Dockerfile: + +```dockerfile +FROM python:3.13-slim-bookworm + +WORKDIR /app + +COPY backend/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY backend/ ./backend/ + +EXPOSE 8000 + +CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"] +``` + +Key points: +- Single-stage build (no build stage needed for Python -- interpreted language) +- Use `python:3.13-slim-bookworm` as base (from stack research) +- Install deps first for Docker layer caching +- Expose port 8000 (FastAPI default, different from old Go app's 8080) +- Run via uvicorn pointing to `backend.main:app` + +Update `stacks/f1-stream/files/redeploy.sh`: +```bash +#!/usr/bin/env bash +set -e + +docker build -t viktorbarzin/f1-stream:v2.0.0 -t viktorbarzin/f1-stream:latest . +docker push viktorbarzin/f1-stream:v2.0.0 +docker push viktorbarzin/f1-stream:latest +kubectl -n f1-stream rollout restart deployment f1-stream +``` + +Then build and push the image by running the redeploy script from the `stacks/f1-stream/files/` directory. Only run the docker build and push steps (skip the kubectl rollout -- that happens after Terraform apply in Plan 02). + +Build command: `cd stacks/f1-stream/files && docker build -t viktorbarzin/f1-stream:v2.0.0 -t viktorbarzin/f1-stream:latest . && docker push viktorbarzin/f1-stream:v2.0.0 && docker push viktorbarzin/f1-stream:latest` + +IMPORTANT: The old Go application files (main.go, go.mod, go.sum, internal/, node_modules/, package.json, package-lock.json, index.html, static/) should be removed from `stacks/f1-stream/files/` since they are no longer needed. Keep only: Dockerfile, redeploy.sh, and backend/. + + +Run `docker images | grep f1-stream` to confirm the image was built. +Run `docker run --rm -d -p 18000:8000 --name f1-test viktorbarzin/f1-stream:v2.0.0 && sleep 2 && curl -s http://localhost:18000/health && docker stop f1-test` to verify the container starts and the health endpoint responds. + + +Docker image `viktorbarzin/f1-stream:v2.0.0` is built, pushed to Docker Hub, and serves a 200 response on GET /health when run locally. + + + + + + +1. `docker images | grep f1-stream` shows the v2.0.0 tag +2. Running the image locally and curling /health returns `{"status": "ok"}` +3. The old Go files have been removed from `stacks/f1-stream/files/` +4. Only Dockerfile, redeploy.sh, and backend/ remain in the files directory + + + +- Docker image viktorbarzin/f1-stream:v2.0.0 exists on Docker Hub +- The image runs a FastAPI server on port 8000 with a working /health endpoint +- Old Go application files are cleaned up + + + +After completion, create `.planning/phases/01-infrastructure-and-deployment/01-01-SUMMARY.md` + diff --git a/.planning/phases/01-infrastructure-and-deployment/01-02-PLAN.md b/.planning/phases/01-infrastructure-and-deployment/01-02-PLAN.md new file mode 100644 index 00000000..bd789069 --- /dev/null +++ b/.planning/phases/01-infrastructure-and-deployment/01-02-PLAN.md @@ -0,0 +1,235 @@ +--- +phase: 01-infrastructure-and-deployment +plan: 02 +type: execute +wave: 2 +depends_on: + - "01-01" +files_modified: + - stacks/f1-stream/main.tf + - .woodpecker/f1-stream.yml +autonomous: true +requirements: + - DEPL-01 + - DEPL-02 + +must_haves: + truths: + - "A request to https://f1.viktorbarzin.me/health returns HTTP 200 with JSON {status: ok}" + - "The Terragrunt stack applies cleanly with no errors" + - "A file written to /data inside the pod survives a pod restart" + - "Woodpecker CI pipeline triggers on push for the f1-stream directory" + artifacts: + - path: "stacks/f1-stream/main.tf" + provides: "Kubernetes deployment, service, ingress, TLS for f1-stream" + contains: "viktorbarzin/f1-stream:v2.0.0" + - path: ".woodpecker/f1-stream.yml" + provides: "CI pipeline for f1-stream service" + contains: "f1-stream" + key_links: + - from: "stacks/f1-stream/main.tf" + to: "Docker Hub viktorbarzin/f1-stream:v2.0.0" + via: "kubernetes_deployment image reference" + pattern: "viktorbarzin/f1-stream:v2.0.0" + - from: "stacks/f1-stream/main.tf" + to: "NFS /mnt/main/f1-stream" + via: "inline NFS volume mount" + pattern: "/mnt/main/f1-stream" + - from: "stacks/f1-stream/main.tf" + to: "modules/kubernetes/ingress_factory" + via: "ingress module call" + pattern: "ingress_factory" +--- + + +Update the Terraform stack to deploy the new Python/FastAPI container, verify NFS mount persistence, and add a Woodpecker CI pipeline. This completes Phase 1 by making the service live on the cluster and reachable at its public URL. + +Purpose: The service must be running on the Kubernetes cluster, reachable at f1.viktorbarzin.me, with NFS storage mounted and CI/CD in place -- ready for application development in Phase 2. +Output: Live deployment at f1.viktorbarzin.me, NFS-backed persistent storage, Woodpecker CI pipeline. + + + +@/Users/viktorbarzin/.claude/get-shit-done/workflows/execute-plan.md +@/Users/viktorbarzin/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-infrastructure-and-deployment/01-01-SUMMARY.md + +# Key reference files: +@stacks/f1-stream/main.tf +@stacks/f1-stream/terragrunt.hcl +@.woodpecker/build-cli.yml +@.woodpecker/default.yml + + + + + + Task 1: Update Terraform deployment for Python/FastAPI and verify NFS mount + stacks/f1-stream/main.tf + +Modify `stacks/f1-stream/main.tf` to update the deployment for the new Python/FastAPI application: + +1. **Change the container image** from `viktorbarzin/f1-stream:v1.3.1` to `viktorbarzin/f1-stream:v2.0.0` + +2. **Change the container port** from 8080 to 8000 (FastAPI/uvicorn default) + +3. **Update the service target_port** from 8080 to 8000 + +4. **Remove old Go-specific environment variables** that are no longer needed: + - Remove `WEBAUTHN_RPID` + - Remove `WEBAUTHN_ORIGIN` + - Remove `WEBAUTHN_DISPLAY_NAME` + - Remove `HEADLESS_EXTRACT_ENABLED` + - Remove `TURN_URL` + - Remove `TURN_SHARED_SECRET` + - Remove `TURN_INTERNAL_URL` + +5. **Remove unused variables** from the top of the file: + - Remove `variable "coturn_turn_secret"` (was for WebRTC/TURN) + - Remove `variable "public_ip"` (was for TURN URL) + - Keep `variable "tls_secret_name"` and `variable "nfs_server"` (still needed) + +6. **Keep the NFS volume mount** exactly as-is -- it already follows the inline NFS pattern: + ```hcl + volume { + name = "data" + nfs { + server = var.nfs_server + path = "/mnt/main/f1-stream" + } + } + ``` + The volume_mount at `/data` stays the same. + +7. **Update resource limits** for Python: + ```hcl + resources { + limits = { + cpu = "500m" + memory = "256Mi" + } + requests = { + cpu = "50m" + memory = "64Mi" + } + } + ``` + Python/FastAPI with uvicorn needs less CPU than Go+Chromium but similar memory. + +8. **Keep everything else unchanged**: namespace, service, tls_secret module, ingress module. + +After editing, apply the Terraform stack: +```bash +cd stacks/f1-stream && terragrunt apply --non-interactive +``` + +Wait for the deployment to roll out: +```bash +kubectl --kubeconfig $(pwd)/config -n f1-stream rollout status deployment/f1-stream --timeout=120s +``` + +Verify the pod is running: +```bash +kubectl --kubeconfig $(pwd)/config -n f1-stream get pods +``` + +Verify the health endpoint responds through the public URL: +```bash +curl -s https://f1.viktorbarzin.me/health +``` + +Verify NFS mount persistence by writing a test file, restarting the pod, and reading it back: +```bash +POD=$(kubectl --kubeconfig $(pwd)/config -n f1-stream get pods -l app=f1-stream -o jsonpath='{.items[0].metadata.name}') +kubectl --kubeconfig $(pwd)/config -n f1-stream exec $POD -- sh -c 'echo "nfs-test-$(date +%s)" > /data/test-file.txt && cat /data/test-file.txt' +kubectl --kubeconfig $(pwd)/config -n f1-stream rollout restart deployment/f1-stream +kubectl --kubeconfig $(pwd)/config -n f1-stream rollout status deployment/f1-stream --timeout=120s +NEW_POD=$(kubectl --kubeconfig $(pwd)/config -n f1-stream get pods -l app=f1-stream -o jsonpath='{.items[0].metadata.name}') +kubectl --kubeconfig $(pwd)/config -n f1-stream exec $NEW_POD -- cat /data/test-file.txt +``` +The test file should contain the same content after the pod restart. + + +1. `terragrunt apply` exits with 0 (no errors) +2. `kubectl get pods -n f1-stream` shows 1/1 Running +3. `curl -s https://f1.viktorbarzin.me/health` returns `{"status":"ok"}` +4. NFS persistence test passes (file survives pod restart) + + +The f1-stream deployment is running on the cluster with the new Python/FastAPI image, reachable at https://f1.viktorbarzin.me/health, and the NFS volume at /data persists data across pod restarts. + + + + + Task 2: Create Woodpecker CI pipeline for f1-stream + .woodpecker/f1-stream.yml + +Create `.woodpecker/f1-stream.yml` following the pattern from `build-cli.yml`: + +```yaml +when: + event: push + path: + include: + - "stacks/f1-stream/files/**" + +clone: + git: + image: woodpeckerci/plugin-git + settings: + attempts: 5 + backoff: 10s + +steps: + - name: build-image + image: woodpeckerci/plugin-docker-buildx + settings: + username: "viktorbarzin" + password: + from_secret: dockerhub-pat + repo: viktorbarzin/f1-stream + dockerfile: stacks/f1-stream/files/Dockerfile + context: stacks/f1-stream/files + auto_tag: true +``` + +Key differences from the default pipeline: +- **Path filter**: Only triggers when files under `stacks/f1-stream/files/` change (the application code) +- **Builds and pushes the Docker image** using the same `woodpeckerci/plugin-docker-buildx` pattern as build-cli.yml +- **Docker context** points to the `stacks/f1-stream/files/` directory where the Dockerfile lives +- Does NOT run Terragrunt apply (that is done manually or by the default pipeline for the platform stack) + + +Verify the YAML is valid: `python3 -c "import yaml; yaml.safe_load(open('.woodpecker/f1-stream.yml')); print('YAML OK')"` +Verify the file exists and references f1-stream correctly. + + +Woodpecker CI pipeline file exists at `.woodpecker/f1-stream.yml`, configured to build and push the Docker image when files under `stacks/f1-stream/files/` change. + + + + + + +1. `curl -s https://f1.viktorbarzin.me/health` returns `{"status":"ok"}` +2. `cd stacks/f1-stream && terragrunt plan --non-interactive` shows no changes (stack is clean) +3. NFS test file written before pod restart is readable after pod restart +4. `.woodpecker/f1-stream.yml` exists and is valid YAML +5. `kubectl --kubeconfig $(pwd)/config -n f1-stream get pods` shows 1/1 Running + + + +- The service is live at https://f1.viktorbarzin.me and responds with 200 on /health +- Terragrunt stack applies cleanly with no manual cluster intervention +- NFS volume mount at /data persists data across pod restarts +- Woodpecker CI pipeline exists for automated image builds + + + +After completion, create `.planning/phases/01-infrastructure-and-deployment/01-02-SUMMARY.md` +