Installer Update Guide
This guide explains the official procedure to update the UTMStack Installer using the automated update script provided by UTMStack.
This process does not involve a manual system upgrade, as the script automatically downloads and runs the latest available installer.
Prerequisites
Before proceeding with the update, ensure the following requirements are met:
Root or sudo privileges
Active internet connection
systemd-based Linux distribution
The script must be executed in the same directory where the UTMStack installer is located
Update Procedure
Navigate to Installation Directory
First, locate and navigate to the directory containing your UTMStack installer.If you don’t know the installer location, use this command to find it:
find /root /home -name 'installer' -type f 2>/dev/nullOnce located, navigate to that directory:
cd /path/to/installer/directoryReplace /path/to/installer/directory with the actual path found in the previous command.
Create the Update Script
Create a new file named update.sh in the installer directory:
nano update.shCopy the complete script below and paste it into the editor:
#!/bin/bash
# ============================================================================
# UTMStack Update Script
# ============================================================================
# Description:
# Automated update script for UTMStack infrastructure. This script handles
# the complete update process including downloading the latest installer,
# managing services, and monitoring the update progress.
#
# Prerequisites:
# - Must be executed in the directory containing the previous UTMStack installer
# - Root/sudo privileges required
# - Active internet connection
# - systemd-based Linux distribution
#
# Usage:
# bash update.sh [OPTIONS]
#
# Options:
# -h, --help Display this help message
# -v, --verbose Enable verbose output
# -d, --dry-run Perform a dry run without making changes
#
# Exit Codes:
# 0 - Success
# 1 - General error
# 2 - Missing prerequisites
# 3 - Download failure
# 4 - Service management failure
# ============================================================================
set -euo pipefail # Exit on error, undefined variables, and pipe failures
IFS=$'\n\t' # Set Internal Field Separator for better word splitting
# ============================================================================
# Global Variables
# ============================================================================
readonly SCRIPT_VERSION="1.0.0"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly INSTALLER_URL="https://github.com/utmstack/UTMStack/releases/latest/download/installer"
readonly INSTALLER_FILE="installer"
readonly LOG_FILE="/utmstack/updates/logs/utmstack-updater.log"
readonly LOG_DIR="/utmstack/updates/logs"
readonly SERVICE_NAME="UTMStackComponentsUpdater"
readonly TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Script options
VERBOSE=false
DRY_RUN=false
# Color codes for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# ============================================================================
# Utility Functions
# ============================================================================
# Print formatted messages with timestamp
log_info() {
echo -e "${BLUE}[INFO]${NC} [$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} [$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} [$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
log_error() {
echo -e "${RED}[ERROR]${NC} [$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
}
log_verbose() {
if [[ "${VERBOSE}" == true ]]; then
echo -e "${BLUE}[VERBOSE]${NC} [$(date '+%Y-%m-%d %H:%M:%S')] $*"
fi
}
# Display help message
show_help() {
sed -n '/^# ===.*UTMStack Update Script/,/^# ===.*$/p' "$0" |
sed 's/^# \?//' |
sed "s/SCRIPT_VERSION/${SCRIPT_VERSION}/"
}
# Print section header
print_header() {
local message="$1"
local width=60
echo ""
echo "$(printf '=%.0s' $(seq 1 $width))"
echo " $message"
echo "$(printf '=%.0s' $(seq 1 $width))"
echo ""
}
# Print step information
print_step() {
local current="$1"
local total="$2"
local description="$3"
echo ""
log_info "Step [$current/$total]: $description"
echo "$(printf -- '-%.0s' $(seq 1 60))"
}
# ============================================================================
# Validation Functions
# ============================================================================
# Check if script is run with sufficient privileges
check_privileges() {
log_verbose "Checking for sudo privileges..."
if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then
log_error "This script requires sudo privileges"
log_info "Please run with: sudo bash $0"
return 2
fi
log_verbose "Privileges check passed"
return 0
}
# Verify required commands are available
check_dependencies() {
log_verbose "Checking required dependencies..."
local missing_deps=()
for cmd in wget chmod systemctl tail; do
if ! command -v "$cmd" &> /dev/null; then
missing_deps+=("$cmd")
fi
done
if [[ ${#missing_deps[@]} -gt 0 ]]; then
log_error "Missing required commands: ${missing_deps[*]}"
return 2
fi
log_verbose "All dependencies are available"
return 0
}
# Verify internet connectivity
check_connectivity() {
log_verbose "Checking internet connectivity..."
if ! wget --spider --quiet --timeout=10 "https://github.com" 2>/dev/null; then
log_error "No internet connectivity detected"
log_info "Please check your network connection and try again"
return 3
fi
log_verbose "Internet connectivity verified"
return 0
}
# Check if systemd service exists
check_service_exists() {
log_verbose "Checking if ${SERVICE_NAME} service exists..."
if ! systemctl list-unit-files | grep -q "^${SERVICE_NAME}.service"; then
log_warning "Service ${SERVICE_NAME} not found in systemd"
return 1
fi
log_verbose "Service exists"
return 0
}
# Check if script is being executed from installer directory
check_installer_directory() {
log_verbose "Checking if installer exists in current directory..."
if [[ ! -f "${INSTALLER_FILE}" ]]; then
echo ""
echo "$(printf '=%.0s' $(seq 1 60))"
log_error "INSTALLER NOT FOUND IN EXECUTION DIRECTORY"
echo "$(printf '=%.0s' $(seq 1 60))"
echo ""
log_info "This script must be executed from the directory where the"
log_info "UTMStack installer is located."
echo ""
log_info "To find your installer location, run:"
log_info " find /root /home -name 'installer' -type f 2>/dev/null"
echo ""
log_info "Then navigate to that directory and run the script:"
log_info " cd /path/to/installer/directory"
log_info " bash update.sh"
echo ""
return 2
fi
log_verbose "Installer file found: ${INSTALLER_FILE}"
log_info "Installer found in current directory: $(pwd)"
return 0
}
# ============================================================================
# Core Functions
# ============================================================================
# Remove old installer file
remove_old_installer() {
print_step 1 8 "Cleaning up old installer"
if [[ "${DRY_RUN}" == true ]]; then
log_info "DRY RUN: Would remove ${INSTALLER_FILE}"
return 0
fi
if [[ -f "${INSTALLER_FILE}" ]]; then
log_info "Removing existing installer file..."
rm -f "${INSTALLER_FILE}"
log_success "Old installer removed successfully"
else
log_info "No existing installer found (clean state)"
fi
}
# Download the latest installer
download_installer() {
print_step 2 8 "Downloading latest installer"
if [[ "${DRY_RUN}" == true ]]; then
log_info "DRY RUN: Would download from ${INSTALLER_URL}"
return 0
fi
log_info "Fetching installer from: ${INSTALLER_URL}"
if wget --timeout=300 --tries=3 --progress=bar:force "${INSTALLER_URL}" -O "${INSTALLER_FILE}" 2>&1; then
log_success "Installer downloaded successfully"
# Verify the downloaded file
if [[ ! -s "${INSTALLER_FILE}" ]]; then
log_error "Downloaded file is empty"
return 3
fi
else
log_error "Failed to download installer"
log_info "Please check your internet connection and try again"
return 3
fi
}
# Make installer executable
set_executable_permissions() {
print_step 3 8 "Setting executable permissions"
if [[ "${DRY_RUN}" == true ]]; then
log_info "DRY RUN: Would set executable permissions on ${INSTALLER_FILE}"
return 0
fi
log_info "Making installer executable..."
chmod +x "${INSTALLER_FILE}"
log_success "Permissions set successfully"
log_verbose "File permissions: $(ls -lh ${INSTALLER_FILE})"
}
# Stop the UTMStack service
stop_service() {
print_step 4 8 "Stopping ${SERVICE_NAME} service"
if [[ "${DRY_RUN}" == true ]]; then
log_info "DRY RUN: Would stop ${SERVICE_NAME} service"
return 0
fi
if ! check_service_exists; then
log_warning "Service not found, skipping stop operation"
return 0
fi
log_info "Stopping service..."
if sudo systemctl stop "${SERVICE_NAME}"; then
log_success "Service stopped successfully"
# Wait for service to fully stop
local timeout=30
local elapsed=0
while systemctl is-active --quiet "${SERVICE_NAME}" && [[ $elapsed -lt $timeout ]]; do
sleep 1
((elapsed++))
log_verbose "Waiting for service to stop... (${elapsed}s)"
done
if systemctl is-active --quiet "${SERVICE_NAME}"; then
log_warning "Service did not stop within ${timeout} seconds"
return 4
fi
else
log_warning "Failed to stop service (may not be running)"
fi
}
# Clean up old log file
cleanup_log_file() {
print_step 5 8 "Cleaning up old log file"
if [[ "${DRY_RUN}" == true ]]; then
log_info "DRY RUN: Would remove ${LOG_FILE}"
return 0
fi
# Ensure log directory exists
if [[ ! -d "${LOG_DIR}" ]]; then
log_info "Creating log directory: ${LOG_DIR}"
sudo mkdir -p "${LOG_DIR}"
fi
if [[ -f "${LOG_FILE}" ]]; then
# Backup old log before removing
local backup_file="${LOG_FILE}.$(date +%Y%m%d_%H%M%S).bak"
log_info "Backing up old log to: ${backup_file}"
sudo cp "${LOG_FILE}" "${backup_file}"
log_info "Removing old log file..."
sudo rm -f "${LOG_FILE}"
log_success "Log file cleaned up"
else
log_info "No existing log file found"
fi
}
# Start the UTMStack service
start_service() {
print_step 6 8 "Starting ${SERVICE_NAME} service"
if [[ "${DRY_RUN}" == true ]]; then
log_info "DRY RUN: Would start ${SERVICE_NAME} service"
return 0
fi
if ! check_service_exists; then
log_error "Service not found, cannot start"
return 4
fi
log_info "Starting service..."
if sudo systemctl start "${SERVICE_NAME}"; then
log_success "Service started successfully"
# Verify service is running
sleep 2
if systemctl is-active --quiet "${SERVICE_NAME}"; then
log_success "Service is running and active"
else
log_error "Service started but is not active"
return 4
fi
else
log_error "Failed to start service"
log_info "Check service status with: sudo systemctl status ${SERVICE_NAME}"
return 4
fi
}
# Run the installer
run_installer() {
print_step 7 8 "Running UTMStack installer"
if [[ "${DRY_RUN}" == true ]]; then
log_info "DRY RUN: Would run installer"
return 0
fi
if [[ ! -f "${INSTALLER_FILE}" ]]; then
log_error "Installer file not found: ${INSTALLER_FILE}"
return 1
fi
log_info "Executing installer..."
echo "$(printf '=%.0s' $(seq 1 60))"
if sudo ./"${INSTALLER_FILE}"; then
echo "$(printf '=%.0s' $(seq 1 60))"
log_success "Installer completed successfully"
else
local exit_code=$?
echo "$(printf '=%.0s' $(seq 1 60))"
log_error "Installer failed with exit code: ${exit_code}"
return 1
fi
}
# Monitor the update log
monitor_log() {
print_step 8 8 "Monitoring update progress"
if [[ "${DRY_RUN}" == true ]]; then
log_info "DRY RUN: Would monitor log file"
return 0
fi
log_info "Following log file: ${LOG_FILE}"
log_info "Press Ctrl+C to exit log monitoring"
echo "$(printf '=%.0s' $(seq 1 60))"
# Wait for log file to be created
local max_wait=10
local wait_count=0
while [[ ! -f "${LOG_FILE}" ]] && [[ $wait_count -lt $max_wait ]]; do
log_verbose "Waiting for log file to be created... (${wait_count}s)"
sleep 1
((wait_count++))
done
if [[ -f "${LOG_FILE}" ]]; then
sudo tail -f "${LOG_FILE}"
else
log_error "Log file was not created after ${max_wait} seconds"
log_info "Update may have failed. Check service status:"
log_info " sudo systemctl status ${SERVICE_NAME}"
return 1
fi
}
# ============================================================================
# Main Execution Function
# ============================================================================
main() {
local exit_code=0
print_header "UTMStack Update Process v${SCRIPT_VERSION}"
log_info "Started at: ${TIMESTAMP}"
log_info "Working directory: ${SCRIPT_DIR}"
# Run pre-flight checks
log_info "Running pre-flight checks..."
check_privileges || exit $?
check_dependencies || exit $?
check_connectivity || exit $?
check_installer_directory || exit $?
log_success "Pre-flight checks completed"
# Execute update steps
remove_old_installer || exit_code=$?
[[ $exit_code -ne 0 ]] && exit $exit_code
download_installer || exit_code=$?
[[ $exit_code -ne 0 ]] && exit $exit_code
set_executable_permissions || exit_code=$?
[[ $exit_code -ne 0 ]] && exit $exit_code
stop_service || exit_code=$?
[[ $exit_code -ne 0 ]] && exit $exit_code
cleanup_log_file || exit_code=$?
[[ $exit_code -ne 0 ]] && exit $exit_code
start_service || exit_code=$?
[[ $exit_code -ne 0 ]] && exit $exit_code
run_installer || exit_code=$?
[[ $exit_code -ne 0 ]] && exit $exit_code
monitor_log || exit_code=$?
if [[ $exit_code -eq 0 ]]; then
print_header "Update Process Completed Successfully"
else
print_header "Update Process Completed with Errors"
fi
log_info "Finished at: $(date '+%Y-%m-%d %H:%M:%S')"
exit $exit_code
}
# ============================================================================
# Script Entry Point
# ============================================================================
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-v|--verbose)
VERBOSE=true
shift
;;
-d|--dry-run)
DRY_RUN=true
log_info "Running in DRY RUN mode - no changes will be made"
shift
;;
*)
log_error "Unknown option: $1"
log_info "Use -h or --help for usage information"
exit 1
;;
esac
done
# Trap errors and interrupts
trap 'log_error "Script interrupted"; exit 130' INT TERM
trap 'log_error "Script failed at line $LINENO with exit code $?"' ERR
# Execute main function
main "$@"Save the file by pressing Ctrl + X, then Y, and Enter.
Make Script Executable
Grant execution permissions to the script:
chmod +x update.shVerify the script has execute permissions:
ls -lh update.shYou should see -rwxr-xr-x at the beginning of the output.
Execute the Update Script
Run the update script with sudo privileges:
sudo bash update.shVerification and Post-Update
After the update completes, verify the installation:
Check Update Logs
Review the update logs for any errors or warnings:
sudo tail -100 /utmstack/updates/logs/utmstack-updater.logVerify Service Status
Confirm that all UTMStack services are running:
sudo systemctl status UTMStackComponentsUpdaterThe service should show “active (running)” status.