From a71369d71c10f4c16d384aa4e7627da4b8ca72bd Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 8 Jul 2026 00:11:08 +0200 Subject: [PATCH] Fix forgejo-backup large dump copy with chunked kubectl cp Split in-pod dumps over 50MiB into 16MiB parts with retry to avoid websocket EOF and connection resets on ~700MiB archives. --- tools/cmd/forgejo-backup | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/tools/cmd/forgejo-backup b/tools/cmd/forgejo-backup index 17b2a65..e981873 100755 --- a/tools/cmd/forgejo-backup +++ b/tools/cmd/forgejo-backup @@ -74,10 +74,43 @@ ok "forgejo-db" "${FORGEJO_DB_NAMESPACE}/${FORGEJO_DB_POD}" # 1. Application dump (repos, packages, attachments, LFS, avatars) ok "forgejo dump" "running in pod…" DUMP_REMOTE="/tmp/forgejo-backup-${TS}.zip" -# Single exec: dump then stream stdout (avoids kubectl cp / multi-exec websocket drops) kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \ - sh -c "forgejo dump -f '${DUMP_REMOTE}' && cat '${DUMP_REMOTE}' && rm -f '${DUMP_REMOTE}'" \ - > "${DUMP_PLAIN}" + forgejo dump -f "${DUMP_REMOTE}" +# Large dumps (~700M) fail via stdout stream or single kubectl cp — use chunks. +dump_size="$(kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \ + stat -c%s "${DUMP_REMOTE}" 2>/dev/null || echo 0)" +copy_from_pod() { + local remote_path="$1" local_path="$2" attempt + for attempt in 1 2 3; do + if kubectl cp -n "${FORGEJO_NAMESPACE}" -c gitea \ + "${FORGEJO_NAMESPACE}/${PROD_POD}:${remote_path}" "${local_path}" 2>/dev/null; then + return 0 + fi + sleep $((attempt * 3)) + done + return 1 +} + +if [[ "${dump_size}" -lt 52428800 ]]; then + copy_from_pod "${DUMP_REMOTE}" "${DUMP_PLAIN}" || exit 1 +else + ok "forgejo dump" "chunked copy (${dump_size} bytes)…" + chunk_dir="$(mktemp -d)" + parts="$(kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \ + sh -c "split -b 16m '${DUMP_REMOTE}' /tmp/forgejo-backup-part- && ls /tmp/forgejo-backup-part-*")" + for part in ${parts}; do + base="$(basename "${part}")" + copy_from_pod "${part}" "${chunk_dir}/${base}" || { bad "forgejo dump" "chunk copy failed: ${base}"; exit 1; } + done + cat "${chunk_dir}"/forgejo-backup-part-* > "${DUMP_PLAIN}" + rm -rf "${chunk_dir}" + kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \ + rm -f /tmp/forgejo-backup-part-* "${DUMP_REMOTE}" || true +fi +if [[ ! -f "${DUMP_PLAIN}" || ! -s "${DUMP_PLAIN}" ]]; then + bad "forgejo dump" "failed to copy dump from pod" + exit 1 +fi ok "forgejo dump" "$(du -h "${DUMP_PLAIN}" | awk '{print $1}') $(basename "${DUMP_PLAIN}")" # 2. PostgreSQL logical dump (plain SQL to stdout; CNPG root FS is read-only)