From 8e766b540e0edeab5c3808afbf73f3d9bf0863ff Mon Sep 17 00:00:00 2001 From: valitovgaziz Date: Fri, 12 Jun 2026 12:22:19 +0500 Subject: [PATCH] feat: CI/CD, per-domain HTTPS, backup, config generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sites.yml — единый источник истины для всех сайтов - generate-configs.sh — генератор nginx конфигов, certbot domains.txt, .env - nginx: per-domain HTTPS (вместо all-or-nothing switch-config) - certbot: единый renew-all.sh, динамический init (без 5 дублирующих скриптов) - backup: контейнер с pg_dump + rclone (Яндекс.Диск), ежедневно в 3AM - Gitea + Gitea Runner в docker-compose (self-hosted Git + CI/CD) - .gitea/workflows/deploy.yml — CI/CD pipeline: push → авто-деплой - Makefile: generate-configs, reconfig, deploy, backup, restore, gitea, help --- .gitea/workflows/deploy.yml | 52 ++ main_dc/.env | 13 +- main_dc/Makefile | 119 ++++- main_dc/backup/Dockerfile | 11 + main_dc/backup/rclone.conf.example | 8 + main_dc/backup/scripts/backup.sh | 47 ++ main_dc/backup/scripts/restore.sh | 43 ++ main_dc/certbot/Dockerfile | 16 +- main_dc/certbot/domains.txt | 10 + main_dc/certbot/scripts/crontab.txt | 2 +- main_dc/certbot/scripts/init-certbot.sh | 85 +--- main_dc/certbot/scripts/renew-all.sh | 17 + main_dc/docker-compose.yml | 88 +++- main_dc/generate-configs.sh | 474 ++++++++++++++++++ main_dc/nginx/Dockerfile | 31 +- main_dc/nginx/conf.available/00-http.conf | 14 + .../nginx/conf.available/10-yalarba.http.conf | 38 ++ .../nginx/conf.available/10-yalarba.ssl.conf | 48 ++ .../conf.available/20-valitovgaziz.http.conf | 38 ++ .../conf.available/20-valitovgaziz.ssl.conf | 48 ++ .../conf.available/30-easysite102.http.conf | 38 ++ .../conf.available/30-easysite102.ssl.conf | 48 ++ .../40-begushiybashkir.http.conf | 39 ++ .../40-begushiybashkir.ssl.conf | 48 ++ .../50-begushiybashkir_idn.http.conf | 39 ++ .../50-begushiybashkir_idn.ssl.conf | 48 ++ main_dc/nginx/conf.d/.gitkeep | 0 main_dc/nginx/entrypoint.sh | 37 ++ main_dc/nginx/nginx-http.conf | 27 +- main_dc/nginx/nginx-ssl.conf | 303 +++-------- main_dc/sites.yml | 49 ++ 31 files changed, 1535 insertions(+), 343 deletions(-) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 main_dc/backup/Dockerfile create mode 100644 main_dc/backup/rclone.conf.example create mode 100644 main_dc/backup/scripts/backup.sh create mode 100644 main_dc/backup/scripts/restore.sh create mode 100644 main_dc/certbot/domains.txt create mode 100644 main_dc/certbot/scripts/renew-all.sh create mode 100755 main_dc/generate-configs.sh create mode 100644 main_dc/nginx/conf.available/00-http.conf create mode 100644 main_dc/nginx/conf.available/10-yalarba.http.conf create mode 100644 main_dc/nginx/conf.available/10-yalarba.ssl.conf create mode 100644 main_dc/nginx/conf.available/20-valitovgaziz.http.conf create mode 100644 main_dc/nginx/conf.available/20-valitovgaziz.ssl.conf create mode 100644 main_dc/nginx/conf.available/30-easysite102.http.conf create mode 100644 main_dc/nginx/conf.available/30-easysite102.ssl.conf create mode 100644 main_dc/nginx/conf.available/40-begushiybashkir.http.conf create mode 100644 main_dc/nginx/conf.available/40-begushiybashkir.ssl.conf create mode 100644 main_dc/nginx/conf.available/50-begushiybashkir_idn.http.conf create mode 100644 main_dc/nginx/conf.available/50-begushiybashkir_idn.ssl.conf create mode 100644 main_dc/nginx/conf.d/.gitkeep create mode 100755 main_dc/nginx/entrypoint.sh create mode 100644 main_dc/sites.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..e1dced5 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,52 @@ +name: Deploy +on: + push: + branches: [main] + paths: + - 'main_dc/**' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Deploy + run: | + cd /home/gaziz/artefacts/tp/main_dc + git pull origin main + + # Если изменился sites.yml — генерируем конфиги + if git diff --name-only HEAD~1 HEAD | grep -q 'main_dc/sites.yml'; then + echo "→ sites.yml changed, generating configs..." + bash generate-configs.sh + fi + + # Авто-детект и пересборка изменённых сервисов + echo "→ Detecting changed services..." + CHANGED=$(git diff --name-only HEAD~1 HEAD | grep -oP 'main_dc/\K[^/]+' | sort -u) + for svc in $CHANGED; do + svc_name="$svc" + # маппинг директорий на имена compose-сервисов + case "$svc" in + BB) svc_name="api_bb" ;; + valitovgaziz) svc_name="valitovgaziz" ;; + nginx|certbot|backup|gitea) svc_name="$svc" ;; + api_bb|api_yal|analytics|db) svc_name="$svc" ;; + yalarba) svc_name="yalarba" ;; + *) svc_name="" ;; + esac + if [ -n "$svc_name" ] && grep -q "^ $svc_name:" docker-compose.yml; then + echo " → Rebuilding $svc_name..." + make stop_$svc_name build_$svc_name start_$svc_name || \ + make stop_$svc build_$svc start_$svc 2>/dev/null || \ + true + fi + done + + # Nginx всегда перезапускаем если изменились конфиги + if echo "$CHANGED" | grep -q 'nginx\|sites.yml'; then + echo " → Reloading nginx..." + docker compose exec -T nginx nginx -s reload 2>/dev/null || \ + docker compose restart nginx + fi diff --git a/main_dc/.env b/main_dc/.env index 54f6630..fd06b63 100644 --- a/main_dc/.env +++ b/main_dc/.env @@ -1,12 +1,11 @@ -#CERTBOT NGINX VARIABLES - EMAIL=valitovgaziz@yandex.ru -DOMAINS_yalarba=yalarba.ru,www.yalarba.ru -DOMAINS_valitovgaziz=valitovgaziz.ru,www.valitovgaziz.ru -DOMAINS_easysite102=easysite102.ru,www.easysite102.ru -DOMAINS_begushiybashkir=xn--80abahjtcfl5d0a8di.xn--p1ai,www.xn--80abahjtcfl5d0a8di.xn--p1ai -DOMAINS_begushiybashkir_latin=begushiybashkir.ru,www.begushiybashkir.ru +#CERTBOT NGINX VARIABLES — авто-сгенерировано, не редактировать вручную ALL_DOMAINS=yalarba.ru,www.yalarba.ru,valitovgaziz.ru,www.valitovgaziz.ru,easysite102.ru,www.easysite102.ru,begushiybashkir.ru,www.begushiybashkir.ru,xn--80abahjtcfl5d0a8di.xn--p1ai,www.xn--80abahjtcfl5d0a8di.xn--p1ai +DOMAINS_begushiybashkir=begushiybashkir.ru,www.begushiybashkir.ru +DOMAINS_begushiybashkir_idn=xn--80abahjtcfl5d0a8di.xn--p1ai,www.xn--80abahjtcfl5d0a8di.xn--p1ai +DOMAINS_easysite102=easysite102.ru,www.easysite102.ru +DOMAINS_valitovgaziz=valitovgaziz.ru,www.valitovgaziz.ru +DOMAINS_yalarba=yalarba.ru,www.yalarba.ru # keycloak KEYCLOAK_ADMIN_PASSWORD=your_secure_password diff --git a/main_dc/Makefile b/main_dc/Makefile index 395790f..9d00503 100644 --- a/main_dc/Makefile +++ b/main_dc/Makefile @@ -237,4 +237,121 @@ start_yalarba: docker compose up yalarba -d # Полный цикл обновления yalarba-nuxt -yalarba: stop_yalarba git build_yalarba start_yalarba wn \ No newline at end of file +yalarba: stop_yalarba git build_yalarba start_yalarba wn + +# ═══════════════════════════════════════════════ +# НОВЫЕ ЦЕЛИ: generate-configs, deploy, backup +# ═══════════════════════════════════════════════ + +# Генерация конфигов из sites.yml +generate-configs: + bash generate-configs.sh + +# Генерация + рестарт nginx +reconfig: generate-configs + docker compose restart nginx + $(MAKE) wn + +# Авто-детект изменённых сервисов и деплой только их +deploy: git + @echo "=== Detecting changes ===" + @CHANGED=$$(git diff --name-only HEAD~1 HEAD | grep -oP 'main_dc/\K[^/]+' | sort -u); \ + for svc in $$CHANGED; do \ + case "$$svc" in \ + BB) name="api_bb" ;; \ + certbot) name="certbot" ;; \ + backup) name="backup" ;; \ + gitea) name="gitea" ;; \ + *) name="$$svc" ;; \ + esac; \ + if grep -q "^ $$name:" docker-compose.yml 2>/dev/null; then \ + echo " → Rebuilding $$name..."; \ + $(MAKE) stop_$$name build_$$name start_$$name 2>/dev/null || \ + $(MAKE) stop_$$svc build_$$svc start_$$svc 2>/dev/null || true; \ + fi; \ + done; \ + if echo "$$CHANGED" | grep -q 'sites.yml\|nginx'; then \ + echo " → Regenerating configs..."; \ + bash generate-configs.sh; \ + docker compose restart nginx; \ + fi + +# Ручной запуск бэкапа +backup: + docker compose exec backup /opt/backup.sh + +# Ручной запуск бэкапа (разовый контейнер) +backup-run: + docker compose run --rm backup /opt/backup.sh + +# Восстановление из бэкапа: make restore [DATE=2026-06-11] +restore: + docker compose run --rm backup /opt/restore.sh $(DATE) + +# Gitea — полный цикл обновления +gitea: stop_gitea git build_gitea start_gitea wn + +stop_gitea: + docker compose down gitea + +build_gitea: + docker compose build gitea --no-cache + +start_gitea: + docker compose up gitea -d + +# Gitea Runner — полный цикл +gitea-runner: stop_gitea-runner git build_gitea-runner start_gitea-runner wn + +stop_gitea-runner: + docker compose down gitea-runner + +build_gitea-runner: + docker compose build gitea-runner --no-cache + +start_gitea-runner: + docker compose up gitea-runner -d + +# Gitea first-time setup helper +gitea-setup: + @echo "=== Gitea Setup ===" + @echo "1. Open http://94.41.23.97:3001 in browser" + @echo "2. Complete initial setup (DB: SQLite3 is fine)" + @echo "3. Create admin user" + @echo "4. Create new repository 'tp' and push:" + @echo " git remote add gitea http://94.41.23.97:3001/USER/tp.git" + @echo " git push -u gitea main" + @echo "5. Register runner:" + @echo " Settings → Actions → Runners → Create Token" + @echo " Update GITEA_RUNNER_REGISTRATION_TOKEN in docker-compose.yml" + @echo " Then: docker compose up -d gitea-runner" + @echo "6. Add secrets in repo Settings → Actions → Secrets:" + @echo " (none needed — runner runs locally)" + +# Показать все доступные цели +help: + @echo "=== Make targets ===" + @echo "" + @echo "Site management:" + @echo " generate-configs — generate nginx configs from sites.yml" + @echo " reconfig — generate configs + restart nginx" + @echo "" + @echo "Deploy:" + @echo " all — full cycle all services" + @echo " deploy — auto-detect changes, rebuild only changed" + @echo " — full cycle for one service" + @echo "" + @echo "Backup:" + @echo " backup — run backup via running container" + @echo " backup-run — run backup in one-shot container" + @echo " restore DATE=... — restore from backup" + @echo "" + @echo "Gitea:" + @echo " gitea — full cycle Gitea" + @echo " gitea-runner — full cycle Runner" + @echo " gitea-setup — first-time setup instructions" + @echo "" + @echo "Monitoring:" + @echo " wn — watch docker ps" + @echo " logs_ — logs for a service" + @echo " bb_db — psql into bb_db" \ No newline at end of file diff --git a/main_dc/backup/Dockerfile b/main_dc/backup/Dockerfile new file mode 100644 index 0000000..23ec602 --- /dev/null +++ b/main_dc/backup/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:3.19 + +RUN apk add --no-cache postgresql-client rclone bash curl + +COPY scripts/ /opt/ +RUN chmod +x /opt/*.sh + +# crontab для расписания бэкапов +RUN echo "$BACKUP_TIME /opt/backup.sh > /proc/1/fd/1 2>&1" > /etc/crontabs/root + +CMD ["crond", "-f", "-l", "2"] diff --git a/main_dc/backup/rclone.conf.example b/main_dc/backup/rclone.conf.example new file mode 100644 index 0000000..7837009 --- /dev/null +++ b/main_dc/backup/rclone.conf.example @@ -0,0 +1,8 @@ +# Пример конфига rclone для Яндекс.Диска +# Скопируй в backup/rclone.conf и заполни токен +# Инструкция: https://rclone.org/yandex/ +[yadisk] +type = yandex +client_id = +client_secret = +token = {"access_token":"...","token_type":"...","expiry":"..."} diff --git a/main_dc/backup/scripts/backup.sh b/main_dc/backup/scripts/backup.sh new file mode 100644 index 0000000..12c48cc --- /dev/null +++ b/main_dc/backup/scripts/backup.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# backup.sh — ежедневный бэкап: pg_dump + файлы → локально + Яндекс.Диск +set -euo pipefail + +BACKUP_DIR="/backups/$(date +%Y-%m-%d)" +RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}" +DB_NAMES="${DB_NAMES:-mydb}" +TIMESTAMP=$(date +%H%M%S) + +mkdir -p "$BACKUP_DIR/db" "$BACKUP_DIR/files" + +echo "=== Backup $TIMESTAMP ===" + +# 1. Дампы всех БД +IFS=',' read -ra databases <<< "$DB_NAMES" +for db in "${databases[@]}"; do + db=$(echo "$db" | xargs) # trim + echo "→ Dumping database: $db" + PGPASSWORD="$DB_PASSWORD" pg_dump -h "$DB_HOST" -p "${DB_PORT:-5432}" \ + -U "$DB_USER" -d "$db" --format=custom \ + -f "$BACKUP_DIR/db/${db}-${TIMESTAMP}.dump" +done + +# 2. Архив файлов +echo "→ Archiving files..." +tar -czf "$BACKUP_DIR/files/certbot-${TIMESTAMP}.tar.gz" -C /data/certbot . 2>/dev/null || true +tar -czf "$BACKUP_DIR/files/uploads-${TIMESTAMP}.tar.gz" -C /data/uploads . 2>/dev/null || true +tar -czf "$BACKUP_DIR/files/analytics-${TIMESTAMP}.tar.gz" -C /data/analytics . 2>/dev/null || true + +# 3. Создаём symlink latest +rm -f /backups/latest +ln -sf "$BACKUP_DIR" /backups/latest + +# 4. Ротация — удаляем старше RETENTION_DAYS +find /backups -maxdepth 1 -type d -name '2*' -mtime "+$RETENTION_DAYS" -exec rm -rf {} \; 2>/dev/null || true + +echo "✓ Local backup saved to $BACKUP_DIR" + +# 5. Синхронизация с Яндекс.Диск +if command -v rclone > /dev/null 2>&1 && [ -n "${RCLONE_REMOTE:-}" ]; then + echo "→ Syncing to cloud: $RCLONE_REMOTE" + rclone sync /backups "$RCLONE_REMOTE" --progress 2>&1 || \ + echo " ⚠ Cloud sync failed (check rclone config)" + echo "✓ Cloud sync complete" +fi + +echo "=== Backup finished ===" diff --git a/main_dc/backup/scripts/restore.sh b/main_dc/backup/scripts/restore.sh new file mode 100644 index 0000000..2d8e8ab --- /dev/null +++ b/main_dc/backup/scripts/restore.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# restore.sh — восстановление из бэкапа +# Использование: docker compose run --rm backup /opt/restore.sh [дата] +set -euo pipefail + +BACKUP_DATE="${1:-latest}" +BACKUP_DIR="/backups/$BACKUP_DATE" + +if [ ! -d "$BACKUP_DIR" ]; then + echo "Ошибка: бэкап $BACKUP_DIR не найден" + echo "Доступные бэкапы:" + ls -d /backups/2* 2>/dev/null || echo " (нет бэкапов)" + exit 1 +fi + +echo "=== Restore from $BACKUP_DIR ===" + +# Восстановить БД +if [ -d "$BACKUP_DIR/db" ]; then + for dump in "$BACKUP_DIR/db"/*.dump; do + [ -f "$dump" ] || continue + db=$(basename "$dump" | sed 's/-.*//') + echo "→ Restoring database: $db" + PGPASSWORD="$DB_PASSWORD" pg_restore -h "$DB_HOST" -p "${DB_PORT:-5432}" \ + -U "$DB_USER" -d "$db" --clean --if-exists "$dump" || \ + echo " ⚠ Restore of $db had warnings (non-fatal)" + done +fi + +# Распаковать файлы +if [ -d "$BACKUP_DIR/files" ]; then + for archive in "$BACKUP_DIR/files"/*.tar.gz; do + [ -f "$archive" ] || continue + name=$(basename "$archive" | sed 's/-.*//') + target="/data/$name" + echo "→ Extracting $name to $target" + mkdir -p "$target" + tar -xzf "$archive" -C "$target" || true + done +fi + +echo "=== Restore completed ===" +echo "При необходимости перезапусти сервисы: docker compose restart" diff --git a/main_dc/certbot/Dockerfile b/main_dc/certbot/Dockerfile index 1056259..5665940 100644 --- a/main_dc/certbot/Dockerfile +++ b/main_dc/certbot/Dockerfile @@ -1,20 +1,10 @@ FROM certbot/certbot -# Проверяем наличие crond (используем command -v вместо which) -RUN if ! command -v crond > /dev/null 2>&1; then \ - echo "Cron not found. Installing cronie..."; \ - apk add --no-cache cronie; \ - else \ - echo "Cron is already installed."; \ - fi +RUN apk add --no-cache cronie docker-cli -# Создаем директории для конфигов RUN mkdir -p /etc/letsencrypt/config -# Копируем конфигурационные файлы COPY scripts/ /opt/ +RUN chmod +x /opt/*.sh -# Устанавливаем права -RUN chmod +x /opt/* - -ENTRYPOINT ["/opt/init-certbot.sh"] \ No newline at end of file +ENTRYPOINT ["/opt/init-certbot.sh"] diff --git a/main_dc/certbot/domains.txt b/main_dc/certbot/domains.txt new file mode 100644 index 0000000..47955e2 --- /dev/null +++ b/main_dc/certbot/domains.txt @@ -0,0 +1,10 @@ +yalarba.ru +www.yalarba.ru +valitovgaziz.ru +www.valitovgaziz.ru +easysite102.ru +www.easysite102.ru +begushiybashkir.ru +www.begushiybashkir.ru +xn--80abahjtcfl5d0a8di.xn--p1ai +www.xn--80abahjtcfl5d0a8di.xn--p1ai diff --git a/main_dc/certbot/scripts/crontab.txt b/main_dc/certbot/scripts/crontab.txt index 3e124f2..d3ec51a 100644 --- a/main_dc/certbot/scripts/crontab.txt +++ b/main_dc/certbot/scripts/crontab.txt @@ -1 +1 @@ -0 0 * * * root /opt/checkRenewCerts.sh > /proc/1/fd/1 2>&1 \ No newline at end of file +0 0 * * * /opt/renew-all.sh > /proc/1/fd/1 2>&1 diff --git a/main_dc/certbot/scripts/init-certbot.sh b/main_dc/certbot/scripts/init-certbot.sh index 515fa15..282c921 100644 --- a/main_dc/certbot/scripts/init-certbot.sh +++ b/main_dc/certbot/scripts/init-certbot.sh @@ -1,69 +1,32 @@ #!/bin/sh +# init-certbot.sh — точка входа certbot контейнера +set -e -# Проверяем наличие сертификатов для yalarba.ru -if [ ! -d "/etc/letsencrypt/live/yalarba.ru" ]; then - echo "Получаем новые сертификаты yalarba.ru ..." - certbot certonly --webroot \ - --config /etc/letsencrypt/config/certbot.ini \ - -w /var/www/certbot \ - -d ${DOMAINS_yalarba} -fi +echo "=== Certbot init ===" -echo "сertificates for ${DOMAINS_yalarba} is ready" +# Получаем сертификаты для всех доменов из DOMAINS_* env +env | grep '^DOMAINS_' | grep -v '^ALL_DOMAINS' | sort | while IFS='=' read -r var_name domains; do + primary_domain=$(echo "$domains" | cut -d, -f1) -# Проверяем наличие сертификатов для valitovgaziz.ru -if [ ! -d "/etc/letsencrypt/live/valitovgaziz.ru" ]; then - echo "Получаем новые сертификаты valitovgaziz ..." - certbot certonly --webroot \ - --config /etc/letsencrypt/config/certbot.ini \ - -w /var/www/certbot \ - -d ${DOMAINS_valitovgaziz} -fi + if [ ! -d "/etc/letsencrypt/live/$primary_domain" ]; then + echo "→ Получаем сертификат для $primary_domain" + certbot certonly --webroot \ + --config /etc/letsencrypt/config/certbot.ini \ + -w /var/www/certbot \ + -d "$domains" + echo "✓ Сертификат для $primary_domain получен" + else + echo "✓ Сертификат для $primary_domain уже существует" + fi +done -echo "сertificates for ${DOMAINS_valitovgaziz} is ready" - -# Проверяем наличие сертификатов для easysite102.ru -if [ ! -d "/etc/letsencrypt/live/easysite102.ru" ]; then - echo "Получаем новые сертификаты easysite102.ru ..." - certbot certonly --webroot \ - --config /etc/letsencrypt/config/certbot.ini \ - -w /var/www/certbot \ - -d ${DOMAINS_easysite102} -fi - -echo "сertificates for ${DOMAINS_easysite102} is ready" - -# Проверяем наличие сертификатов для бегущийбашкир.рф -if [ ! -d "/etc/letsencrypt/live/xn--80abahjtcfl5d0a8di.xn--p1ai" ]; then - echo "Получаем новые сертификаты xn--80abahjtcfl5d0a8di.xn--p1ai(бегущийбашкир.рф) ..." - certbot certonly --webroot \ - --config /etc/letsencrypt/config/certbot.ini \ - -w /var/www/certbot \ - -d ${DOMAINS_begushiybashkir} -fi - -echo "сertificates for ${DOMAINS_begushiybashkir} is ready" - -# Проверяем наличие сертификатов для begushiybashkir.ru -if [ ! -d "/etc/letsencrypt/live/begushiybashkir.ru" ]; then - echo "Получаем новые сертификаты begushiybashkir.ru ..." - certbot certonly --webroot \ - --config /etc/letsencrypt/config/certbot.ini \ - -w /var/www/certbot \ - -d ${DOMAINS_begushiybashkir_latin} -fi - -echo "сertificates for ${DOMAINS_begushiybashkir_latin} is ready" - -set -e # Завершаем работу, если любая команда вернёт ошибку - -# Активируем сервис cron -/usr/sbin/crond -f & -crond -f & - -# Копируем нашу собственную crontab таблицу +# Настраиваем cron для ежедневного обновления cp /opt/crontab.txt /etc/crontabs/root -# Оставляем контейнер открытым -tail -f /dev/null +# Запускаем crond в фоне +crond -b +echo "=== Init завершён, контейнер работает ===" + +# Держим контейнер живым +tail -f /dev/null diff --git a/main_dc/certbot/scripts/renew-all.sh b/main_dc/certbot/scripts/renew-all.sh new file mode 100644 index 0000000..6c9d6e5 --- /dev/null +++ b/main_dc/certbot/scripts/renew-all.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# renew-all.sh — единый скрипт обновления всех сертификатов +set -e + +echo "=== Certbot renewal ===" + +# Обновляем все сертификаты +certbot renew --webroot -w /var/www/certbot + +# Перезагружаем nginx чтобы он подхватил новые сертификаты +if command -v docker > /dev/null 2>&1; then + echo "→ Перезагружаем nginx..." + docker exec nginx nginx -s reload 2>/dev/null || \ + echo " (nginx reload не удался, возможно контейнер не запущен)" +fi + +echo "=== Renewal завершён ===" diff --git a/main_dc/docker-compose.yml b/main_dc/docker-compose.yml index f6a36a5..2029b3b 100644 --- a/main_dc/docker-compose.yml +++ b/main_dc/docker-compose.yml @@ -10,19 +10,15 @@ services: - ./certbot/config:/etc/letsencrypt/config - certbot_data:/etc/letsencrypt - certbot_www:/var/www/certbot + - /var/run/docker.sock:/var/run/docker.sock env_file: - .env environment: - EMAIL=${EMAIL} - - DOMAINS=${ALL_DOMAINS} - STAGING=0 restart: unless-stopped healthcheck: - test: - [ - "CMD-SHELL", - "test -f /etc/letsencrypt/live/$$(echo $${DOMAINS} | cut -d',' -f1)/fullchain.pem || exit 1", - ] + test: ["CMD-SHELL", "ls /etc/letsencrypt/live/*/fullchain.pem 2>/dev/null | head -1 | xargs test -f || exit 1"] interval: 30s timeout: 10s retries: 3 @@ -45,6 +41,7 @@ services: - ./stubSite:/usr/share/nginx/stub/html - ./BB/bbvue/dist:/usr/share/nginx/begushiybashkir/html - analytics_logs:/var/log/analytics:ro + - ./nginx/conf.available:/etc/nginx/conf.available:ro networks: - web-network - internal @@ -52,8 +49,6 @@ services: depends_on: easysite: condition: service_healthy - certbot: - condition: service_healthy api_bb: condition: service_healthy analytics: @@ -254,6 +249,81 @@ services: timeout: 10s retries: 3 +# ────────────────────────────────────────────── +# Gitea — self-hosted Git сервер + CI/CD +# ────────────────────────────────────────────── + gitea: + image: gitea/gitea:latest + container_name: gitea + restart: unless-stopped + ports: + - "3001:3000" + - "2222:22" + volumes: + - gitea_data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + environment: + - USER_UID=1000 + - USER_GID=1000 + - GITEA__server__DOMAIN=git.yalarba.ru + - GITEA__server__SSH_DOMAIN=94.41.23.97 + - GITEA__server__ROOT_URL=https://git.yalarba.ru + networks: + - web-network + - internal + healthcheck: + test: ["CMD", "wget", "--spider", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + + gitea-runner: + image: gitea/act_runner:latest + container_name: gitea-runner + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /home/gaziz/artefacts/tp:/home/gaziz/artefacts/tp + - gitea_runner:/data + environment: + - GITEA_INSTANCE_URL=http://gitea:3000 + - GITEA_RUNNER_REGISTRATION_TOKEN= + depends_on: + gitea: + condition: service_healthy + networks: + - internal + +# ────────────────────────────────────────────── +# Backup — ежедневные бэкапы БД + файлов → локально + Яндекс.Диск +# ────────────────────────────────────────────── + backup: + build: + context: ./backup + dockerfile: Dockerfile + container_name: backup + restart: unless-stopped + volumes: + - /var/backups/tp:/backups + - certbot_data:/data/certbot:ro + - api_bb_uploads:/data/uploads:ro + - analytics_data:/data/analytics:ro + environment: + DB_HOST: db + DB_PORT: 5432 + DB_USER: postgres + DB_PASSWORD: postgres + DB_NAMES: mydb,bb_db + RCLONE_REMOTE: "yadisk:tp-backups" + BACKUP_RETENTION_DAYS: 7 + BACKUP_TIME: "0 3 * * *" + depends_on: + db: + condition: service_healthy + networks: + - internal + volumes: certbot_data: # volume для данных Certbot certbot_www: # volume для данных Certbot @@ -261,6 +331,8 @@ volumes: api_bb_uploads: # Volume для загружаемых файлов бегущий башкир analytics_logs: # Volume для логов аналитики analytics_data: # Volume для данных аналитики + gitea_data: # Volume для Gitea + gitea_runner: # Volume для Gitea Runner networks: web-network: diff --git a/main_dc/generate-configs.sh b/main_dc/generate-configs.sh new file mode 100755 index 0000000..bdf11c1 --- /dev/null +++ b/main_dc/generate-configs.sh @@ -0,0 +1,474 @@ +#!/bin/bash +# generate-configs.sh — генератор конфигов из sites.yml +# Генерирует: nginx-http.conf, nginx-ssl.conf, certbot/domains.txt, обновляет .env +set -euo pipefail + +DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$DIR" + +SITES_YML="$DIR/sites.yml" +NGINX_DIR="$DIR/nginx" +ENV_FILE="$DIR/.env" + +if [ ! -f "$SITES_YML" ]; then + echo "Ошибка: $SITES_YML не найден" + exit 1 +fi + +echo "=== Генерация конфигов из sites.yml ===" + +# Используем python3 с quoted heredoc — предотвращает интерпретацию $ переменных bash +python3 - "$DIR" "$NGINX_DIR" "$ENV_FILE" << 'PYEOF' +import yaml, os, sys + +BASE_DIR = sys.argv[1] +NGINX_DIR = sys.argv[2] +ENV_FILE = sys.argv[3] +SITES_YML = os.path.join(BASE_DIR, "sites.yml") + +with open(SITES_YML) as f: + data = yaml.safe_load(f) + +sites = data.get("sites", {}) +if not sites: + print("Ошибка: в sites.yml нет сайтов") + sys.exit(1) + +# собираем данные +all_domains = [] +env_domains = {} +site_list = [] + +for name, cfg in sites.items(): + domain = cfg["domain"] + aliases = cfg.get("aliases", []) + + all_domains.append(domain) + all_domains.extend(aliases) + + env_key = f"DOMAINS_{name}" + env_val = ",".join([domain] + aliases) + env_domains[env_key] = env_val + + site_list.append({ + "name": name, + "domain": domain, + "aliases": aliases, + "type": cfg.get("type", "upstream"), + "upstream": cfg.get("upstream", ""), + "root": cfg.get("root", ""), + "api": cfg.get("api", {}), + }) + +env_domains["ALL_DOMAINS"] = ",".join(all_domains) + +def all_server_names(): + """Возвращает строку со всеми доменами и алиасами через пробел""" + parts = [] + for s in site_list: + parts.append(s["domain"]) + parts.extend(s["aliases"]) + return " ".join(parts) + +def all_server_names_multiline(): + """Возвращает строку с переносами для nginx server_name""" + lines = [] + for s in site_list: + lines.append(s["domain"]) + for a in s["aliases"]: + lines.append(a) + return " \\\n ".join(lines) + +# ────────────────────────────────────────────── +# 2. Генерация nginx-http.conf +# ────────────────────────────────────────────── +http_conf = f"""# Автоматически сгенерировано generate-configs.sh — не редактировать вручную +# HTTP-only конфигурация (работает когда нет сертификатов) + +server {{ + listen 80; + server_name {all_server_names_multiline()}; + + location / {{ + root /usr/share/nginx/stub/html; + index index.html; + }} + + location /.well-known/acme-challenge/ {{ + root /var/www/certbot; + }} +}} + +# Блок для HTTPS → HTTP редиректа (порт 443) +server {{ + listen 443 ssl; + server_name {all_server_names_multiline()}; + + ssl_certificate /etc/nginx/ssl/dummy.crt; + ssl_certificate_key /etc/nginx/ssl/dummy.key; + + return 301 http://$host$request_uri; +}} +""" + +http_conf_path = os.path.join(NGINX_DIR, "nginx-http.conf") +with open(http_conf_path, "w") as f: + f.write(http_conf.lstrip()) +print(f" ✓ {http_conf_path}") + +# ────────────────────────────────────────────── +# 3. Генерация nginx-ssl.conf +# ────────────────────────────────────────────── +ssl_server_blocks = [] + +for s in site_list: + server_names = " ".join([s["domain"]] + s["aliases"]) + + block = f""" +server {{ + listen 443 ssl; + server_name {server_names}; + + ssl_certificate /etc/letsencrypt/live/{s["domain"]}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{s["domain"]}/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; +""" + if s["type"] == "upstream": + block += f""" + location / {{ + proxy_pass {s["upstream"]}; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + }} +""" + elif s["type"] == "static": + block += f""" + location / {{ + root {s["root"]}; + index index.html; + try_files $uri $uri/ /index.html; + }} +""" + # API routes + for path, target in s["api"].items(): + cors_block = "" + if "/api/" in path: + cors_block = """ + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } +""" + block += f""" + location {path} {{ + proxy_pass {target}; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; +{cors_block} + }} +""" + if s["type"] == "static": + block += f""" + location /uploads/ {{ + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + }} +""" + block += "}" + ssl_server_blocks.append(block) + +ssl_conf = f"""# Автоматически сгенерировано generate-configs.sh — не редактировать вручную +# Полная HTTPS конфигурация + +# --- HTTP → HTTPS редирект --- +server {{ + listen 80; + + server_name {all_server_names()}; + + location /.well-known/acme-challenge/ {{ + root /var/www/certbot; + }} + + location /uploads/ {{ + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + }} + + location / {{ + return 301 https://$host$request_uri; + }} +}} + +# --- HTTPS серверные блоки --- +{''.join(ssl_server_blocks)} +""" + +ssl_conf_path = os.path.join(NGINX_DIR, "nginx-ssl.conf") +with open(ssl_conf_path, "w") as f: + f.write(ssl_conf.lstrip()) +print(f" ✓ {ssl_conf_path}") + +# ────────────────────────────────────────────── +# 4. Генерация per-domain конфигов (conf.available/) +# ────────────────────────────────────────────── +CONF_AVAILABLE = os.path.join(NGINX_DIR, "conf.available") +os.makedirs(CONF_AVAILABLE, exist_ok=True) + +# 00-http.conf — базовый HTTP catch-all (всегда активен) +base_http = f"""# Автоматически сгенерировано generate-configs.sh +server {{ + listen 80 default_server; + server_name _; + + location / {{ + root /usr/share/nginx/stub/html; + index index.html; + }} + + location /.well-known/acme-challenge/ {{ + root /var/www/certbot; + }} +}} +""" +path = os.path.join(CONF_AVAILABLE, "00-http.conf") +with open(path, "w") as f: + f.write(base_http.lstrip()) +print(f" ✓ conf.available/00-http.conf") + +# per-domain: SSL + HTTP fallback +ORDER = ["10", "20", "30", "40", "50", "60", "70", "80", "90"] +for idx, s in enumerate(site_list): + prefix = ORDER[idx] if idx < len(ORDER) else f"{90 + idx}" + safe_name = s["name"] + server_names = " ".join([s["domain"]] + s["aliases"]) + + # --- SSL variant --- + ssl_block = f"""# CERT_DOMAIN={s["domain"]} +# Автоматически сгенерировано generate-configs.sh +server {{ + listen 443 ssl; + server_name {server_names}; + + ssl_certificate /etc/letsencrypt/live/{s["domain"]}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{s["domain"]}/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; +""" + if s["type"] == "upstream": + ssl_block += f""" + location / {{ + proxy_pass {s["upstream"]}; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + }} +""" + elif s["type"] == "static": + ssl_block += f""" + location / {{ + root {s["root"]}; + index index.html; + try_files $uri $uri/ /index.html; + }} +""" + for path, target in s["api"].items(): + cors = "" + if "/api/" in path: + cors = """ + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } +""" + ssl_block += f""" + location {path} {{ + proxy_pass {target}; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; +{cors} + }} +""" + if s["type"] == "static": + ssl_block += f""" + location /uploads/ {{ + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + }} +""" + ssl_block += "}" + + ssl_path = os.path.join(CONF_AVAILABLE, f"{prefix}-{safe_name}.ssl.conf") + with open(ssl_path, "w") as f: + f.write(ssl_block.lstrip()) + + # --- HTTP fallback variant --- + http_block = f"""# HTTP fallback for {s["domain"]} (no SSL cert) +server {{ + listen 80; + server_name {server_names}; +""" + if s["type"] == "upstream": + http_block += f""" + location / {{ + proxy_pass {s["upstream"]}; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + }} +""" + elif s["type"] == "static": + http_block += f""" + location / {{ + root {s["root"]}; + index index.html; + try_files $uri $uri/ /index.html; + }} +""" + for path, target in s["api"].items(): + cors = "" + if "/api/" in path: + cors = """ + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } +""" + http_block += f""" + location {path} {{ + proxy_pass {target}; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; +{cors} + }} +""" + if s["type"] == "static": + http_block += f""" + location /uploads/ {{ + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + }} +""" + http_block += "}" + + http_path = os.path.join(CONF_AVAILABLE, f"{prefix}-{safe_name}.http.conf") + with open(http_path, "w") as f: + f.write(http_block.lstrip()) + + print(f" ✓ conf.available/{prefix}-{safe_name}.ssl.conf + .http.conf") + +# ────────────────────────────────────────────── +# 5. Генерация certbot/domains.txt +# ────────────────────────────────────────────── +domains_txt_path = os.path.join(BASE_DIR, "certbot", "domains.txt") +with open(domains_txt_path, "w") as f: + for d in all_domains: + f.write(d + "\n") +print(f" ✓ {domains_txt_path}") + +# ────────────────────────────────────────────── +# 6. Обновление .env +# ────────────────────────────────────────────── +if os.path.exists(ENV_FILE): + with open(ENV_FILE) as f: + env_lines = f.readlines() +else: + env_lines = [] + +new_env = [] +for line in env_lines: + stripped = line.strip() + if stripped.startswith("DOMAINS_") or stripped.startswith("ALL_DOMAINS") or "CERTBOT NGINX VARIABLES" in stripped: + continue + new_env.append(line) + +# удаляем пустые строки в начале +while new_env and not new_env[0].strip(): + new_env.pop(0) + +domain_keys = {k: v for k, v in env_domains.items()} + +insert_idx = None +for i, line in enumerate(new_env): + if line.strip().startswith("EMAIL="): + insert_idx = i + 1 + break + +env_header = "#CERTBOT NGINX VARIABLES — авто-сгенерировано, не редактировать вручную\n" +domain_lines = [f"{k}={v}\n" for k, v in sorted(domain_keys.items())] + +if insert_idx is not None: + new_env.insert(insert_idx, env_header) + for dl in reversed(domain_lines): + new_env.insert(insert_idx + 1, dl) +else: + new_env = [env_header] + domain_lines + new_env + +with open(ENV_FILE, "w") as f: + f.writelines(new_env) +print(f" ✓ {ENV_FILE} (обновлён)") + +print() +print("=== Генерация завершена ===") +print(f"Сгенерировано {len(site_list)} сайтов:") +for s in site_list: + print(f" • {s['domain']} ({s['type']})") +print() +print("Не забудь перезапустить nginx: docker compose restart nginx") +PYEOF diff --git a/main_dc/nginx/Dockerfile b/main_dc/nginx/Dockerfile index debd912..5782940 100644 --- a/main_dc/nginx/Dockerfile +++ b/main_dc/nginx/Dockerfile @@ -1,28 +1,17 @@ FROM nginx:alpine -# Установка зависимостей RUN apk add --no-cache bash openssl -# Создание директории для сертификатов -RUN mkdir -p /etc/nginx/ssl +# dummy сертификаты для nginx (нужны чтобы nginx стартовал с любым конфигом) +RUN mkdir -p /etc/nginx/ssl && \ + openssl req -x509 -nodes -days 365 \ + -newkey rsa:2048 \ + -keyout /etc/nginx/ssl/dummy.key \ + -out /etc/nginx/ssl/dummy.crt \ + -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" -# Генерация самоподписанных сертификатов (действительны 365 дней) -RUN openssl req -x509 -nodes -days 365 \ - -newkey rsa:2048 \ - -keyout /etc/nginx/ssl/dummy.key \ - -out /etc/nginx/ssl/dummy.crt \ - -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" +RUN mkdir -p /var/www/certbot /etc/nginx/conf.d /etc/nginx/conf.available -# Копируем обе конфигурации -COPY nginx-http.conf /etc/nginx/nginx-http.conf -COPY nginx-ssl.conf /etc/nginx/nginx-ssl.conf - -# Создаем симлинк по умолчанию на HTTP конфиг -RUN ln -sf /etc/nginx/nginx-http.conf /etc/nginx/conf.d/default.conf - -# Скрипт для проверки сертификатов и переключения конфига -COPY switch-config.sh /docker-entrypoint.d/switch-config.sh +# per-domain entrypoint для проверки сертификатов +COPY entrypoint.sh /docker-entrypoint.d/switch-config.sh RUN chmod +x /docker-entrypoint.d/switch-config.sh - -# Создаем необходимые директории -RUN mkdir -p /var/www/certbot \ No newline at end of file diff --git a/main_dc/nginx/conf.available/00-http.conf b/main_dc/nginx/conf.available/00-http.conf new file mode 100644 index 0000000..4f981ca --- /dev/null +++ b/main_dc/nginx/conf.available/00-http.conf @@ -0,0 +1,14 @@ +# Автоматически сгенерировано generate-configs.sh +server { + listen 80 default_server; + server_name _; + + location / { + root /usr/share/nginx/stub/html; + index index.html; + } + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } +} diff --git a/main_dc/nginx/conf.available/10-yalarba.http.conf b/main_dc/nginx/conf.available/10-yalarba.http.conf new file mode 100644 index 0000000..3bdb22b --- /dev/null +++ b/main_dc/nginx/conf.available/10-yalarba.http.conf @@ -0,0 +1,38 @@ +# HTTP fallback for yalarba.ru (no SSL cert) +server { + listen 80; + server_name yalarba.ru www.yalarba.ru; + + location / { + proxy_pass http://yalarba:3000; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + location /api/v1/ { + proxy_pass http://api_yal:8787; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/10-yalarba.ssl.conf b/main_dc/nginx/conf.available/10-yalarba.ssl.conf new file mode 100644 index 0000000..51d3637 --- /dev/null +++ b/main_dc/nginx/conf.available/10-yalarba.ssl.conf @@ -0,0 +1,48 @@ +# CERT_DOMAIN=yalarba.ru +# Автоматически сгенерировано generate-configs.sh +server { + listen 443 ssl; + server_name yalarba.ru www.yalarba.ru; + + ssl_certificate /etc/letsencrypt/live/yalarba.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/yalarba.ru/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + + location / { + proxy_pass http://yalarba:3000; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + location /api/v1/ { + proxy_pass http://api_yal:8787; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/20-valitovgaziz.http.conf b/main_dc/nginx/conf.available/20-valitovgaziz.http.conf new file mode 100644 index 0000000..545ca28 --- /dev/null +++ b/main_dc/nginx/conf.available/20-valitovgaziz.http.conf @@ -0,0 +1,38 @@ +# HTTP fallback for valitovgaziz.ru (no SSL cert) +server { + listen 80; + server_name valitovgaziz.ru www.valitovgaziz.ru; + + location / { + proxy_pass http://valitovgaziz/; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + location /api/ { + proxy_pass http://analytics:3000/; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/20-valitovgaziz.ssl.conf b/main_dc/nginx/conf.available/20-valitovgaziz.ssl.conf new file mode 100644 index 0000000..eee3762 --- /dev/null +++ b/main_dc/nginx/conf.available/20-valitovgaziz.ssl.conf @@ -0,0 +1,48 @@ +# CERT_DOMAIN=valitovgaziz.ru +# Автоматически сгенерировано generate-configs.sh +server { + listen 443 ssl; + server_name valitovgaziz.ru www.valitovgaziz.ru; + + ssl_certificate /etc/letsencrypt/live/valitovgaziz.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/valitovgaziz.ru/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + + location / { + proxy_pass http://valitovgaziz/; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + location /api/ { + proxy_pass http://analytics:3000/; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/30-easysite102.http.conf b/main_dc/nginx/conf.available/30-easysite102.http.conf new file mode 100644 index 0000000..d2443bf --- /dev/null +++ b/main_dc/nginx/conf.available/30-easysite102.http.conf @@ -0,0 +1,38 @@ +# HTTP fallback for easysite102.ru (no SSL cert) +server { + listen 80; + server_name easysite102.ru www.easysite102.ru; + + location / { + proxy_pass http://easysite:3000; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + location /api/v1/ { + proxy_pass http://api_yal:8787; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/30-easysite102.ssl.conf b/main_dc/nginx/conf.available/30-easysite102.ssl.conf new file mode 100644 index 0000000..e9b1752 --- /dev/null +++ b/main_dc/nginx/conf.available/30-easysite102.ssl.conf @@ -0,0 +1,48 @@ +# CERT_DOMAIN=easysite102.ru +# Автоматически сгенерировано generate-configs.sh +server { + listen 443 ssl; + server_name easysite102.ru www.easysite102.ru; + + ssl_certificate /etc/letsencrypt/live/easysite102.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/easysite102.ru/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + + location / { + proxy_pass http://easysite:3000; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + location /api/v1/ { + proxy_pass http://api_yal:8787; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/40-begushiybashkir.http.conf b/main_dc/nginx/conf.available/40-begushiybashkir.http.conf new file mode 100644 index 0000000..6e3d94a --- /dev/null +++ b/main_dc/nginx/conf.available/40-begushiybashkir.http.conf @@ -0,0 +1,39 @@ +# HTTP fallback for begushiybashkir.ru (no SSL cert) +server { + listen 80; + server_name begushiybashkir.ru www.begushiybashkir.ru; + + location / { + root /usr/share/nginx/begushiybashkir/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api_bb:8080/; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } + + location /uploads/ { + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/40-begushiybashkir.ssl.conf b/main_dc/nginx/conf.available/40-begushiybashkir.ssl.conf new file mode 100644 index 0000000..1c05cd1 --- /dev/null +++ b/main_dc/nginx/conf.available/40-begushiybashkir.ssl.conf @@ -0,0 +1,48 @@ +# CERT_DOMAIN=begushiybashkir.ru +# Автоматически сгенерировано generate-configs.sh +server { + listen 443 ssl; + server_name begushiybashkir.ru www.begushiybashkir.ru; + + ssl_certificate /etc/letsencrypt/live/begushiybashkir.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/begushiybashkir.ru/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + + location / { + root /usr/share/nginx/begushiybashkir/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api_bb:8080/; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } + + location /uploads/ { + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/50-begushiybashkir_idn.http.conf b/main_dc/nginx/conf.available/50-begushiybashkir_idn.http.conf new file mode 100644 index 0000000..cf66245 --- /dev/null +++ b/main_dc/nginx/conf.available/50-begushiybashkir_idn.http.conf @@ -0,0 +1,39 @@ +# HTTP fallback for xn--80abahjtcfl5d0a8di.xn--p1ai (no SSL cert) +server { + listen 80; + server_name xn--80abahjtcfl5d0a8di.xn--p1ai www.xn--80abahjtcfl5d0a8di.xn--p1ai; + + location / { + root /usr/share/nginx/begushiybashkir/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api_bb:8080/; + 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; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } + + location /uploads/ { + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.available/50-begushiybashkir_idn.ssl.conf b/main_dc/nginx/conf.available/50-begushiybashkir_idn.ssl.conf new file mode 100644 index 0000000..86b10f6 --- /dev/null +++ b/main_dc/nginx/conf.available/50-begushiybashkir_idn.ssl.conf @@ -0,0 +1,48 @@ +# CERT_DOMAIN=xn--80abahjtcfl5d0a8di.xn--p1ai +# Автоматически сгенерировано generate-configs.sh +server { + listen 443 ssl; + server_name xn--80abahjtcfl5d0a8di.xn--p1ai www.xn--80abahjtcfl5d0a8di.xn--p1ai; + + ssl_certificate /etc/letsencrypt/live/xn--80abahjtcfl5d0a8di.xn--p1ai/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/xn--80abahjtcfl5d0a8di.xn--p1ai/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + + location / { + root /usr/share/nginx/begushiybashkir/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api_bb:8080/; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + + } + + location /uploads/ { + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + } +} \ No newline at end of file diff --git a/main_dc/nginx/conf.d/.gitkeep b/main_dc/nginx/conf.d/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/main_dc/nginx/entrypoint.sh b/main_dc/nginx/entrypoint.sh new file mode 100755 index 0000000..0a398c7 --- /dev/null +++ b/main_dc/nginx/entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# entrypoint.sh — per-domain HTTPS переключение +# Для каждого домена проверяет сертификат и активирует SSL или HTTP конфиг +set -euo pipefail + +CONF_AVAILABLE="/etc/nginx/conf.available" +CONF_D="/etc/nginx/conf.d" +CERT_DIR="/etc/letsencrypt/live" + +rm -f "$CONF_D"/*.conf + +# базовый HTTP (ACME challenge, catch-all redirect) +if [ -f "$CONF_AVAILABLE/00-http.conf" ]; then + ln -sf "$CONF_AVAILABLE/00-http.conf" "$CONF_D/00-http.conf" +fi + +# per-domain конфиги +shopt -s nullglob +for ssl_conf in "$CONF_AVAILABLE"/*.ssl.conf; do + base="$(basename "$ssl_conf" .ssl.conf)" + http_conf="$CONF_AVAILABLE/$base.http.conf" + + # CERT_DOMAIN в первой строке: # CERT_DOMAIN=example.ru + cert_domain="$(head -1 "$ssl_conf" | grep -oP '(?<=# CERT_DOMAIN=).+')" || true + + if [ -n "$cert_domain" ] && [ -f "$CERT_DIR/$cert_domain/fullchain.pem" ]; then + ln -sf "$ssl_conf" "$CONF_D/$base.ssl.conf" + echo " ✓ $base → HTTPS ($cert_domain)" + elif [ -f "$http_conf" ]; then + ln -sf "$http_conf" "$CONF_D/$base.http.conf" + echo " ✓ $base → HTTP (no cert for $cert_domain)" + fi +done + +echo "---" +ls -la "$CONF_D/" | grep -v '^total' +nginx -t diff --git a/main_dc/nginx/nginx-http.conf b/main_dc/nginx/nginx-http.conf index 124e33b..8c41c01 100644 --- a/main_dc/nginx/nginx-http.conf +++ b/main_dc/nginx/nginx-http.conf @@ -1,16 +1,18 @@ +# Автоматически сгенерировано generate-configs.sh — не редактировать вручную +# HTTP-only конфигурация (работает когда нет сертификатов) + server { listen 80; server_name yalarba.ru \ www.yalarba.ru \ - easysite102.ru \ - www.easysite102.ru \ valitovgaziz.ru \ www.valitovgaziz.ru \ - xn--80abahjtcfl5d0a8di.xn--p1ai \ - www.xn--80abahjtcfl5d0a8di.xn--p1ai \ + easysite102.ru \ + www.easysite102.ru \ begushiybashkir.ru \ www.begushiybashkir.ru \ - auth.yalarba.ru; + xn--80abahjtcfl5d0a8di.xn--p1ai \ + www.xn--80abahjtcfl5d0a8di.xn--p1ai; location / { root /usr/share/nginx/stub/html; @@ -25,12 +27,19 @@ server { # Блок для HTTPS → HTTP редиректа (порт 443) server { listen 443 ssl; - server_name yalarba.ru www.yalarba.ru easysite102.ru www.easysite102.ru valitovgaziz.ru www.valitovgaziz.ru xn--80abahjtcfl5d0a8di.xn--p1ai www.xn--80abahjtcfl5d0a8di.xn--p1ai begushiybashkir.ru www.begushiybashkir.ru; + server_name yalarba.ru \ + www.yalarba.ru \ + valitovgaziz.ru \ + www.valitovgaziz.ru \ + easysite102.ru \ + www.easysite102.ru \ + begushiybashkir.ru \ + www.begushiybashkir.ru \ + xn--80abahjtcfl5d0a8di.xn--p1ai \ + www.xn--80abahjtcfl5d0a8di.xn--p1ai; - # Указание пустых сертификатов (обязательно для запуска Nginx) ssl_certificate /etc/nginx/ssl/dummy.crt; ssl_certificate_key /etc/nginx/ssl/dummy.key; - # Редирект всех HTTPS-запросов на HTTP return 301 http://$host$request_uri; -} \ No newline at end of file +} diff --git a/main_dc/nginx/nginx-ssl.conf b/main_dc/nginx/nginx-ssl.conf index b3a4691..54e1de4 100644 --- a/main_dc/nginx/nginx-ssl.conf +++ b/main_dc/nginx/nginx-ssl.conf @@ -1,130 +1,52 @@ -# ================================================ -# КОНФИГУРАЦИЯ NGINX С ПОДДЕРЖКОЙ SSL -# Основные задачи: -# 1. Перенаправление HTTP → HTTPS -# 2. Обслуживание статических файлов -# 3. Проксирование к backend сервисам -# 4. Поддержка нескольких доменов -# ================================================ +# Автоматически сгенерировано generate-configs.sh — не редактировать вручную +# Полная HTTPS конфигурация -# ================================================ -# БЛОК 1: HTTP СЕРВЕР (ПОРТ 80) -# ================================================ +# --- HTTP → HTTPS редирект --- server { - # Прослушивание порта 80 для всех входящих HTTP соединений listen 80; - - # Список доменов, которые обслуживает этот сервер - # Все запросы к этим доменам по HTTP будут обработаны здесь - server_name yalarba.ru www.yalarba.ru - valitovgaziz.ru www.valitovgaziz.ru - easysite102.ru www.easysite102.ru - begushiybashkir.ru - xn--80abahjtcfl5d0a8di.xn--p1ai; # Punycode для IDN домена - # ============================================ - # ЛОКАЦИЯ: Проверочные файлы для Certbot - # ============================================ - # Этот блок КРИТИЧЕСКИ ВАЖЕН для получения SSL сертификатов - # Certbot (Let's Encrypt) размещает здесь временные файлы - # для подтверждения владения доменом + server_name yalarba.ru www.yalarba.ru valitovgaziz.ru www.valitovgaziz.ru easysite102.ru www.easysite102.ru begushiybashkir.ru www.begushiybashkir.ru xn--80abahjtcfl5d0a8di.xn--p1ai www.xn--80abahjtcfl5d0a8di.xn--p1ai; + location /.well-known/acme-challenge/ { - # Директория, где Certbot хранит проверочные файлы root /var/www/certbot; - - # Дополнительные настройки не нужны - nginx просто отдает файлы } - # ============================================ - # ЛОКАЦИЯ: Основное перенаправление - # ============================================ - # Все HTTP запросы перенаправляются на HTTPS - # Это обеспечивает безопасность и правильную SEO-практику - location / { - # 301 - постоянный редирект (лучше для SEO, кэшируется браузерами) - # https://$host$request_uri - сохраняет домен и полный путь запроса - return 301 https://$host$request_uri; - - # Пример: - # HTTP: http://example.com/page?param=1 - # ↓ перенаправление ↓ - # HTTPS: https://example.com/page?param=1 - } - - # ============================================ - # ЛОКАЦИЯ: Загруженные файлы - # ============================================ - # Обслуживание статических файлов (загрузок) по HTTP - # Может быть полезно для прямых ссылок или кэширования location /uploads/ { - # Псевдоним пути - запросы к /uploads/ обслуживаются из /uploads/ на диске alias /uploads/; - - # Кэширование в браузере на 1 год expires 1y; - - # Заголовки кэширования: - # "public" - может кэшироваться прокси-серверами - # "immutable" - файлы никогда не меняются, браузер не проверяет обновления add_header Cache-Control "public, immutable"; - - # Если файл не найден - вернуть 404 ошибку - try_files $uri =404; + } + + location / { + return 301 https://$host$request_uri; } } -# ================================================ -# БЛОК 2: HTTPS СЕРВЕР ДЛЯ YALARBA.RU -# ================================================ +# --- HTTPS серверные блоки --- + server { - # Прослушивание порта 443 с SSL/TLS шифрованием listen 443 ssl; - - # Домены для этого сервера server_name yalarba.ru www.yalarba.ru; - # ============================================ - # НАСТРОЙКИ SSL СЕРТИФИКАТОВ - # ============================================ - # Пути к SSL сертификатам, сгенерированным Certbot ssl_certificate /etc/letsencrypt/live/yalarba.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yalarba.ru/privkey.pem; - # ============================================ - # НАСТРОЙКИ БЕЗОПАСНОСТИ SSL - # ============================================ - # Разрешенные протоколы - только современные безопасные версии ssl_protocols TLSv1.2 TLSv1.3; - - # Сервер выбирает шифры (не клиент) ssl_prefer_server_ciphers on; - - # Список безопасных шифров ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; - # ============================================ - # ЛОКАЦИЯ: Nuxt 4 SSR приложение - # ============================================ location / { - # Проксирование к Nuxt.js SSR серверу proxy_pass http://yalarba:3000; - - # Полный набор заголовков для корректной работы приложения 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; proxy_set_header X-Forwarded-Port $server_port; - - # Длинные таймауты proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; } - # ============================================ - # ЛОКАЦИЯ: REST API (api_yal) - # ============================================ location /api/v1/ { proxy_pass http://api_yal:8787; proxy_set_header Host $host; @@ -135,28 +57,30 @@ server { proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + } } - -# ================================================ -# БЛОК 3: HTTPS СЕРВЕР ДЛЯ VALITOVGAZIZ.RU -# ================================================ server { listen 443 ssl; server_name valitovgaziz.ru www.valitovgaziz.ru; - # Свой SSL сертификат для этого домена ssl_certificate /etc/letsencrypt/live/valitovgaziz.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/valitovgaziz.ru/privkey.pem; - # Те же настройки безопасности SSL ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; - # ============================================ - # ЛОКАЦИЯ: Проксирование к Vue SPA контейнеру - # ============================================ location / { proxy_pass http://valitovgaziz/; proxy_set_header Host $host; @@ -169,79 +93,59 @@ server { proxy_read_timeout 600; } - # ============================================ - # ЛОКАЦИЯ: API для аналитики - # ============================================ location /api/ { proxy_pass http://analytics:3000/; 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; - - add_header Access-Control-Allow-Origin "*" always; - add_header Access-Control-Allow-Methods "POST, GET, OPTIONS" always; - add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; - add_header Access-Control-Allow-Credentials "true" always; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; return 204; } - proxy_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; } } - -# ================================================ -# БЛОК 4: HTTPS СЕРВЕР ДЛЯ EASYSITE102.RU -# ================================================ server { listen 443 ssl; server_name easysite102.ru www.easysite102.ru; - # Свой SSL сертификат ssl_certificate /etc/letsencrypt/live/easysite102.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/easysite102.ru/privkey.pem; - # Безопасные настройки SSL ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; - # ============================================ - # ЛОКАЦИЯ: Проксирование к Nuxt.js приложению - # ============================================ location / { - # ВСЕ запросы проксируются к Nuxt.js серверу proxy_pass http://easysite:3000; - - # Полный набор заголовков для корректной работы приложения 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; proxy_set_header X-Forwarded-Port $server_port; - - # Длинные таймауты для работы приложения proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; } - # ============================================ - # ЛОКАЦИЯ: API Backend для Easysite (api_yal) - # ============================================ location /api/v1/ { proxy_pass http://api_yal:8787; - 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; proxy_set_header X-Forwarded-Port $server_port; - proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; @@ -255,111 +159,26 @@ server { add_header 'Content-Type' 'text/plain charset=UTF-8'; return 204; } + } } - -# ================================================ -# БЛОК 5: HTTPS СЕРВЕР ДЛЯ IDN ДОМЕНА -# (Punycode для "бегущийбашкир.рф") -# ================================================ -server { - listen 443 ssl; - - # Punycode представление кириллического домена - server_name xn--80abahjtcfl5d0a8di.xn--p1ai - www.xn--80abahjtcfl5d0a8di.xn--p1ai; - - # Отдельный сертификат для IDN домена - ssl_certificate /etc/letsencrypt/live/xn--80abahjtcfl5d0a8di.xn--p1ai/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/xn--80abahjtcfl5d0a8di.xn--p1ai/privkey.pem; - - # Стандартные SSL настройки - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; - - # ============================================ - # ЛОКАЦИЯ: SPA приложение (такое же как begushiybashkir.ru) - # ============================================ - location / { - root /usr/share/nginx/begushiybashkir/html; - index index.html; - try_files $uri $uri/ /index.html; - } - - # ============================================ - # ЛОКАЦИЯ: API для "Бегущий Башкир" - # ============================================ - location /api/ { - proxy_pass http://api_bb:8080/; - 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; - proxy_set_header X-Forwarded-Port $server_port; - proxy_connect_timeout 600; - proxy_send_timeout 600; - proxy_read_timeout 600; - - # Те же CORS настройки что и у Easysite - if ($request_method = OPTIONS ) { - add_header 'Access-Control-Allow-Origin' "$http_origin"; - add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; - add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Length' 0; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - return 204; - } - } - - # ============================================ - # ЛОКАЦИЯ: Загруженные файлы (статическое обслуживание) - # ============================================ - location /uploads/ { - # Обслуживание файлов загрузок напрямую из файловой системы - alias /uploads/; - - # Долгое кэширование - файлы загрузок редко меняются - expires 1y; - add_header Cache-Control "public, immutable"; - - # try_files не нужен - nginx сам проверит существование файла - } -} - -# ================================================ -# БЛОК 6: HTTPS СЕРВЕР ДЛЯ BEGUSHIYBASHKIR.RU -# (ДУБЛИРУЕТ БЛОК 5 С ДРУГИМ ДОМЕНОМ) -# ================================================ server { listen 443 ssl; server_name begushiybashkir.ru www.begushiybashkir.ru; - # Свой SSL сертификат для этого домена ssl_certificate /etc/letsencrypt/live/begushiybashkir.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/begushiybashkir.ru/privkey.pem; - # Стандартные SSL настройки ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; - # ВНИМАНИЕ: Весь контент ниже ДОСЛОВНО ДУБЛИРУЕТ - # предыдущий серверный блок для IDN домена - - # ============================================ - # ЛОКАЦИЯ: SPA приложение - # ============================================ location / { root /usr/share/nginx/begushiybashkir/html; index index.html; try_files $uri $uri/ /index.html; } - # ============================================ - # ЛОКАЦИЯ: API для "Бегущий Башкир" - # ============================================ location /api/ { proxy_pass http://api_bb:8080/; proxy_set_header Host $host; @@ -371,8 +190,7 @@ server { proxy_send_timeout 600; proxy_read_timeout 600; - # Копия CORS настроек - if ($request_method = OPTIONS ) { + if ($request_method = OPTIONS) { add_header 'Access-Control-Allow-Origin' "$http_origin"; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; @@ -381,17 +199,58 @@ server { add_header 'Content-Type' 'text/plain charset=UTF-8'; return 204; } + + } + + location /uploads/ { + alias /uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + } +} +server { + listen 443 ssl; + server_name xn--80abahjtcfl5d0a8di.xn--p1ai www.xn--80abahjtcfl5d0a8di.xn--p1ai; + + ssl_certificate /etc/letsencrypt/live/xn--80abahjtcfl5d0a8di.xn--p1ai/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/xn--80abahjtcfl5d0a8di.xn--p1ai/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + + location / { + root /usr/share/nginx/begushiybashkir/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api_bb:8080/; + 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; + proxy_set_header X-Forwarded-Port $server_port; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Length' 0; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + return 204; + } + } - # ============================================ - # ЛОКАЦИЯ: Загруженные файлы - # ============================================ location /uploads/ { alias /uploads/; expires 1y; add_header Cache-Control "public, immutable"; } } -# ================================================ -# КОНЕЦ КОНФИГУРАЦИИ -# ================================================ \ No newline at end of file diff --git a/main_dc/sites.yml b/main_dc/sites.yml new file mode 100644 index 0000000..96a7f10 --- /dev/null +++ b/main_dc/sites.yml @@ -0,0 +1,49 @@ +# Единый источник истины для всех сайтов проекта +# Добавление нового сайта = одна секция в этом файле +# После изменений запусти: bash generate-configs.sh + +sites: + yalarba: + domain: yalarba.ru + aliases: + - www.yalarba.ru + type: upstream + upstream: http://yalarba:3000 + api: + /api/v1/: http://api_yal:8787 + + valitovgaziz: + domain: valitovgaziz.ru + aliases: + - www.valitovgaziz.ru + type: upstream + upstream: http://valitovgaziz/ + api: + /api/: http://analytics:3000/ + + easysite102: + domain: easysite102.ru + aliases: + - www.easysite102.ru + type: upstream + upstream: http://easysite:3000 + api: + /api/v1/: http://api_yal:8787 + + begushiybashkir: + domain: begushiybashkir.ru + aliases: + - www.begushiybashkir.ru + type: static + root: /usr/share/nginx/begushiybashkir/html + api: + /api/: http://api_bb:8080/ + + begushiybashkir_idn: + domain: xn--80abahjtcfl5d0a8di.xn--p1ai + aliases: + - www.xn--80abahjtcfl5d0a8di.xn--p1ai + type: static + root: /usr/share/nginx/begushiybashkir/html + api: + /api/: http://api_bb:8080/