From ce4371d9e4c3b9ac53f3c65da04cd487aa5ac258 Mon Sep 17 00:00:00 2001 From: liquidrinu Date: Tue, 20 May 2025 12:39:47 +0200 Subject: [PATCH 1/8] mega update: k8s backend, frontend, database, pods, helm charts, k8s, load balancing, gitea-ci.yml explanded readme --- .gitea-ci.yml | 12 + .gitignore | 11 +- .prettierrc.json | 3 +- README.md | 685 ++++++++++++++------- architecture.excalidraw | 13 + chart/Chart.yaml | 6 + chart/templates/backend-deployment.yaml | 25 + chart/templates/backend-migration.job.yaml | 25 + chart/templates/backend-secrets.yaml | 2 + chart/templates/backend-service.yaml | 11 + chart/templates/frontend-deployment.yaml | 25 + chart/templates/frontend-service.yaml | 11 + chart/templates/postgres-deployment.yaml | 33 + chart/templates/postgres-pvc.yaml | 10 + chart/templates/postgres-service.yaml | 10 + frontend/Dockerfile | 2 +- frontend/Dockerfile.dev | 17 +- frontend/nginx.conf | 18 +- frontend/vite.config.ts | 7 + mikro-orm.config.ts | 9 +- package.json | 11 +- src/app.ts | 9 +- 22 files changed, 693 insertions(+), 262 deletions(-) create mode 100644 .gitea-ci.yml create mode 100644 architecture.excalidraw create mode 100644 chart/Chart.yaml create mode 100644 chart/templates/backend-deployment.yaml create mode 100644 chart/templates/backend-migration.job.yaml create mode 100644 chart/templates/backend-secrets.yaml create mode 100644 chart/templates/backend-service.yaml create mode 100644 chart/templates/frontend-deployment.yaml create mode 100644 chart/templates/frontend-service.yaml create mode 100644 chart/templates/postgres-deployment.yaml create mode 100644 chart/templates/postgres-pvc.yaml create mode 100644 chart/templates/postgres-service.yaml diff --git a/.gitea-ci.yml b/.gitea-ci.yml new file mode 100644 index 0000000..1821deb --- /dev/null +++ b/.gitea-ci.yml @@ -0,0 +1,12 @@ +image: lachlanevenson/k8s-helm:latest + +stages: + - deploy + +variables: + KUBECONFIG: /root/.kube/config # Adjust if you're mounting a kubeconfig differently + +deploy: + stage: deploy + script: + - helm upgrade --install fusero ./chart -f ./chart/values-prod.yaml diff --git a/.gitignore b/.gitignore index 2a6e875..9e87c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -114,4 +114,13 @@ test/types/index.js dist # general -bkups \ No newline at end of file +docs +bkups + +# k8s +secrets.yml + +# helm +values.dev.* +values.prod.* + diff --git a/.prettierrc.json b/.prettierrc.json index 2ad5967..a3436dc 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -6,5 +6,6 @@ "tabWidth": 2, "useTabs": false, "printWidth": 144, - "bracketSpacing": true + "bracketSpacing": true, + "plugins": ["@helm/prettier-plugin-helm"] } diff --git a/README.md b/README.md index 4fb866c..3598aa6 100644 --- a/README.md +++ b/README.md @@ -1,269 +1,486 @@ -# Fusero App Boilerplate +# ⚡️ Fusero App Boilerplate -A full-stack application boilerplate with React frontend and Node.js backend. +A full-stack application boilerplate with a React frontend and Node.js backend — powered by Fastify, Vite, PostgreSQL, Docker, and optional Kubernetes & Helm support. Built for modern dev workflows and AI-powered backend endpoint generation. -## Project Structure +--- -``` -fusero-app-boilerplate/ -├── frontend/ # React frontend application -├── backend/ # Node.js backend application -├── docker-compose.yml # Production Docker configuration -└── docker-compose.dev.yml # Development Docker configuration -``` +## 📚 Table of Contents -## Prerequisites +- [⚡️ Fusero App Boilerplate](#️-fusero-app-boilerplate) + - [📚 Table of Contents](#-table-of-contents) + - [📁 Project Structure](#-project-structure) + - [⚙️ Prerequisites](#️-prerequisites) + - [💻 Development Setup](#-development-setup) + - [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals) + - [🛠️ Environment Setup](#️-environment-setup) +- [For Kubernetes, these are set in chart/values.yaml:](#for-kubernetes-these-are-set-in-chartvaluesyaml) +- [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db) +- [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service) +- [POSTGRES\_PORT=19095](#postgres_port19095) +- [POSTGRES\_USER=root](#postgres_userroot) +- [POSTGRES\_PASSWORD=root123](#postgres_passwordroot123) + - [🚀 Production Deployment](#-production-deployment) + - [🌐 Frontend Routing in Production](#-frontend-routing-in-production) + - [🔐 HTTPS with Self-Signed Certificates](#-https-with-self-signed-certificates) + - [🧠 Development Best Practices](#-development-best-practices) + - [📘 API Documentation](#-api-documentation) + - [🧩 ChatGPT-Powered Endpoint Creation](#-chatgpt-powered-endpoint-creation) + - [🧪 Troubleshooting](#-troubleshooting) + - [🤝 Contributing](#-contributing) + - [📄 License](#-license) + - [Kubernetes Troubleshooting \& Redeployment Commands](#kubernetes-troubleshooting--redeployment-commands) + - [1. Rebuild the backend Docker image (after code/config changes)](#1-rebuild-the-backend-docker-image-after-codeconfig-changes) + - [2. (If using a remote registry) Push the image](#2-if-using-a-remote-registry-push-the-image) + - [3. Upgrade the Helm release with the latest values](#3-upgrade-the-helm-release-with-the-latest-values) + - [4. Restart the backend deployment to pick up new images and env vars](#4-restart-the-backend-deployment-to-pick-up-new-images-and-env-vars) + - [5. Check backend pod environment variables](#5-check-backend-pod-environment-variables) + - [6. Check backend pod logs for errors](#6-check-backend-pod-logs-for-errors) + - [7. If you change DB env vars or code, repeat steps 1-6](#7-if-you-change-db-env-vars-or-code-repeat-steps-1-6) + - [Frontend Rebuild \& Redeploy (Kubernetes)](#frontend-rebuild--redeploy-kubernetes) + - [1. Rebuild the frontend Docker image](#1-rebuild-the-frontend-docker-image) + - [2. (If using a remote registry) Push the image](#2-if-using-a-remote-registry-push-the-image-1) + - [3. Upgrade the Helm release](#3-upgrade-the-helm-release) + - [4. Restart the frontend deployment](#4-restart-the-frontend-deployment) + - [Port-Forwarding for Local Access](#port-forwarding-for-local-access) + - [Frontend (React app)](#frontend-react-app) + - [Backend (API)](#backend-api) + - [NGINX Backend Service Name: Docker Compose vs Kubernetes](#nginx-backend-service-name-docker-compose-vs-kubernetes) + - [How to update the NGINX config for Kubernetes](#how-to-update-the-nginx-config-for-kubernetes) + - [Cleaning Up Duplicate or Crashing Deployments and Pods in Kubernetes](#cleaning-up-duplicate-or-crashing-deployments-and-pods-in-kubernetes) + - [1. List deployments and pods](#1-list-deployments-and-pods) + - [2. Delete old or crashing deployments (example IDs from your cluster)](#2-delete-old-or-crashing-deployments-example-ids-from-your-cluster) + - [3. Delete old or crashing pods (example IDs from your cluster)](#3-delete-old-or-crashing-pods-example-ids-from-your-cluster) + - [Debugging Frontend Pod Crashes: NGINX SSL Certificate Errors](#debugging-frontend-pod-crashes-nginx-ssl-certificate-errors) + - [How to fix for Kubernetes (Recommended)](#how-to-fix-for-kubernetes-recommended) + - [Connecting to the Database from Your Host (DBeaver, etc.)](#connecting-to-the-database-from-your-host-dbeaver-etc) -- Node.js (v20 or higher) -- npm (v9 or higher) -- Docker and Docker Compose +--- + +## 📁 Project Structure + +fusero-app-boilerplate/ +├── frontend/ # React frontend application +├── backend/ # Node.js backend application +├── docker-compose.yml # Production Docker configuration +├── docker-compose.dev.yml # Development Docker configuration +└── chart/ # Helm chart for Kubernetes deployment + +--- + +## ⚙️ Prerequisites + +- Node.js (v20 or higher) +- npm (v9 or higher) +- Docker & Docker Compose - Git -## Development Setup +--- -### Important Note: Database Must Run in Docker -The PostgreSQL database must always run in Docker, regardless of your development setup choice. This ensures consistent database behavior across all environments. +## 💻 Development Setup -To start the database: -```bash -docker-compose up db -``` +🗃️ PostgreSQL must run in Docker for consistent behavior. -### Running Services Separately (Recommended for Development) +Create volume and start the database: +docker volume create fusero-db-data +docker-compose up -d db -For better debugging experience, run the frontend and backend in separate terminal windows, while keeping the database in Docker: +Backend setup: +cd backend +cp .env.example .env +npm install +npm run migrate +npm run seed +npm run dev & +cd .. -1. **First, ensure the database is running in Docker** - ```bash - docker-compose up db - ``` +Frontend setup: +cd frontend +cp .env.example .env +npm install +npm run dev & +cd .. -2. **Then, in separate terminal windows:** +App is running: +Frontend → http://localhost:3000 +Backend → http://localhost:14000 -#### Terminal 1: Backend Service -```bash -cd backend -npm install +--- + +### Alternate: Running Services in Separate Terminals + +Terminal 1 (backend): +cd backend +npm install npm run dev -``` -The backend will be available at http://localhost:14000 -#### Terminal 2: Frontend Service -```bash -cd frontend -npm install +Terminal 2 (frontend): +cd frontend +npm install npm run dev -``` -The frontend will be available at http://localhost:3000 -### Database Setup +--- -1. **Create a New Volume** - - Ensure the database volume is created: - ```bash - docker volume create fusero-db-data - ``` +## 🛠️ Environment Setup -2. **Run Migrations** - - Apply database migrations to set up the schema: - ```bash - cd backend - npm run migrate - ``` +backend/.env: +POSTGRES_NAME=fusero-boilerplate-db +POSTGRES_HOSTNAME=localhost +POSTGRES_PORT=19095 +POSTGRES_USER=root +POSTGRES_PASSWORD=root123 +JWT_SECRET=your_jwt_secret_key_here -3. **Seed the Database** - - Populate the database with initial data: - ```bash - cd backend - npm run seed - ``` +# For Kubernetes, these are set in chart/values.yaml: +# POSTGRES_NAME=fusero-boilerplate-db +# POSTGRES_HOSTNAME=postgres-service +# POSTGRES_PORT=19095 +# POSTGRES_USER=root +# POSTGRES_PASSWORD=root123 -### Environment Setup +frontend/.env: +VITE_API_BASE_URL=http://localhost:14000/api/v1 -1. **Backend Environment** - - Copy `.env.example` to `.env` in the backend directory - - Configure your environment variables: - ``` - PORT=14000 - DB_HOST=localhost - DB_PORT=19090 - DB_USER=postgres - DB_PASSWORD=postgres - DB_NAME=fusero - JWT_SECRET=your_jwt_secret_key_here - ``` +--- -2. **Frontend Environment** - - Copy `.env.example` to `.env` in the frontend directory - - Set the API base URL: - ``` - VITE_API_BASE_URL=http://localhost:14000/api/v1 - ``` +## 🚀 Production Deployment -## Production Deployment +1. Build and run with Docker: +docker-compose up --build -1. **Build and Run with Docker** - ```bash - docker-compose up --build - ``` +2. Apply migrations and seed inside backend container: +docker exec -it fusero-app-backend npx mikro-orm migration:up +docker exec -it fusero-app-backend npm run seed -2. **Run Migrations and Seeders in Production** - After your containers are up, run the following commands to apply database migrations and seed data inside the backend container: - ```bash - docker exec -it fusero-app-backend npx mikro-orm migration:up - docker exec -it fusero-app-backend npm run seed - ``` - **Note:** These commands must be run inside the backend container so they use the correct Docker network and environment variables. +3. Ensure all required environment variables are configured. +Never commit `.env` files. -3. **Environment Variables** - - Ensure all environment variables are properly set in your production environment - - Never commit `.env` files to version control +--- -## Frontend Routing in Production +## 🌐 Frontend Routing in Production -In production, the frontend is served through nginx. To ensure client-side routing works correctly: +In production, the frontend is served through NGINX. -1. **Nginx Configuration** - - Ensure your nginx configuration includes the following directive to handle unknown routes: - ```nginx - location / { - try_files $uri $uri/ /index.html; - } - ``` - -2. **React Router Configuration** - - Set the `basename` dynamically based on the environment: - - In production, set `basename="/dashboard"`. - - In development, set `basename="/"`. - -3. **Navigation Links** - - Use relative paths in your navigation links (e.g., `to="canvas/canvas-endpoints"` instead of `to="/dashboard/canvas/canvas-endpoints"`). - -## HTTPS with Self-Signed Certificates - -To run the application with HTTPS using a self-signed certificate: - -1. **Generate a Self-Signed Certificate** - ```bash - openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx/ssl/nginx.key -out ./nginx/ssl/nginx.crt - ``` - -2. **Update Docker Compose** - - Ensure your `docker-compose.yml` mounts the certificate files in the nginx service: - ```yaml - volumes: - - ./nginx/ssl:/etc/nginx/ssl - ``` - -3. **Nginx Configuration** - - Use the production nginx configuration that includes SSL settings. - -## Development Best Practices - -1. **Database Management** - - Always run the database in Docker - - Use `docker-compose.dev.yml` for development - - Never run PostgreSQL directly on your host machine - -2. **Running Services Separately** - - For development, it's recommended to run frontend and backend in separate terminal windows - - This allows for better debugging and hot-reloading - - You can see logs from each service clearly - -3. **Code Organization** - - Frontend code should be in the `frontend/` directory - - Backend code should be in the `backend/` directory - - Shared types and utilities should be in their respective directories - -4. **Version Control** - - Commit `package-lock.json` files - - Don't commit `.env` files - - Use meaningful commit messages - -## API Documentation - -The backend API is documented using Swagger/OpenAPI. After starting the backend service, you can access the API documentation at: -- Development: http://localhost:14000/api-docs -- Production: http://your-domain/api-docs - -## Troubleshooting - -1. **Port Conflicts** - - If you encounter port conflicts, check which services are running: - ```bash - docker ps - ``` - - Or check for processes using the ports: - ```bash - lsof -i :3000 - lsof -i :14000 - ``` - -2. **Database Issues** - - Ensure PostgreSQL is running in Docker - - Check database connection settings in `.env` - - Verify database migrations are up to date - - If database issues persist, try: - ```bash - docker-compose -f docker-compose.dev.yml down - docker-compose -f docker-compose.dev.yml up db - ``` - -3. **CORS Issues** - - If you see CORS errors, verify the frontend's API base URL - - Check backend CORS configuration - - Ensure both services are running on the correct ports - -## Contributing - -1. Create a new branch for your feature -2. Make your changes -3. Submit a pull request -4. Ensure all tests pass -5. Update documentation as needed - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. - -## Technical Documentation: ChatGPT-Powered Endpoint Creation - -### Overview -Developers can leverage the ChatGPT modal in the Canvas Endpoints UI to create new Canvas API endpoints using natural language prompts. When a user enters a prompt like "Create a course endpoint for Canvas", the system uses ChatGPT to: - -1. Interpret the intent and generate a JSON object with the required fields for the endpoint (name, method, path, description, etc.). -2. Automatically submit this JSON to the backend endpoint creation API (`/api/v1/canvas-api/endpoints`). -3. Refresh the endpoint list in the UI and display a success message. - -### How it Works -- **Prompt Handling:** - - The frontend sends the user's prompt to `/api/v1/canvas-api/chatgpt/completions`. - - ChatGPT is instructed to return only a JSON object suitable for the endpoint creation form. -- **Auto-Creation:** - - If the response is a valid endpoint JSON (with `name`, `method`, and `path`), the frontend posts it to `/api/v1/canvas-api/endpoints`. - - The endpoint list is refreshed and a toast notification is shown. -- **Fallback:** - - If the response is not a valid endpoint JSON, it is displayed as a normal chat message. - -### Example Prompt -``` -Create a course endpoint for Canvas. Use the Canvas API docs to determine the correct path and required fields. -``` - -### Example ChatGPT Response -``` -{ - "name": "Create Course", - "method": "POST", - "path": "/courses", - "description": "Creates a new course in Canvas." +NGINX configuration (important for React routing): +location / { + try_files $uri $uri/ /index.html; } + +React Router Configuration: +Use `basename="/"` in dev, and `basename="/dashboard"` in production. + +Use relative paths in links: +Correct: to="canvas/canvas-endpoints" +Wrong: to="/dashboard/canvas/canvas-endpoints" + +--- + +## 🔐 HTTPS with Self-Signed Certificates + +Generate a self-signed cert: +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx/ssl/nginx.key -out ./nginx/ssl/nginx.crt + +Ensure `docker-compose.yml` mounts the certs: +volumes: +- ./nginx/ssl:/etc/nginx/ssl + +Configure NGINX to use the cert in production. + +--- + +## 🧠 Development Best Practices + +- Always run the DB via Docker +- Use `docker-compose.dev.yml` for development +- Never run PostgreSQL directly on host +- Run frontend and backend separately for hot reload +- Use `.env.example` as a template +- Never commit `.env` +- Commit `package-lock.json` +- Use meaningful commit messages + +--- + +## 📘 API Documentation + +After running the backend: + +Development → http://localhost:14000/api-docs +Production → https://your-domain/api-docs + +--- + +## 🧩 ChatGPT-Powered Endpoint Creation + +Prompts like "Create a course endpoint for Canvas" auto-generate API endpoints. + +How it works: +1. The frontend sends your prompt to `/api/v1/canvas-api/chatgpt/completions` +2. If ChatGPT returns a valid endpoint JSON, it's POSTed to `/api/v1/canvas-api/endpoints` +3. The UI auto-refreshes the endpoint list and shows a toast + +Example Prompt: +Create a course endpoint for Canvas. + +Expected JSON: +{ + "name": "Create Course", + "method": "POST", + "path": "/courses", + "description": "Creates a new course in Canvas." +} + +Developer Notes: +- Frontend logic: frontend/src/components/CanvasEndpoints.tsx +- Backend API: /api/v1/canvas-api/endpoints + +--- + +## 🧪 Troubleshooting + +Port Conflicts: +docker ps +lsof -i :3000 +lsof -i :14000 + +Database Issues: +Ensure DB is in Docker and configured correctly +Try restarting: +docker-compose -f docker-compose.dev.yml down +docker-compose -f docker-compose.dev.yml up db + +CORS Issues: +Check API base URL in frontend `.env` +Check backend CORS settings +Verify ports match and services are running + +--- + +## 🤝 Contributing + +1. Create a branch +2. Make your changes +3. Pass all tests +4. Open a pull request +5. Update docs if needed + +--- + +## 📄 License + +This project is licensed under the MIT License. +See the LICENSE file for full details. + +--- + +## Kubernetes Troubleshooting & Redeployment Commands + +If your backend is not picking up environment variables or is failing to connect to the database, follow these steps: + +### 1. Rebuild the backend Docker image (after code/config changes) +```bash +docker build -t fusero-backend-dev:local . ``` -### Developer Notes -- The ChatGPT modal logic is in `frontend/src/components/CanvasEndpoints.tsx`. -- The backend endpoint creation API is `/api/v1/canvas-api/endpoints`. -- The system expects ChatGPT to return a JSON object with at least `name`, `method`, and `path`. -- The endpoint list is auto-refreshed after creation. +### 2. (If using a remote registry) Push the image +```bash +docker push /fusero-backend-dev:local +``` ---- \ No newline at end of file +### 3. Upgrade the Helm release with the latest values +```bash +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml +``` + +### 4. Restart the backend deployment to pick up new images and env vars +```bash +kubectl rollout restart deployment/fusero-backend -n fusero +``` + +### 5. Check backend pod environment variables +```bash +kubectl get pods -n fusero +# Replace with the actual pod name from above +kubectl exec -n fusero -- printenv | grep POSTGRES +``` + +### 6. Check backend pod logs for errors +```bash +kubectl logs -n fusero --tail=50 +``` + +### 7. If you change DB env vars or code, repeat steps 1-6 + +--- + +**Note:** +- Make sure your backend code does NOT load `.env` at runtime in Kubernetes. It should use the environment variables provided by the pod. +- If you see connection errors to the DB, always check the pod's environment and logs as above. + +--- + +## Frontend Rebuild & Redeploy (Kubernetes) + +If you change the VITE_API_BASE_URL or any frontend environment variable, rebuild and redeploy the frontend: + +### 1. Rebuild the frontend Docker image +```bash +docker build -t fusero-frontend-dev:local ./frontend +``` + +### 2. (If using a remote registry) Push the image +```bash +docker push /fusero-frontend-dev:local +``` + +### 3. Upgrade the Helm release +```bash +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml +``` + +### 4. Restart the frontend deployment +```bash +kubectl rollout restart deployment/fusero-frontend -n fusero +``` + +--- + +## Port-Forwarding for Local Access + +To access your services running in Kubernetes from your local machine, use these commands: + +### Frontend (React app) +```bash +kubectl port-forward -n fusero svc/fusero-frontend-service 3000:80 +``` +- Access at: http://localhost:3000 + +### Backend (API) +```bash +kubectl port-forward -n fusero svc/fusero-backend-service 14000:14000 +``` +- Access at: http://localhost:14000 + +--- + +## NGINX Backend Service Name: Docker Compose vs Kubernetes + +**If your frontend uses NGINX to proxy API requests, you must update the backend service name depending on your environment:** + +- **Docker Compose/local:** The backend may be named `fusero-app-backend`. +- **Kubernetes:** The backend service is named `fusero-backend-service`. + +### How to update the NGINX config for Kubernetes + +Edit `frontend/nginx.conf`: + +**Change this:** +```nginx +proxy_pass http://fusero-app-backend:14000/; +``` +**To this:** +```nginx +proxy_pass http://fusero-backend-service:14000/; +``` + +Then rebuild and redeploy the frontend: +```bash +docker build -t fusero-frontend-dev:local ./frontend +# (push if needed) +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml +kubectl rollout restart deployment/fusero-frontend -n fusero +``` + +**If you see an NGINX error like `host not found in upstream`, this is the cause!** + +--- + +## Cleaning Up Duplicate or Crashing Deployments and Pods in Kubernetes + +If you see multiple frontend or backend pods (or CrashLoopBackOff errors), clean up your namespace with these steps: + +### 1. List deployments and pods +```bash +kubectl get deployments -n fusero +kubectl get pods -n fusero +``` + +### 2. Delete old or crashing deployments (example IDs from your cluster) +```bash +kubectl delete deployment fusero-frontend-65cb8db99d -n fusero +kubectl delete deployment fusero-frontend-74fcbb778 -n fusero +``` + +### 3. Delete old or crashing pods (example IDs from your cluster) +```bash +kubectl delete pod fusero-frontend-65cb8db99d-f2lhr -n fusero +kubectl delete pod fusero-frontend-74fcbb778-v89gm -n fusero +``` + +**Tip:** Only keep the latest, healthy pods and deployments. If in doubt, check with `kubectl get deployments -n fusero` and `kubectl get pods -n fusero` before deleting. + +--- + +## Debugging Frontend Pod Crashes: NGINX SSL Certificate Errors + +If your frontend pod crashes with an error like: + +``` +nginx: [emerg] cannot load certificate "/etc/nginx/certs/fusero-selfsigned.crt": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory) +``` + +This means NGINX is trying to load an SSL certificate that does not exist in the pod. + +### How to fix for Kubernetes (Recommended) +1. Edit `frontend/nginx.conf`: + - Change: + ```nginx + listen 14443 ssl; + ssl_certificate /etc/nginx/certs/fusero-selfsigned.crt; + ssl_certificate_key /etc/nginx/certs/fusero-selfsigned.key; + ``` + - To: + ```nginx + listen 8080; + # (remove the ssl_certificate and ssl_certificate_key lines) + ``` +2. Rebuild the frontend Docker image: + ```bash + docker build --no-cache -t fusero-frontend-dev:local ./frontend + ``` +3. (If using a remote registry) Push the image. +4. Redeploy with Helm: + ```bash + helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml + ``` +5. Check pod status: + ```bash + kubectl get pods -n fusero + ``` + +**This will make NGINX listen on port 8080 without SSL, which is standard for in-cluster Kubernetes services.** + +--- + +## Connecting to the Database from Your Host (DBeaver, etc.) + +To connect to the Postgres database running in Kubernetes from your local machine (for example, using DBeaver or another SQL client): + +1. **Port-forward the Postgres service:** + ```bash + kubectl port-forward svc/postgres-service 5432:5432 + ``` + - Keep this terminal open while you use your database client. + - If port 5432 is in use on your machine, you can use another local port (e.g., `15432:5432`) and connect to port 15432 in your client. + +2. **Database connection settings:** + - **Host:** `localhost` + - **Port:** `5432` (or your chosen local port) + - **Database:** `fusero-boilerplate-db` + - **Username:** `root` + - **Password:** `root123` + +3. **Open DBeaver (or your preferred client) and create a new Postgres connection using the above settings.** + +4. **Test the connection.** + +--- diff --git a/architecture.excalidraw b/architecture.excalidraw new file mode 100644 index 0000000..9f06bc3 --- /dev/null +++ b/architecture.excalidraw @@ -0,0 +1,13 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..3708c76 --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: fusero +description: Fusero application Helm chart +type: application +version: 0.1.0 +appVersion: "1.0.0" diff --git a/chart/templates/backend-deployment.yaml b/chart/templates/backend-deployment.yaml new file mode 100644 index 0000000..14b3668 --- /dev/null +++ b/chart/templates/backend-deployment.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fusero-backend +spec: + replicas: 1 + selector: + matchLabels: + app: fusero-backend + template: + metadata: + labels: + app: fusero-backend + spec: + containers: + - name: backend + image: {{ .Values.backend.image }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + ports: + - containerPort: {{ .Values.backend.port }} + env: + {{- range $key, $val := .Values.backend.env }} + - name: {{ $key }} + value: "{{ $val }}" + {{- end }} diff --git a/chart/templates/backend-migration.job.yaml b/chart/templates/backend-migration.job.yaml new file mode 100644 index 0000000..2d6bac8 --- /dev/null +++ b/chart/templates/backend-migration.job.yaml @@ -0,0 +1,25 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: fusero-backend-db-init +spec: + backoffLimit: 0 + template: + metadata: + name: fusero-backend-db-init + spec: + containers: + - name: migrate-seed + image: {{ .Values.backend.image }} + command: ["/bin/sh", "-c"] + args: + - | + echo "Running migrations and seeds..." && \ + npx mikro-orm migration:up && \ + npm run seed + env: + {{- range $key, $val := .Values.backend.env }} + - name: {{ $key }} + value: "{{ $val }}" + {{- end }} + restartPolicy: Never diff --git a/chart/templates/backend-secrets.yaml b/chart/templates/backend-secrets.yaml new file mode 100644 index 0000000..3db53f4 --- /dev/null +++ b/chart/templates/backend-secrets.yaml @@ -0,0 +1,2 @@ +# You can skip this if you manage secrets separately +# Included for completeness (values-dev.yaml handles them) diff --git a/chart/templates/backend-service.yaml b/chart/templates/backend-service.yaml new file mode 100644 index 0000000..98c4aad --- /dev/null +++ b/chart/templates/backend-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: fusero-backend-service +spec: + selector: + app: fusero-backend + ports: + - protocol: TCP + port: {{ .Values.backend.port }} + targetPort: {{ .Values.backend.port }} diff --git a/chart/templates/frontend-deployment.yaml b/chart/templates/frontend-deployment.yaml new file mode 100644 index 0000000..5023f09 --- /dev/null +++ b/chart/templates/frontend-deployment.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fusero-frontend +spec: + replicas: 1 + selector: + matchLabels: + app: fusero-frontend + template: + metadata: + labels: + app: fusero-frontend + spec: + containers: + - name: frontend + image: {{ .Values.frontend.image }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + ports: + - containerPort: {{ .Values.frontend.port }} + env: + {{- range $key, $val := .Values.frontend.env }} + - name: {{ $key }} + value: "{{ $val }}" + {{- end }} diff --git a/chart/templates/frontend-service.yaml b/chart/templates/frontend-service.yaml new file mode 100644 index 0000000..660e3bf --- /dev/null +++ b/chart/templates/frontend-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: fusero-frontend-service +spec: + selector: + app: fusero-frontend + ports: + - protocol: TCP + port: 80 + targetPort: {{ .Values.frontend.port }} diff --git a/chart/templates/postgres-deployment.yaml b/chart/templates/postgres-deployment.yaml new file mode 100644 index 0000000..af643e2 --- /dev/null +++ b/chart/templates/postgres-deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: {{ .Values.postgres.image }} + ports: + - containerPort: 5432 + env: + - name: POSTGRES_DB + value: {{ .Values.postgres.dbName }} + - name: POSTGRES_USER + value: {{ .Values.postgres.user }} + - name: POSTGRES_PASSWORD + value: {{ .Values.postgres.password }} + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgres-data + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-pvc-fresh diff --git a/chart/templates/postgres-pvc.yaml b/chart/templates/postgres-pvc.yaml new file mode 100644 index 0000000..d66a485 --- /dev/null +++ b/chart/templates/postgres-pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc-fresh +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.postgres.storage }} diff --git a/chart/templates/postgres-service.yaml b/chart/templates/postgres-service.yaml new file mode 100644 index 0000000..ec5c270 --- /dev/null +++ b/chart/templates/postgres-service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres-service +spec: + selector: + app: postgres + ports: + - protocol: TCP + port: 5432 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 4040591..da95337 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM node:18-alpine as build +FROM node:18-alpine AS build WORKDIR /app diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev index dfc9215..1f2f70f 100644 --- a/frontend/Dockerfile.dev +++ b/frontend/Dockerfile.dev @@ -1,18 +1,17 @@ -FROM node:18-alpine +FROM node:22 WORKDIR /app -# Copy package files +# Copy package files and install dependencies COPY package*.json ./ +RUN npm ci -# Install dependencies -RUN npm install - -# Copy the rest of the application +# Copy .env and source code +COPY .env . COPY . . -# Expose port 8080 +# Expose the dev server port EXPOSE 8080 -# Start development server -CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "8080"] \ No newline at end of file +# Start the dev server — CORRECT host +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "8080"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 6e501a6..6aa0f3c 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,12 +1,13 @@ server { - listen 14443 ssl; + # listen 14443 ssl; + listen 8080; server_name _; - ssl_certificate /etc/nginx/certs/fusero-selfsigned.crt; - ssl_certificate_key /etc/nginx/certs/fusero-selfsigned.key; + # ssl_certificate /etc/nginx/certs/fusero-selfsigned.crt; + # ssl_certificate_key /etc/nginx/certs/fusero-selfsigned.key; location ^~ /api/ { - proxy_pass http://fusero-app-backend:14000/; + proxy_pass http://fusero-backend-service:14000/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -15,11 +16,8 @@ server { } location / { - proxy_pass http://host.docker.internal:8080; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; } } \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 107cdca..db7bf6e 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,6 +6,13 @@ export default defineConfig({ base: '/', server: { port: 8080, + proxy: { + '/api': { + target: 'http://localhost:14000', + changeOrigin: true, + secure: false, + } + } }, build: { rollupOptions: { diff --git a/mikro-orm.config.ts b/mikro-orm.config.ts index aee09ce..34cee35 100644 --- a/mikro-orm.config.ts +++ b/mikro-orm.config.ts @@ -1,10 +1,11 @@ -import dotenv from 'dotenv'; - -// Force reload `.env` even if it was previously loaded -dotenv.config({ override: true }); import { Options } from '@mikro-orm/core'; import { PostgreSqlDriver } from '@mikro-orm/postgresql'; import { Migrator } from '@mikro-orm/migrations'; +import dotenv from 'dotenv'; + +if (process.env.KUBERNETES_SERVICE_HOST === undefined) { + dotenv.config({ override: true }); +} const isProduction = process.env.NODE_ENV === 'production'; diff --git a/package.json b/package.json index f913dcd..1ca7dda 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,14 @@ "start": "npm run build:ts && fastify start -l info -r tsconfig-paths/register dist/src/app.js", "prebuild": "npm run lint", "build:ts": "rimraf dist && tsc", + "build:frontend:dev": "docker build -t fusero-frontend-dev:local -f frontend/Dockerfile.dev ./frontend", + "k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yml", + "k8s:dev:delete": "kubectl delete deployment fusero-frontend-dev 2>/dev/null || true", + "k8s:dev:run": "kubectl port-forward svc/fusero-frontend-service 3000:80", + "k8s:dev:describe": "kubectl describe pod -l app=fusero-frontend", + "k8s:dev:svc": "kubectl describe svc fusero-frontend-service", + "k8s:dev:deployment": "kubectl describe deployment fusero-frontend-dev", + "k8s:get": "kubectl get pods,svc,deployment", "watch:ts": "tsc -w", "dev": "npm run build:ts && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch:ts\" \"npm:dev:start\"", "dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P -r tsconfig-paths/register dist/src/app.js", @@ -27,7 +35,8 @@ "lint:fix": "eslint src/**/*.{js,jsx,ts,tsx} --fix", "migration:create": "npx mikro-orm migration:create", "seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts", - "test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts" + "test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts", + "k8s:exec": "kubectl exec -it $POD_NAME -- /bin/sh" }, "keywords": [], "author": "", diff --git a/src/app.ts b/src/app.ts index 8323ff4..cb9569f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -80,7 +80,14 @@ const app: FastifyPluginAsync = async (app, opts): Promise => { // Register CORS, JWT, and Cookies app.register(fastifyCors, { - origin: ['http://localhost:3000', 'http://localhost:3001', 'http://localhost:8080', 'http://localhost:8081'], + origin: process.env.CORS_ORIGIN + ? process.env.CORS_ORIGIN.split(',').map(origin => origin.trim()) + : [ + 'http://localhost:3000', + 'http://localhost:3001', + 'http://localhost:8080', + 'http://localhost:8081' + ], methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'Accept'], credentials: true, From ad7017820245750cd3a71dff5f797f5289c690fa Mon Sep 17 00:00:00 2001 From: liquidrinu Date: Tue, 20 May 2025 13:00:00 +0200 Subject: [PATCH 2/8] update readme --- README.md | 58 ++++++++++++++++++- chart/values.prod.yml | 22 +++++++ docs/DEPLOY.md | 130 ++++++++++++++++++++++++++++++++++++++++++ docs/GUIDE-TO-K8S.md | 128 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 chart/values.prod.yml create mode 100644 docs/DEPLOY.md create mode 100644 docs/GUIDE-TO-K8S.md diff --git a/README.md b/README.md index 4fb866c..5d1f15f 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,63 @@ fusero-app-boilerplate/ - Docker and Docker Compose - Git -## Development Setup +## 🗃️ Create Docker Volume for Postgres -### Important Note: Database Must Run in Docker +Before starting the database, create a Docker volume to persist Postgres data: +```sh +docker volume create fusero-db-data +``` + +## 🛠️ Running Migrations and Seeding the Database in Kubernetes + +To run migrations and seed the database in your Kubernetes cluster, a job is included in the Helm chart. The job runs the following command: + +```sh +npx mikro-orm migration:up && npm run seed +``` + +This job is triggered automatically on deployment. If you need to rerun it manually, you can delete and recreate the job using kubectl. + +## 💻 Development Setup + +Backend setup: +- Copy the example environment file: + ```sh + cp backend/.env.example backend/.env + ``` +- Install dependencies: + ```sh + npm install + ``` +- Run migrations and seed: + ```sh + npm run migrate + npm run seed + ``` +- Start the backend in development mode: + ```sh + npm run dev & + ``` + +Frontend setup: +- Copy the example environment file: + ```sh + cp frontend/.env.example frontend/.env + ``` +- Install dependencies: + ```sh + npm install + ``` +- Start the frontend in development mode: + ```sh + npm run dev & + ``` + +App is running: +- Frontend → http://localhost:3000 +- Backend → http://localhost:14000 + +## Important Note: Database Must Run in Docker The PostgreSQL database must always run in Docker, regardless of your development setup choice. This ensures consistent database behavior across all environments. To start the database: diff --git a/chart/values.prod.yml b/chart/values.prod.yml new file mode 100644 index 0000000..321ca39 --- /dev/null +++ b/chart/values.prod.yml @@ -0,0 +1,22 @@ +backend: + image: fusero-backend:latest + env: + POSTGRES_HOST: postgres-service + POSTGRES_PORT: "5432" + POSTGRES_NAME: fusero-db + POSTGRES_USER: prod_admin + POSTGRES_PASSWORD: REPLACE_ME + DEFAULT_ADMIN_USERNAME: admin + DEFAULT_ADMIN_EMAIL: admin@fusero.nl + DEFAULT_ADMIN_PASSWORD: STRONG_REPLACE_ME + ENCRYPTION_KEY: PROD_REPLACE_ME_KEY + JWT_SECRET: PROD_REPLACE_ME_JWT + CHATGPT_API_KEY: PROD_REPLACE_ME_CHATGPT + CANVAS_API_KEY: PROD_REPLACE_ME_CANVAS + +frontend: + image: fusero-frontend:latest + +postgres: + image: postgres:15 + storage: 5Gi diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md new file mode 100644 index 0000000..9040f7e --- /dev/null +++ b/docs/DEPLOY.md @@ -0,0 +1,130 @@ +# 📦 Fusero VPS Deployment Guide + +This guide walks you through deploying the Fusero full-stack app to a plain Ubuntu VPS using Kubernetes (k3s), Helm, and automatic HTTPS via cert-manager. + +--- + +## 📋 Prerequisites + +- ✅ Ubuntu 22.04 VPS with root or sudo access +- ✅ Domain names pointed to your VPS IP: + - api.fusero.nl → for the backend + - app.fusero.nl → for the frontend +- ✅ Git access to your repo + +--- + +## ☸️ 1. Install Kubernetes (k3s) + +curl -sfL https://get.k3s.io | sh - + +Set kubeconfig so kubectl works: +echo 'export KUBECONFIG=/etc/rancher/k3s/k3s.yaml' >> ~/.bashrc +source ~/.bashrc + +Verify: +kubectl get nodes + +--- + +## 📦 2. Install Helm + +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + +Verify: +helm version + +--- + +## 📁 3. Clone the Project + +git clone https://your.gitea.repo/fusero-app-boilerplate.git +cd fusero-app-boilerplate + +--- + +## 🔐 4. Set Up HTTPS (cert-manager) + +Install cert-manager: +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.3/cert-manager.yaml + +Check pods: +kubectl get pods -n cert-manager + +Create file cluster-issuer.yaml with this content: + +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: your@email.com + privateKeySecretRef: + name: letsencrypt-prod + solvers: + - http01: + ingress: + class: nginx + +Apply it: +kubectl apply -f cluster-issuer.yaml + +--- + +## 🌍 5. Update DNS + +Ensure both api.fusero.nl and app.fusero.nl point to your VPS IP address. + +Check propagation: +ping api.fusero.nl + +--- + +## 🚀 6. Deploy with Helm + +Ensure you're in the repo root and the chart directory exists. + +helm upgrade --install fusero ./chart -f chart/values-prod.yaml + +This deploys frontend, backend, Postgres, ingress, and HTTPS. + +--- + +## 📜 7. Verify Access + +Frontend: https://app.fusero.nl +Backend API: https://api.fusero.nl + +--- + +## 🔁 8. (Optional) Rerun DB Migrations + +kubectl delete job fusero-backend-db-init +helm upgrade fusero ./chart -f chart/values-prod.yaml + +--- + +## 🧪 9. Useful Commands + +View backend logs: +kubectl logs deployment/fusero-backend + +View frontend logs: +kubectl logs deployment/fusero-frontend + +View pods and services: +kubectl get pods,svc,deployments + +--- + +## ✅ You’re Done! + +You now have a production deployment of Fusero on a raw VPS with: +- Kubernetes (via k3s) +- TLS via Let's Encrypt +- Helm-managed services +- DNS routing for subdomains + +For CI/CD automation via Gitea, see `.gitea-ci.yml` in the repo root. diff --git a/docs/GUIDE-TO-K8S.md b/docs/GUIDE-TO-K8S.md new file mode 100644 index 0000000..e55b6a4 --- /dev/null +++ b/docs/GUIDE-TO-K8S.md @@ -0,0 +1,128 @@ +# 📘 How to Install Kubernetes on Ubuntu 24.04 (Step-by-Step Guide) + +This guide walks you through installing a multi-node Kubernetes cluster on Ubuntu 24.04 using `kubeadm`. + +--- + +## 🧰 Prerequisites + +* Ubuntu 24.04 instances with SSH enabled +* sudo user access +* At least 2GB RAM, 2 CPUs, and 20GB storage per node +* Internet access + +### Sample Setup: + +* **Master Node:** k8s-master-noble (192.168.1.120) +* **Worker 1:** k8s-worker01-noble (192.168.1.121) +* **Worker 2:** k8s-worker02-noble (192.168.1.122) + +--- + +## 1️⃣ Set Hostnames & Update Hosts File + +Run on each node: + +sudo hostnamectl set-hostname "k8s-master-noble" # Master +sudo hostnamectl set-hostname "k8s-worker01-noble" # Worker 1 +sudo hostnamectl set-hostname "k8s-worker02-noble" # Worker 2 + +Edit `/etc/hosts` on all nodes: + +192.168.1.120 k8s-master-noble +192.168.1.121 k8s-worker01-noble +192.168.1.122 k8s-worker02-noble + +--- + +## 2️⃣ Disable Swap & Load Kernel Modules + +sudo swapoff -a +sudo sed -i '/ swap / s/^/#/' /etc/fstab + +sudo modprobe overlay +sudo modprobe br\_netfilter + +echo -e "overlay\nbr\_netfilter" | sudo tee /etc/modules-load.d/k8s.conf + +echo -e "net.bridge.bridge-nf-call-ip6tables = 1\nnet.bridge.bridge-nf-call-iptables = 1\nnet.ipv4.ip\_forward = 1" | sudo tee /etc/sysctl.d/kubernetes.conf + +sudo sysctl --system + +--- + +## 3️⃣ Install and Configure containerd + +sudo apt install -y curl gnupg2 software-properties-common apt-transport-https ca-certificates + +curl -fsSL [https://download.docker.com/linux/ubuntu/gpg](https://download.docker.com/linux/ubuntu/gpg) | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/containerd.gpg + +sudo add-apt-repository "deb \[arch=amd64] [https://download.docker.com/linux/ubuntu](https://download.docker.com/linux/ubuntu) \$(lsb\_release -cs) stable" + +sudo apt update && sudo apt install containerd.io -y + +containerd config default | sudo tee /etc/containerd/config.toml > /dev/null +sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml + +sudo systemctl restart containerd + +--- + +## 4️⃣ Add Kubernetes Repository + +curl -fsSL [https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key](https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key) | sudo gpg --dearmor -o /etc/apt/keyrings/k8s.gpg + +echo "deb \[signed-by=/etc/apt/keyrings/k8s.gpg] [https://pkgs.k8s.io/core:/stable:/v1.30/deb/](https://pkgs.k8s.io/core:/stable:/v1.30/deb/) /" | sudo tee /etc/apt/sources.list.d/k8s.list + +--- + +## 5️⃣ Install kubelet, kubeadm, kubectl + +sudo apt update +sudo apt install kubelet kubeadm kubectl -y + +--- + +## 6️⃣ Initialize Kubernetes Cluster (Master Node Only) + +sudo kubeadm init --control-plane-endpoint=k8s-master-noble + +Then set up kubectl: + +mkdir -p \$HOME/.kube +sudo cp /etc/kubernetes/admin.conf \$HOME/.kube/config +sudo chown \$(id -u):\$(id -g) \$HOME/.kube/config + +--- + +## 7️⃣ Join Worker Nodes + +Use the join command from the `kubeadm init` output on each worker node: + +sudo kubeadm join k8s-master-noble:6443 --token --discovery-token-ca-cert-hash sha256: + +--- + +## 8️⃣ Install Calico Network Add-on (Master Only) + +kubectl apply -f [https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/calico.yaml](https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/calico.yaml) + +Check readiness: + +kubectl get pods -n kube-system +kubectl get nodes + +--- + +## 9️⃣ Test the Cluster + +kubectl create ns demo-app +kubectl create deployment nginx-app --image nginx --replicas 2 --namespace demo-app +kubectl expose deployment nginx-app -n demo-app --type NodePort --port 80 +kubectl get svc -n demo-app + +Then access it: + +curl http\://: + +✅ You now have a fully functional Kubernetes cluster on Ubuntu 24.04! From 7f463ee287898746a0a877ccb91c37a632443674 Mon Sep 17 00:00:00 2001 From: liquidrinu Date: Wed, 21 May 2025 02:14:45 +0200 Subject: [PATCH 3/8] fixed readme upstream --- README.md | 78 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 62823d9..3598aa6 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,26 @@ A full-stack application boilerplate with a React frontend and Node.js backend - [📚 Table of Contents](#-table-of-contents) - [📁 Project Structure](#-project-structure) - [⚙️ Prerequisites](#️-prerequisites) - - [Development Setup](#development-setup) - - [Important Note: Database Must Run in Docker](#important-note-database-must-run-in-docker) - - [Running Services Separately (Recommended for Development)](#running-services-separately-recommended-for-development) - - [Terminal 1: Backend Service](#terminal-1-backend-service) + - [💻 Development Setup](#-development-setup) + - [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals) + - [🛠️ Environment Setup](#️-environment-setup) +- [For Kubernetes, these are set in chart/values.yaml:](#for-kubernetes-these-are-set-in-chartvaluesyaml) +- [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db) +- [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service) +- [POSTGRES\_PORT=19095](#postgres_port19095) +- [POSTGRES\_USER=root](#postgres_userroot) +- [POSTGRES\_PASSWORD=root123](#postgres_passwordroot123) + - [🚀 Production Deployment](#-production-deployment) + - [🌐 Frontend Routing in Production](#-frontend-routing-in-production) + - [🔐 HTTPS with Self-Signed Certificates](#-https-with-self-signed-certificates) + - [🧠 Development Best Practices](#-development-best-practices) + - [📘 API Documentation](#-api-documentation) + - [🧩 ChatGPT-Powered Endpoint Creation](#-chatgpt-powered-endpoint-creation) + - [🧪 Troubleshooting](#-troubleshooting) + - [🤝 Contributing](#-contributing) + - [📄 License](#-license) + - [Kubernetes Troubleshooting \& Redeployment Commands](#kubernetes-troubleshooting--redeployment-commands) + - [1. Rebuild the backend Docker image (after code/config changes)](#1-rebuild-the-backend-docker-image-after-codeconfig-changes) - [2. (If using a remote registry) Push the image](#2-if-using-a-remote-registry-push-the-image) - [3. Upgrade the Helm release with the latest values](#3-upgrade-the-helm-release-with-the-latest-values) - [4. Restart the backend deployment to pick up new images and env vars](#4-restart-the-backend-deployment-to-pick-up-new-images-and-env-vars) @@ -53,36 +69,48 @@ fusero-app-boilerplate/ ## ⚙️ Prerequisites -- Node.js (v20 or higher) -- npm (v9 or higher) -- Docker and Docker Compose +- Node.js (v20 or higher) +- npm (v9 or higher) +- Docker & Docker Compose - Git -## Development Setup +--- -### Important Note: Database Must Run in Docker -The PostgreSQL database must always run in Docker, regardless of your development setup choice. This ensures consistent database behavior across all environments. +## 💻 Development Setup -To start the database: -```bash -docker-compose up db -``` +🗃️ PostgreSQL must run in Docker for consistent behavior. -### Running Services Separately (Recommended for Development) +Create volume and start the database: +docker volume create fusero-db-data +docker-compose up -d db -For better debugging experience, run the frontend and backend in separate terminal windows, while keeping the database in Docker: +Backend setup: +cd backend +cp .env.example .env +npm install +npm run migrate +npm run seed +npm run dev & +cd .. -1. **First, ensure the database is running in Docker** - ```bash - docker-compose up db - ``` +Frontend setup: +cd frontend +cp .env.example .env +npm install +npm run dev & +cd .. -2. **Then, in separate terminal windows:** +App is running: +Frontend → http://localhost:3000 +Backend → http://localhost:14000 -#### Terminal 1: Backend Service -```bash -cd backend -npm install +--- + +### Alternate: Running Services in Separate Terminals + +Terminal 1 (backend): +cd backend +npm install npm run dev Terminal 2 (frontend): From 0167ffdd18e37e45f6249146eb344b8790ac4d70 Mon Sep 17 00:00:00 2001 From: liquidrinu Date: Wed, 21 May 2025 09:27:45 +0200 Subject: [PATCH 4/8] Save current state before cleaning history --- .gitea-ci.yml | 89 ++++- .gitignore | 18 + README.md | 315 +++++++++++++++++- chart/{Chart.yaml => Chart.yml} | 0 chart/secrets.prod.template.yml | 26 ++ ...deployment.yaml => backend-deployment.yml} | 0 ...ion.job.yaml => backend-migration.job.yml} | 0 chart/templates/backend-secrets.yaml | 2 - chart/templates/backend-secrets.yml | 16 + ...ckend-service.yaml => backend-service.yml} | 1 + ...eployment.yaml => frontend-deployment.yml} | 0 ...tend-service.yaml => frontend-service.yml} | 1 + ...eployment.yaml => postgres-deployment.yml} | 0 .../{postgres-pvc.yaml => postgres-pvc.yml} | 0 ...gres-service.yaml => postgres-service.yml} | 1 + chart/values.prod.public.yml | 53 +++ chart/values.prod.yml | 58 +++- package.json | 30 +- src/app.ts | 5 + 19 files changed, 565 insertions(+), 50 deletions(-) rename chart/{Chart.yaml => Chart.yml} (100%) create mode 100644 chart/secrets.prod.template.yml rename chart/templates/{backend-deployment.yaml => backend-deployment.yml} (100%) rename chart/templates/{backend-migration.job.yaml => backend-migration.job.yml} (100%) delete mode 100644 chart/templates/backend-secrets.yaml create mode 100644 chart/templates/backend-secrets.yml rename chart/templates/{backend-service.yaml => backend-service.yml} (83%) rename chart/templates/{frontend-deployment.yaml => frontend-deployment.yml} (100%) rename chart/templates/{frontend-service.yaml => frontend-service.yml} (82%) rename chart/templates/{postgres-deployment.yaml => postgres-deployment.yml} (100%) rename chart/templates/{postgres-pvc.yaml => postgres-pvc.yml} (100%) rename chart/templates/{postgres-service.yaml => postgres-service.yml} (76%) create mode 100644 chart/values.prod.public.yml diff --git a/.gitea-ci.yml b/.gitea-ci.yml index 1821deb..45e7c15 100644 --- a/.gitea-ci.yml +++ b/.gitea-ci.yml @@ -1,12 +1,83 @@ -image: lachlanevenson/k8s-helm:latest +version: 1.0 -stages: - - deploy +workflow: + name: Deploy to Production + on: + push: + branches: + - main -variables: - KUBECONFIG: /root/.kube/config # Adjust if you're mounting a kubeconfig differently +jobs: + build-and-deploy: + name: Build and Deploy + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 -deploy: - stage: deploy - script: - - helm upgrade --install fusero ./chart -f ./chart/values-prod.yaml + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Registry + uses: docker/login-action@v2 + with: + registry: registry.liquidrinu.com + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Build and Push Backend + uses: docker/build-push-action@v4 + with: + context: ./backend + push: true + tags: registry.liquidrinu.com/fusero-backend:latest + cache-from: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache + cache-to: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache,mode=max + + - name: Build and Push Frontend + uses: docker/build-push-action@v4 + with: + context: ./frontend + push: true + tags: registry.liquidrinu.com/fusero-frontend:latest + cache-from: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache + cache-to: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache,mode=max + + - name: Install kubectl + uses: azure/setup-kubectl@v3 + with: + version: "latest" + + - name: Setup kubeconfig + run: | + mkdir -p $HOME/.kube + echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config + chmod 600 $HOME/.kube/config + + - name: Create secrets file + run: | + cat > ./chart/secrets.prod.yml << EOF + backend: + env: + POSTGRES_PASSWORD: "${{ secrets.POSTGRES_PASSWORD }}" + DEFAULT_ADMIN_PASSWORD: "${{ secrets.DEFAULT_ADMIN_PASSWORD }}" + ENCRYPTION_KEY: "${{ secrets.ENCRYPTION_KEY }}" + JWT_SECRET: "${{ secrets.JWT_SECRET }}" + CHATGPT_API_KEY: "${{ secrets.CHATGPT_API_KEY }}" + CANVAS_API_KEY: "${{ secrets.CANVAS_API_KEY }}" + EOF + + - name: Deploy to Kubernetes + run: | + helm upgrade --install fusero ./chart \ + --namespace fusero-prod \ + --create-namespace \ + --values ./chart/values.prod.yml \ + --values ./chart/secrets.prod.yml \ + --set backend.image.repository=registry.liquidrinu.com/fusero-backend \ + --set frontend.image.repository=registry.liquidrinu.com/fusero-frontend + + - name: Verify Deployment + run: | + kubectl rollout status deployment/fusero-backend -n fusero-prod + kubectl rollout status deployment/fusero-frontend -n fusero-prod diff --git a/.gitignore b/.gitignore index 9e87c4d..be7a341 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,21 @@ secrets.yml values.dev.* values.prod.* +# Secrets +chart/secrets.prod.yml +chart/secrets.*.yml +*.env +.env.* + +# Development values with secrets +chart/values.dev.yml + +# Production secrets +chart/secrets.prod.yml +chart/secrets.*.yml + +# Keep templates and public configs +!chart/secrets.prod.template.yml +!chart/values.prod.template.yml +!chart/values.prod.public.yml + diff --git a/README.md b/README.md index 3598aa6..d378443 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,24 @@ A full-stack application boilerplate with a React frontend and Node.js backend - [📁 Project Structure](#-project-structure) - [⚙️ Prerequisites](#️-prerequisites) - [💻 Development Setup](#-development-setup) +- [To create a new migration:](#to-create-a-new-migration) +- [npm run migration:create](#npm-run-migrationcreate) +- [To apply migrations:](#to-apply-migrations) +- [To seed the database:](#to-seed-the-database) - [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals) - [🛠️ Environment Setup](#️-environment-setup) -- [For Kubernetes, these are set in chart/values.yaml:](#for-kubernetes-these-are-set-in-chartvaluesyaml) +- [For Kubernetes, these are set in chart/values.yml:](#for-kubernetes-these-are-set-in-chartvaluesyml) - [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db) - [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service) - [POSTGRES\_PORT=19095](#postgres_port19095) - [POSTGRES\_USER=root](#postgres_userroot) - [POSTGRES\_PASSWORD=root123](#postgres_passwordroot123) - - [🚀 Production Deployment](#-production-deployment) + - [🐳 Docker Development](#-docker-development) +- [To create a new migration:](#to-create-a-new-migration-1) +- [npm run migration:create](#npm-run-migrationcreate-1) +- [To apply migrations:](#to-apply-migrations-1) +- [To seed the database:](#to-seed-the-database-1) + - [🚀 Kubernetes Deployment](#-kubernetes-deployment) - [🌐 Frontend Routing in Production](#-frontend-routing-in-production) - [🔐 HTTPS with Self-Signed Certificates](#-https-with-self-signed-certificates) - [🧠 Development Best Practices](#-development-best-practices) @@ -53,17 +62,66 @@ A full-stack application boilerplate with a React frontend and Node.js backend - [Debugging Frontend Pod Crashes: NGINX SSL Certificate Errors](#debugging-frontend-pod-crashes-nginx-ssl-certificate-errors) - [How to fix for Kubernetes (Recommended)](#how-to-fix-for-kubernetes-recommended) - [Connecting to the Database from Your Host (DBeaver, etc.)](#connecting-to-the-database-from-your-host-dbeaver-etc) + - [🎯 Kubernetes Namespace Management](#-kubernetes-namespace-management) + - [Development Namespace Setup](#development-namespace-setup) + - [Production Namespace Setup](#production-namespace-setup) + - [Namespace Management Commands](#namespace-management-commands) + - [Recommended Kubernetes GUI Tools](#recommended-kubernetes-gui-tools) + - [🆕 Namespaced Development Environment with Helm](#-namespaced-development-environment-with-helm) + - [What was changed:](#what-was-changed) + - [How to use:](#how-to-use) + - [Why use namespaces?](#why-use-namespaces) + - [🔒 Production Security \& Best Practices](#-production-security--best-practices) + - [Environment Variables \& Secrets](#environment-variables--secrets) + - [HTTPS \& Certificates](#https--certificates) + - [CORS \& Security Headers](#cors--security-headers) + - [Logging \& Monitoring](#logging--monitoring) + - [Database Backup](#database-backup) + - [CI/CD \& Automated Testing](#cicd--automated-testing) + - [Troubleshooting Production](#troubleshooting-production) --- ## 📁 Project Structure -fusero-app-boilerplate/ -├── frontend/ # React frontend application -├── backend/ # Node.js backend application -├── docker-compose.yml # Production Docker configuration -├── docker-compose.dev.yml # Development Docker configuration -└── chart/ # Helm chart for Kubernetes deployment +fusero-app-boilerplate/ +├── chart/ # Helm chart for Kubernetes +│ ├── Chart.yaml +│ ├── values.dev.yaml +│ ├── values.prod.yml +│ └── templates/ +├── config/ +├── coverage/ +├── dist/ +├── docs/ +├── frontend/ # React frontend app +│ ├── public/ +│ └── src/ +├── mikro-orm.config.ts +├── nginx/ +├── node_modules/ +├── package.json +├── package-lock.json +├── docker-compose.yml +├── docker-compose.dev.yml +├── .gitignore +├── .gitea-ci.yml +├── .prettierrc.json +├── .eslintrc.json +├── architecture.excalidraw +├── src/ # Node.js backend source +│ ├── apps/ +│ ├── constants/ +│ ├── database/ +│ ├── middleware/ +│ ├── plugins/ +│ ├── shared/ +│ ├── tests/ +│ ├── types/ +│ └── ... +├── test/ +├── utils/ +└── README.md --- @@ -81,14 +139,18 @@ fusero-app-boilerplate/ 🗃️ PostgreSQL must run in Docker for consistent behavior. Create volume and start the database: -docker volume create fusero-db-data +docker volume create fusero-dev-db-data docker-compose up -d db Backend setup: cd backend cp .env.example .env npm install +# To create a new migration: +# npm run migration:create +# To apply migrations: npm run migrate +# To seed the database: npm run seed npm run dev & cd .. @@ -130,7 +192,7 @@ POSTGRES_USER=root POSTGRES_PASSWORD=root123 JWT_SECRET=your_jwt_secret_key_here -# For Kubernetes, these are set in chart/values.yaml: +# For Kubernetes, these are set in chart/values.yml: # POSTGRES_NAME=fusero-boilerplate-db # POSTGRES_HOSTNAME=postgres-service # POSTGRES_PORT=19095 @@ -142,13 +204,47 @@ VITE_API_BASE_URL=http://localhost:14000/api/v1 --- -## 🚀 Production Deployment +## 🐳 Docker Development + +🗃️ PostgreSQL must run in Docker for consistent behavior. + +Create volume and start the database: +docker volume create fusero-dev-db-data +docker-compose up -d db + +Backend setup: +cd backend +cp .env.example .env +npm install +# To create a new migration: +# npm run migration:create +# To apply migrations: +npm run migrate +# To seed the database: +npm run seed +npm run dev & +cd .. + +Frontend setup: +cd frontend +cp .env.example .env +npm install +npm run dev & +cd .. + +App is running: +Frontend → http://localhost:3000 +Backend → http://localhost:14000 + +--- + +## 🚀 Kubernetes Deployment 1. Build and run with Docker: docker-compose up --build 2. Apply migrations and seed inside backend container: -docker exec -it fusero-app-backend npx mikro-orm migration:up +docker exec -it fusero-app-backend npm run migrate docker exec -it fusero-app-backend npm run seed 3. Ensure all required environment variables are configured. @@ -288,7 +384,7 @@ docker push /fusero-backend-dev:local ### 3. Upgrade the Helm release with the latest values ```bash -helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml ``` ### 4. Restart the backend deployment to pick up new images and env vars @@ -334,7 +430,7 @@ docker push /fusero-frontend-dev:local ### 3. Upgrade the Helm release ```bash -helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml ``` ### 4. Restart the frontend deployment @@ -386,7 +482,7 @@ Then rebuild and redeploy the frontend: ```bash docker build -t fusero-frontend-dev:local ./frontend # (push if needed) -helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml kubectl rollout restart deployment/fusero-frontend -n fusero ``` @@ -450,7 +546,7 @@ This means NGINX is trying to load an SSL certificate that does not exist in the 3. (If using a remote registry) Push the image. 4. Redeploy with Helm: ```bash - helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml + helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml ``` 5. Check pod status: ```bash @@ -484,3 +580,190 @@ To connect to the Postgres database running in Kubernetes from your local machin 4. **Test the connection.** --- + +## 🎯 Kubernetes Namespace Management + +### Development Namespace Setup +```bash +# Create development namespace +kubectl create namespace fusero-dev + +# Set current context to development namespace +kubectl config set-context --current --namespace=fusero-dev + +# Deploy to development namespace +helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml +``` + +### Production Namespace Setup +```bash +# Create production namespace +kubectl create namespace fusero-prod + +# Set current context to production namespace +kubectl config set-context --current --namespace=fusero-prod + +# Deploy to production namespace +helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yml +``` + +### Namespace Management Commands +```bash +# List all namespaces +kubectl get namespaces + +# Switch between namespaces +kubectl config set-context --current --namespace= + +# View resources in current namespace +kubectl get all + +# Delete namespace (be careful!) +kubectl delete namespace +``` + +### Recommended Kubernetes GUI Tools +1. **Lens** - The most popular Kubernetes IDE + - Download: https://k8slens.dev/ + - Features: + - Real-time cluster monitoring + - Multi-cluster management + - Namespace isolation + - Resource visualization + - Log streaming + - Terminal access + +2. **K9s** - Terminal-based UI + - Install: `brew install k9s` (Mac) or `scoop install k9s` (Windows) + - Features: + - Fast navigation + - Resource management + - Log viewing + - Port forwarding + +3. **Octant** - Web-based UI + - Install: https://octant.dev/ + - Features: + - Resource visualization + - Configuration management + - Log viewing + - Port forwarding + +--- + +## 🆕 Namespaced Development Environment with Helm + +You have configured your Kubernetes development environment to use a dedicated namespace (`fusero-dev`) for all core services (backend, frontend, and database). This ensures resource isolation and easier management between development and production. + +### What was changed: +- All Kubernetes service templates (`backend-service.yml`, `frontend-service.yml`, `postgres-service.yml`) now use a namespace variable. +- The development values file (`chart/values.dev.yml`) sets `global.namespace: fusero-dev` and updates all internal service hostnames to use this namespace. +- Helm deployments now target the `fusero-dev` namespace for all dev resources. + +### How to use: +1. **Create the development namespace (if not already created):** + ```bash + kubectl create namespace fusero-dev + ``` + +2. **Deploy all services to the namespace:** + ```bash + helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml + ``` + +3. **Check running pods and services:** + ```bash + kubectl get all -n fusero-dev + ``` + +4. **If you update environment variables or service hostnames, repeat the Helm upgrade command.** + +5. **If you see errors about immutable fields (e.g., for Jobs), delete the old Job before redeploying:** + ```bash + kubectl delete job -n fusero-dev + helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml + ``` + +### Why use namespaces? +- Keeps dev and prod resources isolated +- Makes it easy to clean up or redeploy all dev resources +- Prevents accidental cross-environment access + +--- + +## 🔒 Production Security & Best Practices + +### Environment Variables & Secrets + +The application uses a secure secrets management approach: + +1. **Development Environment**: + - Use `.env` files locally (gitignored) + - Copy from `.env.example` as template + +2. **Production Environment**: + - Secrets are managed through Gitea CI/CD secrets + - Template file: `chart/secrets.prod.template.yml` + - Actual secrets are generated during deployment + - Never commit actual secrets to the repository + +3. **Required Secrets**: + - Database credentials + - Admin user credentials + - Security keys (encryption, JWT) + - API keys (ChatGPT, Canvas) + +4. **Secrets in CI/CD**: + - Secrets are stored in Gitea CI/CD settings + - Automatically injected during deployment + - Used to generate `secrets.prod.yml` at runtime + +5. **Security Best Practices**: + - All secrets files are gitignored + - Template files contain placeholder values + - Production secrets are never stored in the repository + - Regular rotation of secrets recommended + +### HTTPS & Certificates +- In production, use trusted certificates (e.g., Let's Encrypt). +- Configure NGINX to enforce HTTPS. + +### CORS & Security Headers +- Lock down CORS settings in production. +- Use security headers (e.g., HSTS, CSP). + +### Logging & Monitoring +- Configure logging for production (e.g., ELK stack, Datadog). +- Set up basic monitoring (e.g., Prometheus, Grafana). + +### Database Backup +- Regularly backup your production database. +- Example: Use `pg_dump` or a managed backup service. + +### CI/CD & Automated Testing +- Run tests before deploying to production. +- Example CI/CD workflow: + ```yaml + # .github/workflows/deploy.yml + name: Deploy to Production + on: + push: + branch: main + jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run Tests + run: npm test + - name: Deploy to Kubernetes + run: helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yml + ``` + +### Troubleshooting Production +- Common issues: + - Database connection errors: Check secrets and network policies. + - Pod crashes: Check logs with `kubectl logs -n fusero-prod`. +- Rollback: Use `helm rollback fusero -n fusero-prod`. + +--- diff --git a/chart/Chart.yaml b/chart/Chart.yml similarity index 100% rename from chart/Chart.yaml rename to chart/Chart.yml diff --git a/chart/secrets.prod.template.yml b/chart/secrets.prod.template.yml new file mode 100644 index 0000000..1ecd2c3 --- /dev/null +++ b/chart/secrets.prod.template.yml @@ -0,0 +1,26 @@ +backend: + env: + # Database + POSTGRES_NAME: "fusero-boilerplate-db" + POSTGRES_HOSTNAME: "postgres-service.fusero-prod.svc.cluster.local" + POSTGRES_PORT: "5432" + POSTGRES_USER: "root" + POSTGRES_PASSWORD: "REPLACE_ME" + + # Admin User + DEFAULT_ADMIN_USERNAME: "admin" + DEFAULT_ADMIN_EMAIL: "admin@fusero.nl" + DEFAULT_ADMIN_PASSWORD: "REPLACE_ME" + + # Security + ENCRYPTION_KEY: "REPLACE_ME" + JWT_SECRET: "REPLACE_ME" + + # API Keys + CHATGPT_API_KEY: "REPLACE_ME" + CANVAS_API_KEY: "REPLACE_ME" + CANVAS_API_URL: "https://talnet.instructure.com/api/v1" + + # Server + FASTIFY_PORT: "14000" + NODE_ENV: "production" diff --git a/chart/templates/backend-deployment.yaml b/chart/templates/backend-deployment.yml similarity index 100% rename from chart/templates/backend-deployment.yaml rename to chart/templates/backend-deployment.yml diff --git a/chart/templates/backend-migration.job.yaml b/chart/templates/backend-migration.job.yml similarity index 100% rename from chart/templates/backend-migration.job.yaml rename to chart/templates/backend-migration.job.yml diff --git a/chart/templates/backend-secrets.yaml b/chart/templates/backend-secrets.yaml deleted file mode 100644 index 3db53f4..0000000 --- a/chart/templates/backend-secrets.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# You can skip this if you manage secrets separately -# Included for completeness (values-dev.yaml handles them) diff --git a/chart/templates/backend-secrets.yml b/chart/templates/backend-secrets.yml new file mode 100644 index 0000000..75556fb --- /dev/null +++ b/chart/templates/backend-secrets.yml @@ -0,0 +1,16 @@ +# You can skip this if you manage secrets separately +# Included for completeness (values-dev.yaml handles them) + +apiVersion: v1 +kind: Secret +metadata: + name: fusero-backend-secrets + namespace: {{ .Values.global.namespace }} +type: Opaque +data: + POSTGRES_PASSWORD: {{ .Values.backend.env.POSTGRES_PASSWORD | b64enc }} + DEFAULT_ADMIN_PASSWORD: {{ .Values.backend.env.DEFAULT_ADMIN_PASSWORD | b64enc }} + ENCRYPTION_KEY: {{ .Values.backend.env.ENCRYPTION_KEY | b64enc }} + JWT_SECRET: {{ .Values.backend.env.JWT_SECRET | b64enc }} + CHATGPT_API_KEY: {{ .Values.backend.env.CHATGPT_API_KEY | b64enc }} + CANVAS_API_KEY: {{ .Values.backend.env.CANVAS_API_KEY | b64enc }} diff --git a/chart/templates/backend-service.yaml b/chart/templates/backend-service.yml similarity index 83% rename from chart/templates/backend-service.yaml rename to chart/templates/backend-service.yml index 98c4aad..5616dae 100644 --- a/chart/templates/backend-service.yaml +++ b/chart/templates/backend-service.yml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Service metadata: name: fusero-backend-service + namespace: {{ .Values.global.namespace }} spec: selector: app: fusero-backend diff --git a/chart/templates/frontend-deployment.yaml b/chart/templates/frontend-deployment.yml similarity index 100% rename from chart/templates/frontend-deployment.yaml rename to chart/templates/frontend-deployment.yml diff --git a/chart/templates/frontend-service.yaml b/chart/templates/frontend-service.yml similarity index 82% rename from chart/templates/frontend-service.yaml rename to chart/templates/frontend-service.yml index 660e3bf..3f56cf0 100644 --- a/chart/templates/frontend-service.yaml +++ b/chart/templates/frontend-service.yml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Service metadata: name: fusero-frontend-service + namespace: {{ .Values.global.namespace }} spec: selector: app: fusero-frontend diff --git a/chart/templates/postgres-deployment.yaml b/chart/templates/postgres-deployment.yml similarity index 100% rename from chart/templates/postgres-deployment.yaml rename to chart/templates/postgres-deployment.yml diff --git a/chart/templates/postgres-pvc.yaml b/chart/templates/postgres-pvc.yml similarity index 100% rename from chart/templates/postgres-pvc.yaml rename to chart/templates/postgres-pvc.yml diff --git a/chart/templates/postgres-service.yaml b/chart/templates/postgres-service.yml similarity index 76% rename from chart/templates/postgres-service.yaml rename to chart/templates/postgres-service.yml index ec5c270..0995e2a 100644 --- a/chart/templates/postgres-service.yaml +++ b/chart/templates/postgres-service.yml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Service metadata: name: postgres-service + namespace: {{ .Values.global.namespace }} spec: selector: app: postgres diff --git a/chart/values.prod.public.yml b/chart/values.prod.public.yml new file mode 100644 index 0000000..b384b76 --- /dev/null +++ b/chart/values.prod.public.yml @@ -0,0 +1,53 @@ +global: + namespace: fusero-prod + security: + cors: + origin: "https://your-domain.com" + methods: "GET,POST,PUT,DELETE" + credentials: true + https: + enabled: true + certSecret: "fusero-tls-secret" + logging: + level: "info" + format: "json" + monitoring: + prometheus: + enabled: true + path: "/metrics" + +backend: + image: fusero-backend:latest + resources: + requests: + cpu: "200m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" + env: + # Non-sensitive values only + NODE_ENV: "production" + FASTIFY_PORT: "14000" + CANVAS_API_URL: "https://talnet.instructure.com/api/v1" + +frontend: + image: fusero-frontend:latest + resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "300m" + memory: "256Mi" + +postgres: + image: postgres:15 + storage: 5Gi + resources: + requests: + cpu: "200m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" diff --git a/chart/values.prod.yml b/chart/values.prod.yml index 321ca39..00879e2 100644 --- a/chart/values.prod.yml +++ b/chart/values.prod.yml @@ -1,22 +1,64 @@ +global: + namespace: fusero-prod + security: + cors: + origin: "https://app.fusero.nl" + methods: "GET,POST,PUT,DELETE" + credentials: true + https: + enabled: true + certSecret: "fusero-tls-secret" + logging: + level: "info" + format: "json" + monitoring: + prometheus: + enabled: true + path: "/metrics" + backend: - image: fusero-backend:latest + image: registry.liquidrinu.com/fusero-backend:latest + resources: + requests: + cpu: "200m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" env: POSTGRES_HOST: postgres-service POSTGRES_PORT: "5432" POSTGRES_NAME: fusero-db POSTGRES_USER: prod_admin - POSTGRES_PASSWORD: REPLACE_ME + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} DEFAULT_ADMIN_USERNAME: admin DEFAULT_ADMIN_EMAIL: admin@fusero.nl - DEFAULT_ADMIN_PASSWORD: STRONG_REPLACE_ME - ENCRYPTION_KEY: PROD_REPLACE_ME_KEY - JWT_SECRET: PROD_REPLACE_ME_JWT - CHATGPT_API_KEY: PROD_REPLACE_ME_CHATGPT - CANVAS_API_KEY: PROD_REPLACE_ME_CANVAS + DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + JWT_SECRET: ${JWT_SECRET} + CHATGPT_API_KEY: ${CHATGPT_API_KEY} + CANVAS_API_KEY: ${CANVAS_API_KEY} + CANVAS_API_URL: https://talnet.instructure.com/api/v1 frontend: - image: fusero-frontend:latest + image: registry.liquidrinu.com/fusero-frontend:latest + resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "300m" + memory: "256Mi" + env: + VITE_API_BASE_URL: https://app.fusero.nl postgres: image: postgres:15 storage: 5Gi + resources: + requests: + cpu: "200m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" diff --git a/package.json b/package.json index 1ca7dda..9095dd2 100644 --- a/package.json +++ b/package.json @@ -10,16 +10,25 @@ "@": "src" }, "scripts": { - "fc": "fastify cli", + "build:ts": "rimraf dist && tsc", + "build:frontend:dev": "docker build -t fusero-frontend-dev:local -f frontend/Dockerfile.dev ./frontend", + "prebuild": "npm run lint", + "start": "npm run build:ts && fastify start -l info -r tsconfig-paths/register dist/src/app.js", + "dev": "npm run build:ts && concurrently -k -p '[{name}]' -n 'TypeScript,App,Watcher' -c 'teal.bold,orange.bold,purple.bold' 'npm:watch:ts' 'npm:dev:start' 'npm:watch:ts'", + "dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P -r tsconfig-paths/register dist/src/app.js", + "watch:ts": "tsc -w", + "lint": "eslint src/**/*.{js,jsx,ts,tsx}", + "lint:fix": "eslint src/**/*.{js,jsx,ts,tsx} --fix", "test": "jest", "test:unit": "tap test/services/**/*.ts", "test:integration": "tap test/integration/**/*.ts", "test:watch": "jest --watch", "test:coverage": "jest --coverage", - "start": "npm run build:ts && fastify start -l info -r tsconfig-paths/register dist/src/app.js", - "prebuild": "npm run lint", - "build:ts": "rimraf dist && tsc", - "build:frontend:dev": "docker build -t fusero-frontend-dev:local -f frontend/Dockerfile.dev ./frontend", + "test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts", + "migration:create": "npx mikro-orm migration:create", + "migrate": "npx mikro-orm migration:up", + "seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts", + "app:generate": "node ./utils/generate-app.js", "k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yml", "k8s:dev:delete": "kubectl delete deployment fusero-frontend-dev 2>/dev/null || true", "k8s:dev:run": "kubectl port-forward svc/fusero-frontend-service 3000:80", @@ -27,15 +36,6 @@ "k8s:dev:svc": "kubectl describe svc fusero-frontend-service", "k8s:dev:deployment": "kubectl describe deployment fusero-frontend-dev", "k8s:get": "kubectl get pods,svc,deployment", - "watch:ts": "tsc -w", - "dev": "npm run build:ts && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch:ts\" \"npm:dev:start\"", - "dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P -r tsconfig-paths/register dist/src/app.js", - "app:generate": "node ./utils/generate-app.js", - "lint": "eslint src/**/*.{js,jsx,ts,tsx}", - "lint:fix": "eslint src/**/*.{js,jsx,ts,tsx} --fix", - "migration:create": "npx mikro-orm migration:create", - "seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts", - "test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts", "k8s:exec": "kubectl exec -it $POD_NAME -- /bin/sh" }, "keywords": [], @@ -103,4 +103,4 @@ "git add" ] } -} +} \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index cb9569f..b3b03e3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -129,6 +129,11 @@ const app: FastifyPluginAsync = async (app, opts): Promise => { export async function buildApp(): Promise { const server = fastify(); await server.register(app); + + // Set the port to 14000 + const port = process.env.PORT || 14000; + await server.listen({ port: Number(port), host: '0.0.0.0' }); + return server; } From 77f8e1554c5951f88527f11f2791bb3018e72f6e Mon Sep 17 00:00:00 2001 From: liquidrinu Date: Wed, 21 May 2025 10:13:38 +0200 Subject: [PATCH 5/8] revised complete k8s deployment --- .gitea-ci.yml | 6 +- .gitignore | 16 +-- README.md | 106 +++++++++++++----- chart/Chart.yaml | 6 + chart/Chart.yml | 2 +- chart/secrets.prod.template.yml | 26 ----- ...deployment.yml => backend-deployment.yaml} | 0 ...ion.job.yml => backend-migration.job.yaml} | 0 ...ckend-secrets.yml => backend-secrets.yaml} | 0 ...ckend-service.yml => backend-service.yaml} | 0 ...eployment.yml => frontend-deployment.yaml} | 0 ...tend-service.yml => frontend-service.yaml} | 0 chart/templates/postgres-configmap.yaml | 18 +++ ...eployment.yml => postgres-deployment.yaml} | 9 ++ .../{postgres-pvc.yml => postgres-pvc.yaml} | 0 ...gres-service.yml => postgres-service.yaml} | 0 ...rod.public.yml => values.prod.public.yaml} | 0 chart/values.prod.yml | 64 ----------- package.json | 2 +- 19 files changed, 126 insertions(+), 129 deletions(-) create mode 100644 chart/Chart.yaml delete mode 100644 chart/secrets.prod.template.yml rename chart/templates/{backend-deployment.yml => backend-deployment.yaml} (100%) rename chart/templates/{backend-migration.job.yml => backend-migration.job.yaml} (100%) rename chart/templates/{backend-secrets.yml => backend-secrets.yaml} (100%) rename chart/templates/{backend-service.yml => backend-service.yaml} (100%) rename chart/templates/{frontend-deployment.yml => frontend-deployment.yaml} (100%) rename chart/templates/{frontend-service.yml => frontend-service.yaml} (100%) create mode 100644 chart/templates/postgres-configmap.yaml rename chart/templates/{postgres-deployment.yml => postgres-deployment.yaml} (71%) rename chart/templates/{postgres-pvc.yml => postgres-pvc.yaml} (100%) rename chart/templates/{postgres-service.yml => postgres-service.yaml} (100%) rename chart/{values.prod.public.yml => values.prod.public.yaml} (100%) delete mode 100644 chart/values.prod.yml diff --git a/.gitea-ci.yml b/.gitea-ci.yml index 45e7c15..4399050 100644 --- a/.gitea-ci.yml +++ b/.gitea-ci.yml @@ -56,7 +56,7 @@ jobs: - name: Create secrets file run: | - cat > ./chart/secrets.prod.yml << EOF + cat > ./chart/secrets.prod.yaml << EOF backend: env: POSTGRES_PASSWORD: "${{ secrets.POSTGRES_PASSWORD }}" @@ -72,8 +72,8 @@ jobs: helm upgrade --install fusero ./chart \ --namespace fusero-prod \ --create-namespace \ - --values ./chart/values.prod.yml \ - --values ./chart/secrets.prod.yml \ + --values ./chart/values.prod.yaml \ + --values ./chart/secrets.prod.yaml \ --set backend.image.repository=registry.liquidrinu.com/fusero-backend \ --set frontend.image.repository=registry.liquidrinu.com/fusero-frontend diff --git a/.gitignore b/.gitignore index be7a341..0e0326d 100644 --- a/.gitignore +++ b/.gitignore @@ -125,20 +125,20 @@ values.dev.* values.prod.* # Secrets -chart/secrets.prod.yml -chart/secrets.*.yml +chart/secrets.prod.yaml +chart/secrets.*.yaml *.env .env.* # Development values with secrets -chart/values.dev.yml +chart/values.dev.yaml # Production secrets -chart/secrets.prod.yml -chart/secrets.*.yml +chart/secrets.prod.yaml +chart/secrets.*.yaml # Keep templates and public configs -!chart/secrets.prod.template.yml -!chart/values.prod.template.yml -!chart/values.prod.public.yml +!chart/secrets.prod.template.yaml +!chart/values.prod.template.yaml +!chart/values.prod.public.yaml diff --git a/README.md b/README.md index d378443..01049ed 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A full-stack application boilerplate with a React frontend and Node.js backend - [To seed the database:](#to-seed-the-database) - [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals) - [🛠️ Environment Setup](#️-environment-setup) -- [For Kubernetes, these are set in chart/values.yml:](#for-kubernetes-these-are-set-in-chartvaluesyml) +- [For Kubernetes, these are set in chart/values.yaml:](#for-kubernetes-these-are-set-in-chartvaluesyaml) - [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db) - [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service) - [POSTGRES\_PORT=19095](#postgres_port19095) @@ -53,6 +53,7 @@ A full-stack application boilerplate with a React frontend and Node.js backend - [Port-Forwarding for Local Access](#port-forwarding-for-local-access) - [Frontend (React app)](#frontend-react-app) - [Backend (API)](#backend-api) + - [Database](#database) - [NGINX Backend Service Name: Docker Compose vs Kubernetes](#nginx-backend-service-name-docker-compose-vs-kubernetes) - [How to update the NGINX config for Kubernetes](#how-to-update-the-nginx-config-for-kubernetes) - [Cleaning Up Duplicate or Crashing Deployments and Pods in Kubernetes](#cleaning-up-duplicate-or-crashing-deployments-and-pods-in-kubernetes) @@ -79,6 +80,7 @@ A full-stack application boilerplate with a React frontend and Node.js backend - [Database Backup](#database-backup) - [CI/CD \& Automated Testing](#cicd--automated-testing) - [Troubleshooting Production](#troubleshooting-production) + - [🆕 Recent Improvements \& Troubleshooting](#-recent-improvements--troubleshooting) --- @@ -88,7 +90,7 @@ fusero-app-boilerplate/ ├── chart/ # Helm chart for Kubernetes │ ├── Chart.yaml │ ├── values.dev.yaml -│ ├── values.prod.yml +│ ├── values.prod.yaml │ └── templates/ ├── config/ ├── coverage/ @@ -102,10 +104,10 @@ fusero-app-boilerplate/ ├── node_modules/ ├── package.json ├── package-lock.json -├── docker-compose.yml -├── docker-compose.dev.yml +├── docker-compose.yaml +├── docker-compose.dev.yaml ├── .gitignore -├── .gitea-ci.yml +├── .gitea-ci.yaml ├── .prettierrc.json ├── .eslintrc.json ├── architecture.excalidraw @@ -192,7 +194,7 @@ POSTGRES_USER=root POSTGRES_PASSWORD=root123 JWT_SECRET=your_jwt_secret_key_here -# For Kubernetes, these are set in chart/values.yml: +# For Kubernetes, these are set in chart/values.yaml: # POSTGRES_NAME=fusero-boilerplate-db # POSTGRES_HOSTNAME=postgres-service # POSTGRES_PORT=19095 @@ -248,7 +250,6 @@ docker exec -it fusero-app-backend npm run migrate docker exec -it fusero-app-backend npm run seed 3. Ensure all required environment variables are configured. -Never commit `.env` files. --- @@ -275,7 +276,7 @@ Wrong: to="/dashboard/canvas/canvas-endpoints" Generate a self-signed cert: openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx/ssl/nginx.key -out ./nginx/ssl/nginx.crt -Ensure `docker-compose.yml` mounts the certs: +Ensure `docker-compose.yaml` mounts the certs: volumes: - ./nginx/ssl:/etc/nginx/ssl @@ -286,7 +287,7 @@ Configure NGINX to use the cert in production. ## 🧠 Development Best Practices - Always run the DB via Docker -- Use `docker-compose.dev.yml` for development +- Use `docker-compose.dev.yaml` for development - Never run PostgreSQL directly on host - Run frontend and backend separately for hot reload - Use `.env.example` as a template @@ -341,8 +342,8 @@ lsof -i :14000 Database Issues: Ensure DB is in Docker and configured correctly Try restarting: -docker-compose -f docker-compose.dev.yml down -docker-compose -f docker-compose.dev.yml up db +docker-compose -f docker-compose.dev.yaml down +docker-compose -f docker-compose.dev.yaml up db CORS Issues: Check API base URL in frontend `.env` @@ -384,7 +385,7 @@ docker push /fusero-backend-dev:local ### 3. Upgrade the Helm release with the latest values ```bash -helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml ``` ### 4. Restart the backend deployment to pick up new images and env vars @@ -430,7 +431,7 @@ docker push /fusero-frontend-dev:local ### 3. Upgrade the Helm release ```bash -helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml ``` ### 4. Restart the frontend deployment @@ -446,16 +447,22 @@ To access your services running in Kubernetes from your local machine, use these ### Frontend (React app) ```bash -kubectl port-forward -n fusero svc/fusero-frontend-service 3000:80 +kubectl port-forward -n fusero-dev svc/fusero-frontend-service 3000:80 ``` - Access at: http://localhost:3000 ### Backend (API) ```bash -kubectl port-forward -n fusero svc/fusero-backend-service 14000:14000 +kubectl port-forward -n fusero-dev svc/fusero-backend-service 14000:14000 ``` - Access at: http://localhost:14000 +### Database +```bash +kubectl port-forward -n fusero-dev svc/postgres-service 5432:5432 +``` +- Access at: localhost:5432 + --- ## NGINX Backend Service Name: Docker Compose vs Kubernetes @@ -482,7 +489,7 @@ Then rebuild and redeploy the frontend: ```bash docker build -t fusero-frontend-dev:local ./frontend # (push if needed) -helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml kubectl rollout restart deployment/fusero-frontend -n fusero ``` @@ -546,7 +553,7 @@ This means NGINX is trying to load an SSL certificate that does not exist in the 3. (If using a remote registry) Push the image. 4. Redeploy with Helm: ```bash - helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml + helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml ``` 5. Check pod status: ```bash @@ -592,7 +599,7 @@ kubectl create namespace fusero-dev kubectl config set-context --current --namespace=fusero-dev # Deploy to development namespace -helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml +helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml ``` ### Production Namespace Setup @@ -604,7 +611,7 @@ kubectl create namespace fusero-prod kubectl config set-context --current --namespace=fusero-prod # Deploy to production namespace -helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yml +helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yaml ``` ### Namespace Management Commands @@ -656,8 +663,8 @@ kubectl delete namespace You have configured your Kubernetes development environment to use a dedicated namespace (`fusero-dev`) for all core services (backend, frontend, and database). This ensures resource isolation and easier management between development and production. ### What was changed: -- All Kubernetes service templates (`backend-service.yml`, `frontend-service.yml`, `postgres-service.yml`) now use a namespace variable. -- The development values file (`chart/values.dev.yml`) sets `global.namespace: fusero-dev` and updates all internal service hostnames to use this namespace. +- All Kubernetes service templates (`backend-service.yaml`, `frontend-service.yaml`, `postgres-service.yaml`) now use a namespace variable. +- The development values file (`chart/values.dev.yaml`) sets `global.namespace: fusero-dev` and updates all internal service hostnames to use this namespace. - Helm deployments now target the `fusero-dev` namespace for all dev resources. ### How to use: @@ -668,7 +675,7 @@ You have configured your Kubernetes development environment to use a dedicated n 2. **Deploy all services to the namespace:** ```bash - helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml + helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml ``` 3. **Check running pods and services:** @@ -681,7 +688,7 @@ You have configured your Kubernetes development environment to use a dedicated n 5. **If you see errors about immutable fields (e.g., for Jobs), delete the old Job before redeploying:** ```bash kubectl delete job -n fusero-dev - helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml + helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml ``` ### Why use namespaces? @@ -703,7 +710,7 @@ The application uses a secure secrets management approach: 2. **Production Environment**: - Secrets are managed through Gitea CI/CD secrets - - Template file: `chart/secrets.prod.template.yml` + - Template file: `chart/secrets.prod.template.yaml` - Actual secrets are generated during deployment - Never commit actual secrets to the repository @@ -716,7 +723,7 @@ The application uses a secure secrets management approach: 4. **Secrets in CI/CD**: - Secrets are stored in Gitea CI/CD settings - Automatically injected during deployment - - Used to generate `secrets.prod.yml` at runtime + - Used to generate `secrets.prod.yaml` at runtime 5. **Security Best Practices**: - All secrets files are gitignored @@ -757,7 +764,7 @@ The application uses a secure secrets management approach: - name: Run Tests run: npm test - name: Deploy to Kubernetes - run: helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yml + run: helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yaml ``` ### Troubleshooting Production @@ -767,3 +774,50 @@ The application uses a secure secrets management approach: - Rollback: Use `helm rollback fusero -n fusero-prod`. --- + +## 🆕 Recent Improvements & Troubleshooting + +- **Consistent File Extensions:** All Kubernetes and Helm YAML files now use the `.yaml` extension for consistency. +- **Secrets Management:** + - Development secrets are stored in `chart/secrets.dev.yaml` (gitignored). + - Production secrets are generated by CI/CD as `chart/secrets.prod.yaml` from Gitea secrets. + - All values files (`values.dev.yaml`, `values.prod.yaml`) reference secrets via environment variables. +- **CORS Configuration:** + - For local development, set `CORS_ORIGIN: "http://localhost:3000"` in `chart/values.dev.yaml` to allow credentialed requests from the frontend. + - Do **not** use `*` for CORS origin if you need credentials. +- **Kubernetes Job Immutability:** + - If you update environment variables or secrets, you must delete the old migration/seed job before redeploying: + ```bash + kubectl delete job fusero-backend-db-init -n fusero-dev + helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml -f chart/secrets.dev.yaml + ``` +- **Port Forwarding for Local Access:** + - Backend: + ```bash + kubectl port-forward -n fusero-dev svc/fusero-backend-service 14000:14000 + ``` + - Frontend: + ```bash + kubectl port-forward -n fusero-dev svc/fusero-frontend-service 3000:80 + ``` + - Database: + ```bash + kubectl port-forward -n fusero-dev svc/postgres-service 5432:5432 + ``` +- **Testing Login with curl:** + ```bash + curl -i -X POST http://localhost:14000/api/v1/app/users/login \ + -H "Content-Type: application/json" \ + -H "Origin: http://localhost:3000" \ + -d '{"username":"admin","password":"admin123"}' + ``` +- **Troubleshooting 401 Errors:** + - If login fails after a redeploy: + - Ensure secrets and values are in sync. + - Re-run the seed job as above. + - Check backend logs for authentication errors: + ```bash + kubectl logs -n fusero-dev -l app=fusero-backend --tail=100 + ``` + +--- diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..22f713b --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: fusero +description: Fusero App Boilerplate Helm Chart +type: application +version: 0.1.0 +appVersion: "1.0.0" diff --git a/chart/Chart.yml b/chart/Chart.yml index 3708c76..22f713b 100644 --- a/chart/Chart.yml +++ b/chart/Chart.yml @@ -1,6 +1,6 @@ apiVersion: v2 name: fusero -description: Fusero application Helm chart +description: Fusero App Boilerplate Helm Chart type: application version: 0.1.0 appVersion: "1.0.0" diff --git a/chart/secrets.prod.template.yml b/chart/secrets.prod.template.yml deleted file mode 100644 index 1ecd2c3..0000000 --- a/chart/secrets.prod.template.yml +++ /dev/null @@ -1,26 +0,0 @@ -backend: - env: - # Database - POSTGRES_NAME: "fusero-boilerplate-db" - POSTGRES_HOSTNAME: "postgres-service.fusero-prod.svc.cluster.local" - POSTGRES_PORT: "5432" - POSTGRES_USER: "root" - POSTGRES_PASSWORD: "REPLACE_ME" - - # Admin User - DEFAULT_ADMIN_USERNAME: "admin" - DEFAULT_ADMIN_EMAIL: "admin@fusero.nl" - DEFAULT_ADMIN_PASSWORD: "REPLACE_ME" - - # Security - ENCRYPTION_KEY: "REPLACE_ME" - JWT_SECRET: "REPLACE_ME" - - # API Keys - CHATGPT_API_KEY: "REPLACE_ME" - CANVAS_API_KEY: "REPLACE_ME" - CANVAS_API_URL: "https://talnet.instructure.com/api/v1" - - # Server - FASTIFY_PORT: "14000" - NODE_ENV: "production" diff --git a/chart/templates/backend-deployment.yml b/chart/templates/backend-deployment.yaml similarity index 100% rename from chart/templates/backend-deployment.yml rename to chart/templates/backend-deployment.yaml diff --git a/chart/templates/backend-migration.job.yml b/chart/templates/backend-migration.job.yaml similarity index 100% rename from chart/templates/backend-migration.job.yml rename to chart/templates/backend-migration.job.yaml diff --git a/chart/templates/backend-secrets.yml b/chart/templates/backend-secrets.yaml similarity index 100% rename from chart/templates/backend-secrets.yml rename to chart/templates/backend-secrets.yaml diff --git a/chart/templates/backend-service.yml b/chart/templates/backend-service.yaml similarity index 100% rename from chart/templates/backend-service.yml rename to chart/templates/backend-service.yaml diff --git a/chart/templates/frontend-deployment.yml b/chart/templates/frontend-deployment.yaml similarity index 100% rename from chart/templates/frontend-deployment.yml rename to chart/templates/frontend-deployment.yaml diff --git a/chart/templates/frontend-service.yml b/chart/templates/frontend-service.yaml similarity index 100% rename from chart/templates/frontend-service.yml rename to chart/templates/frontend-service.yaml diff --git a/chart/templates/postgres-configmap.yaml b/chart/templates/postgres-configmap.yaml new file mode 100644 index 0000000..e2452f6 --- /dev/null +++ b/chart/templates/postgres-configmap.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-config +data: + pg_hba.conf: | + # TYPE DATABASE USER ADDRESS METHOD + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + host all all 0.0.0.0/0 md5 + postgresql.conf: | + listen_addresses = '*' + max_connections = 100 + shared_buffers = 128MB + dynamic_shared_memory_type = posix + max_wal_size = 1GB + min_wal_size = 80MB \ No newline at end of file diff --git a/chart/templates/postgres-deployment.yml b/chart/templates/postgres-deployment.yaml similarity index 71% rename from chart/templates/postgres-deployment.yml rename to chart/templates/postgres-deployment.yaml index af643e2..fceff3b 100644 --- a/chart/templates/postgres-deployment.yml +++ b/chart/templates/postgres-deployment.yaml @@ -27,7 +27,16 @@ spec: volumeMounts: - mountPath: /var/lib/postgresql/data name: postgres-data + - mountPath: /etc/postgresql/pg_hba.conf + name: postgres-config + subPath: pg_hba.conf + - mountPath: /etc/postgresql/postgresql.conf + name: postgres-config + subPath: postgresql.conf volumes: - name: postgres-data persistentVolumeClaim: claimName: postgres-pvc-fresh + - name: postgres-config + configMap: + name: postgres-config diff --git a/chart/templates/postgres-pvc.yml b/chart/templates/postgres-pvc.yaml similarity index 100% rename from chart/templates/postgres-pvc.yml rename to chart/templates/postgres-pvc.yaml diff --git a/chart/templates/postgres-service.yml b/chart/templates/postgres-service.yaml similarity index 100% rename from chart/templates/postgres-service.yml rename to chart/templates/postgres-service.yaml diff --git a/chart/values.prod.public.yml b/chart/values.prod.public.yaml similarity index 100% rename from chart/values.prod.public.yml rename to chart/values.prod.public.yaml diff --git a/chart/values.prod.yml b/chart/values.prod.yml deleted file mode 100644 index 00879e2..0000000 --- a/chart/values.prod.yml +++ /dev/null @@ -1,64 +0,0 @@ -global: - namespace: fusero-prod - security: - cors: - origin: "https://app.fusero.nl" - methods: "GET,POST,PUT,DELETE" - credentials: true - https: - enabled: true - certSecret: "fusero-tls-secret" - logging: - level: "info" - format: "json" - monitoring: - prometheus: - enabled: true - path: "/metrics" - -backend: - image: registry.liquidrinu.com/fusero-backend:latest - resources: - requests: - cpu: "200m" - memory: "256Mi" - limits: - cpu: "500m" - memory: "512Mi" - env: - POSTGRES_HOST: postgres-service - POSTGRES_PORT: "5432" - POSTGRES_NAME: fusero-db - POSTGRES_USER: prod_admin - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - DEFAULT_ADMIN_USERNAME: admin - DEFAULT_ADMIN_EMAIL: admin@fusero.nl - DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD} - ENCRYPTION_KEY: ${ENCRYPTION_KEY} - JWT_SECRET: ${JWT_SECRET} - CHATGPT_API_KEY: ${CHATGPT_API_KEY} - CANVAS_API_KEY: ${CANVAS_API_KEY} - CANVAS_API_URL: https://talnet.instructure.com/api/v1 - -frontend: - image: registry.liquidrinu.com/fusero-frontend:latest - resources: - requests: - cpu: "100m" - memory: "128Mi" - limits: - cpu: "300m" - memory: "256Mi" - env: - VITE_API_BASE_URL: https://app.fusero.nl - -postgres: - image: postgres:15 - storage: 5Gi - resources: - requests: - cpu: "200m" - memory: "256Mi" - limits: - cpu: "500m" - memory: "512Mi" diff --git a/package.json b/package.json index 9095dd2..d923049 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "migrate": "npx mikro-orm migration:up", "seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts", "app:generate": "node ./utils/generate-app.js", - "k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yml", + "k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yaml", "k8s:dev:delete": "kubectl delete deployment fusero-frontend-dev 2>/dev/null || true", "k8s:dev:run": "kubectl port-forward svc/fusero-frontend-service 3000:80", "k8s:dev:describe": "kubectl describe pod -l app=fusero-frontend", From f2ff6a2aa28dafae0e858047bc5c86c2de6c02a6 Mon Sep 17 00:00:00 2001 From: liquidrinu Date: Wed, 21 May 2025 10:17:30 +0200 Subject: [PATCH 6/8] cleanup --- .gitignore | 1 + frontend/nginx.conf.bkup | 27 ------------------- frontend/src/vite-env.d.ts | 1 - src/plugins/Crypto.ts | 33 ------------------------ src/plugins/JWT.ts | 20 -------------- src/shared/exceptions/TooManyRequests.ts | 15 ----------- 6 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 frontend/nginx.conf.bkup delete mode 100644 frontend/src/vite-env.d.ts delete mode 100644 src/plugins/Crypto.ts delete mode 100644 src/plugins/JWT.ts delete mode 100644 src/shared/exceptions/TooManyRequests.ts diff --git a/.gitignore b/.gitignore index 0e0326d..a847b38 100644 --- a/.gitignore +++ b/.gitignore @@ -142,3 +142,4 @@ chart/secrets.*.yaml !chart/values.prod.template.yaml !chart/values.prod.public.yaml +.bkup/ diff --git a/frontend/nginx.conf.bkup b/frontend/nginx.conf.bkup deleted file mode 100644 index 7fdce10..0000000 --- a/frontend/nginx.conf.bkup +++ /dev/null @@ -1,27 +0,0 @@ -server { - listen 80; - server_name _; - - location / { - root /usr/share/nginx/html; - index index.html; - try_files $uri $uri/ /index.html; - } - - # Serve favicon files - location /favicon/ { - alias /usr/share/nginx/html/dist/favicon/; - access_log off; - expires max; - } - - # Proxy API requests to the backend - location /api { - proxy_pass http://fusero-app-backend:14000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } -} \ No newline at end of file diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/frontend/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/src/plugins/Crypto.ts b/src/plugins/Crypto.ts deleted file mode 100644 index d4fdec7..0000000 --- a/src/plugins/Crypto.ts +++ /dev/null @@ -1,33 +0,0 @@ -// import fp from 'fastify-plugin'; -// import crypto from 'crypto'; -// import { FastifyInstance, FastifyPluginAsync } from 'fastify'; - -// const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY as string; // Ensure this is 32 bytes for AES-256 -// const IV_LENGTH = 16; // AES block size - -// const cryptoPlugin: FastifyPluginAsync = async (fastify: FastifyInstance) => { -// fastify.decorate('encrypt', (text: string): string => { -// const iv = crypto.randomBytes(IV_LENGTH); -// const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); -// let encrypted = cipher.update(text); -// encrypted = Buffer.concat([encrypted, cipher.final()]); -// return iv.toString('hex') + ':' + encrypted.toString('hex'); -// }); - -// fastify.decorate('decrypt', (text: string): string => { -// const textParts = text.split(':'); -// const iv = Buffer.from(textParts.shift()!, 'hex'); -// const encryptedText = Buffer.from(textParts.join(':'), 'hex'); -// const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); -// let decrypted = decipher.update(encryptedText); -// decrypted = Buffer.concat([decrypted, decipher.final()]); -// return decrypted.toString(); -// }); -// }; - -// export default fp(cryptoPlugin, { -// name: 'cryptoPlugin', -// decorators: { -// fastify: ['encrypt', 'decrypt'], -// }, -// }); diff --git a/src/plugins/JWT.ts b/src/plugins/JWT.ts deleted file mode 100644 index a62e83c..0000000 --- a/src/plugins/JWT.ts +++ /dev/null @@ -1,20 +0,0 @@ -// import { FastifyPluginAsync } from 'fastify'; -// import fJwt, { FastifyJWTOptions } from '@fastify/jwt'; - -// const jwtPlugin: FastifyPluginAsync = async (fastify) => { -// await fastify.register(fJwt, { -// secret: process.env.JWT_SECRET || 'your-secret-here', -// }); -// }; - -// const authenticateDecorator: FastifyPluginAsync = async (fastify) => { -// fastify.decorate('authenticate', async (request, reply) => { -// try { -// await request.jwtVerify(); -// } catch (err) { -// reply.send(err); -// } -// }); -// }; - -// export { jwtPlugin, authenticateDecorator }; diff --git a/src/shared/exceptions/TooManyRequests.ts b/src/shared/exceptions/TooManyRequests.ts deleted file mode 100644 index 617467d..0000000 --- a/src/shared/exceptions/TooManyRequests.ts +++ /dev/null @@ -1,15 +0,0 @@ -// import { HttpStatusCode } from 'axios'; - -// class TooManyRequests extends Error { -// public httpErrorStatusCode = HttpStatusCode.TooManyRequests; -// constructor(msg: string, customErrorCode?: HttpStatusCode) { -// super(msg); - -// this.httpErrorStatusCode = this.httpErrorStatusCode ?? customErrorCode; - -// // Set the prototype explicitly. -// Object.setPrototypeOf(this, TooManyRequests.prototype); -// } -// } - -// export default TooManyRequests; From 85e761d0d67a65700719095e3f37fb2bad4e4fb3 Mon Sep 17 00:00:00 2001 From: liquidrinu Date: Wed, 21 May 2025 22:05:31 +0200 Subject: [PATCH 7/8] update k8s config --- .gitea-ci.yml | 14 ++++++++++++++ README.md | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/.gitea-ci.yml b/.gitea-ci.yml index 4399050..bc7a7d4 100644 --- a/.gitea-ci.yml +++ b/.gitea-ci.yml @@ -67,6 +67,10 @@ jobs: CANVAS_API_KEY: "${{ secrets.CANVAS_API_KEY }}" EOF + - name: Delete old migration/seed job + run: | + kubectl delete job fusero-backend-db-init -n fusero-prod || true + - name: Deploy to Kubernetes run: | helm upgrade --install fusero ./chart \ @@ -77,6 +81,16 @@ jobs: --set backend.image.repository=registry.liquidrinu.com/fusero-backend \ --set frontend.image.repository=registry.liquidrinu.com/fusero-frontend + - name: Wait for migration/seed job + run: | + kubectl wait --for=condition=complete --timeout=300s job/fusero-backend-db-init -n fusero-prod + JOB_STATUS=$(kubectl get job fusero-backend-db-init -n fusero-prod -o jsonpath='{.status.succeeded}') + if [ "$JOB_STATUS" != "1" ]; then + echo "Migration/seed job failed!" >&2 + kubectl logs job/fusero-backend-db-init -n fusero-prod + exit 1 + fi + - name: Verify Deployment run: | kubectl rollout status deployment/fusero-backend -n fusero-prod diff --git a/README.md b/README.md index 01049ed..46bfc59 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ A full-stack application boilerplate with a React frontend and Node.js backend - [CI/CD \& Automated Testing](#cicd--automated-testing) - [Troubleshooting Production](#troubleshooting-production) - [🆕 Recent Improvements \& Troubleshooting](#-recent-improvements--troubleshooting) + - [🚀 Production Deployment Pipeline (CI/CD)](#-production-deployment-pipeline-cicd) --- @@ -821,3 +822,18 @@ The application uses a secure secrets management approach: ``` --- + +## 🚀 Production Deployment Pipeline (CI/CD) + +- On every push/merge to `main`, the Gitea CI/CD pipeline will: + 1. Build and push Docker images for backend and frontend. + 2. Generate `secrets.prod.yaml` from Gitea CI/CD secrets. + 3. **Delete the old migration/seed job** (`fusero-backend-db-init`) to ensure a fresh run. + 4. Deploy the app with Helm, which triggers the migration/seed job. + 5. **Wait for the migration/seed job to complete.** + 6. **Fail the pipeline if the job fails** (with logs for debugging). + 7. Verify the deployment. + +- This ensures your database is always migrated and seeded with every deploy, and you'll know immediately if something goes wrong. + +- To trigger a production deployment, just push or merge to `main`. From b3b78341664e2d4005ec651b1da1d453e287ae6f Mon Sep 17 00:00:00 2001 From: liquidrinu Date: Wed, 21 May 2025 22:26:43 +0200 Subject: [PATCH 8/8] added ingress controller --- chart/templates/ingress.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 chart/templates/ingress.yaml diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml new file mode 100644 index 0000000..5f61a67 --- /dev/null +++ b/chart/templates/ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: fusero-app-ingress + namespace: fusero-prod + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web +spec: + rules: + - host: app.fusero.nl + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: fusero-backend-service + port: + number: 14000 + - path: / + pathType: Prefix + backend: + service: + name: fusero-frontend-service + port: + number: 80 \ No newline at end of file