From ca24dc650740f11ce701517757cf381dc5776cf5 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 22 Jul 2026 20:34:21 +0200 Subject: [PATCH] fix(backup): worker-safe paths and python WebDAV upload fallback Default cache to /tmp when HOME is unwritable; allow python3 upload when curl is absent (activity-core worker image). --- lib/railiance-backup-common.sh | 33 +++++++++++++++++++++++++++++---- tools/cmd/cnpg-option-a-backup | 11 ++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/railiance-backup-common.sh b/lib/railiance-backup-common.sh index 6a70205..7a9a425 100644 --- a/lib/railiance-backup-common.sh +++ b/lib/railiance-backup-common.sh @@ -38,16 +38,41 @@ railiance_backup_require_tools() { export PATH="$(dirname "${RAILIANCE_BACKUP_AGE_BIN}"):${PATH}" fi local t - for t in age curl kubectl; do + for t in age kubectl; do command -v "$t" >/dev/null 2>&1 || { echo "ERROR: missing required tool: $t (run tools/cmd/install-cnpg-backup-vendor-tools)" >&2; exit 1; } done + # curl optional — upload falls back to python3 urllib (activity-core worker has no curl) + if ! command -v curl >/dev/null 2>&1 && ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: need curl or python3 for Nextcloud upload" >&2 + exit 1 + fi } railiance_backup_nc_upload() { local file="$1" remote_name="$2" - local dest="${RAILIANCE_BACKUP_NC_WEBDAV_URL}/${RAILIANCE_BACKUP_NC_PREFIX}/${remote_name}" - curl -sf -u "${RAILIANCE_BACKUP_NC_TOKEN}:" -T "$file" "$dest" \ - || { echo "ERROR: Nextcloud upload failed: ${remote_name}" >&2; return 1; } + local dest="${RAILIANCE_BACKUP_NC_WEBDAV_URL%/}/${RAILIANCE_BACKUP_NC_PREFIX}/${remote_name}" + if command -v curl >/dev/null 2>&1; then + curl -sf -u "${RAILIANCE_BACKUP_NC_TOKEN}:" -T "$file" "$dest" \ + || { echo "ERROR: Nextcloud upload failed: ${remote_name}" >&2; return 1; } + return 0 + fi + # Python fallback (worker image) + RAILIANCE_BACKUP_NC_TOKEN="${RAILIANCE_BACKUP_NC_TOKEN}" \ + RAILIANCE_BACKUP_UPLOAD_URL="${dest}" \ + RAILIANCE_BACKUP_UPLOAD_FILE="${file}" \ + python3 - <<'PY' || { echo "ERROR: Nextcloud upload failed: ${remote_name}" >&2; return 1; } +import os, urllib.request, base64 +url = os.environ["RAILIANCE_BACKUP_UPLOAD_URL"] +path = os.environ["RAILIANCE_BACKUP_UPLOAD_FILE"] +token = os.environ["RAILIANCE_BACKUP_NC_TOKEN"] +data = open(path, "rb").read() +req = urllib.request.Request(url, data=data, method="PUT") +req.add_header("Authorization", "Basic " + base64.b64encode(f"{token}:".encode()).decode()) +req.add_header("Content-Type", "application/octet-stream") +with urllib.request.urlopen(req, timeout=600) as resp: + if resp.status not in (200, 201, 204): + raise SystemExit(f"HTTP {resp.status}") +PY } railiance_backup_encrypt() { diff --git a/tools/cmd/cnpg-option-a-backup b/tools/cmd/cnpg-option-a-backup index d10018a..3d24ba4 100755 --- a/tools/cmd/cnpg-option-a-backup +++ b/tools/cmd/cnpg-option-a-backup @@ -28,7 +28,16 @@ source "${ROOT}/lib/railiance-backup-common.sh" DRY_RUN="${RAILIANCE_BACKUP_DRY_RUN:-0}" UPDATE_STATUS="${CNPG_UPDATE_STATUS_CM:-1}" STATUS_CM="${CNPG_OPTION_A_STATUS_CM:-cnpg-option-a-status}" -BACKUP_ROOT="${BACKUP_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/railiance/backups/cnpg}" +# Prefer explicit BACKUP_DIR; fall back to cache, then /tmp (worker pods often lack $HOME). +if [[ -n "${BACKUP_DIR:-}" ]]; then + BACKUP_ROOT="${BACKUP_DIR}" +elif [[ -n "${XDG_CACHE_HOME:-}" ]]; then + BACKUP_ROOT="${XDG_CACHE_HOME}/railiance/backups/cnpg" +elif [[ -n "${HOME:-}" && -w "${HOME}" ]]; then + BACKUP_ROOT="${HOME}/.cache/railiance/backups/cnpg" +else + BACKUP_ROOT="/tmp/railiance-backups/cnpg" +fi JSON_ONLY="${CNPG_BACKUP_JSON:-1}" KUBECONFIG_CORE="${KUBECONFIG_CORE:-${KUBECONFIG_COULOMBCORE:-$HOME/.kube/config}}"