#!/usr/bin/env bash set -euo pipefail # ========================================================== # Virtuality Node Installer # Clean step-by-step installer for KVM/QEMU/libvirt/Cockpit # ========================================================== PROJECT_NAME="Virtuality" PROJECT_DIR="/opt/virtuality" STORAGE_DIR="/var/lib/virtuality" ISO_DIR="${STORAGE_DIR}/iso" IMAGES_DIR="${STORAGE_DIR}/images" BACKUP_DIR="${STORAGE_DIR}/backups" LOG_DIR="/var/log/virtuality" COCKPIT_PORT="9090" LOG_FILE="${LOG_DIR}/install_node_$(date +%Y%m%d_%H%M%S).log" TOTAL_STEPS=12 CURRENT_STEP=0 ESC="\033" RESET="${ESC}[0m" BOLD="${ESC}[1m" DIM="${ESC}[2m" GREEN="${ESC}[32m" YELLOW="${ESC}[33m" RED="${ESC}[31m" CYAN="${ESC}[36m" BLUE="${ESC}[34m" GRAY="${ESC}[90m" mkdir -p "$LOG_DIR" timestamp() { date '+%Y-%m-%d %H:%M:%S'; } line() { printf '%*s\n' 72 '' | tr ' ' '─'; } log() { echo "[$(timestamp)] $*" >> "$LOG_FILE"; } print_header() { clear 2>/dev/null || true echo -e "${CYAN}${BOLD}╭────────────────────────────────────────────────────────────╮${RESET}" echo -e "${CYAN}${BOLD}│${RESET} ${BOLD}Virtuality Node Installer${RESET} ${CYAN}${BOLD}│${RESET}" echo -e "${CYAN}${BOLD}│${RESET} KVM / QEMU / libvirt / Cockpit virtualization node ${CYAN}${BOLD}│${RESET}" echo -e "${CYAN}${BOLD}╰────────────────────────────────────────────────────────────╯${RESET}" echo echo -e "${GRAY}Лог установки:${RESET} ${LOG_FILE}" echo -e "${GRAY}Storage:${RESET} ${STORAGE_DIR}" echo -e "${GRAY}Cockpit:${RESET} ${COCKPIT_PORT}/tcp" echo } step() { CURRENT_STEP=$((CURRENT_STEP + 1)) echo echo -e "${BLUE}${BOLD}[${CURRENT_STEP}/${TOTAL_STEPS}]${RESET} ${BOLD}$*${RESET}" log "STEP ${CURRENT_STEP}/${TOTAL_STEPS}: $*" } ok() { echo -e " ${GREEN}✓${RESET} $*"; log "OK: $*"; } warn() { echo -e " ${YELLOW}!${RESET} $*"; log "WARN: $*"; } fail() { echo -e " ${RED}✗${RESET} $*"; log "ERROR: $*"; echo; echo -e "${RED}${BOLD}Установка остановлена.${RESET} Лог: ${LOG_FILE}"; exit 1; } run_logged() { local description="$1" shift log "RUN: $*" if "$@" >> "$LOG_FILE" 2>&1; then ok "$description" else fail "$description" fi } service_state() { systemctl is-active "$1" 2>/dev/null || echo "inactive"; } service_enabled() { systemctl is-enabled "$1" 2>/dev/null || echo "disabled"; } print_header step "Проверяем права и совместимость ОС" if [[ "$EUID" -ne 0 ]]; then fail "Запусти от root: sudo bash install_virtuality_node.sh" fi ok "Права root подтверждены" if ! command -v apt >/dev/null 2>&1; then fail "Установщик рассчитан на Debian/Ubuntu с apt" fi ok "apt найден" if [[ -f /etc/os-release ]]; then OS_NAME="$(. /etc/os-release && echo "${PRETTY_NAME}")" ok "Система: ${OS_NAME}" else warn "Не найден /etc/os-release" fi step "Проверяем аппаратную виртуализацию" if grep -E -q '(vmx|svm)' /proc/cpuinfo; then ok "CPU поддерживает аппаратную виртуализацию vmx/svm" else warn "vmx/svm не найдено. Проверь виртуализацию в BIOS/UEFI" fi if [[ -e /dev/kvm ]]; then ok "/dev/kvm уже доступен" else warn "/dev/kvm пока не найден. После установки модулей проверь через sudo vhealth" fi step "Обновляем apt cache" run_logged "apt update выполнен" apt update step "Устанавливаем базовые утилиты" run_logged "Базовые пакеты установлены" apt install -y \ curl wget git nano htop unzip ca-certificates gnupg lsb-release \ software-properties-common apt-transport-https ufw rsync step "Устанавливаем KVM/QEMU/libvirt" run_logged "Пакеты виртуализации установлены" apt install -y \ qemu-kvm qemu-utils libvirt-daemon-system libvirt-clients virtinst \ bridge-utils dnsmasq-base ovmf swtpm cloud-image-utils step "Устанавливаем Cockpit и модули" run_logged "Cockpit установлен" apt install -y \ cockpit cockpit-machines cockpit-networkmanager cockpit-storaged cockpit-packagekit step "Создаём структуру Virtuality" run_logged "Директории Virtuality созданы" mkdir -p "$PROJECT_DIR" "$STORAGE_DIR" "$ISO_DIR" "$IMAGES_DIR" "$BACKUP_DIR" "$LOG_DIR" run_logged "Права директорий выставлены" chmod 755 "$PROJECT_DIR" "$STORAGE_DIR" "$ISO_DIR" "$IMAGES_DIR" "$BACKUP_DIR" "$LOG_DIR" ok "Project: ${PROJECT_DIR}" ok "ISO: ${ISO_DIR}" ok "Images: ${IMAGES_DIR}" ok "Backups: ${BACKUP_DIR}" step "Добавляем пользователя в группы libvirt/kvm" REAL_USER="${SUDO_USER:-root}" if [[ "$REAL_USER" != "root" ]]; then usermod -aG libvirt "$REAL_USER" >> "$LOG_FILE" 2>&1 || warn "Не удалось добавить ${REAL_USER} в libvirt" usermod -aG kvm "$REAL_USER" >> "$LOG_FILE" 2>&1 || warn "Не удалось добавить ${REAL_USER} в kvm" ok "Пользователь ${REAL_USER} добавлен в группы libvirt/kvm" warn "Чтобы группы применились, нужно выйти из SSH и зайти снова" else warn "Запуск под root без SUDO_USER; пользователь не добавлен в группы" fi step "Включаем systemd-сервисы" run_logged "libvirtd включён и запущен" systemctl enable --now libvirtd run_logged "virtlogd включён и запущен" systemctl enable --now virtlogd run_logged "cockpit.socket включён и запущен" systemctl enable --now cockpit.socket ok "libvirtd: $(service_state libvirtd) / $(service_enabled libvirtd)" ok "virtlogd: $(service_state virtlogd) / $(service_enabled virtlogd)" ok "cockpit.socket: $(service_state cockpit.socket) / $(service_enabled cockpit.socket)" step "Настраиваем firewall" run_logged "Разрешён OpenSSH" ufw allow OpenSSH run_logged "Разрешён Cockpit ${COCKPIT_PORT}/tcp" ufw allow "${COCKPIT_PORT}/tcp" run_logged "Разрешены VNC-порты 5900:5999/tcp" ufw allow 5900:5999/tcp if ufw status | grep -qi inactive; then echo "y" | ufw enable >> "$LOG_FILE" 2>&1 && ok "UFW включён" || warn "Не удалось включить UFW" else ok "UFW уже активен" fi step "Создаём libvirt storage pools" virsh pool-define-as virtuality-images dir --target "$IMAGES_DIR" >> "$LOG_FILE" 2>&1 || true virsh pool-start virtuality-images >> "$LOG_FILE" 2>&1 || true virsh pool-autostart virtuality-images >> "$LOG_FILE" 2>&1 || true ok "Storage pool virtuality-images готов" virsh pool-define-as virtuality-iso dir --target "$ISO_DIR" >> "$LOG_FILE" 2>&1 || true virsh pool-start virtuality-iso >> "$LOG_FILE" 2>&1 || true virsh pool-autostart virtuality-iso >> "$LOG_FILE" 2>&1 || true ok "Storage pool virtuality-iso готов" step "Создаём конфигурацию окружения" cat > "${PROJECT_DIR}/virtuality.env" </dev/null 2>&1; then virsh list --all >> "$LOG_FILE" 2>&1 && ok "virsh list работает" || warn "virsh list вернул предупреждение" virsh pool-list --all >> "$LOG_FILE" 2>&1 && ok "virsh pool-list работает" || warn "virsh pool-list вернул предупреждение" else warn "virsh не найден после установки" fi if ss -tulpn 2>/dev/null | grep -q ':9090'; then ok "Cockpit слушает порт 9090" else warn "Порт 9090 не виден в ss; Cockpit может быть socket-activated" fi if [[ -e /dev/kvm ]]; then ok "/dev/kvm доступен" else warn "/dev/kvm не найден после установки" fi SERVER_IP="$(hostname -I | awk '{print $1}')" echo echo -e "${GREEN}${BOLD}╭────────────────────────────────────────────────────────────╮${RESET}" echo -e "${GREEN}${BOLD}│${RESET} ${BOLD}Установка Virtuality Node завершена${RESET} ${GREEN}${BOLD}│${RESET}" echo -e "${GREEN}${BOLD}╰────────────────────────────────────────────────────────────╯${RESET}" echo echo -e "${BOLD}Cockpit:${RESET} https://${SERVER_IP}:${COCKPIT_PORT}" echo -e "${BOLD}Project:${RESET} ${PROJECT_DIR}" echo -e "${BOLD}ISO:${RESET} ${ISO_DIR}" echo -e "${BOLD}Images:${RESET} ${IMAGES_DIR}" echo -e "${BOLD}Backups:${RESET} ${BACKUP_DIR}" echo -e "${BOLD}Install log:${RESET} ${LOG_FILE}" echo echo -e "${BOLD}Проверка:${RESET}" echo " virsh list --all" echo " virsh pool-list --all" echo " systemctl status libvirtd --no-pager" echo " systemctl status cockpit.socket --no-pager" echo line echo -e "${DIM}Следующий шаг: sudo bash scripts/setup_bridge_br0.sh enp2s0 static${RESET}" echo -e "${DIM}После bridge: sudo bash scripts/create_test_vm.sh${RESET}" line