196 lines
5.5 KiB
Bash
Executable File
196 lines
5.5 KiB
Bash
Executable File
#!/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 |