fix: закрыть P0/P1 расхождения с целевой моделью

- академический отпуск: readOnly, запрет submit/start/finish защиты
- черновики лабораторных скрыты от студентов в списке/деталях/по UUID
- удалено действие «Убрать из группы» из UI и API
- импорт: restoreInactiveStatuses, skipped-отчёт, разблокировка архивного аккаунта
- глобальные материалы STUDENTS/TEACHERS с JWT-фильтрацией и сменой аудитории
- LDD-AUTH-005 переведён на unit-evidence, clean check проходит по P0
- backup: безопасный парсинг .env, verify-latest, кавычки cron
- robots/noindex, комментарии без автора, профиль без черновиков
- docs/18-goal-audit.md; baseline 57 claims / 366 тестов / release PASS
This commit is contained in:
2026-10-29 18:58:21 +03:00
parent 9cb4d0d263
commit 9412d87fed
63 changed files with 1281 additions and 162 deletions

View File

@@ -35,6 +35,7 @@ usage() {
usage:
deploy/backup.sh backup [--if-running]
deploy/backup.sh list
deploy/backup.sh verify-latest
deploy/backup.sh verify <backup-dir>
deploy/backup.sh restore <backup-dir>
@@ -50,13 +51,25 @@ require_docker() {
load_env() {
[[ -f "$ENV_FILE" ]] || die "не найден $ENV_FILE"
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
# Не source-им .env.prod: значения с пробелами (например, cron) иначе
# исполняются как команды, а произвольный shell-код из файла опасен.
local key value line
while IFS= read -r line; do
[[ "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]] || continue
key="${line%%=*}"
value="${line#*=}"
value="${value%$'\r'}"
value="${value#\"}"; value="${value%\"}"
value="${value#\'\'}"; value="${value%\'\'}"
case "$key" in
LMS_DB_NAME | LMS_DB_USER | LMS_DB_PASSWORD | LMS_WEB_PORT | \
LMS_BACKUP_DIR | LMS_BACKUP_KEEP_DAYS | LMS_COMPOSE_PROJECT)
export "$key=$value"
;;
esac
done <"$ENV_FILE"
: "${LMS_DB_NAME:=lms}"
: "${LMS_DB_USER:=lms}"
# Переменные из .env.prod должны применяться и к путям backup.
BACKUP_ROOT="${LMS_BACKUP_DIR:-$PWD/backups}"
KEEP_DAYS="${LMS_BACKUP_KEEP_DAYS:-14}"
PROJECT="${LMS_COMPOSE_PROJECT:-lms}"
@@ -164,6 +177,24 @@ list_backups() {
find "$BACKUP_ROOT" -mindepth 1 -maxdepth 1 -type d -name 'lms-*' -printf '%f\n' | sort
}
latest_backup() {
if [[ -f "$ENV_FILE" ]]; then
load_env
fi
[[ -d "$BACKUP_ROOT" ]] || return 0
find "$BACKUP_ROOT" -mindepth 1 -maxdepth 1 -type d -name 'lms-*' -print 2>/dev/null | sort | tail -n 1 || true
}
verify_latest_backup() {
local dir
dir="$(latest_backup)"
if [[ -z "$dir" ]]; then
info "Backup ещё не создавался — проверять нечего"
return 0
fi
verify_backup "$dir"
}
verify_backup() {
local dir="${1:-}"
[[ -n "$dir" ]] || usage
@@ -248,6 +279,9 @@ case "${1:-backup}" in
list)
list_backups
;;
verify-latest)
verify_latest_backup
;;
verify)
verify_backup "${2:-}"
;;