Created
January 7, 2026 12:44
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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