#!/usr/bin/env bash set -euo pipefail ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" KEEP_RELEASES=2 KEEP_BACKUPS=3 prune_directories() { local base="$1" local keep="$2" local protected="${3:-}" local kept=0 local entry local sorted local name [[ -d "$base" ]] || return 0 if [[ -L "$base" ]]; then printf 'WARNING: retention cleanup skipped for symlink: %s\n' "$base" >&2 return 0 fi if [[ -n "$protected" && -d "$base/$protected" && ! -L "$base/$protected" ]]; then kept=1 fi while IFS= read -r -d '' sorted; do # find prints a numeric mtime sort key before the actual path. Never # pass that sort key to rm (the old updater did, so nothing was freed). entry="${sorted#* }" name="${entry##*/}" if [[ -n "$protected" && "$name" == "$protected" ]]; then continue fi if (( kept < keep )); then kept=$((kept + 1)) continue fi printf 'Removing old retained item: %s\n' "$entry" rm -rf -- "$entry" done < <( find "$base" \ -mindepth 1 \ -maxdepth 1 \ -type d \ -printf '%T@ %p\0' | sort -z -nr ) } cleanup_installed_releases() { local prefix="$1" local releases="$prefix/releases" local backups="$prefix/backups" local current="$prefix/current" local releases_real local current_real local current_release prune_directories "$backups" "$KEEP_BACKUPS" [[ -d "$releases" && ! -L "$releases" ]] || return 0 if [[ ! -L "$current" ]]; then printf 'WARNING: release cleanup skipped: %s is not a symlink\n' "$current" >&2 return 0 fi releases_real="$(readlink -f -- "$releases" || true)" current_real="$(readlink -f -- "$current" || true)" if [[ -z "$releases_real" || -z "$current_real" ]]; then printf 'WARNING: release cleanup skipped: cannot resolve current release\n' >&2 return 0 fi case "$current_real" in "$releases_real"/*) current_release="${current_real##*/}" ;; *) printf 'WARNING: release cleanup skipped: current points outside %s\n' \ "$releases" >&2 return 0 ;; esac prune_directories "$releases" "$KEEP_RELEASES" "$current_release" } # Cargo keeps critical fingerprints, build scripts and compiled deps in target/. # Never remove these: doing so would trigger a full rebuild. Only stale # incremental state can be reclaimed, after a successful managed update. cleanup_safe_cargo_target() { local target="${CARGO_TARGET_DIR:-$ROOT/target}" local profile item if [[ "$target" != /* ]]; then target="$ROOT/$target"; fi [[ -d "$target" && ! -L "$target" ]] || return 0 # Reject targets with symlinked ancestor paths; never follow links. if [[ "$(realpath -ms -- "$target")" != "$(realpath -e -- "$target" 2>/dev/null || true)" ]]; then printf 'WARNING: target cleanup skipped for symlinked target: %s\n' "$target" >&2 return 0 fi command -v flock >/dev/null 2>&1 || return 0 for profile in release debug; do [[ -d "$target/$profile" && ! -L "$target/$profile" ]] || continue # Cargo itself uses this file as its profile build lock. Skip if # another build is using it, instead of competing with cargo. exec {cargo_lock_fd}>>"$target/$profile/.cargo-lock" if ! flock -n -x "$cargo_lock_fd"; then exec {cargo_lock_fd}>&- continue fi if [[ -d "$target/$profile/incremental" && ! -L "$target/$profile/incremental" ]]; then while IFS= read -r -d '' item; do [[ -d "$item" && ! -L "$item" ]] || continue printf 'Removing stale Cargo incremental state: %s\n' "$item" rm -rf -- "$item" done < <(find "$target/$profile/incremental" -mindepth 1 -maxdepth 1 -type d -mtime +30 -print0) fi flock -u "$cargo_lock_fd" exec {cargo_lock_fd}>&- done } cleanup_source_backups() { local target="$1" local backup_dir target="$(cd -- "$target" 2>/dev/null && pwd -P)" || return 0 backup_dir="$target/.upgrade-backup" prune_directories "$backup_dir" "$KEEP_BACKUPS" } if [[ "${1:-}" == "--source-tree" ]]; then shift if (($# == 0)); then exec python3 "$ROOT/scripts/deploy/upgrade.py" fi source_target="$1" python3 "$ROOT/scripts/deploy/upgrade.py" "$@" cleanup_source_backups "$source_target" exit 0 fi prefix="/opt/rustproxy" plan=0 args=("$@") for ((i = 0; i < ${#args[@]}; i++)); do case "${args[i]}" in --prefix) if ((i + 1 < ${#args[@]})); then prefix="${args[i + 1]}" i=$((i + 1)) fi ;; --prefix=*) prefix="${args[i]#--prefix=}" ;; --plan) plan=1 ;; esac done python3 "$ROOT/scripts/deploy/deploy.py" update "$@" if ((plan)); then exit 0 fi if command -v flock >/dev/null 2>&1; then exec {cleanup_lock_fd}>>"$prefix/.deploy.lock" flock -x "$cleanup_lock_fd" cleanup_installed_releases "$prefix" flock -u "$cleanup_lock_fd" exec {cleanup_lock_fd}>&- else printf 'WARNING: flock unavailable; retention cleanup skipped\n' >&2 fi # This cleanup does not remove any Cargo dependency/compiler artifacts. cleanup_safe_cargo_target