Skip to content

Instantly share code, notes, and snippets.

@TechByTom
Last active April 8, 2025 19:28
Show Gist options
  • Select an option

  • Save TechByTom/092eca558039478350314932bca071dc to your computer and use it in GitHub Desktop.

Select an option

Save TechByTom/092eca558039478350314932bca071dc to your computer and use it in GitHub Desktop.
Raspberry Pi Proxmox 8.x Installer
#!/bin/bash
# Proxmox VE 8.x Installer for Raspberry Pi 5
# This script automates the installation of Proxmox VE 8.x on Raspberry Pi 5
# Usage: curl -sSL https://gist.github.com/TechByTom/092eca558039478350314932bca071dc/raw | sudo bash
# or save this script and run with: sudo bash install.sh
# Exit on error
set -e
# ================================================================
# LOGGING SETUP
# ================================================================
# Define log file
LOG_FILE="/var/log/proxmox-install.log"
LOG_VERBOSE=true
# Initialize log file
init_log() {
echo "=== Proxmox VE 8.x Installation Log - $(date) ===" > "$LOG_FILE"
echo "Script version: 1.0.0" >> "$LOG_FILE"
echo "User: $(whoami)" >> "$LOG_FILE"
echo "===========================================" >> "$LOG_FILE"
}
# Function to log messages
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
log_cmd() {
local cmd="$1"
local level="${2:-INFO}"
log "$level" "Running command: $cmd"
local start_time=$(date +%s)
local temp_log=$(mktemp)
# Run the command and capture output and exit code
set +e
eval "$cmd" > "$temp_log" 2>&1
local exit_code=$?
set -e
local end_time=$(date +%s)
local duration=$((end_time - start_time))
# Log the results
log "$level" "Command completed with exit code: $exit_code (took ${duration}s)"
if [ "$LOG_VERBOSE" = true ] || [ $exit_code -ne 0 ]; then
log "$level" "Command output:"
cat "$temp_log" | while read line; do
echo " $line" | tee -a "$LOG_FILE"
done
fi
rm -f "$temp_log"
return $exit_code
}
# Initialize log
init_log
# ================================================================
# FUNCTION DEFINITIONS
# ================================================================
# Function to display progress
show_progress() {
echo "================================================================"
echo " $1"
echo "================================================================"
log "INFO" "Progress: $1"
}
# Function to detect NVMe drive
detect_nvme() {
log "INFO" "Starting NVMe drive detection"
# List all available block devices
log "INFO" "Available block devices:"
lsblk -p | tee -a "$LOG_FILE"
# Try specific NVMe patterns
if [ -e /dev/nvme0n1 ]; then
log "INFO" "Found NVMe drive: /dev/nvme0n1"
echo "/dev/nvme0n1"
return 0
else
# More aggressive detection of any NVMe drives
local nvme_devices=$(find /dev -name "nvme*n*" 2>/dev/null | grep -v "p[0-9]" | sort)
if [ -n "$nvme_devices" ]; then
log "INFO" "Found NVMe drive(s):"
echo "$nvme_devices" | tee -a "$LOG_FILE"
local first_nvme=$(echo "$nvme_devices" | head -n1)
log "INFO" "Using NVMe drive: $first_nvme"
echo "$first_nvme"
return 0
fi
log "WARNING" "No NVMe drives detected"
return 1
fi
}
# Function to migrate from SD card to NVMe
migrate_to_nvme() {
local nvme_device=$1
show_progress "Migrating system from SD card to NVMe SSD ($nvme_device)"
# Create partitions on NVMe
echo "Creating partitions on NVMe drive..."
parted --script "$nvme_device" \
mklabel gpt \
mkpart primary fat32 1MiB 256MiB \
mkpart primary ext4 256MiB 100%
# Format partitions
echo "Formatting partitions..."
mkfs.vfat -F32 "${nvme_device}p1"
mkfs.ext4 -F "${nvme_device}p2"
# Mount partitions
echo "Mounting partitions..."
mkdir -p /mnt/boot /mnt/rootfs
mount "${nvme_device}p1" /mnt/boot
mount "${nvme_device}p2" /mnt/rootfs
# Copy data
echo "Copying system to NVMe drive (this may take a while)..."
rsync -ax --progress /boot/ /mnt/boot/
rsync -ax --progress --exclude '/mnt' --exclude '/proc' --exclude '/sys' --exclude '/dev' --exclude '/run' --exclude '/tmp' / /mnt/rootfs/
# Update fstab
echo "Updating fstab on NVMe drive..."
local boot_uuid=$(blkid -s UUID -o value "${nvme_device}p1")
local root_uuid=$(blkid -s UUID -o value "${nvme_device}p2")
cat > /mnt/rootfs/etc/fstab << EOF
proc /proc proc defaults 0 0
UUID=$boot_uuid /boot vfat defaults 0 2
UUID=$root_uuid / ext4 defaults,noatime 0 1
EOF
# Configure boot from NVMe
echo "Configuring boot from NVMe..."
if [ -f /boot/config.txt ]; then
cp /boot/config.txt /mnt/boot/
if ! grep -q "dtoverlay=nvme" /mnt/boot/config.txt; then
echo "dtoverlay=nvme" >> /mnt/boot/config.txt
fi
if ! grep -q "boot_order=0xf14" /mnt/boot/config.txt; then
echo "boot_order=0xf14" >> /mnt/boot/config.txt
fi
fi
# Create state directory on the NVMe drive
mkdir -p /mnt/rootfs/$STATE_DIR
# Create a marker to indicate migration is complete
touch /mnt/rootfs/$MIGRATION_MARKER
# Create script to continue installation
cat > /mnt/rootfs/root/continue-proxmox-install.sh << 'EOF'
#!/bin/bash
curl -sSL https://raw.githubusercontent.com/yourusername/proxmox-pi5-installer/main/install.sh | bash
EOF
chmod +x /mnt/rootfs/root/continue-proxmox-install.sh
# Copy the current script as a fallback
cp "$0" /mnt/rootfs/root/proxmox-install.sh
chmod +x /mnt/rootfs/root/proxmox-install.sh
# Create systemd service to auto-continue installation after reboot
cat > /mnt/rootfs/etc/systemd/system/continue-proxmox-install.service << EOF
[Unit]
Description=Continue Proxmox Installation
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/root/continue-proxmox-install.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
EOF
# Enable the service
mkdir -p /mnt/rootfs/etc/systemd/system/multi-user.target.wants/
ln -sf /etc/systemd/system/continue-proxmox-install.service /mnt/rootfs/etc/systemd/system/multi-user.target.wants/
sync
umount /mnt/boot
umount /mnt/rootfs
echo "Migration to NVMe completed successfully."
# Ask to reboot
echo ""
echo "The system will now reboot to boot from NVMe."
echo "The installation will automatically continue after reboot."
read -p "Press ENTER to reboot..." confirm
echo "Rebooting system..."
sync
reboot
exit 0
}
# ================================================================
# MAIN SCRIPT
# ================================================================
# Check if Proxmox is already installed
if [ -f "$INSTALL_MARKER" ]; then
show_progress "Proxmox VE is already installed"
echo "This script has already completed a successful installation."
echo "Running it again may cause issues or duplicate configurations."
echo "Press CTRL+C to abort or ENTER to continue anyway..."
read -r < /dev/tty
fi
# Check for partial installation or interrupted runs
if dpkg -l | grep -q proxmox-ve; then
if [ ! -f "$INSTALL_MARKER" ]; then
show_progress "Detected partial Proxmox installation"
echo "It appears that Proxmox packages are installed but the installation"
echo "was not properly completed. This script will attempt to continue."
echo "Press CTRL+C to abort or ENTER to continue..."
read -r < /dev/tty
fi
fi
# Check for partial NVMe migration
BOOT_DEVICE=$(mount | grep " on / " | cut -d' ' -f1)
if [[ ! "$BOOT_DEVICE" == *"nvme"* ]] && [ -d "/mnt/rootfs" ] || [ -d "/mnt/boot" ]; then
show_progress "WARNING: Detected interrupted NVMe migration"
echo "It appears a previous attempt to migrate to NVMe was interrupted."
echo "This could result in an inconsistent system state."
echo ""
echo "1. Continue with a fresh migration attempt"
echo "2. Clean up the interrupted migration and exit"
echo "3. Ignore and continue with the script"
echo ""
echo "Enter your choice (1-3): "
read -r choice < /dev/tty
case "$choice" in
1)
echo "Unmounting any previous migration attempts..."
umount /mnt/boot 2>/dev/null || true
umount /mnt/rootfs 2>/dev/null || true
rm -rf /mnt/boot /mnt/rootfs
echo "Ready for a fresh migration attempt."
;;
2)
echo "Cleaning up..."
umount /mnt/boot 2>/dev/null || true
umount /mnt/rootfs 2>/dev/null || true
rm -rf /mnt/boot /mnt/rootfs
echo "Cleanup complete. Please restart from a clean state."
exit 0
;;
3)
echo "Continuing without cleanup. Use caution."
;;
*)
echo "Invalid choice. Exiting for safety."
exit 1
;;
esac
fi
# Check if running as root
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root" >&2
exit 1
fi
# Check if running on Raspberry Pi 5
if ! grep -q "Raspberry Pi 5" /proc/device-tree/model 2>/dev/null; then
echo "Error: This script is designed specifically for Raspberry Pi 5" >&2
exit 1
fi
# Check if running on 64-bit OS
if [ "$(uname -m)" != "aarch64" ]; then
echo "Error: This script requires a 64-bit OS (arm64/aarch64)" >&2
exit 1
fi
# Verify we're running Raspberry Pi OS Lite (64-bit)
if [ ! -f /etc/os-release ]; then
echo "Error: Cannot determine OS version" >&2
exit 1
fi
source /etc/os-release
# Check for Raspberry Pi hardware
if [ ! -f /proc/device-tree/model ]; then
echo "Error: Cannot detect Raspberry Pi hardware" >&2
exit 1
fi
# Check for Bookworm (Debian 12)
if [[ "$VERSION_CODENAME" != "bookworm" ]] && [[ "$VERSION_ID" != "12" ]]; then
echo "Warning: This script is designed for Raspberry Pi OS Bookworm (Debian 12)" >&2
echo "Current OS: $PRETTY_NAME" >&2
echo "While installation may work on other versions, it is not officially tested." >&2
# Use /dev/tty to ensure we get input even when piped
echo "Press CTRL+C to abort or ENTER to continue anyway..." >&2
read -r < /dev/tty
fi
# Check if running on lite version (no desktop)
# This is a more reliable check for desktop environment
if [ -d "/usr/share/xfce4" ] || [ -d "/usr/share/gnome" ] || systemctl is-active --quiet lightdm.service 2>/dev/null; then
echo "Warning: This script is optimized for Raspberry Pi OS Lite (no desktop)"
echo "Desktop environment detected, which may consume resources needed by Proxmox"
echo "Detected desktop components:"
# Show detailed detection information
if [ -d "/usr/share/xfce4" ]; then
echo "- XFCE desktop environment"
fi
if [ -d "/usr/share/gnome" ]; then
echo "- GNOME desktop environment"
fi
if systemctl is-active --quiet lightdm.service 2>/dev/null; then
echo "- LightDM display manager (running)"
fi
echo "Press CTRL+C to abort or ENTER to continue anyway..."
read -r < /dev/tty
else
echo "No desktop environment detected. Continuing with installation."
fi
# Detect if this is a resume after migration reboot
RESUMED_AFTER_MIGRATION=false
BOOT_DEVICE=$(mount | grep " on / " | cut -d' ' -f1)
# Resume logic for after NVMe migration
if [[ -f "$MIGRATION_MARKER" ]]; then
show_progress "Resuming installation after migration to NVMe"
RESUMED_AFTER_MIGRATION=true
echo "Successfully booted from NVMe drive: $BOOT_DEVICE"
echo "Continuing with Proxmox installation..."
sleep 2
fi
# Check if we need to migrate to NVMe
if [[ "$RESUMED_AFTER_MIGRATION" == false ]]; then
NVME_DEVICE=$(detect_nvme)
BOOT_DEVICE=$(mount | grep " on /boot " | cut -d' ' -f1)
# Is boot device NVMe?
if [[ "$BOOT_DEVICE" == *"nvme"* ]]; then
show_progress "System is already booting from NVMe drive"
elif [ -n "$NVME_DEVICE" ]; then
# Ask if user wants to migrate
read -p "NVMe drive detected ($NVME_DEVICE). Migrate system to NVMe? (y/n): " migrate_choice
if [[ "$migrate_choice" =~ ^[Yy]$ ]]; then
# Check if NVMe already has data
if lsblk -no FSTYPE "$NVME_DEVICE"* 2>/dev/null | grep -q -E 'ext4|vfat'; then
read -p "WARNING: NVMe drive contains existing data. Erase and continue? (y/n): " erase_choice
if [[ ! "$erase_choice" =~ ^[Yy]$ ]]; then
echo "Aborting migration. Continuing with SD card installation."
else
migrate_to_nvme "$NVME_DEVICE"
fi
else
migrate_to_nvme "$NVME_DEVICE"
fi
fi
else
echo "No NVMe drive detected. Continuing with SD card installation."
fi
fi
# Update system
show_progress "Updating system packages"
apt update
apt upgrade -y
# Install required dependencies
show_progress "Installing required dependencies"
apt install -y curl gnupg sudo parted
# Configure hostname and hosts file
show_progress "Configuring hostname and hosts file"
# Use existing hostname if set via Raspberry Pi Imager
CURRENT_HOSTNAME=$(hostname)
echo "Enter hostname for your Proxmox server [$CURRENT_HOSTNAME]: "
read -r HOSTNAME < /dev/tty
HOSTNAME=${HOSTNAME:-$CURRENT_HOSTNAME}
# Get the current IP address
CURRENT_IP=$(hostname -I | awk '{print $1}')
echo "Enter static IP address for your Proxmox server [$CURRENT_IP]: "
read -r IP_ADDRESS < /dev/tty
IP_ADDRESS=${IP_ADDRESS:-$CURRENT_IP}
# Update hostname if needed
if [ "$HOSTNAME" != "$CURRENT_HOSTNAME" ]; then
echo "$HOSTNAME" > /etc/hostname
hostname "$HOSTNAME"
fi
# Update hosts file
cat > /etc/hosts << EOF
127.0.0.1 localhost
$IP_ADDRESS $HOSTNAME
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF
# Configure cgroups for containers (required for LXC)
show_progress "Configuring cgroups for containers"
CMDLINE_FILE="/boot/cmdline.txt"
if ! grep -q "cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1" $CMDLINE_FILE; then
# Create backup of cmdline.txt if one doesn't exist
if [ ! -f "${CMDLINE_FILE}.bak" ]; then
cp $CMDLINE_FILE "${CMDLINE_FILE}.bak"
fi
sed -i 's/$/ cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1/' $CMDLINE_FILE
echo "Added cgroup parameters to cmdline.txt"
else
echo "Cgroup parameters already present in cmdline.txt"
fi
# Install network configuration packages
show_progress "Installing network configuration packages"
if ! dpkg -l | grep -q ifupdown2; then
apt install -y ifupdown2
else
echo "ifupdown2 is already installed"
fi
# Add the Proxmox repository key
show_progress "Adding Proxmox repository"
if [ ! -f /etc/apt/trusted.gpg.d/pveport.gpg ]; then
curl -fsSL https://mirrors.apqa.cn/proxmox/debian/pveport.gpg -o /etc/apt/trusted.gpg.d/pveport.gpg
echo "Added Proxmox repository key"
else
echo "Proxmox repository key already exists"
fi
# Add the Proxmox repository
REPO_FILE="/etc/apt/sources.list.d/pveport.list"
REPO_LINE="deb [arch=arm64] https://mirrors.apqa.cn/proxmox/debian/pve bookworm port"
if [ ! -f "$REPO_FILE" ] || ! grep -q "$REPO_LINE" "$REPO_FILE"; then
echo "$REPO_LINE" > "$REPO_FILE"
echo "Added Proxmox repository"
else
echo "Proxmox repository already configured"
fi
# Update package lists
apt update
# Check for default user from Raspberry Pi Imager
DEFAULT_USER=$(getent passwd 1000 | cut -d: -f1)
if [ -z "$DEFAULT_USER" ]; then
DEFAULT_USER="pi"
fi
show_progress "User account information"
echo "Found user account: $DEFAULT_USER"
echo "This user account will have sudo rights for Proxmox administration."
echo "Additionally, the root account will be used to access the Proxmox web interface."
# Install Proxmox packages
show_progress "Installing Proxmox VE packages (this may take 30+ minutes)"
log "INFO" "Starting Proxmox VE package installation"
log "INFO" "Running apt install for Proxmox packages"
if ! log_cmd "apt install -y proxmox-ve postfix open-iscsi pve-edk2-firmware-aarch64"; then
log "ERROR" "Proxmox installation failed"
echo "ERROR: Proxmox installation failed"
echo "Please check the error messages above and the log file at $LOG_FILE"
exit 1
fi
# Set root password if not already set via Pi Imager
if ! passwd -S root | grep -q "P"; then
show_progress "Setting root password for Proxmox web interface"
log "INFO" "Root password not set, prompting user"
echo "Note: This password will be used to log into the Proxmox web interface."
passwd root
log "INFO" "Root password has been set"
else
show_progress "Root password is already set"
log "INFO" "Root password is already set"
echo "You will use this password to log into the Proxmox web interface."
fi
# Ensure the script continues after apt install
log "INFO" "Proxmox packages installed successfully"
echo "Proxmox packages installed successfully"
# Final configuration
show_progress "Performing final configuration"
# Ensure services start on boot
systemctl enable pve-manager
systemctl enable pvedaemon
systemctl enable pveproxy
systemctl enable pvestatd
# Create success marker
touch "$INSTALL_MARKER"
# Remove the auto-continue service if it exists
if [ -f /etc/systemd/system/continue-proxmox-install.service ]; then
systemctl disable continue-proxmox-install.service
rm -f /etc/systemd/system/continue-proxmox-install.service
fi
# Display completion message
IP_PORT="$IP_ADDRESS:8006"
show_progress "Installation Complete!"
echo ""
echo "Proxmox VE has been installed on your Raspberry Pi 5."
echo "After rebooting, you can access the Proxmox web interface at:"
echo ""
echo " https://$IP_PORT"
echo ""
echo "Login with username 'root' and the password set for the root account."
echo ""
echo "You can also log into your Proxmox server via SSH using your regular"
echo "user account (${DEFAULT_USER}) that was configured with Raspberry Pi Imager."
echo ""
echo "The system will now reboot to apply all changes."
echo "Press ENTER to reboot or CTRL+C to abort..."
read -r confirm < /dev/tty
# Reboot the system
echo "Rebooting now..."
sync
reboot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment