#!/usr/bin/env bash set -euo pipefail # Source common library SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" # shellcheck source=../lib/common.sh source "$SCRIPT_DIR/../lib/common.sh" on_error() { local exit_code=$? local line_number=$1 log_error "Unexpected failure at line ${line_number} (exit code ${exit_code})." } trap 'on_error ${LINENO}' ERR require_pacman() { if ! has_cmd pacman; then log_error "pacman not found. This script is intended for Arch Linux systems." exit 1 fi } detect_kernel_release() { uname -r } select_host_package() { local kernel_release=$1 case "${kernel_release}" in *-lts) echo "virtualbox-host-modules-lts" ;; *-arch*) echo "virtualbox-host-modules-arch" ;; *) echo "virtualbox-host-dkms" ;; esac } collect_kernel_headers() { local -a headers=() local kernel_pkg header_pkg for kernel_pkg in linux linux-lts linux-zen linux-hardened; do if pacman -Q "${kernel_pkg}" > /dev/null 2>&1; then header_pkg="${kernel_pkg}-headers" headers+=("${header_pkg}") fi done if [[ ${#headers[@]} -gt 0 ]]; then printf '%s\n' "${headers[@]}" fi } maybe_remove_conflicting_host_packages() { local selected_package=$1 local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts") local pkg for pkg in "${candidates[@]}"; do if [[ ${pkg} != "${selected_package}" ]] && pacman -Q "${pkg}" > /dev/null 2>&1; then log_warn "Removing conflicting package ${pkg} before installing ${selected_package}." pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}" fi done } install_packages() { local -a packages=() local -a headers=() local host_package=$1 shift if [[ $# -gt 0 ]]; then mapfile -t headers < <(printf '%s\n' "$@" | sort -u) fi packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}") if [[ ${host_package} == "virtualbox-host-dkms" ]]; then packages+=("dkms") fi if [[ ${#headers[@]} -gt 0 ]]; then packages+=("${headers[@]}") fi log_info "Installing packages: ${packages[*]}" pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}" } rebuild_virtualbox_modules() { local host_package=$1 if [[ ${host_package} == "virtualbox-host-dkms" ]]; then if command -v dkms > /dev/null 2>&1; then log_info "Rebuilding VirtualBox DKMS modules for all installed kernels." dkms autoinstall else log_warn "dkms command not found; skipping DKMS rebuild." fi fi } reload_virtualbox_modules() { log_info "Loading VirtualBox kernel modules." if [[ -x /sbin/rcvboxdrv ]]; then /sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules." elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then /usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules." fi local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci) local mod for mod in "${modules[@]}"; do if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then if ! modprobe "${mod}" > /dev/null 2>&1; then log_warn "Module ${mod} failed to load; check dmesg for details." fi fi done if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues." fi log_info "VirtualBox kernel driver loaded successfully." } warn_if_secure_boot_enabled() { local secure_boot_file if [[ -d /sys/firmware/efi/efivars ]]; then secure_boot_file=$(find /sys/firmware/efi/efivars -maxdepth 1 -name 'SecureBoot-*' -print -quit 2> /dev/null || true) if [[ -n ${secure_boot_file} && -r ${secure_boot_file} ]]; then local state state=$(hexdump -n 1 -s 4 -e '1 "%d"' "${secure_boot_file}" 2> /dev/null || echo "0") if [[ ${state} == "1" ]]; then log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually." fi fi fi } remind_group_membership() { local invoking_user=${SUDO_USER:-} if [[ -n ${invoking_user} && ${invoking_user} != "root" ]]; then if ! id -nG "${invoking_user}" | grep -qw "vboxusers"; then log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers" else log_info "User ${invoking_user} is already in the vboxusers group." fi fi } main() { require_root require_pacman PACMAN_INSTALL_FLAGS=(--needed) PACMAN_REMOVE_FLAGS=() if [[ ${PACMAN_CONFIRM:-0} == "1" ]]; then log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation." else PACMAN_INSTALL_FLAGS+=(--noconfirm) PACMAN_REMOVE_FLAGS+=(--noconfirm) fi local kernel_release host_package kernel_release=$(detect_kernel_release) log_info "Detected running kernel: ${kernel_release}" host_package=$(select_host_package "${kernel_release}") log_info "Selected VirtualBox host package: ${host_package}" mapfile -t kernel_headers < <(collect_kernel_headers) if [[ ${host_package} == "virtualbox-host-dkms" && ${#kernel_headers[@]} -eq 0 ]]; then log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules." fi maybe_remove_conflicting_host_packages "${host_package}" install_packages "${host_package}" "${kernel_headers[@]}" rebuild_virtualbox_modules "${host_package}" reload_virtualbox_modules warn_if_secure_boot_enabled remind_group_membership log_info "VirtualBox installation and driver setup complete." } main "$@"