parent
commit
333d9dfefd
  1. 4
      Dockerfile
  2. 21
      Jenkinsfile
  3. 24
      docker-compose.yml
  4. 13
      docker/40-config-js.sh
  5. 18
      docker/nginx/default.conf.template
  6. 6
      runner.sh
  7. 2
      src/services/http.ts

4
Dockerfile

@ -8,9 +8,9 @@ FROM node:22-alpine AS builder
WORKDIR /app WORKDIR /app
# نصب وابستگی‌ها با کش npm و وابسته به package-lock # نصب وابستگی‌ها با کش npm و وابسته به package-lock
# نکته: npm ci درست است؛ اگر fail شد یعنی package-lock.json باید sync شود.
# نکته: npm install درست است؛ اگر fail شد یعنی package-lock.json باید sync شود.
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
RUN --mount=type=cache,target=/root/.npm npm install
# کپی سورس و build # کپی سورس و build
COPY . . COPY . .

21
Jenkinsfile

@ -0,0 +1,21 @@
pipeline {
environment {
production_server_ip = "88.99.212.243"
production_server_name = "newhorizon_germany_001_server"
project_path = "/projects/aqila/aqila_panel"
}
agent any
stages {
stage("deploy"){
steps{
script{
withCredentials([usernamePassword(credentialsId: production_server_name, usernameVariable: "USERNAME", passwordVariable: "PASSWORD")]) {
sh "sshpass -p $PASSWORD ssh -p 1782 $USERNAME@$production_server_ip -o StrictHostKeyChecking=no \"cd $project_path && ./runner.sh\""
def lastCommit = sh(script: "git log -1 --pretty=format:\"%h - %s (%an)\"", returnStdout: true).trim()
sh "curl -F chat_id=-1002316394394 -F message_thread_id=11 -F document=@/var/jenkins_home/jobs/${env.JOB_NAME}/builds/${env.BUILD_NUMBER}/log -F caption='Project name: #${env.JOB_NAME} \nBuild status is ${currentBuild.currentResult} \nBuild url: ${BUILD_URL} \nLast Commit: ${lastCommit}' https://api.telegram.org/bot7207581748:AAFeymryw7S44D86LYfWqYK-tSNeV3TOwBs/sendDocument"
}
}
}
}
}
}

24
docker-compose.yml

@ -1,39 +1,27 @@
# docker-compose برای داشبورد حسینیه (SPA پشت nginx).
#
# منبع واحد env در پروداکشن: فایل .env.production (با env_file خوانده می‌شود).
# همهٔ متغیرها از همان یک جا می‌آیند و در runtime به کانتینر تزریق می‌شوند.
services: services:
web: web:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
image: hosseinieh-dashboard:latest
container_name: hosseinieh-dashboard
image: aqila-panel:latest
container_name: aqila_panel
restart: unless-stopped restart: unless-stopped
# ← تنها منبع متغیرهای محیط در پروداکشن
env_file: env_file:
- .env.production - .env.production
# ← پورت 5010 میزبان به 80 کانتینر (nginx)
ports: ports:
- "5010:80"
# دسترسی به بک‌اندی که روی خود میزبان اجرا می‌شود (host.docker.internal)
# extra_hosts:
# - "host.docker.internal:host-gateway"
- "5013:80"
healthcheck: healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost/healthz"]
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
interval: 30s interval: 30s
timeout: 3s timeout: 3s
retries: 3 retries: 3
start_period: 10s start_period: 10s
networks: networks:
- najm_najm
- aqila_backend_aqila
networks: networks:
najm_najm:
aqila_backend_aqila:
external: true external: true

13
docker/40-config-js.sh

@ -1,26 +1,19 @@
#!/bin/sh #!/bin/sh
# تولید /config.js از متغیرهای محیط — «منبع واحدِ env در runtime».
#
# nginx رسمی هر اسکریپت اجراییِ /docker-entrypoint.d/*.sh را پیش از استارت سرور
# اجرا می‌کند. این‌جا مقادیر env (که از .env.production → docker-compose می‌آیند)
# را در window.__ENV__ می‌نویسیم تا app آن‌ها را در runtime بخواند — بدون rebuild.
#
# توجه: کلید Soniox از این مسیر نمی‌آید — در کد هاردکد شده است (نه env، نه API).
set -eu set -eu
TARGET="/usr/share/nginx/html/config.js" TARGET="/usr/share/nginx/html/config.js"
# پیش‌فرض‌ها (هم‌راستا با fallbackهای کد). با env قابل override.
VITE_API_BASE_URL="${VITE_API_BASE_URL:-/api}" VITE_API_BASE_URL="${VITE_API_BASE_URL:-/api}"
VITE_AUTH_SCHEME="${VITE_AUTH_SCHEME:-Token}"
# escape امنِ مقادیر برای قرارگیری داخل رشتهٔ JS (backslash، کوتیشن، خط جدید).
js_escape() { js_escape() {
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e "s/'/\\\\'/g" | tr '\n' ' '
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e "s/'/\\'/g" | tr '\n' ' '
} }
{ {
echo "window.__ENV__ = {" echo "window.__ENV__ = {"
echo " VITE_API_BASE_URL: '$(js_escape "$VITE_API_BASE_URL")'," echo " VITE_API_BASE_URL: '$(js_escape "$VITE_API_BASE_URL")',"
echo " VITE_AUTH_SCHEME: '$(js_escape "$VITE_AUTH_SCHEME")',"
echo "};" echo "};"
} > "$TARGET" } > "$TARGET"

18
docker/nginx/default.conf.template

@ -25,27 +25,15 @@ server {
} }
# ── پراکسیِ API → بک‌اند (${BACKEND_UPSTREAM}) ── # ── پراکسیِ API → بک‌اند (${BACKEND_UPSTREAM}) ──
# اسلش انتهاییِ proxy_pass پیشوند «/api» را حذف می‌کند تا به ریشهٔ بک‌اند برسد
# (معادل rewrite: p.replace(/^\/api/, '') در vite.config.ts برای dev).
#
# چون به یک هاست خارجیِ HTTPS پراکسی می‌کنیم:
# • Host را برابر خودِ بک‌اند می‌گذاریم (نه $host=localhost:5010) چون Django
# روی ALLOWED_HOSTS/CORS بر اساس Host تصمیم می‌گیرد.
# • SNI را با proxy_ssl_server_name/proxy_ssl_name روشن می‌کنیم تا TLS
# handshake با نام درست انجام شود.
location /api/ { location /api/ {
proxy_pass https://${BACKEND_UPSTREAM}/;
proxy_pass http://${BACKEND_UPSTREAM};
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host ${BACKEND_UPSTREAM};
proxy_set_header Host panel.zainabiyatours.com;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
# TLS به سمت بک‌اند خارجی
proxy_ssl_server_name on;
proxy_ssl_name ${BACKEND_UPSTREAM};
# پشتیبانی از WebSocket/upgrade (برای آینده، مثلاً استریم)
# پشتیبانی از WebSocket/upgrade
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 300s; proxy_read_timeout 300s;

6
runner.sh

@ -1,2 +1,4 @@
git pull origin master && docker compose up -d --build
#!/bin/bash
git pull origin master
DOCKER_BUILDKIT=1 docker compose up -d --build web
docker exec nginx-nginx-1 nginx -s reload

2
src/services/http.ts

@ -117,7 +117,7 @@ export async function apiFetch<T = unknown>(path: string, options: ApiFetchOptio
if (!skipAuth) { if (!skipAuth) {
const token = useAuthStore.getState().token const token = useAuthStore.getState().token
const authScheme = getEnv('VITE_AUTH_SCHEME') ?? 'Bearer'
const authScheme = getEnv('VITE_AUTH_SCHEME') ?? 'Token'
if (token) finalHeaders.set('Authorization', `${authScheme} ${token}`) if (token) finalHeaders.set('Authorization', `${authScheme} ${token}`)
} }

Loading…
Cancel
Save