#!/usr/bin/env bash # Dependency detection and installation for Sysadmin Chronicles. # Source this file; do not execute directly. SC_DISTRO="" detect_distro() { if [ -f /etc/arch-release ]; then SC_DISTRO=arch elif grep -qi ubuntu /etc/os-release 2>/dev/null; then SC_DISTRO=ubuntu elif [ -f /etc/debian_version ]; then SC_DISTRO=debian elif [ -f /etc/fedora-release ]; then SC_DISTRO=fedora elif grep -qi opensuse /etc/os-release 2>/dev/null; then SC_DISTRO=opensuse else SC_DISTRO=unknown fi } # Per-distro package names for canonical dep names. # Format: canonical:arch:debian:ubuntu:fedora:opensuse # Empty field = not applicable / same as above / handled specially. _SC_DEP_MAP=( "libvirt:libvirt:libvirt-daemon-system:libvirt-daemon-system:libvirt:libvirt" "qemu-system:qemu-system-x86:qemu-system-x86:qemu-kvm:qemu-kvm:qemu-kvm" "qemu-img:qemu-img:qemu-utils:qemu-utils:qemu-img:qemu-tools" "virt-install:virt-install:virtinst:virtinst:virt-install:virt-install" "virt-viewer:virt-viewer:virt-viewer:virt-viewer:virt-viewer:virt-viewer" "cloud-localds:cloud-image-utils:cloud-image-utils:cloud-image-utils:cloud-utils:cloud-utils" "genisoimage:cdrtools:genisoimage:genisoimage:genisoimage:genisoimage" "xorriso:libisoburn:xorriso:xorriso:xorriso:xorriso" "nodejs:nodejs:nodejs:nodejs:nodejs:nodejs" "openssh:openssh:openssh-client:openssh-client:openssh-clients:openssh-clients" ) # Arch-only QEMU SPICE/display extras installed alongside qemu-system _SC_ARCH_QEMU_EXTRAS=( qemu-hw-display-qxl qemu-hw-display-virtio-gpu qemu-ui-spice-core qemu-chardev-spice qemu-audio-spice ) map_package() { local dep="$1" local distro="${2:-$SC_DISTRO}" local entry for entry in "${_SC_DEP_MAP[@]}"; do IFS=':' read -r name arch debian ubuntu fedora opensuse <<< "$entry" if [ "$name" = "$dep" ]; then case "$distro" in arch) printf '%s' "$arch" ;; debian) printf '%s' "$debian" ;; ubuntu) printf '%s' "$ubuntu" ;; fedora) printf '%s' "$fedora" ;; opensuse) printf '%s' "$opensuse" ;; esac return fi done } # Outputs canonical dep names that are not yet installed (one per line) check_deps() { local missing=() command -v virsh >/dev/null 2>&1 || missing+=(libvirt) command -v qemu-system-x86_64 >/dev/null 2>&1 || missing+=(qemu-system) command -v qemu-img >/dev/null 2>&1 || missing+=(qemu-img) command -v virt-install >/dev/null 2>&1 || missing+=(virt-install) command -v remote-viewer >/dev/null 2>&1 || missing+=(virt-viewer) command -v node >/dev/null 2>&1 || missing+=(nodejs) command -v ssh >/dev/null 2>&1 || missing+=(openssh) # Need at least one cloud-init ISO tool if ! command -v cloud-localds >/dev/null 2>&1 \ && ! command -v genisoimage >/dev/null 2>&1 \ && ! command -v mkisofs >/dev/null 2>&1 \ && ! command -v xorriso >/dev/null 2>&1; then missing+=(cloud-localds genisoimage xorriso) fi [ "${#missing[@]}" -gt 0 ] && printf '%s\n' "${missing[@]}" || true } # Install a list of canonical dep names. Logs to SC_INSTALL_LOG if set. install_deps() { local deps=("$@") local pkgs=() for dep in "${deps[@]}"; do local pkg pkg="$(map_package "$dep")" [ -n "$pkg" ] && pkgs+=("$pkg") done if [ "$SC_DISTRO" = "arch" ]; then pkgs+=("${_SC_ARCH_QEMU_EXTRAS[@]}") fi [ "${#pkgs[@]}" -eq 0 ] && return 0 local -a pm_cmd case "$SC_DISTRO" in arch) pm_cmd=(pacman -S --noconfirm --needed) ;; debian|ubuntu) pm_cmd=(apt-get install -y) ;; fedora) pm_cmd=(dnf install -y) ;; opensuse) pm_cmd=(zypper install -y) ;; *) echo " Unsupported distro '$SC_DISTRO' — install these manually:" printf ' %s\n' "${pkgs[@]}" return 1 ;; esac sudo "${pm_cmd[@]}" "${pkgs[@]}" if [ -n "${SC_INSTALL_LOG:-}" ] && [ "$SC_INSTALL_LOG" != "/dev/null" ]; then mkdir -p "$(dirname "$SC_INSTALL_LOG")" local distro_label="$SC_DISTRO" for pkg in "${pkgs[@]}"; do local ver="" case "$SC_DISTRO" in arch) ver="$(pacman -Q "$pkg" 2>/dev/null | awk '{print $2}' || true)" ;; debian|ubuntu) ver="$(dpkg -l "$pkg" 2>/dev/null | awk '/^ii/{print $3}' | head -1 || true)" ;; fedora) ver="$(rpm -q --queryformat '%{VERSION}' "$pkg" 2>/dev/null || true)" ;; esac printf '[INSTALLED] %-36s %-14s via %s\n' "$pkg" "${ver:-}" "$distro_label" \ >> "$SC_INSTALL_LOG" done fi } # Outputs already-installed canonical deps (for log completeness) log_present_deps() { local log_file="${SC_INSTALL_LOG:-}" [ -z "$log_file" ] || [ "$log_file" = "/dev/null" ] && return local distro_label="$SC_DISTRO" for dep in libvirt qemu-system qemu-img virt-install virt-viewer nodejs openssh; do local bin case "$dep" in libvirt) bin=virsh ;; qemu-system) bin=qemu-system-x86_64 ;; qemu-img) bin=qemu-img ;; virt-install) bin=virt-install ;; virt-viewer) bin=remote-viewer ;; nodejs) bin=node ;; openssh) bin=ssh ;; esac if command -v "$bin" >/dev/null 2>&1; then printf '[SKIPPED] %-36s already installed\n' "$dep" >> "$log_file" fi done } dep_label() { case "$1" in libvirt) echo "Virtual machine manager (libvirt)" ;; qemu-system) echo "KVM virtualization support (QEMU)" ;; qemu-img) echo "VM disk image tools (qemu-img)" ;; virt-install) echo "VM installer (virt-install)" ;; virt-viewer) echo "SPICE display viewer (virt-viewer)" ;; cloud-localds|genisoimage|xorriso) echo "Cloud image tools" ;; nodejs) echo "Node.js runtime" ;; openssh) echo "SSH client" ;; *) echo "$1" ;; esac }