Files
linux-scripts/gitlab-upgrade-path-calculator.sh
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
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.
2026-05-25 03:31:08 +02:00

727 lines
29 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "$@"