diff --git a/.gitignore b/.gitignore index 9c136d6..e661c59 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ backend/logs/ backend/.pytest_cache/ backend/qlockify-backend-deployment -frontend/qlockify-frontend-deployment \ No newline at end of file +frontend/qlockify-frontend-deployment +nginx/certs/* +!nginx/certs/.gitkeep diff --git a/README.md b/README.md index 0414371..0e7a0c3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ This repository is the deployment layer only. -Docker builds read from the local `./backend` and `./frontend` directories inside this repository. +Docker builds now read from nested application source directories inside this repository: +- `./backend/qlockify-backend-deployment` +- `./frontend/qlockify-frontend-deployment` + Those directories are expected to contain the backend and frontend application source before you build for deployment. ## Local structure @@ -12,7 +15,9 @@ The expected deployment layout is: ```text qlockify-deployment/ backend/ + qlockify-backend-deployment/ frontend/ + qlockify-frontend-deployment/ nginx/ postgres/ docker-compose.yml @@ -21,12 +26,12 @@ qlockify-deployment/ ## Deployment flow 1. Put your application source into: - - `./backend` - - `./frontend` + - `./backend/qlockify-backend-deployment` + - `./frontend/qlockify-frontend-deployment` 2. Configure deployment env files: - `./.env` - - `./backend/.env` - - `./frontend/.env` + - `./backend/qlockify-backend-deployment/.env` + - `./frontend/qlockify-frontend-deployment/.env` 3. From `qlockify-deployment`, build and start the stack: ```powershell @@ -35,6 +40,42 @@ docker compose up --build The backend container runs database migrations and `collectstatic` on startup, then serves Django with Gunicorn using `config.wsgi:application`. +## Domain setup + +The Nginx config is prepared for: +- `qlockify.ir` +- `www.qlockify.ir` + +Requests to `www.qlockify.ir` are redirected to `qlockify.ir`. + +Before bringing the stack up in production: +1. Point the DNS `A` or `AAAA` records for `qlockify.ir` and `www.qlockify.ir` to your server. +2. Set the backend env values in `./backend/qlockify-backend-deployment/.env`: + - `DJANGO_ALLOWED_HOSTS=qlockify.ir,www.qlockify.ir` + - `CORS_ALLOWED_ORIGINS=https://qlockify.ir,https://www.qlockify.ir` + - `CSRF_TRUSTED_ORIGINS=https://qlockify.ir,https://www.qlockify.ir` + - `BASE_URL=https://qlockify.ir` +3. Set the frontend env value in `./frontend/qlockify-frontend-deployment/.env`: + - `VITE_API_BASE_URL=https://qlockify.ir/api` + +## SSL certificates + +HTTPS is configured through Nginx if you place these files in: + +```text +./nginx/certs/fullchain.pem +./nginx/certs/privkey.pem +``` + +The repo keeps `./nginx/certs/.gitkeep` only. Actual certificate files are ignored by git. + +With the current Nginx config: +- `http://qlockify.ir` redirects to `https://qlockify.ir` +- `http://www.qlockify.ir` redirects to `https://qlockify.ir` +- `https://www.qlockify.ir` redirects to `https://qlockify.ir` + +Make sure port `443` is open on the server firewall before starting the stack. + ## SSE Notifications Notifications now use Server-Sent Events at `/api/notifications/stream/`. diff --git a/backend/.env.sample b/backend/.env.sample index eb55a22..d9c6726 100644 --- a/backend/.env.sample +++ b/backend/.env.sample @@ -2,10 +2,10 @@ ENVIRONMENT=development DEBUG=True -# Django Core -DJANGO_SETTINGS_MODULE=config.settings -DJANGO_SECRET_KEY= -DJANGO_ALLOWED_HOSTS= +# Django Core +DJANGO_SETTINGS_MODULE=config.settings +DJANGO_SECRET_KEY= +DJANGO_ALLOWED_HOSTS=qlockify.ir,www.qlockify.ir # Database POSTGRES_DB=app_db @@ -14,9 +14,9 @@ POSTGRES_PASSWORD=app_password POSTGRES_HOST=db POSTGRES_PORT=5432 -# CORS / CSRF -CORS_ALLOWED_ORIGINS=https://app.example.com -CSRF_TRUSTED_ORIGINS=https://app.example.com +# CORS / CSRF +CORS_ALLOWED_ORIGINS=https://qlockify.ir,https://www.qlockify.ir +CSRF_TRUSTED_ORIGINS=https://qlockify.ir,https://www.qlockify.ir # JWT ACCESS_TOKEN_LIFETIME=5 @@ -40,5 +40,5 @@ CELERY_RESULT_BACKEND=redis://redis:6379/0 LANGUAGE_CODE=en-us TIME_ZONE=Asia/Tehran -SMS_APIKEY= -BASE_URL= +SMS_APIKEY= +BASE_URL=https://qlockify.ir diff --git a/docker-compose.yml b/docker-compose.yml index 5673a84..5ce48bd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,17 +72,18 @@ services: expose: - "80" - nginx: - image: nginx:alpine - restart: always - ports: - - "80:80" - # - "443:443" # Uncomment when adding SSL - volumes: - - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro - - ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro - - static_data:/usr/share/nginx/html/staticfiles:ro - - media_data:/usr/share/nginx/html/mediafiles:ro + nginx: + image: nginx:alpine + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro + - ./nginx/certs:/etc/nginx/certs:ro + - ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro + - static_data:/usr/share/nginx/html/staticfiles:ro + - media_data:/usr/share/nginx/html/mediafiles:ro depends_on: - backend - frontend diff --git a/frontend/.env.sample b/frontend/.env.sample index 77ce981..3b14e2d 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -1 +1 @@ -VITE_API_BASE_URL=http://localhost/api +VITE_API_BASE_URL=https://qlockify.ir/api diff --git a/nginx/certs/.gitkeep b/nginx/certs/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/nginx/certs/.gitkeep @@ -0,0 +1 @@ + diff --git a/nginx/nginx.conf b/nginx/nginx.conf index fd1b9b9..b22705d 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,36 +1,65 @@ -server { - listen 80; - server_name localhost; - - client_max_body_size 100M; - sendfile on; - - # Static and Media files - location /static/ { - alias /usr/share/nginx/html/staticfiles/; - expires 30d; - access_log off; - } - - location /media/ { - alias /usr/share/nginx/html/mediafiles/; - expires 30d; - access_log off; - } - - # Protect API Documentation with Basic Auth (from your old project) - location ~ ^/(docs|redoc|openapi.json|api/docs|api/redoc|api/openapi.json|api/v1/docs) { - auth_basic "Restricted API Documentation"; - auth_basic_user_file /etc/nginx/.htpasswd; - - proxy_pass http://backend:8000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - # Standard API Proxy +server { + listen 80; + server_name qlockify.ir www.qlockify.ir; + + return 301 https://qlockify.ir$request_uri; +} + +server { + listen 443 ssl; + http2 on; + server_name www.qlockify.ir; + + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + + return 301 https://qlockify.ir$request_uri; +} + +server { + listen 443 ssl; + http2 on; + server_name qlockify.ir; + + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + + client_max_body_size 100M; + sendfile on; + + # Static and Media files + location /static/ { + alias /usr/share/nginx/html/staticfiles/; + expires 30d; + access_log off; + } + + location /media/ { + alias /usr/share/nginx/html/mediafiles/; + expires 30d; + access_log off; + } + + # Protect API Documentation with Basic Auth + location ~ ^/(docs|redoc|openapi.json|api/docs|api/redoc|api/openapi.json|api/v1/docs) { + auth_basic "Restricted API Documentation"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location /api/notifications/stream/ { proxy_pass http://backend:8000; proxy_http_version 1.1; @@ -53,8 +82,7 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } - - # Admin Panel Proxy + location /admin/ { proxy_pass http://backend:8000; proxy_set_header Host $host; @@ -62,11 +90,12 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } - - # Frontend Proxy - location / { - proxy_pass http://frontend:80; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } -} + + location / { + proxy_pass http://frontend:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +}