fix(ci): restore backups through remote Docker API
quality / changes (push) Successful in 7s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 27s
quality / web (push) Successful in 48s
quality / api (push) Successful in 1m13s
quality / linux-agent-smoke (push) Successful in 22s
quality / docker-check (push) Successful in 4m43s
CD / quality (push) Successful in 7m42s
CD / publish (push) Successful in 5m17s

This commit is contained in:
Denozordec committed 2026-10-26 14:28:36 +07:00
1 parent 57b7652d4d
commit ce90fa9701
6 files changed
+85 -7

No files matched your search

+3 -1
View File
@@ -8,7 +8,7 @@
Bake читает переменные из **окружения** (`REGISTRY`, `IMAGE_TAG`, `CACHE_REF_NODE`, `BASE_NODE`). Скрипт [write-bake-override.sh](write-bake-override.sh) — опциональный helper для локальной отладки.
BuildKit кэширует pnpm store через `RUN --mount=type=cache`. На CI mounts живут, пока named builder `evofw` не удаляют (`cleanup: false`).
BuildKit кэширует pnpm store через `RUN --mount=type=cache`. В CI каждый запуск использует отдельный builder с `cleanup: true`; долговременный кэш хранится в registry (`mode=max`). Общий постоянный builder `evofw` больше не используется.
### Слои и runtime
@@ -33,6 +33,8 @@ BuildKit кэширует pnpm store через `RUN --mount=type=cache`. На C
Runtime smoke, backup/restore, baseline и оставшиеся release gates: [runtime-validation.md](../../docs/runtime-validation.md).
Restore работает и с удалённым Docker daemon: локальная резервная копия передаётся через `docker cp` в остановленный одноразовый helper, без bind mount пути runner. Непосредственно перед запуском повторно проверяется, что целевой named volume не используется. Helper удаляется с его анонимными volumes; целевой named volume сохраняется. На всё время восстановления остальные writers и их автоматический перезапуск должны оставаться остановленными.
## Локальная сборка
Из корня репозитория:
+26 -4
View File
@@ -7,7 +7,29 @@ backup="${3:?BACKUP_FILE required}"
backup="$(cd "$(dirname "$backup")" && pwd)/$(basename "$backup")"
# Named volumes only. All users must remain stopped for the whole operation.
docker volume inspect "$volume" >/dev/null
[ -z "$(docker ps -q --filter "volume=$volume")" ] || { echo 'Stop every container using the volume before restore' >&2; exit 1; }
docker run --rm --network none --mount "type=volume,src=$volume,dst=/data" \
--mount "type=bind,src=$backup,dst=/backup.db,readonly" --entrypoint node \
"$image" tools/sqlite-backup.mjs restore /backup.db /data/app.db --offline
active="$(docker ps -q --filter "volume=$volume")"
[ -z "$active" ] || { echo 'Stop every container using the volume before restore' >&2; exit 1; }
helper="evofw-restore-$(date +%s)-$$"
cleanup() {
result=$?
trap - EXIT HUP INT TERM
# -v removes anonymous helper volumes only, never the target named volume.
docker rm -fv "$helper" >/dev/null 2>&1 || true
exit "$result"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
# Transfer input through the Docker API, including when its daemon is remote.
docker create --name "$helper" --network none --cap-drop ALL \
--security-opt no-new-privileges:true --mount "type=volume,src=$volume,dst=/data" \
--entrypoint node "$image" tools/sqlite-backup.mjs restore /tmp/evofw-restore-input.db /data/app.db --offline >/dev/null
docker cp "$backup" "$helper:/tmp/evofw-restore-input.db"
active="$(docker ps -q --filter "volume=$volume")"
[ -z "$active" ] || { echo 'Target volume became active before restore; helper was not started' >&2; exit 1; }
docker start --attach "$helper"
result="$(docker wait "$helper")"
case "$result" in ''|*[!0-9]*) echo 'Invalid restore container exit status' >&2; exit 1;; esac
[ "$result" -le 255 ] || { echo 'Invalid restore container exit status' >&2; exit 1; }
exit "$result"