a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
727 lines
29 KiB
Bash
Executable File
727 lines
29 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# shellcheck disable=SC2034,SC2015,SC2059
|
||
|
||
#########################################################################################
|
||
#### gitlab-upgrade-path-calculator.sh — Calculate required GitLab upgrade stops ####
|
||
#### and PostgreSQL compatibility from 13.x through 18.x ####
|
||
#### ####
|
||
#### Author: Phil Connor ####
|
||
#### Contact: contact@mylinux.work ####
|
||
#### License: MIT ####
|
||
#### Version 1.00 ####
|
||
#### ####
|
||
#### Usage: ####
|
||
#### ./gitlab-upgrade-path-calculator.sh --from 16.3.0 --to 18.2.0 ####
|
||
#### ./gitlab-upgrade-path-calculator.sh --from 15.4.0 --to latest --pg-version 13 ####
|
||
#### ./gitlab-upgrade-path-calculator.sh --db-check --from 16.0.0 --pg-version 13 ####
|
||
#### ./gitlab-upgrade-path-calculator.sh --list-stops ####
|
||
#### ####
|
||
#### See --help for all options. ####
|
||
#########################################################################################
|
||
|
||
set -euo pipefail
|
||
|
||
# ── Defaults ──────────────────────────────────────────────────────────────────
|
||
|
||
RUN_MODE=""
|
||
FROM_VERSION=""
|
||
TO_VERSION=""
|
||
PG_VERSION=""
|
||
DB_SIZE="" # small, medium, large, xlarge
|
||
FORMAT="text"
|
||
SKIP_CONDITIONAL=false
|
||
VERBOSE="${VERBOSE:-false}"
|
||
COLOR="${COLOR:-auto}"
|
||
SCRIPT_NAME="$(basename "$0")"
|
||
|
||
# ── Required Upgrade Stops ────────────────────────────────────────────────────
|
||
# Format: "version|conditional|notes"
|
||
# conditional: 0 = always required, 1 = conditional
|
||
|
||
STOPS=(
|
||
"13.0.14|0|Required stop for 13.x upgrades"
|
||
"13.1.11|0|Required stop (CSRF token migration)"
|
||
"13.8.8|0|Required stop (duplicate services migration)"
|
||
"13.12.15|0|Required stop (last 13.x before 14.0)"
|
||
"14.0.12|0|Required stop for 14.x upgrades"
|
||
"14.3.6|0|Required stop"
|
||
"14.9.5|0|Required stop"
|
||
"14.10.5|0|Required stop"
|
||
"15.0.5|0|Required stop for 15.x upgrades"
|
||
"15.1.6|1|Required only for multi-node instances"
|
||
"15.4.6|0|Required stop"
|
||
"15.11.13|0|Required stop"
|
||
"16.0.10|1|Required only for instances with many users or large pipeline variables"
|
||
"16.1.8|1|Required only for instances with NPM packages in package registry"
|
||
"16.2.11|1|Required only for instances with large pipeline variables history"
|
||
"16.3.9|0|Required stop"
|
||
"16.7.10|0|Required stop"
|
||
"16.11.10|0|Required stop"
|
||
"17.1.8|1|Required only for instances with large ci_pipeline_messages tables"
|
||
"17.3.7|0|Required stop"
|
||
"17.5.5|0|Required stop"
|
||
"17.8.7|0|Required stop"
|
||
"17.11.7|0|Required stop"
|
||
"18.2.0|0|Required stop (18.x predictable schedule)"
|
||
"18.5.0|0|Required stop (18.x predictable schedule)"
|
||
"18.8.0|0|Required stop (18.x predictable schedule)"
|
||
"18.11.0|0|Required stop (18.x predictable schedule)"
|
||
)
|
||
|
||
# ── PostgreSQL Requirements ───────────────────────────────────────────────────
|
||
# Format: "gl_major|pg_min|pg_max"
|
||
|
||
PG_REQS=(
|
||
"13|11|13"
|
||
"14|12|13"
|
||
"15|12|14"
|
||
"16|13|15"
|
||
"17|14|16"
|
||
"18|16|17"
|
||
)
|
||
|
||
LATEST_VERSION="18.11.0"
|
||
|
||
# ── Colors ────────────────────────────────────────────────────────────────────
|
||
|
||
setup_colors() {
|
||
if [[ "$COLOR" == "never" ]]; then
|
||
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" DIM="" RESET="" CYAN=""
|
||
return
|
||
fi
|
||
if [[ "$COLOR" == "auto" && ! -t 1 ]]; then
|
||
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" DIM="" RESET="" CYAN=""
|
||
return
|
||
fi
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[0;33m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
BOLD='\033[1m'
|
||
DIM='\033[2m'
|
||
RESET='\033[0m'
|
||
}
|
||
|
||
# ── Logging ───────────────────────────────────────────────────────────────────
|
||
|
||
log() { printf "${GREEN}%s${RESET}\n" "$*"; }
|
||
warn() { printf "${YELLOW}⚠ %s${RESET}\n" "$*" >&2; }
|
||
err() { printf "${RED}✗ %s${RESET}\n" "$*" >&2; }
|
||
verbose() { [[ "$VERBOSE" == "true" ]] && printf "${DIM} %s${RESET}\n" "$*" >&2 || true; }
|
||
die() { err "$*"; exit 1; }
|
||
|
||
# ── Help ──────────────────────────────────────────────────────────────────────
|
||
|
||
show_help() {
|
||
cat <<EOF
|
||
Usage: $SCRIPT_NAME [MODE] [OPTIONS]
|
||
|
||
Modes:
|
||
--path Calculate upgrade path (default when --from/--to given)
|
||
--check Auto-detect installed GitLab version, show path to latest
|
||
--list-stops List all known required upgrade stops
|
||
--db-check Check PostgreSQL compatibility for the upgrade path
|
||
|
||
Options:
|
||
--from VERSION Current GitLab version (e.g., 15.4.0)
|
||
--to VERSION Target GitLab version (e.g., 18.2.0 or "latest")
|
||
--pg-version VER Current PostgreSQL major version (e.g., 13, 14, 16)
|
||
--db-size SIZE Database size: small (<10GB), medium (10-50GB),
|
||
large (50-200GB), xlarge (200GB+) — affects migration
|
||
time estimates (default: estimates software time only)
|
||
--skip-conditional Exclude conditional stops from the path
|
||
--format FMT Output format: text, json (default: text)
|
||
--no-color Disable colored output
|
||
--verbose Debug output
|
||
--help Show this help
|
||
|
||
Environment Variables:
|
||
COLOR Color mode: auto, always, never
|
||
VERBOSE Debug output (true/false)
|
||
|
||
Examples:
|
||
$SCRIPT_NAME --from 15.4.0 --to 18.2.0
|
||
$SCRIPT_NAME --from 16.3.0 --to latest --pg-version 13 --db-size large
|
||
$SCRIPT_NAME --check
|
||
$SCRIPT_NAME --check --pg-version 14
|
||
$SCRIPT_NAME --list-stops
|
||
$SCRIPT_NAME --db-check --from 16.0.0 --to 18.5.0 --pg-version 13
|
||
$SCRIPT_NAME --from 15.0.0 --to 18.2.0 --format json
|
||
$SCRIPT_NAME --from 16.3.0 --to 17.8.7 --skip-conditional
|
||
EOF
|
||
}
|
||
|
||
# ── Argument Parsing ──────────────────────────────────────────────────────────
|
||
|
||
parse_args() {
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--path) RUN_MODE="path"; shift ;;
|
||
--check) RUN_MODE="check"; shift ;;
|
||
--list-stops) RUN_MODE="list-stops"; shift ;;
|
||
--db-check) RUN_MODE="db-check"; shift ;;
|
||
--from) FROM_VERSION="$2"; shift 2 ;;
|
||
--to) TO_VERSION="$2"; shift 2 ;;
|
||
--pg-version) PG_VERSION="${2%%.*}"; shift 2 ;;
|
||
--db-size) DB_SIZE="$2"; shift 2 ;;
|
||
--skip-conditional) SKIP_CONDITIONAL=true; shift ;;
|
||
--format) FORMAT="$2"; shift 2 ;;
|
||
--no-color) COLOR="never"; shift ;;
|
||
--verbose) VERBOSE="true"; shift ;;
|
||
--help) show_help; exit 0 ;;
|
||
*) die "Unknown option: $1" ;;
|
||
esac
|
||
done
|
||
|
||
if [[ -z "$RUN_MODE" ]]; then
|
||
if [[ -n "$FROM_VERSION" || -n "$TO_VERSION" ]]; then
|
||
RUN_MODE="path"
|
||
else
|
||
err "No mode specified"; echo ""; show_help; exit 1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# ── Version Helpers ───────────────────────────────────────────────────────────
|
||
|
||
version_to_int() {
|
||
local ver="$1"
|
||
local major minor patch
|
||
IFS='.' read -r major minor patch <<< "$ver"
|
||
patch="${patch:-0}"
|
||
printf '%d' $(( major * 10000 + minor * 100 + patch ))
|
||
}
|
||
|
||
version_major() {
|
||
echo "${1%%.*}"
|
||
}
|
||
|
||
validate_version() {
|
||
local ver="$1"
|
||
if [[ ! "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||
die "Invalid version format: $ver (expected X.Y.Z)"
|
||
fi
|
||
local major
|
||
major=$(version_major "$ver")
|
||
if (( major < 13 )); then
|
||
die "Version $ver is below minimum supported (13.0.0)"
|
||
fi
|
||
}
|
||
|
||
# ── Detection ─────────────────────────────────────────────────────────────────
|
||
|
||
detect_gitlab_version() {
|
||
local ver=""
|
||
if command -v gitlab-ctl >/dev/null 2>&1; then
|
||
ver=$(gitlab-ctl version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1 || true)
|
||
fi
|
||
if [[ -z "$ver" ]] && command -v dpkg >/dev/null 2>&1; then
|
||
ver=$(dpkg -l gitlab-ce gitlab-ee 2>/dev/null | awk '/^ii/{print $3}' | grep -oP '\d+\.\d+\.\d+' | head -1 || true)
|
||
fi
|
||
if [[ -z "$ver" ]] && command -v rpm >/dev/null 2>&1; then
|
||
ver=$(rpm -q gitlab-ce gitlab-ee 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1 || true)
|
||
fi
|
||
echo "$ver"
|
||
}
|
||
|
||
detect_pg_version() {
|
||
local ver=""
|
||
if command -v gitlab-psql >/dev/null 2>&1; then
|
||
ver=$(gitlab-psql --version 2>/dev/null | grep -oP '\d+' | head -1 || true)
|
||
fi
|
||
if [[ -z "$ver" ]] && command -v psql >/dev/null 2>&1; then
|
||
ver=$(psql --version 2>/dev/null | grep -oP '\d+' | head -1 || true)
|
||
fi
|
||
echo "$ver"
|
||
}
|
||
|
||
# ── Core Logic ────────────────────────────────────────────────────────────────
|
||
|
||
get_pg_req() {
|
||
local gl_major="$1"
|
||
local entry
|
||
for entry in "${PG_REQS[@]}"; do
|
||
IFS='|' read -r req_gl req_min req_max <<< "$entry"
|
||
if [[ "$req_gl" == "$gl_major" ]]; then
|
||
echo "${req_min}|${req_max}"
|
||
return
|
||
fi
|
||
done
|
||
echo "unknown|unknown"
|
||
}
|
||
|
||
build_upgrade_path() {
|
||
local from_int to_int
|
||
from_int=$(version_to_int "$FROM_VERSION")
|
||
to_int=$(version_to_int "$TO_VERSION")
|
||
|
||
UPGRADE_PATH=()
|
||
local entry ver ver_int conditional notes
|
||
for entry in "${STOPS[@]}"; do
|
||
IFS='|' read -r ver conditional notes <<< "$entry"
|
||
ver_int=$(version_to_int "$ver")
|
||
|
||
if (( ver_int <= from_int )); then
|
||
continue
|
||
fi
|
||
if (( ver_int > to_int )); then
|
||
continue
|
||
fi
|
||
if [[ "$SKIP_CONDITIONAL" == "true" && "$conditional" == "1" ]]; then
|
||
verbose "Skipping conditional stop: $ver"
|
||
continue
|
||
fi
|
||
|
||
UPGRADE_PATH+=("$entry")
|
||
done
|
||
|
||
# Add target if it's not already in the path
|
||
local last_ver=""
|
||
if [[ ${#UPGRADE_PATH[@]} -gt 0 ]]; then
|
||
IFS='|' read -r last_ver _ _ <<< "${UPGRADE_PATH[-1]}"
|
||
fi
|
||
if [[ "$last_ver" != "$TO_VERSION" ]]; then
|
||
UPGRADE_PATH+=("${TO_VERSION}|0|Target version")
|
||
fi
|
||
}
|
||
|
||
get_pg_warnings() {
|
||
PG_WARNINGS=()
|
||
if [[ -z "$PG_VERSION" ]]; then
|
||
return
|
||
fi
|
||
|
||
local from_major to_major
|
||
from_major=$(version_major "$FROM_VERSION")
|
||
to_major=$(version_major "$TO_VERSION")
|
||
|
||
local gl_major
|
||
for (( gl_major = from_major; gl_major <= to_major; gl_major++ )); do
|
||
local req
|
||
req=$(get_pg_req "$gl_major")
|
||
IFS='|' read -r pg_min pg_max <<< "$req"
|
||
if [[ "$pg_min" == "unknown" ]]; then
|
||
continue
|
||
fi
|
||
if (( PG_VERSION < pg_min )); then
|
||
# Find the last stop before this major version
|
||
local boundary_stop=""
|
||
local prev_major=$(( gl_major - 1 ))
|
||
local entry ver
|
||
for entry in "${STOPS[@]}"; do
|
||
IFS='|' read -r ver _ _ <<< "$entry"
|
||
if [[ "$(version_major "$ver")" == "$prev_major" ]]; then
|
||
boundary_stop="$ver"
|
||
fi
|
||
done
|
||
PG_WARNINGS+=("PostgreSQL ${PG_VERSION} is below minimum for GitLab ${gl_major}.x (requires ${pg_min}+)|Upgrade PostgreSQL to ${pg_min}+ before upgrading past GitLab ${boundary_stop:-${prev_major}.x}|${gl_major}|${pg_min}|${pg_max}")
|
||
fi
|
||
done
|
||
}
|
||
|
||
estimate_downtime() {
|
||
local stop_count=${#UPGRADE_PATH[@]}
|
||
local pg_upgrade_count=${#PG_WARNINGS[@]}
|
||
|
||
# Software time: package install + gitlab-ctl reconfigure per stop
|
||
DT_SW_LOW=$(( stop_count * 5 ))
|
||
DT_SW_HIGH=$(( stop_count * 15 ))
|
||
|
||
# Background migration time per stop, based on database size
|
||
# These run between stops and must complete before proceeding
|
||
local mig_low=0 mig_high=0
|
||
case "$DB_SIZE" in
|
||
small) mig_low=2; mig_high=10 ;; # <10GB: minutes
|
||
medium) mig_low=10; mig_high=30 ;; # 10-50GB: tens of minutes
|
||
large) mig_low=30; mig_high=90 ;; # 50-200GB: up to hours
|
||
xlarge) mig_low=60; mig_high=240 ;; # 200GB+: hours per stop
|
||
*) mig_low=0; mig_high=0 ;; # unknown: show software only
|
||
esac
|
||
DT_MIG_LOW=$(( stop_count * mig_low ))
|
||
DT_MIG_HIGH=$(( stop_count * mig_high ))
|
||
|
||
# PostgreSQL major upgrade time
|
||
DT_PG_LOW=$(( pg_upgrade_count * 15 ))
|
||
DT_PG_HIGH=$(( pg_upgrade_count * 60 ))
|
||
|
||
DT_GL_LOW=$(( DT_SW_LOW + DT_MIG_LOW ))
|
||
DT_GL_HIGH=$(( DT_SW_HIGH + DT_MIG_HIGH ))
|
||
DT_TOTAL_LOW=$(( DT_GL_LOW + DT_PG_LOW ))
|
||
DT_TOTAL_HIGH=$(( DT_GL_HIGH + DT_PG_HIGH ))
|
||
}
|
||
|
||
# ── Text Output ───────────────────────────────────────────────────────────────
|
||
|
||
print_header() {
|
||
printf "\n${BOLD}GitLab Upgrade Path Calculator${RESET}\n"
|
||
printf "══════════════════════════════════════════════════════════════\n\n"
|
||
}
|
||
|
||
format_path_text() {
|
||
print_header
|
||
|
||
printf " ${BOLD}From:${RESET} %s\n" "$FROM_VERSION"
|
||
printf " ${BOLD}To:${RESET} %s\n" "$TO_VERSION"
|
||
printf " ${BOLD}Stops:${RESET} %d\n" "${#UPGRADE_PATH[@]}"
|
||
|
||
if [[ -n "$PG_VERSION" ]]; then
|
||
if [[ ${#PG_WARNINGS[@]} -gt 0 ]]; then
|
||
local last_warn="${PG_WARNINGS[-1]}"
|
||
IFS='|' read -r _ _ _ final_pg_min _ <<< "$last_warn"
|
||
printf "\n ${BOLD}PostgreSQL:${RESET} ${YELLOW}Currently ${PG_VERSION} → Must upgrade to ${final_pg_min}+ before GitLab ${TO_VERSION}${RESET}\n"
|
||
else
|
||
printf "\n ${BOLD}PostgreSQL:${RESET} ${GREEN}${PG_VERSION} — compatible with target${RESET}\n"
|
||
fi
|
||
fi
|
||
|
||
printf "\n ── Upgrade Path ──────────────────────────────────────────\n\n"
|
||
printf " ${DIM}Step Version Notes PG Required${RESET}\n"
|
||
printf " ${DIM}──── ──────────── ─────────────────────────────────────── ──────────${RESET}\n"
|
||
|
||
local step=0 entry ver conditional notes
|
||
for entry in "${UPGRADE_PATH[@]}"; do
|
||
IFS='|' read -r ver conditional notes <<< "$entry"
|
||
step=$((step + 1))
|
||
|
||
local gl_major pg_range
|
||
gl_major=$(version_major "$ver")
|
||
local req
|
||
req=$(get_pg_req "$gl_major")
|
||
IFS='|' read -r pg_min pg_max <<< "$req"
|
||
if [[ "$pg_min" != "unknown" ]]; then
|
||
pg_range="${pg_min}-${pg_max}"
|
||
else
|
||
pg_range="—"
|
||
fi
|
||
|
||
local cond_marker=""
|
||
if [[ "$conditional" == "1" ]]; then
|
||
cond_marker=" ⓘ"
|
||
fi
|
||
|
||
local ver_color="$RESET"
|
||
if [[ "$notes" == "Target version" ]]; then
|
||
ver_color="$GREEN"
|
||
fi
|
||
|
||
printf " %3d ${ver_color}%-12s${RESET} %-39s %s\n" "$step" "$ver" "${notes}${cond_marker}" "$pg_range"
|
||
done
|
||
|
||
if [[ ${#PG_WARNINGS[@]} -gt 0 ]]; then
|
||
printf "\n ── PostgreSQL Upgrade Required ───────────────────────────\n\n"
|
||
local warning
|
||
for warning in "${PG_WARNINGS[@]}"; do
|
||
IFS='|' read -r msg action gl_major pg_min pg_max <<< "$warning"
|
||
printf " ${YELLOW}⚠ %s${RESET}\n" "$msg"
|
||
printf " → %s\n\n" "$action"
|
||
done
|
||
fi
|
||
|
||
estimate_downtime
|
||
printf " ── Estimated Downtime ────────────────────────────────────\n\n"
|
||
printf " Software: %d stops × 5-15 min = %d-%d min\n" "${#UPGRADE_PATH[@]}" "$DT_SW_LOW" "$DT_SW_HIGH"
|
||
printf " ${DIM}(package install + gitlab-ctl reconfigure)${RESET}\n"
|
||
if [[ -n "$DB_SIZE" ]]; then
|
||
printf " Migrations: %d stops × %s db = %d-%d min\n" "${#UPGRADE_PATH[@]}" "$DB_SIZE" "$DT_MIG_LOW" "$DT_MIG_HIGH"
|
||
printf " ${DIM}(background migrations must complete per stop)${RESET}\n"
|
||
else
|
||
printf " Migrations: ${DIM}use --db-size (small/medium/large/xlarge) for estimates${RESET}\n"
|
||
fi
|
||
if [[ ${#PG_WARNINGS[@]} -gt 0 ]]; then
|
||
printf " PG upgrades: %d × 15-60 min = %d-%d min\n" "${#PG_WARNINGS[@]}" "$DT_PG_LOW" "$DT_PG_HIGH"
|
||
fi
|
||
printf "\n ${BOLD}Total estimate: %d-%d min${RESET}" "$DT_TOTAL_LOW" "$DT_TOTAL_HIGH"
|
||
if (( DT_TOTAL_HIGH >= 120 )); then
|
||
local hours_low=$(( DT_TOTAL_LOW / 60 ))
|
||
local hours_high=$(( DT_TOTAL_HIGH / 60 ))
|
||
printf " (%d-%d hrs — plan a full maintenance window)" "$hours_low" "$hours_high"
|
||
fi
|
||
printf "\n\n"
|
||
|
||
if [[ "$SKIP_CONDITIONAL" == "false" ]]; then
|
||
local has_conditional=false
|
||
for entry in "${UPGRADE_PATH[@]}"; do
|
||
IFS='|' read -r _ conditional _ <<< "$entry"
|
||
if [[ "$conditional" == "1" ]]; then
|
||
has_conditional=true
|
||
break
|
||
fi
|
||
done
|
||
if [[ "$has_conditional" == "true" ]]; then
|
||
printf " ${DIM}ⓘ = conditional stop (may be skippable — use --skip-conditional)${RESET}\n\n"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
format_path_json() {
|
||
local steps_json="["
|
||
local step=0 first=true entry ver conditional notes
|
||
for entry in "${UPGRADE_PATH[@]}"; do
|
||
IFS='|' read -r ver conditional notes <<< "$entry"
|
||
step=$((step + 1))
|
||
local gl_major req pg_min pg_max
|
||
gl_major=$(version_major "$ver")
|
||
req=$(get_pg_req "$gl_major")
|
||
IFS='|' read -r pg_min pg_max <<< "$req"
|
||
|
||
[[ "$first" == "true" ]] || steps_json+=","
|
||
first=false
|
||
steps_json+=$(printf '{"step":%d,"version":"%s","conditional":%s,"notes":"%s","pg_min":"%s","pg_max":"%s"}' \
|
||
"$step" "$ver" "$( [[ "$conditional" == "1" ]] && echo "true" || echo "false" )" "$notes" "$pg_min" "$pg_max")
|
||
done
|
||
steps_json+="]"
|
||
|
||
local pg_upgrades_json="["
|
||
first=true
|
||
if [[ ${#PG_WARNINGS[@]} -gt 0 ]]; then
|
||
local warning
|
||
for warning in "${PG_WARNINGS[@]}"; do
|
||
IFS='|' read -r msg action gl_major pg_min pg_max <<< "$warning"
|
||
[[ "$first" == "true" ]] || pg_upgrades_json+=","
|
||
first=false
|
||
pg_upgrades_json+=$(printf '{"before_gitlab":"%s.0.0","min_pg":"%s","max_pg":"%s"}' "$gl_major" "$pg_min" "$pg_max")
|
||
done
|
||
fi
|
||
pg_upgrades_json+="]"
|
||
|
||
estimate_downtime
|
||
|
||
printf '{\n'
|
||
printf ' "from": "%s",\n' "$FROM_VERSION"
|
||
printf ' "to": "%s",\n' "$TO_VERSION"
|
||
printf ' "total_stops": %d,\n' "${#UPGRADE_PATH[@]}"
|
||
printf ' "pg_current": "%s",\n' "${PG_VERSION:-null}"
|
||
printf ' "pg_upgrades_needed": %s,\n' "$pg_upgrades_json"
|
||
printf ' "steps": %s,\n' "$steps_json"
|
||
printf ' "db_size": "%s",\n' "${DB_SIZE:-unknown}"
|
||
printf ' "estimated_downtime_min": {"software": {"low": %d, "high": %d}, "migrations": {"low": %d, "high": %d}, "pg_upgrades": {"low": %d, "high": %d}, "total": {"low": %d, "high": %d}}\n' \
|
||
"$DT_SW_LOW" "$DT_SW_HIGH" "$DT_MIG_LOW" "$DT_MIG_HIGH" "$DT_PG_LOW" "$DT_PG_HIGH" "$DT_TOTAL_LOW" "$DT_TOTAL_HIGH"
|
||
printf '}\n'
|
||
}
|
||
|
||
# ── Mode: --path ──────────────────────────────────────────────────────────────
|
||
|
||
run_path() {
|
||
if [[ -z "$FROM_VERSION" ]]; then
|
||
verbose "Detecting installed GitLab version..."
|
||
FROM_VERSION=$(detect_gitlab_version)
|
||
if [[ -z "$FROM_VERSION" ]]; then
|
||
die "Could not detect installed GitLab version. Use --from VERSION."
|
||
fi
|
||
log "Detected GitLab version: $FROM_VERSION"
|
||
fi
|
||
if [[ -z "$TO_VERSION" || "$TO_VERSION" == "latest" ]]; then
|
||
TO_VERSION="$LATEST_VERSION"
|
||
fi
|
||
|
||
validate_version "$FROM_VERSION"
|
||
validate_version "$TO_VERSION"
|
||
|
||
local from_int to_int
|
||
from_int=$(version_to_int "$FROM_VERSION")
|
||
to_int=$(version_to_int "$TO_VERSION")
|
||
if (( from_int >= to_int )); then
|
||
die "Target version ($TO_VERSION) must be higher than current version ($FROM_VERSION)"
|
||
fi
|
||
|
||
build_upgrade_path
|
||
get_pg_warnings
|
||
|
||
if [[ "$FORMAT" == "json" ]]; then
|
||
format_path_json
|
||
else
|
||
format_path_text
|
||
fi
|
||
}
|
||
|
||
# ── Mode: --check ─────────────────────────────────────────────────────────────
|
||
|
||
run_check() {
|
||
verbose "Detecting installed GitLab version..."
|
||
FROM_VERSION=$(detect_gitlab_version)
|
||
|
||
if [[ -z "$FROM_VERSION" ]]; then
|
||
die "Could not detect installed GitLab version. Use --path --from VERSION instead."
|
||
fi
|
||
|
||
log "Detected GitLab version: $FROM_VERSION"
|
||
|
||
if [[ -z "$PG_VERSION" ]]; then
|
||
verbose "Detecting PostgreSQL version..."
|
||
PG_VERSION=$(detect_pg_version)
|
||
if [[ -n "$PG_VERSION" ]]; then
|
||
log "Detected PostgreSQL version: $PG_VERSION"
|
||
fi
|
||
fi
|
||
|
||
if [[ -z "$TO_VERSION" || "$TO_VERSION" == "latest" ]]; then
|
||
TO_VERSION="$LATEST_VERSION"
|
||
fi
|
||
|
||
validate_version "$FROM_VERSION"
|
||
validate_version "$TO_VERSION"
|
||
|
||
build_upgrade_path
|
||
get_pg_warnings
|
||
|
||
if [[ "$FORMAT" == "json" ]]; then
|
||
format_path_json
|
||
else
|
||
format_path_text
|
||
fi
|
||
}
|
||
|
||
# ── Mode: --list-stops ────────────────────────────────────────────────────────
|
||
|
||
run_list_stops() {
|
||
print_header
|
||
printf " ── All Known Required Upgrade Stops ──────────────────────\n\n"
|
||
printf " ${DIM}Version Type Notes PG Required${RESET}\n"
|
||
printf " ${DIM}──────────── ──────────── ─────────────────────────────────────── ──────────${RESET}\n"
|
||
|
||
local entry ver conditional notes
|
||
for entry in "${STOPS[@]}"; do
|
||
IFS='|' read -r ver conditional notes <<< "$entry"
|
||
|
||
local gl_major req pg_min pg_max pg_range type_label
|
||
gl_major=$(version_major "$ver")
|
||
req=$(get_pg_req "$gl_major")
|
||
IFS='|' read -r pg_min pg_max <<< "$req"
|
||
if [[ "$pg_min" != "unknown" ]]; then
|
||
pg_range="${pg_min}-${pg_max}"
|
||
else
|
||
pg_range="—"
|
||
fi
|
||
|
||
if [[ "$conditional" == "1" ]]; then
|
||
type_label="${YELLOW}conditional${RESET} "
|
||
else
|
||
type_label="${GREEN}required${RESET} "
|
||
fi
|
||
|
||
printf " %-12s %b %-39s %s\n" "$ver" "$type_label" "$notes" "$pg_range"
|
||
done
|
||
|
||
printf "\n ── PostgreSQL Version Requirements ───────────────────────\n\n"
|
||
printf " ${DIM}GitLab Min PG Max PG${RESET}\n"
|
||
printf " ${DIM}───────── ──────── ────────${RESET}\n"
|
||
local pg_entry
|
||
for pg_entry in "${PG_REQS[@]}"; do
|
||
IFS='|' read -r gl_major pg_min pg_max <<< "$pg_entry"
|
||
printf " %-9s %-8s %s\n" "${gl_major}.x" "$pg_min" "$pg_max"
|
||
done
|
||
printf "\n"
|
||
}
|
||
|
||
# ── Mode: --db-check ──────────────────────────────────────────────────────────
|
||
|
||
run_db_check() {
|
||
if [[ -z "$PG_VERSION" ]]; then
|
||
verbose "Detecting PostgreSQL version..."
|
||
PG_VERSION=$(detect_pg_version)
|
||
if [[ -z "$PG_VERSION" ]]; then
|
||
die "Could not detect PostgreSQL version. Use --pg-version VERSION."
|
||
fi
|
||
log "Detected PostgreSQL version: $PG_VERSION"
|
||
fi
|
||
|
||
if [[ -z "$FROM_VERSION" ]]; then
|
||
FROM_VERSION=$(detect_gitlab_version)
|
||
if [[ -z "$FROM_VERSION" ]]; then
|
||
die "Could not detect GitLab version. Use --from VERSION."
|
||
fi
|
||
log "Detected GitLab version: $FROM_VERSION"
|
||
fi
|
||
|
||
if [[ -z "$TO_VERSION" || "$TO_VERSION" == "latest" ]]; then
|
||
TO_VERSION="$LATEST_VERSION"
|
||
fi
|
||
|
||
validate_version "$FROM_VERSION"
|
||
validate_version "$TO_VERSION"
|
||
|
||
build_upgrade_path
|
||
get_pg_warnings
|
||
|
||
if [[ "$FORMAT" == "json" ]]; then
|
||
local pg_json="["
|
||
local first=true
|
||
if [[ ${#PG_WARNINGS[@]} -gt 0 ]]; then
|
||
local warning
|
||
for warning in "${PG_WARNINGS[@]}"; do
|
||
IFS='|' read -r msg action gl_major pg_min pg_max <<< "$warning"
|
||
[[ "$first" == "true" ]] || pg_json+=","
|
||
first=false
|
||
pg_json+=$(printf '{"message":"%s","action":"%s","gitlab_major":"%s","pg_min":"%s","pg_max":"%s"}' \
|
||
"$msg" "$action" "$gl_major" "$pg_min" "$pg_max")
|
||
done
|
||
fi
|
||
pg_json+="]"
|
||
printf '{"pg_current":"%s","from":"%s","to":"%s","compatible":%s,"warnings":%s}\n' \
|
||
"$PG_VERSION" "$FROM_VERSION" "$TO_VERSION" \
|
||
"$( [[ ${#PG_WARNINGS[@]} -eq 0 ]] && echo "true" || echo "false" )" "$pg_json"
|
||
return
|
||
fi
|
||
|
||
print_header
|
||
printf " ${BOLD}PostgreSQL Compatibility Check${RESET}\n\n"
|
||
printf " Current GitLab: %s\n" "$FROM_VERSION"
|
||
printf " Target GitLab: %s\n" "$TO_VERSION"
|
||
printf " Current PostgreSQL: %s\n\n" "$PG_VERSION"
|
||
|
||
local from_major to_major
|
||
from_major=$(version_major "$FROM_VERSION")
|
||
to_major=$(version_major "$TO_VERSION")
|
||
|
||
printf " ── Requirements by GitLab Version ────────────────────────\n\n"
|
||
printf " ${DIM}GitLab Min PG Max PG Your PG %s Status${RESET}\n" "$PG_VERSION"
|
||
printf " ${DIM}───────── ──────── ──────── ────────── ──────────${RESET}\n"
|
||
|
||
local gl_major
|
||
for (( gl_major = from_major; gl_major <= to_major; gl_major++ )); do
|
||
local req pg_min pg_max status
|
||
req=$(get_pg_req "$gl_major")
|
||
IFS='|' read -r pg_min pg_max <<< "$req"
|
||
|
||
if (( PG_VERSION < pg_min )); then
|
||
status="${RED}✗ Too low${RESET}"
|
||
elif (( PG_VERSION > pg_max )); then
|
||
status="${YELLOW}⚠ Too high${RESET}"
|
||
else
|
||
status="${GREEN}✓ OK${RESET}"
|
||
fi
|
||
printf " %-9s %-8s %-8s %-10s %b\n" "${gl_major}.x" "$pg_min" "$pg_max" "$PG_VERSION" "$status"
|
||
done
|
||
|
||
if [[ ${#PG_WARNINGS[@]} -gt 0 ]]; then
|
||
printf "\n ── Action Required ───────────────────────────────────────\n\n"
|
||
local warning
|
||
for warning in "${PG_WARNINGS[@]}"; do
|
||
IFS='|' read -r msg action gl_major pg_min pg_max <<< "$warning"
|
||
printf " ${YELLOW}⚠ %s${RESET}\n" "$msg"
|
||
printf " → %s\n\n" "$action"
|
||
done
|
||
else
|
||
printf "\n ${GREEN}✓ PostgreSQL %s is compatible with the full upgrade path.${RESET}\n\n" "$PG_VERSION"
|
||
fi
|
||
}
|
||
|
||
# ── Main ──────────────────────────────────────────────────────────────────────
|
||
|
||
main() {
|
||
setup_colors
|
||
parse_args "$@"
|
||
setup_colors
|
||
|
||
case "$RUN_MODE" in
|
||
path) run_path ;;
|
||
check) run_check ;;
|
||
list-stops) run_list_stops ;;
|
||
db-check) run_db_check ;;
|
||
*) die "Unknown mode: $RUN_MODE" ;;
|
||
esac
|
||
}
|
||
|
||
main "$@"
|