diff --git a/Makefile b/Makefile index 5292afa..a60e755 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: check build prod-build publish dev game stop venue venue-update venue-https +.PHONY: check build prod-build publish dev game stop venue venue-update venue-https venue-clean PROD_DIR ?= prod @@ -33,6 +33,9 @@ game: venue stop: docker compose -f venue/docker-compose.yml -f venue/docker-compose.https.yml --env-file venue/.env down +venue-clean: + bash venue/clean.sh + publish: @test -n "$(MSG)" || (echo 'Usage: make publish MSG="commit message"' >&2; exit 1) @$(MAKE) build diff --git a/README.md b/README.md index fc00290..870eede 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ make dev # разработка: локальные БД/RustFS, Go-бэкенд и Vite-фронтенд make game # локальная игра: запуск venue с HTTPS из готовых артефактов make stop # остановка venue, данные сохраняются +make venue-clean # полное удаление данных venue без подтверждения ``` `make dev` запускает приложения в текущем терминале; `Ctrl+C` останавливает оба @@ -123,7 +124,13 @@ DNS по `http://:`. Ссылки на материа make stop ``` -Не используйте `down -v`: это удаляет данные. После изменений кода или миграций +Для полного сброса используйте `make venue-clean`. Команда без подтверждения +останавливает площадку и необратимо удаляет аккаунты, игры, команды, результаты +и материалы. Настройки `venue/.env`, сертификаты, настройки Cudy, сборка и +Docker-образы сохраняются; окружение разработки не затрагивается. Повторный +вызов допустим. Следующий `make venue` создаст пустую БД и организатора из `.env`. +Внешние тома БД и материалов обычный `down -v` не удаляет. +После изменений кода или миграций выполните `make venue`, пока интернет доступен. Секреты, сборка, сертификаты и пакеты игры игнорируются Git в `venue/`. diff --git a/docs/agent/COMPLETED_TASKS.md b/docs/agent/COMPLETED_TASKS.md index 33cb8a5..d3dc882 100644 --- a/docs/agent/COMPLETED_TASKS.md +++ b/docs/agent/COMPLETED_TASKS.md @@ -2,6 +2,15 @@ Архив задач, переведённых в статус `DONE`. +### ED-048 — Очистка локальной площадки + +- Статус: `DONE` +- Репозиторий: корневой workspace. +- Цель и результат: добавлена `make venue-clean`; без подтверждения останавливает venue, удаляет внутренние и внешние тома данных и оставляет сервер остановленным. Повторная очистка допустима; ошибки Docker не скрываются. +- Сохраняются: `.env`, сертификаты, настройки Cudy, сборка, Docker-образы и окружение разработки. Следующий `make venue` создаёт пустую БД и организатора из `.env`. +- Проверки: `python3 tests/venue/clean.py`, `bash -n venue/clean.sh`, `make -n venue-clean`, `git diff --check` — успешно. Тест проверяет порядок команд, повторный вызов, ошибки Docker, сохранность файлов и посторонних томов. Ручное ревью diff выполнено; стандарты Go/Vue неприменимы, код дочерних репозиториев не изменён. +- Не проверено: удаление реальных Docker-томов; рабочие данные не затронуты. Commit/push не выполнялись. + ### ED-047 — Сброс локального режима и повторная подготовка пакета - Статус: `DONE` diff --git a/prod b/prod index d21525e..0f3f22b 160000 --- a/prod +++ b/prod @@ -1 +1 @@ -Subproject commit d21525efa4898b55d05465a7689c5190e41c38ea +Subproject commit 0f3f22b3b7da2b5db5bbfc4bc27fe83dad798cd3 diff --git a/tests/venue/README.md b/tests/venue/README.md index a08a259..f4cbb26 100644 --- a/tests/venue/README.md +++ b/tests/venue/README.md @@ -217,3 +217,9 @@ Flow также проверяет переход заполненной БД с `python3 tests/venue/design_system.py`. Требуется установленный Firefox. Smoke проверяет три ширины, клавиатурные вкладки, повторные карточки и ID, локальную форму и окно, состояния, темы и reduced motion. + +## Проверка очистки + +`python3 tests/venue/clean.py` проверяет очистку venue с подставным Docker: +порядок команд, повторный вызов, ошибки и сохранность настроек и посторонних томов. +Реальные данные не удаляются. diff --git a/tests/venue/clean.py b/tests/venue/clean.py new file mode 100644 index 0000000..2707783 --- /dev/null +++ b/tests/venue/clean.py @@ -0,0 +1,78 @@ +"""Check venue cleanup using a fake Docker; never remove real data.""" + +from pathlib import Path +import json +import os +import shutil +import subprocess +import tempfile + + +workspace = Path(__file__).resolve().parents[2] +with tempfile.TemporaryDirectory(prefix="venue-clean-") as directory: + root = Path(directory) + venue = root / "venue" + venue.mkdir() + shutil.copy(workspace / "venue/clean.sh", venue / "clean.sh") + protected = [".env", "certificates/privkey.pem", "cudy/password", "backend/app", + "frontend/dist/index.html"] + for name in protected: + path = venue / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("preserve me") + bin_dir = root / "bin" + bin_dir.mkdir() + docker = bin_dir / "docker" + docker.write_text('''#!/usr/bin/env python3 +import json +import os +from pathlib import Path +import sys +args = sys.argv[1:] +with open(os.environ["TEST_LOG"], "a") as log: + log.write(json.dumps(args) + "\\n") +if os.environ.get("TEST_FAIL") == args[0] or os.environ.get("TEST_FAIL") == " ".join(args[:2]): + sys.exit(1) +state = Path(os.environ["TEST_VOLUMES"]) +names = state.read_text().splitlines() +if args[:2] == ["volume", "ls"]: + print("\\n".join(names)) +elif args[:2] == ["volume", "rm"]: + assert args[2] in names + names.remove(args[2]) + state.write_text("\\n".join(names)) +''') + docker.chmod(0o755) + log = root / "commands" + state = root / "volumes" + targets = ["evening-detective-local_postgres_data", "evening-detective-local_rustfs_data"] + unrelated = ["dev_postgres_data", "evening-detective-local_postgres_data-backup"] + env = {**os.environ, "PATH": str(bin_dir) + os.pathsep + os.environ["PATH"], + "TEST_LOG": str(log), "TEST_VOLUMES": str(state)} + + def check(fail=""): + log.write_text("") + result = subprocess.run(["bash", str(venue / "clean.sh")], + env={**env, "TEST_FAIL": fail}, capture_output=True, text=True) + assert (result.returncode == 0) == (not fail), result.stderr + for name in protected: + assert (venue / name).read_text() == "preserve me" + return [json.loads(line) for line in log.read_text().splitlines()] + + state.write_text("\n".join(targets + unrelated)) + commands = check() + assert commands == [ + ["compose", "-f", "docker-compose.yml", "-f", "docker-compose.https.yml", + "down", "--volumes", "--remove-orphans"], + ["volume", "ls", "--format", "{{.Name}}"], + *[["volume", "rm", name] for name in targets], + ] + assert state.read_text().splitlines() == unrelated + assert len(check()) == 2 # Repeated cleanup skips absent volumes. + state.write_text("\n".join(targets + unrelated)) + assert len(check("compose")) == 1 + assert len(check("volume ls")) == 2 + assert len(check("volume rm")) == 3 + assert state.read_text().splitlines() == targets + unrelated + +print("PASS: cleanup order, repeat calls, Docker failures and preserved files/volumes") diff --git a/venue/clean.sh b/venue/clean.sh new file mode 100644 index 0000000..531d09f --- /dev/null +++ b/venue/clean.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd -- "$(dirname -- "${BASH_SOURCE[0]}")" +if [[ $# -ne 0 ]]; then + echo 'Используйте make venue-clean' >&2 + exit 1 +fi + +echo 'Удаление всех данных площадки: аккаунтов, игр, команд, результатов и материалов.' +docker compose -f docker-compose.yml -f docker-compose.https.yml down --volumes --remove-orphans + +# Compose does not remove the external data volumes. +volumes=$(docker volume ls --format '{{.Name}}') +for volume in evening-detective-local_postgres_data evening-detective-local_rustfs_data; do + if [[ $'\n'"$volumes"$'\n' == *$'\n'"$volume"$'\n'* ]]; then + docker volume rm "$volume" + fi +done +echo 'Площадка очищена и остановлена. Для запуска выполните make venue.'