Contain backup upload credentials and prepare provider recovery gates
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
This commit is contained in:
parent
08a406f2f8
commit
5ef016be01
6 changed files with 219 additions and 14 deletions
|
|
@ -12,8 +12,8 @@ railiance_backup_load_openbao_lane() {
|
|||
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_NC_TOKEN:=$(bao kv get -field=NC_WEBDAV_TOKEN "${RAILIANCE_BACKUP_BAO_PATH}" 2>/dev/null)}"
|
||||
: "${RAILIANCE_BACKUP_NC_WEBDAV_URL:=$(bao kv get -field=NC_WEBDAV_URL "${RAILIANCE_BACKUP_BAO_PATH}" 2>/dev/null)}"
|
||||
}
|
||||
|
||||
railiance_backup_require_openbao_lane() {
|
||||
|
|
@ -51,27 +51,54 @@ railiance_backup_require_tools() {
|
|||
railiance_backup_nc_upload() {
|
||||
local file="$1" remote_name="$2"
|
||||
local dest="${RAILIANCE_BACKUP_NC_WEBDAV_URL%/}/${RAILIANCE_BACKUP_NC_PREFIX}/${remote_name}"
|
||||
if [[ "$dest" != https://* || "$dest" == *$'\n'* || "$dest" == *$'\r'* ||
|
||||
"${RAILIANCE_BACKUP_NC_TOKEN}" == *$'\n'* || "${RAILIANCE_BACKUP_NC_TOKEN}" == *$'\r'* ||
|
||||
"$file" == *$'\n'* || "$file" == *$'\r'* ]]; then
|
||||
echo "ERROR: invalid backup upload input" >&2
|
||||
return 1
|
||||
fi
|
||||
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; }
|
||||
# Credentials and credential-bearing URLs travel over stdin, never argv.
|
||||
local config_url config_token config_file status
|
||||
config_url="${dest//\\/\\\\}"; config_url="${config_url//\"/\\\"}"
|
||||
config_token="${RAILIANCE_BACKUP_NC_TOKEN//\\/\\\\}"; config_token="${config_token//\"/\\\"}"
|
||||
config_file="${file//\\/\\\\}"; config_file="${config_file//\"/\\\"}"
|
||||
status="$(printf 'url = "%s"\nuser = "%s:"\nupload-file = "%s"\n' \
|
||||
"$config_url" "$config_token" "$config_file" | \
|
||||
curl --disable --silent --fail --proto '=https' --max-time 600 \
|
||||
--output /dev/null --write-out '%{http_code}' --config - 2>/dev/null)" \
|
||||
|| { echo "ERROR: Nextcloud upload failed" >&2; return 1; }
|
||||
case "$status" in
|
||||
200|201|204) ;;
|
||||
*) echo "ERROR: Nextcloud upload failed" >&2; return 1 ;;
|
||||
esac
|
||||
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
|
||||
python3 - <<'PY' || { echo "ERROR: Nextcloud upload failed" >&2; return 1; }
|
||||
import os, urllib.request, urllib.error, base64, sys
|
||||
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}")
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
raise urllib.error.HTTPError(req.full_url, code, 'redirect refused', headers, fp)
|
||||
try:
|
||||
with open(path, 'rb') as source:
|
||||
chunks = iter(lambda: source.read(1024 * 1024), b'')
|
||||
req = urllib.request.Request(url, data=chunks, method="PUT")
|
||||
req.add_header("Authorization", "Basic " + base64.b64encode(f"{token}:".encode()).decode())
|
||||
req.add_header("Content-Type", "application/octet-stream")
|
||||
req.add_header("Content-Length", str(os.fstat(source.fileno()).st_size))
|
||||
with urllib.request.build_opener(NoRedirect()).open(req, timeout=600) as resp:
|
||||
if resp.status not in (200, 201, 204):
|
||||
sys.exit(1)
|
||||
except Exception:
|
||||
# URLs may themselves contain credentials: never emit exception text.
|
||||
sys.exit(1)
|
||||
PY
|
||||
}
|
||||
|
||||
|
|
@ -91,4 +118,4 @@ railiance_backup_record_success() {
|
|||
mkdir -p "$stamp_dir"
|
||||
echo "$ts" > "${stamp_dir}/.last-success"
|
||||
echo "$ts" >> "${stamp_dir}/success-log"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue