Compare commits
2 Commits
main
...
hotfix-pat
Author | SHA1 | Date | |
---|---|---|---|
0576193b76 | |||
c0db4dc2c9 |
@ -14,6 +14,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
driver-opts: |
|
||||||
|
image=moby/buildkit:latest
|
||||||
|
network=host
|
||||||
|
|
||||||
- name: Login to Docker Registry
|
- name: Login to Docker Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
@ -30,11 +34,10 @@ jobs:
|
|||||||
tags: registry.liquidrinu.com/fusero-backend:latest
|
tags: registry.liquidrinu.com/fusero-backend:latest
|
||||||
cache-from: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache
|
cache-from: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache
|
||||||
cache-to: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache,mode=max
|
cache-to: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache,mode=max
|
||||||
|
build-args: |
|
||||||
- name: Create .env file
|
BUILDKIT_INLINE_CACHE=1
|
||||||
run: |
|
platforms: linux/amd64
|
||||||
echo "VITE_API_BASE_URL=/api" > ./frontend/.env
|
compression: zstd
|
||||||
# This only affects the CI/CD build, not your local dev .env
|
|
||||||
|
|
||||||
- name: Build and Push Frontend
|
- name: Build and Push Frontend
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
@ -45,6 +48,10 @@ jobs:
|
|||||||
tags: registry.liquidrinu.com/fusero-frontend:latest
|
tags: registry.liquidrinu.com/fusero-frontend:latest
|
||||||
cache-from: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache
|
cache-from: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache
|
||||||
cache-to: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache,mode=max
|
cache-to: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache,mode=max
|
||||||
|
build-args: |
|
||||||
|
BUILDKIT_INLINE_CACHE=1
|
||||||
|
platforms: linux/amd64
|
||||||
|
compression: zstd
|
||||||
|
|
||||||
- name: Install kubectl
|
- name: Install kubectl
|
||||||
uses: azure/setup-kubectl@v3
|
uses: azure/setup-kubectl@v3
|
||||||
@ -74,13 +81,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
kubectl delete job fusero-backend-db-init -n fusero-prod || true
|
kubectl delete job fusero-backend-db-init -n fusero-prod || true
|
||||||
|
|
||||||
- name: Install Helm
|
|
||||||
run: |
|
|
||||||
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
|
||||||
|
|
||||||
- name: Generate values.prod.yaml from template
|
|
||||||
run: cp chart/values.prod.template.yaml chart/values.prod.yaml
|
|
||||||
|
|
||||||
- name: Deploy to Kubernetes
|
- name: Deploy to Kubernetes
|
||||||
run: |
|
run: |
|
||||||
helm upgrade --install fusero ./chart \
|
helm upgrade --install fusero ./chart \
|
||||||
@ -88,8 +88,8 @@ jobs:
|
|||||||
--create-namespace \
|
--create-namespace \
|
||||||
--values ./chart/values.prod.yaml \
|
--values ./chart/values.prod.yaml \
|
||||||
--values ./chart/secrets.prod.yaml \
|
--values ./chart/secrets.prod.yaml \
|
||||||
--set backend.image=registry.liquidrinu.com/fusero-backend:latest \
|
--set backend.image.repository=registry.liquidrinu.com/fusero-backend \
|
||||||
--set frontend.image=registry.liquidrinu.com/fusero-frontend:latest
|
--set frontend.image.repository=registry.liquidrinu.com/fusero-frontend
|
||||||
|
|
||||||
- name: Wait for migration/seed job
|
- name: Wait for migration/seed job
|
||||||
run: |
|
run: |
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -143,7 +143,3 @@ chart/secrets.*.yaml
|
|||||||
!chart/values.prod.public.yaml
|
!chart/values.prod.public.yaml
|
||||||
|
|
||||||
.bkup/
|
.bkup/
|
||||||
|
|
||||||
# Ignore production values and secrets files
|
|
||||||
chart/values.prod.yaml
|
|
||||||
chart/secrets.prod.yaml
|
|
||||||
|
40
Dockerfile
40
Dockerfile
@ -1,4 +1,4 @@
|
|||||||
# Use Node.js 18.3 as the base image
|
# Build stage
|
||||||
FROM node:20-slim AS build
|
FROM node:20-slim AS build
|
||||||
|
|
||||||
# Install Python and build tools for node-gyp
|
# Install Python and build tools for node-gyp
|
||||||
@ -12,31 +12,53 @@ RUN apt-get update && \
|
|||||||
ENV APP_DIR=/usr/src/app/
|
ENV APP_DIR=/usr/src/app/
|
||||||
RUN mkdir -p ${APP_DIR}
|
RUN mkdir -p ${APP_DIR}
|
||||||
|
|
||||||
# Install global dependencies like pm2, ts-node, and typescript as root
|
# Install global dependencies
|
||||||
RUN npm install -g pm2 ts-node typescript
|
RUN npm install -g pm2 ts-node typescript
|
||||||
|
|
||||||
# Create a non-root user and switch to it
|
# Create a non-root user
|
||||||
ENV APP_USER=appuser
|
ENV APP_USER=appuser
|
||||||
RUN adduser --disabled-password --gecos '' ${APP_USER}
|
RUN adduser --disabled-password --gecos '' ${APP_USER}
|
||||||
WORKDIR ${APP_DIR}
|
WORKDIR ${APP_DIR}
|
||||||
RUN chown -R ${APP_USER}:${APP_USER} ${APP_DIR}
|
RUN chown -R ${APP_USER}:${APP_USER} ${APP_DIR}
|
||||||
|
|
||||||
# Switch to non-root user before copying files and installing dependencies
|
# Switch to non-root user
|
||||||
USER ${APP_USER}
|
USER ${APP_USER}
|
||||||
|
|
||||||
# Copy package.json and package-lock.json and install dependencies as appuser
|
# Copy package files and install dependencies
|
||||||
COPY --chown=${APP_USER}:${APP_USER} package.json package-lock.json ./
|
COPY --chown=${APP_USER}:${APP_USER} package.json package-lock.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
# Copy the rest of the application code with appropriate ownership
|
# Copy source code
|
||||||
COPY --chown=${APP_USER}:${APP_USER} . .
|
COPY --chown=${APP_USER}:${APP_USER} . .
|
||||||
|
|
||||||
# Rebuild bcrypt and other native dependencies as appuser
|
# Rebuild native dependencies
|
||||||
RUN npm rebuild bcrypt --build-from-source
|
RUN npm rebuild bcrypt --build-from-source
|
||||||
|
|
||||||
# Build the application using the npm script, assuming "build:ts" is defined
|
# Build the application
|
||||||
RUN npm run build:ts
|
RUN npm run build:ts
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:20-slim
|
||||||
|
|
||||||
|
# Install only production dependencies
|
||||||
|
ENV APP_DIR=/usr/src/app/
|
||||||
|
RUN mkdir -p ${APP_DIR}
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
ENV APP_USER=appuser
|
||||||
|
RUN adduser --disabled-password --gecos '' ${APP_USER}
|
||||||
|
WORKDIR ${APP_DIR}
|
||||||
|
RUN chown -R ${APP_USER}:${APP_USER} ${APP_DIR}
|
||||||
|
|
||||||
|
# Copy only necessary files from build stage
|
||||||
|
COPY --from=build --chown=${APP_USER}:${APP_USER} ${APP_DIR}/dist ./dist
|
||||||
|
COPY --from=build --chown=${APP_USER}:${APP_USER} ${APP_DIR}/package.json ./
|
||||||
|
COPY --from=build --chown=${APP_USER}:${APP_USER} ${APP_DIR}/package-lock.json ./
|
||||||
|
|
||||||
|
# Install only production dependencies
|
||||||
|
USER ${APP_USER}
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
ENV CI=true
|
ENV CI=true
|
||||||
ENV PORT=14000
|
ENV PORT=14000
|
||||||
@ -45,5 +67,5 @@ ENV NODE_ENV=production
|
|||||||
# Expose the application's port
|
# Expose the application's port
|
||||||
EXPOSE ${PORT}
|
EXPOSE ${PORT}
|
||||||
|
|
||||||
# Command to run the application using npm start
|
# Command to run the application
|
||||||
CMD ["npm", "start"]
|
CMD ["npm", "start"]
|
||||||
|
138
README.md
138
README.md
@ -10,9 +10,44 @@ A full-stack application boilerplate with a React frontend and Node.js backend
|
|||||||
- [📚 Table of Contents](#-table-of-contents)
|
- [📚 Table of Contents](#-table-of-contents)
|
||||||
- [📁 Project Structure](#-project-structure)
|
- [📁 Project Structure](#-project-structure)
|
||||||
- [⚙️ Prerequisites](#️-prerequisites)
|
- [⚙️ Prerequisites](#️-prerequisites)
|
||||||
- [Development Setup](#development-setup)
|
- [💻 Development Setup](#-development-setup)
|
||||||
- [Important Note: Database Must Run in Docker](#important-note-database-must-run-in-docker)
|
- [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)
|
||||||
|
- [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)
|
||||||
|
- [🐳 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)
|
||||||
|
- [📘 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)
|
- [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)
|
- [3. Upgrade the Helm release](#3-upgrade-the-helm-release)
|
||||||
- [4. Restart the frontend deployment](#4-restart-the-frontend-deployment)
|
- [4. Restart the frontend deployment](#4-restart-the-frontend-deployment)
|
||||||
- [Port-Forwarding for Local Access](#port-forwarding-for-local-access)
|
- [Port-Forwarding for Local Access](#port-forwarding-for-local-access)
|
||||||
@ -47,17 +82,12 @@ A full-stack application boilerplate with a React frontend and Node.js backend
|
|||||||
- [Troubleshooting Production](#troubleshooting-production)
|
- [Troubleshooting Production](#troubleshooting-production)
|
||||||
- [🆕 Recent Improvements \& Troubleshooting](#-recent-improvements--troubleshooting)
|
- [🆕 Recent Improvements \& Troubleshooting](#-recent-improvements--troubleshooting)
|
||||||
- [🚀 Production Deployment Pipeline (CI/CD)](#-production-deployment-pipeline-cicd)
|
- [🚀 Production Deployment Pipeline (CI/CD)](#-production-deployment-pipeline-cicd)
|
||||||
- [CI/CD Kubernetes Deployment Setup](#cicd-kubernetes-deployment-setup)
|
|
||||||
- [Using Private Docker Registry with Kubernetes](#using-private-docker-registry-with-kubernetes)
|
|
||||||
- [Production Secrets Management (Gitea as Source of Truth)](#production-secrets-management-gitea-as-source-of-truth)
|
|
||||||
- [CI/CD Pipeline Behavior: Multiple Merges to Main](#cicd-pipeline-behavior-multiple-merges-to-main)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|
||||||
```
|
fusero-app-boilerplate/
|
||||||
fusero-app-boilerplate
|
|
||||||
├── chart/ # Helm chart for Kubernetes
|
├── chart/ # Helm chart for Kubernetes
|
||||||
│ ├── Chart.yaml
|
│ ├── Chart.yaml
|
||||||
│ ├── values.dev.yaml
|
│ ├── values.dev.yaml
|
||||||
@ -95,7 +125,6 @@ fusero-app-boilerplate
|
|||||||
├── test/
|
├── test/
|
||||||
├── utils/
|
├── utils/
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -528,94 +557,3 @@ The application uses a secure secrets management approach:
|
|||||||
- This ensures your database is always migrated and seeded with every deploy, and you'll know immediately if something goes wrong.
|
- 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`.
|
- To trigger a production deployment, just push or merge to `main`.
|
||||||
|
|
||||||
## CI/CD Kubernetes Deployment Setup
|
|
||||||
|
|
||||||
To enable automated deployment to your Kubernetes cluster from CI/CD (Gitea Actions):
|
|
||||||
|
|
||||||
1. **Get your kubeconfig file from your Kubernetes master node or provider.**
|
|
||||||
- For self-hosted clusters, it's usually at `~/.kube/config` on the master node.
|
|
||||||
- For managed clusters, download it from your provider's dashboard.
|
|
||||||
|
|
||||||
2. **Edit the kubeconfig file:**
|
|
||||||
- Change the `server:` field to use your cluster's public IP or DNS, e.g.:
|
|
||||||
```yaml
|
|
||||||
server: https://[YOUR_PUBLIC_IP_OR_DNS]:6443
|
|
||||||
```
|
|
||||||
(For IPv6, use square brackets around the address.)
|
|
||||||
|
|
||||||
3. **Base64-encode the kubeconfig file as a single line:**
|
|
||||||
- On Linux:
|
|
||||||
```bash
|
|
||||||
base64 -w 0 /path/to/your/kubeconfig
|
|
||||||
```
|
|
||||||
- On Mac:
|
|
||||||
```bash
|
|
||||||
base64 /path/to/your/kubeconfig | tr -d '\n'
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Add the base64 string as a secret in your Gitea repository:**
|
|
||||||
- Go to **Settings → Secrets**
|
|
||||||
- Name: `KUBE_CONFIG`
|
|
||||||
- Value: (paste the base64 string)
|
|
||||||
|
|
||||||
5. **Make sure port 6443 is open to your CI/CD runner's IP in your VPS firewall/security group.**
|
|
||||||
|
|
||||||
6. **Your pipeline will now be able to deploy to your Kubernetes cluster.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Using Private Docker Registry with Kubernetes
|
|
||||||
|
|
||||||
If you use a private Docker registry (like registry.liquidrinu.com), you must create a Kubernetes secret and reference it in your deployments:
|
|
||||||
|
|
||||||
1. **Create the registry secret:**
|
|
||||||
```bash
|
|
||||||
kubectl create secret docker-registry regcred \
|
|
||||||
--docker-server=registry.liquidrinu.com \
|
|
||||||
--docker-username=YOUR_REGISTRY_USERNAME \
|
|
||||||
--docker-password=YOUR_REGISTRY_PASSWORD \
|
|
||||||
--docker-email=YOUR_EMAIL \
|
|
||||||
-n fusero-prod
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Reference the secret in your deployment YAMLs:**
|
|
||||||
In your deployment spec, add:
|
|
||||||
```yaml
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: regcred
|
|
||||||
```
|
|
||||||
Example:
|
|
||||||
```yaml
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: regcred
|
|
||||||
containers:
|
|
||||||
- name: backend
|
|
||||||
image: ...
|
|
||||||
```
|
|
||||||
|
|
||||||
This allows Kubernetes to authenticate to your private registry and pull images securely.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Production Secrets Management (Gitea as Source of Truth)
|
|
||||||
|
|
||||||
- In production, all sensitive values (like `POSTGRES_PASSWORD`, `DEFAULT_ADMIN_PASSWORD`, etc.) are managed as secrets in your Gitea repository (Settings → Secrets).
|
|
||||||
- The CI/CD pipeline uses these secrets to generate `chart/secrets.prod.yaml` and other files at runtime.
|
|
||||||
- Helm uses these generated files to set environment variables for your Kubernetes resources.
|
|
||||||
- The Postgres password is set from the secret **only when the database is first initialized** (i.e., when the persistent volume is empty). Changing the secret later will not update the password for an existing database unless you reset the DB or delete the volume.
|
|
||||||
- **Summary:** Gitea secrets are the source of truth for production. Always update secrets in Gitea and redeploy to apply changes to new pods.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CI/CD Pipeline Behavior: Multiple Merges to Main
|
|
||||||
|
|
||||||
- If multiple merges or pushes happen to the `main` branch in quick succession, your CI/CD system will start a separate pipeline for each commit.
|
|
||||||
- These pipelines will run in parallel unless your CI/CD is configured to queue or cancel previous runs.
|
|
||||||
- This can result in race conditions, where the last pipeline to finish will determine the final deployed state.
|
|
||||||
- **Best practice:** Avoid merging multiple large changes to `main` at the same time. Wait for the pipeline to finish before merging the next PR, or configure your CI/CD to cancel previous runs on new pushes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
6
chart/Chart.yml
Normal file
6
chart/Chart.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: fusero
|
||||||
|
description: Fusero App Boilerplate Helm Chart
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "1.0.0"
|
@ -12,8 +12,6 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: fusero-backend
|
app: fusero-backend
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
|
||||||
- name: regcred
|
|
||||||
containers:
|
containers:
|
||||||
- name: backend
|
- name: backend
|
||||||
image: {{ .Values.backend.image }}
|
image: {{ .Values.backend.image }}
|
||||||
|
@ -8,62 +8,18 @@ spec:
|
|||||||
metadata:
|
metadata:
|
||||||
name: fusero-backend-db-init
|
name: fusero-backend-db-init
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
|
||||||
- name: regcred
|
|
||||||
containers:
|
containers:
|
||||||
- name: migrate-seed
|
- name: migrate-seed
|
||||||
image: {{ .Values.backend.image }}
|
image: {{ .Values.backend.image }}
|
||||||
command: ["/bin/sh", "-c"]
|
command: ["/bin/sh", "-c"]
|
||||||
args:
|
args:
|
||||||
- |
|
- |
|
||||||
echo "=== Environment Variables ==="
|
echo "Running migrations and seeds..." && \
|
||||||
env | grep -i postgres
|
npx mikro-orm migration:up && \
|
||||||
echo "=== Testing Connection ==="
|
|
||||||
PGPASSWORD=$POSTGRES_PASSWORD psql "postgresql://$POSTGRES_USER@$POSTGRES_HOSTNAME:$POSTGRES_PORT/$POSTGRES_NAME" -c "SELECT 1"
|
|
||||||
echo "=== Running Migrations ==="
|
|
||||||
npx mikro-orm migration:up
|
|
||||||
echo "=== Running Seeds ==="
|
|
||||||
npm run seed
|
npm run seed
|
||||||
env:
|
env:
|
||||||
- name: POSTGRES_HOSTNAME
|
{{- range $key, $val := .Values.backend.env }}
|
||||||
value: "postgres-service"
|
- name: {{ $key }}
|
||||||
- name: POSTGRES_PORT
|
value: "{{ $val }}"
|
||||||
value: "5432"
|
{{- end }}
|
||||||
- name: POSTGRES_NAME
|
|
||||||
value: "fusero-db"
|
|
||||||
- name: POSTGRES_USER
|
|
||||||
value: "prod_admin"
|
|
||||||
- name: POSTGRES_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: fusero-backend-secrets
|
|
||||||
key: POSTGRES_PASSWORD
|
|
||||||
- name: NODE_ENV
|
|
||||||
value: "production"
|
|
||||||
- name: DEFAULT_ADMIN_USERNAME
|
|
||||||
value: "{{ .Values.backend.env.DEFAULT_ADMIN_USERNAME }}"
|
|
||||||
- name: DEFAULT_ADMIN_EMAIL
|
|
||||||
value: "{{ .Values.backend.env.DEFAULT_ADMIN_EMAIL }}"
|
|
||||||
- name: DEFAULT_ADMIN_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: fusero-backend-secrets
|
|
||||||
key: DEFAULT_ADMIN_PASSWORD
|
|
||||||
- name: JWT_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: fusero-backend-secrets
|
|
||||||
key: JWT_SECRET
|
|
||||||
- name: CHATGPT_API_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: fusero-backend-secrets
|
|
||||||
key: CHATGPT_API_KEY
|
|
||||||
- name: CANVAS_API_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: fusero-backend-secrets
|
|
||||||
key: CANVAS_API_KEY
|
|
||||||
- name: CANVAS_API_URL
|
|
||||||
value: "{{ .Values.backend.env.CANVAS_API_URL }}"
|
|
||||||
restartPolicy: Never
|
restartPolicy: Never
|
||||||
|
@ -12,8 +12,6 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: fusero-frontend
|
app: fusero-frontend
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
|
||||||
- name: regcred
|
|
||||||
containers:
|
containers:
|
||||||
- name: frontend
|
- name: frontend
|
||||||
image: {{ .Values.frontend.image }}
|
image: {{ .Values.frontend.image }}
|
||||||
|
@ -8,51 +8,11 @@ data:
|
|||||||
local all all trust
|
local all all trust
|
||||||
host all all 127.0.0.1/32 trust
|
host all all 127.0.0.1/32 trust
|
||||||
host all all ::1/128 trust
|
host all all ::1/128 trust
|
||||||
host all all 0.0.0.0/0 scram-sha-256
|
host all all 0.0.0.0/0 md5
|
||||||
postgresql.conf: |
|
postgresql.conf: |
|
||||||
# Connection Settings
|
|
||||||
listen_addresses = '*'
|
listen_addresses = '*'
|
||||||
max_connections = 100
|
max_connections = 100
|
||||||
|
|
||||||
# Memory Settings
|
|
||||||
shared_buffers = 128MB
|
shared_buffers = 128MB
|
||||||
work_mem = 4MB
|
|
||||||
maintenance_work_mem = 64MB
|
|
||||||
|
|
||||||
# Write Ahead Log
|
|
||||||
max_wal_size = 1GB
|
|
||||||
min_wal_size = 80MB
|
|
||||||
checkpoint_timeout = 5min
|
|
||||||
checkpoint_completion_target = 0.9
|
|
||||||
|
|
||||||
# Query Planner
|
|
||||||
random_page_cost = 1.1
|
|
||||||
effective_cache_size = 4GB
|
|
||||||
|
|
||||||
# Autovacuum
|
|
||||||
autovacuum = on
|
|
||||||
autovacuum_max_workers = 3
|
|
||||||
autovacuum_naptime = 1min
|
|
||||||
autovacuum_vacuum_threshold = 50
|
|
||||||
autovacuum_analyze_threshold = 50
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
log_min_duration_statement = 1000
|
|
||||||
log_checkpoints = on
|
|
||||||
log_connections = on
|
|
||||||
log_disconnections = on
|
|
||||||
log_lock_waits = on
|
|
||||||
log_temp_files = 0
|
|
||||||
log_autovacuum_min_duration = 0
|
|
||||||
|
|
||||||
# Other Settings
|
|
||||||
dynamic_shared_memory_type = posix
|
dynamic_shared_memory_type = posix
|
||||||
effective_io_concurrency = 200
|
max_wal_size = 1GB
|
||||||
default_statistics_target = 100
|
min_wal_size = 80MB
|
||||||
|
|
||||||
# Authentication
|
|
||||||
password_encryption = scram-sha-256
|
|
||||||
|
|
||||||
# Error Reporting
|
|
||||||
log_min_error_statement = error
|
|
||||||
log_statement = 'all'
|
|
@ -19,42 +19,11 @@ spec:
|
|||||||
- containerPort: 5432
|
- containerPort: 5432
|
||||||
env:
|
env:
|
||||||
- name: POSTGRES_DB
|
- name: POSTGRES_DB
|
||||||
value: "{{ .Values.postgres.dbName }}"
|
value: {{ .Values.postgres.dbName }}
|
||||||
- name: POSTGRES_USER
|
- name: POSTGRES_USER
|
||||||
value: "{{ .Values.postgres.user }}"
|
value: {{ .Values.postgres.user }}
|
||||||
- name: POSTGRES_PASSWORD
|
- name: POSTGRES_PASSWORD
|
||||||
value: "{{ .Values.postgres.password }}"
|
value: {{ .Values.postgres.password }}
|
||||||
- name: POSTGRES_HOST_AUTH_METHOD
|
|
||||||
value: "scram-sha-256"
|
|
||||||
startupProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- pg_isready
|
|
||||||
- -U
|
|
||||||
- "{{ .Values.postgres.user }}"
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 5
|
|
||||||
failureThreshold: 30
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- pg_isready
|
|
||||||
- -U
|
|
||||||
- "{{ .Values.postgres.user }}"
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
failureThreshold: 5
|
|
||||||
livenessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- pg_isready
|
|
||||||
- -U
|
|
||||||
- "{{ .Values.postgres.user }}"
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
failureThreshold: 3
|
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /var/lib/postgresql/data
|
- mountPath: /var/lib/postgresql/data
|
||||||
name: postgres-data
|
name: postgres-data
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
global:
|
|
||||||
namespace: fusero-prod
|
|
||||||
security:
|
|
||||||
cors:
|
|
||||||
origin: "https://your-domain.com"
|
|
||||||
methods: "GET,POST,PUT,DELETE"
|
|
||||||
credentials: true
|
|
||||||
https:
|
|
||||||
enabled: true
|
|
||||||
certSecret: "your-tls-secret"
|
|
||||||
logging:
|
|
||||||
level: "info"
|
|
||||||
format: "json"
|
|
||||||
monitoring:
|
|
||||||
prometheus:
|
|
||||||
enabled: true
|
|
||||||
path: "/metrics"
|
|
||||||
|
|
||||||
backend:
|
|
||||||
image: registry.liquidrinu.com/fusero-backend:latest
|
|
||||||
port: 14000
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: "200m"
|
|
||||||
memory: "256Mi"
|
|
||||||
limits:
|
|
||||||
cpu: "500m"
|
|
||||||
memory: "512Mi"
|
|
||||||
env:
|
|
||||||
POSTGRES_HOSTNAME: postgres-service
|
|
||||||
POSTGRES_PORT: "5432"
|
|
||||||
POSTGRES_DB: fusero-db
|
|
||||||
POSTGRES_USER: prod_admin
|
|
||||||
POSTGRES_PASSWORD: "<POSTGRES_PASSWORD>"
|
|
||||||
DEFAULT_ADMIN_USERNAME: admin
|
|
||||||
DEFAULT_ADMIN_EMAIL: admin@your-domain.com
|
|
||||||
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://your-canvas-instance/api/v1
|
|
||||||
|
|
||||||
frontend:
|
|
||||||
image: registry.liquidrinu.com/fusero-frontend:latest
|
|
||||||
port: 80
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: "100m"
|
|
||||||
memory: "128Mi"
|
|
||||||
limits:
|
|
||||||
cpu: "300m"
|
|
||||||
memory: "256Mi"
|
|
||||||
env:
|
|
||||||
VITE_API_BASE_URL: https://your-domain.com
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:15
|
|
||||||
storage: 5Gi
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: "200m"
|
|
||||||
memory: "256Mi"
|
|
||||||
limits:
|
|
||||||
cpu: "500m"
|
|
||||||
memory: "512Mi"
|
|
||||||
password: "<POSTGRES_PASSWORD>"
|
|
||||||
user: "prod_admin"
|
|
||||||
dbName: "fusero-db"
|
|
@ -1,70 +0,0 @@
|
|||||||
global:
|
|
||||||
namespace: fusero-prod
|
|
||||||
security:
|
|
||||||
cors:
|
|
||||||
origin: "https://your-domain.com"
|
|
||||||
methods: "GET,POST,PUT,DELETE"
|
|
||||||
credentials: true
|
|
||||||
https:
|
|
||||||
enabled: true
|
|
||||||
certSecret: "your-tls-secret"
|
|
||||||
logging:
|
|
||||||
level: "info"
|
|
||||||
format: "json"
|
|
||||||
monitoring:
|
|
||||||
prometheus:
|
|
||||||
enabled: true
|
|
||||||
path: "/metrics"
|
|
||||||
|
|
||||||
backend:
|
|
||||||
image: registry.liquidrinu.com/fusero-backend:latest
|
|
||||||
port: 14000
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: "200m"
|
|
||||||
memory: "256Mi"
|
|
||||||
limits:
|
|
||||||
cpu: "500m"
|
|
||||||
memory: "512Mi"
|
|
||||||
env:
|
|
||||||
POSTGRES_HOST: postgres-service
|
|
||||||
POSTGRES_HOSTNAME: 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
|
|
||||||
port: 80
|
|
||||||
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
|
|
||||||
dbName: fusero-db
|
|
||||||
user: prod_admin
|
|
||||||
password: ${POSTGRES_PASSWORD}
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: "200m"
|
|
||||||
memory: "256Mi"
|
|
||||||
limits:
|
|
||||||
cpu: "500m"
|
|
||||||
memory: "512Mi"
|
|
22
chart/values.prod.yml
Normal file
22
chart/values.prod.yml
Normal file
@ -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
|
@ -1,10 +1,14 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext", "ES2015"],
|
"lib": [
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable",
|
||||||
|
"ESNext",
|
||||||
|
"ES2015"
|
||||||
|
],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
@ -12,7 +16,6 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
@ -22,7 +25,6 @@
|
|||||||
"noImplicitReturns": false,
|
"noImplicitReturns": false,
|
||||||
"noImplicitThis": false,
|
"noImplicitThis": false,
|
||||||
"alwaysStrict": false
|
"alwaysStrict": false
|
||||||
|
|
||||||
/* Additional Options */
|
/* Additional Options */
|
||||||
// "forceConsistentCasingInFileNames": true,
|
// "forceConsistentCasingInFileNames": true,
|
||||||
// "strictNullChecks": true,
|
// "strictNullChecks": true,
|
||||||
@ -32,6 +34,12 @@
|
|||||||
// "incremental": true,
|
// "incremental": true,
|
||||||
// "esModuleInterop": true
|
// "esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": [
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"src"
|
||||||
}
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -3,21 +3,12 @@ import { PostgreSqlDriver } from '@mikro-orm/postgresql';
|
|||||||
import { Migrator } from '@mikro-orm/migrations';
|
import { Migrator } from '@mikro-orm/migrations';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
// Load environment variables if not in Kubernetes
|
|
||||||
if (process.env.KUBERNETES_SERVICE_HOST === undefined) {
|
if (process.env.KUBERNETES_SERVICE_HOST === undefined) {
|
||||||
dotenv.config({ override: true });
|
dotenv.config({ override: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
// Validate required environment variables
|
|
||||||
const requiredEnvVars = ['POSTGRES_DB', 'POSTGRES_USER', 'POSTGRES_PASSWORD'];
|
|
||||||
const missingEnvVars = requiredEnvVars.filter(envVar => !process.env[envVar]);
|
|
||||||
|
|
||||||
if (missingEnvVars.length > 0 && isProduction) {
|
|
||||||
throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const config: Options = {
|
const config: Options = {
|
||||||
driver: PostgreSqlDriver,
|
driver: PostgreSqlDriver,
|
||||||
entities: [
|
entities: [
|
||||||
@ -34,14 +25,14 @@ const config: Options = {
|
|||||||
warnWhenNoEntities: true,
|
warnWhenNoEntities: true,
|
||||||
disableDynamicFileAccess: false,
|
disableDynamicFileAccess: false,
|
||||||
},
|
},
|
||||||
dbName: process.env.POSTGRES_DB || 'fusero-boilerplate-db',
|
dbName: process.env.POSTGRES_NAME || 'fusero-boilerplate-db',
|
||||||
host: process.env.POSTGRES_HOSTNAME || 'localhost',
|
host: process.env.POSTGRES_HOSTNAME || 'localhost',
|
||||||
port: Number(process.env.POSTGRES_PORT) || 5432,
|
port: Number(process.env.POSTGRES_PORT) || 5432,
|
||||||
user: process.env.POSTGRES_USER || 'root',
|
user: process.env.POSTGRES_USER || 'root',
|
||||||
password: process.env.POSTGRES_PASSWORD || 'root123',
|
password: process.env.POSTGRES_PASSWORD || 'root123',
|
||||||
debug: !isProduction,
|
debug: !isProduction,
|
||||||
migrations: {
|
migrations: {
|
||||||
tableName: process.env.POSTGRES_DB,
|
tableName: process.env.POSTGRES_NAME,
|
||||||
path: isProduction ? './dist/src/database/migrations' : './src/database/migrations',
|
path: isProduction ? './dist/src/database/migrations' : './src/database/migrations',
|
||||||
glob: '!(*.d).{js,ts}',
|
glob: '!(*.d).{js,ts}',
|
||||||
transactional: true,
|
transactional: true,
|
||||||
@ -52,13 +43,6 @@ const config: Options = {
|
|||||||
snapshot: true,
|
snapshot: true,
|
||||||
emit: 'ts',
|
emit: 'ts',
|
||||||
},
|
},
|
||||||
// Add connection pool settings
|
|
||||||
pool: {
|
|
||||||
min: 2,
|
|
||||||
max: 10,
|
|
||||||
idleTimeoutMillis: 30000,
|
|
||||||
acquireTimeoutMillis: 30000,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -1 +0,0 @@
|
|||||||
# trigger
|
|
Loading…
Reference in New Issue
Block a user