Skip to content

Instantly share code, notes, and snippets.

@bouroo
Created January 7, 2026 12:44
Show Gist options
  • Select an option

  • Save bouroo/f14f68866d3bb9ed68e390e3663399a7 to your computer and use it in GitHub Desktop.

Select an option

Save bouroo/f14f68866d3bb9ed68e390e3663399a7 to your computer and use it in GitHub Desktop.
Harbor Auto-Upgrade Script upgrades Harbor to the latest version with migration support
#!/usr/bin/env bash
#===============================================================================
# Harbor Auto-Upgrade Script
# Automatically upgrades Harbor to the latest version with migration support
#===============================================================================
set -euo pipefail
#-------------------------------------------------------------------------------
# Configuration - Edit these variables as needed
#-------------------------------------------------------------------------------
HARBOR_BASE_DIR="${HARBOR_BASE_DIR:-$HOME}"
HARBOR_DIR="${HARBOR_DIR:-$HARBOR_BASE_DIR/harbor}"
BACKUP_DIR="${BACKUP_DIR:-$HARBOR_BASE_DIR/harbor.bak}"
DOWNLOAD_DIR="${DOWNLOAD_DIR:-/tmp/harbor-upgrade}"
LOG_FILE="${LOG_FILE:-$HARBOR_BASE_DIR/harbor-upgrade-$(date +%Y%m%d_%H%M%S).log}"
# Harbor options
ENABLE_TRIVY="${ENABLE_TRIVY:-true}"
INSTALLER_TYPE="${INSTALLER_TYPE:-online}" # 'online' or 'offline'
# GitHub API settings (optional: use token to avoid rate limiting)
GITHUB_API_TOKEN="${GITHUB_API_TOKEN:-}"
HARBOR_REPO="${HARBOR_REPO:-goharbor/harbor}"
API_URL="${API_URL:-https://api.github.com/repos/${HARBOR_REPO}/releases/latest}"
# Minimum required tools
REQUIRED_TOOLS=("curl" "docker" "jq" "tar" "grep" "sed")
#-------------------------------------------------------------------------------
# Utility Functions
#-------------------------------------------------------------------------------
log_info() {
local msg="[INFO] $*"
echo -e "\033[0;32m${msg}\033[0m" >&2
echo "$msg" >> "$LOG_FILE"
}
log_warn() {
local msg="[WARN] $*"
echo -e "\033[0;33m${msg}\033[0m" >&2
echo "$msg" >> "$LOG_FILE"
}
log_error() {
local msg="[ERROR] $*"
echo -e "\033[0;31m${msg}\033[0m" >&2
echo "$msg" >> "$LOG_FILE"
}
cleanup() {
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log_error "Script failed with exit code $exit_code"
log_error "Check log file: $LOG_FILE"
fi
log_info "Script completed"
}
trap cleanup EXIT
check_prerequisites() {
log_info "Checking prerequisites..."
local missing_tools=()
for tool in "${REQUIRED_TOOLS[@]}"; do
if ! command -v "$tool" &> /dev/null; then
missing_tools+=("$tool")
fi
done
if [[ ${#missing_tools[@]} -gt 0 ]]; then
log_error "Missing required tools: ${missing_tools[*]}"
log_error "Install with: apt-get install -y ${missing_tools[*]}"
exit 1
fi
if ! docker compose version &> /dev/null && ! docker-compose --version &> /dev/null; then
log_error "docker-compose is not installed or docker compose plugin is not available"
exit 1
fi
}
get_latest_harbor_version() {
local auth_header=""
if [[ -n "$GITHUB_API_TOKEN" ]]; then
auth_header="-H 'Authorization: token $GITHUB_API_TOKEN'"
fi
local api_response
api_response=$(eval "curl -sSL $auth_header '$API_URL'" 2>/dev/null)
if ! echo "$api_response" | jq -e '.tag_name' &> /dev/null; then
log_error "Failed to fetch version information from GitHub"
log_error "Response: $api_response"
exit 1
fi
local version
version=$(echo "$api_response" | jq -r '.tag_name')
version=${version#v} # Remove 'v' prefix if present
echo "$version"
}
get_current_harbor_version() {
if [[ -f "$HARBOR_DIR/harbor.yml" ]]; then
if grep -q "^version:" "$HARBOR_DIR/harbor.yml"; then
grep "^version:" "$HARBOR_DIR/harbor.yml" | awk '{print $2}'
else
# Try to get from docker-compose file
if [[ -f "$HARBOR_DIR/docker-compose.yml" ]]; then
grep "harbor-core:" "$HARBOR_DIR/docker-compose.yml" | head -1 | sed -n 's/.*goharbor\/harbor-core:v\([^ ]*\).*/\1/p'
fi
fi
fi
}
download_harbor_installer() {
local version=$1
mkdir -p "$DOWNLOAD_DIR"
local installer_url
local installer_file
if [[ "$INSTALLER_TYPE" == "offline" ]]; then
installer_file="harbor-offline-installer-v${version}.tgz"
else
installer_file="harbor-online-installer-v${version}.tgz"
fi
installer_url="https://github.com/goharbor/harbor/releases/download/v${version}/${installer_file}"
log_info "Downloading Harbor installer version $version..."
log_info "Downloading from: $installer_url"
if curl -fsSL -o "$DOWNLOAD_DIR/$installer_file" "$installer_url" 2>/dev/null; then
log_info "Download completed successfully"
else
log_error "Failed to download Harbor installer"
exit 1
fi
echo "$DOWNLOAD_DIR/$installer_file"
}
verify_download() {
local installer_file=$1
if [[ ! -f "$installer_file" ]]; then
log_error "Installer file not found: $installer_file"
return 1
fi
if [[ ! -s "$installer_file" ]]; then
log_error "Installer file is empty: $installer_file"
return 1
fi
log_info "Installer file verified: $installer_file ($(du -h "$installer_file" | cut -f1))"
}
extract_installer() {
local installer_file=$1
local extract_dir=$2
log_info "Extracting Harbor installer..."
mkdir -p "$extract_dir"
if tar -xzf "$installer_file" -C "$extract_dir" --strip-components=1 2>/dev/null; then
log_info "Extraction completed"
else
log_error "Failed to extract installer"
exit 1
fi
}
backup_harbor() {
log_info "Backing up existing Harbor installation..."
# Remove old backup if it exists
if [[ -d "$BACKUP_DIR" ]]; then
log_warn "Old backup directory found, removing..."
rm -rf "$BACKUP_DIR"
fi
# Stop Harbor
log_info "Stopping Harbor services..."
cd "$HARBOR_DIR" || exit 1
if docker compose down 2>&1 | tee -a "$LOG_FILE"; then
log_info "Harbor services stopped"
else
# Fallback to docker-compose
if docker-compose down 2>&1 | tee -a "$LOG_FILE"; then
log_info "Harbor services stopped (docker-compose)"
else
log_error "Failed to stop Harbor services"
exit 1
fi
fi
# Backup the harbor directory
cd "$HARBOR_BASE_DIR" || exit 1
if mv "$HARBOR_DIR" "$BACKUP_DIR"; then
log_info "Backup created at: $BACKUP_DIR"
else
log_error "Failed to create backup"
exit 1
fi
}
restore_config() {
local new_harbor_dir=$1
log_info "Restoring configuration from backup..."
# Copy harbor.yml from backup
if [[ -f "$BACKUP_DIR/harbor.yml" ]]; then
cp "$BACKUP_DIR/harbor.yml" "$new_harbor_dir/harbor.yml"
log_info "Configuration restored"
else
log_warn "No harbor.yml found in backup, using default configuration"
fi
# Preserve data directory if it exists outside harbor directory
if [[ -d "$BACKUP_DIR/data" && ! -L "$BACKUP_DIR/data" ]]; then
log_info "Preserving data directory..."
cp -r "$BACKUP_DIR/data" "$new_harbor_dir/data"
fi
}
run_migration() {
local version=$1
log_info "Running migration to version $version..."
# Pull the prepare image
log_info "Pulling goharbor/prepare:v${version}..."
if docker pull "goharbor/prepare:v${version}" 2>&1 | tee -a "$LOG_FILE"; then
log_info "Prepare image pulled successfully"
else
log_error "Failed to pull prepare image"
exit 1
fi
# Run migration
log_info "Executing migration..."
if docker run -it --rm -v /:/hostfs "goharbor/prepare:v${version}" migrate -i "$BACKUP_DIR/harbor.yml" 2>&1 | tee -a "$LOG_FILE"; then
log_info "Migration completed successfully"
else
log_error "Migration failed"
exit 1
fi
}
prepare_installation() {
local harbor_dir=$1
local prepare_args=()
log_info "Preparing Harbor installation..."
cd "$harbor_dir" || exit 1
if [[ "$ENABLE_TRIVY" == "true" ]]; then
prepare_args+=("--with-trivy")
log_info "Trivy enabled"
fi
log_info "Current directory: $(pwd)"
log_info "Files in directory:"
ls -la "$harbor_dir" | tee -a "$LOG_FILE"
# Don't suppress stderr - we need to see any errors
if "./prepare" "${prepare_args[@]}" 2>&1 | tee -a "$LOG_FILE"; then
log_info "Preparation completed"
else
log_error "Preparation failed"
exit 1
fi
}
install_harbor() {
local harbor_dir=$1
local install_args=()
log_info "Installing Harbor..."
cd "$harbor_dir" || exit 1
if [[ "$ENABLE_TRIVY" == "true" ]]; then
install_args+=("--with-trivy")
fi
log_info "Current directory: $(pwd)"
log_info "Files in directory after prepare:"
ls -la "$harbor_dir" | tee -a "$LOG_FILE"
# Check if install script exists
if [[ ! -f "$harbor_dir/install" ]]; then
log_error "Install script not found in $harbor_dir"
log_info "Attempting to start Harbor using docker-compose directly..."
# Check if docker-compose.yml exists
if [[ -f "$harbor_dir/docker-compose.yml" ]]; then
log_info "Found docker-compose.yml, starting Harbor services..."
# Pull images first
log_info "Pulling Docker images..."
if docker compose pull 2>&1 | tee -a "$LOG_FILE"; then
log_info "Images pulled successfully"
else
log_warn "Failed to pull some images, continuing..."
fi
# Start services
log_info "Starting Harbor services..."
if docker compose up -d 2>&1 | tee -a "$LOG_FILE"; then
log_info "Harbor started successfully"
return 0
else
log_error "Failed to start Harbor services"
exit 1
fi
else
log_error "Neither install script nor docker-compose.yml found"
exit 1
fi
fi
# Don't suppress stderr - we need to see any errors
if "./install" "${install_args[@]}" 2>&1 | tee -a "$LOG_FILE"; then
log_info "Harbor installed successfully"
else
log_error "Installation failed"
exit 1
fi
}
verify_installation() {
log_info "Verifying Harbor installation..."
cd "$HARBOR_DIR" || exit 1
if docker compose ps 2>&1 | tee -a "$LOG_FILE" | grep -q "Up"; then
log_info "Harbor services are running"
docker compose ps | tee -a "$LOG_FILE"
return 0
else
log_warn "Harbor services may not be running properly"
docker compose ps | tee -a "$LOG_FILE"
return 1
fi
}
#-------------------------------------------------------------------------------
# Main Script
#-------------------------------------------------------------------------------
main() {
log_info "======================================"
log_info "Harbor Auto-Upgrade Script"
log_info "======================================"
log_info "Start time: $(date)"
log_info "Log file: $LOG_FILE"
# Check prerequisites
check_prerequisites
# Get versions
log_info "Fetching latest Harbor version from GitHub..."
LATEST_VERSION=$(get_latest_harbor_version)
log_info "Latest Harbor version: $LATEST_VERSION"
CURRENT_VERSION=$(get_current_harbor_version)
log_info "Current version: ${CURRENT_VERSION:-unknown}"
# Check if upgrade is needed
if [[ "$CURRENT_VERSION" == "$LATEST_VERSION" ]]; then
log_info "Harbor is already up to date (v${LATEST_VERSION})"
log_info "Exiting..."
exit 0
fi
# Confirm upgrade
if [[ "${AUTO_UPGRADE:-false}" != "true" ]]; then
echo ""
read -rp "Do you want to upgrade from v${CURRENT_VERSION:-unknown} to v${LATEST_VERSION}? [y/N] " confirm
if [[ "$confirm" != [yY] ]]; then
log_info "Upgrade cancelled by user"
exit 0
fi
fi
# Download installer
INSTALLER_FILE=$(download_harbor_installer "$LATEST_VERSION")
verify_download "$INSTALLER_FILE"
# Extract installer
NEW_HARBOR_DIR="$HARBOR_BASE_DIR/harbor-new"
extract_installer "$INSTALLER_FILE" "$NEW_HARBOR_DIR"
# Backup existing installation
if [[ -d "$HARBOR_DIR" ]]; then
backup_harbor
restore_config "$NEW_HARBOR_DIR"
run_migration "$LATEST_VERSION"
fi
# Move new installation to final location
log_info "Moving new installation to $HARBOR_DIR..."
mv "$NEW_HARBOR_DIR" "$HARBOR_DIR"
# Prepare and install
prepare_installation "$HARBOR_DIR"
install_harbor "$HARBOR_DIR"
# Verify installation
if verify_installation; then
log_info "======================================"
log_info "Upgrade completed successfully!"
log_info "======================================"
log_info "Old Harbor installation backed up to: $BACKUP_DIR"
log_info "To rollback: cd $BACKUP_DIR && docker compose up -d && cd && mv harbor harbor.new && mv harbor.bak harbor"
log_info "End time: $(date)"
else
log_warn "Installation verification failed. Please check the logs."
exit 1
fi
}
# Run main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment