#!/usr/bin/env bash # Backup Browser read-only helper for Bash, curl, and jq. # Source this file. The temporary key stays in this shell and its child processes. BACKUP_API_BASE="${BACKUP_API_BASE:-https://s3.itcode.team}" _backup_api_require_tools() { command -v curl >/dev/null 2>&1 || { printf 'curl is required\n' >&2; return 1; } command -v jq >/dev/null 2>&1 || { printf 'jq is required\n' >&2; return 1; } } _backup_api_curl() { if [[ -z "${BACKUP_API_TOKEN:-}" ]]; then printf 'Run connect_backup_api first.\n' >&2 return 1 fi local restore_xtrace=0 if [[ $- == *x* ]]; then set +x restore_xtrace=1 fi local status=0 curl --fail --silent --show-error \ --config <(printf 'header = "Authorization: Bearer %s"\n' "$BACKUP_API_TOKEN") \ "$@" || status=$? if (( restore_xtrace )); then set -x; fi return "$status" } _backup_api_encode() { jq -rn --arg value "$1" '$value|@uri' } connect_backup_api() { _backup_api_require_tools || return 1 local token restore_xtrace=0 if [[ $- == *x* ]]; then set +x restore_xtrace=1 fi read -r -s -p 'Temporary Backup Browser agent key: ' token printf '\n' if [[ ! "$token" =~ ^bba_[A-Za-z0-9_-]{43}$ ]]; then unset token printf 'Invalid agent key format.\n' >&2 if (( restore_xtrace )); then set -x; fi return 1 fi BACKUP_API_TOKEN="$token" export BACKUP_API_TOKEN BACKUP_API_BASE unset token if ! _backup_api_curl "$BACKUP_API_BASE/api/v1/catalog" >/dev/null; then disconnect_backup_api if (( restore_xtrace )); then set -x; fi return 1 fi printf 'Connected. Start codex or claude from this shell to pass the temporary read-only access.\n' if (( restore_xtrace )); then set -x; fi } disconnect_backup_api() { unset BACKUP_API_TOKEN printf 'The Backup API key has been cleared from this shell.\n' } get_default_backup_agent_access_path() { local runtime_dir="${XDG_RUNTIME_DIR:-/dev/shm}" printf '%s/backup-browser-agent-%s/access' "$runtime_dir" "$(id -u)" } save_backup_agent_access() { local path path=$(get_default_backup_agent_access_path) || return 1 if [[ -z "${BACKUP_API_TOKEN:-}" ]]; then printf 'Run connect_backup_api first.\n' >&2 return 1 fi local directory directory_owner directory_mode restore_xtrace=0 directory=$(dirname "$path") || return 1 install -d -m 700 "$directory" directory_owner=$(stat -c '%u' "$directory") || return 1 directory_mode=$(stat -c '%a' "$directory") || return 1 if [[ "$directory_owner" != "$(id -u)" || "$directory_mode" != 700 ]]; then printf 'Agent access directory must be owned by the current user with mode 700.\n' >&2 return 1 fi if [[ $- == *x* ]]; then set +x restore_xtrace=1 fi (umask 077; printf '%s' "$BACKUP_API_TOKEN" > "$path") chmod 600 "$path" if (( restore_xtrace )); then set -x; fi printf '%s\n' "$path" } import_backup_agent_access() { local path path=$(get_default_backup_agent_access_path) || return 1 if [[ ! -f "$path" ]]; then printf 'Agent access file not found: %s\n' "$path" >&2 return 1 fi local owner mode expected_owner token restore_xtrace=0 owner=$(stat -c '%u' "$path") || return 1 mode=$(stat -c '%a' "$path") || return 1 expected_owner=$(id -u) if [[ "$owner" != "$expected_owner" || "$mode" != 600 ]]; then printf 'Agent access file must be owned by uid %s with mode 600.\n' "$expected_owner" >&2 return 1 fi if [[ $- == *x* ]]; then set +x restore_xtrace=1 fi token=$(<"$path") if [[ ! "$token" =~ ^bba_[A-Za-z0-9_-]{43}$ ]]; then unset token if (( restore_xtrace )); then set -x; fi printf 'Invalid agent key format.\n' >&2 return 1 fi BACKUP_API_TOKEN="$token" export BACKUP_API_TOKEN BACKUP_API_BASE unset token if (( restore_xtrace )); then set -x; fi _backup_api_curl "$BACKUP_API_BASE/api/v1/catalog" >/dev/null printf 'Agent access imported and verified.\n' } remove_backup_agent_access() { local path path=$(get_default_backup_agent_access_path) || return 1 if [[ -f "$path" ]]; then local owner owner=$(stat -c '%u' "$path") || return 1 if [[ "$owner" != "$(id -u)" ]]; then printf 'Refusing to remove an access file owned by another user.\n' >&2 return 1 fi command rm -- "$path" fi disconnect_backup_api } get_backup_catalog() { _backup_api_curl "$BACKUP_API_BASE/api/v1/catalog" | jq . } get_backup_instruction() { local name="${1:?instruction name is required}" local output="${2:-}" if [[ ! "$name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,126}\.(md|txt|json)$ ]]; then printf 'Invalid instruction name.\n' >&2 return 1 fi if [[ -n "$output" ]]; then _backup_api_curl --output "$output" "$BACKUP_API_BASE/api/v1/instructions/$name?download=1" else _backup_api_curl "$BACKUP_API_BASE/api/v1/instructions/$name" fi } get_backup_snapshots() { local server="${1:?server is required}" [[ "$server" =~ ^[a-z0-9-]+$ ]] || { printf 'Invalid server.\n' >&2; return 1; } _backup_api_curl "$BACKUP_API_BASE/api/v1/repositories/$server/snapshots" | jq . } get_backup_entries() { local server="${1:?server is required}" local item_path="${2:-/}" local snapshot="${3:-latest}" [[ "$server" =~ ^[a-z0-9-]+$ ]] || { printf 'Invalid server.\n' >&2; return 1; } [[ "$snapshot" == latest || "$snapshot" =~ ^[0-9a-f]{8,64}$ ]] || { printf 'Invalid snapshot.\n' >&2; return 1; } local encoded encoded=$(_backup_api_encode "$item_path") || return 1 _backup_api_curl "$BACKUP_API_BASE/api/v1/repositories/$server/snapshots/$snapshot/entries?path=$encoded" | jq . } save_backup_item() { local server="${1:?server is required}" local item_path="${2:?backup path is required}" local output="${3:?output file is required}" local snapshot="${4:-latest}" [[ "$server" =~ ^[a-z0-9-]+$ ]] || { printf 'Invalid server.\n' >&2; return 1; } [[ "$snapshot" == latest || "$snapshot" =~ ^[0-9a-f]{8,64}$ ]] || { printf 'Invalid snapshot.\n' >&2; return 1; } local encoded encoded=$(_backup_api_encode "$item_path") || return 1 _backup_api_curl --output "$output" "$BACKUP_API_BASE/api/v1/repositories/$server/snapshots/$snapshot/download?path=$encoded" } backup_api_help() { printf '%s\n' \ 'get_backup_catalog' \ 'get_backup_instruction NAME [OUTPUT]' \ 'get_backup_snapshots SERVER' \ 'get_backup_entries SERVER [PATH] [SNAPSHOT]' \ 'save_backup_item SERVER PATH OUTPUT [SNAPSHOT]' \ 'import_backup_agent_access' \ 'remove_backup_agent_access' \ 'disconnect_backup_api' }