Compare commits
2 Commits
cb08843956
...
501addc909
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
501addc909 | ||
|
|
ee65d87899 |
@ -23,8 +23,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
shell: sh
|
shell: sh
|
||||||
timeout-minutes: 15
|
timeout-minutes: 12
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${GITHUB_REF_NAME}"
|
BRANCH="${GITHUB_REF_NAME}"
|
||||||
echo "Deploying branch: ${BRANCH}"
|
echo "Deploying branch: ${BRANCH}"
|
||||||
ssh -o StrictHostKeyChecking=no -p 2234 devops@172.17.0.1 "bash /opt/services/scripts/depcanina.sh ${BRANCH}"
|
ssh -o StrictHostKeyChecking=no \
|
||||||
|
-o ServerAliveInterval=30 \
|
||||||
|
-o ServerAliveCountMax=30 \
|
||||||
|
-p 2234 devops@172.17.0.1 \
|
||||||
|
"sudo bash /opt/services/scripts/depcanina.sh ${BRANCH}"
|
||||||
|
|||||||
@ -5,6 +5,8 @@ ENV NEXT_PUBLIC_API_URL=https://stageapi.canina.ir/api
|
|||||||
COPY frontend/application/package*.json ./
|
COPY frontend/application/package*.json ./
|
||||||
RUN npm ci --prefer-offline --no-audit
|
RUN npm ci --prefer-offline --no-audit
|
||||||
COPY frontend/application ./
|
COPY frontend/application ./
|
||||||
|
ARG NEXT_PUBLIC_API_URL
|
||||||
|
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:20-alpine AS admin-builder
|
FROM node:20-alpine AS admin-builder
|
||||||
@ -13,6 +15,8 @@ ENV VITE_API_URL=https://stageapi.canina.ir/api
|
|||||||
COPY frontend/admin-panel/package*.json ./
|
COPY frontend/admin-panel/package*.json ./
|
||||||
RUN npm ci --prefer-offline --no-audit
|
RUN npm ci --prefer-offline --no-audit
|
||||||
COPY frontend/admin-panel ./
|
COPY frontend/admin-panel ./
|
||||||
|
ARG VITE_API_URL
|
||||||
|
ENV VITE_API_URL=$VITE_API_URL
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:20-alpine
|
FROM node:20-alpine
|
||||||
|
|||||||
@ -15,6 +15,7 @@ COPY --from=builder /app/node_modules ./node_modules
|
|||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
COPY --from=builder /app/prisma ./prisma
|
COPY --from=builder /app/prisma ./prisma
|
||||||
COPY --from=builder /app/prisma/scientificTerms.ts ./prisma/scientificTerms.ts
|
COPY --from=builder /app/prisma/scientificTerms.ts ./prisma/scientificTerms.ts
|
||||||
|
RUN chown -R node:node /app
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
USER node
|
USER node
|
||||||
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main"]
|
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main"]
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { PrismaClient } from '@prisma/client';
|
|||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
async function main() {
|
export async function main() {
|
||||||
console.log('Seeding Home Page Components...');
|
console.log('Seeding Home Page Components...');
|
||||||
|
|
||||||
// 1. Seed Hero Banners
|
// 1. Seed Hero Banners
|
||||||
|
|||||||
@ -151,7 +151,7 @@ const PRODUCT_TAGLINES_MAP: Record<string, string> = {
|
|||||||
'canina-novagard-green-pfotenpflege': "مومیایی محافظ پنجهها برای التیام خشکی، قرمزی و ترک خوردگی پنجهها در برف و سرما"
|
'canina-novagard-green-pfotenpflege': "مومیایی محافظ پنجهها برای التیام خشکی، قرمزی و ترک خوردگی پنجهها در برف و سرما"
|
||||||
};
|
};
|
||||||
|
|
||||||
async function main() {
|
export async function main() {
|
||||||
console.log('Seeding products from seed-products-data.json...');
|
console.log('Seeding products from seed-products-data.json...');
|
||||||
|
|
||||||
// 2. Read JSON data
|
// 2. Read JSON data
|
||||||
|
|||||||
@ -296,8 +296,10 @@ async function main() {
|
|||||||
// 3. Seed Products & Categories
|
// 3. Seed Products & Categories
|
||||||
console.log('Seeding Products & Categories...');
|
console.log('Seeding Products & Categories...');
|
||||||
try {
|
try {
|
||||||
const { execSync } = require('child_process');
|
const seedProducts = require('./seed-products');
|
||||||
execSync('npx ts-node prisma/seed-products.ts', { stdio: 'inherit' });
|
if (seedProducts && typeof seedProducts.main === 'function') {
|
||||||
|
await seedProducts.main();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to seed products:', err);
|
console.error('Failed to seed products:', err);
|
||||||
}
|
}
|
||||||
@ -305,8 +307,10 @@ async function main() {
|
|||||||
// 4. Seed Home Components & Testimonials
|
// 4. Seed Home Components & Testimonials
|
||||||
console.log('Seeding Home Components...');
|
console.log('Seeding Home Components...');
|
||||||
try {
|
try {
|
||||||
const { execSync } = require('child_process');
|
const seedHome = require('./seed-home');
|
||||||
execSync('npx ts-node prisma/seed-home.ts', { stdio: 'inherit' });
|
if (seedHome && typeof seedHome.main === 'function') {
|
||||||
|
await seedHome.main();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to seed home components:', err);
|
console.error('Failed to seed home components:', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import axios from 'axios';
|
|||||||
|
|
||||||
export const BASE_DOMAIN = import.meta.env.VITE_API_URL
|
export const BASE_DOMAIN = import.meta.env.VITE_API_URL
|
||||||
? import.meta.env.VITE_API_URL.replace(/\/api$/, '')
|
? import.meta.env.VITE_API_URL.replace(/\/api$/, '')
|
||||||
: (import.meta.env.DEV ? 'http://127.0.0.1:4001' : 'https://api.canina.ir');
|
: (import.meta.env.DEV ? 'http://127.0.0.1:4001' : '');
|
||||||
|
|
||||||
const baseURL = `${BASE_DOMAIN}/api`;
|
const baseURL = `${BASE_DOMAIN}/api`;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const baseURL = process.env.NEXT_PUBLIC_API_URL || (process.env.NODE_ENV === 'development' ? 'http://localhost:4001/api' : 'https://api.canina.ir/api');
|
const baseURL = process.env.NEXT_PUBLIC_API_URL || (process.env.NODE_ENV === 'development' ? 'http://localhost:4001/api' : '/api');
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
|
|||||||
@ -15,13 +15,18 @@ const nextConfig: NextConfig = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
|
// Only proxy /api in development; in production nginx handles it
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/api/:path*',
|
source: '/api/:path*',
|
||||||
destination: 'http://localhost:4001/api/:path*',
|
destination: 'http://localhost:4001/api/:path*',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|
||||||
|
|||||||
10
nginx.conf
10
nginx.conf
@ -1,6 +1,6 @@
|
|||||||
server {
|
server {
|
||||||
listen 8080;
|
listen 8080;
|
||||||
server_name canina.ir stage.canina.ir;
|
server_name __APP_HOST__;
|
||||||
|
|
||||||
# Security Headers
|
# Security Headers
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
@ -21,18 +21,19 @@ server {
|
|||||||
|
|
||||||
# Backend API
|
# Backend API
|
||||||
location /api {
|
location /api {
|
||||||
proxy_pass http://backend:3000;
|
proxy_pass http://__BACKEND_HOST__:3000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection 'upgrade';
|
proxy_set_header Connection 'upgrade';
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache_bypass $http_upgrade;
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
client_max_body_size 50M;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 8080;
|
listen 8080;
|
||||||
server_name admin.canina.ir stageadmin.canina.ir;
|
server_name __ADMIN_HOST__;
|
||||||
|
|
||||||
root /app/admin_static;
|
root /app/admin_static;
|
||||||
index index.html;
|
index index.html;
|
||||||
@ -51,11 +52,12 @@ server {
|
|||||||
|
|
||||||
# Backend API for Admin
|
# Backend API for Admin
|
||||||
location /api {
|
location /api {
|
||||||
proxy_pass http://backend:3000;
|
proxy_pass http://__BACKEND_HOST__:3000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection 'upgrade';
|
proxy_set_header Connection 'upgrade';
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache_bypass $http_upgrade;
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
client_max_body_size 50M;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
scripts/compose.prod.yml
Normal file
72
scripts/compose.prod.yml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
db_prod:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: canino_db_prod
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: canino_prod
|
||||||
|
POSTGRES_PASSWORD: caninopassword_prod
|
||||||
|
POSTGRES_DB: caninodb_prod
|
||||||
|
volumes:
|
||||||
|
- canina_prod_db:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- traefik_public
|
||||||
|
|
||||||
|
redis_prod:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: canino_redis_prod
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- traefik_public
|
||||||
|
|
||||||
|
backend_prod:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: canino_backend_prod
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://canino_prod:caninopassword_prod@db_prod:5432/caninodb_prod?schema=public
|
||||||
|
- REDIS_HOST=redis_prod
|
||||||
|
- REDIS_PORT=6379
|
||||||
|
- PORT=3000
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.canina-api-prod.rule=Host(`api.canina.ir`)"
|
||||||
|
- "traefik.http.routers.canina-api-prod.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.canina-api-prod.tls=true"
|
||||||
|
- "traefik.http.services.canina-api-prod.loadbalancer.server.port=3000"
|
||||||
|
depends_on:
|
||||||
|
- db_prod
|
||||||
|
- redis_prod
|
||||||
|
networks:
|
||||||
|
- traefik_public
|
||||||
|
|
||||||
|
frontend_prod:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
NEXT_PUBLIC_API_URL: "https://api.canina.ir/api"
|
||||||
|
VITE_API_URL: "https://api.canina.ir"
|
||||||
|
container_name: canino_frontend_prod
|
||||||
|
restart: always
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.canina-prod.rule=Host(`canina.ir`) || Host(`admin.canina.ir`)"
|
||||||
|
- "traefik.http.routers.canina-prod.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.canina-prod.tls=true"
|
||||||
|
- "traefik.http.services.canina-prod.loadbalancer.server.port=8080"
|
||||||
|
depends_on:
|
||||||
|
- backend_prod
|
||||||
|
networks:
|
||||||
|
- traefik_public
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik_public:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
canina_prod_db:
|
||||||
72
scripts/compose.stage.yml
Normal file
72
scripts/compose.stage.yml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
db_stage:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: canino_db_stage
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: canino_stage
|
||||||
|
POSTGRES_PASSWORD: caninopassword_stage
|
||||||
|
POSTGRES_DB: caninodb_stage
|
||||||
|
volumes:
|
||||||
|
- canina_stage_db:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- traefik_public
|
||||||
|
|
||||||
|
redis_stage:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: canino_redis_stage
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- traefik_public
|
||||||
|
|
||||||
|
backend_stage:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: canino_backend_stage
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://canino_stage:caninopassword_stage@db_stage:5432/caninodb_stage?schema=public
|
||||||
|
- REDIS_HOST=redis_stage
|
||||||
|
- REDIS_PORT=6379
|
||||||
|
- PORT=3000
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.canina-api-stage.rule=Host(`stageapi.canina.ir`)"
|
||||||
|
- "traefik.http.routers.canina-api-stage.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.canina-api-stage.tls=true"
|
||||||
|
- "traefik.http.services.canina-api-stage.loadbalancer.server.port=3000"
|
||||||
|
depends_on:
|
||||||
|
- db_stage
|
||||||
|
- redis_stage
|
||||||
|
networks:
|
||||||
|
- traefik_public
|
||||||
|
|
||||||
|
frontend_stage:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
NEXT_PUBLIC_API_URL: "https://stageapi.canina.ir/api"
|
||||||
|
VITE_API_URL: "https://stageapi.canina.ir"
|
||||||
|
container_name: canino_frontend_stage
|
||||||
|
restart: always
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.canina-stage.rule=Host(`stage.canina.ir`) || Host(`stageadmin.canina.ir`)"
|
||||||
|
- "traefik.http.routers.canina-stage.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.canina-stage.tls=true"
|
||||||
|
- "traefik.http.services.canina-stage.loadbalancer.server.port=8080"
|
||||||
|
depends_on:
|
||||||
|
- backend_stage
|
||||||
|
networks:
|
||||||
|
- traefik_public
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik_public:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
canina_stage_db:
|
||||||
77
scripts/deploy.sh
Normal file
77
scripts/deploy.sh
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export DOCKER_BUILDKIT=1
|
||||||
|
export COMPOSE_DOCKER_CLI_BUILD=1
|
||||||
|
|
||||||
|
BRANCH="${1:-develop}"
|
||||||
|
echo "Starting Deployment for branch: ${BRANCH}"
|
||||||
|
|
||||||
|
if [ "$BRANCH" == "develop" ]; then
|
||||||
|
COMPOSE_FILE="compose.stage.yml"
|
||||||
|
TARGET_DIR="/opt/services/canina/stage"
|
||||||
|
BACKEND_HOST="backend_stage"
|
||||||
|
APP_HOST="stage.canina.ir"
|
||||||
|
ADMIN_HOST="stageadmin.canina.ir"
|
||||||
|
API_URL="https://stageapi.canina.ir"
|
||||||
|
CONTAINER_PREFIX="canino_backend_stage"
|
||||||
|
else
|
||||||
|
COMPOSE_FILE="compose.prod.yml"
|
||||||
|
TARGET_DIR="/opt/services/canina/prod"
|
||||||
|
BACKEND_HOST="backend_prod"
|
||||||
|
APP_HOST="canina.ir"
|
||||||
|
ADMIN_HOST="admin.canina.ir"
|
||||||
|
API_URL="https://api.canina.ir"
|
||||||
|
CONTAINER_PREFIX="canino_backend_prod"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$TARGET_DIR"
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
if [ ! -d ".git" ]; then
|
||||||
|
echo "Cloning repository..."
|
||||||
|
git clone -b "$BRANCH" https://git.parsaaghayi.ir/parsa/canina.git .
|
||||||
|
else
|
||||||
|
echo "Pulling latest changes..."
|
||||||
|
git fetch origin "$BRANCH"
|
||||||
|
git reset --hard "origin/$BRANCH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Fixing .dockerignore..."
|
||||||
|
sed -i '/node_modules/d' .dockerignore 2>/dev/null || true
|
||||||
|
sed -i '/node_modules/d' backend/.dockerignore 2>/dev/null || true
|
||||||
|
sed -i '/^backend$/d' .dockerignore 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Disabling TS checks for fast deploy..."
|
||||||
|
sed -i 's|tsc -b \&\& vite build|vite build|g' frontend/admin-panel/package.json 2>/dev/null || true
|
||||||
|
sed -i 's|tsc \&\& vite build|vite build|g' frontend/admin-panel/package.json 2>/dev/null || true
|
||||||
|
|
||||||
|
# Copy scientificTerms.ts if needed
|
||||||
|
cp frontend/application/lib/data/scientificTerms.ts backend/prisma/scientificTerms.ts 2>/dev/null || true
|
||||||
|
|
||||||
|
# Prepare docker-compose.yml in TARGET_DIR
|
||||||
|
cp "/opt/services/canina/$COMPOSE_FILE" "$TARGET_DIR/docker-compose.yml"
|
||||||
|
sed -i 's/- REDIS_PORT: 6379/- REDIS_PORT=6379/g' "$TARGET_DIR/docker-compose.yml" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Prepare nginx.conf for current environment
|
||||||
|
echo "Patching nginx.conf for environment..."
|
||||||
|
cp nginx.conf nginx.conf.tmp
|
||||||
|
sed -i "s/__BACKEND_HOST__/${BACKEND_HOST}/g" nginx.conf.tmp
|
||||||
|
sed -i "s/__APP_HOST__/${APP_HOST}/g" nginx.conf.tmp
|
||||||
|
sed -i "s/__ADMIN_HOST__/${ADMIN_HOST}/g" nginx.conf.tmp
|
||||||
|
mv nginx.conf.tmp nginx.conf
|
||||||
|
|
||||||
|
echo "Building Docker images..."
|
||||||
|
docker compose build --build-arg NEXT_PUBLIC_API_URL="${API_URL}/api" --build-arg VITE_API_URL="${API_URL}"
|
||||||
|
|
||||||
|
echo "Starting containers..."
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
echo "Waiting for backend to start..."
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Running migrations and seeding database..."
|
||||||
|
docker exec "$CONTAINER_PREFIX" npx prisma migrate deploy || true
|
||||||
|
docker exec "$CONTAINER_PREFIX" npx prisma db seed || true
|
||||||
|
|
||||||
|
echo "Deployment for $BRANCH completed successfully!"
|
||||||
Loading…
Reference in New Issue
Block a user