diff --git a/Makefile b/Makefile index cf0ae9f..653b832 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ local-up local-down local-logs local-migrate local-test local-seed \ prod-up prod-down prod-logs prod-ps prod-migrate \ prod-cert-init prod-cert-renew \ + check-prod-files check-cert-files \ run migrate test seed-quotes DOMAIN ?= ruakb.ru @@ -47,33 +48,41 @@ local-test: local-seed: $(LOCAL_COMPOSE) exec -T backend python -m app.scripts.upsert_quotes -prod-up: +check-prod-files: + @test -f docker-compose.prod.nginx.yml || (echo "[ERROR] Missing docker-compose.prod.nginx.yml. Run: git pull"; exit 1) + +check-cert-files: check-prod-files + @test -f docker-compose.prod.cert.yml || (echo "[ERROR] Missing docker-compose.prod.cert.yml. Run: git pull"; exit 1) + @test -f deploy/nginx/edge-http-only.conf || (echo "[ERROR] Missing deploy/nginx/edge-http-only.conf. Run: git pull"; exit 1) + @test -f deploy/nginx/edge-https.conf || (echo "[ERROR] Missing deploy/nginx/edge-https.conf. Run: git pull"; exit 1) + +prod-up: check-prod-files $(PROD_COMPOSE) up -d --build $(PROD_COMPOSE) exec -T backend alembic upgrade head -prod-down: +prod-down: check-prod-files $(PROD_COMPOSE) down -prod-logs: +prod-logs: check-prod-files $(PROD_COMPOSE) logs -f --tail=200 -prod-ps: +prod-ps: check-prod-files $(PROD_COMPOSE) ps -prod-migrate: +prod-migrate: check-prod-files $(PROD_COMPOSE) exec -T backend alembic upgrade head # Initial certificate bootstrap: # 1) Start stack with edge nginx on port 80 only. # 2) Obtain cert via certbot webroot challenge. # 3) Restart stack in regular prod mode (80/443). -prod-cert-init: +prod-cert-init: check-cert-files $(CERT_COMPOSE) up -d --build db redis minio backend chat-service worker beat frontend edge $(CERT_COMPOSE) run --rm certbot certonly --webroot -w /var/www/certbot --email "$(LETSENCRYPT_EMAIL)" --agree-tos --no-eff-email -d "$(DOMAIN)" -d "$(WWW_DOMAIN)" $(PROD_COMPOSE) up -d --build edge $(PROD_COMPOSE) exec -T backend alembic upgrade head -prod-cert-renew: +prod-cert-renew: check-prod-files $(PROD_COMPOSE) run --rm certbot renew --webroot -w /var/www/certbot $(PROD_COMPOSE) exec -T edge nginx -s reload diff --git a/celerybeat-schedule b/celerybeat-schedule index d84640c..5006e33 100644 Binary files a/celerybeat-schedule and b/celerybeat-schedule differ diff --git a/deploy/nginx/edge-http-only.conf b/deploy/nginx/edge-http-only.conf new file mode 100644 index 0000000..20c067f --- /dev/null +++ b/deploy/nginx/edge-http-only.conf @@ -0,0 +1,25 @@ +server { + listen 80; + server_name ruakb.ru www.ruakb.ru; + server_tokens off; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + try_files $uri =404; + } + + location / { + proxy_pass http://frontend:80; + proxy_http_version 1.1; + 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; + } +} + +server { + listen 80 default_server; + server_name _; + return 308 http://ruakb.ru$request_uri; +} diff --git a/deploy/nginx/edge-https.conf b/deploy/nginx/edge-https.conf new file mode 100644 index 0000000..e0f33d9 --- /dev/null +++ b/deploy/nginx/edge-https.conf @@ -0,0 +1,49 @@ +server { + listen 80; + server_name ruakb.ru www.ruakb.ru; + server_tokens off; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + try_files $uri =404; + } + + location / { + return 308 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + http2 on; + server_name ruakb.ru www.ruakb.ru; + server_tokens off; + + ssl_certificate /etc/letsencrypt/live/ruakb.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ruakb.ru/privkey.pem; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + location / { + proxy_pass http://frontend:80; + proxy_http_version 1.1; + 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; + } +} + +server { + listen 80 default_server; + server_name _; + return 308 https://ruakb.ru$request_uri; +} diff --git a/docker-compose.prod.cert.yml b/docker-compose.prod.cert.yml new file mode 100644 index 0000000..2f4df3d --- /dev/null +++ b/docker-compose.prod.cert.yml @@ -0,0 +1,12 @@ +services: + edge: + ports: + - "80:80" + volumes: + - ./deploy/nginx/edge-http-only.conf:/etc/nginx/conf.d/default.conf:ro + - letsencrypt:/etc/letsencrypt + - certbot_webroot:/var/www/certbot + +volumes: + letsencrypt: + certbot_webroot: diff --git a/docker-compose.prod.nginx.yml b/docker-compose.prod.nginx.yml new file mode 100644 index 0000000..463b137 --- /dev/null +++ b/docker-compose.prod.nginx.yml @@ -0,0 +1,42 @@ +services: + edge: + image: nginx:1.27-alpine + container_name: law-edge + restart: unless-stopped + depends_on: + frontend: + condition: service_healthy + ports: + - "80:80" + - "443:443" + volumes: + - ./deploy/nginx/edge-https.conf:/etc/nginx/conf.d/default.conf:ro + - letsencrypt:/etc/letsencrypt + - certbot_webroot:/var/www/certbot + + certbot: + image: certbot/certbot:latest + container_name: law-certbot + restart: "no" + volumes: + - letsencrypt:/etc/letsencrypt + - certbot_webroot:/var/www/certbot + + frontend: + ports: [] + + backend: + ports: [] + + db: + ports: [] + + redis: + ports: [] + + minio: + ports: [] + +volumes: + letsencrypt: + certbot_webroot: