Last active
April 8, 2025 19:28
-
-
Save TechByTom/092eca558039478350314932bca071dc to your computer and use it in GitHub Desktop.
Raspberry Pi Proxmox 8.x Installer
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
| #!/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