diff --git a/deploy/README.md b/deploy/README.md index 7cbc893..2a3ea5c 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -25,6 +25,12 @@ docker run --rm caddy:2.10.2-alpine caddy hash-password --plaintext 'ОТДЕЛ ## 3. Проверка и первый запуск +Перед первым запуском проверьте секреты и итоговую Compose-конфигурацию, не выводя значения в лог: + +```bash +./deploy/preflight.sh +``` + ```bash docker compose --env-file .env.production -f compose.production.yaml config --quiet docker compose --env-file .env.production -f compose.production.yaml build @@ -34,6 +40,12 @@ curl -fsS https://rf4spotter.ru/health curl -fsS https://rf4spotter.ru/ready ``` +После обновления DNS и получения сертификатов выполните внешний этап той же проверки: + +```bash +./deploy/preflight.sh --online +``` + До запуска на сервере можно воспроизвести полный production bootstrap на пустых изолированных volumes. Скрипт собирает образы, применяет миграции, проверяет отсутствие демо-уловов, readiness и отправку заявки: ```bash diff --git a/deploy/preflight.sh b/deploy/preflight.sh new file mode 100755 index 0000000..e469da0 --- /dev/null +++ b/deploy/preflight.sh @@ -0,0 +1,38 @@ +#!/bin/sh +set -eu + +repo=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd) +cd "$repo" +env_file=${COMPOSE_ENV_FILE:-.env.production} +online=${1:-} + +fail() { printf 'FAIL %s\n' "$1" >&2; exit 1; } +read_env() { sed -n "s/^$1=//p" "$env_file" | tail -n 1 | sed "s/^'//;s/'$//"; } + +[ -r "$env_file" ] || fail "$env_file is not readable" +if grep -Eq '=(replace-|change-me|example|changeme)' "$env_file"; then fail "placeholder secret remains in $env_file"; fi + +site=$(read_env SITE_DOMAIN) +files=$(read_env FILES_DOMAIN) +root_user=$(read_env MINIO_ROOT_USER) +root_password=$(read_env MINIO_ROOT_PASSWORD) +app_user=$(read_env S3_ACCESS_KEY) +app_password=$(read_env S3_SECRET_KEY) +[ -n "$site" ] && [ -n "$files" ] || fail "SITE_DOMAIN/FILES_DOMAIN missing" +[ "$root_user" != "$app_user" ] || fail "MinIO root and app users must differ" +[ "$root_password" != "$app_password" ] || fail "MinIO root and app passwords must differ" + +docker compose --env-file "$env_file" -f compose.production.yaml config --quiet +printf 'OK environment and Compose configuration\n' + +[ "$online" = "--online" ] || exit 0 +site_ip=$(getent ahostsv4 "$site" | awk 'NR==1 {print $1}') +files_ip=$(getent ahostsv4 "$files" | awk 'NR==1 {print $1}') +[ -n "$site_ip" ] && [ "$site_ip" = "$files_ip" ] || fail "DNS A records are missing or point to different hosts" +for domain in "$site" "$files"; do + openssl s_client -servername "$domain" -connect "$domain:443" /dev/null | openssl x509 -noout -checkhost "$domain" >/dev/null || fail "TLS hostname mismatch: $domain" +done +curl -fsS --max-time 15 "https://$site/health" >/dev/null || fail "public health failed" +curl -fsS --max-time 15 "https://$site/ready" | grep -Eq '"status"[[:space:]]*:[[:space:]]*"ready"' || fail "public readiness failed" +curl -fsS --max-time 15 "https://$files/minio/health/live" >/dev/null || fail "public storage health failed" +printf 'OK DNS, TLS and public health (%s)\n' "$site_ip" diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index ac81e35..0d5553c 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -81,7 +81,7 @@ - [ ] Добавить smoke-проверку административной очереди внешних источников на desktop/mobile без публикации реальных записей. - [ ] Обновить README: архитектура, все переменные окружения, импорт, модерация, backup/restore, эксплуатация логов и известные ограничения. - [ ] Выбрать лицензию кода и политику использования данных. -- [ ] Завершить production-профиль для `rf4spotter.ru`: приложение готово; внешняя проверка 6 сентября выявила неверный сертификат `*.timeweb.ru` на корневом домене и отсутствие DNS для `files.rf4spotter.ru`; требуются IP целевого сервера, DNS, запуск и канал уведомлений (`docs/deployment-status.md`). +- [ ] Завершить production-профиль для `rf4spotter.ru`: приложение и `deploy/preflight.sh` готовы; внешняя проверка 7 сентября не установила соединение с обоими доменами, требуются IP целевого сервера, DNS, запуск и канал уведомлений (`docs/deployment-status.md`). - [ ] Заменить демонстрационные секреты и определить целевое размещение перед внешней публикацией. ## Источники данных и согласование diff --git a/docs/deployment-status.md b/docs/deployment-status.md index 9e4a47c..f9d48e9 100644 --- a/docs/deployment-status.md +++ b/docs/deployment-status.md @@ -1,6 +1,6 @@ # Статус развёртывания rf4spotter.ru -Последняя внешняя read-only проверка: 6 сентября 2026 года. +Последняя внешняя read-only проверка: 7 сентября 2026 года. Web-клиент не смог установить соединение ни с корневым доменом, ни с `files`; прежние DNS/TLS-блокеры считаются незакрытыми до успешного `deploy/preflight.sh --online` на целевом сервере. ## Что видно публично сейчас @@ -25,5 +25,6 @@ - [ ] Убедиться, что Caddy получил сертификаты с SAN `rf4spotter.ru` и `files.rf4spotter.ru`. - [ ] Выполнить `curl` для `/`, `/health`, `/ready` и MinIO health через оба домена. - [ ] Создать первую внешнюю резервную копию, запустить `deploy/monitor.sh` и подключить уведомления. +- [x] Добавить единый preflight без вывода секретов: проверка env/Compose до запуска и DNS/TLS/health после запуска (`deploy/preflight.sh`). Не следует отключать TLS verification или сохранять текущий чужой сертификат в исключения: после правильных DNS и доступных портов Caddy должен получить корректный сертификат автоматически.