initial commit
This commit is contained in:
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
NEXT_HOST=east-guilan-ce.ir
|
||||||
|
LETSENCRYPT_EMAIL=admin@east-guilan-ce.ir
|
||||||
89
.github/workflows/deployment.yml
vendored
Normal file
89
.github/workflows/deployment.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
name: Deployment CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare local validation layout
|
||||||
|
run: |
|
||||||
|
cp .env.example .env
|
||||||
|
mkdir -p backend/guilan-ace-backend frontend/guilan-ace-frontend
|
||||||
|
cat <<'EOF' > backend/guilan-ace-backend/.env
|
||||||
|
DJANGO_SETTINGS_MODULE=config.settings.production
|
||||||
|
SECRET_KEY=validate
|
||||||
|
DEBUG=False
|
||||||
|
ALLOWED_HOSTS=api.example.com
|
||||||
|
DJANGO_HOST=https://api.example.com
|
||||||
|
DB_ENGINE=django.db.backends.postgresql
|
||||||
|
DB_NAME=app
|
||||||
|
DB_USER=app
|
||||||
|
DB_PASSWORD=password
|
||||||
|
DB_HOST=db
|
||||||
|
DB_PORT=5432
|
||||||
|
REDIS_PASSWORD=password
|
||||||
|
REDIS_URL=redis://:password@redis:6379/0
|
||||||
|
CELERY_BROKER_URL=redis://:password@redis:6379/0
|
||||||
|
CELERY_RESULT_BACKEND=redis://:password@redis:6379/1
|
||||||
|
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
|
||||||
|
EMAIL_HOST=localhost
|
||||||
|
EMAIL_PORT=587
|
||||||
|
EMAIL_USE_TLS=False
|
||||||
|
EMAIL_HOST_USER=
|
||||||
|
EMAIL_HOST_PASSWORD=
|
||||||
|
DEFAULT_FROM_EMAIL=noreply@example.com
|
||||||
|
JWT_SECRET_KEY=validate
|
||||||
|
JWT_ALGORITHM=HS256
|
||||||
|
JWT_ACCESS_TOKEN_LIFETIME=3600
|
||||||
|
JWT_REFRESH_TOKEN_LIFETIME=86400
|
||||||
|
CORS_ALLOWED_ORIGINS=https://frontend.example.com
|
||||||
|
FRONTEND_ROOT=https://frontend.example.com
|
||||||
|
FRONTEND_PASSWORD_RESET_PAGE=https://frontend.example.com/reset-password
|
||||||
|
FRONTEND_CALLBACK_URL=https://frontend.example.com/payments/result
|
||||||
|
ZARINPAL_MERCHANT_ID=test
|
||||||
|
ZARINPAL_USE_SANDBOX=True
|
||||||
|
ZARINPAL_CALLBACK_URL=https://api.example.com/api/payments/callback
|
||||||
|
GUNICORN_WORKERS=2
|
||||||
|
GUNICORN_THREADS=2
|
||||||
|
GUNICORN_TIMEOUT=120
|
||||||
|
EOF
|
||||||
|
cat <<'EOF' > frontend/guilan-ace-frontend/.env
|
||||||
|
VITE_API_BASE_URL=https://api.example.com
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Validate compose config
|
||||||
|
run: docker compose config
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- name: Deploy compose stack
|
||||||
|
uses: appleboy/ssh-action@v1.2.0
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
port: ${{ secrets.DEPLOY_PORT }}
|
||||||
|
script: |
|
||||||
|
set -e
|
||||||
|
cd "${{ secrets.DEPLOY_PATH }}"
|
||||||
|
git fetch --prune origin
|
||||||
|
git checkout "${{ vars.DEPLOY_BRANCH || 'main' }}"
|
||||||
|
git pull --ff-only origin "${{ vars.DEPLOY_BRANCH || 'main' }}"
|
||||||
|
docker compose up -d --build --remove-orphans
|
||||||
|
docker image prune -f
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.env
|
||||||
|
data/
|
||||||
|
certs/fullchain.pem
|
||||||
|
certs/privateKey.pem
|
||||||
|
.vscode/
|
||||||
32
README.md
Normal file
32
README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Guilan ACE Deployment
|
||||||
|
|
||||||
|
## Local and deployment layout
|
||||||
|
```text
|
||||||
|
parent-directory/
|
||||||
|
guilan-ace-backend/
|
||||||
|
guilan-ace-frontend/
|
||||||
|
guilan-ace-deployment/
|
||||||
|
backend/
|
||||||
|
Dockerfile
|
||||||
|
frontend/
|
||||||
|
Dockerfile
|
||||||
|
certs/
|
||||||
|
traefik/
|
||||||
|
docker-compose.yml
|
||||||
|
grafana-datasources.yml
|
||||||
|
nginx-static.conf
|
||||||
|
prometheus.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
1. Keep `guilan-ace-backend`, `guilan-ace-frontend`, and `guilan-ace-deployment` as sibling repositories in your local workspace.
|
||||||
|
2. Copy `.env.example` to `.env`.
|
||||||
|
3. On the deployment server, place the backend repo at `guilan-ace-deployment/backend/guilan-ace-backend`.
|
||||||
|
4. On the deployment server, place the frontend repo at `guilan-ace-deployment/frontend/guilan-ace-frontend`.
|
||||||
|
5. Create `backend/guilan-ace-backend/.env` from the backend repo sample.
|
||||||
|
6. Run `docker compose up -d --build`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Traefik terminates TLS and routes frontend, API, admin, static, media, Grafana, Prometheus, and Uptime Kuma.
|
||||||
|
- Alertmanager has been removed from this stack. Prometheus scraping and Grafana provisioning remain intact.
|
||||||
|
- Dockerfiles live only in this deployment repository, and compose is intentionally wired only for the nested production layout.
|
||||||
42
backend/Dockerfile
Normal file
42
backend/Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Set work directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN rm -f /etc/apt/sources.list.d/debian.sources && \
|
||||||
|
printf '%s\n' \
|
||||||
|
'deb http://mirror-linux.runflare.com/debian trixie main' \
|
||||||
|
'deb http://mirror-linux.runflare.com/debian-security trixie-security main' \
|
||||||
|
> /etc/apt/sources.list && \
|
||||||
|
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99no-check-valid
|
||||||
|
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
postgresql-client \
|
||||||
|
build-essential \
|
||||||
|
libpq-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
COPY requirements.txt /app/
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt -i https://package-mirror.liara.ir/repository/pypi/simple
|
||||||
|
|
||||||
|
# Copy project
|
||||||
|
COPY . /app/
|
||||||
|
|
||||||
|
# Create directories for static and media files
|
||||||
|
RUN mkdir -p /app/static /app/media
|
||||||
|
# COPY ./static/ /app/static/
|
||||||
|
|
||||||
|
# Collect static files
|
||||||
|
RUN python manage.py collectstatic --noinput || true
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers=3", "--threads=2", "--timeout=60"]
|
||||||
64
certs/fullchain.example.pem
Normal file
64
certs/fullchain.example.pem
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGEzCCBPugAwIBAgISBZf+U3m3Aftq6nyZTh1KZD6tMA0GCSqGSIb3DQEBCwUA
|
||||||
|
MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
|
||||||
|
EwNSMTIwHhcNMjYwNTE4MTQyOTI0WhcNMjYwODE2MTQyOTIzWjAeMRwwGgYDVQQD
|
||||||
|
DBMqLmVhc3QtZ3VpbGFuLWNlLmlyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
||||||
|
CgKCAgEAx0eyNPO1YVzOZGiC19l5IEedFeMe1Yf2T1srJUr9MSpDKKiE8n3AY0Jq
|
||||||
|
9WgUou9E8ZViu3cYjE8UEuP6s4W+U1iXYWMwqc6hAkSejn8mb4vSdIO1dVEW9BNM
|
||||||
|
spDgXZvbwSs6UWm+sUpxwot4hV9RlzIdSoZ4nrLRJnu7OSW/fO7xU8UNPAgPHarH
|
||||||
|
e/xBPPTeYKq+CcDb7HJUdJMYxDd1oRtZQm/Uz5rDrqf0R4DxAhUBcZXgp8zn4yH1
|
||||||
|
nPjIR+2XUCB2n4QlOfNqhiPa9JwD6ZVrUImaFBdDTZjenE/HHVJ94k1LzMTGgHRv
|
||||||
|
Cp/avx5fw4lYlY8J72eKKFK77fHQpUUk7F7klZ+CHDlrLi+RDyqiJezhVJlPuXXn
|
||||||
|
ivEWvrrVN0EZ+1pO+Xn1xqBDjJ2KP+VbGzSveGgxsO5XHy2BaGumYx7nQwhqynXk
|
||||||
|
BMKC5PCufu8zfvsd2ODSGUvIhihgsXbFRaWEWOQiLqFq3z/4GORnn09DvBqnIsKQ
|
||||||
|
LGI4xDrnb/JYmkCs5zxLey1kDWOtqRjzZ/aVzSUM8h1UOmofXKPGg/ucM1SMEqe5
|
||||||
|
qLyifgYsxZ/OUnrx6OZt1xW4eROGxovyehscaCIIMeIAhKZ4oiJhWfZ4mgb9teiF
|
||||||
|
RylJST+YXqN9/DydVe6AyIUInKAQZL5PsY36B/QK6Fckb2GnOK8CAwEAAaOCAjQw
|
||||||
|
ggIwMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB
|
||||||
|
Af8EAjAAMB0GA1UdDgQWBBTU6TZIKc65xcaDLlCRk8padrq7yjAfBgNVHSMEGDAW
|
||||||
|
gBQAtSnyLY5vMeibTK14Pvrc6QzR0jAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUH
|
||||||
|
MAKGF2h0dHA6Ly9yMTIuaS5sZW5jci5vcmcvMDEGA1UdEQQqMCiCEyouZWFzdC1n
|
||||||
|
dWlsYW4tY2UuaXKCEWVhc3QtZ3VpbGFuLWNlLmlyMBMGA1UdIAQMMAowCAYGZ4EM
|
||||||
|
AQIBMC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9yMTIuYy5sZW5jci5vcmcvNDYu
|
||||||
|
Y3JsMIIBDAYKKwYBBAHWeQIEAgSB/QSB+gD4AHcAwjF+V0UZo0XufzjespBB68fC
|
||||||
|
IVoiv3/Vta12mtkOUs0AAAGeO7NpVgAABAMASDBGAiEAmxz+oc1QeAR1J/yEe1jZ
|
||||||
|
W2hT/U3XF+5q63O+kRjQWO0CIQCGI+xwY/hhPjJr9HkPRTI5NXGt9EeVe0vMAu1s
|
||||||
|
+TMtxgB9ABqLnWsP/r+BtHk5xtIxCobW0QLU8EbiGCyd419eJiXvAAABnjuza0wA
|
||||||
|
CAAABQAUIto1BAMARjBEAiAPAyeQN4zkD/vUAAqo/8sF5uKicaul9fS9y0bUv+8d
|
||||||
|
hgIgQxClI5FycnYL6GTwBxpNWS0uWbDhoTAtj+Mw6NOGOD8wDQYJKoZIhvcNAQEL
|
||||||
|
BQADggEBAHt+R9Da16AbLPZqC5BelK6prKdmeaqkDIDO6aE1aZyuS0xxK228fPAr
|
||||||
|
zcopyWI4Onm29bAYxaeFtUwZurDyqb+jHf0AD8PC2zOFxQCvDyEO9l28yQ51hSR2
|
||||||
|
6KlTNfsUaBAdZctSseZI1wcqJ3cwZLGAJjrTXaMDDDc83UeG/bJ8syU+iGlUhhWu
|
||||||
|
e/hEFTtIQ14U6lvNLwgtsC3Ptwe5gGIauJ+wtTIkavulGrWpGLgEUQPqNFz/NmwM
|
||||||
|
+Nis+oB2qKMJfQBoQgNIVFahsnOvUTJZbx3nEKQfI/OAGQHH2fr6xpg09zbi0j/5
|
||||||
|
Y8q58R3hzVyBdJHytdcI5W/pEJmYzSE=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
52
certs/privateKey.examole.pem
Normal file
52
certs/privateKey.examole.pem
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
Fsu0vT7dhy5GJP6Qmrt7ogjJ9K4grQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
233
docker-compose.yml
Normal file
233
docker-compose.yml
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
name: east-guilan
|
||||||
|
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:v2.11
|
||||||
|
entrypoint: ["/bin/sh", "/traefik-entrypoint.sh"]
|
||||||
|
command:
|
||||||
|
- --providers.docker=true
|
||||||
|
- --providers.docker.exposedbydefault=false
|
||||||
|
- --entrypoints.web.address=:80
|
||||||
|
- --entrypoints.websecure.address=:443
|
||||||
|
- --entrypoints.web.http.redirections.entryPoint.to=websecure
|
||||||
|
- --entrypoints.web.http.redirections.entryPoint.scheme=https
|
||||||
|
- --certificatesresolvers.le.acme.email=${LETSENCRYPT_EMAIL}
|
||||||
|
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
|
||||||
|
- --certificatesresolvers.le.acme.httpchallenge=true
|
||||||
|
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
||||||
|
- --metrics.prometheus=true
|
||||||
|
- --metrics.prometheus.addEntryPointsLabels=true
|
||||||
|
- --metrics.prometheus.addRoutersLabels=true
|
||||||
|
- --metrics.prometheus.addServicesLabels=true
|
||||||
|
- --entrypoints.metrics.address=:8082
|
||||||
|
- --metrics.prometheus.entryPoint=metrics
|
||||||
|
- --api.dashboard=true
|
||||||
|
- traefik.http.routers.metrics.rule=Host(`api.east-guilan-ce.ir`) && Path(`/metrics`)
|
||||||
|
- traefik.http.routers.metrics.entrypoints=websecure
|
||||||
|
- traefik.http.routers.metrics.tls.certresolver=le
|
||||||
|
- traefik.http.services.metrics.loadbalancer.server.port=8000
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./traefik/entrypoint.sh:/traefik-entrypoint.sh:ro
|
||||||
|
- ./certs:/certs:ro
|
||||||
|
- traefik_letsencrypt:/letsencrypt
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
env_file:
|
||||||
|
- ./backend/guilan-ace-backend/.env
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $${DB_USER} -d $${DB_NAME} -h 127.0.0.1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
env_file:
|
||||||
|
- ./backend/guilan-ace-backend/.env
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- redis-server --appendonly yes --requirepass "$${REDIS_PASSWORD}"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" PING"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: ./backend/guilan-ace-backend
|
||||||
|
dockerfile: ./backend/Dockerfile
|
||||||
|
env_file:
|
||||||
|
- ./backend/guilan-ace-backend/.env
|
||||||
|
environment:
|
||||||
|
PROMETHEUS_MULTIPROC_DIR: /tmp/prometheus
|
||||||
|
tmpfs:
|
||||||
|
- /tmp/prometheus
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
volumes:
|
||||||
|
- django_static:/app/staticfiles
|
||||||
|
- django_media:/app/media
|
||||||
|
command: >
|
||||||
|
sh -c "gunicorn config.wsgi:application
|
||||||
|
--bind 0.0.0.0:8000
|
||||||
|
--workers=$${GUNICORN_WORKERS:-3}
|
||||||
|
--threads=$${GUNICORN_THREADS:-2}
|
||||||
|
--timeout=$${GUNICORN_TIMEOUT:-120}"
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.api.rule=Host(`api.east-guilan-ce.ir`) && PathPrefix(`/api`)
|
||||||
|
- traefik.http.routers.api.entrypoints=websecure
|
||||||
|
- traefik.http.routers.api.tls.certresolver=le
|
||||||
|
- traefik.http.routers.api.priority=10
|
||||||
|
- traefik.http.services.api.loadbalancer.server.port=8000
|
||||||
|
- traefik.http.routers.admin.rule=Host(`api.east-guilan-ce.ir`) && PathPrefix(`/admin`)
|
||||||
|
- traefik.http.routers.admin.entrypoints=websecure
|
||||||
|
- traefik.http.routers.admin.tls.certresolver=le
|
||||||
|
- traefik.http.routers.admin.priority=10
|
||||||
|
|
||||||
|
worker:
|
||||||
|
build:
|
||||||
|
context: ./backend/guilan-ace-backend
|
||||||
|
dockerfile: ./backend/Dockerfile
|
||||||
|
env_file:
|
||||||
|
- ./backend/guilan-ace-backend/.env
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
command: celery -A config.services.celery worker --loglevel=INFO
|
||||||
|
volumes:
|
||||||
|
- django_media:/app/media
|
||||||
|
|
||||||
|
beat:
|
||||||
|
build:
|
||||||
|
context: ./backend/guilan-ace-backend
|
||||||
|
dockerfile: ./backend/Dockerfile
|
||||||
|
env_file:
|
||||||
|
- ./backend/guilan-ace-backend/.env
|
||||||
|
depends_on:
|
||||||
|
- worker
|
||||||
|
command: celery -A config.services.celery beat --loglevel=INFO
|
||||||
|
volumes:
|
||||||
|
- django_media:/app/media
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend/guilan-ace-frontend
|
||||||
|
dockerfile: ./frontend/Dockerfile
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.frontend.rule=Host(`${NEXT_HOST}`)
|
||||||
|
- traefik.http.routers.frontend.entrypoints=websecure
|
||||||
|
- traefik.http.routers.frontend.tls.certresolver=le
|
||||||
|
- traefik.http.services.frontend.loadbalancer.server.port=80
|
||||||
|
|
||||||
|
static:
|
||||||
|
image: nginx:1.27-alpine
|
||||||
|
volumes:
|
||||||
|
- ./nginx-static.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
- django_static:/var/www/static:ro
|
||||||
|
- django_media:/var/www/media:ro
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.static.rule=Host(`api.east-guilan-ce.ir`) && PathPrefix(`/static`)
|
||||||
|
- traefik.http.routers.static.entrypoints=websecure
|
||||||
|
- traefik.http.routers.static.tls.certresolver=le
|
||||||
|
- traefik.http.routers.static.priority=20
|
||||||
|
- traefik.http.services.static.loadbalancer.server.port=80
|
||||||
|
- traefik.http.routers.media.rule=Host(`api.east-guilan-ce.ir`) && PathPrefix(`/media`)
|
||||||
|
- traefik.http.routers.media.entrypoints=websecure
|
||||||
|
- traefik.http.routers.media.tls.certresolver=le
|
||||||
|
- traefik.http.routers.media.priority=20
|
||||||
|
|
||||||
|
uptime:
|
||||||
|
image: louislam/uptime-kuma:1
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./data/uptime:/app/data
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.kuma.rule=Host(`uptime.east-guilan-ce.ir`)
|
||||||
|
- traefik.http.routers.kuma.entrypoints=websecure
|
||||||
|
- traefik.http.routers.kuma.tls.certresolver=le
|
||||||
|
- traefik.http.services.kuma.loadbalancer.server.port=3001
|
||||||
|
|
||||||
|
node_exporter:
|
||||||
|
image: prom/node-exporter:v1.9.1
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "9100:9100"
|
||||||
|
volumes:
|
||||||
|
- /:/host:ro,rslave
|
||||||
|
- /proc:/host/proc:ro
|
||||||
|
- /sys:/host/sys:ro
|
||||||
|
- /run/udev:/host/run/udev:ro
|
||||||
|
command:
|
||||||
|
- --path.rootfs=/host
|
||||||
|
- --path.procfs=/host/proc
|
||||||
|
- --path.sysfs=/host/sys
|
||||||
|
- --path.udev.data=/host/run/udev/data
|
||||||
|
|
||||||
|
redis_exporter:
|
||||||
|
image: oliver006/redis_exporter:v1.62.0
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- ./backend/guilan-ace-backend/.env
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- redis_exporter --redis.addr=redis://redis:6379 --redis.password="$${REDIS_PASSWORD}"
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.grafana.rule=Host(`grafana.east-guilan-ce.ir`)
|
||||||
|
- traefik.http.routers.grafana.entrypoints=websecure
|
||||||
|
- traefik.http.routers.grafana.tls.certresolver=le
|
||||||
|
- traefik.http.services.grafana.loadbalancer.server.port=3000
|
||||||
|
volumes:
|
||||||
|
- grafana_data:/var/lib/grafana
|
||||||
|
- ./grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasource.yml:ro
|
||||||
|
environment:
|
||||||
|
GF_SECURITY_ADMIN_USER: admin
|
||||||
|
GF_SECURITY_ADMIN_PASSWORD: changeMeNow
|
||||||
|
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||||
|
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus
|
||||||
|
volumes:
|
||||||
|
- prometheus_data:/prometheus
|
||||||
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.prom.rule=Host(`prometheus.east-guilan-ce.ir`)
|
||||||
|
- traefik.http.routers.prom.entrypoints=websecure
|
||||||
|
- traefik.http.routers.prom.tls.certresolver=le
|
||||||
|
- traefik.http.services.prom.loadbalancer.server.port=9090
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
traefik_letsencrypt:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
django_media:
|
||||||
|
django_static:
|
||||||
|
prometheus_data:
|
||||||
|
grafana_data:
|
||||||
30
frontend/Dockerfile
Normal file
30
frontend/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm config set registry https://package-mirror.liara.ir/repository/npm/ --global
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production image
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy built files to nginx
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Copy nginx configuration
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
7
grafana-datasources.yml
Normal file
7
grafana-datasources.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
datasources:
|
||||||
|
- name: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
access: proxy
|
||||||
|
url: http://prometheus:9090
|
||||||
|
isDefault: true
|
||||||
26
nginx-static.conf
Normal file
26
nginx-static.conf
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Django collected static files
|
||||||
|
location /static/ {
|
||||||
|
alias /var/www/static/;
|
||||||
|
access_log off;
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Django media (user uploads)
|
||||||
|
location /media/ {
|
||||||
|
alias /var/www/media/;
|
||||||
|
access_log off;
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
}
|
||||||
|
|
||||||
|
# No dotfiles
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
26
prometheus.yml
Normal file
26
prometheus.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
evaluation_interval: 15s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: "prometheus"
|
||||||
|
static_configs:
|
||||||
|
- targets: ["prometheus:9090"]
|
||||||
|
|
||||||
|
- job_name: "node"
|
||||||
|
static_configs:
|
||||||
|
- targets: ["node_exporter:9100"]
|
||||||
|
|
||||||
|
- job_name: "traefik"
|
||||||
|
metrics_path: /metrics
|
||||||
|
static_configs:
|
||||||
|
- targets: ["traefik:8082"]
|
||||||
|
|
||||||
|
- job_name: "redis"
|
||||||
|
static_configs:
|
||||||
|
- targets: ["redis_exporter:9121"]
|
||||||
|
|
||||||
|
- job_name: "django"
|
||||||
|
metrics_path: /metrics
|
||||||
|
static_configs:
|
||||||
|
- targets: ["web:8000"]
|
||||||
41
traefik/entrypoint.sh
Normal file
41
traefik/entrypoint.sh
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DYNAMIC_DIR="/etc/traefik/dynamic"
|
||||||
|
TLS_CONFIG="${DYNAMIC_DIR}/custom-tls.yml"
|
||||||
|
CERT_FILE=""
|
||||||
|
KEY_FILE=""
|
||||||
|
|
||||||
|
for candidate in /certs/fullchain.pem /certs/fullchain.crt; do
|
||||||
|
if [ -s "$candidate" ]; then
|
||||||
|
CERT_FILE="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for candidate in /certs/privateKey.pem /certs/privatekey.pem /certs/privkey.pem; do
|
||||||
|
if [ -s "$candidate" ]; then
|
||||||
|
KEY_FILE="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$DYNAMIC_DIR"
|
||||||
|
rm -f "$TLS_CONFIG"
|
||||||
|
|
||||||
|
if [ -n "$CERT_FILE" ] && [ -n "$KEY_FILE" ]; then
|
||||||
|
cat >"$TLS_CONFIG" <<EOF
|
||||||
|
tls:
|
||||||
|
certificates:
|
||||||
|
- certFile: ${CERT_FILE}
|
||||||
|
keyFile: ${KEY_FILE}
|
||||||
|
EOF
|
||||||
|
echo "Traefik: loaded custom TLS certificate from ${CERT_FILE}"
|
||||||
|
else
|
||||||
|
echo "Traefik: custom TLS certificate not found, falling back to ACME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec traefik \
|
||||||
|
--providers.file.directory="${DYNAMIC_DIR}" \
|
||||||
|
--providers.file.watch=true \
|
||||||
|
"$@"
|
||||||
Reference in New Issue
Block a user