Files

154 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
BIN_ROOT="/opt/selective-vpn/bin"
COMPONENTS_RAW=""
DRY_RUN=0
usage() {
cat <<'EOF'
Usage:
rollback.sh [--bin-root DIR] [--component NAME[,NAME...]] [--dry-run]
Description:
Rolls back transport companion binaries by one history step.
Uses history files created by update.sh in BIN_ROOT/.packaging/*.history.
Examples:
./scripts/transport-packaging/rollback.sh --component singbox
./scripts/transport-packaging/rollback.sh --bin-root /tmp/svpn-bin --dry-run
EOF
}
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "[transport-rollback] missing required command: ${cmd}" >&2
exit 1
fi
}
collect_components() {
local state_dir="$1"
local out=()
if [[ -n "$COMPONENTS_RAW" ]]; then
IFS=',' read -r -a out <<< "$COMPONENTS_RAW"
else
if [[ -d "$state_dir" ]]; then
while IFS= read -r file; do
local base
base="$(basename "$file")"
out+=("${base%.history}")
done < <(find "$state_dir" -maxdepth 1 -type f -name '*.history' | sort)
fi
fi
printf '%s\n' "${out[@]}"
}
rollback_component() {
local component="$1"
local state_dir="$2"
local history_file="${state_dir}/${component}.history"
if [[ ! -f "$history_file" ]]; then
echo "[transport-rollback] ${component}: history file not found (${history_file})" >&2
return 1
fi
mapfile -t lines < "$history_file"
if [[ "${#lines[@]}" -lt 2 ]]; then
echo "[transport-rollback] ${component}: not enough history entries to rollback" >&2
return 1
fi
local current_line prev_line
current_line="${lines[${#lines[@]}-1]}"
prev_line="${lines[${#lines[@]}-2]}"
IFS='|' read -r _ts_curr bin_curr version_curr target_curr <<< "$current_line"
IFS='|' read -r _ts_prev bin_prev version_prev target_prev <<< "$prev_line"
local binary_name="${bin_curr:-$bin_prev}"
local active_link="${BIN_ROOT}/${binary_name}"
if [[ -z "$binary_name" || -z "$target_prev" ]]; then
echo "[transport-rollback] ${component}: invalid history lines" >&2
return 1
fi
if [[ ! -e "$target_prev" ]]; then
echo "[transport-rollback] ${component}: previous target does not exist: ${target_prev}" >&2
return 1
fi
echo "[transport-rollback] ${component}: ${binary_name} ${version_curr} -> ${version_prev}"
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "[transport-rollback] DRY-RUN ${component}: switch ${active_link} -> ${target_prev}"
return 0
fi
ln -sfn "$target_prev" "$active_link"
if [[ "${#lines[@]}" -eq 2 ]]; then
printf '%s\n' "${lines[0]}" > "$history_file"
else
printf '%s\n' "${lines[@]:0:${#lines[@]}-1}" > "$history_file"
fi
echo "[transport-rollback] ${component}: active -> ${target_prev}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--bin-root)
BIN_ROOT="${2:-}"
shift 2
;;
--component)
COMPONENTS_RAW="${2:-}"
shift 2
;;
--dry-run)
DRY_RUN=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "[transport-rollback] unknown argument: $1" >&2
usage >&2
exit 1
;;
esac
done
require_cmd ln
require_cmd find
require_cmd basename
state_dir="${BIN_ROOT}/.packaging"
mapfile -t components < <(collect_components "$state_dir")
if [[ "${#components[@]}" -eq 0 ]]; then
echo "[transport-rollback] no components selected/found" >&2
exit 1
fi
echo "[transport-rollback] bin_root=${BIN_ROOT}"
if [[ -n "$COMPONENTS_RAW" ]]; then
echo "[transport-rollback] components=${COMPONENTS_RAW}"
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "[transport-rollback] mode=dry-run"
fi
for component in "${components[@]}"; do
component="$(echo "$component" | xargs)"
if [[ -z "$component" ]]; then
continue
fi
rollback_component "$component" "$state_dir"
done
echo "[transport-rollback] done"