2026-07-07 17:16:30 +02:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# lib/railiance-backup-common.sh — age encrypt + Nextcloud upload helpers (Option A)
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
: "${RAILIANCE_BACKUP_AGE_PUBLIC_KEY:=age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz}"
|
|
|
|
|
: "${RAILIANCE_BACKUP_BAO_PATH:=platform/workloads/railiance/backup/offsite-lane}"
|
|
|
|
|
: "${RAILIANCE_BACKUP_NC_PREFIX:=forgejo}"
|
|
|
|
|
|
|
|
|
|
railiance_backup_load_openbao_lane() {
|
|
|
|
|
if [[ -n "${RAILIANCE_BACKUP_NC_TOKEN:-}" && -n "${RAILIANCE_BACKUP_NC_WEBDAV_URL:-}" ]]; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
command -v bao >/dev/null 2>&1 || return 0
|
|
|
|
|
bao kv metadata get "${RAILIANCE_BACKUP_BAO_PATH}" >/dev/null 2>&1 || return 0
|
|
|
|
|
: "${RAILIANCE_BACKUP_NC_TOKEN:=$(bao kv get -field=NC_WEBDAV_TOKEN "${RAILIANCE_BACKUP_BAO_PATH}")}"
|
|
|
|
|
: "${RAILIANCE_BACKUP_NC_WEBDAV_URL:=$(bao kv get -field=NC_WEBDAV_URL "${RAILIANCE_BACKUP_BAO_PATH}")}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
railiance_backup_require_openbao_lane() {
|
|
|
|
|
railiance_backup_load_openbao_lane
|
2026-07-07 18:05:13 +02:00
|
|
|
# Nextcloud *file drop* shares use /public.php/dav/filesdrop/<token>/ — not /public.php/webdav
|
|
|
|
|
: "${RAILIANCE_BACKUP_NC_WEBDAV_URL:=https://nx4069.your-storageshare.de/public.php/dav/filesdrop/${RAILIANCE_BACKUP_NC_TOKEN}}"
|
2026-07-07 17:16:30 +02:00
|
|
|
if [[ -z "${RAILIANCE_BACKUP_NC_TOKEN:-}" ]]; then
|
|
|
|
|
echo "ERROR: set RAILIANCE_BACKUP_NC_TOKEN or run: bao login -method=oidc -path=netkingdom role=railiance-backup-workload-kv-read" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
railiance_backup_require_tools() {
|
2026-07-22 19:50:59 +02:00
|
|
|
# Prefer vendored static binaries (activity-core hostPath / offline runners).
|
|
|
|
|
local root vendor
|
|
|
|
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
|
vendor="${root}/tools/vendor/bin"
|
|
|
|
|
if [[ -d "$vendor" ]]; then
|
|
|
|
|
export PATH="${vendor}:${PATH}"
|
|
|
|
|
fi
|
|
|
|
|
if [[ -n "${RAILIANCE_BACKUP_AGE_BIN:-}" && -x "${RAILIANCE_BACKUP_AGE_BIN}" ]]; then
|
|
|
|
|
export PATH="$(dirname "${RAILIANCE_BACKUP_AGE_BIN}"):${PATH}"
|
|
|
|
|
fi
|
2026-07-07 17:16:30 +02:00
|
|
|
local t
|
2026-07-22 20:34:21 +02:00
|
|
|
for t in age kubectl; do
|
2026-07-22 19:50:59 +02:00
|
|
|
command -v "$t" >/dev/null 2>&1 || { echo "ERROR: missing required tool: $t (run tools/cmd/install-cnpg-backup-vendor-tools)" >&2; exit 1; }
|
2026-07-07 17:16:30 +02:00
|
|
|
done
|
2026-07-22 20:34:21 +02:00
|
|
|
# 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
|
2026-07-07 17:16:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
railiance_backup_nc_upload() {
|
|
|
|
|
local file="$1" remote_name="$2"
|
2026-07-22 20:34:21 +02:00
|
|
|
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
|
2026-07-07 17:16:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
railiance_backup_encrypt() {
|
|
|
|
|
local src="$1" dest="$2"
|
|
|
|
|
age -r "${RAILIANCE_BACKUP_AGE_PUBLIC_KEY}" -o "$dest" "$src"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
railiance_backup_prune_local() {
|
|
|
|
|
local dir="$1" pattern="$2" keep="${3:-7}"
|
|
|
|
|
find "$dir" -maxdepth 1 -name "$pattern" -type f 2>/dev/null \
|
|
|
|
|
| sort -r | tail -n +"$((keep + 1))" | xargs -r rm -f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
railiance_backup_record_success() {
|
|
|
|
|
local stamp_dir="$1" ts="$2"
|
|
|
|
|
mkdir -p "$stamp_dir"
|
|
|
|
|
echo "$ts" > "${stamp_dir}/.last-success"
|
|
|
|
|
echo "$ts" >> "${stamp_dir}/success-log"
|
|
|
|
|
}
|